cocoamacosnsstatusitem

Cocoa Keyboard Shortcuts in Dialog without an Edit Menu


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 user right-clicks/control-clicks the text field, a menu appears that allows cut, copy, paste, etc. However, the standard Command-X, Command-C, and Command-V keyboard shortcuts do not work in the field. I assume this is because my application does not provide an Edit menu with those shortcuts defined.

I've tried adding an Edit menu item to my application's menu, as suggested in the Ship Some Code blog, but that did not work. The menu items in the Edit menu can be used, but keyboard shortcuts still don't work.

I can imagine a few ways to hack the keyboard handling, but is there a "recommended" way to make this work?

(For details about the app, see Menubar Countdown.)

Related question: Copy/Paste Not Working in Modal Window


Solution

  • What worked for me was using The View Solution presented in Copy and Paste Keyboard Shortcuts at CocoaRocket.

    Basically, this means subclassing NSTextField and overriding performKeyEquivalent:.

    Update: The CocoaRocket site is apparently gone. Here's the Internet Archive link: http://web.archive.org/web/20100126000339/http://www.cocoarocket.com/articles/copypaste.html

    Edit: The Swift code looks like this

    class Editing: NSTextField {
    
      private let commandKey = NSEventModifierFlags.CommandKeyMask.rawValue
      private let commandShiftKey = NSEventModifierFlags.CommandKeyMask.rawValue | NSEventModifierFlags.ShiftKeyMask.rawValue
      override func performKeyEquivalent(event: NSEvent) -> Bool {
        if event.type == NSEventType.KeyDown {
          if (event.modifierFlags.rawValue & NSEventModifierFlags.DeviceIndependentModifierFlagsMask.rawValue) == commandKey {
            switch event.charactersIgnoringModifiers! {
            case "x":
              if NSApp.sendAction(Selector("cut:"), to:nil, from:self) { return true }
            case "c":
              if NSApp.sendAction(Selector("copy:"), to:nil, from:self) { return true }
            case "v":
              if NSApp.sendAction(Selector("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("selectAll:"), to:nil, from:self) { return true }
            default:
              break
            }
          }
          else if (event.modifierFlags.rawValue & NSEventModifierFlags.DeviceIndependentModifierFlagsMask.rawValue) == commandShiftKey {
            if event.charactersIgnoringModifiers == "Z" {
              if NSApp.sendAction(Selector("redo:"), to:nil, from:self) { return true }
            }
          }
        }
        return super.performKeyEquivalent(event)
      }
    }
    

    Edit: The Swift 3 code looks like this

    class Editing: NSTextView {
    
    private let commandKey = NSEventModifierFlags.command.rawValue
    private let commandShiftKey = NSEventModifierFlags.command.rawValue | NSEventModifierFlags.shift.rawValue
    
    override func performKeyEquivalent(with event: NSEvent) -> Bool {
        if event.type == NSEventType.keyDown {
            if (event.modifierFlags.rawValue & NSEventModifierFlags.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 & NSEventModifierFlags.deviceIndependentFlagsMask.rawValue) == commandShiftKey {
                if event.charactersIgnoringModifiers == "Z" {
                    if NSApp.sendAction(Selector(("redo:")), to:nil, from:self) { return true }
                }
            }
        }
        return super.performKeyEquivalent(with: event)
     }
    }