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