001 // License: GPL. For details, see LICENSE file.
002 package org.openstreetmap.josm.tools;
003
004 import static org.openstreetmap.josm.tools.I18n.tr;
005
006 import java.awt.KeyEventDispatcher;
007 import java.awt.KeyboardFocusManager;
008 import java.awt.event.ActionEvent;
009 import java.awt.event.ActionListener;
010 import java.awt.event.KeyEvent;
011 import java.util.HashMap;
012 import java.util.Map;
013 import java.util.Timer;
014 import java.util.TimerTask;
015
016 import javax.swing.AbstractAction;
017 import javax.swing.Action;
018 import javax.swing.JMenuItem;
019 import javax.swing.JPanel;
020 import javax.swing.JPopupMenu;
021 import javax.swing.KeyStroke;
022 import javax.swing.SwingUtilities;
023 import javax.swing.event.PopupMenuEvent;
024 import javax.swing.event.PopupMenuListener;
025
026 import org.openstreetmap.josm.Main;
027 import org.openstreetmap.josm.tools.MultikeyShortcutAction.MultikeyInfo;
028
029 public class MultikeyActionsHandler {
030
031 private static final long DIALOG_DELAY = 1000;
032 private static final String STATUS_BAR_ID = new String("multikeyShortcut");
033
034 private Map<MultikeyShortcutAction, MyAction> myActions = new HashMap<MultikeyShortcutAction,MyAction>();
035
036 private class MyKeyEventDispatcher implements KeyEventDispatcher {
037 @Override
038 public boolean dispatchKeyEvent(KeyEvent e) {
039
040 if (e.getWhen() == lastTimestamp)
041 return false;
042
043 if (lastAction != null && e.getID() == KeyEvent.KEY_PRESSED) {
044 int index = getIndex(e.getKeyCode());
045 if (index >= 0) {
046 lastAction.action.executeMultikeyAction(index, e.getKeyCode() == lastAction.shortcut.getKeyStroke().getKeyCode());
047 }
048 lastAction = null;
049 Main.map.statusLine.resetHelpText(STATUS_BAR_ID);
050 return true;
051 }
052 return false;
053 }
054
055 private int getIndex(int lastKey) {
056 if (lastKey >= KeyEvent.VK_1 && lastKey <= KeyEvent.VK_9)
057 return lastKey - KeyEvent.VK_1;
058 else if (lastKey == KeyEvent.VK_0)
059 return 9;
060 else if (lastKey >= KeyEvent.VK_A && lastKey <= KeyEvent.VK_Z)
061 return lastKey - KeyEvent.VK_A + 10;
062 else
063 return -1;
064 }
065 }
066
067 private class MyAction extends AbstractAction {
068
069 final MultikeyShortcutAction action;
070 final Shortcut shortcut;
071
072 MyAction(MultikeyShortcutAction action) {
073 this.action = action;
074 this.shortcut = action.getMultikeyShortcut();
075 }
076
077 @Override
078 public void actionPerformed(ActionEvent e) {
079 lastTimestamp = e.getWhen();
080 lastAction = this;
081 timer.schedule(new MyTimerTask(lastTimestamp, lastAction), DIALOG_DELAY);
082 Main.map.statusLine.setHelpText(STATUS_BAR_ID, tr("{0}... [please type its number]", (String) action.getValue(SHORT_DESCRIPTION)));
083 }
084
085 @Override
086 public String toString() {
087 return "MultikeyAction" + action.toString();
088 }
089 }
090
091 private class MyTimerTask extends TimerTask {
092 private final long lastTimestamp;
093 private final MyAction lastAction;
094
095 MyTimerTask(long lastTimestamp, MyAction lastAction) {
096 this.lastTimestamp = lastTimestamp;
097 this.lastAction = lastAction;
098 }
099
100 @Override
101 public void run() {
102 if (lastTimestamp == MultikeyActionsHandler.this.lastTimestamp &&
103 lastAction == MultikeyActionsHandler.this.lastAction) {
104 showLayersPopup(lastAction);
105 MultikeyActionsHandler.this.lastAction = null;
106 }
107 }
108 }
109
110 private long lastTimestamp;
111 private MyAction lastAction;
112 private Timer timer;
113
114
115 private MultikeyActionsHandler() {
116 KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(new MyKeyEventDispatcher());
117 timer =new Timer();
118 }
119
120 private static MultikeyActionsHandler instance;
121
122 public static MultikeyActionsHandler getInstance() {
123 if (instance == null) {
124 instance = new MultikeyActionsHandler();
125 }
126 return instance;
127 }
128
129 private String formatMenuText(KeyStroke keyStroke, String index, String description) {
130 String shortcutText = KeyEvent.getKeyModifiersText(keyStroke.getModifiers()) + "+" + KeyEvent.getKeyText(keyStroke.getKeyCode()) + "," + index;
131
132 return "<html><i>" + shortcutText + "</i> " + description;
133
134 }
135
136 private void showLayersPopup(final MyAction action) {
137 SwingUtilities.invokeLater(new Runnable() {
138 @Override
139 public void run() {
140 JPopupMenu layers = new JPopupMenu();
141
142 JMenuItem lbTitle = new JMenuItem((String) action.action.getValue(Action.SHORT_DESCRIPTION));
143 lbTitle.setEnabled(false);
144 JPanel pnTitle = new JPanel();
145 pnTitle.add(lbTitle);
146 layers.add(pnTitle);
147
148 char repeatKey = (char) action.shortcut.getKeyStroke().getKeyCode();
149 boolean repeatKeyUsed = false;
150
151
152 for (final MultikeyInfo info: action.action.getMultikeyCombinations()) {
153
154 if (info.getShortcut() == repeatKey) {
155 repeatKeyUsed = true;
156 }
157
158 JMenuItem item = new JMenuItem(formatMenuText(action.shortcut.getKeyStroke(), String.valueOf(info.getShortcut()), info.getDescription()));
159 item.setMnemonic(info.getShortcut());
160 item.addActionListener(new ActionListener() {
161 @Override
162 public void actionPerformed(ActionEvent e) {
163 action.action.executeMultikeyAction(info.getIndex(), false);
164 }
165 });
166 layers.add(item);
167 }
168
169 if (!repeatKeyUsed) {
170 MultikeyInfo lastLayer = action.action.getLastMultikeyAction();
171 if (lastLayer != null) {
172 JMenuItem repeateItem = new JMenuItem(formatMenuText(action.shortcut.getKeyStroke(),
173 KeyEvent.getKeyText(action.shortcut.getKeyStroke().getKeyCode()),
174 "Repeat " + lastLayer.getDescription()));
175 repeateItem.setMnemonic(action.shortcut.getKeyStroke().getKeyCode());
176 repeateItem.addActionListener(new ActionListener() {
177 @Override
178 public void actionPerformed(ActionEvent e) {
179 action.action.executeMultikeyAction(-1, true);
180 }
181 });
182 layers.add(repeateItem);
183 }
184 }
185 layers.addPopupMenuListener(new PopupMenuListener() {
186
187 @Override
188 public void popupMenuWillBecomeVisible(PopupMenuEvent e) {}
189
190 @Override
191 public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
192 Main.map.statusLine.resetHelpText(STATUS_BAR_ID);
193 }
194
195 @Override
196 public void popupMenuCanceled(PopupMenuEvent e) {}
197 });
198
199 layers.show(Main.parent, Integer.MAX_VALUE, Integer.MAX_VALUE);
200 layers.setLocation(Main.parent.getX() + Main.parent.getWidth() - layers.getWidth(), Main.parent.getY() + Main.parent.getHeight() - layers.getHeight());
201 }
202 });
203 }
204
205 public void addAction(MultikeyShortcutAction action) {
206 if(action.getMultikeyShortcut() != null) {
207 MyAction myAction = new MyAction(action);
208 myActions.put(action, myAction);
209 Main.registerActionShortcut(myAction, myAction.shortcut);
210 }
211 }
212
213 // unregister action and its shortcut completely
214 public void removeAction(MultikeyShortcutAction action) {
215 MyAction a = myActions.get(action);
216 if (a!=null) {
217 Main.unregisterActionShortcut(a, a.shortcut);
218 myActions.remove(action);
219 }
220 }
221 }