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

Last change on this file since 7901 was 7606, checked in by Don-vip, 10 years ago

fix #10592 - fix other shortcuts

  • 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 getAssignedDefault() {
85 return assignedDefault;
86 }
87
88 public boolean getAssignedUser() {
89 return assignedUser;
90 }
91
92 public boolean getAutomatic() {
93 return automatic;
94 }
95
96 public boolean isChangeable() {
97 return !automatic && !"core:none".equals(shortText);
98 }
99
100 private boolean getReset() {
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 (getAutomatic() || getReset() || !getAssignedUser()) {
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 {
257 if(!"core:none".equals(c.shortText)) {
258 l.add(c);
259 }
260 }
261 return l;
262 }
263
264 /** None group: used with KeyEvent.CHAR_UNDEFINED if no shortcut is defined */
265 public static final int NONE = 5000;
266 public static final int MNEMONIC = 5001;
267 /** Reserved group: for system shortcuts only */
268 public static final int RESERVED = 5002;
269 /** Direct group: no modifier */
270 public static final int DIRECT = 5003;
271 /** Alt group */
272 public static final int ALT = 5004;
273 /** Shift group */
274 public static final int SHIFT = 5005;
275 /** Command group. Matches CTRL modifier on Windows/Linux but META modifier on OS X */
276 public static final int CTRL = 5006;
277 /** Alt-Shift group */
278 public static final int ALT_SHIFT = 5007;
279 /** Alt-Command group. Matches ALT-CTRL modifier on Windows/Linux but ALT-META modifier on OS X */
280 public static final int ALT_CTRL = 5008;
281 /** Command-Shift group. Matches CTRL-SHIFT modifier on Windows/Linux but META-SHIFT modifier on OS X */
282 public static final int CTRL_SHIFT = 5009;
283 /** Alt-Command-Shift group. Matches ALT-CTRL-SHIFT modifier on Windows/Linux but ALT-META-SHIFT modifier on OS X */
284 public static final int ALT_CTRL_SHIFT = 5010;
285
286 /* for reassignment */
287 private static int[] mods = {ALT_CTRL, ALT_SHIFT, CTRL_SHIFT, ALT_CTRL_SHIFT};
288 private static int[] keys = {KeyEvent.VK_F1, KeyEvent.VK_F2, KeyEvent.VK_F3, KeyEvent.VK_F4,
289 KeyEvent.VK_F5, KeyEvent.VK_F6, KeyEvent.VK_F7, KeyEvent.VK_F8,
290 KeyEvent.VK_F9, KeyEvent.VK_F10, KeyEvent.VK_F11, KeyEvent.VK_F12};
291
292 // bootstrap
293 private static boolean initdone = false;
294 private static void doInit() {
295 if (initdone) return;
296 initdone = true;
297 int commandDownMask = GuiHelper.getMenuShortcutKeyMaskEx();
298 groups.put(NONE, -1);
299 groups.put(MNEMONIC, KeyEvent.ALT_DOWN_MASK);
300 groups.put(DIRECT, 0);
301 groups.put(ALT, KeyEvent.ALT_DOWN_MASK);
302 groups.put(SHIFT, KeyEvent.SHIFT_DOWN_MASK);
303 groups.put(CTRL, commandDownMask);
304 groups.put(ALT_SHIFT, KeyEvent.ALT_DOWN_MASK|KeyEvent.SHIFT_DOWN_MASK);
305 groups.put(ALT_CTRL, KeyEvent.ALT_DOWN_MASK|commandDownMask);
306 groups.put(CTRL_SHIFT, commandDownMask|KeyEvent.SHIFT_DOWN_MASK);
307 groups.put(ALT_CTRL_SHIFT, KeyEvent.ALT_DOWN_MASK|commandDownMask|KeyEvent.SHIFT_DOWN_MASK);
308
309 // (1) System reserved shortcuts
310 Main.platform.initSystemShortcuts();
311 // (2) User defined shortcuts
312 LinkedList<Shortcut> newshortcuts = new LinkedList<>();
313 for(String s : Main.pref.getAllPrefixCollectionKeys("shortcut.entry.")) {
314 newshortcuts.add(new Shortcut(s));
315 }
316
317 for(Shortcut sc : newshortcuts) {
318 if (sc.getAssignedUser()
319 && findShortcut(sc.getAssignedKey(), sc.getAssignedModifier()) == null) {
320 shortcuts.put(sc.getShortText(), sc);
321 }
322 }
323 // Shortcuts at their default values
324 for(Shortcut sc : newshortcuts) {
325 if (!sc.getAssignedUser() && sc.getAssignedDefault()
326 && findShortcut(sc.getAssignedKey(), sc.getAssignedModifier()) == null) {
327 shortcuts.put(sc.getShortText(), sc);
328 }
329 }
330 // Shortcuts that were automatically moved
331 for(Shortcut sc : newshortcuts) {
332 if (!sc.getAssignedUser() && !sc.getAssignedDefault()
333 && findShortcut(sc.getAssignedKey(), sc.getAssignedModifier()) == null) {
334 shortcuts.put(sc.getShortText(), sc);
335 }
336 }
337 }
338
339 private static int getGroupModifier(int group) {
340 Integer m = groups.get(group);
341 if(m == null)
342 m = -1;
343 return m;
344 }
345
346 private static int findModifier(int group, Integer modifier) {
347 if(modifier == null) {
348 modifier = getGroupModifier(group);
349 if (modifier == null) { // garbage in, no shortcut out
350 modifier = getGroupModifier(NONE);
351 }
352 }
353 return modifier;
354 }
355
356 // shutdown handling
357 public static boolean savePrefs() {
358 boolean changed = false;
359 for (Shortcut sc : shortcuts.values()) {
360 changed = changed | sc.save();
361 }
362 return changed;
363 }
364
365 /**
366 * FOR PLATFORMHOOK USE ONLY
367 *
368 * This registers a system shortcut. See PlatformHook for details.
369 */
370 public static Shortcut registerSystemShortcut(String shortText, String longText, int key, int modifier) {
371 if (shortcuts.containsKey(shortText))
372 return shortcuts.get(shortText);
373 Shortcut potentialShortcut = findShortcut(key, modifier);
374 if (potentialShortcut != null) {
375 // this always is a logic error in the hook
376 Main.error("CONFLICT WITH SYSTEM KEY "+shortText);
377 return null;
378 }
379 potentialShortcut = new Shortcut(shortText, longText, key, RESERVED, key, modifier, true, false);
380 shortcuts.put(shortText, potentialShortcut);
381 return potentialShortcut;
382 }
383
384 /**
385 * Register a shortcut.
386 *
387 * Here you get your shortcuts from. The parameters are:
388 *
389 * @param shortText an ID. re-use a {@code "system:*"} ID if possible, else use something unique.
390 * {@code "menu:*"} is reserved for menu mnemonics, {@code "core:*"} is reserved for
391 * actions that are part of JOSM's core. Use something like
392 * {@code <pluginname>+":"+<actionname>}.
393 * @param longText this will be displayed in the shortcut preferences dialog. Better
394 * use something the user will recognize...
395 * @param requestedKey the key you'd prefer. Use a {@link KeyEvent KeyEvent.VK_*} constant here.
396 * @param requestedGroup the group this shortcut fits best. This will determine the
397 * modifiers your shortcut will get assigned. Use the constants defined above.
398 */
399 public static Shortcut registerShortcut(String shortText, String longText, int requestedKey, int requestedGroup) {
400 return registerShortcut(shortText, longText, requestedKey, requestedGroup, null);
401 }
402
403 // and now the workhorse. same parameters as above, just one more
404 private static Shortcut registerShortcut(String shortText, String longText, int requestedKey, int requestedGroup, Integer modifier) {
405 doInit();
406 Integer defaultModifier = findModifier(requestedGroup, modifier);
407 if (shortcuts.containsKey(shortText)) { // a re-register? maybe a sc already read from the preferences?
408 Shortcut sc = shortcuts.get(shortText);
409 sc.setLongText(longText); // or set by the platformHook, in this case the original longText doesn't match the real action
410 sc.saveDefault();
411 return sc;
412 }
413 Shortcut conflict = findShortcut(requestedKey, defaultModifier);
414 if (conflict != null) {
415 if (Main.isPlatformOsx()) {
416 // Try to reassign Meta to Ctrl
417 int newmodifier = findNewOsxModifier(requestedGroup);
418 if ( findShortcut(requestedKey, newmodifier) == null ) {
419 return reassignShortcut(shortText, longText, requestedKey, conflict, requestedGroup, requestedKey, newmodifier);
420 }
421 }
422 for (int m : mods) {
423 for (int k : keys) {
424 int newmodifier = getGroupModifier(m);
425 if ( findShortcut(k, newmodifier) == null ) {
426 return reassignShortcut(shortText, longText, requestedKey, conflict, m, k, newmodifier);
427 }
428 }
429 }
430 } else {
431 Shortcut newsc = new Shortcut(shortText, longText, requestedKey, requestedGroup, requestedKey, defaultModifier, true, false);
432 newsc.saveDefault();
433 shortcuts.put(shortText, newsc);
434 return newsc;
435 }
436
437 return null;
438 }
439
440 private static int findNewOsxModifier(int requestedGroup) {
441 switch (requestedGroup) {
442 case CTRL: return KeyEvent.CTRL_DOWN_MASK;
443 case ALT_CTRL: return KeyEvent.ALT_DOWN_MASK|KeyEvent.CTRL_DOWN_MASK;
444 case CTRL_SHIFT: return KeyEvent.CTRL_DOWN_MASK|KeyEvent.SHIFT_DOWN_MASK;
445 case ALT_CTRL_SHIFT: return KeyEvent.ALT_DOWN_MASK|KeyEvent.CTRL_DOWN_MASK|KeyEvent.SHIFT_DOWN_MASK;
446 default: return 0;
447 }
448 }
449
450 private static Shortcut reassignShortcut(String shortText, String longText, int requestedKey, Shortcut conflict,
451 int m, int k, int newmodifier) {
452 Shortcut newsc = new Shortcut(shortText, longText, requestedKey, m, k, newmodifier, false, false);
453 Main.info(tr("Silent shortcut conflict: ''{0}'' moved by ''{1}'' to ''{2}''.",
454 shortText, conflict.getShortText(), newsc.getKeyText()));
455 newsc.saveDefault();
456 shortcuts.put(shortText, newsc);
457 return newsc;
458 }
459
460 /**
461 * Replies the platform specific key stroke for the 'Copy' command, i.e.
462 * 'Ctrl-C' on windows or 'Meta-C' on a Mac. null, if the platform specific
463 * copy command isn't known.
464 *
465 * @return the platform specific key stroke for the 'Copy' command
466 */
467 public static KeyStroke getCopyKeyStroke() {
468 Shortcut sc = shortcuts.get("system:copy");
469 if (sc == null) return null;
470 return sc.getKeyStroke();
471 }
472
473 /**
474 * Replies the platform specific key stroke for the 'Paste' command, i.e.
475 * 'Ctrl-V' on windows or 'Meta-V' on a Mac. null, if the platform specific
476 * paste command isn't known.
477 *
478 * @return the platform specific key stroke for the 'Paste' command
479 */
480 public static KeyStroke getPasteKeyStroke() {
481 Shortcut sc = shortcuts.get("system:paste");
482 if (sc == null) return null;
483 return sc.getKeyStroke();
484 }
485
486 /**
487 * Replies the platform specific key stroke for the 'Cut' command, i.e.
488 * 'Ctrl-X' on windows or 'Meta-X' on a Mac. null, if the platform specific
489 * 'Cut' command isn't known.
490 *
491 * @return the platform specific key stroke for the 'Cut' command
492 */
493 public static KeyStroke getCutKeyStroke() {
494 Shortcut sc = shortcuts.get("system:cut");
495 if (sc == null) return null;
496 return sc.getKeyStroke();
497 }
498}
Note: See TracBrowser for help on using the repository browser.