- Timestamp:
- 2021-10-06T13:24:19+02:00 (3 years ago)
- Location:
- trunk/src/org/openstreetmap/josm/gui/tagging/presets
- Files:
-
- 8 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItemGuiSupport.java
r18221 r18254 25 25 26 26 private final Collection<OsmPrimitive> selected; 27 /** True if all selected primitives matched this preset at the moment it was openend. */ 27 28 private final boolean presetInitiallyMatches; 28 29 private final Supplier<Collection<Tag>> changedTagsSupplier; … … 84 85 85 86 /** 86 * Returns whether the preset initially matched(before opening the dialog)87 * Returns true if all selected primitives matched this preset (before opening the dialog) 87 88 * 88 * @return whetherthe preset initially matched89 * @return true if the preset initially matched 89 90 */ 90 91 public boolean isPresetInitiallyMatches() { -
trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Check.java
r18221 r18254 40 40 // find out if our key is already used in the selection. 41 41 final Usage usage = determineBooleanUsage(support.getSelected(), key); 42 final String oneValue = usage. values.isEmpty() ? null : usage.values.last();42 final String oneValue = usage.map.isEmpty() ? null : usage.map.lastKey(); 43 43 def = "on".equals(default_) ? Boolean.TRUE : "off".equals(default_) ? Boolean.FALSE : null; 44 44 45 45 initializeLocaleText(null); 46 46 47 if (usage. values.size() < 2 && (oneValue == null || value_on.equals(oneValue) || value_off.equals(oneValue))) {47 if (usage.map.size() < 2 && (oneValue == null || value_on.equals(oneValue) || value_off.equals(oneValue))) { 48 48 if (def != null && !PROP_FILL_DEFAULT.get()) { 49 49 // default is set and filling default values feature is disabled - check if all primitives are untagged -
trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Combo.java
r18221 r18254 13 13 import java.util.Arrays; 14 14 import java.util.Comparator; 15 import java.util.TreeMap; 15 16 16 17 import javax.swing.AbstractAction; … … 18 19 import javax.swing.JColorChooser; 19 20 import javax.swing.JComponent; 21 import javax.swing.JLabel; 20 22 import javax.swing.JPanel; 21 23 … … 30 32 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport; 31 33 import org.openstreetmap.josm.gui.widgets.JosmComboBox; 34 import org.openstreetmap.josm.gui.widgets.OrientationAction; 32 35 import org.openstreetmap.josm.tools.ColorHelper; 33 36 import org.openstreetmap.josm.tools.GBC; … … 75 78 } 76 79 80 private void addEntry(PresetListEntry entry) { 81 if (!seenValues.containsKey(entry.value)) { 82 dropDownModel.addElement(entry); 83 seenValues.put(entry.value, entry); 84 } 85 } 86 77 87 @Override 78 protected JComponent addToPanelAnchor(JPanel p, String def, TaggingPresetItemGuiSupport support) { 79 if (!usage.unused()) { 80 for (String s : usage.values) { 81 presetListEntries.add(new PresetListEntry(s)); 82 } 83 } 84 if (def != null) { 85 presetListEntries.add(new PresetListEntry(def)); 86 } 87 presetListEntries.add(new PresetListEntry("")); 88 88 protected boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support) { 89 initializeLocaleText(null); 90 usage = determineTextUsage(support.getSelected(), key); 91 seenValues = new TreeMap<>(); 92 // get the standard values from the preset definition 93 initListEntries(); 94 95 // init the model 89 96 dropDownModel = new AutoCompComboBoxModel<PresetListEntry>(Comparator.naturalOrder()); 90 autoCompModel = new AutoCompComboBoxModel<AutoCompletionItem>(Comparator.naturalOrder()); 91 presetListEntries.forEach(dropDownModel::addElement); 97 98 if (!usage.hasUniqueValue() && !usage.unused()) { 99 addEntry(PresetListEntry.ENTRY_DIFFERENT); 100 } 101 presetListEntries.forEach(this::addEntry); 102 if (default_ != null) { 103 addEntry(new PresetListEntry(default_, this)); 104 } 105 addEntry(PresetListEntry.ENTRY_EMPTY); 106 107 usage.map.forEach((value, count) -> { 108 addEntry(new PresetListEntry(value, this)); 109 }); 92 110 93 111 combobox = new JosmComboBox<>(dropDownModel); … … 98 116 // the dropdown list. We don't want that to happen because we want to show taller items in 99 117 // the list than in the editor. We can't use 100 // {@code combobox.setPrototypeDisplayValue( new PresetListEntry(" "));} because that would118 // {@code combobox.setPrototypeDisplayValue(PresetListEntry.ENTRY_EMPTY);} because that would 101 119 // set a fixed cell height in JList. 102 120 combobox.setPreferredHeight(combobox.getPreferredSize().height); … … 107 125 combobox.setEditable(editable); 108 126 127 autoCompModel = new AutoCompComboBoxModel<AutoCompletionItem>(Comparator.naturalOrder()); 109 128 getAllForKeys(Arrays.asList(key)).forEach(autoCompModel::addElement); 110 129 getDisplayValues().forEach(s -> autoCompModel.addElement(new AutoCompletionItem(s, AutoCompletionPriority.IS_IN_STANDARD))); … … 120 139 } 121 140 141 JLabel label = addLabel(p); 142 122 143 if (key != null && ("colour".equals(key) || key.startsWith("colour:") || key.endsWith(":colour"))) { 123 p.add(combobox, GBC.std().fill(GBC.HORIZONTAL)); 144 p.add(combobox, GBC.std().fill(GBC.HORIZONTAL)); // NOSONAR 124 145 JButton button = new JButton(new ChooseColorAction()); 125 146 button.setOpaque(true); 126 147 button.setBorderPainted(false); 127 148 button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); 128 p.add(button, GBC.eol().fill(GBC.VERTICAL)); 149 p.add(button, GBC.eol().fill(GBC.VERTICAL)); // NOSONAR 129 150 ActionListener updateColor = ignore -> button.setBackground(getColor()); 130 151 updateColor.actionPerformed(null); 131 152 combobox.addActionListener(updateColor); 132 153 } else { 133 p.add(combobox, GBC.eol().fill(GBC.HORIZONTAL)); 134 } 135 136 Object itemToSelect = getItemToSelect(default_, support, false); 137 combobox.setSelectedItemText(itemToSelect == null ? null : itemToSelect.toString()); 138 combobox.addActionListener(l -> support.fireItemValueModified(this, key, getSelectedValue())); 154 p.add(combobox, GBC.eol().fill(GBC.HORIZONTAL)); // NOSONAR 155 } 156 157 String valueToSelect = getInitialValue(default_); 158 if (valueToSelect != null) { 159 PresetListEntry selItem = find(valueToSelect); 160 if (selItem != null) { 161 combobox.setSelectedItem(selItem); 162 } else { 163 combobox.setText(valueToSelect); 164 } 165 } 166 combobox.addActionListener(l -> support.fireItemValueModified(this, key, getSelectedItem().value)); 139 167 combobox.addComponentListener(new ComponentListener()); 140 return combobox; 168 169 label.setLabelFor(combobox); 170 combobox.setToolTipText(getKeyTooltipText()); 171 combobox.applyComponentOrientation(OrientationAction.getValueOrientation(key)); 172 173 return true; 174 } 175 176 /** 177 * Finds the PresetListEntry that matches value. 178 * <p> 179 * Looks in the model for an element whose {@code value} matches {@code value}. 180 * 181 * @param value The value to match. 182 * @return The entry or null 183 */ 184 private PresetListEntry find(String value) { 185 return dropDownModel.asCollection().stream().filter(o -> o.value.equals(value)).findAny().orElse(null); 141 186 } 142 187 … … 161 206 162 207 protected Color getColor() { 163 String colorString = String.valueOf(getSelectedValue());208 String colorString = getSelectedItem().value; 164 209 return colorString.startsWith("#") 165 210 ? ColorHelper.html2color(colorString) … … 168 213 169 214 @Override 170 protected Object getSelectedItem() { 171 return combobox.getSelectedItem(); 172 } 173 174 @Override 175 protected String getDisplayIfNull() { 176 if (combobox.isEditable()) 177 return combobox.getEditor().getItem().toString(); 178 else 179 return null; 215 protected PresetListEntry getSelectedItem() { 216 Object sel = combobox.getSelectedItem(); 217 if (sel instanceof PresetListEntry) 218 // selected from the dropdown 219 return (PresetListEntry) sel; 220 if (sel instanceof String) { 221 // free edit. If the free edit corresponds to a known entry, use that entry. This is 222 // to avoid that we write a display_value to the tag's value, eg. if the user did an 223 // undo. 224 PresetListEntry selItem = dropDownModel.find((String) sel); 225 if (selItem != null) 226 return selItem; 227 return new PresetListEntry((String) sel, this); 228 } 229 return PresetListEntry.ENTRY_EMPTY; 180 230 } 181 231 } -
trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/ComboMultiSelect.java
r18251 r18254 3 3 4 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 import static org.openstreetmap.josm.tools.I18n.trc;6 5 7 6 import java.awt.Component; … … 13 12 import java.util.Collections; 14 13 import java.util.List; 15 import java.util.Objects; 16 import java.util.Set; 17 import java.util.concurrent.CopyOnWriteArraySet; 14 import java.util.Map; 15 import java.util.TreeMap; 18 16 import java.util.stream.Collectors; 19 17 20 import javax.swing.JComponent;21 18 import javax.swing.JLabel; 22 19 import javax.swing.JList; … … 25 22 26 23 import org.openstreetmap.josm.data.osm.Tag; 27 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;28 24 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetSelector; 29 25 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets; 30 26 import org.openstreetmap.josm.gui.widgets.JosmListCellRenderer; 31 27 import org.openstreetmap.josm.gui.widgets.OrientationAction; 28 import org.openstreetmap.josm.tools.AlphanumComparator; 32 29 import org.openstreetmap.josm.tools.GBC; 33 30 import org.openstreetmap.josm.tools.Logging; 34 import org.openstreetmap.josm.tools.Utils;35 31 36 32 /** … … 89 85 public boolean values_searchable; // NOSONAR 90 86 91 protected final Set<PresetListEntry> presetListEntries = new CopyOnWriteArraySet<>(); 92 private boolean initialized; 87 /** 88 * The standard entries in the combobox dropdown or multiselect list. These entries are defined 89 * in {@code defaultpresets.xml} (or in other custom preset files). 90 */ 91 protected final List<PresetListEntry> presetListEntries = new ArrayList<>(); 92 /** Helps avoid duplicate list entries */ 93 protected Map<String, PresetListEntry> seenValues = new TreeMap<>(); 93 94 protected Usage usage; 94 protected Object originalValue; 95 /** Used to see if the user edited the value. May be null. */ 96 protected String originalValue; 95 97 96 98 /** … … 134 136 l.setText(value.getListDisplay(width)); 135 137 } 136 String tt = value.value; 137 if (tt != null && !tt.isEmpty()) { 138 l.setToolTipText(tr("Sets the key ''{0}'' to the value ''{1}''.", key, tt)); 139 } else { 140 l.setToolTipText(tr("Clears the key ''{0}''.", key)); 141 } 138 l.setToolTipText(value.getToolTipText(key)); 142 139 l.setIcon(value.getIcon()); 143 140 return l; … … 153 150 * @return splitted items 154 151 */ 155 public static String[]splitEscaped(char delimiter, String s) {152 public static List<String> splitEscaped(char delimiter, String s) { 156 153 if (s == null) 157 return new String[0]; 154 return null; // NOSONAR 155 158 156 List<String> result = new ArrayList<>(); 159 157 boolean backslash = false; … … 176 174 result.add(item.toString()); 177 175 } 178 return result.toArray(new String[0]); 179 } 180 181 protected abstract Object getSelectedItem(); 182 183 protected abstract JComponent addToPanelAnchor(JPanel p, String def, TaggingPresetItemGuiSupport support); 176 return result; 177 } 178 179 /** 180 * Returns the value selected in the combobox or a synthetic value if a multiselect. 181 * 182 * @return the value 183 */ 184 protected abstract PresetListEntry getSelectedItem(); 184 185 185 186 @Override 186 187 public Collection<String> getValues() { 187 initListEntries( false);188 initListEntries(); 188 189 return presetListEntries.stream().map(x -> x.value).collect(Collectors.toSet()); 189 190 } … … 194 195 */ 195 196 public Collection<String> getDisplayValues() { 196 initListEntries( false);197 initListEntries(); 197 198 return presetListEntries.stream().map(PresetListEntry::getDisplayValue).collect(Collectors.toList()); 198 199 } 199 200 200 @Override 201 public boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support) { 202 initListEntries(true); 203 204 // find out if our key is already used in the selection. 205 usage = determineTextUsage(support.getSelected(), key); 206 if (!usage.hasUniqueValue() && !usage.unused()) { 207 presetListEntries.add(new PresetListEntry(DIFFERENT)); 208 } 209 201 /** 202 * Adds the label to the panel 203 * 204 * @param p the panel 205 * @return the label 206 */ 207 protected JLabel addLabel(JPanel p) { 210 208 final JLabel label = new JLabel(tr("{0}:", locale_text)); 211 209 addIcon(label); … … 214 212 label.applyComponentOrientation(OrientationAction.getDefaultComponentOrientation()); 215 213 p.add(label, GBC.std().insets(0, 0, 10, 0)); 216 JComponent component = addToPanelAnchor(p, default_, support); 217 label.setLabelFor(component); 218 component.setToolTipText(getKeyTooltipText()); 219 component.applyComponentOrientation(OrientationAction.getValueOrientation(key)); 220 221 return true; 222 } 223 224 private void initListEntries(boolean cleanup) { 225 if (initialized) { 226 if (cleanup) { // do not cleanup for #getDisplayValues used in Combo#addToPanelAnchor 227 presetListEntries.remove(new PresetListEntry(DIFFERENT)); // possibly added in #addToPanel 214 return label; 215 } 216 217 protected void initListEntries() { 218 if (presetListEntries.isEmpty()) { 219 initListEntriesFromAttributes(); 220 } 221 } 222 223 private List<String> getValuesFromCode(String values_from) { 224 // get the values from a Java function 225 String[] classMethod = values_from.split("#", -1); 226 if (classMethod.length == 2) { 227 try { 228 Method method = Class.forName(classMethod[0]).getMethod(classMethod[1]); 229 // Check method is public static String[] methodName() 230 int mod = method.getModifiers(); 231 if (Modifier.isPublic(mod) && Modifier.isStatic(mod) 232 && method.getReturnType().equals(String[].class) && method.getParameterTypes().length == 0) { 233 return Arrays.asList((String[]) method.invoke(null)); 234 } else { 235 Logging.error(tr("Broken tagging preset \"{0}-{1}\" - Java method given in ''values_from'' is not \"{2}\"", key, text, 236 "public static String[] methodName()")); 237 } 238 } catch (ReflectiveOperationException e) { 239 Logging.error(tr("Broken tagging preset \"{0}-{1}\" - Java method given in ''values_from'' threw {2} ({3})", key, text, 240 e.getClass().getName(), e.getMessage())); 241 Logging.debug(e); 228 242 } 243 } 244 return null; // NOSONAR 245 } 246 247 /** 248 * Checks if list {@code a} is either null or the same length as list {@code b}. 249 * 250 * @param a The list to check 251 * @param b The other list 252 * @param name The name of the list for error reporting 253 * @return {@code a} if both lists have the same length or {@code null} 254 */ 255 private List<String> checkListsSameLength(List<String> a, List<String> b, String name) { 256 if (a != null && a.size() != b.size()) { 257 Logging.error(tr("Broken tagging preset \"{0}-{1}\" - number of items in ''{2}List'' must be the same as in ''values''", 258 key, text, name)); 259 Logging.error(tr("Detailed information: {0} <> {1}", a, b)); 260 return null; // NOSONAR 261 } 262 return a; 263 } 264 265 protected void initListEntriesFromAttributes() { 266 List<String> valueList = null; 267 List<String> displayList = null; 268 List<String> localeDisplayList = null; 269 270 if (values_from != null) { 271 valueList = getValuesFromCode(values_from); 272 } 273 274 if (valueList == null) { 275 // get from {@code values} attribute 276 valueList = splitEscaped(delimiter, values); 277 } 278 if (valueList == null) { 229 279 return; 230 } else if (presetListEntries.isEmpty()) { 231 initListEntriesFromAttributes(); 232 } else { 233 if (values != null) { 234 Logging.warn(tr("Warning in tagging preset \"{0}-{1}\": " 235 + "Ignoring ''{2}'' attribute as ''{3}'' elements are given.", 236 key, text, "values", "list_entry")); 237 } 238 if (display_values != null || locale_display_values != null) { 239 Logging.warn(tr("Warning in tagging preset \"{0}-{1}\": " 240 + "Ignoring ''{2}'' attribute as ''{3}'' elements are given.", 241 key, text, "display_values", "list_entry")); 242 } 243 if (short_descriptions != null || locale_short_descriptions != null) { 244 Logging.warn(tr("Warning in tagging preset \"{0}-{1}\": " 245 + "Ignoring ''{2}'' attribute as ''{3}'' elements are given.", 246 key, text, "short_descriptions", "list_entry")); 247 } 248 for (PresetListEntry e : presetListEntries) { 249 if (e.value_context == null) { 250 e.value_context = values_context; 251 } 252 } 253 } 254 initializeLocaleText(null); 255 initialized = true; 256 } 257 258 private void initListEntriesFromAttributes() { 259 260 String[] valueArray = null; 261 262 if (values_from != null) { 263 String[] classMethod = values_from.split("#", -1); 264 if (classMethod.length == 2) { 265 try { 266 Method method = Class.forName(classMethod[0]).getMethod(classMethod[1]); 267 // Check method is public static String[] methodName() 268 int mod = method.getModifiers(); 269 if (Modifier.isPublic(mod) && Modifier.isStatic(mod) 270 && method.getReturnType().equals(String[].class) && method.getParameterTypes().length == 0) { 271 valueArray = (String[]) method.invoke(null); 272 } else { 273 Logging.error(tr("Broken tagging preset \"{0}-{1}\" - Java method given in ''values_from'' is not \"{2}\"", key, text, 274 "public static String[] methodName()")); 275 } 276 } catch (ReflectiveOperationException e) { 277 Logging.error(tr("Broken tagging preset \"{0}-{1}\" - Java method given in ''values_from'' threw {2} ({3})", key, text, 278 e.getClass().getName(), e.getMessage())); 279 Logging.debug(e); 280 } 281 } 282 } 283 284 if (valueArray == null) { 285 valueArray = splitEscaped(delimiter, values); 286 values = null; 287 } 288 289 String[] displayArray = valueArray; 280 } 281 290 282 if (!values_no_i18n) { 291 final String displ = Utils.firstNonNull(locale_display_values, display_values); 292 displayArray = displ == null ? valueArray : splitEscaped(delimiter, displ); 293 } 294 295 final String descr = Utils.firstNonNull(locale_short_descriptions, short_descriptions); 296 String[] shortDescriptionsArray = descr == null ? null : splitEscaped(delimiter, descr); 297 298 if (displayArray.length != valueArray.length) { 299 Logging.error(tr("Broken tagging preset \"{0}-{1}\" - number of items in ''display_values'' must be the same as in ''values''", 300 key, text)); 301 Logging.error(tr("Detailed information: {0} <> {1}", Arrays.toString(displayArray), Arrays.toString(valueArray))); 302 displayArray = valueArray; 303 } 304 305 if (shortDescriptionsArray != null && shortDescriptionsArray.length != valueArray.length) { 306 Logging.error(tr("Broken tagging preset \"{0}-{1}\" - number of items in ''short_descriptions'' must be the same as in ''values''", 307 key, text)); 308 Logging.error(tr("Detailed information: {0} <> {1}", Arrays.toString(shortDescriptionsArray), Arrays.toString(valueArray))); 309 shortDescriptionsArray = null; 310 } 311 312 final List<PresetListEntry> entries = new ArrayList<>(valueArray.length); 313 for (int i = 0; i < valueArray.length; i++) { 314 final PresetListEntry e = new PresetListEntry(valueArray[i]); 315 final String value = locale_display_values != null || values_no_i18n 316 ? displayArray[i] 317 : trc(values_context, fixPresetString(displayArray[i])); 318 e.locale_display_value = value == null ? null : value.intern(); 319 if (shortDescriptionsArray != null) { 320 final String description = locale_short_descriptions != null 321 ? shortDescriptionsArray[i] 322 : tr(fixPresetString(shortDescriptionsArray[i])); 323 e.locale_short_description = description == null ? null : description.intern(); 324 } 325 326 entries.add(e); 283 localeDisplayList = splitEscaped(delimiter, locale_display_values); 284 displayList = splitEscaped(delimiter, display_values); 285 } 286 List<String> localeShortDescriptionsList = splitEscaped(delimiter, locale_short_descriptions); 287 List<String> shortDescriptionsList = splitEscaped(delimiter, short_descriptions); 288 289 displayList = checkListsSameLength(displayList, valueList, "display"); 290 localeDisplayList = checkListsSameLength(localeDisplayList, valueList, "localeDisplay"); 291 shortDescriptionsList = checkListsSameLength(shortDescriptionsList, valueList, "shortDescriptions"); 292 localeShortDescriptionsList = checkListsSameLength(localeShortDescriptionsList, valueList, "localeShortDescriptions"); 293 294 for (int i = 0; i < valueList.size(); i++) { 295 final PresetListEntry e = new PresetListEntry(valueList.get(i), this); 296 if (displayList != null) 297 e.display_value = displayList.get(i); 298 if (localeDisplayList != null) 299 e.locale_display_value = localeDisplayList.get(i); 300 if (shortDescriptionsList != null) 301 e.short_description = shortDescriptionsList.get(i); 302 if (localeShortDescriptionsList != null) 303 e.locale_short_description = localeShortDescriptionsList.get(i); 304 addListEntry(e); 327 305 } 328 306 329 307 if (values_sort && TaggingPresets.SORT_MENU.get()) { 330 Collections.sort(entries); 331 } 332 333 addListEntries(entries); 334 } 335 336 protected String getDisplayIfNull() { 337 return null; 338 } 339 340 protected Object getItemToSelect(String def, TaggingPresetItemGuiSupport support, boolean multi) { 341 final Object itemToSelect; 308 Collections.sort(presetListEntries, (a, b) -> AlphanumComparator.getInstance().compare(a.getDisplayValue(), b.getDisplayValue())); 309 } 310 } 311 312 /** 313 * Returns the initial value to use for this preset. 314 * <p> 315 * The initial value is the value shown in the control when the preset dialogs opens. 316 * 317 * @param def The default value 318 * @return The initial value to use. 319 */ 320 protected String getInitialValue(String def) { 321 String initialValue = null; 322 342 323 if (usage.hasUniqueValue()) { 343 // all items have the same value (and there were no unset items) 344 originalValue = multi ? usage.getFirst() : getListEntry(usage.getFirst()); 345 itemToSelect = originalValue; 346 } else if (def != null && usage.unused()) { 347 // default is set and all items were unset 348 if (!usage.hadKeys() || PROP_FILL_DEFAULT.get() || isForceUseLastAsDefault()) { 349 // selected osm primitives are untagged or filling default feature is enabled 350 if (multi) { 351 itemToSelect = def; 352 } else { 353 PresetListEntry entry = getListEntry(def); 354 itemToSelect = entry == null ? "" : entry.getDisplayValue(); 355 } 356 } else { 357 // selected osm primitives are tagged and filling default feature is disabled 358 itemToSelect = ""; 359 } 360 originalValue = multi ? DIFFERENT : getListEntry(DIFFERENT); 361 } else if (usage.unused()) { 362 // all items were unset (and so is default) 363 originalValue = multi ? null : getListEntry(""); 364 if (LAST_VALUES.containsKey(key) && isUseLastAsDefault() && (!support.isPresetInitiallyMatches() || isForceUseLastAsDefault())) { 365 itemToSelect = getListEntry(LAST_VALUES.get(key)); 366 } else { 367 itemToSelect = originalValue; 368 } 369 } else { 370 originalValue = multi ? DIFFERENT : getListEntry(DIFFERENT); 371 itemToSelect = originalValue; 372 } 373 return itemToSelect; 374 } 375 376 protected String getSelectedValue() { 377 Object obj = getSelectedItem(); 378 String display = obj == null ? getDisplayIfNull() : obj.toString(); 379 380 if (display == null) { 381 return ""; 382 } 383 return presetListEntries.stream() 384 .filter(entry -> Objects.equals(entry.toString(), display)) 385 .findFirst() 386 .map(entry -> entry.value) 387 .map(Utils::removeWhiteSpaces) 388 .orElse(display); 324 // all selected primitives have the same not empty value for this key 325 initialValue = usage.getFirst(); 326 } else if (!usage.unused()) { 327 // at least one primitive has a value for this key (but not all have the same one) 328 initialValue = DIFFERENT; 329 } else if (PROP_FILL_DEFAULT.get() || isForceUseLastAsDefault()) { 330 // at this point no primitive had any value for this key 331 // use the last value no matter what 332 initialValue = LAST_VALUES.get(key); 333 } else if (!usage.hadKeys() && isUseLastAsDefault()) { 334 // use the last value only on objects with no keys at all 335 initialValue = LAST_VALUES.get(key); 336 } else if (!usage.hadKeys()) { 337 // use the default only on objects with no keys at all 338 initialValue = def; 339 } 340 originalValue = initialValue; 341 return initialValue; 389 342 } 390 343 391 344 @Override 392 345 public void addCommands(List<Tag> changedTags) { 393 String value = getSelected Value();346 String value = getSelectedItem().value; 394 347 395 348 // no change if same as before 396 if (originalValue == null) { 397 if (value.isEmpty()) 398 return; 399 } else if (value.equals(originalValue.toString())) 349 if (value.isEmpty() && originalValue == null) 400 350 return; 351 if (value.equals(originalValue)) 352 return; 353 changedTags.add(new Tag(key, value)); 401 354 402 355 if (isUseLastAsDefault()) { 403 356 LAST_VALUES.put(key, value); 404 357 } 405 changedTags.add(new Tag(key, value));406 358 } 407 359 … … 420 372 } 421 373 374 /** 375 * Returns true if the last entered value should be used as default. 376 * <p> 377 * Note: never used in {@code defaultpresets.xml}. 378 * 379 * @return true if the last entered value should be used as default. 380 */ 422 381 protected boolean isUseLastAsDefault() { 423 382 return use_last_as_default > 0; 424 383 } 425 384 385 /** 386 * Returns true if the last entered value should be used as default also on primitives that 387 * already have tags. 388 * <p> 389 * Note: used for {@code addr:*} tags in {@code defaultpresets.xml}. 390 * 391 * @return true if see above 392 */ 426 393 protected boolean isForceUseLastAsDefault() { 427 394 return use_last_as_default == 2; … … 434 401 public void addListEntry(PresetListEntry e) { 435 402 presetListEntries.add(e); 403 // we need to fix the entries because the XML Parser 404 // {@link org.openstreetmap.josm.tools.XmlObjectParser.Parser#startElement} has used the 405 // default standard constructor for {@link PresetListEntry} if the list entry was defined 406 // using XML {@code <list_entry>}. 407 e.cms = this; 436 408 } 437 409 … … 446 418 } 447 419 448 protected PresetListEntry getListEntry(String value) {449 return presetListEntries.stream().filter(e -> Objects.equals(e.value, value)).findFirst().orElse(null);450 }451 452 420 @Override 453 421 public MatchType getDefaultMatch() { -
trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/KeyedItem.java
r17610 r18254 8 8 import java.util.HashMap; 9 9 import java.util.Map; 10 import java.util.SortedMap; 10 11 import java.util.NoSuchElementException; 11 import java.util.SortedSet; 12 import java.util.TreeSet; 12 import java.util.TreeMap; 13 13 14 14 import javax.swing.JPopupMenu; … … 27 27 public abstract class KeyedItem extends TextItem { 28 28 29 /** Translation of "<different>". Use in combo boxes to display an entry matching several different values. */ 30 protected static final String DIFFERENT = tr("<different>"); 31 29 /** The constant value {@code "<different>"}. */ 30 protected static final String DIFFERENT = "<different>"; 31 /** Translation of {@code "<different>"}. */ 32 protected static final String DIFFERENT_I18N = tr(DIFFERENT); 33 34 /** True if the default value should also be set on primitives that already have tags. */ 32 35 protected static final BooleanProperty PROP_FILL_DEFAULT = new BooleanProperty("taggingpreset.fill-default-for-tagged-primitives", false); 33 36 … … 100 103 */ 101 104 public static class Usage { 102 /** 103 * A set of values that were used for this key. 104 */ 105 public final SortedSet<String> values = new TreeSet<>(); // NOSONAR 105 /** Usage count for all values used for this key */ 106 public final SortedMap<String, Integer> map = new TreeMap<>(); 106 107 private boolean hadKeys; 107 108 private boolean hadEmpty; … … 112 113 */ 113 114 public boolean hasUniqueValue() { 114 return values.size() == 1 && !hadEmpty;115 return map.size() == 1 && !hadEmpty; 115 116 } 116 117 … … 120 121 */ 121 122 public boolean unused() { 122 return values.isEmpty();123 return map.isEmpty(); 123 124 } 124 125 … … 129 130 */ 130 131 public String getFirst() { 131 return values.first();132 return map.firstKey(); 132 133 } 133 134 … … 152 153 String v = s.get(key); 153 154 if (v != null) { 154 returnValue. values.add(v);155 returnValue.map.merge(v, 1, Integer::sum); 155 156 } else { 156 157 returnValue.hadEmpty = true; … … 168 169 String booleanValue = OsmUtils.getNamedOsmBoolean(s.get(key)); 169 170 if (booleanValue != null) { 170 returnValue. values.add(booleanValue);171 returnValue.map.merge(booleanValue, 1, Integer::sum); 171 172 } 172 173 } … … 203 204 switch (MatchType.ofString(match)) { 204 205 case NONE: 205 return null; 206 return null; // NOSONAR 206 207 case KEY: 207 208 return tags.containsKey(key) ? Boolean.TRUE : null; -
trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/MultiSelect.java
r18221 r18254 2 2 package org.openstreetmap.josm.gui.tagging.presets.items; 3 3 4 import java.awt.Dimension; 5 import java.util.Arrays; 6 import java.util.List; 7 import java.util.Set; 8 import java.util.TreeSet; 4 import java.util.TreeMap; 5 import java.util.stream.Collectors; 9 6 10 import javax.swing.JComponent; 7 import javax.swing.DefaultListModel; 8 import javax.swing.JLabel; 11 9 import javax.swing.JList; 12 10 import javax.swing.JPanel; 13 11 import javax.swing.JScrollPane; 14 import javax.swing.ListModel;15 12 16 import org.openstreetmap.josm.data.osm.Tag;17 13 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport; 14 import org.openstreetmap.josm.gui.widgets.OrientationAction; 18 15 import org.openstreetmap.josm.tools.GBC; 19 16 … … 28 25 public short rows; // NOSONAR 29 26 30 protected ConcatenatingJList list; 27 /** The model for the JList */ 28 protected final DefaultListModel<PresetListEntry> model = new DefaultListModel<>(); 29 /** The swing component */ 30 protected final JList<PresetListEntry> list = new JList<>(model); 31 31 32 @Override 33 protected JComponent addToPanelAnchor(JPanel p, String def, TaggingPresetItemGuiSupport support) { 34 list = new ConcatenatingJList(delimiter, presetListEntries.toArray(new PresetListEntry[0])); 35 ComboMultiSelectListCellRenderer renderer = new ComboMultiSelectListCellRenderer(list, list.getCellRenderer(), 200, key); 36 list.setCellRenderer(renderer); 37 Object itemToSelect = getItemToSelect(def, support, true); 38 list.setSelectedItem(itemToSelect == null ? null : new PresetListEntry(itemToSelect.toString())); 39 JScrollPane sp = new JScrollPane(list); 40 // if a number of rows has been specified in the preset, 41 // modify preferred height of scroll pane to match that row count. 42 if (rows > 0) { 43 double height = renderer.getListCellRendererComponent(list, 44 new PresetListEntry("x"), 0, false, false).getPreferredSize().getHeight() * rows; 45 sp.setPreferredSize(new Dimension((int) sp.getPreferredSize().getWidth(), (int) height)); 32 private void addEntry(PresetListEntry entry) { 33 if (!seenValues.containsKey(entry.value)) { 34 model.addElement(entry); 35 seenValues.put(entry.value, entry); 46 36 } 47 list.addListSelectionListener(l -> support.fireItemValueModified(this, key, getSelectedValue()));48 p.add(sp, GBC.eol().fill(GBC.HORIZONTAL));49 return list;50 37 } 51 38 52 39 @Override 53 protected Object getSelectedItem() { 54 return list.getSelectedItem(); 40 protected boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support) { 41 initializeLocaleText(null); 42 usage = determineTextUsage(support.getSelected(), key); 43 seenValues = new TreeMap<>(); 44 initListEntries(); 45 46 model.clear(); 47 if (!usage.hasUniqueValue() && !usage.unused()) { 48 addEntry(PresetListEntry.ENTRY_DIFFERENT); 49 } 50 51 String initialValue = getInitialValue(default_); 52 if (initialValue != null) { 53 // add all values already present to the list, otherwise we would remove all 54 // custom entries unknown to the preset 55 for (String value : initialValue.split(String.valueOf(delimiter), -1)) { 56 PresetListEntry e = new PresetListEntry(value, this); 57 addEntry(e); 58 int i = model.indexOf(e); 59 list.addSelectionInterval(i, i); 60 } 61 } 62 63 presetListEntries.forEach(this::addEntry); 64 65 ComboMultiSelectListCellRenderer renderer = new ComboMultiSelectListCellRenderer(list, list.getCellRenderer(), 200, key); 66 list.setCellRenderer(renderer); 67 68 if (rows > 0) { 69 list.setVisibleRowCount(rows); 70 } 71 JLabel label = addLabel(p); 72 p.add(new JScrollPane(list), GBC.eol().fill(GBC.HORIZONTAL)); // NOSONAR 73 label.setLabelFor(list); 74 75 list.addListSelectionListener(l -> support.fireItemValueModified(this, key, getSelectedItem().value)); 76 list.setToolTipText(getKeyTooltipText()); 77 list.applyComponentOrientation(OrientationAction.getValueOrientation(key)); 78 79 return true; 55 80 } 56 81 57 82 @Override 58 public void addCommands(List<Tag> changedTags) { 59 // Do not create any commands if list has been disabled because of an unknown value (fix #8605) 60 if (list.isEnabled()) { 61 super.addCommands(changedTags); 62 } 63 } 64 65 /** 66 * Class that allows list values to be assigned and retrieved as a comma-delimited 67 * string (extracted from TaggingPreset) 68 */ 69 private static class ConcatenatingJList extends JList<PresetListEntry> { 70 private final char delimiter; 71 72 protected ConcatenatingJList(char del, PresetListEntry... o) { 73 super(o); 74 delimiter = del; 75 } 76 77 public void setSelectedItem(Object o) { 78 if (o == null) { 79 clearSelection(); 80 } else { 81 String s = o.toString(); 82 Set<String> parts = new TreeSet<>(Arrays.asList(s.split(String.valueOf(delimiter), -1))); 83 ListModel<PresetListEntry> lm = getModel(); 84 int[] intParts = new int[lm.getSize()]; 85 int j = 0; 86 for (int i = 0; i < lm.getSize(); i++) { 87 final String value = lm.getElementAt(i).value; 88 if (parts.contains(value)) { 89 intParts[j++] = i; 90 parts.remove(value); 91 } 92 } 93 setSelectedIndices(Arrays.copyOf(intParts, j)); 94 // check if we have actually managed to represent the full 95 // value with our presets. if not, cop out; we will not offer 96 // a selection list that threatens to ruin the value. 97 setEnabled(parts.isEmpty()); 98 } 99 } 100 101 public String getSelectedItem() { 102 ListModel<PresetListEntry> lm = getModel(); 103 int[] si = getSelectedIndices(); 104 StringBuilder builder = new StringBuilder(); 105 for (int i = 0; i < si.length; i++) { 106 if (i > 0) { 107 builder.append(delimiter); 108 } 109 builder.append(lm.getElementAt(si[i]).value); 110 } 111 return builder.toString(); 112 } 83 protected PresetListEntry getSelectedItem() { 84 return new PresetListEntry(list.getSelectedValuesList() 85 .stream().map(e -> e.value).collect(Collectors.joining(String.valueOf(delimiter))), this); 113 86 } 114 87 } -
trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/PresetListEntry.java
r18221 r18254 17 17 /** 18 18 * Preset list entry. 19 * <p> 20 * Used for controls that offer a list of items to choose from like {@link Combo} and 21 * {@link MultiSelect}. 19 22 */ 20 23 public class PresetListEntry implements Comparable<PresetListEntry> { 21 /** Entry value */ 24 /** Used to display an entry matching several different values. */ 25 protected static final PresetListEntry ENTRY_DIFFERENT = new PresetListEntry(KeyedItem.DIFFERENT, null); 26 /** Used to display an empty entry used to clear values. */ 27 protected static final PresetListEntry ENTRY_EMPTY = new PresetListEntry("", null); 28 29 /** 30 * This is the value that is going to be written to the tag on the selected primitive(s). Except 31 * when the value is {@code "<different>"}, which is never written, or the value is empty, which 32 * deletes the tag. {@code value} is never translated. 33 */ 22 34 public String value; // NOSONAR 23 /** The context used for translating {@link #value}*/24 public String value_context; // NOSONAR25 /** Value displayed to the user*/35 /** The ComboMultiSelect that displays the list */ 36 public ComboMultiSelect cms; // NOSONAR 37 /** Text displayed to the user instead of {@link #value}. */ 26 38 public String display_value; // NOSONAR 27 /** Text to be displayed below {@ code display_value}. */39 /** Text to be displayed below {@link #display_value} in the combobox list. */ 28 40 public String short_description; // NOSONAR 29 41 /** The location of icon file to display */ … … 36 48 public String locale_short_description; // NOSONAR 37 49 50 private String cachedDisplayValue = null; 51 private String cachedShortDescription = null; 52 private ImageIcon cachedIcon = null; 53 38 54 /** 39 55 * Constructs a new {@code PresetListEntry}, uninitialized. 56 * 57 * Public default constructor is needed by {@link org.openstreetmap.josm.tools.XmlObjectParser.Parser#startElement} 40 58 */ 41 59 public PresetListEntry() { 42 // Public default constructor is needed43 60 } 44 61 45 62 /** 46 * Constructs a new {@code PresetListEntry}, initialized with a value. 63 * Constructs a new {@code PresetListEntry}, initialized with a value and 64 * {@link ComboMultiSelect} context. 65 * 47 66 * @param value value 67 * @param cms the ComboMultiSelect 48 68 */ 49 public PresetListEntry(String value ) {69 public PresetListEntry(String value, ComboMultiSelect cms) { 50 70 this.value = value; 71 this.cms = cms; 51 72 } 52 73 … … 61 82 */ 62 83 public String getListDisplay(int width) { 63 if (value.equals(KeyedItem.DIFFERENT)) { 64 return "<b>" + KeyedItem.DIFFERENT + "</b>"; 84 String displayValue = getDisplayValue(); 85 Integer count = cms == null ? null : cms.usage.map.get(value); 86 87 if (count != null) { 88 displayValue = String.format("%s (%d)", displayValue, count); 65 89 } 66 90 67 String shortDescription = getShortDescription(true); 68 String displayValue = getDisplayValue(); 91 if (this.equals(ENTRY_DIFFERENT)) { 92 return "<html><b>" + Utils.escapeReservedCharactersHTML(displayValue) + "</b></html>"; 93 } 94 95 String shortDescription = getShortDescription(); 69 96 70 97 if (shortDescription.isEmpty()) { 98 // avoids a collapsed list entry if value == "" 71 99 if (displayValue.isEmpty()) { 72 100 return " "; … … 87 115 */ 88 116 public ImageIcon getIcon() { 89 return icon == null ? null : TaggingPresetItem.loadImageIcon(icon, TaggingPresetReader.getZipIcons(), (int) icon_size); 117 if (icon != null && cachedIcon == null) { 118 cachedIcon = TaggingPresetItem.loadImageIcon(icon, TaggingPresetReader.getZipIcons(), (int) icon_size); 119 } 120 return cachedIcon; 90 121 } 91 122 92 123 /** 93 * Returns the contents ofthe current item view.124 * Returns the contents displayed in the current item view. 94 125 * @return the value to display 95 126 */ 96 127 public String getDisplayValue() { 97 return Utils.firstNonNull(locale_display_value, tr(display_value), trc(value_context, value)); 128 if (cachedDisplayValue == null) { 129 if (cms != null && cms.values_no_i18n) { 130 cachedDisplayValue = Utils.firstNonNull(value, " "); 131 } else { 132 cachedDisplayValue = Utils.firstNonNull( 133 locale_display_value, tr(display_value), trc(cms == null ? null : cms.values_context, value), " "); 134 } 135 } 136 return cachedDisplayValue; 98 137 } 99 138 100 139 /** 101 140 * Returns the short description to display. 102 * @param translated whether the text must be translated103 141 * @return the short description to display 104 142 */ 105 public String getShortDescription(boolean translated) { 106 String shortDesc = translated 107 ? Utils.firstNonNull(locale_short_description, tr(short_description)) 108 : short_description; 109 return shortDesc == null ? "" : shortDesc; 143 public String getShortDescription() { 144 if (cachedShortDescription == null) { 145 cachedShortDescription = Utils.firstNonNull(locale_short_description, tr(short_description), ""); 146 } 147 return cachedShortDescription; 148 } 149 150 /** 151 * Returns the tooltip for this entry. 152 * @param key the tag key 153 * @return the tooltip 154 */ 155 public String getToolTipText(String key) { 156 if (this.equals(ENTRY_DIFFERENT)) { 157 return tr("Keeps the original values of the selected objects unchanged."); 158 } 159 if (value != null && !value.isEmpty()) { 160 return tr("Sets the key ''{0}'' to the value ''{1}''.", key, value); 161 } 162 return tr("Clears the key ''{0}''.", key); 110 163 } 111 164 … … 113 166 @Override 114 167 public String toString() { 115 if (KeyedItem.DIFFERENT.equals(value)) 116 return KeyedItem.DIFFERENT; 117 String displayValue = getDisplayValue(); 118 return displayValue != null ? displayValue.replaceAll("\\s*<.*>\\s*", " ") : ""; // remove additional markup, e.g. <br> 168 if (this.equals(ENTRY_DIFFERENT)) 169 return getDisplayValue(); 170 return getDisplayValue().replaceAll("\\s*<.*>\\s*", " "); // remove additional markup, e.g. <br> 119 171 } 120 172 … … 134 186 @Override 135 187 public int compareTo(PresetListEntry o) { 136 return AlphanumComparator.getInstance().compare(this. getDisplayValue(), o.getDisplayValue());188 return AlphanumComparator.getInstance().compare(this.value, o.value); 137 189 } 138 190 } -
trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Text.java
r18221 r18254 138 138 // cannot be an AutoCompComboBox because the values in the dropdown are different from 139 139 // those we autocomplete on. 140 JosmComboBox<String> comboBox = new JosmComboBox<>(usage.values.toArray(new String[0])); 140 JosmComboBox<String> comboBox = new JosmComboBox<>(); 141 comboBox.getModel().addAllElements(usage.map.keySet()); 141 142 comboBox.setEditable(true); 142 143 comboBox.setEditor(editor); 143 comboBox.getEditor().setItem(DIFFERENT );144 comboBox.getEditor().setItem(DIFFERENT_I18N); 144 145 value = comboBox; 145 originalValue = DIFFERENT ;146 originalValue = DIFFERENT_I18N; 146 147 } 147 148 initializeLocaleText(null);
Note:
See TracChangeset
for help on using the changeset viewer.