aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Framework/MASShortcutBinder.m25
-rw-r--r--Framework/MASShortcutBinderTests.m15
2 files changed, 34 insertions, 6 deletions
diff --git a/Framework/MASShortcutBinder.m b/Framework/MASShortcutBinder.m
index bf85c41..9b9f017 100644
--- a/Framework/MASShortcutBinder.m
+++ b/Framework/MASShortcutBinder.m
@@ -23,7 +23,7 @@
- (void) dealloc
{
for (NSString *bindingName in [_actions allKeys]) {
- [self unbind:bindingName];
+ [self unbind:[self encodeBindingName:bindingName]];
}
}
@@ -42,8 +42,10 @@
- (void) bindShortcutWithDefaultsKey: (NSString*) defaultsKeyName toAction: (dispatch_block_t) action
{
[_actions setObject:[action copy] forKey:defaultsKeyName];
- [self bind:defaultsKeyName toObject:[NSUserDefaultsController sharedUserDefaultsController]
- withKeyPath:[@"values." stringByAppendingString:defaultsKeyName] options:_bindingOptions];
+ [self bind:[self encodeBindingName:defaultsKeyName]
+ toObject:[NSUserDefaultsController sharedUserDefaultsController]
+ withKeyPath:[@"values." stringByAppendingString:defaultsKeyName]
+ options:_bindingOptions];
}
- (void) breakBindingWithDefaultsKey: (NSString*) defaultsKeyName
@@ -51,7 +53,7 @@
[_shortcutMonitor unregisterShortcut:[_shortcuts objectForKey:defaultsKeyName]];
[_shortcuts removeObjectForKey:defaultsKeyName];
[_actions removeObjectForKey:defaultsKeyName];
- [self unbind:defaultsKeyName];
+ [self unbind:[self encodeBindingName:defaultsKeyName]];
}
- (void) registerDefaultShortcuts: (NSDictionary*) defaultShortcuts
@@ -74,6 +76,18 @@
#pragma mark Bindings
+static NSString *const MASDotSymbolReplacement = @"__dot__";
+
+- (NSString*) encodeBindingName: (NSString*) binding
+{
+ return [binding stringByReplacingOccurrencesOfString:@"." withString:MASDotSymbolReplacement];
+}
+
+- (NSString*) decodeBindingName: (NSString*) binding
+{
+ return [binding stringByReplacingOccurrencesOfString:MASDotSymbolReplacement withString:@"."];
+}
+
- (BOOL) isRegisteredAction: (NSString*) name
{
return !![_actions objectForKey:name];
@@ -81,6 +95,7 @@
- (id) valueForUndefinedKey: (NSString*) key
{
+ key = [self decodeBindingName:key];
return [self isRegisteredAction:key] ?
[_shortcuts objectForKey:key] :
[super valueForUndefinedKey:key];
@@ -88,6 +103,8 @@
- (void) setValue: (id) value forUndefinedKey: (NSString*) key
{
+ key = [self decodeBindingName:key];
+
if (![self isRegisteredAction:key]) {
[super setValue:value forUndefinedKey:key];
return;
diff --git a/Framework/MASShortcutBinderTests.m b/Framework/MASShortcutBinderTests.m
index cb04532..35542d4 100644
--- a/Framework/MASShortcutBinderTests.m
+++ b/Framework/MASShortcutBinderTests.m
@@ -95,11 +95,22 @@ static NSString *const SampleDefaultsKey = @"sampleShortcut";
@"Bind shortcut using a default value.");
}
+// The connection between user defaults and shortcuts is implemented
+// using Cocoa Bindings where the dot symbol (“.”) has a special meaning.
+// This means we have to escape the dot symbol used in defaults keys,
+// otherwise things blow up. Details at <http://git.io/x5YS>.
- (void) testBindingsWithDotSymbol
{
static NSString *const SampleDefaultsKeyWithDotSymbol = @"sample.Shortcut";
- XCTAssertThrows([_binder bindShortcutWithDefaultsKey:SampleDefaultsKeyWithDotSymbol toAction:^{}],
- @"Attempting to use a defaults key with a dot symbol crashes with an exception.");
+ MASShortcut *shortcut = [MASShortcut shortcutWithKeyCode:1 modifierFlags:1];
+ XCTAssertNoThrow([_binder bindShortcutWithDefaultsKey:SampleDefaultsKeyWithDotSymbol toAction:^{}],
+ @"Binding a shortcut to a defaults key with a dot symbol must not throw.");
+ [_defaults setObject:[NSKeyedArchiver archivedDataWithRootObject:shortcut] forKey:SampleDefaultsKeyWithDotSymbol];
+ XCTAssertTrue([_monitor isShortcutRegistered:shortcut],
+ @"Read a shortcut value using a defaults key with a dot symbol.");
+ [_binder breakBindingWithDefaultsKey:SampleDefaultsKeyWithDotSymbol];
+ XCTAssertFalse([_monitor isShortcutRegistered:shortcut],
+ @"Breaking a binding with a dot symbol.");
}
@end