macOS: Move VLCTimeField out of misc
[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  * MPSlider
154  *****************************************************************************/
155 @implementation MPSlider
156
157 void _drawKnobInRect(NSRect knobRect)
158 {
159     // Center knob in given rect
160     knobRect.origin.x += (int)((float)(knobRect.size.width - 7)/2.0);
161     knobRect.origin.y += (int)((float)(knobRect.size.height - 7)/2.0);
162
163     // Draw diamond
164     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 3, knobRect.origin.y + 6, 1, 1), NSCompositeSourceOver);
165     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 2, knobRect.origin.y + 5, 3, 1), NSCompositeSourceOver);
166     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 1, knobRect.origin.y + 4, 5, 1), NSCompositeSourceOver);
167     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 0, knobRect.origin.y + 3, 7, 1), NSCompositeSourceOver);
168     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 1, knobRect.origin.y + 2, 5, 1), NSCompositeSourceOver);
169     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 2, knobRect.origin.y + 1, 3, 1), NSCompositeSourceOver);
170     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 3, knobRect.origin.y + 0, 1, 1), NSCompositeSourceOver);
171 }
172
173 void _drawFrameInRect(NSRect frameRect)
174 {
175     // Draw frame
176     NSRectFillUsingOperation(NSMakeRect(frameRect.origin.x, frameRect.origin.y, frameRect.size.width, 1), NSCompositeSourceOver);
177     NSRectFillUsingOperation(NSMakeRect(frameRect.origin.x, frameRect.origin.y + frameRect.size.height-1, frameRect.size.width, 1), NSCompositeSourceOver);
178     NSRectFillUsingOperation(NSMakeRect(frameRect.origin.x, frameRect.origin.y, 1, frameRect.size.height), NSCompositeSourceOver);
179     NSRectFillUsingOperation(NSMakeRect(frameRect.origin.x+frameRect.size.width-1, frameRect.origin.y, 1, frameRect.size.height), NSCompositeSourceOver);
180 }
181
182 - (void)drawRect:(NSRect)rect
183 {
184     // Draw default to make sure the slider behaves correctly
185     [[NSGraphicsContext currentContext] saveGraphicsState];
186     NSRectClip(NSZeroRect);
187     [super drawRect:rect];
188     [[NSGraphicsContext currentContext] restoreGraphicsState];
189
190     // Full size
191     rect = [self bounds];
192     int diff = (int)(([[self cell] knobThickness] - 7.0)/2.0) - 1;
193     rect.origin.x += diff-1;
194     rect.origin.y += diff;
195     rect.size.width -= 2*diff-2;
196     rect.size.height -= 2*diff;
197
198     // Draw dark
199     NSRect knobRect = [[self cell] knobRectFlipped:NO];
200     [[[NSColor blackColor] colorWithAlphaComponent:0.6] set];
201     _drawFrameInRect(rect);
202     _drawKnobInRect(knobRect);
203
204     // Draw shadow
205     [[[NSColor blackColor] colorWithAlphaComponent:0.1] set];
206     rect.origin.x++;
207     rect.origin.y++;
208     knobRect.origin.x++;
209     knobRect.origin.y++;
210     _drawFrameInRect(rect);
211     _drawKnobInRect(knobRect);
212 }
213
214 @end
215
216 /*****************************************************************************
217  * ProgressView
218  *****************************************************************************/
219
220 @implementation VLCProgressView : NSView
221
222 - (void)scrollWheel:(NSEvent *)o_event
223 {
224     BOOL b_forward = NO;
225     CGFloat f_deltaY = [o_event deltaY];
226     CGFloat f_deltaX = [o_event deltaX];
227
228     if ([o_event isDirectionInvertedFromDevice])
229         f_deltaX = -f_deltaX; // optimisation, actually double invertion of f_deltaY here
230     else
231         f_deltaY = -f_deltaY;
232
233     // positive for left / down, negative otherwise
234     CGFloat f_delta = f_deltaX + f_deltaY;
235     CGFloat f_abs;
236     int i_vlckey;
237
238     if (f_delta > 0.0f)
239         f_abs = f_delta;
240     else {
241         b_forward = YES;
242         f_abs = -f_delta;
243     }
244
245     for (NSUInteger i = 0; i < (int)(f_abs/4.+1.) && f_abs > 0.05 ; i++) {
246         if (b_forward)
247             [[VLCCoreInteraction sharedInstance] forwardExtraShort];
248         else
249             [[VLCCoreInteraction sharedInstance] backwardExtraShort];
250     }
251 }
252
253 - (BOOL)acceptsFirstResponder
254 {
255     return YES;
256 }
257
258 @end
259
260 /*****************************************************************************
261  * TimeLineSlider
262  *****************************************************************************/
263
264 @interface TimeLineSlider()
265 {
266     NSImage *o_knob_img;
267     NSRect img_rect;
268     BOOL b_dark;
269 }
270 @end
271
272 @implementation TimeLineSlider
273
274 - (void)awakeFromNib
275 {
276     if (config_GetInt( getIntf(), "macosx-interfacestyle" )) {
277         o_knob_img = imageFromRes(@"progression-knob_dark");
278         b_dark = YES;
279     } else {
280         o_knob_img = imageFromRes(@"progression-knob");
281         b_dark = NO;
282     }
283     img_rect.size = [o_knob_img size];
284     img_rect.origin.x = img_rect.origin.y = 0;
285 }
286
287 - (CGFloat)knobPosition
288 {
289     NSRect knobRect = [[self cell] knobRectFlipped:NO];
290     knobRect.origin.x += knobRect.size.width / 2;
291     return knobRect.origin.x;
292 }
293
294 - (void)drawKnobInRect:(NSRect)knobRect
295 {
296     knobRect.origin.x += (knobRect.size.width - img_rect.size.width) / 2;
297     knobRect.size.width = img_rect.size.width;
298     knobRect.size.height = img_rect.size.height;
299     [o_knob_img drawInRect:knobRect fromRect:img_rect operation:NSCompositeSourceOver fraction:1];
300 }
301
302 - (void)drawRect:(NSRect)rect
303 {
304     [[(VLCVideoWindowCommon *)[self window] controlsBar] drawFancyGradientEffectForTimeSlider];
305     msleep(10000); //wait for the gradient to draw completely
306
307     /* Draw default to make sure the slider behaves correctly */
308     [[NSGraphicsContext currentContext] saveGraphicsState];
309     NSRectClip(NSZeroRect);
310     [super drawRect:rect];
311     [[NSGraphicsContext currentContext] restoreGraphicsState];
312
313     NSRect knobRect = [[self cell] knobRectFlipped:NO];
314     knobRect.origin.y+=1;
315     [self drawKnobInRect: knobRect];
316 }
317
318 @end
319
320 /*****************************************************************************
321  * VLCVolumeSliderCommon
322  *****************************************************************************/
323
324 @implementation VLCVolumeSliderCommon : NSSlider
325
326 - (void)scrollWheel:(NSEvent *)o_event
327 {
328     BOOL b_up = NO;
329     CGFloat f_deltaY = [o_event deltaY];
330     CGFloat f_deltaX = [o_event deltaX];
331
332     if ([o_event isDirectionInvertedFromDevice])
333         f_deltaX = -f_deltaX; // optimisation, actually double invertion of f_deltaY here
334     else
335         f_deltaY = -f_deltaY;
336
337     // positive for left / down, negative otherwise
338     CGFloat f_delta = f_deltaX + f_deltaY;
339     CGFloat f_abs;
340
341     if (f_delta > 0.0f)
342         f_abs = f_delta;
343     else {
344         b_up = YES;
345         f_abs = -f_delta;
346     }
347
348     for (NSUInteger i = 0; i < (int)(f_abs/4.+1.) && f_abs > 0.05 ; i++) {
349         if (b_up)
350             [[VLCCoreInteraction sharedInstance] volumeUp];
351         else
352             [[VLCCoreInteraction sharedInstance] volumeDown];
353     }
354 }
355
356 - (void)drawFullVolumeMarker
357 {
358     CGFloat maxAudioVol = self.maxValue / AOUT_VOLUME_DEFAULT;
359     if (maxAudioVol < 1.)
360         return;
361
362     NSColor *drawingColor;
363     // for bright artwork, a black color is used and vice versa
364     if (_usesBrightArtwork)
365         drawingColor = [[NSColor blackColor] colorWithAlphaComponent:.4];
366     else
367         drawingColor = [[NSColor whiteColor] colorWithAlphaComponent:.4];
368
369     NSBezierPath* bezierPath = [NSBezierPath bezierPath];
370     [self drawFullVolBezierPath:bezierPath];
371     [bezierPath closePath];
372
373     bezierPath.lineWidth = 1.;
374     [drawingColor setStroke];
375     [bezierPath stroke];
376 }
377
378 - (CGFloat)fullVolumePos
379 {
380     CGFloat maxAudioVol = self.maxValue / AOUT_VOLUME_DEFAULT;
381     CGFloat sliderRange = [self frame].size.width - [self knobThickness];
382     CGFloat sliderOrigin = [self knobThickness] / 2.;
383
384     return 1. / maxAudioVol * sliderRange + sliderOrigin;
385 }
386
387 - (void)drawFullVolBezierPath:(NSBezierPath*)bezierPath
388 {
389     CGFloat fullVolPos = [self fullVolumePos];
390     [bezierPath moveToPoint:NSMakePoint(fullVolPos, [self frame].size.height - 3.)];
391     [bezierPath lineToPoint:NSMakePoint(fullVolPos, 2.)];
392 }
393
394 @end
395
396 @implementation VolumeSliderCell
397
398 - (BOOL)continueTracking:(NSPoint)lastPoint at:(NSPoint)currentPoint inView:(NSView *)controlView
399 {
400     VLCVolumeSliderCommon *o_slider = (VLCVolumeSliderCommon *)controlView;
401     CGFloat fullVolumePos = [o_slider fullVolumePos] + 2.;
402
403     CGPoint snapToPoint = currentPoint;
404     if (ABS(fullVolumePos - currentPoint.x) <= 4.)
405         snapToPoint.x = fullVolumePos;
406
407     return [super continueTracking:lastPoint at:snapToPoint inView:controlView];
408 }
409
410 @end
411
412 /*****************************************************************************
413  * ITSlider
414  *****************************************************************************/
415
416 @interface ITSlider()
417 {
418     NSImage *img;
419     NSRect image_rect;
420 }
421 @end
422
423 @implementation ITSlider
424
425 - (void)awakeFromNib
426 {
427     BOOL b_dark = config_GetInt( getIntf(), "macosx-interfacestyle" );
428     if (b_dark)
429         img = imageFromRes(@"volume-slider-knob_dark");
430     else
431         img = imageFromRes(@"volume-slider-knob");
432
433     image_rect.size = [img size];
434     image_rect.origin.x = 0;
435
436     if (b_dark)
437         image_rect.origin.y = -1;
438     else
439         image_rect.origin.y = 0;
440 }
441
442 - (void)drawKnobInRect:(NSRect)knobRect
443 {
444     knobRect.origin.x += (knobRect.size.width - image_rect.size.width) / 2;
445     knobRect.size.width = image_rect.size.width;
446     knobRect.size.height = image_rect.size.height;
447     [img drawInRect:knobRect fromRect:image_rect operation:NSCompositeSourceOver fraction:1];
448 }
449
450 - (void)drawRect:(NSRect)rect
451 {
452     /* Draw default to make sure the slider behaves correctly */
453     [[NSGraphicsContext currentContext] saveGraphicsState];
454     NSRectClip(NSZeroRect);
455     [super drawRect:rect];
456     [[NSGraphicsContext currentContext] restoreGraphicsState];
457
458     [self drawFullVolumeMarker];
459
460     NSRect knobRect = [[self cell] knobRectFlipped:NO];
461     knobRect.origin.y+=2;
462     [self drawKnobInRect: knobRect];
463 }
464
465 @end
466
467 /*****************************************************************************
468  * VLCMainWindowSplitView implementation
469  * comment 1 + 2 taken from NSSplitView.h (10.7 SDK)
470  *****************************************************************************/
471 @implementation VLCMainWindowSplitView : NSSplitView
472 /* Return the color of the dividers that the split view is drawing between subviews. The default implementation of this method returns [NSColor clearColor] for the thick divider style. It will also return [NSColor clearColor] for the thin divider style when the split view is in a textured window. All other thin dividers are drawn with a color that looks good between two white panes. You can override this method to change the color of dividers.
473  */
474 - (NSColor *)dividerColor
475 {
476     return [NSColor colorWithCalibratedRed:.60 green:.60 blue:.60 alpha:1.];
477 }
478
479 /* Return the thickness of the dividers that the split view is drawing between subviews. The default implementation returns a value that depends on the divider style. You can override this method to change the size of dividers.
480  */
481 - (CGFloat)dividerThickness
482 {
483     return 1.0;
484 }
485 @end
486
487 /*****************************************************************************
488  * VLCThreePartImageView interface
489  *****************************************************************************/
490
491 @interface VLCThreePartImageView()
492 {
493     NSImage *_left_img;
494     NSImage *_middle_img;
495     NSImage *_right_img;
496 }
497 @end
498
499 @implementation VLCThreePartImageView
500
501 - (void)setImagesLeft:(NSImage *)left middle: (NSImage *)middle right:(NSImage *)right
502 {
503     _left_img = left;
504     _middle_img = middle;
505     _right_img = right;
506 }
507
508 - (void)drawRect:(NSRect)rect
509 {
510     NSRect bnds = [self bounds];
511     NSDrawThreePartImage( bnds, _left_img, _middle_img, _right_img, NO, NSCompositeSourceOver, 1, NO );
512 }
513
514 @end
515
516 @interface PositionFormatter()
517 {
518     NSCharacterSet *o_forbidden_characters;
519 }
520 @end
521
522 @implementation PositionFormatter
523
524 - (id)init
525 {
526     self = [super init];
527     NSMutableCharacterSet *nonNumbers = [[[NSCharacterSet decimalDigitCharacterSet] invertedSet] mutableCopy];
528     [nonNumbers removeCharactersInString:@"-:"];
529     o_forbidden_characters = [nonNumbers copy];
530
531     return self;
532 }
533
534 - (NSString*)stringForObjectValue:(id)obj
535 {
536     if([obj isKindOfClass:[NSString class]])
537         return obj;
538     if([obj isKindOfClass:[NSNumber class]])
539         return [obj stringValue];
540
541     return nil;
542 }
543
544 - (BOOL)getObjectValue:(id*)obj forString:(NSString*)string errorDescription:(NSString**)error
545 {
546     *obj = [string copy];
547     return YES;
548 }
549
550 - (BOOL)isPartialStringValid:(NSString*)partialString newEditingString:(NSString**)newString errorDescription:(NSString**)error
551 {
552     if ([partialString rangeOfCharacterFromSet:o_forbidden_characters options:NSLiteralSearch].location != NSNotFound) {
553         return NO;
554     } else {
555         return YES;
556     }
557 }
558
559 @end
560
561 @implementation NSView (EnableSubviews)
562
563 - (void)enableSubviews:(BOOL)b_enable
564 {
565     for (NSView *o_view in [self subviews]) {
566         [o_view enableSubviews:b_enable];
567
568         // enable NSControl
569         if ([o_view respondsToSelector:@selector(setEnabled:)]) {
570             [(NSControl *)o_view setEnabled:b_enable];
571         }
572         // also "enable / disable" text views
573         if ([o_view respondsToSelector:@selector(setTextColor:)]) {
574             if (b_enable == NO) {
575                 [(NSTextField *)o_view setTextColor:[NSColor disabledControlTextColor]];
576             } else {
577                 [(NSTextField *)o_view setTextColor:[NSColor controlTextColor]];
578             }
579         }
580
581     }
582 }
583
584 @end
585
586 /*****************************************************************************
587  * VLCByteCountFormatter addition
588  *****************************************************************************/
589
590 @implementation VLCByteCountFormatter
591
592 + (NSString *)stringFromByteCount:(long long)byteCount countStyle:(NSByteCountFormatterCountStyle)countStyle
593 {
594     // Use native implementation on >= mountain lion
595     Class byteFormatterClass = NSClassFromString(@"NSByteCountFormatter");
596     if (byteFormatterClass && [byteFormatterClass respondsToSelector:@selector(stringFromByteCount:countStyle:)]) {
597         return [byteFormatterClass stringFromByteCount:byteCount countStyle:NSByteCountFormatterCountStyleFile];
598     }
599
600     float devider = 0.;
601     float returnValue = 0.;
602     NSString *suffix;
603
604     NSNumberFormatter *theFormatter = [[NSNumberFormatter alloc] init];
605     [theFormatter setLocale:[NSLocale currentLocale]];
606     [theFormatter setAllowsFloats:YES];
607
608     NSString *returnString = @"";
609
610     if (countStyle != NSByteCountFormatterCountStyleDecimal)
611         devider = 1024.;
612     else
613         devider = 1000.;
614
615     if (byteCount < 1000) {
616         returnValue = byteCount;
617         suffix = _NS("B");
618         [theFormatter setMaximumFractionDigits:0];
619         goto end;
620     }
621
622     if (byteCount < 1000000) {
623         returnValue = byteCount / devider;
624         suffix = _NS("KB");
625         [theFormatter setMaximumFractionDigits:0];
626         goto end;
627     }
628
629     if (byteCount < 1000000000) {
630         returnValue = byteCount / devider / devider;
631         suffix = _NS("MB");
632         [theFormatter setMaximumFractionDigits:1];
633         goto end;
634     }
635
636     [theFormatter setMaximumFractionDigits:2];
637     if (byteCount < 1000000000000) {
638         returnValue = byteCount / devider / devider / devider;
639         suffix = _NS("GB");
640         goto end;
641     }
642
643     returnValue = byteCount / devider / devider / devider / devider;
644     suffix = _NS("TB");
645
646 end:
647     returnString = [NSString stringWithFormat:@"%@ %@", [theFormatter stringFromNumber:[NSNumber numberWithFloat:returnValue]], suffix];
648
649     return returnString;
650 }
651
652 @end