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

Last change on this file since 14012 was 14012, checked in by Don-vip, 6 years ago

see #16453 - proper support of different keyboard layouts

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