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