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

Last change on this file since 2474 was 2017, checked in by Gubaer, 15 years ago

removed OptionPaneUtil
cleanup of deprecated Layer API
cleanup of deprecated APIs in OsmPrimitive and Way
cleanup of imports

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