Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 652b5bb6ed | |||
| afc25e4b7d | |||
| 344e3e1999 | |||
| fe750267fb | |||
| cbc369c71a |
+2
-1
@@ -14,4 +14,5 @@ xcuserdata
|
||||
profile
|
||||
*.moved-aside
|
||||
# Finder
|
||||
.DS_Store
|
||||
.DS_Store
|
||||
DerivedData
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
Copyright (c) 2012-2013, Vadim Shpakovski
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+18
-17
@@ -2,12 +2,17 @@
|
||||
|
||||
NSMutableDictionary *MASRegisteredHotKeys();
|
||||
BOOL InstallCommonEventHandler();
|
||||
BOOL InstallHotkeyWithShortcut(MASShortcut *shortcut, UInt32 *outCarbonHotKeyID, EventHotKeyRef *outCarbonHotKey);
|
||||
void UninstallEventHandler();
|
||||
void InstallHotkeyWithShortcut(MASShortcut *shortcut, UInt32 *outCarbonHotKeyID, EventHotKeyRef *outCarbonHotKey);
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@interface MASShortcutHotKey : NSObject
|
||||
@interface MASShortcutHotKey : NSObject {
|
||||
MASShortcut *_shortcut;
|
||||
void (^_handler)();
|
||||
EventHotKeyRef _carbonHotKey;
|
||||
UInt32 _carbonHotKeyID;
|
||||
}
|
||||
|
||||
@property (nonatomic, readonly) MASShortcut *shortcut;
|
||||
@property (nonatomic, readonly, copy) void (^handler)();
|
||||
@@ -24,12 +29,8 @@ void UninstallEventHandler();
|
||||
|
||||
+ (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;
|
||||
|
||||
NSString *monitor = [NSString stringWithFormat:@"%p: %@", shortcut, shortcut.description];
|
||||
MASShortcutHotKey *hotKey = [[[MASShortcutHotKey alloc] initWithShortcut:shortcut handler:handler] autorelease];
|
||||
[MASRegisteredHotKeys() setObject:hotKey forKey:monitor];
|
||||
return monitor;
|
||||
}
|
||||
@@ -61,11 +62,9 @@ void UninstallEventHandler();
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_shortcut = shortcut;
|
||||
_shortcut = [shortcut retain];
|
||||
_handler = [handler copy];
|
||||
|
||||
if (!InstallHotkeyWithShortcut(shortcut, &_carbonHotKeyID, &_carbonHotKey))
|
||||
self = nil;
|
||||
InstallHotkeyWithShortcut(shortcut, &_carbonHotKeyID, &_carbonHotKey);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -73,6 +72,9 @@ void UninstallEventHandler();
|
||||
- (void)dealloc
|
||||
{
|
||||
[self uninstallExisitingHotKey];
|
||||
[_shortcut release];
|
||||
[_handler release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void)uninstallExisitingHotKey
|
||||
@@ -92,7 +94,7 @@ NSMutableDictionary *MASRegisteredHotKeys()
|
||||
static NSMutableDictionary *shared = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
shared = [NSMutableDictionary dictionary];
|
||||
shared = [[NSMutableDictionary alloc] init];
|
||||
});
|
||||
return shared;
|
||||
}
|
||||
@@ -101,20 +103,19 @@ NSMutableDictionary *MASRegisteredHotKeys()
|
||||
|
||||
FourCharCode const kMASShortcutSignature = 'MASS';
|
||||
|
||||
BOOL InstallHotkeyWithShortcut(MASShortcut *shortcut, UInt32 *outCarbonHotKeyID, EventHotKeyRef *outCarbonHotKey)
|
||||
void InstallHotkeyWithShortcut(MASShortcut *shortcut, UInt32 *outCarbonHotKeyID, EventHotKeyRef *outCarbonHotKey)
|
||||
{
|
||||
if ((shortcut == nil) || !InstallCommonEventHandler()) return NO;
|
||||
if ((shortcut == nil) || !InstallCommonEventHandler()) return;
|
||||
|
||||
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;
|
||||
carbonHotKey = NULL;
|
||||
}
|
||||
|
||||
if (outCarbonHotKeyID) *outCarbonHotKeyID = hotKeyID.id;
|
||||
if (outCarbonHotKey) *outCarbonHotKey = carbonHotKey;
|
||||
return YES;
|
||||
}
|
||||
|
||||
static OSStatus CarbonCallback(EventHandlerCallRef inHandlerCallRef, EventRef inEvent, void *inUserData)
|
||||
|
||||
@@ -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
|
||||
@@ -1,104 +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 = shortcut.data;
|
||||
if (shortcutData)
|
||||
[[NSUserDefaults standardUserDefaults] setObject:shortcutData forKey:userDefaultsKey];
|
||||
else
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:userDefaultsKey];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation MASShortcutUserDefaultsHotKey {
|
||||
NSString *_observableKeyPath;
|
||||
}
|
||||
|
||||
@synthesize monitor = _monitor;
|
||||
@synthesize handler = _handler;
|
||||
@synthesize userDefaultsKey = _userDefaultsKey;
|
||||
|
||||
#pragma mark -
|
||||
|
||||
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 = [MASShortcut shortcutWithData:data];
|
||||
if (shortcut == nil) return;
|
||||
self.monitor = [MASShortcut addGlobalHotkeyMonitorWithShortcut:shortcut handler:self.handler];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,7 @@
|
||||
//
|
||||
// Prefix header for all source files of the 'MASShortcut' target in the 'MASShortcut' project
|
||||
//
|
||||
|
||||
#ifdef __OBJC__
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#endif
|
||||
+4
-6
@@ -30,7 +30,10 @@ enum {
|
||||
kMASShortcutGlyphSoutheastArrow = 0x2198,
|
||||
} MASShortcutGlyph;
|
||||
|
||||
@interface MASShortcut : NSObject <NSCoding>
|
||||
@interface MASShortcut : NSObject <NSCoding> {
|
||||
NSUInteger _keyCode; // NSNotFound if empty
|
||||
NSUInteger _modifierFlags; // 0 if empty
|
||||
}
|
||||
|
||||
@property (nonatomic) NSUInteger keyCode;
|
||||
@property (nonatomic) NSUInteger modifierFlags;
|
||||
@@ -51,9 +54,4 @@ enum {
|
||||
|
||||
- (BOOL)isTakenError:(NSError **)error;
|
||||
|
||||
// The following API enable hotkeys with the Option key as the only modifier
|
||||
// For example, Option-G will not generate © and Option-R will not paste ®
|
||||
+ (void)setAllowsAnyHotkeyWithOptionModifier:(BOOL)allow;
|
||||
+ (BOOL)allowsAnyHotkeyWithOptionModifier;
|
||||
|
||||
@end
|
||||
|
||||
+25
-65
@@ -1,12 +1,9 @@
|
||||
#import "MASShortcut.h"
|
||||
|
||||
NSString *const MASShortcutKeyCode = @"KeyCode";
|
||||
NSString *const MASShortcutModifierFlags = @"ModifierFlags";
|
||||
NSString *const kMASShortcutKeyCode = @"KeyCode";
|
||||
NSString *const kMASShortcutModifierFlags = @"ModifierFlags";
|
||||
|
||||
@implementation MASShortcut {
|
||||
NSUInteger _keyCode; // NSNotFound if empty
|
||||
NSUInteger _modifierFlags; // 0 if empty
|
||||
}
|
||||
@implementation MASShortcut
|
||||
|
||||
@synthesize modifierFlags = _modifierFlags;
|
||||
@synthesize keyCode = _keyCode;
|
||||
@@ -15,17 +12,17 @@ NSString *const MASShortcutModifierFlags = @"ModifierFlags";
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)coder
|
||||
{
|
||||
[coder encodeInteger:(self.keyCode != NSNotFound ? (NSInteger)self.keyCode : - 1) forKey:MASShortcutKeyCode];
|
||||
[coder encodeInteger:(NSInteger)self.modifierFlags forKey:MASShortcutModifierFlags];
|
||||
[coder encodeInteger:(self.keyCode != NSNotFound ? (NSInteger)self.keyCode : - 1) forKey:kMASShortcutKeyCode];
|
||||
[coder encodeInteger:(NSInteger)self.modifierFlags forKey:kMASShortcutModifierFlags];
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)decoder
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
NSInteger code = [decoder decodeIntegerForKey:MASShortcutKeyCode];
|
||||
NSInteger code = [decoder decodeIntegerForKey:kMASShortcutKeyCode];
|
||||
self.keyCode = (code < 0 ? NSNotFound : (NSUInteger)code);
|
||||
self.modifierFlags = [decoder decodeIntegerForKey:MASShortcutModifierFlags];
|
||||
self.modifierFlags = [decoder decodeIntegerForKey:kMASShortcutModifierFlags];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -42,12 +39,12 @@ NSString *const MASShortcutModifierFlags = @"ModifierFlags";
|
||||
|
||||
+ (MASShortcut *)shortcutWithKeyCode:(NSUInteger)code modifierFlags:(NSUInteger)flags
|
||||
{
|
||||
return [[self alloc] initWithKeyCode:code modifierFlags:flags];
|
||||
return [[[self alloc] initWithKeyCode:code modifierFlags:flags] autorelease];
|
||||
}
|
||||
|
||||
+ (MASShortcut *)shortcutWithEvent:(NSEvent *)event
|
||||
{
|
||||
return [[self alloc] initWithKeyCode:event.keyCode modifierFlags:event.modifierFlags];
|
||||
return [[[self alloc] initWithKeyCode:event.keyCode modifierFlags:event.modifierFlags] autorelease];
|
||||
}
|
||||
|
||||
+ (MASShortcut *)shortcutWithData:(NSData *)data
|
||||
@@ -105,9 +102,6 @@ NSString *const MASShortcutModifierFlags = @"ModifierFlags";
|
||||
case kVK_F14: return MASShortcutChar(0xF711);
|
||||
case kVK_F15: return MASShortcutChar(0xF712);
|
||||
case kVK_F16: return MASShortcutChar(0xF713);
|
||||
case kVK_F17: return MASShortcutChar(0xF714);
|
||||
case kVK_F18: return MASShortcutChar(0xF715);
|
||||
case kVK_F19: return MASShortcutChar(0xF716);
|
||||
case kVK_Space: return MASShortcutChar(0x20);
|
||||
default: return @"";
|
||||
}
|
||||
@@ -136,9 +130,6 @@ NSString *const MASShortcutModifierFlags = @"ModifierFlags";
|
||||
case kVK_F14: return @"F14";
|
||||
case kVK_F15: return @"F15";
|
||||
case kVK_F16: return @"F16";
|
||||
case kVK_F17: return @"F17";
|
||||
case kVK_F18: return @"F18";
|
||||
case kVK_F19: return @"F19";
|
||||
case kVK_Space: return NSLocalizedString(@"Space", @"Shortcut glyph name for SPACE key");
|
||||
case kVK_Escape: return MASShortcutChar(kMASShortcutGlyphEscape);
|
||||
case kVK_Delete: return MASShortcutChar(kMASShortcutGlyphDeleteLeft);
|
||||
@@ -201,7 +192,7 @@ NSString *const MASShortcutModifierFlags = @"ModifierFlags";
|
||||
if (keystroke.length) {
|
||||
static NSMutableCharacterSet *validChars = nil;
|
||||
if (validChars == nil) {
|
||||
validChars = [[NSMutableCharacterSet alloc] init];
|
||||
validChars = [[[NSMutableCharacterSet alloc] init] autorelease];
|
||||
[validChars formUnionWithCharacterSet:[NSCharacterSet alphanumericCharacterSet]];
|
||||
[validChars formUnionWithCharacterSet:[NSCharacterSet punctuationCharacterSet]];
|
||||
[validChars formUnionWithCharacterSet:[NSCharacterSet symbolCharacterSet]];
|
||||
@@ -238,50 +229,19 @@ NSString *const MASShortcutModifierFlags = @"ModifierFlags";
|
||||
return (self.modifierFlags == NSCommandKeyMask) && ([codeString isEqualToString:@"W"] || [codeString isEqualToString:@"Q"]);
|
||||
}
|
||||
|
||||
BOOL MASShortcutAllowsAnyHotkeyWithOptionModifier = NO;
|
||||
|
||||
+ (void)setAllowsAnyHotkeyWithOptionModifier:(BOOL)allow
|
||||
{
|
||||
MASShortcutAllowsAnyHotkeyWithOptionModifier = allow;
|
||||
}
|
||||
|
||||
+ (BOOL)allowsAnyHotkeyWithOptionModifier
|
||||
{
|
||||
return MASShortcutAllowsAnyHotkeyWithOptionModifier;
|
||||
}
|
||||
|
||||
- (BOOL)isValid
|
||||
{
|
||||
// Allow any function key with any combination of modifiers
|
||||
BOOL includesFunctionKey = ((_keyCode == kVK_F1) || (_keyCode == kVK_F2) || (_keyCode == kVK_F3) || (_keyCode == kVK_F4) ||
|
||||
(_keyCode == kVK_F5) || (_keyCode == kVK_F6) || (_keyCode == kVK_F7) || (_keyCode == kVK_F8) ||
|
||||
(_keyCode == kVK_F9) || (_keyCode == kVK_F10) || (_keyCode == kVK_F11) || (_keyCode == kVK_F12) ||
|
||||
(_keyCode == kVK_F13) || (_keyCode == kVK_F14) || (_keyCode == kVK_F15) || (_keyCode == kVK_F16) ||
|
||||
(_keyCode == kVK_F17) || (_keyCode == kVK_F18) || (_keyCode == kVK_F19) || (_keyCode == kVK_F20));
|
||||
if (includesFunctionKey) return YES;
|
||||
|
||||
// Do not allow any other key without modifiers
|
||||
BOOL hasModifierFlags = (_modifierFlags > 0);
|
||||
if (!hasModifierFlags) return NO;
|
||||
|
||||
// Allow any hotkey containing Control or Command modifier
|
||||
BOOL includesCommand = ((_modifierFlags & NSCommandKeyMask) > 0);
|
||||
BOOL includesControl = ((_modifierFlags & NSControlKeyMask) > 0);
|
||||
if (includesCommand || includesControl) return YES;
|
||||
|
||||
// Allow Option key only in selected cases
|
||||
BOOL includesOption = ((_modifierFlags & NSAlternateKeyMask) > 0);
|
||||
if (includesOption) {
|
||||
|
||||
// Always allow Option-Space and Option-Escape because they do not have any bind system commands
|
||||
if ((_keyCode == kVK_Space) || (_keyCode == kVK_Escape)) return YES;
|
||||
|
||||
// Allow Option modifier with any key even if it will break the system binding
|
||||
if ([[self class] allowsAnyHotkeyWithOptionModifier]) return YES;
|
||||
}
|
||||
|
||||
// The hotkey does not have any modifiers or violates system bindings
|
||||
return NO;
|
||||
BOOL hasFlags = (_modifierFlags > 0);
|
||||
BOOL hasCommand = ((_modifierFlags & NSCommandKeyMask) > 0);
|
||||
BOOL hasControl = ((_modifierFlags & NSControlKeyMask) > 0);
|
||||
BOOL hasOption = ((_modifierFlags & NSAlternateKeyMask) > 0);
|
||||
BOOL isFunction = ((_keyCode == kVK_F1) || (_keyCode == kVK_F2) || (_keyCode == kVK_F3) || (_keyCode == kVK_F4) ||
|
||||
(_keyCode == kVK_F5) || (_keyCode == kVK_F6) || (_keyCode == kVK_F7) || (_keyCode == kVK_F8) ||
|
||||
(_keyCode == kVK_F9) || (_keyCode == kVK_F10) || (_keyCode == kVK_F11) || (_keyCode == kVK_F12) ||
|
||||
(_keyCode == kVK_F13) || (_keyCode == kVK_F14) || (_keyCode == kVK_F15) || (_keyCode == kVK_F16) ||
|
||||
(_keyCode == kVK_F17) || (_keyCode == kVK_F18) || (_keyCode == kVK_F19) || (_keyCode == kVK_F20));
|
||||
BOOL isSpecial = ((_keyCode == kVK_Space) || (_keyCode == kVK_Escape) || (_keyCode == kVK_Return));
|
||||
return ((hasFlags && (hasCommand || hasControl || (hasOption && isSpecial))) || isFunction);
|
||||
}
|
||||
|
||||
- (BOOL)isKeyEquivalent:(NSString *)keyEquivalent flags:(NSUInteger)flags takenInMenu:(NSMenu *)menu error:(NSError **)outError
|
||||
@@ -300,7 +260,7 @@ BOOL MASShortcutAllowsAnyHotkeyWithOptionModifier = NO;
|
||||
|
||||
if (equalFlags && equalHotkeyLowercase) {
|
||||
if (outError) {
|
||||
NSString *format = NSLocalizedString(@"This shortcut cannot be used because it is already used by the menu item ‘%@’.",
|
||||
NSString *format = NSLocalizedString(@"This shortcut cannot be used used because it is already used by the menu item ‘%@’.",
|
||||
@"Message for alert when shortcut is already used");
|
||||
NSDictionary *info = [NSDictionary dictionaryWithObject:[NSString stringWithFormat:format, menuItem.title]
|
||||
forKey:NSLocalizedDescriptionKey];
|
||||
@@ -323,11 +283,11 @@ BOOL MASShortcutAllowsAnyHotkeyWithOptionModifier = NO;
|
||||
CFNumberRef code = CFDictionaryGetValue(hotKeyInfo, kHISymbolicHotKeyCode);
|
||||
CFNumberRef flags = CFDictionaryGetValue(hotKeyInfo, kHISymbolicHotKeyModifiers);
|
||||
|
||||
if (([(__bridge NSNumber *)code unsignedIntegerValue] == self.keyCode) &&
|
||||
([(__bridge NSNumber *)flags unsignedIntegerValue] == self.carbonFlags)) {
|
||||
if (([(NSNumber *)code unsignedIntegerValue] == self.keyCode) &&
|
||||
([(NSNumber *)flags unsignedIntegerValue] == self.carbonFlags)) {
|
||||
|
||||
if (outError) {
|
||||
NSString *description = NSLocalizedString(@"This combination cannot be used because it is already used by a system-wide "
|
||||
NSString *description = NSLocalizedString(@"This combination cannot be used used because it is already used by a system-wide "
|
||||
@"keyboard shortcut.\nIf you really want to use this key combination, most shortcuts "
|
||||
@"can be changed in the Keyboard & Mouse panel in System Preferences.",
|
||||
@"Message for alert when shortcut is already used by the system");
|
||||
|
||||
@@ -0,0 +1,268 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 46;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
47C3BC1E160DF126006E3107 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 47C3BC1D160DF126006E3107 /* Cocoa.framework */; };
|
||||
47C3BC3A160DF182006E3107 /* MASShortcut.m in Sources */ = {isa = PBXBuildFile; fileRef = 47C3BC36160DF182006E3107 /* MASShortcut.m */; };
|
||||
47C3BC3B160DF182006E3107 /* MASShortcut+Monitoring.m in Sources */ = {isa = PBXBuildFile; fileRef = 47C3BC37160DF182006E3107 /* MASShortcut+Monitoring.m */; };
|
||||
47C3BC3F160DF215006E3107 /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 47C3BC3E160DF215006E3107 /* Carbon.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
47C3BC1A160DF126006E3107 /* MASShortcut.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = MASShortcut.dylib; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
47C3BC1D160DF126006E3107 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
|
||||
47C3BC20160DF126006E3107 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; };
|
||||
47C3BC21160DF126006E3107 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; };
|
||||
47C3BC22160DF126006E3107 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
|
||||
47C3BC34160DF176006E3107 /* MASShortcut-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MASShortcut-Prefix.pch"; sourceTree = SOURCE_ROOT; };
|
||||
47C3BC36160DF182006E3107 /* MASShortcut.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MASShortcut.m; sourceTree = SOURCE_ROOT; };
|
||||
47C3BC37160DF182006E3107 /* MASShortcut+Monitoring.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MASShortcut+Monitoring.m"; sourceTree = SOURCE_ROOT; };
|
||||
47C3BC38160DF182006E3107 /* MASShortcut.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MASShortcut.h; sourceTree = SOURCE_ROOT; };
|
||||
47C3BC39160DF182006E3107 /* MASShortcut+Monitoring.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MASShortcut+Monitoring.h"; sourceTree = SOURCE_ROOT; };
|
||||
47C3BC3E160DF215006E3107 /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
47C3BC17160DF126006E3107 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
47C3BC3F160DF215006E3107 /* Carbon.framework in Frameworks */,
|
||||
47C3BC1E160DF126006E3107 /* Cocoa.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
47C3BC0F160DF126006E3107 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
47C3BC3E160DF215006E3107 /* Carbon.framework */,
|
||||
47C3BC23160DF126006E3107 /* MASShortcut */,
|
||||
47C3BC1C160DF126006E3107 /* Frameworks */,
|
||||
47C3BC1B160DF126006E3107 /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
47C3BC1B160DF126006E3107 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
47C3BC1A160DF126006E3107 /* MASShortcut.dylib */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
47C3BC1C160DF126006E3107 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
47C3BC1D160DF126006E3107 /* Cocoa.framework */,
|
||||
47C3BC1F160DF126006E3107 /* Other Frameworks */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
47C3BC1F160DF126006E3107 /* Other Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
47C3BC20160DF126006E3107 /* AppKit.framework */,
|
||||
47C3BC21160DF126006E3107 /* CoreData.framework */,
|
||||
47C3BC22160DF126006E3107 /* Foundation.framework */,
|
||||
);
|
||||
name = "Other Frameworks";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
47C3BC23160DF126006E3107 /* MASShortcut */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
47C3BC36160DF182006E3107 /* MASShortcut.m */,
|
||||
47C3BC37160DF182006E3107 /* MASShortcut+Monitoring.m */,
|
||||
47C3BC38160DF182006E3107 /* MASShortcut.h */,
|
||||
47C3BC39160DF182006E3107 /* MASShortcut+Monitoring.h */,
|
||||
47C3BC24160DF126006E3107 /* Supporting Files */,
|
||||
);
|
||||
path = MASShortcut;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
47C3BC24160DF126006E3107 /* Supporting Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
47C3BC34160DF176006E3107 /* MASShortcut-Prefix.pch */,
|
||||
);
|
||||
name = "Supporting Files";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXHeadersBuildPhase section */
|
||||
47C3BC18160DF126006E3107 /* Headers */ = {
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXHeadersBuildPhase section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
47C3BC19160DF126006E3107 /* MASShortcut */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 47C3BC2B160DF126006E3107 /* Build configuration list for PBXNativeTarget "MASShortcut" */;
|
||||
buildPhases = (
|
||||
47C3BC16160DF126006E3107 /* Sources */,
|
||||
47C3BC17160DF126006E3107 /* Frameworks */,
|
||||
47C3BC18160DF126006E3107 /* Headers */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = MASShortcut;
|
||||
productName = MASShortcut;
|
||||
productReference = 47C3BC1A160DF126006E3107 /* MASShortcut.dylib */;
|
||||
productType = "com.apple.product-type.library.dynamic";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
47C3BC11160DF126006E3107 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 0450;
|
||||
ORGANIZATIONNAME = "Duane Wandless";
|
||||
};
|
||||
buildConfigurationList = 47C3BC14160DF126006E3107 /* Build configuration list for PBXProject "MASShortcut" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
developmentRegion = English;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
);
|
||||
mainGroup = 47C3BC0F160DF126006E3107;
|
||||
productRefGroup = 47C3BC1B160DF126006E3107 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
47C3BC19160DF126006E3107 /* MASShortcut */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
47C3BC16160DF126006E3107 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
47C3BC3A160DF182006E3107 /* MASShortcut.m in Sources */,
|
||||
47C3BC3B160DF182006E3107 /* MASShortcut+Monitoring.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
47C3BC29160DF126006E3107 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ARCHS = "$(ARCHS_STANDARD_32_BIT)";
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_ENABLE_OBJC_EXCEPTIONS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.8;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = macosx;
|
||||
VALID_ARCHS = i386;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
47C3BC2A160DF126006E3107 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ARCHS = "$(ARCHS_STANDARD_32_BIT)";
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_ENABLE_OBJC_EXCEPTIONS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.8;
|
||||
SDKROOT = macosx;
|
||||
VALID_ARCHS = i386;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
47C3BC2C160DF126006E3107 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "MASShortcut-Prefix.pch";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
47C3BC2D160DF126006E3107 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "MASShortcut-Prefix.pch";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
47C3BC14160DF126006E3107 /* Build configuration list for PBXProject "MASShortcut" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
47C3BC29160DF126006E3107 /* Debug */,
|
||||
47C3BC2A160DF126006E3107 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
47C3BC2B160DF126006E3107 /* Build configuration list for PBXNativeTarget "MASShortcut" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
47C3BC2C160DF126006E3107 /* Debug */,
|
||||
47C3BC2D160DF126006E3107 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 47C3BC11160DF126006E3107 /* Project object */;
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
#import "MASShortcutView.h"
|
||||
|
||||
@interface MASShortcutView (UserDefaults)
|
||||
|
||||
@property (nonatomic, copy) NSString *associatedUserDefaultsKey;
|
||||
|
||||
@end
|
||||
@@ -1,130 +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;
|
||||
}
|
||||
|
||||
@synthesize userDefaultsKey = _userDefaultsKey;
|
||||
@synthesize shortcutView = _shortcutView;
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (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 *defaults = [NSUserDefaults standardUserDefaults];
|
||||
[defaults setObject:(shortcut.data ?: [NSKeyedArchiver archivedDataWithRootObject:nil]) forKey:_userDefaultsKey];
|
||||
[defaults synchronize];
|
||||
|
||||
_internalPreferenceChange = NO;
|
||||
}
|
||||
else {
|
||||
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,17 +0,0 @@
|
||||
@class MASShortcut;
|
||||
|
||||
typedef enum {
|
||||
MASShortcutViewAppearanceDefault = 0, // Height = 19 px
|
||||
MASShortcutViewAppearanceTexturedRect, // Height = 25 px
|
||||
MASShortcutViewAppearanceRounded // Height = 43 px
|
||||
} MASShortcutViewAppearance;
|
||||
|
||||
@interface MASShortcutView : NSView
|
||||
|
||||
@property (nonatomic, strong) MASShortcut *shortcutValue;
|
||||
@property (nonatomic, getter = isRecording) BOOL recording;
|
||||
@property (nonatomic, getter = isEnabled) BOOL enabled;
|
||||
@property (nonatomic, copy) void (^shortcutValueChange)(MASShortcutView *sender);
|
||||
@property (nonatomic) MASShortcutViewAppearance appearance;
|
||||
|
||||
@end
|
||||
@@ -1,429 +0,0 @@
|
||||
#import "MASShortcutView.h"
|
||||
#import "MASShortcut.h"
|
||||
|
||||
#define HINT_BUTTON_WIDTH 23.0
|
||||
#define BUTTON_FONT_SIZE 11.0
|
||||
#define SEGMENT_CHROME_WIDTH 6.0
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@interface MASShortcutView () // Private accessors
|
||||
|
||||
@property (nonatomic, getter = isHinting) BOOL hinting;
|
||||
@property (nonatomic, copy) NSString *shortcutPlaceholder;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation MASShortcutView {
|
||||
NSButtonCell *_shortcutCell;
|
||||
NSInteger _shortcutToolTipTag;
|
||||
NSInteger _hintToolTipTag;
|
||||
NSTrackingArea *_hintArea;
|
||||
}
|
||||
|
||||
@synthesize enabled = _enabled;
|
||||
@synthesize hinting = _hinting;
|
||||
@synthesize shortcutValue = _shortcutValue;
|
||||
@synthesize shortcutPlaceholder = _shortcutPlaceholder;
|
||||
@synthesize shortcutValueChange = _shortcutValueChange;
|
||||
@synthesize recording = _recording;
|
||||
@synthesize appearance = _appearance;
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (id)initWithFrame:(CGRect)frameRect
|
||||
{
|
||||
self = [super initWithFrame:frameRect];
|
||||
if (self) {
|
||||
_shortcutCell = [[NSButtonCell alloc] init];
|
||||
_shortcutCell.buttonType = NSPushOnPushOffButton;
|
||||
_shortcutCell.font = [[NSFontManager sharedFontManager] convertFont:_shortcutCell.font toSize:BUTTON_FONT_SIZE];
|
||||
_enabled = YES;
|
||||
[self resetShortcutCellStyle];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[self activateEventMonitoring:NO];
|
||||
[self activateResignObserver:NO];
|
||||
}
|
||||
|
||||
#pragma mark - Public accessors
|
||||
|
||||
- (void)setEnabled:(BOOL)flag
|
||||
{
|
||||
if (_enabled != flag) {
|
||||
_enabled = flag;
|
||||
[self updateTrackingAreas];
|
||||
self.recording = NO;
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setAppearance:(MASShortcutViewAppearance)appearance
|
||||
{
|
||||
if (_appearance != appearance) {
|
||||
_appearance = appearance;
|
||||
[self resetShortcutCellStyle];
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)resetShortcutCellStyle
|
||||
{
|
||||
switch (_appearance) {
|
||||
case MASShortcutViewAppearanceDefault: {
|
||||
_shortcutCell.bezelStyle = NSRoundRectBezelStyle;
|
||||
break;
|
||||
}
|
||||
case MASShortcutViewAppearanceTexturedRect: {
|
||||
_shortcutCell.bezelStyle = NSTexturedRoundedBezelStyle;
|
||||
break;
|
||||
}
|
||||
case MASShortcutViewAppearanceRounded: {
|
||||
_shortcutCell.bezelStyle = NSRoundedBezelStyle;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setRecording:(BOOL)flag
|
||||
{
|
||||
// Only one recorder can be active at the moment
|
||||
static MASShortcutView *currentRecorder = nil;
|
||||
if (flag && (currentRecorder != self)) {
|
||||
currentRecorder.recording = NO;
|
||||
currentRecorder = flag ? self : nil;
|
||||
}
|
||||
|
||||
// Only enabled view supports recording
|
||||
if (flag && !self.enabled) return;
|
||||
|
||||
if (_recording != flag) {
|
||||
_recording = flag;
|
||||
self.shortcutPlaceholder = nil;
|
||||
[self resetToolTips];
|
||||
[self activateEventMonitoring:_recording];
|
||||
[self activateResignObserver:_recording];
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setShortcutValue:(MASShortcut *)shortcutValue
|
||||
{
|
||||
_shortcutValue = shortcutValue;
|
||||
[self resetToolTips];
|
||||
[self setNeedsDisplay:YES];
|
||||
|
||||
if (self.shortcutValueChange) {
|
||||
self.shortcutValueChange(self);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setShortcutPlaceholder:(NSString *)shortcutPlaceholder
|
||||
{
|
||||
_shortcutPlaceholder = shortcutPlaceholder.copy;
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
#pragma mark - Drawing
|
||||
|
||||
- (BOOL)isFlipped
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)drawInRect:(CGRect)frame withTitle:(NSString *)title alignment:(NSTextAlignment)alignment state:(NSInteger)state
|
||||
{
|
||||
_shortcutCell.title = title;
|
||||
_shortcutCell.alignment = alignment;
|
||||
_shortcutCell.state = state;
|
||||
_shortcutCell.enabled = self.enabled;
|
||||
|
||||
switch (_appearance) {
|
||||
case MASShortcutViewAppearanceDefault: {
|
||||
[_shortcutCell drawWithFrame:frame inView:self];
|
||||
break;
|
||||
}
|
||||
case MASShortcutViewAppearanceTexturedRect: {
|
||||
[_shortcutCell drawWithFrame:CGRectOffset(frame, 0.0, 1.0) inView:self];
|
||||
break;
|
||||
}
|
||||
case MASShortcutViewAppearanceRounded: {
|
||||
[_shortcutCell drawWithFrame:CGRectOffset(frame, 0.0, 1.0) inView:self];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)dirtyRect
|
||||
{
|
||||
if (self.shortcutValue) {
|
||||
[self drawInRect:self.bounds withTitle:MASShortcutChar(self.recording ? kMASShortcutGlyphEscape : kMASShortcutGlyphDeleteLeft)
|
||||
alignment:NSRightTextAlignment state:NSOffState];
|
||||
|
||||
CGRect shortcutRect;
|
||||
[self getShortcutRect:&shortcutRect hintRect:NULL];
|
||||
NSString *title = (self.recording
|
||||
? (_hinting
|
||||
? NSLocalizedString(@"Use Old Shortcut", @"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")))
|
||||
: _shortcutValue ? _shortcutValue.description : @"");
|
||||
[self drawInRect:shortcutRect withTitle:title alignment:NSCenterTextAlignment state:self.isRecording ? NSOnState : NSOffState];
|
||||
}
|
||||
else {
|
||||
if (self.recording)
|
||||
{
|
||||
[self drawInRect:self.bounds withTitle:MASShortcutChar(kMASShortcutGlyphEscape) alignment:NSRightTextAlignment state:NSOffState];
|
||||
|
||||
CGRect shortcutRect;
|
||||
[self getShortcutRect:&shortcutRect hintRect:NULL];
|
||||
NSString *title = (_hinting
|
||||
? NSLocalizedString(@"Cancel", @"Cancel action button in recording state")
|
||||
: (self.shortcutPlaceholder.length > 0
|
||||
? self.shortcutPlaceholder
|
||||
: 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(@"Record Shortcut", @"Empty shortcut button in normal state")
|
||||
alignment:NSCenterTextAlignment state:NSOffState];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Mouse handling
|
||||
|
||||
- (void)getShortcutRect:(CGRect *)shortcutRectRef hintRect:(CGRect *)hintRectRef
|
||||
{
|
||||
CGRect shortcutRect, hintRect;
|
||||
CGFloat hintButtonWidth = HINT_BUTTON_WIDTH;
|
||||
switch (self.appearance) {
|
||||
case MASShortcutViewAppearanceTexturedRect: hintButtonWidth += 2.0; break;
|
||||
case MASShortcutViewAppearanceRounded: hintButtonWidth += 3.0; break;
|
||||
default: break;
|
||||
}
|
||||
CGRectDivide(self.bounds, &hintRect, &shortcutRect, hintButtonWidth, CGRectMaxXEdge);
|
||||
if (shortcutRectRef) *shortcutRectRef = shortcutRect;
|
||||
if (hintRectRef) *hintRectRef = hintRect;
|
||||
}
|
||||
|
||||
- (BOOL)locationInShortcutRect:(CGPoint)location
|
||||
{
|
||||
CGRect shortcutRect;
|
||||
[self getShortcutRect:&shortcutRect hintRect:NULL];
|
||||
return CGRectContainsPoint(shortcutRect, [self convertPoint:location fromView:nil]);
|
||||
}
|
||||
|
||||
- (BOOL)locationInHintRect:(CGPoint)location
|
||||
{
|
||||
CGRect hintRect;
|
||||
[self getShortcutRect:NULL hintRect:&hintRect];
|
||||
return CGRectContainsPoint(hintRect, [self convertPoint:location fromView:nil]);
|
||||
}
|
||||
|
||||
- (void)mouseDown:(NSEvent *)event
|
||||
{
|
||||
if (self.enabled) {
|
||||
if (self.shortcutValue) {
|
||||
if (self.recording) {
|
||||
if ([self locationInHintRect:event.locationInWindow]) {
|
||||
self.recording = NO;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ([self locationInShortcutRect:event.locationInWindow]) {
|
||||
self.recording = YES;
|
||||
}
|
||||
else {
|
||||
self.shortcutValue = nil;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (self.recording) {
|
||||
if ([self locationInHintRect:event.locationInWindow]) {
|
||||
self.recording = NO;
|
||||
}
|
||||
}
|
||||
else {
|
||||
self.recording = YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
[super mouseDown:event];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Handling mouse over
|
||||
|
||||
- (void)updateTrackingAreas
|
||||
{
|
||||
[super updateTrackingAreas];
|
||||
|
||||
if (_hintArea) {
|
||||
[self removeTrackingArea:_hintArea];
|
||||
_hintArea = nil;
|
||||
}
|
||||
|
||||
// Forbid hinting if view is disabled
|
||||
if (!self.enabled) return;
|
||||
|
||||
CGRect hintRect;
|
||||
[self getShortcutRect:NULL hintRect:&hintRect];
|
||||
NSTrackingAreaOptions options = (NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingAssumeInside);
|
||||
_hintArea = [[NSTrackingArea alloc] initWithRect:hintRect options:options owner:self userInfo:nil];
|
||||
[self addTrackingArea:_hintArea];
|
||||
}
|
||||
|
||||
- (void)setHinting:(BOOL)flag
|
||||
{
|
||||
if (_hinting != flag) {
|
||||
_hinting = flag;
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)mouseEntered:(NSEvent *)event
|
||||
{
|
||||
self.hinting = YES;
|
||||
}
|
||||
|
||||
- (void)mouseExited:(NSEvent *)event
|
||||
{
|
||||
self.hinting = NO;
|
||||
}
|
||||
|
||||
void *kUserDataShortcut = &kUserDataShortcut;
|
||||
void *kUserDataHint = &kUserDataHint;
|
||||
|
||||
- (void)resetToolTips
|
||||
{
|
||||
if (_shortcutToolTipTag) {
|
||||
[self removeToolTip:_shortcutToolTipTag], _shortcutToolTipTag = 0;
|
||||
}
|
||||
if (_hintToolTipTag) {
|
||||
[self removeToolTip:_hintToolTipTag], _hintToolTipTag = 0;
|
||||
}
|
||||
|
||||
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];
|
||||
_hintToolTipTag = [self addToolTipRect:hintRect owner:self userData:kUserDataHint];
|
||||
}
|
||||
|
||||
- (NSString *)view:(NSView *)view stringForToolTip:(NSToolTipTag)tag point:(CGPoint)point userData:(void *)data
|
||||
{
|
||||
if (data == kUserDataShortcut) {
|
||||
return NSLocalizedString(@"Click to record new shortcut", @"Tooltip for non-empty shortcut button");
|
||||
}
|
||||
else if (data == kUserDataHint) {
|
||||
return NSLocalizedString(@"Delete shortcut", @"Tooltip for hint button near the non-empty shortcut");
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
#pragma mark - Event monitoring
|
||||
|
||||
- (void)activateEventMonitoring:(BOOL)shouldActivate
|
||||
{
|
||||
static BOOL isActive = NO;
|
||||
if (isActive == shouldActivate) return;
|
||||
isActive = shouldActivate;
|
||||
|
||||
static id eventMonitor = nil;
|
||||
if (shouldActivate) {
|
||||
__weak MASShortcutView *weakSelf = self;
|
||||
NSEventMask eventMask = (NSKeyDownMask | NSFlagsChangedMask);
|
||||
eventMonitor = [NSEvent addLocalMonitorForEventsMatchingMask:eventMask handler:^(NSEvent *event) {
|
||||
|
||||
MASShortcut *shortcut = [MASShortcut shortcutWithEvent:event];
|
||||
if ((shortcut.keyCode == kVK_Delete) || (shortcut.keyCode == kVK_ForwardDelete)) {
|
||||
// Delete shortcut
|
||||
weakSelf.shortcutValue = nil;
|
||||
weakSelf.recording = NO;
|
||||
event = nil;
|
||||
}
|
||||
else if (shortcut.keyCode == kVK_Escape) {
|
||||
// Cancel recording
|
||||
weakSelf.recording = NO;
|
||||
event = nil;
|
||||
}
|
||||
else if (shortcut.shouldBypass) {
|
||||
// Command + W, Command + Q, ESC should deactivate recorder
|
||||
weakSelf.recording = NO;
|
||||
}
|
||||
else {
|
||||
// Verify possible shortcut
|
||||
if (shortcut.keyCodeString.length > 0) {
|
||||
if (shortcut.valid) {
|
||||
// Verify that shortcut is not used
|
||||
NSError *error = nil;
|
||||
if ([shortcut isTakenError:&error]) {
|
||||
// Prevent cancel of recording when Alert window is key
|
||||
[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);
|
||||
weakSelf.shortcutPlaceholder = nil;
|
||||
[weakSelf activateResignObserver:YES];
|
||||
[weakSelf activateEventMonitoring:YES];
|
||||
}
|
||||
else {
|
||||
weakSelf.shortcutValue = shortcut;
|
||||
weakSelf.recording = NO;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Key press with or without SHIFT is not valid input
|
||||
NSBeep();
|
||||
}
|
||||
}
|
||||
else {
|
||||
// User is playing with modifier keys
|
||||
weakSelf.shortcutPlaceholder = shortcut.modifierFlagsString;
|
||||
}
|
||||
event = nil;
|
||||
}
|
||||
return event;
|
||||
}];
|
||||
}
|
||||
else {
|
||||
[NSEvent removeMonitor:eventMonitor];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)activateResignObserver:(BOOL)shouldActivate
|
||||
{
|
||||
static BOOL isActive = NO;
|
||||
if (isActive == shouldActivate) return;
|
||||
isActive = shouldActivate;
|
||||
|
||||
static id observer = nil;
|
||||
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
|
||||
if (shouldActivate) {
|
||||
__weak MASShortcutView *weakSelf = self;
|
||||
observer = [notificationCenter addObserverForName:NSWindowDidResignKeyNotification object:self.window
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notification) {
|
||||
weakSelf.recording = NO;
|
||||
}];
|
||||
}
|
||||
else {
|
||||
[notificationCenter removeObserver:observer];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -4,68 +4,19 @@ Some time ago Cocoa developers used a brilliant framework [ShortcutRecorder](htt
|
||||
|
||||
The project MASShortcut introduces modern API and user interface for recording, storing and using global keyboard shortcuts. All code is compatible with Xcode 4.3, Mac OS X 10.7 and the sandboxed environment.
|
||||
|
||||
# Usage
|
||||
# Usage for the branch ‘32-bit’
|
||||
|
||||
I hope, it is really easy:
|
||||
// Make a raw keyboard shortcut
|
||||
MASShortcut *shortcut = [MASShortcut shortcutWithKeyCode:kVK_F1 modifierFlags:NSCommandKeyMask];
|
||||
|
||||
```objective-c
|
||||
// Drop a custom view into XIB and set its class to MASShortcutView
|
||||
@property (nonatomic, weak) IBOutlet MASShortcutView *shortcutView;
|
||||
|
||||
// Think up a preference key to store a global shortcut between launches
|
||||
NSString *const kPreferenceGlobalShortcut = @"GlobalShortcut";
|
||||
|
||||
// Assign the preference key and the shortcut view will take care of persistence
|
||||
self.shortcutView.associatedUserDefaultsKey = kPreferenceGlobalShortcut;
|
||||
|
||||
// Execute your block of code automatically when user triggers a shortcut from preferences
|
||||
[MASShortcut registerGlobalShortcutWithUserDefaultsKey:kPreferenceGlobalShortcut handler:^{
|
||||
// Let me know if you find a better or more convenient API.
|
||||
}];
|
||||
```
|
||||
// Execute your block of code automatically when user triggers a shortcut
|
||||
[MASShortcut addGlobalHotkeyMonitorWithShortcut:shortcut handler:^{
|
||||
|
||||
// Let me know if you find a better or more convenient API.
|
||||
}];
|
||||
|
||||
To set an example, I made a demo project: [MASShortcutDemo](https://github.com/shpakovski/MASShortcutDemo). Enjoy!
|
||||
|
||||
#Notifications
|
||||
By registering for KVO notifications from `NSUserDefaultsController`, you can get a callback whenever a user changes the shortcut, allowing you to perform any UI updates, or other code handling tasks.
|
||||
|
||||
This is just as easy to implement:
|
||||
|
||||
```objective-c
|
||||
// Declare an ivar for key path in the user defaults controller
|
||||
NSString *_observableKeyPath;
|
||||
|
||||
// Make a global context reference
|
||||
void *kGlobalShortcutContext = &kGlobalShortcutContext;
|
||||
|
||||
// Implement when loading view
|
||||
_observableKeyPath = [@"values." stringByAppendingString:kPreferenceGlobalShortcut];
|
||||
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:_observableKeyPath
|
||||
options:NSKeyValueObservingOptionInitial
|
||||
context:kGlobalShortcutContext];
|
||||
|
||||
// Capture the KVO change and do something
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)obj
|
||||
change:(NSDictionary *)change context:(void *)ctx
|
||||
{
|
||||
if (ctx == kGlobalShortcutContext) {
|
||||
NSLog(@"Shortcut has changed");
|
||||
}
|
||||
else {
|
||||
[super observeValueForKeyPath:keyPath ofObject:obj change:change context:ctx];
|
||||
}
|
||||
}
|
||||
|
||||
// Do not forget to remove the observer
|
||||
[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self
|
||||
forKeyPath:_observableKeyPath
|
||||
context:kGlobalShortcutContext];
|
||||
```
|
||||
|
||||
# Non-ARC Version
|
||||
|
||||
If you like retain/release, please check out these forks: [heardrwt/MASShortcut](https://github.com/heardrwt/MASShortcut) and [chendo/MASShortcut](https://github.com/chendo/MASShortcut). However, the preferred way is to enable the `-fobjc-arc` in Xcode source options.
|
||||
|
||||
# Copyright
|
||||
|
||||
MASShortcut is licensed under the 2-clause BSD license.
|
||||
MASShortcut is licensed under the BSD license.
|
||||
Reference in New Issue
Block a user