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