Changeset 18173 in josm for trunk/src/org


Ignore:
Timestamp:
2021-08-24T02:43:50+02:00 (3 years ago)
Author:
Don-vip
Message:

fix #20690 - fix #21240 - Refactoring of UploadDialog, HistoryComboBox and AutoCompletingComboBox (patch by marcello)

Location:
trunk/src/org/openstreetmap/josm
Files:
4 added
4 deleted
23 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/actions/OpenLocationAction.java

    r17335 r18173  
    4444import org.openstreetmap.josm.gui.util.WindowGeometry;
    4545import org.openstreetmap.josm.gui.widgets.HistoryComboBox;
    46 import org.openstreetmap.josm.spi.preferences.Config;
    4746import org.openstreetmap.josm.tools.GBC;
    4847import org.openstreetmap.josm.tools.Logging;
     
    114113     */
    115114    protected void restoreUploadAddressHistory(HistoryComboBox cbHistory) {
    116         cbHistory.setPossibleItemsTopDown(Config.getPref().getList(getClass().getName() + ".uploadAddressHistory",
    117                 Collections.emptyList()));
     115        cbHistory.getModel().prefs().load(getClass().getName() + ".uploadAddressHistory");
    118116    }
    119117
     
    124122    protected void remindUploadAddressHistory(HistoryComboBox cbHistory) {
    125123        cbHistory.addCurrentItemToHistory();
    126         Config.getPref().putList(getClass().getName() + ".uploadAddressHistory", cbHistory.getHistory());
     124        cbHistory.getModel().prefs().save(getClass().getName() + ".uploadAddressHistory");
    127125    }
    128126
  • trunk/src/org/openstreetmap/josm/actions/SearchNotesDownloadAction.java

    r17062 r18173  
    88import java.awt.event.ActionEvent;
    99import java.awt.event.KeyEvent;
    10 import java.util.Collections;
    1110import java.util.Optional;
    1211
     
    4544    public void actionPerformed(ActionEvent e) {
    4645        HistoryComboBox searchTermBox = new HistoryComboBox();
    47         searchTermBox.setPossibleItemsTopDown(Config.getPref().getList(HISTORY_KEY, Collections.emptyList()));
     46        searchTermBox.getModel().prefs().load(HISTORY_KEY);
    4847
    4948        JPanel contentPanel = new JPanel(new GridBagLayout());
     
    7372
    7473        searchTermBox.addCurrentItemToHistory();
    75         Config.getPref().putList(HISTORY_KEY, searchTermBox.getHistory());
     74        searchTermBox.getModel().prefs().save(HISTORY_KEY);
    7675
    7776        performSearch(searchTerm);
  • trunk/src/org/openstreetmap/josm/actions/UploadAction.java

    r16824 r18173  
    240240
    241241        final UploadDialog dialog = UploadDialog.getUploadDialog();
    242         dialog.setChangesetTags(layer.getDataSet());
     242        dialog.initLifeCycle(layer.getDataSet());
    243243        dialog.setUploadedPrimitives(apiData);
    244244        dialog.setVisible(true);
  • trunk/src/org/openstreetmap/josm/actions/search/SearchAction.java

    r17188 r18173  
    1414import java.util.Collections;
    1515import java.util.HashSet;
    16 import java.util.LinkedList;
    1716import java.util.List;
    1817import java.util.Map;
    1918import java.util.function.Predicate;
    20 import java.util.stream.Collectors;
    2119
    2220import javax.swing.JOptionPane;
     
    4341import org.openstreetmap.josm.gui.preferences.ToolbarPreferences.ActionParser;
    4442import org.openstreetmap.josm.gui.progress.ProgressMonitor;
     43import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel;
    4544import org.openstreetmap.josm.spi.preferences.Config;
    4645import org.openstreetmap.josm.tools.Logging;
     
    6766    private static final String SEARCH_EXPRESSION = "searchExpression";
    6867
    69     private static final LinkedList<SearchSetting> searchHistory = new LinkedList<>();
     68    private static AutoCompComboBoxModel<SearchSetting> model = new AutoCompComboBoxModel<>();
     69
     70    /** preferences reader/writer with automatic transmogrification to and from String */
     71    private static AutoCompComboBoxModel<SearchSetting>.Preferences prefs = model.prefs(
     72            SearchSetting::readFromString, SearchSetting::writeToString);
     73
    7074    static {
    7175        SearchCompiler.addMatchFactory(new SimpleMatchFactory() {
     
    8791            }
    8892        });
    89 
    90         for (String s: Config.getPref().getList("search.history", Collections.<String>emptyList())) {
    91             SearchSetting ss = SearchSetting.readFromString(s);
    92             if (ss != null) {
    93                 searchHistory.add(ss);
    94             }
    95         }
     93        model.setSize(Config.getPref().getInt("search.history-size", DEFAULT_SEARCH_HISTORY_SIZE));
    9694    }
    9795
    9896    /**
    9997     * Gets the search history
    100      * @return The last searched terms. Do not modify it.
     98     * @return The last searched terms.
    10199     */
    102100    public static Collection<SearchSetting> getSearchHistory() {
    103         return searchHistory;
     101        return model.asCollection();
    104102    }
    105103
     
    109107     */
    110108    public static void saveToHistory(SearchSetting s) {
    111         if (searchHistory.isEmpty() || !s.equals(searchHistory.getFirst())) {
    112             searchHistory.addFirst(new SearchSetting(s));
    113         } else if (searchHistory.contains(s)) {
    114             // move existing entry to front, fixes #8032 - search history loses entries when re-using queries
    115             searchHistory.remove(s);
    116             searchHistory.addFirst(new SearchSetting(s));
    117         }
    118         int maxsize = Config.getPref().getInt("search.history-size", DEFAULT_SEARCH_HISTORY_SIZE);
    119         while (searchHistory.size() > maxsize) {
    120             searchHistory.removeLast();
    121         }
    122         List<String> savedHistory = searchHistory.stream()
    123                 .map(SearchSetting::writeToString)
    124                 .distinct()
    125                 .collect(Collectors.toList());
    126         Config.getPref().putList("search.history", savedHistory);
     109        model.addTopElement(s);
     110        prefs.save("search.history");
    127111    }
    128112
     
    132116     */
    133117    public static List<String> getSearchExpressionHistory() {
    134         return getSearchHistory().stream()
    135                 .map(ss -> ss.text)
    136                 .collect(Collectors.toList());
     118        return prefs.asStringList();
    137119    }
    138120
     
    176158
    177159        SearchDialog dialog = new SearchDialog(
    178                 initialValues, getSearchExpressionHistory(), ExpertToggleAction.isExpert());
     160                initialValues, model, ExpertToggleAction.isExpert());
    179161
    180162        if (dialog.showDialog().getValue() != 1) return null;
     
    204186     */
    205187    public static void search() {
     188        prefs.load("search.history");
    206189        SearchSetting se = showSearchDialog(lastSearch);
    207190        if (se != null) {
  • trunk/src/org/openstreetmap/josm/data/osm/search/SearchSetting.java

    r13887 r18173  
    77
    88import org.openstreetmap.josm.tools.Logging;
     9import org.openstreetmap.josm.tools.Utils;
    910
    1011/**
     
    5051    @Override
    5152    public String toString() {
     53        return Utils.shortenString(text,
     54            org.openstreetmap.josm.actions.search.SearchAction.MAX_LENGTH_SEARCH_EXPRESSION_DISPLAY);
     55    }
     56
     57    /**
     58     * A more talkative version of toString.
     59     * @return a bit more info than toString
     60     * @since 18173
     61     */
     62    public String toStringEx() {
    5263        String cs = caseSensitive ?
    5364                /*case sensitive*/  trc("search", "CS") :
     
    140151
    141152    /**
     153     * Build a SearchSetting from a plain unformatted string.
     154     * <p>
     155     * All attributes are defaulted, only the search string is set. This function is used in
     156     * {@link org.openstreetmap.josm.gui.download.OverpassQueryWizardDialog}.
     157     *
     158     * @param s The string
     159     * @return The instance
     160     * @since 18173
     161     */
     162    public static SearchSetting fromString(String s) {
     163        if (s.isEmpty())
     164            return null;
     165        SearchSetting result = new SearchSetting();
     166        result.text = s;
     167        return result;
     168    }
     169
     170    /**
    142171     * Builds a string representation of the {@code SearchSetting} object,
    143172     * see {@link #readFromString(String)} for more details.
  • trunk/src/org/openstreetmap/josm/data/tagging/ac/AutoCompletionItem.java

    r18125 r18173  
    9595        if (this == obj)
    9696            return true;
    97         if (obj == null)
    98             return false;
    99         if (obj instanceof String)
    100             return obj.equals(value);
    101         if (getClass() != obj.getClass())
     97        if (!(obj instanceof AutoCompletionItem))
    10298            return false;
    10399        final AutoCompletionItem other = (AutoCompletionItem) obj;
    104         if (priority == null) {
    105             if (other.priority != null)
    106                 return false;
    107         } else if (!priority.equals(other.priority))
     100        if (value == null ? other.value != null : !value.equals(other.value))
    108101            return false;
    109         if (value == null) {
    110             if (other.value != null)
    111                 return false;
    112         } else if (!value.equals(other.value))
    113             return false;
    114         return true;
     102        return priority == null ? other.priority == null : priority.equals(other.priority);
    115103    }
    116104
    117105    @Override
    118106    public int compareTo(AutoCompletionItem other) {
    119         int ret = other.priority.compareTo(priority); // higher priority items come first in the list
    120         if (ret != 0)
    121             return ret;
    122         else
    123             return this.value.compareTo(other.value);
     107        // sort on priority descending
     108        int ret = other.priority.compareTo(priority);
     109        // then alphabetic ascending
     110        return ret != 0 ? ret : this.value.compareTo(other.value);
    124111    }
    125112}
  • trunk/src/org/openstreetmap/josm/gui/dialogs/OsmIdSelectionDialog.java

    r15011 r18173  
    179179     */
    180180    protected void restorePrimitivesHistory(HistoryComboBox cbHistory) {
    181         cbHistory.setPossibleItemsTopDown(
    182                 Config.getPref().getList(getClass().getName() + ".primitivesHistory", Collections.emptyList()));
     181        cbHistory.getModel().prefs().load(getClass().getName() + ".primitivesHistory");
    183182    }
    184183
     
    190189    protected void remindPrimitivesHistory(HistoryComboBox cbHistory) {
    191190        cbHistory.addCurrentItemToHistory();
    192         Config.getPref().putList(getClass().getName() + ".primitivesHistory", cbHistory.getHistory());
     191        cbHistory.getModel().prefs().save(getClass().getName() + ".primitivesHistory");
    193192    }
    194193
  • trunk/src/org/openstreetmap/josm/gui/dialogs/SearchDialog.java

    r17055 r18173  
    1616import java.awt.event.MouseEvent;
    1717import java.util.Arrays;
    18 import java.util.List;
    1918
    2019import javax.swing.BorderFactory;
     
    3837import org.openstreetmap.josm.gui.MainApplication;
    3938import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSException;
     39import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBox;
     40import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel;
    4041import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
    4142import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetSelector;
    4243import org.openstreetmap.josm.gui.widgets.AbstractTextComponentValidator;
    43 import org.openstreetmap.josm.gui.widgets.HistoryComboBox;
    4444import org.openstreetmap.josm.tools.GBC;
    4545import org.openstreetmap.josm.tools.JosmRuntimeException;
     
    5555    private final SearchSetting searchSettings;
    5656
    57     protected final HistoryComboBox hcbSearchString = new HistoryComboBox();
     57    protected final AutoCompComboBox<SearchSetting> hcbSearchString;
    5858
    5959    private JCheckBox addOnToolbar;
     
    7272    /**
    7373     * Constructs a new {@code SearchDialog}.
    74      * @param initialValues initial search settings
    75      * @param searchExpressionHistory list of all texts that were recently used in the search
    76      * @param expertMode expert mode
     74     * @param initialValues initial search settings, eg. when opened for editing from the filter panel
     75     * @param model The combobox model.
     76     * @param expertMode expert mode. Shows more options and the "search syntax" panel.
     77     * @since 18173 (signature)
    7778     */
    78     public SearchDialog(SearchSetting initialValues, List<String> searchExpressionHistory, boolean expertMode) {
    79         this(initialValues, searchExpressionHistory, new PanelOptions(expertMode, false), MainApplication.getMainFrame(),
     79    public SearchDialog(SearchSetting initialValues, AutoCompComboBoxModel<SearchSetting> model, boolean expertMode) {
     80        this(initialValues, model, new PanelOptions(expertMode, false), MainApplication.getMainFrame(),
    8081                initialValues instanceof Filter ? tr("Filter") : tr("Search"),
    8182                initialValues instanceof Filter ? tr("Submit filter") : tr("Search"),
     
    8586    }
    8687
    87     protected SearchDialog(SearchSetting initialValues, List<String> searchExpressionHistory, PanelOptions options,
     88    protected SearchDialog(SearchSetting initialValues, AutoCompComboBoxModel<SearchSetting> model, PanelOptions options,
    8889                           Component mainFrame, String title, String... buttonTexts) {
    8990        super(mainFrame, title, buttonTexts);
     91        hcbSearchString = new AutoCompComboBox<>(model);
    9092        this.searchSettings = new SearchSetting(initialValues);
    91         setContent(buildPanel(searchExpressionHistory, options));
     93        setContent(buildPanel(options));
    9294    }
    9395
     
    101103        /**
    102104         * Constructs new options which determine which parts of the search dialog will be shown
    103          * @param expertMode whether export mode is enabled
    104          * @param overpassQuery whether the panel shall be adapted for Overpass query
     105         * @param expertMode Shows more options and the "search syntax" panel.
     106         * @param overpassQuery Don't show left panels and right "preset" panel. Show different "hints".
    105107         */
    106108        public PanelOptions(boolean expertMode, boolean overpassQuery) {
     
    110112    }
    111113
    112     private JPanel buildPanel(List<String> searchExpressionHistory, PanelOptions options) {
     114    private JPanel buildPanel(PanelOptions options) {
    113115
    114116        // prepare the combo box with the search expressions
     
    116118
    117119        String tooltip = tr("Enter the search expression");
    118         hcbSearchString.setText(searchSettings.text);
     120        hcbSearchString.setText(searchSettings.toString());
    119121        hcbSearchString.setToolTipText(tooltip);
    120 
    121         hcbSearchString.setPossibleItemsTopDown(searchExpressionHistory);
    122122        hcbSearchString.setPreferredSize(new Dimension(40, hcbSearchString.getPreferredSize().height));
    123123        label.setLabelFor(hcbSearchString);
     
    307307    }
    308308
    309     private static JPanel buildHintsSection(HistoryComboBox hcbSearchString, PanelOptions options) {
     309    private static JPanel buildHintsSection(AutoCompComboBox<SearchSetting> hcbSearchString, PanelOptions options) {
    310310        JPanel hintPanel = new JPanel(new GridBagLayout());
    311311        hintPanel.setBorder(BorderFactory.createTitledBorder(tr("Hints")));
     
    466466    private static class SearchKeywordRow extends JPanel {
    467467
    468         private final HistoryComboBox hcb;
    469 
    470         SearchKeywordRow(HistoryComboBox hcb) {
     468        private final AutoCompComboBox<SearchSetting> hcb;
     469
     470        SearchKeywordRow(AutoCompComboBox<SearchSetting> hcb) {
    471471            super(new FlowLayout(FlowLayout.LEFT));
    472472            this.hcb = hcb;
  • trunk/src/org/openstreetmap/josm/gui/dialogs/properties/TagEditHelper.java

    r17168 r18173  
    8888import org.openstreetmap.josm.gui.MainApplication;
    8989import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
    90 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingComboBox;
     90import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBox;
    9191import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
    9292import org.openstreetmap.josm.gui.util.GuiHelper;
     
    400400
    401401    /**
     402     * Returns the edited item with whitespaces removed
     403     * @param cb the combobox
     404     * @return the edited item with whitespaces removed
     405     * @since 18173
     406     */
     407    public static String getEditItem(AutoCompComboBox<AutoCompletionItem> cb) {
     408        return Utils.removeWhiteSpaces(cb.getEditor().getItem().toString());
     409    }
     410
     411    /**
     412     * Returns the selected item or the edited item as string
     413     * @param cb the combobox
     414     * @return the selected item or the edited item as string
     415     * @since 18173
     416     */
     417    public static String getSelectedOrEditItem(AutoCompComboBox<AutoCompletionItem> cb) {
     418        final Object selectedItem = cb.getSelectedItem();
     419        if (selectedItem != null)
     420            return selectedItem.toString();
     421        return getEditItem(cb);
     422    }
     423
     424    /**
    402425     * Warns user about a key being overwritten.
    403426     * @param action The action done by the user. Must state what key is changed
     
    476499            List<AutoCompletionItem> keyList = autocomplete.getTagKeys(DEFAULT_AC_ITEM_COMPARATOR);
    477500
    478             keys = new AutoCompletingComboBox(key);
    479             keys.setPossibleAcItems(keyList);
     501            keys = new AutoCompComboBox<>();
     502            keys.getModel().setComparator(Comparator.naturalOrder()); // according to Comparable
     503            keys.setPrototypeDisplayValue(new AutoCompletionItem(key));
    480504            keys.setEditable(true);
     505            keys.getModel().addAllElements(keyList);
    481506            keys.setSelectedItem(key);
    482507
     
    490515            final String selection = m.size() != 1 ? tr("<different>") : m.entrySet().iterator().next().getKey();
    491516
    492             values = new AutoCompletingComboBox(selection);
     517            values = new AutoCompComboBox<>();
     518            values.getModel().setComparator(Comparator.naturalOrder());
     519            values.setPrototypeDisplayValue(new AutoCompletionItem(selection));
    493520            values.setRenderer(cellRenderer);
    494 
    495521            values.setEditable(true);
    496             values.setPossibleAcItems(valueList);
     522            values.getModel().addAllElements(valueList);
    497523            values.setSelectedItem(selection);
    498524            values.getEditor().setItem(selection);
     525
    499526            p.add(Box.createVerticalStrut(5), GBC.eol());
    500527            p.add(new JLabel(tr("Value")), GBC.std());
     
    522549        @Override
    523550        public void performTagEdit() {
    524             String value = values.getEditItem();
     551            String value = getEditItem(values);
    525552            value = Normalizer.normalize(value, Normalizer.Form.NFC);
    526553            if (value.isEmpty()) {
    527554                value = null; // delete the key
    528555            }
    529             String newkey = keys.getEditItem();
     556            String newkey = getEditItem(keys);
    530557            newkey = Normalizer.normalize(newkey, Normalizer.Form.NFC);
    531558            if (newkey.isEmpty()) {
     
    574601
    575602    protected abstract class AbstractTagsDialog extends ExtendedDialog {
    576         protected AutoCompletingComboBox keys;
    577         protected AutoCompletingComboBox values;
     603        protected AutoCompComboBox<AutoCompletionItem> keys;
     604        protected AutoCompComboBox<AutoCompletionItem> values;
    578605
    579606        AbstractTagsDialog(Component parent, String title, String... buttonTexts) {
     
    621648        }
    622649
    623         private void selectACComboBoxSavingUnixBuffer(AutoCompletingComboBox cb) {
     650        private void selectACComboBoxSavingUnixBuffer(AutoCompComboBox<AutoCompletionItem> cb) {
    624651            // select combobox with saving unix system selection (middle mouse paste)
    625652            Clipboard sysSel = ClipboardUtils.getSystemSelection();
     
    667694                           && IntStream.range(0, size).allMatch(i -> Objects.equals(currentModel.getElementAt(i), correctItems.get(i)));
    668695                   if (!valuesOK) {
    669                        values.setPossibleAcItems(correctItems);
     696                       values.getModel().removeAllElements();
     697                       values.getModel().addAllElements(correctItems);
    670698                   }
    671699                   if (!Objects.equals(key, objKey)) {
     
    688716                return;
    689717            }
    690             buttons.get(0).setIcon(findIcon(keys.getSelectedOrEditItem(), values.getSelectedOrEditItem())
     718            buttons.get(0).setIcon(findIcon(getSelectedOrEditItem(keys), getSelectedOrEditItem(values))
    691719                    .orElse(ImageProvider.get("ok", ImageProvider.ImageSizes.LARGEICON)));
    692720        }
     
    732760
    733761            mainPanel = new JPanel(new GridBagLayout());
    734             keys = new AutoCompletingComboBox();
    735             values = new AutoCompletingComboBox();
     762            keys = new AutoCompComboBox<>();
     763            values = new AutoCompComboBox<>();
     764            keys.getModel().setComparator(Comparator.naturalOrder()); // according to Comparable
     765            values.getModel().setComparator(Comparator.naturalOrder());
     766            keys.setPrototypeDisplayValue(new AutoCompletionItem("dummy"));
     767            values.setPrototypeDisplayValue(new AutoCompletionItem("dummy"));
    736768            keys.setAutocompleteEnabled(AUTOCOMPLETE_KEYS.get());
    737769            values.setAutocompleteEnabled(AUTOCOMPLETE_VALUES.get());
     
    748780            keyList.removeIf(item -> containsDataKey(item.getValue()));
    749781
    750             keys.setPossibleAcItems(keyList);
     782            keys.getModel().removeAllElements();
     783            keys.getModel().addAllElements(keyList);
    751784            keys.setEditable(true);
    752785
     
    939972                    @Override
    940973                    public void actionPerformed(ActionEvent e) {
    941                         keys.setSelectedItem(t.getKey(), true);
     974                        keys.setSelectedItem(t.getKey());
    942975                        // fix #7951, #8298 - update list of values before setting value (?)
    943976                        focus.focusGained(null);
    944                         values.setSelectedItem(t.getValue(), true);
     977                        values.setSelectedItem(t.getValue());
    945978                        selectValuesCombobox();
    946979                    }
     
    10941127         */
    10951128        public final void performTagAdding() {
    1096             String key = keys.getEditItem();
    1097             String value = values.getEditItem();
     1129            String key = getEditItem(keys);
     1130            String value = getEditItem(values);
    10981131            if (key.isEmpty() || value.isEmpty())
    10991132                return;
  • trunk/src/org/openstreetmap/josm/gui/download/OverpassQueryWizardDialog.java

    r17982 r18173  
    1515import org.openstreetmap.josm.gui.dialogs.SearchDialog;
    1616import org.openstreetmap.josm.gui.download.overpass.OverpassWizardRegistration.OverpassWizardCallbacks;
     17import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel;
    1718import org.openstreetmap.josm.tools.Logging;
    1819import org.openstreetmap.josm.tools.SearchCompilerQueryWizard;
     
    3637    private static final int CANCEL = 2;
    3738
     39    private AutoCompComboBoxModel<SearchSetting> model;
     40
     41    /** preferences reader/writer with automatic transmogrification to and from String */
     42    private AutoCompComboBoxModel<SearchSetting>.Preferences prefs;
     43
    3844    /**
    3945     * Create a new {@link OverpassQueryWizardDialog}
     
    4147     */
    4248    public OverpassQueryWizardDialog(OverpassWizardCallbacks callbacks) {
    43         super(new SearchSetting(), OVERPASS_WIZARD_HISTORY.get(), new PanelOptions(false, true), callbacks.getParent(),
     49        super(new SearchSetting(), new AutoCompComboBoxModel<>(), new PanelOptions(false, true), callbacks.getParent(),
    4450                tr("Overpass Query Wizard"),
    4551                tr("Build query"), tr("Build query and execute"), tr("Cancel"));
    4652        this.callbacks = callbacks;
     53        model = hcbSearchString.getModel();
    4754        setButtonIcons("dialogs/magic-wand", "download-overpass", "cancel");
    4855        setCancelButton(CANCEL + 1);
    4956        setDefaultButton(BUILD_AN_EXECUTE_QUERY + 1);
     57        prefs = model.prefs(SearchSetting::fromString, SearchSetting::toString);
     58        prefs.load(OVERPASS_WIZARD_HISTORY);
    5059    }
    5160
     
    7685     */
    7786    private void saveHistory() {
    78         hcbSearchString.addCurrentItemToHistory();
    79         OVERPASS_WIZARD_HISTORY.put(hcbSearchString.getHistory());
     87        hcbSearchString.getModel().addTopElement(SearchSetting.fromString(hcbSearchString.getText()));
     88        prefs.save(OVERPASS_WIZARD_HISTORY);
    8089    }
    8190
  • trunk/src/org/openstreetmap/josm/gui/download/PlaceSelection.java

    r16960 r18173  
    117117        cbSearchExpression = new HistoryComboBox();
    118118        cbSearchExpression.setToolTipText(tr("Enter a place name to search for"));
    119         cbSearchExpression.setPossibleItemsTopDown(Config.getPref().getList(HISTORY_KEY, Collections.emptyList()));
     119        cbSearchExpression.getModel().prefs().load(HISTORY_KEY);
    120120        lpanel.add(cbSearchExpression, GBC.std(1, 1).fill(GBC.HORIZONTAL));
    121121
     
    195195                return;
    196196            cbSearchExpression.addCurrentItemToHistory();
    197             Config.getPref().putList(HISTORY_KEY, cbSearchExpression.getHistory());
     197            cbSearchExpression.getModel().prefs().save(HISTORY_KEY);
    198198            Server server = (Server) serverComboBox.getSelectedItem();
    199199            URL url = server.urlFunction.apply(searchExpression, isSearchMore ? model.getData() : Collections.emptyList());
  • trunk/src/org/openstreetmap/josm/gui/io/BasicUploadSettingsPanel.java

    r17995 r18173  
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55
     6import java.awt.Component;
    67import java.awt.GridBagLayout;
    78import java.awt.event.ActionEvent;
    89import java.awt.event.ActionListener;
    9 import java.awt.event.FocusAdapter;
    1010import java.awt.event.FocusEvent;
     11import java.awt.event.FocusListener;
    1112import java.awt.event.ItemEvent;
    12 import java.awt.event.KeyAdapter;
     13import java.awt.event.ItemListener;
    1314import java.awt.event.KeyEvent;
     15import java.awt.event.KeyListener;
     16import java.util.ArrayList;
    1417import java.util.Arrays;
    15 import java.util.LinkedList;
     18import java.util.Collection;
    1619import java.util.List;
    17 import java.util.Objects;
     20import java.util.Map;
     21import java.util.Optional;
    1822import java.util.concurrent.TimeUnit;
    1923
     
    2428import javax.swing.JPanel;
    2529import javax.swing.JTextField;
    26 import javax.swing.SwingUtilities;
    27 import javax.swing.event.AncestorEvent;
    28 import javax.swing.event.AncestorListener;
    29 import javax.swing.event.ChangeEvent;
    30 import javax.swing.event.ChangeListener;
    31 import javax.swing.event.DocumentEvent;
    32 import javax.swing.event.DocumentListener;
    3330import javax.swing.event.HyperlinkEvent;
     31import javax.swing.event.TableModelEvent;
     32import javax.swing.event.TableModelListener;
    3433
    3534import org.openstreetmap.josm.data.osm.Changeset;
     
    3938import org.openstreetmap.josm.gui.io.UploadTextComponentValidator.UploadCommentValidator;
    4039import org.openstreetmap.josm.gui.io.UploadTextComponentValidator.UploadSourceValidator;
     40import org.openstreetmap.josm.gui.tagging.TagModel;
    4141import org.openstreetmap.josm.gui.widgets.HistoryComboBox;
    4242import org.openstreetmap.josm.gui.widgets.JMultilineLabel;
     
    4949 * @since 2599
    5050 */
    51 public class BasicUploadSettingsPanel extends JPanel {
    52     /**
    53      * Preference name for history collection
    54      */
    55     public static final String HISTORY_KEY = "upload.comment.history";
     51public class BasicUploadSettingsPanel extends JPanel implements ActionListener, FocusListener, ItemListener, KeyListener, TableModelListener {
     52    /**
     53     * Preference name for the history of comments
     54     */
     55    public static final String COMMENT_HISTORY_KEY = "upload.comment.history";
    5656    /**
    5757     * Preference name for last used upload comment
    5858     */
    59     public static final String HISTORY_LAST_USED_KEY = "upload.comment.last-used";
     59    public static final String COMMENT_LAST_USED_KEY = "upload.comment.last-used";
    6060    /**
    6161     * Preference name for the max age search comments may have
    6262     */
    63     public static final String HISTORY_MAX_AGE_KEY = "upload.comment.max-age";
    64     /**
    65      * Preference name for the history of source values
     63    public static final String COMMENT_MAX_AGE_KEY = "upload.comment.max-age";
     64    /**
     65     * Preference name for the history of sources
    6666     */
    6767    public static final String SOURCE_HISTORY_KEY = "upload.source.history";
     
    7979    private final UploadAreaValidator areaValidator = new UploadAreaValidator(new JTextField(), areaValidatorFeedback);
    8080    /** the changeset comment model */
    81     private final transient ChangesetCommentModel changesetCommentModel;
    82     private final transient ChangesetCommentModel changesetSourceModel;
    83     private final transient ChangesetReviewModel changesetReviewModel;
     81    private final transient UploadDialogModel model;
    8482    private final transient JLabel uploadCommentFeedback = new JLabel();
    8583    private final transient UploadCommentValidator uploadCommentValidator = new UploadCommentValidator(
     
    8987            hcbUploadSource.getEditorComponent(), hcbUploadSourceFeedback);
    9088
     89    /** a lock to prevent loops in notifications */
     90    private boolean locked;
     91
     92    /**
     93     * Creates the panel
     94     *
     95     * @param model The tag editor model.
     96     *
     97     * @since 18173 (signature)
     98     */
     99    public BasicUploadSettingsPanel(UploadDialogModel model) {
     100        this.model = model;
     101        this.model.addTableModelListener(this);
     102        build();
     103    }
     104
    91105    protected JPanel buildUploadCommentPanel() {
    92106        JPanel pnl = new JPanel(new GridBagLayout());
     
    95109        hcbUploadComment.setToolTipText(tr("Enter an upload comment"));
    96110        hcbUploadComment.setMaxTextLength(Changeset.MAX_CHANGESET_TAG_LENGTH);
    97         populateHistoryComboBox(hcbUploadComment, HISTORY_KEY, new LinkedList<>());
    98         CommentModelListener commentModelListener = new CommentModelListener(hcbUploadComment, changesetCommentModel);
    99         hcbUploadComment.getEditor().addActionListener(commentModelListener);
    100         hcbUploadComment.getEditorComponent().addFocusListener(commentModelListener);
    101         hcbUploadComment.getEditorComponent().getDocument().addDocumentListener(commentModelListener);
     111        JTextField editor = hcbUploadComment.getEditorComponent();
     112        editor.getDocument().putProperty("tag", "comment");
     113        editor.addKeyListener(this);
     114        editor.addFocusListener(this);
     115        editor.addActionListener(this);
    102116        pnl.add(hcbUploadComment, GBC.eol().fill(GBC.HORIZONTAL));
    103117        pnl.add(uploadCommentFeedback, GBC.eol().insets(0, 3, 0, 0).fill(GBC.HORIZONTAL));
     
    113127        obtainSourceOnce.addHyperlinkListener(e -> {
    114128            if (HyperlinkEvent.EventType.ACTIVATED.equals(e.getEventType())) {
    115                 automaticallyAddSource();
     129                saveEdits();
     130                model.put("source", getSourceFromLayer());
    116131            }
    117132        });
    118133        obtainSourceAutomatically.setSelected(Config.getPref().getBoolean("upload.source.obtainautomatically", false));
    119134        obtainSourceAutomatically.addActionListener(e -> {
    120             if (obtainSourceAutomatically.isSelected())
    121                 automaticallyAddSource();
    122 
     135            if (obtainSourceAutomatically.isSelected()) {
     136                model.put("source", getSourceFromLayer());
     137            }
    123138            obtainSourceOnce.setVisible(!obtainSourceAutomatically.isSelected());
    124139        });
     
    133148        hcbUploadSource.setToolTipText(tr("Enter a source"));
    134149        hcbUploadSource.setMaxTextLength(Changeset.MAX_CHANGESET_TAG_LENGTH);
    135         populateHistoryComboBox(hcbUploadSource, SOURCE_HISTORY_KEY, getDefaultSources());
    136         CommentModelListener sourceModelListener = new CommentModelListener(hcbUploadSource, changesetSourceModel);
    137         hcbUploadSource.getEditor().addActionListener(sourceModelListener);
    138         hcbUploadSource.getEditorComponent().addFocusListener(sourceModelListener);
    139         hcbUploadSource.getEditorComponent().getDocument().addDocumentListener(sourceModelListener);
     150        JTextField editor = hcbUploadSource.getEditorComponent();
     151        editor.getDocument().putProperty("tag", "source");
     152        editor.addKeyListener(this);
     153        editor.addFocusListener(this);
     154        editor.addActionListener(this);
    140155        pnl.add(hcbUploadSource, GBC.eol().fill(GBC.HORIZONTAL));
    141156        pnl.add(hcbUploadSourceFeedback, GBC.eol().insets(0, 3, 0, 0).fill(GBC.HORIZONTAL));
     157
     158        return pnl;
     159    }
     160
     161    /**
     162     * Initializes this life cycle of the panel.
     163     *
     164     * Adds any changeset tags to the map.
     165     *
     166     * @param map Map where tags are added to.
     167     * @since 18173
     168     */
     169    public void initLifeCycle(Map<String, String> map) {
     170        Optional.ofNullable(getLastChangesetTagFromHistory(COMMENT_HISTORY_KEY, new ArrayList<>())).ifPresent(
     171                x -> map.put("comment", x));
     172        Optional.ofNullable(getLastChangesetTagFromHistory(SOURCE_HISTORY_KEY, getDefaultSources())).ifPresent(
     173                x -> map.put("source", x));
    142174        if (obtainSourceAutomatically.isSelected()) {
    143             automaticallyAddSource();
    144         }
    145         pnl.addAncestorListener(new AncestorListener() {
    146             @Override
    147             public void ancestorAdded(AncestorEvent event) {
    148                 if (obtainSourceAutomatically.isSelected())
    149                     automaticallyAddSource();
    150             }
    151 
    152             @Override
    153             public void ancestorRemoved(AncestorEvent event) {
    154                 // Do nothing
    155             }
    156 
    157             @Override
    158             public void ancestorMoved(AncestorEvent event) {
    159                 // Do nothing
    160             }
    161         });
    162         return pnl;
    163     }
    164 
    165     /**
    166      * Add the source tags
    167      */
    168     protected void automaticallyAddSource() {
    169         final String source = MainApplication.getMap().mapView.getLayerInformationForSourceTag();
    170         hcbUploadSource.getModel().setSelectedItem(null); // fix #20134
    171         hcbUploadSource.setText(Utils.shortenString(source, Changeset.MAX_CHANGESET_TAG_LENGTH));
    172         changesetSourceModel.setComment(hcbUploadSource.getText()); // Fix #9965
    173     }
    174 
    175     /**
    176      * Refreshes contents of upload history combo boxes from preferences.
    177      */
    178     protected void refreshHistoryComboBoxes() {
    179         populateHistoryComboBox(hcbUploadComment, HISTORY_KEY, new LinkedList<>());
    180         populateHistoryComboBox(hcbUploadSource, SOURCE_HISTORY_KEY, getDefaultSources());
    181     }
    182 
    183     private static void populateHistoryComboBox(HistoryComboBox hcb, String historyKey, List<String> defaultValues) {
    184         hcb.setPossibleItemsTopDown(Config.getPref().getList(historyKey, defaultValues));
    185         hcb.discardAllUndoableEdits();
    186     }
    187 
    188     /**
    189      * Discards undoable edits of upload history combo boxes.
    190      */
    191     protected void discardAllUndoableEdits() {
     175            map.put("source", getSourceFromLayer());
     176        }
     177        hcbUploadComment.getModel().prefs().load(COMMENT_HISTORY_KEY);
    192178        hcbUploadComment.discardAllUndoableEdits();
     179        hcbUploadSource.getModel().prefs().load(SOURCE_HISTORY_KEY, getDefaultSources());
    193180        hcbUploadSource.discardAllUndoableEdits();
     181    }
     182
     183    /**
     184     * Get a key's value from the model.
     185     * @param key The key
     186     * @return The value or ""
     187     * @since 18173
     188     */
     189    private String get(String key) {
     190        TagModel tm = model.get(key);
     191        return tm == null ? "" : tm.getValue();
     192    }
     193
     194    /**
     195     * Get the topmost item from the history if not expired.
     196     *
     197     * @param historyKey The preferences key.
     198     * @param def A default history.
     199     * @return The history item (may be null).
     200     * @since 18173 (signature)
     201     */
     202    public static String getLastChangesetTagFromHistory(String historyKey, List<String> def) {
     203        Collection<String> history = Config.getPref().getList(historyKey, def);
     204        long age = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()) - getHistoryLastUsedKey();
     205        if (age < getHistoryMaxAgeKey() && !history.isEmpty()) {
     206            return history.iterator().next();
     207        }
     208        return null;
     209    }
     210
     211    /**
     212     * Add the "source" tag
     213     * @return The source from the layer info.
     214     */
     215    private String getSourceFromLayer() {
     216        String source = MainApplication.getMap().mapView.getLayerInformationForSourceTag();
     217        return Utils.shortenString(source, Changeset.MAX_CHANGESET_TAG_LENGTH);
    194218    }
    195219
     
    220244        if (Config.getPref().getBoolean("upload.show.review.request", true)) {
    221245            add(cbRequestReview, gbc);
    222             cbRequestReview.addItemListener(e -> changesetReviewModel.setReviewRequested(e.getStateChange() == ItemEvent.SELECTED));
     246            cbRequestReview.addItemListener(this);
    223247        }
    224248        add(areaValidatorFeedback, gbc);
    225249        add(new JPanel(), GBC.std().fill(GBC.BOTH));
    226     }
    227 
    228     /**
    229      * Creates the panel
    230      *
    231      * @param changesetCommentModel the model for the changeset comment. Must not be null
    232      * @param changesetSourceModel the model for the changeset source. Must not be null.
    233      * @param changesetReviewModel the model for the changeset review. Must not be null.
    234      * @throws NullPointerException if a model is null
    235      * @since 12719 (signature)
    236      */
    237     public BasicUploadSettingsPanel(ChangesetCommentModel changesetCommentModel, ChangesetCommentModel changesetSourceModel,
    238             ChangesetReviewModel changesetReviewModel) {
    239         this.changesetCommentModel = Objects.requireNonNull(changesetCommentModel, "changesetCommentModel");
    240         this.changesetSourceModel = Objects.requireNonNull(changesetSourceModel, "changesetSourceModel");
    241         this.changesetReviewModel = Objects.requireNonNull(changesetReviewModel, "changesetReviewModel");
    242         changesetCommentModel.addChangeListener(new ChangesetCommentChangeListener(hcbUploadComment));
    243         changesetSourceModel.addChangeListener(new ChangesetCommentChangeListener(hcbUploadSource));
    244         changesetReviewModel.addChangeListener(new ChangesetReviewChangeListener());
    245         build();
    246     }
    247 
    248     void setUploadTagDownFocusTraversalHandlers(final ActionListener handler) {
    249         setHistoryComboBoxDownFocusTraversalHandler(handler, hcbUploadComment);
    250         setHistoryComboBoxDownFocusTraversalHandler(handler, hcbUploadSource);
    251     }
    252 
    253     private static void setHistoryComboBoxDownFocusTraversalHandler(ActionListener handler, HistoryComboBox hcb) {
    254         hcb.getEditor().addActionListener(handler);
    255         hcb.getEditorComponent().addKeyListener(new HistoryComboBoxKeyAdapter(hcb, handler));
    256250    }
    257251
     
    263257        if (getHistoryMaxAgeKey() > 0) {
    264258            hcbUploadComment.addCurrentItemToHistory();
    265             Config.getPref().putList(HISTORY_KEY, hcbUploadComment.getHistory());
    266             Config.getPref().putLong(HISTORY_LAST_USED_KEY, TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()));
     259            hcbUploadComment.getModel().prefs().save(COMMENT_HISTORY_KEY);
     260            Config.getPref().putLong(COMMENT_LAST_USED_KEY, TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()));
    267261        }
    268262        // store the history of sources
    269263        hcbUploadSource.addCurrentItemToHistory();
    270         Config.getPref().putList(SOURCE_HISTORY_KEY, hcbUploadSource.getHistory());
     264        hcbUploadSource.getModel().prefs().save(SOURCE_HISTORY_KEY);
    271265
    272266        // store current value of obtaining source automatically
     
    278272     */
    279273    public void startUserInput() {
    280         hcbUploadComment.requestFocusInWindow();
    281274        hcbUploadComment.getEditorComponent().requestFocusInWindow();
    282275        uploadCommentValidator.validate();
     
    312305    }
    313306
    314     /**
    315      * Forces update of comment/source model if matching text field is focused.
    316      * @since 14977
    317      */
    318     public void forceUpdateActiveField() {
    319         updateModelIfFocused(hcbUploadComment, changesetCommentModel);
    320         updateModelIfFocused(hcbUploadSource, changesetSourceModel);
    321     }
    322 
    323     private static void updateModelIfFocused(HistoryComboBox hcb, ChangesetCommentModel changesetModel) {
    324         if (hcb.getEditorComponent().hasFocus()) {
    325             changesetModel.setComment(hcb.getText());
    326         }
    327     }
    328 
    329307    static long getHistoryMaxAgeKey() {
    330         return Config.getPref().getLong(HISTORY_MAX_AGE_KEY, TimeUnit.HOURS.toSeconds(4));
     308        return Config.getPref().getLong(COMMENT_MAX_AGE_KEY, TimeUnit.HOURS.toSeconds(4));
    331309    }
    332310
    333311    static long getHistoryLastUsedKey() {
    334         return Config.getPref().getLong(BasicUploadSettingsPanel.HISTORY_LAST_USED_KEY, 0);
    335     }
    336 
    337     static final class HistoryComboBoxKeyAdapter extends KeyAdapter {
    338         private final HistoryComboBox hcb;
    339         private final ActionListener handler;
    340 
    341         HistoryComboBoxKeyAdapter(HistoryComboBox hcb, ActionListener handler) {
    342             this.hcb = hcb;
    343             this.handler = handler;
    344         }
    345 
    346         @Override
    347         public void keyTyped(KeyEvent e) {
    348             if (e.getKeyCode() == KeyEvent.VK_TAB) {
    349                 handler.actionPerformed(new ActionEvent(hcb, 0, "focusDown"));
     312        return Config.getPref().getLong(COMMENT_LAST_USED_KEY, 0);
     313    }
     314
     315    /**
     316     * Updates the combobox histories when a combobox editor loses focus.
     317     *
     318     * @param text The {@code JTextField} of the combobox editor.
     319     */
     320    private void updateHistory(JTextField text) {
     321        String tag = (String) text.getDocument().getProperty("tag"); // tag is either "comment" or "source"
     322        if (tag.equals("comment")) {
     323            hcbUploadComment.addCurrentItemToHistory();
     324        }
     325        if (tag.equals("source")) {
     326            hcbUploadSource.addCurrentItemToHistory();
     327        }
     328    }
     329
     330    /**
     331     * Updates the table editor model with changes in the comboboxes.
     332     *
     333     * The lock prevents loops in change notifications, eg. the combobox
     334     * notifies the table model and the table model notifies the combobox, which
     335     * throws IllegalStateException.
     336     *
     337     * @param text The {@code JTextField} of the combobox editor.
     338     */
     339    private void updateModel(JTextField text) {
     340        if (!locked) {
     341            locked = true;
     342            try {
     343                String tag = (String) text.getDocument().getProperty("tag"); // tag is either "comment" or "source"
     344                String value = text.getText();
     345                model.put(tag, value.isEmpty() ? null : value); // remove tags with empty values
     346            } finally {
     347                locked = false;
    350348            }
    351349        }
     
    353351
    354352    /**
    355      * Updates the changeset comment model upon changes in the input field.
    356      */
    357     static class CommentModelListener extends FocusAdapter implements ActionListener, DocumentListener {
    358 
    359         private final HistoryComboBox source;
    360         private final ChangesetCommentModel destination;
    361 
    362         CommentModelListener(HistoryComboBox source, ChangesetCommentModel destination) {
    363             this.source = source;
    364             this.destination = destination;
    365         }
    366 
    367         private void setComment() {
    368             SwingUtilities.invokeLater(() -> destination.setComment(source.getText()));
    369         }
    370 
    371         @Override
    372         public void actionPerformed(ActionEvent e) {
    373             setComment();
    374         }
    375 
    376         @Override
    377         public void focusLost(FocusEvent e) {
    378             setComment();
    379         }
    380 
    381         @Override
    382         public void insertUpdate(DocumentEvent e) {
    383             setComment();
    384         }
    385 
    386         @Override
    387         public void removeUpdate(DocumentEvent e) {
    388             setComment();
    389         }
    390 
    391         @Override
    392         public void changedUpdate(DocumentEvent e) {
    393             setComment();
    394         }
    395     }
    396 
    397     /**
    398      * Observes the changeset comment model and keeps the comment input field
    399      * in sync with the current changeset comment
    400      */
    401     static class ChangesetCommentChangeListener implements ChangeListener {
    402 
    403         private final HistoryComboBox destination;
    404 
    405         ChangesetCommentChangeListener(HistoryComboBox destination) {
    406             this.destination = destination;
    407         }
    408 
    409         @Override
    410         public void stateChanged(ChangeEvent e) {
    411             if (!(e.getSource() instanceof ChangesetCommentModel)) return;
    412             String newComment = ((ChangesetCommentModel) e.getSource()).getComment();
    413             if (!destination.getText().trim().equals(newComment)) {
    414                 destination.setText(newComment);
     353     * Save all outstanding edits to the model.
     354     * @see UploadDialog#saveEdits
     355     * @since 18173
     356     */
     357    public void saveEdits() {
     358        updateModel(hcbUploadComment.getEditorComponent());
     359        hcbUploadComment.addCurrentItemToHistory();
     360        updateModel(hcbUploadSource.getEditorComponent());
     361        hcbUploadSource.addCurrentItemToHistory();
     362    }
     363
     364    /**
     365     * Returns the UplodDialog that is our ancestor
     366     *
     367     * @return the UploadDialog or null
     368     */
     369    private UploadDialog getDialog() {
     370        Component d = getRootPane();
     371        while ((d = d.getParent()) != null) {
     372            if (d instanceof UploadDialog)
     373                return (UploadDialog) d;
     374        }
     375        return null;
     376    }
     377
     378    /**
     379     * Update the model when the selection changes in a combobox.
     380     * @param e The action event.
     381     */
     382    @Override
     383    public void actionPerformed(ActionEvent e) {
     384        getDialog().setFocusToUploadButton();
     385    }
     386
     387    @Override
     388    public void focusGained(FocusEvent e) {
     389    }
     390
     391    /**
     392     * Update the model and combobox history when a combobox editor loses focus.
     393     */
     394    @Override
     395    public void focusLost(FocusEvent e) {
     396        Object c = e.getSource();
     397        if (c instanceof JTextField) {
     398            updateModel((JTextField) c);
     399            updateHistory((JTextField) c);
     400        }
     401    }
     402
     403    /**
     404     * Updates the table editor model upon changes in the "review" checkbox.
     405     */
     406    @Override
     407    public void itemStateChanged(ItemEvent e) {
     408        if (!locked) {
     409            locked = true;
     410            try {
     411                model.put("review_requested", e.getStateChange() == ItemEvent.SELECTED ? "yes" : null);
     412            } finally {
     413                locked = false;
    415414            }
    416415        }
     
    418417
    419418    /**
    420      * Observes the changeset review model and keeps the review checkbox
    421      * in sync with the current changeset review request
    422      */
    423     class ChangesetReviewChangeListener implements ChangeListener {
    424         @Override
    425         public void stateChanged(ChangeEvent e) {
    426             if (!(e.getSource() instanceof ChangesetReviewModel)) return;
    427             boolean newState = ((ChangesetReviewModel) e.getSource()).isReviewRequested();
    428             if (cbRequestReview.isSelected() != newState) {
    429                 cbRequestReview.setSelected(newState);
     419     * Updates the controls upon changes in the table editor model.
     420     */
     421    @Override
     422    public void tableChanged(TableModelEvent e) {
     423        if (!locked) {
     424            locked = true;
     425            try {
     426                hcbUploadComment.setText(get("comment"));
     427                hcbUploadSource.setText(get("source"));
     428                cbRequestReview.setSelected(get("review_requested").equals("yes"));
     429            } finally {
     430                locked = false;
    430431            }
    431432        }
    432433    }
     434
     435    /**
     436     * Set the focus directly to the upload button if "Enter" key is pressed in any combobox.
     437     */
     438    @Override
     439    public void keyTyped(KeyEvent e) {
     440        if (e.getKeyChar() == KeyEvent.VK_ENTER) {
     441            getDialog().setFocusToUploadButton();
     442        }
     443    }
     444
     445    @Override
     446    public void keyPressed(KeyEvent e) {
     447    }
     448
     449    @Override
     450    public void keyReleased(KeyEvent e) {
     451    }
    433452}
  • trunk/src/org/openstreetmap/josm/gui/io/ChangesetManagementPanel.java

    r17709 r18173  
    2929import org.openstreetmap.josm.gui.widgets.JosmComboBox;
    3030import org.openstreetmap.josm.spi.preferences.Config;
    31 import org.openstreetmap.josm.tools.CheckParameterUtil;
    3231import org.openstreetmap.josm.tools.ImageProvider;
    3332
     
    5655    private OpenChangesetComboBoxModel model;
    5756
     57    /** the changeset comment model */
     58    private final transient UploadDialogModel uploadDialogModel;
     59
    5860    /**
    5961     * Constructs a new {@code ChangesetManagementPanel}.
    6062     *
    61      * @param changesetCommentModel the changeset comment model. Must not be null.
    62      * @throws IllegalArgumentException if {@code changesetCommentModel} is null
    63      */
    64     public ChangesetManagementPanel(ChangesetCommentModel changesetCommentModel) {
    65         CheckParameterUtil.ensureParameterNotNull(changesetCommentModel, "changesetCommentModel");
     63     * @param uploadDialogModel The tag editor model.
     64     *
     65     * @since 18173 (signature)
     66     */
     67    public ChangesetManagementPanel(UploadDialogModel uploadDialogModel) {
     68        this.uploadDialogModel = uploadDialogModel;
    6669        build();
    6770        refreshGUI();
     
    273276                Changeset cs = (Changeset) cbOpenChangesets.getSelectedItem();
    274277                if (cs == null) return;
     278                uploadDialogModel.putAll(getSelectedChangeset().getKeys());
    275279                firePropertyChange(SELECTED_CHANGESET_PROP, null, cs);
    276280            }
     
    280284    /**
    281285     * Refreshes the list of open changesets
    282      *
    283286     */
    284287    class RefreshAction extends AbstractAction {
     
    296299    /**
    297300     * Closes the currently selected changeset
    298      *
    299301     */
    300302    class CloseChangesetAction extends AbstractAction implements ItemListener {
  • trunk/src/org/openstreetmap/josm/gui/io/IUploadDialog.java

    r14977 r18173  
    6666     */
    6767    void handleIllegalChunkSize();
    68 
    69     /**
    70      * Forces update of comment/source model if matching text field is active.
    71      * @since 14977
    72      */
    73     void forceUpdateActiveField();
    7468}
  • trunk/src/org/openstreetmap/josm/gui/io/UploadDialog.java

    r17709 r18173  
    1818import java.lang.Character.UnicodeBlock;
    1919import java.util.ArrayList;
    20 import java.util.Collection;
    2120import java.util.Collections;
    2221import java.util.HashMap;
    23 import java.util.LinkedHashSet;
    2422import java.util.List;
    2523import java.util.Locale;
     
    2725import java.util.Map.Entry;
    2826import java.util.Optional;
    29 import java.util.Set;
    3027import java.util.stream.Collectors;
    3128
     
    4037
    4138import org.openstreetmap.josm.data.APIDataSet;
    42 import org.openstreetmap.josm.data.Version;
    4339import org.openstreetmap.josm.data.osm.Changeset;
    4440import org.openstreetmap.josm.data.osm.DataSet;
     
    4844import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
    4945import org.openstreetmap.josm.gui.help.HelpUtil;
     46import org.openstreetmap.josm.gui.tagging.TagEditorPanel;
    5047import org.openstreetmap.josm.gui.util.GuiHelper;
    5148import org.openstreetmap.josm.gui.util.MultiLineFlowLayout;
     
    6158import org.openstreetmap.josm.tools.ImageProvider;
    6259import org.openstreetmap.josm.tools.InputMapUtils;
     60import org.openstreetmap.josm.tools.Logging;
    6361import org.openstreetmap.josm.tools.Utils;
    6462
     
    7270    private static UploadDialog uploadDialog;
    7371
    74     /** the "created_by" changeset OSM key */
    75     private static final String CREATED_BY = "created_by";
    76 
    7772    /** the panel with the objects to upload */
    7873    private UploadedObjectsSummaryPanel pnlUploadedObjects;
     
    8580
    8681    private TitledBorder tagSettingsBorder;
    87     /** checkbox for selecting whether an atomic upload is to be used  */
    88     private TagSettingsPanel pnlTagSettings;
     82    /** a border around the tag editor panel */
     83    private JPanel pnlTagEditorBorder;
     84    /** the tag editor panel */
     85    private TagEditorPanel pnlTagEditor;
    8986    /** the tabbed pane used below of the list of primitives  */
    9087    private JTabbedPane tpConfigPanels;
     
    9289    private JButton btnUpload;
    9390
    94     /** the changeset comment model keeping the state of the changeset comment */
    95     private final transient ChangesetCommentModel changesetCommentModel = new ChangesetCommentModel();
    96     private final transient ChangesetCommentModel changesetSourceModel = new ChangesetCommentModel();
    97     private final transient ChangesetReviewModel changesetReviewModel = new ChangesetReviewModel();
     91    /** the model keeping the state of the changeset tags */
     92    private final transient UploadDialogModel model = new UploadDialogModel();
    9893
    9994    private transient DataSet dataSet;
     
    138133        splitPane.setRightComponent(tpConfigPanels);
    139134
    140         pnlBasicUploadSettings = new BasicUploadSettingsPanel(changesetCommentModel, changesetSourceModel, changesetReviewModel);
     135        pnlBasicUploadSettings = new BasicUploadSettingsPanel(model);
    141136        tpConfigPanels.add(pnlBasicUploadSettings);
    142137        tpConfigPanels.setTitleAt(0, tr("Description"));
    143         tpConfigPanels.setToolTipTextAt(0, tr("Decide how to upload the data and which changeset to use"));
    144 
     138        tpConfigPanels.setToolTipTextAt(0, tr("Describe the changes you made"));
     139
     140        JPanel pnlSettings = new JPanel(new GridBagLayout());
     141        pnlTagEditorBorder = new JPanel(new BorderLayout());
    145142        tagSettingsBorder = BorderFactory.createTitledBorder(tr("Tags of new changeset"));
    146         pnlTagSettings = new TagSettingsPanel(changesetCommentModel, changesetSourceModel, changesetReviewModel);
    147         pnlTagSettings.setBorder(tagSettingsBorder);
    148         pnlChangesetManagement = new ChangesetManagementPanel(changesetCommentModel);
     143        pnlTagEditorBorder.setBorder(tagSettingsBorder);
     144        pnlTagEditor = new TagEditorPanel(model, null, Changeset.MAX_CHANGESET_TAG_LENGTH);
     145        pnlTagEditorBorder.add(pnlTagEditor, BorderLayout.CENTER);
     146
     147        pnlChangesetManagement = new ChangesetManagementPanel(model);
    149148        pnlUploadStrategySelectionPanel = new UploadStrategySelectionPanel();
    150         JPanel pnlChangeset = new JPanel(new GridBagLayout());
    151         pnlChangeset.add(pnlChangesetManagement, GBC.eop().fill(GBC.HORIZONTAL));
    152         pnlChangeset.add(pnlUploadStrategySelectionPanel, GBC.eop().fill(GBC.HORIZONTAL));
    153         pnlChangeset.add(pnlTagSettings, GBC.eol().fill(GBC.BOTH));
    154         tpConfigPanels.add(pnlChangeset);
     149        pnlSettings.add(pnlChangesetManagement, GBC.eop().fill(GBC.HORIZONTAL));
     150        pnlSettings.add(pnlUploadStrategySelectionPanel, GBC.eop().fill(GBC.HORIZONTAL));
     151        pnlSettings.add(pnlTagEditorBorder, GBC.eol().fill(GBC.BOTH));
     152
     153        tpConfigPanels.add(pnlSettings);
    155154        tpConfigPanels.setTitleAt(1, tr("Settings"));
     155        tpConfigPanels.setToolTipTextAt(1, tr("Decide how to upload the data and which changeset to use"));
    156156
    157157        JPanel pnl = new JPanel(new BorderLayout());
     
    181181        pnl.add(new JButton(cancelAction));
    182182        InputMapUtils.addEscapeAction(getRootPane(), cancelAction);
     183
     184        // -- help button
    183185        pnl.add(new JButton(new ContextSensitiveHelpAction(ht("/Dialog/Upload"))));
    184186        HelpUtil.setHelpContext(getRootPane(), ht("/Dialog/Upload"));
     
    201203                pnlBasicUploadSettings.getUploadParameterSummaryPanel()
    202204        );
    203         pnlChangesetManagement.addPropertyChangeListener(this);
     205        pnlUploadedObjects.addPropertyChangeListener(pnlUploadStrategySelectionPanel);
    204206        pnlUploadedObjects.addPropertyChangeListener(
    205207                pnlBasicUploadSettings.getUploadParameterSummaryPanel()
    206208        );
    207         pnlUploadedObjects.addPropertyChangeListener(pnlUploadStrategySelectionPanel);
     209        pnlUploadStrategySelectionPanel.addPropertyChangeListener(this);
    208210        pnlUploadStrategySelectionPanel.addPropertyChangeListener(
    209211                pnlBasicUploadSettings.getUploadParameterSummaryPanel()
     
    218220        );
    219221
    220         pnlBasicUploadSettings.setUploadTagDownFocusTraversalHandlers(e -> btnUpload.requestFocusInWindow());
    221 
    222222        // Enable/disable the upload button if at least an upload validator rejects upload
    223223        pnlBasicUploadSettings.getUploadTextValidators().forEach(v -> v.addChangeListener(e -> btnUpload.setEnabled(
     
    227227
    228228        Config.getPref().addPreferenceChangeListener(this);
     229    }
     230
     231    /**
     232     * Initializes this life cycle of the dialog.
     233     *
     234     * Initializes the dialog each time before it is made visible. We cannot do
     235     * this in the constructor because the dialog is a singleton.
     236     *
     237     * @param dataSet The Dataset we want to upload
     238     * @since 18173
     239     */
     240    public void initLifeCycle(DataSet dataSet) {
     241        Map<String, String> map = new HashMap<>();
     242        this.dataSet = dataSet;
     243        pnlBasicUploadSettings.initLifeCycle(map);
     244        model.clear();
     245        model.putAll(map);
     246        model.putAll(this.dataSet);
    229247    }
    230248
     
    253271
    254272    /**
    255      * Sets the tags for this upload based on (later items overwrite earlier ones):
    256      * <ul>
    257      * <li>previous "source" and "comment" input</li>
    258      * <li>the tags set in the dataset (see {@link DataSet#getChangeSetTags()})</li>
    259      * <li>the tags from the selected open changeset</li>
    260      * <li>the JOSM user agent (see {@link Version#getAgentString(boolean)})</li>
    261      * </ul>
    262      *
    263      * @param dataSet to obtain the tags set in the dataset
    264      */
    265     public void setChangesetTags(DataSet dataSet) {
    266         setChangesetTags(dataSet, false);
    267     }
    268 
    269     /**
    270      * Sets the tags for this upload based on (later items overwrite earlier ones):
    271      * <ul>
    272      * <li>previous "source" and "comment" input</li>
    273      * <li>the tags set in the dataset (see {@link DataSet#getChangeSetTags()})</li>
    274      * <li>the tags from the selected open changeset</li>
    275      * <li>the JOSM user agent (see {@link Version#getAgentString(boolean)})</li>
    276      * </ul>
    277      *
    278      * @param dataSet to obtain the tags set in the dataset
    279      * @param keepSourceComment if {@code true}, keep upload {@code source} and {@code comment} current values from models
    280      */
    281     private void setChangesetTags(DataSet dataSet, boolean keepSourceComment) {
    282         final Map<String, String> tags = new HashMap<>();
    283 
    284         // obtain from previous input
    285         if (!keepSourceComment) {
    286             tags.put("source", getLastChangesetSourceFromHistory());
    287             tags.put("comment", getCommentWithDataSetHashTag(getLastChangesetCommentFromHistory(), dataSet));
    288         }
    289 
    290         // obtain from dataset
    291         if (dataSet != null) {
    292             tags.putAll(dataSet.getChangeSetTags());
    293         }
    294         this.dataSet = dataSet;
    295 
    296         // obtain from selected open changeset
    297         if (pnlChangesetManagement.getSelectedChangeset() != null) {
    298             tags.putAll(pnlChangesetManagement.getSelectedChangeset().getKeys());
    299         }
    300 
    301         // set/adapt created_by
    302         final String agent = Version.getInstance().getAgentString(false);
    303         final String createdBy = tags.get(CREATED_BY);
    304         if (createdBy == null || createdBy.isEmpty()) {
    305             tags.put(CREATED_BY, agent);
    306         } else if (!createdBy.contains(agent)) {
    307             tags.put(CREATED_BY, createdBy + ';' + agent);
    308         }
    309 
    310         // remove empty values
    311         tags.keySet().removeIf(key -> {
    312             final String v = tags.get(key);
    313             return v == null || v.isEmpty();
    314         });
    315 
    316         // ignore source/comment to keep current values from models ?
    317         if (keepSourceComment) {
    318             tags.put("source", changesetSourceModel.getComment());
    319             tags.put("comment", getCommentWithDataSetHashTag(changesetCommentModel.getComment(), dataSet));
    320         }
    321 
    322         pnlTagSettings.initFromTags(tags);
    323         pnlTagSettings.tableChanged(null);
    324         pnlBasicUploadSettings.discardAllUndoableEdits();
    325     }
    326 
    327     /**
    328      * Returns the given comment with appended hashtags from dataset changeset tags, if not already present.
    329      * @param comment changeset comment. Can be null
    330      * @param dataSet optional dataset, which can contain hashtags in its changeset tags
    331      * @return comment with dataset changesets tags, if any, not duplicated
    332      */
    333     static String getCommentWithDataSetHashTag(String comment, DataSet dataSet) {
    334         StringBuilder result = comment == null ? new StringBuilder() : new StringBuilder(comment);
    335         if (dataSet != null) {
    336             String hashtags = dataSet.getChangeSetTags().get("hashtags");
    337             if (hashtags != null) {
    338                 Set<String> sanitizedHashtags = new LinkedHashSet<>();
    339                 for (String hashtag : hashtags.split(";", -1)) {
    340                     sanitizedHashtags.add(hashtag.startsWith("#") ? hashtag : "#" + hashtag);
    341                 }
    342                 if (!sanitizedHashtags.isEmpty()) {
    343                     result.append(' ').append(String.join(" ", sanitizedHashtags));
    344                 }
    345             }
    346         }
    347         return result.toString();
     273     * Sets the input focus to upload button.
     274     * @since 18173
     275     */
     276    public void setFocusToUploadButton() {
     277        btnUpload.requestFocus();
    348278    }
    349279
     
    360290        tpConfigPanels.setSelectedIndex(0);
    361291        pnlBasicUploadSettings.startUserInput();
    362         pnlTagSettings.startUserInput();
     292        pnlTagEditor.initAutoCompletion(MainApplication.getLayerManager().getEditLayer());
    363293        pnlUploadStrategySelectionPanel.initFromPreferences();
    364294        UploadParameterSummaryPanel pnl = pnlBasicUploadSettings.getUploadParameterSummaryPanel();
     
    375305    public Changeset getChangeset() {
    376306        Changeset cs = Optional.ofNullable(pnlChangesetManagement.getSelectedChangeset()).orElseGet(Changeset::new);
    377         cs.setKeys(pnlTagSettings.getTags(false));
     307        cs.setKeys(model.getTags(false));
    378308        return cs;
    379309    }
     
    395325    }
    396326
     327    /**
     328     * Get the upload dialog model.
     329     *
     330     * @return The model.
     331     * @since 18173
     332     */
     333    public UploadDialogModel getModel() {
     334        return model;
     335    }
     336
    397337    @Override
    398338    public String getUploadComment() {
    399         return changesetCommentModel.getComment();
     339        return model.getValue("comment");
    400340    }
    401341
    402342    @Override
    403343    public String getUploadSource() {
    404         return changesetSourceModel.getComment();
     344        return model.getValue("source");
    405345    }
    406346
     
    496436        @Override
    497437        public void actionPerformed(ActionEvent e) {
    498             // force update of model in case dialog is closed before focus lost event, see #17452
    499             dialog.forceUpdateActiveField();
     438            Map<String, String> tags = dialog.getTags(true);
     439            Logging.info("Starting upload with tags {0}", tags);
    500440
    501441            /* test for empty tags in the changeset metadata and proceed only after user's confirmation.
    502442             * though, accept if key and value are empty (cf. xor). */
    503443            List<String> emptyChangesetTags = new ArrayList<>();
    504             for (final Entry<String, String> i : dialog.getTags(true).entrySet()) {
     444            for (final Entry<String, String> i : tags.entrySet()) {
    505445                final boolean isKeyEmpty = Utils.isStripEmpty(i.getKey());
    506446                final boolean isValueEmpty = Utils.isStripEmpty(i.getValue());
     
    589529        if (evt.getPropertyName().equals(ChangesetManagementPanel.SELECTED_CHANGESET_PROP)) {
    590530            Changeset cs = (Changeset) evt.getNewValue();
    591             setChangesetTags(dataSet, cs == null); // keep comment/source of first tab for new changesets
    592531            if (cs == null) {
    593532                tagSettingsBorder.setTitle(tr("Tags of new changeset"));
     
    610549                    osmServerUrlChanged(e.getNewValue());
    611550                    break;
    612                 case BasicUploadSettingsPanel.HISTORY_KEY:
    613                 case BasicUploadSettingsPanel.SOURCE_HISTORY_KEY:
    614                     pnlBasicUploadSettings.refreshHistoryComboBoxes();
    615                     break;
    616551                default:
    617552                    return;
     
    630565    }
    631566
    632     private static String getLastChangesetTagFromHistory(String historyKey, List<String> def) {
    633         Collection<String> history = Config.getPref().getList(historyKey, def);
    634         long age = System.currentTimeMillis() / 1000 - BasicUploadSettingsPanel.getHistoryLastUsedKey();
    635         if (age < BasicUploadSettingsPanel.getHistoryMaxAgeKey() && !history.isEmpty()) {
    636             return history.iterator().next();
    637         }
    638         return null;
    639     }
    640 
    641     /**
    642      * Returns the last changeset comment from history.
    643      * @return the last changeset comment from history
    644      */
    645     public static String getLastChangesetCommentFromHistory() {
    646         return getLastChangesetTagFromHistory(BasicUploadSettingsPanel.HISTORY_KEY, new ArrayList<String>());
    647     }
    648 
    649     /**
    650      * Returns the last changeset source from history.
    651      * @return the last changeset source from history
    652      */
    653     public static String getLastChangesetSourceFromHistory() {
    654         return getLastChangesetTagFromHistory(BasicUploadSettingsPanel.SOURCE_HISTORY_KEY, BasicUploadSettingsPanel.getDefaultSources());
    655     }
    656 
     567    /* -------------------------------------------------------------------------- */
     568    /* Interface IUploadDialog                                                    */
     569    /* -------------------------------------------------------------------------- */
    657570    @Override
    658571    public Map<String, String> getTags(boolean keepEmpty) {
    659         return pnlTagSettings.getTags(keepEmpty);
     572        saveEdits();
     573        return model.getTags(keepEmpty);
    660574    }
    661575
     
    677591    }
    678592
    679     @Override
    680     public void forceUpdateActiveField() {
    681         if (tpConfigPanels.getSelectedComponent() == pnlBasicUploadSettings) {
    682             pnlBasicUploadSettings.forceUpdateActiveField();
    683         }
     593    /**
     594     * Save all outstanding edits to the model.
     595     * <p>
     596     * The combobox editors and the tag cell editor need to be manually saved
     597     * because they normally save on focus loss, eg. when the "Upload" button is
     598     * pressed, but there's no focus change when Ctrl+Enter is pressed.
     599     *
     600     * @since 18173
     601     */
     602    public void saveEdits() {
     603        pnlBasicUploadSettings.saveEdits();
     604        pnlTagEditor.saveEdits();
    684605    }
    685606
  • trunk/src/org/openstreetmap/josm/gui/preferences/projection/CustomProjectionChoice.java

    r15011 r18173  
    2323import org.openstreetmap.josm.data.projection.ProjectionConfigurationException;
    2424import org.openstreetmap.josm.data.projection.Projections;
    25 import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem;
    2625import org.openstreetmap.josm.gui.ExtendedDialog;
    2726import org.openstreetmap.josm.gui.widgets.AbstractTextComponentValidator;
     
    2928import org.openstreetmap.josm.gui.widgets.HtmlPanel;
    3029import org.openstreetmap.josm.gui.widgets.JosmTextField;
    31 import org.openstreetmap.josm.spi.preferences.Config;
    3230import org.openstreetmap.josm.tools.GBC;
    3331import org.openstreetmap.josm.tools.ImageProvider;
     
    6462            input = new JosmTextField(30);
    6563            cbInput = new HistoryComboBox();
    66             cbInput.setPrototypeDisplayValue(new AutoCompletionItem("xxxx"));
    6764            cbInput.setEditor(new BasicComboBoxEditor() {
    6865                @Override
     
    7471                    "+proj=lonlat +ellps=WGS84 +datum=WGS84 +bounds=-180,-90,180,90",
    7572                    "+proj=tmerc +lat_0=0 +lon_0=9 +k_0=1 +x_0=3500000 +y_0=0 +ellps=bessel +nadgrids=BETA2007.gsb");
    76             cbInput.setPossibleItemsTopDown(Config.getPref().getList("projection.custom.value.history", samples));
     73            cbInput.getModel().prefs().load("projection.custom.value.history", samples);
    7774            cbInput.setText(initialText == null ? "" : initialText);
    7875
     
    146143        public void rememberHistory() {
    147144            cbInput.addCurrentItemToHistory();
    148             Config.getPref().putList("projection.custom.value.history", cbInput.getHistory());
     145            cbInput.getModel().prefs().save("projection.custom.value.history");
    149146        }
    150147    }
  • trunk/src/org/openstreetmap/josm/gui/preferences/server/OsmApiUrlInputPanel.java

    r17160 r18173  
    109109    public void initFromPreferences() {
    110110        String url = OsmApi.getOsmApi().getServerUrl();
    111         tfOsmServerUrl.setPossibleItems(SERVER_URL_HISTORY.get());
     111        tfOsmServerUrl.getModel().prefs().load(SERVER_URL_HISTORY);
    112112        if (Config.getUrls().getDefaultOsmApiUrl().equals(url.trim())) {
    113113            cbUseDefaultServerUrl.setSelected(true);
     
    131131            Config.getPref().put("osm-server.url", hmiUrl);
    132132            tfOsmServerUrl.addCurrentItemToHistory();
    133             SERVER_URL_HISTORY.put(tfOsmServerUrl.getHistory());
     133            tfOsmServerUrl.getModel().prefs().save(SERVER_URL_HISTORY);
    134134        }
    135135        String newUrl = OsmApi.getOsmApi().getServerUrl();
  • trunk/src/org/openstreetmap/josm/gui/preferences/server/OverpassServerPanel.java

    r17162 r18173  
    4242     */
    4343    public final void initFromPreferences() {
    44         overpassServer.setPossibleItems(OverpassDownloadReader.OVERPASS_SERVER_HISTORY.get());
     44        overpassServer.getModel().prefs().load(OverpassDownloadReader.OVERPASS_SERVER_HISTORY);
    4545        overpassServer.setText(OverpassDownloadReader.OVERPASS_SERVER.get());
    4646        forMultiFetch.setSelected(OverpassDownloadReader.FOR_MULTI_FETCH.get());
     
    5252    public final void saveToPreferences() {
    5353        OverpassDownloadReader.OVERPASS_SERVER.put(overpassServer.getText());
    54         OverpassDownloadReader.OVERPASS_SERVER_HISTORY.put(overpassServer.getHistory());
     54        overpassServer.getModel().prefs().save(OverpassDownloadReader.OVERPASS_SERVER_HISTORY);
    5555        OverpassDownloadReader.FOR_MULTI_FETCH.put(forMultiFetch.isSelected());
    5656    }
  • trunk/src/org/openstreetmap/josm/gui/tagging/TagEditorModel.java

    r17585 r18173  
    646646    }
    647647
    648     private void commitPendingEdit() {
     648    protected void commitPendingEdit() {
    649649        if (endEditListener != null) {
    650650            endEditListener.endCellEditing();
  • trunk/src/org/openstreetmap/josm/gui/tagging/TagEditorPanel.java

    r17596 r18173  
    222222        validate();
    223223    }
     224
     225    /**
     226     * Save all outstanding edits to the model.
     227     * @see org.openstreetmap.josm.gui.io.UploadDialog#saveEdits
     228     * @since 18173
     229     */
     230    public void saveEdits() {
     231        tagTable.endCellEditing();
     232    }
    224233}
  • trunk/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletingComboBox.java

    r18141 r18173  
    22package org.openstreetmap.josm.gui.tagging.ac;
    33
    4 import java.awt.datatransfer.Clipboard;
    5 import java.awt.datatransfer.Transferable;
    6 import java.awt.event.FocusEvent;
    7 import java.awt.event.FocusListener;
    8 import java.awt.im.InputContext;
    9 import java.util.Collection;
    10 import java.util.Collections;
    11 import java.util.LinkedList;
    12 import java.util.Locale;
    13 
    14 import javax.swing.ComboBoxModel;
    15 import javax.swing.DefaultComboBoxModel;
    16 import javax.swing.text.AttributeSet;
    17 import javax.swing.text.BadLocationException;
    18 import javax.swing.text.JTextComponent;
    19 import javax.swing.text.PlainDocument;
    20 import javax.swing.text.StyleConstants;
    21 
    224import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem;
    23 import org.openstreetmap.josm.data.tagging.ac.AutoCompletionPriority;
    24 import org.openstreetmap.josm.gui.MainApplication;
    25 import org.openstreetmap.josm.gui.MapFrame;
    26 import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
    27 import org.openstreetmap.josm.gui.widgets.JosmComboBox;
    28 import org.openstreetmap.josm.spi.preferences.Config;
    29 import org.openstreetmap.josm.tools.Logging;
    30 import org.openstreetmap.josm.tools.Utils;
    315
    326/**
    33  * Auto-completing ComboBox.
     7 * An auto-completing ComboBox.
     8 *
    349 * @author guilhem.bonnefille@gmail.com
    3510 * @since 272
     11 * @deprecated Use the generic type {@link AutoCompComboBox} instead.  Eg.
     12 *             {@code AutoCompComboBox<AutoCompletionItem>} or {@code AutoCompComboBox<String>}.
    3613 */
    37 public class AutoCompletingComboBox extends JosmComboBox<AutoCompletionItem> {
    38 
    39     private boolean autocompleteEnabled = true;
    40     private boolean locked;
    41 
    42     private int maxTextLength = -1;
    43     private boolean useFixedLocale;
    44 
    45     private final transient InputContext privateInputContext = InputContext.getInstance();
    46 
    47     static final class InnerFocusListener implements FocusListener {
    48         private final JTextComponent editorComponent;
    49 
    50         InnerFocusListener(JTextComponent editorComponent) {
    51             this.editorComponent = editorComponent;
    52         }
    53 
    54         @Override
    55         public void focusLost(FocusEvent e) {
    56             MapFrame map = MainApplication.getMap();
    57             if (map != null) {
    58                 map.keyDetector.setEnabled(true);
    59             }
    60         }
    61 
    62         @Override
    63         public void focusGained(FocusEvent e) {
    64             MapFrame map = MainApplication.getMap();
    65             if (map != null) {
    66                 map.keyDetector.setEnabled(false);
    67             }
    68             // save unix system selection (middle mouse paste)
    69             Clipboard sysSel = ClipboardUtils.getSystemSelection();
    70             if (sysSel != null) {
    71                 Transferable old = ClipboardUtils.getClipboardContent(sysSel);
    72                 editorComponent.selectAll();
    73                 if (old != null) {
    74                     sysSel.setContents(old, null);
    75                 }
    76             } else if (e != null && e.getOppositeComponent() != null) {
    77                 // Select all characters when the change of focus occurs inside JOSM only.
    78                 // When switching from another application, it is annoying, see #13747
    79                 editorComponent.selectAll();
    80             }
    81         }
    82     }
    83 
    84     /**
    85      * Auto-complete a JosmComboBox.
    86      * <br>
    87      * Inspired by <a href="http://www.orbital-computer.de/JComboBox">Thomas Bierhance example</a>.
    88      */
    89     class AutoCompletingComboBoxDocument extends PlainDocument {
    90 
    91         @Override
    92         public void remove(int offs, int len) throws BadLocationException {
    93             try {
    94                 super.remove(offs, len);
    95             } catch (IllegalArgumentException e) {
    96                 // IAE can happen with Devanagari script, see #15825
    97                 Logging.error(e);
    98             }
    99         }
    100 
    101         @Override
    102         public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
    103             // TODO get rid of code duplication w.r.t. AutoCompletingTextField.AutoCompletionDocument.insertString
    104 
    105             if (maxTextLength > -1 && str.length() + getLength() > maxTextLength)
    106                 return;
    107 
    108             super.insertString(offs, str, a);
    109 
    110             if (locked)
    111                 return; // don't get in a loop
    112 
    113             if (!autocompleteEnabled)
    114                 return;
    115 
    116             // input method for non-latin characters (e.g. scim)
    117             if (a != null && a.isDefined(StyleConstants.ComposedTextAttribute))
    118                 return;
    119 
    120             // if the cursor isn't at the end of the text we don't autocomplete.
    121             // If a highlighted autocompleted suffix was present and we get here Swing has
    122             // already removed it from the document. getLength() therefore doesn't include the autocompleted suffix.
    123             if (offs + str.length() < getLength()) {
    124                 return;
    125             }
    126 
    127             String prefix = getText(0, getLength()); // the whole text after insertion
    128 
    129             if (Config.getPref().getBoolean("autocomplete.dont_complete_numbers", true)
    130                     && prefix.matches("^\\d+$"))
    131                 return;
    132 
    133             autocomplete(prefix);
    134 
    135             // save unix system selection (middle mouse paste)
    136             Clipboard sysSel = ClipboardUtils.getSystemSelection();
    137             if (sysSel != null) {
    138                 Transferable old = ClipboardUtils.getClipboardContent(sysSel);
    139                 if (old != null) {
    140                     sysSel.setContents(old, null);
    141                 }
    142             }
    143         }
    144     }
    145 
    146     /**
    147      * Creates a <code>AutoCompletingComboBox</code> with a default prototype display value.
    148      */
    149     public AutoCompletingComboBox() {
    150         this("Foo");
    151     }
    152 
    153     /**
    154      * Creates a <code>AutoCompletingComboBox</code> with the specified prototype display value.
    155      * @param prototype the <code>Object</code> used to compute the maximum number of elements to be displayed at once
    156      *                  before displaying a scroll bar. It also affects the initial width of the combo box.
    157      * @since 5520
    158      */
    159     public AutoCompletingComboBox(String prototype) {
    160         super(new AutoCompletionItem(prototype));
    161         final JTextComponent editorComponent = this.getEditorComponent();
    162         editorComponent.setDocument(new AutoCompletingComboBoxDocument());
    163         editorComponent.addFocusListener(new InnerFocusListener(editorComponent));
    164     }
    165 
    166     /**
    167      * Autocomplete a string.
    168      * <p>
    169      * Look in the model for an item whose true prefix matches the string. If
    170      * found, set the editor to the item and select the item in the model too.
    171      *
    172      * @param prefix The prefix to autocomplete.
    173      */
    174     private void autocomplete(String prefix) {
    175         // candidate item for autocomplete
    176         AutoCompletionItem item = findBestCandidate(prefix);
    177         if (item != null) {
    178             try {
    179                 locked = true;
    180                 setSelectedItem(item);
    181                 getEditor().setItem(item);
    182                 // select the autocompleted suffix in the editor
    183                 getEditorComponent().select(prefix.length(), item.getValue().length());
    184             } finally {
    185                 locked = false;
    186             }
    187         }
    188     }
    189 
    190     /**
    191      * Find the best candidate for autocompletion.
    192      * @param prefix The true prefix to match.
    193      * @return The best candidate (may be null)
    194      */
    195     private AutoCompletionItem findBestCandidate(String prefix) {
    196         ComboBoxModel<AutoCompletionItem> model = getModel();
    197         AutoCompletionItem bestCandidate = null;
    198         for (int i = 0, n = model.getSize(); i < n; i++) {
    199             AutoCompletionItem currentItem = model.getElementAt(i);
    200             // the "same" string is always the best candidate, but it is of
    201             // no use for autocompletion
    202             if (currentItem.getValue().equals(prefix))
    203                 return null;
    204             if (currentItem.getValue().startsWith(prefix)
    205             && (bestCandidate == null || currentItem.getPriority().compareTo(bestCandidate.getPriority()) > 0)) {
    206                 bestCandidate = currentItem;
    207             }
    208         }
    209         return bestCandidate;
    210     }
    211 
    212     /**
    213      * Sets the maximum text length.
    214      * @param length the maximum text length in number of characters
    215      */
    216     public void setMaxTextLength(int length) {
    217         this.maxTextLength = length;
    218     }
    219 
    220     /**
    221      * Selects a given item in the ComboBox model
    222      * @param item the item of type AutoCompletionItem, String or null
    223      * @param disableAutoComplete if true, autocomplete {@linkplain #setAutocompleteEnabled is disabled} during the operation
    224      * @since 15885
    225      */
    226     public void setSelectedItem(Object item, final boolean disableAutoComplete) {
    227         final boolean previousState = isAutocompleteEnabled();
    228         if (disableAutoComplete) {
    229             // disable autocomplete to prevent unnecessary actions in AutoCompletingComboBoxDocument#insertString
    230             setAutocompleteEnabled(false);
    231         }
    232         setSelectedItem(item);
    233         setAutocompleteEnabled(previousState);
    234     }
    235 
    236     /**
    237      * Sets the items of the combobox to the given {@code String}s in reversed order (last element first).
    238      * @param elems String items
    239      */
    240     public void setPossibleItems(Collection<String> elems) {
    241         DefaultComboBoxModel<AutoCompletionItem> model = (DefaultComboBoxModel<AutoCompletionItem>) this.getModel();
    242         Object oldValue = this.getEditor().getItem(); // Do not use getSelectedItem(); (fix #8013)
    243         model.removeAllElements();
    244         for (String elem : elems) {
    245             model.addElement(new AutoCompletionItem(elem, AutoCompletionPriority.UNKNOWN));
    246         }
    247         this.setSelectedItem(null);
    248         this.setSelectedItem(oldValue, true);
    249     }
    250 
    251     /**
    252      * Sets the items of the combobox to the given {@code String}s in top down order.
    253      * @param elems Collection of String items (is not changed)
    254      * @since 15011
    255      */
    256     public void setPossibleItemsTopDown(Collection<String> elems) {
    257         // We have to reverse the history, because ComboBoxHistory will reverse it again in addElement()
    258         LinkedList<String> reversed = new LinkedList<>(elems);
    259         Collections.reverse(reversed);
    260         setPossibleItems(reversed);
    261     }
    262 
    263     /**
    264      * Sets the items of the combobox to the given {@code AutoCompletionItem}s.
    265      * @param elems AutoCompletionItem items
    266      * @since 12859
    267      */
    268     public void setPossibleAcItems(Collection<AutoCompletionItem> elems) {
    269         DefaultComboBoxModel<AutoCompletionItem> model = (DefaultComboBoxModel<AutoCompletionItem>) this.getModel();
    270         Object oldValue = getSelectedItem();
    271         Object editorOldValue = this.getEditor().getItem();
    272         model.removeAllElements();
    273         for (AutoCompletionItem elem : elems) {
    274             model.addElement(elem);
    275         }
    276         setSelectedItem(oldValue);
    277         this.getEditor().setItem(editorOldValue);
    278     }
    279 
    280     /**
    281      * Determines if autocompletion is enabled.
    282      * @return {@code true} if autocompletion is enabled, {@code false} otherwise.
    283      */
    284     public final boolean isAutocompleteEnabled() {
    285         return autocompleteEnabled;
    286     }
    287 
    288     /**
    289      * Sets whether the autocompletion is enabled
    290      * @param autocompleteEnabled {@code true} to enable autocompletion
    291      * @since 15567 (visibility)
    292      */
    293     public void setAutocompleteEnabled(boolean autocompleteEnabled) {
    294         this.autocompleteEnabled = autocompleteEnabled;
    295     }
    296 
    297     /**
    298      * If the locale is fixed, English keyboard layout will be used by default for this combobox
    299      * all other components can still have different keyboard layout selected
    300      * @param f fixed locale
    301      */
    302     public void setFixedLocale(boolean f) {
    303         useFixedLocale = f;
    304         if (useFixedLocale) {
    305             Locale oldLocale = privateInputContext.getLocale();
    306             Logging.info("Using English input method");
    307             if (!privateInputContext.selectInputMethod(new Locale("en", "US"))) {
    308                 // Unable to use English keyboard layout, disable the feature
    309                 Logging.warn("Unable to use English input method");
    310                 useFixedLocale = false;
    311                 if (oldLocale != null) {
    312                     Logging.info("Restoring input method to " + oldLocale);
    313                     if (!privateInputContext.selectInputMethod(oldLocale)) {
    314                         Logging.warn("Unable to restore input method to " + oldLocale);
    315                     }
    316                 }
    317             }
    318         }
    319     }
    320 
    321     @Override
    322     public InputContext getInputContext() {
    323         if (useFixedLocale) {
    324             return privateInputContext;
    325         }
    326         return super.getInputContext();
    327     }
    328 
    329     /**
    330      * Returns the edited item with whitespaces removed
    331      * @return the edited item with whitespaces removed
    332      * @since 15835
    333      */
    334     public String getEditItem() {
    335         return Utils.removeWhiteSpaces(getEditor().getItem().toString());
    336     }
    337 
    338     /**
    339      * Returns the selected item or the edited item as string
    340      * @return the selected item or the edited item as string
    341      * @see #getSelectedItem()
    342      * @see #getEditItem()
    343      * @since 15835
    344      */
    345     public String getSelectedOrEditItem() {
    346         final Object selectedItem = getSelectedItem();
    347         if (selectedItem instanceof AutoCompletionItem) {
    348             return ((AutoCompletionItem) selectedItem).getValue();
    349         } else if (selectedItem instanceof String) {
    350             return (String) selectedItem;
    351         } else {
    352             return getEditItem();
    353         }
    354     }
     14@Deprecated
     15public class AutoCompletingComboBox extends AutoCompComboBox<AutoCompletionItem> {
    35516}
  • trunk/src/org/openstreetmap/josm/gui/widgets/HistoryComboBox.java

    r18131 r18173  
    44import java.util.List;
    55
    6 import javax.swing.text.JTextComponent;
    7 
    8 import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem;
    9 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingComboBox;
    10 import org.openstreetmap.josm.spi.preferences.Config;
     6import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBox;
    117
    128/**
    13  * An {@link AutoCompletingComboBox} which keeps a history
     9 * A History ComboBox
     10 * <p>
     11 * A HistoryComboBox is an {@link AutoCompComboBox} specialized in {@code String}s.
    1412 */
    15 public class HistoryComboBox extends AutoCompletingComboBox {
    16     private final ComboBoxHistory model;
    17 
    18     /**
    19      * The default size of the search history.
    20      */
    21     public static final int DEFAULT_SEARCH_HISTORY_SIZE = 15;
     13public class HistoryComboBox extends AutoCompComboBox<String> {
    2214
    2315    /**
     
    2517     */
    2618    public HistoryComboBox() {
    27         int maxsize = Config.getPref().getInt("search.history-size", DEFAULT_SEARCH_HISTORY_SIZE);
    28         model = new ComboBoxHistory(maxsize);
    29         setModel(model);
    30         setEditable(true);
     19        super(new HistoryComboBoxModel());
     20        setPrototypeDisplayValue("dummy");
     21    }
     22
     23    @Override
     24    public HistoryComboBoxModel getModel() {
     25        return (HistoryComboBoxModel) dataModel;
    3126    }
    3227
    3328    /**
    34      * Returns the text contained in this component
    35      * @return the text
    36      * @see JTextComponent#getText()
    37      */
    38     public String getText() {
    39         return getEditorComponent().getText();
    40     }
    41 
    42     /**
    43      * Sets the text of this component to the specified text
    44      * @param value the text to set
    45      * @see JTextComponent#setText(java.lang.String)
    46      */
    47     public void setText(String value) {
    48         setAutocompleteEnabled(false);
    49         getEditorComponent().setText(value);
    50         setAutocompleteEnabled(true);
    51     }
    52 
    53     /**
    54      * Adds or moves the current element to the top of the history
    55      * @see ComboBoxHistory#addElement(java.lang.String)
     29     * Adds the item in the editor to the top of the history. If the item is already present, don't
     30     * add another but move it to the top. The item is then selected.
    5631     */
    5732    public void addCurrentItemToHistory() {
    58         Object item = getEditor().getItem();
    59         // This avoids instantiating multiple AutoCompletionItems
    60         if (item instanceof AutoCompletionItem) {
    61             model.addElement((AutoCompletionItem) item);
    62         } else {
    63             model.addElement(item.toString());
    64         }
    65     }
    66 
    67     /**
    68      * Sets the elements of the ComboBox to the given items
    69      * @param history the items to set
    70      * @see ComboBoxHistory#setItemsAsString(java.util.List)
    71      */
    72     public void setHistory(List<String> history) {
    73         model.setItemsAsString(history);
     33        String newItem = getModel().addTopElement(getEditor().getItem().toString());
     34        getModel().setSelectedItem(newItem);
    7435    }
    7536
     
    7738     * Returns the items as strings
    7839     * @return the items as strings
    79      * @see ComboBoxHistory#asStringList()
     40     * @deprecated Has been moved to the model, where it belongs. Use
     41     *     {@link HistoryComboBoxModel#asStringList} instead.  Probably you want to use
     42     *     {@link HistoryComboBoxModel.Preferences#load} and
     43     *     {@link HistoryComboBoxModel.Preferences#save}.
    8044     */
     45    @Deprecated
    8146    public List<String> getHistory() {
    82         return model.asStringList();
     47        return getModel().asStringList();
    8348    }
    8449}
  • trunk/src/org/openstreetmap/josm/gui/widgets/JosmComboBox.java

    r16453 r18173  
    107107    public JTextField getEditorComponent() {
    108108        return (JTextField) getEditor().getEditorComponent();
     109    }
     110
     111    /**
     112     * Returns the text in the combobox editor.
     113     * @return the text
     114     * @see JTextComponent#getText
     115     * @since 18173
     116     */
     117    public String getText() {
     118        return getEditorComponent().getText();
     119    }
     120
     121    /**
     122     * Sets the text in the combobox editor.
     123     * @param value the text to set
     124     * @see JTextComponent#setText
     125     * @since 18173
     126     */
     127    public void setText(String value) {
     128        getEditorComponent().setText(value);
    109129    }
    110130
     
    164184    }
    165185
    166     protected final void init(E prototype) {
    167         init(prototype, true);
    168     }
    169 
    170     protected final void init(E prototype, boolean registerPropertyChangeListener) {
     186    /**
     187     * Set the prototypeCellValue property and calculate the height of the dropdown.
     188     */
     189    @Override
     190    public void setPrototypeDisplayValue(E prototype) {
    171191        if (prototype != null) {
    172             setPrototypeDisplayValue(prototype);
     192            super.setPrototypeDisplayValue(prototype);
    173193            int screenHeight = GuiHelper.getScreenSize().height;
    174194            // Compute maximum number of visible items based on the preferred size of the combo box.
     
    189209            setMaximumRowCount(Math.max(getMaximumRowCount(), maxsize));
    190210        }
     211    }
     212
     213    protected final void init(E prototype) {
     214        init(prototype, true);
     215    }
     216
     217    protected final void init(E prototype, boolean registerPropertyChangeListener) {
     218        setPrototypeDisplayValue(prototype);
    191219        // Handle text contextual menus for editable comboboxes
    192220        if (registerPropertyChangeListener) {
Note: See TracChangeset for help on using the changeset viewer.