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