Ticket #21240: 21240-2.patch

File 21240-2.patch, 170.5 KB (added by marcello@…, 4 years ago)

Revised patch

  • src/org/openstreetmap/josm/actions/OpenLocationAction.java

     
    4343import org.openstreetmap.josm.gui.util.GuiHelper;
    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;
    4948import org.openstreetmap.josm.tools.Shortcut;
     
    113112     * @param cbHistory the history combo box
    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
    120118    /**
     
    123121     */
    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
    129127    @Override
  • src/org/openstreetmap/josm/actions/SearchNotesDownloadAction.java

     
    77import java.awt.GridBagLayout;
    88import java.awt.event.ActionEvent;
    99import java.awt.event.KeyEvent;
    10 import java.util.Collections;
    1110import java.util.Optional;
    1211
    1312import javax.swing.JLabel;
     
    4443    @Override
    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());
    5049        GridBagConstraints gc = new GridBagConstraints();
     
    7271        }
    7372
    7473        searchTermBox.addCurrentItemToHistory();
    75         Config.getPref().putList(HISTORY_KEY, searchTermBox.getHistory());
     74        searchTermBox.getModel().prefs().save(HISTORY_KEY);
    7675
    7776        performSearch(searchTerm);
    7877    }
  • src/org/openstreetmap/josm/actions/UploadAction.java

     
    239239        ChangesetUpdater.check();
    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);
    245245        dialog.rememberUserInput();
  • src/org/openstreetmap/josm/actions/search/SearchAction.java

     
    1313import java.util.Collection;
    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;
    2321
     
    4240import org.openstreetmap.josm.gui.preferences.ToolbarPreferences;
    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;
    4746import org.openstreetmap.josm.tools.Shortcut;
     
    6665
    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() {
    7276            @Override
     
    8690                }
    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
    106104    /**
     
    108106     * @param s The search to save
    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
    129113    /**
     
    131115     * @return The list of search texts.
    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
    139121    private static volatile SearchSetting lastSearch;
     
    175157        }
    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;
    181163
     
    203185     * Launches the dialog for specifying search criteria and runs a search
    204186     */
    205187    public static void search() {
     188        prefs.load("search.history");
    206189        SearchSetting se = showSearchDialog(lastSearch);
    207190        if (se != null) {
    208191            searchWithHistory(se);
  • src/org/openstreetmap/josm/data/osm/search/SearchSetting.java

     
    66import java.util.Objects;
    77
    88import org.openstreetmap.josm.tools.Logging;
     9import org.openstreetmap.josm.tools.Utils;
    910
    1011/**
    1112 * This class defines a set of parameters that is used to
     
    4950
    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 18158
     61     */
     62    public String toStringEx() {
    5263        String cs = caseSensitive ?
    5364                /*case sensitive*/  trc("search", "CS") :
    5465                    /*case insensitive*/  trc("search", "CI");
     
    139150    }
    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 18158
     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.
    144173     * @return A string representation of the {@code SearchSetting} object.
  • src/org/openstreetmap/josm/data/tagging/ac/AutoCompletionItem.java

     
    9494    public boolean equals(Object obj) {
    9595        if (this == obj)
    9696            return true;
    97         if (obj == null)
     97        if (!(obj instanceof AutoCompletionItem))
    9898            return false;
    99         if (obj instanceof String)
    100             return obj.equals(value);
    101         if (getClass() != obj.getClass())
    102             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}
  • src/org/openstreetmap/josm/gui/dialogs/OsmIdSelectionDialog.java

     
    178178     * @param cbHistory the {@link HistoryComboBox} to which the history is restored to
    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
    185184    /**
     
    189188     */
    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
    195194    /**
  • src/org/openstreetmap/josm/gui/dialogs/SearchDialog.java

     
    1515import java.awt.event.MouseAdapter;
    1616import java.awt.event.MouseEvent;
    1717import java.util.Arrays;
    18 import java.util.List;
    1918
    2019import javax.swing.BorderFactory;
    2120import javax.swing.ButtonGroup;
     
    3736import org.openstreetmap.josm.gui.ExtendedDialog;
    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;
    4646import org.openstreetmap.josm.tools.Logging;
     
    5454
    5555    private final SearchSetting searchSettings;
    5656
    57     protected final HistoryComboBox hcbSearchString = new HistoryComboBox();
     57    protected final AutoCompComboBox<SearchSetting> hcbSearchString;
    5858
    5959    private JCheckBox addOnToolbar;
    6060    private JCheckBox caseSensitive;
     
    7171    private TaggingPresetSelector selector;
    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.
    7777     */
    78     public SearchDialog(SearchSetting initialValues, List<String> searchExpressionHistory, boolean expertMode) {
    79         this(initialValues, searchExpressionHistory, new PanelOptions(expertMode, false), MainApplication.getMainFrame(),
     78    public SearchDialog(SearchSetting initialValues, AutoCompComboBoxModel<SearchSetting> model, boolean expertMode) {
     79        this(initialValues, model, new PanelOptions(expertMode, false), MainApplication.getMainFrame(),
    8080                initialValues instanceof Filter ? tr("Filter") : tr("Search"),
    8181                initialValues instanceof Filter ? tr("Submit filter") : tr("Search"),
    8282                tr("Cancel"));
     
    8484        configureContextsensitiveHelp("/Action/Search", true /* show help button */);
    8585    }
    8686
    87     protected SearchDialog(SearchSetting initialValues, List<String> searchExpressionHistory, PanelOptions options,
     87    protected SearchDialog(SearchSetting initialValues, AutoCompComboBoxModel<SearchSetting> model, PanelOptions options,
    8888                           Component mainFrame, String title, String... buttonTexts) {
    8989        super(mainFrame, title, buttonTexts);
     90        hcbSearchString = new AutoCompComboBox<>(model);
    9091        this.searchSettings = new SearchSetting(initialValues);
    91         setContent(buildPanel(searchExpressionHistory, options));
     92        setContent(buildPanel(options));
    9293    }
    9394
    9495    /**
     
    100101
    101102        /**
    102103         * 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
     104         * @param expertMode Shows more options and the "search syntax" panel.
     105         * @param overpassQuery Don't show left panels and right "preset" panel. Show different "hints".
    105106         */
    106107        public PanelOptions(boolean expertMode, boolean overpassQuery) {
    107108            this.expertMode = expertMode;
     
    109110        }
    110111    }
    111112
    112     private JPanel buildPanel(List<String> searchExpressionHistory, PanelOptions options) {
     113    private JPanel buildPanel(PanelOptions options) {
    113114
    114115        // prepare the combo box with the search expressions
    115116        JLabel label = new JLabel(searchSettings instanceof Filter ? tr("Filter string:") : tr("Search string:"));
    116117
    117118        String tooltip = tr("Enter the search expression");
    118         hcbSearchString.setText(searchSettings.text);
     119        // FIXME do we need this?
     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);
    124124
     
    306306        return addOnToolbar.isSelected();
    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")));
    312312
     
    465465
    466466    private static class SearchKeywordRow extends JPanel {
    467467
    468         private final HistoryComboBox hcb;
     468        private final AutoCompComboBox<SearchSetting> hcb;
    469469
    470         SearchKeywordRow(HistoryComboBox hcb) {
     470        SearchKeywordRow(AutoCompComboBox<SearchSetting> hcb) {
    471471            super(new FlowLayout(FlowLayout.LEFT));
    472472            this.hcb = hcb;
    473473        }
  • src/org/openstreetmap/josm/gui/dialogs/properties/TagEditHelper.java

     
    8787import org.openstreetmap.josm.gui.IExtendedDialog;
    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;
    9393import org.openstreetmap.josm.gui.util.WindowGeometry;
     
    399399    }
    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 18158
     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 18158
     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
    404427     * @param togglePref  The preference to save the checkbox state to
     
    475498            AutoCompletionManager autocomplete = AutoCompletionManager.of(OsmDataManager.getInstance().getActiveDataSet());
    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
    483508            p.add(Box.createVerticalStrut(5), GBC.eol());
     
    489514
    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());
    501528            p.add(Box.createHorizontalStrut(10), GBC.std());
     
    521548
    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()) {
    532559                newkey = key;
     
    573600    }
    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) {
    580607            super(parent, title, buttonTexts);
     
    620647            super.setVisible(visible);
    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();
    626653            if (sysSel != null) {
     
    666693                   boolean valuesOK = size == currentModel.getSize()
    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)) {
    672700                       values.getEditor().selectAll();
     
    687715            if (buttons.isEmpty()) {
    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        }
    693721
     
    731759            configureContextsensitiveHelp("/Dialog/AddValue", true /* show help button */);
    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());
    738770
     
    747779            // remove the object's tag keys from the list
    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
    753786            mainPanel.add(keys, GBC.eop().fill(GBC.HORIZONTAL));
     
    938971                        tr("Choose recent tag {0}", count), null, tr("Use this tag again"), sc, false) {
    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                    }
    947980                };
     
    10931126         * Read tags from comboboxes and add it to all selected objects
    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;
    11001133            for (OsmPrimitive osm : sel) {
  • src/org/openstreetmap/josm/gui/download/OverpassQueryWizardDialog.java

     
    1414import org.openstreetmap.josm.data.preferences.ListProperty;
    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;
    1920import org.openstreetmap.josm.tools.UncheckedParseException;
     
    3536    private static final int BUILD_AN_EXECUTE_QUERY = 1;
    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}
    4046     * @param callbacks The Overpass download source panel.
    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
    5261    @Override
     
    7584     * Saves the latest, successfully parsed search term.
    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
    8291    /**
  • src/org/openstreetmap/josm/gui/download/PlaceSelection.java

     
    116116
    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
    122122        panel.add(lpanel, GBC.std().fill(GBC.HORIZONTAL).insets(5, 5, 0, 5));
     
    194194            if (!isEnabled() || searchExpression.trim().isEmpty())
    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());
    200200            NameQueryTask task = new NameQueryTask(url, data -> {
  • src/org/openstreetmap/josm/gui/io/BasicUploadSettingsPanel.java

     
    33
    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
    2024import javax.swing.BorderFactory;
     
    2327import javax.swing.JLabel;
    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;
    3635import org.openstreetmap.josm.data.osm.OsmPrimitive;
     
    3837import org.openstreetmap.josm.gui.io.UploadTextComponentValidator.UploadAreaValidator;
    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;
    4343import org.openstreetmap.josm.spi.preferences.Config;
     
    4848 * BasicUploadSettingsPanel allows to enter the basic parameters required for uploading data.
    4949 * @since 2599
    5050 */
    51 public class BasicUploadSettingsPanel extends JPanel {
     51public class BasicUploadSettingsPanel extends JPanel implements ActionListener, FocusListener, ItemListener, KeyListener, TableModelListener {
    5252    /**
    53      * Preference name for history collection
     53     * Preference name for the history of comments
    5454     */
    55     public static final String HISTORY_KEY = "upload.comment.history";
     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";
     63    public static final String COMMENT_MAX_AGE_KEY = "upload.comment.max-age";
    6464    /**
    65      * Preference name for the history of source values
     65     * Preference name for the history of sources
    6666     */
    6767    public static final String SOURCE_HISTORY_KEY = "upload.source.history";
    6868
     
    7878    private final JLabel areaValidatorFeedback = new JLabel();
    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(
    8684            hcbUploadComment.getEditorComponent(), uploadCommentFeedback);
     
    8886    private final transient UploadSourceValidator uploadSourceValidator = new UploadSourceValidator(
    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 18158 (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());
    93107        pnl.setBorder(BorderFactory.createTitledBorder(tr("Provide a brief comment for the changes you are uploading:")));
     
    94108
    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));
    104118        return pnl;
     
    112126                "<html>(<a href=\"urn:changeset-source\">" + tr("just once") + "</a>)</html>");
    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        });
    125140        JPanel obtainSource = new JPanel(new GridBagLayout());
     
    132147
    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));
    142         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             }
    151157
    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         });
    162158        return pnl;
    163159    }
    164160
    165161    /**
    166      * Add the source tags
     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 18158
    167168     */
    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
     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));
     174        if (obtainSourceAutomatically.isSelected()) {
     175            map.put("source", getSourceFromLayer());
     176        }
     177        hcbUploadComment.getModel().prefs().load(COMMENT_HISTORY_KEY);
     178        hcbUploadComment.discardAllUndoableEdits();
     179        hcbUploadSource.getModel().prefs().load(SOURCE_HISTORY_KEY, getDefaultSources());
     180        hcbUploadSource.discardAllUndoableEdits();
    173181    }
    174182
    175183    /**
    176      * Refreshes contents of upload history combo boxes from preferences.
     184     * Get a key's value from the model.
     185     * @param key The key
     186     * @return The value or ""
     187     * @since 18158
    177188     */
    178     protected void refreshHistoryComboBoxes() {
    179         populateHistoryComboBox(hcbUploadComment, HISTORY_KEY, new LinkedList<>());
    180         populateHistoryComboBox(hcbUploadSource, SOURCE_HISTORY_KEY, getDefaultSources());
     189    private String get(String key) {
     190        TagModel tm = model.get(key);
     191        return tm == null ? "" : tm.getValue();
    181192    }
    182193
    183     private static void populateHistoryComboBox(HistoryComboBox hcb, String historyKey, List<String> defaultValues) {
    184         hcb.setPossibleItemsTopDown(Config.getPref().getList(historyKey, defaultValues));
    185         hcb.discardAllUndoableEdits();
     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 18158 (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;
    186209    }
    187210
    188211    /**
    189      * Discards undoable edits of upload history combo boxes.
     212     * Add the "source" tag
     213     * @return The source from the layer info.
     214     * @since xxx
    190215     */
    191     protected void discardAllUndoableEdits() {
    192         hcbUploadComment.discardAllUndoableEdits();
    193         hcbUploadSource.discardAllUndoableEdits();
     216    private String getSourceFromLayer() {
     217        String source = MainApplication.getMap().mapView.getLayerInformationForSourceTag();
     218        return Utils.shortenString(source, Changeset.MAX_CHANGESET_TAG_LENGTH);
    194219    }
    195220
    196221    /**
     
    219244        add(pnlUploadParameterSummary, gbc);
    220245        if (Config.getPref().getBoolean("upload.show.review.request", true)) {
    221246            add(cbRequestReview, gbc);
    222             cbRequestReview.addItemListener(e -> changesetReviewModel.setReviewRequested(e.getStateChange() == ItemEvent.SELECTED));
     247            cbRequestReview.addItemListener(this);
    223248        }
    224249        add(areaValidatorFeedback, gbc);
    225250        add(new JPanel(), GBC.std().fill(GBC.BOTH));
     
    226251    }
    227252
    228253    /**
    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));
    256     }
    257 
    258     /**
    259254     * Remembers the user input in the preference settings
    260255     */
    261256    public void rememberUserInput() {
     
    262257        // store the history of comments
    263258        if (getHistoryMaxAgeKey() > 0) {
    264259            hcbUploadComment.addCurrentItemToHistory();
    265             Config.getPref().putList(HISTORY_KEY, hcbUploadComment.getHistory());
    266             Config.getPref().putLong(HISTORY_LAST_USED_KEY, TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()));
     260            hcbUploadComment.getModel().prefs().save(COMMENT_HISTORY_KEY);
     261            Config.getPref().putLong(COMMENT_LAST_USED_KEY, TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()));
    267262        }
    268263        // store the history of sources
    269264        hcbUploadSource.addCurrentItemToHistory();
    270         Config.getPref().putList(SOURCE_HISTORY_KEY, hcbUploadSource.getHistory());
     265        hcbUploadSource.getModel().prefs().save(SOURCE_HISTORY_KEY);
    271266
    272267        // store current value of obtaining source automatically
    273268        Config.getPref().putBoolean("upload.source.obtainautomatically", obtainSourceAutomatically.isSelected());
     
    277272     * Initializes the panel for user input
    278273     */
    279274    public void startUserInput() {
    280         hcbUploadComment.requestFocusInWindow();
    281275        hcbUploadComment.getEditorComponent().requestFocusInWindow();
    282276        uploadCommentValidator.validate();
    283277        uploadSourceValidator.validate();
     
    311305        return pnlUploadParameterSummary;
    312306    }
    313307
    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 
    329308    static long getHistoryMaxAgeKey() {
    330         return Config.getPref().getLong(HISTORY_MAX_AGE_KEY, TimeUnit.HOURS.toSeconds(4));
     309        return Config.getPref().getLong(COMMENT_MAX_AGE_KEY, TimeUnit.HOURS.toSeconds(4));
    331310    }
    332311
    333312    static long getHistoryLastUsedKey() {
    334         return Config.getPref().getLong(BasicUploadSettingsPanel.HISTORY_LAST_USED_KEY, 0);
     313        return Config.getPref().getLong(COMMENT_LAST_USED_KEY, 0);
    335314    }
    336315
    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;
     316    /**
     317     * Updates the combobox histories when a combobox editor loses focus.
     318     *
     319     * @param text The {@code JTextField} of the combobox editor.
     320     * @since xxx
     321     */
     322    private void updateHistory(JTextField text) {
     323        String tag = (String) text.getDocument().getProperty("tag"); // tag is either "comment" or "source"
     324        if (tag.equals("comment")) {
     325            hcbUploadComment.addCurrentItemToHistory();
    344326        }
     327        if (tag.equals("source")) {
     328            hcbUploadSource.addCurrentItemToHistory();
     329        }
     330    }
    345331
    346         @Override
    347         public void keyTyped(KeyEvent e) {
    348             if (e.getKeyCode() == KeyEvent.VK_TAB) {
    349                 handler.actionPerformed(new ActionEvent(hcb, 0, "focusDown"));
     332    /**
     333     * Updates the table editor model with changes in the comboboxes.
     334     *
     335     * The lock prevents loops in change notifications, eg. the combobox
     336     * notifies the table model and the table model notifies the combobox, which
     337     * throws IllegalStateException.
     338     *
     339     * @param text The {@code JTextField} of the combobox editor.
     340     * @since xxx
     341     */
     342    private void updateModel(JTextField text) {
     343        if (!locked) {
     344            locked = true;
     345            try {
     346                String tag = (String) text.getDocument().getProperty("tag"); // tag is either "comment" or "source"
     347                String value = text.getText();
     348                model.put(tag, value.isEmpty() ? null : value); // remove tags with empty values
     349            } finally {
     350                locked = false;
    350351            }
    351352        }
    352353    }
    353354
    354355    /**
    355      * Updates the changeset comment model upon changes in the input field.
     356     * Save all outstanding edits to the model.
     357     * @see UploadDialog#saveEdits
     358     * @since xxx
    356359     */
    357     static class CommentModelListener extends FocusAdapter implements ActionListener, DocumentListener {
     360    public void saveEdits() {
     361        updateModel(hcbUploadComment.getEditorComponent());
     362        hcbUploadComment.addCurrentItemToHistory();
     363        updateModel(hcbUploadSource.getEditorComponent());
     364        hcbUploadSource.addCurrentItemToHistory();
     365    }
    358366
    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;
     367    /**
     368     * Returns the UplodDialog that is our ancestor
     369     *
     370     * @return the UploadDialog or null
     371     */
     372    private UploadDialog getDialog() {
     373        Component d = getRootPane();
     374        while ((d = d.getParent()) != null) {
     375            if (d instanceof UploadDialog)
     376                return (UploadDialog) d;
    365377        }
     378        return null;
     379    }
    366380
    367         private void setComment() {
    368             SwingUtilities.invokeLater(() -> destination.setComment(source.getText()));
    369         }
     381    /**
     382     * Update the model when the selection changes in a combobox.
     383     * @param e The action event.
     384     */
     385    @Override
     386    public void actionPerformed(ActionEvent e) {
     387        getDialog().setFocusToUploadButton();
     388    }
    370389
    371         @Override
    372         public void actionPerformed(ActionEvent e) {
    373             setComment();
    374         }
     390    @Override
     391    public void focusGained(FocusEvent e) {
     392    }
    375393
    376         @Override
    377         public void focusLost(FocusEvent e) {
    378             setComment();
     394    /**
     395     * Update the model and combobox history when a combobox editor loses focus.
     396     */
     397    @Override
     398    public void focusLost(FocusEvent e) {
     399        Object c = e.getSource();
     400        if (c instanceof JTextField) {
     401            updateModel((JTextField) c);
     402            updateHistory((JTextField) c);
    379403        }
     404    }
    380405
    381         @Override
    382         public void insertUpdate(DocumentEvent e) {
    383             setComment();
     406    /**
     407     * Updates the table editor model upon changes in the "review" checkbox.
     408     */
     409    @Override
     410    public void itemStateChanged(ItemEvent e) {
     411        if (!locked) {
     412            locked = true;
     413            try {
     414                model.put("review_requested", e.getStateChange() == ItemEvent.SELECTED ? "yes" : null);
     415            } finally {
     416                locked = false;
     417            }
    384418        }
    385 
    386         @Override
    387         public void removeUpdate(DocumentEvent e) {
    388             setComment();
    389         }
    390 
    391         @Override
    392         public void changedUpdate(DocumentEvent e) {
    393             setComment();
    394         }
    395419    }
    396420
    397421    /**
    398      * Observes the changeset comment model and keeps the comment input field
    399      * in sync with the current changeset comment
     422     * Updates the controls upon changes in the table editor model.
    400423     */
    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);
     424    @Override
     425    public void tableChanged(TableModelEvent e) {
     426        if (!locked) {
     427            locked = true;
     428            try {
     429                hcbUploadComment.setText(get("comment"));
     430                hcbUploadSource.setText(get("source"));
     431                cbRequestReview.setSelected(get("review_requested").equals("yes"));
     432            } finally {
     433                locked = false;
    415434            }
    416435        }
    417436    }
    418437
    419438    /**
    420      * Observes the changeset review model and keeps the review checkbox
    421      * in sync with the current changeset review request
     439     * Set the focus directly to the upload button if "Enter" key is pressed in any combobox.
    422440     */
    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);
    430             }
     441    @Override
     442    public void keyTyped(KeyEvent e) {
     443        if (e.getKeyChar() == KeyEvent.VK_ENTER) {
     444            getDialog().setFocusToUploadButton();
    431445        }
    432446    }
     447
     448    @Override
     449    public void keyPressed(KeyEvent e) {
     450    }
     451
     452    @Override
     453    public void keyReleased(KeyEvent e) {
     454    }
    433455}
  • src/org/openstreetmap/josm/gui/io/ChangesetCommentModel.java

     
    1 // License: GPL. For details, see LICENSE file.
    2 package org.openstreetmap.josm.gui.io;
    3 
    4 import java.util.Arrays;
    5 import java.util.List;
    6 import java.util.Objects;
    7 import java.util.stream.Collectors;
    8 
    9 import org.openstreetmap.josm.gui.util.ChangeNotifier;
    10 import org.openstreetmap.josm.tools.Utils;
    11 
    12 /**
    13  * ChangesetCommentModel is an observable model for the changeset comment edited
    14  * in the {@link UploadDialog}.
    15  * @since 3133
    16  */
    17 public class ChangesetCommentModel extends ChangeNotifier {
    18     private String comment = "";
    19 
    20     /**
    21      * Sets the current changeset comment and notifies observers if the comment has changed.
    22      *
    23      * @param comment the new upload comment. Empty string assumed if null.
    24      */
    25     public void setComment(String comment) {
    26         String oldValue = this.comment;
    27         this.comment = comment == null ? "" : comment.trim();
    28         if (!Objects.equals(oldValue, this.comment)) {
    29             fireStateChanged();
    30         }
    31     }
    32 
    33     /**
    34      * Replies the current changeset comment in this model.
    35      *
    36      * @return the current changeset comment in this model.
    37      */
    38     public String getComment() {
    39         return comment == null ? "" : comment;
    40     }
    41 
    42     /**
    43      * Extracts the list of hashtags from the comment text.
    44      * @return the list of hashtags from the comment text. Can be empty, but not null.
    45      * @since 13109
    46      */
    47     public List<String> findHashTags() {
    48         return Arrays.stream(comment.split("\\s", -1))
    49                 .map(s -> Utils.strip(s, ",;"))
    50                 .filter(s -> s.matches("#[a-zA-Z][a-zA-Z_\\-0-9]+"))
    51                 .collect(Collectors.toList());
    52     }
    53 }
  • src/org/openstreetmap/josm/gui/io/ChangesetManagementPanel.java

    Property changes on: src/org/openstreetmap/josm/gui/io/ChangesetCommentModel.java
    ___________________________________________________________________
    Deleted: svn:eol-style
    ## -1 +0,0 ##
    -native
    \ No newline at end of property
     
    2828import org.openstreetmap.josm.gui.widgets.JMultilineLabel;
    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
    3433/**
     
    5554    private JCheckBox cbCloseAfterUpload;
    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     * @param uploadDialogModel The tag editor model.
     64     *
     65     * @since xxx (signature)
    6366     */
    64     public ChangesetManagementPanel(ChangesetCommentModel changesetCommentModel) {
    65         CheckParameterUtil.ensureParameterNotNull(changesetCommentModel, "changesetCommentModel");
     67    public ChangesetManagementPanel(UploadDialogModel uploadDialogModel) {
     68        this.uploadDialogModel = uploadDialogModel;
    6669        build();
    6770        refreshGUI();
    6871    }
     
    272275                }
    273276                Changeset cs = (Changeset) cbOpenChangesets.getSelectedItem();
    274277                if (cs == null) return;
     278                uploadDialogModel.putAll(getSelectedChangeset().getKeys());
    275279                firePropertyChange(SELECTED_CHANGESET_PROP, null, cs);
    276280            }
    277281        }
     
    279283
    280284    /**
    281285     * Refreshes the list of open changesets
    282      *
    283286     */
    284287    class RefreshAction extends AbstractAction {
    285288        RefreshAction() {
     
    295298
    296299    /**
    297300     * Closes the currently selected changeset
    298      *
    299301     */
    300302    class CloseChangesetAction extends AbstractAction implements ItemListener {
    301303        CloseChangesetAction() {
  • src/org/openstreetmap/josm/gui/io/ChangesetReviewModel.java

     
    1 // License: GPL. For details, see LICENSE file.
    2 package org.openstreetmap.josm.gui.io;
    3 
    4 import org.openstreetmap.josm.gui.util.ChangeNotifier;
    5 
    6 /**
    7  * ChangesetReviewModel is an observable model for the changeset review requested
    8  * in the {@link UploadDialog}.
    9  * @since 12719
    10  */
    11 public class ChangesetReviewModel extends ChangeNotifier {
    12     private boolean review;
    13 
    14     /**
    15      * Sets the current changeset review request state and notifies observers if it has changed.
    16      *
    17      * @param review the new review request state
    18      */
    19     public void setReviewRequested(boolean review) {
    20         boolean oldValue = this.review;
    21         this.review = review;
    22         if (oldValue != this.review) {
    23             fireStateChanged();
    24         }
    25     }
    26 
    27     /**
    28      * Determines if a changeset review has been requested.
    29      *
    30      * @return {@code true} if a changeset review has been requested
    31      */
    32     public boolean isReviewRequested() {
    33         return review;
    34     }
    35 }
  • src/org/openstreetmap/josm/gui/io/IUploadDialog.java

    Property changes on: src/org/openstreetmap/josm/gui/io/ChangesetReviewModel.java
    ___________________________________________________________________
    Deleted: svn:eol-style
    ## -1 +0,0 ##
    -native
    \ No newline at end of property
     
    6565     * Handles illegal chunk size.
    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}
  • src/org/openstreetmap/josm/gui/io/TagSettingsPanel.java

     
    1 // License: GPL. For details, see LICENSE file.
    2 package org.openstreetmap.josm.gui.io;
    3 
    4 import java.awt.BorderLayout;
    5 import java.util.Map;
    6 import java.util.Objects;
    7 import java.util.Optional;
    8 
    9 import javax.swing.JPanel;
    10 import javax.swing.event.ChangeEvent;
    11 import javax.swing.event.ChangeListener;
    12 import javax.swing.event.TableModelEvent;
    13 import javax.swing.event.TableModelListener;
    14 
    15 import org.openstreetmap.josm.data.osm.Changeset;
    16 import org.openstreetmap.josm.gui.MainApplication;
    17 import org.openstreetmap.josm.gui.tagging.TagEditorPanel;
    18 import org.openstreetmap.josm.gui.tagging.TagModel;
    19 import org.openstreetmap.josm.spi.preferences.Config;
    20 
    21 /**
    22  * Tag settings panel of upload dialog.
    23  * @since 2599
    24  */
    25 public class TagSettingsPanel extends JPanel implements TableModelListener {
    26 
    27     /** checkbox for selecting whether an atomic upload is to be used  */
    28     private final TagEditorPanel pnlTagEditor = new TagEditorPanel(null, null, Changeset.MAX_CHANGESET_TAG_LENGTH);
    29     /** the model for the changeset comment */
    30     private final transient ChangesetCommentModel changesetCommentModel;
    31     private final transient ChangesetCommentModel changesetSourceModel;
    32     private final transient ChangesetReviewModel changesetReviewModel;
    33 
    34     /**
    35      * Creates a new panel
    36      *
    37      * @param changesetCommentModel the changeset comment model. Must not be null.
    38      * @param changesetSourceModel the changeset source model. Must not be null.
    39      * @param changesetReviewModel the model for the changeset review. Must not be null.
    40      * @throws NullPointerException if a model is null
    41      * @since 12719 (signature)
    42      */
    43     public TagSettingsPanel(ChangesetCommentModel changesetCommentModel, ChangesetCommentModel changesetSourceModel,
    44             ChangesetReviewModel changesetReviewModel) {
    45         this.changesetCommentModel = Objects.requireNonNull(changesetCommentModel, "changesetCommentModel");
    46         this.changesetSourceModel = Objects.requireNonNull(changesetSourceModel, "changesetSourceModel");
    47         this.changesetReviewModel = Objects.requireNonNull(changesetReviewModel, "changesetReviewModel");
    48         changesetCommentModel.addChangeListener(new ChangesetCommentChangeListener("comment", "hashtags"));
    49         changesetSourceModel.addChangeListener(new ChangesetCommentChangeListener("source"));
    50         changesetReviewModel.addChangeListener(new ChangesetReviewChangeListener());
    51         build();
    52         pnlTagEditor.getModel().addTableModelListener(this);
    53     }
    54 
    55     protected void build() {
    56         setLayout(new BorderLayout());
    57         add(pnlTagEditor, BorderLayout.CENTER);
    58     }
    59 
    60     protected void setProperty(String key, String value) {
    61         String val = (value == null ? "" : value).trim();
    62         String commentInTag = getTagEditorValue(key);
    63         if (val.equals(commentInTag))
    64             return;
    65 
    66         if (val.isEmpty()) {
    67             pnlTagEditor.getModel().delete(key);
    68             return;
    69         }
    70         TagModel tag = pnlTagEditor.getModel().get(key);
    71         if (tag == null) {
    72             tag = new TagModel(key, val);
    73             pnlTagEditor.getModel().add(tag);
    74         } else {
    75             pnlTagEditor.getModel().updateTagValue(tag, val);
    76         }
    77     }
    78 
    79     protected String getTagEditorValue(String key) {
    80         TagModel tag = pnlTagEditor.getModel().get(key);
    81         return tag == null ? null : tag.getValue();
    82     }
    83 
    84     /**
    85      * Initialize panel from the given tags.
    86      * @param tags the tags used to initialize the panel
    87      */
    88     public void initFromTags(Map<String, String> tags) {
    89         pnlTagEditor.getModel().initFromTags(tags);
    90     }
    91 
    92     /**
    93      * Replies the map with the current tags in the tag editor model.
    94      * @param keepEmpty {@code true} to keep empty tags
    95      * @return the map with the current tags in the tag editor model.
    96      */
    97     public Map<String, String> getTags(boolean keepEmpty) {
    98         forceCommentFieldReload();
    99         return pnlTagEditor.getModel().getTags(keepEmpty);
    100     }
    101 
    102     /**
    103      * Initializes the panel for user input
    104      */
    105     public void startUserInput() {
    106         pnlTagEditor.initAutoCompletion(MainApplication.getLayerManager().getEditLayer());
    107     }
    108 
    109     /* -------------------------------------------------------------------------- */
    110     /* Interface TableChangeListener                                              */
    111     /* -------------------------------------------------------------------------- */
    112     @Override
    113     public void tableChanged(TableModelEvent e) {
    114         changesetCommentModel.setComment(getTagEditorValue("comment"));
    115         changesetSourceModel.setComment(getTagEditorValue("source"));
    116         changesetReviewModel.setReviewRequested("yes".equals(getTagEditorValue("review_requested")));
    117     }
    118 
    119     /**
    120      * Force update the fields if the user is currently changing them. See #5676
    121      */
    122     private void forceCommentFieldReload() {
    123         setProperty("comment", changesetCommentModel.getComment());
    124         setProperty("source", changesetSourceModel.getComment());
    125         setProperty("review_requested", changesetReviewModel.isReviewRequested() ? "yes" : null);
    126     }
    127 
    128     /**
    129      * Observes the changeset comment model and keeps the tag editor in sync
    130      * with the current changeset comment
    131      */
    132     class ChangesetCommentChangeListener implements ChangeListener {
    133 
    134         private final String key;
    135         private final String hashtagsKey;
    136 
    137         ChangesetCommentChangeListener(String key) {
    138             this(key, null);
    139         }
    140 
    141         ChangesetCommentChangeListener(String key, String hashtagsKey) {
    142             this.key = key;
    143             this.hashtagsKey = hashtagsKey;
    144         }
    145 
    146         @Override
    147         public void stateChanged(ChangeEvent e) {
    148             if (e.getSource() instanceof ChangesetCommentModel) {
    149                 ChangesetCommentModel model = ((ChangesetCommentModel) e.getSource());
    150                 String newValue = model.getComment();
    151                 String oldValue = Optional.ofNullable(getTagEditorValue(key)).orElse("");
    152                 if (!oldValue.equals(newValue)) {
    153                     setProperty(key, newValue);
    154                     if (hashtagsKey != null && Config.getPref().getBoolean("upload.changeset.hashtags", true)) {
    155                         String newHashTags = String.join(";", model.findHashTags());
    156                         String oldHashTags = Optional.ofNullable(getTagEditorValue(hashtagsKey)).orElse("");
    157                         if (!oldHashTags.equals(newHashTags)) {
    158                             setProperty(hashtagsKey, newHashTags);
    159                         }
    160                     }
    161                 }
    162             }
    163         }
    164     }
    165 
    166     /**
    167      * Observes the changeset review model and keeps the tag editor in sync
    168      * with the current changeset review request
    169      */
    170     class ChangesetReviewChangeListener implements ChangeListener {
    171 
    172         private static final String KEY = "review_requested";
    173 
    174         @Override
    175         public void stateChanged(ChangeEvent e) {
    176             if (e.getSource() instanceof ChangesetReviewModel) {
    177                 boolean newState = ((ChangesetReviewModel) e.getSource()).isReviewRequested();
    178                 boolean oldState = "yes".equals(Optional.ofNullable(getTagEditorValue(KEY)).orElse(""));
    179                 if (oldState != newState) {
    180                     setProperty(KEY, newState ? "yes" : null);
    181                 }
    182             }
    183         }
    184     }
    185 }
  • src/org/openstreetmap/josm/gui/io/UploadDialog.java

    Property changes on: src/org/openstreetmap/josm/gui/io/TagSettingsPanel.java
    ___________________________________________________________________
    Deleted: svn:eol-style
    ## -1 +0,0 ##
    -native
    \ No newline at end of property
     
    1717import java.beans.PropertyChangeListener;
    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;
    2624import java.util.Map;
    2725import java.util.Map.Entry;
    2826import java.util.Optional;
    29 import java.util.Set;
    3027import java.util.stream.Collectors;
    3128
    3229import javax.swing.AbstractAction;
     
    3936import javax.swing.border.TitledBorder;
    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;
    4541import org.openstreetmap.josm.data.osm.OsmPrimitive;
     
    4743import org.openstreetmap.josm.gui.MainApplication;
    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;
    5249import org.openstreetmap.josm.gui.util.WindowGeometry;
     
    6057import org.openstreetmap.josm.tools.GBC;
    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
    6563/**
     
    7169    /** the unique instance of the upload dialog */
    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;
    7974    /** the panel to select the changeset used */
     
    8479    private UploadStrategySelectionPanel pnlUploadStrategySelectionPanel;
    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;
    9188    /** the upload button */
    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;
    10095
     
    137132        tpConfigPanels = new CompactTabbedPane();
    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"));
     138        tpConfigPanels.setToolTipTextAt(0, tr("Describe the changes you made"));
    144139
     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());
    158158        pnl.add(splitPane, BorderLayout.CENTER);
     
    180180        CancelAction cancelAction = new CancelAction(this);
    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"));
    185187        return pnl;
     
    200202        pnlChangesetManagement.addPropertyChangeListener(
    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()
    210212        );
     
    217219                () -> tpConfigPanels.setSelectedIndex(2)
    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(
    224224                pnlBasicUploadSettings.getUploadTextValidators().stream().noneMatch(UploadTextComponentValidator::isUploadRejected))));
     
    229229    }
    230230
    231231    /**
     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 xxx
     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);
     247    }
     248
     249    /**
    232250     * Sets the collection of primitives to upload
    233251     *
    234252     * @param toUpload the dataset with the objects to upload. If null, assumes the empty
     
    252270    }
    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
     273     * Sets the input focus to upload button.
     274     * @since xxx
    264275     */
    265     public void setChangesetTags(DataSet dataSet) {
    266         setChangesetTags(dataSet, false);
     276    public void setFocusToUploadButton() {
     277        btnUpload.requestFocus();
    267278    }
    268279
    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();
    348     }
    349 
    350280    @Override
    351281    public void rememberUserInput() {
    352282        pnlBasicUploadSettings.rememberUserInput();
     
    359289    public void startUserInput() {
    360290        tpConfigPanels.setSelectedIndex(0);
    361291        pnlBasicUploadSettings.startUserInput();
    362         pnlTagSettings.startUserInput();
     292        pnlTagEditor.initAutoCompletion(MainApplication.getLayerManager().getEditLayer());
    363293        pnlUploadStrategySelectionPanel.initFromPreferences();
    364294        UploadParameterSummaryPanel pnl = pnlBasicUploadSettings.getUploadParameterSummaryPanel();
    365295        pnl.setUploadStrategySpecification(pnlUploadStrategySelectionPanel.getUploadStrategySpecification());
     
    374304     */
    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    }
    380310
     
    394324        return spec;
    395325    }
    396326
     327    /**
     328     * Get the upload dialog model.
     329     *
     330     * @return The model.
     331     * @since xxx
     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
    407347    @Override
     
    495435
    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());
    507447                final boolean ignoreKey = "comment".equals(i.getKey()) || "source".equals(i.getKey());
     
    588528    public void propertyChange(PropertyChangeEvent evt) {
    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"));
    594533            } else {
     
    609548                case "osm-server.url":
    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;
    618553            }
     
    629564        setTitle(tr("Upload to ''{0}''", url));
    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
    662576    @Override
     
    676590        tpConfigPanels.setSelectedIndex(0);
    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 xxx
     601     */
     602    public void saveEdits() {
     603        pnlBasicUploadSettings.saveEdits();
     604        pnlTagEditor.saveEdits();
    684605    }
    685606
    686607    /**
  • src/org/openstreetmap/josm/gui/io/UploadDialogModel.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.io;
     3
     4import java.util.Arrays;
     5import java.util.LinkedHashSet;
     6import java.util.List;
     7import java.util.Map;
     8import java.util.Set;
     9import java.util.stream.Collectors;
     10
     11import org.openstreetmap.josm.data.Version;
     12import org.openstreetmap.josm.data.osm.DataSet;
     13import org.openstreetmap.josm.gui.tagging.TagEditorModel;
     14import org.openstreetmap.josm.gui.tagging.TagModel;
     15import org.openstreetmap.josm.spi.preferences.Config;
     16import org.openstreetmap.josm.tools.Utils;
     17
     18/**
     19 * A model for the upload dialog
     20 *
     21 * @since xxx
     22 */
     23public class UploadDialogModel extends TagEditorModel {
     24    /** the "created_by" changeset OSM key */
     25    private static final String CREATED_BY = "created_by";
     26    /** the user-agent */
     27    private final String agent = Version.getInstance().getAgentString(false);
     28    /** whether to extract hashtags from comment */
     29    private final boolean hashtags = Config.getPref().getBoolean("upload.changeset.hashtags", true);
     30
     31    /** a lock to prevent loops  */
     32    private boolean locked;
     33
     34    @Override
     35    public void fireTableDataChanged() {
     36        if (!locked) {
     37            try {
     38                locked = true;
     39                // add "hashtags" if any
     40                if (hashtags) {
     41                    put("hashtags", findHashTags(getValue("comment")));
     42                }
     43                // add/update "created_by"
     44                final String createdBy = getValue(CREATED_BY);
     45                if (createdBy.isEmpty()) {
     46                    put(CREATED_BY, agent);
     47                } else if (!createdBy.contains(agent)) {
     48                    put(CREATED_BY, createdBy + ';' + agent);
     49                }
     50                super.fireTableDataChanged();
     51            } finally {
     52                locked = false;
     53            }
     54        }
     55    }
     56
     57    /**
     58     * Get the value of a key.
     59     *
     60     * @param key The key to retrieve
     61     * @return The value (may be null)
     62     */
     63    public String getValue(String key) {
     64        TagModel tag = get(key);
     65        return tag == null ? "" : tag.getValue();
     66    }
     67
     68    /**
     69     * Extracts the list of hashtags from the comment text.
     70     * @param comment The comment with the hashtags
     71     * @return the hashtags separated by ";" or null
     72     */
     73    String findHashTags(String comment) {
     74        String hashtags = String.join(";",
     75            Arrays.stream(comment.split("\\s", -1))
     76                .map(s -> Utils.strip(s, ",;"))
     77                .filter(s -> s.matches("#[a-zA-Z][-_a-zA-Z0-9]+"))
     78                .collect(Collectors.toList()));
     79        return hashtags.isEmpty() ? null : hashtags;
     80    }
     81
     82    /**
     83     * Returns the given comment with appended hashtags from dataset changeset tags, if not already present.
     84     * @param comment changeset comment. Can be null
     85     * @param dataSet optional dataset, which can contain hashtags in its changeset tags
     86     * @return comment with dataset changesets tags, if any, not duplicated
     87     */
     88    static String addHashTagsFromDataSet(String comment, DataSet dataSet) {
     89        StringBuilder result = comment == null ? new StringBuilder() : new StringBuilder(comment);
     90        if (dataSet != null) {
     91            String hashtags = dataSet.getChangeSetTags().get("hashtags");
     92            if (hashtags != null) {
     93                Set<String> sanitizedHashtags = new LinkedHashSet<>();
     94                for (String hashtag : hashtags.split(";", -1)) {
     95                    sanitizedHashtags.add(hashtag.startsWith("#") ? hashtag : "#" + hashtag);
     96                }
     97                if (!sanitizedHashtags.isEmpty()) {
     98                    result.append(' ').append(String.join(" ", sanitizedHashtags));
     99                }
     100            }
     101        }
     102        return result.toString();
     103    }
     104
     105    /**
     106     * Inserts/updates/deletes a tag.
     107     *
     108     * Existing keys are updated. Others are added. A value of {@code null}
     109     * deletes the key.
     110     *
     111     * @param key The key of the tag to insert.
     112     * @param value The value of the tag to insert.
     113     */
     114    private void doPut(String key, String value) {
     115        List<TagModel> l = tags.stream().filter(tm -> tm.getName().equals(key)).collect(Collectors.toList());
     116        if (!l.isEmpty()) {
     117            if (value != null)
     118                l.get(0).setValue(value);
     119            else
     120                tags.remove(l.get(0));
     121        } else if (value != null) {
     122            tags.add(new TagModel(key, value));
     123        }
     124    }
     125
     126    /**
     127     * Inserts/updates/deletes a tag.
     128     *
     129     * Existing keys are updated. Others are added. A value of {@code null}
     130     * deletes the key.
     131     *
     132     * @param key The key of the tag to insert.
     133     * @param value The value of the tag to insert.
     134     */
     135    public void put(String key, String value) {
     136        commitPendingEdit();
     137        doPut(key, value);
     138        setDirty(true);
     139        fireTableDataChanged();
     140    }
     141
     142    /**
     143     * Inserts/updates/deletes all tags from {@code map}.
     144     *
     145     * Existing keys are updated. Others are added. A value of {@code null}
     146     * deletes the key.
     147     *
     148     * @param map a map of tags to insert or update
     149     */
     150    public void putAll(Map<String, String> map) {
     151        commitPendingEdit();
     152        map.forEach((key, value) -> doPut(key, value));
     153        setDirty(true);
     154        fireTableDataChanged();
     155    }
     156
     157    /**
     158     * Inserts all tags from a {@code DataSet}.
     159     *
     160     * @param dataSet The DataSet to take tags from.
     161     */
     162    public void putAll(DataSet dataSet) {
     163        if (dataSet != null) {
     164            putAll(dataSet.getChangeSetTags());
     165            put("comment", addHashTagsFromDataSet(getValue("comment"), dataSet));
     166        }
     167    }
     168}
  • src/org/openstreetmap/josm/gui/preferences/projection/CustomProjectionChoice.java

    Property changes on: src/org/openstreetmap/josm/gui/io/UploadDialogModel.java
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
    2222import org.openstreetmap.josm.data.projection.Projection;
    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;
    2827import org.openstreetmap.josm.gui.widgets.HistoryComboBox;
    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;
    3432import org.openstreetmap.josm.tools.Logging;
     
    6361        private void build(String initialText, final ActionListener listener) {
    6462            input = new JosmTextField(30);
    6563            cbInput = new HistoryComboBox();
    66             cbInput.setPrototypeDisplayValue(new AutoCompletionItem("xxxx"));
    6764            cbInput.setEditor(new BasicComboBoxEditor() {
    6865                @Override
    6966                protected JosmTextField createEditorComponent() {
     
    7370            List<String> samples = Arrays.asList(
    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
    7976            final HtmlPanel errorsPanel = new HtmlPanel();
     
    145142
    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    }
    151148
  • src/org/openstreetmap/josm/gui/preferences/server/OsmApiUrlInputPanel.java

     
    108108     */
    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);
    114114            propagator.propagate(Config.getUrls().getDefaultOsmApiUrl());
     
    130130        } else {
    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();
    136136
  • src/org/openstreetmap/josm/gui/preferences/server/OverpassServerPanel.java

     
    4141     * Initializes the panel from preferences
    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());
    4747    }
     
    5151     */
    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    }
    5757}
  • src/org/openstreetmap/josm/gui/tagging/TagEditorModel.java

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

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

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.tagging.ac;
     3
     4import java.awt.datatransfer.Clipboard;
     5import java.awt.datatransfer.StringSelection;
     6import java.awt.datatransfer.Transferable;
     7import java.awt.event.FocusEvent;
     8import java.awt.event.FocusListener;
     9import java.awt.event.KeyEvent;
     10import java.awt.event.KeyListener;
     11import java.awt.im.InputContext;
     12import java.util.Collection;
     13import java.util.Collections;
     14import java.util.LinkedList;
     15import java.util.Locale;
     16import java.util.Objects;
     17import java.util.regex.Pattern;
     18
     19import javax.swing.JTextField;
     20import javax.swing.SwingUtilities;
     21import javax.swing.text.AbstractDocument;
     22import javax.swing.text.AttributeSet;
     23import javax.swing.text.BadLocationException;
     24import javax.swing.text.DocumentFilter;
     25import javax.swing.text.JTextComponent;
     26import javax.swing.text.StyleConstants;
     27
     28import org.openstreetmap.josm.gui.MainApplication;
     29import org.openstreetmap.josm.gui.MapFrame;
     30import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
     31import org.openstreetmap.josm.gui.widgets.JosmComboBox;
     32import org.openstreetmap.josm.spi.preferences.Config;
     33import org.openstreetmap.josm.tools.Logging;
     34
     35/**
     36 * An auto-completing ComboBox.
     37 * <p>
     38 * When the user starts typing, this combobox will suggest the
     39 * {@link AutoCompComboBoxModel#findBestCandidate best matching item} from its list.  The items can
     40 * be of any type while the items' {@code toString} values are shown in the combobox and editor.
     41 *
     42 * @author guilhem.bonnefille@gmail.com
     43 * @author marcello@perathoner.de
     44 * @param <E> the type of the combobox entries
     45 * @since xxx
     46 */
     47public class AutoCompComboBox<E> extends JosmComboBox<E> implements KeyListener {
     48
     49    /** a regex that matches numbers */
     50    private static final Pattern IS_NUMBER = Pattern.compile("^\\d+$");
     51    /** true if the combobox should autocomplete */
     52    private boolean autocompleteEnabled = true;
     53    /** the editor will not accept text longer than this. -1 to disable */
     54    private int maxTextLength = -1;
     55    /** force a different keyboard input locale for the editor */
     56    private boolean useFixedLocale;
     57
     58    /** Whether to autocomplete numbers */
     59    private final boolean AUTOCOMPLETE_NUMBERS = !Config.getPref().getBoolean("autocomplete.dont_complete_numbers", true);
     60
     61    private final transient InputContext privateInputContext = InputContext.getInstance();
     62
     63    static final class InnerFocusListener implements FocusListener {
     64        private final JTextComponent editorComponent;
     65
     66        InnerFocusListener(JTextComponent editorComponent) {
     67            this.editorComponent = editorComponent;
     68        }
     69
     70        @Override
     71        public void focusLost(FocusEvent e) {
     72            MapFrame map = MainApplication.getMap();
     73            if (map != null) {
     74                map.keyDetector.setEnabled(true);
     75            }
     76        }
     77
     78        @Override
     79        public void focusGained(FocusEvent e) {
     80            MapFrame map = MainApplication.getMap();
     81            if (map != null) {
     82                map.keyDetector.setEnabled(false);
     83            }
     84            // save unix system selection (middle mouse paste)
     85            Clipboard sysSel = ClipboardUtils.getSystemSelection();
     86            if (sysSel != null) {
     87                Transferable old = ClipboardUtils.getClipboardContent(sysSel);
     88                editorComponent.selectAll();
     89                if (old != null) {
     90                    sysSel.setContents(old, null);
     91                }
     92            } else if (e != null && e.getOppositeComponent() != null) {
     93                // Select all characters when the change of focus occurs inside JOSM only.
     94                // When switching from another application, it is annoying, see #13747
     95                editorComponent.selectAll();
     96            }
     97        }
     98    }
     99
     100    /**
     101     * A {@link DocumentFilter} to limit the text length in the editor.
     102     */
     103    private class MaxLengthDocumentFilter extends DocumentFilter {
     104        @Override
     105        public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr)
     106                throws BadLocationException {
     107            int newLen = fb.getDocument().getLength() + string.length();
     108            if (maxTextLength == -1 || newLen <= maxTextLength ||
     109                    // allow longer text while composing characters or it will be hard to compose
     110                    // the last characters before the limit
     111                    ((attr != null) && attr.isDefined(StyleConstants.ComposedTextAttribute))) {
     112                super.insertString(fb, offset, string, attr);
     113            }
     114        }
     115
     116        @Override
     117        public void replace(FilterBypass fb, int offset, int length, String string, AttributeSet attr)
     118                throws BadLocationException {
     119            int newLen = fb.getDocument().getLength() - length + string.length();
     120            if (maxTextLength == -1 || newLen <= maxTextLength ||
     121                    // allow longer text while composing characters or it will be hard to compose
     122                    // the last characters before the limit
     123                    ((attr != null) && attr.isDefined(StyleConstants.ComposedTextAttribute))) {
     124                super.replace(fb, offset, length, string, attr);
     125            }
     126        }
     127    }
     128
     129    /**
     130     * Constructs an {@code AutoCompletingComboBox}.
     131     */
     132    public AutoCompComboBox() {
     133        this(new AutoCompComboBoxModel<E>());
     134    }
     135
     136    /**
     137     * Constructs an {@code AutoCompletingComboBox} with a supplied {@link AutoCompComboBoxModel}.
     138     *
     139     * @param model the model
     140     */
     141    public AutoCompComboBox(AutoCompComboBoxModel<E> model) {
     142        super(model);
     143        Objects.requireNonNull(model, "A model cannot be null.");
     144        setEditable(true);
     145        final JTextComponent editorComponent = getEditorComponent();
     146        editorComponent.addFocusListener(new InnerFocusListener(editorComponent));
     147        editorComponent.addKeyListener(this);
     148        ((AbstractDocument) editorComponent.getDocument()).setDocumentFilter(new MaxLengthDocumentFilter());
     149    }
     150
     151    /**
     152     * Returns the {@link AutoCompComboBoxModel} currently used.
     153     *
     154     * @return the model
     155     */
     156    @Override
     157    public AutoCompComboBoxModel<E> getModel() {
     158        return (AutoCompComboBoxModel<E>) dataModel;
     159    }
     160
     161    /**
     162     * Autocompletes what the user typed in.
     163     * <p>
     164     * Gets the user input from the editor, finds the best matching item in the model, selects it in
     165     * the list, sets the editor text, and highlights the autocompleted part. If there is no
     166     * matching item, removes the list selection.
     167     */
     168    private void autocomplete() {
     169        JTextField editor = getEditorComponent();
     170        String prefix = editor.getText();
     171        if (!AUTOCOMPLETE_NUMBERS && IS_NUMBER.matcher(prefix).matches())
     172            return;
     173
     174        E item = getModel().findBestCandidate(prefix);
     175        if (item != null) {
     176            String text = item.toString();
     177            // This calls setItem() if the selected item changed
     178            // See: javax.swing.plaf.basic.BasicComboBoxUI.Handler.contentsChanged(ListDataEvent e)
     179            setSelectedItem(item);
     180            // set manually in case the selected item didn't change
     181            editor.setText(text);
     182            // select the autocompleted suffix in the editor
     183            editor.select(prefix.length(), text.length());
     184            // copy the whole autocompleted string to the unix system-wide selection (aka
     185            // middle-click), else only the selected suffix would be copied
     186            copyToSysSel(text);
     187        } else {
     188            setSelectedItem(null);
     189            // avoid setItem because it selects the whole text (on windows only)
     190            editor.setText(prefix);
     191        }
     192    }
     193
     194    /**
     195     * Copies a String to the UNIX system-wide selection (aka middle-click).
     196     *
     197     * @param s the string to copy
     198     */
     199    void copyToSysSel(String s) {
     200        Clipboard sysSel = ClipboardUtils.getSystemSelection();
     201        if (sysSel != null) {
     202            Transferable transferable = new StringSelection(s);
     203            sysSel.setContents(transferable, null);
     204        }
     205    }
     206
     207    /**
     208     * Sets the maximum text length.
     209     *
     210     * @param length the maximum text length in number of characters
     211     */
     212    public void setMaxTextLength(int length) {
     213        maxTextLength = length;
     214    }
     215
     216    /**
     217     * Sets the items of the combobox to the given {@code String}s in reversed order (last element
     218     * first).
     219     *
     220     * @param elems The string items to set
     221     * @deprecated Has been moved to the model, where it belongs. Use
     222     *     {@link org.openstreetmap.josm.gui.widgets.HistoryComboBoxModel#addAllStrings} instead. Probably you want to use
     223     *     {@link org.openstreetmap.josm.gui.widgets.HistoryComboBoxModel.Preferences#load} and
     224     *     {@link org.openstreetmap.josm.gui.widgets.HistoryComboBoxModel.Preferences#save}.
     225     */
     226    @Deprecated
     227    public void setPossibleItems(Collection<E> elems) {
     228        // We have to reverse the history, because ComboBoxHistory will reverse it again in addElement()
     229        LinkedList<E> reversed = new LinkedList<>(elems);
     230        Collections.reverse(reversed);
     231        setPossibleAcItems(reversed);
     232    }
     233
     234    /**
     235     * Sets the items of the combobox to the given {@code String}s in top down order.
     236     *
     237     * @param elems The strings to set.
     238     * @since 15011
     239     * @deprecated Has been moved to the model, where it belongs. Use
     240     *     {@link org.openstreetmap.josm.gui.widgets.HistoryComboBoxModel#addAllStrings} instead. Probably you want to use
     241     *     {@link org.openstreetmap.josm.gui.widgets.HistoryComboBoxModel.Preferences#load} and
     242     *     {@link org.openstreetmap.josm.gui.widgets.HistoryComboBoxModel.Preferences#save}.
     243     */
     244    @Deprecated
     245    public void setPossibleItemsTopDown(Collection<E> elems) {
     246        setPossibleAcItems(elems);
     247    }
     248
     249    /**
     250     * Sets the items of the combobox to the given {@code AutoCompletionItem}s.
     251     *
     252     * @param elems AutoCompletionItem items
     253     * @since 12859
     254     * @deprecated Use {@link AutoCompComboBoxModel#addAllElements} instead.
     255     */
     256    @Deprecated
     257    public void setPossibleAcItems(Collection<E> elems) {
     258        Object oldValue = getEditor().getItem();
     259        getModel().removeAllElements();
     260        getModel().addAllElements(elems);
     261        getEditor().setItem(oldValue);
     262    }
     263
     264    /**
     265     * Returns {@code true} if autocompletion is enabled.
     266     *
     267     * @return {@code true} if autocompletion is enabled.
     268     */
     269    public final boolean isAutocompleteEnabled() {
     270        return autocompleteEnabled;
     271    }
     272
     273    /**
     274     * Enables or disables the autocompletion.
     275     *
     276     * @param enabled {@code true} to enable autocompletion
     277     * @return {@code true} if autocomplete was enabled before calling this
     278     * @since xxx (signature)
     279     */
     280    public boolean setAutocompleteEnabled(boolean enabled) {
     281        boolean oldEnabled = this.autocompleteEnabled;
     282        this.autocompleteEnabled = enabled;
     283        return oldEnabled;
     284    }
     285
     286    /**
     287     * Fixes the locale for keyboard input to US-English.
     288     * <p>
     289     * If the locale is fixed, English keyboard layout will be used by default for this combobox.
     290     * All other components can still have different keyboard layout selected.
     291     *
     292     * @param f if {@code true} use fixed locale
     293     */
     294    public void setFixedLocale(boolean f) {
     295        useFixedLocale = f;
     296        if (useFixedLocale) {
     297            Locale oldLocale = privateInputContext.getLocale();
     298            Logging.info("Using English input method");
     299            if (!privateInputContext.selectInputMethod(new Locale("en", "US"))) {
     300                // Unable to use English keyboard layout, disable the feature
     301                Logging.warn("Unable to use English input method");
     302                useFixedLocale = false;
     303                if (oldLocale != null) {
     304                    Logging.info("Restoring input method to " + oldLocale);
     305                    if (!privateInputContext.selectInputMethod(oldLocale)) {
     306                        Logging.warn("Unable to restore input method to " + oldLocale);
     307                    }
     308                }
     309            }
     310        }
     311    }
     312
     313    @Override
     314    public InputContext getInputContext() {
     315        if (useFixedLocale) {
     316            return privateInputContext;
     317        }
     318        return super.getInputContext();
     319    }
     320
     321    /*
     322     * The KeyListener interface
     323     */
     324
     325    /**
     326     * Listens to key events and eventually schedules an autocomplete.
     327     *
     328     * @param e the key event
     329     */
     330    @Override
     331    public void keyTyped(KeyEvent e) {
     332        if (autocompleteEnabled
     333                // and selection is at the end
     334                && getEditorComponent().getSelectionEnd() == getEditorComponent().getText().length()
     335                // and something visible was typed
     336                && !Character.isISOControl(e.getKeyChar())) {
     337            // We got the event before the editor component could see it. Let the editor do its job first.
     338            SwingUtilities.invokeLater(() -> autocomplete());
     339        }
     340    }
     341
     342    @Override
     343    public void keyPressed(KeyEvent e) {
     344    }
     345
     346    @Override
     347    public void keyReleased(KeyEvent e) {
     348    }
     349}
  • src/org/openstreetmap/josm/gui/tagging/ac/AutoCompComboBoxModel.java

    Property changes on: src/org/openstreetmap/josm/gui/tagging/ac/AutoCompComboBox.java
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.tagging.ac;
     3
     4import java.util.ArrayList;
     5import java.util.Collection;
     6import java.util.Comparator;
     7import java.util.Iterator;
     8import java.util.List;
     9import java.util.Objects;
     10import java.util.function.Function;
     11
     12import javax.swing.AbstractListModel;
     13import javax.swing.MutableComboBoxModel;
     14
     15import org.openstreetmap.josm.data.preferences.ListProperty;
     16import org.openstreetmap.josm.spi.preferences.Config;
     17
     18/**
     19 * A data model for the {@link AutoCompComboBox}
     20 *
     21 * @author marcello@perathoner.de
     22 * @param <E> The element type.
     23 * @since xxx
     24 */
     25public class AutoCompComboBoxModel<E> extends AbstractListModel<E> implements MutableComboBoxModel<E>, Iterable<E> {
     26
     27    /**
     28     * The comparator used by {@link #findBestCandidate}
     29     * <p>
     30     * The comparator is used exclusively for autocompleting, and not for sorting the combobox
     31     * entries.  The default comparator sorts elements in alphabetical order according to
     32     * {@code E::toString}.
     33     */
     34    private Comparator<E> comparator;
     35    /** The maximum number of elements to hold, -1 for no limit. Used for histories. */
     36    private int maxSize = -1;
     37
     38    /** the elements shown in the dropdown */
     39    protected ArrayList<E> elements = new ArrayList<>();
     40    /** the selected element in the dropdown or null */
     41    protected Object selected;
     42
     43    /**
     44     * Constructs a new empty model with a default {@link #comparator}.
     45     */
     46    public AutoCompComboBoxModel() {
     47        setComparator(Comparator.comparing(E::toString));
     48    }
     49
     50    /**
     51     * Constructs a new empty model with a custom {@link #comparator}.
     52     *
     53     * @param comparator A custom {@link #comparator}.
     54     */
     55    public AutoCompComboBoxModel(Comparator<E> comparator) {
     56        setComparator(comparator);
     57    }
     58
     59    /**
     60     * Sets a custom {@link #comparator}.
     61     * <p>
     62     * Example:
     63     * {@code setComparator(Comparator.comparing(E::getPriority).thenComparing(E::toString));}
     64     * <p>
     65     * If {@code <E>} implements {@link java.lang.Comparable Comparable} you can automagically create a
     66     * comparator with {@code setComparator(Comparator.naturalOrder());}.
     67     *
     68     * @param comparator A custom comparator.
     69     */
     70    public void setComparator(Comparator<E> comparator) {
     71        Objects.requireNonNull(comparator, "A comparator cannot be null.");
     72        this.comparator = comparator;
     73    }
     74
     75    /**
     76     * Sets the maximum number of elements.
     77     *
     78     * @param size The maximal number of elements in the model.
     79     */
     80    public void setSize(int size) {
     81        maxSize = size;
     82    }
     83
     84    /**
     85     * Returns a copy of the element list.
     86     * @return a copy of the data
     87     */
     88    public Collection<E> asCollection() {
     89        return new ArrayList<>(elements);
     90    }
     91
     92    //
     93    // interface java.lang.Iterable
     94    //
     95
     96    @Override
     97    public Iterator<E> iterator() {
     98        return elements.iterator();
     99    }
     100
     101    //
     102    // interface javax.swing.MutableComboBoxModel
     103    //
     104
     105    /**
     106     * Adds an element to the end of the model. Does nothing if max size is already reached.
     107     */
     108    @Override
     109    public void addElement(E element) {
     110        if (element != null && (maxSize == -1 || getSize() < maxSize)) {
     111            elements.add(element);
     112        }
     113    }
     114
     115    @Override
     116    public void removeElement(Object elem) {
     117        elements.remove(elem);
     118    }
     119
     120    @Override
     121    public void removeElementAt(int index) {
     122        Object elem = getElementAt(index);
     123        if (elem == selected) {
     124            if (index == 0) {
     125                setSelectedItem(getSize() == 1 ? null : getElementAt(index + 1));
     126            } else {
     127                setSelectedItem(getElementAt(index - 1));
     128            }
     129        }
     130        elements.remove(index);
     131        fireIntervalRemoved(this, index, index);
     132    }
     133
     134    /**
     135     * Adds an element at a specific index.
     136     *
     137     * @param element The element to add
     138     * @param index Location to add the element
     139     */
     140    @Override
     141    public void insertElementAt(E element, int index) {
     142        if (maxSize != -1 && maxSize <= getSize()) {
     143            removeElementAt(getSize() - 1);
     144        }
     145        elements.add(index, element);
     146    }
     147
     148    //
     149    // javax.swing.ComboBoxModel
     150    //
     151
     152    /**
     153     * Set the value of the selected item. The selected item may be null.
     154     *
     155     * @param elem The combo box value or null for no selection.
     156     */
     157    @Override
     158    public void setSelectedItem(Object elem) {
     159        if ((selected != null && !selected.equals(elem)) ||
     160            (selected == null && elem != null)) {
     161            selected = elem;
     162            fireContentsChanged(this, -1, -1);
     163        }
     164    }
     165
     166    @Override
     167    public Object getSelectedItem() {
     168        return selected;
     169    }
     170
     171    //
     172    // javax.swing.ListModel
     173    //
     174
     175    @Override
     176    public int getSize() {
     177        return elements.size();
     178    }
     179
     180    @Override
     181    public E getElementAt(int index) {
     182        if (index >= 0 && index < elements.size())
     183            return elements.get(index);
     184        else
     185            return null;
     186    }
     187
     188    //
     189    // end interfaces
     190    //
     191
     192    /**
     193     * Adds all elements from the collection.
     194     *
     195     * @param elems The elements to add.
     196     */
     197    public void addAllElements(Collection<E> elems) {
     198        elems.forEach(e -> addElement(e));
     199    }
     200
     201    /**
     202     * Adds all elements from the collection of string representations.
     203     *
     204     * @param strings The string representation of the elements to add.
     205     * @param buildE A {@link java.util.function.Function} that builds an {@code <E>} from a
     206     *               {@code String}.
     207     */
     208    public void addAllElements(Collection<String> strings, Function<String, E> buildE) {
     209        strings.forEach(s -> addElement(buildE.apply(s)));
     210    }
     211
     212    /**
     213     * Adds an element to the top of the list.
     214     * <p>
     215     * If the element is already in the model, moves it to the top.  If the model gets too big,
     216     * deletes the last element.
     217     *
     218     * @param newElement the element to add
     219     * @return The element that is at the top now.
     220     */
     221    public E addTopElement(E newElement) {
     222        // if the element is already at the top, do nothing
     223        if (newElement.equals(getElementAt(0)))
     224            return getElementAt(0);
     225
     226        removeElement(newElement);
     227        insertElementAt(newElement, 0);
     228        return newElement;
     229    }
     230
     231    /**
     232     * Empties the list.
     233     */
     234    public void removeAllElements() {
     235        if (!elements.isEmpty()) {
     236            int firstIndex = 0;
     237            int lastIndex = elements.size() - 1;
     238            elements.clear();
     239            selected = null;
     240            fireIntervalRemoved(this, firstIndex, lastIndex);
     241        } else {
     242            selected = null;
     243        }
     244    }
     245
     246    /**
     247     * Finds the best candidate for autocompletion.
     248     * <p>
     249     * Looks in the model for an element whose prefix matches {@code prefix}. If more than one
     250     * element matches {@code prefix}, returns the first of the matching elements (first according
     251     * to {@link #comparator}). An element that is equal to {@code prefix} is always preferred.
     252     *
     253     * @param prefix The prefix to match.
     254     * @return The best candidate (may be null)
     255     */
     256    public E findBestCandidate(String prefix) {
     257        return elements.stream()
     258            .filter(o -> o.toString().startsWith(prefix))
     259            // an element equal to the prefix is always the best candidate
     260            .min((x, y) -> x.toString().equals(prefix) ? -1 :
     261                           y.toString().equals(prefix) ? 1 :
     262                           comparator.compare(x, y))
     263            .orElse(null);
     264    }
     265
     266    /**
     267     * Gets a preference loader and saver.
     268     *
     269     * @param readE A {@link Function} that builds an {@code <E>} from a {@link String}.
     270     * @param writeE A {@code Function} that serializes an {@code <E>} to a {@code String}
     271     * @return The {@link Preferences} instance.
     272     */
     273    public Preferences prefs(Function<String, E> readE, Function<E, String> writeE) {
     274        return new Preferences(readE, writeE);
     275    }
     276
     277    /**
     278     * Loads and saves the model to the JOSM preferences.
     279     * <p>
     280     * Obtainable through {@link #prefs}.
     281     */
     282    public final class Preferences {
     283
     284        /** A {@link Function} that builds an {@code <E>} from a {@code String}. */
     285        private Function<String, E> readE;
     286        /** A {@code Function} that serializes {@code <E>} to a {@code String}. */
     287        private Function<E, String> writeE;
     288
     289        /**
     290         * Private constructor
     291         *
     292         * @param readE A {@link Function} that builds an {@code <E>} from a {@code String}.
     293         * @param writeE A {@code Function} that serializes an {@code <E>} to a {@code String}
     294         */
     295        private Preferences(Function<String, E> readE, Function<E, String> writeE) {
     296            this.readE = readE;
     297            this.writeE = writeE;
     298        }
     299
     300        /**
     301         * Loads the model from the JOSM preferences.
     302         * @param key The preferences key
     303         */
     304        public void load(String key) {
     305            removeAllElements();
     306            addAllElements(Config.getPref().getList(key), readE);
     307        }
     308
     309        /**
     310         * Loads the model from the JOSM preferences.
     311         *
     312         * @param key The preferences key
     313         * @param defaults A list of default values.
     314         */
     315        public void load(String key, List<String> defaults) {
     316            removeAllElements();
     317            addAllElements(Config.getPref().getList(key, defaults), readE);
     318        }
     319
     320        /**
     321         * Loads the model from the JOSM preferences.
     322         *
     323         * @param prop The property holding the strings.
     324         */
     325        public void load(ListProperty prop) {
     326            removeAllElements();
     327            addAllElements(prop.get(), readE);
     328        }
     329
     330        /**
     331         * Returns the model elements as list of strings.
     332         *
     333         * @return a list of strings
     334         */
     335        public List<String> asStringList() {
     336            List<String> list = new ArrayList<>(getSize());
     337            forEach(element -> list.add(writeE.apply(element)));
     338            return list;
     339        }
     340
     341        /**
     342         * Saves the model to the JOSM preferences.
     343         *
     344        * @param key The preferences key
     345        */
     346        public void save(String key) {
     347            Config.getPref().putList(key, asStringList());
     348        }
     349
     350        /**
     351         * Saves the model to the JOSM preferences.
     352         *
     353         * @param prop The property to write to.
     354         */
     355        public void save(ListProperty prop) {
     356            prop.put(asStringList());
     357        }
     358    }
     359}
  • src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletingComboBox.java

    Property changes on: src/org/openstreetmap/josm/gui/tagging/ac/AutoCompComboBoxModel.java
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
    11// License: GPL. For details, see LICENSE file.
    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}
  • src/org/openstreetmap/josm/gui/widgets/ComboBoxHistory.java

     
    1 // License: GPL. For details, see LICENSE file.
    2 package org.openstreetmap.josm.gui.widgets;
    3 
    4 import java.util.ArrayList;
    5 import java.util.Iterator;
    6 import java.util.List;
    7 import java.util.NoSuchElementException;
    8 
    9 import javax.swing.DefaultComboBoxModel;
    10 
    11 import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem;
    12 
    13 /**
    14  * A data model for {@link HistoryComboBox}
    15  */
    16 class ComboBoxHistory extends DefaultComboBoxModel<AutoCompletionItem> implements Iterable<AutoCompletionItem> {
    17 
    18     private final int maxSize;
    19 
    20     /**
    21      * Constructs a {@code ComboBoxHistory} keeping track of {@code maxSize} items
    22      * @param size the history size
    23      */
    24     ComboBoxHistory(int size) {
    25         maxSize = size;
    26     }
    27 
    28     /**
    29      * Adds or moves an element to the top of the history
    30      * @param s the element to add
    31      */
    32     public void addElement(String s) {
    33         addElement(new AutoCompletionItem(s));
    34     }
    35 
    36     /**
    37      * Adds or moves an element to the top of the history
    38      * @param o the element to add
    39      */
    40     @Override
    41     public void addElement(AutoCompletionItem o) {
    42         String newEntry = o.getValue();
    43 
    44         boolean alreadyAdded = false;
    45         // if history contains this object already, delete it,
    46         // so that it looks like a move to the top
    47         for (int i = 0; i < getSize(); i++) {
    48             String oldEntry = getElementAt(i).getValue();
    49             if (oldEntry.equals(newEntry)) {
    50                 if (i == 0) {
    51                     alreadyAdded = true;
    52                     break;
    53                 } else {
    54                     removeElementAt(i);
    55                 }
    56             }
    57         }
    58 
    59         if (!alreadyAdded) {
    60             // insert element at the top
    61             insertElementAt(o, 0);
    62         }
    63 
    64         // remove an element, if the history gets too large
    65         if (getSize() > maxSize) {
    66             removeElementAt(getSize()-1);
    67         }
    68 
    69         // set selected item to the one just added
    70         setSelectedItem(o);
    71     }
    72 
    73     @Override
    74     public Iterator<AutoCompletionItem> iterator() {
    75         return new Iterator<AutoCompletionItem>() {
    76 
    77             private int position = -1;
    78 
    79             @Override
    80             public void remove() {
    81                 removeElementAt(position);
    82             }
    83 
    84             @Override
    85             public boolean hasNext() {
    86                 return position < getSize()-1 && getSize() > 0;
    87             }
    88 
    89             @Override
    90             public AutoCompletionItem next() {
    91                 if (!hasNext())
    92                     throw new NoSuchElementException();
    93                 position++;
    94                 return getElementAt(position);
    95             }
    96         };
    97     }
    98 
    99     /**
    100      * {@link javax.swing.DefaultComboBoxModel#removeAllElements() Removes all items}
    101      * and {@link ComboBoxHistory#addElement(String) adds} the given items.
    102      * @param items the items to set
    103      */
    104     public void setItemsAsString(List<String> items) {
    105         removeAllElements();
    106         for (int i = items.size()-1; i >= 0; i--) {
    107             addElement(items.get(i));
    108         }
    109     }
    110 
    111     /**
    112      * Returns the {@link AutoCompletionItem} items as strings
    113      * @return a list of strings
    114      */
    115     public List<String> asStringList() {
    116         List<String> list = new ArrayList<>(maxSize);
    117         for (AutoCompletionItem item : this) {
    118             list.add(item.getValue());
    119         }
    120         return list;
    121     }
    122 }
  • src/org/openstreetmap/josm/gui/widgets/HistoryComboBox.java

    Property changes on: src/org/openstreetmap/josm/gui/widgets/ComboBoxHistory.java
    ___________________________________________________________________
    Deleted: svn:eol-style
    ## -1 +0,0 ##
    -native
    \ No newline at end of property
     
    33
    44import java.util.List;
    55
    6 import javax.swing.text.JTextComponent;
     6import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBox;
    77
    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;
    11 
    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;
     13public class HistoryComboBox extends AutoCompComboBox<String> {
    1714
    1815    /**
    19      * The default size of the search history.
    20      */
    21     public static final int DEFAULT_SEARCH_HISTORY_SIZE = 15;
    22 
    23     /**
    2416     * Constructs a new {@code HistoryComboBox}.
    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");
    3121    }
    3222
    33     /**
    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();
     23    @Override
     24    public HistoryComboBoxModel getModel() {
     25        return (HistoryComboBoxModel) dataModel;
    4026    }
    4127
    4228    /**
    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)
     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.
    4631     */
    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)
    56      */
    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         }
     33        String newItem = getModel().addTopElement(getEditor().getItem().toString());
     34        getModel().setSelectedItem(newItem);
    6535    }
    6636
    6737    /**
    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);
    74     }
    75 
    76     /**
    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}
  • src/org/openstreetmap/josm/gui/widgets/HistoryComboBoxModel.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.widgets;
     3
     4import java.util.ArrayList;
     5import java.util.List;
     6
     7import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel;
     8import org.openstreetmap.josm.spi.preferences.Config;
     9
     10/**
     11 * A data model for the {@link HistoryComboBox}.
     12 * <p>
     13 * This model is an {@link AutoCompComboBoxModel} specialized in {@code String}s. It offers
     14 * convenience functions to serialize to and from the JOSM preferences.
     15 *
     16 * @since xxx
     17 */
     18public class HistoryComboBoxModel extends AutoCompComboBoxModel<String> {
     19
     20    HistoryComboBoxModel() {
     21        // The user's preference for max. number of items in histories.
     22        setSize(Config.getPref().getInt("search.history-size", 15));
     23    }
     24
     25    /**
     26     * Adds strings to the model.
     27     * <p>
     28     * Strings are added only until the max. history size is reached.
     29     *
     30     * @param strings the strings to add
     31     */
     32    public void addAllStrings(List<String> strings) {
     33        strings.forEach(s -> addElement(s));
     34    }
     35
     36    /**
     37     * Gets all items in the history as a list of strings.
     38     *
     39     * @return the items in the history
     40     */
     41    public List<String> asStringList() {
     42        List<String> list = new ArrayList<>(getSize());
     43        this.forEach(item -> list.add(item));
     44        return list;
     45    }
     46
     47    /**
     48     * Gets a preference loader and saver for this model.
     49     *
     50     * @return the instance
     51     */
     52    public Preferences prefs() {
     53        return prefs(x -> x, x -> x);
     54    }
     55}
  • src/org/openstreetmap/josm/gui/widgets/JosmComboBox.java

    Property changes on: src/org/openstreetmap/josm/gui/widgets/HistoryComboBoxModel.java
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
    109109    }
    110110
    111111    /**
     112     * Returns the text in the combobox editor.
     113     * @return the text
     114     * @see JTextComponent#getText
     115     * @since xxx
     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 xxx
     126     */
     127    public void setText(String value) {
     128        getEditorComponent().setText(value);
     129    }
     130
     131    /**
    112132     * Finds the prototype display value to use among the given possible candidates.
    113133     * @param possibleValues The possible candidates that will be iterated.
    114134     * @return The value that needs the largest display height on screen.
     
    163183                .orElse(null);
    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.
    175195            // This assumes that items have the same height as the combo box, which is not granted by the look and feel
     
    188208            }
    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) {
    193221            addPropertyChangeListener("editable", handler);
  • test/unit/org/openstreetmap/josm/gui/io/BasicUploadSettingsPanelTest.java

     
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.gui.io;
    33
     4import static org.junit.jupiter.api.Assertions.assertEquals;
    45import static org.junit.jupiter.api.Assertions.assertNotNull;
     6import static org.junit.jupiter.api.Assertions.assertNull;
    57
     8import java.util.Arrays;
     9import java.util.List;
     10
     11import org.openstreetmap.josm.spi.preferences.Config;
     12
    613import org.openstreetmap.josm.testutils.annotations.BasicPreferences;
    714
    815import org.junit.jupiter.api.Test;
     
    1724     */
    1825    @Test
    1926    void testBasicUploadSettingsPanel() {
    20         assertNotNull(new BasicUploadSettingsPanel(new ChangesetCommentModel(), new ChangesetCommentModel(), new ChangesetReviewModel()));
     27        assertNotNull(new BasicUploadSettingsPanel(new UploadDialogModel()));
    2128    }
     29
     30    private static void doTestGetLastChangesetTagFromHistory(String historyKey, List<String> def) {
     31        Config.getPref().putList(historyKey, null);
     32        Config.getPref().putInt(BasicUploadSettingsPanel.COMMENT_LAST_USED_KEY, 0);
     33        Config.getPref().putInt(BasicUploadSettingsPanel.COMMENT_MAX_AGE_KEY, 30);
     34        assertNull(BasicUploadSettingsPanel.getLastChangesetTagFromHistory(historyKey, def));          // age NOK (history empty)
     35
     36        Config.getPref().putList(historyKey, Arrays.asList("foo", "bar"));
     37        assertNull(BasicUploadSettingsPanel.getLastChangesetTagFromHistory(historyKey, def));          // age NOK (history not empty)
     38
     39        Config.getPref().putLong(BasicUploadSettingsPanel.COMMENT_LAST_USED_KEY, System.currentTimeMillis() / 1000);
     40        assertEquals("foo", BasicUploadSettingsPanel.getLastChangesetTagFromHistory(historyKey, def)); // age OK, history not empty
     41
     42        Config.getPref().putList(historyKey, null);
     43        assertEquals(def.get(0), BasicUploadSettingsPanel.getLastChangesetTagFromHistory(historyKey, def));   // age OK, history empty
     44    }
     45
     46    /**
     47     * Test of {@link BasicUploadSettingsPanel#getLastChangesetTagFromHistory} method.
     48     */
     49    @Test
     50    void testGetLastChangesetCommentFromHistory() {
     51        doTestGetLastChangesetTagFromHistory(
     52                BasicUploadSettingsPanel.COMMENT_HISTORY_KEY,
     53                Arrays.asList("baz", "quux"));
     54    }
     55
     56    /**
     57     * Test of {@link BasicUploadSettingsPanel#getLastChangesetTagFromHistory} method.
     58     */
     59    @Test
     60    void testGetLastChangesetSourceFromHistory() {
     61        doTestGetLastChangesetTagFromHistory(
     62                BasicUploadSettingsPanel.SOURCE_HISTORY_KEY,
     63                BasicUploadSettingsPanel.getDefaultSources());
     64    }
    2265}
  • test/unit/org/openstreetmap/josm/gui/io/ChangesetCommentModelTest.java

     
    1 // License: GPL. For details, see LICENSE file.
    2 package org.openstreetmap.josm.gui.io;
    3 
    4 import static org.junit.jupiter.api.Assertions.assertEquals;
    5 
    6 import java.util.Arrays;
    7 import java.util.Collections;
    8 
    9 import org.junit.jupiter.api.extension.RegisterExtension;
    10 import org.junit.jupiter.api.Test;
    11 import org.openstreetmap.josm.testutils.JOSMTestRules;
    12 
    13 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
    14 
    15 /**
    16  * Unit tests of {@link ChangesetCommentModel} class.
    17  */
    18 class ChangesetCommentModelTest {
    19 
    20     /**
    21      * Setup tests
    22      */
    23     @RegisterExtension
    24     @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
    25     public JOSMTestRules test = new JOSMTestRules();
    26 
    27     /**
    28      * Test of {@link ChangesetCommentModel#findHashTags}.
    29      */
    30     @Test
    31     void testFindHashTags() {
    32         ChangesetCommentModel model = new ChangesetCommentModel();
    33         assertEquals(Collections.emptyList(), model.findHashTags());
    34         model.setComment(" ");
    35         assertEquals(Collections.emptyList(), model.findHashTags());
    36         model.setComment(" #");
    37         assertEquals(Collections.emptyList(), model.findHashTags());
    38         model.setComment(" # ");
    39         assertEquals(Collections.emptyList(), model.findHashTags());
    40         model.setComment(" https://example.com/#map ");
    41         assertEquals(Collections.emptyList(), model.findHashTags());
    42         model.setComment("#59606086");
    43         assertEquals(Collections.emptyList(), model.findHashTags());
    44         model.setComment(" #foo ");
    45         assertEquals(Arrays.asList("#foo"), model.findHashTags());
    46         model.setComment(" #foo #bar baz");
    47         assertEquals(Arrays.asList("#foo", "#bar"), model.findHashTags());
    48         model.setComment(" #foo, #bar, baz");
    49         assertEquals(Arrays.asList("#foo", "#bar"), model.findHashTags());
    50         model.setComment(" #foo; #bar; baz");
    51         assertEquals(Arrays.asList("#foo", "#bar"), model.findHashTags());
    52         model.setComment("#hotosm-project-4773 #DRONEBIRD #OsakaQuake2018 #AOYAMAVISION");
    53         assertEquals(Arrays.asList("#hotosm-project-4773", "#DRONEBIRD", "#OsakaQuake2018", "#AOYAMAVISION"), model.findHashTags());
    54     }
    55 }
  • test/unit/org/openstreetmap/josm/gui/io/ChangesetManagementPanelTest.java

    Property changes on: test/unit/org/openstreetmap/josm/gui/io/ChangesetCommentModelTest.java
    ___________________________________________________________________
    Deleted: svn:eol-style
    ## -1 +0,0 ##
    -native
    \ No newline at end of property
     
    44import static org.junit.jupiter.api.Assertions.assertNotNull;
    55
    66import org.junit.jupiter.api.Test;
     7
    78import org.openstreetmap.josm.testutils.annotations.BasicPreferences;
    89
    910/**
     
    1617     */
    1718    @Test
    1819    void testChangesetManagementPanel() {
    19         assertNotNull(new ChangesetManagementPanel(new ChangesetCommentModel()));
     20        assertNotNull(new ChangesetManagementPanel(new UploadDialogModel()));
    2021    }
    2122}
  • test/unit/org/openstreetmap/josm/gui/io/TagSettingsPanelTest.java

     
    1 // License: GPL. For details, see LICENSE file.
    2 package org.openstreetmap.josm.gui.io;
    3 
    4 import static org.junit.jupiter.api.Assertions.assertNotNull;
    5 
    6 import org.junit.jupiter.api.extension.RegisterExtension;
    7 import org.junit.jupiter.api.Test;
    8 import org.openstreetmap.josm.testutils.JOSMTestRules;
    9 
    10 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
    11 
    12 /**
    13  * Unit tests of {@link TagSettingsPanel} class.
    14  */
    15 class TagSettingsPanelTest {
    16 
    17     /**
    18      * Setup tests
    19      */
    20     @RegisterExtension
    21     @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
    22     public JOSMTestRules test = new JOSMTestRules().preferences();
    23 
    24     /**
    25      * Test of {@link TagSettingsPanel#TagSettingsPanel}.
    26      */
    27     @Test
    28     void testTagSettingsPanel() {
    29         assertNotNull(new TagSettingsPanel(new ChangesetCommentModel(), new ChangesetCommentModel(), new ChangesetReviewModel()));
    30     }
    31 }
  • test/unit/org/openstreetmap/josm/gui/io/UploadDialogModelTest.java

    Property changes on: test/unit/org/openstreetmap/josm/gui/io/TagSettingsPanelTest.java
    ___________________________________________________________________
    Deleted: svn:eol-style
    ## -1 +0,0 ##
    -native
    \ No newline at end of property
     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.io;
     3
     4import static org.junit.jupiter.api.Assertions.assertNotNull;
     5import static org.junit.jupiter.api.Assertions.assertNull;
     6import static org.junit.jupiter.api.Assertions.assertEquals;
     7
     8import org.junit.jupiter.api.extension.RegisterExtension;
     9import org.junit.jupiter.api.Test;
     10
     11import org.openstreetmap.josm.data.osm.DataSet;
     12import org.openstreetmap.josm.testutils.JOSMTestRules;
     13
     14/**
     15 * Unit tests of {@link UploadDialogModel} class.
     16 */
     17
     18public class UploadDialogModelTest {
     19    /**
     20     * Setup tests
     21     */
     22    @RegisterExtension
     23    public JOSMTestRules test = new JOSMTestRules().preferences().main();
     24
     25    /**
     26     * Test of {@link UploadDialogModel}.
     27     */
     28    @Test
     29    void testUploadDialogModel() {
     30        assertNotNull(new UploadDialogModel());
     31    }
     32
     33    @Test
     34    void testFindHashTags() {
     35        UploadDialogModel model = new UploadDialogModel();
     36
     37        assertNull(model.findHashTags(" "));
     38        assertNull(model.findHashTags(" #"));
     39        assertNull(model.findHashTags(" # "));
     40        assertNull(model.findHashTags(" https://example.com/#map "));
     41        assertNull(model.findHashTags("#59606086"));
     42        assertEquals("#foo", model.findHashTags(" #foo "));
     43        assertEquals("#foo;#bar", model.findHashTags(" #foo #bar baz"));
     44        assertEquals("#foo;#bar", model.findHashTags(" #foo, #bar, baz"));
     45        assertEquals("#foo;#bar", model.findHashTags(" #foo; #bar; baz"));
     46        assertEquals("#hotosm-project-4773;#DRONEBIRD;#OsakaQuake2018;#AOYAMAVISION",
     47            model.findHashTags("#hotosm-project-4773 #DRONEBIRD #OsakaQuake2018 #AOYAMAVISION"));
     48    }
     49
     50    @Test
     51    void testCommentWithHashtags() {
     52        UploadDialogModel model = new UploadDialogModel();
     53        model.add("comment", "comment with a #hashtag");
     54        assertEquals("#hashtag", model.getValue("hashtags"));
     55    }
     56
     57    @Test
     58    void testGetCommentWithDataSetHashTag() {
     59        assertEquals("", UploadDialogModel.addHashTagsFromDataSet(null, null));
     60        DataSet ds = new DataSet();
     61        assertEquals("foo", UploadDialogModel.addHashTagsFromDataSet("foo", ds));
     62        ds.getChangeSetTags().put("hashtags", "bar");
     63        assertEquals("foo #bar", UploadDialogModel.addHashTagsFromDataSet("foo", ds));
     64        ds.getChangeSetTags().put("hashtags", "bar;baz;#bar");
     65        assertEquals("foo #bar #baz", UploadDialogModel.addHashTagsFromDataSet("foo", ds));
     66    }
     67
     68}
  • test/unit/org/openstreetmap/josm/gui/io/UploadDialogTest.java

    Property changes on: test/unit/org/openstreetmap/josm/gui/io/UploadDialogModelTest.java
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
    1212import java.util.List;
    1313import java.util.Map;
    1414import java.util.concurrent.ConcurrentHashMap;
    15 import java.util.function.Supplier;
    1615
    1716import javax.swing.JOptionPane;
    1817
    1918import org.junit.jupiter.api.Test;
    2019import org.openstreetmap.josm.TestUtils;
    21 import org.openstreetmap.josm.data.osm.DataSet;
    2220import org.openstreetmap.josm.gui.io.UploadDialog.UploadAction;
    2321import org.openstreetmap.josm.io.UploadStrategySpecification;
    2422import org.openstreetmap.josm.spi.preferences.Config;
     
    8179        public Map<String, String> getTags(boolean keepEmpty) {
    8280            return new ConcurrentHashMap<>();
    8381        }
    84 
    85         @Override
    86         public void forceUpdateActiveField() {
    87             // Do nothing
    88         }
    8982    }
    9083
    9184    /**
     
    115108        assertTrue(UploadDialog.UploadAction.isUploadCommentTooShort("\u0860"));
    116109    }
    117110
    118     private static void doTestGetLastChangesetTagFromHistory(String historyKey, Supplier<String> methodToTest, String def) {
    119         Config.getPref().putList(historyKey, null);
    120         Config.getPref().putInt(BasicUploadSettingsPanel.HISTORY_LAST_USED_KEY, 0);
    121         Config.getPref().putInt(BasicUploadSettingsPanel.HISTORY_MAX_AGE_KEY, 30);
    122         assertNull(methodToTest.get());          // age NOK (history empty)
    123         Config.getPref().putList(historyKey, Arrays.asList("foo"));
    124         assertNull(methodToTest.get());          // age NOK (history not empty)
    125         Config.getPref().putLong(BasicUploadSettingsPanel.HISTORY_LAST_USED_KEY, System.currentTimeMillis() / 1000);
    126         assertEquals("foo", methodToTest.get()); // age OK, history not empty
    127         Config.getPref().putList(historyKey, null);
    128         assertEquals(def, methodToTest.get());   // age OK, history empty
    129     }
    130 
    131     /**
    132      * Test of {@link UploadDialog#getLastChangesetCommentFromHistory} method.
    133      */
    134     @Test
    135     void testGetLastChangesetCommentFromHistory() {
    136         doTestGetLastChangesetTagFromHistory(
    137                 BasicUploadSettingsPanel.HISTORY_KEY,
    138                 UploadDialog::getLastChangesetCommentFromHistory,
    139                 null);
    140     }
    141 
    142     /**
    143      * Test of {@link UploadDialog#getLastChangesetSourceFromHistory} method.
    144      */
    145     @Test
    146     void testGetLastChangesetSourceFromHistory() {
    147         doTestGetLastChangesetTagFromHistory(
    148                 BasicUploadSettingsPanel.SOURCE_HISTORY_KEY,
    149                 UploadDialog::getLastChangesetSourceFromHistory,
    150                 BasicUploadSettingsPanel.getDefaultSources().get(0));
    151     }
    152 
    153111    private static void doTestValidateUploadTag(String prefix) {
    154112        List<String> def = Collections.emptyList();
    155113        Config.getPref().putList(prefix + ".mandatory-terms", null);
     
    185143        doTestValidateUploadTag("upload.comment");
    186144        doTestValidateUploadTag("upload.source");
    187145    }
    188 
    189     @Test
    190     void testGetCommentWithDataSetHashTag() {
    191         assertEquals("", UploadDialog.getCommentWithDataSetHashTag(null, null));
    192         DataSet ds = new DataSet();
    193         assertEquals("foo", UploadDialog.getCommentWithDataSetHashTag("foo", ds));
    194         ds.getChangeSetTags().put("hashtags", "bar");
    195         assertEquals("foo #bar", UploadDialog.getCommentWithDataSetHashTag("foo", ds));
    196         ds.getChangeSetTags().put("hashtags", "bar;baz;#bar");
    197         assertEquals("foo #bar #baz", UploadDialog.getCommentWithDataSetHashTag("foo", ds));
    198     }
    199146}
  • test/unit/org/openstreetmap/josm/gui/tagging/ac/AutoCompComboBoxModelTest.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.tagging.ac;
     3
     4import static org.junit.jupiter.api.Assertions.assertEquals;
     5import static org.junit.jupiter.api.Assertions.assertNotNull;
     6import static org.junit.jupiter.api.Assertions.assertNull;
     7import static org.junit.jupiter.api.Assertions.assertSame;
     8
     9import java.util.Comparator;
     10import java.util.LinkedHashMap;
     11import java.util.Map;
     12
     13import org.openstreetmap.josm.data.osm.search.SearchSetting;
     14import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem;
     15import org.openstreetmap.josm.data.tagging.ac.AutoCompletionPriority;
     16
     17import org.junit.jupiter.api.Test;
     18import org.openstreetmap.josm.testutils.annotations.FullPreferences;
     19
     20/**
     21 * Test class for {@link AutoCompComboBoxModel}
     22 */
     23@FullPreferences
     24class AutoCompComboBoxModelTest {
     25
     26    class TestData {
     27        public String s;
     28        public AutoCompletionItem ac;
     29        public SearchSetting ss;
     30
     31        TestData(String s, AutoCompletionPriority p) {
     32            this.s = s;
     33            this.ss = new SearchSetting();
     34            ss.text = s;
     35            this.ac = new AutoCompletionItem(s, p);
     36        }
     37    }
     38
     39    // CHECKSTYLE.OFF: SingleSpaceSeparator
     40    // CHECKSTYLE.OFF: ParenPad
     41
     42    Map<String, TestData> testData = new LinkedHashMap<String, TestData>() {{
     43        put("a1",   new TestData("a1",   AutoCompletionPriority.UNKNOWN));
     44        put("a2",   new TestData("a2",   AutoCompletionPriority.IS_IN_STANDARD));
     45        put("a3",   new TestData("a3",   AutoCompletionPriority.IS_IN_DATASET));
     46        put("a4",   new TestData("a4",   AutoCompletionPriority.IS_IN_STANDARD_AND_IN_DATASET));
     47        put("b",    new TestData("b",    AutoCompletionPriority.UNKNOWN));
     48        put("bcde", new TestData("bcde", AutoCompletionPriority.UNKNOWN));
     49        put("bde",  new TestData("bde",  AutoCompletionPriority.UNKNOWN));
     50        put("bdef", new TestData("bdef", AutoCompletionPriority.IS_IN_STANDARD_AND_IN_DATASET));
     51    }};
     52
     53    @Test
     54    void testAutoCompModel() {
     55        assertNotNull(new AutoCompComboBoxModel<String>());
     56        assertNotNull(new AutoCompComboBoxModel<SearchSetting>());
     57        assertNotNull(new AutoCompComboBoxModel<AutoCompletionItem>());
     58    }
     59
     60    @Test
     61    void testAutoCompModelFindString() {
     62        AutoCompComboBoxModel<String> model = new AutoCompComboBoxModel<>();
     63        testData.forEach((k, v) -> model.addElement(v.s));
     64
     65        assertNull(model.findBestCandidate("bb"));
     66        assertEquals("a1",   model.findBestCandidate("a" ));
     67        assertEquals("b",    model.findBestCandidate("b" ));
     68        assertEquals("bcde", model.findBestCandidate("bc"));
     69        assertEquals("bde",  model.findBestCandidate("bd"));
     70    }
     71
     72    @Test
     73    void testAutoCompModelFindSearchSetting() {
     74        AutoCompComboBoxModel<SearchSetting> model = new AutoCompComboBoxModel<>();
     75        // Use the default Comparator (that compares on toString).
     76        testData.forEach((k, v) -> model.addElement(v.ss));
     77
     78        assertNull(model.findBestCandidate("bb"));
     79        // test for sameness (aka ==).  Some objects are expensive to copy, so we want to be able to
     80        // round-trip an object thru the AutoCompComboBox without copying it.
     81        assertSame(testData.get("a1"  ).ss, model.findBestCandidate("a" ));
     82        assertSame(testData.get("b"   ).ss, model.findBestCandidate("b" ));
     83        assertSame(testData.get("bcde").ss, model.findBestCandidate("bc"));
     84        assertSame(testData.get("bde" ).ss, model.findBestCandidate("bd"));
     85    }
     86
     87    @Test
     88    void testAutoCompModelFindAutoCompletionItem() {
     89        AutoCompComboBoxModel<AutoCompletionItem> model = new AutoCompComboBoxModel<>();
     90        // AutoCompletionItem implements Comparable. Build a Comparator from Comparable.
     91        model.setComparator(Comparator.naturalOrder());
     92        testData.forEach((k, v) -> model.addElement(v.ac));
     93
     94        assertNull(model.findBestCandidate("bb"));
     95        assertSame(testData.get("a4"  ).ac, model.findBestCandidate("a" )); // higher prio than "a1"
     96        assertSame(testData.get("b"   ).ac, model.findBestCandidate("b" ));
     97        assertSame(testData.get("bcde").ac, model.findBestCandidate("bc"));
     98        assertSame(testData.get("bdef").ac, model.findBestCandidate("bd")); // higher prio than "bde"
     99    }
     100}
  • test/unit/org/openstreetmap/josm/gui/tagging/ac/AutoCompComboBoxTest.java

    Property changes on: test/unit/org/openstreetmap/josm/gui/tagging/ac/AutoCompComboBoxModelTest.java
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.tagging.ac;
     3
     4import static org.junit.jupiter.api.Assertions.assertNotNull;
     5
     6import org.junit.jupiter.api.Test;
     7import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem;
     8import org.openstreetmap.josm.testutils.annotations.FullPreferences;
     9
     10/**
     11 * Test class for {@link AutoCompletingComboBox}
     12 */
     13@FullPreferences
     14class AutoCompComboBoxTest {
     15
     16    @Test
     17    void testAutoCompletingComboBox() {
     18        assertNotNull(new AutoCompComboBox<String>());
     19        assertNotNull(new AutoCompComboBox<AutoCompletionItem>());
     20    }
     21}