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