]> git.sesse.net Git - vlc/blob - modules/gui/macosx/MainWindow.m
macosx: set a proper main window frame on termination
[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, "metadata-network-access", returnValue == NSAlertDefaultReturn);
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
796                 if ([[VLCMain sharedInstance] isTerminating])
797                     [self setFrame:frameBeforePlayback display:YES];
798                 else
799                     [[self animator] setFrame:frameBeforePlayback display:YES];
800
801             }
802         }
803
804         frameBeforePlayback = NSMakeRect(0, 0, 0, 0);
805
806         // update fs button to reflect state for next startup
807         if (var_InheritBool(VLCIntf, "fullscreen") || var_GetBool(pl_Get(VLCIntf), "fullscreen")) {
808             [o_controls_bar setFullscreenState:YES];
809         }
810
811         [self makeFirstResponder: o_playlist_table];
812         [[[VLCMain sharedInstance] voutController] updateWindowLevelForHelperWindows: NSNormalWindowLevel];
813
814         // restore alpha value to 1 for the case that macosx-opaqueness is set to < 1
815         [self setAlphaValue:1.0];
816     }
817
818     if (b_nativeFullscreenMode) {
819         if ([self hasActiveVideo] && [self fullscreen]) {
820             [[o_controls_bar bottomBarView] setHidden: b_videoPlayback];
821             [o_fspanel setActive: nil];
822         } else {
823             [[o_controls_bar bottomBarView] setHidden: NO];
824             [o_fspanel setNonActive: nil];
825         }
826     }
827 }
828
829 #pragma mark -
830 #pragma mark Lion native fullscreen handling
831 - (void)windowWillEnterFullScreen:(NSNotification *)notification
832 {
833     [super windowWillEnterFullScreen:notification];
834
835     // update split view frame after removing title bar
836     if (b_dark_interface) {
837         NSRect frame = [[self contentView] frame];
838         frame.origin.y += [o_controls_bar height];
839         frame.size.height -= [o_controls_bar height];
840         [o_split_view setFrame:frame];
841     }
842 }
843
844 - (void)windowWillExitFullScreen:(NSNotification *)notification
845 {
846     [super windowWillExitFullScreen: notification];
847
848     // update split view frame after readding title bar
849     if (b_dark_interface) {
850         NSRect frame = [o_split_view frame];
851         frame.size.height -= [o_titlebar_view frame].size.height;
852         [o_split_view setFrame:frame];
853     }
854 }
855 #pragma mark -
856 #pragma mark Fullscreen support
857
858 - (void)showFullscreenController
859 {
860     id currentWindow = [NSApp keyWindow];
861     if ([currentWindow respondsToSelector:@selector(hasActiveVideo)] && [currentWindow hasActiveVideo]) {
862         if ([currentWindow respondsToSelector:@selector(fullscreen)] && [currentWindow fullscreen] && ![[currentWindow videoView] isHidden]) {
863
864             if ([[VLCMain sharedInstance] activeVideoPlayback])
865                 [o_fspanel fadeIn];
866         }
867     }
868
869 }
870
871 #pragma mark -
872 #pragma mark split view delegate
873 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)dividerIndex
874 {
875     if (dividerIndex == 0)
876         return 300.;
877     else
878         return proposedMax;
879 }
880
881 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)dividerIndex
882 {
883     if (dividerIndex == 0)
884         return 100.;
885     else
886         return proposedMin;
887 }
888
889 - (BOOL)splitView:(NSSplitView *)splitView canCollapseSubview:(NSView *)subview
890 {
891     return ([subview isEqual:o_left_split_view]);
892 }
893
894 - (BOOL)splitView:(NSSplitView *)splitView shouldAdjustSizeOfSubview:(NSView *)subview
895 {
896     if ([subview isEqual:o_left_split_view])
897         return NO;
898     return YES;
899 }
900
901 - (void)mainSplitViewDidResizeSubviews:(id)object
902 {
903     f_lastLeftSplitViewWidth = [o_left_split_view frame].size.width;
904     config_PutInt(VLCIntf, "macosx-show-sidebar", ![o_split_view isSubviewCollapsed:o_left_split_view]);
905     [[[VLCMain sharedInstance] mainMenu] updateSidebarMenuItem];
906 }
907
908 - (void)toggleLeftSubSplitView
909 {
910     [o_split_view adjustSubviews];
911     if ([o_split_view isSubviewCollapsed:o_left_split_view])
912         [o_split_view setPosition:f_lastLeftSplitViewWidth ofDividerAtIndex:0];
913     else
914         [o_split_view setPosition:[o_split_view minPossiblePositionOfDividerAtIndex:0] ofDividerAtIndex:0];
915     [[[VLCMain sharedInstance] mainMenu] updateSidebarMenuItem];
916 }
917
918 #pragma mark -
919 #pragma mark private playlist magic
920 - (void)_updatePlaylistTitle
921 {
922     playlist_t * p_playlist = pl_Get(VLCIntf);
923     PL_LOCK;
924     playlist_item_t * currentPlaylistRoot = [[[VLCMain sharedInstance] playlist] currentPlaylistRoot];
925     PL_UNLOCK;
926     if (currentPlaylistRoot == p_playlist->p_local_category || currentPlaylistRoot == p_playlist->p_ml_category) {
927         if (currentPlaylistRoot == p_playlist->p_local_category)
928             [o_chosen_category_lbl setStringValue: [_NS("Playlist") stringByAppendingString:[self _playbackDurationOfNode:p_playlist->p_local_category]]];
929         else
930             [o_chosen_category_lbl setStringValue: [_NS("Media Library") stringByAppendingString:[self _playbackDurationOfNode:p_playlist->p_ml_category]]];
931     }
932 }
933
934 - (NSString *)_playbackDurationOfNode:(playlist_item_t*)node
935 {
936     if (!node)
937         return @"";
938
939     playlist_t * p_playlist = pl_Get(VLCIntf);
940     PL_LOCK;
941     mtime_t mt_duration = playlist_GetNodeDuration( node );
942     PL_UNLOCK;
943
944     if (mt_duration < 1)
945         return @"";
946
947     mt_duration = mt_duration / 1000000;
948
949     NSDate *date = [NSDate dateWithTimeIntervalSince1970:mt_duration];
950     NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
951     [formatter setDateFormat:@"HH:mm:ss"];
952     [formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
953
954     NSString *playbackDuration = [NSString stringWithFormat:@" — %@",[formatter stringFromDate:date]];
955     [formatter release];
956     return playbackDuration;
957 }
958
959 #pragma mark -
960 #pragma mark Side Bar Data handling
961 /* taken under BSD-new from the PXSourceList sample project, adapted for VLC */
962 - (NSUInteger)sourceList:(PXSourceList*)sourceList numberOfChildrenOfItem:(id)item
963 {
964     //Works the same way as the NSOutlineView data source: `nil` means a parent item
965     if (item==nil)
966         return [o_sidebaritems count];
967     else
968         return [[item children] count];
969 }
970
971
972 - (id)sourceList:(PXSourceList*)aSourceList child:(NSUInteger)index ofItem:(id)item
973 {
974     //Works the same way as the NSOutlineView data source: `nil` means a parent item
975     if (item==nil)
976         return [o_sidebaritems objectAtIndex:index];
977     else
978         return [[item children] objectAtIndex:index];
979 }
980
981
982 - (id)sourceList:(PXSourceList*)aSourceList objectValueForItem:(id)item
983 {
984     return [item title];
985 }
986
987 - (void)sourceList:(PXSourceList*)aSourceList setObjectValue:(id)object forItem:(id)item
988 {
989     [item setTitle:object];
990 }
991
992 - (BOOL)sourceList:(PXSourceList*)aSourceList isItemExpandable:(id)item
993 {
994     return [item hasChildren];
995 }
996
997
998 - (BOOL)sourceList:(PXSourceList*)aSourceList itemHasBadge:(id)item
999 {
1000     if ([[item identifier] isEqualToString: @"playlist"] || [[item identifier] isEqualToString: @"medialibrary"])
1001         return YES;
1002
1003     return [item hasBadge];
1004 }
1005
1006
1007 - (NSInteger)sourceList:(PXSourceList*)aSourceList badgeValueForItem:(id)item
1008 {
1009     playlist_t * p_playlist = pl_Get(VLCIntf);
1010     NSInteger i_playlist_size = 0;
1011
1012     if ([[item identifier] isEqualToString: @"playlist"]) {
1013         PL_LOCK;
1014         i_playlist_size = p_playlist->p_local_category->i_children;
1015         PL_UNLOCK;
1016
1017         return i_playlist_size;
1018     }
1019     if ([[item identifier] isEqualToString: @"medialibrary"]) {
1020         PL_LOCK;
1021         if (p_playlist->p_ml_category)
1022             i_playlist_size = p_playlist->p_ml_category->i_children;
1023         PL_UNLOCK;
1024
1025         return i_playlist_size;
1026     }
1027
1028     return [item badgeValue];
1029 }
1030
1031
1032 - (BOOL)sourceList:(PXSourceList*)aSourceList itemHasIcon:(id)item
1033 {
1034     return [item hasIcon];
1035 }
1036
1037
1038 - (NSImage*)sourceList:(PXSourceList*)aSourceList iconForItem:(id)item
1039 {
1040     return [item icon];
1041 }
1042
1043 - (NSMenu*)sourceList:(PXSourceList*)aSourceList menuForEvent:(NSEvent*)theEvent item:(id)item
1044 {
1045     if ([theEvent type] == NSRightMouseDown || ([theEvent type] == NSLeftMouseDown && ([theEvent modifierFlags] & NSControlKeyMask) == NSControlKeyMask)) {
1046         if (item != nil) {
1047             if ([item sdtype] > 0)
1048             {
1049                 NSMenu *m = [[NSMenu alloc] init];
1050                 playlist_t * p_playlist = pl_Get(VLCIntf);
1051                 BOOL sd_loaded = playlist_IsServicesDiscoveryLoaded(p_playlist, [[item identifier] UTF8String]);
1052                 if (!sd_loaded)
1053                     [m addItemWithTitle:_NS("Enable") action:@selector(sdmenuhandler:) keyEquivalent:@""];
1054                 else
1055                     [m addItemWithTitle:_NS("Disable") action:@selector(sdmenuhandler:) keyEquivalent:@""];
1056                 [[m itemAtIndex:0] setRepresentedObject: [item identifier]];
1057
1058                 return [m autorelease];
1059             }
1060         }
1061     }
1062
1063     return nil;
1064 }
1065
1066 - (IBAction)sdmenuhandler:(id)sender
1067 {
1068     NSString * identifier = [sender representedObject];
1069     if ([identifier length] > 0 && ![identifier isEqualToString:@"lua{sd='freebox',longname='Freebox TV'}"]) {
1070         playlist_t * p_playlist = pl_Get(VLCIntf);
1071         BOOL sd_loaded = playlist_IsServicesDiscoveryLoaded(p_playlist, [identifier UTF8String]);
1072
1073         if (!sd_loaded)
1074             playlist_ServicesDiscoveryAdd(p_playlist, [identifier UTF8String]);
1075         else
1076             playlist_ServicesDiscoveryRemove(p_playlist, [identifier UTF8String]);
1077     }
1078 }
1079
1080 #pragma mark -
1081 #pragma mark Side Bar Delegate Methods
1082 /* taken under BSD-new from the PXSourceList sample project, adapted for VLC */
1083 - (BOOL)sourceList:(PXSourceList*)aSourceList isGroupAlwaysExpanded:(id)group
1084 {
1085     if ([[group identifier] isEqualToString:@"library"])
1086         return YES;
1087
1088     return NO;
1089 }
1090
1091 - (void)sourceListSelectionDidChange:(NSNotification *)notification
1092 {
1093     playlist_t * p_playlist = pl_Get(VLCIntf);
1094
1095     NSIndexSet *selectedIndexes = [o_sidebar_view selectedRowIndexes];
1096     id item = [o_sidebar_view itemAtRow:[selectedIndexes firstIndex]];
1097
1098     //Set the label text to represent the new selection
1099     if ([item sdtype] > -1 && [[item identifier] length] > 0) {
1100         BOOL sd_loaded = playlist_IsServicesDiscoveryLoaded(p_playlist, [[item identifier] UTF8String]);
1101         if (!sd_loaded)
1102             playlist_ServicesDiscoveryAdd(p_playlist, [[item identifier] UTF8String]);
1103     }
1104
1105     [o_chosen_category_lbl setStringValue:[item title]];
1106
1107     if ([[item identifier] isEqualToString:@"playlist"]) {
1108         [[[VLCMain sharedInstance] playlist] setPlaylistRoot:p_playlist->p_local_category];
1109         [o_chosen_category_lbl setStringValue: [[o_chosen_category_lbl stringValue] stringByAppendingString:[self _playbackDurationOfNode:p_playlist->p_local_category]]];
1110     } else if ([[item identifier] isEqualToString:@"medialibrary"]) {
1111         if (p_playlist->p_ml_category) {
1112             [[[VLCMain sharedInstance] playlist] setPlaylistRoot:p_playlist->p_ml_category];
1113             [o_chosen_category_lbl setStringValue: [[o_chosen_category_lbl stringValue] stringByAppendingString:[self _playbackDurationOfNode:p_playlist->p_ml_category]]];
1114         }
1115     } else {
1116         playlist_item_t * pl_item;
1117         PL_LOCK;
1118         pl_item = playlist_ChildSearchName(p_playlist->p_root, [[item untranslatedTitle] UTF8String]);
1119         PL_UNLOCK;
1120         [[[VLCMain sharedInstance] playlist] setPlaylistRoot: pl_item];
1121     }
1122
1123     PL_LOCK;
1124     if ([[[VLCMain sharedInstance] playlist] currentPlaylistRoot] != p_playlist->p_local_category || p_playlist->p_local_category->i_children > 0)
1125         [self hideDropZone];
1126     else
1127         [self showDropZone];
1128     PL_UNLOCK;
1129
1130     if ([[item identifier] isEqualToString:@"podcast{longname=\"Podcasts\"}"])
1131         [self showPodcastControls];
1132     else
1133         [self hidePodcastControls];
1134
1135     [[NSNotificationCenter defaultCenter] postNotificationName: @"VLCMediaKeySupportSettingChanged"
1136                                                         object: nil
1137                                                       userInfo: nil];
1138 }
1139
1140 - (NSDragOperation)sourceList:(PXSourceList *)aSourceList validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
1141 {
1142     if ([[item identifier] isEqualToString:@"playlist"] || [[item identifier] isEqualToString:@"medialibrary"]) {
1143         NSPasteboard *o_pasteboard = [info draggingPasteboard];
1144         if ([[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"] || [[o_pasteboard types] containsObject: NSFilenamesPboardType])
1145             return NSDragOperationGeneric;
1146     }
1147     return NSDragOperationNone;
1148 }
1149
1150 - (BOOL)sourceList:(PXSourceList *)aSourceList acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index
1151 {
1152     NSPasteboard *o_pasteboard = [info draggingPasteboard];
1153
1154     playlist_t * p_playlist = pl_Get(VLCIntf);
1155     playlist_item_t *p_node;
1156
1157     if ([[item identifier] isEqualToString:@"playlist"])
1158         p_node = p_playlist->p_local_category;
1159     else
1160         p_node = p_playlist->p_ml_category;
1161
1162     if ([[o_pasteboard types] containsObject: NSFilenamesPboardType]) {
1163         NSArray *o_values = [[o_pasteboard propertyListForType: NSFilenamesPboardType] sortedArrayUsingSelector: @selector(caseInsensitiveCompare:)];
1164         NSUInteger count = [o_values count];
1165         NSMutableArray *o_array = [NSMutableArray arrayWithCapacity:count];
1166
1167         for(NSUInteger i = 0; i < count; i++) {
1168             NSDictionary *o_dic;
1169             char *psz_uri = vlc_path2uri([[o_values objectAtIndex:i] UTF8String], NULL);
1170             if (!psz_uri)
1171                 continue;
1172
1173             o_dic = [NSDictionary dictionaryWithObject:[NSString stringWithCString:psz_uri encoding:NSUTF8StringEncoding] forKey:@"ITEM_URL"];
1174
1175             free(psz_uri);
1176
1177             [o_array addObject: o_dic];
1178         }
1179
1180         [[[VLCMain sharedInstance] playlist] appendNodeArray:o_array inNode: p_node atPos:-1 enqueue:YES];
1181         return YES;
1182     }
1183     else if ([[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"]) {
1184         NSArray * array = [[[VLCMain sharedInstance] playlist] draggedItems];
1185
1186         NSUInteger count = [array count];
1187         playlist_item_t * p_item = NULL;
1188
1189         PL_LOCK;
1190         for(NSUInteger i = 0; i < count; i++) {
1191             p_item = [[array objectAtIndex:i] pointerValue];
1192             if (!p_item) continue;
1193             playlist_NodeAddCopy(p_playlist, p_item, p_node, PLAYLIST_END);
1194         }
1195         PL_UNLOCK;
1196
1197         return YES;
1198     }
1199     return NO;
1200 }
1201
1202 - (id)sourceList:(PXSourceList *)aSourceList persistentObjectForItem:(id)item
1203 {
1204     return [item identifier];
1205 }
1206
1207 - (id)sourceList:(PXSourceList *)aSourceList itemForPersistentObject:(id)object
1208 {
1209     /* the following code assumes for sakes of simplicity that only the top level
1210      * items are allowed to have children */
1211
1212     NSArray * array = [NSArray arrayWithArray: o_sidebaritems]; // read-only arrays are noticebly faster
1213     NSUInteger count = [array count];
1214     if (count < 1)
1215         return nil;
1216
1217     for (NSUInteger x = 0; x < count; x++) {
1218         id item = [array objectAtIndex:x]; // save one objc selector call
1219         if ([[item identifier] isEqualToString:object])
1220             return item;
1221     }
1222
1223     return nil;
1224 }
1225
1226 #pragma mark -
1227 #pragma mark Podcast
1228
1229 - (IBAction)addPodcast:(id)sender
1230 {
1231     [NSApp beginSheet:o_podcast_subscribe_window modalForWindow:self modalDelegate:self didEndSelector:NULL contextInfo:nil];
1232 }
1233
1234 - (IBAction)addPodcastWindowAction:(id)sender
1235 {
1236     [o_podcast_subscribe_window orderOut:sender];
1237     [NSApp endSheet: o_podcast_subscribe_window];
1238
1239     if (sender == o_podcast_subscribe_ok_btn && [[o_podcast_subscribe_url_fld stringValue] length] > 0) {
1240         NSMutableString * podcastConf = [[NSMutableString alloc] init];
1241         if (config_GetPsz(VLCIntf, "podcast-urls") != NULL)
1242             [podcastConf appendFormat:@"%s|", config_GetPsz(VLCIntf, "podcast-urls")];
1243
1244         [podcastConf appendString: [o_podcast_subscribe_url_fld stringValue]];
1245         config_PutPsz(VLCIntf, "podcast-urls", [podcastConf UTF8String]);
1246         var_SetString(pl_Get(VLCIntf), "podcast-urls", [podcastConf UTF8String]);
1247         [podcastConf release];
1248     }
1249 }
1250
1251 - (IBAction)removePodcast:(id)sender
1252 {
1253     if (config_GetPsz(VLCIntf, "podcast-urls") != NULL) {
1254         [o_podcast_unsubscribe_pop removeAllItems];
1255         [o_podcast_unsubscribe_pop addItemsWithTitles:[[NSString stringWithUTF8String:config_GetPsz(VLCIntf, "podcast-urls")] componentsSeparatedByString:@"|"]];
1256         [NSApp beginSheet:o_podcast_unsubscribe_window modalForWindow:self modalDelegate:self didEndSelector:NULL contextInfo:nil];
1257     }
1258 }
1259
1260 - (IBAction)removePodcastWindowAction:(id)sender
1261 {
1262     [o_podcast_unsubscribe_window orderOut:sender];
1263     [NSApp endSheet: o_podcast_unsubscribe_window];
1264
1265     if (sender == o_podcast_unsubscribe_ok_btn) {
1266         NSMutableArray * urls = [[NSMutableArray alloc] initWithArray:[[NSString stringWithUTF8String:config_GetPsz(VLCIntf, "podcast-urls")] componentsSeparatedByString:@"|"]];
1267         [urls removeObjectAtIndex: [o_podcast_unsubscribe_pop indexOfSelectedItem]];
1268         config_PutPsz(VLCIntf, "podcast-urls", [[urls componentsJoinedByString:@"|"] UTF8String]);
1269         var_SetString(pl_Get(VLCIntf), "podcast-urls", config_GetPsz(VLCIntf, "podcast-urls"));
1270         [urls release];
1271
1272         /* reload the podcast module, since it won't update its list when removing podcasts */
1273         playlist_t * p_playlist = pl_Get(VLCIntf);
1274         if (playlist_IsServicesDiscoveryLoaded(p_playlist, "podcast{longname=\"Podcasts\"}")) {
1275             playlist_ServicesDiscoveryRemove(p_playlist, "podcast{longname=\"Podcasts\"}");
1276             playlist_ServicesDiscoveryAdd(p_playlist, "podcast{longname=\"Podcasts\"}");
1277             [[[VLCMain sharedInstance] playlist] playlistUpdated];
1278         }
1279     }
1280 }
1281
1282 - (void)showPodcastControls
1283 {
1284     NSRect podcastViewDimensions = [o_podcast_view frame];
1285     NSRect rightSplitRect = [o_right_split_view frame];
1286     NSRect playlistTableRect = [o_playlist_table frame];
1287
1288     podcastViewDimensions.size.width = rightSplitRect.size.width;
1289     podcastViewDimensions.origin.x = podcastViewDimensions.origin.y = .0;
1290     [o_podcast_view setFrame:podcastViewDimensions];
1291
1292     playlistTableRect.origin.y = playlistTableRect.origin.y + podcastViewDimensions.size.height;
1293     playlistTableRect.size.height = playlistTableRect.size.height - podcastViewDimensions.size.height;
1294     [o_playlist_table setFrame:playlistTableRect];
1295     [o_playlist_table setNeedsDisplay:YES];
1296
1297     [o_right_split_view addSubview: o_podcast_view positioned: NSWindowAbove relativeTo: o_right_split_view];
1298     b_podcastView_displayed = YES;
1299 }
1300
1301 - (void)hidePodcastControls
1302 {
1303     if (b_podcastView_displayed) {
1304         NSRect podcastViewDimensions = [o_podcast_view frame];
1305         NSRect playlistTableRect = [o_playlist_table frame];
1306
1307         playlistTableRect.origin.y = playlistTableRect.origin.y - podcastViewDimensions.size.height;
1308         playlistTableRect.size.height = playlistTableRect.size.height + podcastViewDimensions.size.height;
1309
1310         [o_podcast_view removeFromSuperviewWithoutNeedingDisplay];
1311         [o_playlist_table setFrame: playlistTableRect];
1312         b_podcastView_displayed = NO;
1313     }
1314 }
1315
1316 @end
1317
1318 @implementation VLCDetachedVideoWindow
1319
1320 - (void)awakeFromNib
1321 {
1322     // sets lion fullscreen behaviour
1323     [super awakeFromNib];
1324     [self setAcceptsMouseMovedEvents: YES];
1325
1326     if (b_dark_interface) {
1327         [self setBackgroundColor: [NSColor clearColor]];
1328
1329         [self setOpaque: NO];
1330         [self display];
1331         [self setHasShadow:NO];
1332         [self setHasShadow:YES];
1333
1334         NSRect winrect = [self frame];
1335         CGFloat f_titleBarHeight = [o_titlebar_view frame].size.height;
1336
1337         [self setTitle: _NS("VLC media player")];
1338         [o_titlebar_view setFrame: NSMakeRect(0, winrect.size.height - f_titleBarHeight, winrect.size.width, f_titleBarHeight)];
1339         [[self contentView] addSubview: o_titlebar_view positioned: NSWindowAbove relativeTo: nil];
1340
1341     } else {
1342         [self setBackgroundColor: [NSColor blackColor]];
1343     }
1344
1345     NSRect videoViewRect = [[self contentView] bounds];
1346     if (b_dark_interface)
1347         videoViewRect.size.height -= [o_titlebar_view frame].size.height;
1348     CGFloat f_bottomBarHeight = [[self controlsBar] height];
1349     videoViewRect.size.height -= f_bottomBarHeight;
1350     videoViewRect.origin.y = f_bottomBarHeight;
1351     [o_video_view setFrame: videoViewRect];
1352
1353     if (b_dark_interface) {
1354         o_color_backdrop = [[VLCColorView alloc] initWithFrame: [o_video_view frame]];
1355         [[self contentView] addSubview: o_color_backdrop positioned: NSWindowBelow relativeTo: o_video_view];
1356         [o_color_backdrop setAutoresizingMask:NSViewHeightSizable | NSViewWidthSizable];
1357
1358         [self setContentMinSize: NSMakeSize(363., f_min_video_height + [[self controlsBar] height] + [o_titlebar_view frame].size.height)];
1359     } else {
1360         [self setContentMinSize: NSMakeSize(363., f_min_video_height + [[self controlsBar] height])];
1361     }
1362 }
1363
1364 - (void)dealloc
1365 {
1366     if (b_dark_interface)
1367         [o_color_backdrop release];
1368
1369     [super dealloc];
1370 }
1371
1372 @end