aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTomáš Znamenáček2014-08-06 18:05:43 +0200
committerTomáš Znamenáček2015-01-07 15:39:39 +0100
commit444d1bccb9770738fa4ea40383c23f44a55089c2 (patch)
treea4f06e82263b751685e51ee3e1479fd9eebb1d16
parentd8efb0755ab99ef60411f77fa4b635f466973d0f (diff)
downloadMASShortcut-444d1bccb9770738fa4ea40383c23f44a55089c2.tar.bz2
Refactored the shortcut dispatcher and bindings to user defaults.
This is a big change that was hard to split into smaller commits. There’s now a new class to bind shortcuts to actions, a new class to bind user defaults’ keys to actions, and a new way to associate user defaults with the recorder control (MASShortcutView). I have also updated the demo app to go with the changes. The new class to associate shortcuts with actions is called MASShortcutMonitor. It wraps the Carbon hotkey magic and offers a simple interface to add a shortcut along with a block that should be run when the shortcut is pressed. It’s the lowest-level interface. Since the usual requirement is to store the shortcuts into user defaults, there’s also a higher-level interface offered by the MASShortcutBinder class. That takes a defaults key and associates it with a block. When the shortcut stored under the defaults key changes, the binder automatically switches to the new shortcut. The class is a wrapper built atop of the previous one, the MASShortcutMonitor – it simply adds, updates and removes shortcuts as the user defaults change. I have removed the special user defaults integration code from the recorder control (MASShortcutView) and replaced it with a small Cocoa Bindings shim. This means that in order to keep the recorder control in sync with the defaults you just have to call the usual bind:toObject:withKeyPath:options: method, like this: [_shortcutView bind:MASShortcutBinding toObject:[NSUserDefaultsController sharedUserDefaultsController] withKeyPath[@"values.ExampleDefaultsKey" options:@{NSValueTransformerNameBindingOption:NSKeyedUnarchiveFromDataTransformerName}]; That’s more verbose than the previous solution, but it’s much cleaner and can be swept under a convenience call if needed. I might also add a dictionaryValue property later that would make it possible to bind the value to user defaults directly, without a transformer, and would enable backward compatibility with Shortcut Recorder.
-rw-r--r--Demo/AppDelegate.m38
-rw-r--r--Demo/Prefix.pch5
-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
-rw-r--r--MASShortcut.xcodeproj/project.pbxproj60
20 files changed, 431 insertions, 468 deletions
diff --git a/Demo/AppDelegate.m b/Demo/AppDelegate.m
index 54b64e9..d2c07ba 100644
--- a/Demo/AppDelegate.m
+++ b/Demo/AppDelegate.m
@@ -1,43 +1,37 @@
#import "AppDelegate.h"
-#import <MASShortcut/Shortcut.h>
NSString *const MASPreferenceKeyShortcut = @"MASDemoShortcut";
NSString *const MASPreferenceKeyShortcutEnabled = @"MASDemoShortcutEnabled";
NSString *const MASPreferenceKeyConstantShortcutEnabled = @"MASDemoConstantShortcutEnabled";
@implementation AppDelegate {
- __weak id _constantShortcutMonitor;
+ MASShortcutMonitor *_shortcutMonitor;
+ MASShortcutBinder *_shortcutBinder;
}
-@synthesize window = _window;
-@synthesize shortcutView = _shortcutView;
-
#pragma mark -
- (void)awakeFromNib
{
[super awakeFromNib];
+ _shortcutBinder = [[MASShortcutBinder alloc] init];
+ _shortcutMonitor = [[MASShortcutMonitor alloc] init];
+ [_shortcutBinder setShortcutMonitor:_shortcutMonitor];
+
// Checkbox will enable and disable the shortcut view
[self.shortcutView bind:@"enabled" toObject:self withKeyPath:@"shortcutEnabled" options:nil];
}
-- (void)dealloc
-{
- // Cleanup
- [self.shortcutView unbind:@"enabled"];
-}
-
-#pragma mark - NSApplicationDelegate
+#pragma mark NSApplicationDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
- // Uncomment the following lines to make Command-Shift-D the default shortcut
-// MASShortcut *defaultShortcut = [MASShortcut shortcutWithKeyCode:0x2 modifierFlags:NSCommandKeyMask|NSShiftKeyMask];
-// [MASShortcut setGlobalShortcut:defaultShortcut forUserDefaultsKey:MASPreferenceKeyShortcut];
-
// Shortcut view will follow and modify user preferences automatically
- self.shortcutView.associatedUserDefaultsKey = MASPreferenceKeyShortcut;
+ [_shortcutView bind:MASShortcutBinding
+ toObject:[NSUserDefaultsController sharedUserDefaultsController]
+ withKeyPath:[@"values." stringByAppendingString:MASPreferenceKeyShortcut]
+ options:@{NSValueTransformerNameBindingOption:NSKeyedUnarchiveFromDataTransformerName}];
// Activate the global keyboard shortcut if it was enabled last time
[self resetShortcutRegistration];
@@ -69,14 +63,14 @@ NSString *const MASPreferenceKeyConstantShortcutEnabled = @"MASDemoConstantShort
- (void)resetShortcutRegistration
{
if (self.shortcutEnabled) {
- [MASShortcut registerGlobalShortcutWithUserDefaultsKey:MASPreferenceKeyShortcut handler:^{
+ [_shortcutBinder bindShortcutWithDefaultsKey:MASPreferenceKeyShortcut toAction:^{
[[NSAlert alertWithMessageText:NSLocalizedString(@"Global hotkey has been pressed.", @"Alert message for custom shortcut")
defaultButton:NSLocalizedString(@"OK", @"Default button for the alert on custom shortcut")
alternateButton:nil otherButton:nil informativeTextWithFormat:@""] runModal];
}];
}
else {
- [MASShortcut unregisterGlobalShortcutWithUserDefaultsKey:MASPreferenceKeyShortcut];
+ [_shortcutBinder breakBindingWithDefaultsKey:MASPreferenceKeyShortcut];
}
}
@@ -97,16 +91,16 @@ NSString *const MASPreferenceKeyConstantShortcutEnabled = @"MASDemoConstantShort
- (void)resetConstantShortcutRegistration
{
+ MASShortcut *shortcut = [MASShortcut shortcutWithKeyCode:kVK_F2 modifierFlags:NSCommandKeyMask];
if (self.constantShortcutEnabled) {
- MASShortcut *shortcut = [MASShortcut shortcutWithKeyCode:kVK_F2 modifierFlags:NSCommandKeyMask];
- _constantShortcutMonitor = [MASShortcut addGlobalHotkeyMonitorWithShortcut:shortcut handler:^{
+ [_shortcutMonitor registerShortcut:shortcut withAction:^{
[[NSAlert alertWithMessageText:NSLocalizedString(@"⌘F2 has been pressed.", @"Alert message for constant shortcut")
defaultButton:NSLocalizedString(@"OK", @"Default button for the alert on constant shortcut")
alternateButton:nil otherButton:nil informativeTextWithFormat:@""] runModal];
}];
}
else {
- [MASShortcut removeGlobalHotkeyMonitor:_constantShortcutMonitor];
+ [_shortcutMonitor unregisterShortcut:shortcut];
}
}
diff --git a/Demo/Prefix.pch b/Demo/Prefix.pch
index aabef47..20d47b6 100644
--- a/Demo/Prefix.pch
+++ b/Demo/Prefix.pch
@@ -1,3 +1,2 @@
-#ifdef __OBJC__
- #import <Cocoa/Cocoa.h>
-#endif
+#import <Cocoa/Cocoa.h>
+#import <MASShortcut/Shortcut.h>
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"
diff --git a/MASShortcut.xcodeproj/project.pbxproj b/MASShortcut.xcodeproj/project.pbxproj
index e79cb66..f8f29e5 100644
--- a/MASShortcut.xcodeproj/project.pbxproj
+++ b/MASShortcut.xcodeproj/project.pbxproj
@@ -10,14 +10,8 @@
0D827CD71990D4420010B8EF /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D827CD61990D4420010B8EF /* Cocoa.framework */; };
0D827D251990D55E0010B8EF /* MASShortcut.h in Headers */ = {isa = PBXBuildFile; fileRef = 0D827D1B1990D55E0010B8EF /* MASShortcut.h */; settings = {ATTRIBUTES = (Public, ); }; };
0D827D261990D55E0010B8EF /* MASShortcut.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D827D1C1990D55E0010B8EF /* MASShortcut.m */; };
- 0D827D271990D55E0010B8EF /* MASShortcut+Monitoring.h in Headers */ = {isa = PBXBuildFile; fileRef = 0D827D1D1990D55E0010B8EF /* MASShortcut+Monitoring.h */; settings = {ATTRIBUTES = (Public, ); }; };
- 0D827D281990D55E0010B8EF /* MASShortcut+Monitoring.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D827D1E1990D55E0010B8EF /* MASShortcut+Monitoring.m */; };
- 0D827D291990D55E0010B8EF /* MASShortcut+UserDefaults.h in Headers */ = {isa = PBXBuildFile; fileRef = 0D827D1F1990D55E0010B8EF /* MASShortcut+UserDefaults.h */; settings = {ATTRIBUTES = (Public, ); }; };
- 0D827D2A1990D55E0010B8EF /* MASShortcut+UserDefaults.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D827D201990D55E0010B8EF /* MASShortcut+UserDefaults.m */; };
0D827D2B1990D55E0010B8EF /* MASShortcutView.h in Headers */ = {isa = PBXBuildFile; fileRef = 0D827D211990D55E0010B8EF /* MASShortcutView.h */; settings = {ATTRIBUTES = (Public, ); }; };
0D827D2C1990D55E0010B8EF /* MASShortcutView.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D827D221990D55E0010B8EF /* MASShortcutView.m */; };
- 0D827D2D1990D55E0010B8EF /* MASShortcutView+UserDefaults.h in Headers */ = {isa = PBXBuildFile; fileRef = 0D827D231990D55E0010B8EF /* MASShortcutView+UserDefaults.h */; settings = {ATTRIBUTES = (Public, ); }; };
- 0D827D2E1990D55E0010B8EF /* MASShortcutView+UserDefaults.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D827D241990D55E0010B8EF /* MASShortcutView+UserDefaults.m */; };
0D827D381990D5E70010B8EF /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D827CD61990D4420010B8EF /* Cocoa.framework */; };
0D827D6F1990D6110010B8EF /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D827D6A1990D6110010B8EF /* AppDelegate.m */; };
0D827D711990D6110010B8EF /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D827D6D1990D6110010B8EF /* main.m */; };
@@ -32,6 +26,13 @@
0D827D99199110F60010B8EF /* Prefix.pch in Headers */ = {isa = PBXBuildFile; fileRef = 0D827D98199110F60010B8EF /* Prefix.pch */; };
0D827D9E19911A190010B8EF /* MASShortcutValidator.h in Headers */ = {isa = PBXBuildFile; fileRef = 0D827D9C19911A190010B8EF /* MASShortcutValidator.h */; };
0D827D9F19911A190010B8EF /* MASShortcutValidator.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D827D9D19911A190010B8EF /* MASShortcutValidator.m */; };
+ 0D827DA519912D240010B8EF /* MASShortcutMonitor.h in Headers */ = {isa = PBXBuildFile; fileRef = 0D827DA319912D240010B8EF /* MASShortcutMonitor.h */; };
+ 0D827DAD199132840010B8EF /* MASShortcutBinder.h in Headers */ = {isa = PBXBuildFile; fileRef = 0D827DAB199132840010B8EF /* MASShortcutBinder.h */; };
+ 0DC2F17619922798003A0131 /* MASHotKey.h in Headers */ = {isa = PBXBuildFile; fileRef = 0DC2F17419922798003A0131 /* MASHotKey.h */; };
+ 0DC2F17719922798003A0131 /* MASHotKey.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DC2F17519922798003A0131 /* MASHotKey.m */; };
+ 0DC2F17C199232EA003A0131 /* MASShortcutMonitor.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D827DA419912D240010B8EF /* MASShortcutMonitor.m */; };
+ 0DC2F17D199232F7003A0131 /* MASShortcutBinder.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D827DAC199132840010B8EF /* MASShortcutBinder.m */; };
+ 0DC2F18919925F8F003A0131 /* MASShortcutBinderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DC2F18819925F8F003A0131 /* MASShortcutBinderTests.m */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -67,14 +68,8 @@
0D827CEB1990D4420010B8EF /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; };
0D827D1B1990D55E0010B8EF /* MASShortcut.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MASShortcut.h; path = Framework/MASShortcut.h; sourceTree = "<group>"; };
0D827D1C1990D55E0010B8EF /* MASShortcut.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MASShortcut.m; path = Framework/MASShortcut.m; sourceTree = "<group>"; };
- 0D827D1D1990D55E0010B8EF /* MASShortcut+Monitoring.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "MASShortcut+Monitoring.h"; path = "Framework/MASShortcut+Monitoring.h"; sourceTree = "<group>"; };
- 0D827D1E1990D55E0010B8EF /* MASShortcut+Monitoring.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "MASShortcut+Monitoring.m"; path = "Framework/MASShortcut+Monitoring.m"; sourceTree = "<group>"; };
- 0D827D1F1990D55E0010B8EF /* MASShortcut+UserDefaults.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "MASShortcut+UserDefaults.h"; path = "Framework/MASShortcut+UserDefaults.h"; sourceTree = "<group>"; };
- 0D827D201990D55E0010B8EF /* MASShortcut+UserDefaults.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "MASShortcut+UserDefaults.m"; path = "Framework/MASShortcut+UserDefaults.m"; sourceTree = "<group>"; };
0D827D211990D55E0010B8EF /* MASShortcutView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MASShortcutView.h; path = Framework/MASShortcutView.h; sourceTree = "<group>"; };
0D827D221990D55E0010B8EF /* MASShortcutView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MASShortcutView.m; path = Framework/MASShortcutView.m; sourceTree = "<group>"; };
- 0D827D231990D55E0010B8EF /* MASShortcutView+UserDefaults.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "MASShortcutView+UserDefaults.h"; path = "Framework/MASShortcutView+UserDefaults.h"; sourceTree = "<group>"; };
- 0D827D241990D55E0010B8EF /* MASShortcutView+UserDefaults.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "MASShortcutView+UserDefaults.m"; path = "Framework/MASShortcutView+UserDefaults.m"; sourceTree = "<group>"; };
0D827D2F1990D5640010B8EF /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Framework/Info.plist; sourceTree = "<group>"; };
0D827D371990D5E70010B8EF /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; };
0D827D691990D6110010B8EF /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
@@ -92,6 +87,13 @@
0D827D98199110F60010B8EF /* Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Prefix.pch; path = Framework/Prefix.pch; sourceTree = "<group>"; };
0D827D9C19911A190010B8EF /* MASShortcutValidator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MASShortcutValidator.h; path = Framework/MASShortcutValidator.h; sourceTree = "<group>"; };
0D827D9D19911A190010B8EF /* MASShortcutValidator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MASShortcutValidator.m; path = Framework/MASShortcutValidator.m; sourceTree = "<group>"; };
+ 0D827DA319912D240010B8EF /* MASShortcutMonitor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MASShortcutMonitor.h; path = Framework/MASShortcutMonitor.h; sourceTree = "<group>"; };
+ 0D827DA419912D240010B8EF /* MASShortcutMonitor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MASShortcutMonitor.m; path = Framework/MASShortcutMonitor.m; sourceTree = "<group>"; };
+ 0D827DAB199132840010B8EF /* MASShortcutBinder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MASShortcutBinder.h; path = Framework/MASShortcutBinder.h; sourceTree = "<group>"; };
+ 0D827DAC199132840010B8EF /* MASShortcutBinder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MASShortcutBinder.m; path = Framework/MASShortcutBinder.m; sourceTree = "<group>"; };
+ 0DC2F17419922798003A0131 /* MASHotKey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MASHotKey.h; path = Framework/MASHotKey.h; sourceTree = "<group>"; };
+ 0DC2F17519922798003A0131 /* MASHotKey.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MASHotKey.m; path = Framework/MASHotKey.m; sourceTree = "<group>"; };
+ 0DC2F18819925F8F003A0131 /* MASShortcutBinderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MASShortcutBinderTests.m; path = Framework/MASShortcutBinderTests.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -161,7 +163,7 @@
isa = PBXGroup;
children = (
0D827DA019912A660010B8EF /* Model */,
- 0D827DA219912A870010B8EF /* Handling & Persistence */,
+ 0D827DA219912A870010B8EF /* Watching & Storage */,
0D827DA119912A6D0010B8EF /* UI */,
0D827D2F1990D5640010B8EF /* Info.plist */,
0D827D98199110F60010B8EF /* Prefix.pch */,
@@ -211,21 +213,22 @@
children = (
0D827D211990D55E0010B8EF /* MASShortcutView.h */,
0D827D221990D55E0010B8EF /* MASShortcutView.m */,
- 0D827D231990D55E0010B8EF /* MASShortcutView+UserDefaults.h */,
- 0D827D241990D55E0010B8EF /* MASShortcutView+UserDefaults.m */,
);
name = UI;
sourceTree = "<group>";
};
- 0D827DA219912A870010B8EF /* Handling & Persistence */ = {
+ 0D827DA219912A870010B8EF /* Watching & Storage */ = {
isa = PBXGroup;
children = (
- 0D827D1D1990D55E0010B8EF /* MASShortcut+Monitoring.h */,
- 0D827D1E1990D55E0010B8EF /* MASShortcut+Monitoring.m */,
- 0D827D1F1990D55E0010B8EF /* MASShortcut+UserDefaults.h */,
- 0D827D201990D55E0010B8EF /* MASShortcut+UserDefaults.m */,
- );
- name = "Handling & Persistence";
+ 0DC2F17419922798003A0131 /* MASHotKey.h */,
+ 0DC2F17519922798003A0131 /* MASHotKey.m */,
+ 0D827DA319912D240010B8EF /* MASShortcutMonitor.h */,
+ 0D827DA419912D240010B8EF /* MASShortcutMonitor.m */,
+ 0D827DAB199132840010B8EF /* MASShortcutBinder.h */,
+ 0D827DAC199132840010B8EF /* MASShortcutBinder.m */,
+ 0DC2F18819925F8F003A0131 /* MASShortcutBinderTests.m */,
+ );
+ name = "Watching & Storage";
sourceTree = "<group>";
};
/* End PBXGroup section */
@@ -239,11 +242,11 @@
0D827D99199110F60010B8EF /* Prefix.pch in Headers */,
0D827D9719910FF70010B8EF /* MASKeyCodes.h in Headers */,
0D827D251990D55E0010B8EF /* MASShortcut.h in Headers */,
- 0D827D2D1990D55E0010B8EF /* MASShortcutView+UserDefaults.h in Headers */,
- 0D827D271990D55E0010B8EF /* MASShortcut+Monitoring.h in Headers */,
+ 0D827DAD199132840010B8EF /* MASShortcutBinder.h in Headers */,
0D827D771990F81E0010B8EF /* Shortcut.h in Headers */,
- 0D827D291990D55E0010B8EF /* MASShortcut+UserDefaults.h in Headers */,
+ 0DC2F17619922798003A0131 /* MASHotKey.h in Headers */,
0D827D9E19911A190010B8EF /* MASShortcutValidator.h in Headers */,
+ 0D827DA519912D240010B8EF /* MASShortcutMonitor.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -368,12 +371,12 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 0DC2F17719922798003A0131 /* MASHotKey.m in Sources */,
0D827D9F19911A190010B8EF /* MASShortcutValidator.m in Sources */,
- 0D827D2E1990D55E0010B8EF /* MASShortcutView+UserDefaults.m in Sources */,
+ 0DC2F17C199232EA003A0131 /* MASShortcutMonitor.m in Sources */,
0D827D2C1990D55E0010B8EF /* MASShortcutView.m in Sources */,
- 0D827D2A1990D55E0010B8EF /* MASShortcut+UserDefaults.m in Sources */,
0D827D261990D55E0010B8EF /* MASShortcut.m in Sources */,
- 0D827D281990D55E0010B8EF /* MASShortcut+Monitoring.m in Sources */,
+ 0DC2F17D199232F7003A0131 /* MASShortcutBinder.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -391,6 +394,7 @@
buildActionMask = 2147483647;
files = (
0D827D9419910B740010B8EF /* MASShortcutTests.m in Sources */,
+ 0DC2F18919925F8F003A0131 /* MASShortcutBinderTests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};