]> git.sesse.net Git - vlc/blob - modules/gui/macosx/MainWindow.m
macosx: add yosemite graphics to project file and use them in code
[vlc] / modules / gui / macosx / MainWindow.m
1 /*****************************************************************************
2  * MainWindow.m: MacOS X interface module
3  *****************************************************************************
4  * Copyright (C) 2002-2013 VLC authors and VideoLAN
5  * $Id$
6  *
7  * Authors: Felix Paul Kühne <fkuehne -at- videolan -dot- org>
8  *          Jon Lech Johansen <jon-vl@nanocrew.net>
9  *          Christophe Massiot <massiot@via.ecp.fr>
10  *          Derk-Jan Hartman <hartman at videolan.org>
11  *          David Fuhrmann <david dot fuhrmann at googlemail dot com>
12  *
13  * This program is free software; you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation; either version 2 of the License, or
16  * (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software
25  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
26  *****************************************************************************/
27
28 #import "intf.h"
29 #import "CompatibilityFixes.h"
30 #import "MainWindow.h"
31 #import "CoreInteraction.h"
32 #import "AudioEffects.h"
33 #import "MainMenu.h"
34 #import "open.h"
35 #import "controls.h" // TODO: remove me
36 #import "playlist.h"
37 #import "SideBarItem.h"
38 #import <math.h>
39 #import <vlc_playlist.h>
40 #import <vlc_url.h>
41 #import <vlc_strings.h>
42 #import <vlc_services_discovery.h>
43
44 #import "ControlsBar.h"
45 #import "VideoView.h"
46 #import "VLCVoutWindowController.h"
47
48
49 @interface VLCMainWindow (Internal)
50 - (void)resizePlaylistAfterCollapse;
51 - (void)makeSplitViewVisible;
52 - (void)makeSplitViewHidden;
53 - (void)showPodcastControls;
54 - (void)hidePodcastControls;
55 @end
56
57
58 @implementation VLCMainWindow
59
60 @synthesize nativeFullscreenMode=b_nativeFullscreenMode;
61 @synthesize nonembedded=b_nonembedded;
62 @synthesize fsPanel=o_fspanel;
63
64 static VLCMainWindow *_o_sharedInstance = nil;
65
66 + (VLCMainWindow *)sharedInstance
67 {
68     return _o_sharedInstance ? _o_sharedInstance : [[self alloc] init];
69 }
70
71 #pragma mark -
72 #pragma mark Initialization
73
74 - (id)init
75 {
76     if (_o_sharedInstance) {
77         [self dealloc];
78         return _o_sharedInstance;
79     } else
80         _o_sharedInstance = [super init];
81
82     return _o_sharedInstance;
83 }
84
85 - (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask
86                   backing:(NSBackingStoreType)backingType defer:(BOOL)flag
87 {
88     self = [super initWithContentRect:contentRect styleMask:styleMask
89                               backing:backingType defer:flag];
90     _o_sharedInstance = self;
91
92     [[VLCMain sharedInstance] updateTogglePlaylistState];
93
94     return self;
95 }
96
97 - (BOOL)isEvent:(NSEvent *)o_event forKey:(const char *)keyString
98 {
99     char *key;
100     NSString *o_key;
101
102     key = config_GetPsz(VLCIntf, keyString);
103     o_key = [NSString stringWithFormat:@"%s", key];
104     FREENULL(key);
105
106     unsigned int i_keyModifiers = [[VLCStringUtility sharedInstance] VLCModifiersToCocoa:o_key];
107
108     NSString * characters = [o_event charactersIgnoringModifiers];
109     if ([characters length] > 0) {
110         return [[characters lowercaseString] isEqualToString: [[VLCStringUtility sharedInstance] VLCKeyToString: o_key]] &&
111                 (i_keyModifiers & NSShiftKeyMask)     == ([o_event modifierFlags] & NSShiftKeyMask) &&
112                 (i_keyModifiers & NSControlKeyMask)   == ([o_event modifierFlags] & NSControlKeyMask) &&
113                 (i_keyModifiers & NSAlternateKeyMask) == ([o_event modifierFlags] & NSAlternateKeyMask) &&
114                 (i_keyModifiers & NSCommandKeyMask)   == ([o_event modifierFlags] & NSCommandKeyMask);
115     }
116     return NO;
117 }
118
119 - (BOOL)performKeyEquivalent:(NSEvent *)o_event
120 {
121     BOOL b_force = NO;
122     // these are key events which should be handled by vlc core, but are attached to a main menu item
123     if (![self isEvent: o_event forKey: "key-vol-up"] &&
124         ![self isEvent: o_event forKey: "key-vol-down"] &&
125         ![self isEvent: o_event forKey: "key-vol-mute"] &&
126         ![self isEvent: o_event forKey: "key-prev"] &&
127         ![self isEvent: o_event forKey: "key-next"] &&
128         ![self isEvent: o_event forKey: "key-jump+short"] &&
129         ![self isEvent: o_event forKey: "key-jump-short"]) {
130         /* We indeed want to prioritize some Cocoa key equivalent against libvlc,
131          so we perform the menu equivalent now. */
132         if ([[NSApp mainMenu] performKeyEquivalent:o_event])
133             return TRUE;
134     }
135     else
136         b_force = YES;
137
138     return [[VLCMain sharedInstance] hasDefinedShortcutKey:o_event force:b_force] ||
139            [(VLCControls *)[[VLCMain sharedInstance] controls] keyEvent:o_event];
140 }
141
142 - (void)dealloc
143 {
144     if (b_dark_interface)
145         [o_color_backdrop release];
146
147     [[NSNotificationCenter defaultCenter] removeObserver: self];
148     [o_sidebaritems release];
149     [o_fspanel release];
150
151     [super dealloc];
152 }
153
154 - (void)awakeFromNib
155 {
156     // sets lion fullscreen behaviour
157     [super awakeFromNib];
158
159     BOOL b_splitviewShouldBeHidden = NO;
160
161     if (!OSX_SNOW_LEOPARD)
162         [self setRestorable: NO];
163     [self setFrameAutosaveName:@"mainwindow"];
164
165     /* setup the styled interface */
166     b_nativeFullscreenMode = NO;
167 #ifdef MAC_OS_X_VERSION_10_7
168     if (!OSX_SNOW_LEOPARD)
169         b_nativeFullscreenMode = var_InheritBool(VLCIntf, "macosx-nativefullscreenmode");
170 #endif
171     [self useOptimizedDrawing: YES];
172
173     [[o_search_fld cell] setPlaceholderString: _NS("Search")];
174     [[o_search_fld cell] accessibilitySetOverrideValue:_NS("Enter a term to search the playlist. Results will be selected in the table.") forAttribute:NSAccessibilityDescriptionAttribute];
175
176     [o_dropzone_btn setTitle: _NS("Open media...")];
177     [[o_dropzone_btn cell] accessibilitySetOverrideValue:_NS("Click to open an advanced dialog to select the media to play. You can also drop files here to play.") forAttribute:NSAccessibilityDescriptionAttribute];
178     [o_dropzone_lbl setStringValue: _NS("Drop media here")];
179     [o_dropzone_img setImage: imageFromRes(@"dropzone")];
180
181     [o_podcast_add_btn setTitle: _NS("Subscribe")];
182     [o_podcast_remove_btn setTitle: _NS("Unsubscribe")];
183     [o_podcast_subscribe_title_lbl setStringValue: _NS("Subscribe to a podcast")];
184     [o_podcast_subscribe_subtitle_lbl setStringValue: _NS("Enter URL of the podcast to subscribe to:")];
185     [o_podcast_subscribe_cancel_btn setTitle: _NS("Cancel")];
186     [o_podcast_subscribe_ok_btn setTitle: _NS("Subscribe")];
187     [o_podcast_unsubscribe_title_lbl setStringValue: _NS("Unsubscribe from a podcast")];
188     [o_podcast_unsubscribe_subtitle_lbl setStringValue: _NS("Select the podcast you would like to unsubscribe from:")];
189     [o_podcast_unsubscribe_ok_btn setTitle: _NS("Unsubscribe")];
190     [o_podcast_unsubscribe_cancel_btn setTitle: _NS("Cancel")];
191
192     /* interface builder action */
193     CGFloat f_threshold_height = f_min_video_height + [o_controls_bar height];
194     if (b_dark_interface)
195         f_threshold_height += [o_titlebar_view frame].size.height;
196     if ([[self contentView] frame].size.height < f_threshold_height)
197         b_splitviewShouldBeHidden = YES;
198
199     [self setDelegate: self];
200     [self setExcludedFromWindowsMenu: YES];
201     [self setAcceptsMouseMovedEvents: YES];
202     // Set that here as IB seems to be buggy
203     if (b_dark_interface)
204         [self setContentMinSize:NSMakeSize(604., 288. + [o_titlebar_view frame].size.height)];
205     else
206         [self setContentMinSize:NSMakeSize(604., 288.)];
207
208     [self setTitle: _NS("VLC media player")];
209
210     b_dropzone_active = YES;
211     [o_dropzone_view setFrame: [o_playlist_table frame]];
212     [o_left_split_view setFrame: [o_sidebar_view frame]];
213
214     if (!OSX_SNOW_LEOPARD) {
215         /* the default small size of the search field is slightly different on Lion, let's work-around that */
216         NSRect frame;
217         frame = [o_search_fld frame];
218         frame.origin.y = frame.origin.y + 2.0;
219         frame.size.height = frame.size.height - 1.0;
220         [o_search_fld setFrame: frame];
221     }
222
223     /* reload the sidebar */
224     [self reloadSidebar];
225
226     o_fspanel = [[VLCFSPanel alloc] initWithContentRect:NSMakeRect(110.,267.,549.,87.)
227                                               styleMask:NSTexturedBackgroundWindowMask
228                                                 backing:NSBackingStoreBuffered
229                                                   defer:YES];
230
231     /* make sure we display the desired default appearance when VLC launches for the first time */
232     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
233     if (![defaults objectForKey:@"VLCFirstRun"]) {
234         [defaults setObject:[NSDate date] forKey:@"VLCFirstRun"];
235
236         NSUInteger i_sidebaritem_count = [o_sidebaritems count];
237         for (NSUInteger x = 0; x < i_sidebaritem_count; x++)
238             [o_sidebar_view expandItem: [o_sidebaritems objectAtIndex:x] expandChildren: YES];
239
240         [o_fspanel center];
241
242         NSAlert *albumArtAlert = [NSAlert alertWithMessageText:_NS("Check for album art and metadata?") defaultButton:_NS("Enable Metadata Retrieval") alternateButton:_NS("No, Thanks") otherButton:nil informativeTextWithFormat:@"%@",_NS("VLC can check online for album art and metadata to enrich your playback experience, e.g. by providing track information when playing Audio CDs. To provide this functionality, VLC will send information about your contents to trusted services in an anonymized form.")];
243         NSInteger returnValue = [albumArtAlert runModal];
244         config_PutInt(VLCIntf, "metadata-network-access", returnValue == NSAlertDefaultReturn);
245     }
246
247     // select playlist item by default
248     [o_sidebar_view selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
249
250     if (b_dark_interface) {
251         [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(windowResizedOrMoved:) name: NSWindowDidResizeNotification object: nil];
252         [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(windowResizedOrMoved:) name: NSWindowDidMoveNotification object: nil];
253
254         [self setBackgroundColor: [NSColor clearColor]];
255         [self setOpaque: NO];
256         [self display];
257         [self setHasShadow:NO];
258         [self setHasShadow:YES];
259
260         NSRect winrect = [self frame];
261         CGFloat f_titleBarHeight = [o_titlebar_view frame].size.height;
262
263         [o_titlebar_view setFrame: NSMakeRect(0, winrect.size.height - f_titleBarHeight,
264                                               winrect.size.width, f_titleBarHeight)];
265         [[self contentView] addSubview: o_titlebar_view positioned: NSWindowAbove relativeTo: o_split_view];
266
267         if (winrect.size.height > 100) {
268             [self setFrame: winrect display:YES animate:YES];
269             previousSavedFrame = winrect;
270         }
271
272         winrect = [o_split_view frame];
273         winrect.size.height = winrect.size.height - f_titleBarHeight;
274         [o_split_view setFrame: winrect];
275         [o_video_view setFrame: winrect];
276
277         o_color_backdrop = [[VLCColorView alloc] initWithFrame: [o_split_view frame]];
278         [[self contentView] addSubview: o_color_backdrop positioned: NSWindowBelow relativeTo: o_split_view];
279         [o_color_backdrop setAutoresizingMask:NSViewHeightSizable | NSViewWidthSizable];
280     } else {
281         [o_video_view setFrame: [o_split_view frame]];
282         [o_playlist_table setBorderType: NSNoBorder];
283         [o_sidebar_scrollview setBorderType: NSNoBorder];
284     }
285
286     [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(someWindowWillClose:) name: NSWindowWillCloseNotification object: nil];
287     [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(someWindowWillMiniaturize:) name: NSWindowWillMiniaturizeNotification object:nil];
288     [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(applicationWillTerminate:) name: NSApplicationWillTerminateNotification object: nil];
289     [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(mainSplitViewDidResizeSubviews:) name: NSSplitViewDidResizeSubviewsNotification object:o_split_view];
290
291     if (b_splitviewShouldBeHidden) {
292         [self hideSplitView: YES];
293         f_lastSplitViewHeight = 300;
294     }
295
296     /* sanity check for the window size */
297     NSRect frame = [self frame];
298     NSSize screenSize = [[self screen] frame].size;
299     if (screenSize.width <= frame.size.width || screenSize.height <= frame.size.height) {
300         nativeVideoSize = screenSize;
301         [self resizeWindow];
302     }
303
304     /* update fs button to reflect state for next startup */
305     if (var_InheritBool(pl_Get(VLCIntf), "fullscreen"))
306         [o_controls_bar setFullscreenState:YES];
307
308     /* restore split view */
309     f_lastLeftSplitViewWidth = 200;
310     /* trick NSSplitView implementation, which pretends to know better than us */
311     if (!config_GetInt(VLCIntf, "macosx-show-sidebar"))
312         [self performSelector:@selector(toggleLeftSubSplitView) withObject:nil afterDelay:0.05];
313 }
314
315 #pragma mark -
316 #pragma mark appearance management
317
318 - (void)reloadSidebar
319 {
320     BOOL isAReload = NO;
321     if (o_sidebaritems) {
322         [o_sidebaritems release];
323         isAReload = YES;
324     }
325
326     o_sidebaritems = [[NSMutableArray alloc] init];
327     SideBarItem *libraryItem = [SideBarItem itemWithTitle:_NS("LIBRARY") identifier:@"library"];
328     SideBarItem *playlistItem = [SideBarItem itemWithTitle:_NS("Playlist") identifier:@"playlist"];
329     [playlistItem setIcon: imageFromRes(@"sidebar-playlist")];
330     SideBarItem *medialibraryItem = [SideBarItem itemWithTitle:_NS("Media Library") identifier:@"medialibrary"];
331     [medialibraryItem setIcon: imageFromRes(@"sidebar-playlist")];
332     SideBarItem *mycompItem = [SideBarItem itemWithTitle:_NS("MY COMPUTER") identifier:@"mycomputer"];
333     SideBarItem *devicesItem = [SideBarItem itemWithTitle:_NS("DEVICES") identifier:@"devices"];
334     SideBarItem *lanItem = [SideBarItem itemWithTitle:_NS("LOCAL NETWORK") identifier:@"localnetwork"];
335     SideBarItem *internetItem = [SideBarItem itemWithTitle:_NS("INTERNET") identifier:@"internet"];
336
337     /* SD subnodes, inspired by the Qt4 intf */
338     char **ppsz_longnames = NULL;
339     int *p_categories = NULL;
340     char **ppsz_names = vlc_sd_GetNames(pl_Get(VLCIntf), &ppsz_longnames, &p_categories);
341     if (!ppsz_names)
342         msg_Err(VLCIntf, "no sd item found"); //TODO
343     char **ppsz_name = ppsz_names, **ppsz_longname = ppsz_longnames;
344     int *p_category = p_categories;
345     NSMutableArray *internetItems = [[NSMutableArray alloc] init];
346     NSMutableArray *devicesItems = [[NSMutableArray alloc] init];
347     NSMutableArray *lanItems = [[NSMutableArray alloc] init];
348     NSMutableArray *mycompItems = [[NSMutableArray alloc] init];
349     NSString *o_identifier;
350     for (; ppsz_name && *ppsz_name; ppsz_name++, ppsz_longname++, p_category++) {
351         o_identifier = [NSString stringWithCString: *ppsz_name encoding: NSUTF8StringEncoding];
352         switch (*p_category) {
353             case SD_CAT_INTERNET:
354                 [internetItems addObject: [SideBarItem itemWithTitle: _NS(*ppsz_longname) identifier: o_identifier]];
355                 if (!strncmp(*ppsz_name, "podcast", 7))
356                     [[internetItems lastObject] setIcon: imageFromRes(@"sidebar-podcast")];
357                 else
358                     [[internetItems lastObject] setIcon: [NSImage imageNamed:@"NSApplicationIcon"]];
359                 [[internetItems lastObject] setSdtype: SD_CAT_INTERNET];
360                 [[internetItems lastObject] setUntranslatedTitle: [NSString stringWithUTF8String:*ppsz_longname]];
361                 break;
362             case SD_CAT_DEVICES:
363                 [devicesItems addObject: [SideBarItem itemWithTitle: _NS(*ppsz_longname) identifier: o_identifier]];
364                 [[devicesItems lastObject] setIcon: [NSImage imageNamed:@"NSApplicationIcon"]];
365                 [[devicesItems lastObject] setSdtype: SD_CAT_DEVICES];
366                 [[devicesItems lastObject] setUntranslatedTitle: [NSString stringWithUTF8String:*ppsz_longname]];
367                 break;
368             case SD_CAT_LAN:
369                 [lanItems addObject: [SideBarItem itemWithTitle: _NS(*ppsz_longname) identifier: o_identifier]];
370                 [[lanItems lastObject] setIcon: imageFromRes(@"sidebar-local")];
371                 [[lanItems lastObject] setSdtype: SD_CAT_LAN];
372                 [[lanItems lastObject] setUntranslatedTitle: [NSString stringWithUTF8String:*ppsz_longname]];
373                 break;
374             case SD_CAT_MYCOMPUTER:
375                 [mycompItems addObject: [SideBarItem itemWithTitle: _NS(*ppsz_longname) identifier: o_identifier]];
376                 if (!strncmp(*ppsz_name, "video_dir", 9))
377                     [[mycompItems lastObject] setIcon: imageFromRes(@"sidebar-movie")];
378                 else if (!strncmp(*ppsz_name, "audio_dir", 9))
379                     [[mycompItems lastObject] setIcon: imageFromRes(@"sidebar-music")];
380                 else if (!strncmp(*ppsz_name, "picture_dir", 11))
381                     [[mycompItems lastObject] setIcon: imageFromRes(@"sidebar-pictures")];
382                 else
383                     [[mycompItems lastObject] setIcon: [NSImage imageNamed:@"NSApplicationIcon"]];
384                 [[mycompItems lastObject] setUntranslatedTitle: [NSString stringWithUTF8String:*ppsz_longname]];
385                 [[mycompItems lastObject] setSdtype: SD_CAT_MYCOMPUTER];
386                 break;
387             default:
388                 msg_Warn(VLCIntf, "unknown SD type found, skipping (%s)", *ppsz_name);
389                 break;
390         }
391
392         free(*ppsz_name);
393         free(*ppsz_longname);
394     }
395     [mycompItem setChildren: [NSArray arrayWithArray: mycompItems]];
396     [devicesItem setChildren: [NSArray arrayWithArray: devicesItems]];
397     [lanItem setChildren: [NSArray arrayWithArray: lanItems]];
398     [internetItem setChildren: [NSArray arrayWithArray: internetItems]];
399     [mycompItems release];
400     [devicesItems release];
401     [lanItems release];
402     [internetItems release];
403     free(ppsz_names);
404     free(ppsz_longnames);
405     free(p_categories);
406
407     [libraryItem setChildren: [NSArray arrayWithObjects:playlistItem, medialibraryItem, nil]];
408     [o_sidebaritems addObject: libraryItem];
409     if ([mycompItem hasChildren])
410         [o_sidebaritems addObject: mycompItem];
411     if ([devicesItem hasChildren])
412         [o_sidebaritems addObject: devicesItem];
413     if ([lanItem hasChildren])
414         [o_sidebaritems addObject: lanItem];
415     if ([internetItem hasChildren])
416         [o_sidebaritems addObject: internetItem];
417
418     [o_sidebar_view reloadData];
419     [o_sidebar_view setDropItem:playlistItem dropChildIndex:NSOutlineViewDropOnItemIndex];
420     [o_sidebar_view registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, @"VLCPlaylistItemPboardType", nil]];
421
422     [o_sidebar_view setAutosaveName:@"mainwindow-sidebar"];
423     [(PXSourceList *)o_sidebar_view setDataSource:self];
424     [o_sidebar_view setDelegate:self];
425     [o_sidebar_view setAutosaveExpandedItems:YES];
426
427     [o_sidebar_view expandItem: libraryItem expandChildren: YES];
428
429     if (isAReload) {
430         NSUInteger i_sidebaritem_count = [o_sidebaritems count];
431         for (NSUInteger x = 0; x < i_sidebaritem_count; x++)
432             [o_sidebar_view expandItem: [o_sidebaritems objectAtIndex:x] expandChildren: YES];
433     }
434 }
435
436 - (VLCMainWindowControlsBar *)controlsBar;
437 {
438     return (VLCMainWindowControlsBar *)o_controls_bar;
439 }
440
441 - (void)resizePlaylistAfterCollapse
442 {
443     // no animation here since we might be in the middle of another resize animation
444     NSRect plrect;
445     plrect = [o_playlist_table frame];
446     plrect.size.height = [o_split_view frame].size.height - 20.0; // actual pl top bar height, which differs from its frame
447     [o_playlist_table setFrame: plrect];
448     [o_playlist_table setNeedsDisplay: YES];
449
450     NSRect rightSplitRect;
451     rightSplitRect = [o_right_split_view frame];
452     plrect = [o_dropzone_box frame];
453     plrect.origin.x = (rightSplitRect.size.width - plrect.size.width) / 2;
454     plrect.origin.y = (rightSplitRect.size.height - plrect.size.height) / 2;
455     [o_dropzone_view setFrame: [o_playlist_table frame]];
456     [o_dropzone_box setFrame: plrect];
457     [o_dropzone_view setNeedsDisplay: YES];
458 }
459
460 - (void)makeSplitViewVisible
461 {
462     if (b_dark_interface)
463         [self setContentMinSize: NSMakeSize(604., 288. + [o_titlebar_view frame].size.height)];
464     else
465         [self setContentMinSize: NSMakeSize(604., 288.)];
466
467     NSRect old_frame = [self frame];
468     CGFloat newHeight = [self minSize].height;
469     if (old_frame.size.height < newHeight) {
470         NSRect new_frame = old_frame;
471         new_frame.origin.y = old_frame.origin.y + old_frame.size.height - newHeight;
472         new_frame.size.height = newHeight;
473
474         [[self animator] setFrame: new_frame display: YES animate: YES];
475     }
476
477     [o_video_view setHidden: YES];
478     [o_split_view setHidden: NO];
479     if (b_nativeFullscreenMode && [self fullscreen]) {
480         [[o_controls_bar bottomBarView] setHidden: NO];
481         [o_fspanel setNonActive:nil];
482     }
483
484     [self makeFirstResponder: o_playlist_table];
485 }
486
487 - (void)makeSplitViewHidden
488 {
489     if (b_dark_interface)
490         [self setContentMinSize: NSMakeSize(604., f_min_video_height + [o_titlebar_view frame].size.height)];
491     else
492         [self setContentMinSize: NSMakeSize(604., f_min_video_height)];
493
494     [o_split_view setHidden: YES];
495     [o_video_view setHidden: NO];
496     if (b_nativeFullscreenMode && [self fullscreen]) {
497         [[o_controls_bar bottomBarView] setHidden: YES];
498         [o_fspanel setActive:nil];
499     }
500
501     if ([[o_video_view subviews] count] > 0)
502         [self makeFirstResponder: [[o_video_view subviews] objectAtIndex:0]];
503 }
504
505
506 - (void)changePlaylistState:(VLCPlaylistStateEvent)event
507 {
508     // Beware, this code is really ugly
509
510     msg_Dbg(VLCIntf, "toggle playlist from state: removed splitview %i, minimized view %i. Event %i", b_splitview_removed, b_minimized_view, event);
511     if (![self isVisible] && event == psUserMenuEvent) {
512         [self makeKeyAndOrderFront: nil];
513         return;
514     }
515
516     BOOL b_activeVideo = [[VLCMain sharedInstance] activeVideoPlayback];
517     BOOL b_restored = NO;
518
519     // ignore alt if triggered through main menu shortcut
520     BOOL b_have_alt_key = ([[NSApp currentEvent] modifierFlags] & NSAlternateKeyMask) != 0;
521     if (event == psUserMenuEvent)
522         b_have_alt_key = NO;
523
524     // eUserMenuEvent is now handled same as eUserEvent
525     if(event == psUserMenuEvent)
526         event = psUserEvent;
527
528     if (b_dropzone_active && b_have_alt_key) {
529         [self hideDropZone];
530         return;
531     }
532
533     if (!(b_nativeFullscreenMode && b_fullscreen) && !b_splitview_removed && ((b_have_alt_key && b_activeVideo)
534                                                                               || (b_nonembedded && event == psUserEvent)
535                                                                               || (!b_activeVideo && event == psUserEvent)
536                                                                               || (b_minimized_view && event == psVideoStartedOrStoppedEvent))) {
537         // for starting playback, window is resized through resized events
538         // for stopping playback, resize through reset to previous frame
539         [self hideSplitView: event != psVideoStartedOrStoppedEvent];
540         b_minimized_view = NO;
541     } else {
542         if (b_splitview_removed) {
543             if (!b_nonembedded || (event == psUserEvent && b_nonembedded))
544                 [self showSplitView: event != psVideoStartedOrStoppedEvent];
545
546             if (event != psUserEvent)
547                 b_minimized_view = YES;
548             else
549                 b_minimized_view = NO;
550
551             if (b_activeVideo)
552                 b_restored = YES;
553         }
554
555         if (!b_nonembedded) {
556             if (([o_video_view isHidden] && b_activeVideo) || b_restored || (b_activeVideo && event != psUserEvent))
557                 [self makeSplitViewHidden];
558             else
559                 [self makeSplitViewVisible];
560         } else {
561             [o_split_view setHidden: NO];
562             [o_playlist_table setHidden: NO];
563             [o_video_view setHidden: YES];
564         }
565     }
566
567     msg_Dbg(VLCIntf, "toggle playlist to state: removed splitview %i, minimized view %i", b_splitview_removed, b_minimized_view);
568 }
569
570 - (IBAction)dropzoneButtonAction:(id)sender
571 {
572     [[[VLCMain sharedInstance] open] openFileGeneric];
573 }
574
575 #pragma mark -
576 #pragma mark overwritten default functionality
577
578 - (void)windowResizedOrMoved:(NSNotification *)notification
579 {
580     [self saveFrameUsingName: [self frameAutosaveName]];
581 }
582
583 - (void)applicationWillTerminate:(NSNotification *)notification
584 {
585     config_PutInt(VLCIntf, "macosx-show-sidebar", ![o_split_view isSubviewCollapsed:o_left_split_view]);
586
587     [self saveFrameUsingName: [self frameAutosaveName]];
588 }
589
590
591 - (void)someWindowWillClose:(NSNotification *)notification
592 {
593     id obj = [notification object];
594
595     // hasActiveVideo is defined for VLCVideoWindowCommon and subclasses
596     if ([obj respondsToSelector:@selector(hasActiveVideo)] && [obj hasActiveVideo]) {
597         if ([[VLCMain sharedInstance] activeVideoPlayback])
598             [[VLCCoreInteraction sharedInstance] stop];
599     }
600 }
601
602 - (void)someWindowWillMiniaturize:(NSNotification *)notification
603 {
604     if (config_GetInt(VLCIntf, "macosx-pause-minimized")) {
605         id obj = [notification object];
606
607         if ([obj class] == [VLCVideoWindowCommon class] || [obj class] == [VLCDetachedVideoWindow class] || ([obj class] == [VLCMainWindow class] && !b_nonembedded)) {
608             if ([[VLCMain sharedInstance] activeVideoPlayback])
609                 [[VLCCoreInteraction sharedInstance] pause];
610         }
611     }
612 }
613
614 #pragma mark -
615 #pragma mark Update interface and respond to foreign events
616 - (void)showDropZone
617 {
618     b_dropzone_active = YES;
619     [o_right_split_view addSubview: o_dropzone_view positioned:NSWindowAbove relativeTo:o_playlist_table];
620     [o_dropzone_view setFrame: [o_playlist_table frame]];
621     [o_playlist_table setHidden:YES];
622 }
623
624 - (void)hideDropZone
625 {
626     b_dropzone_active = NO;
627     [o_dropzone_view removeFromSuperview];
628     [o_playlist_table setHidden: NO];
629 }
630
631 - (void)hideSplitView:(BOOL)b_with_resize
632 {
633     if (b_with_resize) {
634         NSRect winrect = [self frame];
635         f_lastSplitViewHeight = [o_split_view frame].size.height;
636         winrect.size.height = winrect.size.height - f_lastSplitViewHeight;
637         winrect.origin.y = winrect.origin.y + f_lastSplitViewHeight;
638         [self setFrame: winrect display: YES animate: YES];
639     }
640
641     [self performSelector:@selector(hideDropZone) withObject:nil afterDelay:0.1];
642     if (b_dark_interface) {
643         [self setContentMinSize: NSMakeSize(604., [o_controls_bar height] + [o_titlebar_view frame].size.height)];
644         [self setContentMaxSize: NSMakeSize(FLT_MAX, [o_controls_bar height] + [o_titlebar_view frame].size.height)];
645     } else {
646         [self setContentMinSize: NSMakeSize(604., [o_controls_bar height])];
647         [self setContentMaxSize: NSMakeSize(FLT_MAX, [o_controls_bar height])];
648     }
649
650     b_splitview_removed = YES;
651 }
652
653 - (void)showSplitView:(BOOL)b_with_resize
654 {
655     [self updateWindow];
656     if (b_dark_interface)
657         [self setContentMinSize:NSMakeSize(604., 288. + [o_titlebar_view frame].size.height)];
658     else
659         [self setContentMinSize:NSMakeSize(604., 288.)];
660     [self setContentMaxSize: NSMakeSize(FLT_MAX, FLT_MAX)];
661
662     if (b_with_resize) {
663         NSRect winrect;
664         winrect = [self frame];
665         winrect.size.height = winrect.size.height + f_lastSplitViewHeight;
666         winrect.origin.y = winrect.origin.y - f_lastSplitViewHeight;
667         [self setFrame: winrect display: YES animate: YES];
668     }
669
670     [self performSelector:@selector(resizePlaylistAfterCollapse) withObject: nil afterDelay:0.75];
671
672     b_splitview_removed = NO;
673 }
674
675 - (void)updateTimeSlider
676 {
677     [o_controls_bar updateTimeSlider];
678     [o_fspanel updatePositionAndTime];
679
680     [[[VLCMain sharedInstance] voutController] updateWindowsControlsBarWithSelector:@selector(updateTimeSlider)];
681 }
682
683 - (void)updateName
684 {
685     input_thread_t * p_input;
686     p_input = pl_CurrentInput(VLCIntf);
687     if (p_input) {
688         NSString *aString;
689
690         if (!config_GetPsz(VLCIntf, "video-title")) {
691             char *format = var_InheritString(VLCIntf, "input-title-format");
692             char *formated = str_format_meta(p_input, format);
693             free(format);
694             aString = [NSString stringWithUTF8String:formated];
695             free(formated);
696         } else
697             aString = [NSString stringWithUTF8String:config_GetPsz(VLCIntf, "video-title")];
698
699         char *uri = input_item_GetURI(input_GetItem(p_input));
700
701         NSURL * o_url = [NSURL URLWithString:[NSString stringWithUTF8String:uri]];
702         if ([o_url isFileURL]) {
703             [self setRepresentedURL: o_url];
704             [[[VLCMain sharedInstance] voutController] updateWindowsUsingBlock:^(VLCVideoWindowCommon *o_window) {
705                 [o_window setRepresentedURL:o_url];
706             }];
707         } else {
708             [self setRepresentedURL: nil];
709             [[[VLCMain sharedInstance] voutController] updateWindowsUsingBlock:^(VLCVideoWindowCommon *o_window) {
710                 [o_window setRepresentedURL:nil];
711             }];
712         }
713         free(uri);
714
715         if ([aString isEqualToString:@""]) {
716             if ([o_url isFileURL])
717                 aString = [[NSFileManager defaultManager] displayNameAtPath: [o_url path]];
718             else
719                 aString = [o_url absoluteString];
720         }
721
722         if ([aString length] > 0) {
723             [self setTitle: aString];
724             [[[VLCMain sharedInstance] voutController] updateWindowsUsingBlock:^(VLCVideoWindowCommon *o_window) {
725                 [o_window setTitle:aString];
726             }];
727
728             [o_fspanel setStreamTitle: aString];
729         } else {
730             [self setTitle: _NS("VLC media player")];
731             [self setRepresentedURL: nil];
732         }
733
734         vlc_object_release(p_input);
735     } else {
736         [self setTitle: _NS("VLC media player")];
737         [self setRepresentedURL: nil];
738     }
739 }
740
741 - (void)updateWindow
742 {
743     [o_controls_bar updateControls];
744     [[[VLCMain sharedInstance] voutController] updateWindowsControlsBarWithSelector:@selector(updateControls)];
745
746     bool b_seekable = false;
747
748     playlist_t * p_playlist = pl_Get(VLCIntf);
749     input_thread_t * p_input = playlist_CurrentInput(p_playlist);
750     if (p_input) {
751         /* seekable streams */
752         b_seekable = var_GetBool(p_input, "can-seek");
753
754         vlc_object_release(p_input);
755     }
756
757     [self updateTimeSlider];
758     if ([o_fspanel respondsToSelector:@selector(setSeekable:)])
759         [o_fspanel setSeekable: b_seekable];
760
761     PL_LOCK;
762     if ([[[VLCMain sharedInstance] playlist] currentPlaylistRoot] != p_playlist->p_local_category || p_playlist->p_local_category->i_children > 0)
763         [self hideDropZone];
764     else
765         [self showDropZone];
766     PL_UNLOCK;
767     [o_sidebar_view setNeedsDisplay:YES];
768
769     [self _updatePlaylistTitle];
770 }
771
772 - (void)setPause
773 {
774     [o_controls_bar setPause];
775     [o_fspanel setPause];
776
777     [[[VLCMain sharedInstance] voutController] updateWindowsControlsBarWithSelector:@selector(setPause)];
778 }
779
780 - (void)setPlay
781 {
782     [o_controls_bar setPlay];
783     [o_fspanel setPlay];
784
785     [[[VLCMain sharedInstance] voutController] updateWindowsControlsBarWithSelector:@selector(setPlay)];
786 }
787
788 - (void)updateVolumeSlider
789 {
790     [[self controlsBar] updateVolumeSlider];
791     [o_fspanel setVolumeLevel: [[VLCCoreInteraction sharedInstance] volume]];
792 }
793
794 #pragma mark -
795 #pragma mark Video Output handling
796
797 - (void)videoplayWillBeStarted
798 {
799     if (!b_fullscreen)
800         frameBeforePlayback = [self frame];
801 }
802
803 - (void)setVideoplayEnabled
804 {
805     BOOL b_videoPlayback = [[VLCMain sharedInstance] activeVideoPlayback];
806         
807     if (!b_videoPlayback) {
808         if (!b_nonembedded && (!b_nativeFullscreenMode || (b_nativeFullscreenMode && !b_fullscreen)) && frameBeforePlayback.size.width > 0 && frameBeforePlayback.size.height > 0) {
809
810             // only resize back to minimum view of this is still desired final state
811             CGFloat f_threshold_height = f_min_video_height + [o_controls_bar height];
812             if(frameBeforePlayback.size.height > f_threshold_height || b_minimized_view) {
813
814                 if ([[VLCMain sharedInstance] isTerminating])
815                     [self setFrame:frameBeforePlayback display:YES];
816                 else
817                     [[self animator] setFrame:frameBeforePlayback display:YES];
818
819             }
820         }
821
822         frameBeforePlayback = NSMakeRect(0, 0, 0, 0);
823
824         // update fs button to reflect state for next startup
825         if (var_InheritBool(VLCIntf, "fullscreen") || var_GetBool(pl_Get(VLCIntf), "fullscreen")) {
826             [o_controls_bar setFullscreenState:YES];
827         }
828
829         [self makeFirstResponder: o_playlist_table];
830         [[[VLCMain sharedInstance] voutController] updateWindowLevelForHelperWindows: NSNormalWindowLevel];
831
832         // restore alpha value to 1 for the case that macosx-opaqueness is set to < 1
833         [self setAlphaValue:1.0];
834     }
835
836     if (b_nativeFullscreenMode) {
837         if ([self hasActiveVideo] && [self fullscreen]) {
838             [[o_controls_bar bottomBarView] setHidden: b_videoPlayback];
839             [o_fspanel setActive: nil];
840         } else {
841             [[o_controls_bar bottomBarView] setHidden: NO];
842             [o_fspanel setNonActive: nil];
843         }
844     }
845 }
846
847 #pragma mark -
848 #pragma mark Lion native fullscreen handling
849 - (void)windowWillEnterFullScreen:(NSNotification *)notification
850 {
851     [super windowWillEnterFullScreen:notification];
852
853     // update split view frame after removing title bar
854     if (b_dark_interface) {
855         NSRect frame = [[self contentView] frame];
856         frame.origin.y += [o_controls_bar height];
857         frame.size.height -= [o_controls_bar height];
858         [o_split_view setFrame:frame];
859     }
860 }
861
862 - (void)windowWillExitFullScreen:(NSNotification *)notification
863 {
864     [super windowWillExitFullScreen: notification];
865
866     // update split view frame after readding title bar
867     if (b_dark_interface) {
868         NSRect frame = [o_split_view frame];
869         frame.size.height -= [o_titlebar_view frame].size.height;
870         [o_split_view setFrame:frame];
871     }
872 }
873 #pragma mark -
874 #pragma mark Fullscreen support
875
876 - (void)showFullscreenController
877 {
878     id currentWindow = [NSApp keyWindow];
879     if ([currentWindow respondsToSelector:@selector(hasActiveVideo)] && [currentWindow hasActiveVideo]) {
880         if ([currentWindow respondsToSelector:@selector(fullscreen)] && [currentWindow fullscreen] && ![[currentWindow videoView] isHidden]) {
881
882             if ([[VLCMain sharedInstance] activeVideoPlayback])
883                 [o_fspanel fadeIn];
884         }
885     }
886
887 }
888
889 #pragma mark -
890 #pragma mark split view delegate
891 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)dividerIndex
892 {
893     if (dividerIndex == 0)
894         return 300.;
895     else
896         return proposedMax;
897 }
898
899 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)dividerIndex
900 {
901     if (dividerIndex == 0)
902         return 100.;
903     else
904         return proposedMin;
905 }
906
907 - (BOOL)splitView:(NSSplitView *)splitView canCollapseSubview:(NSView *)subview
908 {
909     return ([subview isEqual:o_left_split_view]);
910 }
911
912 - (BOOL)splitView:(NSSplitView *)splitView shouldAdjustSizeOfSubview:(NSView *)subview
913 {
914     if ([subview isEqual:o_left_split_view])
915         return NO;
916     return YES;
917 }
918
919 - (void)mainSplitViewDidResizeSubviews:(id)object
920 {
921     f_lastLeftSplitViewWidth = [o_left_split_view frame].size.width;
922     config_PutInt(VLCIntf, "macosx-show-sidebar", ![o_split_view isSubviewCollapsed:o_left_split_view]);
923     [[[VLCMain sharedInstance] mainMenu] updateSidebarMenuItem];
924 }
925
926 - (void)toggleLeftSubSplitView
927 {
928     [o_split_view adjustSubviews];
929     if ([o_split_view isSubviewCollapsed:o_left_split_view])
930         [o_split_view setPosition:f_lastLeftSplitViewWidth ofDividerAtIndex:0];
931     else
932         [o_split_view setPosition:[o_split_view minPossiblePositionOfDividerAtIndex:0] ofDividerAtIndex:0];
933     [[[VLCMain sharedInstance] mainMenu] updateSidebarMenuItem];
934 }
935
936 #pragma mark -
937 #pragma mark private playlist magic
938 - (void)_updatePlaylistTitle
939 {
940     playlist_t * p_playlist = pl_Get(VLCIntf);
941     PL_LOCK;
942     playlist_item_t * currentPlaylistRoot = [[[VLCMain sharedInstance] playlist] currentPlaylistRoot];
943     PL_UNLOCK;
944     if (currentPlaylistRoot == p_playlist->p_local_category || currentPlaylistRoot == p_playlist->p_ml_category) {
945         if (currentPlaylistRoot == p_playlist->p_local_category)
946             [o_chosen_category_lbl setStringValue: [_NS("Playlist") stringByAppendingString:[self _playbackDurationOfNode:p_playlist->p_local_category]]];
947         else
948             [o_chosen_category_lbl setStringValue: [_NS("Media Library") stringByAppendingString:[self _playbackDurationOfNode:p_playlist->p_ml_category]]];
949     }
950 }
951
952 - (NSString *)_playbackDurationOfNode:(playlist_item_t*)node
953 {
954     if (!node)
955         return @"";
956
957     playlist_t * p_playlist = pl_Get(VLCIntf);
958     PL_LOCK;
959     mtime_t mt_duration = playlist_GetNodeDuration( node );
960     PL_UNLOCK;
961
962     if (mt_duration < 1)
963         return @"";
964
965     mt_duration = mt_duration / 1000000;
966
967     NSDate *date = [NSDate dateWithTimeIntervalSince1970:mt_duration];
968     NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
969     [formatter setDateFormat:@"HH:mm:ss"];
970     [formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
971
972     NSString *playbackDuration = [NSString stringWithFormat:@" — %@",[formatter stringFromDate:date]];
973     [formatter release];
974     return playbackDuration;
975 }
976
977 #pragma mark -
978 #pragma mark Side Bar Data handling
979 /* taken under BSD-new from the PXSourceList sample project, adapted for VLC */
980 - (NSUInteger)sourceList:(PXSourceList*)sourceList numberOfChildrenOfItem:(id)item
981 {
982     //Works the same way as the NSOutlineView data source: `nil` means a parent item
983     if (item==nil)
984         return [o_sidebaritems count];
985     else
986         return [[item children] count];
987 }
988
989
990 - (id)sourceList:(PXSourceList*)aSourceList child:(NSUInteger)index ofItem:(id)item
991 {
992     //Works the same way as the NSOutlineView data source: `nil` means a parent item
993     if (item==nil)
994         return [o_sidebaritems objectAtIndex:index];
995     else
996         return [[item children] objectAtIndex:index];
997 }
998
999
1000 - (id)sourceList:(PXSourceList*)aSourceList objectValueForItem:(id)item
1001 {
1002     return [item title];
1003 }
1004
1005 - (void)sourceList:(PXSourceList*)aSourceList setObjectValue:(id)object forItem:(id)item
1006 {
1007     [item setTitle:object];
1008 }
1009
1010 - (BOOL)sourceList:(PXSourceList*)aSourceList isItemExpandable:(id)item
1011 {
1012     return [item hasChildren];
1013 }
1014
1015
1016 - (BOOL)sourceList:(PXSourceList*)aSourceList itemHasBadge:(id)item
1017 {
1018     if ([[item identifier] isEqualToString: @"playlist"] || [[item identifier] isEqualToString: @"medialibrary"])
1019         return YES;
1020
1021     return [item hasBadge];
1022 }
1023
1024
1025 - (NSInteger)sourceList:(PXSourceList*)aSourceList badgeValueForItem:(id)item
1026 {
1027     playlist_t * p_playlist = pl_Get(VLCIntf);
1028     NSInteger i_playlist_size = 0;
1029
1030     if ([[item identifier] isEqualToString: @"playlist"]) {
1031         PL_LOCK;
1032         i_playlist_size = p_playlist->p_local_category->i_children;
1033         PL_UNLOCK;
1034
1035         return i_playlist_size;
1036     }
1037     if ([[item identifier] isEqualToString: @"medialibrary"]) {
1038         PL_LOCK;
1039         if (p_playlist->p_ml_category)
1040             i_playlist_size = p_playlist->p_ml_category->i_children;
1041         PL_UNLOCK;
1042
1043         return i_playlist_size;
1044     }
1045
1046     return [item badgeValue];
1047 }
1048
1049
1050 - (BOOL)sourceList:(PXSourceList*)aSourceList itemHasIcon:(id)item
1051 {
1052     return [item hasIcon];
1053 }
1054
1055
1056 - (NSImage*)sourceList:(PXSourceList*)aSourceList iconForItem:(id)item
1057 {
1058     return [item icon];
1059 }
1060
1061 - (NSMenu*)sourceList:(PXSourceList*)aSourceList menuForEvent:(NSEvent*)theEvent item:(id)item
1062 {
1063     if ([theEvent type] == NSRightMouseDown || ([theEvent type] == NSLeftMouseDown && ([theEvent modifierFlags] & NSControlKeyMask) == NSControlKeyMask)) {
1064         if (item != nil) {
1065             if ([item sdtype] > 0)
1066             {
1067                 NSMenu *m = [[NSMenu alloc] init];
1068                 playlist_t * p_playlist = pl_Get(VLCIntf);
1069                 BOOL sd_loaded = playlist_IsServicesDiscoveryLoaded(p_playlist, [[item identifier] UTF8String]);
1070                 if (!sd_loaded)
1071                     [m addItemWithTitle:_NS("Enable") action:@selector(sdmenuhandler:) keyEquivalent:@""];
1072                 else
1073                     [m addItemWithTitle:_NS("Disable") action:@selector(sdmenuhandler:) keyEquivalent:@""];
1074                 [[m itemAtIndex:0] setRepresentedObject: [item identifier]];
1075
1076                 return [m autorelease];
1077             }
1078         }
1079     }
1080
1081     return nil;
1082 }
1083
1084 - (IBAction)sdmenuhandler:(id)sender
1085 {
1086     NSString * identifier = [sender representedObject];
1087     if ([identifier length] > 0 && ![identifier isEqualToString:@"lua{sd='freebox',longname='Freebox TV'}"]) {
1088         playlist_t * p_playlist = pl_Get(VLCIntf);
1089         BOOL sd_loaded = playlist_IsServicesDiscoveryLoaded(p_playlist, [identifier UTF8String]);
1090
1091         if (!sd_loaded)
1092             playlist_ServicesDiscoveryAdd(p_playlist, [identifier UTF8String]);
1093         else
1094             playlist_ServicesDiscoveryRemove(p_playlist, [identifier UTF8String]);
1095     }
1096 }
1097
1098 #pragma mark -
1099 #pragma mark Side Bar Delegate Methods
1100 /* taken under BSD-new from the PXSourceList sample project, adapted for VLC */
1101 - (BOOL)sourceList:(PXSourceList*)aSourceList isGroupAlwaysExpanded:(id)group
1102 {
1103     if ([[group identifier] isEqualToString:@"library"])
1104         return YES;
1105
1106     return NO;
1107 }
1108
1109 - (void)sourceListSelectionDidChange:(NSNotification *)notification
1110 {
1111     playlist_t * p_playlist = pl_Get(VLCIntf);
1112
1113     NSIndexSet *selectedIndexes = [o_sidebar_view selectedRowIndexes];
1114     id item = [o_sidebar_view itemAtRow:[selectedIndexes firstIndex]];
1115
1116     //Set the label text to represent the new selection
1117     if ([item sdtype] > -1 && [[item identifier] length] > 0) {
1118         BOOL sd_loaded = playlist_IsServicesDiscoveryLoaded(p_playlist, [[item identifier] UTF8String]);
1119         if (!sd_loaded)
1120             playlist_ServicesDiscoveryAdd(p_playlist, [[item identifier] UTF8String]);
1121     }
1122
1123     [o_chosen_category_lbl setStringValue:[item title]];
1124
1125     if ([[item identifier] isEqualToString:@"playlist"]) {
1126         [[[VLCMain sharedInstance] playlist] setPlaylistRoot:p_playlist->p_local_category];
1127         [o_chosen_category_lbl setStringValue: [[o_chosen_category_lbl stringValue] stringByAppendingString:[self _playbackDurationOfNode:p_playlist->p_local_category]]];
1128     } else if ([[item identifier] isEqualToString:@"medialibrary"]) {
1129         if (p_playlist->p_ml_category) {
1130             [[[VLCMain sharedInstance] playlist] setPlaylistRoot:p_playlist->p_ml_category];
1131             [o_chosen_category_lbl setStringValue: [[o_chosen_category_lbl stringValue] stringByAppendingString:[self _playbackDurationOfNode:p_playlist->p_ml_category]]];
1132         }
1133     } else {
1134         playlist_item_t * pl_item;
1135         PL_LOCK;
1136         pl_item = playlist_ChildSearchName(p_playlist->p_root, [[item untranslatedTitle] UTF8String]);
1137         PL_UNLOCK;
1138         [[[VLCMain sharedInstance] playlist] setPlaylistRoot: pl_item];
1139     }
1140
1141     PL_LOCK;
1142     if ([[[VLCMain sharedInstance] playlist] currentPlaylistRoot] != p_playlist->p_local_category || p_playlist->p_local_category->i_children > 0)
1143         [self hideDropZone];
1144     else
1145         [self showDropZone];
1146     PL_UNLOCK;
1147
1148     if ([[item identifier] isEqualToString:@"podcast{longname=\"Podcasts\"}"])
1149         [self showPodcastControls];
1150     else
1151         [self hidePodcastControls];
1152
1153     [[NSNotificationCenter defaultCenter] postNotificationName: @"VLCMediaKeySupportSettingChanged"
1154                                                         object: nil
1155                                                       userInfo: nil];
1156 }
1157
1158 - (NSDragOperation)sourceList:(PXSourceList *)aSourceList validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
1159 {
1160     if ([[item identifier] isEqualToString:@"playlist"] || [[item identifier] isEqualToString:@"medialibrary"]) {
1161         NSPasteboard *o_pasteboard = [info draggingPasteboard];
1162         if ([[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"] || [[o_pasteboard types] containsObject: NSFilenamesPboardType])
1163             return NSDragOperationGeneric;
1164     }
1165     return NSDragOperationNone;
1166 }
1167
1168 - (BOOL)sourceList:(PXSourceList *)aSourceList acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index
1169 {
1170     NSPasteboard *o_pasteboard = [info draggingPasteboard];
1171
1172     playlist_t * p_playlist = pl_Get(VLCIntf);
1173     playlist_item_t *p_node;
1174
1175     if ([[item identifier] isEqualToString:@"playlist"])
1176         p_node = p_playlist->p_local_category;
1177     else
1178         p_node = p_playlist->p_ml_category;
1179
1180     if ([[o_pasteboard types] containsObject: NSFilenamesPboardType]) {
1181         NSArray *o_values = [[o_pasteboard propertyListForType: NSFilenamesPboardType] sortedArrayUsingSelector: @selector(caseInsensitiveCompare:)];
1182         NSUInteger count = [o_values count];
1183         NSMutableArray *o_array = [NSMutableArray arrayWithCapacity:count];
1184
1185         for(NSUInteger i = 0; i < count; i++) {
1186             NSDictionary *o_dic;
1187             char *psz_uri = vlc_path2uri([[o_values objectAtIndex:i] UTF8String], NULL);
1188             if (!psz_uri)
1189                 continue;
1190
1191             o_dic = [NSDictionary dictionaryWithObject:[NSString stringWithCString:psz_uri encoding:NSUTF8StringEncoding] forKey:@"ITEM_URL"];
1192
1193             free(psz_uri);
1194
1195             [o_array addObject: o_dic];
1196         }
1197
1198         [[[VLCMain sharedInstance] playlist] appendNodeArray:o_array inNode: p_node atPos:-1 enqueue:YES];
1199         return YES;
1200     }
1201     else if ([[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"]) {
1202         NSArray * array = [[[VLCMain sharedInstance] playlist] draggedItems];
1203
1204         NSUInteger count = [array count];
1205         playlist_item_t * p_item = NULL;
1206
1207         PL_LOCK;
1208         for(NSUInteger i = 0; i < count; i++) {
1209             p_item = [[array objectAtIndex:i] pointerValue];
1210             if (!p_item) continue;
1211             playlist_NodeAddCopy(p_playlist, p_item, p_node, PLAYLIST_END);
1212         }
1213         PL_UNLOCK;
1214
1215         return YES;
1216     }
1217     return NO;
1218 }
1219
1220 - (id)sourceList:(PXSourceList *)aSourceList persistentObjectForItem:(id)item
1221 {
1222     return [item identifier];
1223 }
1224
1225 - (id)sourceList:(PXSourceList *)aSourceList itemForPersistentObject:(id)object
1226 {
1227     /* the following code assumes for sakes of simplicity that only the top level
1228      * items are allowed to have children */
1229
1230     NSArray * array = [NSArray arrayWithArray: o_sidebaritems]; // read-only arrays are noticebly faster
1231     NSUInteger count = [array count];
1232     if (count < 1)
1233         return nil;
1234
1235     for (NSUInteger x = 0; x < count; x++) {
1236         id item = [array objectAtIndex:x]; // save one objc selector call
1237         if ([[item identifier] isEqualToString:object])
1238             return item;
1239     }
1240
1241     return nil;
1242 }
1243
1244 #pragma mark -
1245 #pragma mark Podcast
1246
1247 - (IBAction)addPodcast:(id)sender
1248 {
1249     [NSApp beginSheet:o_podcast_subscribe_window modalForWindow:self modalDelegate:self didEndSelector:NULL contextInfo:nil];
1250 }
1251
1252 - (IBAction)addPodcastWindowAction:(id)sender
1253 {
1254     [o_podcast_subscribe_window orderOut:sender];
1255     [NSApp endSheet: o_podcast_subscribe_window];
1256
1257     if (sender == o_podcast_subscribe_ok_btn && [[o_podcast_subscribe_url_fld stringValue] length] > 0) {
1258         NSMutableString * podcastConf = [[NSMutableString alloc] init];
1259         if (config_GetPsz(VLCIntf, "podcast-urls") != NULL)
1260             [podcastConf appendFormat:@"%s|", config_GetPsz(VLCIntf, "podcast-urls")];
1261
1262         [podcastConf appendString: [o_podcast_subscribe_url_fld stringValue]];
1263         config_PutPsz(VLCIntf, "podcast-urls", [podcastConf UTF8String]);
1264         var_SetString(pl_Get(VLCIntf), "podcast-urls", [podcastConf UTF8String]);
1265         [podcastConf release];
1266     }
1267 }
1268
1269 - (IBAction)removePodcast:(id)sender
1270 {
1271     if (config_GetPsz(VLCIntf, "podcast-urls") != NULL) {
1272         [o_podcast_unsubscribe_pop removeAllItems];
1273         [o_podcast_unsubscribe_pop addItemsWithTitles:[[NSString stringWithUTF8String:config_GetPsz(VLCIntf, "podcast-urls")] componentsSeparatedByString:@"|"]];
1274         [NSApp beginSheet:o_podcast_unsubscribe_window modalForWindow:self modalDelegate:self didEndSelector:NULL contextInfo:nil];
1275     }
1276 }
1277
1278 - (IBAction)removePodcastWindowAction:(id)sender
1279 {
1280     [o_podcast_unsubscribe_window orderOut:sender];
1281     [NSApp endSheet: o_podcast_unsubscribe_window];
1282
1283     if (sender == o_podcast_unsubscribe_ok_btn) {
1284         NSMutableArray * urls = [[NSMutableArray alloc] initWithArray:[[NSString stringWithUTF8String:config_GetPsz(VLCIntf, "podcast-urls")] componentsSeparatedByString:@"|"]];
1285         [urls removeObjectAtIndex: [o_podcast_unsubscribe_pop indexOfSelectedItem]];
1286         config_PutPsz(VLCIntf, "podcast-urls", [[urls componentsJoinedByString:@"|"] UTF8String]);
1287         var_SetString(pl_Get(VLCIntf), "podcast-urls", config_GetPsz(VLCIntf, "podcast-urls"));
1288         [urls release];
1289
1290         /* reload the podcast module, since it won't update its list when removing podcasts */
1291         playlist_t * p_playlist = pl_Get(VLCIntf);
1292         if (playlist_IsServicesDiscoveryLoaded(p_playlist, "podcast{longname=\"Podcasts\"}")) {
1293             playlist_ServicesDiscoveryRemove(p_playlist, "podcast{longname=\"Podcasts\"}");
1294             playlist_ServicesDiscoveryAdd(p_playlist, "podcast{longname=\"Podcasts\"}");
1295             [[[VLCMain sharedInstance] playlist] playlistUpdated];
1296         }
1297     }
1298 }
1299
1300 - (void)showPodcastControls
1301 {
1302     NSRect podcastViewDimensions = [o_podcast_view frame];
1303     NSRect rightSplitRect = [o_right_split_view frame];
1304     NSRect playlistTableRect = [o_playlist_table frame];
1305
1306     podcastViewDimensions.size.width = rightSplitRect.size.width;
1307     podcastViewDimensions.origin.x = podcastViewDimensions.origin.y = .0;
1308     [o_podcast_view setFrame:podcastViewDimensions];
1309
1310     playlistTableRect.origin.y = playlistTableRect.origin.y + podcastViewDimensions.size.height;
1311     playlistTableRect.size.height = playlistTableRect.size.height - podcastViewDimensions.size.height;
1312     [o_playlist_table setFrame:playlistTableRect];
1313     [o_playlist_table setNeedsDisplay:YES];
1314
1315     [o_right_split_view addSubview: o_podcast_view positioned: NSWindowAbove relativeTo: o_right_split_view];
1316     b_podcastView_displayed = YES;
1317 }
1318
1319 - (void)hidePodcastControls
1320 {
1321     if (b_podcastView_displayed) {
1322         NSRect podcastViewDimensions = [o_podcast_view frame];
1323         NSRect playlistTableRect = [o_playlist_table frame];
1324
1325         playlistTableRect.origin.y = playlistTableRect.origin.y - podcastViewDimensions.size.height;
1326         playlistTableRect.size.height = playlistTableRect.size.height + podcastViewDimensions.size.height;
1327
1328         [o_podcast_view removeFromSuperviewWithoutNeedingDisplay];
1329         [o_playlist_table setFrame: playlistTableRect];
1330         b_podcastView_displayed = NO;
1331     }
1332 }
1333
1334 @end
1335
1336 @implementation VLCDetachedVideoWindow
1337
1338 - (void)awakeFromNib
1339 {
1340     // sets lion fullscreen behaviour
1341     [super awakeFromNib];
1342     [self setAcceptsMouseMovedEvents: YES];
1343
1344     if (b_dark_interface) {
1345         [self setBackgroundColor: [NSColor clearColor]];
1346
1347         [self setOpaque: NO];
1348         [self display];
1349         [self setHasShadow:NO];
1350         [self setHasShadow:YES];
1351
1352         NSRect winrect = [self frame];
1353         CGFloat f_titleBarHeight = [o_titlebar_view frame].size.height;
1354
1355         [self setTitle: _NS("VLC media player")];
1356         [o_titlebar_view setFrame: NSMakeRect(0, winrect.size.height - f_titleBarHeight, winrect.size.width, f_titleBarHeight)];
1357         [[self contentView] addSubview: o_titlebar_view positioned: NSWindowAbove relativeTo: nil];
1358
1359     } else {
1360         [self setBackgroundColor: [NSColor blackColor]];
1361     }
1362
1363     NSRect videoViewRect = [[self contentView] bounds];
1364     if (b_dark_interface)
1365         videoViewRect.size.height -= [o_titlebar_view frame].size.height;
1366     CGFloat f_bottomBarHeight = [[self controlsBar] height];
1367     videoViewRect.size.height -= f_bottomBarHeight;
1368     videoViewRect.origin.y = f_bottomBarHeight;
1369     [o_video_view setFrame: videoViewRect];
1370
1371     if (b_dark_interface) {
1372         o_color_backdrop = [[VLCColorView alloc] initWithFrame: [o_video_view frame]];
1373         [[self contentView] addSubview: o_color_backdrop positioned: NSWindowBelow relativeTo: o_video_view];
1374         [o_color_backdrop setAutoresizingMask:NSViewHeightSizable | NSViewWidthSizable];
1375
1376         [self setContentMinSize: NSMakeSize(363., f_min_video_height + [[self controlsBar] height] + [o_titlebar_view frame].size.height)];
1377     } else {
1378         [self setContentMinSize: NSMakeSize(363., f_min_video_height + [[self controlsBar] height])];
1379     }
1380 }
1381
1382 - (void)dealloc
1383 {
1384     if (b_dark_interface)
1385         [o_color_backdrop release];
1386
1387     [super dealloc];
1388 }
1389
1390 @end