| | 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() + " <small style=\"color:" + col + "\">" |
| | 110 | + matchingTags + "</small></html>"); |
| | 111 | } |
| | 112 | } |
| | 113 | } |
| | 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 | } |
| 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 | |
| | 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 | /** |