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