]> git.sesse.net Git - vlc/blob - modules/gui/macosx/MainWindow.m
macosx: move detached window stuff in extra xib file and only load that when necessary
[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_aout_intf.h>
40 #import <vlc_url.h>
41 #import <vlc_strings.h>
42 #import <vlc_services_discovery.h>
43 #import <vlc_aout_intf.h>
44
45 #import "ControlsBar.h"
46
47
48 @interface VLCMainWindow ()
49 - (void)resizePlaylistAfterCollapse;
50 - (void)makeSplitViewVisible;
51 - (void)makeSplitViewHidden;
52
53 @end
54
55 @implementation VLCMainWindow
56 static const float f_min_video_height = 70.0;
57
58 static VLCMainWindow *_o_sharedInstance = nil;
59
60 + (VLCMainWindow *)sharedInstance
61 {
62     return _o_sharedInstance ? _o_sharedInstance : [[self alloc] init];
63 }
64
65 #pragma mark -
66 #pragma mark Initialization
67
68 - (id)init
69 {
70     if (_o_sharedInstance) {
71         [self dealloc];
72         return _o_sharedInstance;
73     } else
74         _o_sharedInstance = [super init];
75
76     return _o_sharedInstance;
77 }
78
79 - (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask
80                   backing:(NSBackingStoreType)backingType defer:(BOOL)flag
81 {
82     self = [super initWithContentRect:contentRect styleMask:styleMask
83                               backing:backingType defer:flag];
84     _o_sharedInstance = self;
85
86     [[VLCMain sharedInstance] updateTogglePlaylistState];
87
88     return self;
89 }
90
91 - (BOOL)isEvent:(NSEvent *)o_event forKey:(const char *)keyString
92 {
93     char *key;
94     NSString *o_key;
95
96     key = config_GetPsz(VLCIntf, keyString);
97     o_key = [NSString stringWithFormat:@"%s", key];
98     FREENULL(key);
99
100     unsigned int i_keyModifiers = [[VLCStringUtility sharedInstance] VLCModifiersToCocoa:o_key];
101
102     NSString * characters = [o_event charactersIgnoringModifiers];
103     if ([characters length] > 0) {
104         return [[characters lowercaseString] isEqualToString: [[VLCStringUtility sharedInstance] VLCKeyToString: o_key]] &&
105                 (i_keyModifiers & NSShiftKeyMask)     == ([o_event modifierFlags] & NSShiftKeyMask) &&
106                 (i_keyModifiers & NSControlKeyMask)   == ([o_event modifierFlags] & NSControlKeyMask) &&
107                 (i_keyModifiers & NSAlternateKeyMask) == ([o_event modifierFlags] & NSAlternateKeyMask) &&
108                 (i_keyModifiers & NSCommandKeyMask)   == ([o_event modifierFlags] & NSCommandKeyMask);
109     }
110     return NO;
111 }
112
113 - (BOOL)performKeyEquivalent:(NSEvent *)o_event
114 {
115     BOOL b_force = NO;
116     // these are key events which should be handled by vlc core, but are attached to a main menu item
117     if (![self isEvent: o_event forKey: "key-vol-up"] &&
118         ![self isEvent: o_event forKey: "key-vol-down"] &&
119         ![self isEvent: o_event forKey: "key-vol-mute"]) {
120         /* We indeed want to prioritize some Cocoa key equivalent against libvlc,
121          so we perform the menu equivalent now. */
122         if ([[NSApp mainMenu] performKeyEquivalent:o_event])
123             return TRUE;
124     }
125     else
126         b_force = YES;
127
128     return [[VLCMain sharedInstance] hasDefinedShortcutKey:o_event force:b_force] ||
129            [(VLCControls *)[[VLCMain sharedInstance] controls] keyEvent:o_event];
130 }
131
132 - (void)dealloc
133 {
134     if (b_dark_interface)
135         [o_color_backdrop release];
136
137     [[NSNotificationCenter defaultCenter] removeObserver: self];
138     [o_sidebaritems release];
139
140     if (o_extra_video_window) {
141         [o_extra_video_window release];
142         o_extra_video_window = nil;
143     }
144
145     [super dealloc];
146 }
147
148 - (void)awakeFromNib
149 {
150     BOOL b_splitviewShouldBeHidden = NO;
151
152     /* setup the styled interface */
153     b_nativeFullscreenMode = NO;
154 #ifdef MAC_OS_X_VERSION_10_7
155     if (!OSX_SNOW_LEOPARD)
156         b_nativeFullscreenMode = var_InheritBool(VLCIntf, "macosx-nativefullscreenmode");
157 #endif
158     t_hide_mouse_timer = nil;
159     [self useOptimizedDrawing: YES];
160     
161     [[o_search_fld cell] setPlaceholderString: _NS("Search")];
162     [[o_search_fld cell] accessibilitySetOverrideValue:_NS("Enter a term to search the playlist. Results will be selected in the table.") forAttribute:NSAccessibilityDescriptionAttribute];
163
164     [o_dropzone_btn setTitle: _NS("Open media...")];
165     [[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];
166     [o_dropzone_lbl setStringValue: _NS("Drop media here")];
167
168     [o_podcast_add_btn setTitle: _NS("Subscribe")];
169     [o_podcast_remove_btn setTitle: _NS("Unsubscribe")];
170     [o_podcast_subscribe_title_lbl setStringValue: _NS("Subscribe to a podcast")];
171     [o_podcast_subscribe_subtitle_lbl setStringValue: _NS("Enter URL of the podcast to subscribe to:")];
172     [o_podcast_subscribe_cancel_btn setTitle: _NS("Cancel")];
173     [o_podcast_subscribe_ok_btn setTitle: _NS("Subscribe")];
174     [o_podcast_unsubscribe_title_lbl setStringValue: _NS("Unsubscribe from a podcast")];
175     [o_podcast_unsubscribe_subtitle_lbl setStringValue: _NS("Select the podcast you would like to unsubscribe from:")];
176     [o_podcast_unsubscribe_ok_btn setTitle: _NS("Unsubscribe")];
177     [o_podcast_unsubscribe_cancel_btn setTitle: _NS("Cancel")];
178
179     /* interface builder action */
180     float f_threshold_height = f_min_video_height + [[o_controls_bar bottomBarView] frame].size.height;
181     if (b_dark_interface)
182         f_threshold_height += [o_titlebar_view frame].size.height;
183     if ([[self contentView] frame].size.height < f_threshold_height)
184         b_splitviewShouldBeHidden = YES;
185
186     [self setDelegate: self];
187     [self setExcludedFromWindowsMenu: YES];
188     [self setAcceptsMouseMovedEvents: YES];
189     // Set that here as IB seems to be buggy
190     if (b_dark_interface) {
191         [self setContentMinSize:NSMakeSize(604., 288. + [o_titlebar_view frame].size.height)];
192     } else {
193         [self setContentMinSize:NSMakeSize(604., 288.)];
194     }
195
196     [self setTitle: _NS("VLC media player")];
197
198     b_dropzone_active = YES;
199     o_temp_view = [[NSView alloc] init];
200     [o_temp_view setAutoresizingMask:NSViewHeightSizable | NSViewWidthSizable];
201     [o_dropzone_view setFrame: [o_playlist_table frame]];
202     [o_left_split_view setFrame: [o_sidebar_view frame]];
203     
204     if (b_nativeFullscreenMode) {
205         [self setCollectionBehavior: NSWindowCollectionBehaviorFullScreenPrimary];
206     } else {
207         [o_titlebar_view setFullscreenButtonHidden: YES];
208     }
209
210     if (!OSX_SNOW_LEOPARD) {
211         /* the default small size of the search field is slightly different on Lion, let's work-around that */
212         NSRect frame;
213         frame = [o_search_fld frame];
214         frame.origin.y = frame.origin.y + 2.0;
215         frame.size.height = frame.size.height - 1.0;
216         [o_search_fld setFrame: frame];
217     }
218
219     /* create the sidebar */
220     o_sidebaritems = [[NSMutableArray alloc] init];
221     SideBarItem *libraryItem = [SideBarItem itemWithTitle:_NS("LIBRARY") identifier:@"library"];
222     SideBarItem *playlistItem = [SideBarItem itemWithTitle:_NS("Playlist") identifier:@"playlist"];
223     [playlistItem setIcon: [NSImage imageNamed:@"sidebar-playlist"]];
224     SideBarItem *medialibraryItem = [SideBarItem itemWithTitle:_NS("Media Library") identifier:@"medialibrary"];
225     [medialibraryItem setIcon: [NSImage imageNamed:@"sidebar-playlist"]];
226     SideBarItem *mycompItem = [SideBarItem itemWithTitle:_NS("MY COMPUTER") identifier:@"mycomputer"];
227     SideBarItem *devicesItem = [SideBarItem itemWithTitle:_NS("DEVICES") identifier:@"devices"];
228     SideBarItem *lanItem = [SideBarItem itemWithTitle:_NS("LOCAL NETWORK") identifier:@"localnetwork"];
229     SideBarItem *internetItem = [SideBarItem itemWithTitle:_NS("INTERNET") identifier:@"internet"];
230
231     /* SD subnodes, inspired by the Qt4 intf */
232     char **ppsz_longnames;
233     int *p_categories;
234     char **ppsz_names = vlc_sd_GetNames(pl_Get(VLCIntf), &ppsz_longnames, &p_categories);
235     if (!ppsz_names)
236         msg_Err(VLCIntf, "no sd item found"); //TODO
237     char **ppsz_name = ppsz_names, **ppsz_longname = ppsz_longnames;
238     int *p_category = p_categories;
239     NSMutableArray *internetItems = [[NSMutableArray alloc] init];
240     NSMutableArray *devicesItems = [[NSMutableArray alloc] init];
241     NSMutableArray *lanItems = [[NSMutableArray alloc] init];
242     NSMutableArray *mycompItems = [[NSMutableArray alloc] init];
243     NSString *o_identifier;
244     for (; *ppsz_name; ppsz_name++, ppsz_longname++, p_category++) {
245         o_identifier = [NSString stringWithCString: *ppsz_name encoding: NSUTF8StringEncoding];
246         switch (*p_category) {
247             case SD_CAT_INTERNET:
248                     [internetItems addObject: [SideBarItem itemWithTitle: _NS(*ppsz_longname) identifier: o_identifier]];
249                     if (!strncmp(*ppsz_name, "podcast", 7))
250                         [[internetItems lastObject] setIcon: [NSImage imageNamed:@"sidebar-podcast"]];
251                     else
252                         [[internetItems lastObject] setIcon: [NSImage imageNamed:@"NSApplicationIcon"]];
253                     [[internetItems lastObject] setSdtype: SD_CAT_INTERNET];
254                     [[internetItems lastObject] setUntranslatedTitle: [NSString stringWithUTF8String: *ppsz_longname]];
255                 break;
256             case SD_CAT_DEVICES:
257                     [devicesItems addObject: [SideBarItem itemWithTitle: _NS(*ppsz_longname) identifier: o_identifier]];
258                     [[devicesItems lastObject] setIcon: [NSImage imageNamed:@"NSApplicationIcon"]];
259                     [[devicesItems lastObject] setSdtype: SD_CAT_DEVICES];
260                     [[devicesItems lastObject] setUntranslatedTitle: [NSString stringWithUTF8String: *ppsz_longname]];
261                 break;
262             case SD_CAT_LAN:
263                     [lanItems addObject: [SideBarItem itemWithTitle: _NS(*ppsz_longname) identifier: o_identifier]];
264                     [[lanItems lastObject] setIcon: [NSImage imageNamed:@"sidebar-local"]];
265                     [[lanItems lastObject] setSdtype: SD_CAT_LAN];
266                     [[lanItems lastObject] setUntranslatedTitle: [NSString stringWithUTF8String: *ppsz_longname]];
267                 break;
268             case SD_CAT_MYCOMPUTER:
269                     [mycompItems addObject: [SideBarItem itemWithTitle: _NS(*ppsz_longname) identifier: o_identifier]];
270                     if (!strncmp(*ppsz_name, "video_dir", 9))
271                         [[mycompItems lastObject] setIcon: [NSImage imageNamed:@"sidebar-movie"]];
272                     else if (!strncmp(*ppsz_name, "audio_dir", 9))
273                         [[mycompItems lastObject] setIcon: [NSImage imageNamed:@"sidebar-music"]];
274                     else if (!strncmp(*ppsz_name, "picture_dir", 11))
275                         [[mycompItems lastObject] setIcon: [NSImage imageNamed:@"sidebar-pictures"]];
276                     else
277                         [[mycompItems lastObject] setIcon: [NSImage imageNamed:@"NSApplicationIcon"]];
278                     [[mycompItems lastObject] setUntranslatedTitle: [NSString stringWithUTF8String: *ppsz_longname]];
279                     [[mycompItems lastObject] setSdtype: SD_CAT_MYCOMPUTER];
280                 break;
281             default:
282                 msg_Warn(VLCIntf, "unknown SD type found, skipping (%s)", *ppsz_name);
283                 break;
284         }
285
286         free(*ppsz_name);
287         free(*ppsz_longname);
288     }
289     [mycompItem setChildren: [NSArray arrayWithArray: mycompItems]];
290     [devicesItem setChildren: [NSArray arrayWithArray: devicesItems]];
291     [lanItem setChildren: [NSArray arrayWithArray: lanItems]];
292     [internetItem setChildren: [NSArray arrayWithArray: internetItems]];
293     [mycompItems release];
294     [devicesItems release];
295     [lanItems release];
296     [internetItems release];
297     free(ppsz_names);
298     free(ppsz_longnames);
299     free(p_categories);
300
301     [libraryItem setChildren: [NSArray arrayWithObjects: playlistItem, medialibraryItem, nil]];
302     [o_sidebaritems addObject: libraryItem];
303     if ([mycompItem hasChildren])
304         [o_sidebaritems addObject: mycompItem];
305     if ([devicesItem hasChildren])
306         [o_sidebaritems addObject: devicesItem];
307     if ([lanItem hasChildren])
308         [o_sidebaritems addObject: lanItem];
309     if ([internetItem hasChildren])
310         [o_sidebaritems addObject: internetItem];
311
312     [o_sidebar_view reloadData];
313     [o_sidebar_view selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
314     [o_sidebar_view setDropItem:playlistItem dropChildIndex:NSOutlineViewDropOnItemIndex];
315     [o_sidebar_view registerForDraggedTypes:[NSArray arrayWithObjects: NSFilenamesPboardType, @"VLCPlaylistItemPboardType", nil]];
316
317     [o_sidebar_view setAutosaveName:@"mainwindow-sidebar"];
318     [(PXSourceList *)o_sidebar_view setDataSource:self];
319     [o_sidebar_view setDelegate:self];
320     [o_sidebar_view setAutosaveExpandedItems:YES];
321
322     [o_sidebar_view expandItem: libraryItem expandChildren: YES];
323
324     /* make sure we display the desired default appearance when VLC launches for the first time */
325     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
326     if (![defaults objectForKey:@"VLCFirstRun"]) {
327         [defaults setObject:[NSDate date] forKey:@"VLCFirstRun"];
328
329         NSUInteger i_sidebaritem_count = [o_sidebaritems count];
330         for (NSUInteger x = 0; x < i_sidebaritem_count; x++)
331             [o_sidebar_view expandItem: [o_sidebaritems objectAtIndex: x] expandChildren: YES];
332     }
333
334     if (b_dark_interface) {
335         [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(windowResizedOrMoved:) name: NSWindowDidResizeNotification object: nil];
336         [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(windowResizedOrMoved:) name: NSWindowDidMoveNotification object: nil];
337
338         [self setBackgroundColor: [NSColor clearColor]];
339         [self setOpaque: NO];
340         [self display];
341         [self setHasShadow:NO];
342         [self setHasShadow:YES];
343
344         NSRect winrect = [self frame];
345         CGFloat f_titleBarHeight = [o_titlebar_view frame].size.height;
346
347         [o_titlebar_view setFrame: NSMakeRect(0, winrect.size.height - f_titleBarHeight,
348                                               winrect.size.width, f_titleBarHeight)];
349         [[self contentView] addSubview: o_titlebar_view positioned: NSWindowAbove relativeTo: o_split_view];
350
351         if (winrect.size.height > 100) {
352             [self setFrame: winrect display:YES animate:YES];
353             previousSavedFrame = winrect;
354         }
355
356         winrect = [o_split_view frame];
357         winrect.size.height = winrect.size.height - f_titleBarHeight;
358         [o_split_view setFrame: winrect];
359         [o_video_view setFrame: winrect];
360
361         o_color_backdrop = [[VLCColorView alloc] initWithFrame: [o_split_view frame]];
362         [[self contentView] addSubview: o_color_backdrop positioned: NSWindowBelow relativeTo: o_split_view];
363         [o_color_backdrop setAutoresizingMask:NSViewHeightSizable | NSViewWidthSizable];
364         
365     } else {
366         [o_video_view setFrame: [o_split_view frame]];
367         [o_playlist_table setBorderType: NSNoBorder];
368         [o_sidebar_scrollview setBorderType: NSNoBorder];
369     }
370
371     [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(someWindowWillClose:) name: NSWindowWillCloseNotification object: nil];
372     [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(someWindowWillMiniaturize:) name: NSWindowWillMiniaturizeNotification object:nil];
373     [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(applicationWillTerminate:) name: NSApplicationWillTerminateNotification object: nil];
374
375     [o_split_view setAutosaveName:@"10thanniversary-splitview"];
376     if (b_splitviewShouldBeHidden) {
377         [self hideSplitView];
378         i_lastSplitViewHeight = 300;
379     }
380
381     /* sanity check for the window size */
382     NSRect frame = [self frame];
383     NSSize screenSize = [[self screen] frame].size;
384     if (screenSize.width <= frame.size.width || screenSize.height <= frame.size.height) {
385         nativeVideoSize = screenSize;
386         [self resizeWindow];
387     }
388 }
389
390 #pragma mark -
391
392 - (VLCMainWindowControlsBar *)controlsBar;
393 {
394     return (VLCMainWindowControlsBar *)o_controls_bar;
395 }
396
397 - (void)resizePlaylistAfterCollapse
398 {
399     NSRect plrect;
400     plrect = [o_playlist_table frame];
401     plrect.size.height = i_lastSplitViewHeight - 20.0; // actual pl top bar height, which differs from its frame
402     [[o_playlist_table animator] setFrame: plrect];
403
404     NSRect rightSplitRect;
405     rightSplitRect = [o_right_split_view frame];
406     plrect = [o_dropzone_box frame];
407     plrect.origin.x = (rightSplitRect.size.width - plrect.size.width) / 2;
408     plrect.origin.y = (rightSplitRect.size.height - plrect.size.height) / 2;
409     [[o_dropzone_box animator] setFrame: plrect];
410 }
411
412 - (void)makeSplitViewVisible
413 {
414     if (b_dark_interface)
415         [self setContentMinSize: NSMakeSize(604., 288. + [o_titlebar_view frame].size.height)];
416     else
417         [self setContentMinSize: NSMakeSize(604., 288.)];
418
419     NSRect old_frame = [self frame];
420     float newHeight = [self minSize].height;
421     if (old_frame.size.height < newHeight) {
422         NSRect new_frame = old_frame;
423         new_frame.origin.y = old_frame.origin.y + old_frame.size.height - newHeight;
424         new_frame.size.height = newHeight;
425
426         [[self animator] setFrame: new_frame display: YES animate: YES];
427     }
428
429     [o_video_view setHidden: YES];
430     [o_split_view setHidden: NO];
431     [self makeFirstResponder: nil];
432
433 }
434
435 - (void)makeSplitViewHidden
436 {
437     if (b_dark_interface)
438         [self setContentMinSize: NSMakeSize(604., f_min_video_height + [o_titlebar_view frame].size.height)];
439     else
440         [self setContentMinSize: NSMakeSize(604., f_min_video_height)];
441
442     [o_split_view setHidden: YES];
443     [o_video_view setHidden: NO];
444
445     if ([[o_video_view subviews] count] > 0)
446         [self makeFirstResponder: [[o_video_view subviews] objectAtIndex:0]];
447 }
448
449 // only exception for an controls bar button action
450 - (IBAction)togglePlaylist:(id)sender
451 {
452     if (![self isVisible] && sender != nil) {
453         [self makeKeyAndOrderFront: sender];
454         return;
455     }
456
457     BOOL b_activeVideo = [[VLCMain sharedInstance] activeVideoPlayback];
458     BOOL b_restored = NO;
459
460     // TODO: implement toggle playlist in this situation (triggerd via menu item).
461     // but for now we block this case, to avoid displaying only the half
462     if (b_nativeFullscreenMode && b_fullscreen && b_activeVideo && sender != nil)
463         return;
464
465     if (b_dropzone_active && ([[NSApp currentEvent] modifierFlags] & NSAlternateKeyMask) != 0) {
466         [self hideDropZone];
467         return;
468     }
469
470     if (!(b_nativeFullscreenMode && b_fullscreen) && !b_splitview_removed && ((([[NSApp currentEvent] modifierFlags] & NSAlternateKeyMask) != 0 && b_activeVideo)
471                                                                               || (b_nonembedded && sender != nil)
472                                                                               || (!b_activeVideo && sender != nil)
473                                                                               || b_minimized_view))
474         [self hideSplitView];
475     else {
476         if (b_splitview_removed) {
477             if (!b_nonembedded || (sender != nil && b_nonembedded))
478                 [self showSplitView];
479
480             if (sender == nil)
481                 b_minimized_view = YES;
482             else
483                 b_minimized_view = NO;
484
485             if (b_activeVideo)
486                 b_restored = YES;
487         }
488
489         if (!b_nonembedded) {
490             if (([o_video_view isHidden] && b_activeVideo) || b_restored || (b_activeVideo && sender == nil))
491                 [self makeSplitViewHidden];
492             else
493                 [self makeSplitViewVisible];
494         } else {
495             [o_split_view setHidden: NO];
496             [o_playlist_table setHidden: NO];
497             [o_video_view setHidden: !b_activeVideo];
498             if (b_activeVideo && [[o_video_view subviews] count] > 0)
499                 [[o_video_view window] makeFirstResponder: [[o_video_view subviews] objectAtIndex:0]];
500         }
501     }
502 }
503
504 - (IBAction)dropzoneButtonAction:(id)sender
505 {
506     [[[VLCMain sharedInstance] open] openFileGeneric];
507 }
508
509 #pragma mark -
510 #pragma mark overwritten default functionality
511
512 - (void)windowResizedOrMoved:(NSNotification *)notification
513 {
514     [self saveFrameUsingName: [self frameAutosaveName]];
515 }
516
517 - (void)applicationWillTerminate:(NSNotification *)notification
518 {
519     [self saveFrameUsingName: [self frameAutosaveName]];
520 }
521
522
523 - (void)someWindowWillClose:(NSNotification *)notification
524 {
525     id obj = [notification object];
526     if (obj == o_detached_video_window || obj == o_extra_video_window || (obj == self && !b_nonembedded)) {
527         if ([[VLCMain sharedInstance] activeVideoPlayback])
528             [[VLCCoreInteraction sharedInstance] stop];
529     }
530 }
531
532 - (void)someWindowWillMiniaturize:(NSNotification *)notification
533 {
534     if (config_GetInt(VLCIntf, "macosx-pause-minimized")) {
535         id obj = [notification object];
536         if (obj == o_detached_video_window || obj == o_extra_video_window || (obj == self && !b_nonembedded)) {
537             if ([[VLCMain sharedInstance] activeVideoPlayback])
538                 [[VLCCoreInteraction sharedInstance] pause];
539         }
540     }
541 }
542
543 - (NSSize)windowWillResize:(NSWindow *)window toSize:(NSSize)proposedFrameSize
544 {
545     id videoWindow = [o_video_view window];
546     if (![[VLCMain sharedInstance] activeVideoPlayback] || nativeVideoSize.width == 0. || nativeVideoSize.height == 0. || window != videoWindow)
547         return proposedFrameSize;
548
549     // needed when entering lion fullscreen mode
550     if (b_fullscreen)
551         return proposedFrameSize;
552
553     if ([[VLCCoreInteraction sharedInstance] aspectRatioIsLocked]) {
554         NSRect videoWindowFrame = [videoWindow frame];
555         NSRect viewRect = [o_video_view convertRect:[o_video_view bounds] toView: nil];
556         NSRect contentRect = [videoWindow contentRectForFrameRect:videoWindowFrame];
557         float marginy = viewRect.origin.y + videoWindowFrame.size.height - contentRect.size.height;
558         float marginx = contentRect.size.width - viewRect.size.width;
559         if (b_dark_interface)// && b_video_deco)
560             marginy += [o_titlebar_view frame].size.height;
561
562         proposedFrameSize.height = (proposedFrameSize.width - marginx) * nativeVideoSize.height / nativeVideoSize.width + marginy;
563     }
564
565     return proposedFrameSize;
566 }
567
568 #pragma mark -
569 #pragma mark Update interface and respond to foreign events
570 - (void)showDropZone
571 {
572     b_dropzone_active = YES;
573     [o_right_split_view addSubview: o_dropzone_view positioned:NSWindowAbove relativeTo:o_playlist_table];
574     [o_dropzone_view setFrame: [o_playlist_table frame]];
575     [[o_playlist_table animator] setHidden:YES];
576 }
577
578 - (void)hideDropZone
579 {
580     b_dropzone_active = NO;
581     [o_dropzone_view removeFromSuperview];
582     [[o_playlist_table animator] setHidden: NO];
583 }
584
585 - (void)hideSplitView
586 {
587     NSRect winrect = [self frame];
588     i_lastSplitViewHeight = [o_split_view frame].size.height;
589     winrect.size.height = winrect.size.height - i_lastSplitViewHeight;
590     winrect.origin.y = winrect.origin.y + i_lastSplitViewHeight;
591     [self setFrame: winrect display: YES animate: YES];
592     [self performSelector:@selector(hideDropZone) withObject:nil afterDelay:0.1];
593     if (b_dark_interface) {
594         [self setContentMinSize: NSMakeSize(604., [[o_controls_bar bottomBarView] frame].size.height + [o_titlebar_view frame].size.height)];
595         [self setContentMaxSize: NSMakeSize(FLT_MAX, [[o_controls_bar bottomBarView] frame].size.height + [o_titlebar_view frame].size.height)];
596     } else {
597         [self setContentMinSize: NSMakeSize(604., [[o_controls_bar bottomBarView] frame].size.height)];
598         [self setContentMaxSize: NSMakeSize(FLT_MAX, [[o_controls_bar bottomBarView] frame].size.height)];
599     }
600
601     b_splitview_removed = YES;
602 }
603
604 - (void)showSplitView
605 {
606     [self updateWindow];
607     if (b_dark_interface)
608         [self setContentMinSize:NSMakeSize(604., 288. + [o_titlebar_view frame].size.height)];
609     else
610         [self setContentMinSize:NSMakeSize(604., 288.)];
611     [self setContentMaxSize: NSMakeSize(FLT_MAX, FLT_MAX)];
612
613     NSRect winrect;
614     winrect = [self frame];
615     winrect.size.height = winrect.size.height + i_lastSplitViewHeight;
616     winrect.origin.y = winrect.origin.y - i_lastSplitViewHeight;
617     [self setFrame: winrect display: YES animate: YES];
618
619     [self performSelector:@selector(resizePlaylistAfterCollapse) withObject: nil afterDelay:0.75];
620
621     b_splitview_removed = NO;
622 }
623
624 - (void)updateTimeSlider
625 {
626     [o_controls_bar updateTimeSlider];
627     [[self controlsBar] updatePosAndTimeInFSPanel:o_fspanel];
628     if (o_detached_video_window)
629         [[o_detached_video_window controlsBar] updateTimeSlider];
630 }
631
632 - (void)updateName
633 {
634     input_thread_t * p_input;
635     p_input = pl_CurrentInput(VLCIntf);
636     if (p_input) {
637         NSString *aString;
638         char *format = var_InheritString(VLCIntf, "input-title-format");
639         char *formated = str_format_meta(pl_Get(VLCIntf), format);
640         free(format);
641         aString = [NSString stringWithUTF8String:formated];
642         free(formated);
643
644         char *uri = input_item_GetURI(input_GetItem(p_input));
645
646         NSURL * o_url = [NSURL URLWithString: [NSString stringWithUTF8String: uri]];
647         if ([o_url isFileURL]) {
648             [self setRepresentedURL: o_url];
649             if (o_detached_video_window)
650                 [o_detached_video_window setRepresentedURL: o_url];
651         } else {
652             [self setRepresentedURL: nil];
653             if (o_detached_video_window)
654                 [o_detached_video_window setRepresentedURL: nil];
655         }
656         free(uri);
657
658         if ([aString isEqualToString:@""]) {
659             if ([o_url isFileURL])
660                 aString = [[NSFileManager defaultManager] displayNameAtPath: [o_url path]];
661             else
662                 aString = [o_url absoluteString];
663         }
664
665         [self setTitle: aString];
666         if (b_nonembedded && o_detached_video_window && [[VLCMain sharedInstance] activeVideoPlayback])
667             [o_detached_video_window setTitle: aString];
668
669         [o_fspanel setStreamTitle: aString];
670         vlc_object_release(p_input);
671     } else {
672         [self setTitle: _NS("VLC media player")];
673         [self setRepresentedURL: nil];
674     }
675 }
676
677 - (void)updateWindow
678 {
679     [o_controls_bar updateControls];
680     if (o_detached_video_window)
681         [[o_detached_video_window controlsBar] updateControls];
682
683     bool b_seekable = false;
684
685     playlist_t * p_playlist = pl_Get(VLCIntf);
686     input_thread_t * p_input = playlist_CurrentInput(p_playlist);
687     if (p_input) {
688         /* seekable streams */
689         b_seekable = var_GetBool(p_input, "can-seek");
690
691         vlc_object_release(p_input);
692     }
693
694     [self updateTimeSlider];
695     [o_fspanel setSeekable: b_seekable];
696
697     PL_LOCK;
698     if ([[[VLCMain sharedInstance] playlist] currentPlaylistRoot] != p_playlist->p_local_category || p_playlist->p_local_category->i_children > 0)
699         [self hideDropZone];
700     else
701         [self showDropZone];
702     PL_UNLOCK;
703     [o_sidebar_view setNeedsDisplay:YES];
704 }
705
706 - (void)setPause
707 {
708     [o_controls_bar setPause];
709     if (o_detached_video_window)
710         [[o_detached_video_window controlsBar] setPause];
711     [o_fspanel setPause];
712 }
713
714 - (void)setPlay
715 {
716     [o_controls_bar setPlay];
717     if (o_detached_video_window)
718         [[o_detached_video_window controlsBar] setPlay];
719     [o_fspanel setPlay];
720 }
721
722 - (void)updateVolumeSlider
723 {
724     [[self controlsBar] updateVolumeSlider];
725     [o_fspanel setVolumeLevel: [[VLCCoreInteraction sharedInstance] volume]];
726 }
727
728 #pragma mark -
729 #pragma mark Video Output handling
730 - (id)videoView
731 {
732     return o_video_view;
733 }
734
735 - (void)setupVideoView
736 {
737     BOOL b_video_deco = var_InheritBool(VLCIntf, "video-deco");
738     BOOL b_video_wallpaper = var_InheritBool(VLCIntf, "video-wallpaper");
739
740     // TODO: make lion fullscreen compatible with video-wallpaper and !embedded-video
741     if ((b_video_wallpaper || !b_video_deco) && !b_nativeFullscreenMode) {
742         // b_video_wallpaper is priorized over !b_video_deco
743
744         msg_Dbg(VLCIntf, "Creating background / blank window");
745         NSScreen *screen = [NSScreen screenWithDisplayID:(CGDirectDisplayID)var_InheritInteger(VLCIntf, "macosx-vdev")];
746         if (!screen)
747             screen = [self screen];
748
749         NSRect window_rect;
750         if (b_video_wallpaper)
751             window_rect = [screen frame];
752         else
753             window_rect = [self frame];
754
755         if (o_extra_video_window)
756             [o_extra_video_window release];
757
758         NSUInteger mask = NSBorderlessWindowMask;
759         if (!OSX_SNOW_LEOPARD && !b_video_deco)
760             mask |= NSResizableWindowMask;
761
762         BOOL b_no_video_deco_only = !b_video_wallpaper;
763         o_extra_video_window = [[VLCVideoWindowCommon alloc] initWithContentRect:window_rect styleMask:mask backing:NSBackingStoreBuffered defer:YES];
764         [o_extra_video_window setDelegate:self];
765
766         if (b_video_wallpaper)
767             [o_extra_video_window setLevel:CGWindowLevelForKey(kCGDesktopWindowLevelKey) + 1];
768
769         [o_extra_video_window setBackgroundColor: [NSColor blackColor]];
770         [o_extra_video_window setCanBecomeKeyWindow: !b_video_wallpaper];
771         [o_extra_video_window setCanBecomeMainWindow: !b_video_wallpaper];
772         [o_extra_video_window setAcceptsMouseMovedEvents:!b_video_wallpaper];
773         [o_extra_video_window setMovableByWindowBackground: !b_video_wallpaper];
774         [o_extra_video_window useOptimizedDrawing: YES];
775
776         [o_video_view retain];
777         if ([o_video_view superview] != NULL)
778             [o_video_view removeFromSuperviewWithoutNeedingDisplay];
779         window_rect.origin.x = window_rect.origin.y = 0;
780         [o_video_view setFrame: window_rect];
781         [[o_extra_video_window contentView] addSubview: o_video_view positioned:NSWindowAbove relativeTo:nil];
782         [o_video_view release];
783
784         if (b_video_wallpaper)
785             [o_extra_video_window orderBack:nil];
786         else {
787             [o_extra_video_window center];
788             [o_extra_video_window setFrameAutosaveName:@"extra-videowindow"];
789             [o_extra_video_window setContentMinSize: NSMakeSize(f_min_video_height, f_min_video_height)];
790         }
791
792         b_nonembedded = YES;
793     } else {
794         if (var_InheritBool(VLCIntf, "embedded-video") || b_nativeFullscreenMode) {
795             if ([o_video_view window] != self) {
796                 [o_video_view removeFromSuperviewWithoutNeedingDisplay];
797                 [o_video_view setFrame: [o_split_view frame]];
798                 [[self contentView] addSubview:o_video_view positioned:NSWindowAbove relativeTo:nil];
799             }
800             b_nonembedded = NO;
801         } else {
802             if ([o_video_view superview] != NULL)
803                 [o_video_view removeFromSuperviewWithoutNeedingDisplay];
804
805             if (!o_detached_video_window) {
806                 NSWindowController *o_controller = [[NSWindowController alloc] initWithWindowNibName:@"DetachedVideoWindow"];
807                 [o_controller loadWindow];
808                 o_detached_video_window = (VLCDetachedVideoWindow *)[o_controller window];
809                 [o_controller release];
810
811                 // event occurs before window is created, so call again
812                 [[VLCMain sharedInstance] playbackStatusUpdated];
813             }
814
815             [o_detached_video_window setDelegate: self];
816             if (b_dark_interface) {
817                 [o_detached_video_window setContentMinSize: NSMakeSize(363., f_min_video_height + [[[o_detached_video_window controlsBar] bottomBarView] frame].size.height + [o_titlebar_view frame].size.height)];
818             } else {
819                 [o_detached_video_window setContentMinSize: NSMakeSize(363., f_min_video_height + [[[o_detached_video_window controlsBar] bottomBarView] frame].size.height)];
820             }
821
822             NSRect videoFrame;
823             videoFrame.size = [[o_detached_video_window contentView] frame].size;
824             videoFrame.size.height -= [[[o_detached_video_window controlsBar] bottomBarView] frame].size.height;
825             if (b_dark_interface)
826                 videoFrame.size.height -= [o_titlebar_view frame].size.height;
827
828             videoFrame.origin.x = .0;
829             videoFrame.origin.y = [[[o_detached_video_window controlsBar] bottomBarView] frame].size.height;
830
831             [o_video_view setFrame: videoFrame];
832             [[o_detached_video_window contentView] addSubview: o_video_view positioned:NSWindowAbove relativeTo:nil];
833             [o_detached_video_window setLevel:NSNormalWindowLevel];
834             [o_detached_video_window useOptimizedDrawing: YES];
835
836             b_nonembedded = YES;
837         }
838     }
839
840     if (!b_video_wallpaper) {
841         [[o_video_view window] makeKeyAndOrderFront: self];
842
843         vout_thread_t *p_vout = getVout();
844         if (p_vout) {
845             if (var_GetBool(p_vout, "video-on-top"))
846                 [[o_video_view window] setLevel: NSStatusWindowLevel];
847             else
848                 [[o_video_view window] setLevel: NSNormalWindowLevel];
849             vlc_object_release(p_vout);
850         }
851     }
852
853     [[o_video_view window] setAlphaValue: config_GetFloat(VLCIntf, "macosx-opaqueness")];
854 }
855
856 - (void)setVideoplayEnabled
857 {
858     BOOL b_videoPlayback = [[VLCMain sharedInstance] activeVideoPlayback];
859
860     if (b_videoPlayback) {
861         frameBeforePlayback = [self frame];
862
863         // look for 'start at fullscreen'
864         [[VLCMain sharedInstance] fullscreenChanged];
865     } else {
866         if (!b_nonembedded)
867             [[self animator] setFrame:frameBeforePlayback display:YES];
868
869         [self makeFirstResponder: nil];
870         if (o_detached_video_window)
871             [o_detached_video_window orderOut: nil];
872         if (o_extra_video_window)
873             [o_extra_video_window orderOut: nil];
874
875         if ([self level] != NSNormalWindowLevel)
876             [self setLevel: NSNormalWindowLevel];
877         if (o_detached_video_window && [o_detached_video_window level] != NSNormalWindowLevel)
878             [o_detached_video_window setLevel: NSNormalWindowLevel];
879
880         // restore alpha value to 1 for the case that macosx-opaqueness is set to < 1
881         [self setAlphaValue:1.0];
882     }
883
884     if (b_nativeFullscreenMode) {
885         if ([NSApp presentationOptions] & NSApplicationPresentationFullScreen)
886             [[o_controls_bar bottomBarView] setHidden: b_videoPlayback];
887         else
888             [[o_controls_bar bottomBarView] setHidden: NO];
889         if (b_videoPlayback && b_fullscreen)
890             [o_fspanel setActive: nil];
891         if (!b_videoPlayback)
892             [o_fspanel setNonActive: nil];
893     }
894
895     if (!b_videoPlayback && b_fullscreen) {
896         if (!b_nativeFullscreenMode)
897             [[VLCCoreInteraction sharedInstance] toggleFullscreen];
898     }
899 }
900
901 - (void)resizeWindow
902 {
903     if (b_fullscreen || (b_nativeFullscreenMode && [NSApp presentationOptions] & NSApplicationPresentationFullScreen))
904         return;
905
906     id o_videoWindow = [o_video_view window];
907     NSSize windowMinSize = [o_videoWindow minSize];
908     NSRect screenFrame = [[o_videoWindow screen] visibleFrame];
909
910     NSPoint topleftbase = NSMakePoint(0, [o_videoWindow frame].size.height);
911     NSPoint topleftscreen = [o_videoWindow convertBaseToScreen: topleftbase];
912
913     unsigned int i_width = nativeVideoSize.width;
914     unsigned int i_height = nativeVideoSize.height;
915     if (i_width < windowMinSize.width)
916         i_width = windowMinSize.width;
917     if (i_height < f_min_video_height)
918         i_height = f_min_video_height;
919
920     /* Calculate the window's new size */
921     NSRect new_frame;
922     new_frame.size.width = [o_videoWindow frame].size.width - [o_video_view frame].size.width + i_width;
923     new_frame.size.height = [o_videoWindow frame].size.height - [o_video_view frame].size.height + i_height;
924     new_frame.origin.x = topleftscreen.x;
925     new_frame.origin.y = topleftscreen.y - new_frame.size.height;
926
927     /* make sure the window doesn't exceed the screen size the window is on */
928     if (new_frame.size.width > screenFrame.size.width) {
929         new_frame.size.width = screenFrame.size.width;
930         new_frame.origin.x = screenFrame.origin.x;
931     }
932     if (new_frame.size.height > screenFrame.size.height) {
933         new_frame.size.height = screenFrame.size.height;
934         new_frame.origin.y = screenFrame.origin.y;
935     }
936     if (new_frame.origin.y < screenFrame.origin.y)
937         new_frame.origin.y = screenFrame.origin.y;
938
939     CGFloat right_screen_point = screenFrame.origin.x + screenFrame.size.width;
940     CGFloat right_window_point = new_frame.origin.x + new_frame.size.width;
941     if (right_window_point > right_screen_point)
942         new_frame.origin.x -= (right_window_point - right_screen_point);
943
944     [[o_videoWindow animator] setFrame:new_frame display:YES];
945 }
946
947 - (void)setNativeVideoSize:(NSSize)size
948 {
949     nativeVideoSize = size;
950
951     if (var_InheritBool(VLCIntf, "macosx-video-autoresize") && !b_fullscreen && !var_InheritBool(VLCIntf, "video-wallpaper"))
952         [self performSelectorOnMainThread:@selector(resizeWindow) withObject:nil waitUntilDone:NO];
953 }
954
955 //  Called automatically if window's acceptsMouseMovedEvents property is true
956 - (void)mouseMoved:(NSEvent *)theEvent
957 {
958     if (b_fullscreen)
959         [self recreateHideMouseTimer];
960
961     [super mouseMoved: theEvent];
962 }
963
964 - (void)recreateHideMouseTimer
965 {
966     if (t_hide_mouse_timer != nil) {
967         [t_hide_mouse_timer invalidate];
968         [t_hide_mouse_timer release];
969     }
970
971     t_hide_mouse_timer = [NSTimer scheduledTimerWithTimeInterval:2
972                                                           target:self
973                                                         selector:@selector(hideMouseCursor:)
974                                                         userInfo:nil
975                                                          repeats:NO];
976     [t_hide_mouse_timer retain];
977 }
978
979 //  NSTimer selectors require this function signature as per Apple's docs
980 - (void)hideMouseCursor:(NSTimer *)timer
981 {
982     [NSCursor setHiddenUntilMouseMoves: YES];
983 }
984
985 #pragma mark -
986 #pragma mark Fullscreen support
987 - (void)showFullscreenController
988 {
989      if (b_fullscreen && [[VLCMain sharedInstance] activeVideoPlayback])
990         [o_fspanel fadeIn];
991 }
992
993 - (BOOL)fullscreen
994 {
995     return b_fullscreen;
996 }
997
998 - (void)lockFullscreenAnimation
999 {
1000     [o_animation_lock lock];
1001 }
1002
1003 - (void)unlockFullscreenAnimation
1004 {
1005     [o_animation_lock unlock];
1006 }
1007
1008 - (void)enterFullscreen
1009 {
1010     NSMutableDictionary *dict1, *dict2;
1011     NSScreen *screen;
1012     NSRect screen_rect;
1013     NSRect rect;
1014     BOOL blackout_other_displays = var_InheritBool(VLCIntf, "macosx-black");
1015     o_current_video_window = [o_video_view window];
1016
1017     screen = [NSScreen screenWithDisplayID:(CGDirectDisplayID)var_InheritInteger(VLCIntf, "macosx-vdev")];
1018     [self lockFullscreenAnimation];
1019
1020     if (!screen) {
1021         msg_Dbg(VLCIntf, "chosen screen isn't present, using current screen for fullscreen mode");
1022         screen = [o_current_video_window screen];
1023     }
1024     if (!screen) {
1025         msg_Dbg(VLCIntf, "Using deepest screen");
1026         screen = [NSScreen deepestScreen];
1027     }
1028
1029     screen_rect = [screen frame];
1030
1031     [o_controls_bar setFullscreenState:YES];
1032     if (o_detached_video_window)
1033         [[o_detached_video_window controlsBar] setFullscreenState:YES];
1034
1035     [self recreateHideMouseTimer];
1036
1037     if (blackout_other_displays)
1038         [screen blackoutOtherScreens];
1039
1040     /* Make sure we don't see the window flashes in float-on-top mode */
1041     i_originalLevel = [o_current_video_window level];
1042     [o_current_video_window setLevel:NSNormalWindowLevel];
1043
1044     /* Only create the o_fullscreen_window if we are not in the middle of the zooming animation */
1045     if (!o_fullscreen_window) {
1046         /* We can't change the styleMask of an already created NSWindow, so we create another window, and do eye catching stuff */
1047
1048         rect = [[o_video_view superview] convertRect: [o_video_view frame] toView: nil]; /* Convert to Window base coord */
1049         rect.origin.x += [o_current_video_window frame].origin.x;
1050         rect.origin.y += [o_current_video_window frame].origin.y;
1051         o_fullscreen_window = [[VLCWindow alloc] initWithContentRect:rect styleMask: NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES];
1052         [o_fullscreen_window setBackgroundColor: [NSColor blackColor]];
1053         [o_fullscreen_window setCanBecomeKeyWindow: YES];
1054         [o_fullscreen_window setCanBecomeMainWindow: YES];
1055
1056         if (![o_current_video_window isVisible] || [o_current_video_window alphaValue] == 0.0) {
1057             /* We don't animate if we are not visible, instead we
1058              * simply fade the display */
1059             CGDisplayFadeReservationToken token;
1060
1061             if (blackout_other_displays) {
1062                 CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
1063                 CGDisplayFade(token, 0.5, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES);
1064             }
1065
1066             if ([screen mainScreen])
1067                 [NSApp setPresentationOptions:(NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar)];
1068
1069             [[o_video_view superview] replaceSubview:o_video_view with:o_temp_view];
1070             [o_temp_view setFrame:[o_video_view frame]];
1071             [o_fullscreen_window setContentView:o_video_view];
1072
1073             [o_fullscreen_window makeKeyAndOrderFront:self];
1074             [o_fullscreen_window orderFront:self animate:YES];
1075
1076             [o_fullscreen_window setFrame:screen_rect display:YES animate:YES];
1077             [o_fullscreen_window setLevel:NSNormalWindowLevel];
1078
1079             if (blackout_other_displays) {
1080                 CGDisplayFade(token, 0.3, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO);
1081                 CGReleaseDisplayFadeReservation(token);
1082             }
1083
1084             /* Will release the lock */
1085             [self hasBecomeFullscreen];
1086
1087             return;
1088         }
1089
1090         /* Make sure we don't see the o_video_view disappearing of the screen during this operation */
1091         NSDisableScreenUpdates();
1092         [[o_video_view superview] replaceSubview:o_video_view with:o_temp_view];
1093         [o_temp_view setFrame:[o_video_view frame]];
1094         [o_fullscreen_window setContentView:o_video_view];
1095         [o_fullscreen_window makeKeyAndOrderFront:self];
1096         NSEnableScreenUpdates();
1097     }
1098
1099     /* We are in fullscreen (and no animation is running) */
1100     if (b_fullscreen) {
1101         /* Make sure we are hidden */
1102         [o_current_video_window orderOut: self];
1103
1104         [self unlockFullscreenAnimation];
1105         return;
1106     }
1107
1108     if (o_fullscreen_anim1) {
1109         [o_fullscreen_anim1 stopAnimation];
1110         [o_fullscreen_anim1 release];
1111     }
1112     if (o_fullscreen_anim2) {
1113         [o_fullscreen_anim2 stopAnimation];
1114         [o_fullscreen_anim2 release];
1115     }
1116
1117     if ([screen mainScreen])
1118         [NSApp setPresentationOptions:(NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar)];
1119
1120     dict1 = [[NSMutableDictionary alloc] initWithCapacity:2];
1121     dict2 = [[NSMutableDictionary alloc] initWithCapacity:3];
1122
1123     [dict1 setObject:o_current_video_window forKey:NSViewAnimationTargetKey];
1124     [dict1 setObject:NSViewAnimationFadeOutEffect forKey:NSViewAnimationEffectKey];
1125
1126     [dict2 setObject:o_fullscreen_window forKey:NSViewAnimationTargetKey];
1127     [dict2 setObject:[NSValue valueWithRect:[o_fullscreen_window frame]] forKey:NSViewAnimationStartFrameKey];
1128     [dict2 setObject:[NSValue valueWithRect:screen_rect] forKey:NSViewAnimationEndFrameKey];
1129
1130     /* Strategy with NSAnimation allocation:
1131      - Keep at most 2 animation at a time
1132      - leaveFullscreen/enterFullscreen are the only responsible for releasing and alloc-ing
1133      */
1134     o_fullscreen_anim1 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict1]];
1135     o_fullscreen_anim2 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict2]];
1136
1137     [dict1 release];
1138     [dict2 release];
1139
1140     [o_fullscreen_anim1 setAnimationBlockingMode: NSAnimationNonblocking];
1141     [o_fullscreen_anim1 setDuration: 0.3];
1142     [o_fullscreen_anim1 setFrameRate: 30];
1143     [o_fullscreen_anim2 setAnimationBlockingMode: NSAnimationNonblocking];
1144     [o_fullscreen_anim2 setDuration: 0.2];
1145     [o_fullscreen_anim2 setFrameRate: 30];
1146
1147     [o_fullscreen_anim2 setDelegate: self];
1148     [o_fullscreen_anim2 startWhenAnimation: o_fullscreen_anim1 reachesProgress: 1.0];
1149
1150     [o_fullscreen_anim1 startAnimation];
1151     /* fullscreenAnimation will be unlocked when animation ends */
1152 }
1153
1154 - (void)hasBecomeFullscreen
1155 {
1156     if ([[o_video_view subviews] count] > 0)
1157         [o_fullscreen_window makeFirstResponder: [[o_video_view subviews] objectAtIndex:0]];
1158
1159     [o_fullscreen_window makeKeyWindow];
1160     [o_fullscreen_window setAcceptsMouseMovedEvents: YES];
1161
1162     /* tell the fspanel to move itself to front next time it's triggered */
1163     [o_fspanel setVoutWasUpdated: (int)[[o_fullscreen_window screen] displayID]];
1164     [o_fspanel setActive: nil];
1165
1166     if ([o_current_video_window isVisible])
1167         [o_current_video_window orderOut: self];
1168
1169     b_fullscreen = YES;
1170     [self unlockFullscreenAnimation];
1171 }
1172
1173 - (void)leaveFullscreen
1174 {
1175     [self leaveFullscreenAndFadeOut: NO];
1176 }
1177
1178 - (void)leaveFullscreenAndFadeOut: (BOOL)fadeout
1179 {
1180     NSMutableDictionary *dict1, *dict2;
1181     NSRect frame;
1182     BOOL blackout_other_displays = var_InheritBool(VLCIntf, "macosx-black");
1183
1184     if (!o_current_video_window)
1185         return;
1186
1187     [self lockFullscreenAnimation];
1188
1189     [o_controls_bar setFullscreenState:NO];
1190     if (o_detached_video_window)
1191         [[o_detached_video_window controlsBar] setFullscreenState:NO];
1192
1193     /* We always try to do so */
1194     [NSScreen unblackoutScreens];
1195
1196     vout_thread_t *p_vout = getVout();
1197     if (p_vout) {
1198         if (var_GetBool(p_vout, "video-on-top"))
1199             [[o_video_view window] setLevel: NSStatusWindowLevel];
1200         else
1201             [[o_video_view window] setLevel: NSNormalWindowLevel];
1202         vlc_object_release(p_vout);
1203     }
1204     [[o_video_view window] makeKeyAndOrderFront: nil];
1205
1206     /* Don't do anything if o_fullscreen_window is already closed */
1207     if (!o_fullscreen_window) {
1208         [self unlockFullscreenAnimation];
1209         return;
1210     }
1211
1212     if (fadeout) {
1213         /* We don't animate if we are not visible, instead we
1214          * simply fade the display */
1215         CGDisplayFadeReservationToken token;
1216
1217         if (blackout_other_displays) {
1218             CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
1219             CGDisplayFade(token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES);
1220         }
1221
1222         [o_fspanel setNonActive: nil];
1223         [NSApp setPresentationOptions: NSApplicationPresentationDefault];
1224
1225         /* Will release the lock */
1226         [self hasEndedFullscreen];
1227
1228         /* Our window is hidden, and might be faded. We need to workaround that, so note it
1229          * here */
1230         b_window_is_invisible = YES;
1231
1232         if (blackout_other_displays) {
1233             CGDisplayFade(token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO);
1234             CGReleaseDisplayFadeReservation(token);
1235         }
1236
1237         return;
1238     }
1239
1240     [o_current_video_window setAlphaValue: 0.0];
1241     [o_current_video_window orderFront: self];
1242     [[o_video_view window] orderFront: self];
1243
1244     [o_fspanel setNonActive: nil];
1245     [NSApp setPresentationOptions:(NSApplicationPresentationDefault)];
1246
1247     if (o_fullscreen_anim1) {
1248         [o_fullscreen_anim1 stopAnimation];
1249         [o_fullscreen_anim1 release];
1250     }
1251     if (o_fullscreen_anim2) {
1252         [o_fullscreen_anim2 stopAnimation];
1253         [o_fullscreen_anim2 release];
1254     }
1255
1256     frame = [[o_temp_view superview] convertRect: [o_temp_view frame] toView: nil]; /* Convert to Window base coord */
1257     frame.origin.x += [o_current_video_window frame].origin.x;
1258     frame.origin.y += [o_current_video_window frame].origin.y;
1259
1260     dict2 = [[NSMutableDictionary alloc] initWithCapacity:2];
1261     [dict2 setObject:o_current_video_window forKey:NSViewAnimationTargetKey];
1262     [dict2 setObject:NSViewAnimationFadeInEffect forKey:NSViewAnimationEffectKey];
1263
1264     o_fullscreen_anim2 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict2, nil]];
1265     [dict2 release];
1266
1267     [o_fullscreen_anim2 setAnimationBlockingMode: NSAnimationNonblocking];
1268     [o_fullscreen_anim2 setDuration: 0.3];
1269     [o_fullscreen_anim2 setFrameRate: 30];
1270
1271     [o_fullscreen_anim2 setDelegate: self];
1272
1273     dict1 = [[NSMutableDictionary alloc] initWithCapacity:3];
1274
1275     [dict1 setObject:o_fullscreen_window forKey:NSViewAnimationTargetKey];
1276     [dict1 setObject:[NSValue valueWithRect:[o_fullscreen_window frame]] forKey:NSViewAnimationStartFrameKey];
1277     [dict1 setObject:[NSValue valueWithRect:frame] forKey:NSViewAnimationEndFrameKey];
1278
1279     o_fullscreen_anim1 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict1, nil]];
1280     [dict1 release];
1281
1282     [o_fullscreen_anim1 setAnimationBlockingMode: NSAnimationNonblocking];
1283     [o_fullscreen_anim1 setDuration: 0.2];
1284     [o_fullscreen_anim1 setFrameRate: 30];
1285     [o_fullscreen_anim2 startWhenAnimation: o_fullscreen_anim1 reachesProgress: 1.0];
1286
1287     /* Make sure o_fullscreen_window is the frontmost window */
1288     [o_fullscreen_window orderFront: self];
1289
1290     [o_fullscreen_anim1 startAnimation];
1291     /* fullscreenAnimation will be unlocked when animation ends */
1292 }
1293
1294 - (void)hasEndedFullscreen
1295 {
1296     b_fullscreen = NO;
1297
1298     /* This function is private and should be only triggered at the end of the fullscreen change animation */
1299     /* Make sure we don't see the o_video_view disappearing of the screen during this operation */
1300     NSDisableScreenUpdates();
1301     [o_video_view retain];
1302     [o_video_view removeFromSuperviewWithoutNeedingDisplay];
1303     [[o_temp_view superview] replaceSubview:o_temp_view with:o_video_view];
1304     [o_video_view release];
1305     [o_video_view setFrame:[o_temp_view frame]];
1306     if ([[o_video_view subviews] count] > 0)
1307         [[o_video_view window] makeFirstResponder: [[o_video_view subviews] objectAtIndex:0]];
1308     if (!b_nonembedded)
1309             [super makeKeyAndOrderFront:self]; /* our version contains a workaround */
1310     else
1311         [[o_video_view window] makeKeyAndOrderFront: self];
1312     [o_fullscreen_window orderOut: self];
1313     NSEnableScreenUpdates();
1314
1315     [o_fullscreen_window release];
1316     o_fullscreen_window = nil;
1317     [[o_video_view window] setLevel:i_originalLevel];
1318     [[o_video_view window] setAlphaValue: config_GetFloat(VLCIntf, "macosx-opaqueness")];
1319
1320     // if we quit fullscreen because there is no video anymore, make sure non-embedded window is not visible
1321     if (![[VLCMain sharedInstance] activeVideoPlayback] && b_nonembedded)
1322         [o_current_video_window orderOut: self];
1323
1324     o_current_video_window = nil;
1325     [self unlockFullscreenAnimation];
1326 }
1327
1328 - (void)animationDidEnd:(NSAnimation*)animation
1329 {
1330     NSArray *viewAnimations;
1331     if (o_makekey_anim == animation) {
1332         [o_makekey_anim release];
1333         return;
1334     }
1335     if ([animation currentValue] < 1.0)
1336         return;
1337
1338     /* Fullscreen ended or started (we are a delegate only for leaveFullscreen's/enterFullscren's anim2) */
1339     viewAnimations = [o_fullscreen_anim2 viewAnimations];
1340     if ([viewAnimations count] >=1 &&
1341         [[[viewAnimations objectAtIndex: 0] objectForKey: NSViewAnimationEffectKey] isEqualToString:NSViewAnimationFadeInEffect]) {
1342         /* Fullscreen ended */
1343         [self hasEndedFullscreen];
1344     } else
1345         /* Fullscreen started */
1346         [self hasBecomeFullscreen];
1347 }
1348
1349 - (void)makeKeyAndOrderFront: (id)sender
1350 {
1351     /* Hack
1352      * when we exit fullscreen and fade out, we may endup in
1353      * having a window that is faded. We can't have it fade in unless we
1354      * animate again. */
1355
1356     if (!b_window_is_invisible) {
1357         /* Make sure we don't do it too much */
1358         [super makeKeyAndOrderFront: sender];
1359         return;
1360     }
1361
1362     [super setAlphaValue:0.0f];
1363     [super makeKeyAndOrderFront: sender];
1364
1365     NSMutableDictionary * dict = [[NSMutableDictionary alloc] initWithCapacity:2];
1366     [dict setObject:self forKey:NSViewAnimationTargetKey];
1367     [dict setObject:NSViewAnimationFadeInEffect forKey:NSViewAnimationEffectKey];
1368
1369     o_makekey_anim = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict]];
1370     [dict release];
1371
1372     [o_makekey_anim setAnimationBlockingMode: NSAnimationNonblocking];
1373     [o_makekey_anim setDuration: 0.1];
1374     [o_makekey_anim setFrameRate: 30];
1375     [o_makekey_anim setDelegate: self];
1376
1377     [o_makekey_anim startAnimation];
1378     b_window_is_invisible = NO;
1379
1380     /* fullscreenAnimation will be unlocked when animation ends */
1381 }
1382
1383 #pragma mark -
1384 #pragma mark Lion native fullscreen handling
1385 - (void)windowWillEnterFullScreen:(NSNotification *)notification
1386 {
1387     // workaround, see #6668
1388     [NSApp setPresentationOptions:(NSApplicationPresentationFullScreen | NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar)];
1389
1390     var_SetBool(pl_Get(VLCIntf), "fullscreen", true);
1391
1392     vout_thread_t *p_vout = getVout();
1393     if (p_vout) {
1394         var_SetBool(p_vout, "fullscreen", true);
1395         vlc_object_release(p_vout);
1396     }
1397
1398     [o_video_view setFrame: [[self contentView] frame]];
1399     b_fullscreen = YES;
1400
1401     [self recreateHideMouseTimer];
1402     i_originalLevel = [self level];
1403     [self setLevel:NSNormalWindowLevel];
1404
1405     if (b_dark_interface) {
1406         [o_titlebar_view removeFromSuperviewWithoutNeedingDisplay];
1407
1408         NSRect winrect;
1409         CGFloat f_titleBarHeight = [o_titlebar_view frame].size.height;
1410         winrect = [self frame];
1411
1412         winrect.size.height = winrect.size.height - f_titleBarHeight;
1413         [self setFrame: winrect display:NO animate:NO];
1414         winrect = [o_split_view frame];
1415         winrect.size.height = winrect.size.height + f_titleBarHeight;
1416         [o_split_view setFrame: winrect];
1417     }
1418
1419     if ([[VLCMain sharedInstance] activeVideoPlayback])
1420         [[o_controls_bar bottomBarView] setHidden: YES];
1421
1422     [self setMovableByWindowBackground: NO];
1423 }
1424
1425 - (void)windowDidEnterFullScreen:(NSNotification *)notification
1426 {
1427     // Indeed, we somehow can have an "inactive" fullscreen (but a visible window!).
1428     // But this creates some problems when leaving fs over remote intfs, so activate app here.
1429     [NSApp activateIgnoringOtherApps:YES];
1430
1431     [o_fspanel setVoutWasUpdated: (int)[[self screen] displayID]];
1432     [o_fspanel setActive: nil];
1433 }
1434
1435 - (void)windowWillExitFullScreen:(NSNotification *)notification
1436 {
1437
1438     var_SetBool(pl_Get(VLCIntf), "fullscreen", false);
1439
1440     vout_thread_t *p_vout = getVout();
1441     if (p_vout) {
1442         var_SetBool(p_vout, "fullscreen", false);
1443         vlc_object_release(p_vout);
1444     }
1445
1446     [o_video_view setFrame: [o_split_view frame]];
1447     [NSCursor setHiddenUntilMouseMoves: NO];
1448     [o_fspanel setNonActive: nil];
1449     [self setLevel:i_originalLevel];
1450     b_fullscreen = NO;
1451
1452     if (b_dark_interface) {
1453         NSRect winrect;
1454         CGFloat f_titleBarHeight = [o_titlebar_view frame].size.height;
1455         winrect = [self frame];
1456
1457         [o_titlebar_view setFrame: NSMakeRect(0, winrect.size.height - f_titleBarHeight,
1458                                               winrect.size.width, f_titleBarHeight)];
1459         [[self contentView] addSubview: o_titlebar_view];
1460
1461         winrect.size.height = winrect.size.height + f_titleBarHeight;
1462         [self setFrame: winrect display:NO animate:NO];
1463         winrect = [o_split_view frame];
1464         winrect.size.height = winrect.size.height - f_titleBarHeight;
1465         [o_split_view setFrame: winrect];
1466         [o_video_view setFrame: winrect];
1467     }
1468
1469     if ([[VLCMain sharedInstance] activeVideoPlayback])
1470         [[o_controls_bar bottomBarView] setHidden: NO];
1471
1472     [self setMovableByWindowBackground: YES];
1473 }
1474
1475 #pragma mark -
1476 #pragma mark split view delegate
1477 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)dividerIndex
1478 {
1479     if (dividerIndex == 0)
1480         return 300.;
1481     else
1482         return proposedMax;
1483 }
1484
1485 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)dividerIndex
1486 {
1487     if (dividerIndex == 0)
1488         return 100.;
1489     else
1490         return proposedMin;
1491 }
1492
1493 - (BOOL)splitView:(NSSplitView *)splitView canCollapseSubview:(NSView *)subview
1494 {
1495     return ([subview isEqual:o_left_split_view]);
1496 }
1497
1498 - (BOOL)splitView:(NSSplitView *)splitView shouldAdjustSizeOfSubview:(NSView *)subview
1499 {
1500     if ([subview isEqual:o_left_split_view])
1501         return NO;
1502     return YES;
1503 }
1504
1505 #pragma mark -
1506 #pragma mark Side Bar Data handling
1507 /* taken under BSD-new from the PXSourceList sample project, adapted for VLC */
1508 - (NSUInteger)sourceList:(PXSourceList*)sourceList numberOfChildrenOfItem:(id)item
1509 {
1510     //Works the same way as the NSOutlineView data source: `nil` means a parent item
1511     if (item==nil)
1512         return [o_sidebaritems count];
1513     else
1514         return [[item children] count];
1515 }
1516
1517
1518 - (id)sourceList:(PXSourceList*)aSourceList child:(NSUInteger)index ofItem:(id)item
1519 {
1520     //Works the same way as the NSOutlineView data source: `nil` means a parent item
1521     if (item==nil)
1522         return [o_sidebaritems objectAtIndex:index];
1523     else
1524         return [[item children] objectAtIndex:index];
1525 }
1526
1527
1528 - (id)sourceList:(PXSourceList*)aSourceList objectValueForItem:(id)item
1529 {
1530     return [item title];
1531 }
1532
1533 - (void)sourceList:(PXSourceList*)aSourceList setObjectValue:(id)object forItem:(id)item
1534 {
1535     [item setTitle:object];
1536 }
1537
1538 - (BOOL)sourceList:(PXSourceList*)aSourceList isItemExpandable:(id)item
1539 {
1540     return [item hasChildren];
1541 }
1542
1543
1544 - (BOOL)sourceList:(PXSourceList*)aSourceList itemHasBadge:(id)item
1545 {
1546     if ([[item identifier] isEqualToString: @"playlist"] || [[item identifier] isEqualToString: @"medialibrary"])
1547         return YES;
1548
1549     return [item hasBadge];
1550 }
1551
1552
1553 - (NSInteger)sourceList:(PXSourceList*)aSourceList badgeValueForItem:(id)item
1554 {
1555     playlist_t * p_playlist = pl_Get(VLCIntf);
1556     NSInteger i_playlist_size;
1557
1558     if ([[item identifier] isEqualToString: @"playlist"]) {
1559         PL_LOCK;
1560         i_playlist_size = p_playlist->p_local_category->i_children;
1561         PL_UNLOCK;
1562
1563         return i_playlist_size;
1564     }
1565     if ([[item identifier] isEqualToString: @"medialibrary"]) {
1566         PL_LOCK;
1567         i_playlist_size = p_playlist->p_ml_category->i_children;
1568         PL_UNLOCK;
1569
1570         return i_playlist_size;
1571     }
1572
1573     return [item badgeValue];
1574 }
1575
1576
1577 - (BOOL)sourceList:(PXSourceList*)aSourceList itemHasIcon:(id)item
1578 {
1579     return [item hasIcon];
1580 }
1581
1582
1583 - (NSImage*)sourceList:(PXSourceList*)aSourceList iconForItem:(id)item
1584 {
1585     return [item icon];
1586 }
1587
1588 - (NSMenu*)sourceList:(PXSourceList*)aSourceList menuForEvent:(NSEvent*)theEvent item:(id)item
1589 {
1590     if ([theEvent type] == NSRightMouseDown || ([theEvent type] == NSLeftMouseDown && ([theEvent modifierFlags] & NSControlKeyMask) == NSControlKeyMask)) {
1591         if (item != nil) {
1592             NSMenu * m;
1593             if ([item sdtype] > 0)
1594             {
1595                 m = [[NSMenu alloc] init];
1596                 playlist_t * p_playlist = pl_Get(VLCIntf);
1597                 BOOL sd_loaded = playlist_IsServicesDiscoveryLoaded(p_playlist, [[item identifier] UTF8String]);
1598                 if (!sd_loaded)
1599                     [m addItemWithTitle:_NS("Enable") action:@selector(sdmenuhandler:) keyEquivalent:@""];
1600                 else
1601                     [m addItemWithTitle:_NS("Disable") action:@selector(sdmenuhandler:) keyEquivalent:@""];
1602                 [[m itemAtIndex:0] setRepresentedObject: [item identifier]];
1603             }
1604             return [m autorelease];
1605         }
1606     }
1607
1608     return nil;
1609 }
1610
1611 - (IBAction)sdmenuhandler:(id)sender
1612 {
1613     NSString * identifier = [sender representedObject];
1614     if ([identifier length] > 0 && ![identifier isEqualToString:@"lua{sd='freebox',longname='Freebox TV'}"]) {
1615         playlist_t * p_playlist = pl_Get(VLCIntf);
1616         BOOL sd_loaded = playlist_IsServicesDiscoveryLoaded(p_playlist, [identifier UTF8String]);
1617
1618         if (!sd_loaded)
1619             playlist_ServicesDiscoveryAdd(p_playlist, [identifier UTF8String]);
1620         else
1621             playlist_ServicesDiscoveryRemove(p_playlist, [identifier UTF8String]);
1622     }
1623 }
1624
1625 #pragma mark -
1626 #pragma mark Side Bar Delegate Methods
1627 /* taken under BSD-new from the PXSourceList sample project, adapted for VLC */
1628 - (BOOL)sourceList:(PXSourceList*)aSourceList isGroupAlwaysExpanded:(id)group
1629 {
1630     if ([[group identifier] isEqualToString:@"library"])
1631         return YES;
1632
1633     return NO;
1634 }
1635
1636 - (void)sourceListSelectionDidChange:(NSNotification *)notification
1637 {
1638     playlist_t * p_playlist = pl_Get(VLCIntf);
1639
1640     NSIndexSet *selectedIndexes = [o_sidebar_view selectedRowIndexes];
1641     id item = [o_sidebar_view itemAtRow:[selectedIndexes firstIndex]];
1642
1643
1644     //Set the label text to represent the new selection
1645     if ([item sdtype] > -1 && [[item identifier] length] > 0) {
1646         BOOL sd_loaded = playlist_IsServicesDiscoveryLoaded(p_playlist, [[item identifier] UTF8String]);
1647         if (!sd_loaded)
1648             playlist_ServicesDiscoveryAdd(p_playlist, [[item identifier] UTF8String]);
1649     }
1650
1651     [o_chosen_category_lbl setStringValue:[item title]];
1652
1653     if ([[item identifier] isEqualToString:@"playlist"]) {
1654         [[[VLCMain sharedInstance] playlist] setPlaylistRoot:p_playlist->p_local_category];
1655     } else if ([[item identifier] isEqualToString:@"medialibrary"]) {
1656         [[[VLCMain sharedInstance] playlist] setPlaylistRoot:p_playlist->p_ml_category];
1657     } else {
1658         playlist_item_t * pl_item;
1659         PL_LOCK;
1660         pl_item = playlist_ChildSearchName(p_playlist->p_root, [[item untranslatedTitle] UTF8String]);
1661         PL_UNLOCK;
1662         [[[VLCMain sharedInstance] playlist] setPlaylistRoot: pl_item];
1663     }
1664
1665     PL_LOCK;
1666     if ([[[VLCMain sharedInstance] playlist] currentPlaylistRoot] != p_playlist->p_local_category || p_playlist->p_local_category->i_children > 0)
1667         [self hideDropZone];
1668     else
1669         [self showDropZone];
1670     PL_UNLOCK;
1671
1672     if ([[item identifier] isEqualToString:@"podcast{longname=\"Podcasts\"}"])
1673         [self showPodcastControls];
1674     else
1675         [self hidePodcastControls];
1676 }
1677
1678 - (NSDragOperation)sourceList:(PXSourceList *)aSourceList validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
1679 {
1680     if ([[item identifier] isEqualToString:@"playlist"] || [[item identifier] isEqualToString:@"medialibrary"]) {
1681         NSPasteboard *o_pasteboard = [info draggingPasteboard];
1682         if ([[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"] || [[o_pasteboard types] containsObject: NSFilenamesPboardType])
1683             return NSDragOperationGeneric;
1684     }
1685     return NSDragOperationNone;
1686 }
1687
1688 - (BOOL)sourceList:(PXSourceList *)aSourceList acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index
1689 {
1690     NSPasteboard *o_pasteboard = [info draggingPasteboard];
1691
1692     playlist_t * p_playlist = pl_Get(VLCIntf);
1693     playlist_item_t *p_node;
1694
1695     if ([[item identifier] isEqualToString:@"playlist"])
1696         p_node = p_playlist->p_local_category;
1697     else
1698         p_node = p_playlist->p_ml_category;
1699
1700     if ([[o_pasteboard types] containsObject: NSFilenamesPboardType]) {
1701         NSArray *o_values = [[o_pasteboard propertyListForType: NSFilenamesPboardType] sortedArrayUsingSelector: @selector(caseInsensitiveCompare:)];
1702         NSUInteger count = [o_values count];
1703         NSMutableArray *o_array = [NSMutableArray arrayWithCapacity:count];
1704
1705         for(NSUInteger i = 0; i < count; i++) {
1706             NSDictionary *o_dic;
1707             char *psz_uri = vlc_path2uri([[o_values objectAtIndex:i] UTF8String], NULL);
1708             if (!psz_uri)
1709                 continue;
1710
1711             o_dic = [NSDictionary dictionaryWithObject:[NSString stringWithCString:psz_uri encoding:NSUTF8StringEncoding] forKey:@"ITEM_URL"];
1712
1713             free(psz_uri);
1714
1715             [o_array addObject: o_dic];
1716         }
1717
1718         [[[VLCMain sharedInstance] playlist] appendNodeArray:o_array inNode: p_node atPos:-1 enqueue:YES];
1719         return YES;
1720     }
1721     else if ([[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"]) {
1722         NSArray * array = [[[VLCMain sharedInstance] playlist] draggedItems];
1723
1724         NSUInteger count = [array count];
1725         playlist_item_t * p_item = NULL;
1726
1727         PL_LOCK;
1728         for(NSUInteger i = 0; i < count; i++) {
1729             p_item = [[array objectAtIndex:i] pointerValue];
1730             if (!p_item) continue;
1731             playlist_NodeAddCopy(p_playlist, p_item, p_node, PLAYLIST_END);
1732         }
1733         PL_UNLOCK;
1734
1735         return YES;
1736     }
1737     return NO;
1738 }
1739
1740 - (id)sourceList:(PXSourceList *)aSourceList persistentObjectForItem:(id)item
1741 {
1742     return [item identifier];
1743 }
1744
1745 - (id)sourceList:(PXSourceList *)aSourceList itemForPersistentObject:(id)object
1746 {
1747     /* the following code assumes for sakes of simplicity that only the top level
1748      * items are allowed to have children */
1749
1750     NSArray * array = [NSArray arrayWithArray: o_sidebaritems]; // read-only arrays are noticebly faster
1751     NSUInteger count = [array count];
1752     if (count < 1)
1753         return nil;
1754
1755     for (NSUInteger x = 0; x < count; x++) {
1756         id item = [array objectAtIndex: x]; // save one objc selector call
1757         if ([[item identifier] isEqualToString:object])
1758             return item;
1759     }
1760
1761     return nil;
1762 }
1763
1764 #pragma mark -
1765 #pragma mark Podcast
1766
1767 - (IBAction)addPodcast:(id)sender
1768 {
1769     [NSApp beginSheet:o_podcast_subscribe_window modalForWindow:self modalDelegate:self didEndSelector:NULL contextInfo:nil];
1770 }
1771
1772 - (IBAction)addPodcastWindowAction:(id)sender
1773 {
1774     [o_podcast_subscribe_window orderOut:sender];
1775     [NSApp endSheet: o_podcast_subscribe_window];
1776
1777     if (sender == o_podcast_subscribe_ok_btn && [[o_podcast_subscribe_url_fld stringValue] length] > 0) {
1778         NSMutableString * podcastConf = [[NSMutableString alloc] init];
1779         if (config_GetPsz(VLCIntf, "podcast-urls") != NULL)
1780             [podcastConf appendFormat:@"%s|", config_GetPsz(VLCIntf, "podcast-urls")];
1781
1782         [podcastConf appendString: [o_podcast_subscribe_url_fld stringValue]];
1783         config_PutPsz(VLCIntf, "podcast-urls", [podcastConf UTF8String]);
1784
1785         vlc_object_t *p_obj = (vlc_object_t*)vlc_object_find_name(VLCIntf->p_libvlc, "podcast");
1786         if (p_obj) {
1787             var_SetString(p_obj, "podcast-urls", [podcastConf UTF8String]);
1788             vlc_object_release(p_obj);
1789         }
1790         [podcastConf release];
1791     }
1792 }
1793
1794 - (IBAction)removePodcast:(id)sender
1795 {
1796     if (config_GetPsz(VLCIntf, "podcast-urls") != NULL) {
1797         [o_podcast_unsubscribe_pop removeAllItems];
1798         [o_podcast_unsubscribe_pop addItemsWithTitles:[[NSString stringWithUTF8String:config_GetPsz(VLCIntf, "podcast-urls")] componentsSeparatedByString:@"|"]];
1799         [NSApp beginSheet:o_podcast_unsubscribe_window modalForWindow:self modalDelegate:self didEndSelector:NULL contextInfo:nil];
1800     }
1801 }
1802
1803 - (IBAction)removePodcastWindowAction:(id)sender
1804 {
1805     [o_podcast_unsubscribe_window orderOut:sender];
1806     [NSApp endSheet: o_podcast_unsubscribe_window];
1807
1808     if (sender == o_podcast_unsubscribe_ok_btn) {
1809         NSMutableArray * urls = [[NSMutableArray alloc] initWithArray:[[NSString stringWithUTF8String:config_GetPsz(VLCIntf, "podcast-urls")] componentsSeparatedByString:@"|"]];
1810         [urls removeObjectAtIndex: [o_podcast_unsubscribe_pop indexOfSelectedItem]];
1811         config_PutPsz(VLCIntf, "podcast-urls", [[urls componentsJoinedByString:@"|"] UTF8String]);
1812         [urls release];
1813
1814         vlc_object_t *p_obj = (vlc_object_t*)vlc_object_find_name(VLCIntf->p_libvlc, "podcast");
1815         if (p_obj) {
1816             var_SetString(p_obj, "podcast-urls", config_GetPsz(VLCIntf, "podcast-urls"));
1817             vlc_object_release(p_obj);
1818         }
1819
1820         /* reload the podcast module, since it won't update its list when removing podcasts */
1821         playlist_t * p_playlist = pl_Get(VLCIntf);
1822         if (playlist_IsServicesDiscoveryLoaded(p_playlist, "podcast{longname=\"Podcasts\"}")) {
1823             playlist_ServicesDiscoveryRemove(p_playlist, "podcast{longname=\"Podcasts\"}");
1824             playlist_ServicesDiscoveryAdd(p_playlist, "podcast{longname=\"Podcasts\"}");
1825             [o_playlist_table reloadData];
1826         }
1827
1828     }
1829 }
1830
1831 - (void)showPodcastControls
1832 {
1833     NSRect podcastViewDimensions = [o_podcast_view frame];
1834     NSRect rightSplitRect = [o_right_split_view frame];
1835     NSRect playlistTableRect = [o_playlist_table frame];
1836
1837     podcastViewDimensions.size.width = rightSplitRect.size.width;
1838     podcastViewDimensions.origin.x = podcastViewDimensions.origin.y = .0;
1839     [o_podcast_view setFrame:podcastViewDimensions];
1840
1841     playlistTableRect.origin.y = playlistTableRect.origin.y + podcastViewDimensions.size.height;
1842     playlistTableRect.size.height = playlistTableRect.size.height - podcastViewDimensions.size.height;
1843     [o_playlist_table setFrame:playlistTableRect];
1844     [o_playlist_table setNeedsDisplay:YES];
1845
1846     [o_right_split_view addSubview: o_podcast_view positioned: NSWindowAbove relativeTo: o_right_split_view];
1847     b_podcastView_displayed = YES;
1848 }
1849
1850 - (void)hidePodcastControls
1851 {
1852     if (b_podcastView_displayed) {
1853         NSRect podcastViewDimensions = [o_podcast_view frame];
1854         NSRect playlistTableRect = [o_playlist_table frame];
1855
1856         playlistTableRect.origin.y = playlistTableRect.origin.y - podcastViewDimensions.size.height;
1857         playlistTableRect.size.height = playlistTableRect.size.height + podcastViewDimensions.size.height;
1858
1859         [o_podcast_view removeFromSuperviewWithoutNeedingDisplay];
1860         [o_playlist_table setFrame: playlistTableRect];
1861         b_podcastView_displayed = NO;
1862     }
1863 }
1864
1865 @end
1866
1867 @implementation VLCDetachedVideoWindow
1868
1869 - (void)awakeFromNib
1870 {
1871     [self setAcceptsMouseMovedEvents: YES];
1872
1873     if (b_dark_interface) {
1874         [self setBackgroundColor: [NSColor clearColor]];
1875         [self setOpaque: NO];
1876         [self display];
1877         [self setHasShadow:NO];
1878         [self setHasShadow:YES];
1879
1880         NSRect winrect = [self frame];
1881         CGFloat f_titleBarHeight = [o_titlebar_view frame].size.height;
1882
1883         [self setTitle: _NS("VLC media player")];
1884         [o_titlebar_view setFrame: NSMakeRect(0, winrect.size.height - f_titleBarHeight, winrect.size.width, f_titleBarHeight)];
1885         [[self contentView] addSubview: o_titlebar_view positioned: NSWindowAbove relativeTo: nil];
1886
1887         // native fs not supported with detached view yet
1888         [o_titlebar_view setFullscreenButtonHidden: YES];
1889
1890     }
1891 }
1892
1893 @end