[7937] | 1 | // License: GPL. For details, see LICENSE file.
|
---|
| 2 | package org.openstreetmap.josm.gui.util;
|
---|
| 3 |
|
---|
| 4 | import static org.openstreetmap.josm.tools.I18n.tr;
|
---|
| 5 |
|
---|
| 6 | import java.awt.AWTEvent;
|
---|
| 7 | import java.awt.Component;
|
---|
| 8 | import java.awt.KeyboardFocusManager;
|
---|
| 9 | import java.awt.Toolkit;
|
---|
| 10 | import java.awt.event.AWTEventListener;
|
---|
| 11 | import java.awt.event.KeyEvent;
|
---|
[8338] | 12 | import java.util.List;
|
---|
[7937] | 13 | import java.util.Set;
|
---|
| 14 | import java.util.TreeSet;
|
---|
[8489] | 15 | import java.util.concurrent.CopyOnWriteArrayList;
|
---|
[7937] | 16 |
|
---|
| 17 | import javax.swing.JFrame;
|
---|
| 18 | import javax.swing.SwingUtilities;
|
---|
| 19 | import javax.swing.Timer;
|
---|
| 20 |
|
---|
[12518] | 21 | import org.openstreetmap.josm.tools.ListenerList;
|
---|
[12620] | 22 | import org.openstreetmap.josm.tools.Logging;
|
---|
[7937] | 23 |
|
---|
| 24 | /**
|
---|
[8072] | 25 | * Helper object that allows cross-platform detection of key press and release events
|
---|
| 26 | * instance is available globally as {@code Main.map.keyDetector}.
|
---|
[7937] | 27 | * @since 7217
|
---|
| 28 | */
|
---|
| 29 | public class AdvancedKeyPressDetector implements AWTEventListener {
|
---|
| 30 |
|
---|
| 31 | // events for crossplatform key holding processing
|
---|
| 32 | // thanks to http://www.arco.in-berlin.de/keyevent.html
|
---|
| 33 | private final Set<Integer> set = new TreeSet<>();
|
---|
| 34 | private KeyEvent releaseEvent;
|
---|
| 35 | private Timer timer;
|
---|
| 36 |
|
---|
[8489] | 37 | private final List<KeyPressReleaseListener> keyListeners = new CopyOnWriteArrayList<>();
|
---|
[12522] | 38 | /** @deprecated replaced by {@link #modifierExListeners} */
|
---|
[12517] | 39 | @Deprecated
|
---|
[8489] | 40 | private final List<ModifierListener> modifierListeners = new CopyOnWriteArrayList<>();
|
---|
[12518] | 41 | private final ListenerList<ModifierExListener> modifierExListeners = ListenerList.create();
|
---|
[12522] | 42 | /** @deprecated replaced by {@link #previousModifiersEx} */
|
---|
[12518] | 43 | @Deprecated
|
---|
[7937] | 44 | private int previousModifiers;
|
---|
[12517] | 45 | private int previousModifiersEx;
|
---|
[7937] | 46 |
|
---|
| 47 | private boolean enabled = true;
|
---|
| 48 |
|
---|
| 49 | /**
|
---|
| 50 | * Adds an object that wants to receive key press and release events.
|
---|
| 51 | * @param l listener to add
|
---|
| 52 | */
|
---|
[8489] | 53 | public void addKeyListener(KeyPressReleaseListener l) {
|
---|
[7937] | 54 | keyListeners.add(l);
|
---|
| 55 | }
|
---|
| 56 |
|
---|
| 57 | /**
|
---|
| 58 | * Adds an object that wants to receive key modifier changed events.
|
---|
| 59 | * @param l listener to add
|
---|
[12517] | 60 | * @deprecated use {@link #addModifierExListener} instead
|
---|
[7937] | 61 | */
|
---|
[12517] | 62 | @Deprecated
|
---|
[8489] | 63 | public void addModifierListener(ModifierListener l) {
|
---|
[7937] | 64 | modifierListeners.add(l);
|
---|
| 65 | }
|
---|
| 66 |
|
---|
| 67 | /**
|
---|
[12517] | 68 | * Adds an object that wants to receive extended key modifier changed events.
|
---|
| 69 | * @param l listener to add
|
---|
[12518] | 70 | * @since 12517
|
---|
[12517] | 71 | */
|
---|
| 72 | public void addModifierExListener(ModifierExListener l) {
|
---|
[12518] | 73 | modifierExListeners.addListener(l);
|
---|
[12517] | 74 | }
|
---|
| 75 |
|
---|
| 76 | /**
|
---|
[7937] | 77 | * Removes the listener.
|
---|
| 78 | * @param l listener to remove
|
---|
| 79 | */
|
---|
[8489] | 80 | public void removeKeyListener(KeyPressReleaseListener l) {
|
---|
[7937] | 81 | keyListeners.remove(l);
|
---|
| 82 | }
|
---|
| 83 |
|
---|
| 84 | /**
|
---|
| 85 | * Removes the key modifier listener.
|
---|
| 86 | * @param l listener to remove
|
---|
[12517] | 87 | * @deprecated use {@link #removeModifierExListener} instead
|
---|
[7937] | 88 | */
|
---|
[12517] | 89 | @Deprecated
|
---|
[8489] | 90 | public void removeModifierListener(ModifierListener l) {
|
---|
[7937] | 91 | modifierListeners.remove(l);
|
---|
| 92 | }
|
---|
| 93 |
|
---|
| 94 | /**
|
---|
[12517] | 95 | * Removes the extended key modifier listener.
|
---|
| 96 | * @param l listener to remove
|
---|
[12518] | 97 | * @since 12517
|
---|
[12517] | 98 | */
|
---|
| 99 | public void removeModifierExListener(ModifierExListener l) {
|
---|
[12518] | 100 | modifierExListeners.removeListener(l);
|
---|
[12517] | 101 | }
|
---|
| 102 |
|
---|
| 103 | /**
|
---|
[7937] | 104 | * Register this object as AWTEventListener
|
---|
| 105 | */
|
---|
| 106 | public void register() {
|
---|
| 107 | try {
|
---|
| 108 | Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.KEY_EVENT_MASK);
|
---|
| 109 | } catch (SecurityException ex) {
|
---|
[12620] | 110 | Logging.warn(ex);
|
---|
[7937] | 111 | }
|
---|
[10611] | 112 | timer = new Timer(0, e -> {
|
---|
| 113 | timer.stop();
|
---|
| 114 | if (set.remove(releaseEvent.getKeyCode()) && enabled && isFocusInMainWindow()) {
|
---|
| 115 | for (KeyPressReleaseListener q: keyListeners) {
|
---|
| 116 | q.doKeyReleased(releaseEvent);
|
---|
[7937] | 117 | }
|
---|
| 118 | }
|
---|
| 119 | });
|
---|
| 120 | }
|
---|
| 121 |
|
---|
| 122 | /**
|
---|
| 123 | * Unregister this object as AWTEventListener
|
---|
| 124 | * lists of listeners are not cleared!
|
---|
| 125 | */
|
---|
| 126 | public void unregister() {
|
---|
[8537] | 127 | if (timer != null) {
|
---|
| 128 | timer.stop();
|
---|
| 129 | }
|
---|
[7937] | 130 | set.clear();
|
---|
[8489] | 131 | if (!keyListeners.isEmpty()) {
|
---|
[12620] | 132 | Logging.warn(tr("Some of the key listeners forgot to remove themselves: {0}"), keyListeners.toString());
|
---|
[7937] | 133 | }
|
---|
[8489] | 134 | if (!modifierListeners.isEmpty()) {
|
---|
[12620] | 135 | Logging.warn(tr("Some of the key modifier listeners forgot to remove themselves: {0}"), modifierListeners.toString());
|
---|
[8489] | 136 | }
|
---|
[12518] | 137 | if (modifierExListeners.hasListeners()) {
|
---|
[12620] | 138 | Logging.warn(tr("Some of the key modifier listeners forgot to remove themselves: {0}"), modifierExListeners.toString());
|
---|
[12517] | 139 | }
|
---|
[7937] | 140 | try {
|
---|
| 141 | Toolkit.getDefaultToolkit().removeAWTEventListener(this);
|
---|
| 142 | } catch (SecurityException ex) {
|
---|
[12620] | 143 | Logging.warn(ex);
|
---|
[7937] | 144 | }
|
---|
| 145 | }
|
---|
| 146 |
|
---|
| 147 | private void processKeyEvent(KeyEvent e) {
|
---|
[12620] | 148 | if (Logging.isTraceEnabled()) {
|
---|
| 149 | Logging.trace("AdvancedKeyPressDetector enabled={0} => processKeyEvent({1}) from {2}",
|
---|
| 150 | enabled, e, new Exception().getStackTrace()[2]);
|
---|
[8441] | 151 | }
|
---|
[7937] | 152 | if (e.getID() == KeyEvent.KEY_PRESSED) {
|
---|
| 153 | if (timer.isRunning()) {
|
---|
| 154 | timer.stop();
|
---|
[11386] | 155 | } else if (set.add(e.getKeyCode()) && enabled && isFocusInMainWindow()) {
|
---|
| 156 | for (KeyPressReleaseListener q: keyListeners) {
|
---|
[12620] | 157 | Logging.trace("{0} => doKeyPressed({1})", q, e);
|
---|
[11386] | 158 | q.doKeyPressed(e);
|
---|
[7937] | 159 | }
|
---|
| 160 | }
|
---|
| 161 | } else if (e.getID() == KeyEvent.KEY_RELEASED) {
|
---|
| 162 | if (timer.isRunning()) {
|
---|
| 163 | timer.stop();
|
---|
[11386] | 164 | if (set.remove(e.getKeyCode()) && enabled && isFocusInMainWindow()) {
|
---|
| 165 | for (KeyPressReleaseListener q: keyListeners) {
|
---|
[12620] | 166 | Logging.trace("{0} => doKeyReleased({1})", q, e);
|
---|
[11386] | 167 | q.doKeyReleased(e);
|
---|
[7937] | 168 | }
|
---|
| 169 | }
|
---|
| 170 | } else {
|
---|
| 171 | releaseEvent = e;
|
---|
| 172 | timer.restart();
|
---|
| 173 | }
|
---|
| 174 | }
|
---|
| 175 | }
|
---|
| 176 |
|
---|
| 177 | @Override
|
---|
[12517] | 178 | @SuppressWarnings("deprecation")
|
---|
[7937] | 179 | public void eventDispatched(AWTEvent e) {
|
---|
| 180 | if (!(e instanceof KeyEvent)) {
|
---|
| 181 | return;
|
---|
| 182 | }
|
---|
| 183 | KeyEvent ke = (KeyEvent) e;
|
---|
| 184 |
|
---|
| 185 | // check if ctrl, alt, shift modifiers are changed
|
---|
| 186 | int modif = ke.getModifiers();
|
---|
| 187 | if (previousModifiers != modif) {
|
---|
| 188 | previousModifiers = modif;
|
---|
[8489] | 189 | for (ModifierListener m: modifierListeners) {
|
---|
| 190 | m.modifiersChanged(modif);
|
---|
[7937] | 191 | }
|
---|
| 192 | }
|
---|
| 193 |
|
---|
[12517] | 194 | // check if ctrl, alt, shift extended modifiers are changed
|
---|
| 195 | int modifEx = ke.getModifiersEx();
|
---|
| 196 | if (previousModifiersEx != modifEx) {
|
---|
| 197 | previousModifiersEx = modifEx;
|
---|
[12518] | 198 | modifierExListeners.fireEvent(m -> m.modifiersExChanged(modifEx));
|
---|
[12517] | 199 | }
|
---|
| 200 |
|
---|
[7937] | 201 | processKeyEvent(ke);
|
---|
| 202 | }
|
---|
| 203 |
|
---|
| 204 | /**
|
---|
| 205 | * Allows to determine if the key with specific code is pressed now
|
---|
| 206 | * @param keyCode the key code, for example KeyEvent.VK_ENTER
|
---|
| 207 | * @return true if the key is pressed now
|
---|
| 208 | */
|
---|
| 209 | public boolean isKeyPressed(int keyCode) {
|
---|
| 210 | return set.contains(keyCode);
|
---|
| 211 | }
|
---|
| 212 |
|
---|
| 213 | /**
|
---|
| 214 | * Sets the enabled state of the key detector. We need to disable it when text fields that disable
|
---|
| 215 | * shortcuts gain focus.
|
---|
| 216 | * @param enabled if {@code true}, enables this key detector. If {@code false}, disables it
|
---|
| 217 | * @since 7539
|
---|
| 218 | */
|
---|
| 219 | public final void setEnabled(boolean enabled) {
|
---|
| 220 | this.enabled = enabled;
|
---|
[12620] | 221 | if (Logging.isTraceEnabled()) {
|
---|
| 222 | Logging.trace("AdvancedKeyPressDetector enabled={0} from {1}", enabled, new Exception().getStackTrace()[1]);
|
---|
[8441] | 223 | }
|
---|
[7937] | 224 | }
|
---|
| 225 |
|
---|
[8870] | 226 | private static boolean isFocusInMainWindow() {
|
---|
[7937] | 227 | Component focused = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
|
---|
| 228 | return focused != null && SwingUtilities.getWindowAncestor(focused) instanceof JFrame;
|
---|
| 229 | }
|
---|
| 230 | }
|
---|