Ticket #22677: 22677.1.patch
File 22677.1.patch, 10.9 KB (added by , 4 months ago) |
---|
-
src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetSelector.java
Subject: [PATCH] See #22677: remove man_made=communications_tower The root problem is that people are (apparently) doing a search for communication and selecting the entry with communication in the name. ---
4 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 5 6 6 import java.awt.BorderLayout; 7 import java.awt.Color; 7 8 import java.awt.Component; 8 9 import java.awt.Dimension; 9 10 import java.awt.event.ActionEvent; … … 12 13 import java.util.Collection; 13 14 import java.util.Collections; 14 15 import java.util.EnumSet; 16 import java.util.HashMap; 15 17 import java.util.HashSet; 16 18 import java.util.Iterator; 17 19 import java.util.List; 18 20 import java.util.Locale; 21 import java.util.Map; 19 22 import java.util.Objects; 20 23 import java.util.Set; 24 import java.util.function.Predicate; 21 25 import java.util.regex.Pattern; 22 26 23 27 import javax.swing.AbstractAction; … … 43 47 import org.openstreetmap.josm.gui.tagging.presets.items.ComboMultiSelect; 44 48 import org.openstreetmap.josm.gui.tagging.presets.items.Key; 45 49 import org.openstreetmap.josm.gui.tagging.presets.items.KeyedItem; 50 import org.openstreetmap.josm.gui.tagging.presets.items.Label; 46 51 import org.openstreetmap.josm.gui.tagging.presets.items.Roles; 47 52 import org.openstreetmap.josm.gui.tagging.presets.items.Roles.Role; 48 53 import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher; 49 54 import org.openstreetmap.josm.gui.widgets.SearchTextResultListPanel; 55 import org.openstreetmap.josm.tools.ColorHelper; 50 56 import org.openstreetmap.josm.tools.Destroyable; 51 57 import org.openstreetmap.josm.tools.Utils; 52 58 … … 64 70 65 71 private static final Pattern PATTERN_PUNCTUATION = Pattern.compile("\\p{Punct}", Pattern.UNICODE_CHARACTER_CLASS); 66 72 private static final Pattern PATTERN_WHITESPACE = Pattern.compile("\\s", Pattern.UNICODE_CHARACTER_CLASS); 73 private static final Pattern PATTERN_GROUP = Pattern.compile("[\\s/]", Pattern.UNICODE_CHARACTER_CLASS); 67 74 68 75 private static final BooleanProperty SEARCH_IN_TAGS = new BooleanProperty("taggingpreset.dialog.search-in-tags", true); 69 76 private static final BooleanProperty ONLY_APPLICABLE = new BooleanProperty("taggingpreset.dialog.only-applicable-to-selection", true); … … 74 81 private boolean typesInSelectionDirty = true; 75 82 private final transient PresetClassifications classifications = new PresetClassifications(); 76 83 77 private staticclass ResultListCellRenderer implements ListCellRenderer<TaggingPreset> {84 private class ResultListCellRenderer implements ListCellRenderer<TaggingPreset> { 78 85 private final DefaultListCellRenderer def = new DefaultListCellRenderer(); 79 86 @Override 80 87 public Component getListCellRendererComponent(JList<? extends TaggingPreset> list, TaggingPreset tp, int index, 81 88 boolean isSelected, boolean cellHasFocus) { 82 89 JLabel result = (JLabel) def.getListCellRendererComponent(list, tp, index, isSelected, cellHasFocus); 90 result.setToolTipText(null); 83 91 result.setText(tp.getName()); 92 if (list.getModel().getSize() < 200 // Maybe this should be configurable? 93 && TaggingPresetSelector.this.ckSearchInTags != null 94 && TaggingPresetSelector.this.ckSearchInTags.isSelected()) { 95 final String searchText = getSearchText(); 96 final PresetClassification classification = new PresetClassification(tp); 97 final String[] wordSet = PATTERN_WHITESPACE.split(searchText, -1); 98 if (classification.isMatchingTags(wordSet) > 0) { 99 final String matchingTags = buildMatchingTagText(getSearchableTags(tp), wordSet); 100 if (matchingTags.length() > 0) { 101 result.setToolTipText(tr("Matching tags: {0}", matchingTags)); 102 Color complement = ColorHelper.complement(result.getBackground()); 103 // This gray works for light/dark themes 104 if (result.getBackground().getRGB() == Color.WHITE.getRGB()) { 105 complement = Color.GRAY; 106 } 107 String col = ColorHelper.color2html(complement); 108 result.setText("<html>" + tp.getName() + " <small style=\"color:" + col + "\">" 109 + matchingTags + "</small></html>"); 110 } 111 } 112 } 84 113 result.setIcon((Icon) tp.getValue(Action.SMALL_ICON)); 85 114 return result; 86 115 } 116 117 /** 118 * Get the text to show a user for tags that match the given search string 119 * @param tagMap The map of searchable tags 120 * @param wordSet The words to filter on 121 * @return The string to show the user 122 */ 123 private String buildMatchingTagText(Map<String, List<String>> tagMap, String[] wordSet) { 124 final Predicate<String> matchesWords = word -> { 125 for (String checkWord : wordSet) { 126 if (word.contains(checkWord)) { 127 return true; 128 } 129 } 130 return false; 131 }; 132 final StringBuilder sb = new StringBuilder(); 133 for (Map.Entry<String, List<String>> entry : tagMap.entrySet()) { 134 if (sb.length() > 0 && sb.lastIndexOf(" ") != sb.length() - 1) { 135 sb.append(' '); 136 } 137 int length = sb.length(); 138 // Add the entry if it matches 139 if (matchesWords.test(entry.getKey())) { 140 sb.append(entry.getKey()); 141 if (entry.getValue().size() == 1) { 142 sb.append('=').append(entry.getValue().get(0)); 143 } 144 } 145 146 if (!entry.getValue().isEmpty() && (length == sb.length() || entry.getValue().size() > 1)) { 147 // Add values if they match 148 boolean added = false; 149 for (String value : entry.getValue()) { 150 if (matchesWords.test(value)) { 151 // Add the key if it hasn't been added already 152 if (!added) { 153 if (length == sb.length()) { 154 sb.append(entry.getKey()).append('='); 155 } else if (length + entry.getKey().length() == sb.length()) { 156 sb.append('='); 157 } 158 } 159 if (added) { 160 sb.append(';'); 161 } else { 162 added = true; 163 } 164 sb.append(value); 165 } 166 } 167 } 168 } 169 return sb.toString(); 170 } 87 171 } 88 172 89 173 /** … … 112 196 this.preset = preset; 113 197 Set<String> groupSet = new HashSet<>(); 114 198 Set<String> nameSet = new HashSet<>(); 115 Set<String> tagSet = new HashSet<>();116 199 TaggingPreset group = preset.group; 117 200 while (group != null) { 118 201 addLocaleNames(groupSet, group); … … 119 202 group = group.group; 120 203 } 121 204 addLocaleNames(nameSet, preset); 122 for (TaggingPresetItem item: preset.data) { 123 if (item instanceof KeyedItem) { 124 tagSet.add(((KeyedItem) item).key); 125 if (item instanceof ComboMultiSelect) { 126 final ComboMultiSelect cms = (ComboMultiSelect) item; 127 if (cms.values_searchable) { 128 tagSet.addAll(cms.getDisplayValues()); 129 } 130 } 131 if (item instanceof Key && ((Key) item).value != null) { 132 tagSet.add(((Key) item).value); 133 } 134 } else if (item instanceof Roles) { 135 for (Role role : ((Roles) item).roles) { 136 tagSet.add(role.key); 137 } 138 } 139 } 205 Map<String, List<String>> searchableTags = getSearchableTags(preset); 206 Set<String> tagSet = new HashSet<>(searchableTags.keySet()); 207 searchableTags.values().forEach(tagSet::addAll); 208 140 209 // These should be "frozen" arrays 141 210 this.groupsSimplified = groupSet.stream().map(PresetClassification::simplifyString) 142 211 .toArray(String[]::new); … … 231 300 } 232 301 233 302 /** 303 * Get the searchable tag map for a preset 304 * @param preset The preset to parse 305 * @return A map of key to values that are searchable 306 */ 307 private static Map<String, List<String>> getSearchableTags(TaggingPreset preset) { 308 final Map<String, List<String>> tagMap = new HashMap<>(); 309 for (TaggingPresetItem item: preset.data) { 310 if (item instanceof KeyedItem) { 311 Collection<String> list = tagMap.computeIfAbsent(((KeyedItem) item).key, k -> new ArrayList<>()); 312 if (item instanceof ComboMultiSelect) { 313 final ComboMultiSelect cms = (ComboMultiSelect) item; 314 if (cms.values_searchable) { 315 list.addAll(cms.getDisplayValues()); 316 } 317 } 318 if (item instanceof Key && ((Key) item).value != null) { 319 list.add(((Key) item).value); 320 } 321 } else if (item instanceof Roles) { 322 for (Role role : ((Roles) item).roles) { 323 tagMap.computeIfAbsent(role.key, k -> new ArrayList<>()); // new list just in case a role is also a tag 324 } 325 } 326 } 327 return tagMap; 328 } 329 330 /** 234 331 * Constructs a new {@code TaggingPresetSelector}. 235 332 * @param displayOnlyApplicable if {@code true} display "Show only applicable to selection" checkbox 236 333 * @param displaySearchInTags if {@code true} display "Search in tags" checkbox … … 330 427 final String[] nameWords; 331 428 332 429 if (searchText.contains("/")) { 333 groupWords = searchText.substring(0, searchText.lastIndexOf('/')).split("(?U)[\\s/]", -1);430 groupWords = PATTERN_GROUP.split(searchText.substring(0, searchText.lastIndexOf('/')), -1); 334 431 nameWords = PATTERN_WHITESPACE.split(searchText.substring(searchText.indexOf('/') + 1), -1); 335 432 } else { 336 433 groupWords = null;