49494647bbda36443e81b96294d910259d77f472
[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: nil];
213     [fullScreenButton bind:@"value" toObject:videoView withKeyPath:@"fullScreen" options: nil];
214     [fullScreenButton bind:@"enabled" toObject:mediaPlayer withKeyPath:@"media" options: [NSDictionary dictionaryWithObject:@"NonNilAsBoolTransformer" forKey:NSValueTransformerNameBindingOption]];
215     [fillScreenButton bind:@"enabled" toObject:mediaPlayer withKeyPath:@"media" options: [NSDictionary dictionaryWithObject:@"NonNilAsBoolTransformer" forKey:NSValueTransformerNameBindingOption]];
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     /* Sound */
240     [mediaSoundVolume bind:@"value" toObject:[VLCLibrary sharedLibrary] withKeyPath:@"audio.volume" options: nil];
241
242     /* mediaPlayer */
243     [mediaPlayerPlayPauseStopButton bind:@"enabled" toObject:mediaPlayer withKeyPath:@"media" options: [NSDictionary dictionaryWithObject:@"NonNilAsBoolTransformer" forKey:NSValueTransformerNameBindingOption]];
244     [mediaPlayerPlayPauseStopButton bind:@"state"   toObject:mediaPlayer withKeyPath:@"playing" options: nil];
245     [mediaPlayerPlayPauseStopButton bind:@"alternateImage" toObject:mediaPlayer withKeyPath:@"stateAsButtonAlternateImage" options: nil];
246     [mediaPlayerPlayPauseStopButton bind:@"image"   toObject:mediaPlayer withKeyPath:@"stateAsButtonImage" options: nil];
247     [mediaPlayerBackwardPrevButton  bind:@"enabled" toObject:mediaPlayer withKeyPath:@"playing" options: nil];
248     [mediaPlayerForwardNextButton   bind:@"enabled" toObject:mediaPlayer withKeyPath:@"playing" options: nil];
249     [mediaPlayerForwardNextButton   setTarget:mediaPlayer];
250     [mediaPlayerForwardNextButton   setAction:@selector(fastForward)];
251     [mediaPlayerBackwardPrevButton  setTarget:mediaPlayer];
252     [mediaPlayerBackwardPrevButton  setAction:@selector(rewind)];
253     [mediaPlayerPlayPauseStopButton setTarget:mediaPlayer];
254     [mediaPlayerPlayPauseStopButton setAction:@selector(pause)];
255     
256     /* Last minute setup */
257     [categoriesListView expandItem:nil expandChildren:YES];
258     [categoriesListView selectRowIndexes:[NSIndexSet indexSetWithIndex:[categoriesListView numberOfRows] > 0 ? [categoriesListView numberOfRows]-1 : 0] byExtendingSelection:NO];
259 }
260
261 - (void)dealloc
262 {
263     [navigatorView release];
264     [mediaPlayer release];
265     [categoriesTreeController release];
266     [mediaArrayController release];
267     [super dealloc];
268 }
269
270 - (void)mediaListViewItemDoubleClicked:(id)sender
271 {
272     [mediaPlayer setMedia:[[mediaArrayController selectedObjects] objectAtIndex:0]];
273     [mediaPlayer play];
274 }
275
276 - (void)videoViewItemClicked:(id)sender
277 {
278     id object = [sender selectedObject];
279     NSAssert( [object isKindOfClass:[VLCMedia class]], @"Object is not a VLCMedia" );
280
281     [mediaPlayer setMedia:object];
282     [mediaPlayer play];
283 }
284
285 - (BOOL)videoViewVisible
286 {
287     NSAssert( mainSplitView && [[mainSplitView subviews] count] == 2, @"No split view or wrong split view");
288     return  ([[[mainSplitView subviews] objectAtIndex:0] frame].size.height > 50.);
289 }
290
291 - (BOOL)navigatorViewVisible
292 {
293     NSAssert( mainSplitView && [[mainSplitView subviews] count] == 2, @"No split view or wrong split view");
294     return  ([[[mainSplitView subviews] objectAtIndex:1] frame].size.height > 6.);
295 }
296
297
298 - (void)setNavigatorViewVisible:(BOOL)visible
299 {
300     NSAssert( mainSplitView && [[mainSplitView subviews] count] == 2, @"No split view or wrong split view");
301     if(!([self navigatorViewVisible] ^ visible))
302         return; /* Nothing to do */
303     
304     if(visible)
305     {
306         /* Show the navigator view (playlist view) */
307         if( navigatorHeight < 100.f ) navigatorHeight = 100.f;
308         if( ![self videoViewVisible] && ![self navigatorViewVisible] )
309         {
310             /* Nothing is visible, only our toolbar */
311             NSRect frame = [self frame];
312             frame.origin.y += navigatorHeight;
313             frame.size.height += navigatorHeight;
314             [[self animator] setFrame:frame display:YES];
315         }
316         else
317             [[self animator] setContentHeight:[mainSplitView bounds].size.height + navigatorHeight + [mainSplitView dividerThickness]];
318         /* Hack, because sliding cause some glitches */
319         [navigatorView moveSubviewsToVisible];
320     }
321     else
322     {
323         /* Hide the navigator view (playlist view) */
324         navigatorHeight = [navigatorView bounds].size.height;
325         [[self animator] setContentHeight:[mainSplitView bounds].size.height - navigatorHeight + [mainSplitView dividerThickness]];
326         /* Hack, because sliding cause some glitches */
327         [navigatorView moveSubviewsToVisible];
328     }
329 }
330 @end
331
332 @implementation VLCMainWindow (SplitViewDelegating)
333 - (CGFloat)splitView:(NSSplitView *)sender constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)offset
334 {
335     CGFloat minHeight = 34.;
336
337     /* Hack, because sliding cause some glitches */
338     [navigatorView moveSubviewsToVisible];
339
340     /* Make a stuck point at the bottom of the nav view */
341     if( [sender bounds].size.height - proposedPosition < minHeight )
342          return [sender bounds].size.height;
343
344     return proposedPosition;
345 }
346
347 - (void)splitView:(NSSplitView *)sender resizeSubviewsWithOldSize:(NSSize)oldSize
348 {
349     [sender adjustSubviews];
350
351     /* Hack, because sliding cause some glitches */
352     [navigatorView setFrame:[[navigatorView superview] bounds]];
353     [navigatorView moveSubviewsToVisible];
354 }
355
356 - (void)splitViewWillResizeSubviews:(NSNotification *)aNotification
357 {
358     /* Hack, because sliding cause some glitches */
359     [navigatorView moveSubviewsToVisible];
360
361     /* This could be changed from now on, so post a KVO notification */
362     [self willChangeValueForKey:@"navigatorViewVisible"];
363 }
364 - (void)splitViewDidResizeSubviews:(NSNotification *)aNotification
365 {
366     [self didChangeValueForKey:@"navigatorViewVisible"];
367 }
368 @end
369
370 @implementation VLCMainWindow (NSWindowDelegating)
371 - (NSSize)windowWillResize:(NSWindow *)window toSize:(NSSize)proposedFrameSize
372 {
373     if( proposedFrameSize.height < 120.f)
374         proposedFrameSize.height = [self minSize].height;
375     return proposedFrameSize;
376 }
377 @end
378
379 @implementation VLCMainWindow (NavigatorViewHidingShowing)
380 - (float)contentHeight
381 {
382     return [self contentRectForFrameRect:[self frame]].size.height;
383 }
384
385 - (void)setContentHeight:(float)height
386 {
387     /* Set the Height while keeping the mainSplitView at his current position */
388     [mainSplitView setFixedCursorDuringResize:YES];
389     NSRect contentRect = [self contentRectForFrameRect:[self frame]];
390     float delta = height - contentRect.size.height;
391     contentRect.size.height = height;
392         NSRect windowFrame = [self frameRectForContentRect:contentRect];
393     windowFrame.origin.y -= delta;
394     windowFrame = [self constrainFrameRect:windowFrame toScreen:[self screen]];
395     [self setFrame:windowFrame display:YES];
396     [mainSplitView setFixedCursorDuringResize:NO];
397 }
398
399 + (id)defaultAnimationForKey:(NSString *)key
400 {
401     if([key isEqualToString:@"contentHeight"])
402     {
403         return [CABasicAnimation animation];
404     }
405     return [super defaultAnimationForKey: key];
406 }
407 @end
408
409 @implementation VLCMainWindow (NSToolbarDelegating)
410 /* Our item identifiers */
411 static NSString * VLCToolbarMediaControl     = @"VLCToolbarMediaControl";
412 static NSString * VLCToolbarMediaAudioVolume = @"VLCToolbarMediaAudioVolume";
413 static NSString * VLCToolbarMediaDescription = @"VLCToolbarMediaDescription";
414
415 - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar
416 {
417     return [NSArray arrayWithObjects:
418                         NSToolbarCustomizeToolbarItemIdentifier,
419                         NSToolbarFlexibleSpaceItemIdentifier,
420                         NSToolbarSpaceItemIdentifier,
421                         NSToolbarSeparatorItemIdentifier,
422                         VLCToolbarMediaControl,
423                         VLCToolbarMediaAudioVolume,
424                         VLCToolbarMediaDescription,
425                         nil ];
426 }
427
428 - (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) toolbar
429 {
430     return [NSArray arrayWithObjects:
431                         VLCToolbarMediaControl,
432                         VLCToolbarMediaAudioVolume,
433                         VLCToolbarMediaDescription,
434                         nil ];
435 }
436
437 - (NSToolbarItem *) toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag
438 {
439     NSToolbarItem *toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier: itemIdentifier] autorelease];
440  
441     if( [itemIdentifier isEqual: VLCToolbarMediaControl] )
442     {
443         [toolbarItem setLabel:@"Media Controls"];
444         [toolbarItem setPaletteLabel:@"Media Controls"];
445      
446         [toolbarItem setView:toolbarMediaControl];
447         [toolbarItem setMinSize:[[toolbarItem view] frame].size];
448         [toolbarItem setMaxSize:[[toolbarItem view] frame].size];
449
450         /* TODO: setup a menu */
451     }
452     else if( [itemIdentifier isEqual: VLCToolbarMediaAudioVolume] )
453     {
454         [toolbarItem setLabel:@"Audio Volume"];
455         [toolbarItem setPaletteLabel:@"Audio Volume"];
456      
457         [toolbarItem setView:toolbarMediaAudioVolume];
458         [toolbarItem setMinSize:[[toolbarItem view] frame].size];
459         [toolbarItem setMaxSize:[[toolbarItem view] frame].size];
460
461         /* TODO: setup a menu */
462     }
463     else  if( [itemIdentifier isEqual: VLCToolbarMediaDescription] )
464     {
465         [toolbarItem setLabel:@"Media Description"];
466         [toolbarItem setPaletteLabel:@"Media Description"];
467      
468         [toolbarItem setView:toolbarMediaDescription];
469         [toolbarItem setMinSize:[[toolbarItem view] frame].size];
470         [toolbarItem setMaxSize:NSMakeSize(10000 /* Can be really big */, NSHeight([[toolbarItem view] frame]))];
471
472         /* TODO: setup a menu */
473     }
474     else
475     {
476         /* itemIdentifier referred to a toolbar item that is not
477          * provided or supported by us or Cocoa
478          * Returning nil will inform the toolbar
479          * that this kind of item is not supported */
480         toolbarItem = nil;
481     }
482     return toolbarItem;
483 }
484 @end