bba82b906e4e4b6ae10ee4f96eaa48f5e86edd6d
[vlc.git] / extras / MacOSX / VLC_app / Sources / VLCMainWindow.m
1 /*****************************************************************************
2  * VLCMainWindow.m: VLCMainWindow implementation
3  *****************************************************************************
4  * Copyright (C) 2007 Pierre d'Herbemont
5  * Copyright (C) 2007 the VideoLAN team
6  * $Id$
7  *
8  * Authors: Pierre d'Herbemont <pdherbemont # videolan.org>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 #import "VLCMainWindow.h"
26 #import "ImageAndTextCell.h"
27 #import "VLCMediaArrayController.h"
28 #import "VLCBrowsableVideoView.h"
29 #import "VLCAppAdditions.h"
30
31 @interface VLCMainWindow (NavigatorViewHidingShowing)
32 @property float contentHeight; /* animatable, keep the mainSplitView cursor at the same place, enabling playlist(navigator) togling */
33 @end
34
35 /******************************************************************************
36  * VLCMainWindow (CategoriesListDelegate)
37  */
38 @implementation VLCMainWindow (CategoriesListDelegate)
39 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item
40 {
41     return [[item representedObject] isKindOfClass:[NSDictionary class]];
42 }
43 - (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item
44 {
45     return !([[item representedObject] isKindOfClass:[NSDictionary class]]);
46 }
47 - (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
48 {
49     [cell setRepresentedObject:[item representedObject]];
50 }
51 @end
52
53 /******************************************************************************
54  * VLCMainWindow (CategoriesListDataSource)
55  */
56 @implementation VLCMainWindow (CategoriesListDataSource)
57 /* Drag and drop */
58 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id < NSDraggingInfo >)info item:(id)item childIndex:(NSInteger)index
59 {
60     int i;
61
62     if(![item respondsToSelector:@selector(representedObject)])
63         return NO;
64     
65     NSArray *droppedItems = [[info draggingPasteboard] propertyListForType:@"VLCMediaURLType"];
66     if( !droppedItems )
67         droppedItems = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
68     if( !droppedItems )
69         droppedItems = [[info draggingPasteboard] propertyListForType:NSURLPboardType];
70
71     NSAssert( droppedItems, @"Dropped an unsupported object type on the outline View" );
72
73     VLCMediaList * mediaList = [(VLCMedia *)[item representedObject] subitems];
74
75     for (i = 0; i < [droppedItems count]; i++)
76     {
77         NSString * filename = [droppedItems objectAtIndex:i];
78                 VLCMedia *media = [VLCMedia mediaWithPath:filename];
79         [mediaList lock];
80                 [mediaList insertMedia:media atIndex:index+1];
81         [mediaList unlock];
82     }
83     return YES;
84 }
85
86 - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id < NSDraggingInfo >)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
87 {
88     NSArray *droppedItems = [[info draggingPasteboard] propertyListForType:@"VLCMediaURLType"];
89     if( !droppedItems )
90         droppedItems = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
91     if( !droppedItems )
92         droppedItems = [[info draggingPasteboard] propertyListForType:NSURLPboardType];
93
94     if(! droppedItems ||
95        ![item respondsToSelector:@selector(representedObject)] ||
96        ![[item representedObject] isKindOfClass:[VLCMedia class]] )
97     {
98         return NSDragOperationNone;
99     }
100
101     return NSDragOperationMove;
102 }
103 @end
104
105 /******************************************************************************
106  * VLCMainWindow
107  */
108 @implementation VLCMainWindow
109 - (void)awakeFromNib;
110 {
111     NSTableColumn * tableColumn;
112
113     /* Check ib outlets */
114     NSAssert( mainSplitView, @"No split view or wrong split view");
115     NSAssert( fullScreenButton, @"No fullscreen button");
116
117     /***********************************
118      * Init the media player
119      */
120     mediaPlayer = [[VLCMediaPlayer alloc] initWithVideoView:videoView];
121
122     /***********************************
123      * CategoriesList OutlineView content
124      */
125     /* categoriesTreeController */ 
126     categoriesTreeController = [[NSTreeController alloc] init];
127     [categoriesTreeController setContent:controller.categories];
128   
129     [categoriesTreeController setChildrenKeyPath:@"childrenInCategoriesList"];
130     //[categoriesTreeController bind:@"contentArray" toObject:controller withKeyPath:@"arrayOfMasters" options:nil];
131
132     /* Bind the "name" table column */
133     tableColumn = [categoriesListView tableColumnWithIdentifier:@"name"];
134         [tableColumn bind:@"value" toObject: categoriesTreeController withKeyPath:@"arrangedObjects.descriptionInCategoriesList" options:nil];
135     [tableColumn setEditable:YES];
136     /* FIXME: this doesn't work obviously. */
137         [tableColumn bind:@"editable" toObject: categoriesTreeController withKeyPath:@"arrangedObjects.editableInCategoriesList" options:nil];
138
139     /* Use an ImageAndTextCell in the "name" table column */
140     ImageAndTextCell * cell = [[ImageAndTextCell alloc] init];
141     [cell setFont:[[tableColumn dataCell] font]];
142     [cell setImageKeyPath:@"image"];
143
144     [tableColumn setDataCell:cell];
145
146     /* Other setup */
147     [categoriesListView setIndentationMarkerFollowsCell:YES];
148     [categoriesListView setAutoresizesOutlineColumn:NO];
149     [categoriesListView setSelectionHighlightStyle:NSTableViewSelectionHighlightStyleSourceList];
150     [categoriesListView setDelegate:self];
151
152     [categoriesListView registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSURLPboardType, @"VLCMediaURLType", nil]];
153     [categoriesListView setDataSource: self];
154
155     /***********************************
156      * mediaListView setup
157      */
158
159     mediaArrayController = [[VLCMediaArrayController alloc] init];
160
161     /* 1- Drag and drop */
162     [mediaListView registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSURLPboardType, nil]];
163     [mediaListView setDataSource:mediaArrayController];
164
165     /* 2- Double click */
166     [mediaListView setTarget:self];
167     [mediaListView setDoubleAction:@selector(mediaListViewItemDoubleClicked:)];
168
169         /* 3- binding for "title" column */
170     tableColumn = [mediaListView tableColumnWithIdentifier:@"title"];
171         [tableColumn bind:@"value" toObject: mediaArrayController withKeyPath:@"arrangedObjects.metaDictionary.title" options:nil];
172
173         /* 4- binding for "state" column */
174     tableColumn = [mediaListView tableColumnWithIdentifier:@"state"];
175         [tableColumn bind:@"value" toObject: mediaArrayController withKeyPath:@"arrangedObjects.stateAsImage" options:nil];
176
177     /* 5- Search & Predicate */
178     NSMutableDictionary * bindingOptions = [NSMutableDictionary dictionary];
179     [bindingOptions setObject:@"metaDictionary.title contains[c] $value" forKey:NSPredicateFormatBindingOption];
180     [bindingOptions setObject:@"No Title" forKey:NSDisplayNameBindingOption];
181     [mediaListSearchField bind:@"predicate" toObject: mediaArrayController withKeyPath:@"filterPredicate" options:bindingOptions];
182     
183     /* 6- Bind the @"contentArray" and contentMediaList of the mediaArrayController */
184     [mediaArrayController bind:@"contentArray" toObject:categoriesTreeController withKeyPath:@"selection.childrenInCategoriesListForDetailView.media" options:nil];
185     [mediaArrayController bind:@"contentMediaList" toObject:categoriesTreeController withKeyPath:@"selection.childrenInCategoriesListForDetailView.parentMediaList" options:nil];
186     
187     /* 7- Aspect */
188     [mediaListView setSelectionHighlightStyle:NSTableViewSelectionHighlightStyleSourceList];
189     [mediaListView setAllowsTypeSelect:YES];
190
191     /***********************************
192      * videoView setup
193      */
194     [videoView setItemsTree:controller.categories];
195     [videoView setNodeKeyPath:@"childrenInVideoView"];
196     [videoView setContentKeyPath:@"descriptionInVideoView"];
197     [videoView setTarget:self];
198     [videoView setAction:@selector(videoViewItemClicked:)];
199     
200     /***********************************
201      * Toolbar setup
202      */
203     [[self toolbar] setDelegate:self];
204
205     /***********************************
206      * Other interface element setup
207      */
208
209     [mediaListItemsCount bind:@"displayPatternValue1" toObject:mediaArrayController withKeyPath:@"arrangedObjects.@count" options:[NSDictionary dictionaryWithObject:@"%{value1}@ items" forKey:NSDisplayPatternBindingOption]];
210     [mediaListItemFetchedStatus bind:@"animate" toObject:categoriesTreeController withKeyPath:@"selection.currentlyFetchingItems" options:[NSDictionary dictionaryWithObject:@"%{value1}@ items" forKey:NSDisplayPatternBindingOption]];
211
212     [fillScreenButton bind:@"value" toObject:videoView withKeyPath:@"fillScreen" options: [NSDictionary dictionaryWithObject:NSNegateBooleanTransformerName forKey:NSValueTransformerNameBindingOption]];
213     [fullScreenButton bind:@"value" toObject:videoView withKeyPath:@"fullScreen" options: nil];
214     [fullScreenButton bind:@"enabled" toObject:mediaPlayer withKeyPath:@"playing" options: nil];
215     [fillScreenButton bind:@"enabled" toObject:mediaPlayer withKeyPath:@"playing" options: nil];
216
217     [mediaReadingProgressSlider bind:@"enabled" toObject:mediaPlayer withKeyPath:@"media" options: [NSDictionary dictionaryWithObject:@"NonNilAsBoolTransformer" forKey:NSValueTransformerNameBindingOption]];
218     [mediaReadingProgressSlider bind:@"enabled2" toObject:mediaPlayer withKeyPath:@"seekable" options: nil];
219
220     [mediaReadingProgressSlider bind:@"value" toObject:mediaPlayer withKeyPath:@"position" options:
221         [NSDictionary dictionaryWithObjectsAndKeys:@"Float10000FoldTransformer", NSValueTransformerNameBindingOption,
222                                                   [NSNumber numberWithBool:NO], NSConditionallySetsEnabledBindingOption, nil ]];
223     [mediaReadingProgressText bind:@"value" toObject:mediaPlayer withKeyPath:@"time.stringValue" options: nil];
224     [mediaDescriptionText bind:@"value" toObject:mediaPlayer withKeyPath:@"description" options: nil];
225     [self bind:@"representedFilename" toObject:mediaPlayer withKeyPath:@"media.url" options: [NSDictionary dictionaryWithObject:@"URLToRepresentedFileNameTransformer" forKey:NSValueTransformerNameBindingOption]];
226     [self bind:@"title" toObject:mediaPlayer withKeyPath:@"description" options: nil];
227
228     [navigatorViewToggleButton bind:@"value" toObject:self withKeyPath:@"navigatorViewVisible" options: nil];
229
230     /* Playlist buttons */
231     [removePlaylistButton bind:@"enabled" toObject:categoriesTreeController withKeyPath:@"selection.editableInCategoriesList" options: nil];
232     [removePlaylistButton setTarget:categoriesTreeController];
233     [removePlaylistButton setAction:@selector(remove:)];
234     [addPlaylistButton setTarget:controller];
235     [addPlaylistButton setAction:@selector(addPlaylist:)];
236
237     [mainSplitView setDelegate:self];
238
239     /* Last minute setup */
240     [categoriesListView expandItem:nil expandChildren:YES];
241     [categoriesListView selectRowIndexes:[NSIndexSet indexSetWithIndex:[categoriesListView numberOfRows] > 0 ? [categoriesListView numberOfRows]-1 : 0] byExtendingSelection:NO];
242 }
243
244 - (void)dealloc
245 {
246     [navigatorView release];
247     [mediaPlayer release];
248     [categoriesTreeController release];
249     [mediaArrayController release];
250     [super dealloc];
251 }
252
253 - (void)mediaListViewItemDoubleClicked:(id)sender
254 {
255     [mediaPlayer setMedia:[[mediaArrayController selectedObjects] objectAtIndex:0]];
256     [mediaPlayer play];
257 }
258
259 - (void)videoViewItemClicked:(id)sender
260 {
261     id object = [sender selectedObject];
262     NSAssert( [object isKindOfClass:[VLCMedia class]], @"Object is not a VLCMedia" );
263
264     [mediaPlayer setMedia:object];
265     [mediaPlayer play];
266 }
267
268 - (BOOL)videoViewVisible
269 {
270     NSAssert( mainSplitView && [[mainSplitView subviews] count] == 2, @"No split view or wrong split view");
271     return  ([[[mainSplitView subviews] objectAtIndex:0] frame].size.height > 50.);
272 }
273
274 - (BOOL)navigatorViewVisible
275 {
276     NSAssert( mainSplitView && [[mainSplitView subviews] count] == 2, @"No split view or wrong split view");
277     return  ([[[mainSplitView subviews] objectAtIndex:1] frame].size.height > 6.);
278 }
279
280
281 - (void)setNavigatorViewVisible:(BOOL)visible
282 {
283     NSAssert( mainSplitView && [[mainSplitView subviews] count] == 2, @"No split view or wrong split view");
284     if(!([self navigatorViewVisible] ^ visible))
285         return; /* Nothing to do */
286     
287     if(visible)
288     {
289         /* Show the navigator view (playlist view) */
290         if( navigatorHeight < 100.f ) navigatorHeight = 100.f;
291         if( ![self videoViewVisible] && ![self navigatorViewVisible] )
292         {
293             /* Nothing is visible, only our toolbar */
294             NSRect frame = [self frame];
295             frame.origin.y += navigatorHeight;
296             frame.size.height += navigatorHeight;
297             [[self animator] setFrame:frame display:YES];
298         }
299         else
300             [[self animator] setContentHeight:[mainSplitView bounds].size.height + navigatorHeight + [mainSplitView dividerThickness]];
301         /* Hack, because sliding cause some glitches */
302         [navigatorView moveSubviewsToVisible];
303     }
304     else
305     {
306         /* Hide the navigator view (playlist view) */
307         navigatorHeight = [navigatorView bounds].size.height;
308         [[self animator] setContentHeight:[mainSplitView bounds].size.height - navigatorHeight + [mainSplitView dividerThickness]];
309         /* Hack, because sliding cause some glitches */
310         [navigatorView moveSubviewsToVisible];
311     }
312 }
313 @end
314
315 @implementation VLCMainWindow (SplitViewDelegating)
316 - (CGFloat)splitView:(NSSplitView *)sender constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)offset
317 {
318     CGFloat minHeight = 34.;
319
320     /* Hack, because sliding cause some glitches */
321     [navigatorView moveSubviewsToVisible];
322
323     /* Make a stuck point at the bottom of the nav view */
324     if( [sender bounds].size.height - proposedPosition < minHeight )
325          return [sender bounds].size.height;
326
327     return proposedPosition;
328 }
329
330 - (void)splitView:(NSSplitView *)sender resizeSubviewsWithOldSize:(NSSize)oldSize
331 {
332     [sender adjustSubviews];
333
334     /* Hack, because sliding cause some glitches */
335     [navigatorView setFrame:[[navigatorView superview] bounds]];
336     [navigatorView moveSubviewsToVisible];
337 }
338
339 - (void)splitViewWillResizeSubviews:(NSNotification *)aNotification
340 {
341     /* Hack, because sliding cause some glitches */
342     [navigatorView moveSubviewsToVisible];
343
344     /* This could be changed from now on, so post a KVO notification */
345     [self willChangeValueForKey:@"navigatorViewVisible"];
346 }
347 - (void)splitViewDidResizeSubviews:(NSNotification *)aNotification
348 {
349     [self didChangeValueForKey:@"navigatorViewVisible"];
350 }
351 @end
352
353 @implementation VLCMainWindow (NSWindowDelegating)
354 - (NSSize)windowWillResize:(NSWindow *)window toSize:(NSSize)proposedFrameSize
355 {
356     if( proposedFrameSize.height < 120.f)
357         proposedFrameSize.height = [self minSize].height;
358     return proposedFrameSize;
359 }
360 @end
361
362 @implementation VLCMainWindow (NavigatorViewHidingShowing)
363 - (float)contentHeight
364 {
365     return [self contentRectForFrameRect:[self frame]].size.height;
366 }
367
368 - (void)setContentHeight:(float)height
369 {
370     /* Set the Height while keeping the mainSplitView at his current position */
371     [mainSplitView setFixedCursorDuringResize:YES];
372     NSRect contentRect = [self contentRectForFrameRect:[self frame]];
373     float delta = height - contentRect.size.height;
374     contentRect.size.height = height;
375         NSRect windowFrame = [self frameRectForContentRect:contentRect];
376     windowFrame.origin.y -= delta;
377     windowFrame = [self constrainFrameRect:windowFrame toScreen:[self screen]];
378     [self setFrame:windowFrame display:YES];
379     [mainSplitView setFixedCursorDuringResize:NO];
380 }
381
382 + (id)defaultAnimationForKey:(NSString *)key
383 {
384     if([key isEqualToString:@"contentHeight"])
385     {
386         return [CABasicAnimation animation];
387     }
388     return [super defaultAnimationForKey: key];
389 }
390 @end
391
392 @implementation VLCMainWindow (NSToolbarDelegating)
393 /* Our item identifiers */
394 static NSString * VLCToolbarMediaControl     = @"VLCToolbarMediaControl";
395 static NSString * VLCToolbarMediaAudioVolume = @"VLCToolbarMediaAudioVolume";
396 static NSString * VLCToolbarMediaDescription = @"VLCToolbarMediaDescription";
397
398 - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar
399 {
400     return [NSArray arrayWithObjects:
401                         NSToolbarCustomizeToolbarItemIdentifier,
402                         NSToolbarFlexibleSpaceItemIdentifier,
403                         NSToolbarSpaceItemIdentifier,
404                         NSToolbarSeparatorItemIdentifier,
405                         VLCToolbarMediaControl,
406                         VLCToolbarMediaAudioVolume,
407                         VLCToolbarMediaDescription,
408                         nil ];
409 }
410
411 - (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) toolbar
412 {
413     return [NSArray arrayWithObjects:
414                         VLCToolbarMediaControl,
415                         VLCToolbarMediaAudioVolume,
416                         VLCToolbarMediaDescription,
417                         nil ];
418 }
419
420 - (NSToolbarItem *) toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag
421 {
422     NSToolbarItem *toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier: itemIdentifier] autorelease];
423  
424     if( [itemIdentifier isEqual: VLCToolbarMediaControl] )
425     {
426         [toolbarItem setLabel:@"Media Controls"];
427         [toolbarItem setPaletteLabel:@"Media Controls"];
428      
429         [toolbarItem setView:toolbarMediaControl];
430         [toolbarItem setMinSize:[[toolbarItem view] frame].size];
431         [toolbarItem setMaxSize:[[toolbarItem view] frame].size];
432
433         /* TODO: setup a menu */
434     }
435     else if( [itemIdentifier isEqual: VLCToolbarMediaAudioVolume] )
436     {
437         [toolbarItem setLabel:@"Audio Volume"];
438         [toolbarItem setPaletteLabel:@"Audio Volume"];
439      
440         [toolbarItem setView:toolbarMediaAudioVolume];
441         [toolbarItem setMinSize:[[toolbarItem view] frame].size];
442         [toolbarItem setMaxSize:[[toolbarItem view] frame].size];
443
444         /* TODO: setup a menu */
445     }
446     else  if( [itemIdentifier isEqual: VLCToolbarMediaDescription] )
447     {
448         [toolbarItem setLabel:@"Media Description"];
449         [toolbarItem setPaletteLabel:@"Media Description"];
450      
451         [toolbarItem setView:toolbarMediaDescription];
452         [toolbarItem setMinSize:[[toolbarItem view] frame].size];
453         [toolbarItem setMaxSize:NSMakeSize(10000 /* Can be really big */, NSHeight([[toolbarItem view] frame]))];
454
455         /* TODO: setup a menu */
456     }
457     else
458     {
459         /* itemIdentifier referred to a toolbar item that is not
460          * provided or supported by us or Cocoa
461          * Returning nil will inform the toolbar
462          * that this kind of item is not supported */
463         toolbarItem = nil;
464     }
465     return toolbarItem;
466 }
467 @end