Ticket #22677: 22677.2.patch

File 22677.2.patch, 11.3 KB (added by taylor.smock, 2 years ago)

Handle check groups

  • src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetSelector.java

     
    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;
     
    4044import org.openstreetmap.josm.data.osm.OsmPrimitive;
    4145import org.openstreetmap.josm.data.preferences.BooleanProperty;
    4246import org.openstreetmap.josm.gui.MainApplication;
     47import org.openstreetmap.josm.gui.tagging.presets.items.Check;
     48import org.openstreetmap.josm.gui.tagging.presets.items.CheckGroup;
    4349import org.openstreetmap.josm.gui.tagging.presets.items.ComboMultiSelect;
    4450import org.openstreetmap.josm.gui.tagging.presets.items.Key;
    4551import org.openstreetmap.josm.gui.tagging.presets.items.KeyedItem;
     
    4753import org.openstreetmap.josm.gui.tagging.presets.items.Roles.Role;
    4854import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
    4955import org.openstreetmap.josm.gui.widgets.SearchTextResultListPanel;
     56import org.openstreetmap.josm.tools.ColorHelper;
    5057import org.openstreetmap.josm.tools.Destroyable;
    5158import org.openstreetmap.josm.tools.Utils;
    5259
     
    6471
    6572    private static final Pattern PATTERN_PUNCTUATION = Pattern.compile("\\p{Punct}", Pattern.UNICODE_CHARACTER_CLASS);
    6673    private static final Pattern PATTERN_WHITESPACE = Pattern.compile("\\s", Pattern.UNICODE_CHARACTER_CLASS);
     74    private static final Pattern PATTERN_GROUP = Pattern.compile("[\\s/]", Pattern.UNICODE_CHARACTER_CLASS);
    6775
    6876    private static final BooleanProperty SEARCH_IN_TAGS = new BooleanProperty("taggingpreset.dialog.search-in-tags", true);
    6977    private static final BooleanProperty ONLY_APPLICABLE = new BooleanProperty("taggingpreset.dialog.only-applicable-to-selection", true);
     
    7482    private boolean typesInSelectionDirty = true;
    7583    private final transient PresetClassifications classifications = new PresetClassifications();
    7684
    77     private static class ResultListCellRenderer implements ListCellRenderer<TaggingPreset> {
     85    private class ResultListCellRenderer implements ListCellRenderer<TaggingPreset> {
    7886        private final DefaultListCellRenderer def = new DefaultListCellRenderer();
    7987        @Override
    8088        public Component getListCellRendererComponent(JList<? extends TaggingPreset> list, TaggingPreset tp, int index,
    8189                boolean isSelected, boolean cellHasFocus) {
    8290            JLabel result = (JLabel) def.getListCellRendererComponent(list, tp, index, isSelected, cellHasFocus);
     91            result.setToolTipText(null);
    8392            result.setText(tp.getName());
     93            if (list.getModel().getSize() < 200 // Maybe this should be configurable?
     94                    && TaggingPresetSelector.this.ckSearchInTags != null
     95                    && TaggingPresetSelector.this.ckSearchInTags.isSelected()) {
     96                final String searchText = getSearchText();
     97                final PresetClassification classification = new PresetClassification(tp);
     98                final String[] wordSet = PATTERN_WHITESPACE.split(searchText, -1);
     99                if (classification.isMatchingTags(wordSet) > 0) {
     100                    final String matchingTags = buildMatchingTagText(getSearchableTags(tp), wordSet);
     101                    if (matchingTags.length() > 0) {
     102                        result.setToolTipText(tr("Matching tags: {0}", matchingTags));
     103                        Color complement = ColorHelper.complement(result.getBackground());
     104                        // This gray works for light/dark themes
     105                        if (result.getBackground().getRGB() == Color.WHITE.getRGB()) {
     106                            complement = Color.GRAY;
     107                        }
     108                        String col = ColorHelper.color2html(complement);
     109                        result.setText("<html>" + tp.getName() + "&nbsp;&nbsp;&nbsp;&nbsp;<small style=\"color:" + col + "\">"
     110                        + matchingTags + "</small></html>");
     111                    }
     112                }
     113            }
    84114            result.setIcon((Icon) tp.getValue(Action.SMALL_ICON));
    85115            return result;
    86116        }
     117
     118        /**
     119         * Get the text to show a user for tags that match the given search string
     120         * @param tagMap The map of searchable tags
     121         * @param wordSet The words to filter on
     122         * @return The string to show the user
     123         */
     124        private String buildMatchingTagText(Map<String, List<String>> tagMap, String[] wordSet) {
     125            final Predicate<String> matchesWords = word -> {
     126                for (String checkWord : wordSet) {
     127                    if (word.contains(checkWord)) {
     128                        return true;
     129                    }
     130                }
     131                return false;
     132            };
     133            final StringBuilder sb = new StringBuilder();
     134            for (Map.Entry<String, List<String>> entry : tagMap.entrySet()) {
     135                if (sb.length() > 0 && sb.lastIndexOf(" ") != sb.length() - 1) {
     136                    sb.append(' ');
     137                }
     138                int length = sb.length();
     139                // Add the entry if it matches
     140                if (matchesWords.test(entry.getKey())) {
     141                    sb.append(entry.getKey());
     142                    if (entry.getValue().size() == 1) {
     143                        sb.append('=').append(entry.getValue().get(0));
     144                    }
     145                }
     146
     147                if (!entry.getValue().isEmpty() && (length == sb.length() || entry.getValue().size() > 1)) {
     148                    // Add values if they match
     149                    boolean added = false;
     150                    for (String value : entry.getValue()) {
     151                        if (matchesWords.test(value)) {
     152                            // Add the key if it hasn't been added already
     153                            if (!added) {
     154                                if (length == sb.length()) {
     155                                    sb.append(entry.getKey()).append('=');
     156                                } else if (length + entry.getKey().length() == sb.length()) {
     157                                    sb.append('=');
     158                                }
     159                            }
     160                            if (added) {
     161                                sb.append(';');
     162                            } else {
     163                                added = true;
     164                            }
     165                            sb.append(value);
     166                        }
     167                    }
     168                }
     169            }
     170            return sb.toString();
     171        }
    87172    }
    88173
    89174    /**
     
    112197            this.preset = preset;
    113198            Set<String> groupSet = new HashSet<>();
    114199            Set<String> nameSet = new HashSet<>();
    115             Set<String> tagSet = new HashSet<>();
    116200            TaggingPreset group = preset.group;
    117201            while (group != null) {
    118202                addLocaleNames(groupSet, group);
     
    119203                group = group.group;
    120204            }
    121205            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             }
     206            Map<String, List<String>> searchableTags = getSearchableTags(preset);
     207            Set<String> tagSet = new HashSet<>(searchableTags.keySet());
     208            searchableTags.values().forEach(tagSet::addAll);
     209
    140210            // These should be "frozen" arrays
    141211            this.groupsSimplified = groupSet.stream().map(PresetClassification::simplifyString)
    142212                    .toArray(String[]::new);
     
    231301    }
    232302
    233303    /**
     304     * Get the searchable tag map for a preset
     305     * @param preset The preset to parse
     306     * @return A map of key to values that are searchable
     307     */
     308    private static Map<String, List<String>> getSearchableTags(TaggingPreset preset) {
     309        final Map<String, List<String>> tagMap = new HashMap<>();
     310        for (TaggingPresetItem item: preset.data) {
     311            if (item instanceof KeyedItem) {
     312                addKeyedItems(tagMap, (KeyedItem) item);
     313            } else if (item instanceof Roles) {
     314                for (Role role : ((Roles) item).roles) {
     315                    tagMap.computeIfAbsent(role.key, k -> new ArrayList<>()); // new list just in case a role is also a tag
     316                }
     317            } else if (item instanceof CheckGroup) {
     318                for (Check check : ((CheckGroup) item).checks) {
     319                    addKeyedItems(tagMap, check);
     320                }
     321            }
     322        }
     323        return tagMap;
     324    }
     325
     326    private static void addKeyedItems(Map<String, List<String>> tagMap, KeyedItem item) {
     327        Collection<String> list = tagMap.computeIfAbsent(item.key, k -> new ArrayList<>());
     328        if (item instanceof ComboMultiSelect) {
     329            final ComboMultiSelect cms = (ComboMultiSelect) item;
     330            if (cms.values_searchable) {
     331                list.addAll(cms.getValues());
     332                list.addAll(cms.getDisplayValues());
     333            }
     334        } else if (item instanceof Check) {
     335            Check check = (Check) item;
     336            list.addAll(check.getValues());
     337        } else if (item instanceof Key && ((Key) item).value != null) {
     338            list.add(((Key) item).value);
     339        }
     340    }
     341
     342    /**
    234343     * Constructs a new {@code TaggingPresetSelector}.
    235344     * @param displayOnlyApplicable if {@code true} display "Show only applicable to selection" checkbox
    236345     * @param displaySearchInTags if {@code true} display "Search in tags" checkbox
     
    330439            final String[] nameWords;
    331440
    332441            if (searchText.contains("/")) {
    333                 groupWords = searchText.substring(0, searchText.lastIndexOf('/')).split("(?U)[\\s/]", -1);
     442                groupWords = PATTERN_GROUP.split(searchText.substring(0, searchText.lastIndexOf('/')), -1);
    334443                nameWords = PATTERN_WHITESPACE.split(searchText.substring(searchText.indexOf('/') + 1), -1);
    335444            } else {
    336445                groupWords = null;