Ticket #21240: 21240-2.patch
| File 21240-2.patch, 170.5 KB (added by , 4 years ago) |
|---|
-
src/org/openstreetmap/josm/actions/OpenLocationAction.java
43 43 import org.openstreetmap.josm.gui.util.GuiHelper; 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; 49 48 import org.openstreetmap.josm.tools.Shortcut; … … 113 112 * @param cbHistory the history combo box 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 120 118 /** … … 123 121 */ 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 129 127 @Override -
src/org/openstreetmap/josm/actions/SearchNotesDownloadAction.java
7 7 import java.awt.GridBagLayout; 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 13 12 import javax.swing.JLabel; … … 44 43 @Override 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()); 50 49 GridBagConstraints gc = new GridBagConstraints(); … … 72 71 } 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); 78 77 } -
src/org/openstreetmap/josm/actions/UploadAction.java
239 239 ChangesetUpdater.check(); 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); 245 245 dialog.rememberUserInput(); -
src/org/openstreetmap/josm/actions/search/SearchAction.java
13 13 import java.util.Collection; 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; 23 21 … … 42 40 import org.openstreetmap.josm.gui.preferences.ToolbarPreferences; 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; 47 46 import org.openstreetmap.josm.tools.Shortcut; … … 66 65 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() { 72 76 @Override … … 86 90 } 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 106 104 /** … … 108 106 * @param s The search to save 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 129 113 /** … … 131 115 * @return The list of search texts. 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 139 121 private static volatile SearchSetting lastSearch; … … 175 157 } 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; 181 163 … … 203 185 * Launches the dialog for specifying search criteria and runs a search 204 186 */ 205 187 public static void search() { 188 prefs.load("search.history"); 206 189 SearchSetting se = showSearchDialog(lastSearch); 207 190 if (se != null) { 208 191 searchWithHistory(se); -
src/org/openstreetmap/josm/data/osm/search/SearchSetting.java
6 6 import java.util.Objects; 7 7 8 8 import org.openstreetmap.josm.tools.Logging; 9 import org.openstreetmap.josm.tools.Utils; 9 10 10 11 /** 11 12 * This class defines a set of parameters that is used to … … 49 50 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 18158 61 */ 62 public String toStringEx() { 52 63 String cs = caseSensitive ? 53 64 /*case sensitive*/ trc("search", "CS") : 54 65 /*case insensitive*/ trc("search", "CI"); … … 139 150 } 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 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 /** 142 171 * Builds a string representation of the {@code SearchSetting} object, 143 172 * see {@link #readFromString(String)} for more details. 144 173 * @return A string representation of the {@code SearchSetting} object. -
src/org/openstreetmap/josm/data/tagging/ac/AutoCompletionItem.java
94 94 public boolean equals(Object obj) { 95 95 if (this == obj) 96 96 return true; 97 if ( obj == null)97 if (!(obj instanceof AutoCompletionItem)) 98 98 return false; 99 if (obj instanceof String)100 return obj.equals(value);101 if (getClass() != obj.getClass())102 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 } -
src/org/openstreetmap/josm/gui/dialogs/OsmIdSelectionDialog.java
178 178 * @param cbHistory the {@link HistoryComboBox} to which the history is restored to 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 185 184 /** … … 189 188 */ 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 195 194 /** -
src/org/openstreetmap/josm/gui/dialogs/SearchDialog.java
15 15 import java.awt.event.MouseAdapter; 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; 21 20 import javax.swing.ButtonGroup; … … 37 36 import org.openstreetmap.josm.gui.ExtendedDialog; 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; 46 46 import org.openstreetmap.josm.tools.Logging; … … 54 54 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; 60 60 private JCheckBox caseSensitive; … … 71 71 private TaggingPresetSelector selector; 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 search76 * @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 77 */ 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(), 80 80 initialValues instanceof Filter ? tr("Filter") : tr("Search"), 81 81 initialValues instanceof Filter ? tr("Submit filter") : tr("Search"), 82 82 tr("Cancel")); … … 84 84 configureContextsensitiveHelp("/Action/Search", true /* show help button */); 85 85 } 86 86 87 protected SearchDialog(SearchSetting initialValues, List<String> searchExpressionHistory, PanelOptions options,87 protected SearchDialog(SearchSetting initialValues, AutoCompComboBoxModel<SearchSetting> model, PanelOptions options, 88 88 Component mainFrame, String title, String... buttonTexts) { 89 89 super(mainFrame, title, buttonTexts); 90 hcbSearchString = new AutoCompComboBox<>(model); 90 91 this.searchSettings = new SearchSetting(initialValues); 91 setContent(buildPanel( searchExpressionHistory,options));92 setContent(buildPanel(options)); 92 93 } 93 94 94 95 /** … … 100 101 101 102 /** 102 103 * 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 query104 * @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". 105 106 */ 106 107 public PanelOptions(boolean expertMode, boolean overpassQuery) { 107 108 this.expertMode = expertMode; … … 109 110 } 110 111 } 111 112 112 private JPanel buildPanel( List<String> searchExpressionHistory,PanelOptions options) {113 private JPanel buildPanel(PanelOptions options) { 113 114 114 115 // prepare the combo box with the search expressions 115 116 JLabel label = new JLabel(searchSettings instanceof Filter ? tr("Filter string:") : tr("Search string:")); 116 117 117 118 String tooltip = tr("Enter the search expression"); 118 hcbSearchString.setText(searchSettings.text); 119 // FIXME do we need this? 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); 124 124 … … 306 306 return addOnToolbar.isSelected(); 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"))); 312 312 … … 465 465 466 466 private static class SearchKeywordRow extends JPanel { 467 467 468 private final HistoryComboBoxhcb;468 private final AutoCompComboBox<SearchSetting> hcb; 469 469 470 SearchKeywordRow( HistoryComboBoxhcb) {470 SearchKeywordRow(AutoCompComboBox<SearchSetting> hcb) { 471 471 super(new FlowLayout(FlowLayout.LEFT)); 472 472 this.hcb = hcb; 473 473 } -
src/org/openstreetmap/josm/gui/dialogs/properties/TagEditHelper.java
87 87 import org.openstreetmap.josm.gui.IExtendedDialog; 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; 93 93 import org.openstreetmap.josm.gui.util.WindowGeometry; … … 399 399 } 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 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 /** 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 404 427 * @param togglePref The preference to save the checkbox state to … … 475 498 AutoCompletionManager autocomplete = AutoCompletionManager.of(OsmDataManager.getInstance().getActiveDataSet()); 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 483 508 p.add(Box.createVerticalStrut(5), GBC.eol()); … … 489 514 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()); 501 528 p.add(Box.createHorizontalStrut(10), GBC.std()); … … 521 548 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()) { 532 559 newkey = key; … … 573 600 } 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) { 580 607 super(parent, title, buttonTexts); … … 620 647 super.setVisible(visible); 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(); 626 653 if (sysSel != null) { … … 666 693 boolean valuesOK = size == currentModel.getSize() 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)) { 672 700 values.getEditor().selectAll(); … … 687 715 if (buttons.isEmpty()) { 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 } 693 721 … … 731 759 configureContextsensitiveHelp("/Dialog/AddValue", true /* show help button */); 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()); 738 770 … … 747 779 // remove the object's tag keys from the list 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 753 786 mainPanel.add(keys, GBC.eop().fill(GBC.HORIZONTAL)); … … 938 971 tr("Choose recent tag {0}", count), null, tr("Use this tag again"), sc, false) { 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 } 947 980 }; … … 1093 1126 * Read tags from comboboxes and add it to all selected objects 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; 1100 1133 for (OsmPrimitive osm : sel) { -
src/org/openstreetmap/josm/gui/download/OverpassQueryWizardDialog.java
14 14 import org.openstreetmap.josm.data.preferences.ListProperty; 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; 19 20 import org.openstreetmap.josm.tools.UncheckedParseException; … … 35 36 private static final int BUILD_AN_EXECUTE_QUERY = 1; 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} 40 46 * @param callbacks The Overpass download source panel. 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 52 61 @Override … … 75 84 * Saves the latest, successfully parsed search term. 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 82 91 /** -
src/org/openstreetmap/josm/gui/download/PlaceSelection.java
116 116 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 122 122 panel.add(lpanel, GBC.std().fill(GBC.HORIZONTAL).insets(5, 5, 0, 5)); … … 194 194 if (!isEnabled() || searchExpression.trim().isEmpty()) 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()); 200 200 NameQueryTask task = new NameQueryTask(url, data -> { -
src/org/openstreetmap/josm/gui/io/BasicUploadSettingsPanel.java
3 3 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 20 24 import javax.swing.BorderFactory; … … 23 27 import javax.swing.JLabel; 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; 36 35 import org.openstreetmap.josm.data.osm.OsmPrimitive; … … 38 37 import org.openstreetmap.josm.gui.io.UploadTextComponentValidator.UploadAreaValidator; 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; 43 43 import org.openstreetmap.josm.spi.preferences.Config; … … 48 48 * BasicUploadSettingsPanel allows to enter the basic parameters required for uploading data. 49 49 * @since 2599 50 50 */ 51 public class BasicUploadSettingsPanel extends JPanel {51 public class BasicUploadSettingsPanel extends JPanel implements ActionListener, FocusListener, ItemListener, KeyListener, TableModelListener { 52 52 /** 53 * Preference name for history collection53 * Preference name for the history of comments 54 54 */ 55 public static final String HISTORY_KEY = "upload.comment.history";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";63 public static final String COMMENT_MAX_AGE_KEY = "upload.comment.max-age"; 64 64 /** 65 * Preference name for the history of source values65 * Preference name for the history of sources 66 66 */ 67 67 public static final String SOURCE_HISTORY_KEY = "upload.source.history"; 68 68 … … 78 78 private final JLabel areaValidatorFeedback = new JLabel(); 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( 86 84 hcbUploadComment.getEditorComponent(), uploadCommentFeedback); … … 88 86 private final transient UploadSourceValidator uploadSourceValidator = new UploadSourceValidator( 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 18158 (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()); 93 107 pnl.setBorder(BorderFactory.createTitledBorder(tr("Provide a brief comment for the changes you are uploading:"))); … … 94 108 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)); 104 118 return pnl; … … 112 126 "<html>(<a href=\"urn:changeset-source\">" + tr("just once") + "</a>)</html>"); 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 }); 125 140 JPanel obtainSource = new JPanel(new GridBagLayout()); … … 132 147 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)); 142 if (obtainSourceAutomatically.isSelected()) {143 automaticallyAddSource();144 }145 pnl.addAncestorListener(new AncestorListener() {146 @Override147 public void ancestorAdded(AncestorEvent event) {148 if (obtainSourceAutomatically.isSelected())149 automaticallyAddSource();150 }151 157 152 @Override153 public void ancestorRemoved(AncestorEvent event) {154 // Do nothing155 }156 157 @Override158 public void ancestorMoved(AncestorEvent event) {159 // Do nothing160 }161 });162 158 return pnl; 163 159 } 164 160 165 161 /** 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 167 168 */ 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(); 173 181 } 174 182 175 183 /** 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 177 188 */ 178 pr otected 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(); 181 192 } 182 193 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; 186 209 } 187 210 188 211 /** 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 190 215 */ 191 pr otected 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); 194 219 } 195 220 196 221 /** … … 219 244 add(pnlUploadParameterSummary, gbc); 220 245 if (Config.getPref().getBoolean("upload.show.review.request", true)) { 221 246 add(cbRequestReview, gbc); 222 cbRequestReview.addItemListener( e -> changesetReviewModel.setReviewRequested(e.getStateChange() == ItemEvent.SELECTED));247 cbRequestReview.addItemListener(this); 223 248 } 224 249 add(areaValidatorFeedback, gbc); 225 250 add(new JPanel(), GBC.std().fill(GBC.BOTH)); … … 226 251 } 227 252 228 253 /** 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 }257 258 /**259 254 * Remembers the user input in the preference settings 260 255 */ 261 256 public void rememberUserInput() { … … 262 257 // store the history of comments 263 258 if (getHistoryMaxAgeKey() > 0) { 264 259 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())); 267 262 } 268 263 // store the history of sources 269 264 hcbUploadSource.addCurrentItemToHistory(); 270 Config.getPref().putList(SOURCE_HISTORY_KEY, hcbUploadSource.getHistory());265 hcbUploadSource.getModel().prefs().save(SOURCE_HISTORY_KEY); 271 266 272 267 // store current value of obtaining source automatically 273 268 Config.getPref().putBoolean("upload.source.obtainautomatically", obtainSourceAutomatically.isSelected()); … … 277 272 * Initializes the panel for user input 278 273 */ 279 274 public void startUserInput() { 280 hcbUploadComment.requestFocusInWindow();281 275 hcbUploadComment.getEditorComponent().requestFocusInWindow(); 282 276 uploadCommentValidator.validate(); 283 277 uploadSourceValidator.validate(); … … 311 305 return pnlUploadParameterSummary; 312 306 } 313 307 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 308 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)); 331 310 } 332 311 333 312 static long getHistoryLastUsedKey() { 334 return Config.getPref().getLong( BasicUploadSettingsPanel.HISTORY_LAST_USED_KEY, 0);313 return Config.getPref().getLong(COMMENT_LAST_USED_KEY, 0); 335 314 } 336 315 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(); 344 326 } 327 if (tag.equals("source")) { 328 hcbUploadSource.addCurrentItemToHistory(); 329 } 330 } 345 331 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; 350 351 } 351 352 } 352 353 } 353 354 354 355 /** 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 356 359 */ 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 } 358 366 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; 365 377 } 378 return null; 379 } 366 380 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 } 370 389 371 @Override 372 public void actionPerformed(ActionEvent e) { 373 setComment(); 374 } 390 @Override 391 public void focusGained(FocusEvent e) { 392 } 375 393 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); 379 403 } 404 } 380 405 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 } 384 418 } 385 386 @Override387 public void removeUpdate(DocumentEvent e) {388 setComment();389 }390 391 @Override392 public void changedUpdate(DocumentEvent e) {393 setComment();394 }395 419 } 396 420 397 421 /** 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. 400 423 */ 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; 415 434 } 416 435 } 417 436 } 418 437 419 438 /** 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. 422 440 */ 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(); 431 445 } 432 446 } 447 448 @Override 449 public void keyPressed(KeyEvent e) { 450 } 451 452 @Override 453 public void keyReleased(KeyEvent e) { 454 } 433 455 } -
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 edited14 * in the {@link UploadDialog}.15 * @since 313316 */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 1310946 */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
28 28 import org.openstreetmap.josm.gui.widgets.JMultilineLabel; 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 34 33 /** … … 55 54 private JCheckBox cbCloseAfterUpload; 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 * @param uploadDialogModel The tag editor model. 64 * 65 * @since xxx (signature) 63 66 */ 64 public ChangesetManagementPanel( ChangesetCommentModel changesetCommentModel) {65 CheckParameterUtil.ensureParameterNotNull(changesetCommentModel, "changesetCommentModel");67 public ChangesetManagementPanel(UploadDialogModel uploadDialogModel) { 68 this.uploadDialogModel = uploadDialogModel; 66 69 build(); 67 70 refreshGUI(); 68 71 } … … 272 275 } 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 } 277 281 } … … 279 283 280 284 /** 281 285 * Refreshes the list of open changesets 282 *283 286 */ 284 287 class RefreshAction extends AbstractAction { 285 288 RefreshAction() { … … 295 298 296 299 /** 297 300 * Closes the currently selected changeset 298 *299 301 */ 300 302 class CloseChangesetAction extends AbstractAction implements ItemListener { 301 303 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 requested8 * in the {@link UploadDialog}.9 * @since 1271910 */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 state18 */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 requested31 */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
65 65 * Handles illegal chunk size. 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 } -
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 259924 */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 panel36 *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 null41 * @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 panel87 */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 tags95 * @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 input104 */105 public void startUserInput() {106 pnlTagEditor.initAutoCompletion(MainApplication.getLayerManager().getEditLayer());107 }108 109 /* -------------------------------------------------------------------------- */110 /* Interface TableChangeListener */111 /* -------------------------------------------------------------------------- */112 @Override113 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 #5676121 */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 sync130 * with the current changeset comment131 */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 @Override147 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 sync168 * with the current changeset review request169 */170 class ChangesetReviewChangeListener implements ChangeListener {171 172 private static final String KEY = "review_requested";173 174 @Override175 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
17 17 import java.beans.PropertyChangeListener; 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; 26 24 import java.util.Map; 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 32 29 import javax.swing.AbstractAction; … … 39 36 import javax.swing.border.TitledBorder; 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; 45 41 import org.openstreetmap.josm.data.osm.OsmPrimitive; … … 47 43 import org.openstreetmap.josm.gui.MainApplication; 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; 52 49 import org.openstreetmap.josm.gui.util.WindowGeometry; … … 60 57 import org.openstreetmap.josm.tools.GBC; 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 65 63 /** … … 71 69 /** the unique instance of the upload dialog */ 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; 79 74 /** the panel to select the changeset used */ … … 84 79 private UploadStrategySelectionPanel pnlUploadStrategySelectionPanel; 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; 91 88 /** the upload button */ 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; 100 95 … … 137 132 tpConfigPanels = new CompactTabbedPane(); 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("De cide how to upload the data and which changeset to use"));138 tpConfigPanels.setToolTipTextAt(0, tr("Describe the changes you made")); 144 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()); 158 158 pnl.add(splitPane, BorderLayout.CENTER); … … 180 180 CancelAction cancelAction = new CancelAction(this); 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")); 185 187 return pnl; … … 200 202 pnlChangesetManagement.addPropertyChangeListener( 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() 210 212 ); … … 217 219 () -> tpConfigPanels.setSelectedIndex(2) 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( 224 224 pnlBasicUploadSettings.getUploadTextValidators().stream().noneMatch(UploadTextComponentValidator::isUploadRejected)))); … … 229 229 } 230 230 231 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 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 /** 232 250 * Sets the collection of primitives to upload 233 251 * 234 252 * @param toUpload the dataset with the objects to upload. If null, assumes the empty … … 252 270 } 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 273 * Sets the input focus to upload button. 274 * @since xxx 264 275 */ 265 public void set ChangesetTags(DataSet dataSet) {266 setChangesetTags(dataSet, false);276 public void setFocusToUploadButton() { 277 btnUpload.requestFocus(); 267 278 } 268 279 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 dataset279 * @param keepSourceComment if {@code true}, keep upload {@code source} and {@code comment} current values from models280 */281 private void setChangesetTags(DataSet dataSet, boolean keepSourceComment) {282 final Map<String, String> tags = new HashMap<>();283 284 // obtain from previous input285 if (!keepSourceComment) {286 tags.put("source", getLastChangesetSourceFromHistory());287 tags.put("comment", getCommentWithDataSetHashTag(getLastChangesetCommentFromHistory(), dataSet));288 }289 290 // obtain from dataset291 if (dataSet != null) {292 tags.putAll(dataSet.getChangeSetTags());293 }294 this.dataSet = dataSet;295 296 // obtain from selected open changeset297 if (pnlChangesetManagement.getSelectedChangeset() != null) {298 tags.putAll(pnlChangesetManagement.getSelectedChangeset().getKeys());299 }300 301 // set/adapt created_by302 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 values311 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 null330 * @param dataSet optional dataset, which can contain hashtags in its changeset tags331 * @return comment with dataset changesets tags, if any, not duplicated332 */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 350 280 @Override 351 281 public void rememberUserInput() { 352 282 pnlBasicUploadSettings.rememberUserInput(); … … 359 289 public void startUserInput() { 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(); 365 295 pnl.setUploadStrategySpecification(pnlUploadStrategySelectionPanel.getUploadStrategySpecification()); … … 374 304 */ 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 } 380 310 … … 394 324 return spec; 395 325 } 396 326 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 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 407 347 @Override … … 495 435 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()); 507 447 final boolean ignoreKey = "comment".equals(i.getKey()) || "source".equals(i.getKey()); … … 588 528 public void propertyChange(PropertyChangeEvent evt) { 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")); 594 533 } else { … … 609 548 case "osm-server.url": 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; 618 553 } … … 629 564 setTitle(tr("Upload to ''{0}''", url)); 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 662 576 @Override … … 676 590 tpConfigPanels.setSelectedIndex(0); 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 xxx 601 */ 602 public void saveEdits() { 603 pnlBasicUploadSettings.saveEdits(); 604 pnlTagEditor.saveEdits(); 684 605 } 685 606 686 607 /** -
src/org/openstreetmap/josm/gui/io/UploadDialogModel.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.LinkedHashSet; 6 import java.util.List; 7 import java.util.Map; 8 import java.util.Set; 9 import java.util.stream.Collectors; 10 11 import org.openstreetmap.josm.data.Version; 12 import org.openstreetmap.josm.data.osm.DataSet; 13 import org.openstreetmap.josm.gui.tagging.TagEditorModel; 14 import org.openstreetmap.josm.gui.tagging.TagModel; 15 import org.openstreetmap.josm.spi.preferences.Config; 16 import org.openstreetmap.josm.tools.Utils; 17 18 /** 19 * A model for the upload dialog 20 * 21 * @since xxx 22 */ 23 public 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
22 22 import org.openstreetmap.josm.data.projection.Projection; 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; 28 27 import org.openstreetmap.josm.gui.widgets.HistoryComboBox; 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; 34 32 import org.openstreetmap.josm.tools.Logging; … … 63 61 private void build(String initialText, final ActionListener listener) { 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 69 66 protected JosmTextField createEditorComponent() { … … 73 70 List<String> samples = Arrays.asList( 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 79 76 final HtmlPanel errorsPanel = new HtmlPanel(); … … 145 142 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 } 151 148 -
src/org/openstreetmap/josm/gui/preferences/server/OsmApiUrlInputPanel.java
108 108 */ 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); 114 114 propagator.propagate(Config.getUrls().getDefaultOsmApiUrl()); … … 130 130 } else { 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(); 136 136 -
src/org/openstreetmap/josm/gui/preferences/server/OverpassServerPanel.java
41 41 * Initializes the panel from preferences 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()); 47 47 } … … 51 51 */ 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 } 57 57 } -
src/org/openstreetmap/josm/gui/tagging/TagEditorModel.java
645 645 this.endEditListener = endEditListener; 646 646 } 647 647 648 pr ivatevoid commitPendingEdit() {648 protected void commitPendingEdit() { 649 649 if (endEditListener != null) { 650 650 endEditListener.endCellEditing(); 651 651 } -
src/org/openstreetmap/josm/gui/tagging/TagEditorPanel.java
221 221 model.getTags(), presetHandler); 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 xxx 229 */ 230 public void saveEdits() { 231 tagTable.endCellEditing(); 232 } 224 233 } -
src/org/openstreetmap/josm/gui/tagging/ac/AutoCompComboBox.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.ac; 3 4 import java.awt.datatransfer.Clipboard; 5 import java.awt.datatransfer.StringSelection; 6 import java.awt.datatransfer.Transferable; 7 import java.awt.event.FocusEvent; 8 import java.awt.event.FocusListener; 9 import java.awt.event.KeyEvent; 10 import java.awt.event.KeyListener; 11 import java.awt.im.InputContext; 12 import java.util.Collection; 13 import java.util.Collections; 14 import java.util.LinkedList; 15 import java.util.Locale; 16 import java.util.Objects; 17 import java.util.regex.Pattern; 18 19 import javax.swing.JTextField; 20 import javax.swing.SwingUtilities; 21 import javax.swing.text.AbstractDocument; 22 import javax.swing.text.AttributeSet; 23 import javax.swing.text.BadLocationException; 24 import javax.swing.text.DocumentFilter; 25 import javax.swing.text.JTextComponent; 26 import javax.swing.text.StyleConstants; 27 28 import org.openstreetmap.josm.gui.MainApplication; 29 import org.openstreetmap.josm.gui.MapFrame; 30 import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils; 31 import org.openstreetmap.josm.gui.widgets.JosmComboBox; 32 import org.openstreetmap.josm.spi.preferences.Config; 33 import 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 */ 47 public 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. 2 package org.openstreetmap.josm.gui.tagging.ac; 3 4 import java.util.ArrayList; 5 import java.util.Collection; 6 import java.util.Comparator; 7 import java.util.Iterator; 8 import java.util.List; 9 import java.util.Objects; 10 import java.util.function.Function; 11 12 import javax.swing.AbstractListModel; 13 import javax.swing.MutableComboBoxModel; 14 15 import org.openstreetmap.josm.data.preferences.ListProperty; 16 import 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 */ 25 public 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
1 1 // License: GPL. For details, see LICENSE file. 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 } -
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} items22 * @param size the history size23 */24 ComboBoxHistory(int size) {25 maxSize = size;26 }27 28 /**29 * Adds or moves an element to the top of the history30 * @param s the element to add31 */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 history38 * @param o the element to add39 */40 @Override41 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 top47 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 top61 insertElementAt(o, 0);62 }63 64 // remove an element, if the history gets too large65 if (getSize() > maxSize) {66 removeElementAt(getSize()-1);67 }68 69 // set selected item to the one just added70 setSelectedItem(o);71 }72 73 @Override74 public Iterator<AutoCompletionItem> iterator() {75 return new Iterator<AutoCompletionItem>() {76 77 private int position = -1;78 79 @Override80 public void remove() {81 removeElementAt(position);82 }83 84 @Override85 public boolean hasNext() {86 return position < getSize()-1 && getSize() > 0;87 }88 89 @Override90 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 set103 */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 strings113 * @return a list of strings114 */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
3 3 4 4 import java.util.List; 5 5 6 import javax.swing.text.JTextComponent;6 import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBox; 7 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;11 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; 13 public class HistoryComboBox extends AutoCompComboBox<String> { 17 14 18 15 /** 19 * The default size of the search history.20 */21 public static final int DEFAULT_SEARCH_HISTORY_SIZE = 15;22 23 /**24 16 * Constructs a new {@code HistoryComboBox}. 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"); 31 21 } 32 22 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; 40 26 } 41 27 42 28 /** 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. 46 31 */ 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 history55 * @see ComboBoxHistory#addElement(java.lang.String)56 */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 } 33 String newItem = getModel().addTopElement(getEditor().getItem().toString()); 34 getModel().setSelectedItem(newItem); 65 35 } 66 36 67 37 /** 68 * Sets the elements of the ComboBox to the given items69 * @param history the items to set70 * @see ComboBoxHistory#setItemsAsString(java.util.List)71 */72 public void setHistory(List<String> history) {73 model.setItemsAsString(history);74 }75 76 /**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 } -
src/org/openstreetmap/josm/gui/widgets/HistoryComboBoxModel.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.List; 6 7 import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel; 8 import 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 */ 18 public 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
109 109 } 110 110 111 111 /** 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 /** 112 132 * Finds the prototype display value to use among the given possible candidates. 113 133 * @param possibleValues The possible candidates that will be iterated. 114 134 * @return The value that needs the largest display height on screen. … … 163 183 .orElse(null); 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. 175 195 // This assumes that items have the same height as the combo box, which is not granted by the look and feel … … 188 208 } 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) { 193 221 addPropertyChangeListener("editable", handler); -
test/unit/org/openstreetmap/josm/gui/io/BasicUploadSettingsPanelTest.java
1 1 // License: GPL. For details, see LICENSE file. 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; 5 7 8 import java.util.Arrays; 9 import java.util.List; 10 11 import org.openstreetmap.josm.spi.preferences.Config; 12 6 13 import org.openstreetmap.josm.testutils.annotations.BasicPreferences; 7 14 8 15 import org.junit.jupiter.api.Test; … … 17 24 */ 18 25 @Test 19 26 void testBasicUploadSettingsPanel() { 20 assertNotNull(new BasicUploadSettingsPanel(new ChangesetCommentModel(), new ChangesetCommentModel(), new ChangesetReviewModel()));27 assertNotNull(new BasicUploadSettingsPanel(new UploadDialogModel())); 21 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()); 64 } 22 65 } -
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 tests22 */23 @RegisterExtension24 @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")25 public JOSMTestRules test = new JOSMTestRules();26 27 /**28 * Test of {@link ChangesetCommentModel#findHashTags}.29 */30 @Test31 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
4 4 import static org.junit.jupiter.api.Assertions.assertNotNull; 5 5 6 6 import org.junit.jupiter.api.Test; 7 7 8 import org.openstreetmap.josm.testutils.annotations.BasicPreferences; 8 9 9 10 /** … … 16 17 */ 17 18 @Test 18 19 void testChangesetManagementPanel() { 19 assertNotNull(new ChangesetManagementPanel(new ChangesetCommentModel()));20 assertNotNull(new ChangesetManagementPanel(new UploadDialogModel())); 20 21 } 21 22 } -
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 tests19 */20 @RegisterExtension21 @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 @Test28 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. 2 package org.openstreetmap.josm.gui.io; 3 4 import static org.junit.jupiter.api.Assertions.assertNotNull; 5 import static org.junit.jupiter.api.Assertions.assertNull; 6 import static org.junit.jupiter.api.Assertions.assertEquals; 7 8 import org.junit.jupiter.api.extension.RegisterExtension; 9 import org.junit.jupiter.api.Test; 10 11 import org.openstreetmap.josm.data.osm.DataSet; 12 import org.openstreetmap.josm.testutils.JOSMTestRules; 13 14 /** 15 * Unit tests of {@link UploadDialogModel} class. 16 */ 17 18 public 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
12 12 import java.util.List; 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; 18 17 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; 24 22 import org.openstreetmap.josm.spi.preferences.Config; … … 81 79 public Map<String, String> getTags(boolean keepEmpty) { 82 80 return new ConcurrentHashMap<>(); 83 81 } 84 85 @Override86 public void forceUpdateActiveField() {87 // Do nothing88 }89 82 } 90 83 91 84 /** … … 115 108 assertTrue(UploadDialog.UploadAction.isUploadCommentTooShort("\u0860")); 116 109 } 117 110 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 }152 153 111 private static void doTestValidateUploadTag(String prefix) { 154 112 List<String> def = Collections.emptyList(); 155 113 Config.getPref().putList(prefix + ".mandatory-terms", null); … … 185 143 doTestValidateUploadTag("upload.comment"); 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 } -
test/unit/org/openstreetmap/josm/gui/tagging/ac/AutoCompComboBoxModelTest.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.ac; 3 4 import static org.junit.jupiter.api.Assertions.assertEquals; 5 import static org.junit.jupiter.api.Assertions.assertNotNull; 6 import static org.junit.jupiter.api.Assertions.assertNull; 7 import static org.junit.jupiter.api.Assertions.assertSame; 8 9 import java.util.Comparator; 10 import java.util.LinkedHashMap; 11 import java.util.Map; 12 13 import org.openstreetmap.josm.data.osm.search.SearchSetting; 14 import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem; 15 import org.openstreetmap.josm.data.tagging.ac.AutoCompletionPriority; 16 17 import org.junit.jupiter.api.Test; 18 import org.openstreetmap.josm.testutils.annotations.FullPreferences; 19 20 /** 21 * Test class for {@link AutoCompComboBoxModel} 22 */ 23 @FullPreferences 24 class 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. 2 package org.openstreetmap.josm.gui.tagging.ac; 3 4 import static org.junit.jupiter.api.Assertions.assertNotNull; 5 6 import org.junit.jupiter.api.Test; 7 import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem; 8 import org.openstreetmap.josm.testutils.annotations.FullPreferences; 9 10 /** 11 * Test class for {@link AutoCompletingComboBox} 12 */ 13 @FullPreferences 14 class AutoCompComboBoxTest { 15 16 @Test 17 void testAutoCompletingComboBox() { 18 assertNotNull(new AutoCompComboBox<String>()); 19 assertNotNull(new AutoCompComboBox<AutoCompletionItem>()); 20 } 21 }
