001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.widgets;
003
004import java.awt.Component;
005import java.awt.event.FocusEvent;
006import java.awt.event.FocusListener;
007import java.awt.event.InputEvent;
008import java.awt.event.KeyEvent;
009import java.util.List;
010import java.util.Set;
011
012import javax.swing.Action;
013import javax.swing.JMenu;
014import javax.swing.JMenuItem;
015import javax.swing.KeyStroke;
016
017import org.openstreetmap.josm.actions.JosmAction;
018import org.openstreetmap.josm.gui.MainApplication;
019import org.openstreetmap.josm.tools.Pair;
020import org.openstreetmap.josm.tools.Shortcut;
021
022/**
023 * An interface for components with code that can be used for disabling shortcuts while they hold focus.
024 *
025 * @author Taylor Smock
026 * @since 18285 (code extracted for {@link DisableShortcutsOnFocusGainedTextField}
027 */
028public interface DisableShortcutsOnFocusGainedComponent extends FocusListener {
029
030    @Override
031    default void focusGained(FocusEvent e) {
032        disableMenuActions();
033        unregisterActionShortcuts();
034    }
035
036    @Override
037    default void focusLost(FocusEvent e) {
038        restoreActionShortcuts();
039        restoreMenuActions();
040    }
041
042    /**
043     * Get the unregistered action shortcuts.
044     * This should not be used outside the {@link DisableShortcutsOnFocusGainedComponent} interface.
045     * @return The list of unregistered action shortcuts (modifiable)
046     */
047    List<Pair<Action, Shortcut>> getUnregisteredActionShortcuts();
048
049    /**
050     * Get the disabled menu action list
051     * This should not be used outside the {@link DisableShortcutsOnFocusGainedComponent} interface.
052     * @return The list of disabled menu actions (modifiable)
053     */
054    Set<JosmAction> getDisabledMenuActions();
055
056    /**
057     * Disables all relevant menu actions.
058     * Note: This was protected
059     * @see #hasToBeDisabled
060     */
061    default void disableMenuActions() {
062        getDisabledMenuActions().clear();
063        for (int i = 0; i < MainApplication.getMenu().getMenuCount(); i++) {
064            JMenu menu = MainApplication.getMenu().getMenu(i);
065            if (menu != null) {
066                for (int j = 0; j < menu.getItemCount(); j++) {
067                    JMenuItem item = menu.getItem(j);
068                    if (item != null) {
069                        Action action = item.getAction();
070                        if (action instanceof JosmAction && action.isEnabled()) {
071                            Shortcut shortcut = ((JosmAction) action).getShortcut();
072                            if (shortcut != null) {
073                                KeyStroke ks = shortcut.getKeyStroke();
074                                if (hasToBeDisabled(ks)) {
075                                    action.setEnabled(false);
076                                    getDisabledMenuActions().add((JosmAction) action);
077                                }
078                            }
079                        }
080                    }
081                }
082            }
083        }
084    }
085
086    /**
087     * Unregisters all relevant action shortcuts.
088     * Note: This was protected
089     * @see #hasToBeDisabled
090     */
091    default void unregisterActionShortcuts() {
092        getUnregisteredActionShortcuts().clear();
093        // Unregister all actions with Shift modifier or without modifiers to avoid them to be triggered by typing in this text field
094        for (Shortcut shortcut : Shortcut.listAll()) {
095            KeyStroke ks = shortcut.getKeyStroke();
096            if (hasToBeDisabled(ks)) {
097                Action action = MainApplication.getRegisteredActionShortcut(shortcut);
098                if (action != null) {
099                    MainApplication.unregisterActionShortcut(action, shortcut);
100                    getUnregisteredActionShortcuts().add(new Pair<>(action, shortcut));
101                }
102            }
103        }
104    }
105
106    /**
107     * Returns true if the given shortcut has Shift modifier or no modifier and is not an actions key.
108     * Note: This was protected
109     * @param ks key stroke
110     * @return {@code true} if the given shortcut has to be disabled
111     * @see KeyEvent#isActionKey()
112     */
113    default boolean hasToBeDisabled(KeyStroke ks) {
114        if (this instanceof Component) {
115            return ks != null && (ks.getModifiers() == 0 || isOnlyShift(ks.getModifiers())) && !new KeyEvent((Component) this,
116                    KeyEvent.KEY_PRESSED, 0, ks.getModifiers(), ks.getKeyCode(), ks.getKeyChar()).isActionKey();
117        }
118        throw new UnsupportedOperationException(this.getClass().getSimpleName() + " is not an instanceof Component");
119    }
120
121    /**
122     * Check if the modifiers is only shift
123     * Note: This was private
124     * @param modifiers The modifiers to check
125     * @return {@code true} if the only modifier is {@link InputEvent#SHIFT_DOWN_MASK}
126     */
127    static boolean isOnlyShift(int modifiers) {
128        return (modifiers & InputEvent.SHIFT_DOWN_MASK) != 0
129                && (modifiers & InputEvent.CTRL_DOWN_MASK) == 0
130                && (modifiers & InputEvent.ALT_DOWN_MASK) == 0
131                && (modifiers & InputEvent.ALT_GRAPH_DOWN_MASK) == 0
132                && (modifiers & InputEvent.META_DOWN_MASK) == 0;
133    }
134
135    /**
136     * Restore all actions previously disabled
137     * Note: This was protected
138     */
139    default void restoreMenuActions() {
140        for (JosmAction a : getDisabledMenuActions()) {
141            a.setEnabled(true);
142        }
143        getDisabledMenuActions().clear();
144    }
145
146    /**
147     * Restore all action shortcuts previously unregistered
148     * Note: This was protected
149     */
150    default void restoreActionShortcuts() {
151        for (Pair<Action, Shortcut> p : getUnregisteredActionShortcuts()) {
152            MainApplication.registerActionShortcut(p.a, p.b);
153        }
154        getUnregisteredActionShortcuts().clear();
155    }
156}