Question

Our swing application uses the "spacebar" key as a shortcut key. In other words, if you press the spacebar anywhere within the application window, it will perform a certain behaviour. What's more, this same behaviour can also be performed by using a JMenuItem in the window's menu bar.

The normal way to implement this is to simply set "spacebar" as the "ACCELERATOR_KEY" for the corresponding JMenuItem, and just let swing take care of the rest. Unfortunately, we can't do this, because if we set "spacebar" as the ACCELERATOR_KEY, the shortcut fires every time the user hits spacebar, even at bad times, like when the user is typing regular text in a textfield inside the application window.

To get around that problem, we've implemented the spacebar as a window-level pseudo shortcut key WITHOUT properly installing it in the corresponding JMenuItem as an ACCELERATOR-KEY. It's working fine, except of course the JMenuItem does not have any shortcut key "hint" on it. That is, the menu item should say "spacebar" in little grey letters right on the menu item, so that our users can "discover" the shortcut key without reading the user manual.

But the text is missing since it only gets added to the menu item when you install the shortcut as an ACCELERATOR-KEY.

So my question is: How can I modify that shortcut key hint text on our JMenuItem, so that I can add the appropriate "hint" myself? It would be nice if I could do this without having to mess around at the Look-and-Feel level, since our application is multiplatform and uses several different LAFS.

Was it helpful?

Solution

Thanks for the suggestions, guys. This discussion has led me to a couple of different solutions that work.

First, to solve the problem of displaying shortcut "hint" text without actually having a working shortcut key. Just add the shortcut key to the Action/JMenuItem as normal (this will give you your hint text) and then simply unbind the shortcut key right after it has been added. An easy (but not the only) way to do this is to create a subclass of JMenuItem, and override a single method:

   @Override public void setAccelerator( KeyStroke keyStroke ) {
      super.setAccelerator( keyStroke );
      getInputMap( WHEN_IN_FOCUSED_WINDOW ).put( keyStroke, "none" );
   }

Voila! A non-functional shortcut key, with the hint text on the menu item still intact.


A better solution to my original problem, however, is to change the behaviour of all shortcut keys so that they do not fire when the keyboard focus is inside a text component. As far as I can tell, that is really the only situation where an (unmodified) shortcut key like SPACE would collide with the behaviour of an existing window component. This second solution is a bit more effort, but it's betteron because it allows me to remove all my other custom code for handling the shortcut key without installing it in the JMenuBar.

Instead, I can just use the regular shortcut key architecture in swing (i.e. install it in the JMenuBar or Action object) with only a single minor modification:

This part is in a custom subclass of JMenuItem again:

   /** {@inheritDoc} */
   @Override public void setAccelerator( KeyStroke keyStroke ) {
      super.setAccelerator( keyStroke );
      if ( doClickAction_ == null ) {
         doClickAction_ = new DoClickAction( getActionMap().get( "doClick" ) );
         getActionMap().put("doClick", doClickAction_);
      }
   }

And this is a new private inner class in my JMenuItem subclass:

   /**
    * This action wraps the original "doClick" action for this menu item,
    * altering it so that it only fires if the keyboard focus is not in any
    * kind of text area or text field.
    * 

* This allows accelerator keys to be added to {@link IMenuItem}s, * but the user can still type those keys into text components without * triggering the accelerator key behaviour. */ private static class DoClickAction extends AbstractAction { // the original action, i.e. that we are wrapping private final Action doClickAction_; DoClickAction( Action doClickAction ) { if ( doClickAction == null ) { throw new IllegalArgumentException(); } doClickAction_ = doClickAction; } @Override public void actionPerformed( ActionEvent e ) { final KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager(); if ( kfm.getFocusOwner() instanceof JTextComponent == false ) { doClickAction_.actionPerformed( e ); } } }

OTHER TIPS

Taking into consideration your requirement not to touch the L&F code, which is where the accelerator text is laid out...

Looking at the code for JMenuItem, it seems that you could create a subclass of JMenutItem for your "spacebar" item, which would override the setAccelerator method to assign the accelerator keyStroke, but not go through the configuration of the action. This should allow for the UI code to do its job of laying out the menu as you like it, without the pesky action being triggered when your users are actually trying to put spaces between their words (do they really need to do that anyway?).

I don't know if text components are the only component that causes a problem. If so, then maybe you can just customize the Action to use the KeyboardFocusManager to determine which component currently has focus. If it is a text component then you don't invoke your Action.

Does setting the accelerator interfere with the way you've currently implemented it all? If not you could just set the accelerator to the space bar... I am going to go ahead and assume you can't do that. The other thing you could do is override the getAccelerator() on your JMenuItem, so you don't have to copy most of the code from JMenuItem (not that there is much there).

JMenuItem clickSpace = new JMenuItem("Item With Accelerator Hint -->") {
        @Override
        public KeyStroke getAccelerator()
        {
            return KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0);
        }
    }
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top