f72d0020a6894add756532a54d49fe3b966ca06d
[vlc.git] / modules / gui / macosx / misc.m
1 /*****************************************************************************
2  * misc.m: code not specific to vlc
3  *****************************************************************************
4  * Copyright (C) 2003-2015 VLC authors and VideoLAN
5  * $Id$
6  *
7  * Authors: Jon Lech Johansen <jon-vl@nanocrew.net>
8  *          Felix Paul K├╝hne <fkuehne at videolan dot 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 "CompatibilityFixes.h"
26 #import "misc.h"
27 #import "VLCMain.h"                                          /* VLCApplication */
28 #import "VLCMainWindow.h"
29 #import "VLCMainMenu.h"
30 #import "VLCControlsBarCommon.h"
31 #import "VLCCoreInteraction.h"
32 #import <vlc_keys.h>
33
34 /*****************************************************************************
35  * VLCDragDropView
36  *****************************************************************************/
37
38 @implementation VLCDropDisabledImageView
39
40 - (void)awakeFromNib
41 {
42     [self unregisterDraggedTypes];
43 }
44
45 @end
46
47 /*****************************************************************************
48  * VLCDragDropView
49  *****************************************************************************/
50
51 @interface VLCDragDropView()
52 {
53     bool b_activeDragAndDrop;
54 }
55 @end
56
57 @implementation VLCDragDropView
58
59 - (id)initWithFrame:(NSRect)frame
60 {
61     self = [super initWithFrame:frame];
62     if (self) {
63         // default value
64         [self setDrawBorder:YES];
65     }
66
67     return self;
68 }
69
70 - (void)enablePlaylistItems
71 {
72     [self registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, @"VLCPlaylistItemPboardType", nil]];
73 }
74
75 - (BOOL)mouseDownCanMoveWindow
76 {
77     return YES;
78 }
79
80 - (void)dealloc
81 {
82     [self unregisterDraggedTypes];
83 }
84
85 - (void)awakeFromNib
86 {
87     [self registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]];
88 }
89
90 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
91 {
92     if ((NSDragOperationGeneric & [sender draggingSourceOperationMask]) == NSDragOperationGeneric) {
93         b_activeDragAndDrop = YES;
94         [self setNeedsDisplay:YES];
95
96         return NSDragOperationCopy;
97     }
98
99     return NSDragOperationNone;
100 }
101
102 - (void)draggingEnded:(id < NSDraggingInfo >)sender
103 {
104     b_activeDragAndDrop = NO;
105     [self setNeedsDisplay:YES];
106 }
107
108 - (void)draggingExited:(id < NSDraggingInfo >)sender
109 {
110     b_activeDragAndDrop = NO;
111     [self setNeedsDisplay:YES];
112 }
113
114 - (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
115 {
116     return YES;
117 }
118
119 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
120 {
121     BOOL b_returned;
122
123     if (_dropHandler && [_dropHandler respondsToSelector:@selector(performDragOperation:)])
124         b_returned = [_dropHandler performDragOperation:sender];
125     else // default
126         b_returned = [[VLCCoreInteraction sharedInstance] performDragOperation:sender];
127
128     [self setNeedsDisplay:YES];
129     return b_returned;
130 }
131
132 - (void)concludeDragOperation:(id <NSDraggingInfo>)sender
133 {
134     [self setNeedsDisplay:YES];
135 }
136
137 - (void)drawRect:(NSRect)dirtyRect
138 {
139     if ([self drawBorder] && b_activeDragAndDrop) {
140         NSRect frameRect = [self bounds];
141
142         [[NSColor selectedControlColor] set];
143         NSFrameRectWithWidthUsingOperation(frameRect, 2., NSCompositeSourceOver);
144     }
145
146     [super drawRect:dirtyRect];
147 }
148
149 @end
150
151
152 /*****************************************************************************
153  * VLCVolumeSliderCommon
154  *****************************************************************************/
155
156 @implementation VLCVolumeSliderCommon : NSSlider
157
158 - (void)scrollWheel:(NSEvent *)o_event
159 {
160     BOOL b_up = NO;
161     CGFloat f_deltaY = [o_event deltaY];
162     CGFloat f_deltaX = [o_event deltaX];
163
164     if ([o_event isDirectionInvertedFromDevice])
165         f_deltaX = -f_deltaX; // optimisation, actually double invertion of f_deltaY here
166     else
167         f_deltaY = -f_deltaY;
168
169     // positive for left / down, negative otherwise
170     CGFloat f_delta = f_deltaX + f_deltaY;
171     CGFloat f_abs;
172
173     if (f_delta > 0.0f)
174         f_abs = f_delta;
175     else {
176         b_up = YES;
177         f_abs = -f_delta;
178     }
179
180     for (NSUInteger i = 0; i < (int)(f_abs/4.+1.) && f_abs > 0.05 ; i++) {
181         if (b_up)
182             [[VLCCoreInteraction sharedInstance] volumeUp];
183         else
184             [[VLCCoreInteraction sharedInstance] volumeDown];
185     }
186 }
187
188 - (void)drawFullVolumeMarker
189 {
190     CGFloat maxAudioVol = self.maxValue / AOUT_VOLUME_DEFAULT;
191     if (maxAudioVol < 1.)
192         return;
193
194     NSColor *drawingColor;
195     // for bright artwork, a black color is used and vice versa
196     if (_usesBrightArtwork)
197         drawingColor = [[NSColor blackColor] colorWithAlphaComponent:.4];
198     else
199         drawingColor = [[NSColor whiteColor] colorWithAlphaComponent:.4];
200
201     NSBezierPath* bezierPath = [NSBezierPath bezierPath];
202     [self drawFullVolBezierPath:bezierPath];
203     [bezierPath closePath];
204
205     bezierPath.lineWidth = 1.;
206     [drawingColor setStroke];
207     [bezierPath stroke];
208 }
209
210 - (CGFloat)fullVolumePos
211 {
212     CGFloat maxAudioVol = self.maxValue / AOUT_VOLUME_DEFAULT;
213     CGFloat sliderRange = [self frame].size.width - [self knobThickness];
214     CGFloat sliderOrigin = [self knobThickness] / 2.;
215
216     return 1. / maxAudioVol * sliderRange + sliderOrigin;
217 }
218
219 - (void)drawFullVolBezierPath:(NSBezierPath*)bezierPath
220 {
221     CGFloat fullVolPos = [self fullVolumePos];
222     [bezierPath moveToPoint:NSMakePoint(fullVolPos, [self frame].size.height - 3.)];
223     [bezierPath lineToPoint:NSMakePoint(fullVolPos, 2.)];
224 }
225
226 @end
227
228 @implementation VolumeSliderCell
229
230 - (BOOL)continueTracking:(NSPoint)lastPoint at:(NSPoint)currentPoint inView:(NSView *)controlView
231 {
232     VLCVolumeSliderCommon *o_slider = (VLCVolumeSliderCommon *)controlView;
233     CGFloat fullVolumePos = [o_slider fullVolumePos] + 2.;
234
235     CGPoint snapToPoint = currentPoint;
236     if (ABS(fullVolumePos - currentPoint.x) <= 4.)
237         snapToPoint.x = fullVolumePos;
238
239     return [super continueTracking:lastPoint at:snapToPoint inView:controlView];
240 }
241
242 @end
243
244 /*****************************************************************************
245  * ITSlider
246  *****************************************************************************/
247
248 @interface ITSlider()
249 {
250     NSImage *img;
251     NSRect image_rect;
252 }
253 @end
254
255 @implementation ITSlider
256
257 - (void)awakeFromNib
258 {
259     BOOL b_dark = config_GetInt( getIntf(), "macosx-interfacestyle" );
260     if (b_dark)
261         img = imageFromRes(@"volume-slider-knob_dark");
262     else
263         img = imageFromRes(@"volume-slider-knob");
264
265     image_rect.size = [img size];
266     image_rect.origin.x = 0;
267
268     if (b_dark)
269         image_rect.origin.y = -1;
270     else
271         image_rect.origin.y = 0;
272 }
273
274 - (void)drawKnobInRect:(NSRect)knobRect
275 {
276     knobRect.origin.x += (knobRect.size.width - image_rect.size.width) / 2;
277     knobRect.size.width = image_rect.size.width;
278     knobRect.size.height = image_rect.size.height;
279     [img drawInRect:knobRect fromRect:image_rect operation:NSCompositeSourceOver fraction:1];
280 }
281
282 - (void)drawRect:(NSRect)rect
283 {
284     /* Draw default to make sure the slider behaves correctly */
285     [[NSGraphicsContext currentContext] saveGraphicsState];
286     NSRectClip(NSZeroRect);
287     [super drawRect:rect];
288     [[NSGraphicsContext currentContext] restoreGraphicsState];
289
290     [self drawFullVolumeMarker];
291
292     NSRect knobRect = [[self cell] knobRectFlipped:NO];
293     knobRect.origin.y+=2;
294     [self drawKnobInRect: knobRect];
295 }
296
297 @end
298
299 /*****************************************************************************
300  * VLCMainWindowSplitView implementation
301  *****************************************************************************/
302 @implementation VLCMainWindowSplitView : NSSplitView
303
304 // Custom color for the dividers
305 - (NSColor *)dividerColor
306 {
307     return [NSColor colorWithCalibratedRed:.60 green:.60 blue:.60 alpha:1.];
308 }
309
310 // Custom thickness for the divider
311 - (CGFloat)dividerThickness
312 {
313     return 1.0;
314 }
315
316 @end
317
318 /*****************************************************************************
319  * VLCThreePartImageView interface
320  *****************************************************************************/
321
322 @interface VLCThreePartImageView()
323 {
324     NSImage *_left_img;
325     NSImage *_middle_img;
326     NSImage *_right_img;
327 }
328 @end
329
330 @implementation VLCThreePartImageView
331
332 - (void)setImagesLeft:(NSImage *)left middle: (NSImage *)middle right:(NSImage *)right
333 {
334     _left_img = left;
335     _middle_img = middle;
336     _right_img = right;
337 }
338
339 - (void)drawRect:(NSRect)rect
340 {
341     NSRect bnds = [self bounds];
342     NSDrawThreePartImage( bnds, _left_img, _middle_img, _right_img, NO, NSCompositeSourceOver, 1, NO );
343 }
344
345 @end
346
347 @interface PositionFormatter()
348 {
349     NSCharacterSet *o_forbidden_characters;
350 }
351 @end
352
353 @implementation PositionFormatter
354
355 - (id)init
356 {
357     self = [super init];
358     NSMutableCharacterSet *nonNumbers = [[[NSCharacterSet decimalDigitCharacterSet] invertedSet] mutableCopy];
359     [nonNumbers removeCharactersInString:@"-:"];
360     o_forbidden_characters = [nonNumbers copy];
361
362     return self;
363 }
364
365 - (NSString*)stringForObjectValue:(id)obj
366 {
367     if([obj isKindOfClass:[NSString class]])
368         return obj;
369     if([obj isKindOfClass:[NSNumber class]])
370         return [obj stringValue];
371
372     return nil;
373 }
374
375 - (BOOL)getObjectValue:(id*)obj forString:(NSString*)string errorDescription:(NSString**)error
376 {
377     *obj = [string copy];
378     return YES;
379 }
380
381 - (BOOL)isPartialStringValid:(NSString*)partialString newEditingString:(NSString**)newString errorDescription:(NSString**)error
382 {
383     if ([partialString rangeOfCharacterFromSet:o_forbidden_characters options:NSLiteralSearch].location != NSNotFound) {
384         return NO;
385     } else {
386         return YES;
387     }
388 }
389
390 @end
391
392 @implementation NSView (EnableSubviews)
393
394 - (void)enableSubviews:(BOOL)b_enable
395 {
396     for (NSView *o_view in [self subviews]) {
397         [o_view enableSubviews:b_enable];
398
399         // enable NSControl
400         if ([o_view respondsToSelector:@selector(setEnabled:)]) {
401             [(NSControl *)o_view setEnabled:b_enable];
402         }
403         // also "enable / disable" text views
404         if ([o_view respondsToSelector:@selector(setTextColor:)]) {
405             if (b_enable == NO) {
406                 [(NSTextField *)o_view setTextColor:[NSColor disabledControlTextColor]];
407             } else {
408                 [(NSTextField *)o_view setTextColor:[NSColor controlTextColor]];
409             }
410         }
411
412     }
413 }
414
415 @end
416
417 /*****************************************************************************
418  * VLCByteCountFormatter addition
419  *****************************************************************************/
420
421 @implementation VLCByteCountFormatter
422
423 + (NSString *)stringFromByteCount:(long long)byteCount countStyle:(NSByteCountFormatterCountStyle)countStyle
424 {
425     // Use native implementation on >= mountain lion
426     Class byteFormatterClass = NSClassFromString(@"NSByteCountFormatter");
427     if (byteFormatterClass && [byteFormatterClass respondsToSelector:@selector(stringFromByteCount:countStyle:)]) {
428         return [byteFormatterClass stringFromByteCount:byteCount countStyle:NSByteCountFormatterCountStyleFile];
429     }
430
431     float devider = 0.;
432     float returnValue = 0.;
433     NSString *suffix;
434
435     NSNumberFormatter *theFormatter = [[NSNumberFormatter alloc] init];
436     [theFormatter setLocale:[NSLocale currentLocale]];
437     [theFormatter setAllowsFloats:YES];
438
439     NSString *returnString = @"";
440
441     if (countStyle != NSByteCountFormatterCountStyleDecimal)
442         devider = 1024.;
443     else
444         devider = 1000.;
445
446     if (byteCount < 1000) {
447         returnValue = byteCount;
448         suffix = _NS("B");
449         [theFormatter setMaximumFractionDigits:0];
450         goto end;
451     }
452
453     if (byteCount < 1000000) {
454         returnValue = byteCount / devider;
455         suffix = _NS("KB");
456         [theFormatter setMaximumFractionDigits:0];
457         goto end;
458     }
459
460     if (byteCount < 1000000000) {
461         returnValue = byteCount / devider / devider;
462         suffix = _NS("MB");
463         [theFormatter setMaximumFractionDigits:1];
464         goto end;
465     }
466
467     [theFormatter setMaximumFractionDigits:2];
468     if (byteCount < 1000000000000) {
469         returnValue = byteCount / devider / devider / devider;
470         suffix = _NS("GB");
471         goto end;
472     }
473
474     returnValue = byteCount / devider / devider / devider / devider;
475     suffix = _NS("TB");
476
477 end:
478     returnString = [NSString stringWithFormat:@"%@ %@", [theFormatter stringFromNumber:[NSNumber numberWithFloat:returnValue]], suffix];
479
480     return returnString;
481 }
482
483 @end