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, 5 months 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.