Ticket #22677: 22677.1.patch

File 22677.1.patch, 10.9 KB (added by taylor.smock, 4 months ago)

Update to current JOSM source

  • 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.
    ---
     
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55
    66import java.awt.BorderLayout;
     7import java.awt.Color;
    78import java.awt.Component;
    89import java.awt.Dimension;
    910import java.awt.event.ActionEvent;
     
    1213import java.util.Collection;
    1314import java.util.Collections;
    1415import java.util.EnumSet;
     16import java.util.HashMap;
    1517import java.util.HashSet;
    1618import java.util.Iterator;
    1719import java.util.List;
    1820import java.util.Locale;
     21import java.util.Map;
    1922import java.util.Objects;
    2023import java.util.Set;
     24import java.util.function.Predicate;
    2125import java.util.regex.Pattern;
    2226
    2327import javax.swing.AbstractAction;
     
    4347import org.openstreetmap.josm.gui.tagging.presets.items.ComboMultiSelect;
    4448import org.openstreetmap.josm.gui.tagging.presets.items.Key;
    4549import org.openstreetmap.josm.gui.tagging.presets.items.KeyedItem;
     50import org.openstreetmap.josm.gui.tagging.presets.items.Label;
    4651import org.openstreetmap.josm.gui.tagging.presets.items.Roles;
    4752import org.openstreetmap.josm.gui.tagging.presets.items.Roles.Role;
    4853import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
    4954import org.openstreetmap.josm.gui.widgets.SearchTextResultListPanel;
     55import org.openstreetmap.josm.tools.ColorHelper;
    5056import org.openstreetmap.josm.tools.Destroyable;
    5157import org.openstreetmap.josm.tools.Utils;
    5258
     
    6470
    6571    private static final Pattern PATTERN_PUNCTUATION = Pattern.compile("\\p{Punct}", Pattern.UNICODE_CHARACTER_CLASS);
    6672    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);
    6774
    6875    private static final BooleanProperty SEARCH_IN_TAGS = new BooleanProperty("taggingpreset.dialog.search-in-tags", true);
    6976    private static final BooleanProperty ONLY_APPLICABLE = new BooleanProperty("taggingpreset.dialog.only-applicable-to-selection", true);
     
    7481    private boolean typesInSelectionDirty = true;
    7582    private final transient PresetClassifications classifications = new PresetClassifications();
    7683
    77     private static class ResultListCellRenderer implements ListCellRenderer<TaggingPreset> {
     84    private class ResultListCellRenderer implements ListCellRenderer<TaggingPreset> {
    7885        private final DefaultListCellRenderer def = new DefaultListCellRenderer();
    7986        @Override
    8087        public Component getListCellRendererComponent(JList<? extends TaggingPreset> list, TaggingPreset tp, int index,
    8188                boolean isSelected, boolean cellHasFocus) {
    8289            JLabel result = (JLabel) def.getListCellRendererComponent(list, tp, index, isSelected, cellHasFocus);
     90            result.setToolTipText(null);
    8391            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() + "&nbsp;&nbsp;&nbsp;&nbsp;<small style=\"color:" + col + "\">"
     109                        + matchingTags + "</small></html>");
     110                    }
     111                }
     112            }
    84113            result.setIcon((Icon) tp.getValue(Action.SMALL_ICON));
    85114            return result;
    86115        }
     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        }
    87171    }
    88172
    89173    /**
     
    112196            this.preset = preset;
    113197            Set<String> groupSet = new HashSet<>();
    114198            Set<String> nameSet = new HashSet<>();
    115             Set<String> tagSet = new HashSet<>();
    116199            TaggingPreset group = preset.group;
    117200            while (group != null) {
    118201                addLocaleNames(groupSet, group);
     
    119202                group = group.group;
    120203            }
    121204            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
    140209            // These should be "frozen" arrays
    141210            this.groupsSimplified = groupSet.stream().map(PresetClassification::simplifyString)
    142211                    .toArray(String[]::new);
     
    231300    }
    232301
    233302    /**
     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    /**
    234331     * Constructs a new {@code TaggingPresetSelector}.
    235332     * @param displayOnlyApplicable if {@code true} display "Show only applicable to selection" checkbox
    236333     * @param displaySearchInTags if {@code true} display "Search in tags" checkbox
     
    330427            final String[] nameWords;
    331428
    332429            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);
    334431                nameWords = PATTERN_WHITESPACE.split(searchText.substring(searchText.indexOf('/') + 1), -1);
    335432            } else {
    336433                groupWords = null;