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