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