Skip to content

Commit

Permalink
Alter the recording logic of modifier-only shortcuts.
Browse files Browse the repository at this point in the history
Refs #114
  • Loading branch information
Kentzo committed Apr 7, 2020
1 parent c0d8ae9 commit c069155
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 40 deletions.
30 changes: 18 additions & 12 deletions Library/SRRecorderControl.h
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,10 @@ IB_DESIGNABLE
@discussion
Defaults to NO.
Normally the control uses a non modifier flags key event as a trigger to end recording. To end the recording
with modifier flags only the -recorderControl:canRecordShortcut: method is sent to the delegate repeatedly.
When YES changes the control behavior to allow recording of a shortcut without a key code.
In this mode recording ends either when key code is pressed (as usual) or when all modifier flags are relased.
Instead of capturing only currently pressed modifier flags, the control XORs its internal value whenever
a modifier key is pressed. I.e. whenever a modifier key is pressed it either added or removed.
*/
@property IBInspectable BOOL allowsModifierFlagsOnlyShortcut;

Expand Down Expand Up @@ -215,22 +217,26 @@ IB_DESIGNABLE
@property (getter=isClearButtonHighlighted, readonly) BOOL clearButtonHighlighted;

/*!
Check whether a given combination is valid.
Check whether a given combination can be recorded.
@discussion
Subclasses may override to provide custom verfication logic for a proposed shortcut.
@param aModifierFlags Proposed modifier flags.
@param aKeyCode Code of the pressed key.
@seealso allowedModifierFlags
@seealso allowsEmptyModifierFlags
@seealso requiredModifierFlags
@seealso -areModifierFlagsAllowed:forKeyCode:
*/
- (BOOL)areModifierFlagsValid:(NSEventModifierFlags)aModifierFlags forKeyCode:(SRKeyCode)aKeyCode;

/*!
Check whether a given combination is allowed.
Check whether given modifier flags are allowed by control's configuration and delegate.
@discussion
Subclasses may override to provide custom verification logic for allowed modifier flags.
@param aModifierFlags Proposed modifier flags.
Expand All @@ -240,7 +246,7 @@ IB_DESIGNABLE
@seealso allowsEmptyModifierFlags
@seealso requiredModifierFlags
@seealso -[SRRecorderControlDelegate recorderControl:shouldUnconditionallyAllowModifierFlags:forKeyCode:];
*/
- (BOOL)areModifierFlagsAllowed:(NSEventModifierFlags)aModifierFlags forKeyCode:(SRKeyCode)aKeyCode;

Expand Down Expand Up @@ -424,13 +430,13 @@ NS_SWIFT_NAME(RecorderControlDelegate)
@param aModifierFlags Proposed modifier flags.
@param aKeyCode Code of the pressed key.
@param aKeyCode Code of the pressed key, if any.
@return YES if the control should ignore the rules; otherwise, NO.
@discussion
Normally, you wouldn't allow a user to record a shourcut without modifier flags set: disallow 'a', but allow cmd-'a'.
However, some keys are designed to be key shortcuts by itself, e.g. functional keys.
Normally, you wouldn't allow a user to record a shortcut without modifier flags set.
However, some keys, like functional keys, are designed to be key shortcuts by itself.
By implementing this method the delegate can allow these special keys to be set without modifier flags
even when the control is configured to disallow empty modifier flags.
Expand Down
80 changes: 52 additions & 28 deletions Library/SRRecorderControl.m
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,7 @@ @implementation SRRecorderControl

// +NSEvent.modifierFlags may change across run loop calls
// Extra care is needed to ensure that all methods will see the same flags.
NSEventModifierFlags _currentlyDrawnRecordingModifierFlags;
NSEventModifierFlags _accessibilityRecordingModifierFlags;
NSEventModifierFlags _lastSeenModifierFlags;

BOOL _isLazilyInitializingStyle;
}
Expand Down Expand Up @@ -388,12 +387,10 @@ - (NSString *)drawingLabel

if (self.isRecording)
{
_currentlyDrawnRecordingModifierFlags = NSEvent.modifierFlags & self.allowedModifierFlags;

if (_currentlyDrawnRecordingModifierFlags)
if (_lastSeenModifierFlags)
{
__auto_type layoutDirection = self.stringValueRespectsUserInterfaceLayoutDirection ? self.userInterfaceLayoutDirection : NSUserInterfaceLayoutDirectionLeftToRight;
label = [SRSymbolicModifierFlagsTransformer.sharedTransformer transformedValue:@(_currentlyDrawnRecordingModifierFlags)
label = [SRSymbolicModifierFlagsTransformer.sharedTransformer transformedValue:@(_lastSeenModifierFlags)
layoutDirection:layoutDirection];
}
else
Expand Down Expand Up @@ -478,6 +475,8 @@ - (BOOL)beginRecording
return;
}

self->_lastSeenModifierFlags = NSEvent.modifierFlags & self.allowedModifierFlags;

self.needsDisplay = YES;

[self willChangeValueForKey:@"isRecording"];
Expand Down Expand Up @@ -554,8 +553,7 @@ - (void)endRecordingWithObjectValue:(SRShortcut *)anObjectValue
[self didChangeValueForKey:@"isRecording"];

self.objectValue = anObjectValue;
self->_currentlyDrawnRecordingModifierFlags = 0;
self->_accessibilityRecordingModifierFlags = 0;
self->_lastSeenModifierFlags = 0;

[self updateActiveConstraints];
[self updateTrackingAreas];
Expand Down Expand Up @@ -753,10 +751,10 @@ - (BOOL)areModifierFlagsValid:(NSEventModifierFlags)aModifierFlags forKeyCode:(S
__block BOOL allowModifierFlags = YES;

os_activity_initiate("-[SRRecorderControl areModifierFlagsValid:forKeyCode:]", OS_ACTIVITY_FLAG_DEFAULT, ^{
allowModifierFlags = [self areModifierFlagsAllowed:aModifierFlags forKeyCode:aKeyCode];

if ((aModifierFlags & self.requiredModifierFlags) != self.requiredModifierFlags)
allowModifierFlags = NO;
else
allowModifierFlags = [self areModifierFlagsAllowed:aModifierFlags forKeyCode:aKeyCode];
});

return allowModifierFlags;
Expand All @@ -770,11 +768,11 @@ - (BOOL)areModifierFlagsAllowed:(NSEventModifierFlags)aModifierFlags forKeyCode:
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
__auto_type DelegateShouldUnconditionallyAllowModifierFlags = ^{
if (!allowModifierFlags && [self.delegate respondsToSelector:@selector(recorderControl:shouldUnconditionallyAllowModifierFlags:forKeyCode:)])
if ([self.delegate respondsToSelector:@selector(recorderControl:shouldUnconditionallyAllowModifierFlags:forKeyCode:)])
{
return [self.delegate recorderControl:self shouldUnconditionallyAllowModifierFlags:aModifierFlags forKeyCode:aKeyCode];
}
else if (!allowModifierFlags && [self.delegate respondsToSelector:@selector(shortcutRecorder:shouldUnconditionallyAllowModifierFlags:forKeyCode:)])
else if ([self.delegate respondsToSelector:@selector(shortcutRecorder:shouldUnconditionallyAllowModifierFlags:forKeyCode:)])
{
return [self.delegate shortcutRecorder:self shouldUnconditionallyAllowModifierFlags:aModifierFlags forKeyCode:aKeyCode];
}
Expand All @@ -784,12 +782,11 @@ - (BOOL)areModifierFlagsAllowed:(NSEventModifierFlags)aModifierFlags forKeyCode:
#pragma clang diagnostic pop

os_activity_initiate("-[SRRecorderControl areModifierFlagsAllowed:forKeyCode:]", OS_ACTIVITY_FLAG_IF_NONE_PRESENT, ^{
if (aModifierFlags == 0 && !self.allowsEmptyModifierFlags)
allowModifierFlags = NO;
else if ((aModifierFlags & self.allowedModifierFlags) != aModifierFlags)
allowModifierFlags = NO;

allowModifierFlags = DelegateShouldUnconditionallyAllowModifierFlags();
if ((aModifierFlags == 0 && !self.allowsEmptyModifierFlags) ||
((aModifierFlags & self.allowedModifierFlags) != aModifierFlags))
{
allowModifierFlags = DelegateShouldUnconditionallyAllowModifierFlags();
}
});

return allowModifierFlags;
Expand Down Expand Up @@ -956,8 +953,7 @@ - (NSString *)accessibilityLabel
{
if (self.isRecording)
{
_accessibilityRecordingModifierFlags = _currentlyDrawnRecordingModifierFlags;
return [SRLiteralModifierFlagsTransformer.sharedTransformer transformedValue:@(_accessibilityRecordingModifierFlags)
return [SRLiteralModifierFlagsTransformer.sharedTransformer transformedValue:@(_lastSeenModifierFlags)
layoutDirection:NSUserInterfaceLayoutDirectionLeftToRight];
}
else
Expand Down Expand Up @@ -1694,19 +1690,47 @@ - (BOOL)performKeyEquivalent:(NSEvent *)anEvent

- (void)flagsChanged:(NSEvent *)anEvent
{
if ([self canCaptureKeyEvent] && self.isRecording)
if (self.isRecording && [self canCaptureKeyEvent])
{
__auto_type modifierFlags = anEvent.modifierFlags & SRCocoaModifierFlagsMask;

if (self.allowsModifierFlagsOnlyShortcut)
{
SRShortcut *newObjectValue = [SRShortcut shortcutWithEvent:anEvent];
__auto_type keyCode = anEvent.keyCode;
__auto_type nextModifierFlags = _lastSeenModifierFlags;

// Only XOR when flag is added.
if ((modifierFlags & NSEventModifierFlagCommand) && (keyCode == kVK_Command || keyCode == kVK_RightCommand))
nextModifierFlags ^= NSEventModifierFlagCommand;
else if ((modifierFlags & NSEventModifierFlagOption) && (keyCode == kVK_Option || keyCode == kVK_RightOption))
nextModifierFlags ^= NSEventModifierFlagOption;
else if ((modifierFlags & NSEventModifierFlagShift) && (keyCode == kVK_Shift || keyCode == kVK_RightShift))
nextModifierFlags ^= NSEventModifierFlagShift;
else if ((modifierFlags & NSEventModifierFlagControl) && (keyCode == kVK_Control || keyCode == kVK_RightControl))
nextModifierFlags ^= NSEventModifierFlagControl;
else if (modifierFlags == 0 && _lastSeenModifierFlags != 0)
{
SRShortcut *newObjectValue = [SRShortcut shortcutWithCode:SRKeyCodeNone
modifierFlags:_lastSeenModifierFlags
characters:nil
charactersIgnoringModifiers:nil];

if ([self canEndRecordingWithObjectValue:newObjectValue])
[self endRecordingWithObjectValue:newObjectValue];
}
if ([self canEndRecordingWithObjectValue:newObjectValue])
[self endRecordingWithObjectValue:newObjectValue];
}

NSEventModifierFlags modifierFlags = anEvent.modifierFlags & SRCocoaModifierFlagsMask;
if (modifierFlags != 0 && ![self areModifierFlagsAllowed:modifierFlags forKeyCode:anEvent.keyCode])
[self playAlert];
if (nextModifierFlags != _lastSeenModifierFlags && ![self areModifierFlagsAllowed:nextModifierFlags forKeyCode:SRKeyCodeNone])
[self playAlert];
else
_lastSeenModifierFlags = nextModifierFlags;
}
else
{
if (![self areModifierFlagsAllowed:modifierFlags forKeyCode:SRKeyCodeNone])
[self playAlert];
else
_lastSeenModifierFlags = modifierFlags;
}

[self setNeedsDisplayInRect:self.style.labelDrawingGuide.frame];
}
Expand Down

0 comments on commit c069155

Please sign in to comment.