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

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

Sonar: various code style cleanup:

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