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 >= 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> < 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}