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/MASShortcutView.m | 50 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) (limited to 'Framework/MASShortcutView.m') 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 -- cgit v1.2.3