Ticket #15217: 15217.patch
File 15217.patch, 69.5 KB (added by , 21 months ago) |
---|
-
resources/data/defaultpresets.xml
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/resources/data/defaultpresets.xml b/resources/data/defaultpresets.xml
a b 1 1 <?xml version="1.0" encoding="UTF-8"?> 2 <presets xmlns="http://josm.openstreetmap.de/tagging-preset-1.0" >2 <presets xmlns="http://josm.openstreetmap.de/tagging-preset-1.0" name="defaultpresets"> 3 3 <!-- 4 4 Format description: https://josm.openstreetmap.de/wiki/TaggingPresets 5 5 --> -
resources/data/tagging-preset.xsd
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/resources/data/tagging-preset.xsd b/resources/data/tagging-preset.xsd
a b 75 75 </documentation> 76 76 </annotation> 77 77 </attribute> 78 <attribute name="object_keys" type="string"> 79 <annotation> 80 <documentation> 81 The keys to indicate that the preset is a real object. Some keys may be an object by default, such as highway. 82 </documentation> 83 </annotation> 84 </attribute> 78 85 79 86 <anyAttribute processContents="skip" /> 80 87 </extension> … … 134 141 </sequence> 135 142 <attributeGroup ref="tns:attributes.name" /> 136 143 <attributeGroup ref="tns:attributes.icon" /> 144 <attributeGroup ref="tns:attributes.deprecated" /> 137 145 <attribute name="type" type="string"> 138 146 <annotation> 139 147 <documentation><![CDATA[ … … 268 276 </annotation> 269 277 </attribute> 270 278 <attribute name="match" type="tns:match" /> 279 <attribute name="object" type="boolean"> 280 <annotation> 281 <documentation> 282 Specify that this preset is an object. Example: highways are objects, contact information is not. 283 </documentation> 284 </annotation> 285 </attribute> 271 286 </complexType> 272 287 273 288 <complexType name="link"> … … 300 315 </attribute> 301 316 <attributeGroup ref="tns:attributes.text" /> 302 317 <attribute name="name" use="prohibited" /> 318 <attribute name="alternative" type="boolean"> 319 <annotation> 320 <documentation> 321 If specified to be true, this indicates that the preset_link points to an alternative tagging of this object 322 </documentation> 323 </annotation> 324 </attribute> 325 <attribute name="parent" type="boolean"> 326 <annotation> 327 <documentation> 328 Indicate that the linked preset should use values from this preset for autofill purposes 329 </documentation> 330 </annotation> 331 </attribute> 303 332 <anyAttribute processContents="skip" /> 304 333 </complexType> 305 334 … … 328 357 <attributeGroup ref="tns:attributes.key" /> 329 358 <attributeGroup ref="tns:attributes.text" /> 330 359 <attributeGroup ref="tns:attributes.icon" /> 360 <attributeGroup ref="tns:attributes.deprecated" /> 331 361 <attribute name="use_last_as_default" type="tns:last_default" /> 332 362 <attribute name="auto_increment" type="string"> 333 363 <annotation> … … 353 383 ]]></documentation> 354 384 </annotation> 355 385 </attribute> 386 387 <attribute name="i18n" type="boolean"> 388 <annotation> 389 <documentation> 390 If true, this freeform key can have i18n variants. Examples would be name and name:en. 391 </documentation> 392 </annotation> 393 </attribute> 356 394 357 395 <attribute name="type" use="prohibited" /> 358 396 <attribute name="name" use="prohibited" /> … … 378 416 </annotation> 379 417 </attribute> 380 418 <attributeGroup ref="tns:attributes.icon" /> 419 <attributeGroup ref="tns:attributes.deprecated" /> 381 420 <anyAttribute processContents="skip" /> 382 421 </complexType> 383 422 … … 403 442 <attributeGroup ref="tns:attributes.text" /> 404 443 <attributeGroup ref="tns:attributes.icon" /> 405 444 <attributeGroup ref="tns:attributes.values" /> 445 <attributeGroup ref="tns:attributes.deprecated" /> 406 446 <attribute name="use_last_as_default" type="tns:last_default" /> 407 447 <attribute name="editable" type="boolean"> 408 448 <annotation> … … 431 471 <attributeGroup ref="tns:attributes.text" /> 432 472 <attributeGroup ref="tns:attributes.icon" /> 433 473 <attributeGroup ref="tns:attributes.values" /> 474 <attributeGroup ref="tns:attributes.deprecated" /> 434 475 <attribute name="use_last_as_default" type="tns:last_default" /> 435 476 <attribute name="match" type="tns:match" /> 436 477 … … 439 480 <attribute name="name" use="prohibited" /> 440 481 <attribute name="delete-if-empty" use="prohibited" /> 441 482 <attribute name="display-values" use="prohibited" /> 483 <attribute name="value_count_key" type="string"> 484 <annotation> 485 <documentation> 486 The reference to the key that will hold the number of values that this multiselect should contain. lanes and turn:lanes would use this. 487 </documentation> 488 </annotation> 489 </attribute> 442 490 <anyAttribute processContents="skip" /> 443 491 </complexType> 444 492 … … 591 639 592 640 <!-- Types and documentation for attributes --> 593 641 642 <simpleType name="type.value_type"> 643 <restriction base="string"> 644 <enumeration value="opening_hours"> 645 <annotation> 646 <documentation> 647 A standard opening hours tag 648 </documentation> 649 </annotation> 650 </enumeration> 651 <enumeration value="opening_hours_mixed"> 652 <annotation> 653 <documentation> 654 A tag with both text values and opening hours 655 </documentation> 656 </annotation> 657 </enumeration> 658 <enumeration value="conditional"> 659 <annotation> 660 <documentation> 661 A conditional tag such as maxspeed:conditional 662 </documentation> 663 </annotation> 664 </enumeration> 665 <enumeration value="integer"/> 666 <enumeration value="website"/> 667 <enumeration value="phone"/> 668 <enumeration value="wikipedia"/> 669 <enumeration value="wikidata"/> 670 </restriction> 671 </simpleType> 672 594 673 <attributeGroup name="attributes.name"> 595 674 <attribute name="name" type="string" use="required"> 596 675 <annotation> … … 607 686 </annotation> 608 687 </attribute> 609 688 </attributeGroup> 689 690 <attributeGroup name="attributes.deprecated"> 691 <attribute name="deprecated" type="boolean"> 692 <annotation> 693 <documentation> 694 If indicated to be true, this preset is deprecated and will not normally show up. 695 </documentation> 696 </annotation> 697 </attribute> 698 </attributeGroup> 610 699 611 700 <attributeGroup name="attributes.key"> 612 701 <attribute name="key" type="string" use="required"> … … 698 787 <documentation><![CDATA[ 699 788 The character that separates values. In case of <combo /> the default is comma. In case of <multiselect /> the default is semicolon and this will also be used to separate selected values in the tag. 700 789 ]]></documentation> 790 </annotation> 791 </attribute> 792 <attribute name="value_type" type="tns:type.value_type"> 793 <annotation> 794 <documentation> 795 The type of value to avoid hardcoding the property in JOSM 796 </documentation> 701 797 </annotation> 702 798 </attribute> 703 799 </attributeGroup> -
src/org/openstreetmap/josm/data/preferences/sources/ExtendedSourceEntry.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/data/preferences/sources/ExtendedSourceEntry.java b/src/org/openstreetmap/josm/data/preferences/sources/ExtendedSourceEntry.java
a b 28 28 /** minimum JOSM version required to enable this source entry */ 29 29 public Integer minJosmVersion; 30 30 31 /** 32 * A constructor that is only supposed to be used by {@link org.openstreetmap.josm.tools.XmlObjectParser}. 33 * All values will be filled in using field references. 34 * @since xxx 35 */ 36 public ExtendedSourceEntry() { 37 this(null, null, null); 38 } 39 31 40 /** 32 41 * Constructs a new {@code ExtendedSourceEntry}. 33 42 * @param type type of source entry -
new file src/org/openstreetmap/josm/data/tagging/ac/AutoCompletionItemRunnable.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/data/tagging/ac/AutoCompletionItemRunnable.java b/src/org/openstreetmap/josm/data/tagging/ac/AutoCompletionItemRunnable.java new file mode 100644
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.tagging.ac; 3 4 import java.util.Objects; 5 6 import org.openstreetmap.josm.gui.util.GuiHelper; 7 8 /** 9 * An autocompletion item that performs additional actions when it is selected 10 * @since xxx 11 */ 12 public class AutoCompletionItemRunnable extends AutoCompletionItem implements Runnable { 13 private final Runnable runnable; 14 15 /** 16 * Create a new AutoCompletionItemRunnable 17 * @param runnable The runnable to use. This will be run in the EDT (non-blocking). 18 * @param value The value for the key 19 * @param priority The {@link AutoCompletionPriority}, but it should usually be at least {@link AutoCompletionPriority#IS_IN_STANDARD} 20 */ 21 public AutoCompletionItemRunnable(Runnable runnable, String value, AutoCompletionPriority priority) { 22 super(value, priority); 23 Objects.requireNonNull(runnable, "runnable"); 24 this.runnable = runnable; 25 } 26 27 @Override 28 public void run() { 29 GuiHelper.runInEDT(this.runnable); 30 } 31 32 @Override 33 public int hashCode() { 34 return super.hashCode() * 31 + this.runnable.hashCode(); 35 } 36 37 @Override 38 public boolean equals(Object obj) { 39 return super.equals(obj) 40 && this.getClass().equals(obj.getClass()) 41 && this.runnable.equals(((AutoCompletionItemRunnable) obj).runnable); 42 } 43 } -
src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletingTextField.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletingTextField.java b/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletingTextField.java
a b 298 298 rememberOriginalValue(getText()); 299 299 return this; 300 300 } 301 302 @Override 303 public void focusLost(FocusEvent e) { 304 super.focusLost(e); 305 if (this.getHighlighter().getHighlights().length != 0) { 306 final String currentFilter = this.autoCompletionList.getFilter(); 307 try { 308 this.autoCompletionList.applyFilter(this.getText()); 309 if (this.autoCompletionList.getFilteredSize() == 1 && this.autoCompletionList.getFilteredItemAt(0) instanceof Runnable) { 310 ((Runnable) this.autoCompletionList.getFilteredItemAt(0)).run(); 311 } 312 } finally { 313 this.autoCompletionList.applyFilter(currentFilter); 314 } 315 } 316 } 301 317 } -
src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletionList.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletionList.java b/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletionList.java
a b 187 187 // apply the pattern to list of possible values. If it matches, add the 188 188 // value to the list of filtered values 189 189 // 190 list.stream().filter(e -> e.getValue().startsWith(filter)).forEach(filtered::add); 190 // Prefer items that are autofill capable 191 list.stream().filter(e -> e instanceof Runnable && e.getValue().startsWith(filter)).forEach(filtered::add); 192 // But fall back to non-autofill items 193 if (list.isEmpty()) { 194 list.stream().filter(e -> e.getValue().startsWith(filter)).forEach(filtered::add); 195 } 191 196 fireTableDataChanged(); 192 197 } 193 198 -
src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletionManager.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletionManager.java b/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletionManager.java
a b 2 2 package org.openstreetmap.josm.gui.tagging.ac; 3 3 4 4 import java.util.ArrayList; 5 import java.util.Arrays;6 5 import java.util.Collection; 7 6 import java.util.Collections; 8 7 import java.util.Comparator; … … 13 12 import java.util.Map; 14 13 import java.util.Map.Entry; 15 14 import java.util.Objects; 15 import java.util.Optional; 16 16 import java.util.Set; 17 import java.util.function. Function;17 import java.util.function.Consumer; 18 18 import java.util.stream.Collectors; 19 19 20 20 import org.openstreetmap.josm.data.osm.DataSet; … … 31 31 import org.openstreetmap.josm.data.osm.event.TagsChangedEvent; 32 32 import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent; 33 33 import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem; 34 import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItemRunnable; 34 35 import org.openstreetmap.josm.data.tagging.ac.AutoCompletionPriority; 35 36 import org.openstreetmap.josm.data.tagging.ac.AutoCompletionSet; 36 37 import org.openstreetmap.josm.gui.MainApplication; … … 41 42 import org.openstreetmap.josm.gui.layer.OsmDataLayer; 42 43 import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset; 43 44 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets; 44 import org.openstreetmap.josm.gui.tagging.presets.items. Roles.Role;45 import org.openstreetmap.josm.gui.tagging.presets.items.Key; 45 46 import org.openstreetmap.josm.tools.CheckParameterUtil; 46 47 import org.openstreetmap.josm.tools.MultiMap; 47 48 import org.openstreetmap.josm.tools.Utils; … … 49 50 /** 50 51 * AutoCompletionManager holds a cache of keys with a list of 51 52 * possible auto completion values for each key. 52 * 53 * <p> 53 54 * Each DataSet can be assigned one AutoCompletionManager instance such that 54 55 * <ol> 55 56 * <li>any key used in a tag in the data set is part of the key list in the cache</li> … … 60 61 * slow down tabbing from input field to input field. Looping through the complete 61 62 * data set in order to build up the auto completion list for a specific input 62 63 * field is not efficient enough, hence this cache. 63 * 64 * <p> 64 65 * TODO: respect the relation type for member role autocompletion 65 66 */ 66 67 public class AutoCompletionManager implements DataSetListener { 67 68 68 /** 69 69 * Data class to remember tags that the user has entered. 70 70 */ … … 187 187 188 188 /** 189 189 * make sure, the keys and values of all tags held by primitive are 190 * in the auto 190 * in the autocompletion cache 191 191 * 192 192 * @param primitive an OSM primitive 193 193 */ … … 239 239 } 240 240 241 241 /** 242 * replies the auto 242 * replies the autocompletion values allowed for a specific key. Replies 243 243 * an empty list if key is null or if key is not in {@link #getTagKeys()}. 244 244 * 245 245 * @param key OSM key 246 * @return the list of auto 246 * @return the list of autocompletion values 247 247 */ 248 248 protected List<String> getDataValues(String key) { 249 249 return new ArrayList<>(getTagCache().getValues(key)); … … 293 293 if (r != null && !Utils.isEmpty(presets)) { 294 294 for (TaggingPreset tp : presets) { 295 295 if (tp.roles != null) { 296 list.add(Utils.transform(tp.roles.roles, (Function<Role, String>)x -> x.key), AutoCompletionPriority.IS_IN_STANDARD);296 list.add(Utils.transform(tp.roles.roles, x -> x.key), AutoCompletionPriority.IS_IN_STANDARD); 297 297 } 298 298 } 299 299 list.add(r.getMemberRoles(), AutoCompletionPriority.IS_IN_DATASET); … … 321 321 * @param key the tag key 322 322 */ 323 323 public void populateWithTagValues(AutoCompletionList list, String key) { 324 populateWithTagValues(list, Arrays.asList(key));324 populateWithTagValues(list, Collections.singletonList(key)); 325 325 } 326 326 327 327 /** … … 352 352 * @since 18221 353 353 */ 354 354 public List<AutoCompletionItem> getAllForKeys(List<String> keys) { 355 Map<String, AutoCompletionPriority> map = new HashMap<>(); 355 return getAllForKeys(null, null, keys); 356 } 357 358 /** 359 * Returns all cached {@link AutoCompletionItem}s for given keys. 360 * 361 * @param runnable The runnable to call when the AutoCompletionItem is selected 362 * @param keys retrieve the items for these keys 363 * @param currentPreset the preset to use to find child presets 364 * @return the currently cached items, sorted by priority and alphabet 365 * @since xxx 366 */ 367 public List<AutoCompletionItem> getAllForKeys(Consumer<TaggingPreset> runnable, TaggingPreset currentPreset, List<String> keys) { 368 Map<String, AutoCompletionPriority> map = new HashMap<>(keys.size()); 356 369 357 370 for (String key : keys) { 358 371 for (String value : TaggingPresets.getPresetValues(key)) { … … 365 378 map.merge(value, AutoCompletionPriority.UNKNOWN, AutoCompletionPriority::mergeWith); 366 379 } 367 380 } 368 return map.entrySet().stream().map(e -> new AutoCompletionItem(e.getKey(), e.getValue())).sorted().collect(Collectors.toList()); 381 final boolean containsPrimaryKey = keys.stream().anyMatch(TaggingPresets::isPrimaryKey); 382 return map.entrySet().stream().map(entry -> createAutoCompletionItem(containsPrimaryKey, entry, runnable, currentPreset)).sorted() 383 .collect(Collectors.toList()); 384 } 385 386 /** 387 * Create an autocompletion item from an entry 388 * 389 * @param primaryKey {@code true} if the key is something that can uniquely identify the object 390 * @param entry The entry to create the item from 391 * @param runnable The runnable to use if we end up creating a {@link AutoCompletionItemRunnable} 392 * @param currentPreset The current preset 393 * @return The created autocompletion item 394 */ 395 private static AutoCompletionItem createAutoCompletionItem(boolean primaryKey, Entry<String, AutoCompletionPriority> entry, 396 Consumer<TaggingPreset> runnable, 397 TaggingPreset currentPreset) { 398 if (runnable == null || AutoCompletionPriority.UNKNOWN.equals(entry.getValue()) 399 || AutoCompletionPriority.IS_IN_SELECTION.equals(entry.getValue()) 400 || AutoCompletionPriority.IS_IN_DATASET.equals(entry.getValue()) 401 || !primaryKey) { 402 return new AutoCompletionItem(entry.getKey(), entry.getValue()); 403 } else if (AutoCompletionPriority.IS_IN_STANDARD.equals(entry.getValue()) 404 || AutoCompletionPriority.IS_IN_STANDARD_AND_IN_DATASET.equals(entry.getValue())) { 405 final Map<String, String> tagMap = currentPreset.getMinimalTagMap(); 406 Optional<TaggingPreset> best = currentPreset.getChildrenPresets().stream().filter(p -> 407 // Allow any preset where the preset name matches the value 408 (p.name != null && p.name.equals(entry.getKey())) || 409 // Filter out results that don't have any keys with the value 410 p.data.stream().filter(Key.class::isInstance).map(Key.class::cast).anyMatch(key -> key.value.equals(entry.getKey()))) 411 .max(Comparator.comparingLong(p -> countCommonKeys(p, tagMap) + (p.name != null && p.name.equals(entry.getKey()) ? 1 : 0))); 412 if (best.isPresent()) { 413 return new AutoCompletionItemRunnable(() -> runnable.accept(best.get()), entry.getKey(), entry.getValue()); 414 } 415 return new AutoCompletionItem(entry.getKey(), entry.getValue()); 416 } 417 throw new IllegalStateException("Unexpected value: " + entry.getValue()); 418 } 419 420 private static int countCommonKeys(TaggingPreset preset, Map<String, String> keys) { 421 return preset.getMinimalTagMap().entrySet().stream().mapToInt(presetEntry -> { 422 if (keys.containsKey(presetEntry.getKey())) { 423 if (Objects.equals(keys.get(presetEntry.getKey()), presetEntry.getValue())) { 424 return 2; 425 } 426 return 1; 427 } 428 return 0; 429 }).sum(); 369 430 } 370 431 371 432 /** … … 396 457 * @since 12859 397 458 */ 398 459 public AutoCompletionSet getTagValues(String key) { 399 return getTagValues( Arrays.asList(key));460 return getTagValues(Collections.singletonList(key)); 400 461 } 401 462 402 463 /** -
src/org/openstreetmap/josm/gui/tagging/ac/AutoCompTextField.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompTextField.java b/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompTextField.java
a b 5 5 import java.awt.datatransfer.Clipboard; 6 6 import java.awt.datatransfer.StringSelection; 7 7 import java.awt.datatransfer.Transferable; 8 import java.awt.event.FocusEvent; 8 9 import java.awt.event.KeyEvent; 9 10 import java.awt.event.KeyListener; 10 11 import java.util.EventObject; … … 338 339 rememberOriginalValue(getText()); 339 340 return this; 340 341 } 342 343 @Override 344 public void focusLost(FocusEvent e) { 345 super.focusLost(e); 346 if (this.autocompleteEnabled) { 347 E item = getModel().findBestCandidate(getText()); 348 if (item instanceof Runnable) { 349 ((Runnable) item).run(); 350 } 351 } 352 } 341 353 } -
src/org/openstreetmap/josm/gui/tagging/presets/items/Check.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/items/Check.java b/src/org/openstreetmap/josm/gui/tagging/presets/items/Check.java
a b 92 92 p.add(check, GBC.eol()); // Do not fill, see #15104 93 93 } 94 94 check.addChangeListener(l -> support.fireItemValueModified(this, key, getValue())); 95 support.addField(this.key, this.check); 95 96 return true; 96 97 } 97 98 -
src/org/openstreetmap/josm/gui/tagging/presets/items/Combo.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/items/Combo.java b/src/org/openstreetmap/josm/gui/tagging/presets/items/Combo.java
a b 10 10 import java.awt.event.ActionListener; 11 11 import java.awt.event.ComponentAdapter; 12 12 import java.awt.event.ComponentEvent; 13 import java.util. Arrays;13 import java.util.Collections; 14 14 import java.util.Comparator; 15 import java.util.List; 16 import java.util.Objects; 15 17 16 18 import javax.swing.AbstractAction; 17 19 import javax.swing.JButton; … … 27 29 import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxEditor; 28 30 import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel; 29 31 import org.openstreetmap.josm.gui.tagging.ac.AutoCompTextField; 32 import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset; 30 33 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem; 31 34 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport; 32 35 import org.openstreetmap.josm.gui.widgets.JosmComboBox; … … 85 88 86 89 @Override 87 90 protected boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support) { 91 return addToPanel(p, support, null); 92 } 93 94 @Override 95 protected boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support, TaggingPreset preset) { 88 96 initializeLocaleText(null); 89 97 usage = determineTextUsage(support.getSelected(), key); 90 98 seenValues.clear(); … … 124 132 combobox.setEditable(editable); 125 133 126 134 autoCompModel = new AutoCompComboBoxModel<>(Comparator.<AutoCompletionItem>naturalOrder()); 127 getAllForKeys(Arrays.asList(key)).forEach(autoCompModel::addElement); 128 getDisplayValues().forEach(s -> autoCompModel.addElement(new AutoCompletionItem(s, AutoCompletionPriority.IS_IN_STANDARD))); 135 List<AutoCompletionItem> allForKeys = getAllForKeys(support::fillFromPreset, preset, Collections.singletonList(key)); 136 autoCompModel.addAllElements(allForKeys); 137 getDisplayValues().stream().filter(value -> allForKeys.stream().noneMatch(item -> Objects.equals(item.getValue(), value))) 138 .map(s -> new AutoCompletionItem(s, AutoCompletionPriority.IS_IN_STANDARD)) 139 .forEach(autoCompModel::addElement); 129 140 130 141 AutoCompTextField<AutoCompletionItem> tf = editor.getEditorComponent(); 131 142 tf.setModel(autoCompModel); … … 168 179 combobox.setToolTipText(getKeyTooltipText()); 169 180 combobox.applyComponentOrientation(OrientationAction.getValueOrientation(key)); 170 181 182 support.addField(this.key, this.combobox); 171 183 return true; 172 184 } 173 185 -
src/org/openstreetmap/josm/gui/tagging/presets/items/Key.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/items/Key.java b/src/org/openstreetmap/josm/gui/tagging/presets/items/Key.java
a b 4 4 import java.util.Collection; 5 5 import java.util.Collections; 6 6 import java.util.List; 7 import java.util.Map; 8 import java.util.Objects; 7 9 8 10 import javax.swing.JPanel; 9 11 … … 46 48 return Collections.singleton(value); 47 49 } 48 50 51 @Override 52 public Boolean matches(Map<String, String> tags) { 53 switch (MatchType.ofString(match)) { 54 case KEY_VALUE: 55 return tags.containsKey(key) && value.equals(tags.get(key)) ? Boolean.TRUE : null; 56 case KEY_VALUE_REQUIRED: 57 return tags.containsKey(key) && value.equals(tags.get(key)); 58 default: 59 return super.matches(tags); 60 } 61 } 62 49 63 @Override 50 64 public String toString() { 51 65 return "Key [key=" + key + ", value=" + value + ", text=" + text 52 66 + ", text_context=" + text_context + ", match=" + match 53 67 + ']'; 54 68 } 69 70 @Override 71 public int hashCode() { 72 return Objects.hash(this.key, this.value, this.text, this.text_context, this.match); 73 } 74 75 @Override 76 public boolean equals(Object obj) { 77 if (obj != null && obj.getClass() == this.getClass()) { 78 Key other = (Key) obj; 79 return Objects.equals(this.key, other.key) 80 && Objects.equals(this.value, other.value) 81 && Objects.equals(this.text, other.text) 82 && Objects.equals(this.text_context, other.text_context) 83 && Objects.equals(this.match, other.match); 84 } 85 return false; 86 } 55 87 } -
src/org/openstreetmap/josm/gui/tagging/presets/items/KeyedItem.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/items/KeyedItem.java b/src/org/openstreetmap/josm/gui/tagging/presets/items/KeyedItem.java
a b 4 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 5 6 6 import java.util.Collection; 7 import java.util.EnumSet;8 7 import java.util.HashMap; 9 8 import java.util.Map; 10 9 import java.util.SortedMap; … … 68 67 /** Positive if key and value matches, negative otherwise. */ 69 68 KEY_VALUE_REQUIRED("keyvalue!"); 70 69 70 // Avoid many array instantiations 71 private static MatchType[] allValues = values(); 72 71 73 private final String value; 72 74 73 75 MatchType(String value) { … … 88 90 * @return the {@code MatchType} for the given textual value 89 91 */ 90 92 public static MatchType ofString(String type) { 91 for (MatchType i : EnumSet.allOf(MatchType.class)) {93 for (MatchType i : allValues) { 92 94 if (i.getValue().equals(type)) 93 95 return i; 94 96 } -
src/org/openstreetmap/josm/gui/tagging/presets/items/MultiSelect.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/items/MultiSelect.java b/src/org/openstreetmap/josm/gui/tagging/presets/items/MultiSelect.java
a b 97 97 list.addListSelectionListener(l -> support.fireItemValueModified(this, key, getSelectedItem().value)); 98 98 list.setToolTipText(getKeyTooltipText()); 99 99 list.applyComponentOrientation(OrientationAction.getValueOrientation(key)); 100 support.addField(this.key, this.list); 100 101 101 102 return true; 102 103 } -
src/org/openstreetmap/josm/gui/tagging/presets/items/PresetLink.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/items/PresetLink.java b/src/org/openstreetmap/josm/gui/tagging/presets/items/PresetLink.java
a b 5 5 6 6 import java.awt.event.MouseAdapter; 7 7 import java.awt.event.MouseEvent; 8 import java.util.ArrayList; 8 9 import java.util.Collection; 10 import java.util.Collections; 9 11 import java.util.List; 12 import java.util.Objects; 10 13 import java.util.Optional; 14 import java.util.regex.Matcher; 15 import java.util.regex.Pattern; 16 import java.util.stream.Stream; 11 17 12 18 import javax.swing.JLabel; 13 19 import javax.swing.JPanel; 14 20 21 15 22 import org.openstreetmap.josm.data.osm.OsmPrimitive; 16 23 import org.openstreetmap.josm.data.osm.Tag; 17 24 import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset; 18 25 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport; 19 26 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetLabel; 27 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetMenu; 20 28 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets; 21 29 import org.openstreetmap.josm.tools.GBC; 22 30 … … 41 49 } 42 50 } 43 51 44 /** The exact name of the preset to link to. Required. */ 52 /** 53 * The pattern for linking to specific presets. josm-preset:[//<preset name>][/]<path>, see #12716. 54 * If just josm-preset:<path> is specified, the preset path is relative (so the linked preset must 55 * exist in the file that created this preset). 56 */ 57 private static final Pattern PRESET_LINK_PATTERN = Pattern.compile("^josm-preset:(//(.*?)/)?(.*)$"); 58 59 /** 60 * The exact name of the preset to link to. Required. 61 * This may match {@link #PRESET_LINK_PATTERN}. 62 */ 45 63 public String preset_name = ""; // NOSONAR 64 /** 65 * If {@code true}, the {@link #preset_name} is a parent of this preset. 66 * Used for autocomplete, but {@link #preset_name} <i>must</i> match the {@link #PRESET_LINK_PATTERN}. 67 * @since xxx 68 */ 69 public boolean parent; // NOSONAR 70 71 /** 72 * Get the preset for a given preset name 73 * @param taggingPreset The tagging preset this link is part of. May be {@code null} 74 * @param presetName The preset name to find. May either be a straight name or it may match {@link #PRESET_LINK_PATTERN}. 75 * @return The preset, if any 76 * @since xxx 77 */ 78 private static Optional<TaggingPreset> getPreset(TaggingPreset taggingPreset, String presetName) { 79 final Matcher matcher = PRESET_LINK_PATTERN.matcher(presetName); 80 Stream<TaggingPreset> presetStream = TaggingPresets.getTaggingPresets().stream() 81 .filter(preset -> !(preset instanceof TaggingPresetMenu)); 82 if (matcher.matches()) { 83 String path = matcher.group(3); 84 if (path != null) { 85 String presetList = matcher.group(2); 86 if (presetList != null) { 87 presetStream = presetStream.filter(preset -> presetList.equals(preset.preset_list_name)); 88 } else if (taggingPreset != null) { 89 presetStream = presetStream.filter(preset -> Objects.equals(taggingPreset.preset_list_name, preset.preset_list_name)); 90 } 91 final String[] expectedPath = path.split("/", -1); 92 final List<String> groupList = new ArrayList<>(); 93 presetStream = presetStream.filter(preset -> checkPath(expectedPath, preset, groupList)); 94 } 95 } else { 96 presetStream = presetStream.filter(preset -> presetName.equals(preset.name)); 97 } 98 return presetStream.findFirst(); 99 } 100 101 /** 102 * Ensure that a path matches a preset path 103 * @param expectedPath The expected path 104 * @param preset The preset to build a path from 105 * @return {@code true} if the paths are equal 106 */ 107 private static synchronized boolean checkPath(String[] expectedPath, TaggingPreset preset, List<String> presetList) { 108 TaggingPreset current = preset; 109 presetList.clear(); 110 while (current != null && current.name != null) { 111 presetList.add(current.name); 112 current = current.group; 113 } 114 Collections.reverse(presetList); 115 if (expectedPath.length == presetList.size()) { 116 for (int i = 0; i < expectedPath.length; i++) { 117 if (!presetList.get(i).equals(expectedPath[i])) { 118 return false; 119 } 120 } 121 return true; 122 } 123 return false; 124 } 125 126 /** 127 * If this preset has a parent (see {@link #parent}), then this preset will be used 128 * for autocomplete values for {@link #preset_name}. Please note that we currently 129 * force {@link #preset_name} to match the {@link #PRESET_LINK_PATTERN}. 130 * @param taggingPreset The tagging preset this link is part of. May be {@code null}. 131 * @since xxx 132 */ 133 public void reverseLinkPreset(TaggingPreset taggingPreset) { 134 if (this.parent) { 135 final String presetName = preset_name; 136 final Matcher matcher = PRESET_LINK_PATTERN.matcher(presetName); 137 if (!matcher.matches()) { 138 throw new IllegalArgumentException( 139 "JOSM only supports matching presets to parents if the parent preset_name is declarative. " 140 + PRESET_LINK_PATTERN); 141 } 142 Optional<TaggingPreset> preset = getPreset(taggingPreset, presetName); 143 preset.ifPresent(tPreset -> tPreset.addChildPreset(taggingPreset)); 144 } 145 } 46 146 47 147 /** 48 148 * Creates a label to be inserted aboive this link … … 55 155 56 156 @Override 57 157 public boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support) { 58 final String presetName = preset_name; 59 Optional<TaggingPreset> found = TaggingPresets.getTaggingPresets().stream().filter(preset -> presetName.equals(preset.name)).findFirst(); 158 final Optional<TaggingPreset> found = getPreset(null, preset_name); 60 159 if (found.isPresent()) { 61 160 TaggingPreset t = found.get(); 62 161 JLabel lbl = new TaggingPresetLabel(t); -
src/org/openstreetmap/josm/gui/tagging/presets/items/Text.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/items/Text.java b/src/org/openstreetmap/josm/gui/tagging/presets/items/Text.java
a b 10 10 import java.text.NumberFormat; 11 11 import java.text.ParseException; 12 12 import java.util.ArrayList; 13 import java.util.Arrays; 13 14 import java.util.Collection; 14 15 import java.util.Collections; 15 16 import java.util.List; … … 29 30 import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel; 30 31 import org.openstreetmap.josm.gui.tagging.ac.AutoCompTextField; 31 32 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager; 33 import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset; 32 34 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem; 33 35 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport; 34 36 import org.openstreetmap.josm.gui.util.DocumentAdapter; … … 72 74 private transient TemplateEntry valueTemplate; 73 75 74 76 @Override 75 public boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support) { 77 protected boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support) { 78 return this.addToPanel(p, support, null); 79 } 80 81 @Override 82 public boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support, TaggingPreset preset) { 76 83 77 84 AutoCompComboBoxModel<AutoCompletionItem> model = new AutoCompComboBoxModel<>(); 78 85 List<String> keys = new ArrayList<>(); 79 86 keys.add(key); 80 87 if (alternative_autocomplete_keys != null) { 81 for (String k : alternative_autocomplete_keys.split(",", -1)) { 82 keys.add(k); 83 } 88 keys.addAll(Arrays.asList(alternative_autocomplete_keys.split(",", -1))); 84 89 } 85 getAllForKeys(keys).forEach(model::addElement);90 model.addAllElements(getAllForKeys(support::fillFromPreset, preset, keys)); 86 91 87 92 AutoCompTextField<AutoCompletionItem> textField; 88 93 AutoCompComboBoxEditor<AutoCompletionItem> editor = null; … … 208 213 label.applyComponentOrientation(support.getDefaultComponentOrientation()); 209 214 value.setToolTipText(getKeyTooltipText()); 210 215 value.applyComponentOrientation(OrientationAction.getNamelikeOrientation(key)); 216 support.addField(this.key, this.value); 211 217 return true; 212 218 } 213 219 -
src/org/openstreetmap/josm/gui/tagging/presets/TaggingPreset.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPreset.java b/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPreset.java
a b 14 14 import java.io.File; 15 15 import java.util.ArrayList; 16 16 import java.util.Collection; 17 import java.util.Collections; 17 18 import java.util.EnumSet; 19 import java.util.HashMap; 20 import java.util.HashSet; 18 21 import java.util.LinkedHashSet; 19 22 import java.util.List; 20 23 import java.util.Map; … … 62 65 import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener; 63 66 import org.openstreetmap.josm.gui.preferences.ToolbarPreferences; 64 67 import org.openstreetmap.josm.gui.tagging.presets.items.Key; 68 import org.openstreetmap.josm.gui.tagging.presets.items.KeyedItem; 65 69 import org.openstreetmap.josm.gui.tagging.presets.items.Link; 66 70 import org.openstreetmap.josm.gui.tagging.presets.items.Optional; 67 71 import org.openstreetmap.josm.gui.tagging.presets.items.PresetLink; … … 136 140 * Show the preset name if true 137 141 */ 138 142 public boolean preset_name_label; 143 /** 144 * The name of the preset list (e.g. the name suggestion index) 145 * @since xxx 146 */ 147 public String preset_list_name; 139 148 140 149 /** 141 150 * The types as preparsed collection. … … 170 179 171 180 /** Support functions */ 172 181 protected TaggingPresetItemGuiSupport itemGuiSupport; 182 private final Collection<TaggingPreset> children = new HashSet<>(); 173 183 174 184 /** 175 185 * Create an empty tagging preset. This will not have any items and … … 349 359 } 350 360 } 351 361 362 /** 363 * Add a child preset to be used for autocomplete suggestions 364 * @param child The child preset 365 * @since xxx 366 */ 367 public void addChildPreset(TaggingPreset child) { 368 this.children.add(child); 369 } 370 371 /** 372 * Get the children presets to be used for autocompletion 373 * @return The child presets 374 */ 375 public Collection<TaggingPreset> getChildrenPresets() { 376 return Collections.unmodifiableCollection(this.children); 377 } 378 352 379 private static class PresetPanel extends JPanel { 353 380 private boolean hasElements; 354 381 … … 422 449 TaggingPresetItem previous = null; 423 450 for (TaggingPresetItem i : data) { 424 451 if (i instanceof Link) { 425 i.addToPanel(linkPanel, itemGuiSupport );452 i.addToPanel(linkPanel, itemGuiSupport, this); 426 453 p.hasElements = true; 427 454 } else { 428 455 if (i instanceof PresetLink) { … … 431 458 itemPanel.add(link.createLabel(), GBC.eol().insets(0, 8, 0, 0)); 432 459 } 433 460 } 434 if (i.addToPanel(itemPanel, itemGuiSupport )) {461 if (i.addToPanel(itemPanel, itemGuiSupport, this)) { 435 462 p.hasElements = true; 436 463 } 437 464 } … … 735 762 } 736 763 } 737 764 765 /** 766 * This builds a map of key->value objects, where the value may be {@code null}. 767 * If the value is {@code null}, it means that it is not pre-set. 768 * @return A map of tags 769 * @since xxx 770 */ 771 public Map<String, String> getMinimalTagMap() { 772 Map<String, String> tagMap = new HashMap<>(); 773 for (TaggingPresetItem item : this.data) { 774 if (item instanceof Key) { 775 Key key = (Key) item; 776 tagMap.put(key.key, key.value); 777 } else if (item instanceof KeyedItem) { 778 tagMap.put(((KeyedItem) item).key, null); 779 } 780 } 781 return tagMap; 782 } 783 738 784 /** 739 785 * Action that adds or removes the button on main toolbar 740 786 */ -
src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItem.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItem.java b/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItem.java
a b 11 11 import java.util.EnumSet; 12 12 import java.util.List; 13 13 import java.util.Map; 14 import java.util.RandomAccess; 14 15 import java.util.Set; 16 import java.util.function.Consumer; 15 17 16 18 import javax.swing.ImageIcon; 17 19 import javax.swing.JPanel; … … 66 68 * @since 18221 67 69 */ 68 70 protected List<AutoCompletionItem> getAllForKeys(List<String> keys) { 71 return getAllForKeys(null, null, keys); 72 } 73 74 /** 75 * Returns all cached {@link AutoCompletionItem}s for given keys. 76 * 77 * @param consumer a consumer to be called when an AutoCompletionItem is selected. May be {@code null}. 78 * @param currentPreset The preset we are getting keys for 79 * @param keys retrieve the items for these keys 80 * @return the currently cached items, sorted by priority and alphabet 81 * @since xxx 82 */ 83 protected List<AutoCompletionItem> getAllForKeys(Consumer<TaggingPreset> consumer, TaggingPreset currentPreset, List<String> keys) { 69 84 DataSet data = OsmDataManager.getInstance().getEditDataSet(); 70 85 if (data == null) { 71 86 return Collections.emptyList(); 72 87 } 73 return AutoCompletionManager.of(data).getAllForKeys( keys);88 return AutoCompletionManager.of(data).getAllForKeys(consumer, currentPreset, keys); 74 89 } 75 90 76 91 /** … … 83 98 */ 84 99 protected abstract boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support); 85 100 101 /** 102 * Called by {@link TaggingPreset#createPanel} during tagging preset panel creation. 103 * All components defining this tagging preset item must be added to given panel. 104 * 105 * @param p The panel where components must be added 106 * @param support supporting class for creating the GUI 107 * @param preset the preset that this data item is being added to the panel for 108 * @return {@code true} if this item adds semantic tagging elements, {@code false} otherwise. 109 * @since xxx 110 */ 111 protected boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support, TaggingPreset preset) { 112 return addToPanel(p, support); 113 } 114 86 115 /** 87 116 * Adds the new tags to apply to selected OSM primitives when the preset holding this item is applied. 88 117 * @param changedTags The list of changed tags to modify if needed … … 170 199 */ 171 200 public static boolean matches(Iterable<? extends TaggingPresetItem> data, Map<String, String> tags) { 172 201 boolean atLeastOnePositiveMatch = false; 173 for (TaggingPresetItem item : data) { 174 Boolean m = item.matches(tags); 175 if (m != null && !m) 176 return false; 177 else if (m != null) { 178 atLeastOnePositiveMatch = true; 202 if (data instanceof List && data instanceof RandomAccess) { 203 List<? extends TaggingPresetItem> dataList = (List<? extends TaggingPresetItem>) data; 204 for (int i = 0; i < dataList.size(); i++) { 205 Boolean m = dataList.get(i).matches(tags); 206 if (m != null && !m) 207 return false; 208 else if (m != null) { 209 atLeastOnePositiveMatch = true; 210 } 211 } 212 } else { 213 for (TaggingPresetItem item : data) { 214 Boolean m = item.matches(tags); 215 if (m != null && !m) 216 return false; 217 else if (m != null) { 218 atLeastOnePositiveMatch = true; 219 } 179 220 } 180 221 } 181 222 return atLeastOnePositiveMatch; -
src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItemGuiSupport.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItemGuiSupport.java b/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItemGuiSupport.java
a b 5 5 import java.util.Arrays; 6 6 import java.util.Collection; 7 7 import java.util.Collections; 8 import java.util.HashMap; 9 import java.util.HashSet; 10 import java.util.List; 11 import java.util.Set; 8 12 import java.util.function.Supplier; 9 13 14 import javax.swing.ComboBoxModel; 15 import javax.swing.JComboBox; 16 import javax.swing.JComponent; 17 import javax.swing.JList; 18 import javax.swing.JTextField; 19 import javax.swing.ListModel; 20 import javax.swing.ListSelectionModel; 21 10 22 import org.openstreetmap.josm.data.osm.OsmPrimitive; 11 23 import org.openstreetmap.josm.data.osm.Tag; 12 24 import org.openstreetmap.josm.data.osm.Tagged; 13 25 import org.openstreetmap.josm.data.osm.search.SearchCompiler; 26 import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem; 27 import org.openstreetmap.josm.gui.tagging.presets.items.Key; 28 import org.openstreetmap.josm.gui.tagging.presets.items.PresetListEntry; 14 29 import org.openstreetmap.josm.gui.widgets.OrientationAction; 30 import org.openstreetmap.josm.gui.widgets.QuadStateCheckBox; 31 import org.openstreetmap.josm.tools.JosmRuntimeException; 15 32 import org.openstreetmap.josm.tools.ListenerList; 16 33 import org.openstreetmap.josm.tools.Utils; 17 34 import org.openstreetmap.josm.tools.template_engine.TemplateEngineDataProvider; … … 22 39 * @since 17609 23 40 */ 24 41 public final class TaggingPresetItemGuiSupport implements TemplateEngineDataProvider { 42 private final HashMap<String, JComponent> keyComponentPrefillMap = new HashMap<>(); 43 private final HashMap<JComponent, Object> defaultMap = new HashMap<>(); 25 44 26 45 private final Collection<OsmPrimitive> selected; 27 46 /** True if all selected primitives matched this preset at the moment it was openend. */ … … 181 200 if (enabled) 182 201 listeners.fireEvent(e -> e.itemValueModified(source, key, newValue)); 183 202 } 203 204 /** 205 * Add a field to be used when updating/filling from a preset. 206 * This should be called after the field has been set up with its default value (if any). 207 * @param key The key that will be used to update the component 208 * @param component The component to update 209 */ 210 public void addField(String key, JComponent component) { 211 this.keyComponentPrefillMap.put(key, component); 212 if (component instanceof JTextField) { 213 this.defaultMap.put(component, ((JTextField) component).getText()); 214 } else if (component instanceof QuadStateCheckBox) { 215 this.defaultMap.put(component, ((QuadStateCheckBox) component).getState()); 216 } else if (component instanceof JList) { 217 this.defaultMap.put(component, ((JList<?>) component).getSelectedValuesList()); 218 } else if (component instanceof JComboBox) { 219 this.defaultMap.put(component, ((JComboBox<?>) component).getSelectedObjects()); 220 } else if (component != null) { 221 throw new JosmRuntimeException(component.getClass() + " is not supported"); 222 } 223 } 224 225 /** 226 * Update the UI elements with those from another preset 227 * @param presetItem The item to fill data in from 228 * @since xxx 229 */ 230 public void fillFromPreset(TaggingPreset presetItem) { 231 // Clear current fields 232 resetComponents(); 233 for (TaggingPresetItem item : presetItem.data) { 234 if (item instanceof Key) { 235 fillComponent((Key) item); 236 } 237 } 238 } 239 240 private void resetComponents() { 241 for (JComponent component : this.keyComponentPrefillMap.values()) { 242 if (component instanceof JTextField) { 243 ((JTextField) component).setText((String) this.defaultMap.get(component)); 244 } else if (component instanceof QuadStateCheckBox) { 245 QuadStateCheckBox.State state = (QuadStateCheckBox.State) this.defaultMap.get(component); 246 ((QuadStateCheckBox) component).setState(state != null ? state : QuadStateCheckBox.State.UNSET); 247 } else if (component instanceof JComboBox) { 248 ((JComboBox<?>) component).setSelectedItem(null); // TODO avoid losing original state 249 } else if (component instanceof JList) { 250 ((JList<?>) component).clearSelection(); // TODO avoid losing original state 251 } else if (component != null) { 252 throw new JosmRuntimeException(component.getClass() + " is not supported"); 253 } 254 } 255 } 256 257 /** 258 * Update the data in a component 259 * @param item The data to fill in 260 */ 261 private void fillComponent(Key item) { 262 JComponent component = this.keyComponentPrefillMap.get(item.key); 263 if (component instanceof JTextField) { 264 ((JTextField) component).setText(item.value); 265 } else if (component instanceof JComboBox) { 266 ComboBoxModel<?> comboBoxModel = ((JComboBox<?>) component).getModel(); 267 Object firstElement = comboBoxModel.getElementAt(0); 268 if (firstElement instanceof String) { 269 throw new JosmRuntimeException(comboBoxModel.getElementAt(0).getClass() + " is not supported"); 270 } else if (firstElement instanceof AutoCompletionItem) { 271 throw new JosmRuntimeException(comboBoxModel.getElementAt(0).getClass() + " is not supported"); 272 } else if (firstElement instanceof PresetListEntry) { 273 Set<String> values = new HashSet<>(splitOsmValue(item.value)); 274 for (int i = 0; i < comboBoxModel.getSize(); i++) { 275 PresetListEntry entry = (PresetListEntry) comboBoxModel.getElementAt(i); 276 if (values.contains(entry.value)) { 277 comboBoxModel.setSelectedItem(entry); 278 break; 279 } 280 } 281 } else { 282 throw new JosmRuntimeException(comboBoxModel.getElementAt(0).getClass() + " is not supported"); 283 } 284 } else if (component instanceof QuadStateCheckBox) { 285 QuadStateCheckBox box = (QuadStateCheckBox) component; 286 final boolean selected = Boolean.parseBoolean(item.value) || "yes".equals(item.value); 287 box.setState(selected ? QuadStateCheckBox.State.SELECTED : QuadStateCheckBox.State.NOT_SELECTED); 288 } else if (component instanceof JList) { 289 ListModel<?> model = ((JList<?>) component).getModel(); 290 ListSelectionModel selectionModel = ((JList<?>) component).getSelectionModel(); 291 Set<String> values = new HashSet<>(splitOsmValue(item.value)); 292 for (int i = 0; i < model.getSize(); i++) { 293 PresetListEntry entry = (PresetListEntry) model.getElementAt(i); 294 if (values.contains(entry.value)) { 295 selectionModel.addSelectionInterval(i, i); 296 } 297 } 298 } else if (component != null) { 299 throw new JosmRuntimeException(component.getClass() + " is not supported"); 300 } 301 } 302 303 /** 304 * Split osm values using `;` 305 * @param value the value to split 306 * @return The list of values 307 */ 308 private static List<String> splitOsmValue(String value) { 309 return Arrays.asList(value.split(";", -1)); 310 } 184 311 } -
src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetReader.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetReader.java b/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetReader.java
a b 23 23 24 24 import javax.swing.JOptionPane; 25 25 26 import org.openstreetmap.josm.data.preferences.sources.ExtendedSourceEntry; 26 27 import org.openstreetmap.josm.data.preferences.sources.PresetPrefHelper; 27 28 import org.openstreetmap.josm.gui.MainApplication; 28 29 import org.openstreetmap.josm.gui.tagging.presets.items.Check; … … 135 136 136 137 private static XmlObjectParser buildParser() { 137 138 XmlObjectParser parser = new XmlObjectParser(); 139 parser.mapOnStart("presets", ExtendedSourceEntry.class); 138 140 parser.mapOnStart("item", TaggingPreset.class); 139 141 parser.mapOnStart("separator", TaggingPresetSeparator.class); 140 142 parser.mapBoth("group", TaggingPresetMenu.class); … … 180 182 static Collection<TaggingPreset> readAll(Reader in, boolean validate, HashSetWithLast<TaggingPreset> all) throws SAXException { 181 183 XmlObjectParser parser = buildParser(); 182 184 185 /** to be used to help link presets */ 186 ExtendedSourceEntry entry = null; 183 187 /** to detect end of {@code <checkgroup>} */ 184 188 CheckGroup lastcheckgroup = null; 185 189 /** to detect end of {@code <group>} */ … … 247 251 } 248 252 continue; 249 253 } 254 if (o instanceof ExtendedSourceEntry) { 255 if (entry != null) { 256 throw new SAXException(tr("Preset has multiple <preset> elements")); 257 } 258 entry = (ExtendedSourceEntry) o; 259 continue; 260 } 250 261 if (!(o instanceof TaggingPresetItem) && !checks.isEmpty()) { 251 262 all.getLast().data.addAll(checks); 252 263 checks.clear(); … … 338 349 all.getLast().data.addAll(checks); 339 350 checks.clear(); 340 351 } 352 if (entry != null) { 353 for (TaggingPreset preset : all) { 354 preset.preset_list_name = entry.name; 355 } 356 } 341 357 return all; 342 358 } 343 359 … … 364 380 */ 365 381 static Collection<TaggingPreset> readAll(String source, boolean validate, HashSetWithLast<TaggingPreset> all) 366 382 throws SAXException, IOException { 367 Collection<TaggingPreset> tp ;383 Collection<TaggingPreset> tp = all; 368 384 Logging.debug("Reading presets from {0}", source); 369 385 Stopwatch stopwatch = Stopwatch.createStarted(); 370 386 try ( … … 377 393 I18n.addTexts(zipIcons); 378 394 } 379 395 try (InputStreamReader r = UTFInputStreamReader.create(zip == null ? cf.getInputStream() : zip)) { 380 tp = readAll(new BufferedReader(r), validate, all);396 tp.addAll(readAll(new BufferedReader(r), validate, new HashSetWithLast<>())); 381 397 } 382 398 } 383 399 Logging.debug(stopwatch.toString("Reading presets")); -
src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresets.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresets.java b/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresets.java
a b 4 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 5 6 6 import java.util.ArrayList; 7 import java.util.Arrays; 7 8 import java.util.Collection; 8 9 import java.util.Collections; 9 10 import java.util.HashMap; 10 11 import java.util.HashSet; 12 import java.util.List; 11 13 import java.util.Map; 14 import java.util.Objects; 12 15 import java.util.Set; 16 import java.util.concurrent.ExecutionException; 17 import java.util.regex.Pattern; 18 import java.util.stream.Collectors; 13 19 14 20 import javax.swing.JMenu; 15 21 import javax.swing.JMenuItem; 22 import javax.swing.JOptionPane; 16 23 import javax.swing.JSeparator; 24 import javax.swing.SwingWorker; 17 25 18 26 import org.openstreetmap.josm.actions.PreferencesAction; 19 27 import org.openstreetmap.josm.data.osm.IPrimitive; … … 23 31 import org.openstreetmap.josm.gui.MainApplication; 24 32 import org.openstreetmap.josm.gui.MainMenu; 25 33 import org.openstreetmap.josm.gui.MenuScroller; 34 import org.openstreetmap.josm.gui.Notification; 26 35 import org.openstreetmap.josm.gui.preferences.ToolbarPreferences; 27 36 import org.openstreetmap.josm.gui.preferences.map.TaggingPresetPreference; 28 37 import org.openstreetmap.josm.gui.tagging.presets.items.CheckGroup; 38 import org.openstreetmap.josm.gui.tagging.presets.items.Key; 29 39 import org.openstreetmap.josm.gui.tagging.presets.items.KeyedItem; 40 import org.openstreetmap.josm.gui.tagging.presets.items.PresetLink; 30 41 import org.openstreetmap.josm.gui.tagging.presets.items.Roles; 31 42 import org.openstreetmap.josm.gui.tagging.presets.items.Roles.Role; 43 import org.openstreetmap.josm.spi.preferences.Config; 44 import org.openstreetmap.josm.tools.JosmRuntimeException; 32 45 import org.openstreetmap.josm.tools.Logging; 33 46 import org.openstreetmap.josm.tools.MultiMap; 34 47 import org.openstreetmap.josm.tools.SubclassFilteredCollection; … … 39 52 */ 40 53 public final class TaggingPresets { 41 54 55 /** Patterns for primary keys */ 56 private static final List<Pattern> PRIMARY_KEY_PATTERNS = Config.getPref().getList("tagging.presets.primary.key.patterns", 57 Arrays.asList("^name(:.*)?$", "^brand$")).stream().map(Pattern::compile).collect(Collectors.toList()); 58 42 59 /** The collection of tagging presets */ 43 60 private static final Collection<TaggingPreset> taggingPresets = new ArrayList<>(); 44 61 … … 58 75 */ 59 76 public static final ListProperty ICON_SOURCES = new ListProperty("taggingpreset.icon.sources", null); 60 77 private static final IntegerProperty MIN_ELEMENTS_FOR_SCROLLER = new IntegerProperty("taggingpreset.min-elements-for-scroller", 15); 78 /** 79 * A runnable that builds links for autocomplete usage. This can take a few minutes, so run in a background thread. 80 * It doesn't matter if it is done sequentially, so we can use the {@link SwingWorker} executor. 81 */ 82 private static PresetLinkBuilder presetLinkBuilder; 83 84 private static class PresetLinkBuilder extends SwingWorker<Void, Void> { 85 86 @Override 87 protected Void doInBackground() { 88 // This defaults to false since it significantly slows down startup. 89 if (Config.getPref().getBoolean("expert.tagging.preset.children.from.heuristics", true)) { 90 // If we want to try and guess what preset something goes to. Only accounts for presets that ask no questions. 91 getPresetChildrenFromTags(); 92 } 93 if (isCancelled()) { 94 return null; 95 } 96 getPresetChildrenFromLinks(); 97 return null; 98 } 99 100 @Override 101 protected void done() { 102 super.done(); 103 resetPresetLinkBuilder(this); 104 if (MainApplication.isDisplayingMapView() && !isCancelled()) { 105 new Notification(tr("Preset linking done. Autofill is now available")).setIcon(JOptionPane.INFORMATION_MESSAGE).show(); 106 } 107 try { 108 get(); 109 } catch (ExecutionException e) { 110 throw new JosmRuntimeException(e); 111 } catch (InterruptedException e) { 112 Logging.trace(e); 113 Thread.currentThread().interrupt(); 114 } 115 } 116 117 private static void resetPresetLinkBuilder(PresetLinkBuilder current) { 118 if (presetLinkBuilder == current) { 119 presetLinkBuilder = null; 120 } 121 } 122 } 61 123 62 124 private TaggingPresets() { 63 125 // Hide constructor for utility classes … … 67 129 * Initializes tagging presets from preferences. 68 130 */ 69 131 public static void readFromPreferences() { 132 if (presetLinkBuilder != null) { 133 presetLinkBuilder.cancel(true); 134 } 70 135 taggingPresets.clear(); 71 136 taggingPresets.addAll(TaggingPresetReader.readFromPreferences(false, false)); 72 137 cachePresets(taggingPresets); 138 presetLinkBuilder = new PresetLinkBuilder(); 139 presetLinkBuilder.execute(); 140 } 141 142 /** 143 * Generate preset children from the tags. Example: `amenity=fast_food` + `name=McDonald's` will have a parent with `amenity=fast_food`. 144 * This adds a {@link PresetLink} that has {@link PresetLink#parent} set to {@code true}. 145 * This only generates where all of the {@link TaggingPreset#data} objects are {@link Key} objects. 146 */ 147 private static void getPresetChildrenFromTags() { 148 // Used to avoid multiple instantiations of an array list 149 List<String> parentList = new ArrayList<>(); 150 for (TaggingPreset preset : taggingPresets) { 151 if (preset.data.stream().allMatch(Key.class::isInstance)) { 152 // OK. Let us find the parent. 153 Collection<TaggingPreset> matching = TaggingPresets.getMatchingPresets(null, preset.getMinimalTagMap(), false); 154 List<TaggingPreset> other = matching.stream().filter(p -> !p.equals(preset) && !p.data.equals(preset.data)) 155 .collect(Collectors.toList()); 156 if (other.size() == 1) { 157 TaggingPreset parent = other.iterator().next(); 158 parentList.clear(); 159 String presetListName = parent.preset_list_name; 160 while (parent != null && parent.name != null) { 161 parentList.add(parent.name); 162 parent = parent.group; 163 } 164 // Add this last in order to have it in the "right" position when the list is reversed 165 parentList.add(presetListName); 166 Collections.reverse(parentList); 167 PresetLink presetLink = new PresetLink(); 168 presetLink.preset_name = parentList.stream() 169 .filter(Objects::nonNull).collect(Collectors.joining("/", "josm-preset://", "")); 170 presetLink.parent = presetLink.preset_name.length() > "josm-preset://".length(); 171 if (presetLink.parent) { 172 preset.data.add(presetLink); 173 } 174 } 175 } 176 } 177 } 178 179 /** 180 * Tell the parent preset about their children for autocomplete 181 */ 182 private static void getPresetChildrenFromLinks() { 183 for (TaggingPreset preset : taggingPresets) { 184 for (TaggingPresetItem item : preset.data) { 185 if (item instanceof PresetLink) { 186 ((PresetLink) item).reverseLinkPreset(preset); 187 } 188 } 189 } 73 190 } 74 191 75 192 /** … … 228 345 return PRESET_TAG_CACHE.get(key) != null; 229 346 } 230 347 348 /** 349 * Check if a key is a primary (important) key 350 * @param key The key to check 351 * @return {@code true} if it can be used to prefill other data 352 * @since xxx 353 */ 354 public static boolean isPrimaryKey(String key) { 355 for (Pattern pattern : PRIMARY_KEY_PATTERNS) { 356 if (pattern.matcher(key).matches()) { 357 return true; 358 } 359 } 360 return false; 361 } 362 231 363 /** 232 364 * Replies a new collection of all presets matching the parameters. 233 365 * 234 * @param t the preset types to include 366 * @param t the preset types to include. {@code null} means any preset type is ok. 235 367 * @param tags the tags to perform matching on, see {@link TaggingPresetItem#matches(Map)} 236 368 * @param onlyShowable whether only {@link TaggingPreset#isShowable() showable} presets should be returned 237 369 * @return a new collection of all presets matching the parameters.