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