I have an LSUIElement
application that displays a menubar status item. The application can display a dialog window that contains a text field.
If the u
Swift 4.2 for Thomas Kilian solution
class MTextField: NSSecureTextField {
private let commandKey = NSEvent.ModifierFlags.command.rawValue
private let commandShiftKey = NSEvent.ModifierFlags.command.rawValue | NSEvent.ModifierFlags.shift.rawValue
override func performKeyEquivalent(with event: NSEvent) -> Bool {
if event.type == NSEvent.EventType.keyDown {
if (event.modifierFlags.rawValue & NSEvent.ModifierFlags.deviceIndependentFlagsMask.rawValue) == commandKey {
switch event.charactersIgnoringModifiers! {
case "x":
if NSApp.sendAction(#selector(NSText.cut(_:)), to:nil, from:self) { return true }
case "c":
if NSApp.sendAction(#selector(NSText.copy(_:)), to:nil, from:self) { return true }
case "v":
if NSApp.sendAction(#selector(NSText.paste(_:)), to:nil, from:self) { return true }
case "z":
if NSApp.sendAction(Selector(("undo:")), to:nil, from:self) { return true }
case "a":
if NSApp.sendAction(#selector(NSResponder.selectAll(_:)), to:nil, from:self) { return true }
default:
break
}
}
else if (event.modifierFlags.rawValue & NSEvent.ModifierFlags.deviceIndependentFlagsMask.rawValue) == commandShiftKey {
if event.charactersIgnoringModifiers == "Z" {
if NSApp.sendAction(Selector(("redo:")), to:nil, from:self) { return true }
}
}
}
return super.performKeyEquivalent(with: event)
}
}
Swift 5 solution for NSApplication
subclass
open override func sendEvent(_ event: NSEvent) {
if event.type == .keyDown {
if event.modifierFlags.contains(.command) && NSEvent.ModifierFlags.deviceIndependentFlagsMask.contains(.command) {
if event.modifierFlags.contains(.shift) && NSEvent.ModifierFlags.deviceIndependentFlagsMask.contains(.shift) {
if event.charactersIgnoringModifiers == "Z" {
if NSApp.sendAction(Selector("redo:"), to:nil, from:self) { return }
}
}
guard let key = event.charactersIgnoringModifiers else { return super.sendEvent(event) }
switch key {
case "x":
if NSApp.sendAction(Selector("cut:"), to:nil, from:self) { return }
case "c":
if NSApp.sendAction(Selector("copy:"), to:nil, from:self) { return }
case "v":
if NSApp.sendAction(Selector("paste:"), to:nil, from:self) { return }
case "z":
if NSApp.sendAction(Selector("undo:"), to:nil, from:self) { return }
case "a":
if NSApp.sendAction(Selector("selectAll:"), to:nil, from:self) { return }
default:
break
}
}
}
super.sendEvent(event)
}
Thank you for this solution! It helped me greatly, so I decided to contribute some code in hope it helps someone else. The suggested solution above worked perfectly after I converted it to Swift 4.2
. I then refactored the code a little. I think this is a little cleaner. This is Swift 4.2
compatible:
// NSEventExtensions.swift
import AppKit
extension NSEvent {
func containsKeyModifierFlags(_ flags: NSEvent.ModifierFlags) -> Bool {
switch modifierFlags.intersection(.deviceIndependentFlagsMask) {
case [flags]: return true
default: return false
}
}
}
// SearchFiled.swift
import AppKit
import Carbon
final class SearchField: NSSearchField {
override func performKeyEquivalent(with event: NSEvent) -> Bool {
switch event.type {
case .keyDown: return performKeyDownEquivalent(with: event)
default: return super.performKeyEquivalent(with: event)
}
}
// MARK: - private
private func performKeyDownEquivalent(with event: NSEvent) -> Bool {
if event.containsKeyModifierFlags(.command) {
switch Int(event.keyCode) {
case kVK_ANSI_X: return NSApp.sendAction(#selector(NSText.cut(_:)), to: nil, from: self)
case kVK_ANSI_C: return NSApp.sendAction(#selector(NSText.copy(_:)), to: nil, from: self)
case kVK_ANSI_V: return NSApp.sendAction(#selector(NSText.paste(_:)), to: nil, from: self)
case kVK_ANSI_Z: return NSApp.sendAction(Selector(("undo:")), to: nil, from: self)
case kVK_ANSI_A: return NSApp.sendAction(#selector(NSResponder.selectAll(_:)), to: nil, from: self)
default: break
}
} else if event.containsKeyModifierFlags([.command, .shift]) {
switch Int(event.keyCode) {
case kVK_ANSI_Z: return NSApp.sendAction(Selector(("redo:")), to: nil, from: self)
default: break
}
}
return false
}
}
No need to add a new class, extension or really any code at all.
The bonus here is that the items are not hidden from your users, and this takes less time that creating a new class and reassigning all of your existing TextFields to it.
I've improved Adrian's solution to work when Caps Lock is on as well:
- (void)sendEvent:(NSEvent *)event
{
if (event.type == NSKeyDown)
{
NSString *inputKey = [event.charactersIgnoringModifiers lowercaseString];
if ((event.modifierFlags & NSDeviceIndependentModifierFlagsMask) == NSCommandKeyMask ||
(event.modifierFlags & NSDeviceIndependentModifierFlagsMask) == (NSCommandKeyMask | NSAlphaShiftKeyMask))
{
if ([inputKey isEqualToString:@"x"])
{
if ([self sendAction:@selector(cut:) to:nil from:self])
return;
}
else if ([inputKey isEqualToString:@"c"])
{
if ([self sendAction:@selector(copy:) to:nil from:self])
return;
}
else if ([inputKey isEqualToString:@"v"])
{
if ([self sendAction:@selector(paste:) to:nil from:self])
return;
}
else if ([inputKey isEqualToString:@"z"])
{
if ([self sendAction:NSSelectorFromString(@"undo:") to:nil from:self])
return;
}
else if ([inputKey isEqualToString:@"a"])
{
if ([self sendAction:@selector(selectAll:) to:nil from:self])
return;
}
}
else if ((event.modifierFlags & NSDeviceIndependentModifierFlagsMask) == (NSCommandKeyMask | NSShiftKeyMask) ||
(event.modifierFlags & NSDeviceIndependentModifierFlagsMask) == (NSCommandKeyMask | NSShiftKeyMask | NSAlphaShiftKeyMask))
{
if ([inputKey isEqualToString:@"z"])
{
if ([self sendAction:NSSelectorFromString(@"redo:") to:nil from:self])
return;
}
}
}
[super sendEvent:event];
}
Adrian's solution is good, but better I think to use a switch statement rather than all those string comparisons, e.g.:
uint const modifierCode = (theEvent.modifierFlags & NSDeviceIndependentModifierFlagsMask);
BOOL usingModifiers = ( modifierCode != 0 );
//BOOL const usingShiftKey = ((theEvent.modifierFlags & NSShiftKeyMask) != 0);
//BOOL const usingCommandKey = ((theEvent.modifierFlags & NSCommandKeyMask) != 0);
NSString * ch = [theEvent charactersIgnoringModifiers];
if ( ( usingModifiers ) && ( ch.length == 1 ) ) switch ( [ch characterAtIndex:0] )
{
case 'x':
if ( modifierCode == NSCommandKeyMask ) [m cut]; // <-- m = model
break;
case 'c':
if ( modifierCode == NSCommandKeyMask ) [m copy];
break;
case 'v':
if ( modifierCode == NSCommandKeyMask ) [m paste];
break;
case 'z':
if ( modifierCode == NSCommandKeyMask ) [m undo];
break;
case 'Z':
if ( modifierCode == ( NSCommandKeyMask | NSShiftKeyMask ) ) [m redo];
break;
default: // etc.
break;
}
else switch ( theEvent.keyCode ) // <-- for independent keycodes!
{
case kVK_Home:
[m moveToBeginningOfDocument:nil];
break;
case kVK_End: // etc!