Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to bind command-? as a swing action accelerator for a help menu?

The standard key combination for help is command-? on macs. How can I bind this key combination to a menu item.

Note: As our users have different keyboard layouts I´m looking for a solution that does not require knowledge about what key "?" is located on.

Using KeyStroke.getKeyStroke(String), which javadoc says;

Parses a string and returns a `KeyStroke`. The string must have the following syntax:

<modifiers>* (<typedID> | <pressedReleasedID>)

modifiers := shift | control | ctrl | meta | alt | button1 | button2 | button3
typedID := typed <typedKey>
typedKey := string of length 1 giving Unicode character.
pressedReleasedID := (pressed | released) key
key := KeyEvent key code name, i.e. the name following "VK_".

I have this example code:

import javax.swing.*;
import java.awt.Dimension;
import java.awt.event.ActionEvent;

public class HelpShortcut extends JFrame {

    public HelpShortcut(){
        // A few keystrokes to experiment with
        //KeyStroke keyStroke = KeyStroke.getKeyStroke("pressed A");    // A simple reference - Works
        //KeyStroke keyStroke = KeyStroke.getKeyStroke("typed ?");      // Works
        KeyStroke keyStroke = KeyStroke.getKeyStroke("meta typed ?");   // What we want - Does not work

        // If we provide an invalid keystroke we get a null back - fail fast
        if (keyStroke==null) throw new RuntimeException("Invalid keystroke");

        // Create a simple menuItem linked to our action with the keystroke as accelerator
        JMenuItem helpMenuItem = new JMenuItem(new HelpAction());
        helpMenuItem.setAccelerator(keyStroke);

        // Install the menubar with a help menu
        JMenuBar mainMenu = new JMenuBar();
        JMenu helpMenu = new JMenu("Help");
        helpMenu.add(helpMenuItem);
        mainMenu.add(helpMenu);

        setJMenuBar(mainMenu);
    }

    // Scaffolding
    public static void main(String[] pArgs) {
        HelpShortcut helpShortcut= new HelpShortcut();
        helpShortcut.setLocationRelativeTo(null);
        helpShortcut.setSize(new Dimension(100, 162));
        helpShortcut.setVisible(true);
    }

    private class HelpAction extends AbstractAction {

        public HelpAction() {
            putValue(Action.NAME,"Help me!");

        }

        @Override
        public void actionPerformed(final ActionEvent pActionEvent) {
            JOptionPane.showMessageDialog(HelpShortcut.this,"You should ask StackOverflow!");
        }

    }
}
like image 957
Løkling Avatar asked Nov 16 '11 19:11

Løkling


3 Answers

On my keyboard the "?" is above the "/" key so you also next to use the shift key to type the "?". So to do the binding you need to use:

// KeyStroke keyStroke = KeyStroke.getKeyStroke("meta typed ?");
int modifier = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() 
             + KeyEvent.SHIFT_DOWN_MASK;
KeyStroke keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_SLASH, modifier);
like image 51
camickr Avatar answered Nov 10 '22 17:11

camickr


See KeyEvent API doc - notes section:

Not all characters have a keycode associated with them. For example, there is no keycode for the question mark because there is no keyboard for which it appears on the primary layer.

like image 36
wzberger Avatar answered Nov 10 '22 15:11

wzberger


(surprisingly - to me :-) modifiers to bindings of keyIDs "typed" are not supported: while you can create and bind such into the inputMap, they are never found because keyStrokes internally generated for typed keyEvents do use the keyChar and ignore the modifiers. That creation happens in JComponent.processKeyBindings(...)

boolean processKeyBindings(KeyEvent e, boolean pressed) {
  if (!SwingUtilities.isValidKeyEventForKeyBindings(e)) {
      return false;
  }
  // Get the KeyStroke
  KeyStroke ks;

  if (e.getID() == KeyEvent.KEY_TYPED) {
      ks = KeyStroke.getKeyStroke(e.getKeyChar());
  }
  else {
  ks = KeyStroke.getKeyStroke(e.getKeyCode(),e.getModifiers(),
                (pressed ? false:true));
  }

Thinking about it, that may make sense: pressed/released handles the physical keys, while typed is the final combined "output" of one or more physical keys. If there is no valid keyChar for any given combination, there is no keyTyped event generated.

The base problem is the well-known usa centrism of the swing/awt developers: they counted as physical keys only those on the us layout ;-) No way (that I know of) to get at other keys in a layout-agnostic manner. Hope to be proven wrong

like image 41
kleopatra Avatar answered Nov 10 '22 15:11

kleopatra