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.actions.JosmAction; 018import org.openstreetmap.josm.gui.MainApplication; 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>, and the number of columns is set to 0. 033 */ 034 public DisableShortcutsOnFocusGainedTextField() { 035 // Contents can be set with parent methods 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 columns is 0. 041 * 042 * @param text the text to be displayed, or <code>null</code> 043 */ 044 public DisableShortcutsOnFocusGainedTextField(String text) { 045 super(text); 046 } 047 048 /** 049 * Constructs a new empty <code>TextField</code> with the specified number of columns. 050 * A default model is created and the initial string is set to <code>null</code>. 051 * 052 * @param columns the number of columns to use to calculate 053 * the preferred width; if columns is set to zero, the 054 * preferred width will be whatever naturally results from the component implementation 055 */ 056 public DisableShortcutsOnFocusGainedTextField(int columns) { 057 super(columns); 058 } 059 060 /** 061 * Constructs a new <code>TextField</code> initialized with the 062 * specified text and columns. A default model is created. 063 * 064 * @param text the text to be displayed, or <code>null</code> 065 * @param columns the number of columns to use to calculate 066 * the preferred width; if columns is set to zero, the 067 * preferred width will be whatever naturally results from the component implementation 068 */ 069 public DisableShortcutsOnFocusGainedTextField(String text, int columns) { 070 super(text, columns); 071 } 072 073 /** 074 * Constructs a new <code>JTextField</code> that uses the given text 075 * storage model and the given number of columns. 076 * This is the constructor through which the other constructors feed. 077 * If the document is <code>null</code>, a default model is created. 078 * 079 * @param doc the text storage to use; if this is <code>null</code>, 080 * a default will be provided by calling the 081 * <code>createDefaultModel</code> method 082 * @param text the initial string to display, or <code>null</code> 083 * @param columns the number of columns to use to calculate 084 * the preferred width >= 0; if <code>columns</code> 085 * is set to zero, the preferred width will be whatever 086 * naturally results from the component implementation 087 * @throws IllegalArgumentException if <code>columns</code> < 0 088 */ 089 public DisableShortcutsOnFocusGainedTextField(Document doc, String text, int columns) { 090 super(doc, text, columns); 091 } 092 093 private final transient List<Pair<Action, Shortcut>> unregisteredActionShortcuts = new ArrayList<>(); 094 private final Set<JosmAction> disabledMenuActions = new HashSet<>(); 095 096 @Override 097 public void focusGained(FocusEvent e) { 098 super.focusGained(e); 099 disableMenuActions(); 100 unregisterActionShortcuts(); 101 } 102 103 @Override 104 public void focusLost(FocusEvent e) { 105 super.focusLost(e); 106 restoreActionShortcuts(); 107 restoreMenuActions(); 108 } 109 110 /** 111 * Disables all relevant menu actions. 112 * @see #hasToBeDisabled 113 */ 114 protected void disableMenuActions() { 115 disabledMenuActions.clear(); 116 for (int i = 0; i < MainApplication.getMenu().getMenuCount(); i++) { 117 JMenu menu = MainApplication.getMenu().getMenu(i); 118 if (menu != null) { 119 for (int j = 0; j < menu.getItemCount(); j++) { 120 JMenuItem item = menu.getItem(j); 121 if (item != null) { 122 Action action = item.getAction(); 123 if (action instanceof JosmAction && action.isEnabled()) { 124 Shortcut shortcut = ((JosmAction) action).getShortcut(); 125 if (shortcut != null) { 126 KeyStroke ks = shortcut.getKeyStroke(); 127 if (hasToBeDisabled(ks)) { 128 action.setEnabled(false); 129 disabledMenuActions.add((JosmAction) action); 130 } 131 } 132 } 133 } 134 } 135 } 136 } 137 } 138 139 /** 140 * Unregisters all relevant action shortcuts. 141 * @see #hasToBeDisabled 142 */ 143 protected void unregisterActionShortcuts() { 144 unregisteredActionShortcuts.clear(); 145 // Unregister all actions without modifiers to avoid them to be triggered by typing in this text field 146 for (Shortcut shortcut : Shortcut.listAll()) { 147 KeyStroke ks = shortcut.getKeyStroke(); 148 if (hasToBeDisabled(ks)) { 149 Action action = MainApplication.getRegisteredActionShortcut(shortcut); 150 if (action != null) { 151 MainApplication.unregisterActionShortcut(action, shortcut); 152 unregisteredActionShortcuts.add(new Pair<>(action, shortcut)); 153 } 154 } 155 } 156 } 157 158 /** 159 * Returns true if the given shortcut has no modifier and is not an actions key. 160 * @param ks key stroke 161 * @return {@code true} if the given shortcut has to be disabled 162 * @see KeyEvent#isActionKey() 163 */ 164 protected boolean hasToBeDisabled(KeyStroke ks) { 165 return ks != null && ks.getModifiers() == 0 && !new KeyEvent( 166 this, KeyEvent.KEY_PRESSED, 0, ks.getModifiers(), ks.getKeyCode(), ks.getKeyChar()).isActionKey(); 167 } 168 169 /** 170 * Restore all actions previously disabled 171 */ 172 protected void restoreMenuActions() { 173 for (JosmAction a : disabledMenuActions) { 174 a.setEnabled(true); 175 } 176 disabledMenuActions.clear(); 177 } 178 179 /** 180 * Restore all action shortcuts previously unregistered 181 */ 182 protected void restoreActionShortcuts() { 183 for (Pair<Action, Shortcut> p : unregisteredActionShortcuts) { 184 MainApplication.registerActionShortcut(p.a, p.b); 185 } 186 unregisteredActionShortcuts.clear(); 187 } 188}