// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.gui.widgets;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.swing.Action;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.KeyStroke;
import javax.swing.text.Document;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.actions.JosmAction;
import org.openstreetmap.josm.tools.Pair;
import org.openstreetmap.josm.tools.Shortcut;
/**
* A JTextField that disabled all JOSM shortcuts composed of a single key without modifier (except F1 to F12),
* in order to avoid them to be triggered while typing.
* This allows to include text fields in toggle dialogs (needed for relation filter).
* @since 5696
*/
public class DisableShortcutsOnFocusGainedTextField extends JosmTextField implements FocusListener {
/**
* Constructs a new <code>TextField</code>. A default model is created,
* the initial string is <code>null</code>,
* and the number of columns is set to 0.
*/
public DisableShortcutsOnFocusGainedTextField() {
init();
}
/**
* Constructs a new <code>TextField</code> initialized with the
* specified text. A default model is created and the number of
* columns is 0.
*
* @param text the text to be displayed, or <code>null</code>
*/
public DisableShortcutsOnFocusGainedTextField(String text) {
super(text);
init();
}
/**
* Constructs a new empty <code>TextField</code> with the specified
* number of columns.
* A default model is created and the initial string is set to
* <code>null</code>.
*
* @param columns the number of columns to use to calculate
* the preferred width; if columns is set to zero, the
* preferred width will be whatever naturally results from
* the component implementation
*/
public DisableShortcutsOnFocusGainedTextField(int columns) {
super(columns);
init();
}
/**
* Constructs a new <code>TextField</code> initialized with the
* specified text and columns. A default model is created.
*
* @param text the text to be displayed, or <code>null</code>
* @param columns the number of columns to use to calculate
* the preferred width; if columns is set to zero, the
* preferred width will be whatever naturally results from
* the component implementation
*/
public DisableShortcutsOnFocusGainedTextField(String text, int columns) {
super(text, columns);
init();
}
/**
* Constructs a new <code>JTextField</code> that uses the given text
* storage model and the given number of columns.
* This is the constructor through which the other constructors feed.
* If the document is <code>null</code>, a default model is created.
*
* @param doc the text storage to use; if this is <code>null</code>,
* a default will be provided by calling the
* <code>createDefaultModel</code> method
* @param text the initial string to display, or <code>null</code>
* @param columns the number of columns to use to calculate
* the preferred width >= 0; if <code>columns</code>
* is set to zero, the preferred width will be whatever
* naturally results from the component implementation
* @exception IllegalArgumentException if <code>columns</code> < 0
*/
public DisableShortcutsOnFocusGainedTextField(Document doc, String text, int columns) {
super(doc, text, columns);
init();
}
private final List<Pair<Action,Shortcut>> unregisteredActionShortcuts = new ArrayList<>();
private final Set<JosmAction> disabledMenuActions = new HashSet<>();
protected final void init() {
addFocusListener(this);
}
@Override
public void focusGained(FocusEvent e) {
disableMenuActions();
unregisterActionShortcuts();
}
@Override
public void focusLost(FocusEvent e) {
restoreActionShortcuts();
restoreMenuActions();
}
/**
* Disables all relevant menu actions.
* @see #hasToBeDisabled
*/
protected void disableMenuActions() {
disabledMenuActions.clear();
for (int i = 0; i < Main.main.menu.getMenuCount(); i++) {
JMenu menu = Main.main.menu.getMenu(i);
if (menu != null) {
for (int j = 0; j < menu.getItemCount(); j++) {
JMenuItem item = menu.getItem(j);
if (item != null) {
Action action = item.getAction();
if (action instanceof JosmAction && action.isEnabled()) {
Shortcut shortcut = ((JosmAction) action).getShortcut();
if (shortcut != null) {
KeyStroke ks = shortcut.getKeyStroke();
if (hasToBeDisabled(ks)) {
action.setEnabled(false);
disabledMenuActions.add((JosmAction) action);
}
}
}
}
}
}
}
}
/**
* Unregisters all relevant action shortcuts.
* @see #hasToBeDisabled
*/
protected void unregisterActionShortcuts() {
unregisteredActionShortcuts.clear();
// Unregister all actions without modifiers to avoid them to be triggered by typing in this text field
for (Shortcut shortcut : Shortcut.listAll()) {
KeyStroke ks = shortcut.getKeyStroke();
if (hasToBeDisabled(ks)) {
Action action = Main.getRegisteredActionShortcut(shortcut);
if (action != null) {
Main.unregisterActionShortcut(action, shortcut);
unregisteredActionShortcuts.add(new Pair<>(action,shortcut));
}
}
}
}
/**
* Returns true if the given shortcut has no modifier and is not an actions key.
* @see KeyEvent#isActionKey()
*/
protected boolean hasToBeDisabled(KeyStroke ks) {
return ks != null && ks.getModifiers() == 0 && !new KeyEvent(
this, KeyEvent.KEY_PRESSED, 0, ks.getModifiers(), ks.getKeyCode(), ks.getKeyChar()).isActionKey();
}
/**
* Restore all actions previously disabled
*/
protected void restoreMenuActions() {
for (JosmAction a : disabledMenuActions) {
a.setEnabled(true);
}
disabledMenuActions.clear();
}
/**
* Restore all action shortcuts previously unregistered
*/
protected void restoreActionShortcuts() {
for (Pair<Action,Shortcut> p : unregisteredActionShortcuts) {
Main.registerActionShortcut(p.a, p.b);
}
unregisteredActionShortcuts.clear();
}
}