Support other displays fade out. Change video-device to point to the unique screen ID.
[vlc.git] / modules / gui / macosx / misc.m
1 /*****************************************************************************
2  * misc.m: code not specific to vlc
3  *****************************************************************************
4  * Copyright (C) 2003-2007 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Jon Lech Johansen <jon-vl@nanocrew.net>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 #include <Cocoa/Cocoa.h>
25
26 #include "intf.h"                                          /* VLCApplication */
27 #include "misc.h"
28 #include "playlist.h"
29 #include "controls.h"
30
31 /*****************************************************************************
32  * NSAnimation (VLCAdditions)
33  *
34  *  Missing extension to NSAnimation
35  *****************************************************************************/
36
37 @implementation NSAnimation (VLCAdditions)
38 /* fake class attributes  */
39 static NSMapTable *VLCAdditions_userInfo = NULL;
40
41 + (void)load
42 {
43     /* init our fake object attribute */
44     VLCAdditions_userInfo = NSCreateMapTable(NSNonRetainedObjectMapKeyCallBacks, NSObjectMapValueCallBacks, 16);
45 }
46
47 - (void)dealloc
48 {
49     NSMapRemove(VLCAdditions_userInfo, self);
50     [super dealloc];
51 }
52
53 - (void)setUserInfo: (void *)userInfo
54 {
55     NSMapInsert(VLCAdditions_userInfo, self, (void*)userInfo);
56 }
57
58 - (void *)userInfo
59 {
60     return NSMapGet(VLCAdditions_userInfo, self);
61 }
62 @end
63
64 /*****************************************************************************
65  * NSScreen (VLCAdditions)
66  *
67  *  Missing extension to NSScreen
68  *****************************************************************************/
69
70 @implementation NSScreen (VLCAdditions)
71
72 static NSMutableArray *blackoutWindows = NULL;
73
74 + (void)load
75 {
76     /* init our fake object attribute */
77     blackoutWindows = [[NSMutableArray alloc] initWithCapacity:1];
78 }
79
80 + (NSScreen *)screenWithDisplayID: (CGDirectDisplayID)displayID
81 {
82     int i;
83     
84     for( i = 0; i < [[NSScreen screens] count]; i++ )
85     {
86         NSScreen *screen = [[NSScreen screens] objectAtIndex: i];
87         if([screen displayID] == displayID)
88             return screen;
89     }
90     return nil;
91 }
92
93 - (CGDirectDisplayID)displayID
94 {
95     return (CGDirectDisplayID)_screenNumber;
96 }
97
98 - (void)blackoutOtherScreens
99 {
100     unsigned int i;
101
102     /* Free our previous blackout window (follow blackoutWindow alloc strategy) */
103     [blackoutWindows makeObjectsPerformSelector:@selector(close)];
104     [blackoutWindows removeAllObjects];
105
106     
107     for(i = 0; i < [[NSScreen screens] count]; i++)
108     {
109         VLCWindow *blackoutWindow;
110         NSScreen *screen = [[NSScreen screens] objectAtIndex: i];
111         if(self == screen)
112             continue;
113         /* blackoutWindow alloc strategy
114             - The NSMutableArray blackoutWindows has the blackoutWindow references
115             - blackoutOtherDisplays is responsible for alloc/releasing its Windows
116         */
117         blackoutWindow = [[VLCWindow alloc] initWithContentRect: [screen frame] styleMask: NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO];
118         [blackoutWindow setBackgroundColor:[NSColor blackColor]];
119         [blackoutWindow setLevel: NSFloatingWindowLevel]; /* Disappear when Expose is triggered */
120         
121         [blackoutWindow orderFront: self animate: YES];
122
123         [blackoutWindows addObject: blackoutWindow];
124         [blackoutWindow release];
125     }
126 }
127
128 + (void)unblackoutScreens
129 {
130     unsigned int i;
131
132     for(i = 0; i < [blackoutWindows count]; i++)
133     {
134         VLCWindow *blackoutWindow = [blackoutWindows objectAtIndex: i];
135         [blackoutWindow closeAndAnimate: YES];
136     }
137 }
138
139 @end
140
141 /*****************************************************************************
142  * VLCWindow
143  *
144  *  Missing extension to NSWindow
145  *****************************************************************************/
146
147 @implementation VLCWindow
148 - (id)initWithContentRect:(NSRect)contentRect styleMask:(unsigned int)styleMask
149     backing:(NSBackingStoreType)backingType defer:(BOOL)flag
150 {
151     self = [super initWithContentRect:contentRect styleMask:styleMask backing:backingType defer:flag];
152     if( self )
153         b_isset_canBecomeKeyWindow = NO;
154     return self;
155 }
156 - (void)setCanBecomeKeyWindow: (BOOL)canBecomeKey
157 {
158     b_isset_canBecomeKeyWindow = YES;
159     b_canBecomeKeyWindow = canBecomeKey;
160 }
161
162 - (BOOL)canBecomeKeyWindow
163 {
164     if(b_isset_canBecomeKeyWindow)
165         return b_canBecomeKeyWindow;
166
167     return [super canBecomeKeyWindow];
168 }
169
170 - (void)closeAndAnimate: (BOOL)animate
171 {
172     NSInvocation *invoc;
173     
174     if (!animate || MACOS_VERSION < 10.4f)
175     {
176         [super close];
177         return;
178     }
179
180     invoc = [NSInvocation invocationWithMethodSignature:[super methodSignatureForSelector:@selector(close)]];
181     [invoc setTarget: (id)super];
182
183     if (![self isVisible] || [self alphaValue] == 0.0)
184     {
185         [super close];
186         return;
187     }
188
189     [self orderOut: self animate: YES callback: invoc];
190 }
191
192 - (void)orderOut: (id)sender animate: (BOOL)animate
193 {
194     NSInvocation *invoc = [NSInvocation invocationWithMethodSignature:[super methodSignatureForSelector:@selector(orderOut:)]];
195     [invoc setTarget: (id)super];
196     [invoc setArgument: sender atIndex: 0];
197     [self orderOut: sender animate: animate callback: invoc];
198 }
199
200 - (void)orderOut: (id)sender animate: (BOOL)animate callback:(NSInvocation *)callback
201 {
202     NSViewAnimation *anim;
203     NSViewAnimation *current_anim;
204     NSMutableDictionary *dict;
205
206     if (!animate || MACOS_VERSION < 10.4f)
207     {
208         [self orderOut: sender];
209         return;
210     }
211
212     dict = [[NSMutableDictionary alloc] initWithCapacity:2];
213
214     [dict setObject:self forKey:NSViewAnimationTargetKey];
215
216     [dict setObject:NSViewAnimationFadeOutEffect forKey:NSViewAnimationEffectKey];
217     anim = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict, nil]];
218     [dict release];
219
220     [anim setAnimationBlockingMode:NSAnimationNonblocking];
221     [anim setDuration:0.9];
222     [anim setFrameRate:30];
223     [anim setUserInfo: callback];
224
225     @synchronized(self) {
226         current_anim = self->animation;
227
228         if ([[[current_anim viewAnimations] objectAtIndex:0] objectForKey: NSViewAnimationEffectKey] == NSViewAnimationFadeOutEffect && [current_anim isAnimating])
229         {
230             [anim release];
231         }
232         else
233         {
234             if (current_anim)
235             {
236                 [current_anim stopAnimation];
237                 [anim setCurrentProgress:1.0-[current_anim currentProgress]];
238                 [current_anim release];
239             }
240             else
241                 [anim setCurrentProgress:1.0 - [self alphaValue]];
242             self->animation = anim;
243             [self setDelegate: self];
244             [anim startAnimation];
245         }
246     }
247 }
248
249 - (void)orderFront: (id)sender animate: (BOOL)animate
250 {
251     NSViewAnimation *anim;
252     NSViewAnimation *current_anim;
253     NSMutableDictionary *dict;
254     
255     if (!animate || MACOS_VERSION < 10.4f)
256     {
257         [super orderFront: sender];
258         [self setAlphaValue: 1.0];
259         return;
260     }
261
262     if (![self isVisible])
263     {
264         [self setAlphaValue: 0.0];
265         [super orderFront: sender];
266     }
267     else if ([self alphaValue] == 1.0)
268     {
269         [super orderFront: self];
270         return;
271     }
272
273     dict = [[NSMutableDictionary alloc] initWithCapacity:2];
274
275     [dict setObject:self forKey:NSViewAnimationTargetKey];
276     
277     [dict setObject:NSViewAnimationFadeInEffect forKey:NSViewAnimationEffectKey];
278     anim = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict, nil]];
279     [dict release];
280     
281     [anim setAnimationBlockingMode:NSAnimationNonblocking];
282     [anim setDuration:0.5];
283     [anim setFrameRate:30];
284
285     @synchronized(self) {
286         current_anim = self->animation;
287
288         if ([[[current_anim viewAnimations] objectAtIndex:0] objectForKey: NSViewAnimationEffectKey] == NSViewAnimationFadeInEffect && [current_anim isAnimating])
289         {
290             [anim release];
291         }
292         else
293         {
294             if (current_anim)
295             {
296                 [current_anim stopAnimation];
297                 [anim setCurrentProgress:1.0 - [current_anim currentProgress]];
298                 [current_anim release];
299             }
300             else
301                 [anim setCurrentProgress:[self alphaValue]];
302             self->animation = anim;
303             [self setDelegate: self];
304             [self orderFront: sender];
305             [anim startAnimation];
306         }
307     }
308 }
309
310 - (void)animationDidEnd:(NSAnimation*)anim
311 {
312     if ([self alphaValue] <= 0.0)
313     {
314         NSInvocation * invoc;
315         [super orderOut: nil];
316         [self setAlphaValue: 1.0];
317         if ((invoc = [anim userInfo]))
318             [invoc invoke];
319     }
320 }
321 @end
322
323 /*****************************************************************************
324  * VLCControllerWindow
325  *****************************************************************************/
326
327 @implementation VLCControllerWindow
328
329 - (id)initWithContentRect:(NSRect)contentRect styleMask:(unsigned int)styleMask
330     backing:(NSBackingStoreType)backingType defer:(BOOL)flag
331 {
332     self = [super initWithContentRect:contentRect styleMask:styleMask //& ~NSTitledWindowMask
333     backing:backingType defer:flag];
334
335     [[VLCMain sharedInstance] updateTogglePlaylistState];
336
337     return( self );
338 }
339
340 - (BOOL)performKeyEquivalent:(NSEvent *)o_event
341 {
342     /* We indeed want to prioritize Cocoa key equivalent against libvlc,
343        so we perform the menu equivalent now. */
344     if([[NSApp mainMenu] performKeyEquivalent:o_event])
345         return TRUE;
346
347     return [[VLCMain sharedInstance] hasDefinedShortcutKey:o_event] ||
348            [(VLCControls *)[[VLCMain sharedInstance] getControls] keyEvent:o_event];
349 }
350
351 @end
352
353
354
355 /*****************************************************************************
356  * VLCControllerView
357  *****************************************************************************/
358
359 @implementation VLCControllerView
360
361 - (void)dealloc
362 {
363     [self unregisterDraggedTypes];
364     [super dealloc];
365 }
366
367 #if GC_ENABLED
368 - (void)finalize
369 {
370     /* dealloc doesn't get called on 10.5 if GC is enabled, so we need to provide the basic functionality here */
371     [self unregisterDraggedTypes];
372     [super finalize];
373 }
374 #endif
375
376 - (void)awakeFromNib
377 {
378     [self registerForDraggedTypes:[NSArray arrayWithObjects:NSTIFFPboardType, 
379         NSFilenamesPboardType, nil]];
380 }
381
382 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
383 {
384     if ((NSDragOperationGeneric & [sender draggingSourceOperationMask]) 
385                 == NSDragOperationGeneric)
386     {
387         return NSDragOperationGeneric;
388     }
389     else
390     {
391         return NSDragOperationNone;
392     }
393 }
394
395 - (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
396 {
397     return YES;
398 }
399
400 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
401 {
402     NSPasteboard *o_paste = [sender draggingPasteboard];
403     NSArray *o_types = [NSArray arrayWithObjects: NSFilenamesPboardType, nil];
404     NSString *o_desired_type = [o_paste availableTypeFromArray:o_types];
405     NSData *o_carried_data = [o_paste dataForType:o_desired_type];
406
407     if( o_carried_data )
408     {
409         if ([o_desired_type isEqualToString:NSFilenamesPboardType])
410         {
411             int i;
412             NSArray *o_array = [NSArray array];
413             NSArray *o_values = [[o_paste propertyListForType: NSFilenamesPboardType]
414                         sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
415
416             for( i = 0; i < (int)[o_values count]; i++)
417             {
418                 NSDictionary *o_dic;
419                 o_dic = [NSDictionary dictionaryWithObject:[o_values objectAtIndex:i] forKey:@"ITEM_URL"];
420                 o_array = [o_array arrayByAddingObject: o_dic];
421             }
422             [(VLCPlaylist *)[[VLCMain sharedInstance] getPlaylist] appendArray: o_array atPos: -1 enqueue:NO];
423             return YES;
424         }
425     }
426     [self setNeedsDisplay:YES];
427     return YES;
428 }
429
430 - (void)concludeDragOperation:(id <NSDraggingInfo>)sender
431 {
432     [self setNeedsDisplay:YES];
433 }
434
435 @end
436
437 /*****************************************************************************
438  * VLBrushedMetalImageView
439  *****************************************************************************/
440
441 @implementation VLBrushedMetalImageView
442
443 - (BOOL)mouseDownCanMoveWindow
444 {
445     return YES;
446 }
447
448 - (void)dealloc
449 {
450     [self unregisterDraggedTypes];
451     [super dealloc];
452 }
453
454 #if GC_ENABLED
455 - (void)finalize
456 {
457     /* dealloc doesn't get called on 10.5 if GC is enabled, so we need to provide the basic functionality here */
458     [self unregisterDraggedTypes];
459     [super finalize];
460 }
461 #endif
462
463 - (void)awakeFromNib
464 {
465     [self registerForDraggedTypes:[NSArray arrayWithObjects:NSTIFFPboardType, 
466         NSFilenamesPboardType, nil]];
467 }
468
469 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
470 {
471     if ((NSDragOperationGeneric & [sender draggingSourceOperationMask]) 
472                 == NSDragOperationGeneric)
473     {
474         return NSDragOperationGeneric;
475     }
476     else
477     {
478         return NSDragOperationNone;
479     }
480 }
481
482 - (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
483 {
484     return YES;
485 }
486
487 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
488 {
489     NSPasteboard *o_paste = [sender draggingPasteboard];
490     NSArray *o_types = [NSArray arrayWithObjects: NSFilenamesPboardType, nil];
491     NSString *o_desired_type = [o_paste availableTypeFromArray:o_types];
492     NSData *o_carried_data = [o_paste dataForType:o_desired_type];
493     BOOL b_autoplay = config_GetInt( VLCIntf, "macosx-autoplay" );
494
495     if( o_carried_data )
496     {
497         if ([o_desired_type isEqualToString:NSFilenamesPboardType])
498         {
499             int i;
500             NSArray *o_array = [NSArray array];
501             NSArray *o_values = [[o_paste propertyListForType: NSFilenamesPboardType]
502                         sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
503
504             for( i = 0; i < (int)[o_values count]; i++)
505             {
506                 NSDictionary *o_dic;
507                 o_dic = [NSDictionary dictionaryWithObject:[o_values objectAtIndex:i] forKey:@"ITEM_URL"];
508                 o_array = [o_array arrayByAddingObject: o_dic];
509             }
510             if( b_autoplay )
511                 [[[VLCMain sharedInstance] getPlaylist] appendArray: o_array atPos: -1 enqueue:NO];
512             else
513                 [[[VLCMain sharedInstance] getPlaylist] appendArray: o_array atPos: -1 enqueue:YES];
514             return YES;
515         }
516     }
517     [self setNeedsDisplay:YES];
518     return YES;
519 }
520
521 - (void)concludeDragOperation:(id <NSDraggingInfo>)sender
522 {
523     [self setNeedsDisplay:YES];
524 }
525
526 @end
527
528
529 /*****************************************************************************
530  * MPSlider
531  *****************************************************************************/
532 @implementation MPSlider
533
534 void _drawKnobInRect(NSRect knobRect)
535 {
536     // Center knob in given rect
537     knobRect.origin.x += (int)((float)(knobRect.size.width - 7)/2.0);
538     knobRect.origin.y += (int)((float)(knobRect.size.height - 7)/2.0);
539     
540     // Draw diamond
541     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 3, knobRect.origin.y + 6, 1, 1), NSCompositeSourceOver);
542     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 2, knobRect.origin.y + 5, 3, 1), NSCompositeSourceOver);
543     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 1, knobRect.origin.y + 4, 5, 1), NSCompositeSourceOver);
544     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 0, knobRect.origin.y + 3, 7, 1), NSCompositeSourceOver);
545     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 1, knobRect.origin.y + 2, 5, 1), NSCompositeSourceOver);
546     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 2, knobRect.origin.y + 1, 3, 1), NSCompositeSourceOver);
547     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 3, knobRect.origin.y + 0, 1, 1), NSCompositeSourceOver);
548 }
549
550 void _drawFrameInRect(NSRect frameRect)
551 {
552     // Draw frame
553     NSRectFillUsingOperation(NSMakeRect(frameRect.origin.x, frameRect.origin.y, frameRect.size.width, 1), NSCompositeSourceOver);
554     NSRectFillUsingOperation(NSMakeRect(frameRect.origin.x, frameRect.origin.y + frameRect.size.height-1, frameRect.size.width, 1), NSCompositeSourceOver);
555     NSRectFillUsingOperation(NSMakeRect(frameRect.origin.x, frameRect.origin.y, 1, frameRect.size.height), NSCompositeSourceOver);
556     NSRectFillUsingOperation(NSMakeRect(frameRect.origin.x+frameRect.size.width-1, frameRect.origin.y, 1, frameRect.size.height), NSCompositeSourceOver);
557 }
558
559 - (void)drawRect:(NSRect)rect
560 {
561     // Draw default to make sure the slider behaves correctly
562     [[NSGraphicsContext currentContext] saveGraphicsState];
563     NSRectClip(NSZeroRect);
564     [super drawRect:rect];
565     [[NSGraphicsContext currentContext] restoreGraphicsState];
566     
567     // Full size
568     rect = [self bounds];
569     int diff = (int)(([[self cell] knobThickness] - 7.0)/2.0) - 1;
570     rect.origin.x += diff-1;
571     rect.origin.y += diff;
572     rect.size.width -= 2*diff-2;
573     rect.size.height -= 2*diff;
574     
575     // Draw dark
576     NSRect knobRect = [[self cell] knobRectFlipped:NO];
577     [[[NSColor blackColor] colorWithAlphaComponent:0.6] set];
578     _drawFrameInRect(rect);
579     _drawKnobInRect(knobRect);
580     
581     // Draw shadow
582     [[[NSColor blackColor] colorWithAlphaComponent:0.1] set];
583     rect.origin.x++;
584     rect.origin.y++;
585     knobRect.origin.x++;
586     knobRect.origin.y++;
587     _drawFrameInRect(rect);
588     _drawKnobInRect(knobRect);
589 }
590
591 @end
592
593
594 /*****************************************************************************
595  * ITSlider
596  *****************************************************************************/
597
598 @implementation ITSlider
599
600 - (void)awakeFromNib
601 {
602     if ([[self cell] class] != [ITSliderCell class]) {
603         // replace cell
604         NSSliderCell *oldCell = [self cell];
605         NSSliderCell *newCell = [[[ITSliderCell alloc] init] autorelease];
606         [newCell setTag:[oldCell tag]];
607         [newCell setTarget:[oldCell target]];
608         [newCell setAction:[oldCell action]];
609         [newCell setControlSize:[oldCell controlSize]];
610         [newCell setType:[oldCell type]];
611         [newCell setState:[oldCell state]]; 
612         [newCell setAllowsTickMarkValuesOnly:[oldCell allowsTickMarkValuesOnly]];
613         [newCell setAltIncrementValue:[oldCell altIncrementValue]];
614         [newCell setControlTint:[oldCell controlTint]];
615         [newCell setKnobThickness:[oldCell knobThickness]];
616         [newCell setMaxValue:[oldCell maxValue]];
617         [newCell setMinValue:[oldCell minValue]];
618         [newCell setDoubleValue:[oldCell doubleValue]];
619         [newCell setNumberOfTickMarks:[oldCell numberOfTickMarks]];
620         [newCell setEditable:[oldCell isEditable]];
621         [newCell setEnabled:[oldCell isEnabled]];
622         [newCell setEntryType:[oldCell entryType]];
623         [newCell setHighlighted:[oldCell isHighlighted]];
624         [newCell setTickMarkPosition:[oldCell tickMarkPosition]];
625         [self setCell:newCell];
626     }
627 }
628
629 @end
630
631 /*****************************************************************************
632  * ITSliderCell
633  *****************************************************************************/
634 @implementation ITSliderCell
635
636 - (id)init
637 {
638     self = [super init];
639     _knobOff = [[NSImage imageNamed:@"volumeslider_normal"] retain];
640     _knobOn = [[NSImage imageNamed:@"volumeslider_blue"] retain];
641     b_mouse_down = FALSE;
642     return self;
643 }
644
645 - (void)dealloc
646 {
647     [_knobOff release];
648     [_knobOn release];
649     [super dealloc];
650 }
651
652 - (void)drawKnob:(NSRect)knob_rect
653 {
654     NSImage *knob;
655
656     if( b_mouse_down )
657         knob = _knobOn;
658     else
659         knob = _knobOff;
660
661     [[self controlView] lockFocus];
662     [knob compositeToPoint:NSMakePoint( knob_rect.origin.x + 1,
663         knob_rect.origin.y + knob_rect.size.height -2 )  
664         operation:NSCompositeSourceOver];
665     [[self controlView] unlockFocus];
666 }
667
668 - (void)stopTracking:(NSPoint)lastPoint at:(NSPoint)stopPoint inView: 
669         (NSView *)controlView mouseIsUp:(BOOL)flag
670 {
671     b_mouse_down = NO;
672     [self drawKnob];
673     [super stopTracking:lastPoint at:stopPoint inView:controlView mouseIsUp:flag];
674 }
675
676 - (BOOL)startTrackingAt:(NSPoint)startPoint inView:(NSView *)controlView
677 {
678     b_mouse_down = YES;
679     [self drawKnob];
680     return [super startTrackingAt:startPoint inView:controlView];
681 }
682
683 @end
684