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