// // VolumeSlider.m // Cog // // Created by Vincent Spader on 2/8/09. // Copyright 2009 __MyCompanyName__. All rights reserved. // #import "VolumeSlider.h" #import "CogAudio/Helper.h" #import "PlaybackController.h" static void *kVolumeSliderContext = &kVolumeSliderContext; @implementation VolumeSlider { NSTimer *currentTimer; BOOL wasInsideSnapRange; BOOL observersadded; } - (id)initWithFrame:(NSRect)frame { self = [super initWithFrame:frame]; return self; } - (id)initWithCoder:(NSCoder *)coder { self = [super initWithCoder:coder]; return self; } - (void)awakeFromNib { BOOL volumeLimit = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] boolForKey:@"volumeLimit"]; MAX_VOLUME = (volumeLimit) ? 100.0 : 800.0; wasInsideSnapRange = NO; textView = [[NSText alloc] init]; [textView setFrame:NSMakeRect(0, 0, 50, 20)]; textView.drawsBackground = NO; textView.editable = NO; textView.alignment = NSTextAlignmentCenter; NSViewController *viewController = [[NSViewController alloc] init]; viewController.view = textView; popover = [[NSPopover alloc] init]; popover.contentViewController = viewController; // Don't hide the popover automatically. popover.behavior = NSPopoverBehaviorTransient; popover.animates = NO; [popover setContentSize:textView.bounds.size]; [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.volumeLimit" options:0 context:kVolumeSliderContext]; observersadded = YES; } - (void)dealloc { if(observersadded) { [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.volumeLimit" context:kVolumeSliderContext]; } } - (void)updateToolTip { double value = [self doubleValue]; double volume; volume = linearToLogarithmic(value, MAX_VOLUME); NSString *text = [NSString stringWithFormat:@"%0.lf%%", volume]; [textView setString:text]; } - (void)showToolTip { [self updateToolTip]; double progress = (self.maxValue - [self doubleValue]) / (self.maxValue - self.minValue); CGFloat width = self.knobThickness - 1; // Show tooltip to the left of the Slider Knob CGFloat height = self.knobThickness / 2.f + (self.bounds.size.height - self.knobThickness) * progress - 1; NSWindow *window = self.window; NSPoint screenPoint = [window convertPointToScreen:NSMakePoint(width + 1, height + 1)]; if(window.screen.frame.size.width < screenPoint.x + textView.bounds.size.width + 64) // wing it [popover showRelativeToRect:NSMakeRect(1, height, 2, 2) ofView:self preferredEdge:NSRectEdgeMinX]; else [popover showRelativeToRect:NSMakeRect(width, height, 2, 2) ofView:self preferredEdge:NSRectEdgeMaxX]; [self.window.parentWindow makeKeyWindow]; } - (void)showToolTipForDuration:(NSTimeInterval)duration { [self showToolTip]; [self hideToolTipAfterDelay:duration]; } - (void)showToolTipForView:(NSView *)view closeAfter:(NSTimeInterval)duration { [self updateToolTip]; [popover showRelativeToRect:view.bounds ofView:view preferredEdge:NSRectEdgeMaxY]; [self hideToolTipAfterDelay:duration]; } - (void)hideToolTip { [popover close]; } - (void)hideToolTipAfterDelay:(NSTimeInterval)duration { if(currentTimer) { [currentTimer invalidate]; currentTimer = nil; } if(duration > 0.0) { currentTimer = [NSTimer scheduledTimerWithTimeInterval:duration target:self selector:@selector(hideToolTip) userInfo:nil repeats:NO]; [[NSRunLoop mainRunLoop] addTimer:currentTimer forMode:NSRunLoopCommonModes]; } } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if(context != kVolumeSliderContext) { [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; return; } if([keyPath isEqualToString:@"values.volumeLimit"]) { BOOL volumeLimit = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] boolForKey:@"volumeLimit"]; const double new_MAX_VOLUME = (volumeLimit) ? 100.0 : 800.0; if(MAX_VOLUME != new_MAX_VOLUME) { double currentLevel = linearToLogarithmic([self doubleValue], MAX_VOLUME); [self setDoubleValue:logarithmicToLinear(currentLevel, new_MAX_VOLUME)]; } MAX_VOLUME = new_MAX_VOLUME; } } - (BOOL)sendAction:(SEL)theAction to:(id)theTarget { // Snap to 100% if value is close double snapTarget = logarithmicToLinear(100.0, MAX_VOLUME); double snapProgress = ([self doubleValue] - snapTarget) / (self.maxValue - self.minValue); if(fabs(snapProgress) < 0.005) { [self setDoubleValue:snapTarget]; if(!wasInsideSnapRange) { if(@available(macOS 10.11, *)) { [[NSHapticFeedbackManager defaultPerformer] performFeedbackPattern:NSHapticFeedbackPatternGeneric performanceTime:NSHapticFeedbackPerformanceTimeDefault]; } } wasInsideSnapRange = YES; } else { wasInsideSnapRange = NO; } [self showToolTip]; return [super sendAction:theAction to:theTarget]; } - (void)scrollWheel:(NSEvent *)theEvent { double change = [theEvent deltaY]; [self setDoubleValue:[self doubleValue] + change]; [[self target] changeVolume:self]; [self showToolTipForDuration:1.0]; } @end