misc.m 31.3 KB
Newer Older
1 2 3
/*****************************************************************************
 * misc.m: code not specific to vlc
 *****************************************************************************
4
 * Copyright (C) 2003-2015 VLC authors and VideoLAN
5
 * $Id$
6 7
 *
 * Authors: Jon Lech Johansen <jon-vl@nanocrew.net>
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
8
 *          Felix Paul Kühne <fkuehne at videolan dot org>
9 10 11 12 13 14 15 16 17 18 19 20 21
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
Antoine Cellerier's avatar
Antoine Cellerier committed
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 "intf.h"                                          /* VLCApplication */
28
#import "VLCMainWindow.h"
David Fuhrmann's avatar
David Fuhrmann committed
29
#import "VLCMainMenu.h"
30
#import "ControlsBar.h"
31
#import "VLCCoreInteraction.h"
32
#import <CoreAudio/CoreAudio.h>
33 34
#import <vlc_keys.h>

35
NSString *const VLCOpenTextFieldWasClicked = @"VLCOpenTextFieldWasClicked";
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57

/*****************************************************************************
 * NSSound (VLCAdditions)
 *
 * added code to change the system volume, needed for the apple remote code
 * this is simplified code, which won't let you set the exact volume
 * (that's what the audio output is for after all), but just the system volume
 * in steps of 1/16 (matching the default AR or volume key implementation).
 *****************************************************************************/

@implementation NSSound (VLCAdditions)

+ (float)systemVolumeForChannel:(int)channel
{
    AudioDeviceID i_device;
    float f_volume;
    OSStatus err;
    UInt32 i_size;

    i_size = sizeof( i_device );
    AudioObjectPropertyAddress deviceAddress = { kAudioHardwarePropertyDefaultOutputDevice, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster };
    err = AudioObjectGetPropertyData( kAudioObjectSystemObject, &deviceAddress, 0, NULL, &i_size, &i_device );
58
    if (err != noErr) {
59
        msg_Warn( getIntf(), "couldn't get main audio output device" );
60 61 62 63 64 65
        return .0;
    }

    AudioObjectPropertyAddress propertyAddress = { kAudioDevicePropertyVolumeScalar, kAudioDevicePropertyScopeOutput, channel };
    i_size = sizeof( f_volume );
    err = AudioObjectGetPropertyData(i_device, &propertyAddress, 0, NULL, &i_size, &f_volume);
66
    if (err != noErr) {
67
        msg_Warn( getIntf(), "couldn't get volume value" );
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
        return .0;
    }

    return f_volume;
}

+ (bool)setSystemVolume:(float)f_volume forChannel:(int)i_channel
{
    /* the following code will fail on S/PDIF devices. there is an easy work-around, but we'd like to match the OS behavior */

    AudioDeviceID i_device;
    OSStatus err;
    UInt32 i_size;
    Boolean b_writeable;

    i_size = sizeof( i_device );
    AudioObjectPropertyAddress deviceAddress = { kAudioHardwarePropertyDefaultOutputDevice, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster };
    err = AudioObjectGetPropertyData( kAudioObjectSystemObject, &deviceAddress, 0, NULL, &i_size, &i_device );
86
    if (err != noErr) {
87
        msg_Warn( getIntf(), "couldn't get main audio output device" );
88 89 90 91 92 93 94
        return NO;
    }

    AudioObjectPropertyAddress propertyAddress = { kAudioDevicePropertyVolumeScalar, kAudioDevicePropertyScopeOutput, i_channel };
    i_size = sizeof( f_volume );
    err = AudioObjectIsPropertySettable( i_device, &propertyAddress, &b_writeable );
    if (err != noErr || !b_writeable ) {
95
        msg_Warn( getIntf(), "we can't set the main audio devices' volume" );
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
        return NO;
    }
    err = AudioObjectSetPropertyData(i_device, &propertyAddress, 0, NULL, i_size, &f_volume);

    return YES;
}

+ (void)increaseSystemVolume
{
    float f_volume = [NSSound systemVolumeForChannel:1]; // we trust that mono is always available and that all channels got the same volume
    f_volume += .0625; // 1/16 to match the OS
    bool b_returned = YES;

    /* since core audio doesn't provide a reasonable way to see how many channels we got, let's see how long we can do this */
    for (NSUInteger x = 1; b_returned ; x++)
        b_returned = [NSSound setSystemVolume: f_volume forChannel:x];
}

+ (void)decreaseSystemVolume
{
    float f_volume = [NSSound systemVolumeForChannel:1]; // we trust that mono is always available and that all channels got the same volume
    f_volume -= .0625; // 1/16 to match the OS
    bool b_returned = YES;

    /* since core audio doesn't provide a reasonable way to see how many channels we got, let's see how long we can do this */
    for (NSUInteger x = 1; b_returned ; x++)
        b_returned = [NSSound setSystemVolume: f_volume forChannel:x];
}

@end
126

127 128 129 130 131 132 133 134
/*****************************************************************************
 * NSAnimation (VLCAdditions)
 *
 *  Missing extension to NSAnimation
 *****************************************************************************/

@implementation NSAnimation (VLCAdditions)
/* fake class attributes  */
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
135
static NSMapTable *VLCAdditions_userInfo = NULL;
136 137 138 139 140 141 142 143 144

+ (void)load
{
    /* init our fake object attribute */
    VLCAdditions_userInfo = NSCreateMapTable(NSNonRetainedObjectMapKeyCallBacks, NSObjectMapValueCallBacks, 16);
}

- (void)dealloc
{
145
    NSMapRemove(VLCAdditions_userInfo, (__bridge const void * __nullable)(self));
146 147 148 149
}

- (void)setUserInfo: (void *)userInfo
{
150
    NSMapInsert(VLCAdditions_userInfo, (__bridge const void * __nullable)(self), (void*)userInfo);
151 152 153 154
}

- (void *)userInfo
{
155
    return NSMapGet(VLCAdditions_userInfo, (__bridge const void * __nullable)(self));
156 157 158 159 160 161 162 163 164 165 166
}
@end

/*****************************************************************************
 * NSScreen (VLCAdditions)
 *
 *  Missing extension to NSScreen
 *****************************************************************************/

@implementation NSScreen (VLCAdditions)

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
167
static NSMutableArray *blackoutWindows = NULL;
168

169 170
static bool b_old_spaces_style = YES;

171 172 173
+ (void)load
{
    /* init our fake object attribute */
174
    blackoutWindows = [[NSMutableArray alloc] initWithCapacity:1];
175

176
    if (!OSX_LION && !OSX_MOUNTAIN_LION) {
177 178 179 180 181 182 183
        NSUserDefaults *userDefaults = [[NSUserDefaults alloc] init];
        [userDefaults addSuiteNamed:@"com.apple.spaces"];
        /* this is system settings -> mission control -> monitors using different spaces */
        NSNumber *o_span_displays = [userDefaults objectForKey:@"spans-displays"];

        b_old_spaces_style = [o_span_displays boolValue];
    }
184 185 186 187
}

+ (NSScreen *)screenWithDisplayID: (CGDirectDisplayID)displayID
{
188
    NSUInteger count = [[NSScreen screens] count];
189

190
    for ( NSUInteger i = 0; i < count; i++ ) {
191
        NSScreen *screen = [[NSScreen screens] objectAtIndex:i];
192
        if ([screen displayID] == displayID)
193 194 195 196 197
            return screen;
    }
    return nil;
}

198
- (BOOL)hasMenuBar
199
{
200
    if (b_old_spaces_style)
201
        return ([self displayID] == [[[NSScreen screens] firstObject] displayID]);
202 203
    else
        return YES;
204 205
}

206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
- (BOOL)hasDock
{
    NSRect screen_frame = [self frame];
    NSRect screen_visible_frame = [self visibleFrame];
    CGFloat f_menu_bar_thickness = [self hasMenuBar] ? [[NSStatusBar systemStatusBar] thickness] : 0.0;

    BOOL b_found_dock = NO;
    if (screen_visible_frame.size.width < screen_frame.size.width)
        b_found_dock = YES;
    else if (screen_visible_frame.size.height + f_menu_bar_thickness < screen_frame.size.height)
        b_found_dock = YES;

    return b_found_dock;
}

221 222 223 224 225
- (BOOL)isScreen: (NSScreen*)screen
{
    return ([self displayID] == [screen displayID]);
}

226 227
- (CGDirectDisplayID)displayID
{
228
    return (CGDirectDisplayID)[[[self deviceDescription] objectForKey: @"NSScreenNumber"] intValue];
229 230 231 232 233 234 235 236
}

- (void)blackoutOtherScreens
{
    /* Free our previous blackout window (follow blackoutWindow alloc strategy) */
    [blackoutWindows makeObjectsPerformSelector:@selector(close)];
    [blackoutWindows removeAllObjects];

237
    NSUInteger screenCount = [[NSScreen screens] count];
238
    for (NSUInteger i = 0; i < screenCount; i++) {
239
        NSScreen *screen = [[NSScreen screens] objectAtIndex:i];
240 241
        VLCWindow *blackoutWindow;
        NSRect screen_rect;
242

243
        if ([self isScreen: screen])
244
            continue;
245 246

        screen_rect = [screen frame];
247
        screen_rect.origin.x = screen_rect.origin.y = 0;
248

249 250 251 252
        /* blackoutWindow alloc strategy
            - The NSMutableArray blackoutWindows has the blackoutWindow references
            - blackoutOtherDisplays is responsible for alloc/releasing its Windows
        */
253 254
        blackoutWindow = [[VLCWindow alloc] initWithContentRect: screen_rect styleMask: NSBorderlessWindowMask
                backing: NSBackingStoreBuffered defer: NO screen: screen];
255 256
        [blackoutWindow setBackgroundColor:[NSColor blackColor]];
        [blackoutWindow setLevel: NSFloatingWindowLevel]; /* Disappear when Expose is triggered */
257
        [blackoutWindow setReleasedWhenClosed:NO]; // window is released when deleted from array above
258

259
        [blackoutWindow displayIfNeeded];
260 261 262
        [blackoutWindow orderFront: self animate: YES];

        [blackoutWindows addObject: blackoutWindow];
263

264
        [screen setFullscreenPresentationOptions];
265 266 267 268 269
    }
}

+ (void)unblackoutScreens
{
270
    NSUInteger blackoutWindowCount = [blackoutWindows count];
271

272
    for (NSUInteger i = 0; i < blackoutWindowCount; i++) {
273
        VLCWindow *blackoutWindow = [blackoutWindows objectAtIndex:i];
274
        [[blackoutWindow screen] setNonFullscreenPresentationOptions];
275 276 277 278
        [blackoutWindow closeAndAnimate: YES];
    }
}

279 280 281 282 283 284 285 286 287 288
- (void)setFullscreenPresentationOptions
{
    NSApplicationPresentationOptions presentationOpts = [NSApp presentationOptions];
    if ([self hasMenuBar])
        presentationOpts |= NSApplicationPresentationAutoHideMenuBar;
    if ([self hasMenuBar] || [self hasDock])
        presentationOpts |= NSApplicationPresentationAutoHideDock;
    [NSApp setPresentationOptions:presentationOpts];
}

289 290 291 292 293 294 295 296 297 298
- (void)setNonFullscreenPresentationOptions
{
    NSApplicationPresentationOptions presentationOpts = [NSApp presentationOptions];
    if ([self hasMenuBar])
        presentationOpts &= (~NSApplicationPresentationAutoHideMenuBar);
    if ([self hasMenuBar] || [self hasDock])
        presentationOpts &= (~NSApplicationPresentationAutoHideDock);
    [NSApp setPresentationOptions:presentationOpts];
}

299 300
@end

Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
301
/*****************************************************************************
302
 * VLCDragDropView
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
303 304
 *****************************************************************************/

305 306 307 308 309 310 311 312 313 314 315 316 317
@implementation VLCDropDisabledImageView

- (void)awakeFromNib
{
    [self unregisterDraggedTypes];
}

@end

/*****************************************************************************
 * VLCDragDropView
 *****************************************************************************/

318 319 320 321 322 323
@interface VLCDragDropView()
{
    bool b_activeDragAndDrop;
}
@end

324 325
@implementation VLCDragDropView

326 327 328 329 330 331 332 333 334 335
- (id)initWithFrame:(NSRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // default value
        [self setDrawBorder:YES];
    }

    return self;
}
336 337 338 339 340

- (void)enablePlaylistItems
{
    [self registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, @"VLCPlaylistItemPboardType", nil]];
}
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
341 342 343 344 345 346

- (BOOL)mouseDownCanMoveWindow
{
    return YES;
}

347 348 349 350 351 352 353
- (void)dealloc
{
    [self unregisterDraggedTypes];
}

- (void)awakeFromNib
{
354
    [self registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]];
355 356 357 358
}

- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
{
359 360 361 362 363 364
    if ((NSDragOperationGeneric & [sender draggingSourceOperationMask]) == NSDragOperationGeneric) {
        b_activeDragAndDrop = YES;
        [self setNeedsDisplay:YES];

        return NSDragOperationCopy;
    }
365 366

    return NSDragOperationNone;
367 368
}

369 370 371 372 373 374 375 376 377 378 379 380
- (void)draggingEnded:(id < NSDraggingInfo >)sender
{
    b_activeDragAndDrop = NO;
    [self setNeedsDisplay:YES];
}

- (void)draggingExited:(id < NSDraggingInfo >)sender
{
    b_activeDragAndDrop = NO;
    [self setNeedsDisplay:YES];
}

381 382 383 384 385 386 387
- (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
{
    return YES;
}

- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
{
388
    BOOL b_returned;
389 390

    if (_dropHandler && [_dropHandler respondsToSelector:@selector(performDragOperation:)])
David Fuhrmann's avatar
David Fuhrmann committed
391
        b_returned = [_dropHandler performDragOperation:sender];
392
    else // default
David Fuhrmann's avatar
David Fuhrmann committed
393
        b_returned = [[VLCCoreInteraction sharedInstance] performDragOperation:sender];
394

395
    [self setNeedsDisplay:YES];
396
    return b_returned;
397 398 399 400 401 402 403
}

- (void)concludeDragOperation:(id <NSDraggingInfo>)sender
{
    [self setNeedsDisplay:YES];
}

404 405
- (void)drawRect:(NSRect)dirtyRect
{
406
    if ([self drawBorder] && b_activeDragAndDrop) {
407 408 409
        NSRect frameRect = [self bounds];

        [[NSColor selectedControlColor] set];
410
        NSFrameRectWithWidthUsingOperation(frameRect, 2., NSCompositeSourceOver);
411 412 413 414 415
    }

    [super drawRect:dirtyRect];
}

Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
416 417 418
@end


419 420 421 422 423
/*****************************************************************************
 * MPSlider
 *****************************************************************************/
@implementation MPSlider

Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
424
void _drawKnobInRect(NSRect knobRect)
425
{
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
426 427 428
    // Center knob in given rect
    knobRect.origin.x += (int)((float)(knobRect.size.width - 7)/2.0);
    knobRect.origin.y += (int)((float)(knobRect.size.height - 7)/2.0);
429

Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
430 431 432 433 434 435 436 437
    // Draw diamond
    NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 3, knobRect.origin.y + 6, 1, 1), NSCompositeSourceOver);
    NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 2, knobRect.origin.y + 5, 3, 1), NSCompositeSourceOver);
    NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 1, knobRect.origin.y + 4, 5, 1), NSCompositeSourceOver);
    NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 0, knobRect.origin.y + 3, 7, 1), NSCompositeSourceOver);
    NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 1, knobRect.origin.y + 2, 5, 1), NSCompositeSourceOver);
    NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 2, knobRect.origin.y + 1, 3, 1), NSCompositeSourceOver);
    NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 3, knobRect.origin.y + 0, 1, 1), NSCompositeSourceOver);
438 439
}

Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
440
void _drawFrameInRect(NSRect frameRect)
441
{
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
442 443 444 445 446
    // Draw frame
    NSRectFillUsingOperation(NSMakeRect(frameRect.origin.x, frameRect.origin.y, frameRect.size.width, 1), NSCompositeSourceOver);
    NSRectFillUsingOperation(NSMakeRect(frameRect.origin.x, frameRect.origin.y + frameRect.size.height-1, frameRect.size.width, 1), NSCompositeSourceOver);
    NSRectFillUsingOperation(NSMakeRect(frameRect.origin.x, frameRect.origin.y, 1, frameRect.size.height), NSCompositeSourceOver);
    NSRectFillUsingOperation(NSMakeRect(frameRect.origin.x+frameRect.size.width-1, frameRect.origin.y, 1, frameRect.size.height), NSCompositeSourceOver);
447 448
}

Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
449
- (void)drawRect:(NSRect)rect
450
{
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
451 452 453 454 455
    // Draw default to make sure the slider behaves correctly
    [[NSGraphicsContext currentContext] saveGraphicsState];
    NSRectClip(NSZeroRect);
    [super drawRect:rect];
    [[NSGraphicsContext currentContext] restoreGraphicsState];
456

Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
457 458 459 460 461 462 463
    // Full size
    rect = [self bounds];
    int diff = (int)(([[self cell] knobThickness] - 7.0)/2.0) - 1;
    rect.origin.x += diff-1;
    rect.origin.y += diff;
    rect.size.width -= 2*diff-2;
    rect.size.height -= 2*diff;
464

Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
465 466 467 468 469
    // Draw dark
    NSRect knobRect = [[self cell] knobRectFlipped:NO];
    [[[NSColor blackColor] colorWithAlphaComponent:0.6] set];
    _drawFrameInRect(rect);
    _drawKnobInRect(knobRect);
470

Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
471 472 473 474 475 476 477 478
    // Draw shadow
    [[[NSColor blackColor] colorWithAlphaComponent:0.1] set];
    rect.origin.x++;
    rect.origin.y++;
    knobRect.origin.x++;
    knobRect.origin.y++;
    _drawFrameInRect(rect);
    _drawKnobInRect(knobRect);
479 480
}

481 482
@end

483 484 485 486 487 488 489 490
/*****************************************************************************
 * ProgressView
 *****************************************************************************/

@implementation VLCProgressView : NSView

- (void)scrollWheel:(NSEvent *)o_event
{
491
    BOOL b_forward = NO;
492 493 494
    CGFloat f_deltaY = [o_event deltaY];
    CGFloat f_deltaX = [o_event deltaX];

495
    if ([o_event isDirectionInvertedFromDevice])
496 497 498 499 500 501 502 503 504
        f_deltaX = -f_deltaX; // optimisation, actually double invertion of f_deltaY here
    else
        f_deltaY = -f_deltaY;

    // positive for left / down, negative otherwise
    CGFloat f_delta = f_deltaX + f_deltaY;
    CGFloat f_abs;
    int i_vlckey;

505
    if (f_delta > 0.0f)
506 507
        f_abs = f_delta;
    else {
508
        b_forward = YES;
509 510 511
        f_abs = -f_delta;
    }

512 513 514 515 516 517
    for (NSUInteger i = 0; i < (int)(f_abs/4.+1.) && f_abs > 0.05 ; i++) {
        if (b_forward)
            [[VLCCoreInteraction sharedInstance] forwardExtraShort];
        else
            [[VLCCoreInteraction sharedInstance] backwardExtraShort];
    }
518 519 520 521 522 523 524 525 526
}

- (BOOL)acceptsFirstResponder
{
    return YES;
}

@end

527 528 529 530
/*****************************************************************************
 * TimeLineSlider
 *****************************************************************************/

531 532 533 534 535 536 537 538
@interface TimeLineSlider()
{
    NSImage *o_knob_img;
    NSRect img_rect;
    BOOL b_dark;
}
@end

539 540
@implementation TimeLineSlider

541 542
- (void)awakeFromNib
{
543
    if (config_GetInt( getIntf(), "macosx-interfacestyle" )) {
544
        o_knob_img = imageFromRes(@"progression-knob_dark");
545 546
        b_dark = YES;
    } else {
547
        o_knob_img = imageFromRes(@"progression-knob");
548 549
        b_dark = NO;
    }
550 551 552 553 554 555 556 557 558 559 560
    img_rect.size = [o_knob_img size];
    img_rect.origin.x = img_rect.origin.y = 0;
}

- (CGFloat)knobPosition
{
    NSRect knobRect = [[self cell] knobRectFlipped:NO];
    knobRect.origin.x += knobRect.size.width / 2;
    return knobRect.origin.x;
}

561 562
- (void)drawKnobInRect:(NSRect)knobRect
{
563 564 565 566
    knobRect.origin.x += (knobRect.size.width - img_rect.size.width) / 2;
    knobRect.size.width = img_rect.size.width;
    knobRect.size.height = img_rect.size.height;
    [o_knob_img drawInRect:knobRect fromRect:img_rect operation:NSCompositeSourceOver fraction:1];
567 568 569 570
}

- (void)drawRect:(NSRect)rect
{
571 572
    [[(VLCVideoWindowCommon *)[self window] controlsBar] drawFancyGradientEffectForTimeSlider];
    msleep(10000); //wait for the gradient to draw completely
573

574 575 576 577 578 579 580
    /* Draw default to make sure the slider behaves correctly */
    [[NSGraphicsContext currentContext] saveGraphicsState];
    NSRectClip(NSZeroRect);
    [super drawRect:rect];
    [[NSGraphicsContext currentContext] restoreGraphicsState];

    NSRect knobRect = [[self cell] knobRectFlipped:NO];
581
    knobRect.origin.y+=1;
582 583 584 585
    [self drawKnobInRect: knobRect];
}

@end
586

587 588 589 590 591 592 593 594
/*****************************************************************************
 * VLCVolumeSliderCommon
 *****************************************************************************/

@implementation VLCVolumeSliderCommon : NSSlider

- (void)scrollWheel:(NSEvent *)o_event
{
595
    BOOL b_up = NO;
596 597 598
    CGFloat f_deltaY = [o_event deltaY];
    CGFloat f_deltaX = [o_event deltaX];

599
    if ([o_event isDirectionInvertedFromDevice])
600 601 602 603 604 605 606 607
        f_deltaX = -f_deltaX; // optimisation, actually double invertion of f_deltaY here
    else
        f_deltaY = -f_deltaY;

    // positive for left / down, negative otherwise
    CGFloat f_delta = f_deltaX + f_deltaY;
    CGFloat f_abs;

608
    if (f_delta > 0.0f)
609 610
        f_abs = f_delta;
    else {
611
        b_up = YES;
612 613 614
        f_abs = -f_delta;
    }

615 616 617 618 619 620
    for (NSUInteger i = 0; i < (int)(f_abs/4.+1.) && f_abs > 0.05 ; i++) {
        if (b_up)
            [[VLCCoreInteraction sharedInstance] volumeUp];
        else
            [[VLCCoreInteraction sharedInstance] volumeDown];
    }
621 622
}

623 624
- (void)drawFullVolumeMarker
{
625 626 627 628
    CGFloat maxAudioVol = self.maxValue / AOUT_VOLUME_DEFAULT;
    if (maxAudioVol < 1.)
        return;

629
    NSColor *drawingColor;
630
    // for bright artwork, a black color is used and vice versa
631
    if (_usesBrightArtwork)
632
        drawingColor = [[NSColor blackColor] colorWithAlphaComponent:.4];
633
    else
634
        drawingColor = [[NSColor whiteColor] colorWithAlphaComponent:.4];
635 636

    NSBezierPath* bezierPath = [NSBezierPath bezierPath];
637
    [self drawFullVolBezierPath:bezierPath];
638 639 640 641 642
    [bezierPath closePath];

    bezierPath.lineWidth = 1.;
    [drawingColor setStroke];
    [bezierPath stroke];
643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658
}

- (CGFloat)fullVolumePos
{
    CGFloat maxAudioVol = self.maxValue / AOUT_VOLUME_DEFAULT;
    CGFloat sliderRange = [self frame].size.width - [self knobThickness];
    CGFloat sliderOrigin = [self knobThickness] / 2.;

    return 1. / maxAudioVol * sliderRange + sliderOrigin;
}

- (void)drawFullVolBezierPath:(NSBezierPath*)bezierPath
{
    CGFloat fullVolPos = [self fullVolumePos];
    [bezierPath moveToPoint:NSMakePoint(fullVolPos, [self frame].size.height - 3.)];
    [bezierPath lineToPoint:NSMakePoint(fullVolPos, 2.)];
659
}
660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675

@end

@implementation VolumeSliderCell

- (BOOL)continueTracking:(NSPoint)lastPoint at:(NSPoint)currentPoint inView:(NSView *)controlView
{
    VLCVolumeSliderCommon *o_slider = (VLCVolumeSliderCommon *)controlView;
    CGFloat fullVolumePos = [o_slider fullVolumePos] + 2.;

    CGPoint snapToPoint = currentPoint;
    if (ABS(fullVolumePos - currentPoint.x) <= 4.)
        snapToPoint.x = fullVolumePos;

    return [super continueTracking:lastPoint at:snapToPoint inView:controlView];
}
676

677 678
@end

679 680 681 682
/*****************************************************************************
 * ITSlider
 *****************************************************************************/

683 684 685 686 687 688 689
@interface ITSlider()
{
    NSImage *img;
    NSRect image_rect;
}
@end

690 691
@implementation ITSlider

692
- (void)awakeFromNib
693
{
694
    BOOL b_dark = config_GetInt( getIntf(), "macosx-interfacestyle" );
695
    if (b_dark)
696
        img = imageFromRes(@"volume-slider-knob_dark");
697
    else
698
        img = imageFromRes(@"volume-slider-knob");
699

700 701
    image_rect.size = [img size];
    image_rect.origin.x = 0;
702 703 704 705 706 707 708 709 710

    if (b_dark)
        image_rect.origin.y = -1;
    else
        image_rect.origin.y = 0;
}

- (void)drawKnobInRect:(NSRect)knobRect
{
711 712 713 714
    knobRect.origin.x += (knobRect.size.width - image_rect.size.width) / 2;
    knobRect.size.width = image_rect.size.width;
    knobRect.size.height = image_rect.size.height;
    [img drawInRect:knobRect fromRect:image_rect operation:NSCompositeSourceOver fraction:1];
715 716
}

717
- (void)drawRect:(NSRect)rect
718
{
719 720 721 722 723
    /* Draw default to make sure the slider behaves correctly */
    [[NSGraphicsContext currentContext] saveGraphicsState];
    NSRectClip(NSZeroRect);
    [super drawRect:rect];
    [[NSGraphicsContext currentContext] restoreGraphicsState];
724

725 726
    [self drawFullVolumeMarker];

727 728 729
    NSRect knobRect = [[self cell] knobRectFlipped:NO];
    knobRect.origin.y+=2;
    [self drawKnobInRect: knobRect];
730 731 732 733
}

@end

734 735 736 737 738 739
/*****************************************************************************
 * VLCTimeField implementation
 *****************************************************************************
 * we need this to catch our click-event in the controller window
 *****************************************************************************/

740 741 742 743 744 745 746 747 748 749
@interface VLCTimeField()
{
    NSShadow * o_string_shadow;
    NSTextAlignment textAlignment;

    NSString *o_remaining_identifier;
    BOOL b_time_remaining;
}
@end

750
@implementation VLCTimeField
751 752
+ (void)initialize
{
753
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
754 755 756 757
    NSDictionary *appDefaults = [NSDictionary dictionaryWithObjectsAndKeys:
                                 @"NO", @"DisplayTimeAsTimeRemaining",
                                 @"YES", @"DisplayFullscreenTimeAsTimeRemaining",
                                 nil];
758

759 760 761
    [defaults registerDefaults:appDefaults];
}

762 763 764 765
- (void)setRemainingIdentifier:(NSString *)o_string
{
    o_remaining_identifier = o_string;
    b_time_remaining = [[NSUserDefaults standardUserDefaults] boolForKey:o_remaining_identifier];
766 767
}

768 769 770 771 772 773
- (void)setAlignment:(NSTextAlignment)alignment
{
    textAlignment = alignment;
    [self setStringValue:[self stringValue]];
}

774 775
- (void)setStringValue:(NSString *)string
{
776
    if (!o_string_shadow) {
777 778
        o_string_shadow = [[NSShadow alloc] init];
        [o_string_shadow setShadowColor: [NSColor colorWithCalibratedWhite:1.0 alpha:0.5]];
779
        [o_string_shadow setShadowOffset:NSMakeSize(0.0, -1.0)];
780 781 782
        [o_string_shadow setShadowBlurRadius:0.0];
    }

783
    NSMutableAttributedString *o_attributed_string = [[NSMutableAttributedString alloc] initWithString:string attributes: nil];
784 785 786
    NSUInteger i_stringLength = [string length];

    [o_attributed_string addAttribute: NSShadowAttributeName value: o_string_shadow range: NSMakeRange(0, i_stringLength)];
787
    [o_attributed_string setAlignment: textAlignment range: NSMakeRange(0, i_stringLength)];
788 789 790
    [self setAttributedStringValue: o_attributed_string];
}

791 792
- (void)mouseDown: (NSEvent *)ourEvent
{
793
    if ( [ourEvent clickCount] > 1 )
794
        [[[VLCMain sharedInstance] mainMenu] goToSpecificTime: nil];
795
    else
796
    {
797 798
        if (o_remaining_identifier) {
            b_time_remaining = [[NSUserDefaults standardUserDefaults] boolForKey:o_remaining_identifier];
799 800
            b_time_remaining = !b_time_remaining;
            [[NSUserDefaults standardUserDefaults] setObject:(b_time_remaining ? @"YES" : @"NO") forKey:o_remaining_identifier];
801 802
        } else {
            b_time_remaining = !b_time_remaining;
803
        }
804 805 806 807 808
    }
}

- (BOOL)timeRemaining
{
809
    if (o_remaining_identifier)
810 811 812
        return [[NSUserDefaults standardUserDefaults] boolForKey:o_remaining_identifier];
    else
        return b_time_remaining;
813
}
814

815
@end
816 817 818

/*****************************************************************************
 * VLCMainWindowSplitView implementation
819
 * comment 1 + 2 taken from NSSplitView.h (10.7 SDK)
820 821 822 823 824 825
 *****************************************************************************/
@implementation VLCMainWindowSplitView : NSSplitView
/* 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.
 */
- (NSColor *)dividerColor
{
826
    return [NSColor colorWithCalibratedRed:.60 green:.60 blue:.60 alpha:1.];
827 828 829 830 831 832
}

/* 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.
 */
- (CGFloat)dividerThickness
{
833 834
    return 1.0;
}
835
@end
836 837 838 839

/*****************************************************************************
 * VLCThreePartImageView interface
 *****************************************************************************/
840 841 842 843 844 845 846 847 848

@interface VLCThreePartImageView()
{
    NSImage *_left_img;
    NSImage *_middle_img;
    NSImage *_right_img;
}
@end

849
@implementation VLCThreePartImageView
850

851 852
- (void)setImagesLeft:(NSImage *)left middle: (NSImage *)middle right:(NSImage *)right
{
853 854 855
    _left_img = left;
    _middle_img = middle;
    _right_img = right;
856 857 858 859 860
}

- (void)drawRect:(NSRect)rect
{
    NSRect bnds = [self bounds];
861
    NSDrawThreePartImage( bnds, _left_img, _middle_img, _right_img, NO, NSCompositeSourceOver, 1, NO );
862 863 864
}

@end
865

866 867 868 869 870 871
@interface PositionFormatter()
{
    NSCharacterSet *o_forbidden_characters;
}
@end

872 873 874 875 876 877
@implementation PositionFormatter

- (id)init
{
    self = [super init];
    NSMutableCharacterSet *nonNumbers = [[[NSCharacterSet decimalDigitCharacterSet] invertedSet] mutableCopy];
878
    [nonNumbers removeCharactersInString:@"-:"];
879 880 881 882 883 884 885
    o_forbidden_characters = [nonNumbers copy];

    return self;
}

- (NSString*)stringForObjectValue:(id)obj
{
886 887 888 889 890 891
    if([obj isKindOfClass:[NSString class]])
        return obj;
    if([obj isKindOfClass:[NSNumber class]])
        return [obj stringValue];

    return nil;
892 893 894 895
}

- (BOOL)getObjectValue:(id*)obj forString:(NSString*)string errorDescription:(NSString**)error
{
896
    *obj = [string copy];
897 898 899
    return YES;
}

900
- (BOOL)isPartialStringValid:(NSString*)partialString newEditingString:(NSString**)newString errorDescription:(NSString**)error
901 902 903 904 905 906 907 908
{
    if ([partialString rangeOfCharacterFromSet:o_forbidden_characters options:NSLiteralSearch].location != NSNotFound) {
        return NO;
    } else {
        return YES;
    }
}

909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933
@end

@implementation NSView (EnableSubviews)

- (void)enableSubviews:(BOOL)b_enable
{
    for (NSView *o_view in [self subviews]) {
        [o_view enableSubviews:b_enable];

        // enable NSControl
        if ([o_view respondsToSelector:@selector(setEnabled:)]) {
            [(NSControl *)o_view setEnabled:b_enable];
        }
        // also "enable / disable" text views
        if ([o_view respondsToSelector:@selector(setTextColor:)]) {
            if (b_enable == NO) {
                [(NSTextField *)o_view setTextColor:[NSColor disabledControlTextColor]];
            } else {
                [(NSTextField *)o_view setTextColor:[NSColor controlTextColor]];
            }
        }

    }
}

934
@end
935 936 937 938 939 940 941 942 943

/*****************************************************************************
 * VLCByteCountFormatter addition
 *****************************************************************************/

@implementation VLCByteCountFormatter

+ (NSString *)stringFromByteCount:(long long)byteCount countStyle:(NSByteCountFormatterCountStyle)countStyle
{
944 945 946 947 948
    // Use native implementation on >= mountain lion
    Class byteFormatterClass = NSClassFromString(@"NSByteCountFormatter");
    if (byteFormatterClass && [byteFormatterClass respondsToSelector:@selector(stringFromByteCount:countStyle:)]) {
        return [byteFormatterClass stringFromByteCount:byteCount countStyle:NSByteCountFormatterCountStyleFile];
    }
949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002

    float devider = 0.;
    float returnValue = 0.;
    NSString *suffix;

    NSNumberFormatter *theFormatter = [[NSNumberFormatter alloc] init];
    [theFormatter setLocale:[NSLocale currentLocale]];
    [theFormatter setAllowsFloats:YES];

    NSString *returnString = @"";

    if (countStyle != NSByteCountFormatterCountStyleDecimal)
        devider = 1024.;
    else
        devider = 1000.;

    if (byteCount < 1000) {
        returnValue = byteCount;
        suffix = _NS("B");
        [theFormatter setMaximumFractionDigits:0];
        goto end;
    }

    if (byteCount < 1000000) {
        returnValue = byteCount / devider;
        suffix = _NS("KB");
        [theFormatter setMaximumFractionDigits:0];
        goto end;
    }

    if (byteCount < 1000000000) {
        returnValue = byteCount / devider / devider;
        suffix = _NS("MB");
        [theFormatter setMaximumFractionDigits:1];
        goto end;
    }

    [theFormatter setMaximumFractionDigits:2];
    if (byteCount < 1000000000000) {
        returnValue = byteCount / devider / devider / devider;
        suffix = _NS("GB");
        goto end;
    }

    returnValue = byteCount / devider / devider / devider / devider;
    suffix = _NS("TB");

end:
    returnString = [NSString stringWithFormat:@"%@ %@", [theFormatter stringFromNumber:[NSNumber numberWithFloat:returnValue]], suffix];

    return returnString;
}

@end
1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013

@implementation VLCOpenTextField

- (void)mouseDown:(NSEvent *)theEvent
{
    [[NSNotificationCenter defaultCenter] postNotificationName: VLCOpenTextFieldWasClicked
                                                        object: self];
    [super mouseDown: theEvent];
}

@end