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

Last change on this file since 8674 was 8510, checked in by Don-vip, 9 years ago

checkstyle: enable relevant whitespace checks and fix them

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