aboutsummaryrefslogtreecommitdiffstats
path: root/Framework
diff options
context:
space:
mode:
Diffstat (limited to 'Framework')
-rw-r--r--Framework/MASHotKey.h12
-rw-r--r--Framework/MASHotKey.m44
-rw-r--r--Framework/MASShortcut+Monitoring.h8
-rw-r--r--Framework/MASShortcut+Monitoring.m165
-rw-r--r--Framework/MASShortcut+UserDefaults.h9
-rw-r--r--Framework/MASShortcut+UserDefaults.m98
-rw-r--r--Framework/MASShortcutBinder.h10
-rw-r--r--Framework/MASShortcutBinder.m83
-rw-r--r--Framework/MASShortcutBinderTests.m82
-rw-r--r--Framework/MASShortcutMonitor.h9
-rw-r--r--Framework/MASShortcutMonitor.m84
-rw-r--r--Framework/MASShortcutView+UserDefaults.h7
-rw-r--r--Framework/MASShortcutView+UserDefaults.m125
-rw-r--r--Framework/MASShortcutView.h2
-rw-r--r--Framework/MASShortcutView.m50
-rw-r--r--Framework/Prefix.pch1
-rw-r--r--Framework/Shortcut.h7
17 files changed, 381 insertions, 415 deletions
diff --git a/Framework/MASHotKey.h b/Framework/MASHotKey.h
new file mode 100644
index 0000000..1d267e4
--- /dev/null
+++ b/Framework/MASHotKey.h
@@ -0,0 +1,12 @@
+#import "MASShortcut.h"
+
+extern FourCharCode const MASHotKeySignature;
+
+@interface MASHotKey : NSObject
+
+@property(readonly) UInt32 carbonID;
+@property(copy) dispatch_block_t action;
+
++ (instancetype) registeredHotKeyWithShortcut: (MASShortcut*) shortcut;
+
+@end
diff --git a/Framework/MASHotKey.m b/Framework/MASHotKey.m
new file mode 100644
index 0000000..7886440
--- /dev/null
+++ b/Framework/MASHotKey.m
@@ -0,0 +1,44 @@
+#import "MASHotKey.h"
+
+FourCharCode const MASHotKeySignature = 'MASS';
+
+@interface MASHotKey ()
+@property(assign) EventHotKeyRef hotKeyRef;
+@property(assign) UInt32 carbonID;
+@end
+
+@implementation MASHotKey
+
+- (instancetype) initWithShortcut: (MASShortcut*) shortcut
+{
+ self = [super init];
+
+ static UInt32 CarbonHotKeyID = 0;
+
+ _carbonID = ++CarbonHotKeyID;
+ EventHotKeyID hotKeyID = { .signature = MASHotKeySignature, .id = _carbonID };
+
+ OSStatus status = RegisterEventHotKey([shortcut carbonKeyCode], [shortcut carbonFlags],
+ hotKeyID, GetEventDispatcherTarget(), kEventHotKeyExclusive, &_hotKeyRef);
+
+ if (status != noErr) {
+ return nil;
+ }
+
+ return self;
+}
+
++ (instancetype) registeredHotKeyWithShortcut: (MASShortcut*) shortcut
+{
+ return [[self alloc] initWithShortcut:shortcut];
+}
+
+- (void) dealloc
+{
+ if (_hotKeyRef) {
+ UnregisterEventHotKey(_hotKeyRef);
+ _hotKeyRef = NULL;
+ }
+}
+
+@end
diff --git a/Framework/MASShortcut+Monitoring.h b/Framework/MASShortcut+Monitoring.h
deleted file mode 100644
index aa8f224..0000000
--- a/Framework/MASShortcut+Monitoring.h
+++ /dev/null
@@ -1,8 +0,0 @@
-#import "MASShortcut.h"
-
-@interface MASShortcut (Monitoring)
-
-+ (id)addGlobalHotkeyMonitorWithShortcut:(MASShortcut *)shortcut handler:(void (^)())handler;
-+ (void)removeGlobalHotkeyMonitor:(id)monitor;
-
-@end
diff --git a/Framework/MASShortcut+Monitoring.m b/Framework/MASShortcut+Monitoring.m
deleted file mode 100644
index b6750b4..0000000
--- a/Framework/MASShortcut+Monitoring.m
+++ /dev/null
@@ -1,165 +0,0 @@
-#import "MASShortcut+Monitoring.h"
-
-NSMutableDictionary *MASRegisteredHotKeys(void);
-BOOL InstallCommonEventHandler(void);
-BOOL InstallHotkeyWithShortcut(MASShortcut *shortcut, UInt32 *outCarbonHotKeyID, EventHotKeyRef *outCarbonHotKey);
-void UninstallEventHandler(void);
-
-#pragma mark -
-
-@interface MASShortcutHotKey : NSObject
-
-@property (nonatomic, readonly) MASShortcut *shortcut;
-@property (nonatomic, readonly, copy) void (^handler)();
-@property (nonatomic, readonly) EventHotKeyRef carbonHotKey;
-@property (nonatomic, readonly) UInt32 carbonHotKeyID;
-
-- (id)initWithShortcut:(MASShortcut *)shortcut handler:(void (^)())handler;
-
-@end
-
-#pragma mark -
-
-@implementation MASShortcut (Monitoring)
-
-+ (id)addGlobalHotkeyMonitorWithShortcut:(MASShortcut *)shortcut handler:(void (^)())handler
-{
- NSString *monitor = [NSString stringWithFormat:@"%@", shortcut.description];
- if ([MASRegisteredHotKeys() objectForKey:monitor]) return nil;
-
- MASShortcutHotKey *hotKey = [[MASShortcutHotKey alloc] initWithShortcut:shortcut handler:handler];
- if (hotKey == nil) return nil;
-
- [MASRegisteredHotKeys() setObject:hotKey forKey:monitor];
- return monitor;
-}
-
-+ (void)removeGlobalHotkeyMonitor:(id)monitor
-{
- if (monitor == nil) return;
- NSMutableDictionary *registeredHotKeys = MASRegisteredHotKeys();
- [registeredHotKeys removeObjectForKey:monitor];
- if (registeredHotKeys.count == 0) {
- UninstallEventHandler();
- }
-}
-
-@end
-
-#pragma mark -
-
-@implementation MASShortcutHotKey
-
-@synthesize carbonHotKeyID = _carbonHotKeyID;
-@synthesize handler = _handler;
-@synthesize shortcut = _shortcut;
-@synthesize carbonHotKey = _carbonHotKey;
-
-#pragma mark -
-
-- (id)initWithShortcut:(MASShortcut *)shortcut handler:(void (^)())handler
-{
- self = [super init];
- if (self) {
- _shortcut = shortcut;
- _handler = [handler copy];
-
- if (!InstallHotkeyWithShortcut(shortcut, &_carbonHotKeyID, &_carbonHotKey))
- self = nil;
- }
- return self;
-}
-
-- (void)dealloc
-{
- [self uninstallExisitingHotKey];
-}
-
-- (void)uninstallExisitingHotKey
-{
- if (_carbonHotKey) {
- UnregisterEventHotKey(_carbonHotKey);
- _carbonHotKey = NULL;
- }
-}
-
-@end
-
-#pragma mark - Carbon magic
-
-NSMutableDictionary *MASRegisteredHotKeys()
-{
- static NSMutableDictionary *shared = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- shared = [NSMutableDictionary dictionary];
- });
- return shared;
-}
-
-#pragma mark -
-
-FourCharCode const kMASShortcutSignature = 'MASS';
-
-BOOL InstallHotkeyWithShortcut(MASShortcut *shortcut, UInt32 *outCarbonHotKeyID, EventHotKeyRef *outCarbonHotKey)
-{
- if ((shortcut == nil) || !InstallCommonEventHandler()) return NO;
-
- static UInt32 sCarbonHotKeyID = 0;
- EventHotKeyID hotKeyID = { .signature = kMASShortcutSignature, .id = ++ sCarbonHotKeyID };
- EventHotKeyRef carbonHotKey = NULL;
- if (RegisterEventHotKey(shortcut.carbonKeyCode, shortcut.carbonFlags, hotKeyID, GetEventDispatcherTarget(), kEventHotKeyExclusive, &carbonHotKey) != noErr) {
- return NO;
- }
-
- if (outCarbonHotKeyID) *outCarbonHotKeyID = hotKeyID.id;
- if (outCarbonHotKey) *outCarbonHotKey = carbonHotKey;
- return YES;
-}
-
-static OSStatus CarbonCallback(EventHandlerCallRef inHandlerCallRef, EventRef inEvent, void *inUserData)
-{
- if (GetEventClass(inEvent) != kEventClassKeyboard) return noErr;
-
- EventHotKeyID hotKeyID;
- OSStatus status = GetEventParameter(inEvent, kEventParamDirectObject, typeEventHotKeyID, NULL, sizeof(hotKeyID), NULL, &hotKeyID);
- if (status != noErr) return status;
-
- if (hotKeyID.signature != kMASShortcutSignature) return noErr;
-
- [MASRegisteredHotKeys() enumerateKeysAndObjectsUsingBlock:^(id key, MASShortcutHotKey *hotKey, BOOL *stop) {
- if (hotKeyID.id == hotKey.carbonHotKeyID) {
- if (hotKey.handler) {
- hotKey.handler();
- }
- *stop = YES;
- }
- }];
-
- return noErr;
-}
-
-#pragma mark -
-
-static EventHandlerRef sEventHandler = NULL;
-
-BOOL InstallCommonEventHandler()
-{
- if (sEventHandler == NULL) {
- EventTypeSpec hotKeyPressedSpec = { .eventClass = kEventClassKeyboard, .eventKind = kEventHotKeyPressed };
- OSStatus status = InstallEventHandler(GetEventDispatcherTarget(), CarbonCallback, 1, &hotKeyPressedSpec, NULL, &sEventHandler);
- if (status != noErr) {
- sEventHandler = NULL;
- return NO;
- }
- }
- return YES;
-}
-
-void UninstallEventHandler()
-{
- if (sEventHandler) {
- RemoveEventHandler(sEventHandler);
- sEventHandler = NULL;
- }
-}
diff --git a/Framework/MASShortcut+UserDefaults.h b/Framework/MASShortcut+UserDefaults.h
deleted file mode 100644
index 9f2ecb9..0000000
--- a/Framework/MASShortcut+UserDefaults.h
+++ /dev/null
@@ -1,9 +0,0 @@
-#import "MASShortcut.h"
-
-@interface MASShortcut (UserDefaults)
-
-+ (void)registerGlobalShortcutWithUserDefaultsKey:(NSString *)userDefaultsKey handler:(void (^)())handler;
-+ (void)unregisterGlobalShortcutWithUserDefaultsKey:(NSString *)userDefaultsKey;
-+ (void)setGlobalShortcut:(MASShortcut *)shortcut forUserDefaultsKey:(NSString *)userDefaultsKey;
-
-@end
diff --git a/Framework/MASShortcut+UserDefaults.m b/Framework/MASShortcut+UserDefaults.m
deleted file mode 100644
index 006c579..0000000
--- a/Framework/MASShortcut+UserDefaults.m
+++ /dev/null
@@ -1,98 +0,0 @@
-#import "MASShortcut+UserDefaults.h"
-#import "MASShortcut+Monitoring.h"
-
-@interface MASShortcutUserDefaultsHotKey : NSObject
-
-@property (nonatomic, readonly) NSString *userDefaultsKey;
-@property (nonatomic, copy) void (^handler)();
-@property (nonatomic, weak) id monitor;
-
-- (id)initWithUserDefaultsKey:(NSString *)userDefaultsKey handler:(void (^)())handler;
-
-@end
-
-#pragma mark -
-
-@implementation MASShortcut (UserDefaults)
-
-+ (NSMutableDictionary *)registeredUserDefaultsHotKeys
-{
- static NSMutableDictionary *shared = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- shared = [NSMutableDictionary dictionary];
- });
- return shared;
-}
-
-+ (void)registerGlobalShortcutWithUserDefaultsKey:(NSString *)userDefaultsKey handler:(void (^)())handler
-{
- MASShortcutUserDefaultsHotKey *hotKey = [[MASShortcutUserDefaultsHotKey alloc] initWithUserDefaultsKey:userDefaultsKey handler:handler];
- [[self registeredUserDefaultsHotKeys] setObject:hotKey forKey:userDefaultsKey];
-}
-
-+ (void)unregisterGlobalShortcutWithUserDefaultsKey:(NSString *)userDefaultsKey
-{
- NSMutableDictionary *registeredHotKeys = [self registeredUserDefaultsHotKeys];
- [registeredHotKeys removeObjectForKey:userDefaultsKey];
-}
-
-+ (void)setGlobalShortcut:(MASShortcut *)shortcut forUserDefaultsKey:(NSString *)userDefaultsKey
-{
- NSData *shortcutData = [NSArchiver archivedDataWithRootObject:shortcut];
- if (shortcutData)
- [[NSUserDefaults standardUserDefaults] setObject:shortcutData forKey:userDefaultsKey];
- else
- [[NSUserDefaults standardUserDefaults] removeObjectForKey:userDefaultsKey];
-}
-
-@end
-
-#pragma mark -
-
-@implementation MASShortcutUserDefaultsHotKey {
- NSString *_observableKeyPath;
-}
-
-void *MASShortcutUserDefaultsContext = &MASShortcutUserDefaultsContext;
-
-- (id)initWithUserDefaultsKey:(NSString *)userDefaultsKey handler:(void (^)())handler
-{
- self = [super init];
- if (self) {
- _userDefaultsKey = userDefaultsKey.copy;
- _handler = [handler copy];
- _observableKeyPath = [@"values." stringByAppendingString:_userDefaultsKey];
- [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:_observableKeyPath options:NSKeyValueObservingOptionInitial context:MASShortcutUserDefaultsContext];
- }
- return self;
-}
-
-- (void)dealloc
-{
- [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:_observableKeyPath context:MASShortcutUserDefaultsContext];
- [MASShortcut removeGlobalHotkeyMonitor:self.monitor];
-}
-
-#pragma mark -
-
-- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
-{
- if (context == MASShortcutUserDefaultsContext) {
- [MASShortcut removeGlobalHotkeyMonitor:self.monitor];
- [self installHotKeyFromUserDefaults];
- }
- else {
- [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
- }
-}
-
-- (void)installHotKeyFromUserDefaults
-{
- NSData *data = [[NSUserDefaults standardUserDefaults] dataForKey:_userDefaultsKey];
- MASShortcut *shortcut = [NSKeyedUnarchiver unarchiveObjectWithData:data];
- if (shortcut == nil) return;
- self.monitor = [MASShortcut addGlobalHotkeyMonitorWithShortcut:shortcut handler:self.handler];
-}
-
-@end
diff --git a/Framework/MASShortcutBinder.h b/Framework/MASShortcutBinder.h
new file mode 100644
index 0000000..74842f3
--- /dev/null
+++ b/Framework/MASShortcutBinder.h
@@ -0,0 +1,10 @@
+#import "MASShortcutMonitor.h"
+
+@interface MASShortcutBinder : NSObject
+
+@property(strong) MASShortcutMonitor *shortcutMonitor;
+
+- (void) bindShortcutWithDefaultsKey: (NSString*) defaultsKeyName toAction: (dispatch_block_t) action;
+- (void) breakBindingWithDefaultsKey: (NSString*) defaultsKeyName;
+
+@end
diff --git a/Framework/MASShortcutBinder.m b/Framework/MASShortcutBinder.m
new file mode 100644
index 0000000..d6249a8
--- /dev/null
+++ b/Framework/MASShortcutBinder.m
@@ -0,0 +1,83 @@
+#import "MASShortcutBinder.h"
+#import "MASShortcut.h"
+
+@interface MASShortcutBinder ()
+@property(strong) NSMutableDictionary *actions;
+@property(strong) NSMutableDictionary *shortcuts;
+@end
+
+@implementation MASShortcutBinder
+
+#pragma mark Initialization
+
+- (id) init
+{
+ self = [super init];
+ [self setActions:[NSMutableDictionary dictionary]];
+ [self setShortcuts:[NSMutableDictionary dictionary]];
+ return self;
+}
+
+- (void) dealloc
+{
+ for (NSString *bindingName in [_actions allKeys]) {
+ [self unbind:bindingName];
+ }
+}
+
+#pragma mark Bindings
+
+- (void) bindShortcutWithDefaultsKey: (NSString*) defaultsKeyName toAction: (dispatch_block_t) action
+{
+ [_actions setObject:[action copy] forKey:defaultsKeyName];
+ NSDictionary *bindingOptions = @{NSValueTransformerNameBindingOption: NSKeyedUnarchiveFromDataTransformerName};
+ [self bind:defaultsKeyName toObject:[NSUserDefaultsController sharedUserDefaultsController]
+ withKeyPath:[@"values." stringByAppendingString:defaultsKeyName] options:bindingOptions];
+}
+
+- (void) breakBindingWithDefaultsKey: (NSString*) defaultsKeyName
+{
+ [_shortcutMonitor unregisterShortcut:[_shortcuts objectForKey:defaultsKeyName]];
+ [_shortcuts removeObjectForKey:defaultsKeyName];
+ [_actions removeObjectForKey:defaultsKeyName];
+ [self unbind:defaultsKeyName];
+}
+
+- (BOOL) isRegisteredAction: (NSString*) name
+{
+ return !![_actions objectForKey:name];
+}
+
+- (id) valueForUndefinedKey: (NSString*) key
+{
+ return [self isRegisteredAction:key] ?
+ [_shortcuts objectForKey:key] :
+ [super valueForUndefinedKey:key];
+}
+
+- (void) setValue: (id) value forUndefinedKey: (NSString*) key
+{
+ if (![self isRegisteredAction:key]) {
+ [super setValue:value forUndefinedKey:key];
+ return;
+ }
+
+ MASShortcut *newShortcut = value;
+ MASShortcut *currentShortcut = [_shortcuts objectForKey:key];
+
+ // Unbind previous shortcut if any
+ if (currentShortcut != nil) {
+ [_shortcutMonitor unregisterShortcut:currentShortcut];
+ }
+
+ // Just deleting the old shortcut
+ if (newShortcut == nil) {
+ return;
+ }
+
+ // Bind new shortcut
+ [_shortcuts setObject:newShortcut forKey:key];
+ [_shortcutMonitor registerShortcut:newShortcut withAction:[_actions objectForKey:key]];
+}
+
+@end
diff --git a/Framework/MASShortcutBinderTests.m b/Framework/MASShortcutBinderTests.m
new file mode 100644
index 0000000..f416304
--- /dev/null
+++ b/Framework/MASShortcutBinderTests.m
@@ -0,0 +1,82 @@
+#import "MASShortcutBinder.h"
+
+static NSString *const SampleDefaultsKey = @"sampleShortcut";
+
+@interface MASShortcutBinderTests : XCTestCase
+@property(strong) MASShortcutBinder *binder;
+@property(strong) MASShortcutMonitor *monitor;
+@property(strong) NSUserDefaults *defaults;
+@end
+
+@implementation MASShortcutBinderTests
+
+- (void) setUp
+{
+ [super setUp];
+
+ [self setBinder:[[MASShortcutBinder alloc] init]];
+ [self setMonitor:[[MASShortcutMonitor alloc] init]];
+ [_binder setShortcutMonitor:_monitor];
+
+ [self setDefaults:[[NSUserDefaults alloc] init]];
+ [_defaults removeObjectForKey:SampleDefaultsKey];
+}
+
+- (void) tearDown
+{
+ [self setMonitor:nil];
+ [self setDefaults:nil];
+ [self setBinder:nil];
+ [super tearDown];
+}
+
+- (void) testInitialValueReading
+{
+ MASShortcut *shortcut = [MASShortcut shortcutWithKeyCode:1 modifierFlags:1];
+ [_defaults setObject:[NSKeyedArchiver archivedDataWithRootObject:shortcut] forKey:SampleDefaultsKey];
+ [_binder bindShortcutWithDefaultsKey:SampleDefaultsKey toAction:^{}];
+ XCTAssertTrue([_monitor isShortcutRegistered:shortcut],
+ @"Pass the initial shortcut from defaults to shortcut monitor.");
+}
+
+- (void) testValueChangeReading
+{
+ MASShortcut *shortcut = [MASShortcut shortcutWithKeyCode:1 modifierFlags:1];
+ [_binder bindShortcutWithDefaultsKey:SampleDefaultsKey toAction:^{}];
+ [_defaults setObject:[NSKeyedArchiver archivedDataWithRootObject:shortcut] forKey:SampleDefaultsKey];
+ XCTAssertTrue([_monitor isShortcutRegistered:shortcut],
+ @"Pass the shortcut from defaults to shortcut monitor after defaults change.");
+}
+
+- (void) testValueClearing
+{
+ MASShortcut *shortcut = [MASShortcut shortcutWithKeyCode:1 modifierFlags:1];
+ [_binder bindShortcutWithDefaultsKey:SampleDefaultsKey toAction:^{}];
+ [_defaults setObject:[NSKeyedArchiver archivedDataWithRootObject:shortcut] forKey:SampleDefaultsKey];
+ [_defaults removeObjectForKey:SampleDefaultsKey];
+ XCTAssertFalse([_monitor isShortcutRegistered:shortcut],
+ @"Unregister shortcut from monitor after value is cleared from defaults.");
+}
+
+- (void) testBindingRemoval
+{
+ MASShortcut *shortcut = [MASShortcut shortcutWithKeyCode:1 modifierFlags:1];
+ [_binder bindShortcutWithDefaultsKey:SampleDefaultsKey toAction:^{}];
+ [_defaults setObject:[NSKeyedArchiver archivedDataWithRootObject:shortcut] forKey:SampleDefaultsKey];
+ [_binder breakBindingWithDefaultsKey:SampleDefaultsKey];
+ XCTAssertFalse([_monitor isShortcutRegistered:shortcut],
+ @"Unregister shortcut from monitor after binding was removed.");
+}
+
+- (void) testRebinding
+{
+ MASShortcut *shortcut = [MASShortcut shortcutWithKeyCode:1 modifierFlags:1];
+ [_defaults setObject:[NSKeyedArchiver archivedDataWithRootObject:shortcut] forKey:SampleDefaultsKey];
+ [_binder bindShortcutWithDefaultsKey:SampleDefaultsKey toAction:^{}];
+ [_binder breakBindingWithDefaultsKey:SampleDefaultsKey];
+ [_binder bindShortcutWithDefaultsKey:SampleDefaultsKey toAction:^{}];
+ XCTAssertTrue([_monitor isShortcutRegistered:shortcut],
+ @"Bind after unbinding.");
+}
+
+@end
diff --git a/Framework/MASShortcutMonitor.h b/Framework/MASShortcutMonitor.h
new file mode 100644
index 0000000..c29747c
--- /dev/null
+++ b/Framework/MASShortcutMonitor.h
@@ -0,0 +1,9 @@
+#import "MASShortcut.h"
+
+@interface MASShortcutMonitor : NSObject
+
+- (void) registerShortcut: (MASShortcut*) shortcut withAction: (dispatch_block_t) action;
+- (BOOL) isShortcutRegistered: (MASShortcut*) shortcut;
+- (void) unregisterShortcut: (MASShortcut*) shortcut;
+
+@end
diff --git a/Framework/MASShortcutMonitor.m b/Framework/MASShortcutMonitor.m
new file mode 100644
index 0000000..f1c7e9c
--- /dev/null
+++ b/Framework/MASShortcutMonitor.m
@@ -0,0 +1,84 @@
+#import "MASShortcutMonitor.h"
+#import "MASHotKey.h"
+
+@interface MASShortcutMonitor ()
+@property(assign) EventHandlerRef eventHandlerRef;
+@property(strong) NSMutableDictionary *hotKeys;
+@end
+
+static OSStatus MASCarbonEventCallback(EventHandlerCallRef, EventRef, void*);
+
+@implementation MASShortcutMonitor
+
+- (instancetype) init
+{
+ self = [super init];
+ [self setHotKeys:[NSMutableDictionary dictionary]];
+ EventTypeSpec hotKeyPressedSpec = { .eventClass = kEventClassKeyboard, .eventKind = kEventHotKeyPressed };
+ OSStatus status = InstallEventHandler(GetEventDispatcherTarget(), MASCarbonEventCallback,
+ 1, &hotKeyPressedSpec, (__bridge void*)self, &_eventHandlerRef);
+ if (status != noErr) {
+ return nil;
+ }
+ return self;
+}
+
+- (void) dealloc
+{
+ if (_eventHandlerRef) {
+ RemoveEventHandler(_eventHandlerRef);
+ _eventHandlerRef = NULL;
+ }
+}
+
+#pragma mark Registration
+
+- (void) registerShortcut: (MASShortcut*) shortcut withAction: (dispatch_block_t) action
+{
+ MASHotKey *hotKey = [MASHotKey registeredHotKeyWithShortcut:shortcut];
+ [hotKey setAction:action];
+ [_hotKeys setObject:hotKey forKey:shortcut];
+}
+
+- (void) unregisterShortcut: (MASShortcut*) shortcut
+{
+ [_hotKeys removeObjectForKey:shortcut];
+}
+
+- (BOOL) isShortcutRegistered: (MASShortcut*) shortcut
+{
+ return !![_hotKeys objectForKey:shortcut];
+}
+
+#pragma mark Event Handling
+
+- (void) handleEvent: (EventRef) event
+{
+ if (GetEventClass(event) != kEventClassKeyboard) {
+ return;
+ }
+
+ EventHotKeyID hotKeyID;
+ OSStatus status = GetEventParameter(event, kEventParamDirectObject, typeEventHotKeyID, NULL, sizeof(hotKeyID), NULL, &hotKeyID);
+ if (status != noErr || hotKeyID.signature != MASHotKeySignature) {
+ return;
+ }
+
+ [_hotKeys enumerateKeysAndObjectsUsingBlock:^(MASShortcut *shortcut, MASHotKey *hotKey, BOOL *stop) {
+ if (hotKeyID.id == [hotKey carbonID]) {
+ if ([hotKey action]) {
+ dispatch_async(dispatch_get_main_queue(), [hotKey action]);
+ }
+ *stop = YES;
+ }
+ }];
+}
+
+@end
+
+static OSStatus MASCarbonEventCallback(EventHandlerCallRef _, EventRef event, void *context)
+{
+ MASShortcutMonitor *dispatcher = (__bridge id)context;
+ [dispatcher handleEvent:event];
+ return noErr;
+}
diff --git a/Framework/MASShortcutView+UserDefaults.h b/Framework/MASShortcutView+UserDefaults.h
deleted file mode 100644
index 05d3c5b..0000000
--- a/Framework/MASShortcutView+UserDefaults.h
+++ /dev/null
@@ -1,7 +0,0 @@
-#import "MASShortcutView.h"
-
-@interface MASShortcutView (UserDefaults)
-
-@property (nonatomic, copy) NSString *associatedUserDefaultsKey;
-
-@end
diff --git a/Framework/MASShortcutView+UserDefaults.m b/Framework/MASShortcutView+UserDefaults.m
deleted file mode 100644
index 55584f4..0000000
--- a/Framework/MASShortcutView+UserDefaults.m
+++ /dev/null
@@ -1,125 +0,0 @@
-#import "MASShortcutView+UserDefaults.h"
-#import "MASShortcut.h"
-#import <objc/runtime.h>
-
-@interface MASShortcutDefaultsObserver : NSObject
-
-@property (nonatomic, readonly) NSString *userDefaultsKey;
-@property (nonatomic, readonly, weak) MASShortcutView *shortcutView;
-
-- (id)initWithShortcutView:(MASShortcutView *)shortcutView userDefaultsKey:(NSString *)userDefaultsKey;
-
-@end
-
-#pragma mark -
-
-@implementation MASShortcutView (UserDefaults)
-
-void *MASAssociatedDefaultsObserver = &MASAssociatedDefaultsObserver;
-
-- (NSString *)associatedUserDefaultsKey
-{
- MASShortcutDefaultsObserver *defaultsObserver = objc_getAssociatedObject(self, MASAssociatedDefaultsObserver);
- return defaultsObserver.userDefaultsKey;
-}
-
-- (void)setAssociatedUserDefaultsKey:(NSString *)associatedUserDefaultsKey
-{
- // First, stop observing previous shortcut view
- objc_setAssociatedObject(self, MASAssociatedDefaultsObserver, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
-
- if (associatedUserDefaultsKey.length == 0) return;
-
- // Next, start observing current shortcut view
- MASShortcutDefaultsObserver *defaultsObserver = [[MASShortcutDefaultsObserver alloc] initWithShortcutView:self userDefaultsKey:associatedUserDefaultsKey];
- objc_setAssociatedObject(self, MASAssociatedDefaultsObserver, defaultsObserver, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
-}
-
-@end
-
-#pragma mark -
-
-@implementation MASShortcutDefaultsObserver {
- MASShortcut *_originalShortcut;
- BOOL _internalPreferenceChange;
- BOOL _internalShortcutChange;
-}
-
-- (id)initWithShortcutView:(MASShortcutView *)shortcutView userDefaultsKey:(NSString *)userDefaultsKey
-{
- self = [super init];
- if (self) {
- _originalShortcut = shortcutView.shortcutValue;
- _shortcutView = shortcutView;
- _userDefaultsKey = userDefaultsKey.copy;
- [self startObservingShortcutView];
- }
- return self;
-}
-
-- (void)dealloc
-{
- // __weak _shortcutView is not yet deallocated because it refers MASShortcutDefaultsObserver
- [self stopObservingShortcutView];
-}
-
-#pragma mark -
-
-void *kShortcutValueObserver = &kShortcutValueObserver;
-
-- (void)startObservingShortcutView
-{
- // Read initial shortcut value from user preferences
- NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
- NSData *data = [defaults dataForKey:_userDefaultsKey];
- _shortcutView.shortcutValue = [NSKeyedUnarchiver unarchiveObjectWithData:data];
-
- // Observe user preferences to update shortcut value when it changed
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(userDefaultsDidChange:) name:NSUserDefaultsDidChangeNotification object:defaults];
-
- // Observe the keyboard shortcut that user inputs by hand
- [_shortcutView addObserver:self forKeyPath:@"shortcutValue" options:0 context:kShortcutValueObserver];
-}
-
-- (void)userDefaultsDidChange:(NSNotification *)note
-{
- // Ignore notifications posted from -[self observeValueForKeyPath:]
- if (_internalPreferenceChange) return;
-
- _internalShortcutChange = YES;
- NSData *data = [note.object dataForKey:_userDefaultsKey];
- _shortcutView.shortcutValue = [NSKeyedUnarchiver unarchiveObjectWithData:data];
- _internalShortcutChange = NO;
-}
-
-- (void)stopObservingShortcutView
-{
- // Stop observing keyboard hotkeys entered by user in the shortcut view
- [_shortcutView removeObserver:self forKeyPath:@"shortcutValue" context:kShortcutValueObserver];
-
- // Stop observing user preferences
- [[NSNotificationCenter defaultCenter] removeObserver:self name:NSUserDefaultsDidChangeNotification object:[NSUserDefaults standardUserDefaults]];
-
- // Restore original hotkey in the shortcut view
- _shortcutView.shortcutValue = _originalShortcut;
-}
-
-- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
-{
- if (context == kShortcutValueObserver) {
- if (_internalShortcutChange) return;
- MASShortcut *shortcut = [object valueForKey:keyPath];
- _internalPreferenceChange = YES;
-
- NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
- [defaults setObject:[NSKeyedArchiver archivedDataWithRootObject:shortcut] forKey:_userDefaultsKey];
- [defaults synchronize];
-
- _internalPreferenceChange = NO;
- }
- else {
- [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
- }
-}
-
-@end
diff --git a/Framework/MASShortcutView.h b/Framework/MASShortcutView.h
index 5d788b9..8cff0ca 100644
--- a/Framework/MASShortcutView.h
+++ b/Framework/MASShortcutView.h
@@ -1,5 +1,7 @@
@class MASShortcut, MASShortcutValidator;
+extern NSString *const MASShortcutBinding;
+
typedef enum {
MASShortcutViewAppearanceDefault = 0, // Height = 19 px
MASShortcutViewAppearanceTexturedRect, // Height = 25 px
diff --git a/Framework/MASShortcutView.m b/Framework/MASShortcutView.m
index 6f562a5..d03efbc 100644
--- a/Framework/MASShortcutView.m
+++ b/Framework/MASShortcutView.m
@@ -1,6 +1,8 @@
#import "MASShortcutView.h"
#import "MASShortcutValidator.h"
+NSString *const MASShortcutBinding = @"shortcutValue";
+
#define HINT_BUTTON_WIDTH 23.0
#define BUTTON_FONT_SIZE 11.0
#define SEGMENT_CHROME_WIDTH 6.0
@@ -149,6 +151,7 @@
_shortcutValue = shortcutValue;
[self resetToolTips];
[self setNeedsDisplay:YES];
+ [self propagateValue:shortcutValue forBinding:@"shortcutValue"];
if (self.shortcutValueChange) {
self.shortcutValueChange(self);
@@ -462,4 +465,51 @@ void *kUserDataHint = &kUserDataHint;
}
}
+#pragma mark Bindings
+
+// http://tomdalling.com/blog/cocoa/implementing-your-own-cocoa-bindings/
+-(void) propagateValue:(id)value forBinding:(NSString*)binding;
+{
+ NSParameterAssert(binding != nil);
+
+ //WARNING: bindingInfo contains NSNull, so it must be accounted for
+ NSDictionary* bindingInfo = [self infoForBinding:binding];
+ if(!bindingInfo)
+ return; //there is no binding
+
+ //apply the value transformer, if one has been set
+ NSDictionary* bindingOptions = [bindingInfo objectForKey:NSOptionsKey];
+ if(bindingOptions){
+ NSValueTransformer* transformer = [bindingOptions valueForKey:NSValueTransformerBindingOption];
+ if(!transformer || (id)transformer == [NSNull null]){
+ NSString* transformerName = [bindingOptions valueForKey:NSValueTransformerNameBindingOption];
+ if(transformerName && (id)transformerName != [NSNull null]){
+ transformer = [NSValueTransformer valueTransformerForName:transformerName];
+ }
+ }
+
+ if(transformer && (id)transformer != [NSNull null]){
+ if([[transformer class] allowsReverseTransformation]){
+ value = [transformer reverseTransformedValue:value];
+ } else {
+ NSLog(@"WARNING: binding \"%@\" has value transformer, but it doesn't allow reverse transformations in %s", binding, __PRETTY_FUNCTION__);
+ }
+ }
+ }
+
+ id boundObject = [bindingInfo objectForKey:NSObservedObjectKey];
+ if(!boundObject || boundObject == [NSNull null]){
+ NSLog(@"ERROR: NSObservedObjectKey was nil for binding \"%@\" in %s", binding, __PRETTY_FUNCTION__);
+ return;
+ }
+
+ NSString* boundKeyPath = [bindingInfo objectForKey:NSObservedKeyPathKey];
+ if(!boundKeyPath || (id)boundKeyPath == [NSNull null]){
+ NSLog(@"ERROR: NSObservedKeyPathKey was nil for binding \"%@\" in %s", binding, __PRETTY_FUNCTION__);
+ return;
+ }
+
+ [boundObject setValue:value forKeyPath:boundKeyPath];
+}
+
@end
diff --git a/Framework/Prefix.pch b/Framework/Prefix.pch
index 7f2c544..3e71c31 100644
--- a/Framework/Prefix.pch
+++ b/Framework/Prefix.pch
@@ -1 +1,2 @@
#import <AppKit/AppKit.h>
+#import <Carbon/Carbon.h> \ No newline at end of file
diff --git a/Framework/Shortcut.h b/Framework/Shortcut.h
index 3087a51..dce07a5 100644
--- a/Framework/Shortcut.h
+++ b/Framework/Shortcut.h
@@ -1,4 +1,5 @@
#import "MASShortcut.h"
-#import "MASShortcutView+UserDefaults.h"
-#import "MASShortcut+UserDefaults.h"
-#import "MASShortcut+Monitoring.h" \ No newline at end of file
+#import "MASShortcutValidator.h"
+#import "MASShortcutMonitor.h"
+#import "MASShortcutBinder.h"
+#import "MASShortcutView.h"