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}