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