Permalink
Browse files

Adds super-easy API for associated preference key.

  • Loading branch information...
1 parent 27a5c52 commit 7eb4408ce34dd831ef279c182672976c8aa3f605 @shpakovski committed Jul 6, 2012
Showing with 143 additions and 20 deletions.
  1. +7 −0 MASShortcutView+UserDefaults.h
  2. +115 −0 MASShortcutView+UserDefaults.m
  3. +21 −20 MASShortcutView.m
View
7 MASShortcutView+UserDefaults.h
@@ -0,0 +1,7 @@
+#import "MASShortcutView.h"
+
+@interface MASShortcutView (UserDefaults)
+
+@property (nonatomic, copy) NSString *associatedUserDefaultsKey;
+
+@end
View
115 MASShortcutView+UserDefaults.m
@@ -0,0 +1,115 @@
+#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 *kDefaultsObserver = &kDefaultsObserver;
+
+- (NSString *)associatedUserDefaultsKey
+{
+ MASShortcutDefaultsObserver *defaultsObserver = objc_getAssociatedObject(self, kDefaultsObserver);
+ return defaultsObserver.userDefaultsKey;
+}
+
+- (void)setAssociatedUserDefaultsKey:(NSString *)associatedUserDefaultsKey
+{
+ MASShortcutDefaultsObserver *defaultsObserver = [[MASShortcutDefaultsObserver alloc] initWithShortcutView:self userDefaultsKey:associatedUserDefaultsKey];
+ objc_setAssociatedObject(self, kDefaultsObserver, 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 = [MASShortcut shortcutWithData: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 = [MASShortcut shortcutWithData: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 standardUserDefaults] setObject:shortcut.data forKey:_userDefaultsKey];
+ _internalPreferenceChange = NO;
+ }
+ else {
+ [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
+ }
+}
+
+@end
View
41 MASShortcutView.m
@@ -62,7 +62,7 @@ - (void)setRecording:(BOOL)flag
static MASShortcutView *currentRecorder = nil;
if (flag && (currentRecorder != self)) {
currentRecorder.recording = NO;
- currentRecorder = self;
+ currentRecorder = flag ? self : nil;
}
// Only enabled view supports recording
@@ -117,10 +117,10 @@ - (void)drawRect:(CGRect)dirtyRect
[self getShortcutRect:&shortcutRect hintRect:NULL];
NSString *title = (self.recording
? (_hinting
- ? NSLocalizedString(@"Use old shortuct", @"Cancel action button for non-empty shortcut in recording state")
+ ? NSLocalizedString(@"Use Old Shortuct", @"Cancel action button for non-empty shortcut in recording state")
: (self.shortcutPlaceholder.length > 0
? self.shortcutPlaceholder
- : NSLocalizedString(@"Type new shortcut", @"Non-empty shortcut button in recording state")))
+ : NSLocalizedString(@"Type New Shortcut", @"Non-empty shortcut button in recording state")))
: _shortcutValue ? _shortcutValue.description : @"");
[self drawInRect:shortcutRect withTitle:title alignment:NSCenterTextAlignment state:self.isRecording ? NSOnState : NSOffState];
}
@@ -132,15 +132,15 @@ - (void)drawRect:(CGRect)dirtyRect
CGRect shortcutRect;
[self getShortcutRect:&shortcutRect hintRect:NULL];
NSString *title = (_hinting
- ? NSLocalizedString(@"Click to cancel", @"Cancel action button in recording state")
+ ? NSLocalizedString(@"Cancel", @"Cancel action button in recording state")
: (self.shortcutPlaceholder.length > 0
? self.shortcutPlaceholder
- : NSLocalizedString(@"Type shortcut", @"Empty shortcut button in recording state")));
+ : NSLocalizedString(@"Type Shortcut", @"Empty shortcut button in recording state")));
[self drawInRect:shortcutRect withTitle:title alignment:NSCenterTextAlignment state:NSOnState];
}
else
{
- [self drawInRect:self.bounds withTitle:NSLocalizedString(@"Click to record", @"Empty shortcut button in normal state")
+ [self drawInRect:self.bounds withTitle:NSLocalizedString(@"Record Shortcut", @"Empty shortcut button in normal state")
alignment:NSCenterTextAlignment state:NSOffState];
}
}
@@ -216,7 +216,7 @@ - (void)updateTrackingAreas
}
// Forbid hinting if view is disabled
- if (self.enabled) return;
+ if (!self.enabled) return;
CGRect hintRect;
[self getShortcutRect:NULL hintRect:&hintRect];
@@ -256,7 +256,7 @@ - (void)resetToolTips
}
if ((self.shortcutValue == nil) || self.recording || !self.enabled) return;
-
+
CGRect shortcutRect, hintRect;
[self getShortcutRect:&shortcutRect hintRect:&hintRect];
_shortcutToolTipTag = [self addToolTipRect:shortcutRect owner:self userData:kUserDataShortcut];
@@ -284,23 +284,24 @@ - (void)activateEventMonitoring:(BOOL)shouldActivate
static id eventMonitor = nil;
if (shouldActivate) {
+ __weak MASShortcutView *weakSelf = self;
eventMonitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSKeyDownMask handler:^(NSEvent *event) {
MASShortcut *shortcut = [MASShortcut shortcutWithEvent:event];
if ((shortcut.keyCode == kVK_Delete) || (shortcut.keyCode == kVK_ForwardDelete)) {
// Delete shortcut
- self.shortcutValue = nil;
- self.recording = NO;
+ weakSelf.shortcutValue = nil;
+ weakSelf.recording = NO;
event = nil;
}
else if (shortcut.keyCode == kVK_Escape) {
// Cancel recording
- self.recording = NO;
+ weakSelf.recording = NO;
event = nil;
}
else if (shortcut.shouldBypass) {
// Command + W, Command + Q, ESC should deactivate recorder
- self.recording = NO;
+ weakSelf.recording = NO;
}
else {
// Verify possible shortcut
@@ -310,20 +311,20 @@ - (void)activateEventMonitoring:(BOOL)shouldActivate
NSError *error = nil;
if ([shortcut isTakenError:&error]) {
// Prevent cancel of recording when Alert window is key
- [self activateResignObserver:NO];
- [self activateEventMonitoring:NO];
+ [weakSelf activateResignObserver:NO];
+ [weakSelf activateEventMonitoring:NO];
NSString *format = NSLocalizedString(@"The key combination %@ cannot be used",
@"Title for alert when shortcut is already used");
NSRunCriticalAlertPanel([NSString stringWithFormat:format, shortcut], error.localizedDescription,
NSLocalizedString(@"OK", @"Alert button when shortcut is already used"),
nil, nil);
- self.shortcutPlaceholder = nil;
- [self activateResignObserver:YES];
- [self activateEventMonitoring:YES];
+ weakSelf.shortcutPlaceholder = nil;
+ [weakSelf activateResignObserver:YES];
+ [weakSelf activateEventMonitoring:YES];
}
else {
- self.shortcutValue = shortcut;
- self.recording = NO;
+ weakSelf.shortcutValue = shortcut;
+ weakSelf.recording = NO;
}
}
else {
@@ -333,7 +334,7 @@ - (void)activateEventMonitoring:(BOOL)shouldActivate
}
else {
// User is playing with modifier keys
- self.shortcutPlaceholder = shortcut.modifierFlagsString;
+ weakSelf.shortcutPlaceholder = shortcut.modifierFlagsString;
}
event = nil;
}

0 comments on commit 7eb4408

Please sign in to comment.