001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.widgets;
003
004import java.awt.event.FocusEvent;
005import java.awt.event.KeyEvent;
006import java.util.ArrayList;
007import java.util.HashSet;
008import java.util.List;
009import java.util.Set;
010
011import javax.swing.Action;
012import javax.swing.JMenu;
013import javax.swing.JMenuItem;
014import javax.swing.KeyStroke;
015import javax.swing.text.Document;
016
017import org.openstreetmap.josm.Main;
018import org.openstreetmap.josm.actions.JosmAction;
019import org.openstreetmap.josm.tools.Pair;
020import org.openstreetmap.josm.tools.Shortcut;
021
022/**
023 * A JTextField that disabled all JOSM shortcuts composed of a single key without modifier (except F1 to F12),
024 * in order to avoid them to be triggered while typing.
025 * This allows to include text fields in toggle dialogs (needed for relation filter).
026 * @since 5696
027 */
028public class DisableShortcutsOnFocusGainedTextField extends JosmTextField {
029
030    /**
031     * Constructs a new <code>TextField</code>.  A default model is created,
032     * the initial string is <code>null</code>,
033     * and the number of columns is set to 0.
034     */
035    public DisableShortcutsOnFocusGainedTextField() {
036    }
037
038    /**
039     * Constructs a new <code>TextField</code> initialized with the
040     * specified text. A default model is created and the number of
041     * columns is 0.
042     *
043     * @param text the text to be displayed, or <code>null</code>
044     */
045    public DisableShortcutsOnFocusGainedTextField(String text) {
046        super(text);
047    }
048
049    /**
050     * Constructs a new empty <code>TextField</code> with the specified
051     * number of columns.
052     * A default model is created and the initial string is set to
053     * <code>null</code>.
054     *
055     * @param columns  the number of columns to use to calculate
056     *   the preferred width; if columns is set to zero, the
057     *   preferred width will be whatever naturally results from
058     *   the component implementation
059     */
060    public DisableShortcutsOnFocusGainedTextField(int columns) {
061        super(columns);
062    }
063
064    /**
065     * Constructs a new <code>TextField</code> initialized with the
066     * specified text and columns.  A default model is created.
067     *
068     * @param text the text to be displayed, or <code>null</code>
069     * @param columns  the number of columns to use to calculate
070     *   the preferred width; if columns is set to zero, the
071     *   preferred width will be whatever naturally results from
072     *   the component implementation
073     */
074    public DisableShortcutsOnFocusGainedTextField(String text, int columns) {
075        super(text, columns);
076    }
077
078    /**
079     * Constructs a new <code>JTextField</code> that uses the given text
080     * storage model and the given number of columns.
081     * This is the constructor through which the other constructors feed.
082     * If the document is <code>null</code>, a default model is created.
083     *
084     * @param doc  the text storage to use; if this is <code>null</code>,
085     *      a default will be provided by calling the
086     *      <code>createDefaultModel</code> method
087     * @param text  the initial string to display, or <code>null</code>
088     * @param columns  the number of columns to use to calculate
089     *   the preferred width &gt;= 0; if <code>columns</code>
090     *   is set to zero, the preferred width will be whatever
091     *   naturally results from the component implementation
092     * @exception IllegalArgumentException if <code>columns</code> &lt; 0
093     */
094    public DisableShortcutsOnFocusGainedTextField(Document doc, String text, int columns) {
095        super(doc, text, columns);
096    }
097
098    private final List<Pair<Action,Shortcut>> unregisteredActionShortcuts = new ArrayList<>();
099    private final Set<JosmAction> disabledMenuActions = new HashSet<>();
100
101    @Override
102    public void focusGained(FocusEvent e) {
103        super.focusGained(e);
104        disableMenuActions();
105        unregisterActionShortcuts();
106    }
107
108    @Override
109    public void focusLost(FocusEvent e) {
110        super.focusLost(e);
111        restoreActionShortcuts();
112        restoreMenuActions();
113    }
114
115    /**
116     * Disables all relevant menu actions.
117     * @see #hasToBeDisabled
118     */
119    protected void disableMenuActions() {
120        disabledMenuActions.clear();
121        for (int i = 0; i < Main.main.menu.getMenuCount(); i++) {
122            JMenu menu = Main.main.menu.getMenu(i);
123            if (menu != null) {
124                for (int j = 0; j < menu.getItemCount(); j++) {
125                    JMenuItem item = menu.getItem(j);
126                    if (item != null) {
127                        Action action = item.getAction();
128                        if (action instanceof JosmAction && action.isEnabled()) {
129                            Shortcut shortcut = ((JosmAction) action).getShortcut();
130                            if (shortcut != null) {
131                                KeyStroke ks = shortcut.getKeyStroke();
132                                if (hasToBeDisabled(ks)) {
133                                    action.setEnabled(false);
134                                    disabledMenuActions.add((JosmAction) action);
135                                }
136                            }
137                        }
138                    }
139                }
140            }
141        }
142    }
143
144    /**
145     * Unregisters all relevant action shortcuts.
146     * @see #hasToBeDisabled
147     */
148    protected void unregisterActionShortcuts() {
149        unregisteredActionShortcuts.clear();
150        // Unregister all actions without modifiers to avoid them to be triggered by typing in this text field
151        for (Shortcut shortcut : Shortcut.listAll()) {
152            KeyStroke ks = shortcut.getKeyStroke();
153            if (hasToBeDisabled(ks)) {
154                Action action = Main.getRegisteredActionShortcut(shortcut);
155                if (action != null) {
156                    Main.unregisterActionShortcut(action, shortcut);
157                    unregisteredActionShortcuts.add(new Pair<>(action,shortcut));
158                }
159            }
160        }
161    }
162
163    /**
164     * Returns true if the given shortcut has no modifier and is not an actions key.
165     * @see KeyEvent#isActionKey()
166     */
167    protected boolean hasToBeDisabled(KeyStroke ks) {
168        return ks != null && ks.getModifiers() == 0 && !new KeyEvent(
169                this, KeyEvent.KEY_PRESSED, 0, ks.getModifiers(), ks.getKeyCode(), ks.getKeyChar()).isActionKey();
170    }
171
172    /**
173     * Restore all actions previously disabled
174     */
175    protected void restoreMenuActions() {
176        for (JosmAction a : disabledMenuActions) {
177            a.setEnabled(true);
178        }
179        disabledMenuActions.clear();
180    }
181
182    /**
183     * Restore all action shortcuts previously unregistered
184     */
185    protected void restoreActionShortcuts() {
186        for (Pair<Action,Shortcut> p : unregisteredActionShortcuts) {
187            Main.registerActionShortcut(p.a, p.b);
188        }
189        unregisteredActionShortcuts.clear();
190    }
191}