From c0a143e0be4852a9709af8e5dcf8fa65fa165d35 Mon Sep 17 00:00:00 2001
From: vspader NDHotKeyEvent provides a thread safe Objective-C interface to HotKey events as well as some additional feature to key track of all the hot keys in your application.Thread Saftey
By default the class object NDHotKeyEvent is not thread safe as the underlying functions that it relies on are not thread safe and the mechanism for keeping track of all of the NDHotKeyEvent instances is not thread safe either. Thread saftey can be enable be defining the flag NDHotKeyEventThreadSafe before compiling.
Even with the flag NDHotKeyEventThreadSafe defined instances of NDHotKeyEvent will still not be thread safe, that is, it is safe to invoke methods of different instance with different threads as well as class methods, but it is not safe to invoke methods of the same instance with different threads.
+The functions stringForKeyCodeAndModifierFlags and unicharForKeyCode are never thread safe.
+ +Created by Nathan Day on Wed Feb 26 2003.
+ Copyright © 2002 Nathan Day. All rights reserved.
This class is a wrapper for Carbon Event HotKeys and provides some feature to key track of all the hot keys in your application. It can be used to be notified of key down as well as key up evernts and when a hot key is being taken by another object (see the protocol NDHotKeyEventTragetWillChange)
+ + */ +@interface NDHotKeyEvent : NSObject[decoder allowsKeyedCoding] == YES
.
+ @param decoder A subclass of NSCoder
+ @result A initialized NDHotKeyEvent
+ */
+- (id)initWithCoder:(NSCoder *)decoder;
+
+/*!
+ @method encodeWithCoder:
+ @abstract Encodes the receiver using encoder
+ @discussion Encodes the following properties of a NDHotKeyEvent;
+ [encoder allowsKeyedCoding] == YES
.
+ @param encoder A subclass of NSCoder.
+ */
+- (void)encodeWithCoder:(NSCoder *)encoder;
+
+/*!
+ @method setEnabled:
+ @abstract Set the hot key enabled or disable.
+ @discussion setEnabled: registers or unregisters the recievers hot key combination.
+ @param flag YES to enable, NO to disable.
+ @result Returns YES if successful
+ */
+- (BOOL)setEnabled:(BOOL)flag;
+
+/*!
+ @method isEnabled
+ @abstract Find out if a hot key is enabled.
+ @discussion Returns YES if the hot key is registered.
+ @result YES if enabled.
+ */
+- (BOOL)isEnabled;
+
+/*!
+ @method target
+ @abstract Get the hot key event target.
+ @discussion Returns the object that is sent the key pressed and key released hot key events, see the methods -selector, -selectorReleased and selectorPressed.
+ @result The target object.
+ */
+- (id)target;
+
+/*!
+ @method selector
+ @abstract The selector for a key released event.
+ @discussion This is the selector sent when the hot key combination for the reciever is released. This is the same selector has returned from the method [NDHotKeyEvent selectorReleased]
+ @result The method selector.
+ */
+- (SEL)selector;
+
+/*!
+ @method selectorReleased
+ @abstract The selector for a key released event.
+ @discussion This is the selector sent when the hot key combination for the reciever is released. This is the same selector has returned from the method [NDHotKeyEvent selector]
+ @result The method selector.
+ */
+- (SEL)selectorReleased;
+
+/*!
+ @method selectorPressed
+ @abstract The selector for a key pressed event.
+ @discussion This is the selector sent when the hot key combination for the reciever is pressed.
+ @result The method selector.
+ */
+- (SEL)selectorPressed;
+
+/*!
+ @method currentEventType
+ @abstract Get the current hot key event type.
+ @discussion This value returns what event last occured. Can be used in your target when it is sent a event message to find out what event occured, possible values are
+ ++ @result The last event type. + */ +- (int)currentEventType; + +/*! + @method setTarget:selector: + @abstract Set the hot key target. + @discussion Set the target object and selector to be sent when the hot key is released. + @param target The traget object. + @param selector The selector. + @result returns YES if successful. + */ +- (BOOL)setTarget:(id)target selector:(SEL)selector; + +/*! + @method setTarget:selectorReleased:selectorPressed: + @abstract Set the hot key target. + @discussion Set the target object and selector to be sent when the hot key is pressed and wehn it is released. + @param target The traget object. + @param selectorReleased The key released selector. + @param selectorPressed The key pressed selector. + @result returns YES if successful. + */ +- (BOOL)setTarget:(id)target selectorReleased:(SEL)selectorReleased selectorPressed:(SEL)selectorPressed; + +/*! + @method performHotKeyReleased + @abstract Invoke the target with the release selector. + @discussion Use to send the selector for a release event, though this method can be called by you. + */ +- (void)performHotKeyReleased; + +/*! + @method performHotKeyPressed + @abstract Invoke the target with the press selector. + @discussion Use to send the selector for a presse event, though this method can be called by you. + */ +- (void)performHotKeyPressed; + +/*! + @method keyCode + @abstract Get the hot key key code. + @discussion The key code for the hot key, this is hardware specific. + @result The key code. + */ +- (unsigned short)keyCode; + +/*! + @method character + @abstract Get the hot key character. + @discussion This is the character for the key code, without modifier keys. The character is for display purposes only and dose not determine the key code. + @result A uni code character. + */ +- (unichar)character; + +/*! + @method modifierFlags + @abstract Get the hot key modifer key flags. + @discussion The modifierFlags can be a bitwise and combination of NSControlKeyMask, NSAlternateKeyMask, NSShiftKeyMask, and NSCommandKeyMask. + @result The modifer key flags. + */ +- (unsigned int)modifierFlags; + +/*! + @method stringValue + @abstract Get a string got the hot keys. + @discussion This is a string that can be used for display purposes. + @result A NSString + */ +- (NSString *)stringValue; + +@end + +/*! + @protocol NSObject(NDHotKeyEventTragetWillChange) + @abstract Informal protocol used to inform a NDHotKeyEvent target of events. + @discussion The informal protocol NDHotKeyEventTragetWillChange defines a method used to notify a NDHotKeyEvent target that the target will change. + */ +@interface NSObject (NDHotKeyEventTragetWillChange) + +/*! + @method targetWillChangeToObject:forHotKeyEvent: + @abstract Message sent to a target object to inform it that the target is going to change. + @discussion This method can be used to notify the receiver that it will no longer be the target for a NDHotKeyEvent or used to prevent the target from changing by returning NO + @param target The new target for the NDHotKeyEvent + @param hotKeyEvent The NDHotKeyEvent for which the target is changing. + @result Return NO to prevent the target from changing, otherwise return YES. + */ +- (BOOL)targetWillChangeToObject:(id)target forHotKeyEvent:(NDHotKeyEvent *)hotKeyEvent; + +@end + +/*! + @function stringForKeyCodeAndModifierFlags + @abstract Get a string for hot key parameters. + @discussion Returns a string representation of the passed in hot key values. + @param keyCode A key code. + @param aChar A character representation of the key code. + @param modifierFlags modifer flags, comman, option, shift and control. + @result A NSString representing the hot key combination. + */ +NSString * stringForKeyCodeAndModifierFlags( unsigned short keyCode, unichar aChar, unsigned int modifierFlags ); +/*! + @function unicharForKeyCode + @abstract Get a unicode charater for the key combination. + @discussion The uncode chararter for the key combination. + @param keyCode The key code used by the keyboard, can vary across hardware. + @result A unichar + */ +unichar unicharForKeyCode( unsigned short keyCode ); diff --git a/Custom/NDHotKeyEvent.m b/Custom/NDHotKeyEvent.m new file mode 100755 index 000000000..802c4d0c7 --- /dev/null +++ b/Custom/NDHotKeyEvent.m @@ -0,0 +1,1135 @@ +/* + * NDHotKeyEvent.m + * NDHotKeyEvent + * + * Created by Nathan Day on Wed Feb 26 2003. + * Copyright (c) 2002 Nathan Day. All rights reserved. + */ + +#import "NDHotKeyEvent.h" + +@interface NDHotKeyEvent (Private) ++ (NSHashTable *)allHotKeyEvents; +- (BOOL)addHotKey; +- (void)removeHotKey; +- (BOOL)setCollectiveEnabled:(BOOL)aFlag; +- (BOOL)collectiveEnable; +@end + +static NSString * kArchivingKeyCodeKey = @"KeyCodeKey", + * kArchivingCharacterKey = @"CharacterKey", + * kArchivingModifierFlagsKey = @"ModifierFlagsKey", + * kArchivingSelectorReleasedCodeKey = @"SelectorReleasedCodeKey", + * kArchivingSelectorPressedCodeKey = @"SelectorPressedCodeKey"; +const OSType NDHotKeyDefaultSignature = 'NDHK'; + +static OSStatus switchHotKey( NDHotKeyEvent * self, BOOL aFlag ); + +unichar unicodeForFunctionKey( UInt32 aKeyCode ); + +/* + * class implementation NDHotKeyEvent + */ +@implementation NDHotKeyEvent + +#ifdef NDHotKeyEventThreadSafe + static NSLock * hotKeysLock = NULL; + #warning Thread saftey has been enabled for NDHotKeyEvent class methods + #define NDHotKeyEventLock [hotKeysLock lock] + #define NDHotKeyEventUnlock [hotKeysLock unlock] +#else + #warning The NDHotKeyEvent class methods are NOT thread safe + #define NDHotKeyEventLock // lock + #define NDHotKeyEventUnlock // unlock +#endif + +static NSHashTable * allHotKeyEvents = NULL; +static BOOL isInstalled = NO; +static OSType signature = 0; + +unsigned int cocoaModifierFlagsToCarbonModifierFlags( unsigned int aModifierFlags ); + +pascal OSErr eventHandlerCallback( EventHandlerCallRef anInHandlerCallRef, EventRef anInEvent, void * self ); + +unsigned hashValueHashFunction( NSHashTable * aTable, const void * aHotKeyEvent ); +BOOL isEqualHashFunction( NSHashTable * aTable, const void * aFirstHotKeyEvent, const void * aSecondHotKeyEvent); +NSString * describeHashFunction( NSHashTable * aTable, const void * aHotKeyEvent ); + +struct HotKeyMappingEntry +{ + unsigned short keyCode; + unsigned int modifierFlags; + NDHotKeyEvent * hotKeyEvent; +}; + +/* + * +install + */ ++ (BOOL)install +{ + if( isInstalled == NO ) + { + NSHashTable * theHotKeyEvents = [self allHotKeyEvents]; + EventTypeSpec theTypeSpec[] = + { + { kEventClassKeyboard, kEventHotKeyPressed }, + { kEventClassKeyboard, kEventHotKeyReleased } + }; + + NDHotKeyEventLock; + if( theHotKeyEvents != nil && isInstalled == NO ) + { + if( InstallEventHandler( GetEventDispatcherTarget(), NewEventHandlerUPP((EventHandlerProcPtr)eventHandlerCallback), 2, theTypeSpec, theHotKeyEvents, nil ) == noErr ) + { + isInstalled = YES; + } + else + { + NSLog(@"Could not install Event handler"); + } + } + NDHotKeyEventUnlock; + } + + return isInstalled; +} + +#ifdef NDHotKeyEventThreadSafe +/* + * +initialize: + */ ++ (void)initialize +{ + while( hotKeysLock == nil ) + { + NSLock * theInstance = [[NSLock alloc] init]; + + if( !CompareAndSwap( nil, (unsigned long int)theInstance, (unsigned long int*)&hotKeysLock) ) + [theInstance release]; // did not use instance + } +} +#endif + +/* + * +setSignature: + */ ++ (void)setSignature:(OSType)aSignature +{ + NSAssert( signature == 0 || aSignature == signature, @"The signature used by NDHotKeyEvent can only be set once safely" ); + signature = aSignature; +} + +/* + * +signature + */ ++ (OSType)signature +{ + signature = signature ? signature : NDHotKeyDefaultSignature; + return signature; +} + +/* + * +setAllEnabled: + */ ++ (BOOL)setAllEnabled:(BOOL)aFlag +{ + BOOL theAllSucceeded = YES; + NSHashTable * theHashTable = [NDHotKeyEvent allHotKeyEvents]; + + /* + * need to install before to make sure the method 'setCollectiveEnabled:' + * doesn't try install since install tries to aquire the lock 'hotKeysLock' + */ + if( theHashTable && [NDHotKeyEvent install] ) + { + NSHashEnumerator theEnumerator; + struct HotKeyMappingEntry * theHotKeyMapEntry; + NDHotKeyEventLock; + theEnumerator = NSEnumerateHashTable( theHashTable ); + + while( (theHotKeyMapEntry = (struct HotKeyMappingEntry*)NSNextHashEnumeratorItem(&theEnumerator) ) ) + { + if( ![theHotKeyMapEntry->hotKeyEvent setCollectiveEnabled:aFlag] ) + theAllSucceeded = NO; + } + + NSEndHashTableEnumeration( &theEnumerator ); + NDHotKeyEventUnlock; + } + + return theAllSucceeded; +} + +/* + * +isEnabledKeyCode:modifierFlags: + */ ++ (BOOL)isEnabledKeyCode:(unsigned short)aKeyCode modifierFlags:(unsigned int)aModifierFlags +{ + return [[self findHotKeyForKeyCode:aKeyCode modifierFlags:aModifierFlags] isEnabled]; +} + +/* + * +getHotKeyForKeyCode:character:modifierFlags: + */ ++ (NDHotKeyEvent *)getHotKeyForKeyCode:(unsigned short)aKeyCode character:(unichar)aChar modifierFlags:(unsigned int)aModifierFlags +{ + NDHotKeyEvent * theHotKey = nil; + + theHotKey = [self findHotKeyForKeyCode:aKeyCode modifierFlags:aModifierFlags]; + return theHotKey ? theHotKey : [self hotKeyWithKeyCode:aKeyCode character:aChar modifierFlags:aModifierFlags]; +} + +/* + * +findHotKeyForKeyCode:modifierFlags: + */ ++ (NDHotKeyEvent *)findHotKeyForKeyCode:(unsigned short)aKeyCode modifierFlags:(unsigned int)aModifierFlags +{ + struct HotKeyMappingEntry * theFoundEntry = NULL; + NSHashTable * theHashTable = [NDHotKeyEvent allHotKeyEvents]; + + if( theHashTable ) + { + struct HotKeyMappingEntry theDummyEntry; + + theDummyEntry.keyCode = aKeyCode; + theDummyEntry.modifierFlags = aModifierFlags; + theDummyEntry.hotKeyEvent = nil; + + NDHotKeyEventLock; + theFoundEntry = NSHashGet( theHashTable, (void*)&theDummyEntry); + if( theFoundEntry != NULL ) + [[theFoundEntry->hotKeyEvent retain] autorelease]; + NDHotKeyEventUnlock; + } + + return (theFoundEntry) ? theFoundEntry->hotKeyEvent : nil; +} + +/* + * +hotKeyWithKeyCode:character:modifierFlags: + */ ++ (id)hotKeyWithKeyCode:(unsigned short)aKeyCode character:(unichar)aChar modifierFlags:(unsigned int)aModifierFlags +{ + return [self hotKeyWithKeyCode:aKeyCode character:aChar modifierFlags:aModifierFlags target:nil selector:NULL]; +} + +/* + * +hotKeyWithKeyCode:character:modifierFlags:target:selector: + */ ++ (id)hotKeyWithKeyCode:(unsigned short)aKeyCode character:(unichar)aChar modifierFlags:(unsigned int)aModifierFlags target:(id)aTarget selector:(SEL)aSelector +{ + return [[[self alloc] initWithKeyCode:aKeyCode character:aChar modifierFlags:aModifierFlags target:aTarget selector:aSelector] autorelease]; +} + ++ (id)hotKeyWithWithPropertyList:(id)aPropertyList +{ + return [[[self alloc] initWithPropertyList:aPropertyList] autorelease]; +} + ++ (NSString *)description +{ + NSHashTable * theHashTable = [NDHotKeyEvent allHotKeyEvents]; + NSString * theDescription = nil; + if( theHashTable ) + { + NDHotKeyEventLock; + theDescription = NSStringFromHashTable(theHashTable); + NDHotKeyEventUnlock; + } + return theDescription; +} + +/* + * -init + */ +- (id)init +{ + [self release]; + NSAssert( NO, @"You can not initialize a Hot Key with the init method" ); + return nil; +} + +/* + * -initWithKeyCode:character:modifierFlags: + */ +- (id)initWithKeyCode:(unsigned short)aKeyCode character:(unichar)aChar modifierFlags:(unsigned int)aModifierFlags +{ + return [self initWithKeyCode:aKeyCode character:aChar modifierFlags:aModifierFlags target:nil selector:NULL]; +} + +/* + * -initWithKeyCode:character:modifierFlags:target:selector: + */ +- (id)initWithKeyCode:(unsigned short)aKeyCode character:(unichar)aChar modifierFlags:(unsigned int)aModifierFlags target:(id)aTarget selector:(SEL)aSelector +{ + if( (self = [super init]) != nil ) + { + keyCode = aKeyCode; + character = aChar; + modifierFlags = aModifierFlags; + target = aTarget; + selectorReleased = aSelector; + currentEventType = NDHotKeyNoEvent; + isEnabled.collective = YES; + + if( ![self addHotKey] ) + { + [self release]; + self = nil; + } + } + else + { + [self release]; + self = nil; + } + + return self; +} + +- (id)initWithCoder:(NSCoder *)aDecoder +{ + if( (self = [super init]) != nil) + { + if( [aDecoder allowsKeyedCoding] ) + { + keyCode = [[aDecoder decodeObjectForKey:kArchivingKeyCodeKey] unsignedShortValue]; + character = [[aDecoder decodeObjectForKey:kArchivingCharacterKey] unsignedShortValue]; + modifierFlags = [[aDecoder decodeObjectForKey:kArchivingModifierFlagsKey] unsignedIntValue]; + + selectorReleased = NSSelectorFromString( [aDecoder decodeObjectForKey:kArchivingSelectorReleasedCodeKey] ); + selectorPressed = NSSelectorFromString( [aDecoder decodeObjectForKey:kArchivingSelectorPressedCodeKey] ); + } + else + { + [aDecoder decodeValueOfObjCType:@encode(unsigned short) at:&keyCode]; + [aDecoder decodeValueOfObjCType:@encode(unichar) at:&character]; + [aDecoder decodeValueOfObjCType:@encode(unsigned int) at:&modifierFlags]; + + selectorReleased = NSSelectorFromString( [aDecoder decodeObject] ); + selectorPressed = NSSelectorFromString( [aDecoder decodeObject] ); + } + + if( ![self addHotKey] ) + { + [self release]; + self = nil; + } + } + + return self; +} + +- (void)encodeWithCoder:(NSCoder *)anEncoder +{ + if( [anEncoder allowsKeyedCoding] ) + { + [anEncoder encodeObject:[NSNumber numberWithUnsignedShort:keyCode] forKey:kArchivingKeyCodeKey]; + [anEncoder encodeObject:[NSNumber numberWithUnsignedShort:character] forKey:kArchivingCharacterKey]; + [anEncoder encodeObject:[NSNumber numberWithUnsignedInt:modifierFlags] forKey:kArchivingModifierFlagsKey]; + + [anEncoder encodeObject:NSStringFromSelector( selectorReleased ) forKey:kArchivingSelectorReleasedCodeKey]; + [anEncoder encodeObject:NSStringFromSelector( selectorPressed ) forKey:kArchivingSelectorPressedCodeKey]; + } + else + { + [anEncoder encodeValueOfObjCType:@encode(unsigned short) at:&keyCode]; + [anEncoder encodeValueOfObjCType:@encode(unichar) at:&character]; + [anEncoder encodeValueOfObjCType:@encode(unsigned int) at:&modifierFlags]; + + [anEncoder encodeObject:NSStringFromSelector( selectorReleased )]; + [anEncoder encodeObject:NSStringFromSelector( selectorPressed )]; + } +} + +- (id)initWithPropertyList:(id)aPropertyList +{ + if( aPropertyList ) + { + NSString * theCharacter; + NSNumber * theKeyCode, + * theModiferFlag; + SEL theKeyPressedSelector, + theKeyReleasedSelector; + + theKeyCode = [aPropertyList objectForKey:kArchivingKeyCodeKey]; + theCharacter = [aPropertyList objectForKey:kArchivingCharacterKey]; + theModiferFlag = [aPropertyList objectForKey:kArchivingModifierFlagsKey]; + theKeyPressedSelector = NSSelectorFromString([aPropertyList objectForKey:kArchivingSelectorPressedCodeKey]); + theKeyReleasedSelector = NSSelectorFromString([aPropertyList objectForKey:kArchivingSelectorReleasedCodeKey]); + + self = [self initWithKeyCode:[theKeyCode unsignedShortValue] character:[theCharacter characterAtIndex:0] modifierFlags:[theModiferFlag unsignedIntValue]]; + } + else + { + [self release]; + self = nil; + } + + return self; +} + +- (id)propertyList +{ + return [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithUnsignedShort:[self keyCode]], kArchivingKeyCodeKey, + [NSString stringWithCharacters:&character length:1] , kArchivingCharacterKey, + [NSNumber numberWithUnsignedInt:[self modifierFlags]], kArchivingModifierFlagsKey, + NSStringFromSelector( selectorPressed ), kArchivingSelectorPressedCodeKey, + NSStringFromSelector( selectorReleased ), kArchivingSelectorReleasedCodeKey, + nil]; +} + +/* + * -release + */ +- (oneway void)release +{ + /* + * We need to remove the hot key from the hash table before it's retain count reaches zero + */ + if( [self retainCount] == 1 ) + { + NSHashTable * theHashTable = [NDHotKeyEvent allHotKeyEvents]; + if( theHashTable ) + { + struct HotKeyMappingEntry theDummyEntry; + + theDummyEntry.keyCode = [self keyCode]; + theDummyEntry.modifierFlags = [self modifierFlags]; + theDummyEntry.hotKeyEvent = nil; + + NDHotKeyEventLock; + switchHotKey( self, NO ); + if( [self retainCount] == 1 ) // check again because it might have changed + { + id theHotKeyEvent = NSHashGet( theHashTable, (void*)&theDummyEntry ); + if( theHotKeyEvent ) + NSHashRemove( theHashTable, theHotKeyEvent ); + } + NDHotKeyEventUnlock; + } + } +// else + [super release]; +} + +- (void)dealloc +{ +// if( UnregisterEventHotKey( reference ) != noErr ) // in lock from release +// NSLog( @"Failed to unregister hot key %@", self ); + [super dealloc]; +} + +/* + * -setEnabled: + */ +- (BOOL)setEnabled:(BOOL)aFlag +{ + BOOL theResult = YES; + + if( [NDHotKeyEvent install] ) + { + /* + * if individual and collective YES then currently ON, otherwise currently off + */ + NDHotKeyEventLock; + if( aFlag == YES && isEnabled.collective == YES && isEnabled.individual == NO ) + { + theResult = (switchHotKey( self, YES ) == noErr); + } + else if( aFlag == NO && isEnabled.collective == YES && isEnabled.individual == YES ) + { + theResult = (switchHotKey( self, NO ) == noErr); + } + NDHotKeyEventUnlock; + + if( theResult ) + isEnabled.individual = aFlag; + else + NSLog(@"%s failed ", aFlag ? "enable" : "disable" ); + } + else + theResult = NO; + + return theResult; +} + +/* + * -isEnabled + */ +- (BOOL)isEnabled +{ + return isEnabled.individual && isEnabled.collective; +} + +/* + * -target + */ +- (id)target +{ + return target; +} + +/* +* -selector +*/ +- (SEL)selector +{ + return selectorReleased; +} + +/* + * -selectorReleased + */ +- (SEL)selectorReleased +{ + return selectorReleased; +} + +/* +* -selectorPressed +*/ +- (SEL)selectorPressed +{ + return selectorPressed; +} + +/* + * -currentEventType + * (NDHotKeyNoEvent | NDHotKeyPressedEvent | NDHotKeyReleasedEvent) + */ +- (int)currentEventType +{ + return currentEventType; +} + +/* + * -setTarget:selector: + */ +- (BOOL)setTarget:(id)aTarget selector:(SEL)aSelector +{ + return [self setTarget:aTarget selectorReleased:aSelector selectorPressed:(SEL)0]; +} + +/* + * -setTarget:selectorReleased:selectorPressed: + */ +- (BOOL)setTarget:(id)aTarget selectorReleased:(SEL)aSelectorReleased selectorPressed:(SEL)aSelectorPressed +{ + [self setEnabled:NO]; + if( target && target != aTarget ) + { + [self setEnabled:NO]; + if( ![target respondsToSelector:@selector(targetWillChangeToObject:forHotKeyEvent:)] || [target targetWillChangeToObject:aTarget forHotKeyEvent:self] ) + { + target = aTarget; + selectorReleased = aSelectorReleased; + selectorPressed = aSelectorPressed; + } + } + else + { + target = aTarget; + selectorReleased = aSelectorReleased; + selectorPressed = aSelectorPressed; + } + + return target == aTarget; // was change succesful +} + +/* + * -performHotKeyReleased + */ +- (void)performHotKeyReleased +{ + NSAssert( target, @"NDHotKeyEvent tried to perfrom release with no target" ); + + if( selectorReleased && [target respondsToSelector:selectorReleased]) + { + currentEventType = NDHotKeyReleasedEvent; + [target performSelector:selectorReleased withObject:self]; + currentEventType = NDHotKeyNoEvent; + } +} + +/* + * -performHotKeyPressed + */ +- (void)performHotKeyPressed +{ + NSAssert( target, @"NDHotKeyEvent tried to perfrom press with no target" ); + + if( selectorPressed && [target respondsToSelector:selectorPressed]) + { + currentEventType = NDHotKeyPressedEvent; + [target performSelector:selectorPressed withObject:self]; + currentEventType = NDHotKeyNoEvent; + } +} + +/* + * -keyCode + */ +- (unsigned short)keyCode +{ + return keyCode; +} + +/* + * -character + */ +- (unichar)character +{ + return character; +} + +/* + * -modifierFlags + */ +- (unsigned int)modifierFlags +{ + return modifierFlags; +} + +/* + * -stringValue + */ +- (NSString *)stringValue +{ + NSString * theStringValue = nil; + NDHotKeyEventLock; + theStringValue = stringForKeyCodeAndModifierFlags( [self keyCode], [self character], [self modifierFlags] ); + NDHotKeyEventUnlock; + + return theStringValue; +} + + +/* + * -isEqual: + */ +- (BOOL)isEqual:(id)anObject +{ + return [super isEqual:anObject] || ([anObject isKindOfClass:[self class]] == YES && [self keyCode] == [(NDHotKeyEvent*)anObject keyCode] && [self modifierFlags] == [anObject modifierFlags]); +} + +/* + * -hash + */ +- (unsigned int)hash +{ + return ((unsigned int)keyCode & ~modifierFlags) | (modifierFlags & ~((unsigned int)keyCode)); // xor +} + +/* + * -description + */ +- (NSString *)description +{ + return [NSString stringWithFormat:@"{\n\tKey Combination: %@,\n\tEnabled: %s\n\tKey Press Selector: %@\n\tKey Release Selector: %@\n}\n", [self stringValue], + [self isEnabled] ? "yes" : "no", + NSStringFromSelector([self selectorPressed]), + NSStringFromSelector([self selectorReleased])]; +} + +/* + * cocoaModifierFlagsToCarbonModifierFlags() + */ +unsigned int cocoaModifierFlagsToCarbonModifierFlags( unsigned int aModifierFlags ) +{ + unsigned int theCarbonModifierFlags = 0; + + if(aModifierFlags & NSShiftKeyMask) + theCarbonModifierFlags |= shiftKey; + + if(aModifierFlags & NSControlKeyMask) + theCarbonModifierFlags |= controlKey; + + if(aModifierFlags & NSAlternateKeyMask) + theCarbonModifierFlags |= optionKey; + + if(aModifierFlags & NSCommandKeyMask) + theCarbonModifierFlags |= cmdKey; + + return theCarbonModifierFlags; +} + +/* + * eventHandlerCallback() + */ +pascal OSErr eventHandlerCallback( EventHandlerCallRef anInHandlerCallRef, EventRef anInEvent, void * anInUserData ) +{ +// NSHashTable * allHotKeyEvents = (NSHashTable *)anInUserData; + EventHotKeyID theHotKeyID; + OSStatus theError; + + NSCAssert( GetEventClass( anInEvent ) == kEventClassKeyboard, @"Got event that is not a hot key event" ); + + theError = GetEventParameter( anInEvent, kEventParamDirectObject, typeEventHotKeyID, NULL, sizeof(EventHotKeyID), NULL, &theHotKeyID ); + + if( theError == noErr ) + { + NDHotKeyEvent * theHotKeyEvent; + UInt32 theEventKind; + + NSCAssert( [NDHotKeyEvent signature] == theHotKeyID.signature, @"Got hot key event with wrong signature" ); + + theHotKeyEvent = (NDHotKeyEvent*)theHotKeyID.id; + + theEventKind = GetEventKind( anInEvent ); + if( kEventHotKeyPressed == theEventKind ) + { + [theHotKeyEvent performHotKeyPressed]; + } + else if( kEventHotKeyReleased == theEventKind ) + { + [theHotKeyEvent performHotKeyReleased]; + } + } + + return theError; +} + +/* + * hashValueHashFunction() + */ +unsigned hashValueHashFunction( NSHashTable * aTable, const void * aHotKeyEntry ) +{ + struct HotKeyMappingEntry * theHotKeyEntry; + unsigned int theKeyCode, + theModifiers; + + theHotKeyEntry = (struct HotKeyMappingEntry*)aHotKeyEntry; + theKeyCode = (unsigned int)theHotKeyEntry->keyCode; + theModifiers = (unsigned int)theHotKeyEntry->modifierFlags; + return theKeyCode ^ theModifiers; // xor +} + +/* + * isEqualHashFunction() + */ +BOOL isEqualHashFunction( NSHashTable * aTable, const void * aFirstHotKeyEntry, const void * aSecondHotKeyEntry) +{ + struct HotKeyMappingEntry * theFirst, + * theSecond; + + theFirst = (struct HotKeyMappingEntry*)aFirstHotKeyEntry; + theSecond = (struct HotKeyMappingEntry*)aSecondHotKeyEntry; + return theFirst->keyCode == theSecond->keyCode && theFirst->modifierFlags == theSecond->modifierFlags; +} + +/* + * describeHashFunction() + */ +NSString * describeHashFunction( NSHashTable * aTable, const void * aHotKeyEntry ) +{ + NDHotKeyEvent * theHotKey; + + theHotKey = ((struct HotKeyMappingEntry*)aHotKeyEntry)->hotKeyEvent; + return [theHotKey description]; +} + +@end + +@implementation NDHotKeyEvent (Private) + +/* + * +allHotKeyEvents + */ ++ (NSHashTable *)allHotKeyEvents +{ + if( allHotKeyEvents == NULL ) + { + NSHashTableCallBacks theHashCallBacks; + + theHashCallBacks.hash = hashValueHashFunction; + theHashCallBacks.isEqual = isEqualHashFunction; + theHashCallBacks.retain = NULL; + theHashCallBacks.release = NULL; + theHashCallBacks.describe = describeHashFunction; + + NDHotKeyEventLock; + if( allHotKeyEvents == NULL ) + allHotKeyEvents = NSCreateHashTableWithZone( theHashCallBacks, 0, NULL); + NDHotKeyEventUnlock; + } + + return allHotKeyEvents; +} + +/* + * -addHotKey + */ +- (BOOL)addHotKey +{ + BOOL theSuccess = NO; + NSHashTable * theHashTable = [NDHotKeyEvent allHotKeyEvents]; + if( theHashTable ) + { + struct HotKeyMappingEntry * theEntry; + + theEntry = (struct HotKeyMappingEntry *)malloc(sizeof(struct HotKeyMappingEntry)); + + theEntry->keyCode = [self keyCode]; + theEntry->modifierFlags = [self modifierFlags]; + theEntry->hotKeyEvent = self; + + NDHotKeyEventLock; + theSuccess = NSHashInsertIfAbsent( theHashTable, (void*)theEntry ) == NULL; + NDHotKeyEventUnlock; + } + + return theSuccess; +} + +/* + * -removeHotKey + */ +- (void)removeHotKey +{ + [self setEnabled:NO]; + + NSHashTable * theHashTable = [NDHotKeyEvent allHotKeyEvents]; + if( theHashTable ) + { + struct HotKeyMappingEntry theDummyEntry; + id theHotKeyEvent; + + theDummyEntry.keyCode = [self keyCode]; + theDummyEntry.modifierFlags = [self modifierFlags]; + theDummyEntry.hotKeyEvent = nil; + + NDHotKeyEventLock; + theHotKeyEvent = NSHashGet( theHashTable, (void*)&theDummyEntry); + if( theHotKeyEvent ) + NSHashRemove( theHashTable, theHotKeyEvent ); + NDHotKeyEventUnlock; + } +} + +/* + * setCollectiveEnabled: + */ +- (BOOL)setCollectiveEnabled:(BOOL)aFlag +{ + BOOL theResult = YES; + + if( [NDHotKeyEvent install] ) + { + /* + * if individual and collective YES then currently ON, otherwise currently off + */ + NDHotKeyEventLock; + if( aFlag == YES && isEnabled.collective == NO && isEnabled.individual == YES ) + { + theResult = (switchHotKey( self, YES ) == noErr); + } + else if( aFlag == NO && isEnabled.collective == YES && isEnabled.individual == YES ) + { + theResult = (switchHotKey( self, NO ) == noErr); + } + NDHotKeyEventUnlock; + + if( theResult ) + isEnabled.collective = aFlag; + else + NSLog(@"%s failed", aFlag ? "enable" : "disable" ); + } + else + theResult = NO; + + return theResult; +} + +/* + * collectiveEnable() + */ +- (BOOL)collectiveEnable +{ + return isEnabled.collective; +} + +/* + * switchHotKey() + */ +static OSStatus switchHotKey( NDHotKeyEvent * self, BOOL aFlag ) +{ + OSStatus theError; + if( aFlag ) + { + EventHotKeyID theHotKeyID; + + theHotKeyID.signature = [NDHotKeyEvent signature]; + theHotKeyID.id = (unsigned int)self; + + NSCAssert( theHotKeyID.signature, @"HotKeyEvent signature has not been set yet" ); + NSCParameterAssert(sizeof(unsigned long) >= sizeof(id) ); + + theError = RegisterEventHotKey( self->keyCode, cocoaModifierFlagsToCarbonModifierFlags(self->modifierFlags), theHotKeyID, GetEventDispatcherTarget(), 0, &self->reference ); + } + else + { + theError = UnregisterEventHotKey( self->reference ); + } + + return theError; +} + +@end + + +/* + * stringForKeyCodeAndModifierFlags() + */ +NSString * stringForKeyCodeAndModifierFlags( unsigned short aKeyCode, unichar aChar, unsigned int aModifierFlags ) +{ + NSString * stringForCharacter( const unsigned short aKeyCode, unichar aCharacter ); + NSString * stringForModifiers( unsigned int aModifierFlags ); + + return [stringForModifiers(aModifierFlags) stringByAppendingString:stringForCharacter( aKeyCode, aChar )]; +} + +/* + * unicharForKeyCode() + */ +unichar unicharForKeyCode( unsigned short aKeyCode ) +{ + static UInt32 theState = 0; + const void * theKeyboardLayoutData; + KeyboardLayoutRef theCurrentKeyBoardLayout; + UInt32 theChar = kNullCharCode; + + if( KLGetCurrentKeyboardLayout( &theCurrentKeyBoardLayout ) == noErr && KLGetKeyboardLayoutProperty( theCurrentKeyBoardLayout, kKLKCHRData, &theKeyboardLayoutData) == noErr ) + { + theChar = KeyTranslate ( theKeyboardLayoutData, aKeyCode, &theState ); + + switch( theChar ) + { + case kHomeCharCode: theChar = NSHomeFunctionKey; break; +// case kEnterCharCode: theChar = ; break; + case kEndCharCode: theChar = NSEndFunctionKey; break; + case kHelpCharCode: theChar = NSHelpFunctionKey; break; +// case kBellCharCode: theChar = ; break; +// case kBackspaceCharCode: theChar = ; break; +// case kTabCharCode: theChar = ; break; +// case kLineFeedCharCode: theChar = ; break; + case kPageUpCharCode: theChar = NSPageUpFunctionKey; break; + case kPageDownCharCode: theChar = NSPageDownFunctionKey; break; +// case kReturnCharCode: theChar = ; break; + case kFunctionKeyCharCode: theChar = unicodeForFunctionKey( aKeyCode ); break; +// case kCommandCharCode: theChar = ; break; +// case kCheckCharCode: theChar = ; break; +// case kDiamondCharCode : theChar = ; break; +// case kAppleLogoCharCode: theChar = ; break; +// case kEscapeCharCode: theChar = ; break; + case kClearCharCode: + theChar = (aKeyCode==0x47) ? NSInsertFunctionKey : theChar; + break; + case kLeftArrowCharCode: theChar = NSLeftArrowFunctionKey; break; + case kRightArrowCharCode: theChar = NSRightArrowFunctionKey; break; + case kUpArrowCharCode: theChar = NSUpArrowFunctionKey; break; + case kDownArrowCharCode: theChar = NSDownArrowFunctionKey; break; +// case kSpaceCharCode: theChar = ; break; + case kDeleteCharCode: theChar = NSDeleteCharFunctionKey; break; +// case kBulletCharCode: theChar = ; break; +// case kNonBreakingSpaceCharCode: theChar = ; break; + } + } + + return theChar; +} + +unichar unicodeForFunctionKey( UInt32 aKeyCode ) +{ + switch( aKeyCode ) + { + case 0x7A: return NSF1FunctionKey; + case 0x78: return NSF2FunctionKey; + case 0x63: return NSF3FunctionKey; + case 0x76: return NSF4FunctionKey; + case 0x60: return NSF5FunctionKey; + case 0x61: return NSF6FunctionKey; + case 0x62: return NSF7FunctionKey; + case 0x64: return NSF8FunctionKey; + case 0x65: return NSF9FunctionKey; + case 0x6D: return NSF10FunctionKey; + case 0x67: return NSF11FunctionKey; + case 0x6F: return NSF12FunctionKey; + default: return 0x00; + } +} + +NSString * stringForCharacter( const unsigned short aKeyCode, unichar aCharacter ) +{ + NSString * theString = nil; + switch( aCharacter ) + { + case NSUpArrowFunctionKey: + aCharacter = 0x2191; + theString = [NSString stringWithCharacters:&aCharacter length:1]; + break; + case NSDownArrowFunctionKey: + aCharacter = 0x2193; + theString = [NSString stringWithCharacters:&aCharacter length:1]; + break; + case NSLeftArrowFunctionKey: + aCharacter = 0x2190; + theString = [NSString stringWithCharacters:&aCharacter length:1]; + break; + case NSRightArrowFunctionKey: + aCharacter = 0x2192; + theString = [NSString stringWithCharacters:&aCharacter length:1]; + break; + case NSF1FunctionKey: theString = @"F1"; break; + case NSF2FunctionKey: theString = @"F2"; break; + case NSF3FunctionKey: theString = @"F3"; break; + case NSF4FunctionKey: theString = @"F4"; break; + case NSF5FunctionKey: theString = @"F5"; break; + case NSF6FunctionKey: theString = @"F6"; break; + case NSF7FunctionKey: theString = @"F7"; break; + case NSF8FunctionKey: theString = @"F8"; break; + case NSF9FunctionKey: theString = @"F9"; break; + case NSF10FunctionKey: theString = @"F10"; break; + case NSF11FunctionKey: theString = @"F11"; break; + case NSF12FunctionKey: theString = @"F12"; break; + case NSF13FunctionKey: theString = @"F13"; break; + case NSF14FunctionKey: theString = @"F14"; break; + case NSF15FunctionKey: theString = @"F15"; break; + case NSF16FunctionKey: theString = @"F16"; break; + case NSF17FunctionKey: theString = @"F17"; break; + case NSF18FunctionKey: theString = @"F18"; break; + case NSF19FunctionKey: theString = @"F19"; break; + case NSF20FunctionKey: theString = @"F20"; break; + case NSF21FunctionKey: theString = @"F21"; break; + case NSF22FunctionKey: theString = @"F22"; break; + case NSF23FunctionKey: theString = @"F23"; break; + case NSF24FunctionKey: theString = @"F24"; break; + case NSF25FunctionKey: theString = @"F25"; break; + case NSF26FunctionKey: theString = @"F26"; break; + case NSF27FunctionKey: theString = @"F27"; break; + case NSF28FunctionKey: theString = @"F28"; break; + case NSF29FunctionKey: theString = @"F29"; break; + case NSF30FunctionKey: theString = @"F30"; break; + case NSF31FunctionKey: theString = @"F31"; break; + case NSF32FunctionKey: theString = @"F32"; break; + case NSF33FunctionKey: theString = @"F33"; break; + case NSF34FunctionKey: theString = @"F34"; break; + case NSF35FunctionKey: theString = @"F35"; break; + case NSInsertFunctionKey: theString = @"Ins"; break; + case NSDeleteFunctionKey: theString = @"Delete"; break; + case NSHomeFunctionKey: + aCharacter = 0x2196; + theString = [NSString stringWithCharacters:&aCharacter length:1]; + break; + case NSBeginFunctionKey: theString = @"Begin"; break; + case NSEndFunctionKey: theString = @"End"; break; + case NSPageUpFunctionKey: + // aCharacter = 0x21DE; + // theString = [NSString stringWithCharacters:&aCharacter length:1]; + theString = @"PgUp"; + break; + case NSPageDownFunctionKey: +// aCharacter = 0x21DF; +// theString = [NSString stringWithCharacters:&aCharacter length:1]; + theString = @"PgDn"; + break; + case NSPrintScreenFunctionKey: theString = @"Print"; break; + case NSScrollLockFunctionKey: theString = @"ScrollLock"; break; + case NSPauseFunctionKey: theString = @"Pause"; break; + case NSSysReqFunctionKey: theString = @"SysReq"; break; + case NSBreakFunctionKey: theString = @"Break"; break; + case NSResetFunctionKey: theString = @"Reset"; break; + case NSStopFunctionKey: theString = @"Stop"; break; + case NSMenuFunctionKey: theString = @"Menu"; break; + case NSUserFunctionKey: theString = @"User"; break; + case NSSystemFunctionKey: theString = @"System"; break; + case NSPrintFunctionKey: theString = @"Print"; break; + case NSClearLineFunctionKey: theString = @"ClearLine"; break; + case NSClearDisplayFunctionKey: theString = @"ClearDisplay"; break; + case NSInsertLineFunctionKey: theString = @"InsertLine"; break; + case NSDeleteLineFunctionKey: theString = @"DeleteLine"; break; + case NSInsertCharFunctionKey: theString = @"InsertChar"; break; + case NSDeleteCharFunctionKey: + aCharacter = 0x2326; + theString = [NSString stringWithCharacters:&aCharacter length:1]; + break; + case NSPrevFunctionKey: theString = @"Prev"; break; + case NSNextFunctionKey: theString = @"Next"; break; + case NSSelectFunctionKey: theString = @"Select"; break; + case NSExecuteFunctionKey: theString = @"Exec"; break; + case NSUndoFunctionKey: theString = @"Undo"; break; + case NSRedoFunctionKey: theString = @"Redo"; break; + case NSFindFunctionKey: theString = @"Find"; break; + case NSHelpFunctionKey: theString = @"Help"; break; + case NSModeSwitchFunctionKey: theString = @"ModeSwitch"; break; + case kEscapeCharCode: theString = @"Esc"; break; + case kTabCharCode: + aCharacter = 0x21E5; + theString = [NSString stringWithCharacters:&aCharacter length:1]; + break; + case kSpaceCharCode: theString = @"Space"; break; + case kEnterCharCode: + aCharacter = 0x21B5; + theString = [NSString stringWithCharacters:&aCharacter length:1]; + break; + case kReturnCharCode: + aCharacter = 0x21A9; + theString = [NSString stringWithCharacters:&aCharacter length:1]; + break; + case kDeleteCharCode: theString = @"Del"; break; + case '0'...'9': + case '=': + case '/': + case '*': + case '-': + case '+': + case '.': + theString = [NSString stringWithCharacters:&aCharacter length:1]; + if( aKeyCode > 60 ) + theString = [NSString stringWithFormat:@"[%@]", theString]; + break; + default: + aCharacter = unicharForKeyCode(aKeyCode); + + if( aCharacter >= 'a' && aCharacter <= 'z' ) // convert to uppercase + aCharacter = aCharacter + 'A' - 'a'; + + theString = [NSString stringWithCharacters:&aCharacter length:1]; + break; + } + return theString; +} + +NSString * stringForModifiers( unsigned int aModifierFlags ) +{ + NSMutableString * theString; + unichar theCharacter; + + theString = [NSMutableString string]; + if( aModifierFlags & NSControlKeyMask) + { + theCharacter = kControlUnicode; + [theString appendString:[NSString stringWithCharacters:&theCharacter length:1]]; + } + + if( aModifierFlags & NSAlternateKeyMask) + { + theCharacter = kOptionUnicode; + [theString appendString:[NSString stringWithCharacters:&theCharacter length:1]]; + } + + if( aModifierFlags & NSShiftKeyMask) + { + theCharacter = kShiftUnicode; + [theString appendString:[NSString stringWithCharacters:&theCharacter length:1]]; + } + + if( aModifierFlags & NSCommandKeyMask) + { + theCharacter = kCommandUnicode; + [theString appendString:[NSString stringWithCharacters:&theCharacter length:1]]; + } + + return theString; +} diff --git a/Preferences/General/HotKeyControl.h b/Preferences/General/HotKeyControl.h new file mode 100644 index 000000000..2143afab0 --- /dev/null +++ b/Preferences/General/HotKeyControl.h @@ -0,0 +1,28 @@ +// +// HotKeyControl.h +// General +// +// Created by Zaphod Beeblebrox on 9/4/06. +// Copyright 2006 __MyCompanyName__. All rights reserved. +// + +#import+
++ Value Description + NDHotKeyNoEvent The hot key has not been pressed yet. + NDHotKeyPressedEvent The hot key was pressed last. + NDHotKeyReleasedEvent The hot key was released last.
NDHotKeyEvent provides a thread safe Objective-C interface to HotKey events as well as some additional feature to key track of all the hot keys in your application.
+ +Even with the flag NDHotKeyEventThreadSafe defined instances of NDHotKeyEvent will still not be thread safe, that is, it is safe to invoke methods of different instance with different threads as well as class methods, but it is not safe to invoke methods of the same instance with different threads.
+The functions stringForKeyCodeAndModifierFlags and unicharForKeyCode are never thread safe.
+ +Created by Nathan Day on Wed Feb 26 2003.
+ Copyright © 2002 Nathan Day. All rights reserved.
This class is a wrapper for Carbon Event HotKeys and provides some feature to key track of all the hot keys in your application. It can be used to be notified of key down as well as key up evernts and when a hot key is being taken by another object (see the protocol NDHotKeyEventTragetWillChange)
+ + */ +@interface NDHotKeyEvent : NSObject[decoder allowsKeyedCoding] == YES
.
+ @param decoder A subclass of NSCoder
+ @result A initialized NDHotKeyEvent
+ */
+- (id)initWithCoder:(NSCoder *)decoder;
+
+/*!
+ @method encodeWithCoder:
+ @abstract Encodes the receiver using encoder
+ @discussion Encodes the following properties of a NDHotKeyEvent;
+ [encoder allowsKeyedCoding] == YES
.
+ @param encoder A subclass of NSCoder.
+ */
+- (void)encodeWithCoder:(NSCoder *)encoder;
+
+/*!
+ @method setEnabled:
+ @abstract Set the hot key enabled or disable.
+ @discussion setEnabled: registers or unregisters the recievers hot key combination.
+ @param flag YES to enable, NO to disable.
+ @result Returns YES if successful
+ */
+- (BOOL)setEnabled:(BOOL)flag;
+
+/*!
+ @method isEnabled
+ @abstract Find out if a hot key is enabled.
+ @discussion Returns YES if the hot key is registered.
+ @result YES if enabled.
+ */
+- (BOOL)isEnabled;
+
+/*!
+ @method target
+ @abstract Get the hot key event target.
+ @discussion Returns the object that is sent the key pressed and key released hot key events, see the methods -selector, -selectorReleased and selectorPressed.
+ @result The target object.
+ */
+- (id)target;
+
+/*!
+ @method selector
+ @abstract The selector for a key released event.
+ @discussion This is the selector sent when the hot key combination for the reciever is released. This is the same selector has returned from the method [NDHotKeyEvent selectorReleased]
+ @result The method selector.
+ */
+- (SEL)selector;
+
+/*!
+ @method selectorReleased
+ @abstract The selector for a key released event.
+ @discussion This is the selector sent when the hot key combination for the reciever is released. This is the same selector has returned from the method [NDHotKeyEvent selector]
+ @result The method selector.
+ */
+- (SEL)selectorReleased;
+
+/*!
+ @method selectorPressed
+ @abstract The selector for a key pressed event.
+ @discussion This is the selector sent when the hot key combination for the reciever is pressed.
+ @result The method selector.
+ */
+- (SEL)selectorPressed;
+
+/*!
+ @method currentEventType
+ @abstract Get the current hot key event type.
+ @discussion This value returns what event last occured. Can be used in your target when it is sent a event message to find out what event occured, possible values are
+ ++ @result The last event type. + */ +- (int)currentEventType; + +/*! + @method setTarget:selector: + @abstract Set the hot key target. + @discussion Set the target object and selector to be sent when the hot key is released. + @param target The traget object. + @param selector The selector. + @result returns YES if successful. + */ +- (BOOL)setTarget:(id)target selector:(SEL)selector; + +/*! + @method setTarget:selectorReleased:selectorPressed: + @abstract Set the hot key target. + @discussion Set the target object and selector to be sent when the hot key is pressed and wehn it is released. + @param target The traget object. + @param selectorReleased The key released selector. + @param selectorPressed The key pressed selector. + @result returns YES if successful. + */ +- (BOOL)setTarget:(id)target selectorReleased:(SEL)selectorReleased selectorPressed:(SEL)selectorPressed; + +/*! + @method performHotKeyReleased + @abstract Invoke the target with the release selector. + @discussion Use to send the selector for a release event, though this method can be called by you. + */ +- (void)performHotKeyReleased; + +/*! + @method performHotKeyPressed + @abstract Invoke the target with the press selector. + @discussion Use to send the selector for a presse event, though this method can be called by you. + */ +- (void)performHotKeyPressed; + +/*! + @method keyCode + @abstract Get the hot key key code. + @discussion The key code for the hot key, this is hardware specific. + @result The key code. + */ +- (unsigned short)keyCode; + +/*! + @method character + @abstract Get the hot key character. + @discussion This is the character for the key code, without modifier keys. The character is for display purposes only and dose not determine the key code. + @result A uni code character. + */ +- (unichar)character; + +/*! + @method modifierFlags + @abstract Get the hot key modifer key flags. + @discussion The modifierFlags can be a bitwise and combination of NSControlKeyMask, NSAlternateKeyMask, NSShiftKeyMask, and NSCommandKeyMask. + @result The modifer key flags. + */ +- (unsigned int)modifierFlags; + +/*! + @method stringValue + @abstract Get a string got the hot keys. + @discussion This is a string that can be used for display purposes. + @result A NSString + */ +- (NSString *)stringValue; + +@end + +/*! + @protocol NSObject(NDHotKeyEventTragetWillChange) + @abstract Informal protocol used to inform a NDHotKeyEvent target of events. + @discussion The informal protocol NDHotKeyEventTragetWillChange defines a method used to notify a NDHotKeyEvent target that the target will change. + */ +@interface NSObject (NDHotKeyEventTragetWillChange) + +/*! + @method targetWillChangeToObject:forHotKeyEvent: + @abstract Message sent to a target object to inform it that the target is going to change. + @discussion This method can be used to notify the receiver that it will no longer be the target for a NDHotKeyEvent or used to prevent the target from changing by returning NO + @param target The new target for the NDHotKeyEvent + @param hotKeyEvent The NDHotKeyEvent for which the target is changing. + @result Return NO to prevent the target from changing, otherwise return YES. + */ +- (BOOL)targetWillChangeToObject:(id)target forHotKeyEvent:(NDHotKeyEvent *)hotKeyEvent; + +@end + +/*! + @function stringForKeyCodeAndModifierFlags + @abstract Get a string for hot key parameters. + @discussion Returns a string representation of the passed in hot key values. + @param keyCode A key code. + @param aChar A character representation of the key code. + @param modifierFlags modifer flags, comman, option, shift and control. + @result A NSString representing the hot key combination. + */ +NSString * stringForKeyCodeAndModifierFlags( unsigned short keyCode, unichar aChar, unsigned int modifierFlags ); +/*! + @function unicharForKeyCode + @abstract Get a unicode charater for the key combination. + @discussion The uncode chararter for the key combination. + @param keyCode The key code used by the keyboard, can vary across hardware. + @result A unichar + */ +unichar unicharForKeyCode( unsigned short keyCode ); diff --git a/Preferences/General/NDHotKeyEvent.m b/Preferences/General/NDHotKeyEvent.m new file mode 100755 index 000000000..3c29b9ccd --- /dev/null +++ b/Preferences/General/NDHotKeyEvent.m @@ -0,0 +1,1135 @@ +/* + * NDHotKeyEvent.m + * NDHotKeyEvent + * + * Created by Nathan Day on Wed Feb 26 2003. + * Copyright (c) 2002 Nathan Day. All rights reserved. + */ + +#import "NDHotKeyEvent.h" + +@interface NDHotKeyEvent (Private) ++ (NSHashTable *)allHotKeyEvents; +- (BOOL)addHotKey; +- (void)removeHotKey; +- (BOOL)setCollectiveEnabled:(BOOL)aFlag; +- (BOOL)collectiveEnable; +@end + +static NSString * kArchivingKeyCodeKey = @"KeyCodeKey", + * kArchivingCharacterKey = @"CharacterKey", + * kArchivingModifierFlagsKey = @"ModifierFlagsKey", + * kArchivingSelectorReleasedCodeKey = @"SelectorReleasedCodeKey", + * kArchivingSelectorPressedCodeKey = @"SelectorPressedCodeKey"; +const OSType NDHotKeyDefaultSignature = 'NDHK'; + +static OSStatus switchHotKey( NDHotKeyEvent * self, BOOL aFlag ); + +unichar unicodeForFunctionKey( UInt32 aKeyCode ); + +/* + * class implementation NDHotKeyEvent + */ +@implementation NDHotKeyEvent + +#ifdef NDHotKeyEventThreadSafe + static NSLock * hotKeysLock = NULL; + #warning Thread saftey has been enabled for NDHotKeyEvent class methods + #define NDHotKeyEventLock [hotKeysLock lock] + #define NDHotKeyEventUnlock [hotKeysLock unlock] +#else + #warning The NDHotKeyEvent class methods are NOT thread safe + #define NDHotKeyEventLock // lock + #define NDHotKeyEventUnlock // unlock +#endif + +static NSHashTable * allHotKeyEvents = NULL; +static BOOL isInstalled = NO; +static OSType signature = 0; + +unsigned int cocoaModifierFlagsToCarbonModifierFlags( unsigned int aModifierFlags ); + +pascal OSErr eventHandlerCallback( EventHandlerCallRef anInHandlerCallRef, EventRef anInEvent, void * self ); + +unsigned hashValueHashFunction( NSHashTable * aTable, const void * aHotKeyEvent ); +BOOL isEqualHashFunction( NSHashTable * aTable, const void * aFirstHotKeyEvent, const void * aSecondHotKeyEvent); +NSString * describeHashFunction( NSHashTable * aTable, const void * aHotKeyEvent ); + +struct HotKeyMappingEntry +{ + unsigned short keyCode; + unsigned int modifierFlags; + NDHotKeyEvent * hotKeyEvent; +}; + +/* + * +install + */ ++ (BOOL)install +{ + if( isInstalled == NO ) + { + NSHashTable * theHotKeyEvents = [self allHotKeyEvents]; + EventTypeSpec theTypeSpec[] = + { + { kEventClassKeyboard, kEventHotKeyPressed }, + { kEventClassKeyboard, kEventHotKeyReleased } + }; + + NDHotKeyEventLock; + if( theHotKeyEvents != nil && isInstalled == NO ) + { + if( InstallEventHandler( GetEventDispatcherTarget(), NewEventHandlerUPP((EventHandlerProcPtr)eventHandlerCallback), 2, theTypeSpec, theHotKeyEvents, nil ) == noErr ) + { + isInstalled = YES; + } + else + { + NSLog(@"Could not install Event handler"); + } + } + NDHotKeyEventUnlock; + } + + return isInstalled; +} + +#ifdef NDHotKeyEventThreadSafe +/* + * +initialize: + */ ++ (void)initialize +{ + while( hotKeysLock == nil ) + { + NSLock * theInstance = [[NSLock alloc] init]; + + if( !CompareAndSwap( nil, (unsigned long int)theInstance, (unsigned long int*)&hotKeysLock) ) + [theInstance release]; // did not use instance + } +} +#endif + +/* + * +setSignature: + */ ++ (void)setSignature:(OSType)aSignature +{ + NSAssert( signature == 0 || aSignature == signature, @"The signature used by NDHotKeyEvent can only be set once safely" ); + signature = aSignature; +} + +/* + * +signature + */ ++ (OSType)signature +{ + signature = signature ? signature : NDHotKeyDefaultSignature; + return signature; +} + +/* + * +setAllEnabled: + */ ++ (BOOL)setAllEnabled:(BOOL)aFlag +{ + BOOL theAllSucceeded = YES; + NSHashTable * theHashTable = [NDHotKeyEvent allHotKeyEvents]; + + /* + * need to install before to make sure the method 'setCollectiveEnabled:' + * doesn't try install since install tries to aquire the lock 'hotKeysLock' + */ + if( theHashTable && [NDHotKeyEvent install] ) + { + NSHashEnumerator theEnumerator; + struct HotKeyMappingEntry * theHotKeyMapEntry; + NDHotKeyEventLock; + theEnumerator = NSEnumerateHashTable( theHashTable ); + + while( (theHotKeyMapEntry = (struct HotKeyMappingEntry*)NSNextHashEnumeratorItem(&theEnumerator) ) ) + { + if( ![theHotKeyMapEntry->hotKeyEvent setCollectiveEnabled:aFlag] ) + theAllSucceeded = NO; + } + + NSEndHashTableEnumeration( &theEnumerator ); + NDHotKeyEventUnlock; + } + + return theAllSucceeded; +} + +/* + * +isEnabledKeyCode:modifierFlags: + */ ++ (BOOL)isEnabledKeyCode:(unsigned short)aKeyCode modifierFlags:(unsigned int)aModifierFlags +{ + return [[self findHotKeyForKeyCode:aKeyCode modifierFlags:aModifierFlags] isEnabled]; +} + +/* + * +getHotKeyForKeyCode:character:modifierFlags: + */ ++ (NDHotKeyEvent *)getHotKeyForKeyCode:(unsigned short)aKeyCode character:(unichar)aChar modifierFlags:(unsigned int)aModifierFlags +{ + NDHotKeyEvent * theHotKey = nil; + + theHotKey = [self findHotKeyForKeyCode:aKeyCode modifierFlags:aModifierFlags]; + return theHotKey ? theHotKey : [self hotKeyWithKeyCode:aKeyCode character:aChar modifierFlags:aModifierFlags]; +} + +/* + * +findHotKeyForKeyCode:modifierFlags: + */ ++ (NDHotKeyEvent *)findHotKeyForKeyCode:(unsigned short)aKeyCode modifierFlags:(unsigned int)aModifierFlags +{ + struct HotKeyMappingEntry * theFoundEntry = NULL; + NSHashTable * theHashTable = [NDHotKeyEvent allHotKeyEvents]; + + if( theHashTable ) + { + struct HotKeyMappingEntry theDummyEntry; + + theDummyEntry.keyCode = aKeyCode; + theDummyEntry.modifierFlags = aModifierFlags; + theDummyEntry.hotKeyEvent = nil; + + NDHotKeyEventLock; + theFoundEntry = NSHashGet( theHashTable, (void*)&theDummyEntry); + if( theFoundEntry != NULL ) + [[theFoundEntry->hotKeyEvent retain] autorelease]; + NDHotKeyEventUnlock; + } + + return (theFoundEntry) ? theFoundEntry->hotKeyEvent : nil; +} + +/* + * +hotKeyWithKeyCode:character:modifierFlags: + */ ++ (id)hotKeyWithKeyCode:(unsigned short)aKeyCode character:(unichar)aChar modifierFlags:(unsigned int)aModifierFlags +{ + return [self hotKeyWithKeyCode:aKeyCode character:aChar modifierFlags:aModifierFlags target:nil selector:NULL]; +} + +/* + * +hotKeyWithKeyCode:character:modifierFlags:target:selector: + */ ++ (id)hotKeyWithKeyCode:(unsigned short)aKeyCode character:(unichar)aChar modifierFlags:(unsigned int)aModifierFlags target:(id)aTarget selector:(SEL)aSelector +{ + return [[[self alloc] initWithKeyCode:aKeyCode character:aChar modifierFlags:aModifierFlags target:aTarget selector:aSelector] autorelease]; +} + ++ (id)hotKeyWithWithPropertyList:(id)aPropertyList +{ + return [[[self alloc] initWithPropertyList:aPropertyList] autorelease]; +} + ++ (NSString *)description +{ + NSHashTable * theHashTable = [NDHotKeyEvent allHotKeyEvents]; + NSString * theDescription = nil; + if( theHashTable ) + { + NDHotKeyEventLock; + theDescription = NSStringFromHashTable(theHashTable); + NDHotKeyEventUnlock; + } + return theDescription; +} + +/* + * -init + */ +- (id)init +{ + [self release]; + NSAssert( NO, @"You can not initialize a Hot Key with the init method" ); + return nil; +} + +/* + * -initWithKeyCode:character:modifierFlags: + */ +- (id)initWithKeyCode:(unsigned short)aKeyCode character:(unichar)aChar modifierFlags:(unsigned int)aModifierFlags +{ + return [self initWithKeyCode:aKeyCode character:aChar modifierFlags:aModifierFlags target:nil selector:NULL]; +} + +/* + * -initWithKeyCode:character:modifierFlags:target:selector: + */ +- (id)initWithKeyCode:(unsigned short)aKeyCode character:(unichar)aChar modifierFlags:(unsigned int)aModifierFlags target:(id)aTarget selector:(SEL)aSelector +{ + if( (self = [super init]) != nil ) + { + keyCode = aKeyCode; + character = aChar; + modifierFlags = aModifierFlags; + target = aTarget; + selectorReleased = aSelector; + currentEventType = NDHotKeyNoEvent; + isEnabled.collective = YES; + + if( ![self addHotKey] ) + { + [self release]; + self = nil; + } + } + else + { + [self release]; + self = nil; + } + + return self; +} + +- (id)initWithCoder:(NSCoder *)aDecoder +{ + if( (self = [super init]) != nil) + { + if( [aDecoder allowsKeyedCoding] ) + { + keyCode = [[aDecoder decodeObjectForKey:kArchivingKeyCodeKey] unsignedShortValue]; + character = [[aDecoder decodeObjectForKey:kArchivingCharacterKey] unsignedShortValue]; + modifierFlags = [[aDecoder decodeObjectForKey:kArchivingModifierFlagsKey] unsignedIntValue]; + + selectorReleased = NSSelectorFromString( [aDecoder decodeObjectForKey:kArchivingSelectorReleasedCodeKey] ); + selectorPressed = NSSelectorFromString( [aDecoder decodeObjectForKey:kArchivingSelectorPressedCodeKey] ); + } + else + { + [aDecoder decodeValueOfObjCType:@encode(unsigned short) at:&keyCode]; + [aDecoder decodeValueOfObjCType:@encode(unichar) at:&character]; + [aDecoder decodeValueOfObjCType:@encode(unsigned int) at:&modifierFlags]; + + selectorReleased = NSSelectorFromString( [aDecoder decodeObject] ); + selectorPressed = NSSelectorFromString( [aDecoder decodeObject] ); + } + + if( ![self addHotKey] ) + { + [self release]; + self = nil; + } + } + + return self; +} + +- (void)encodeWithCoder:(NSCoder *)anEncoder +{ + if( [anEncoder allowsKeyedCoding] ) + { + [anEncoder encodeObject:[NSNumber numberWithUnsignedShort:keyCode] forKey:kArchivingKeyCodeKey]; + [anEncoder encodeObject:[NSNumber numberWithUnsignedShort:character] forKey:kArchivingCharacterKey]; + [anEncoder encodeObject:[NSNumber numberWithUnsignedInt:modifierFlags] forKey:kArchivingModifierFlagsKey]; + + [anEncoder encodeObject:NSStringFromSelector( selectorReleased ) forKey:kArchivingSelectorReleasedCodeKey]; + [anEncoder encodeObject:NSStringFromSelector( selectorPressed ) forKey:kArchivingSelectorPressedCodeKey]; + } + else + { + [anEncoder encodeValueOfObjCType:@encode(unsigned short) at:&keyCode]; + [anEncoder encodeValueOfObjCType:@encode(unichar) at:&character]; + [anEncoder encodeValueOfObjCType:@encode(unsigned int) at:&modifierFlags]; + + [anEncoder encodeObject:NSStringFromSelector( selectorReleased )]; + [anEncoder encodeObject:NSStringFromSelector( selectorPressed )]; + } +} + +- (id)initWithPropertyList:(id)aPropertyList +{ + if( aPropertyList ) + { + NSString * theCharacter; + NSNumber * theKeyCode, + * theModiferFlag; + SEL theKeyPressedSelector, + theKeyReleasedSelector; + + theKeyCode = [aPropertyList objectForKey:kArchivingKeyCodeKey]; + theCharacter = [aPropertyList objectForKey:kArchivingCharacterKey]; + theModiferFlag = [aPropertyList objectForKey:kArchivingModifierFlagsKey]; + theKeyPressedSelector = NSSelectorFromString([aPropertyList objectForKey:kArchivingSelectorPressedCodeKey]); + theKeyReleasedSelector = NSSelectorFromString([aPropertyList objectForKey:kArchivingSelectorReleasedCodeKey]); + + self = [self initWithKeyCode:[theKeyCode unsignedShortValue] character:[theCharacter characterAtIndex:0] modifierFlags:[theModiferFlag unsignedIntValue]]; + } + else + { + [self release]; + self = nil; + } + + return self; +} + +- (id)propertyList +{ + return [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithUnsignedShort:[self keyCode]], kArchivingKeyCodeKey, + [NSString stringWithCharacters:&character length:1] , kArchivingCharacterKey, + [NSNumber numberWithUnsignedInt:[self modifierFlags]], kArchivingModifierFlagsKey, + NSStringFromSelector( selectorPressed ), kArchivingSelectorPressedCodeKey, + NSStringFromSelector( selectorReleased ), kArchivingSelectorReleasedCodeKey, + nil]; +} + +/* + * -release + */ +- (oneway void)release +{ + /* + * We need to remove the hot key from the hash table before it's retain count reaches zero + */ + if( [self retainCount] == 1 ) + { + NSHashTable * theHashTable = [NDHotKeyEvent allHotKeyEvents]; + if( theHashTable ) + { + struct HotKeyMappingEntry theDummyEntry; + + theDummyEntry.keyCode = [self keyCode]; + theDummyEntry.modifierFlags = [self modifierFlags]; + theDummyEntry.hotKeyEvent = nil; + + NDHotKeyEventLock; + switchHotKey( self, NO ); + if( [self retainCount] == 1 ) // check again because it might have changed + { + id theHotKeyEvent = NSHashGet( theHashTable, (void*)&theDummyEntry ); + if( theHotKeyEvent ) + NSHashRemove( theHashTable, theHotKeyEvent ); + } + NDHotKeyEventUnlock; + } + } +// else + [super release]; +} + +- (void)dealloc +{ + if( UnregisterEventHotKey( reference ) != noErr ) // in lock from release + NSLog( @"Failed to unregister hot key %@", self ); + [super dealloc]; +} + +/* + * -setEnabled: + */ +- (BOOL)setEnabled:(BOOL)aFlag +{ + BOOL theResult = YES; + + if( [NDHotKeyEvent install] ) + { + /* + * if individual and collective YES then currently ON, otherwise currently off + */ + NDHotKeyEventLock; + if( aFlag == YES && isEnabled.collective == YES && isEnabled.individual == NO ) + { + theResult = (switchHotKey( self, YES ) == noErr); + } + else if( aFlag == NO && isEnabled.collective == YES && isEnabled.individual == YES ) + { + theResult = (switchHotKey( self, NO ) == noErr); + } + NDHotKeyEventUnlock; + + if( theResult ) + isEnabled.individual = aFlag; + else + NSLog(@"%s failed ", aFlag ? "enable" : "disable" ); + } + else + theResult = NO; + + return theResult; +} + +/* + * -isEnabled + */ +- (BOOL)isEnabled +{ + return isEnabled.individual && isEnabled.collective; +} + +/* + * -target + */ +- (id)target +{ + return target; +} + +/* +* -selector +*/ +- (SEL)selector +{ + return selectorReleased; +} + +/* + * -selectorReleased + */ +- (SEL)selectorReleased +{ + return selectorReleased; +} + +/* +* -selectorPressed +*/ +- (SEL)selectorPressed +{ + return selectorPressed; +} + +/* + * -currentEventType + * (NDHotKeyNoEvent | NDHotKeyPressedEvent | NDHotKeyReleasedEvent) + */ +- (int)currentEventType +{ + return currentEventType; +} + +/* + * -setTarget:selector: + */ +- (BOOL)setTarget:(id)aTarget selector:(SEL)aSelector +{ + return [self setTarget:aTarget selectorReleased:aSelector selectorPressed:(SEL)0]; +} + +/* + * -setTarget:selectorReleased:selectorPressed: + */ +- (BOOL)setTarget:(id)aTarget selectorReleased:(SEL)aSelectorReleased selectorPressed:(SEL)aSelectorPressed +{ + [self setEnabled:NO]; + if( target && target != aTarget ) + { + [self setEnabled:NO]; + if( ![target respondsToSelector:@selector(targetWillChangeToObject:forHotKeyEvent:)] || [target targetWillChangeToObject:aTarget forHotKeyEvent:self] ) + { + target = aTarget; + selectorReleased = aSelectorReleased; + selectorPressed = aSelectorPressed; + } + } + else + { + target = aTarget; + selectorReleased = aSelectorReleased; + selectorPressed = aSelectorPressed; + } + + return target == aTarget; // was change succesful +} + +/* + * -performHotKeyReleased + */ +- (void)performHotKeyReleased +{ + NSAssert( target, @"NDHotKeyEvent tried to perfrom release with no target" ); + + if( selectorReleased && [target respondsToSelector:selectorReleased]) + { + currentEventType = NDHotKeyReleasedEvent; + [target performSelector:selectorReleased withObject:self]; + currentEventType = NDHotKeyNoEvent; + } +} + +/* + * -performHotKeyPressed + */ +- (void)performHotKeyPressed +{ + NSAssert( target, @"NDHotKeyEvent tried to perfrom press with no target" ); + + if( selectorPressed && [target respondsToSelector:selectorPressed]) + { + currentEventType = NDHotKeyPressedEvent; + [target performSelector:selectorPressed withObject:self]; + currentEventType = NDHotKeyNoEvent; + } +} + +/* + * -keyCode + */ +- (unsigned short)keyCode +{ + return keyCode; +} + +/* + * -character + */ +- (unichar)character +{ + return character; +} + +/* + * -modifierFlags + */ +- (unsigned int)modifierFlags +{ + return modifierFlags; +} + +/* + * -stringValue + */ +- (NSString *)stringValue +{ + NSString * theStringValue = nil; + NDHotKeyEventLock; + theStringValue = stringForKeyCodeAndModifierFlags( [self keyCode], [self character], [self modifierFlags] ); + NDHotKeyEventUnlock; + + return theStringValue; +} + + +/* + * -isEqual: + */ +- (BOOL)isEqual:(id)anObject +{ + return [super isEqual:anObject] || ([anObject isKindOfClass:[self class]] == YES && [self keyCode] == [(NDHotKeyEvent*)anObject keyCode] && [self modifierFlags] == [anObject modifierFlags]); +} + +/* + * -hash + */ +- (unsigned int)hash +{ + return ((unsigned int)keyCode & ~modifierFlags) | (modifierFlags & ~((unsigned int)keyCode)); // xor +} + +/* + * -description + */ +- (NSString *)description +{ + return [NSString stringWithFormat:@"{\n\tKey Combination: %@,\n\tEnabled: %s\n\tKey Press Selector: %@\n\tKey Release Selector: %@\n}\n", [self stringValue], + [self isEnabled] ? "yes" : "no", + NSStringFromSelector([self selectorPressed]), + NSStringFromSelector([self selectorReleased])]; +} + +/* + * cocoaModifierFlagsToCarbonModifierFlags() + */ +unsigned int cocoaModifierFlagsToCarbonModifierFlags( unsigned int aModifierFlags ) +{ + unsigned int theCarbonModifierFlags = 0; + + if(aModifierFlags & NSShiftKeyMask) + theCarbonModifierFlags |= shiftKey; + + if(aModifierFlags & NSControlKeyMask) + theCarbonModifierFlags |= controlKey; + + if(aModifierFlags & NSAlternateKeyMask) + theCarbonModifierFlags |= optionKey; + + if(aModifierFlags & NSCommandKeyMask) + theCarbonModifierFlags |= cmdKey; + + return theCarbonModifierFlags; +} + +/* + * eventHandlerCallback() + */ +pascal OSErr eventHandlerCallback( EventHandlerCallRef anInHandlerCallRef, EventRef anInEvent, void * anInUserData ) +{ +// NSHashTable * allHotKeyEvents = (NSHashTable *)anInUserData; + EventHotKeyID theHotKeyID; + OSStatus theError; + + NSCAssert( GetEventClass( anInEvent ) == kEventClassKeyboard, @"Got event that is not a hot key event" ); + + theError = GetEventParameter( anInEvent, kEventParamDirectObject, typeEventHotKeyID, NULL, sizeof(EventHotKeyID), NULL, &theHotKeyID ); + + if( theError == noErr ) + { + NDHotKeyEvent * theHotKeyEvent; + UInt32 theEventKind; + + NSCAssert( [NDHotKeyEvent signature] == theHotKeyID.signature, @"Got hot key event with wrong signature" ); + + theHotKeyEvent = (NDHotKeyEvent*)theHotKeyID.id; + + theEventKind = GetEventKind( anInEvent ); + if( kEventHotKeyPressed == theEventKind ) + { + [theHotKeyEvent performHotKeyPressed]; + } + else if( kEventHotKeyReleased == theEventKind ) + { + [theHotKeyEvent performHotKeyReleased]; + } + } + + return theError; +} + +/* + * hashValueHashFunction() + */ +unsigned hashValueHashFunction( NSHashTable * aTable, const void * aHotKeyEntry ) +{ + struct HotKeyMappingEntry * theHotKeyEntry; + unsigned int theKeyCode, + theModifiers; + + theHotKeyEntry = (struct HotKeyMappingEntry*)aHotKeyEntry; + theKeyCode = (unsigned int)theHotKeyEntry->keyCode; + theModifiers = (unsigned int)theHotKeyEntry->modifierFlags; + return theKeyCode ^ theModifiers; // xor +} + +/* + * isEqualHashFunction() + */ +BOOL isEqualHashFunction( NSHashTable * aTable, const void * aFirstHotKeyEntry, const void * aSecondHotKeyEntry) +{ + struct HotKeyMappingEntry * theFirst, + * theSecond; + + theFirst = (struct HotKeyMappingEntry*)aFirstHotKeyEntry; + theSecond = (struct HotKeyMappingEntry*)aSecondHotKeyEntry; + return theFirst->keyCode == theSecond->keyCode && theFirst->modifierFlags == theSecond->modifierFlags; +} + +/* + * describeHashFunction() + */ +NSString * describeHashFunction( NSHashTable * aTable, const void * aHotKeyEntry ) +{ + NDHotKeyEvent * theHotKey; + + theHotKey = ((struct HotKeyMappingEntry*)aHotKeyEntry)->hotKeyEvent; + return [theHotKey description]; +} + +@end + +@implementation NDHotKeyEvent (Private) + +/* + * +allHotKeyEvents + */ ++ (NSHashTable *)allHotKeyEvents +{ + if( allHotKeyEvents == NULL ) + { + NSHashTableCallBacks theHashCallBacks; + + theHashCallBacks.hash = hashValueHashFunction; + theHashCallBacks.isEqual = isEqualHashFunction; + theHashCallBacks.retain = NULL; + theHashCallBacks.release = NULL; + theHashCallBacks.describe = describeHashFunction; + + NDHotKeyEventLock; + if( allHotKeyEvents == NULL ) + allHotKeyEvents = NSCreateHashTableWithZone( theHashCallBacks, 0, NULL); + NDHotKeyEventUnlock; + } + + return allHotKeyEvents; +} + +/* + * -addHotKey + */ +- (BOOL)addHotKey +{ + BOOL theSuccess = NO; + NSHashTable * theHashTable = [NDHotKeyEvent allHotKeyEvents]; + if( theHashTable ) + { + struct HotKeyMappingEntry * theEntry; + + theEntry = (struct HotKeyMappingEntry *)malloc(sizeof(struct HotKeyMappingEntry)); + + theEntry->keyCode = [self keyCode]; + theEntry->modifierFlags = [self modifierFlags]; + theEntry->hotKeyEvent = self; + + NDHotKeyEventLock; + theSuccess = NSHashInsertIfAbsent( theHashTable, (void*)theEntry ) == NULL; + NDHotKeyEventUnlock; + } + + return theSuccess; +} + +/* + * -removeHotKey + */ +- (void)removeHotKey +{ + [self setEnabled:NO]; + + NSHashTable * theHashTable = [NDHotKeyEvent allHotKeyEvents]; + if( theHashTable ) + { + struct HotKeyMappingEntry theDummyEntry; + id theHotKeyEvent; + + theDummyEntry.keyCode = [self keyCode]; + theDummyEntry.modifierFlags = [self modifierFlags]; + theDummyEntry.hotKeyEvent = nil; + + NDHotKeyEventLock; + theHotKeyEvent = NSHashGet( theHashTable, (void*)&theDummyEntry); + if( theHotKeyEvent ) + NSHashRemove( theHashTable, theHotKeyEvent ); + NDHotKeyEventUnlock; + } +} + +/* + * setCollectiveEnabled: + */ +- (BOOL)setCollectiveEnabled:(BOOL)aFlag +{ + BOOL theResult = YES; + + if( [NDHotKeyEvent install] ) + { + /* + * if individual and collective YES then currently ON, otherwise currently off + */ + NDHotKeyEventLock; + if( aFlag == YES && isEnabled.collective == NO && isEnabled.individual == YES ) + { + theResult = (switchHotKey( self, YES ) == noErr); + } + else if( aFlag == NO && isEnabled.collective == YES && isEnabled.individual == YES ) + { + theResult = (switchHotKey( self, NO ) == noErr); + } + NDHotKeyEventUnlock; + + if( theResult ) + isEnabled.collective = aFlag; + else + NSLog(@"%s failed", aFlag ? "enable" : "disable" ); + } + else + theResult = NO; + + return theResult; +} + +/* + * collectiveEnable() + */ +- (BOOL)collectiveEnable +{ + return isEnabled.collective; +} + +/* + * switchHotKey() + */ +static OSStatus switchHotKey( NDHotKeyEvent * self, BOOL aFlag ) +{ + OSStatus theError; + if( aFlag ) + { + EventHotKeyID theHotKeyID; + + theHotKeyID.signature = [NDHotKeyEvent signature]; + theHotKeyID.id = (unsigned int)self; + + NSCAssert( theHotKeyID.signature, @"HotKeyEvent signature has not been set yet" ); + NSCParameterAssert(sizeof(unsigned long) >= sizeof(id) ); + + theError = RegisterEventHotKey( self->keyCode, cocoaModifierFlagsToCarbonModifierFlags(self->modifierFlags), theHotKeyID, GetEventDispatcherTarget(), 0, &self->reference ); + } + else + { + theError = UnregisterEventHotKey( self->reference ); + } + + return theError; +} + +@end + + +/* + * stringForKeyCodeAndModifierFlags() + */ +NSString * stringForKeyCodeAndModifierFlags( unsigned short aKeyCode, unichar aChar, unsigned int aModifierFlags ) +{ + NSString * stringForCharacter( const unsigned short aKeyCode, unichar aCharacter ); + NSString * stringForModifiers( unsigned int aModifierFlags ); + + return [stringForModifiers(aModifierFlags) stringByAppendingString:stringForCharacter( aKeyCode, aChar )]; +} + +/* + * unicharForKeyCode() + */ +unichar unicharForKeyCode( unsigned short aKeyCode ) +{ + static UInt32 theState = 0; + const void * theKeyboardLayoutData; + KeyboardLayoutRef theCurrentKeyBoardLayout; + UInt32 theChar = kNullCharCode; + + if( KLGetCurrentKeyboardLayout( &theCurrentKeyBoardLayout ) == noErr && KLGetKeyboardLayoutProperty( theCurrentKeyBoardLayout, kKLKCHRData, &theKeyboardLayoutData) == noErr ) + { + theChar = KeyTranslate ( theKeyboardLayoutData, aKeyCode, &theState ); + + switch( theChar ) + { + case kHomeCharCode: theChar = NSHomeFunctionKey; break; +// case kEnterCharCode: theChar = ; break; + case kEndCharCode: theChar = NSEndFunctionKey; break; + case kHelpCharCode: theChar = NSHelpFunctionKey; break; +// case kBellCharCode: theChar = ; break; +// case kBackspaceCharCode: theChar = ; break; +// case kTabCharCode: theChar = ; break; +// case kLineFeedCharCode: theChar = ; break; + case kPageUpCharCode: theChar = NSPageUpFunctionKey; break; + case kPageDownCharCode: theChar = NSPageDownFunctionKey; break; +// case kReturnCharCode: theChar = ; break; + case kFunctionKeyCharCode: theChar = unicodeForFunctionKey( aKeyCode ); break; +// case kCommandCharCode: theChar = ; break; +// case kCheckCharCode: theChar = ; break; +// case kDiamondCharCode : theChar = ; break; +// case kAppleLogoCharCode: theChar = ; break; +// case kEscapeCharCode: theChar = ; break; + case kClearCharCode: + theChar = (aKeyCode==0x47) ? NSInsertFunctionKey : theChar; + break; + case kLeftArrowCharCode: theChar = NSLeftArrowFunctionKey; break; + case kRightArrowCharCode: theChar = NSRightArrowFunctionKey; break; + case kUpArrowCharCode: theChar = NSUpArrowFunctionKey; break; + case kDownArrowCharCode: theChar = NSDownArrowFunctionKey; break; +// case kSpaceCharCode: theChar = ; break; + case kDeleteCharCode: theChar = NSDeleteCharFunctionKey; break; +// case kBulletCharCode: theChar = ; break; +// case kNonBreakingSpaceCharCode: theChar = ; break; + } + } + + return theChar; +} + +unichar unicodeForFunctionKey( UInt32 aKeyCode ) +{ + switch( aKeyCode ) + { + case 0x7A: return NSF1FunctionKey; + case 0x78: return NSF2FunctionKey; + case 0x63: return NSF3FunctionKey; + case 0x76: return NSF4FunctionKey; + case 0x60: return NSF5FunctionKey; + case 0x61: return NSF6FunctionKey; + case 0x62: return NSF7FunctionKey; + case 0x64: return NSF8FunctionKey; + case 0x65: return NSF9FunctionKey; + case 0x6D: return NSF10FunctionKey; + case 0x67: return NSF11FunctionKey; + case 0x6F: return NSF12FunctionKey; + default: return 0x00; + } +} + +NSString * stringForCharacter( const unsigned short aKeyCode, unichar aCharacter ) +{ + NSString * theString = nil; + switch( aCharacter ) + { + case NSUpArrowFunctionKey: + aCharacter = 0x2191; + theString = [NSString stringWithCharacters:&aCharacter length:1]; + break; + case NSDownArrowFunctionKey: + aCharacter = 0x2193; + theString = [NSString stringWithCharacters:&aCharacter length:1]; + break; + case NSLeftArrowFunctionKey: + aCharacter = 0x2190; + theString = [NSString stringWithCharacters:&aCharacter length:1]; + break; + case NSRightArrowFunctionKey: + aCharacter = 0x2192; + theString = [NSString stringWithCharacters:&aCharacter length:1]; + break; + case NSF1FunctionKey: theString = @"F1"; break; + case NSF2FunctionKey: theString = @"F2"; break; + case NSF3FunctionKey: theString = @"F3"; break; + case NSF4FunctionKey: theString = @"F4"; break; + case NSF5FunctionKey: theString = @"F5"; break; + case NSF6FunctionKey: theString = @"F6"; break; + case NSF7FunctionKey: theString = @"F7"; break; + case NSF8FunctionKey: theString = @"F8"; break; + case NSF9FunctionKey: theString = @"F9"; break; + case NSF10FunctionKey: theString = @"F10"; break; + case NSF11FunctionKey: theString = @"F11"; break; + case NSF12FunctionKey: theString = @"F12"; break; + case NSF13FunctionKey: theString = @"F13"; break; + case NSF14FunctionKey: theString = @"F14"; break; + case NSF15FunctionKey: theString = @"F15"; break; + case NSF16FunctionKey: theString = @"F16"; break; + case NSF17FunctionKey: theString = @"F17"; break; + case NSF18FunctionKey: theString = @"F18"; break; + case NSF19FunctionKey: theString = @"F19"; break; + case NSF20FunctionKey: theString = @"F20"; break; + case NSF21FunctionKey: theString = @"F21"; break; + case NSF22FunctionKey: theString = @"F22"; break; + case NSF23FunctionKey: theString = @"F23"; break; + case NSF24FunctionKey: theString = @"F24"; break; + case NSF25FunctionKey: theString = @"F25"; break; + case NSF26FunctionKey: theString = @"F26"; break; + case NSF27FunctionKey: theString = @"F27"; break; + case NSF28FunctionKey: theString = @"F28"; break; + case NSF29FunctionKey: theString = @"F29"; break; + case NSF30FunctionKey: theString = @"F30"; break; + case NSF31FunctionKey: theString = @"F31"; break; + case NSF32FunctionKey: theString = @"F32"; break; + case NSF33FunctionKey: theString = @"F33"; break; + case NSF34FunctionKey: theString = @"F34"; break; + case NSF35FunctionKey: theString = @"F35"; break; + case NSInsertFunctionKey: theString = @"Ins"; break; + case NSDeleteFunctionKey: theString = @"Delete"; break; + case NSHomeFunctionKey: + aCharacter = 0x2196; + theString = [NSString stringWithCharacters:&aCharacter length:1]; + break; + case NSBeginFunctionKey: theString = @"Begin"; break; + case NSEndFunctionKey: theString = @"End"; break; + case NSPageUpFunctionKey: + // aCharacter = 0x21DE; + // theString = [NSString stringWithCharacters:&aCharacter length:1]; + theString = @"PgUp"; + break; + case NSPageDownFunctionKey: +// aCharacter = 0x21DF; +// theString = [NSString stringWithCharacters:&aCharacter length:1]; + theString = @"PgDn"; + break; + case NSPrintScreenFunctionKey: theString = @"Print"; break; + case NSScrollLockFunctionKey: theString = @"ScrollLock"; break; + case NSPauseFunctionKey: theString = @"Pause"; break; + case NSSysReqFunctionKey: theString = @"SysReq"; break; + case NSBreakFunctionKey: theString = @"Break"; break; + case NSResetFunctionKey: theString = @"Reset"; break; + case NSStopFunctionKey: theString = @"Stop"; break; + case NSMenuFunctionKey: theString = @"Menu"; break; + case NSUserFunctionKey: theString = @"User"; break; + case NSSystemFunctionKey: theString = @"System"; break; + case NSPrintFunctionKey: theString = @"Print"; break; + case NSClearLineFunctionKey: theString = @"ClearLine"; break; + case NSClearDisplayFunctionKey: theString = @"ClearDisplay"; break; + case NSInsertLineFunctionKey: theString = @"InsertLine"; break; + case NSDeleteLineFunctionKey: theString = @"DeleteLine"; break; + case NSInsertCharFunctionKey: theString = @"InsertChar"; break; + case NSDeleteCharFunctionKey: + aCharacter = 0x2326; + theString = [NSString stringWithCharacters:&aCharacter length:1]; + break; + case NSPrevFunctionKey: theString = @"Prev"; break; + case NSNextFunctionKey: theString = @"Next"; break; + case NSSelectFunctionKey: theString = @"Select"; break; + case NSExecuteFunctionKey: theString = @"Exec"; break; + case NSUndoFunctionKey: theString = @"Undo"; break; + case NSRedoFunctionKey: theString = @"Redo"; break; + case NSFindFunctionKey: theString = @"Find"; break; + case NSHelpFunctionKey: theString = @"Help"; break; + case NSModeSwitchFunctionKey: theString = @"ModeSwitch"; break; + case kEscapeCharCode: theString = @"Esc"; break; + case kTabCharCode: + aCharacter = 0x21E5; + theString = [NSString stringWithCharacters:&aCharacter length:1]; + break; + case kSpaceCharCode: theString = @"Space"; break; + case kEnterCharCode: + aCharacter = 0x21B5; + theString = [NSString stringWithCharacters:&aCharacter length:1]; + break; + case kReturnCharCode: + aCharacter = 0x21A9; + theString = [NSString stringWithCharacters:&aCharacter length:1]; + break; + case kDeleteCharCode: theString = @"Del"; break; + case '0'...'9': + case '=': + case '/': + case '*': + case '-': + case '+': + case '.': + theString = [NSString stringWithCharacters:&aCharacter length:1]; + if( aKeyCode > 60 ) + theString = [NSString stringWithFormat:@"[%@]", theString]; + break; + default: + aCharacter = unicharForKeyCode(aKeyCode); + + if( aCharacter >= 'a' && aCharacter <= 'z' ) // convert to uppercase + aCharacter = aCharacter + 'A' - 'a'; + + theString = [NSString stringWithCharacters:&aCharacter length:1]; + break; + } + return theString; +} + +NSString * stringForModifiers( unsigned int aModifierFlags ) +{ + NSMutableString * theString; + unichar theCharacter; + + theString = [NSMutableString string]; + if( aModifierFlags & NSControlKeyMask) + { + theCharacter = kControlUnicode; + [theString appendString:[NSString stringWithCharacters:&theCharacter length:1]]; + } + + if( aModifierFlags & NSAlternateKeyMask) + { + theCharacter = kOptionUnicode; + [theString appendString:[NSString stringWithCharacters:&theCharacter length:1]]; + } + + if( aModifierFlags & NSShiftKeyMask) + { + theCharacter = kShiftUnicode; + [theString appendString:[NSString stringWithCharacters:&theCharacter length:1]]; + } + + if( aModifierFlags & NSCommandKeyMask) + { + theCharacter = kCommandUnicode; + [theString appendString:[NSString stringWithCharacters:&theCharacter length:1]]; + } + + return theString; +}+
++ Value Description + NDHotKeyNoEvent The hot key has not been pressed yet. + NDHotKeyPressedEvent The hot key was pressed last. + NDHotKeyReleasedEvent The hot key was released last.