Changeset 18173 in josm
- Timestamp:
- 2021-08-24T02:43:50+02:00 (3 years ago)
- Location:
- trunk
- Files:
-
- 7 added
- 6 deleted
- 26 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/actions/OpenLocationAction.java
r17335 r18173 44 44 import org.openstreetmap.josm.gui.util.WindowGeometry; 45 45 import org.openstreetmap.josm.gui.widgets.HistoryComboBox; 46 import org.openstreetmap.josm.spi.preferences.Config;47 46 import org.openstreetmap.josm.tools.GBC; 48 47 import org.openstreetmap.josm.tools.Logging; … … 114 113 */ 115 114 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"); 118 116 } 119 117 … … 124 122 protected void remindUploadAddressHistory(HistoryComboBox cbHistory) { 125 123 cbHistory.addCurrentItemToHistory(); 126 Config.getPref().putList(getClass().getName() + ".uploadAddressHistory", cbHistory.getHistory());124 cbHistory.getModel().prefs().save(getClass().getName() + ".uploadAddressHistory"); 127 125 } 128 126 -
trunk/src/org/openstreetmap/josm/actions/SearchNotesDownloadAction.java
r17062 r18173 8 8 import java.awt.event.ActionEvent; 9 9 import java.awt.event.KeyEvent; 10 import java.util.Collections;11 10 import java.util.Optional; 12 11 … … 45 44 public void actionPerformed(ActionEvent e) { 46 45 HistoryComboBox searchTermBox = new HistoryComboBox(); 47 searchTermBox. setPossibleItemsTopDown(Config.getPref().getList(HISTORY_KEY, Collections.emptyList()));46 searchTermBox.getModel().prefs().load(HISTORY_KEY); 48 47 49 48 JPanel contentPanel = new JPanel(new GridBagLayout()); … … 73 72 74 73 searchTermBox.addCurrentItemToHistory(); 75 Config.getPref().putList(HISTORY_KEY, searchTermBox.getHistory());74 searchTermBox.getModel().prefs().save(HISTORY_KEY); 76 75 77 76 performSearch(searchTerm); -
trunk/src/org/openstreetmap/josm/actions/UploadAction.java
r16824 r18173 240 240 241 241 final UploadDialog dialog = UploadDialog.getUploadDialog(); 242 dialog. setChangesetTags(layer.getDataSet());242 dialog.initLifeCycle(layer.getDataSet()); 243 243 dialog.setUploadedPrimitives(apiData); 244 244 dialog.setVisible(true); -
trunk/src/org/openstreetmap/josm/actions/search/SearchAction.java
r17188 r18173 14 14 import java.util.Collections; 15 15 import java.util.HashSet; 16 import java.util.LinkedList;17 16 import java.util.List; 18 17 import java.util.Map; 19 18 import java.util.function.Predicate; 20 import java.util.stream.Collectors;21 19 22 20 import javax.swing.JOptionPane; … … 43 41 import org.openstreetmap.josm.gui.preferences.ToolbarPreferences.ActionParser; 44 42 import org.openstreetmap.josm.gui.progress.ProgressMonitor; 43 import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel; 45 44 import org.openstreetmap.josm.spi.preferences.Config; 46 45 import org.openstreetmap.josm.tools.Logging; … … 67 66 private static final String SEARCH_EXPRESSION = "searchExpression"; 68 67 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 70 74 static { 71 75 SearchCompiler.addMatchFactory(new SimpleMatchFactory() { … … 87 91 } 88 92 }); 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)); 96 94 } 97 95 98 96 /** 99 97 * Gets the search history 100 * @return The last searched terms. Do not modify it.98 * @return The last searched terms. 101 99 */ 102 100 public static Collection<SearchSetting> getSearchHistory() { 103 return searchHistory;101 return model.asCollection(); 104 102 } 105 103 … … 109 107 */ 110 108 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"); 127 111 } 128 112 … … 132 116 */ 133 117 public static List<String> getSearchExpressionHistory() { 134 return getSearchHistory().stream() 135 .map(ss -> ss.text) 136 .collect(Collectors.toList()); 118 return prefs.asStringList(); 137 119 } 138 120 … … 176 158 177 159 SearchDialog dialog = new SearchDialog( 178 initialValues, getSearchExpressionHistory(), ExpertToggleAction.isExpert());160 initialValues, model, ExpertToggleAction.isExpert()); 179 161 180 162 if (dialog.showDialog().getValue() != 1) return null; … … 204 186 */ 205 187 public static void search() { 188 prefs.load("search.history"); 206 189 SearchSetting se = showSearchDialog(lastSearch); 207 190 if (se != null) { -
trunk/src/org/openstreetmap/josm/data/osm/search/SearchSetting.java
r13887 r18173 7 7 8 8 import org.openstreetmap.josm.tools.Logging; 9 import org.openstreetmap.josm.tools.Utils; 9 10 10 11 /** … … 50 51 @Override 51 52 public String toString() { 53 return Utils.shortenString(text, 54 org.openstreetmap.josm.actions.search.SearchAction.MAX_LENGTH_SEARCH_EXPRESSION_DISPLAY); 55 } 56 57 /** 58 * A more talkative version of toString. 59 * @return a bit more info than toString 60 * @since 18173 61 */ 62 public String toStringEx() { 52 63 String cs = caseSensitive ? 53 64 /*case sensitive*/ trc("search", "CS") : … … 140 151 141 152 /** 153 * Build a SearchSetting from a plain unformatted string. 154 * <p> 155 * All attributes are defaulted, only the search string is set. This function is used in 156 * {@link org.openstreetmap.josm.gui.download.OverpassQueryWizardDialog}. 157 * 158 * @param s The string 159 * @return The instance 160 * @since 18173 161 */ 162 public static SearchSetting fromString(String s) { 163 if (s.isEmpty()) 164 return null; 165 SearchSetting result = new SearchSetting(); 166 result.text = s; 167 return result; 168 } 169 170 /** 142 171 * Builds a string representation of the {@code SearchSetting} object, 143 172 * see {@link #readFromString(String)} for more details. -
trunk/src/org/openstreetmap/josm/data/tagging/ac/AutoCompletionItem.java
r18125 r18173 95 95 if (this == obj) 96 96 return true; 97 if (obj == null) 98 return false; 99 if (obj instanceof String) 100 return obj.equals(value); 101 if (getClass() != obj.getClass()) 97 if (!(obj instanceof AutoCompletionItem)) 102 98 return false; 103 99 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)) 108 101 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); 115 103 } 116 104 117 105 @Override 118 106 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); 124 111 } 125 112 } -
trunk/src/org/openstreetmap/josm/gui/dialogs/OsmIdSelectionDialog.java
r15011 r18173 179 179 */ 180 180 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"); 183 182 } 184 183 … … 190 189 protected void remindPrimitivesHistory(HistoryComboBox cbHistory) { 191 190 cbHistory.addCurrentItemToHistory(); 192 Config.getPref().putList(getClass().getName() + ".primitivesHistory", cbHistory.getHistory());191 cbHistory.getModel().prefs().save(getClass().getName() + ".primitivesHistory"); 193 192 } 194 193 -
trunk/src/org/openstreetmap/josm/gui/dialogs/SearchDialog.java
r17055 r18173 16 16 import java.awt.event.MouseEvent; 17 17 import java.util.Arrays; 18 import java.util.List;19 18 20 19 import javax.swing.BorderFactory; … … 38 37 import org.openstreetmap.josm.gui.MainApplication; 39 38 import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSException; 39 import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBox; 40 import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel; 40 41 import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset; 41 42 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetSelector; 42 43 import org.openstreetmap.josm.gui.widgets.AbstractTextComponentValidator; 43 import org.openstreetmap.josm.gui.widgets.HistoryComboBox;44 44 import org.openstreetmap.josm.tools.GBC; 45 45 import org.openstreetmap.josm.tools.JosmRuntimeException; … … 55 55 private final SearchSetting searchSettings; 56 56 57 protected final HistoryComboBox hcbSearchString = new HistoryComboBox();57 protected final AutoCompComboBox<SearchSetting> hcbSearchString; 58 58 59 59 private JCheckBox addOnToolbar; … … 72 72 /** 73 73 * Constructs a new {@code SearchDialog}. 74 * @param initialValues initial search settings 75 * @param searchExpressionHistory list of all texts that were recently used in the search 76 * @param expertMode expert mode 74 * @param initialValues initial search settings, eg. when opened for editing from the filter panel 75 * @param model The combobox model. 76 * @param expertMode expert mode. Shows more options and the "search syntax" panel. 77 * @since 18173 (signature) 77 78 */ 78 public SearchDialog(SearchSetting initialValues, List<String> searchExpressionHistory, boolean expertMode) {79 this(initialValues, searchExpressionHistory, new PanelOptions(expertMode, false), MainApplication.getMainFrame(),79 public SearchDialog(SearchSetting initialValues, AutoCompComboBoxModel<SearchSetting> model, boolean expertMode) { 80 this(initialValues, model, new PanelOptions(expertMode, false), MainApplication.getMainFrame(), 80 81 initialValues instanceof Filter ? tr("Filter") : tr("Search"), 81 82 initialValues instanceof Filter ? tr("Submit filter") : tr("Search"), … … 85 86 } 86 87 87 protected SearchDialog(SearchSetting initialValues, List<String> searchExpressionHistory, PanelOptions options,88 protected SearchDialog(SearchSetting initialValues, AutoCompComboBoxModel<SearchSetting> model, PanelOptions options, 88 89 Component mainFrame, String title, String... buttonTexts) { 89 90 super(mainFrame, title, buttonTexts); 91 hcbSearchString = new AutoCompComboBox<>(model); 90 92 this.searchSettings = new SearchSetting(initialValues); 91 setContent(buildPanel( searchExpressionHistory,options));93 setContent(buildPanel(options)); 92 94 } 93 95 … … 101 103 /** 102 104 * Constructs new options which determine which parts of the search dialog will be shown 103 * @param expertMode whether export mode is enabled104 * @param overpassQuery whether the panel shall be adapted for Overpass query105 * @param expertMode Shows more options and the "search syntax" panel. 106 * @param overpassQuery Don't show left panels and right "preset" panel. Show different "hints". 105 107 */ 106 108 public PanelOptions(boolean expertMode, boolean overpassQuery) { … … 110 112 } 111 113 112 private JPanel buildPanel( List<String> searchExpressionHistory,PanelOptions options) {114 private JPanel buildPanel(PanelOptions options) { 113 115 114 116 // prepare the combo box with the search expressions … … 116 118 117 119 String tooltip = tr("Enter the search expression"); 118 hcbSearchString.setText(searchSettings.t ext);120 hcbSearchString.setText(searchSettings.toString()); 119 121 hcbSearchString.setToolTipText(tooltip); 120 121 hcbSearchString.setPossibleItemsTopDown(searchExpressionHistory);122 122 hcbSearchString.setPreferredSize(new Dimension(40, hcbSearchString.getPreferredSize().height)); 123 123 label.setLabelFor(hcbSearchString); … … 307 307 } 308 308 309 private static JPanel buildHintsSection( HistoryComboBoxhcbSearchString, PanelOptions options) {309 private static JPanel buildHintsSection(AutoCompComboBox<SearchSetting> hcbSearchString, PanelOptions options) { 310 310 JPanel hintPanel = new JPanel(new GridBagLayout()); 311 311 hintPanel.setBorder(BorderFactory.createTitledBorder(tr("Hints"))); … … 466 466 private static class SearchKeywordRow extends JPanel { 467 467 468 private final HistoryComboBoxhcb;469 470 SearchKeywordRow( HistoryComboBoxhcb) {468 private final AutoCompComboBox<SearchSetting> hcb; 469 470 SearchKeywordRow(AutoCompComboBox<SearchSetting> hcb) { 471 471 super(new FlowLayout(FlowLayout.LEFT)); 472 472 this.hcb = hcb; -
trunk/src/org/openstreetmap/josm/gui/dialogs/properties/TagEditHelper.java
r17168 r18173 88 88 import org.openstreetmap.josm.gui.MainApplication; 89 89 import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils; 90 import org.openstreetmap.josm.gui.tagging.ac.AutoComp letingComboBox;90 import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBox; 91 91 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager; 92 92 import org.openstreetmap.josm.gui.util.GuiHelper; … … 400 400 401 401 /** 402 * Returns the edited item with whitespaces removed 403 * @param cb the combobox 404 * @return the edited item with whitespaces removed 405 * @since 18173 406 */ 407 public static String getEditItem(AutoCompComboBox<AutoCompletionItem> cb) { 408 return Utils.removeWhiteSpaces(cb.getEditor().getItem().toString()); 409 } 410 411 /** 412 * Returns the selected item or the edited item as string 413 * @param cb the combobox 414 * @return the selected item or the edited item as string 415 * @since 18173 416 */ 417 public static String getSelectedOrEditItem(AutoCompComboBox<AutoCompletionItem> cb) { 418 final Object selectedItem = cb.getSelectedItem(); 419 if (selectedItem != null) 420 return selectedItem.toString(); 421 return getEditItem(cb); 422 } 423 424 /** 402 425 * Warns user about a key being overwritten. 403 426 * @param action The action done by the user. Must state what key is changed … … 476 499 List<AutoCompletionItem> keyList = autocomplete.getTagKeys(DEFAULT_AC_ITEM_COMPARATOR); 477 500 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)); 480 504 keys.setEditable(true); 505 keys.getModel().addAllElements(keyList); 481 506 keys.setSelectedItem(key); 482 507 … … 490 515 final String selection = m.size() != 1 ? tr("<different>") : m.entrySet().iterator().next().getKey(); 491 516 492 values = new AutoCompletingComboBox(selection); 517 values = new AutoCompComboBox<>(); 518 values.getModel().setComparator(Comparator.naturalOrder()); 519 values.setPrototypeDisplayValue(new AutoCompletionItem(selection)); 493 520 values.setRenderer(cellRenderer); 494 495 521 values.setEditable(true); 496 values. setPossibleAcItems(valueList);522 values.getModel().addAllElements(valueList); 497 523 values.setSelectedItem(selection); 498 524 values.getEditor().setItem(selection); 525 499 526 p.add(Box.createVerticalStrut(5), GBC.eol()); 500 527 p.add(new JLabel(tr("Value")), GBC.std()); … … 522 549 @Override 523 550 public void performTagEdit() { 524 String value = values.getEditItem();551 String value = getEditItem(values); 525 552 value = Normalizer.normalize(value, Normalizer.Form.NFC); 526 553 if (value.isEmpty()) { 527 554 value = null; // delete the key 528 555 } 529 String newkey = keys.getEditItem();556 String newkey = getEditItem(keys); 530 557 newkey = Normalizer.normalize(newkey, Normalizer.Form.NFC); 531 558 if (newkey.isEmpty()) { … … 574 601 575 602 protected abstract class AbstractTagsDialog extends ExtendedDialog { 576 protected AutoComp letingComboBoxkeys;577 protected AutoComp letingComboBoxvalues;603 protected AutoCompComboBox<AutoCompletionItem> keys; 604 protected AutoCompComboBox<AutoCompletionItem> values; 578 605 579 606 AbstractTagsDialog(Component parent, String title, String... buttonTexts) { … … 621 648 } 622 649 623 private void selectACComboBoxSavingUnixBuffer(AutoComp letingComboBoxcb) {650 private void selectACComboBoxSavingUnixBuffer(AutoCompComboBox<AutoCompletionItem> cb) { 624 651 // select combobox with saving unix system selection (middle mouse paste) 625 652 Clipboard sysSel = ClipboardUtils.getSystemSelection(); … … 667 694 && IntStream.range(0, size).allMatch(i -> Objects.equals(currentModel.getElementAt(i), correctItems.get(i))); 668 695 if (!valuesOK) { 669 values.setPossibleAcItems(correctItems); 696 values.getModel().removeAllElements(); 697 values.getModel().addAllElements(correctItems); 670 698 } 671 699 if (!Objects.equals(key, objKey)) { … … 688 716 return; 689 717 } 690 buttons.get(0).setIcon(findIcon( keys.getSelectedOrEditItem(), values.getSelectedOrEditItem())718 buttons.get(0).setIcon(findIcon(getSelectedOrEditItem(keys), getSelectedOrEditItem(values)) 691 719 .orElse(ImageProvider.get("ok", ImageProvider.ImageSizes.LARGEICON))); 692 720 } … … 732 760 733 761 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")); 736 768 keys.setAutocompleteEnabled(AUTOCOMPLETE_KEYS.get()); 737 769 values.setAutocompleteEnabled(AUTOCOMPLETE_VALUES.get()); … … 748 780 keyList.removeIf(item -> containsDataKey(item.getValue())); 749 781 750 keys.setPossibleAcItems(keyList); 782 keys.getModel().removeAllElements(); 783 keys.getModel().addAllElements(keyList); 751 784 keys.setEditable(true); 752 785 … … 939 972 @Override 940 973 public void actionPerformed(ActionEvent e) { 941 keys.setSelectedItem(t.getKey() , true);974 keys.setSelectedItem(t.getKey()); 942 975 // fix #7951, #8298 - update list of values before setting value (?) 943 976 focus.focusGained(null); 944 values.setSelectedItem(t.getValue() , true);977 values.setSelectedItem(t.getValue()); 945 978 selectValuesCombobox(); 946 979 } … … 1094 1127 */ 1095 1128 public final void performTagAdding() { 1096 String key = keys.getEditItem();1097 String value = values.getEditItem();1129 String key = getEditItem(keys); 1130 String value = getEditItem(values); 1098 1131 if (key.isEmpty() || value.isEmpty()) 1099 1132 return; -
trunk/src/org/openstreetmap/josm/gui/download/OverpassQueryWizardDialog.java
r17982 r18173 15 15 import org.openstreetmap.josm.gui.dialogs.SearchDialog; 16 16 import org.openstreetmap.josm.gui.download.overpass.OverpassWizardRegistration.OverpassWizardCallbacks; 17 import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel; 17 18 import org.openstreetmap.josm.tools.Logging; 18 19 import org.openstreetmap.josm.tools.SearchCompilerQueryWizard; … … 36 37 private static final int CANCEL = 2; 37 38 39 private AutoCompComboBoxModel<SearchSetting> model; 40 41 /** preferences reader/writer with automatic transmogrification to and from String */ 42 private AutoCompComboBoxModel<SearchSetting>.Preferences prefs; 43 38 44 /** 39 45 * Create a new {@link OverpassQueryWizardDialog} … … 41 47 */ 42 48 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(), 44 50 tr("Overpass Query Wizard"), 45 51 tr("Build query"), tr("Build query and execute"), tr("Cancel")); 46 52 this.callbacks = callbacks; 53 model = hcbSearchString.getModel(); 47 54 setButtonIcons("dialogs/magic-wand", "download-overpass", "cancel"); 48 55 setCancelButton(CANCEL + 1); 49 56 setDefaultButton(BUILD_AN_EXECUTE_QUERY + 1); 57 prefs = model.prefs(SearchSetting::fromString, SearchSetting::toString); 58 prefs.load(OVERPASS_WIZARD_HISTORY); 50 59 } 51 60 … … 76 85 */ 77 86 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); 80 89 } 81 90 -
trunk/src/org/openstreetmap/josm/gui/download/PlaceSelection.java
r16960 r18173 117 117 cbSearchExpression = new HistoryComboBox(); 118 118 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); 120 120 lpanel.add(cbSearchExpression, GBC.std(1, 1).fill(GBC.HORIZONTAL)); 121 121 … … 195 195 return; 196 196 cbSearchExpression.addCurrentItemToHistory(); 197 Config.getPref().putList(HISTORY_KEY, cbSearchExpression.getHistory());197 cbSearchExpression.getModel().prefs().save(HISTORY_KEY); 198 198 Server server = (Server) serverComboBox.getSelectedItem(); 199 199 URL url = server.urlFunction.apply(searchExpression, isSearchMore ? model.getData() : Collections.emptyList()); -
trunk/src/org/openstreetmap/josm/gui/io/BasicUploadSettingsPanel.java
r17995 r18173 4 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 5 6 import java.awt.Component; 6 7 import java.awt.GridBagLayout; 7 8 import java.awt.event.ActionEvent; 8 9 import java.awt.event.ActionListener; 9 import java.awt.event.FocusAdapter;10 10 import java.awt.event.FocusEvent; 11 import java.awt.event.FocusListener; 11 12 import java.awt.event.ItemEvent; 12 import java.awt.event. KeyAdapter;13 import java.awt.event.ItemListener; 13 14 import java.awt.event.KeyEvent; 15 import java.awt.event.KeyListener; 16 import java.util.ArrayList; 14 17 import java.util.Arrays; 15 import java.util. LinkedList;18 import java.util.Collection; 16 19 import java.util.List; 17 import java.util.Objects; 20 import java.util.Map; 21 import java.util.Optional; 18 22 import java.util.concurrent.TimeUnit; 19 23 … … 24 28 import javax.swing.JPanel; 25 29 import 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;33 30 import javax.swing.event.HyperlinkEvent; 31 import javax.swing.event.TableModelEvent; 32 import javax.swing.event.TableModelListener; 34 33 35 34 import org.openstreetmap.josm.data.osm.Changeset; … … 39 38 import org.openstreetmap.josm.gui.io.UploadTextComponentValidator.UploadCommentValidator; 40 39 import org.openstreetmap.josm.gui.io.UploadTextComponentValidator.UploadSourceValidator; 40 import org.openstreetmap.josm.gui.tagging.TagModel; 41 41 import org.openstreetmap.josm.gui.widgets.HistoryComboBox; 42 42 import org.openstreetmap.josm.gui.widgets.JMultilineLabel; … … 49 49 * @since 2599 50 50 */ 51 public class BasicUploadSettingsPanel extends JPanel {52 /** 53 * Preference name for history collection54 */ 55 public static final String HISTORY_KEY = "upload.comment.history";51 public class BasicUploadSettingsPanel extends JPanel implements ActionListener, FocusListener, ItemListener, KeyListener, TableModelListener { 52 /** 53 * Preference name for the history of comments 54 */ 55 public static final String COMMENT_HISTORY_KEY = "upload.comment.history"; 56 56 /** 57 57 * Preference name for last used upload comment 58 58 */ 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"; 60 60 /** 61 61 * Preference name for the max age search comments may have 62 62 */ 63 public static final String HISTORY_MAX_AGE_KEY = "upload.comment.max-age";64 /** 65 * Preference name for the history of source values63 public static final String COMMENT_MAX_AGE_KEY = "upload.comment.max-age"; 64 /** 65 * Preference name for the history of sources 66 66 */ 67 67 public static final String SOURCE_HISTORY_KEY = "upload.source.history"; … … 79 79 private final UploadAreaValidator areaValidator = new UploadAreaValidator(new JTextField(), areaValidatorFeedback); 80 80 /** 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; 84 82 private final transient JLabel uploadCommentFeedback = new JLabel(); 85 83 private final transient UploadCommentValidator uploadCommentValidator = new UploadCommentValidator( … … 89 87 hcbUploadSource.getEditorComponent(), hcbUploadSourceFeedback); 90 88 89 /** a lock to prevent loops in notifications */ 90 private boolean locked; 91 92 /** 93 * Creates the panel 94 * 95 * @param model The tag editor model. 96 * 97 * @since 18173 (signature) 98 */ 99 public BasicUploadSettingsPanel(UploadDialogModel model) { 100 this.model = model; 101 this.model.addTableModelListener(this); 102 build(); 103 } 104 91 105 protected JPanel buildUploadCommentPanel() { 92 106 JPanel pnl = new JPanel(new GridBagLayout()); … … 95 109 hcbUploadComment.setToolTipText(tr("Enter an upload comment")); 96 110 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); 102 116 pnl.add(hcbUploadComment, GBC.eol().fill(GBC.HORIZONTAL)); 103 117 pnl.add(uploadCommentFeedback, GBC.eol().insets(0, 3, 0, 0).fill(GBC.HORIZONTAL)); … … 113 127 obtainSourceOnce.addHyperlinkListener(e -> { 114 128 if (HyperlinkEvent.EventType.ACTIVATED.equals(e.getEventType())) { 115 automaticallyAddSource(); 129 saveEdits(); 130 model.put("source", getSourceFromLayer()); 116 131 } 117 132 }); 118 133 obtainSourceAutomatically.setSelected(Config.getPref().getBoolean("upload.source.obtainautomatically", false)); 119 134 obtainSourceAutomatically.addActionListener(e -> { 120 if (obtainSourceAutomatically.isSelected()) 121 automaticallyAddSource();122 135 if (obtainSourceAutomatically.isSelected()) { 136 model.put("source", getSourceFromLayer()); 137 } 123 138 obtainSourceOnce.setVisible(!obtainSourceAutomatically.isSelected()); 124 139 }); … … 133 148 hcbUploadSource.setToolTipText(tr("Enter a source")); 134 149 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); 140 155 pnl.add(hcbUploadSource, GBC.eol().fill(GBC.HORIZONTAL)); 141 156 pnl.add(hcbUploadSourceFeedback, GBC.eol().insets(0, 3, 0, 0).fill(GBC.HORIZONTAL)); 157 158 return pnl; 159 } 160 161 /** 162 * Initializes this life cycle of the panel. 163 * 164 * Adds any changeset tags to the map. 165 * 166 * @param map Map where tags are added to. 167 * @since 18173 168 */ 169 public void initLifeCycle(Map<String, String> map) { 170 Optional.ofNullable(getLastChangesetTagFromHistory(COMMENT_HISTORY_KEY, new ArrayList<>())).ifPresent( 171 x -> map.put("comment", x)); 172 Optional.ofNullable(getLastChangesetTagFromHistory(SOURCE_HISTORY_KEY, getDefaultSources())).ifPresent( 173 x -> map.put("source", x)); 142 174 if (obtainSourceAutomatically.isSelected()) { 143 automaticallyAddSource(); 144 } 145 pnl.addAncestorListener(new AncestorListener() { 146 @Override 147 public void ancestorAdded(AncestorEvent event) { 148 if (obtainSourceAutomatically.isSelected()) 149 automaticallyAddSource(); 150 } 151 152 @Override 153 public void ancestorRemoved(AncestorEvent event) { 154 // Do nothing 155 } 156 157 @Override 158 public void ancestorMoved(AncestorEvent event) { 159 // Do nothing 160 } 161 }); 162 return pnl; 163 } 164 165 /** 166 * Add the source tags 167 */ 168 protected void automaticallyAddSource() { 169 final String source = MainApplication.getMap().mapView.getLayerInformationForSourceTag(); 170 hcbUploadSource.getModel().setSelectedItem(null); // fix #20134 171 hcbUploadSource.setText(Utils.shortenString(source, Changeset.MAX_CHANGESET_TAG_LENGTH)); 172 changesetSourceModel.setComment(hcbUploadSource.getText()); // Fix #9965 173 } 174 175 /** 176 * Refreshes contents of upload history combo boxes from preferences. 177 */ 178 protected void refreshHistoryComboBoxes() { 179 populateHistoryComboBox(hcbUploadComment, HISTORY_KEY, new LinkedList<>()); 180 populateHistoryComboBox(hcbUploadSource, SOURCE_HISTORY_KEY, getDefaultSources()); 181 } 182 183 private static void populateHistoryComboBox(HistoryComboBox hcb, String historyKey, List<String> defaultValues) { 184 hcb.setPossibleItemsTopDown(Config.getPref().getList(historyKey, defaultValues)); 185 hcb.discardAllUndoableEdits(); 186 } 187 188 /** 189 * Discards undoable edits of upload history combo boxes. 190 */ 191 protected void discardAllUndoableEdits() { 175 map.put("source", getSourceFromLayer()); 176 } 177 hcbUploadComment.getModel().prefs().load(COMMENT_HISTORY_KEY); 192 178 hcbUploadComment.discardAllUndoableEdits(); 179 hcbUploadSource.getModel().prefs().load(SOURCE_HISTORY_KEY, getDefaultSources()); 193 180 hcbUploadSource.discardAllUndoableEdits(); 181 } 182 183 /** 184 * Get a key's value from the model. 185 * @param key The key 186 * @return The value or "" 187 * @since 18173 188 */ 189 private String get(String key) { 190 TagModel tm = model.get(key); 191 return tm == null ? "" : tm.getValue(); 192 } 193 194 /** 195 * Get the topmost item from the history if not expired. 196 * 197 * @param historyKey The preferences key. 198 * @param def A default history. 199 * @return The history item (may be null). 200 * @since 18173 (signature) 201 */ 202 public static String getLastChangesetTagFromHistory(String historyKey, List<String> def) { 203 Collection<String> history = Config.getPref().getList(historyKey, def); 204 long age = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()) - getHistoryLastUsedKey(); 205 if (age < getHistoryMaxAgeKey() && !history.isEmpty()) { 206 return history.iterator().next(); 207 } 208 return null; 209 } 210 211 /** 212 * Add the "source" tag 213 * @return The source from the layer info. 214 */ 215 private String getSourceFromLayer() { 216 String source = MainApplication.getMap().mapView.getLayerInformationForSourceTag(); 217 return Utils.shortenString(source, Changeset.MAX_CHANGESET_TAG_LENGTH); 194 218 } 195 219 … … 220 244 if (Config.getPref().getBoolean("upload.show.review.request", true)) { 221 245 add(cbRequestReview, gbc); 222 cbRequestReview.addItemListener( e -> changesetReviewModel.setReviewRequested(e.getStateChange() == ItemEvent.SELECTED));246 cbRequestReview.addItemListener(this); 223 247 } 224 248 add(areaValidatorFeedback, gbc); 225 249 add(new JPanel(), GBC.std().fill(GBC.BOTH)); 226 }227 228 /**229 * Creates the panel230 *231 * @param changesetCommentModel the model for the changeset comment. Must not be null232 * @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 null235 * @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 250 } 257 251 … … 263 257 if (getHistoryMaxAgeKey() > 0) { 264 258 hcbUploadComment.addCurrentItemToHistory(); 265 Config.getPref().putList(HISTORY_KEY, hcbUploadComment.getHistory());266 Config.getPref().putLong( HISTORY_LAST_USED_KEY, TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()));259 hcbUploadComment.getModel().prefs().save(COMMENT_HISTORY_KEY); 260 Config.getPref().putLong(COMMENT_LAST_USED_KEY, TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())); 267 261 } 268 262 // store the history of sources 269 263 hcbUploadSource.addCurrentItemToHistory(); 270 Config.getPref().putList(SOURCE_HISTORY_KEY, hcbUploadSource.getHistory());264 hcbUploadSource.getModel().prefs().save(SOURCE_HISTORY_KEY); 271 265 272 266 // store current value of obtaining source automatically … … 278 272 */ 279 273 public void startUserInput() { 280 hcbUploadComment.requestFocusInWindow();281 274 hcbUploadComment.getEditorComponent().requestFocusInWindow(); 282 275 uploadCommentValidator.validate(); … … 312 305 } 313 306 314 /**315 * Forces update of comment/source model if matching text field is focused.316 * @since 14977317 */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 329 307 static long getHistoryMaxAgeKey() { 330 return Config.getPref().getLong( HISTORY_MAX_AGE_KEY, TimeUnit.HOURS.toSeconds(4));308 return Config.getPref().getLong(COMMENT_MAX_AGE_KEY, TimeUnit.HOURS.toSeconds(4)); 331 309 } 332 310 333 311 static long getHistoryLastUsedKey() { 334 return Config.getPref().getLong(BasicUploadSettingsPanel.HISTORY_LAST_USED_KEY, 0); 335 } 336 337 static final class HistoryComboBoxKeyAdapter extends KeyAdapter { 338 private final HistoryComboBox hcb; 339 private final ActionListener handler; 340 341 HistoryComboBoxKeyAdapter(HistoryComboBox hcb, ActionListener handler) { 342 this.hcb = hcb; 343 this.handler = handler; 344 } 345 346 @Override 347 public void keyTyped(KeyEvent e) { 348 if (e.getKeyCode() == KeyEvent.VK_TAB) { 349 handler.actionPerformed(new ActionEvent(hcb, 0, "focusDown")); 312 return Config.getPref().getLong(COMMENT_LAST_USED_KEY, 0); 313 } 314 315 /** 316 * Updates the combobox histories when a combobox editor loses focus. 317 * 318 * @param text The {@code JTextField} of the combobox editor. 319 */ 320 private void updateHistory(JTextField text) { 321 String tag = (String) text.getDocument().getProperty("tag"); // tag is either "comment" or "source" 322 if (tag.equals("comment")) { 323 hcbUploadComment.addCurrentItemToHistory(); 324 } 325 if (tag.equals("source")) { 326 hcbUploadSource.addCurrentItemToHistory(); 327 } 328 } 329 330 /** 331 * Updates the table editor model with changes in the comboboxes. 332 * 333 * The lock prevents loops in change notifications, eg. the combobox 334 * notifies the table model and the table model notifies the combobox, which 335 * throws IllegalStateException. 336 * 337 * @param text The {@code JTextField} of the combobox editor. 338 */ 339 private void updateModel(JTextField text) { 340 if (!locked) { 341 locked = true; 342 try { 343 String tag = (String) text.getDocument().getProperty("tag"); // tag is either "comment" or "source" 344 String value = text.getText(); 345 model.put(tag, value.isEmpty() ? null : value); // remove tags with empty values 346 } finally { 347 locked = false; 350 348 } 351 349 } … … 353 351 354 352 /** 355 * Updates the changeset comment model upon changes in the input field. 356 */ 357 static class CommentModelListener extends FocusAdapter implements ActionListener, DocumentListener { 358 359 private final HistoryComboBox source; 360 private final ChangesetCommentModel destination; 361 362 CommentModelListener(HistoryComboBox source, ChangesetCommentModel destination) { 363 this.source = source; 364 this.destination = destination; 365 } 366 367 private void setComment() { 368 SwingUtilities.invokeLater(() -> destination.setComment(source.getText())); 369 } 370 371 @Override 372 public void actionPerformed(ActionEvent e) { 373 setComment(); 374 } 375 376 @Override 377 public void focusLost(FocusEvent e) { 378 setComment(); 379 } 380 381 @Override 382 public void insertUpdate(DocumentEvent e) { 383 setComment(); 384 } 385 386 @Override 387 public void removeUpdate(DocumentEvent e) { 388 setComment(); 389 } 390 391 @Override 392 public void changedUpdate(DocumentEvent e) { 393 setComment(); 394 } 395 } 396 397 /** 398 * Observes the changeset comment model and keeps the comment input field 399 * in sync with the current changeset comment 400 */ 401 static class ChangesetCommentChangeListener implements ChangeListener { 402 403 private final HistoryComboBox destination; 404 405 ChangesetCommentChangeListener(HistoryComboBox destination) { 406 this.destination = destination; 407 } 408 409 @Override 410 public void stateChanged(ChangeEvent e) { 411 if (!(e.getSource() instanceof ChangesetCommentModel)) return; 412 String newComment = ((ChangesetCommentModel) e.getSource()).getComment(); 413 if (!destination.getText().trim().equals(newComment)) { 414 destination.setText(newComment); 353 * Save all outstanding edits to the model. 354 * @see UploadDialog#saveEdits 355 * @since 18173 356 */ 357 public void saveEdits() { 358 updateModel(hcbUploadComment.getEditorComponent()); 359 hcbUploadComment.addCurrentItemToHistory(); 360 updateModel(hcbUploadSource.getEditorComponent()); 361 hcbUploadSource.addCurrentItemToHistory(); 362 } 363 364 /** 365 * Returns the UplodDialog that is our ancestor 366 * 367 * @return the UploadDialog or null 368 */ 369 private UploadDialog getDialog() { 370 Component d = getRootPane(); 371 while ((d = d.getParent()) != null) { 372 if (d instanceof UploadDialog) 373 return (UploadDialog) d; 374 } 375 return null; 376 } 377 378 /** 379 * Update the model when the selection changes in a combobox. 380 * @param e The action event. 381 */ 382 @Override 383 public void actionPerformed(ActionEvent e) { 384 getDialog().setFocusToUploadButton(); 385 } 386 387 @Override 388 public void focusGained(FocusEvent e) { 389 } 390 391 /** 392 * Update the model and combobox history when a combobox editor loses focus. 393 */ 394 @Override 395 public void focusLost(FocusEvent e) { 396 Object c = e.getSource(); 397 if (c instanceof JTextField) { 398 updateModel((JTextField) c); 399 updateHistory((JTextField) c); 400 } 401 } 402 403 /** 404 * Updates the table editor model upon changes in the "review" checkbox. 405 */ 406 @Override 407 public void itemStateChanged(ItemEvent e) { 408 if (!locked) { 409 locked = true; 410 try { 411 model.put("review_requested", e.getStateChange() == ItemEvent.SELECTED ? "yes" : null); 412 } finally { 413 locked = false; 415 414 } 416 415 } … … 418 417 419 418 /** 420 * Observes the changeset review model and keeps the review checkbox 421 * in sync with the current changeset review request 422 */ 423 class ChangesetReviewChangeListener implements ChangeListener { 424 @Override 425 public void stateChanged(ChangeEvent e) { 426 if (!(e.getSource() instanceof ChangesetReviewModel)) return; 427 boolean newState = ((ChangesetReviewModel) e.getSource()).isReviewRequested(); 428 if (cbRequestReview.isSelected() != newState) { 429 cbRequestReview.setSelected(newState); 419 * Updates the controls upon changes in the table editor model. 420 */ 421 @Override 422 public void tableChanged(TableModelEvent e) { 423 if (!locked) { 424 locked = true; 425 try { 426 hcbUploadComment.setText(get("comment")); 427 hcbUploadSource.setText(get("source")); 428 cbRequestReview.setSelected(get("review_requested").equals("yes")); 429 } finally { 430 locked = false; 430 431 } 431 432 } 432 433 } 434 435 /** 436 * Set the focus directly to the upload button if "Enter" key is pressed in any combobox. 437 */ 438 @Override 439 public void keyTyped(KeyEvent e) { 440 if (e.getKeyChar() == KeyEvent.VK_ENTER) { 441 getDialog().setFocusToUploadButton(); 442 } 443 } 444 445 @Override 446 public void keyPressed(KeyEvent e) { 447 } 448 449 @Override 450 public void keyReleased(KeyEvent e) { 451 } 433 452 } -
trunk/src/org/openstreetmap/josm/gui/io/ChangesetManagementPanel.java
r17709 r18173 29 29 import org.openstreetmap.josm.gui.widgets.JosmComboBox; 30 30 import org.openstreetmap.josm.spi.preferences.Config; 31 import org.openstreetmap.josm.tools.CheckParameterUtil;32 31 import org.openstreetmap.josm.tools.ImageProvider; 33 32 … … 56 55 private OpenChangesetComboBoxModel model; 57 56 57 /** the changeset comment model */ 58 private final transient UploadDialogModel uploadDialogModel; 59 58 60 /** 59 61 * Constructs a new {@code ChangesetManagementPanel}. 60 62 * 61 * @param changesetCommentModel the changeset comment model. Must not be null. 62 * @throws IllegalArgumentException if {@code changesetCommentModel} is null 63 */ 64 public ChangesetManagementPanel(ChangesetCommentModel changesetCommentModel) { 65 CheckParameterUtil.ensureParameterNotNull(changesetCommentModel, "changesetCommentModel"); 63 * @param uploadDialogModel The tag editor model. 64 * 65 * @since 18173 (signature) 66 */ 67 public ChangesetManagementPanel(UploadDialogModel uploadDialogModel) { 68 this.uploadDialogModel = uploadDialogModel; 66 69 build(); 67 70 refreshGUI(); … … 273 276 Changeset cs = (Changeset) cbOpenChangesets.getSelectedItem(); 274 277 if (cs == null) return; 278 uploadDialogModel.putAll(getSelectedChangeset().getKeys()); 275 279 firePropertyChange(SELECTED_CHANGESET_PROP, null, cs); 276 280 } … … 280 284 /** 281 285 * Refreshes the list of open changesets 282 *283 286 */ 284 287 class RefreshAction extends AbstractAction { … … 296 299 /** 297 300 * Closes the currently selected changeset 298 *299 301 */ 300 302 class CloseChangesetAction extends AbstractAction implements ItemListener { -
trunk/src/org/openstreetmap/josm/gui/io/IUploadDialog.java
r14977 r18173 66 66 */ 67 67 void handleIllegalChunkSize(); 68 69 /**70 * Forces update of comment/source model if matching text field is active.71 * @since 1497772 */73 void forceUpdateActiveField();74 68 } -
trunk/src/org/openstreetmap/josm/gui/io/UploadDialog.java
r17709 r18173 18 18 import java.lang.Character.UnicodeBlock; 19 19 import java.util.ArrayList; 20 import java.util.Collection;21 20 import java.util.Collections; 22 21 import java.util.HashMap; 23 import java.util.LinkedHashSet;24 22 import java.util.List; 25 23 import java.util.Locale; … … 27 25 import java.util.Map.Entry; 28 26 import java.util.Optional; 29 import java.util.Set;30 27 import java.util.stream.Collectors; 31 28 … … 40 37 41 38 import org.openstreetmap.josm.data.APIDataSet; 42 import org.openstreetmap.josm.data.Version;43 39 import org.openstreetmap.josm.data.osm.Changeset; 44 40 import org.openstreetmap.josm.data.osm.DataSet; … … 48 44 import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction; 49 45 import org.openstreetmap.josm.gui.help.HelpUtil; 46 import org.openstreetmap.josm.gui.tagging.TagEditorPanel; 50 47 import org.openstreetmap.josm.gui.util.GuiHelper; 51 48 import org.openstreetmap.josm.gui.util.MultiLineFlowLayout; … … 61 58 import org.openstreetmap.josm.tools.ImageProvider; 62 59 import org.openstreetmap.josm.tools.InputMapUtils; 60 import org.openstreetmap.josm.tools.Logging; 63 61 import org.openstreetmap.josm.tools.Utils; 64 62 … … 72 70 private static UploadDialog uploadDialog; 73 71 74 /** the "created_by" changeset OSM key */75 private static final String CREATED_BY = "created_by";76 77 72 /** the panel with the objects to upload */ 78 73 private UploadedObjectsSummaryPanel pnlUploadedObjects; … … 85 80 86 81 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; 89 86 /** the tabbed pane used below of the list of primitives */ 90 87 private JTabbedPane tpConfigPanels; … … 92 89 private JButton btnUpload; 93 90 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(); 98 93 99 94 private transient DataSet dataSet; … … 138 133 splitPane.setRightComponent(tpConfigPanels); 139 134 140 pnlBasicUploadSettings = new BasicUploadSettingsPanel( changesetCommentModel, changesetSourceModel, changesetReviewModel);135 pnlBasicUploadSettings = new BasicUploadSettingsPanel(model); 141 136 tpConfigPanels.add(pnlBasicUploadSettings); 142 137 tpConfigPanels.setTitleAt(0, tr("Description")); 143 tpConfigPanels.setToolTipTextAt(0, tr("Decide how to upload the data and which changeset to use")); 144 138 tpConfigPanels.setToolTipTextAt(0, tr("Describe the changes you made")); 139 140 JPanel pnlSettings = new JPanel(new GridBagLayout()); 141 pnlTagEditorBorder = new JPanel(new BorderLayout()); 145 142 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); 149 148 pnlUploadStrategySelectionPanel = new UploadStrategySelectionPanel(); 150 JPanel pnlChangeset = new JPanel(new GridBagLayout());151 pnl Changeset.add(pnlChangesetManagement, GBC.eop().fill(GBC.HORIZONTAL));152 pnl Changeset.add(pnlUploadStrategySelectionPanel, GBC.eop().fill(GBC.HORIZONTAL));153 pnlChangeset.add(pnlTagSettings, GBC.eol().fill(GBC.BOTH)); 154 tpConfigPanels.add(pnl Changeset);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); 155 154 tpConfigPanels.setTitleAt(1, tr("Settings")); 155 tpConfigPanels.setToolTipTextAt(1, tr("Decide how to upload the data and which changeset to use")); 156 156 157 157 JPanel pnl = new JPanel(new BorderLayout()); … … 181 181 pnl.add(new JButton(cancelAction)); 182 182 InputMapUtils.addEscapeAction(getRootPane(), cancelAction); 183 184 // -- help button 183 185 pnl.add(new JButton(new ContextSensitiveHelpAction(ht("/Dialog/Upload")))); 184 186 HelpUtil.setHelpContext(getRootPane(), ht("/Dialog/Upload")); … … 201 203 pnlBasicUploadSettings.getUploadParameterSummaryPanel() 202 204 ); 203 pnl ChangesetManagement.addPropertyChangeListener(this);205 pnlUploadedObjects.addPropertyChangeListener(pnlUploadStrategySelectionPanel); 204 206 pnlUploadedObjects.addPropertyChangeListener( 205 207 pnlBasicUploadSettings.getUploadParameterSummaryPanel() 206 208 ); 207 pnlUpload edObjects.addPropertyChangeListener(pnlUploadStrategySelectionPanel);209 pnlUploadStrategySelectionPanel.addPropertyChangeListener(this); 208 210 pnlUploadStrategySelectionPanel.addPropertyChangeListener( 209 211 pnlBasicUploadSettings.getUploadParameterSummaryPanel() … … 218 220 ); 219 221 220 pnlBasicUploadSettings.setUploadTagDownFocusTraversalHandlers(e -> btnUpload.requestFocusInWindow());221 222 222 // Enable/disable the upload button if at least an upload validator rejects upload 223 223 pnlBasicUploadSettings.getUploadTextValidators().forEach(v -> v.addChangeListener(e -> btnUpload.setEnabled( … … 227 227 228 228 Config.getPref().addPreferenceChangeListener(this); 229 } 230 231 /** 232 * Initializes this life cycle of the dialog. 233 * 234 * Initializes the dialog each time before it is made visible. We cannot do 235 * this in the constructor because the dialog is a singleton. 236 * 237 * @param dataSet The Dataset we want to upload 238 * @since 18173 239 */ 240 public void initLifeCycle(DataSet dataSet) { 241 Map<String, String> map = new HashMap<>(); 242 this.dataSet = dataSet; 243 pnlBasicUploadSettings.initLifeCycle(map); 244 model.clear(); 245 model.putAll(map); 246 model.putAll(this.dataSet); 229 247 } 230 248 … … 253 271 254 272 /** 255 * Sets the tags for this upload based on (later items overwrite earlier ones): 256 * <ul> 257 * <li>previous "source" and "comment" input</li> 258 * <li>the tags set in the dataset (see {@link DataSet#getChangeSetTags()})</li> 259 * <li>the tags from the selected open changeset</li> 260 * <li>the JOSM user agent (see {@link Version#getAgentString(boolean)})</li> 261 * </ul> 262 * 263 * @param dataSet to obtain the tags set in the dataset 264 */ 265 public void setChangesetTags(DataSet dataSet) { 266 setChangesetTags(dataSet, false); 267 } 268 269 /** 270 * Sets the tags for this upload based on (later items overwrite earlier ones): 271 * <ul> 272 * <li>previous "source" and "comment" input</li> 273 * <li>the tags set in the dataset (see {@link DataSet#getChangeSetTags()})</li> 274 * <li>the tags from the selected open changeset</li> 275 * <li>the JOSM user agent (see {@link Version#getAgentString(boolean)})</li> 276 * </ul> 277 * 278 * @param dataSet to obtain the tags set in the dataset 279 * @param keepSourceComment if {@code true}, keep upload {@code source} and {@code comment} current values from models 280 */ 281 private void setChangesetTags(DataSet dataSet, boolean keepSourceComment) { 282 final Map<String, String> tags = new HashMap<>(); 283 284 // obtain from previous input 285 if (!keepSourceComment) { 286 tags.put("source", getLastChangesetSourceFromHistory()); 287 tags.put("comment", getCommentWithDataSetHashTag(getLastChangesetCommentFromHistory(), dataSet)); 288 } 289 290 // obtain from dataset 291 if (dataSet != null) { 292 tags.putAll(dataSet.getChangeSetTags()); 293 } 294 this.dataSet = dataSet; 295 296 // obtain from selected open changeset 297 if (pnlChangesetManagement.getSelectedChangeset() != null) { 298 tags.putAll(pnlChangesetManagement.getSelectedChangeset().getKeys()); 299 } 300 301 // set/adapt created_by 302 final String agent = Version.getInstance().getAgentString(false); 303 final String createdBy = tags.get(CREATED_BY); 304 if (createdBy == null || createdBy.isEmpty()) { 305 tags.put(CREATED_BY, agent); 306 } else if (!createdBy.contains(agent)) { 307 tags.put(CREATED_BY, createdBy + ';' + agent); 308 } 309 310 // remove empty values 311 tags.keySet().removeIf(key -> { 312 final String v = tags.get(key); 313 return v == null || v.isEmpty(); 314 }); 315 316 // ignore source/comment to keep current values from models ? 317 if (keepSourceComment) { 318 tags.put("source", changesetSourceModel.getComment()); 319 tags.put("comment", getCommentWithDataSetHashTag(changesetCommentModel.getComment(), dataSet)); 320 } 321 322 pnlTagSettings.initFromTags(tags); 323 pnlTagSettings.tableChanged(null); 324 pnlBasicUploadSettings.discardAllUndoableEdits(); 325 } 326 327 /** 328 * Returns the given comment with appended hashtags from dataset changeset tags, if not already present. 329 * @param comment changeset comment. Can be null 330 * @param dataSet optional dataset, which can contain hashtags in its changeset tags 331 * @return comment with dataset changesets tags, if any, not duplicated 332 */ 333 static String getCommentWithDataSetHashTag(String comment, DataSet dataSet) { 334 StringBuilder result = comment == null ? new StringBuilder() : new StringBuilder(comment); 335 if (dataSet != null) { 336 String hashtags = dataSet.getChangeSetTags().get("hashtags"); 337 if (hashtags != null) { 338 Set<String> sanitizedHashtags = new LinkedHashSet<>(); 339 for (String hashtag : hashtags.split(";", -1)) { 340 sanitizedHashtags.add(hashtag.startsWith("#") ? hashtag : "#" + hashtag); 341 } 342 if (!sanitizedHashtags.isEmpty()) { 343 result.append(' ').append(String.join(" ", sanitizedHashtags)); 344 } 345 } 346 } 347 return result.toString(); 273 * Sets the input focus to upload button. 274 * @since 18173 275 */ 276 public void setFocusToUploadButton() { 277 btnUpload.requestFocus(); 348 278 } 349 279 … … 360 290 tpConfigPanels.setSelectedIndex(0); 361 291 pnlBasicUploadSettings.startUserInput(); 362 pnlTag Settings.startUserInput();292 pnlTagEditor.initAutoCompletion(MainApplication.getLayerManager().getEditLayer()); 363 293 pnlUploadStrategySelectionPanel.initFromPreferences(); 364 294 UploadParameterSummaryPanel pnl = pnlBasicUploadSettings.getUploadParameterSummaryPanel(); … … 375 305 public Changeset getChangeset() { 376 306 Changeset cs = Optional.ofNullable(pnlChangesetManagement.getSelectedChangeset()).orElseGet(Changeset::new); 377 cs.setKeys( pnlTagSettings.getTags(false));307 cs.setKeys(model.getTags(false)); 378 308 return cs; 379 309 } … … 395 325 } 396 326 327 /** 328 * Get the upload dialog model. 329 * 330 * @return The model. 331 * @since 18173 332 */ 333 public UploadDialogModel getModel() { 334 return model; 335 } 336 397 337 @Override 398 338 public String getUploadComment() { 399 return changesetCommentModel.getComment();339 return model.getValue("comment"); 400 340 } 401 341 402 342 @Override 403 343 public String getUploadSource() { 404 return changesetSourceModel.getComment();344 return model.getValue("source"); 405 345 } 406 346 … … 496 436 @Override 497 437 public void actionPerformed(ActionEvent e) { 498 // force update of model in case dialog is closed before focus lost event, see #17452499 dialog.forceUpdateActiveField();438 Map<String, String> tags = dialog.getTags(true); 439 Logging.info("Starting upload with tags {0}", tags); 500 440 501 441 /* test for empty tags in the changeset metadata and proceed only after user's confirmation. 502 442 * though, accept if key and value are empty (cf. xor). */ 503 443 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()) { 505 445 final boolean isKeyEmpty = Utils.isStripEmpty(i.getKey()); 506 446 final boolean isValueEmpty = Utils.isStripEmpty(i.getValue()); … … 589 529 if (evt.getPropertyName().equals(ChangesetManagementPanel.SELECTED_CHANGESET_PROP)) { 590 530 Changeset cs = (Changeset) evt.getNewValue(); 591 setChangesetTags(dataSet, cs == null); // keep comment/source of first tab for new changesets592 531 if (cs == null) { 593 532 tagSettingsBorder.setTitle(tr("Tags of new changeset")); … … 610 549 osmServerUrlChanged(e.getNewValue()); 611 550 break; 612 case BasicUploadSettingsPanel.HISTORY_KEY:613 case BasicUploadSettingsPanel.SOURCE_HISTORY_KEY:614 pnlBasicUploadSettings.refreshHistoryComboBoxes();615 break;616 551 default: 617 552 return; … … 630 565 } 631 566 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 /* -------------------------------------------------------------------------- */ 657 570 @Override 658 571 public Map<String, String> getTags(boolean keepEmpty) { 659 return pnlTagSettings.getTags(keepEmpty); 572 saveEdits(); 573 return model.getTags(keepEmpty); 660 574 } 661 575 … … 677 591 } 678 592 679 @Override 680 public void forceUpdateActiveField() { 681 if (tpConfigPanels.getSelectedComponent() == pnlBasicUploadSettings) { 682 pnlBasicUploadSettings.forceUpdateActiveField(); 683 } 593 /** 594 * Save all outstanding edits to the model. 595 * <p> 596 * The combobox editors and the tag cell editor need to be manually saved 597 * because they normally save on focus loss, eg. when the "Upload" button is 598 * pressed, but there's no focus change when Ctrl+Enter is pressed. 599 * 600 * @since 18173 601 */ 602 public void saveEdits() { 603 pnlBasicUploadSettings.saveEdits(); 604 pnlTagEditor.saveEdits(); 684 605 } 685 606 -
trunk/src/org/openstreetmap/josm/gui/preferences/projection/CustomProjectionChoice.java
r15011 r18173 23 23 import org.openstreetmap.josm.data.projection.ProjectionConfigurationException; 24 24 import org.openstreetmap.josm.data.projection.Projections; 25 import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem;26 25 import org.openstreetmap.josm.gui.ExtendedDialog; 27 26 import org.openstreetmap.josm.gui.widgets.AbstractTextComponentValidator; … … 29 28 import org.openstreetmap.josm.gui.widgets.HtmlPanel; 30 29 import org.openstreetmap.josm.gui.widgets.JosmTextField; 31 import org.openstreetmap.josm.spi.preferences.Config;32 30 import org.openstreetmap.josm.tools.GBC; 33 31 import org.openstreetmap.josm.tools.ImageProvider; … … 64 62 input = new JosmTextField(30); 65 63 cbInput = new HistoryComboBox(); 66 cbInput.setPrototypeDisplayValue(new AutoCompletionItem("xxxx"));67 64 cbInput.setEditor(new BasicComboBoxEditor() { 68 65 @Override … … 74 71 "+proj=lonlat +ellps=WGS84 +datum=WGS84 +bounds=-180,-90,180,90", 75 72 "+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); 77 74 cbInput.setText(initialText == null ? "" : initialText); 78 75 … … 146 143 public void rememberHistory() { 147 144 cbInput.addCurrentItemToHistory(); 148 Config.getPref().putList("projection.custom.value.history", cbInput.getHistory());145 cbInput.getModel().prefs().save("projection.custom.value.history"); 149 146 } 150 147 } -
trunk/src/org/openstreetmap/josm/gui/preferences/server/OsmApiUrlInputPanel.java
r17160 r18173 109 109 public void initFromPreferences() { 110 110 String url = OsmApi.getOsmApi().getServerUrl(); 111 tfOsmServerUrl. setPossibleItems(SERVER_URL_HISTORY.get());111 tfOsmServerUrl.getModel().prefs().load(SERVER_URL_HISTORY); 112 112 if (Config.getUrls().getDefaultOsmApiUrl().equals(url.trim())) { 113 113 cbUseDefaultServerUrl.setSelected(true); … … 131 131 Config.getPref().put("osm-server.url", hmiUrl); 132 132 tfOsmServerUrl.addCurrentItemToHistory(); 133 SERVER_URL_HISTORY.put(tfOsmServerUrl.getHistory());133 tfOsmServerUrl.getModel().prefs().save(SERVER_URL_HISTORY); 134 134 } 135 135 String newUrl = OsmApi.getOsmApi().getServerUrl(); -
trunk/src/org/openstreetmap/josm/gui/preferences/server/OverpassServerPanel.java
r17162 r18173 42 42 */ 43 43 public final void initFromPreferences() { 44 overpassServer. setPossibleItems(OverpassDownloadReader.OVERPASS_SERVER_HISTORY.get());44 overpassServer.getModel().prefs().load(OverpassDownloadReader.OVERPASS_SERVER_HISTORY); 45 45 overpassServer.setText(OverpassDownloadReader.OVERPASS_SERVER.get()); 46 46 forMultiFetch.setSelected(OverpassDownloadReader.FOR_MULTI_FETCH.get()); … … 52 52 public final void saveToPreferences() { 53 53 OverpassDownloadReader.OVERPASS_SERVER.put(overpassServer.getText()); 54 OverpassDownloadReader.OVERPASS_SERVER_HISTORY.put(overpassServer.getHistory());54 overpassServer.getModel().prefs().save(OverpassDownloadReader.OVERPASS_SERVER_HISTORY); 55 55 OverpassDownloadReader.FOR_MULTI_FETCH.put(forMultiFetch.isSelected()); 56 56 } -
trunk/src/org/openstreetmap/josm/gui/tagging/TagEditorModel.java
r17585 r18173 646 646 } 647 647 648 pr ivatevoid commitPendingEdit() {648 protected void commitPendingEdit() { 649 649 if (endEditListener != null) { 650 650 endEditListener.endCellEditing(); -
trunk/src/org/openstreetmap/josm/gui/tagging/TagEditorPanel.java
r17596 r18173 222 222 validate(); 223 223 } 224 225 /** 226 * Save all outstanding edits to the model. 227 * @see org.openstreetmap.josm.gui.io.UploadDialog#saveEdits 228 * @since 18173 229 */ 230 public void saveEdits() { 231 tagTable.endCellEditing(); 232 } 224 233 } -
trunk/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletingComboBox.java
r18141 r18173 2 2 package org.openstreetmap.josm.gui.tagging.ac; 3 3 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 22 4 import 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;31 5 32 6 /** 33 * Auto-completing ComboBox. 7 * An auto-completing ComboBox. 8 * 34 9 * @author guilhem.bonnefille@gmail.com 35 10 * @since 272 11 * @deprecated Use the generic type {@link AutoCompComboBox} instead. Eg. 12 * {@code AutoCompComboBox<AutoCompletionItem>} or {@code AutoCompComboBox<String>}. 36 13 */ 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 15 public class AutoCompletingComboBox extends AutoCompComboBox<AutoCompletionItem> { 355 16 } -
trunk/src/org/openstreetmap/josm/gui/widgets/HistoryComboBox.java
r18131 r18173 4 4 import java.util.List; 5 5 6 import javax.swing.text.JTextComponent; 7 8 import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem; 9 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingComboBox; 10 import org.openstreetmap.josm.spi.preferences.Config; 6 import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBox; 11 7 12 8 /** 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. 14 12 */ 15 public class HistoryComboBox extends AutoCompletingComboBox { 16 private final ComboBoxHistory model; 17 18 /** 19 * The default size of the search history. 20 */ 21 public static final int DEFAULT_SEARCH_HISTORY_SIZE = 15; 13 public class HistoryComboBox extends AutoCompComboBox<String> { 22 14 23 15 /** … … 25 17 */ 26 18 public HistoryComboBox() { 27 int maxsize = Config.getPref().getInt("search.history-size", DEFAULT_SEARCH_HISTORY_SIZE); 28 model = new ComboBoxHistory(maxsize); 29 setModel(model); 30 setEditable(true); 19 super(new HistoryComboBoxModel()); 20 setPrototypeDisplayValue("dummy"); 21 } 22 23 @Override 24 public HistoryComboBoxModel getModel() { 25 return (HistoryComboBoxModel) dataModel; 31 26 } 32 27 33 28 /** 34 * Returns the text contained in this component 35 * @return the text 36 * @see JTextComponent#getText() 37 */ 38 public String getText() { 39 return getEditorComponent().getText(); 40 } 41 42 /** 43 * Sets the text of this component to the specified text 44 * @param value the text to set 45 * @see JTextComponent#setText(java.lang.String) 46 */ 47 public void setText(String value) { 48 setAutocompleteEnabled(false); 49 getEditorComponent().setText(value); 50 setAutocompleteEnabled(true); 51 } 52 53 /** 54 * Adds or moves the current element to the top of the history 55 * @see ComboBoxHistory#addElement(java.lang.String) 29 * Adds the item in the editor to the top of the history. If the item is already present, don't 30 * add another but move it to the top. The item is then selected. 56 31 */ 57 32 public void addCurrentItemToHistory() { 58 Object item = getEditor().getItem(); 59 // This avoids instantiating multiple AutoCompletionItems 60 if (item instanceof AutoCompletionItem) { 61 model.addElement((AutoCompletionItem) item); 62 } else { 63 model.addElement(item.toString()); 64 } 65 } 66 67 /** 68 * Sets the elements of the ComboBox to the given items 69 * @param history the items to set 70 * @see ComboBoxHistory#setItemsAsString(java.util.List) 71 */ 72 public void setHistory(List<String> history) { 73 model.setItemsAsString(history); 33 String newItem = getModel().addTopElement(getEditor().getItem().toString()); 34 getModel().setSelectedItem(newItem); 74 35 } 75 36 … … 77 38 * Returns the items as strings 78 39 * @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}. 80 44 */ 45 @Deprecated 81 46 public List<String> getHistory() { 82 return model.asStringList();47 return getModel().asStringList(); 83 48 } 84 49 } -
trunk/src/org/openstreetmap/josm/gui/widgets/JosmComboBox.java
r16453 r18173 107 107 public JTextField getEditorComponent() { 108 108 return (JTextField) getEditor().getEditorComponent(); 109 } 110 111 /** 112 * Returns the text in the combobox editor. 113 * @return the text 114 * @see JTextComponent#getText 115 * @since 18173 116 */ 117 public String getText() { 118 return getEditorComponent().getText(); 119 } 120 121 /** 122 * Sets the text in the combobox editor. 123 * @param value the text to set 124 * @see JTextComponent#setText 125 * @since 18173 126 */ 127 public void setText(String value) { 128 getEditorComponent().setText(value); 109 129 } 110 130 … … 164 184 } 165 185 166 protected final void init(E prototype) {167 init(prototype, true);168 }169 170 p rotected 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) { 171 191 if (prototype != null) { 172 s etPrototypeDisplayValue(prototype);192 super.setPrototypeDisplayValue(prototype); 173 193 int screenHeight = GuiHelper.getScreenSize().height; 174 194 // Compute maximum number of visible items based on the preferred size of the combo box. … … 189 209 setMaximumRowCount(Math.max(getMaximumRowCount(), maxsize)); 190 210 } 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); 191 219 // Handle text contextual menus for editable comboboxes 192 220 if (registerPropertyChangeListener) { -
trunk/test/unit/org/openstreetmap/josm/gui/io/BasicUploadSettingsPanelTest.java
r18037 r18173 2 2 package org.openstreetmap.josm.gui.io; 3 3 4 import static org.junit.jupiter.api.Assertions.assertEquals; 4 5 import static org.junit.jupiter.api.Assertions.assertNotNull; 6 import static org.junit.jupiter.api.Assertions.assertNull; 7 8 import java.util.Arrays; 9 import java.util.List; 10 11 import org.openstreetmap.josm.spi.preferences.Config; 5 12 6 13 import org.openstreetmap.josm.testutils.annotations.BasicPreferences; … … 18 25 @Test 19 26 void testBasicUploadSettingsPanel() { 20 assertNotNull(new BasicUploadSettingsPanel(new ChangesetCommentModel(), new ChangesetCommentModel(), new ChangesetReviewModel())); 27 assertNotNull(new BasicUploadSettingsPanel(new UploadDialogModel())); 28 } 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()); 21 64 } 22 65 } -
trunk/test/unit/org/openstreetmap/josm/gui/io/ChangesetManagementPanelTest.java
r18037 r18173 5 5 6 6 import org.junit.jupiter.api.Test; 7 7 8 import org.openstreetmap.josm.testutils.annotations.BasicPreferences; 8 9 … … 17 18 @Test 18 19 void testChangesetManagementPanel() { 19 assertNotNull(new ChangesetManagementPanel(new ChangesetCommentModel()));20 assertNotNull(new ChangesetManagementPanel(new UploadDialogModel())); 20 21 } 21 22 } -
trunk/test/unit/org/openstreetmap/josm/gui/io/UploadDialogTest.java
r18037 r18173 13 13 import java.util.Map; 14 14 import java.util.concurrent.ConcurrentHashMap; 15 import java.util.function.Supplier;16 15 17 16 import javax.swing.JOptionPane; … … 19 18 import org.junit.jupiter.api.Test; 20 19 import org.openstreetmap.josm.TestUtils; 21 import org.openstreetmap.josm.data.osm.DataSet;22 20 import org.openstreetmap.josm.gui.io.UploadDialog.UploadAction; 23 21 import org.openstreetmap.josm.io.UploadStrategySpecification; … … 82 80 return new ConcurrentHashMap<>(); 83 81 } 84 85 @Override86 public void forceUpdateActiveField() {87 // Do nothing88 }89 82 } 90 83 … … 114 107 // test with unassigned unicode characters ==> no unicode block 115 108 assertTrue(UploadDialog.UploadAction.isUploadCommentTooShort("\u0860")); 116 }117 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 empty127 Config.getPref().putList(historyKey, null);128 assertEquals(def, methodToTest.get()); // age OK, history empty129 }130 131 /**132 * Test of {@link UploadDialog#getLastChangesetCommentFromHistory} method.133 */134 @Test135 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 @Test146 void testGetLastChangesetSourceFromHistory() {147 doTestGetLastChangesetTagFromHistory(148 BasicUploadSettingsPanel.SOURCE_HISTORY_KEY,149 UploadDialog::getLastChangesetSourceFromHistory,150 BasicUploadSettingsPanel.getDefaultSources().get(0));151 109 } 152 110 … … 186 144 doTestValidateUploadTag("upload.source"); 187 145 } 188 189 @Test190 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 }199 146 }
Note:
See TracChangeset
for help on using the changeset viewer.