Cocoa Keyboard Shortcuts in Dialog without an Edit Menu

前端 未结 16 1253
Happy的楠姐
Happy的楠姐 2020-12-04 07:28

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

相关标签:
16条回答
  • 2020-12-04 07:35

    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)
        }
    
    }
    
    0 讨论(0)
  • 2020-12-04 07:35

    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)
    }
    
    0 讨论(0)
  • 2020-12-04 07:36

    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
        }
    }
    
    0 讨论(0)
  • 2020-12-04 07:38

    No need to add a new class, extension or really any code at all.

    1. Just add a new MenuItems in one of your menus and name them 'Copy', 'Cut' and 'Paste'.
    2. Add the correct shortcut keys to each item.
    3. Control+drag to connect them up to the corresponding methods listed under first responder.

    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.

    0 讨论(0)
  • 2020-12-04 07:39

    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];
    }
    
    0 讨论(0)
  • 2020-12-04 07:40

    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!
    
    0 讨论(0)
提交回复
热议问题