You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

233 lines
8.4 KiB

//
// SRValidator.h
// ShortcutRecorder
//
// Copyright 2006-2012 Contributors. All rights reserved.
//
// License: BSD
//
// Contributors:
// David Dauer
// Jesper
// Jamie Kirkpatrick
// Andy Kim
// Silvio Rizzi
// Ilya Kulakov
#import "SRValidator.h"
#import "SRCommon.h"
#import "SRKeyCodeTransformer.h"
@implementation SRValidator
- (instancetype)initWithDelegate:(NSObject<SRValidatorDelegate> *)aDelegate;
{
self = [super init];
if (self)
{
_delegate = aDelegate;
}
return self;
}
- (instancetype)init
{
return [self initWithDelegate:nil];
}
#pragma mark Methods
- (BOOL)isKeyCode:(unsigned short)aKeyCode andFlagsTaken:(NSEventModifierFlags)aFlags error:(NSError **)outError;
{
if ([self isKeyCode:aKeyCode andFlagTakenInDelegate:aFlags error:outError])
return YES;
if ((![self.delegate respondsToSelector:@selector(shortcutValidatorShouldCheckSystemShortcuts:)] ||
[self.delegate shortcutValidatorShouldCheckSystemShortcuts:self]) &&
[self isKeyCode:aKeyCode andFlagsTakenInSystemShortcuts:aFlags error:outError])
{
return YES;
}
if ((![self.delegate respondsToSelector:@selector(shortcutValidatorShouldCheckMenu:)] ||
[self.delegate shortcutValidatorShouldCheckMenu:self]) &&
[self isKeyCode:aKeyCode andFlags:aFlags takenInMenu:[NSApp mainMenu] error:outError])
{
return YES;
}
return NO;
}
- (BOOL)isKeyCode:(unsigned short)aKeyCode andFlagTakenInDelegate:(NSEventModifierFlags)aFlags error:(NSError **)outError
{
if (self.delegate)
{
NSString *delegateReason = nil;
if ([self.delegate respondsToSelector:@selector(shortcutValidator:isKeyCode:andFlagsTaken:reason:)] &&
[self.delegate shortcutValidator:self
isKeyCode:aKeyCode
andFlagsTaken:aFlags
reason:&delegateReason])
{
if (outError)
{
BOOL isASCIIOnly = YES;
if ([self.delegate respondsToSelector:@selector(shortcutValidatorShouldUseASCIIStringForKeyCodes:)])
isASCIIOnly = [self.delegate shortcutValidatorShouldUseASCIIStringForKeyCodes:self];
NSString *shortcut = isASCIIOnly ? SRReadableASCIIStringForCocoaModifierFlagsAndKeyCode(aFlags, aKeyCode) : SRReadableStringForCocoaModifierFlagsAndKeyCode(aFlags, aKeyCode);
NSString *failureReason = [NSString stringWithFormat:
SRLoc(@"The key combination \"%@\" can't be used!"),
shortcut];
NSString *description = [NSString stringWithFormat:
SRLoc(@"The key combination \"%@\" can't be used because %@."),
shortcut,
[delegateReason length] ? delegateReason : @"it's already used"];
NSDictionary *userInfo = @{
NSLocalizedFailureReasonErrorKey : failureReason,
NSLocalizedDescriptionKey: description
};
*outError = [NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:userInfo];
}
return YES;
}
}
return NO;
}
- (BOOL)isKeyCode:(unsigned short)aKeyCode andFlagsTakenInSystemShortcuts:(NSEventModifierFlags)aFlags error:(NSError **)outError
{
CFArrayRef s = NULL;
OSStatus err = CopySymbolicHotKeys(&s);
if (err != noErr)
return YES;
NSArray *symbolicHotKeys = (NSArray *)CFBridgingRelease(s);
aFlags &= SRCocoaModifierFlagsMask;
for (NSDictionary *symbolicHotKey in symbolicHotKeys)
{
if ((__bridge CFBooleanRef)symbolicHotKey[(__bridge NSString *)kHISymbolicHotKeyEnabled] != kCFBooleanTrue)
continue;
unsigned short symbolicHotKeyCode = [symbolicHotKey[(__bridge NSString *)kHISymbolicHotKeyCode] integerValue];
if (symbolicHotKeyCode == aKeyCode)
{
UInt32 symbolicHotKeyFlags = [symbolicHotKey[(__bridge NSString *)kHISymbolicHotKeyModifiers] unsignedIntValue];
symbolicHotKeyFlags &= SRCarbonModifierFlagsMask;
if (SRCarbonToCocoaFlags(symbolicHotKeyFlags) == aFlags)
{
if (outError)
{
BOOL isASCIIOnly = YES;
if ([self.delegate respondsToSelector:@selector(shortcutValidatorShouldUseASCIIStringForKeyCodes:)])
isASCIIOnly = [self.delegate shortcutValidatorShouldUseASCIIStringForKeyCodes:self];
NSString *shortcut = isASCIIOnly ? SRReadableASCIIStringForCocoaModifierFlagsAndKeyCode(aFlags, aKeyCode) : SRReadableStringForCocoaModifierFlagsAndKeyCode(aFlags, aKeyCode);
NSString *failureReason = [NSString stringWithFormat:
SRLoc(@"The key combination \"%@\" can't be used!"),
shortcut];
NSString *description = [NSString stringWithFormat:
SRLoc(@"The key combination \"%@\" can't be used because it's already used by a system-wide keyboard shortcut. If you really want to use this key combination, most shortcuts can be changed in the Keyboard panel in System Preferences."),
shortcut];
NSDictionary *userInfo = @{
NSLocalizedFailureReasonErrorKey: failureReason,
NSLocalizedDescriptionKey: description
};
*outError = [NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:userInfo];
}
return YES;
}
}
}
return NO;
}
- (BOOL)isKeyCode:(unsigned short)aKeyCode andFlags:(NSEventModifierFlags)aFlags takenInMenu:(NSMenu *)aMenu error:(NSError **)outError
{
aFlags &= SRCocoaModifierFlagsMask;
for (NSMenuItem *menuItem in [aMenu itemArray])
{
if (menuItem.hasSubmenu && [self isKeyCode:aKeyCode andFlags:aFlags takenInMenu:menuItem.submenu error:outError])
return YES;
NSString *keyEquivalent = menuItem.keyEquivalent;
if (![keyEquivalent length])
continue;
NSEventModifierFlags keyEquivalentModifierMask = menuItem.keyEquivalentModifierMask;
if (SRKeyCodeWithFlagsEqualToKeyEquivalentWithFlags(aKeyCode, aFlags, keyEquivalent, keyEquivalentModifierMask))
{
if (outError)
{
BOOL isASCIIOnly = YES;
if ([self.delegate respondsToSelector:@selector(shortcutValidatorShouldUseASCIIStringForKeyCodes:)])
isASCIIOnly = [self.delegate shortcutValidatorShouldUseASCIIStringForKeyCodes:self];
NSString *shortcut = isASCIIOnly ? SRReadableASCIIStringForCocoaModifierFlagsAndKeyCode(aFlags, aKeyCode) : SRReadableStringForCocoaModifierFlagsAndKeyCode(aFlags, aKeyCode);
NSString *failureReason = [NSString stringWithFormat:SRLoc(@"The key combination \"%@\" can't be used!"), shortcut];
NSString *description = [NSString stringWithFormat:SRLoc(@"The key combination \"%@\" can't be used because it's already used by the menu item \"%@\"."), shortcut, menuItem.SR_path];
NSDictionary *userInfo = @{
NSLocalizedFailureReasonErrorKey: failureReason,
NSLocalizedDescriptionKey: description
};
*outError = [NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:userInfo];
}
return YES;
}
}
return NO;
}
@end
@implementation NSMenuItem (SRValidator)
- (NSString *)SR_path
{
NSMutableArray *items = [NSMutableArray array];
static const NSUInteger Limit = 1000;
NSMenuItem *currentMenuItem = self;
NSUInteger i = 0;
do
{
[items insertObject:currentMenuItem atIndex:0];
currentMenuItem = currentMenuItem.parentItem;
++i;
}
while (currentMenuItem && i < Limit);
NSMutableString *path = [NSMutableString string];
for (NSMenuItem *menuItem in items)
[path appendFormat:@"%@➝", menuItem.title];
if ([path length] > 1)
[path deleteCharactersInRange:NSMakeRange([path length] - 1, 1)];
return path;
}
@end