]> git.sesse.net Git - vlc/blob - modules/gui/macosx/MainWindow.m
6b932b9c6aea009ac99fbfd9d42ae89eb2edf7e4
[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     PL_LOCK;
1149     if ([[[VLCMain sharedInstance] playlist] currentPlaylistRoot] != p_playlist->p_local_category || p_playlist->p_local_category->i_children > 0)
1150         [self hideDropZone];
1151     else
1152         [self showDropZone];
1153     PL_UNLOCK;
1154
1155     if ([[item identifier] isEqualToString:@"podcast{longname=\"Podcasts\"}"])
1156         [self showPodcastControls];
1157     else
1158         [self hidePodcastControls];
1159
1160     [[NSNotificationCenter defaultCenter] postNotificationName: @"VLCMediaKeySupportSettingChanged"
1161                                                         object: nil
1162                                                       userInfo: nil];
1163 }
1164
1165 - (NSDragOperation)sourceList:(PXSourceList *)aSourceList validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
1166 {
1167     if ([[item identifier] isEqualToString:@"playlist"] || [[item identifier] isEqualToString:@"medialibrary"]) {
1168         NSPasteboard *o_pasteboard = [info draggingPasteboard];
1169         if ([[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"] || [[o_pasteboard types] containsObject: NSFilenamesPboardType])
1170             return NSDragOperationGeneric;
1171     }
1172     return NSDragOperationNone;
1173 }
1174
1175 - (BOOL)sourceList:(PXSourceList *)aSourceList acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index
1176 {
1177     NSPasteboard *o_pasteboard = [info draggingPasteboard];
1178
1179     playlist_t * p_playlist = pl_Get(VLCIntf);
1180     playlist_item_t *p_node;
1181
1182     if ([[item identifier] isEqualToString:@"playlist"])
1183         p_node = p_playlist->p_local_category;
1184     else
1185         p_node = p_playlist->p_ml_category;
1186
1187     if ([[o_pasteboard types] containsObject: NSFilenamesPboardType]) {
1188         NSArray *o_values = [[o_pasteboard propertyListForType: NSFilenamesPboardType] sortedArrayUsingSelector: @selector(caseInsensitiveCompare:)];
1189         NSUInteger count = [o_values count];
1190         NSMutableArray *o_array = [NSMutableArray arrayWithCapacity:count];
1191
1192         for(NSUInteger i = 0; i < count; i++) {
1193             NSDictionary *o_dic;
1194             char *psz_uri = vlc_path2uri([[o_values objectAtIndex:i] UTF8String], NULL);
1195             if (!psz_uri)
1196                 continue;
1197
1198             o_dic = [NSDictionary dictionaryWithObject:[NSString stringWithCString:psz_uri encoding:NSUTF8StringEncoding] forKey:@"ITEM_URL"];
1199
1200             free(psz_uri);
1201
1202             [o_array addObject: o_dic];
1203         }
1204
1205         [[[VLCMain sharedInstance] playlist] appendNodeArray:o_array inNode: p_node atPos:-1 enqueue:YES];
1206         return YES;
1207     }
1208     else if ([[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"]) {
1209         NSArray * array = [[[VLCMain sharedInstance] playlist] draggedItems];
1210
1211         NSUInteger count = [array count];
1212         playlist_item_t * p_item = NULL;
1213
1214         PL_LOCK;
1215         for(NSUInteger i = 0; i < count; i++) {
1216             p_item = [[array objectAtIndex:i] pointerValue];
1217             if (!p_item) continue;
1218             playlist_NodeAddCopy(p_playlist, p_item, p_node, PLAYLIST_END);
1219         }
1220         PL_UNLOCK;
1221
1222         return YES;
1223     }
1224     return NO;
1225 }
1226
1227 - (id)sourceList:(PXSourceList *)aSourceList persistentObjectForItem:(id)item
1228 {
1229     return [item identifier];
1230 }
1231
1232 - (id)sourceList:(PXSourceList *)aSourceList itemForPersistentObject:(id)object
1233 {
1234     /* the following code assumes for sakes of simplicity that only the top level
1235      * items are allowed to have children */
1236
1237     NSArray * array = [NSArray arrayWithArray: o_sidebaritems]; // read-only arrays are noticebly faster
1238     NSUInteger count = [array count];
1239     if (count < 1)
1240         return nil;
1241
1242     for (NSUInteger x = 0; x < count; x++) {
1243         id item = [array objectAtIndex:x]; // save one objc selector call
1244         if ([[item identifier] isEqualToString:object])
1245             return item;
1246     }
1247
1248     return nil;
1249 }
1250
1251 #pragma mark -
1252 #pragma mark Podcast
1253
1254 - (IBAction)addPodcast:(id)sender
1255 {
1256     [NSApp beginSheet:o_podcast_subscribe_window modalForWindow:self modalDelegate:self didEndSelector:NULL contextInfo:nil];
1257 }
1258
1259 - (IBAction)addPodcastWindowAction:(id)sender
1260 {
1261     [o_podcast_subscribe_window orderOut:sender];
1262     [NSApp endSheet: o_podcast_subscribe_window];
1263
1264     if (sender == o_podcast_subscribe_ok_btn && [[o_podcast_subscribe_url_fld stringValue] length] > 0) {
1265         NSMutableString * podcastConf = [[NSMutableString alloc] init];
1266         if (config_GetPsz(VLCIntf, "podcast-urls") != NULL)
1267             [podcastConf appendFormat:@"%s|", config_GetPsz(VLCIntf, "podcast-urls")];
1268
1269         [podcastConf appendString: [o_podcast_subscribe_url_fld stringValue]];
1270         config_PutPsz(VLCIntf, "podcast-urls", [podcastConf UTF8String]);
1271         var_SetString(pl_Get(VLCIntf), "podcast-urls", [podcastConf UTF8String]);
1272         [podcastConf release];
1273     }
1274 }
1275
1276 - (IBAction)removePodcast:(id)sender
1277 {
1278     if (config_GetPsz(VLCIntf, "podcast-urls") != NULL) {
1279         [o_podcast_unsubscribe_pop removeAllItems];
1280         [o_podcast_unsubscribe_pop addItemsWithTitles:[[NSString stringWithUTF8String:config_GetPsz(VLCIntf, "podcast-urls")] componentsSeparatedByString:@"|"]];
1281         [NSApp beginSheet:o_podcast_unsubscribe_window modalForWindow:self modalDelegate:self didEndSelector:NULL contextInfo:nil];
1282     }
1283 }
1284
1285 - (IBAction)removePodcastWindowAction:(id)sender
1286 {
1287     [o_podcast_unsubscribe_window orderOut:sender];
1288     [NSApp endSheet: o_podcast_unsubscribe_window];
1289
1290     if (sender == o_podcast_unsubscribe_ok_btn) {
1291         NSMutableArray * urls = [[NSMutableArray alloc] initWithArray:[[NSString stringWithUTF8String:config_GetPsz(VLCIntf, "podcast-urls")] componentsSeparatedByString:@"|"]];
1292         [urls removeObjectAtIndex: [o_podcast_unsubscribe_pop indexOfSelectedItem]];
1293         config_PutPsz(VLCIntf, "podcast-urls", [[urls componentsJoinedByString:@"|"] UTF8String]);
1294         var_SetString(pl_Get(VLCIntf), "podcast-urls", config_GetPsz(VLCIntf, "podcast-urls"));
1295         [urls release];
1296
1297         /* reload the podcast module, since it won't update its list when removing podcasts */
1298         playlist_t * p_playlist = pl_Get(VLCIntf);
1299         if (playlist_IsServicesDiscoveryLoaded(p_playlist, "podcast{longname=\"Podcasts\"}")) {
1300             playlist_ServicesDiscoveryRemove(p_playlist, "podcast{longname=\"Podcasts\"}");
1301             playlist_ServicesDiscoveryAdd(p_playlist, "podcast{longname=\"Podcasts\"}");
1302             [[[VLCMain sharedInstance] playlist] playlistUpdated];
1303         }
1304     }
1305 }
1306
1307 - (void)showPodcastControls
1308 {
1309     NSRect podcastViewDimensions = [o_podcast_view frame];
1310     NSRect rightSplitRect = [o_right_split_view frame];
1311     NSRect playlistTableRect = [o_playlist_table frame];
1312
1313     podcastViewDimensions.size.width = rightSplitRect.size.width;
1314     podcastViewDimensions.origin.x = podcastViewDimensions.origin.y = .0;
1315     [o_podcast_view setFrame:podcastViewDimensions];
1316
1317     playlistTableRect.origin.y = playlistTableRect.origin.y + podcastViewDimensions.size.height;
1318     playlistTableRect.size.height = playlistTableRect.size.height - podcastViewDimensions.size.height;
1319     [o_playlist_table setFrame:playlistTableRect];
1320     [o_playlist_table setNeedsDisplay:YES];
1321
1322     [o_right_split_view addSubview: o_podcast_view positioned: NSWindowAbove relativeTo: o_right_split_view];
1323     b_podcastView_displayed = YES;
1324 }
1325
1326 - (void)hidePodcastControls
1327 {
1328     if (b_podcastView_displayed) {
1329         NSRect podcastViewDimensions = [o_podcast_view frame];
1330         NSRect playlistTableRect = [o_playlist_table frame];
1331
1332         playlistTableRect.origin.y = playlistTableRect.origin.y - podcastViewDimensions.size.height;
1333         playlistTableRect.size.height = playlistTableRect.size.height + podcastViewDimensions.size.height;
1334
1335         [o_podcast_view removeFromSuperviewWithoutNeedingDisplay];
1336         [o_playlist_table setFrame: playlistTableRect];
1337         b_podcastView_displayed = NO;
1338     }
1339 }
1340
1341 @end
1342
1343 @implementation VLCDetachedVideoWindow
1344
1345 - (void)awakeFromNib
1346 {
1347     // sets lion fullscreen behaviour
1348     [super awakeFromNib];
1349     [self setAcceptsMouseMovedEvents: YES];
1350
1351     if (b_dark_interface) {
1352         [self setBackgroundColor: [NSColor clearColor]];
1353
1354         [self setOpaque: NO];
1355         [self display];
1356         [self setHasShadow:NO];
1357         [self setHasShadow:YES];
1358
1359         NSRect winrect = [self frame];
1360         CGFloat f_titleBarHeight = [o_titlebar_view frame].size.height;
1361
1362         [self setTitle: _NS("VLC media player")];
1363         [o_titlebar_view setFrame: NSMakeRect(0, winrect.size.height - f_titleBarHeight, winrect.size.width, f_titleBarHeight)];
1364         [[self contentView] addSubview: o_titlebar_view positioned: NSWindowAbove relativeTo: nil];
1365
1366     } else {
1367         [self setBackgroundColor: [NSColor blackColor]];
1368     }
1369
1370     NSRect videoViewRect = [[self contentView] bounds];
1371     if (b_dark_interface)
1372         videoViewRect.size.height -= [o_titlebar_view frame].size.height;
1373     CGFloat f_bottomBarHeight = [[self controlsBar] height];
1374     videoViewRect.size.height -= f_bottomBarHeight;
1375     videoViewRect.origin.y = f_bottomBarHeight;
1376     [o_video_view setFrame: videoViewRect];
1377
1378     if (b_dark_interface) {
1379         o_color_backdrop = [[VLCColorView alloc] initWithFrame: [o_video_view frame]];
1380         [[self contentView] addSubview: o_color_backdrop positioned: NSWindowBelow relativeTo: o_video_view];
1381         [o_color_backdrop setAutoresizingMask:NSViewHeightSizable | NSViewWidthSizable];
1382
1383         [self setContentMinSize: NSMakeSize(363., f_min_video_height + [[self controlsBar] height] + [o_titlebar_view frame].size.height)];
1384     } else {
1385         [self setContentMinSize: NSMakeSize(363., f_min_video_height + [[self controlsBar] height])];
1386     }
1387 }
1388
1389 - (void)dealloc
1390 {
1391     if (b_dark_interface)
1392         [o_color_backdrop release];
1393
1394     [super dealloc];
1395 }
1396
1397 @end