11

Having registered key bindings for "SPACE" and "released SPACE" which works as advertised when space is the only key pressed/released, I notice that pressing space, then pressing ctrl (or any other modifier key), then releasing space and finally releasing ctrl will cause the action associated with "SPACE" to be performed, but not the action associated with "released SPACE".

What is the preferred way to cause the action to be performed once space is no longer pressed (or a modifier key is pressed simultaneously)? I only tried this on Windows 7, 64-bit.

import javax.swing.SwingUtilities;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.AbstractAction;
import javax.swing.KeyStroke;
import java.awt.event.ActionEvent;
import java.awt.Cursor;

class Bind extends JPanel {
  {
    getInputMap().put(KeyStroke.getKeyStroke("SPACE"), "pressed");
    getInputMap().put(KeyStroke.getKeyStroke("released SPACE"), "released");
    getActionMap().put("pressed", new AbstractAction() {
      @Override public void actionPerformed(ActionEvent e) {
        System.out.println("pressed");
        setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)); 
      }
    });
    getActionMap().put("released", new AbstractAction() {
      @Override public void actionPerformed(ActionEvent e) {
        System.out.println("released");
        setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); 
      }
    });
  }
  public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
      @Override public void run() {
        JFrame f = new JFrame("Key Bindings");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
        f.add(new Bind());
        f.setSize(640, 480);
        f.setVisible(true);
      }
    });
  }
}

UPDATE: This is the way to avoid sticky space when accidentally hitting ctrl, alt, or shift before releasing space:

import javax.swing.SwingUtilities;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.AbstractAction;
import javax.swing.KeyStroke;
import java.awt.event.ActionEvent;
import java.awt.Cursor;

class Bind extends JPanel {
  {
    getInputMap().put(KeyStroke.getKeyStroke("SPACE"), "pressed");
    getInputMap().put(KeyStroke.getKeyStroke("released SPACE"), "released");
    getInputMap().put(KeyStroke.getKeyStroke("ctrl released SPACE"), "released");
    getInputMap().put(KeyStroke.getKeyStroke("shift released SPACE"), "released");
    getInputMap().put(KeyStroke.getKeyStroke("shift ctrl released SPACE"), "released");
    getInputMap().put(KeyStroke.getKeyStroke("alt released SPACE"), "released");
    getInputMap().put(KeyStroke.getKeyStroke("alt ctrl released SPACE"), "released");
    getInputMap().put(KeyStroke.getKeyStroke("alt shift released SPACE"), "released");
    getInputMap().put(KeyStroke.getKeyStroke("alt shift ctrl released SPACE"), "released");
    getActionMap().put("pressed", new AbstractAction() {
      @Override public void actionPerformed(ActionEvent e) {
        System.out.println("pressed");
        setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)); 
      }
    });
    getActionMap().put("released", new AbstractAction() {
      @Override public void actionPerformed(ActionEvent e) {
        System.out.println("released");
        setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); 
      }
    });
  }
  public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
      @Override public void run() {
        JFrame f = new JFrame("Key Bindings");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
        f.add(new Bind());
        f.setSize(640, 480);
        f.setVisible(true);
      }
    });
  }
}
Aksel
  • 533
  • 4
  • 12

2 Answers2

7

Makes sense that the released SPACE event isn't fired when the Control key is still held down. I would expect a control released SPACE event to be fired.

Add the following to your code:

getInputMap().put(KeyStroke.getKeyStroke("control released SPACE"), "released");

For the same reason the SPACE event won't fire if you first hold the Control key down. So you would also need to add bindings for control SPACE.

You would need to do this for all the modifier keys, which may or may not be a simpler solution than tracking the key events.

camickr
  • 321,443
  • 19
  • 166
  • 288
  • 1
    I accept your answer as the answer I would have made myself (add bindings for ALL space+modifier combinations), but I do not accept the logic of it (I do not agree that it makes sense). When the action for space is performed I would expect the action for released space to be performed at some point after space has been released, modifiers or not. As you point out I have to add bindings for all combinations of space+modifier keys, and all combinations of modifier keys (also ctrl+alt+shift+space) to prevent this sort of behaviour. – Aksel Dec 16 '11 at 09:14
  • 2
    It makes complete sense (whether you agree with it or not). If a Space event is generated every time the Space key is pressed then how would you ever differentiate from Space and Control/Space? With your logic you would always get 2 events, one for Control/Space and one for Space. So even if the user would only trying to execute the Control/Space Action a Space Action would be thrown in for free. – camickr Dec 16 '11 at 16:43
  • 1
    I am not saying I would get a space event for ctrl+space, I am saying I want a released space event upon a space event. What I get now is a released ctrl+space event upon a space event, and while that may make sense to you, and even though you have made yourself judge of what makes sense or not, it is not very useful to me. – Aksel Dec 16 '11 at 17:07
  • 1
    You must agree that it is a bit annoying if the panning mode enabled by pressing space is made semi-permanent by accidentally hitting ctrl before releasing space? – Aksel Dec 16 '11 at 17:16
  • 1
    This may be me getting too hung up on details, I feel that key bindings is the correct tool for the task at hand, I just do not think that they work quite the way that I should like. I am living with the panning mode getting stuck, after all it is no showstopper. – Aksel Dec 16 '11 at 17:20
  • 1
    You are the one judging what makes sense or not based on how you want things to work and on what you think is useful. I am simply stating why it does what it does. A computer can't determine what you want or expect. It can only do what it is told. Only a single event is generated every time the state (pressed/released) of a key changed. Therefore whenever the state of a key is changed the event notes which key was changed and it also notes if any modifiers that where pressed at the time. Its that simple. A computure can't read the intent of a user. – camickr Dec 16 '11 at 17:55
  • 1
    Maybe your design is the problem. Usually when I see apps that have some kind on zoom functionality. There are two keystrokes used to handle the actions. Something like Control/Plus to zoom and Control/Minus to unzoom. Maybe you need the same, Plus to pan Minus to unpan. Most programs I use do Actions on a "pressed" event not on "released" events. Maybe this is one of the reasons why. That is take a look at accelerators and how they are used on menu items. – camickr Dec 16 '11 at 17:59
  • 1
    One program which uses space bar for panning like this is Photoshop. I could make it a toggle instead. That makes sense... – Aksel Dec 16 '11 at 19:50
4

It's possible that your OS doesn't fire keyReleased events, but only keyPressed and keyTyped events, or some other combination, so check for that first. You might just need to check for keyTyped events instead of keyReleased and you'll be done with it.

Short answer:

Use a bitmask or an array to keep track of which keys are currently in the "pressed" state, then use those valued to trigger events. That is, don't use the Swing events directly to trigger responses in your application - you need an extra layer that essentially stores the state of the keyboard, and from that state, takes the relevant actions.

There are also methods available (see the end of this tutorial - "isAltDown", "isCtrlDown" etc.) to check if modifier keys are pressed when you receive an event like the "Space" key being pressed.

Long answer:

You're correct that the events get fired when the keys get pressed and released. It kind of has to work that way so that you can support applications that should treat those events separately, as opposed to together. One example (though this isn't the only one) is video games on PC where you might be pressing multiple letter/modifier keys at once (for example, A to go left, and W to go forward) and the game has to treat these two event as distinct inputs, as opposed to composite inputs, resulting in your movement going forward-left.

So, what you basically want to do, if you need to deal with composite inputs, is have a simply array of the actions your app needs to respond to, and their associated key bindings (whether single or multi-keys doesn't really matter). When a key is pressed, you basically turn on a flag for that key that says it's currently "pressed", and clear the flag when it's released.

Then, to trigger your events, you just check all keys that are pressed (via checking which key "flags" are active), and if a particular event's key combination is pressed, then the event is fired.

If you have fewer than 32 keys that trigger events, then you can actually do this with a bitmask and a 32-bit int value, rather than an array. In fact, it's much simpler to do it this way if you can. If you need up to 64 keys, do the same thing with a long. If you have very few keys that trigger events (8 or less, for example) you can use the 8-bit short type.

jefflunt
  • 33,527
  • 7
  • 88
  • 126
  • 1
    So, the even shorter answer is: Don't use key bindings. I suspected as much. It makes me sad. :-) – Aksel Dec 15 '11 at 19:37
  • 1
    I actually just modified my answer, and added a paragraph to the very beginning. It's possible that your OS isn't firing all three events. It's a quirk of Swing when applied to multiple platforms. That might actually be your answer. – jefflunt Dec 15 '11 at 19:41
  • 1
    Reading through the whole `KeyEvent` docs (at least the top section/overview), or may not help: http://docs.oracle.com/javase/6/docs/api/java/awt/event/KeyEvent.html – jefflunt Dec 15 '11 at 19:43