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 }