113 lines
5.3 KiB
Mathematica
113 lines
5.3 KiB
Mathematica
![]() |
#import "MASShortcutValidator.h"
|
|||
|
#import "MASLocalization.h"
|
|||
|
|
|||
|
@implementation MASShortcutValidator
|
|||
|
|
|||
|
+ (instancetype) sharedValidator
|
|||
|
{
|
|||
|
static dispatch_once_t once;
|
|||
|
static MASShortcutValidator *sharedInstance;
|
|||
|
dispatch_once(&once, ^{
|
|||
|
sharedInstance = [[self alloc] init];
|
|||
|
});
|
|||
|
return sharedInstance;
|
|||
|
}
|
|||
|
|
|||
|
- (BOOL) isShortcutValid: (MASShortcut*) shortcut
|
|||
|
{
|
|||
|
NSUInteger keyCode = [shortcut keyCode];
|
|||
|
NSUInteger modifiers = [shortcut modifierFlags];
|
|||
|
|
|||
|
// 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 = (modifiers > 0);
|
|||
|
if (!hasModifierFlags) return NO;
|
|||
|
|
|||
|
// Allow any hotkey containing Control or Command modifier
|
|||
|
BOOL includesCommand = ((modifiers & NSCommandKeyMask) > 0);
|
|||
|
BOOL includesControl = ((modifiers & NSControlKeyMask) > 0);
|
|||
|
if (includesCommand || includesControl) return YES;
|
|||
|
|
|||
|
// Allow Option key only in selected cases
|
|||
|
BOOL includesOption = ((modifiers & 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 (_allowAnyShortcutWithOptionModifier) return YES;
|
|||
|
}
|
|||
|
|
|||
|
// The hotkey does not have any modifiers or violates system bindings
|
|||
|
return NO;
|
|||
|
}
|
|||
|
|
|||
|
- (BOOL) isShortcut: (MASShortcut*) shortcut alreadyTakenInMenu: (NSMenu*) menu explanation: (NSString**) explanation
|
|||
|
{
|
|||
|
NSString *keyEquivalent = [shortcut keyCodeStringForKeyEquivalent];
|
|||
|
NSUInteger flags = [shortcut modifierFlags];
|
|||
|
|
|||
|
for (NSMenuItem *menuItem in menu.itemArray) {
|
|||
|
if (menuItem.hasSubmenu && [self isShortcut:shortcut alreadyTakenInMenu:[menuItem submenu] explanation:explanation]) return YES;
|
|||
|
|
|||
|
BOOL equalFlags = (MASPickCocoaModifiers(menuItem.keyEquivalentModifierMask) == flags);
|
|||
|
BOOL equalHotkeyLowercase = [menuItem.keyEquivalent.lowercaseString isEqualToString:keyEquivalent];
|
|||
|
|
|||
|
// Check if the cases are different, we know ours is lower and that shift is included in our modifiers
|
|||
|
// If theirs is capitol, we need to add shift to their modifiers
|
|||
|
if (equalHotkeyLowercase && ![menuItem.keyEquivalent isEqualToString:keyEquivalent]) {
|
|||
|
equalFlags = (MASPickCocoaModifiers(menuItem.keyEquivalentModifierMask | NSShiftKeyMask) == flags);
|
|||
|
}
|
|||
|
|
|||
|
if (equalFlags && equalHotkeyLowercase) {
|
|||
|
if (explanation) {
|
|||
|
*explanation = MASLocalizedString(@"This shortcut cannot be used because it is already used by the menu item ‘%@’.",
|
|||
|
@"Message for alert when shortcut is already used");
|
|||
|
*explanation = [NSString stringWithFormat:*explanation, menuItem.title];
|
|||
|
}
|
|||
|
return YES;
|
|||
|
}
|
|||
|
}
|
|||
|
return NO;
|
|||
|
}
|
|||
|
|
|||
|
- (BOOL) isShortcutAlreadyTakenBySystem: (MASShortcut*) shortcut explanation: (NSString**) explanation
|
|||
|
{
|
|||
|
CFArrayRef globalHotKeys;
|
|||
|
if (CopySymbolicHotKeys(&globalHotKeys) == noErr) {
|
|||
|
|
|||
|
// Enumerate all global hotkeys and check if any of them matches current shortcut
|
|||
|
for (CFIndex i = 0, count = CFArrayGetCount(globalHotKeys); i < count; i++) {
|
|||
|
CFDictionaryRef hotKeyInfo = CFArrayGetValueAtIndex(globalHotKeys, i);
|
|||
|
CFNumberRef code = CFDictionaryGetValue(hotKeyInfo, kHISymbolicHotKeyCode);
|
|||
|
CFNumberRef flags = CFDictionaryGetValue(hotKeyInfo, kHISymbolicHotKeyModifiers);
|
|||
|
CFNumberRef enabled = CFDictionaryGetValue(hotKeyInfo, kHISymbolicHotKeyEnabled);
|
|||
|
|
|||
|
if (([(__bridge NSNumber *)code unsignedIntegerValue] == [shortcut keyCode]) &&
|
|||
|
([(__bridge NSNumber *)flags unsignedIntegerValue] == [shortcut carbonFlags]) &&
|
|||
|
([(__bridge NSNumber *)enabled boolValue])) {
|
|||
|
|
|||
|
if (explanation) {
|
|||
|
*explanation = MASLocalizedString(@"This combination cannot be 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");
|
|||
|
}
|
|||
|
return YES;
|
|||
|
}
|
|||
|
}
|
|||
|
CFRelease(globalHotKeys);
|
|||
|
}
|
|||
|
return [self isShortcut:shortcut alreadyTakenInMenu:[NSApp mainMenu] explanation:explanation];
|
|||
|
}
|
|||
|
|
|||
|
@end
|