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