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

Revision 5002, 17.7 KB checked in by stoecker, 3 months ago (diff)

remove deprecations, all plugins are updated and release, external plugins should break, so they get updated

  • Property svn:eol-style set to native
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.ArrayList;
8import java.util.Arrays;
9import java.util.Collection;
10import java.util.HashMap;
11import java.util.LinkedHashMap;
12import java.util.LinkedList;
13import java.util.List;
14import java.util.Map;
15
16import javax.swing.AbstractAction;
17import javax.swing.AbstractButton;
18import javax.swing.JMenu;
19import javax.swing.JOptionPane;
20import javax.swing.KeyStroke;
21
22import org.openstreetmap.josm.Main;
23
24/**
25 * Global shortcut class.
26 *
27 * Note: This class represents a single shortcut, contains the factory to obtain
28 *       shortcut objects from, manages shortcuts and shortcut collisions, and
29 *       finally manages loading and saving shortcuts to/from the preferences.
30 *
31 * Action authors: You only need the {@see #registerShortcut} factory. Ignore everything
32 *                 else.
33 *
34 * All: Use only public methods that are also marked to be used. The others are
35 *      public so the shortcut preferences can use them.
36 *
37 */
38public class Shortcut {
39    private String shortText;        // the unique ID of the shortcut
40    private String longText;         // a human readable description that will be shown in the preferences
41    private int requestedKey;        // the key, the caller requested
42    private int requestedGroup;      // the group, the caller requested
43    private int assignedKey;         // the key that actually is used
44    private int assignedModifier;    // the modifiers that are used
45    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.)
46    private boolean assignedUser;    // true if the user changed this shortcut
47    private boolean automatic;       // true if the user cannot change this shortcut (Note: it also will not be saved into the preferences)
48    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)
49
50    // simple constructor
51    private Shortcut(String shortText, String longText, int requestedKey, int requestedGroup, int assignedKey, int assignedModifier, boolean assignedDefault, boolean assignedUser) {
52        this.shortText = shortText;
53        this.longText = longText;
54        this.requestedKey = requestedKey;
55        this.requestedGroup = requestedGroup;
56        this.assignedKey = assignedKey;
57        this.assignedModifier = assignedModifier;
58        this.assignedDefault = assignedDefault;
59        this.assignedUser = assignedUser;
60        this.automatic = false;
61        this.reset = false;
62    }
63
64    public String getShortText() {
65        return shortText;
66    }
67
68    public String getLongText() {
69        return longText;
70    }
71
72    // a shortcut will be renamed when it is handed out again, because the original name
73    // may be a dummy
74    private void setLongText(String longText) {
75        this.longText = longText;
76    }
77
78    private int getRequestedKey() {
79        return requestedKey;
80    }
81
82    public int getRequestedGroup() {
83        return requestedGroup;
84    }
85
86    public int getAssignedKey() {
87        return assignedKey;
88    }
89
90    public int getAssignedModifier() {
91        return assignedModifier;
92    }
93
94    public boolean getAssignedDefault() {
95        return assignedDefault;
96    }
97
98    public boolean getAssignedUser() {
99        return assignedUser;
100    }
101
102    public boolean getAutomatic() {
103        return automatic;
104    }
105
106    public boolean isChangeable() {
107        return !automatic && !shortText.equals("core:none");
108    }
109
110    private boolean getReset() {
111        return reset;
112    }
113
114    /**
115     * FOR PREF PANE ONLY
116     */
117    public void setAutomatic() {
118        automatic = true;
119    }
120
121    /**
122     * FOR PREF PANE ONLY
123     */
124    public void setAssignedModifier(int assignedModifier) {
125        this.assignedModifier = assignedModifier;
126    }
127
128    /**
129     * FOR PREF PANE ONLY
130     */
131    public void setAssignedKey(int assignedKey) {
132        this.assignedKey = assignedKey;
133    }
134
135    /**
136     * FOR PREF PANE ONLY
137     */
138    public void setAssignedUser(boolean assignedUser) {
139        this.reset = (this.assignedUser || reset) && !assignedUser;
140        if (assignedUser) {
141            assignedDefault = false;
142        } else if (reset) {
143            assignedKey = requestedKey;
144            assignedModifier = findModifier(requestedGroup, null);
145        }
146        this.assignedUser = assignedUser;
147    }
148
149    /**
150     * Use this to register the shortcut with Swing
151     */
152    public KeyStroke getKeyStroke() {
153        if (assignedModifier != -1)
154            return KeyStroke.getKeyStroke(assignedKey, assignedModifier);
155        return null;
156    }
157
158    // create a shortcut object from an string as saved in the preferences
159    private Shortcut(String prefString) {
160        ArrayList<String> s = (new ArrayList<String>(Main.pref.getCollection(prefString)));
161        this.shortText = prefString.substring(15);
162        this.longText = s.get(0);
163        this.requestedKey = Integer.parseInt(s.get(1));
164        this.requestedGroup = Integer.parseInt(s.get(2));
165        this.assignedKey = Integer.parseInt(s.get(3));
166        this.assignedModifier = Integer.parseInt(s.get(4));
167        this.assignedDefault = Boolean.parseBoolean(s.get(5));
168        this.assignedUser = Boolean.parseBoolean(s.get(6));
169    }
170
171    private void saveDefault() {
172        Main.pref.getCollection("shortcut.entry."+shortText, Arrays.asList(new String[]{longText,
173        String.valueOf(requestedKey), String.valueOf(requestedGroup), String.valueOf(requestedKey),
174        String.valueOf(getGroupModifier(requestedGroup)), String.valueOf(true), String.valueOf(false)}));
175    }
176
177    // get a string that can be put into the preferences
178    private boolean save() {
179        if (getAutomatic() || getReset() || !getAssignedUser()) {
180            return Main.pref.putCollection("shortcut.entry."+shortText, null);
181        } else {
182            return Main.pref.putCollection("shortcut.entry."+shortText, Arrays.asList(new String[]{longText,
183            String.valueOf(requestedKey), String.valueOf(requestedGroup), String.valueOf(assignedKey),
184            String.valueOf(assignedModifier), String.valueOf(assignedDefault), String.valueOf(assignedUser)}));
185        }
186    }
187
188    private boolean isSame(int isKey, int isModifier) {
189        // an unassigned shortcut is different from any other shortcut
190        return isKey == assignedKey && isModifier == assignedModifier && assignedModifier != getGroupModifier(NONE);
191    }
192
193    public boolean isEvent(KeyEvent e) {
194        return getKeyStroke() != null && getKeyStroke().equals(
195        KeyStroke.getKeyStroke(e.getKeyCode(), e.getModifiers()));
196    }
197
198    /**
199     * use this to set a menu's mnemonic
200     */
201    public void setMnemonic(JMenu menu) {
202        if (assignedModifier == getGroupModifier(MNEMONIC) && getKeyStroke() != null && KeyEvent.getKeyText(assignedKey).length() == 1) {
203            menu.setMnemonic(KeyEvent.getKeyText(assignedKey).charAt(0)); //getKeyStroke().getKeyChar() seems not to work here
204        }
205    }
206    /**
207     * use this to set a buttons's mnemonic
208     */
209    public void setMnemonic(AbstractButton button) {
210        if (assignedModifier == getGroupModifier(MNEMONIC)  && getKeyStroke() != null && KeyEvent.getKeyText(assignedKey).length() == 1) {
211            button.setMnemonic(KeyEvent.getKeyText(assignedKey).charAt(0)); //getKeyStroke().getKeyChar() seems not to work here
212        }
213    }
214    /**
215     * use this to set a actions's accelerator
216     */
217    public void setAccelerator(AbstractAction action) {
218        if (getKeyStroke() != null) {
219            action.putValue(AbstractAction.ACCELERATOR_KEY, getKeyStroke());
220        }
221    }
222
223    /**
224     * use this to get a human readable text for your shortcut
225     */
226    public String getKeyText() {
227        KeyStroke keyStroke = getKeyStroke();
228        if (keyStroke == null) return "";
229        String modifText = KeyEvent.getKeyModifiersText(keyStroke.getModifiers());
230        if ("".equals (modifText)) return KeyEvent.getKeyText (keyStroke.getKeyCode ());
231        return modifText + "+" + KeyEvent.getKeyText(keyStroke.getKeyCode ());
232    }
233
234    @Override
235    public String toString() {
236        return getKeyText();
237    }
238
239    ///////////////////////////////
240    // everything's static below //
241    ///////////////////////////////
242
243    // here we store our shortcuts
244    private static Map<String, Shortcut> shortcuts = new LinkedHashMap<String, Shortcut>();
245
246    // and here our modifier groups
247    private static Map<Integer, Integer> groups= new HashMap<Integer, Integer>();
248
249    // check if something collides with an existing shortcut
250    private static Shortcut findShortcut(int requestedKey, int modifier) {
251        if (modifier == getGroupModifier(NONE))
252            return null;
253        for (Shortcut sc : shortcuts.values()) {
254            if (sc.isSame(requestedKey, modifier))
255                return sc;
256        }
257        return null;
258    }
259
260    /**
261     * FOR PREF PANE ONLY
262     */
263    public static List<Shortcut> listAll() {
264        List<Shortcut> l = new ArrayList<Shortcut>();
265        for(Shortcut c : shortcuts.values())
266        {
267            if(!c.shortText.equals("core:none")) {
268                l.add(c);
269            }
270        }
271        return l;
272    }
273
274    public static final int NONE = 5000;
275    public static final int MNEMONIC = 5001;
276    public static final int RESERVED = 5002;
277    public static final int DIRECT = 5003;
278    public static final int ALT = 5004;
279    public static final int SHIFT = 5005;
280    public static final int CTRL = 5006;
281    public static final int ALT_SHIFT = 5007;
282    public static final int ALT_CTRL = 5008;
283    public static final int CTRL_SHIFT = 5009;
284    public static final int ALT_CTRL_SHIFT = 5010;
285
286    /* for reassignment */
287    private static int[] mods = {ALT_CTRL, ALT_SHIFT, CTRL_SHIFT, ALT_CTRL_SHIFT};
288    private static int[] keys = {KeyEvent.VK_F1, KeyEvent.VK_F2, KeyEvent.VK_F3, KeyEvent.VK_F4,
289                                 KeyEvent.VK_F5, KeyEvent.VK_F6, KeyEvent.VK_F7, KeyEvent.VK_F8,
290                                 KeyEvent.VK_F9, KeyEvent.VK_F10, KeyEvent.VK_F11, KeyEvent.VK_F12};
291
292    // bootstrap
293    private static boolean initdone = false;
294    private static void doInit() {
295        if (initdone) return;
296        initdone = true;
297        groups.put(NONE, -1);
298        groups.put(MNEMONIC, KeyEvent.ALT_DOWN_MASK);
299        groups.put(DIRECT, 0);
300        groups.put(ALT, KeyEvent.ALT_DOWN_MASK);
301        groups.put(SHIFT, KeyEvent.SHIFT_DOWN_MASK);
302        groups.put(CTRL, KeyEvent.CTRL_DOWN_MASK);
303        groups.put(ALT_SHIFT, KeyEvent.ALT_DOWN_MASK|KeyEvent.SHIFT_DOWN_MASK);
304        groups.put(ALT_CTRL, KeyEvent.ALT_DOWN_MASK|KeyEvent.CTRL_DOWN_MASK);
305        groups.put(CTRL_SHIFT, KeyEvent.CTRL_DOWN_MASK|KeyEvent.SHIFT_DOWN_MASK);
306        groups.put(ALT_CTRL_SHIFT, KeyEvent.ALT_DOWN_MASK|KeyEvent.CTRL_DOWN_MASK|KeyEvent.SHIFT_DOWN_MASK);
307
308        // (1) System reserved shortcuts
309        Main.platform.initSystemShortcuts();
310        // (2) User defined shortcuts
311        LinkedList<Shortcut> newshortcuts = new LinkedList<Shortcut>();
312        for(String s : Main.pref.getAllPrefixCollectionKeys("shortcut.entry.")) {
313            newshortcuts.add(new Shortcut(s));
314        }
315
316        for(Shortcut sc : newshortcuts) {
317            if (sc.getAssignedUser()
318            && findShortcut(sc.getAssignedKey(), sc.getAssignedModifier()) == null) {
319                shortcuts.put(sc.getShortText(), sc);
320            }
321        }
322        // Shortcuts at their default values
323        for(Shortcut sc : newshortcuts) {
324            if (!sc.getAssignedUser() && sc.getAssignedDefault()
325            && findShortcut(sc.getAssignedKey(), sc.getAssignedModifier()) == null) {
326                shortcuts.put(sc.getShortText(), sc);
327            }
328        }
329        // Shortcuts that were automatically moved
330        for(Shortcut sc : newshortcuts) {
331            if (!sc.getAssignedUser() && !sc.getAssignedDefault()
332            && findShortcut(sc.getAssignedKey(), sc.getAssignedModifier()) == null) {
333                shortcuts.put(sc.getShortText(), sc);
334            }
335        }
336    }
337
338    private static int getGroupModifier(int group) {
339        Integer m = groups.get(group);
340        if(m == null)
341            m = -1;
342        return m;
343    }
344
345    private static int findModifier(int group, Integer modifier) {
346        if(modifier == null) {
347            modifier = getGroupModifier(group);
348            if (modifier == null) { // garbage in, no shortcut out
349                modifier = getGroupModifier(NONE);
350            }
351        }
352        return modifier;
353    }
354
355    // shutdown handling
356    public static boolean savePrefs() {
357        boolean changed = false;
358        for (Shortcut sc : shortcuts.values()) {
359            changed = changed | sc.save();
360        }
361        return changed;
362    }
363
364    /**
365     * FOR PLATFORMHOOK USE ONLY
366     *
367     * This registers a system shortcut. See PlatformHook for details.
368     */
369    public static Shortcut registerSystemShortcut(String shortText, String longText, int key, int modifier) {
370        if (shortcuts.containsKey(shortText))
371            return shortcuts.get(shortText);
372        Shortcut potentialShortcut = findShortcut(key, modifier);
373        if (potentialShortcut != null) {
374            // this always is a logic error in the hook
375            System.err.println("CONFLICT WITH SYSTEM KEY "+shortText);
376            return null;
377        }
378        potentialShortcut = new Shortcut(shortText, longText, key, RESERVED, key, modifier, true, false);
379        shortcuts.put(shortText, potentialShortcut);
380        return potentialShortcut;
381    }
382
383    /**
384     * Register a shortcut.
385     *
386     * Here you get your shortcuts from. The parameters are:
387     *
388     * @param shortText an ID. re-use a {@code "system:*"} ID if possible, else use something unique.
389     * {@code "menu:*"} is reserved for menu mnemonics, {@code "core:*"} is reserved for
390     * actions that are part of JOSM's core. Use something like
391     * {@code <pluginname>+":"+<actionname>}.
392     * @param longText this will be displayed in the shortcut preferences dialog. Better
393     * use something the user will recognize...
394     * @param requestedKey the key you'd prefer. Use a {@link KeyEvent KeyEvent.VK_*} constant here.
395     * @param requestedGroup the group this shortcut fits best. This will determine the
396     * modifiers your shortcut will get assigned. Use the constants defined above.
397     */
398    public static Shortcut registerShortcut(String shortText, String longText, int requestedKey, int requestedGroup) {
399        return registerShortcut(shortText, longText, requestedKey, requestedGroup, null);
400    }
401
402    // and now the workhorse. same parameters as above, just one more
403    private static Shortcut registerShortcut(String shortText, String longText, int requestedKey, int requestedGroup, Integer modifier) {
404        doInit();
405        Integer defaultModifier = findModifier(requestedGroup, modifier);
406        if (shortcuts.containsKey(shortText)) { // a re-register? maybe a sc already read from the preferences?
407            Shortcut sc = shortcuts.get(shortText);
408            sc.setLongText(longText); // or set by the platformHook, in this case the original longText doesn't match the real action
409            sc.saveDefault();
410            return sc;
411        }
412        Shortcut conflict = findShortcut(requestedKey, defaultModifier);
413        if (conflict != null) {
414            for (int m : mods) {
415                for (int k : keys) {
416                    int newmodifier = getGroupModifier(m);
417                    if ( findShortcut(k, newmodifier) == null ) {
418                        Shortcut newsc = new Shortcut(shortText, longText, requestedKey, m, k, newmodifier, false, false);
419                        System.out.println(tr("Silent shortcut conflict: ''{0}'' moved by ''{1}'' to ''{2}''.",
420                            shortText, conflict.getShortText(), newsc.getKeyText()));
421                        newsc.saveDefault();
422                        shortcuts.put(shortText, newsc);
423                        return newsc;
424                    }
425                }
426            }
427        } else {
428            Shortcut newsc = new Shortcut(shortText, longText, requestedKey, requestedGroup, requestedKey, defaultModifier, true, false);
429            newsc.saveDefault();
430            shortcuts.put(shortText, newsc);
431            return newsc;
432        }
433
434        return null;
435    }
436
437    /**
438     * Replies the platform specific key stroke for the 'Copy' command, i.e.
439     * 'Ctrl-C' on windows or 'Meta-C' on a Mac. null, if the platform specific
440     * copy command isn't known.
441     *
442     * @return the platform specific key stroke for the  'Copy' command
443     */
444    static public KeyStroke getCopyKeyStroke() {
445        Shortcut sc = shortcuts.get("system:copy");
446        if (sc == null) return null;
447        return sc.getKeyStroke();
448    }
449
450    /**
451     * Replies the platform specific key stroke for the 'Paste' command, i.e.
452     * 'Ctrl-V' on windows or 'Meta-V' on a Mac. null, if the platform specific
453     * paste command isn't known.
454     *
455     * @return the platform specific key stroke for the 'Paste' command
456     */
457    static public KeyStroke getPasteKeyStroke() {
458        Shortcut sc = shortcuts.get("system:paste");
459        if (sc == null) return null;
460        return sc.getKeyStroke();
461    }
462
463    /**
464     * Replies the platform specific key stroke for the 'Cut' command, i.e.
465     * 'Ctrl-X' on windows or 'Meta-X' on a Mac. null, if the platform specific
466     * 'Cut' command isn't known.
467     *
468     * @return the platform specific key stroke for the 'Cut' command
469     */
470    static public KeyStroke getCutKeyStroke() {
471        Shortcut sc = shortcuts.get("system:cut");
472        if (sc == null) return null;
473        return sc.getKeyStroke();
474    }
475}
Note: See TracBrowser for help on using the repository browser.