source: trunk/Cocoa/Pester/Source/PSVolumeController.m @ 595

Last change on this file since 595 was 595, checked in by Nicholas Riley, 10 years ago

Keyboard-accessible and better-behaving volume slider.

File size: 4.3 KB
Line 
1//
2//  PSVolumeController.m
3//  Pester
4//
5//  Created by Nicholas Riley on Tue Apr 08 2003.
6//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
7//
8
9#import "PSVolumeController.h"
10#import "NJRSoundManager.h"
11#import "NJRNonCenteringWindow.h"
12#include <Carbon/Carbon.h>
13
14@interface NSMenu (SnowLeopardAdditions)
15- (BOOL)popUpMenuPositioningItem:(NSMenuItem *)item atLocation:(NSPoint)location inView:(NSView *)view;
16- (void)setAllowsContextMenuPlugIns:(BOOL)allows;
17- (void)cancelTracking;
18@end
19
20@interface NSMenuItem (SnowLeopardAdditions)
21- (void)setView:(NSView *)view;
22@end
23
24@implementation PSVolumeController
25
26+ (PSVolumeController *)controllerWithVolume:(float)volume delegate:(id)aDelegate;
27{
28    return [[self alloc] initWithVolume: volume delegate: aDelegate];
29}
30
31- (id)initWithVolume:(float)volume delegate:(id)aDelegate;
32{
33    if ( (self = [self initWithWindowNibName: @"Volume"]) != nil) {
34        [self window]; // connect outlets
35
36        if ([NJRSoundManager volumeIsNotMutedOrInvalid: volume])
37            [volumeSlider setFloatValue: volume];
38
39        delegate = [aDelegate retain];
40
41        NSView *view = [aDelegate volumeControllerLaunchingView: self];
42
43        // In 10.6, we can no longer force the modal session to work by "seeding" the slider with a mouse-down event.
44        // Instead, use a menu.  (This should mostly work on 10.5 too, but is currently untested.)
45        if ([NSMenu instancesRespondToSelector: @selector(popUpMenuPositioningItem:atLocation:inView:)]) {
46            menu = [[NSMenu alloc] initWithTitle: @""];
47            NSMenuItem *menuItem = [[NSMenuItem alloc] init];
48            [menuItem setView: contentView];
49            [menu addItem: menuItem];
50            [menuItem release];
51            NSPoint point;
52            if (view != nil) {
53                NSSize size = [view bounds].size;
54                point = [view isFlipped] ? NSMakePoint(0, size.height) : NSZeroPoint;
55            } else {
56                point = [NSEvent mouseLocation];
57            }
58            [menu setAllowsContextMenuPlugIns: NO];
59            // replace with http://waffle.wootest.net/2007/08/07/popping-up-a-menu-in-cocoa/
60            [menu popUpMenuPositioningItem: nil atLocation: point inView: view];
61            [menu release];
62        } else {
63            NSWindow *window = [[NJRNonCenteringWindow alloc] initWithContentRect: [contentView bounds] styleMask: NSBorderlessWindowMask backing: NSBackingStoreBuffered defer: NO];
64            [window setContentView: contentView];
65            [window setOpaque: NO];
66            [window setBackgroundColor: [NSColor colorWithCalibratedWhite: 0.81f alpha: 0.9f]];
67            [window setHasShadow: YES];
68            [window setOneShot: YES];
69            [window setDelegate: self];
70
71            if (view != nil) {
72                NSRect rect = [view convertRect: [view bounds] toView: nil];
73                NSWindow *parentWindow = [view window];
74                rect.origin = [parentWindow convertBaseToScreen: rect.origin];
75                rect.origin.x -= [window frame].size.width - rect.size.width + 1;
76                [window setFrameTopLeftPoint: rect.origin];
77                NSRect visibleFrame = [[parentWindow screen] visibleFrame];
78                if (!NSContainsRect(visibleFrame, [window frame])) {
79                    NSPoint viewTopLeft = { rect.origin.x, rect.origin.y + rect.size.height };
80                    [window setFrameOrigin: viewTopLeft];
81                }
82            }
83            // -[NSApplication beginModalSessionForWindow:] shows and centers the window; we use NJRNonCenteringWindow to prevent the repositioning from succeeding
84            NSModalSession session = [NSApp beginModalSessionForWindow: window];
85            [volumeSlider mouseDown: [NSApp currentEvent]];
86            [NSApp runModalSession: session];
87            [NSApp endModalSession: session];
88            [window close];
89        }
90
91        [self autorelease];
92    }
93    return self;
94}
95
96- (void)dealloc;
97{
98    [delegate release];
99    [super dealloc];
100}
101
102- (IBAction)volumeSet:(NSSlider *)sender;
103{
104    // XXX don't delay preview for keyboard adjustment
105    [delegate volumeController: self didSetVolume: [sender floatValue]];
106    NSLog(@"%@", [NSApp currentEvent]);
107    unsigned eventMask = NSEventMaskFromType([[NSApp currentEvent] type]);
108    // The event may simply be a mouse-up: close the menu.
109    if (eventMask & (NSLeftMouseUpMask | NSRightMouseDownMask | NSOtherMouseDownMask))
110        [menu cancelTracking];
111    // On a quick click, the event may be a mouse down but the mouse button is no longer down.
112    if (!(eventMask & (NSLeftMouseDownMask | NSRightMouseDownMask | NSOtherMouseDownMask)))
113        return;
114    // 10.6+: use [NSEvent pressedMouseButtons] instead
115    if (GetCurrentButtonState() == 0)
116        [menu cancelTracking];
117}
118
119@end
Note: See TracBrowser for help on using the repository browser.