source: josm/trunk/src/org/openstreetmap/josm/tools/Shortcut.java@ 6113

Last change on this file since 6113 was 5979, checked in by akks, 11 years ago

fix #8652 [based on patch by cmuelle8] enable dialog and panels hiding by TAB
disable TAB for angle snapping by default (please use A-A or assign different key)

  • Property svn:eol-style set to native
File size: 17.7 KB
Line 
1//License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.tools;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.event.KeyEvent;
7import java.util.ArrayList;
8import java.util.Arrays;
9import java.util.HashMap;
10import java.util.LinkedHashMap;
11import java.util.LinkedList;
12import java.util.List;
13import java.util.Map;
14
15import javax.swing.AbstractAction;
16import javax.swing.AbstractButton;
17import javax.swing.JMenu;
18import javax.swing.KeyStroke;
19
20import org.openstreetmap.josm.Main;
21
22/**
23 * Global shortcut class.
24 *
25 * Note: This class represents a single shortcut, contains the factory to obtain
26 * shortcut objects from, manages shortcuts and shortcut collisions, and
27 * finally manages loading and saving shortcuts to/from the preferences.
28 *
29 * Action authors: You only need the {@link #registerShortcut} factory. Ignore everything
30 * else.
31 *
32 * All: Use only public methods that are also marked to be used. The others are
33 * public so the shortcut preferences can use them.
34 *
35 */
36public class Shortcut {
37 private String shortText; // the unique ID of the shortcut
38 private String longText; // a human readable description that will be shown in the preferences
39 private int requestedKey; // the key, the caller requested
40 private int requestedGroup; // the group, the caller requested
41 private int assignedKey; // the key that actually is used
42 private int assignedModifier; // the modifiers that are used
43 private boolean assignedDefault; // true if it got assigned what was requested. (Note: modifiers will be ignored in favour of group when loading it from the preferences then.)
44 private boolean assignedUser; // true if the user changed this shortcut
45 private boolean automatic; // true if the user cannot change this shortcut (Note: it also will not be saved into the preferences)
46 private boolean reset; // true if the user requested this shortcut to be set to its default value (will happen on next restart, as this shortcut will not be saved to the preferences)
47
48 // simple constructor
49 private Shortcut(String shortText, String longText, int requestedKey, int requestedGroup, int assignedKey, int assignedModifier, boolean assignedDefault, boolean assignedUser) {
50 this.shortText = shortText;
51 this.longText = longText;
52 this.requestedKey = requestedKey;
53 this.requestedGroup = requestedGroup;
54 this.assignedKey = assignedKey;
55 this.assignedModifier = assignedModifier;
56 this.assignedDefault = assignedDefault;
57 this.assignedUser = assignedUser;
58 this.automatic = false;
59 this.reset = false;
60 }
61
62 public String getShortText() {
63 return shortText;
64 }
65
66 public String getLongText() {
67 return longText;
68 }
69
70 // a shortcut will be renamed when it is handed out again, because the original name
71 // may be a dummy
72 private void setLongText(String longText) {
73 this.longText = longText;
74 }
75
76 private int getRequestedKey() {
77 return requestedKey;
78 }
79
80 public int getRequestedGroup() {
81 return requestedGroup;
82 }
83
84 public int getAssignedKey() {
85 return assignedKey;
86 }
87
88 public int getAssignedModifier() {
89 return assignedModifier;
90 }
91
92 public boolean getAssignedDefault() {
93 return assignedDefault;
94 }
95
96 public boolean getAssignedUser() {
97 return assignedUser;
98 }
99
100 public boolean getAutomatic() {
101 return automatic;
102 }
103
104 public boolean isChangeable() {
105 return !automatic && !shortText.equals("core:none");
106 }
107
108 private boolean getReset() {
109 return reset;
110 }
111
112 /**
113 * FOR PREF PANE ONLY
114 */
115 public void setAutomatic() {
116 automatic = true;
117 }
118
119 /**
120 * FOR PREF PANE ONLY
121 */
122 public void setAssignedModifier(int assignedModifier) {
123 this.assignedModifier = assignedModifier;
124 }
125
126 /**
127 * FOR PREF PANE ONLY
128 */
129 public void setAssignedKey(int assignedKey) {
130 this.assignedKey = assignedKey;
131 }
132
133 /**
134 * FOR PREF PANE ONLY
135 */
136 public void setAssignedUser(boolean assignedUser) {
137 this.reset = (this.assignedUser || reset) && !assignedUser;
138 if (assignedUser) {
139 assignedDefault = false;
140 } else if (reset) {
141 assignedKey = requestedKey;
142 assignedModifier = findModifier(requestedGroup, null);
143 }
144 this.assignedUser = assignedUser;
145 }
146
147 /**
148 * Use this to register the shortcut with Swing
149 */
150 public KeyStroke getKeyStroke() {
151 if (assignedModifier != -1)
152 return KeyStroke.getKeyStroke(assignedKey, assignedModifier);
153 return null;
154 }
155
156 // create a shortcut object from an string as saved in the preferences
157 private Shortcut(String prefString) {
158 ArrayList<String> s = (new ArrayList<String>(Main.pref.getCollection(prefString)));
159 this.shortText = prefString.substring(15);
160 this.longText = s.get(0);
161 this.requestedKey = Integer.parseInt(s.get(1));
162 this.requestedGroup = Integer.parseInt(s.get(2));
163 this.assignedKey = Integer.parseInt(s.get(3));
164 this.assignedModifier = Integer.parseInt(s.get(4));
165 this.assignedDefault = Boolean.parseBoolean(s.get(5));
166 this.assignedUser = Boolean.parseBoolean(s.get(6));
167 }
168
169 private void saveDefault() {
170 Main.pref.getCollection("shortcut.entry."+shortText, Arrays.asList(new String[]{longText,
171 String.valueOf(requestedKey), String.valueOf(requestedGroup), String.valueOf(requestedKey),
172 String.valueOf(getGroupModifier(requestedGroup)), String.valueOf(true), String.valueOf(false)}));
173 }
174
175 // get a string that can be put into the preferences
176 private boolean save() {
177 if (getAutomatic() || getReset() || !getAssignedUser()) {
178 return Main.pref.putCollection("shortcut.entry."+shortText, null);
179 } else {
180 return Main.pref.putCollection("shortcut.entry."+shortText, Arrays.asList(new String[]{longText,
181 String.valueOf(requestedKey), String.valueOf(requestedGroup), String.valueOf(assignedKey),
182 String.valueOf(assignedModifier), String.valueOf(assignedDefault), String.valueOf(assignedUser)}));
183 }
184 }
185
186 private boolean isSame(int isKey, int isModifier) {
187 // an unassigned shortcut is different from any other shortcut
188 return isKey == assignedKey && isModifier == assignedModifier && assignedModifier != getGroupModifier(NONE);
189 }
190
191 public boolean isEvent(KeyEvent e) {
192 return getKeyStroke() != null && getKeyStroke().equals(
193 KeyStroke.getKeyStroke(e.getKeyCode(), e.getModifiers()));
194 }
195
196 /**
197 * use this to set a menu's mnemonic
198 */
199 public void setMnemonic(JMenu menu) {
200 if (assignedModifier == getGroupModifier(MNEMONIC) && getKeyStroke() != null && KeyEvent.getKeyText(assignedKey).length() == 1) {
201 menu.setMnemonic(KeyEvent.getKeyText(assignedKey).charAt(0)); //getKeyStroke().getKeyChar() seems not to work here
202 }
203 }
204 /**
205 * use this to set a buttons's mnemonic
206 */
207 public void setMnemonic(AbstractButton button) {
208 if (assignedModifier == getGroupModifier(MNEMONIC) && getKeyStroke() != null && KeyEvent.getKeyText(assignedKey).length() == 1) {
209 button.setMnemonic(KeyEvent.getKeyText(assignedKey).charAt(0)); //getKeyStroke().getKeyChar() seems not to work here
210 }
211 }
212 /**
213 * use this to set a actions's accelerator
214 */
215 public void setAccelerator(AbstractAction action) {
216 if (getKeyStroke() != null) {
217 action.putValue(AbstractAction.ACCELERATOR_KEY, getKeyStroke());
218 }
219 }
220
221 /**
222 * use this to get a human readable text for your shortcut
223 */
224 public String getKeyText() {
225 KeyStroke keyStroke = getKeyStroke();
226 if (keyStroke == null) return "";
227 String modifText = KeyEvent.getKeyModifiersText(keyStroke.getModifiers());
228 if ("".equals (modifText)) return KeyEvent.getKeyText (keyStroke.getKeyCode ());
229 return modifText + "+" + KeyEvent.getKeyText(keyStroke.getKeyCode ());
230 }
231
232 @Override
233 public String toString() {
234 return getKeyText();
235 }
236
237 ///////////////////////////////
238 // everything's static below //
239 ///////////////////////////////
240
241 // here we store our shortcuts
242 private static Map<String, Shortcut> shortcuts = new LinkedHashMap<String, Shortcut>();
243
244 // and here our modifier groups
245 private static Map<Integer, Integer> groups= new HashMap<Integer, Integer>();
246
247 // check if something collides with an existing shortcut
248 public static Shortcut findShortcut(int requestedKey, int modifier) {
249 if (modifier == getGroupModifier(NONE))
250 return null;
251 for (Shortcut sc : shortcuts.values()) {
252 if (sc.isSame(requestedKey, modifier))
253 return sc;
254 }
255 return null;
256 }
257
258 /**
259 * FOR PREF PANE ONLY
260 */
261 public static List<Shortcut> listAll() {
262 List<Shortcut> l = new ArrayList<Shortcut>();
263 for(Shortcut c : shortcuts.values())
264 {
265 if(!c.shortText.equals("core:none")) {
266 l.add(c);
267 }
268 }
269 return l;
270 }
271
272 public static final int NONE = 5000;
273 public static final int MNEMONIC = 5001;
274 public static final int RESERVED = 5002;
275 public static final int DIRECT = 5003;
276 public static final int ALT = 5004;
277 public static final int SHIFT = 5005;
278 public static final int CTRL = 5006;
279 public static final int ALT_SHIFT = 5007;
280 public static final int ALT_CTRL = 5008;
281 public static final int CTRL_SHIFT = 5009;
282 public static final int ALT_CTRL_SHIFT = 5010;
283
284 /* for reassignment */
285 private static int[] mods = {ALT_CTRL, ALT_SHIFT, CTRL_SHIFT, ALT_CTRL_SHIFT};
286 private static int[] keys = {KeyEvent.VK_F1, KeyEvent.VK_F2, KeyEvent.VK_F3, KeyEvent.VK_F4,
287 KeyEvent.VK_F5, KeyEvent.VK_F6, KeyEvent.VK_F7, KeyEvent.VK_F8,
288 KeyEvent.VK_F9, KeyEvent.VK_F10, KeyEvent.VK_F11, KeyEvent.VK_F12};
289
290 // bootstrap
291 private static boolean initdone = false;
292 private static void doInit() {
293 if (initdone) return;
294 initdone = true;
295 groups.put(NONE, -1);
296 groups.put(MNEMONIC, KeyEvent.ALT_DOWN_MASK);
297 groups.put(DIRECT, 0);
298 groups.put(ALT, KeyEvent.ALT_DOWN_MASK);
299 groups.put(SHIFT, KeyEvent.SHIFT_DOWN_MASK);
300 groups.put(CTRL, KeyEvent.CTRL_DOWN_MASK);
301 groups.put(ALT_SHIFT, KeyEvent.ALT_DOWN_MASK|KeyEvent.SHIFT_DOWN_MASK);
302 groups.put(ALT_CTRL, KeyEvent.ALT_DOWN_MASK|KeyEvent.CTRL_DOWN_MASK);
303 groups.put(CTRL_SHIFT, KeyEvent.CTRL_DOWN_MASK|KeyEvent.SHIFT_DOWN_MASK);
304 groups.put(ALT_CTRL_SHIFT, KeyEvent.ALT_DOWN_MASK|KeyEvent.CTRL_DOWN_MASK|KeyEvent.SHIFT_DOWN_MASK);
305
306 // (1) System reserved shortcuts
307 Main.platform.initSystemShortcuts();
308 // (2) User defined shortcuts
309 LinkedList<Shortcut> newshortcuts = new LinkedList<Shortcut>();
310 for(String s : Main.pref.getAllPrefixCollectionKeys("shortcut.entry.")) {
311 newshortcuts.add(new Shortcut(s));
312 }
313
314 for(Shortcut sc : newshortcuts) {
315 if (sc.getAssignedUser()
316 && findShortcut(sc.getAssignedKey(), sc.getAssignedModifier()) == null) {
317 shortcuts.put(sc.getShortText(), sc);
318 }
319 }
320 // Shortcuts at their default values
321 for(Shortcut sc : newshortcuts) {
322 if (!sc.getAssignedUser() && sc.getAssignedDefault()
323 && findShortcut(sc.getAssignedKey(), sc.getAssignedModifier()) == null) {
324 shortcuts.put(sc.getShortText(), sc);
325 }
326 }
327 // Shortcuts that were automatically moved
328 for(Shortcut sc : newshortcuts) {
329 if (!sc.getAssignedUser() && !sc.getAssignedDefault()
330 && findShortcut(sc.getAssignedKey(), sc.getAssignedModifier()) == null) {
331 shortcuts.put(sc.getShortText(), sc);
332 }
333 }
334 }
335
336 private static int getGroupModifier(int group) {
337 Integer m = groups.get(group);
338 if(m == null)
339 m = -1;
340 return m;
341 }
342
343 private static int findModifier(int group, Integer modifier) {
344 if(modifier == null) {
345 modifier = getGroupModifier(group);
346 if (modifier == null) { // garbage in, no shortcut out
347 modifier = getGroupModifier(NONE);
348 }
349 }
350 return modifier;
351 }
352
353 // shutdown handling
354 public static boolean savePrefs() {
355 boolean changed = false;
356 for (Shortcut sc : shortcuts.values()) {
357 changed = changed | sc.save();
358 }
359 return changed;
360 }
361
362 /**
363 * FOR PLATFORMHOOK USE ONLY
364 *
365 * This registers a system shortcut. See PlatformHook for details.
366 */
367 public static Shortcut registerSystemShortcut(String shortText, String longText, int key, int modifier) {
368 if (shortcuts.containsKey(shortText))
369 return shortcuts.get(shortText);
370 Shortcut potentialShortcut = findShortcut(key, modifier);
371 if (potentialShortcut != null) {
372 // this always is a logic error in the hook
373 System.err.println("CONFLICT WITH SYSTEM KEY "+shortText);
374 return null;
375 }
376 potentialShortcut = new Shortcut(shortText, longText, key, RESERVED, key, modifier, true, false);
377 shortcuts.put(shortText, potentialShortcut);
378 return potentialShortcut;
379 }
380
381 /**
382 * Register a shortcut.
383 *
384 * Here you get your shortcuts from. The parameters are:
385 *
386 * @param shortText an ID. re-use a {@code "system:*"} ID if possible, else use something unique.
387 * {@code "menu:*"} is reserved for menu mnemonics, {@code "core:*"} is reserved for
388 * actions that are part of JOSM's core. Use something like
389 * {@code <pluginname>+":"+<actionname>}.
390 * @param longText this will be displayed in the shortcut preferences dialog. Better
391 * use something the user will recognize...
392 * @param requestedKey the key you'd prefer. Use a {@link KeyEvent KeyEvent.VK_*} constant here.
393 * @param requestedGroup the group this shortcut fits best. This will determine the
394 * modifiers your shortcut will get assigned. Use the constants defined above.
395 */
396 public static Shortcut registerShortcut(String shortText, String longText, int requestedKey, int requestedGroup) {
397 return registerShortcut(shortText, longText, requestedKey, requestedGroup, null);
398 }
399
400 // and now the workhorse. same parameters as above, just one more
401 private static Shortcut registerShortcut(String shortText, String longText, int requestedKey, int requestedGroup, Integer modifier) {
402 doInit();
403 Integer defaultModifier = findModifier(requestedGroup, modifier);
404 if (shortcuts.containsKey(shortText)) { // a re-register? maybe a sc already read from the preferences?
405 Shortcut sc = shortcuts.get(shortText);
406 sc.setLongText(longText); // or set by the platformHook, in this case the original longText doesn't match the real action
407 sc.saveDefault();
408 return sc;
409 }
410 Shortcut conflict = findShortcut(requestedKey, defaultModifier);
411 if (conflict != null) {
412 for (int m : mods) {
413 for (int k : keys) {
414 int newmodifier = getGroupModifier(m);
415 if ( findShortcut(k, newmodifier) == null ) {
416 Shortcut newsc = new Shortcut(shortText, longText, requestedKey, m, k, newmodifier, false, false);
417 System.out.println(tr("Silent shortcut conflict: ''{0}'' moved by ''{1}'' to ''{2}''.",
418 shortText, conflict.getShortText(), newsc.getKeyText()));
419 newsc.saveDefault();
420 shortcuts.put(shortText, newsc);
421 return newsc;
422 }
423 }
424 }
425 } else {
426 Shortcut newsc = new Shortcut(shortText, longText, requestedKey, requestedGroup, requestedKey, defaultModifier, true, false);
427 newsc.saveDefault();
428 shortcuts.put(shortText, newsc);
429 return newsc;
430 }
431
432 return null;
433 }
434
435 /**
436 * Replies the platform specific key stroke for the 'Copy' command, i.e.
437 * 'Ctrl-C' on windows or 'Meta-C' on a Mac. null, if the platform specific
438 * copy command isn't known.
439 *
440 * @return the platform specific key stroke for the 'Copy' command
441 */
442 static public KeyStroke getCopyKeyStroke() {
443 Shortcut sc = shortcuts.get("system:copy");
444 if (sc == null) return null;
445 return sc.getKeyStroke();
446 }
447
448 /**
449 * Replies the platform specific key stroke for the 'Paste' command, i.e.
450 * 'Ctrl-V' on windows or 'Meta-V' on a Mac. null, if the platform specific
451 * paste command isn't known.
452 *
453 * @return the platform specific key stroke for the 'Paste' command
454 */
455 static public KeyStroke getPasteKeyStroke() {
456 Shortcut sc = shortcuts.get("system:paste");
457 if (sc == null) return null;
458 return sc.getKeyStroke();
459 }
460
461 /**
462 * Replies the platform specific key stroke for the 'Cut' command, i.e.
463 * 'Ctrl-X' on windows or 'Meta-X' on a Mac. null, if the platform specific
464 * 'Cut' command isn't known.
465 *
466 * @return the platform specific key stroke for the 'Cut' command
467 */
468 static public KeyStroke getCutKeyStroke() {
469 Shortcut sc = shortcuts.get("system:cut");
470 if (sc == null) return null;
471 return sc.getKeyStroke();
472 }
473}
Note: See TracBrowser for help on using the repository browser.