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

Last change on this file since 4153 was 3490, checked in by bastiK, 14 years ago

fixed #5349 - A LOT of gpx tracks fill entire screen with popup information

  • Property svn:eol-style set to native
File size: 21.5 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.HashMap;
9import java.util.LinkedHashMap;
10import java.util.List;
11import java.util.Map;
12
13import javax.swing.AbstractButton;
14import javax.swing.JMenu;
15import javax.swing.JOptionPane;
16import javax.swing.KeyStroke;
17
18import org.openstreetmap.josm.Main;
19
20/**
21 * Global shortcut class.
22 *
23 * Note: This class represents a single shortcut, contains the factory to obtain
24 * shortcut objects from, manages shortcuts and shortcut collisions, and
25 * finally manages loading and saving shortcuts to/from the preferences.
26 *
27 * Action authors: You only need the registerShortcut() factory. Ignore everything
28 * else.
29 *
30 * All: Use only public methods that are also marked to be used. The others are
31 * public so the shortcut preferences can use them.
32 *
33 */
34public class Shortcut {
35 // public static final int SHIFT = KeyEvent.SHIFT_DOWN_MASK;
36 // public static final int CTRL = KeyEvent.CTRL_DOWN_MASK;
37 // public static final int SHIFT_CTRL = KeyEvent.SHIFT_DOWN_MASK|KeyEvent.CTRL_DOWN_MASK;
38 public static final int SHIFT_DEFAULT = 1;
39 private String shortText; // the unique ID of the shortcut
40 private String longText; // a human readable description that will be shown in the preferences
41 private int requestedKey; // the key, the caller requested
42 private int requestedGroup; // the group, the caller requested
43 private int assignedKey; // the key that actually is used
44 private int assignedModifier; // the modifiers that are used
45 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.)
46 private boolean assignedUser; // true if the user changed this shortcut
47 private boolean automatic; // true if the user cannot change this shortcut (Note: it also will not be saved into the preferences)
48 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)
49
50 // simple constructor
51 private Shortcut(String shortText, String longText, int requestedKey, int requestedGroup, int assignedKey, int assignedModifier, boolean assignedDefault, boolean assignedUser) {
52 this.shortText = shortText;
53 this.longText = longText;
54 this.requestedKey = requestedKey;
55 this.requestedGroup = requestedGroup;
56 this.assignedKey = assignedKey;
57 this.assignedModifier = assignedModifier;
58 this.assignedDefault = assignedDefault;
59 this.assignedUser = assignedUser;
60 this.automatic = false;
61 this.reset = false;
62 }
63
64 public String getShortText() {
65 return shortText;
66 }
67
68 public String getLongText() {
69 return longText;
70 }
71
72 // a shortcut will be renamed when it is handed out again, because the original name
73 // may be a dummy
74 private void setLongText(String longText) {
75 this.longText = longText;
76 }
77
78 private int getRequestedKey() {
79 return requestedKey;
80 }
81
82 public int getRequestedGroup() {
83 return requestedGroup;
84 }
85
86 public int getAssignedKey() {
87 return assignedKey;
88 }
89
90 public int getAssignedModifier() {
91 return assignedModifier;
92 }
93
94 public boolean getAssignedDefault() {
95 return assignedDefault;
96 }
97
98 public boolean getAssignedUser() {
99 return assignedUser;
100 }
101
102 public boolean getAutomatic() {
103 return automatic;
104 }
105
106 public boolean isChangeable() {
107 return !automatic && !shortText.equals("core:none");
108 }
109
110 private boolean getReset() {
111 return reset;
112 }
113
114 /**
115 * FOR PREF PANE ONLY
116 */
117 public void setAutomatic() {
118 automatic = true;
119 }
120
121 /**
122 * FOR PREF PANE ONLY
123 */
124 public void setAssignedModifier(int assignedModifier) {
125 this.assignedModifier = assignedModifier;
126 }
127
128 /**
129 * FOR PREF PANE ONLY
130 */
131 public void setAssignedKey(int assignedKey) {
132 this.assignedKey = assignedKey;
133 }
134
135 /**
136 * FOR PREF PANE ONLY
137 */
138 public void setAssignedUser(boolean assignedUser) {
139 this.reset = (!this.assignedUser && assignedUser);
140 if (assignedUser) {
141 assignedDefault = false;
142 }
143 this.assignedUser = assignedUser;
144 }
145
146 /**
147 * Use this to register the shortcut with Swing
148 */
149 public KeyStroke getKeyStroke() {
150 if (assignedModifier != -1)
151 return KeyStroke.getKeyStroke(assignedKey, assignedModifier);
152 return null;
153 }
154
155 private boolean isSame(int isKey, int isModifier) {
156 // -1 --- an unassigned shortcut is different from any other shortcut
157 return( isKey == assignedKey && isModifier == assignedModifier && assignedModifier != groups.get(GROUP_NONE));
158 }
159
160 // create a shortcut object from an string as saved in the preferences
161 private Shortcut(String prefString) {
162 String[] s = prefString.split(";");
163 this.shortText = s[0];
164 this.longText = s[1];
165 this.requestedKey = Integer.parseInt(s[2]);
166 this.requestedGroup = Integer.parseInt(s[3]);
167 this.assignedKey = Integer.parseInt(s[4]);
168 this.assignedModifier = Integer.parseInt(s[5]);
169 this.assignedDefault = Boolean.parseBoolean(s[6]);
170 this.assignedUser = Boolean.parseBoolean(s[7]);
171 }
172
173 // get a string that can be put into the preferences
174 private String asPrefString() {
175 return shortText + ";" + longText + ";" + requestedKey + ";" + requestedGroup + ";" + assignedKey + ";" + assignedModifier + ";" + assignedDefault + ";" + assignedUser;
176 }
177
178 private boolean isSame(Shortcut other) {
179 return assignedKey == other.assignedKey && assignedModifier == other.assignedModifier;
180 }
181
182 /**
183 * use this to set a menu's mnemonic
184 */
185 public void setMnemonic(JMenu menu) {
186 if (requestedGroup == GROUP_MNEMONIC && assignedModifier == groups.get(requestedGroup + GROUPS_DEFAULT) && getKeyStroke() != null && KeyEvent.getKeyText(assignedKey).length() == 1) {
187 menu.setMnemonic(KeyEvent.getKeyText(assignedKey).charAt(0)); //getKeyStroke().getKeyChar() seems not to work here
188 }
189 }
190 /**
191 * use this to set a buttons's mnemonic
192 */
193 public void setMnemonic(AbstractButton button) {
194 if (requestedGroup == GROUP_MNEMONIC && assignedModifier == groups.get(requestedGroup + GROUPS_DEFAULT) && getKeyStroke() != null && KeyEvent.getKeyText(assignedKey).length() == 1) {
195 button.setMnemonic(KeyEvent.getKeyText(assignedKey).charAt(0)); //getKeyStroke().getKeyChar() seems not to work here
196 }
197 }
198
199 /**
200 * use this to get a human readable text for your shortcut
201 */
202 public String getKeyText() {
203 KeyStroke keyStroke = getKeyStroke();
204 if (keyStroke == null) return "";
205 String modifText = KeyEvent.getKeyModifiersText(keyStroke.getModifiers());
206 if ("".equals (modifText)) return KeyEvent.getKeyText (keyStroke.getKeyCode ());
207 return modifText + "+" + KeyEvent.getKeyText(keyStroke.getKeyCode ());
208 }
209
210 ///////////////////////////////
211 // everything's static below //
212 ///////////////////////////////
213
214 // here we store our shortcuts
215 private static Map<String, Shortcut> shortcuts = new LinkedHashMap<String, Shortcut>();
216
217 // and here our modifier groups
218 private static Map<Integer, Integer> groups = new HashMap<Integer, Integer>();
219
220 // check if something collides with an existing shortcut
221 private static Shortcut findShortcut(int requestedKey, int modifier) {
222 if (modifier == groups.get(GROUP_NONE))
223 return null;
224 for (Shortcut sc : shortcuts.values()) {
225 if (sc.isSame(requestedKey, modifier))
226 return sc;
227 }
228 return null;
229 }
230
231 /**
232 * FOR PREF PANE ONLY
233 */
234 public static List<Shortcut> listAll() {
235 List<Shortcut> l = new ArrayList<Shortcut>();
236 for(Shortcut c : shortcuts.values())
237 {
238 if(!c.shortText.equals("core:none")) {
239 l.add(c);
240 }
241 }
242 return l;
243 }
244
245 // try to find an unused shortcut
246 private static Shortcut findRandomShortcut(String shortText, String longText, int requestedKey, int requestedGroup) {
247 int[] mods = {groups.get(requestedGroup + GROUPS_DEFAULT), groups.get(requestedGroup + GROUPS_ALT1), groups.get(requestedGroup + GROUPS_ALT2)};
248 for (int m : mods) {
249 for (int k = KeyEvent.VK_A; k < KeyEvent.VK_Z; k++) { // we'll limit ourself to 100% safe keys
250 if ( findShortcut(k, m) == null )
251 return new Shortcut(shortText, longText, requestedKey, requestedGroup, k, m, false, false);
252 }
253 }
254 return new Shortcut(shortText, longText, requestedKey, requestedGroup, requestedKey, groups.get(GROUP_NONE), false, false);
255 }
256
257 // use these constants to request shortcuts
258 public static final int GROUP_NONE = 0; // no shortcut
259 public static final int GROUP_HOTKEY = 1; // a button action, will use another modifier than MENU on system with a meta key
260 public static final int GROUP_MENU = 2; // a menu action, e.g. "ctrl-e"/"cmd-e" (export)
261 public static final int GROUP_EDIT = 3; // direct edit key, e.g. "a" (add)
262 public static final int GROUP_LAYER = 4; // toggle one of the right-hand-side windows, e.g. "alt-l" (layers)
263 public static final int GROUP_DIRECT = 5; // for non-letter keys, preferable without modifier, e.g. F5
264 public static final int GROUP_MNEMONIC = 6; // for use with Menu.setMnemonic() only!
265 public static final int GROUP__MAX = 7;
266 public static final int GROUP_RESERVED = 1000;
267 public static final int GROUPS_DEFAULT = 0;
268 public static final int GROUPS_ALT1 = GROUP__MAX;
269 public static final int GROUPS_ALT2 = GROUP__MAX * 2;
270
271 // bootstrap
272 private static boolean initdone = false;
273 private static void doInit() {
274 if (initdone) return;
275 initdone = true;
276 // if we have no modifier groups in the config, we have to create them
277 if (Main.pref.get("shortcut.groups.configured", null) == null) {
278 Main.platform.initShortcutGroups();
279 Main.pref.put("shortcut.groups.configured", true);
280 }
281 // pull in the groups
282 for (int i = GROUP_NONE; i < GROUP__MAX+GROUPS_ALT2*2; i++) { // fill more groups, so registering with e.g. ALT2+MNEMONIC won't NPE
283 groups.put(i, Main.pref.getInteger("shortcut.groups."+i, -1));
284 }
285 // (1) System reserved shortcuts
286 Main.platform.initSystemShortcuts();
287 // (2) User defined shortcuts
288 int i = 0;
289 String p = Main.pref.get("shortcut.shortcut."+i, null);
290 while (p != null) {
291 Shortcut sc = new Shortcut(p);
292 if (sc.getAssignedUser()) {
293 registerShortcut(sc);
294 }
295 i++;
296 p = Main.pref.get("shortcut.shortcut."+i, null);
297 }
298 // Shortcuts at their default values
299 i = 0;
300 p = Main.pref.get("shortcut.shortcut."+i, null);
301 while (p != null) {
302 Shortcut sc = new Shortcut(p);
303 if (!sc.getAssignedUser() && sc.getAssignedDefault()) {
304 registerShortcut(sc);
305 }
306 i++;
307 p = Main.pref.get("shortcut.shortcut."+i, null);
308 }
309 // Shortcuts that were automatically moved
310 i = 0;
311 p = Main.pref.get("shortcut.shortcut."+i, null);
312 while (p != null) {
313 Shortcut sc = new Shortcut(p);
314 if (!sc.getAssignedUser() && !sc.getAssignedDefault()) {
315 registerShortcut(sc);
316 }
317 i++;
318 p = Main.pref.get("shortcut.shortcut."+i, null);
319 }
320 }
321
322 // shutdown handling
323 public static boolean savePrefs() {
324 // we save this directly from the preferences pane, so don't overwrite these values here
325 // for (int i = GROUP_NONE; i < GROUP__MAX+GROUPS_ALT2; i++) {
326 // Main.pref.put("shortcut.groups."+i, Groups.get(i).toString());
327 // }
328 boolean changed = false;
329 int i = 0;
330 for (Shortcut sc : shortcuts.values()) {
331 // TODO: Remove sc.getAssignedUser() when we fixed all internal conflicts
332 if (!sc.getAutomatic() && !sc.getReset() && sc.getAssignedUser()) {
333 changed = changed | Main.pref.put("shortcut.shortcut."+i, sc.asPrefString());
334 i++;
335 }
336 }
337 changed = changed | Main.pref.put("shortcut.shortcut."+i, "");
338 return changed;
339 }
340
341 // this is used to register a shortcut that was read from the preferences
342 private static void registerShortcut(Shortcut sc) {
343 // put a user configured shortcut in as-is -- unless there's a conflict
344 if(sc.getAssignedUser() && findShortcut(sc.getAssignedKey(),
345 sc.getAssignedModifier()) == null) {
346 shortcuts.put(sc.getShortText(), sc);
347 } else {
348 registerShortcut(sc.getShortText(), sc.getLongText(), sc.getRequestedKey(),
349 sc.getRequestedGroup(), sc.getAssignedModifier(), sc);
350 }
351 }
352
353 /**
354 * FOR PLATFORMHOOK USE ONLY
355 *
356 * This registers a system shortcut. See PlatformHook for details.
357 */
358 public static Shortcut registerSystemShortcut(String shortText, String longText, int key, int modifier) {
359 if (shortcuts.containsKey(shortText))
360 return shortcuts.get(shortText);
361 Shortcut potentialShortcut = findShortcut(key, modifier);
362 if (potentialShortcut != null) {
363 // this always is a logic error in the hook
364 System.err.println("CONFLICT WITH SYSTEM KEY "+shortText);
365 return null;
366 }
367 potentialShortcut = new Shortcut(shortText, longText, key, GROUP_RESERVED, key, modifier, true, false);
368 shortcuts.put(shortText, potentialShortcut);
369 return potentialShortcut;
370 }
371
372 /**
373 * Register a shortcut.
374 *
375 * Here you get your shortcuts from. The parameters are:
376 *
377 * shortText - an ID. re-use a "system:*" ID if possible, else use something unique.
378 * "menu:*" is reserved for menu mnemonics, "core:*" is reserved for
379 * actions that are part of JOSM's core. Use something like
380 * <pluginname>+":"+<actionname>
381 * longText - this will be displayed in the shortcut preferences dialog. Better
382 * use soomething the user will recognize...
383 * requestedKey - the key you'd prefer. Use a KeyEvent.VK_* constant here.
384 * requestedGroup - the group this shortcut fits best. This will determine the
385 * modifiers your shortcut will get assigned. Use the GROUP_*
386 * constants defined above.
387 */
388 public static Shortcut registerShortcut(String shortText, String longText, int requestedKey, int requestedGroup, int modifier) {
389 return registerShortcut(shortText, longText, requestedKey, requestedGroup, modifier, null);
390 }
391 public static Shortcut registerShortcut(String shortText, String longText, int requestedKey, int requestedGroup) {
392 return registerShortcut(shortText, longText, requestedKey, requestedGroup, null, null);
393 }
394
395 // and now the workhorse. same parameters as above, just one more: if originalShortcut is not null and
396 // is different from the shortcut that will be assigned, a popup warning will be displayed to the user.
397 // This is used when registering shortcuts that have been visible to the user before (read: have been
398 // read from the preferences file). New shortcuts will never warn, even when they land on some funny
399 // random fallback key like Ctrl+Alt+Shift+Z for "File Open..." <g>
400 private static Shortcut registerShortcut(String shortText, String longText, int requestedKey, int requestedGroup, Integer modifier,
401 Shortcut originalShortcut) {
402 doInit();
403 if (shortcuts.containsKey(shortText)) { // a re-register? maybe a sc already read from the preferences?
404 Shortcut sc = shortcuts.get(shortText);
405 sc.setLongText(longText); // or set by the platformHook, in this case the original longText doesn't match the real action
406 return sc;
407 }
408 Integer defaultModifier = groups.get(requestedGroup + GROUPS_DEFAULT);
409 if(modifier != null) {
410 if(modifier == SHIFT_DEFAULT) {
411 defaultModifier |= KeyEvent.SHIFT_DOWN_MASK;
412 } else {
413 defaultModifier = modifier;
414 }
415 }
416 else if (defaultModifier == null) { // garbage in, no shortcut out
417 defaultModifier = groups.get(GROUP_NONE + GROUPS_DEFAULT);
418 }
419 Shortcut conflictsWith = null;
420 Shortcut potentialShortcut = findShortcut(requestedKey, defaultModifier);
421 if (potentialShortcut != null) { // 3 stage conflict handling
422 conflictsWith = potentialShortcut;
423 defaultModifier = groups.get(requestedGroup + GROUPS_ALT1);
424 if (defaultModifier == null) { // garbage in, no shortcurt out
425 defaultModifier = groups.get(GROUP_NONE + GROUPS_DEFAULT);
426 }
427 potentialShortcut = findShortcut(requestedKey, defaultModifier);
428 if (potentialShortcut != null) {
429 defaultModifier = groups.get(requestedGroup + GROUPS_ALT2);
430 if (defaultModifier == null) { // garbage in, no shortcurt out
431 defaultModifier = groups.get(GROUP_NONE + GROUPS_DEFAULT);
432 }
433 potentialShortcut = findShortcut(requestedKey, defaultModifier);
434 if (potentialShortcut != null) { // if all 3 modifiers for a group are used, we give up
435 potentialShortcut = findRandomShortcut(shortText, longText, requestedKey, requestedGroup);
436 } else {
437 potentialShortcut = new Shortcut(shortText, longText, requestedKey, requestedGroup, requestedKey, defaultModifier, false, false);
438 }
439 } else {
440 potentialShortcut = new Shortcut(shortText, longText, requestedKey, requestedGroup, requestedKey, defaultModifier, false, false);
441 }
442 if (originalShortcut != null && !originalShortcut.isSame(potentialShortcut)) {
443 displayWarning(conflictsWith, potentialShortcut, shortText, longText);
444 } else if (originalShortcut == null) {
445 System.out.println("Silent shortcut conflict: '"+shortText+"' moved by '"+conflictsWith.getShortText()+"' to '"+potentialShortcut.getKeyText()+"'.");
446 }
447 } else {
448 potentialShortcut = new Shortcut(shortText, longText, requestedKey, requestedGroup, requestedKey, defaultModifier, true, false);
449 }
450
451 shortcuts.put(shortText, potentialShortcut);
452 return potentialShortcut;
453 }
454
455 // a lengthy warning message
456 private static void displayWarning(Shortcut conflictsWith, Shortcut potentialShortcut, String shortText, String longText) {
457 JOptionPane.showMessageDialog(Main.parent,
458 tr("Setting the keyboard shortcut ''{0}'' for the action ''{1}'' ({2}) failed\n"+
459 "because the shortcut is already taken by the action ''{3}'' ({4}).\n\n",
460 conflictsWith.getKeyText(), longText, shortText,
461 conflictsWith.getLongText(), conflictsWith.getShortText())+
462 (potentialShortcut.getKeyText().equals("") ?
463 tr("This action will have no shortcut.\n\n")
464 :
465 tr("Using the shortcut ''{0}'' instead.\n\n", potentialShortcut.getKeyText())
466 )+
467 tr("(Hint: You can edit the shortcuts in the preferences.)"),
468 tr("Error"),
469 JOptionPane.ERROR_MESSAGE
470 );
471 }
472
473 /**
474 * Replies the platform specific key stroke for the 'Copy' command, i.e.
475 * 'Ctrl-C' on windows or 'Meta-C' on a Mac. null, if the platform specific
476 * copy command isn't known.
477 *
478 * @return the platform specific key stroke for the 'Copy' command
479 */
480 static public KeyStroke getCopyKeyStroke() {
481 Shortcut sc = shortcuts.get("system:copy");
482 if (sc == null) return null;
483 return sc.getKeyStroke();
484 }
485
486 /**
487 * Replies the platform specific key stroke for the 'Paste' command, i.e.
488 * 'Ctrl-V' on windows or 'Meta-V' on a Mac. null, if the platform specific
489 * paste command isn't known.
490 *
491 * @return the platform specific key stroke for the 'Paste' command
492 */
493 static public KeyStroke getPasteKeyStroke() {
494 Shortcut sc = shortcuts.get("system:paste");
495 if (sc == null) return null;
496 return sc.getKeyStroke();
497 }
498
499 /**
500 * Replies the platform specific key stroke for the 'Cut' command, i.e.
501 * 'Ctrl-X' on windows or 'Meta-X' on a Mac. null, if the platform specific
502 * 'Cut' command isn't known.
503 *
504 * @return the platform specific key stroke for the 'Cut' command
505 */
506 static public KeyStroke getCutKeyStroke() {
507 Shortcut sc = shortcuts.get("system:cut");
508 if (sc == null) return null;
509 return sc.getKeyStroke();
510 }
511}
Note: See TracBrowser for help on using the repository browser.