From 444d1bccb9770738fa4ea40383c23f44a55089c2 Mon Sep 17 00:00:00 2001 From: Tomáš Znamenáček Date: Wed, 6 Aug 2014 18:05:43 +0200 Subject: 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. --- Framework/MASShortcutBinderTests.m | 82 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 Framework/MASShortcutBinderTests.m (limited to 'Framework/MASShortcutBinderTests.m') 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 -- cgit v1.2.3