]> git.sesse.net Git - vlc/blob - modules/gui/macosx/MainWindow.m
macosx: fix podcast view interfering with dropzone
[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                 if (!strncmp(*ppsz_name, "podcast", 7))
357                     [[internetItems lastObject] setIcon: imageFromRes(@"sidebar-podcast")];
358                 else
359                     [[internetItems lastObject] setIcon: [NSImage imageNamed:@"NSApplicationIcon"]];
360                 [[internetItems lastObject] setSdtype: SD_CAT_INTERNET];
361                 [[internetItems lastObject] setUntranslatedTitle: [NSString stringWithUTF8String:*ppsz_longname]];
362                 break;
363             case SD_CAT_DEVICES:
364                 [devicesItems addObject: [SideBarItem itemWithTitle: _NS(*ppsz_longname) identifier: o_identifier]];
365                 [[devicesItems lastObject] setIcon: [NSImage imageNamed:@"NSApplicationIcon"]];
366                 [[devicesItems lastObject] setSdtype: SD_CAT_DEVICES];
367                 [[devicesItems lastObject] setUntranslatedTitle: [NSString stringWithUTF8String:*ppsz_longname]];
368                 break;
369             case SD_CAT_LAN:
370                 [lanItems addObject: [SideBarItem itemWithTitle: _NS(*ppsz_longname) identifier: o_identifier]];
371                 [[lanItems lastObject] setIcon: imageFromRes(@"sidebar-local")];
372                 [[lanItems lastObject] setSdtype: SD_CAT_LAN];
373                 [[lanItems lastObject] setUntranslatedTitle: [NSString stringWithUTF8String:*ppsz_longname]];
374                 break;
375             case SD_CAT_MYCOMPUTER:
376                 [mycompItems addObject: [SideBarItem itemWithTitle: _NS(*ppsz_longname) identifier: o_identifier]];
377                 if (!strncmp(*ppsz_name, "video_dir", 9))
378                     [[mycompItems lastObject] setIcon: imageFromRes(@"sidebar-movie")];
379                 else if (!strncmp(*ppsz_name, "audio_dir", 9))
380                     [[mycompItems lastObject] setIcon: imageFromRes(@"sidebar-music")];
381                 else if (!strncmp(*ppsz_name, "picture_dir", 11))
382                     [[mycompItems lastObject] setIcon: imageFromRes(@"sidebar-pictures")];
383                 else
384                     [[mycompItems lastObject] setIcon: [NSImage imageNamed:@"NSApplicationIcon"]];
385                 [[mycompItems lastObject] setUntranslatedTitle: [NSString stringWithUTF8String:*ppsz_longname]];
386                 [[mycompItems lastObject] setSdtype: SD_CAT_MYCOMPUTER];
387                 break;
388             default:
389                 msg_Warn(VLCIntf, "unknown SD type found, skipping (%s)", *ppsz_name);
390                 break;
391         }
392
393         free(*ppsz_name);
394         free(*ppsz_longname);
395     }
396     [mycompItem setChildren: [NSArray arrayWithArray: mycompItems]];
397     [devicesItem setChildren: [NSArray arrayWithArray: devicesItems]];
398     [lanItem setChildren: [NSArray arrayWithArray: lanItems]];
399     [internetItem setChildren: [NSArray arrayWithArray: internetItems]];
400     [mycompItems release];
401     [devicesItems release];
402     [lanItems release];
403     [internetItems release];
404     free(ppsz_names);
405     free(ppsz_longnames);
406     free(p_categories);
407
408     [libraryItem setChildren: [NSArray arrayWithObjects:playlistItem, medialibraryItem, nil]];
409     [o_sidebaritems addObject: libraryItem];
410     if ([mycompItem hasChildren])
411         [o_sidebaritems addObject: mycompItem];
412     if ([devicesItem hasChildren])
413         [o_sidebaritems addObject: devicesItem];
414     if ([lanItem hasChildren])
415         [o_sidebaritems addObject: lanItem];
416     if ([internetItem hasChildren])
417         [o_sidebaritems addObject: internetItem];
418
419     [o_sidebar_view reloadData];
420     [o_sidebar_view setDropItem:playlistItem dropChildIndex:NSOutlineViewDropOnItemIndex];
421     [o_sidebar_view registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, @"VLCPlaylistItemPboardType", nil]];
422
423     [o_sidebar_view setAutosaveName:@"mainwindow-sidebar"];
424     [(PXSourceList *)o_sidebar_view setDataSource:self];
425     [o_sidebar_view setDelegate:self];
426     [o_sidebar_view setAutosaveExpandedItems:YES];
427
428     [o_sidebar_view expandItem: libraryItem expandChildren: YES];
429
430     if (isAReload) {
431         NSUInteger i_sidebaritem_count = [o_sidebaritems count];
432         for (NSUInteger x = 0; x < i_sidebaritem_count; x++)
433             [o_sidebar_view expandItem: [o_sidebaritems objectAtIndex:x] expandChildren: YES];
434     }
435 }
436
437 - (VLCMainWindowControlsBar *)controlsBar;
438 {
439     return (VLCMainWindowControlsBar *)o_controls_bar;
440 }
441
442 - (void)resizePlaylistAfterCollapse
443 {
444     // no animation here since we might be in the middle of another resize animation
445     NSRect rightSplitRect = [o_right_split_view frame];
446
447     NSRect plrect;
448     plrect.size.height = rightSplitRect.size.height - 20.0; // actual pl top bar height, which differs from its frame
449     plrect.size.width = rightSplitRect.size.width;
450     plrect.origin.x = plrect.origin.y = 0.;
451
452     NSRect dropzoneboxRect = [o_dropzone_box frame];
453     dropzoneboxRect.origin.x = (plrect.size.width - dropzoneboxRect.size.width) / 2;
454     dropzoneboxRect.origin.y = (plrect.size.height - dropzoneboxRect.size.height) / 2;
455
456     [o_playlist_table setFrame: plrect];
457     [o_dropzone_view setFrame: plrect];
458     [o_dropzone_box setFrame: dropzoneboxRect];
459     [o_dropzone_view setNeedsDisplay: YES];
460     [o_playlist_table setNeedsDisplay: YES];
461 }
462
463 - (void)makeSplitViewVisible
464 {
465     if (b_dark_interface)
466         [self setContentMinSize: NSMakeSize(604., f_min_window_height + [o_titlebar_view frame].size.height)];
467     else
468         [self setContentMinSize: NSMakeSize(604., f_min_window_height)];
469
470     NSRect old_frame = [self frame];
471     CGFloat newHeight = [self minSize].height;
472     if (old_frame.size.height < newHeight) {
473         NSRect new_frame = old_frame;
474         new_frame.origin.y = old_frame.origin.y + old_frame.size.height - newHeight;
475         new_frame.size.height = newHeight;
476
477         [[self animator] setFrame: new_frame display: YES animate: YES];
478     }
479
480     [o_video_view setHidden: YES];
481     [o_split_view setHidden: NO];
482     if (b_nativeFullscreenMode && [self fullscreen]) {
483         [[o_controls_bar bottomBarView] setHidden: NO];
484         [o_fspanel setNonActive:nil];
485     }
486
487     [self makeFirstResponder: o_playlist_table];
488 }
489
490 - (void)makeSplitViewHidden
491 {
492     if (b_dark_interface)
493         [self setContentMinSize: NSMakeSize(604., f_min_video_height + [o_titlebar_view frame].size.height)];
494     else
495         [self setContentMinSize: NSMakeSize(604., f_min_video_height)];
496
497     [o_split_view setHidden: YES];
498     [o_video_view setHidden: NO];
499     if (b_nativeFullscreenMode && [self fullscreen]) {
500         [[o_controls_bar bottomBarView] setHidden: YES];
501         [o_fspanel setActive:nil];
502     }
503
504     if ([[o_video_view subviews] count] > 0)
505         [self makeFirstResponder: [[o_video_view subviews] objectAtIndex:0]];
506 }
507
508
509 - (void)changePlaylistState:(VLCPlaylistStateEvent)event
510 {
511     // Beware, this code is really ugly
512
513     msg_Dbg(VLCIntf, "toggle playlist from state: removed splitview %i, minimized view %i. Event %i", b_splitview_removed, b_minimized_view, event);
514     if (![self isVisible] && event == psUserMenuEvent) {
515         [self makeKeyAndOrderFront: nil];
516         return;
517     }
518
519     BOOL b_activeVideo = [[VLCMain sharedInstance] activeVideoPlayback];
520     BOOL b_restored = NO;
521
522     // ignore alt if triggered through main menu shortcut
523     BOOL b_have_alt_key = ([[NSApp currentEvent] modifierFlags] & NSAlternateKeyMask) != 0;
524     if (event == psUserMenuEvent)
525         b_have_alt_key = NO;
526
527     // eUserMenuEvent is now handled same as eUserEvent
528     if(event == psUserMenuEvent)
529         event = psUserEvent;
530
531     if (b_dropzone_active && b_have_alt_key) {
532         [self hideDropZone];
533         return;
534     }
535
536     if (!(b_nativeFullscreenMode && b_fullscreen) && !b_splitview_removed && ((b_have_alt_key && b_activeVideo)
537                                                                               || (b_nonembedded && event == psUserEvent)
538                                                                               || (!b_activeVideo && event == psUserEvent)
539                                                                               || (b_minimized_view && event == psVideoStartedOrStoppedEvent))) {
540         // for starting playback, window is resized through resized events
541         // for stopping playback, resize through reset to previous frame
542         [self hideSplitView: event != psVideoStartedOrStoppedEvent];
543         b_minimized_view = NO;
544     } else {
545         if (b_splitview_removed) {
546             if (!b_nonembedded || (event == psUserEvent && b_nonembedded))
547                 [self showSplitView: event != psVideoStartedOrStoppedEvent];
548
549             if (event != psUserEvent)
550                 b_minimized_view = YES;
551             else
552                 b_minimized_view = NO;
553
554             if (b_activeVideo)
555                 b_restored = YES;
556         }
557
558         if (!b_nonembedded) {
559             if (([o_video_view isHidden] && b_activeVideo) || b_restored || (b_activeVideo && event != psUserEvent))
560                 [self makeSplitViewHidden];
561             else
562                 [self makeSplitViewVisible];
563         } else {
564             [o_split_view setHidden: NO];
565             [o_playlist_table setHidden: NO];
566             [o_video_view setHidden: YES];
567         }
568     }
569
570     msg_Dbg(VLCIntf, "toggle playlist to state: removed splitview %i, minimized view %i", b_splitview_removed, b_minimized_view);
571 }
572
573 - (IBAction)dropzoneButtonAction:(id)sender
574 {
575     [[[VLCMain sharedInstance] open] openFileGeneric];
576 }
577
578 #pragma mark -
579 #pragma mark overwritten default functionality
580
581 - (void)windowResizedOrMoved:(NSNotification *)notification
582 {
583     [self saveFrameUsingName: [self frameAutosaveName]];
584 }
585
586 - (void)applicationWillTerminate:(NSNotification *)notification
587 {
588     config_PutInt(VLCIntf, "macosx-show-sidebar", ![o_split_view isSubviewCollapsed:o_left_split_view]);
589
590     [self saveFrameUsingName: [self frameAutosaveName]];
591 }
592
593
594 - (void)someWindowWillClose:(NSNotification *)notification
595 {
596     id obj = [notification object];
597
598     // hasActiveVideo is defined for VLCVideoWindowCommon and subclasses
599     if ([obj respondsToSelector:@selector(hasActiveVideo)] && [obj hasActiveVideo]) {
600         if ([[VLCMain sharedInstance] activeVideoPlayback])
601             [[VLCCoreInteraction sharedInstance] stop];
602     }
603 }
604
605 - (void)someWindowWillMiniaturize:(NSNotification *)notification
606 {
607     if (config_GetInt(VLCIntf, "macosx-pause-minimized")) {
608         id obj = [notification object];
609
610         if ([obj class] == [VLCVideoWindowCommon class] || [obj class] == [VLCDetachedVideoWindow class] || ([obj class] == [VLCMainWindow class] && !b_nonembedded)) {
611             if ([[VLCMain sharedInstance] activeVideoPlayback])
612                 [[VLCCoreInteraction sharedInstance] pause];
613         }
614     }
615 }
616
617 #pragma mark -
618 #pragma mark Update interface and respond to foreign events
619 - (void)showDropZone
620 {
621     b_dropzone_active = YES;
622     [o_right_split_view addSubview: o_dropzone_view positioned:NSWindowAbove relativeTo:o_playlist_table];
623     [o_dropzone_view setFrame: [o_playlist_table frame]];
624     [o_playlist_table setHidden:YES];
625 }
626
627 - (void)hideDropZone
628 {
629     b_dropzone_active = NO;
630     [o_dropzone_view removeFromSuperview];
631     [o_playlist_table setHidden: NO];
632 }
633
634 - (void)hideSplitView:(BOOL)b_with_resize
635 {
636     // cancel pending pl resizes, in case of fast toggle between both modes
637     [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(resizePlaylistAfterCollapse) object:nil];
638
639     if (b_with_resize) {
640         NSRect winrect = [self frame];
641         f_lastSplitViewHeight = [o_split_view frame].size.height;
642         winrect.size.height = winrect.size.height - f_lastSplitViewHeight;
643         winrect.origin.y = winrect.origin.y + f_lastSplitViewHeight;
644         [self setFrame: winrect display: YES animate: YES];
645     }
646
647     if (b_dark_interface) {
648         [self setContentMinSize: NSMakeSize(604., [o_controls_bar height] + [o_titlebar_view frame].size.height)];
649         [self setContentMaxSize: NSMakeSize(FLT_MAX, [o_controls_bar height] + [o_titlebar_view frame].size.height)];
650     } else {
651         [self setContentMinSize: NSMakeSize(604., [o_controls_bar height])];
652         [self setContentMaxSize: NSMakeSize(FLT_MAX, [o_controls_bar height])];
653     }
654
655     b_splitview_removed = YES;
656 }
657
658 - (void)showSplitView:(BOOL)b_with_resize
659 {
660     [self updateWindow];
661     if (b_dark_interface)
662         [self setContentMinSize:NSMakeSize(604., f_min_window_height + [o_titlebar_view frame].size.height)];
663     else
664         [self setContentMinSize:NSMakeSize(604., f_min_window_height)];
665     [self setContentMaxSize: NSMakeSize(FLT_MAX, FLT_MAX)];
666
667     if (b_with_resize) {
668         NSRect winrect;
669         winrect = [self frame];
670         winrect.size.height = winrect.size.height + f_lastSplitViewHeight;
671         winrect.origin.y = winrect.origin.y - f_lastSplitViewHeight;
672         [self setFrame: winrect display: YES animate: YES];
673     }
674
675     // cancel pending pl resizes, in case of fast toggle between both modes
676     [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(resizePlaylistAfterCollapse) object:nil];
677     [self performSelector:@selector(resizePlaylistAfterCollapse) withObject: nil afterDelay:0.75];
678
679     b_splitview_removed = NO;
680 }
681
682 - (void)updateTimeSlider
683 {
684     [o_controls_bar updateTimeSlider];
685     [o_fspanel updatePositionAndTime];
686
687     [[[VLCMain sharedInstance] voutController] updateWindowsControlsBarWithSelector:@selector(updateTimeSlider)];
688 }
689
690 - (void)updateName
691 {
692     input_thread_t * p_input;
693     p_input = pl_CurrentInput(VLCIntf);
694     if (p_input) {
695         NSString *aString;
696
697         if (!config_GetPsz(VLCIntf, "video-title")) {
698             char *format = var_InheritString(VLCIntf, "input-title-format");
699             char *formated = str_format_meta(p_input, format);
700             free(format);
701             aString = [NSString stringWithUTF8String:formated];
702             free(formated);
703         } else
704             aString = [NSString stringWithUTF8String:config_GetPsz(VLCIntf, "video-title")];
705
706         char *uri = input_item_GetURI(input_GetItem(p_input));
707
708         NSURL * o_url = [NSURL URLWithString:[NSString stringWithUTF8String:uri]];
709         if ([o_url isFileURL]) {
710             [self setRepresentedURL: o_url];
711             [[[VLCMain sharedInstance] voutController] updateWindowsUsingBlock:^(VLCVideoWindowCommon *o_window) {
712                 [o_window setRepresentedURL:o_url];
713             }];
714         } else {
715             [self setRepresentedURL: nil];
716             [[[VLCMain sharedInstance] voutController] updateWindowsUsingBlock:^(VLCVideoWindowCommon *o_window) {
717                 [o_window setRepresentedURL:nil];
718             }];
719         }
720         free(uri);
721
722         if ([aString isEqualToString:@""]) {
723             if ([o_url isFileURL])
724                 aString = [[NSFileManager defaultManager] displayNameAtPath: [o_url path]];
725             else
726                 aString = [o_url absoluteString];
727         }
728
729         if ([aString length] > 0) {
730             [self setTitle: aString];
731             [[[VLCMain sharedInstance] voutController] updateWindowsUsingBlock:^(VLCVideoWindowCommon *o_window) {
732                 [o_window setTitle:aString];
733             }];
734
735             [o_fspanel setStreamTitle: aString];
736         } else {
737             [self setTitle: _NS("VLC media player")];
738             [self setRepresentedURL: nil];
739         }
740
741         vlc_object_release(p_input);
742     } else {
743         [self setTitle: _NS("VLC media player")];
744         [self setRepresentedURL: nil];
745     }
746 }
747
748 - (void)updateWindow
749 {
750     [o_controls_bar updateControls];
751     [[[VLCMain sharedInstance] voutController] updateWindowsControlsBarWithSelector:@selector(updateControls)];
752
753     bool b_seekable = false;
754
755     playlist_t * p_playlist = pl_Get(VLCIntf);
756     input_thread_t * p_input = playlist_CurrentInput(p_playlist);
757     if (p_input) {
758         /* seekable streams */
759         b_seekable = var_GetBool(p_input, "can-seek");
760
761         vlc_object_release(p_input);
762     }
763
764     [self updateTimeSlider];
765     if ([o_fspanel respondsToSelector:@selector(setSeekable:)])
766         [o_fspanel setSeekable: b_seekable];
767
768     PL_LOCK;
769     if ([[[VLCMain sharedInstance] playlist] currentPlaylistRoot] != p_playlist->p_local_category || p_playlist->p_local_category->i_children > 0)
770         [self hideDropZone];
771     else
772         [self showDropZone];
773     PL_UNLOCK;
774     [o_sidebar_view setNeedsDisplay:YES];
775
776     [self _updatePlaylistTitle];
777 }
778
779 - (void)setPause
780 {
781     [o_controls_bar setPause];
782     [o_fspanel setPause];
783
784     [[[VLCMain sharedInstance] voutController] updateWindowsControlsBarWithSelector:@selector(setPause)];
785 }
786
787 - (void)setPlay
788 {
789     [o_controls_bar setPlay];
790     [o_fspanel setPlay];
791
792     [[[VLCMain sharedInstance] voutController] updateWindowsControlsBarWithSelector:@selector(setPlay)];
793 }
794
795 - (void)updateVolumeSlider
796 {
797     [[self controlsBar] updateVolumeSlider];
798     [o_fspanel setVolumeLevel: [[VLCCoreInteraction sharedInstance] volume]];
799 }
800
801 #pragma mark -
802 #pragma mark Video Output handling
803
804 - (void)videoplayWillBeStarted
805 {
806     if (!b_fullscreen)
807         frameBeforePlayback = [self frame];
808 }
809
810 - (void)setVideoplayEnabled
811 {
812     BOOL b_videoPlayback = [[VLCMain sharedInstance] activeVideoPlayback];
813         
814     if (!b_videoPlayback) {
815         if (!b_nonembedded && (!b_nativeFullscreenMode || (b_nativeFullscreenMode && !b_fullscreen)) && frameBeforePlayback.size.width > 0 && frameBeforePlayback.size.height > 0) {
816
817             // only resize back to minimum view of this is still desired final state
818             CGFloat f_threshold_height = f_min_video_height + [o_controls_bar height];
819             if(frameBeforePlayback.size.height > f_threshold_height || b_minimized_view) {
820
821                 if ([[VLCMain sharedInstance] isTerminating])
822                     [self setFrame:frameBeforePlayback display:YES];
823                 else
824                     [[self animator] setFrame:frameBeforePlayback display:YES];
825
826             }
827         }
828
829         frameBeforePlayback = NSMakeRect(0, 0, 0, 0);
830
831         // update fs button to reflect state for next startup
832         if (var_InheritBool(VLCIntf, "fullscreen") || var_GetBool(pl_Get(VLCIntf), "fullscreen")) {
833             [o_controls_bar setFullscreenState:YES];
834         }
835
836         [self makeFirstResponder: o_playlist_table];
837         [[[VLCMain sharedInstance] voutController] updateWindowLevelForHelperWindows: NSNormalWindowLevel];
838
839         // restore alpha value to 1 for the case that macosx-opaqueness is set to < 1
840         [self setAlphaValue:1.0];
841     }
842
843     if (b_nativeFullscreenMode) {
844         if ([self hasActiveVideo] && [self fullscreen]) {
845             [[o_controls_bar bottomBarView] setHidden: b_videoPlayback];
846             [o_fspanel setActive: nil];
847         } else {
848             [[o_controls_bar bottomBarView] setHidden: NO];
849             [o_fspanel setNonActive: nil];
850         }
851     }
852 }
853
854 #pragma mark -
855 #pragma mark Lion native fullscreen handling
856 - (void)windowWillEnterFullScreen:(NSNotification *)notification
857 {
858     [super windowWillEnterFullScreen:notification];
859
860     // update split view frame after removing title bar
861     if (b_dark_interface) {
862         NSRect frame = [[self contentView] frame];
863         frame.origin.y += [o_controls_bar height];
864         frame.size.height -= [o_controls_bar height];
865         [o_split_view setFrame:frame];
866     }
867 }
868
869 - (void)windowWillExitFullScreen:(NSNotification *)notification
870 {
871     [super windowWillExitFullScreen: notification];
872
873     // update split view frame after readding title bar
874     if (b_dark_interface) {
875         NSRect frame = [o_split_view frame];
876         frame.size.height -= [o_titlebar_view frame].size.height;
877         [o_split_view setFrame:frame];
878     }
879 }
880 #pragma mark -
881 #pragma mark Fullscreen support
882
883 - (void)showFullscreenController
884 {
885     id currentWindow = [NSApp keyWindow];
886     if ([currentWindow respondsToSelector:@selector(hasActiveVideo)] && [currentWindow hasActiveVideo]) {
887         if ([currentWindow respondsToSelector:@selector(fullscreen)] && [currentWindow fullscreen] && ![[currentWindow videoView] isHidden]) {
888
889             if ([[VLCMain sharedInstance] activeVideoPlayback])
890                 [o_fspanel fadeIn];
891         }
892     }
893
894 }
895
896 #pragma mark -
897 #pragma mark split view delegate
898 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)dividerIndex
899 {
900     if (dividerIndex == 0)
901         return 300.;
902     else
903         return proposedMax;
904 }
905
906 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)dividerIndex
907 {
908     if (dividerIndex == 0)
909         return 100.;
910     else
911         return proposedMin;
912 }
913
914 - (BOOL)splitView:(NSSplitView *)splitView canCollapseSubview:(NSView *)subview
915 {
916     return ([subview isEqual:o_left_split_view]);
917 }
918
919 - (BOOL)splitView:(NSSplitView *)splitView shouldAdjustSizeOfSubview:(NSView *)subview
920 {
921     if ([subview isEqual:o_left_split_view])
922         return NO;
923     return YES;
924 }
925
926 - (void)mainSplitViewDidResizeSubviews:(id)object
927 {
928     f_lastLeftSplitViewWidth = [o_left_split_view frame].size.width;
929     config_PutInt(VLCIntf, "macosx-show-sidebar", ![o_split_view isSubviewCollapsed:o_left_split_view]);
930     [[[VLCMain sharedInstance] mainMenu] updateSidebarMenuItem];
931 }
932
933 - (void)toggleLeftSubSplitView
934 {
935     [o_split_view adjustSubviews];
936     if ([o_split_view isSubviewCollapsed:o_left_split_view])
937         [o_split_view setPosition:f_lastLeftSplitViewWidth ofDividerAtIndex:0];
938     else
939         [o_split_view setPosition:[o_split_view minPossiblePositionOfDividerAtIndex:0] ofDividerAtIndex:0];
940     [[[VLCMain sharedInstance] mainMenu] updateSidebarMenuItem];
941 }
942
943 #pragma mark -
944 #pragma mark private playlist magic
945 - (void)_updatePlaylistTitle
946 {
947     playlist_t * p_playlist = pl_Get(VLCIntf);
948     PL_LOCK;
949     playlist_item_t * currentPlaylistRoot = [[[VLCMain sharedInstance] playlist] currentPlaylistRoot];
950     PL_UNLOCK;
951     if (currentPlaylistRoot == p_playlist->p_local_category || currentPlaylistRoot == p_playlist->p_ml_category) {
952         if (currentPlaylistRoot == p_playlist->p_local_category)
953             [o_chosen_category_lbl setStringValue: [_NS("Playlist") stringByAppendingString:[self _playbackDurationOfNode:p_playlist->p_local_category]]];
954         else
955             [o_chosen_category_lbl setStringValue: [_NS("Media Library") stringByAppendingString:[self _playbackDurationOfNode:p_playlist->p_ml_category]]];
956     }
957 }
958
959 - (NSString *)_playbackDurationOfNode:(playlist_item_t*)node
960 {
961     if (!node)
962         return @"";
963
964     playlist_t * p_playlist = pl_Get(VLCIntf);
965     PL_LOCK;
966     mtime_t mt_duration = playlist_GetNodeDuration( node );
967     PL_UNLOCK;
968
969     if (mt_duration < 1)
970         return @"";
971
972     mt_duration = mt_duration / 1000000;
973
974     NSDate *date = [NSDate dateWithTimeIntervalSince1970:mt_duration];
975     NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
976     [formatter setDateFormat:@"HH:mm:ss"];
977     [formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
978
979     NSString *playbackDuration = [NSString stringWithFormat:@" — %@",[formatter stringFromDate:date]];
980     [formatter release];
981     return playbackDuration;
982 }
983
984 #pragma mark -
985 #pragma mark Side Bar Data handling
986 /* taken under BSD-new from the PXSourceList sample project, adapted for VLC */
987 - (NSUInteger)sourceList:(PXSourceList*)sourceList numberOfChildrenOfItem:(id)item
988 {
989     //Works the same way as the NSOutlineView data source: `nil` means a parent item
990     if (item==nil)
991         return [o_sidebaritems count];
992     else
993         return [[item children] count];
994 }
995
996
997 - (id)sourceList:(PXSourceList*)aSourceList child:(NSUInteger)index ofItem:(id)item
998 {
999     //Works the same way as the NSOutlineView data source: `nil` means a parent item
1000     if (item==nil)
1001         return [o_sidebaritems objectAtIndex:index];
1002     else
1003         return [[item children] objectAtIndex:index];
1004 }
1005
1006
1007 - (id)sourceList:(PXSourceList*)aSourceList objectValueForItem:(id)item
1008 {
1009     return [item title];
1010 }
1011
1012 - (void)sourceList:(PXSourceList*)aSourceList setObjectValue:(id)object forItem:(id)item
1013 {
1014     [item setTitle:object];
1015 }
1016
1017 - (BOOL)sourceList:(PXSourceList*)aSourceList isItemExpandable:(id)item
1018 {
1019     return [item hasChildren];
1020 }
1021
1022
1023 - (BOOL)sourceList:(PXSourceList*)aSourceList itemHasBadge:(id)item
1024 {
1025     if ([[item identifier] isEqualToString: @"playlist"] || [[item identifier] isEqualToString: @"medialibrary"])
1026         return YES;
1027
1028     return [item hasBadge];
1029 }
1030
1031
1032 - (NSInteger)sourceList:(PXSourceList*)aSourceList badgeValueForItem:(id)item
1033 {
1034     playlist_t * p_playlist = pl_Get(VLCIntf);
1035     NSInteger i_playlist_size = 0;
1036
1037     if ([[item identifier] isEqualToString: @"playlist"]) {
1038         PL_LOCK;
1039         i_playlist_size = p_playlist->p_local_category->i_children;
1040         PL_UNLOCK;
1041
1042         return i_playlist_size;
1043     }
1044     if ([[item identifier] isEqualToString: @"medialibrary"]) {
1045         PL_LOCK;
1046         if (p_playlist->p_ml_category)
1047             i_playlist_size = p_playlist->p_ml_category->i_children;
1048         PL_UNLOCK;
1049
1050         return i_playlist_size;
1051     }
1052
1053     return [item badgeValue];
1054 }
1055
1056
1057 - (BOOL)sourceList:(PXSourceList*)aSourceList itemHasIcon:(id)item
1058 {
1059     return [item hasIcon];
1060 }
1061
1062
1063 - (NSImage*)sourceList:(PXSourceList*)aSourceList iconForItem:(id)item
1064 {
1065     return [item icon];
1066 }
1067
1068 - (NSMenu*)sourceList:(PXSourceList*)aSourceList menuForEvent:(NSEvent*)theEvent item:(id)item
1069 {
1070     if ([theEvent type] == NSRightMouseDown || ([theEvent type] == NSLeftMouseDown && ([theEvent modifierFlags] & NSControlKeyMask) == NSControlKeyMask)) {
1071         if (item != nil) {
1072             if ([item sdtype] > 0)
1073             {
1074                 NSMenu *m = [[NSMenu alloc] init];
1075                 playlist_t * p_playlist = pl_Get(VLCIntf);
1076                 BOOL sd_loaded = playlist_IsServicesDiscoveryLoaded(p_playlist, [[item identifier] UTF8String]);
1077                 if (!sd_loaded)
1078                     [m addItemWithTitle:_NS("Enable") action:@selector(sdmenuhandler:) keyEquivalent:@""];
1079                 else
1080                     [m addItemWithTitle:_NS("Disable") action:@selector(sdmenuhandler:) keyEquivalent:@""];
1081                 [[m itemAtIndex:0] setRepresentedObject: [item identifier]];
1082
1083                 return [m autorelease];
1084             }
1085         }
1086     }
1087
1088     return nil;
1089 }
1090
1091 - (IBAction)sdmenuhandler:(id)sender
1092 {
1093     NSString * identifier = [sender representedObject];
1094     if ([identifier length] > 0 && ![identifier isEqualToString:@"lua{sd='freebox',longname='Freebox TV'}"]) {
1095         playlist_t * p_playlist = pl_Get(VLCIntf);
1096         BOOL sd_loaded = playlist_IsServicesDiscoveryLoaded(p_playlist, [identifier UTF8String]);
1097
1098         if (!sd_loaded)
1099             playlist_ServicesDiscoveryAdd(p_playlist, [identifier UTF8String]);
1100         else
1101             playlist_ServicesDiscoveryRemove(p_playlist, [identifier UTF8String]);
1102     }
1103 }
1104
1105 #pragma mark -
1106 #pragma mark Side Bar Delegate Methods
1107 /* taken under BSD-new from the PXSourceList sample project, adapted for VLC */
1108 - (BOOL)sourceList:(PXSourceList*)aSourceList isGroupAlwaysExpanded:(id)group
1109 {
1110     if ([[group identifier] isEqualToString:@"library"])
1111         return YES;
1112
1113     return NO;
1114 }
1115
1116 - (void)sourceListSelectionDidChange:(NSNotification *)notification
1117 {
1118     playlist_t * p_playlist = pl_Get(VLCIntf);
1119
1120     NSIndexSet *selectedIndexes = [o_sidebar_view selectedRowIndexes];
1121     id item = [o_sidebar_view itemAtRow:[selectedIndexes firstIndex]];
1122
1123     //Set the label text to represent the new selection
1124     if ([item sdtype] > -1 && [[item identifier] length] > 0) {
1125         BOOL sd_loaded = playlist_IsServicesDiscoveryLoaded(p_playlist, [[item identifier] UTF8String]);
1126         if (!sd_loaded)
1127             playlist_ServicesDiscoveryAdd(p_playlist, [[item identifier] UTF8String]);
1128     }
1129
1130     [o_chosen_category_lbl setStringValue:[item title]];
1131
1132     if ([[item identifier] isEqualToString:@"playlist"]) {
1133         [[[VLCMain sharedInstance] playlist] setPlaylistRoot:p_playlist->p_local_category];
1134         [o_chosen_category_lbl setStringValue: [[o_chosen_category_lbl stringValue] stringByAppendingString:[self _playbackDurationOfNode:p_playlist->p_local_category]]];
1135     } else if ([[item identifier] isEqualToString:@"medialibrary"]) {
1136         if (p_playlist->p_ml_category) {
1137             [[[VLCMain sharedInstance] playlist] setPlaylistRoot:p_playlist->p_ml_category];
1138             [o_chosen_category_lbl setStringValue: [[o_chosen_category_lbl stringValue] stringByAppendingString:[self _playbackDurationOfNode:p_playlist->p_ml_category]]];
1139         }
1140     } else {
1141         playlist_item_t * pl_item;
1142         PL_LOCK;
1143         pl_item = playlist_ChildSearchName(p_playlist->p_root, [[item untranslatedTitle] UTF8String]);
1144         PL_UNLOCK;
1145         [[[VLCMain sharedInstance] playlist] setPlaylistRoot: pl_item];
1146     }
1147
1148     // Note the order: first hide the podcast controls, then show the drop zone
1149     if ([[item identifier] isEqualToString:@"podcast{longname=\"Podcasts\"}"])
1150         [self showPodcastControls];
1151     else
1152         [self hidePodcastControls];
1153
1154     PL_LOCK;
1155     if ([[[VLCMain sharedInstance] playlist] currentPlaylistRoot] != p_playlist->p_local_category || p_playlist->p_local_category->i_children > 0)
1156         [self hideDropZone];
1157     else
1158         [self showDropZone];
1159     PL_UNLOCK;
1160
1161     [[NSNotificationCenter defaultCenter] postNotificationName: @"VLCMediaKeySupportSettingChanged"
1162                                                         object: nil
1163                                                       userInfo: nil];
1164 }
1165
1166 - (NSDragOperation)sourceList:(PXSourceList *)aSourceList validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
1167 {
1168     if ([[item identifier] isEqualToString:@"playlist"] || [[item identifier] isEqualToString:@"medialibrary"]) {
1169         NSPasteboard *o_pasteboard = [info draggingPasteboard];
1170         if ([[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"] || [[o_pasteboard types] containsObject: NSFilenamesPboardType])
1171             return NSDragOperationGeneric;
1172     }
1173     return NSDragOperationNone;
1174 }
1175
1176 - (BOOL)sourceList:(PXSourceList *)aSourceList acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index
1177 {
1178     NSPasteboard *o_pasteboard = [info draggingPasteboard];
1179
1180     playlist_t * p_playlist = pl_Get(VLCIntf);
1181     playlist_item_t *p_node;
1182
1183     if ([[item identifier] isEqualToString:@"playlist"])
1184         p_node = p_playlist->p_local_category;
1185     else
1186         p_node = p_playlist->p_ml_category;
1187
1188     if ([[o_pasteboard types] containsObject: NSFilenamesPboardType]) {
1189         NSArray *o_values = [[o_pasteboard propertyListForType: NSFilenamesPboardType] sortedArrayUsingSelector: @selector(caseInsensitiveCompare:)];
1190         NSUInteger count = [o_values count];
1191         NSMutableArray *o_array = [NSMutableArray arrayWithCapacity:count];
1192
1193         for(NSUInteger i = 0; i < count; i++) {
1194             NSDictionary *o_dic;
1195             char *psz_uri = vlc_path2uri([[o_values objectAtIndex:i] UTF8String], NULL);
1196             if (!psz_uri)
1197                 continue;
1198
1199             o_dic = [NSDictionary dictionaryWithObject:[NSString stringWithCString:psz_uri encoding:NSUTF8StringEncoding] forKey:@"ITEM_URL"];
1200
1201             free(psz_uri);
1202
1203             [o_array addObject: o_dic];
1204         }
1205
1206         [[[VLCMain sharedInstance] playlist] appendNodeArray:o_array inNode: p_node atPos:-1 enqueue:YES];
1207         return YES;
1208     }
1209     else if ([[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"]) {
1210         NSArray * array = [[[VLCMain sharedInstance] playlist] draggedItems];
1211
1212         NSUInteger count = [array count];
1213         playlist_item_t * p_item = NULL;
1214
1215         PL_LOCK;
1216         for(NSUInteger i = 0; i < count; i++) {
1217             p_item = [[array objectAtIndex:i] pointerValue];
1218             if (!p_item) continue;
1219             playlist_NodeAddCopy(p_playlist, p_item, p_node, PLAYLIST_END);
1220         }
1221         PL_UNLOCK;
1222
1223         return YES;
1224     }
1225     return NO;
1226 }
1227
1228 - (id)sourceList:(PXSourceList *)aSourceList persistentObjectForItem:(id)item
1229 {
1230     return [item identifier];
1231 }
1232
1233 - (id)sourceList:(PXSourceList *)aSourceList itemForPersistentObject:(id)object
1234 {
1235     /* the following code assumes for sakes of simplicity that only the top level
1236      * items are allowed to have children */
1237
1238     NSArray * array = [NSArray arrayWithArray: o_sidebaritems]; // read-only arrays are noticebly faster
1239     NSUInteger count = [array count];
1240     if (count < 1)
1241         return nil;
1242
1243     for (NSUInteger x = 0; x < count; x++) {
1244         id item = [array objectAtIndex:x]; // save one objc selector call
1245         if ([[item identifier] isEqualToString:object])
1246             return item;
1247     }
1248
1249     return nil;
1250 }
1251
1252 #pragma mark -
1253 #pragma mark Podcast
1254
1255 - (IBAction)addPodcast:(id)sender
1256 {
1257     [NSApp beginSheet:o_podcast_subscribe_window modalForWindow:self modalDelegate:self didEndSelector:NULL contextInfo:nil];
1258 }
1259
1260 - (IBAction)addPodcastWindowAction:(id)sender
1261 {
1262     [o_podcast_subscribe_window orderOut:sender];
1263     [NSApp endSheet: o_podcast_subscribe_window];
1264
1265     if (sender == o_podcast_subscribe_ok_btn && [[o_podcast_subscribe_url_fld stringValue] length] > 0) {
1266         NSMutableString * podcastConf = [[NSMutableString alloc] init];
1267         if (config_GetPsz(VLCIntf, "podcast-urls") != NULL)
1268             [podcastConf appendFormat:@"%s|", config_GetPsz(VLCIntf, "podcast-urls")];
1269
1270         [podcastConf appendString: [o_podcast_subscribe_url_fld stringValue]];
1271         config_PutPsz(VLCIntf, "podcast-urls", [podcastConf UTF8String]);
1272         var_SetString(pl_Get(VLCIntf), "podcast-urls", [podcastConf UTF8String]);
1273         [podcastConf release];
1274     }
1275 }
1276
1277 - (IBAction)removePodcast:(id)sender
1278 {
1279     if (config_GetPsz(VLCIntf, "podcast-urls") != NULL) {
1280         [o_podcast_unsubscribe_pop removeAllItems];
1281         [o_podcast_unsubscribe_pop addItemsWithTitles:[[NSString stringWithUTF8String:config_GetPsz(VLCIntf, "podcast-urls")] componentsSeparatedByString:@"|"]];
1282         [NSApp beginSheet:o_podcast_unsubscribe_window modalForWindow:self modalDelegate:self didEndSelector:NULL contextInfo:nil];
1283     }
1284 }
1285
1286 - (IBAction)removePodcastWindowAction:(id)sender
1287 {
1288     [o_podcast_unsubscribe_window orderOut:sender];
1289     [NSApp endSheet: o_podcast_unsubscribe_window];
1290
1291     if (sender == o_podcast_unsubscribe_ok_btn) {
1292         NSMutableArray * urls = [[NSMutableArray alloc] initWithArray:[[NSString stringWithUTF8String:config_GetPsz(VLCIntf, "podcast-urls")] componentsSeparatedByString:@"|"]];
1293         [urls removeObjectAtIndex: [o_podcast_unsubscribe_pop indexOfSelectedItem]];
1294         config_PutPsz(VLCIntf, "podcast-urls", [[urls componentsJoinedByString:@"|"] UTF8String]);
1295         var_SetString(pl_Get(VLCIntf), "podcast-urls", config_GetPsz(VLCIntf, "podcast-urls"));
1296         [urls release];
1297
1298         /* reload the podcast module, since it won't update its list when removing podcasts */
1299         playlist_t * p_playlist = pl_Get(VLCIntf);
1300         if (playlist_IsServicesDiscoveryLoaded(p_playlist, "podcast{longname=\"Podcasts\"}")) {
1301             playlist_ServicesDiscoveryRemove(p_playlist, "podcast{longname=\"Podcasts\"}");
1302             playlist_ServicesDiscoveryAdd(p_playlist, "podcast{longname=\"Podcasts\"}");
1303             [[[VLCMain sharedInstance] playlist] playlistUpdated];
1304         }
1305     }
1306 }
1307
1308 - (void)showPodcastControls
1309 {
1310     NSRect podcastViewDimensions = [o_podcast_view frame];
1311     NSRect rightSplitRect = [o_right_split_view frame];
1312     NSRect playlistTableRect = [o_playlist_table frame];
1313
1314     podcastViewDimensions.size.width = rightSplitRect.size.width;
1315     podcastViewDimensions.origin.x = podcastViewDimensions.origin.y = .0;
1316     [o_podcast_view setFrame:podcastViewDimensions];
1317
1318     playlistTableRect.origin.y = playlistTableRect.origin.y + podcastViewDimensions.size.height;
1319     playlistTableRect.size.height = playlistTableRect.size.height - podcastViewDimensions.size.height;
1320     [o_playlist_table setFrame:playlistTableRect];
1321     [o_playlist_table setNeedsDisplay:YES];
1322
1323     [o_right_split_view addSubview: o_podcast_view positioned: NSWindowAbove relativeTo: o_right_split_view];
1324     b_podcastView_displayed = YES;
1325 }
1326
1327 - (void)hidePodcastControls
1328 {
1329     if (b_podcastView_displayed) {
1330         NSRect podcastViewDimensions = [o_podcast_view frame];
1331         NSRect playlistTableRect = [o_playlist_table frame];
1332
1333         playlistTableRect.origin.y = playlistTableRect.origin.y - podcastViewDimensions.size.height;
1334         playlistTableRect.size.height = playlistTableRect.size.height + podcastViewDimensions.size.height;
1335
1336         [o_podcast_view removeFromSuperviewWithoutNeedingDisplay];
1337         [o_playlist_table setFrame: playlistTableRect];
1338         b_podcastView_displayed = NO;
1339     }
1340 }
1341
1342 @end
1343
1344 @implementation VLCDetachedVideoWindow
1345
1346 - (void)awakeFromNib
1347 {
1348     // sets lion fullscreen behaviour
1349     [super awakeFromNib];
1350     [self setAcceptsMouseMovedEvents: YES];
1351
1352     if (b_dark_interface) {
1353         [self setBackgroundColor: [NSColor clearColor]];
1354
1355         [self setOpaque: NO];
1356         [self display];
1357         [self setHasShadow:NO];
1358         [self setHasShadow:YES];
1359
1360         NSRect winrect = [self frame];
1361         CGFloat f_titleBarHeight = [o_titlebar_view frame].size.height;
1362
1363         [self setTitle: _NS("VLC media player")];
1364         [o_titlebar_view setFrame: NSMakeRect(0, winrect.size.height - f_titleBarHeight, winrect.size.width, f_titleBarHeight)];
1365         [[self contentView] addSubview: o_titlebar_view positioned: NSWindowAbove relativeTo: nil];
1366
1367     } else {
1368         [self setBackgroundColor: [NSColor blackColor]];
1369     }
1370
1371     NSRect videoViewRect = [[self contentView] bounds];
1372     if (b_dark_interface)
1373         videoViewRect.size.height -= [o_titlebar_view frame].size.height;
1374     CGFloat f_bottomBarHeight = [[self controlsBar] height];
1375     videoViewRect.size.height -= f_bottomBarHeight;
1376     videoViewRect.origin.y = f_bottomBarHeight;
1377     [o_video_view setFrame: videoViewRect];
1378
1379     if (b_dark_interface) {
1380         o_color_backdrop = [[VLCColorView alloc] initWithFrame: [o_video_view frame]];
1381         [[self contentView] addSubview: o_color_backdrop positioned: NSWindowBelow relativeTo: o_video_view];
1382         [o_color_backdrop setAutoresizingMask:NSViewHeightSizable | NSViewWidthSizable];
1383
1384         [self setContentMinSize: NSMakeSize(363., f_min_video_height + [[self controlsBar] height] + [o_titlebar_view frame].size.height)];
1385     } else {
1386         [self setContentMinSize: NSMakeSize(363., f_min_video_height + [[self controlsBar] height])];
1387     }
1388 }
1389
1390 - (void)dealloc
1391 {
1392     if (b_dark_interface)
1393         [o_color_backdrop release];
1394
1395     [super dealloc];
1396 }
1397
1398 @end