001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.tagging.ac;
003
004import java.awt.im.InputContext;
005import java.util.Locale;
006
007import javax.swing.ComboBoxEditor;
008
009import org.openstreetmap.josm.gui.widgets.JosmComboBox;
010import org.openstreetmap.josm.tools.Logging;
011
012/**
013 * An auto-completing ComboBox.
014 * <p>
015 * When the user starts typing, this combobox will suggest the
016 * {@link AutoCompComboBoxModel#findBestCandidate best matching item} from its list.  The items can
017 * be of any type while the items' {@code toString} values are shown in the combobox and editor.
018 *
019 * @author guilhem.bonnefille@gmail.com
020 * @author marcello@perathoner.de
021 * @param <E> the type of the combobox entries
022 * @since 18173
023 */
024public class AutoCompComboBox<E> extends JosmComboBox<E> implements AutoCompListener {
025
026    /** force a different keyboard input locale for the editor */
027    private boolean useFixedLocale;
028    private final transient InputContext privateInputContext = InputContext.getInstance();
029
030    /**
031     * Constructs an {@code AutoCompletingComboBox}.
032     */
033    public AutoCompComboBox() {
034        this(new AutoCompComboBoxModel<E>());
035    }
036
037    /**
038     * Constructs an {@code AutoCompletingComboBox} with a supplied {@link AutoCompComboBoxModel}.
039     *
040     * @param model the model
041     */
042    public AutoCompComboBox(AutoCompComboBoxModel<E> model) {
043        super(model);
044        setEditor(new AutoCompComboBoxEditor<E>());
045        setEditable(true);
046        getEditorComponent().setModel(model);
047        getEditorComponent().addAutoCompListener(this);
048    }
049
050    /**
051     * Returns the {@link AutoCompComboBoxModel} currently used.
052     *
053     * @return the model or null
054     */
055    @Override
056    public AutoCompComboBoxModel<E> getModel() {
057        return (AutoCompComboBoxModel<E>) dataModel;
058    }
059
060    @Override
061    public void setEditor(ComboBoxEditor newEditor) {
062        if (editor != null) {
063            editor.getEditorComponent().removePropertyChangeListener(this);
064        }
065        super.setEditor(newEditor);
066        if (editor != null) {
067            // listen to orientation changes in the editor
068            editor.getEditorComponent().addPropertyChangeListener(this);
069        }
070    }
071
072    /**
073     * Returns the editor component
074     *
075     * @return the editor component
076     * @see ComboBoxEditor#getEditorComponent()
077     * @since 18221
078     */
079    @Override
080    @SuppressWarnings("unchecked")
081    public AutoCompTextField<E> getEditorComponent() {
082        return getEditor() == null ? null : (AutoCompTextField<E>) getEditor().getEditorComponent();
083    }
084
085    /**
086     * Selects the autocompleted item in the dropdown.
087     *
088     * @param item the item selected for autocomplete
089     */
090    private void autocomplete(Object item) {
091        // Save the text in case item is null, because setSelectedItem will erase it.
092        String savedText = getText();
093        setSelectedItem(item);
094        setText(savedText);
095    }
096
097    /**
098     * Enables or disables the autocompletion.
099     *
100     * @param enabled {@code true} to enable autocompletion
101     * @return {@code true} if autocomplete was enabled before calling this
102     * @since 18173 (signature)
103     */
104    public boolean setAutocompleteEnabled(boolean enabled) {
105        return getEditorComponent().setAutocompleteEnabled(enabled);
106    }
107
108    /**
109     * Fixes the locale for keyboard input to US-English.
110     * <p>
111     * If the locale is fixed, English keyboard layout will be used by default for this combobox.
112     * All other components can still have different keyboard layout selected.
113     *
114     * @param f if {@code true} use fixed locale
115     */
116    public void setFixedLocale(boolean f) {
117        useFixedLocale = f;
118        if (useFixedLocale) {
119            Locale oldLocale = privateInputContext.getLocale();
120            Logging.info("Using English input method");
121            if (!privateInputContext.selectInputMethod(new Locale("en", "US"))) {
122                // Unable to use English keyboard layout, disable the feature
123                Logging.warn("Unable to use English input method");
124                useFixedLocale = false;
125                if (oldLocale != null) {
126                    Logging.info("Restoring input method to " + oldLocale);
127                    if (!privateInputContext.selectInputMethod(oldLocale)) {
128                        Logging.warn("Unable to restore input method to " + oldLocale);
129                    }
130                }
131            }
132        }
133    }
134
135    @Override
136    public InputContext getInputContext() {
137        if (useFixedLocale) {
138            return privateInputContext;
139        }
140        return super.getInputContext();
141    }
142
143    /** AutoCompListener Interface */
144
145    @Override
146    public void autoCompBefore(AutoCompEvent e) {
147    }
148
149    @Override
150    public void autoCompPerformed(AutoCompEvent e) {
151        autocomplete(e.getItem());
152    }
153}