001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.util;
003
004import java.awt.AWTEvent;
005import java.awt.Component;
006import java.awt.KeyboardFocusManager;
007import java.awt.Toolkit;
008import java.awt.event.AWTEventListener;
009import java.awt.event.ActionEvent;
010import java.awt.event.ActionListener;
011import java.awt.event.KeyEvent;
012import java.util.ArrayList;
013import java.util.Set;
014import java.util.TreeSet;
015import javax.swing.JFrame;
016import javax.swing.SwingUtilities;
017import javax.swing.Timer;
018
019import org.openstreetmap.josm.Main;
020
021import static org.openstreetmap.josm.tools.I18n.tr;
022/**
023 * Helper object that allows cross-platform detection of key press and realease events
024 * instance is available globally as Main.map.keyDetector
025 * @since 7217
026 */
027public class AdvancedKeyPressDetector implements AWTEventListener {
028
029    // events for crossplatform key holding processing
030    // thanks to http://www.arco.in-berlin.de/keyevent.html
031    private final Set<Integer> set = new TreeSet<>();
032    private KeyEvent releaseEvent;
033    private Timer timer;
034
035    private final ArrayList<KeyPressReleaseListener> keyListeners = new ArrayList<>();
036    private final ArrayList<ModifierListener> modifierListeners = new ArrayList<>();
037    private int previousModifiers;
038
039    /**
040     * Adds an object that wants to receive key press and release events
041     */
042    public void addKeyListener(KeyPressReleaseListener l) {
043        keyListeners.add(l);
044    }
045
046    /**
047     * Adds an object that wants to receive key modifier changed events
048     */
049    public void addModifierListener(ModifierListener l) {
050        modifierListeners.add(l);
051    }
052
053    /**
054     * Removes the listener
055     */
056    public void removeKeyListener(KeyPressReleaseListener l) {
057        keyListeners.remove(l);
058    }
059
060    /**
061     * Removes the key modifier listener
062     */
063    public void removeModifierListener(ModifierListener l) {
064        modifierListeners.remove(l);
065    }
066
067
068    /**
069     * Register this object as AWTEventListener
070     */
071    public void register() {
072        try {
073            Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.KEY_EVENT_MASK);
074        } catch (SecurityException ex) {
075            Main.warn(ex);
076        }
077        timer = new Timer(0, new ActionListener() {
078            @Override
079            public void actionPerformed(ActionEvent e) {
080                timer.stop();
081                if (set.remove(releaseEvent.getKeyCode())) {
082                    for (KeyPressReleaseListener q: keyListeners) {
083                        q.doKeyReleased(releaseEvent);
084                    }
085                }
086            }
087        });
088    }
089
090    /**
091     * Unregister this object as AWTEventListener
092     * lists of listeners are not cleared!
093     */
094    public void unregister() {
095        timer.stop(); set.clear();
096        if (!keyListeners.isEmpty()) {
097            Main.warn(tr("Some of the key listeners forgot to remove themselves: {0}"), keyListeners.toString());
098        }
099        if (!modifierListeners.isEmpty()) {
100            Main.warn(tr("Some of the key modifier listeners forgot to remove themselves: {0}"), modifierListeners.toString());
101        }
102        try {
103            Toolkit.getDefaultToolkit().removeAWTEventListener(this);
104        } catch (SecurityException ex) {
105            Main.warn(ex);
106        }
107    }
108
109    private void processKeyEvent(KeyEvent e) {
110        if (e.getID() == KeyEvent.KEY_PRESSED) {
111            if (timer.isRunning()) {
112                timer.stop();
113            } else if (set.add((e.getKeyCode()))) {
114                for (KeyPressReleaseListener q: keyListeners) {
115                    q.doKeyPressed(e);
116                }
117            }
118        } else if (e.getID() == KeyEvent.KEY_RELEASED) {
119            if (timer.isRunning()) {
120                timer.stop();
121                if (set.remove(e.getKeyCode())) {
122                    for (KeyPressReleaseListener q: keyListeners) {
123                        q.doKeyReleased(e);
124                    }
125                }
126            } else {
127                releaseEvent = e;
128                timer.restart();
129            }
130        }
131    }
132
133    @Override
134    public void eventDispatched(AWTEvent e) {
135        if (!(e instanceof KeyEvent)) {
136            return;
137        }
138        KeyEvent ke = (KeyEvent) e;
139
140        // check if ctrl, alt, shift modifiers are changed
141        int modif = ke.getModifiers();
142        if (previousModifiers != modif) {
143            previousModifiers = modif;
144            for (ModifierListener m: modifierListeners) {
145                m.modifiersChanged(modif);
146            }
147        }
148
149        // check if key press is done in main window, not in dialogs
150        Component focused = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
151        if (SwingUtilities.getWindowAncestor(focused) instanceof JFrame) {
152            processKeyEvent(ke);
153        }
154    }
155
156    /**
157     * Allows to determine if the key with specific code is pressed now
158     * @param keyCode the key code, for example KeyEvent.VK_ENTER
159     * @return true if the key is pressed now
160     */
161    public boolean isKeyPressed(int keyCode) {
162        return set.contains(keyCode);
163    }
164}