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