diff options
Diffstat (limited to 'AppKit/GTMHotKeyTextField.m')
-rw-r--r-- | AppKit/GTMHotKeyTextField.m | 455 |
1 files changed, 121 insertions, 334 deletions
diff --git a/AppKit/GTMHotKeyTextField.m b/AppKit/GTMHotKeyTextField.m index afefeb4..27cd5b9 100644 --- a/AppKit/GTMHotKeyTextField.m +++ b/AppKit/GTMHotKeyTextField.m @@ -1,6 +1,6 @@ // GTMHotKeyTextField.m // -// Copyright 2006-2008 Google Inc. +// Copyright 2006-2010 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy @@ -18,204 +18,133 @@ #import "GTMHotKeyTextField.h" #import <Carbon/Carbon.h> -#import "GTMSystemVersion.h" #import "GTMObjectSingleton.h" -#import "GTMNSObject+KeyValueObserving.h" -#import "GTMTypeCasting.h" #if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4 +#import "GTMSystemVersion.h" typedef struct __TISInputSource* TISInputSourceRef; + static TISInputSourceRef(*GTM_TISCopyCurrentKeyboardLayoutInputSource)(void) = NULL; static void * (*GTM_TISGetInputSourceProperty)(TISInputSourceRef inputSource, CFStringRef propertyKey) = NULL; static CFStringRef kGTM_TISPropertyUnicodeKeyLayoutData = NULL; #endif // MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4 - -@interface GTMHotKeyTextField (PrivateMethods) +@interface GTMHotKeyTextFieldCell (PrivateMethods) - (void)setupBinding:(id)bound withPath:(NSString *)path; - (void)updateDisplayedPrettyString; -- (void)hotKeyValueChanged:(GTMKeyValueChangeNotification *)note; -+ (BOOL)isValidHotKey:(NSDictionary *)hotKey; -+ (NSString *)displayStringForHotKey:(NSDictionary *)hotKey; ++ (NSString *)displayStringForHotKey:(GTMHotKey *)hotKey; + (BOOL)doesKeyCodeRequireModifier:(UInt16)keycode; @end @interface GTMHotKeyFieldEditor (PrivateMethods) -- (NSDictionary *)hotKeyDictionary; -- (void)setHotKeyDictionary:(NSDictionary *)hotKey; +- (GTMHotKeyTextFieldCell *)cell; +- (void)setCell:(GTMHotKeyTextFieldCell *)cell; - (BOOL)shouldBypassEvent:(NSEvent *)theEvent; - (void)processEventToHotKeyAndString:(NSEvent *)theEvent; - (void)windowResigned:(NSNotification *)notification; -- (NSDictionary *)hotKeyDictionaryForEvent:(NSEvent *)event; +- (GTMHotKey *)hotKeyForEvent:(NSEvent *)event; @end -@implementation GTMHotKeyTextField +@implementation GTMHotKey -#if GTM_SUPPORT_GC -- (void)finalize { - if (boundObject_ && boundKeyPath_) { - [boundObject_ gtm_removeObserver:self - forKeyPath:boundKeyPath_ - selector:@selector(hotKeyValueChanged:)]; - } - [super finalize]; -} -#endif +@synthesize modifiers = modifiers_; +@synthesize keyCode = keyCode_; +@synthesize doubledModifier = doubledModifier_; -- (void)dealloc { - if (boundObject_ && boundKeyPath_) { - [boundObject_ gtm_removeObserver:self - forKeyPath:boundKeyPath_ - selector:@selector(hotKeyValueChanged:)]; - } - [boundObject_ release]; - [boundKeyPath_ release]; - [hotKeyDict_ release]; - [super dealloc]; ++ (id)hotKeyWithKeyCode:(NSUInteger)keyCode + modifiers:(NSUInteger)modifiers + useDoubledModifier:(BOOL)doubledModifier { + return [[[[self class] alloc] initWithKeyCode:keyCode + modifiers:modifiers + useDoubledModifier:doubledModifier] autorelease]; } -#pragma mark Bindings - - -- (void)bind:(NSString *)binding toObject:(id)observableController - withKeyPath:(NSString *)keyPath - options:(NSDictionary *)options { - if ([binding isEqualToString:NSValueBinding]) { - // Update to our new binding - [self setupBinding:observableController withPath:keyPath]; - // TODO: Should deal with the bind options - } - [super bind:binding - toObject:observableController - withKeyPath:keyPath - options:options]; -} - -- (void)unbind:(NSString *)binding { - // Clean up value on unbind - if ([binding isEqualToString:NSValueBinding]) { - if (boundObject_ && boundKeyPath_) { - [boundObject_ gtm_removeObserver:self - forKeyPath:boundKeyPath_ - selector:@selector(hotKeyValueChanged:)]; - } - [boundObject_ release]; - boundObject_ = nil; - [boundKeyPath_ release]; - boundKeyPath_ = nil; - } - [super unbind:binding]; -} - -- (void)hotKeyValueChanged:(GTMKeyValueChangeNotification *)note { - NSDictionary *change = [note change]; - // Our binding has changed, update - id changedValue = [change objectForKey:NSKeyValueChangeNewKey]; - // NSUserDefaultsController does not appear to pass on the new object and, - // perhaps other controllers may not, so if we get a nil or NSNull back - // here let's directly retrieve the hotKeyDict_ from the object. - if (!changedValue || changedValue == [NSNull null]) { - id object = [note object]; - NSString *keyPath = [note keyPath]; - changedValue = [object valueForKeyPath:keyPath]; +- (id)initWithKeyCode:(NSUInteger)keyCode + modifiers:(NSUInteger)modifiers + useDoubledModifier:(BOOL)doubledModifier { + if ((self = [super init])) { + modifiers_ = modifiers; + keyCode_ = keyCode; + doubledModifier_ = doubledModifier; } - [hotKeyDict_ autorelease]; - hotKeyDict_ = [changedValue copy]; - [self updateDisplayedPrettyString]; + return self; } - -// Private convenience method for attaching to a new binding -- (void)setupBinding:(id)bound withPath:(NSString *)path { - // Release previous - if (boundObject_ && boundKeyPath_) { - [boundObject_ gtm_removeObserver:self - forKeyPath:boundKeyPath_ - selector:@selector(hotKeyValueChanged:)]; - } - [boundObject_ release]; - [boundKeyPath_ release]; - // Set new - boundObject_ = [bound retain]; - boundKeyPath_ = [path copy]; - // Make ourself an observer - [boundObject_ gtm_addObserver:self - forKeyPath:boundKeyPath_ - selector:@selector(hotKeyValueChanged:) - userInfo:nil - options:NSKeyValueObservingOptionNew]; - // Pull in any current value - [hotKeyDict_ autorelease]; - hotKeyDict_ = [[boundObject_ valueForKeyPath:boundKeyPath_] copy]; - // Update the display string - [self updateDisplayedPrettyString]; +- (BOOL)isEqual:(id)object { + return [object isKindOfClass:[GTMHotKey class]] + && [object modifiers] == [self modifiers] + && [(GTMHotKey *)object keyCode] == [self keyCode] + && [object doubledModifier] == [self doubledModifier]; } -#pragma mark Defeating NSControl - -- (int)logBadValueAccess { - // Defeating NSControl - _GTMDevLog(@"Hot key fields don't take numbers."); - return 0; +- (NSUInteger)hash { + return [self modifiers] + [self keyCode] + [self doubledModifier]; } -- (void)logBadStringValueAccess { - // Defeating NSControl - _GTMDevLog(@"Hot key fields want dictionaries, not strings."); +- (id)copyWithZone:(NSZone *)zone { + return NSCopyObject(self, 0, zone); } +@end -- (double)doubleValue { - return [self logBadValueAccess]; -} - -- (void)setDoubleValue:(double)value { - [self logBadValueAccess]; -} +@implementation GTMHotKeyTextField -- (float)floatValue { - return [self logBadValueAccess]; ++ (Class)cellClass { + return [GTMHotKeyTextFieldCell class]; } -- (void)setFloatValue:(float)value { - [self logBadValueAccess]; -} +@end -- (int)intValue { - return [self logBadValueAccess]; +@implementation GTMHotKeyTextFieldCell +- (void)dealloc { + [hotKey_ release]; + [super dealloc]; } -- (void)setIntValue:(int)value { - [self logBadValueAccess]; +- (id)copyWithZone:(NSZone *)zone { + GTMHotKeyTextFieldCell *copy = [super copyWithZone:zone]; + copy->hotKey_ = nil; + [copy setObjectValue:[self objectValue]]; + return copy; } -#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 - -- (NSInteger)integerValue { - return [self logBadValueAccess]; -} +#pragma mark Defeating NSCell -- (void)setIntegerValue:(NSInteger)value { - [self logBadValueAccess]; +- (void)logBadValueAccess { + _GTMDevLog(@"Hot key fields want hot key dictionaries as object values."); } -#endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 - - (id)objectValue { - return [self hotKeyValue]; + return hotKey_; } - (void)setObjectValue:(id)object { - [self setHotKeyValue:object]; + // Sanity only if set, nil is OK + if (object && ![object isKindOfClass:[GTMHotKey class]]) { + [self logBadValueAccess]; + return; + } + if (![hotKey_ isEqual:object]) { + // Otherwise we directly update ourself + [hotKey_ autorelease]; + hotKey_ = [object copy]; + [self updateDisplayedPrettyString]; + } } - (NSString *)stringValue { - return [[self class] displayStringForHotKey:hotKeyDict_]; + return [[self class] displayStringForHotKey:hotKey_]; } - (void)setStringValue:(NSString *)string { - [self logBadStringValueAccess]; + // Since we are a text cell, lots of AppKit objects will attempt to + // set out string value. Our Field editor should already have done + // that for us, so check to make sure what AppKit is setting us to is + // what we expect. + if (![string isEqual:[self stringValue]]) { + [self logBadValueAccess]; + } } - (NSAttributedString *)attributedStringValue { @@ -229,112 +158,55 @@ static CFStringRef kGTM_TISPropertyUnicodeKeyLayoutData = NULL; } - (void)setAttributedStringValue:(NSAttributedString *)string { - [self logBadStringValueAccess]; -} - -- (void)takeDoubleValueFrom:(id)sender { [self logBadValueAccess]; } -- (void)takeFloatValueFrom:(id)sender { - [self logBadValueAccess]; -} - -- (void)takeIntValueFrom:(id)sender { - [self logBadValueAccess]; -} - -- (void)takeObjectValueFrom:(id)sender { - // Defeating NSControl - _GTMDevLog(@"Hot key fields want dictionaries via bindings, " - @"not from controls."); -} - -- (void)takeStringValueFrom:(id)sender { - [self logBadStringValueAccess]; -} - - (id)formatter { return nil; } - (void)setFormatter:(NSFormatter *)newFormatter { - // Defeating NSControl - _GTMDevLog(@"Hot key fields don't accept formatters."); -} - -#pragma mark Hot Key Support - -+ (BOOL)isValidHotKey:(NSDictionary *)hotKeyDict { - if (!hotKeyDict || - ![hotKeyDict isKindOfClass:[NSDictionary class]] || - ![hotKeyDict objectForKey:kGTMHotKeyModifierFlagsKey] || - ![hotKeyDict objectForKey:kGTMHotKeyKeyCodeKey] || - ![hotKeyDict objectForKey:kGTMHotKeyDoubledModifierKey]) { - return NO; + if (newFormatter) { + // Defeating NSCell + _GTMDevLog(@"Hot key fields don't accept formatters."); } - return YES; } -- (void)setHotKeyValue:(NSDictionary *)hotKey { - // Sanity only if set, nil is OK - if (hotKey && ![[self class] isValidHotKey:hotKey]) { - return; - } - - // If we are bound we want to round trip through that interface - if (boundObject_ && boundKeyPath_) { - // If the change is accepted this will call us back as an observer - [boundObject_ setValue:hotKey forKeyPath:boundKeyPath_]; - } else { - // Otherwise we directly update ourself - [hotKeyDict_ autorelease]; - hotKeyDict_ = [hotKey copy]; - [self updateDisplayedPrettyString]; - } +- (id)_fieldEditor { + GTMHotKeyFieldEditor *editor = [GTMHotKeyFieldEditor sharedHotKeyFieldEditor]; + [editor setCell:self]; + return editor; } -- (NSDictionary *)hotKeyValue { - return hotKeyDict_; -} +#pragma mark Hot Key Support // Private method to update the displayed text of the field with the // user-readable representation. - (void)updateDisplayedPrettyString { - // Basic validation - if (![[self class] isValidHotKey:hotKeyDict_]) { - [super setStringValue:@""]; - return; - } - // Pretty string - NSString *prettyString = [[self class] displayStringForHotKey:hotKeyDict_]; + NSString *prettyString = [[self class] displayStringForHotKey:hotKey_]; if (!prettyString) { prettyString = @""; } - [super setStringValue:prettyString]; + [super setObjectValue:prettyString]; } -+ (NSString *)displayStringForHotKey:(NSDictionary *)hotKeyDict { - if (!hotKeyDict) return nil; ++ (NSString *)displayStringForHotKey:(GTMHotKey *)hotKey { + if (!hotKey) return nil; NSBundle *bundle = [NSBundle bundleForClass:[self class]]; // Modifiers - unsigned int flags - = [[hotKeyDict objectForKey:kGTMHotKeyModifierFlagsKey] unsignedIntValue]; - NSString *mods = [[self class] stringForModifierFlags:flags]; - if (flags && ![mods length]) return nil; + NSUInteger modifiers = [hotKey modifiers]; + NSString *mods = [[self class] stringForModifierFlags:modifiers]; + if (modifiers && ![mods length]) return nil; // Handle double modifier case - if ([[hotKeyDict objectForKey:kGTMHotKeyDoubledModifierKey] boolValue]) { + if ([hotKey doubledModifier]) { return [NSString stringWithFormat:@"%@ + %@", mods, mods]; } // Keycode - NSNumber *keyCodeNumber = [hotKeyDict objectForKey:kGTMHotKeyKeyCodeKey]; - if (!keyCodeNumber) return nil; - unsigned int keycode - = [keyCodeNumber unsignedIntValue]; + NSUInteger keycode = [hotKey keyCode]; NSString *keystroke = [[self class] stringForKeycode:keycode useGlyph:NO resourceBundle:bundle]; @@ -347,70 +219,6 @@ static CFStringRef kGTM_TISPropertyUnicodeKeyLayoutData = NULL; return [NSString stringWithFormat:@"%@%@", mods, keystroke]; } - -#pragma mark Field Editor Callbacks - -- (BOOL)textShouldBeginEditing:(GTMHotKeyFieldEditor *)fieldEditor { - // Sanity - if (![fieldEditor isKindOfClass:[GTMHotKeyFieldEditor class]]) { - _GTMDevLog(@"Field editor not appropriate for field, check window delegate"); - return NO; - } - - // We don't call super from here, because we are defeating default behavior - // as a result we have to call the delegate ourself. - id myDelegate = [self delegate]; - SEL selector = @selector(control:textShouldBeginEditing:); - if ([myDelegate respondsToSelector:selector]) { - if (![myDelegate control:self textShouldBeginEditing:fieldEditor]) return NO; - } - - // Update the field editor internal hotkey representation - [fieldEditor setHotKeyDictionary:hotKeyDict_]; // OK if its nil - return YES; -} - -- (void)textDidChange:(NSNotification *)notification { - // Sanity - GTMHotKeyFieldEditor *fieldEditor = GTM_STATIC_CAST(GTMHotKeyFieldEditor, - [notification object]); - if (![fieldEditor isKindOfClass:[GTMHotKeyFieldEditor class]]) { - _GTMDevLog(@"Field editor not appropriate for field, check window delegate"); - return; - } - - // When the field changes we want to read in the current hotkey value so - // bindings can validate - [self setHotKeyValue:[fieldEditor hotKeyDictionary]]; - - // Let super handle the notifications - [super textDidChange:notification]; -} - -- (BOOL)textShouldEndEditing:(GTMHotKeyFieldEditor *)fieldEditor { - // Sanity - if (![fieldEditor isKindOfClass:[GTMHotKeyFieldEditor class]]) { - _GTMDevLog(@"Field editor not appropriate for field, check window delegate"); - return NO; - } - - // Again we are defeating default behavior so we have to do delegate handling - // ourself. In this case our goal is simply to prevent the superclass from - // doing its own KVO, but we can also skip [[self cell] isEntryAcceptable:]. - // We'll also ignore the delegate control:textShouldEndEditing:. The field - // editor is done whether they like it or not. - id myDelegate = [self delegate]; - SEL selector = @selector(control:textShouldEndEditing:); - if ([myDelegate respondsToSelector:selector]) { - [myDelegate control:self textShouldEndEditing:fieldEditor]; - } - - // The end is always allowed, so set new value - [self setHotKeyValue:[fieldEditor hotKeyDictionary]]; - - return YES; -} - #pragma mark Class methods building strings for use w/in the UI. #if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4 @@ -473,7 +281,7 @@ static CFStringRef kGTM_TISPropertyUnicodeKeyLayoutData = NULL; // #define the class name to something else, and then you won't have any // conflicts. -+ (NSString *)stringForModifierFlags:(unsigned int)flags { ++ (NSString *)stringForModifierFlags:(NSUInteger)flags { UniChar modChars[4]; // We only look for 4 flags unsigned int charCount = 0; // These are in the same order as the menu manager shows them @@ -706,9 +514,10 @@ static CFStringRef kGTM_TISPropertyUnicodeKeyLayoutData = NULL; [str1 uppercaseString], [str2 uppercaseString]]; } else { - keystrokeString = [[[NSString alloc] initWithBytes:&secondChar - length:1 - encoding:NSMacOSRomanStringEncoding] autorelease]; + keystrokeString + = [[[NSString alloc] initWithBytes:&secondChar + length:1 + encoding:NSMacOSRomanStringEncoding] autorelease]; [keystrokeString uppercaseString]; } } @@ -745,6 +554,8 @@ static CFStringRef kGTM_TISPropertyUnicodeKeyLayoutData = NULL; GTMOBJECT_SINGLETON_BOILERPLATE(GTMHotKeyFieldEditor, sharedHotKeyFieldEditor) +@synthesize cell = cell_; + - (id)init { if ((self = [super init])) { [self setFieldEditor:YES]; // We are a field editor @@ -755,7 +566,7 @@ GTMOBJECT_SINGLETON_BOILERPLATE(GTMHotKeyFieldEditor, sharedHotKeyFieldEditor) // COV_NF_START // Singleton so never called. - (void)dealloc { - [hotKeyDict_ release]; + [cell_ release]; [super dealloc]; } // COV_NF_END @@ -863,15 +674,23 @@ GTMOBJECT_SINGLETON_BOILERPLATE(GTMHotKeyFieldEditor, sharedHotKeyFieldEditor) // hotkey plumbing. - (void)processEventToHotKeyAndString:(NSEvent *)theEvent { // Construct a dictionary of the event as a hotkey pref - NSDictionary *newHotKey = [self hotKeyDictionaryForEvent:theEvent]; - if (!newHotKey) { - NSBeep(); - return; // No action, but don't give up focus - } - NSString *prettyString = [GTMHotKeyTextField displayStringForHotKey:newHotKey]; - if (!prettyString) { - NSBeep(); - return; + GTMHotKey *newHotKey = nil; + NSString *prettyString = @""; + // 51 is "the delete key" + const NSUInteger allModifiers = (NSCommandKeyMask | NSAlternateKeyMask | + NSControlKeyMask | NSShiftKeyMask); + if (!(([theEvent keyCode] == 51 ) + && (([theEvent modifierFlags] & allModifiers)== 0))) { + newHotKey = [self hotKeyForEvent:theEvent]; + if (!newHotKey) { + NSBeep(); + return; // No action, but don't give up focus + } + prettyString = [GTMHotKeyTextFieldCell displayStringForHotKey:newHotKey]; + if (!prettyString) { + NSBeep(); + return; + } } // Replacement range @@ -885,12 +704,7 @@ GTMOBJECT_SINGLETON_BOILERPLATE(GTMHotKeyFieldEditor, sharedHotKeyFieldEditor) return; } - // Replacement was allowed, update - [hotKeyDict_ autorelease]; - hotKeyDict_ = [newHotKey retain]; - - // Set string on self, allowing super to handle attribute copying - [self setString:prettyString]; + [[self cell] setObjectValue:newHotKey]; // Finish the change [self didChangeText]; @@ -898,31 +712,10 @@ GTMOBJECT_SINGLETON_BOILERPLATE(GTMHotKeyFieldEditor, sharedHotKeyFieldEditor) // Force editing to end. This sends focus off into space slightly, but // its better than constantly capturing user events. This is exactly // like the Apple editor in their Keyboard pref pane. - id delegate = [self delegate]; - if ([delegate respondsToSelector:@selector(cell)]) { - [[delegate cell] endEditing:self]; - } -} - -- (NSDictionary *)hotKeyDictionary { - return hotKeyDict_; + [[self window] makeFirstResponder:nil]; } -- (void)setHotKeyDictionary:(NSDictionary *)hotKey { - [hotKeyDict_ autorelease]; - hotKeyDict_ = [hotKey copy]; - // Update content - NSString *prettyString = nil; - if (hotKeyDict_) { - prettyString = [GTMHotKeyTextField displayStringForHotKey:hotKey]; - } - if (!prettyString) { - prettyString = @""; - } - [self setString:prettyString]; -} - -- (NSDictionary *)hotKeyDictionaryForEvent:(NSEvent *)event { +- (GTMHotKey *)hotKeyForEvent:(NSEvent *)event { if (!event) return nil; // Check event @@ -933,7 +726,7 @@ GTMOBJECT_SINGLETON_BOILERPLATE(GTMHotKeyFieldEditor, sharedHotKeyFieldEditor) NSControlKeyMask | NSShiftKeyMask); BOOL requiresModifiers - = [GTMHotKeyTextField doesKeyCodeRequireModifier:keycode]; + = [GTMHotKeyTextFieldCell doesKeyCodeRequireModifier:keycode]; if (requiresModifiers) { // If we aren't a function key, and have no modifiers do nothing. if (!(flags & allModifiers)) return nil; @@ -947,15 +740,9 @@ GTMOBJECT_SINGLETON_BOILERPLATE(GTMHotKeyFieldEditor, sharedHotKeyFieldEditor) if (flags & NSAlternateKeyMask) cleanFlags |= NSAlternateKeyMask; if (flags & NSControlKeyMask) cleanFlags |= NSControlKeyMask; if (flags & NSShiftKeyMask) cleanFlags |= NSShiftKeyMask; - - return [NSDictionary dictionaryWithObjectsAndKeys: - [NSNumber numberWithBool:NO], - kGTMHotKeyDoubledModifierKey, - [NSNumber numberWithUnsignedInt:keycode], - kGTMHotKeyKeyCodeKey, - [NSNumber numberWithUnsignedInt:cleanFlags], - kGTMHotKeyModifierFlagsKey, - nil]; + return [GTMHotKey hotKeyWithKeyCode:keycode + modifiers:cleanFlags + useDoubledModifier:NO]; } @end |