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