MacOSX/VLC_app: Better naming scheme for the different UI elements.
[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 /******************************************************************************
32  * VLCMainWindow (CategoriesListDelegate)
33  */
34 @implementation VLCMainWindow (CategoriesListDelegate)
35 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item
36 {
37     return [[item representedObject] isKindOfClass:[NSDictionary class]];
38 }
39 - (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item
40 {
41     return !([[item representedObject] isKindOfClass:[NSDictionary class]]);
42 }
43 - (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
44 {
45     [cell setRepresentedObject:[item representedObject]];
46 }
47 @end
48
49 /******************************************************************************
50  * VLCMainWindow (CategoriesListDataSource)
51  */
52 @implementation VLCMainWindow (CategoriesListDataSource)
53 /* Drag and drop */
54 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id < NSDraggingInfo >)info item:(id)item childIndex:(NSInteger)index
55 {
56     int i;
57
58     if(![item respondsToSelector:@selector(representedObject)])
59         return NO;
60     
61     NSArray *droppedItems = [[info draggingPasteboard] propertyListForType:@"VLCMediaURLType"];
62     if( !droppedItems )
63         droppedItems = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
64     if( !droppedItems )
65         droppedItems = [[info draggingPasteboard] propertyListForType:NSURLPboardType];
66
67     NSAssert( droppedItems, @"Dropped an unsupported object type on the outline View" );
68
69     VLCMediaList * mediaList = [(VLCMedia *)[item representedObject] subitems];
70
71     for (i = 0; i < [droppedItems count]; i++)
72     {
73         NSString * filename = [droppedItems objectAtIndex:i];
74                 VLCMedia *media = [VLCMedia mediaWithPath:filename];
75         [mediaList lock];
76                 [mediaList insertMedia:media atIndex:index+1];
77         [mediaList unlock];
78     }
79     return YES;
80 }
81
82 - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id < NSDraggingInfo >)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
83 {
84     NSArray *droppedItems = [[info draggingPasteboard] propertyListForType:@"VLCMediaURLType"];
85     if( !droppedItems )
86         droppedItems = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
87     if( !droppedItems )
88         droppedItems = [[info draggingPasteboard] propertyListForType:NSURLPboardType];
89
90     if(! droppedItems ||
91        ![item respondsToSelector:@selector(representedObject)] ||
92        ![[item representedObject] isKindOfClass:[VLCMedia class]] )
93     {
94         return NSDragOperationNone;
95     }
96
97     return NSDragOperationMove;
98 }
99 @end
100
101 /******************************************************************************
102  * VLCMainWindow
103  */
104 @implementation VLCMainWindow
105 - (void)awakeFromNib;
106 {
107     NSTableColumn * tableColumn;
108
109     /* Check ib outlets */
110     NSAssert( mainSplitView, @"No split view or wrong split view");
111     NSAssert( fullScreenButton, @"No fullscreen button");
112
113     /***********************************
114      * Init the media player
115      */
116     mediaPlayer = [[VLCMediaPlayer alloc] initWithVideoView:videoView];
117
118     /***********************************
119      * CategoriesList OutlineView content
120      */
121     /* categoriesTreeController */ 
122     categoriesTreeController = [[NSTreeController alloc] init];
123     [categoriesTreeController setContent:controller.categories];
124   
125     [categoriesTreeController setChildrenKeyPath:@"childrenInCategoriesList"];
126     //[categoriesTreeController bind:@"contentArray" toObject:controller withKeyPath:@"arrayOfMasters" options:nil];
127
128     /* Bind the "name" table column */
129     tableColumn = [categoriesListView tableColumnWithIdentifier:@"name"];
130         [tableColumn bind:@"value" toObject: categoriesTreeController withKeyPath:@"arrangedObjects.descriptionInCategoriesList" options:nil];
131     [tableColumn setEditable:YES];
132     /* FIXME: this doesn't work obviously. */
133         [tableColumn bind:@"editable" toObject: categoriesTreeController withKeyPath:@"arrangedObjects.editableInCategoriesList" options:nil];
134
135     /* Use an ImageAndTextCell in the "name" table column */
136     ImageAndTextCell * cell = [[ImageAndTextCell alloc] init];
137     [cell setFont:[[tableColumn dataCell] font]];
138     [cell setImageKeyPath:@"image"];
139
140     [tableColumn setDataCell:cell];
141
142     /* Other setup */
143     [categoriesListView setSelectionHighlightStyle:NSTableViewSelectionHighlightStyleSourceList];
144     [categoriesListView setDelegate:self];
145
146     [categoriesListView registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSURLPboardType, @"VLCMediaURLType", nil]];
147     [categoriesListView setDataSource: self];
148
149     /***********************************
150      * mediaListView setup
151      */
152
153     mediaArrayController = [[VLCMediaArrayController alloc] init];
154
155     /* 1- Drag and drop */
156     [mediaListView registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSURLPboardType, nil]];
157     [mediaListView setDataSource:mediaArrayController];
158
159     /* 2- Double click */
160     [mediaListView setTarget:self];
161     [mediaListView setDoubleAction:@selector(mediaListViewItemDoubleClicked:)];
162
163         /* 3- binding for "title" column */
164     tableColumn = [mediaListView tableColumnWithIdentifier:@"title"];
165         [tableColumn bind:@"value" toObject: mediaArrayController withKeyPath:@"arrangedObjects.metaDictionary.title" options:nil];
166
167         /* 4- binding for "state" column */
168     tableColumn = [mediaListView tableColumnWithIdentifier:@"state"];
169         [tableColumn bind:@"value" toObject: mediaArrayController withKeyPath:@"arrangedObjects.stateAsImage" options:nil];
170
171     /* 5- Search & Predicate */
172     NSMutableDictionary * bindingOptions = [NSMutableDictionary dictionary];
173     [bindingOptions setObject:@"metaDictionary.title contains[c] $value" forKey:NSPredicateFormatBindingOption];
174     [bindingOptions setObject:@"No Title" forKey:NSDisplayNameBindingOption];
175     [mediaListSearchField bind:@"predicate" toObject: mediaArrayController withKeyPath:@"filterPredicate" options:bindingOptions];
176     
177     /* 6- Bind the @"contentArray" and contentMediaList of the mediaArrayController */
178     [mediaArrayController bind:@"contentArray" toObject:categoriesTreeController withKeyPath:@"selection.childrenInCategoriesListForDetailView.media" options:nil];
179     [mediaArrayController bind:@"contentMediaList" toObject:categoriesTreeController withKeyPath:@"selection.childrenInCategoriesListForDetailView.parentMediaList" options:nil];
180     
181     /* 7- Aspect */
182     [mediaListView setSelectionHighlightStyle:NSTableViewSelectionHighlightStyleSourceList];
183     [mediaListView setAllowsTypeSelect:YES];
184
185     /***********************************
186      * videoView setup
187      */
188     [videoView setItemsTree:controller.categories];
189     [videoView setNodeKeyPath:@"childrenInVideoView"];
190     [videoView setContentKeyPath:@"descriptionInVideoView"];
191     [videoView setTarget:self];
192     [videoView setAction:@selector(videoViewItemClicked:)];
193     
194
195     /***********************************
196      * Other interface element setup
197      */
198
199     [mediaListItemsCount bind:@"displayPatternValue1" toObject:mediaArrayController withKeyPath:@"arrangedObjects.@count" options:[NSDictionary dictionaryWithObject:@"%{value1}@ items" forKey:NSDisplayPatternBindingOption]];
200     [mediaListItemFetchedStatus bind:@"animate" toObject:categoriesTreeController withKeyPath:@"selection.currentlyFetchingItems" options:[NSDictionary dictionaryWithObject:@"%{value1}@ items" forKey:NSDisplayPatternBindingOption]];
201
202     [fillScreenButton bind:@"value" toObject:videoView withKeyPath:@"fillScreen" options: [NSDictionary dictionaryWithObject:NSNegateBooleanTransformerName forKey:NSValueTransformerNameBindingOption]];
203     [fullScreenButton bind:@"value" toObject:videoView withKeyPath:@"fullScreen" options: nil];
204     [fullScreenButton bind:@"enabled" toObject:mediaPlayer withKeyPath:@"playing" options: nil];
205     [fillScreenButton bind:@"enabled" toObject:mediaPlayer withKeyPath:@"playing" options: nil];
206
207     [mediaReadingProgressSlider bind:@"enabled" toObject:mediaPlayer withKeyPath:@"media" options: [NSDictionary dictionaryWithObject:@"NonNilAsBoolTransformer" forKey:NSValueTransformerNameBindingOption]];
208     [mediaReadingProgressSlider bind:@"enabled2" toObject:mediaPlayer withKeyPath:@"seekable" options: nil];
209
210     [mediaReadingProgressSlider bind:@"value" toObject:mediaPlayer withKeyPath:@"position" options:
211         [NSDictionary dictionaryWithObjectsAndKeys:@"Float10000FoldTransformer", NSValueTransformerNameBindingOption,
212                                                   [NSNumber numberWithBool:NO], NSConditionallySetsEnabledBindingOption, nil ]];
213     [mediaReadingProgressText bind:@"value" toObject:mediaPlayer withKeyPath:@"time.stringValue" options: nil];
214     [mediaDescriptionText bind:@"value" toObject:mediaPlayer withKeyPath:@"description" options: nil];
215
216     [navigatorViewToggleButton bind:@"value" toObject:self withKeyPath:@"navigatorViewVisible" options: nil];
217
218     /* Playlist buttons */
219     [removePlaylistButton bind:@"enabled" toObject:categoriesTreeController withKeyPath:@"selection.editableInCategoriesList" options: nil];
220     [removePlaylistButton setTarget:categoriesTreeController];
221     [removePlaylistButton setAction:@selector(remove:)];
222     [addPlaylistButton setTarget:controller];
223     [addPlaylistButton setAction:@selector(addPlaylist:)];
224
225     [mainSplitView setDelegate:self];
226
227     /* Last minute setup */
228     [categoriesListView expandItem:nil expandChildren:YES];
229     [categoriesListView selectRowIndexes:[NSIndexSet indexSetWithIndex:[categoriesListView numberOfRows] > 0 ? [categoriesListView numberOfRows]-1 : 0] byExtendingSelection:NO];
230 }
231
232 - (void)dealloc
233 {
234     [navigatorView release];
235     [mediaPlayer release];
236     [categoriesTreeController release];
237     [mediaArrayController release];
238     [super dealloc];
239 }
240
241 - (void)mediaListViewItemDoubleClicked:(id)sender
242 {
243     [mediaPlayer setMedia:[[mediaArrayController selectedObjects] objectAtIndex:0]];
244     [mediaPlayer play];
245 }
246
247 - (void)videoViewItemClicked:(id)sender
248 {
249     id object = [sender selectedObject];
250     NSAssert( [object isKindOfClass:[VLCMedia class]], @"Object is not a VLCMedia" );
251
252     [mediaPlayer setMedia:object];
253     [mediaPlayer play];
254 }
255
256 - (BOOL)videoViewVisible
257 {
258     NSAssert( mainSplitView && [[mainSplitView subviews] count] == 2, @"No split view or wrong split view");
259     return  ([[[mainSplitView subviews] objectAtIndex:0] frame].size.height > 50.);
260 }
261
262 - (BOOL)navigatorViewVisible
263 {
264     NSAssert( mainSplitView && [[mainSplitView subviews] count] == 2, @"No split view or wrong split view");
265     return  ([[[mainSplitView subviews] objectAtIndex:1] frame].size.height > 6.);
266 }
267
268
269 - (void)setNavigatorViewVisible:(BOOL)visible
270 {
271     NSAssert( mainSplitView && [[mainSplitView subviews] count] == 2, @"No split view or wrong split view");
272     if(!([self navigatorViewVisible] ^ visible))
273         return; /* Nothing to do */
274     
275     if(visible)
276     {
277         if( !navigatorHeight ) navigatorHeight = 100.f;
278         if( ![self videoViewVisible] && ![self navigatorViewVisible] )
279         {
280             NSRect frame = [self frame];
281             frame.origin.y -= navigatorHeight;
282             frame.size.height += navigatorHeight;
283             [[self animator] setFrame:frame display:YES];
284         }
285         else
286             [[mainSplitView animator] setSliderPosition:([mainSplitView bounds].size.height - navigatorHeight - [mainSplitView dividerThickness])];
287         /* Hack, because sliding cause some glitches */
288         [navigatorView moveSubviewsToVisible];
289     }
290     else
291     {
292         navigatorHeight = [navigatorView bounds].size.height;
293         NSRect frame0 = [self frame];
294         NSRect frame1 = [[[mainSplitView subviews] objectAtIndex: 1] frame];
295         frame0.size.height -= frame1.size.height;
296         frame0.origin.y += frame1.size.height;
297         frame1.size.height = 0;
298         [[mainSplitView animator] setSliderPosition:([mainSplitView bounds].size.height)];
299         /* Hack, because sliding cause some glitches */
300         [navigatorView moveSubviewsToVisible];
301     }
302 }
303 @end
304
305 @implementation VLCMainWindow (SplitViewDelegating)
306 - (CGFloat)splitView:(NSSplitView *)sender constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)offset
307 {
308     CGFloat minHeight = 34.;
309
310     /* Hack, because sliding cause some glitches */
311     [navigatorView moveSubviewsToVisible];
312
313     /* Get the bottom of the navigator view to get stuck at some points */
314     if( [sender bounds].size.height - proposedPosition < minHeight*3./2. &&
315         [sender bounds].size.height - proposedPosition >= minHeight/2 )
316          return [sender bounds].size.height - minHeight;
317     if( [sender bounds].size.height - proposedPosition < minHeight/2 )
318          return [sender bounds].size.height;
319     return proposedPosition;
320 }
321
322 - (void)splitView:(NSSplitView *)sender resizeSubviewsWithOldSize:(NSSize)oldSize
323 {
324     [sender  adjustSubviews];
325
326     /* Hack, because sliding cause some glitches */
327     [navigatorView setFrame:[[navigatorView superview] bounds]];
328     [navigatorView moveSubviewsToVisible];
329 }
330
331 - (void)splitViewWillResizeSubviews:(NSNotification *)aNotification
332 {
333     /* Hack, because sliding cause some glitches */
334     [navigatorView moveSubviewsToVisible];
335     
336     /* This could be changed from now on, so post a KVO notification */
337     [self willChangeValueForKey:@"navigatorViewVisible"];
338 }
339 - (void)splitViewDidResizeSubviews:(NSNotification *)aNotification
340 {
341     [self didChangeValueForKey:@"navigatorViewVisible"];
342 }
343 @end
344
345 @implementation VLCMainWindow (NSWindowDelegating)
346 - (NSSize)windowWillResize:(NSWindow *)window toSize:(NSSize)proposedFrameSize
347 {
348     if( proposedFrameSize.height < 120.f)
349         proposedFrameSize.height = [self minSize].height;
350     return proposedFrameSize;
351 }
352 @end