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

Last change on this file since 19050 was 19050, checked in by taylor.smock, 15 months ago

Revert most var changes from r19048, fix most new compile warnings and checkstyle issues

Also, document why various ErrorProne checks were originally disabled and fix
generic SonarLint issues.

  • Property svn:eol-style set to native
File size: 26.7 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.InputEvent;
7import java.awt.event.KeyEvent;
8import java.util.ArrayList;
9import java.util.Arrays;
10import java.util.Comparator;
11import java.util.HashMap;
12import java.util.List;
13import java.util.Map;
14import java.util.Optional;
15import java.util.concurrent.CopyOnWriteArrayList;
16import java.util.function.Predicate;
17import java.util.stream.Collectors;
18
19import javax.swing.AbstractAction;
20import javax.swing.AbstractButton;
21import javax.swing.Action;
22import javax.swing.JMenu;
23import javax.swing.KeyStroke;
24import javax.swing.text.JTextComponent;
25
26import org.openstreetmap.josm.data.Preferences;
27import org.openstreetmap.josm.spi.preferences.Config;
28
29/**
30 * Global shortcut class.
31 * <p>
32 * Note: This class represents a single shortcut, contains the factory to obtain
33 * shortcut objects from, manages shortcuts and shortcut collisions, and
34 * finally manages loading and saving shortcuts to/from the preferences.
35 * <p>
36 * Action authors: You only need the {@link #registerShortcut} factory. Ignore everything else.
37 * <p>
38 * All: Use only public methods that are also marked to be used. The others are
39 * public so the shortcut preferences can use them.
40 * @since 1084
41 */
42public final class Shortcut {
43 /** the unique ID of the shortcut */
44 private final String shortText;
45 /** a human readable description that will be shown in the preferences */
46 private String longText;
47 /** the key, the caller requested */
48 private final int requestedKey;
49 /** the group, the caller requested */
50 private final int requestedGroup;
51 /** the key that actually is used */
52 private int assignedKey;
53 /** the modifiers that are used */
54 private int assignedModifier;
55 /** true if it got assigned what was requested.
56 * (Note: modifiers will be ignored in favour of group when loading it from the preferences then.) */
57 private boolean assignedDefault;
58 /** true if the user changed this shortcut */
59 private boolean assignedUser;
60 /** true if the user cannot change this shortcut (Note: it also will not be saved into the preferences) */
61 private boolean automatic;
62 /** true if the user requested this shortcut to be set to its default value
63 * (will happen on next restart, as this shortcut will not be saved to the preferences) */
64 private boolean reset;
65
66 // simple constructor
67 private Shortcut(String shortText, String longText, int requestedKey, int requestedGroup, int assignedKey, int assignedModifier,
68 boolean assignedDefault, boolean assignedUser) {
69 this.shortText = shortText;
70 this.longText = longText;
71 this.requestedKey = requestedKey;
72 this.requestedGroup = requestedGroup;
73 this.assignedKey = assignedKey;
74 this.assignedModifier = assignedModifier;
75 this.assignedDefault = assignedDefault;
76 this.assignedUser = assignedUser;
77 this.automatic = false;
78 this.reset = false;
79 }
80
81 public String getShortText() {
82 return shortText;
83 }
84
85 public String getLongText() {
86 return longText;
87 }
88
89 // a shortcut will be renamed when it is handed out again, because the original name may be a dummy
90 private void setLongText(String longText) {
91 this.longText = longText;
92 }
93
94 public int getAssignedKey() {
95 return assignedKey;
96 }
97
98 public int getAssignedModifier() {
99 return assignedModifier;
100 }
101
102 public boolean isAssignedDefault() {
103 return assignedDefault;
104 }
105
106 public boolean isAssignedUser() {
107 return assignedUser;
108 }
109
110 public boolean isAutomatic() {
111 return automatic;
112 }
113
114 public boolean isChangeable() {
115 return !automatic && !"core:none".equals(shortText);
116 }
117
118 private boolean isReset() {
119 return reset;
120 }
121
122 /**
123 * FOR PREF PANE ONLY
124 */
125 public void setAutomatic() {
126 automatic = true;
127 }
128
129 /**
130 * FOR PREF PANE ONLY.<p>
131 * Sets the modifiers that are used.
132 * @param assignedModifier assigned modifier
133 */
134 public void setAssignedModifier(int assignedModifier) {
135 this.assignedModifier = assignedModifier;
136 }
137
138 /**
139 * FOR PREF PANE ONLY.<p>
140 * Sets the key that actually is used.
141 * @param assignedKey assigned key
142 */
143 public void setAssignedKey(int assignedKey) {
144 this.assignedKey = assignedKey;
145 }
146
147 /**
148 * FOR PREF PANE ONLY.<p>
149 * Sets whether the user has changed this shortcut.
150 * @param assignedUser {@code true} if the user has changed this shortcut
151 */
152 public void setAssignedUser(boolean assignedUser) {
153 this.reset = (this.assignedUser || reset) && !assignedUser;
154 if (assignedUser) {
155 assignedDefault = false;
156 } else if (reset) {
157 assignedKey = requestedKey;
158 assignedModifier = findModifier(requestedGroup, null);
159 }
160 this.assignedUser = assignedUser;
161 }
162
163 /**
164 * Use this to register the shortcut with Swing
165 * @return the key stroke
166 */
167 public KeyStroke getKeyStroke() {
168 if (assignedModifier != -1)
169 return KeyStroke.getKeyStroke(assignedKey, assignedModifier);
170 return null;
171 }
172
173 // create a shortcut object from an string as saved in the preferences
174 private Shortcut(String prefString) {
175 List<String> s = new ArrayList<>(Config.getPref().getList(prefString));
176 this.shortText = prefString.substring(15);
177 this.longText = s.get(0);
178 this.requestedKey = Integer.parseInt(s.get(1));
179 this.requestedGroup = Integer.parseInt(s.get(2));
180 this.assignedKey = Integer.parseInt(s.get(3));
181 this.assignedModifier = Integer.parseInt(s.get(4));
182 this.assignedDefault = Boolean.parseBoolean(s.get(5));
183 this.assignedUser = Boolean.parseBoolean(s.get(6));
184 }
185
186 private void saveDefault() {
187 Config.getPref().getList("shortcut.entry."+shortText, Arrays.asList(longText,
188 String.valueOf(requestedKey), String.valueOf(requestedGroup), String.valueOf(requestedKey),
189 String.valueOf(getGroupModifier(requestedGroup)), String.valueOf(true), String.valueOf(false)));
190 }
191
192 // get a string that can be put into the preferences
193 private boolean save() {
194 if (isAutomatic() || isReset() || !isAssignedUser()) {
195 return Config.getPref().putList("shortcut.entry."+shortText, null);
196 } else {
197 return Config.getPref().putList("shortcut.entry."+shortText, Arrays.asList(longText,
198 String.valueOf(requestedKey), String.valueOf(requestedGroup), String.valueOf(assignedKey),
199 String.valueOf(assignedModifier), String.valueOf(assignedDefault), String.valueOf(assignedUser)));
200 }
201 }
202
203 private boolean isSame(int isKey, int isModifier) {
204 // an unassigned shortcut is different from any other shortcut
205 return isKey == assignedKey && isModifier == assignedModifier && assignedModifier != getGroupModifier(NONE);
206 }
207
208 public boolean isEvent(KeyEvent e) {
209 KeyStroke ks = getKeyStroke();
210 return ks != null && ks.equals(KeyStroke.getKeyStroke(e.getKeyCode(), e.getModifiersEx()));
211 }
212
213 /**
214 * use this to set a menu's mnemonic
215 * @param menu menu
216 */
217 public void setMnemonic(JMenu menu) {
218 if (assignedModifier == getGroupModifier(MNEMONIC) && getKeyStroke() != null && KeyEvent.getKeyText(assignedKey).length() == 1) {
219 menu.setMnemonic(KeyEvent.getKeyText(assignedKey).charAt(0)); //getKeyStroke().getKeyChar() seems not to work here
220 }
221 }
222
223 /**
224 * use this to set a buttons's mnemonic
225 * @param button button
226 */
227 public void setMnemonic(AbstractButton button) {
228 if (assignedModifier == getGroupModifier(MNEMONIC) && getKeyStroke() != null && KeyEvent.getKeyText(assignedKey).length() == 1) {
229 button.setMnemonic(KeyEvent.getKeyText(assignedKey).charAt(0)); //getKeyStroke().getKeyChar() seems not to work here
230 }
231 }
232
233 /**
234 * Sets the mnemonic key on a text component.
235 * @param component component
236 */
237 public void setFocusAccelerator(JTextComponent component) {
238 if (assignedModifier == getGroupModifier(MNEMONIC) && getKeyStroke() != null && KeyEvent.getKeyText(assignedKey).length() == 1) {
239 component.setFocusAccelerator(KeyEvent.getKeyText(assignedKey).charAt(0));
240 }
241 }
242
243 /**
244 * use this to set a actions's accelerator
245 * @param action action
246 */
247 public void setAccelerator(AbstractAction action) {
248 if (getKeyStroke() != null) {
249 action.putValue(Action.ACCELERATOR_KEY, getKeyStroke());
250 }
251 }
252
253 /**
254 * Returns a human readable text for the shortcut.
255 * @return a human readable text for the shortcut
256 */
257 public String getKeyText() {
258 return getKeyText(getKeyStroke());
259 }
260
261 /**
262 * Returns a human readable text for the key stroke.
263 * @param keyStroke key stroke to convert to human readable text
264 * @return a human readable text for the key stroke
265 * @since 12520
266 */
267 public static String getKeyText(KeyStroke keyStroke) {
268 if (keyStroke == null) return "";
269 String modifText = InputEvent.getModifiersExText(keyStroke.getModifiers());
270 if (modifText.isEmpty()) return KeyEvent.getKeyText(keyStroke.getKeyCode());
271 return modifText + '+' + KeyEvent.getKeyText(keyStroke.getKeyCode());
272 }
273
274 /**
275 * Sets the action tooltip to the tooltip text plus the {@linkplain #getKeyText(KeyStroke) key stroke text}
276 * this shortcut represents.
277 *
278 * @param action action
279 * @param tooltip Tooltip text to display
280 * @since 14689
281 */
282 public void setTooltip(Action action, String tooltip) {
283 setTooltip(action, tooltip, getKeyStroke());
284 }
285
286 /**
287 * Sets the action tooltip to the tooltip text plus the {@linkplain #getKeyText(KeyStroke) key stroke text}.
288 *
289 * @param action action
290 * @param tooltip Tooltip text to display
291 * @param keyStroke Key stroke associated (to display accelerator between parenthesis)
292 * @since 14689
293 */
294 public static void setTooltip(Action action, String tooltip, KeyStroke keyStroke) {
295 action.putValue(Action.SHORT_DESCRIPTION, makeTooltip(tooltip, keyStroke));
296 }
297
298 @Override
299 public String toString() {
300 return getKeyText();
301 }
302
303 ///////////////////////////////
304 // everything's static below //
305 ///////////////////////////////
306
307 // here we store our shortcuts
308 private static final ShortcutCollection shortcuts = new ShortcutCollection();
309
310 private static final class ShortcutCollection extends CopyOnWriteArrayList<Shortcut> {
311 private static final long serialVersionUID = 1L;
312 @Override
313 public boolean add(Shortcut shortcut) {
314 // expensive consistency check only in debug mode
315 if (Logging.isDebugEnabled()
316 && stream().map(Shortcut::getShortText).anyMatch(shortcut.getShortText()::equals)) {
317 Logging.warn(new AssertionError(shortcut.getShortText() + " already added"));
318 }
319 return super.add(shortcut);
320 }
321
322 void replace(Shortcut newShortcut) {
323 final Optional<Shortcut> existing = findShortcutByKeyOrShortText(-1, NONE, newShortcut.shortText);
324 if (existing.isPresent()) {
325 replaceAll(sc -> existing.get() == sc ? newShortcut : sc);
326 } else {
327 add(newShortcut);
328 }
329 }
330 }
331
332 // and here our modifier groups
333 private static final Map<Integer, Integer> groups = new HashMap<>();
334
335 // check if something collides with an existing shortcut
336
337 /**
338 * Returns the registered shortcut fot the key and modifier
339 * @param requestedKey the requested key
340 * @param modifier the modifier
341 * @return an {@link Optional} registered shortcut, never {@code null}
342 */
343 public static Optional<Shortcut> findShortcut(int requestedKey, int modifier) {
344 return findShortcutByKeyOrShortText(requestedKey, modifier, null);
345 }
346
347 private static Optional<Shortcut> findShortcutByKeyOrShortText(int requestedKey, int modifier, String shortText) {
348 final Predicate<Shortcut> sameKey = sc -> modifier != getGroupModifier(NONE) && sc.isSame(requestedKey, modifier);
349 final Predicate<Shortcut> sameShortText = sc -> sc.getShortText().equals(shortText);
350 return shortcuts.stream()
351 .filter(sameKey.or(sameShortText))
352 .sorted(Comparator.comparingInt(sc -> sameShortText.test(sc) ? 0 : 1))
353 .findAny();
354 }
355
356 /**
357 * Returns a list of all shortcuts.
358 * @return a list of all shortcuts
359 */
360 public static List<Shortcut> listAll() {
361 return shortcuts.stream()
362 .filter(c -> !"core:none".equals(c.shortText))
363 .collect(Collectors.toList());
364 }
365
366 /** None group: used with KeyEvent.CHAR_UNDEFINED if no shortcut is defined */
367 public static final int NONE = 5000;
368 public static final int MNEMONIC = 5001;
369 /** Reserved group: for system shortcuts only */
370 public static final int RESERVED = 5002;
371 /** Direct group: no modifier */
372 public static final int DIRECT = 5003;
373 /** Alt group */
374 public static final int ALT = 5004;
375 /** Shift group */
376 public static final int SHIFT = 5005;
377 /** Command group. Matches CTRL modifier on Windows/Linux but META modifier on OS X */
378 public static final int CTRL = 5006;
379 /** Alt-Shift group */
380 public static final int ALT_SHIFT = 5007;
381 /** Alt-Command group. Matches ALT-CTRL modifier on Windows/Linux but ALT-META modifier on OS X */
382 public static final int ALT_CTRL = 5008;
383 /** Command-Shift group. Matches CTRL-SHIFT modifier on Windows/Linux but META-SHIFT modifier on OS X */
384 public static final int CTRL_SHIFT = 5009;
385 /** Alt-Command-Shift group. Matches ALT-CTRL-SHIFT modifier on Windows/Linux but ALT-META-SHIFT modifier on OS X */
386 public static final int ALT_CTRL_SHIFT = 5010;
387
388 /* for reassignment */
389 private static final int[] mods = {ALT_CTRL, ALT_SHIFT, CTRL_SHIFT, ALT_CTRL_SHIFT};
390 private static final int[] keys = {KeyEvent.VK_F1, KeyEvent.VK_F2, KeyEvent.VK_F3, KeyEvent.VK_F4,
391 KeyEvent.VK_F5, KeyEvent.VK_F6, KeyEvent.VK_F7, KeyEvent.VK_F8,
392 KeyEvent.VK_F9, KeyEvent.VK_F10, KeyEvent.VK_F11, KeyEvent.VK_F12};
393
394 // bootstrap
395 private static boolean initdone;
396 private static void doInit() {
397 if (initdone) return;
398 initdone = true;
399 int commandDownMask = PlatformManager.getPlatform().getMenuShortcutKeyMaskEx();
400 groups.put(NONE, -1);
401 groups.put(MNEMONIC, InputEvent.ALT_DOWN_MASK);
402 groups.put(DIRECT, 0);
403 groups.put(ALT, InputEvent.ALT_DOWN_MASK);
404 groups.put(SHIFT, InputEvent.SHIFT_DOWN_MASK);
405 groups.put(CTRL, commandDownMask);
406 groups.put(ALT_SHIFT, InputEvent.ALT_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK);
407 groups.put(ALT_CTRL, InputEvent.ALT_DOWN_MASK | commandDownMask);
408 groups.put(CTRL_SHIFT, commandDownMask | InputEvent.SHIFT_DOWN_MASK);
409 groups.put(ALT_CTRL_SHIFT, InputEvent.ALT_DOWN_MASK | commandDownMask | InputEvent.SHIFT_DOWN_MASK);
410
411 // (1) System reserved shortcuts
412 PlatformManager.getPlatform().initSystemShortcuts();
413 // (2) User defined shortcuts
414 Preferences.main().getAllPrefixCollectionKeys("shortcut.entry.").stream()
415 .map(Shortcut::new)
416 .filter(sc -> !findShortcut(sc.getAssignedKey(), sc.getAssignedModifier()).isPresent())
417 .sorted(Comparator.comparing(sc -> sc.isAssignedUser() ? 1 : sc.isAssignedDefault() ? 2 : 3))
418 .forEachOrdered(shortcuts::replace);
419 }
420
421 private static int getGroupModifier(int group) {
422 return Optional.ofNullable(groups.get(group)).orElse(-1);
423 }
424
425 private static int findModifier(int group, Integer modifier) {
426 if (modifier == null) {
427 modifier = getGroupModifier(group);
428 if (modifier == null) { // garbage in, no shortcut out
429 modifier = getGroupModifier(NONE);
430 }
431 }
432 return modifier;
433 }
434
435 // shutdown handling
436
437 /**
438 * Save shortcuts to preferences
439 * @return {@code true} if preferences were changed
440 */
441 public static boolean savePrefs() {
442 return shortcuts.stream()
443 .map(Shortcut::save)
444 .reduce(Boolean.FALSE, Boolean::logicalOr); // has changed
445 }
446
447 /**
448 * FOR PLATFORMHOOK USE ONLY.
449 * <p>
450 * This registers a system shortcut. See PlatformHook for details.
451 * @param shortText an ID. re-use a {@code "system:*"} ID if possible, else use something unique.
452 * @param longText this will be displayed in the shortcut preferences dialog. Better
453 * use something the user will recognize...
454 * @param key the key. Use a {@link KeyEvent KeyEvent.VK_*} constant here.
455 * @param modifier the modifier. Use a {@link KeyEvent KeyEvent.*_MASK} constant here.
456 * @return the system shortcut
457 */
458 public static Shortcut registerSystemShortcut(String shortText, String longText, int key, int modifier) {
459 final Optional<Shortcut> existing = findShortcutByKeyOrShortText(key, modifier, shortText);
460 if (existing.isPresent() && shortText.equals(existing.get().getShortText())) {
461 return existing.get();
462 } else if (existing.isPresent()) {
463 // this always is a logic error in the hook
464 Logging.error("CONFLICT WITH SYSTEM KEY " + shortText + ": " + existing.get());
465 return null;
466 }
467 final Shortcut shortcut = new Shortcut(shortText, longText, key, RESERVED, key, modifier, true, false);
468 shortcuts.add(shortcut);
469 return shortcut;
470 }
471
472 /**
473 * Register a shortcut linked to several characters.
474 *
475 * @param shortText an ID. re-use a {@code "system:*"} ID if possible, else use something unique.
476 * {@code "menu:*"} is reserved for menu mnemonics, {@code "core:*"} is reserved for
477 * actions that are part of JOSM's core. Use something like
478 * {@code <pluginname>+":"+<actionname>}.
479 * @param longText this will be displayed in the shortcut preferences dialog. Better
480 * use something the user will recognize...
481 * @param characters the characters you'd prefer
482 * @param requestedGroup the group this shortcut fits best. This will determine the
483 * modifiers your shortcut will get assigned. Use the constants defined above.
484 * @return the shortcut
485 */
486 public static List<Shortcut> registerMultiShortcuts(String shortText, String longText, List<Character> characters, int requestedGroup) {
487 List<Shortcut> result = new ArrayList<>();
488 int i = 1;
489 Map<Integer, Integer> regularKeyCodes = KeyboardUtils.getRegularKeyCodesMap();
490 for (Character c : characters) {
491 Integer code = (int) c;
492 result.add(registerShortcut(
493 shortText + " (" + i + ')', longText,
494 // Add extended keyCode if not a regular one
495 regularKeyCodes.containsKey(code) ? regularKeyCodes.get(code) :
496 isDeadKey(code) ? code : c | KeyboardUtils.EXTENDED_KEYCODE_FLAG,
497 requestedGroup));
498 i++;
499 }
500 return result;
501 }
502
503 static boolean isDeadKey(int keyCode) {
504 return KeyEvent.VK_DEAD_GRAVE <= keyCode && keyCode <= KeyEvent.VK_DEAD_SEMIVOICED_SOUND;
505 }
506
507 /**
508 * Register a shortcut.
509 * <p>
510 * Here you get your shortcuts from. The parameters are:
511 *
512 * @param shortText an ID. re-use a {@code "system:*"} ID if possible, else use something unique.
513 * {@code "menu:*"} is reserved for menu mnemonics, {@code "core:*"} is reserved for
514 * actions that are part of JOSM's core. Use something like
515 * {@code <pluginname>+":"+<actionname>}.
516 * @param longText this will be displayed in the shortcut preferences dialog. Better
517 * use something the user will recognize...
518 * @param requestedKey the key you'd prefer. Use a {@link KeyEvent KeyEvent.VK_*} constant here.
519 * @param requestedGroup the group this shortcut fits best. This will determine the
520 * modifiers your shortcut will get assigned. Use the constants defined above.
521 * @return the shortcut
522 */
523 public static Shortcut registerShortcut(String shortText, String longText, int requestedKey, int requestedGroup) {
524 return registerShortcut(shortText, longText, requestedKey, requestedGroup, null);
525 }
526
527 // and now the workhorse. same parameters as above, just one more
528 private static Shortcut registerShortcut(String shortText, String longText, int requestedKey, int requestedGroup, Integer modifier) {
529 doInit();
530 int defaultModifier = findModifier(requestedGroup, modifier);
531 final Optional<Shortcut> existing = findShortcutByKeyOrShortText(requestedKey, defaultModifier, shortText);
532 if (existing.isPresent() && shortText.equals(existing.get().getShortText())) {
533 // a re-register? maybe a sc already read from the preferences?
534 final Shortcut sc = existing.get();
535 sc.setLongText(longText); // or set by the platformHook, in this case the original longText doesn't match the real action
536 sc.saveDefault();
537 return sc;
538 } else if (existing.isPresent()) {
539 final Shortcut conflict = existing.get();
540 if (PlatformManager.isPlatformOsx()) {
541 // Try to reassign Meta to Ctrl
542 int newmodifier = findNewOsxModifier(requestedGroup);
543 if (!findShortcut(requestedKey, newmodifier).isPresent()) {
544 Logging.info("Reassigning macOS shortcut '" + shortText + "' from Meta to Ctrl because of conflict with " + conflict);
545 return reassignShortcut(shortText, longText, requestedKey, conflict, requestedGroup, requestedKey, newmodifier);
546 }
547 }
548 for (int m : mods) {
549 for (int k : keys) {
550 int newmodifier = getGroupModifier(m);
551 if (!findShortcut(k, newmodifier).isPresent()) {
552 Logging.info("Reassigning shortcut '" + shortText + "' from " + modifier + " to " + newmodifier +
553 " because of conflict with " + conflict);
554 return reassignShortcut(shortText, longText, requestedKey, conflict, m, k, newmodifier);
555 }
556 }
557 }
558 } else {
559 Shortcut newsc = new Shortcut(shortText, longText, requestedKey, requestedGroup, requestedKey, defaultModifier, true, false);
560 newsc.saveDefault();
561 shortcuts.add(newsc);
562 return newsc;
563 }
564
565 return null;
566 }
567
568 private static int findNewOsxModifier(int requestedGroup) {
569 switch (requestedGroup) {
570 case CTRL: return InputEvent.CTRL_DOWN_MASK;
571 case ALT_CTRL: return InputEvent.ALT_DOWN_MASK | InputEvent.CTRL_DOWN_MASK;
572 case CTRL_SHIFT: return InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK;
573 case ALT_CTRL_SHIFT: return InputEvent.ALT_DOWN_MASK | InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK;
574 default: return 0;
575 }
576 }
577
578 private static Shortcut reassignShortcut(String shortText, String longText, int requestedKey, Shortcut conflict,
579 int m, int k, int newmodifier) {
580 Shortcut newsc = new Shortcut(shortText, longText, requestedKey, m, k, newmodifier, false, false);
581 Logging.info(tr("Silent shortcut conflict: ''{0}'' moved by ''{1}'' to ''{2}''.",
582 shortText, conflict.getShortText(), newsc.getKeyText()));
583 newsc.saveDefault();
584 shortcuts.add(newsc);
585 return newsc;
586 }
587
588 /**
589 * Replies the platform specific key stroke for the 'Copy' command, i.e.
590 * 'Ctrl-C' on windows or 'Meta-C' on a Mac. null, if the platform specific
591 * copy command isn't known.
592 *
593 * @return the platform specific key stroke for the 'Copy' command
594 */
595 public static KeyStroke getCopyKeyStroke() {
596 return getKeyStrokeForShortKey("system:copy");
597 }
598
599 /**
600 * Replies the platform specific key stroke for the 'Paste' command, i.e.
601 * 'Ctrl-V' on windows or 'Meta-V' on a Mac. null, if the platform specific
602 * paste command isn't known.
603 *
604 * @return the platform specific key stroke for the 'Paste' command
605 */
606 public static KeyStroke getPasteKeyStroke() {
607 return getKeyStrokeForShortKey("system:paste");
608 }
609
610 /**
611 * Replies the platform specific key stroke for the 'Cut' command, i.e.
612 * 'Ctrl-X' on windows or 'Meta-X' on a Mac. null, if the platform specific
613 * 'Cut' command isn't known.
614 *
615 * @return the platform specific key stroke for the 'Cut' command
616 */
617 public static KeyStroke getCutKeyStroke() {
618 return getKeyStrokeForShortKey("system:cut");
619 }
620
621 private static KeyStroke getKeyStrokeForShortKey(String shortKey) {
622 return shortcuts.stream()
623 .filter(sc -> shortKey.equals(sc.getShortText()))
624 .findAny()
625 .map(Shortcut::getKeyStroke)
626 .orElse(null);
627 }
628
629 /**
630 * Returns the tooltip text plus the {@linkplain #getKeyText(KeyStroke) key stroke text}.
631 * <p>
632 * Tooltips are usually not system dependent, unless the
633 * JVM is too dumb to provide correct names for all the keys.
634 * <p>
635 * Some LAFs don't understand HTML, such as the OSX LAFs.
636 *
637 * @param tooltip Tooltip text to display
638 * @param keyStroke Key stroke associated (to display accelerator between parenthesis)
639 * @return Full tooltip text (tooltip + accelerator)
640 * @since 14689
641 */
642 public static String makeTooltip(String tooltip, KeyStroke keyStroke) {
643 final Optional<String> keyStrokeText = Optional.ofNullable(keyStroke)
644 .map(Shortcut::getKeyText)
645 .filter(text -> !text.isEmpty());
646
647 final boolean canHtml = PlatformManager.getPlatform().isHtmlSupportedInMenuTooltips();
648
649 StringBuilder result = new StringBuilder(48);
650 if (canHtml) {
651 result.append("<html>");
652 }
653 result.append(tooltip);
654 if (keyStrokeText.isPresent()) {
655 result.append(' ');
656 if (canHtml) {
657 result.append("<font size='-2'>");
658 }
659 result.append('(').append(keyStrokeText.get()).append(')');
660 if (canHtml) {
661 result.append("</font>");
662 }
663 }
664 if (canHtml) {
665 result.append("&nbsp;</html>");
666 }
667 return result.toString();
668 }
669}
Note: See TracBrowser for help on using the repository browser.