Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

KeyBindings stuck on actionPerformed()

If you add a Key Binding in java with a mask - let's just say the ActionEvent.ALT_MASK with KeyEvent.VK_A - and then you perform that key (ALT + A) BUT, you release the alt key just before the 'A' key, you will usually encounter a problem where the actionPerformed() in a class (implementing ActionListener) will keep being activated. This probably (98% sure) means that the Key Binding never registered that the key was released. If you release the 'A' key before the alt key, you are fine, but - like I said - if you release the alt key perhaps 1/10 of a second before the other key, it keeps repeating.

Note: This only happens - apparently - in my program (here)

Try it for yourself if you don't believe me. Here is a snippet of my code:

    public ConwayPanel() {
        super();

        setBackground(new Color(245, 255, 245, 255)); // BG slightly green - all ready

        paused = true; // nothing to play... in FUTURE put cool organism in

        startX = 0; // starting position of the left of the grid
        startY = 0; // starting position of the top of the grid
        zoom = 15; // the width of each cell (EXCLUDING the lines that make up the boundaries)
        cellNum = 1000; // The number of cells

        cells = new boolean[cellNum][cellNum]; // populate cells with false/dead

        currentX = 0; // current x cursor position
        currentY = 0; // current y cursor position

        flipBoundaries = new int[4];

        hideCurrentPos = false; // don't want to hide cursor position unless explicitly told to do so

        defineMaps(); // creates Key enums
        setKeyBindings(); // defines Key and KeyNoMask key bindings
        Timer timer = new Timer(100, new KeyListener());
        timer.start();

        setupMouseListeners(); // creates MouseListener, MouseMotionListener and MouseWheelListener

        setFocusable(true); // make isFocusable() true
        requestFocusInWindow(); // get focus for listeners
    }

    private void defineMaps() {
        for (KeyAltMask key : KeyAltMask.values()) {
            keyMap.put(key, false); // value true when key is pressed - all initiated to false
        }
        for (KeyNoMask key : KeyNoMask.values()) {
            keyNoMaskMap.put(key, false); // value true when key is pressed - all initiated to false
        }
    }

    private void setKeyBindings() {
        InputMap inMap = getInputMap(JComponent.WHEN_FOCUSED/* or... WHEN_IN_FOCUSED_WINDOW*/);
        ActionMap actMap = getActionMap();

        for (final KeyAltMask key : KeyAltMask.values()) {
            KeyStroke pressed = KeyStroke.getKeyStroke(key.getKeyCode(), ActionEvent.ALT_MASK, false); // just right! (not blocking shortcut key and preventing accidental keyboard mishaps)
            KeyStroke released = KeyStroke.getKeyStroke(key.getKeyCode(), ActionEvent.ALT_MASK, true); // just right! (not blocking shortcut key and preventing accidental keyboard mishaps)

            inMap.put(pressed, key.toString() + "pressed");
            inMap.put(released, key.toString() + "released");

            actMap.put(key.toString() + "pressed", new AbstractAction() { // adds each value of Key into a HashMap (when the key is pressed) and puts that HashMap action into ActionMap

                private static final long serialVersionUID = 1L;

                @Override
                public void actionPerformed(ActionEvent e) {
                    keyMap.put(key, true);
                }
            });

            actMap.put(key.toString() + "released", new AbstractAction() { // adds each value of Key into a HashMap (when the key is released) and puts that HashMap action into ActionMap

                private static final long serialVersionUID = 1L;

                @Override
                public void actionPerformed(ActionEvent e) {
                    keyMap.put(key, false);
                }
            });
        }

        for (final KeyNoMask key : KeyNoMask.values()) {
            KeyStroke pressed = KeyStroke.getKeyStroke(key.getKeyCode(), 0, false);
            KeyStroke released = KeyStroke.getKeyStroke(key.getKeyCode(), 0, true);
            inMap.put(pressed, key.toString() + "pressed");
            inMap.put(released, key.toString() + "released");
            actMap.put(key.toString() + "pressed", new AbstractAction() { // adds each value of KeyNoMask into a HashMap (when the key is pressed) and puts that HashMap action into ActionMap

                private static final long serialVersionUID = 1L;

                @Override
                public void actionPerformed(ActionEvent e) {
                    keyNoMaskMap.put(key, true);
                }
            });
            actMap.put(key.toString() + "released", new AbstractAction() { // adds each value of KeyNoMask into a HashMap (when the key is released) and puts that HashMap action into ActionMap

                private static final long serialVersionUID = 1L;

                @Override
                public void actionPerformed(ActionEvent e) {
                    keyNoMaskMap.put(key, false);
                }
            });
        }
    }

    private class KeyListener implements ActionListener { // probably not great to have same name, but "real" KeyListener not imported

        @Override
        public void actionPerformed(ActionEvent e) {
            for (KeyAltMask key : KeyAltMask.values()) { // run through the ALL of the keys
                if (keyMap.get(key)) { // if key in HashMap is true (i.e. the actionPerformed() above set it true)
                    switch(key.toString()) {
                        case "c": // clear all cells and pause if not paused
                            for (int y = 0; y < cellNum; y++) {
                                for (int x = 0; x < cellNum; x++) {
                                    cells[x][y] = false;
                                }
                            }
                            if (!paused) {
                                paused = true;
                            }
                            break;
                        case "f": // fill all cells and pause if not paused
                            for (int y = 0; y < cellNum; y++) {
                                for (int x = 0; x < cellNum; x++) {
                                    cells[x][y] = true;
                                }
                                if (!paused) {
                                    paused = true;
                                }
                            }
                            break;
                        case "i": // invert all cells and pause if not paused
                            for (int y = 0; y < cellNum; y++) {
                                for (int x = 0; x < cellNum; x++) {
                                    cells[x][y] = !cells[x][y];
                                }
                                if (!paused) {
                                    paused = true;
                                }
                            }
                            break;
                        case "l": // lock all cells that have a live/true cell
                            for (int y = 0; y < cellNum; y++) {
                                for (int x = 0; x < cellNum; x++) {
                                    if (cells[x][y]) {
                                        //set Lock
                                    }
                                }
                            }
                            break;
                        case "p": // pause/play
                            paused = !paused;
                            break;
                        case "s": // step once
                            step = true;
                            break;
                        case "h": // hide current cursor position
                            hideCurrentPos = !hideCurrentPos;
                            break;
//                        default:
                    }
                }
            }

            for (KeyNoMask key : KeyNoMask.values()) { //  run through ALL of the keys (this is the beauty of key bindings - you can move the cursor diagonally). I kinda like a pause after the first key press, though
                if (keyNoMaskMap.get(key)) { // if key in HashMap is true (i.e. the actionPerformed() above returned true)
                    switch(key.toString()) { // move cursor position appropriately and pause if not paused
                        case "down":
                            currentY += currentY == cellNum - 1 ? 0 : 1;
                            if (!paused) {
                                paused = true;
                            }
                            break;
                        case "up":
                            currentY -= currentY == 0 ? 0 : 1;
                            if (!paused) {
                                paused = true;
                            }
                            break;
                        case "left":
                            currentX -= currentX == 0 ? 0 : 1;
                            if (!paused) {
                                paused = true;
                            }

                            break;
                        case "right":
                            currentX += currentX == cellNum - 1 ? 0 : 1;
                            if (!paused) {
                                paused = true;
                            }
                            break;
                        case "space": // flip pixel at current cursor position
                            flipCell(currentX, currentY);
                            if (!paused) {
                                paused = true;
                            }
//                        default:
                    }
                }
            }
        }
    }

It's a lot of code, but it's pretty standard for KeyBindings at least. So, I was wondering if there was a way to get around this. Is this the os's fault or is it Java's fault and how can I fix it. I would like to avoid having an else in actionPerformed() because I need this to be fast. Also, is there anyway to optimize the actionPerformed() method because it seems like it might be a little bit screwy.

I just put this together, but it doesn't do it here! Small executable:

package bindingstest;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.HashMap;
import java.util.Map;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;

/**
 *
 * @author Dylan AND Hovercraft Full Of Eels
 */
public class BindingsTest {
    static Map<Key, Boolean> keyMap = new HashMap<>();

    enum Key { // possibly used in conjunction with mask in order to prevent keyboard mishaps - it will probably be ALT in FUTURE
        a(KeyEvent.VK_A),
        b(KeyEvent.VK_B),
        c(KeyEvent.VK_C),
        d(KeyEvent.VK_D),
        e(KeyEvent.VK_E),
        f(KeyEvent.VK_F);
        private final int keyCode;

        private Key(int keyCode) {
            this.keyCode = keyCode; // KeyEvent.VK_...
        }

        public int getKeyCode() {
            return keyCode;
        }
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {

        JFrame frame = new JFrame();
        frame.setVisible(true);
        frame.setBounds(50, 50, 1000, 1000);

        JPanel panel = new JPanel();
        panel.setFocusable(true);
        panel.requestFocusInWindow();

        for (Key key : Key.values()) {
            keyMap.put(key, false);
        }

        InputMap inMap = panel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
        ActionMap actMap = panel.getActionMap();

        for (final Key key : Key.values()) {
            KeyStroke pressed = KeyStroke.getKeyStroke(key.getKeyCode(), ActionEvent.ALT_MASK, false);
            KeyStroke released = KeyStroke.getKeyStroke(key.getKeyCode(), ActionEvent.ALT_MASK, true);

            inMap.put(pressed, key.toString() + "pressed");
            inMap.put(released, key.toString() + "released");

            actMap.put(key.toString() + "pressed", new AbstractAction() {

                private static final long serialVersionUID = 1L;

                @Override
                public void actionPerformed(ActionEvent e) {
                    keyMap.put(key, true);
                }
            });

            actMap.put(key.toString() + "released", new AbstractAction() {

                private static final long serialVersionUID = 1L;

                @Override
                public void actionPerformed(ActionEvent e) {
                    keyMap.put(key, false);
                }
            });
        }

        for (final Key key : Key.values()) {
            KeyStroke pressed = KeyStroke.getKeyStroke(key.getKeyCode(), 0, false);
            KeyStroke released = KeyStroke.getKeyStroke(key.getKeyCode(), 0, true);
            inMap.put(pressed, key.toString() + "pressed");
            inMap.put(released, key.toString() + "released");
            actMap.put(key.toString() + "pressed", new AbstractAction() {

                private static final long serialVersionUID = 1L;

                @Override
                public void actionPerformed(ActionEvent e) {
                    keyMap.put(key, true);
                }
            });
            actMap.put(key.toString() + "released", new AbstractAction() {

                private static final long serialVersionUID = 1L;

                @Override
                public void actionPerformed(ActionEvent e) {
                    keyMap.put(key, false);
                }
            });
        }

        Timer timer = new Timer(100, new KeyListener());
        timer.start();

        frame.add(panel);
    }

    private static class KeyListener implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            for (Key key : Key.values()) { // run through the ALL of the keys
                if (keyMap.get(key)) { // if key in HashMap is true (i.e. the actionPerformed() above set it true)
                    switch(key.toString()) {
                        case "a":
                            System.out.println("a");
                            break;
                        case "b":
                            System.out.println("b");
                            break;
                        case "c":
                            System.out.println("c");
                            break;
                        case "d":
                            System.out.println("d");
                            break;
                        case "e":
                            System.out.println("e");
                            break;
                        case "f":
                            System.out.println("f");
                    }
                }
            }
        }
    }
}
like image 701
dylnmc Avatar asked Aug 10 '14 19:08

dylnmc


1 Answers

OK, I see what you're saying, and thanks for posting compilable code. One solution to use both release KeyStrokes, one for alt-key and one for plain key. For example,

  InputMap inMap = panel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
  ActionMap actMap = panel.getActionMap();

  for (final Key key : Key.values()) {
     KeyStroke altPressed = KeyStroke.getKeyStroke(key.getKeyCode(),
           InputEvent.ALT_DOWN_MASK, false);
     KeyStroke altReleased = KeyStroke.getKeyStroke(key.getKeyCode(),
           InputEvent.ALT_DOWN_MASK, true);
     KeyStroke released  = KeyStroke.getKeyStroke(key.getKeyCode(),
           0, true);

     inMap.put(altPressed, altPressed.toString());
     inMap.put(altReleased, altReleased.toString());
     inMap.put(released, released.toString());

     actMap.put(altPressed.toString(), new AbstractAction() {

        private static final long serialVersionUID = 1L;

        @Override
        public void actionPerformed(ActionEvent e) {
           keyMap.put(key, true);
        }
     });

     Action releaseAction = new AbstractAction() {

        private static final long serialVersionUID = 1L;

        @Override
        public void actionPerformed(ActionEvent e) {
           keyMap.put(key, false);
        }
     };

     actMap.put(altReleased.toString(), releaseAction);
     actMap.put(released.toString(), releaseAction);

Another solution is to not do the above, but rather to re-set the Map with each iteration of the Timer:

     for (Key key : Key.values()) { // run through the ALL of the keys
        if (keyMap.get(key)) { // if key in HashMap is true (i.e. the
                               // actionPerformed() above set it true)
           switch (key.toString()) {
           case "a":
              System.out.println("a");
              break;
           case "b":
              System.out.println("b");
              break;
           case "c":
              System.out.println("c");
              break;
           case "d":
              System.out.println("d");
              break;
           case "e":
              System.out.println("e");
              break;
           case "f":
              System.out.println("f");
           }

           // ***** add this *****
           keyMap.put(key, Boolean.FALSE);
        }
     }

This second solution suffers from the delay that the OS puts in keystroke submission when the key is held down.

like image 136
Hovercraft Full Of Eels Avatar answered Nov 06 '22 15:11

Hovercraft Full Of Eels