source: josm/trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetSelector.java@ 7015

Last change on this file since 7015 was 7015, checked in by Don-vip, 10 years ago

see #8465 - fix generics for JComboBox/JList

File size: 19.3 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.tagging;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.BorderLayout;
7import java.awt.Component;
8import java.awt.Dimension;
9import java.awt.event.ActionEvent;
10import java.awt.event.ActionListener;
11import java.awt.event.ItemEvent;
12import java.awt.event.ItemListener;
13import java.awt.event.KeyAdapter;
14import java.awt.event.KeyEvent;
15import java.awt.event.MouseAdapter;
16import java.awt.event.MouseEvent;
17import java.util.ArrayList;
18import java.util.Collection;
19import java.util.Collections;
20import java.util.EnumSet;
21import java.util.HashSet;
22import java.util.List;
23
24import javax.swing.AbstractAction;
25import javax.swing.AbstractListModel;
26import javax.swing.Action;
27import javax.swing.BoxLayout;
28import javax.swing.DefaultListCellRenderer;
29import javax.swing.Icon;
30import javax.swing.JCheckBox;
31import javax.swing.JLabel;
32import javax.swing.JList;
33import javax.swing.JPanel;
34import javax.swing.JPopupMenu;
35import javax.swing.JScrollPane;
36import javax.swing.event.DocumentEvent;
37import javax.swing.event.DocumentListener;
38import javax.swing.event.ListSelectionEvent;
39import javax.swing.event.ListSelectionListener;
40
41import org.openstreetmap.josm.Main;
42import org.openstreetmap.josm.data.SelectionChangedListener;
43import org.openstreetmap.josm.data.osm.Node;
44import org.openstreetmap.josm.data.osm.OsmPrimitive;
45import org.openstreetmap.josm.data.osm.Relation;
46import org.openstreetmap.josm.data.osm.Way;
47import org.openstreetmap.josm.data.preferences.BooleanProperty;
48import org.openstreetmap.josm.gui.preferences.map.TaggingPresetPreference;
49import org.openstreetmap.josm.gui.tagging.TaggingPresetItems.Key;
50import org.openstreetmap.josm.gui.tagging.TaggingPresetItems.KeyedItem;
51import org.openstreetmap.josm.gui.tagging.TaggingPresetItems.Role;
52import org.openstreetmap.josm.gui.tagging.TaggingPresetItems.Roles;
53import org.openstreetmap.josm.gui.widgets.JosmTextField;
54import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
55
56/**
57 * GUI component to select tagging preset: the list with filter and two checkboxes
58 * @since 6068
59 */
60public class TaggingPresetSelector extends JPanel implements SelectionChangedListener {
61
62 private static final int CLASSIFICATION_IN_FAVORITES = 300;
63 private static final int CLASSIFICATION_NAME_MATCH = 300;
64 private static final int CLASSIFICATION_GROUP_MATCH = 200;
65 private static final int CLASSIFICATION_TAGS_MATCH = 100;
66
67 private static final BooleanProperty SEARCH_IN_TAGS = new BooleanProperty("taggingpreset.dialog.search-in-tags", true);
68 private static final BooleanProperty ONLY_APPLICABLE = new BooleanProperty("taggingpreset.dialog.only-applicable-to-selection", true);
69
70 private JosmTextField edSearchText;
71 private JList<TaggingPreset> lsResult;
72 private JCheckBox ckOnlyApplicable;
73 private JCheckBox ckSearchInTags;
74 private final EnumSet<TaggingPresetType> typesInSelection = EnumSet.noneOf(TaggingPresetType.class);
75 private boolean typesInSelectionDirty = true;
76 private final List<PresetClassification> classifications = new ArrayList<>();
77 private ResultListModel lsResultModel = new ResultListModel();
78
79 private ActionListener dblClickListener;
80 private ActionListener clickListener;
81
82 private static class ResultListCellRenderer extends DefaultListCellRenderer {
83 @Override
84 public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
85 JLabel result = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
86 TaggingPreset tp = (TaggingPreset)value;
87 result.setText(tp.getName());
88 result.setIcon((Icon) tp.getValue(Action.SMALL_ICON));
89 return result;
90 }
91 }
92
93 private static class ResultListModel extends AbstractListModel<TaggingPreset> {
94
95 private List<PresetClassification> presets = new ArrayList<>();
96
97 public void setPresets(List<PresetClassification> presets) {
98 this.presets = presets;
99 fireContentsChanged(this, 0, Integer.MAX_VALUE);
100 }
101
102 public List<PresetClassification> getPresets() {
103 return presets;
104 }
105
106 @Override
107 public TaggingPreset getElementAt(int index) {
108 return presets.get(index).preset;
109 }
110
111 @Override
112 public int getSize() {
113 return presets.size();
114 }
115 }
116
117 private static class PresetClassification implements Comparable<PresetClassification> {
118 public final TaggingPreset preset;
119 public int classification;
120 public int favoriteIndex;
121 private final Collection<String> groups = new HashSet<>();
122 private final Collection<String> names = new HashSet<>();
123 private final Collection<String> tags = new HashSet<>();
124
125 PresetClassification(TaggingPreset preset) {
126 this.preset = preset;
127 TaggingPreset group = preset.group;
128 while (group != null) {
129 Collections.addAll(groups, group.getLocaleName().toLowerCase().split("\\s"));
130 group = group.group;
131 }
132 Collections.addAll(names, preset.getLocaleName().toLowerCase().split("\\s"));
133 for (TaggingPresetItem item: preset.data) {
134 if (item instanceof KeyedItem) {
135 tags.add(((KeyedItem) item).key);
136 if (item instanceof TaggingPresetItems.ComboMultiSelect) {
137 final TaggingPresetItems.ComboMultiSelect cms = (TaggingPresetItems.ComboMultiSelect) item;
138 if (Boolean.parseBoolean(cms.values_searchable)) {
139 tags.addAll(cms.getDisplayValues());
140 }
141 }
142 if (item instanceof Key && ((Key) item).value != null) {
143 tags.add(((Key) item).value);
144 }
145 } else if (item instanceof Roles) {
146 for (Role role : ((Roles) item).roles) {
147 tags.add(role.key);
148 }
149 }
150 }
151 }
152
153 private int isMatching(Collection<String> values, String[] searchString) {
154 int sum = 0;
155 for (String word: searchString) {
156 boolean found = false;
157 boolean foundFirst = false;
158 for (String value: values) {
159 int index = value.toLowerCase().indexOf(word);
160 if (index == 0) {
161 foundFirst = true;
162 break;
163 } else if (index > 0) {
164 found = true;
165 }
166 }
167 if (foundFirst) {
168 sum += 2;
169 } else if (found) {
170 sum += 1;
171 } else
172 return 0;
173 }
174 return sum;
175 }
176
177 int isMatchingGroup(String[] words) {
178 return isMatching(groups, words);
179 }
180
181 int isMatchingName(String[] words) {
182 return isMatching(names, words);
183 }
184
185 int isMatchingTags(String[] words) {
186 return isMatching(tags, words);
187 }
188
189 @Override
190 public int compareTo(PresetClassification o) {
191 int result = o.classification - classification;
192 if (result == 0)
193 return preset.getName().compareTo(o.preset.getName());
194 else
195 return result;
196 }
197
198 @Override
199 public String toString() {
200 return classification + " " + preset.toString();
201 }
202 }
203
204 /**
205 * Constructs a new {@code TaggingPresetSelector}.
206 */
207 public TaggingPresetSelector(boolean displayOnlyApplicable, boolean displaySearchInTags) {
208 super(new BorderLayout());
209 if (TaggingPresetPreference.taggingPresets!=null) {
210 loadPresets(TaggingPresetPreference.taggingPresets);
211 }
212
213 edSearchText = new JosmTextField();
214 edSearchText.getDocument().addDocumentListener(new DocumentListener() {
215 @Override public void removeUpdate(DocumentEvent e) { filterPresets(); }
216 @Override public void insertUpdate(DocumentEvent e) { filterPresets(); }
217 @Override public void changedUpdate(DocumentEvent e) { filterPresets(); }
218 });
219 edSearchText.addKeyListener(new KeyAdapter() {
220 @Override
221 public void keyPressed(KeyEvent e) {
222 switch (e.getKeyCode()) {
223 case KeyEvent.VK_DOWN:
224 selectPreset(lsResult.getSelectedIndex() + 1);
225 break;
226 case KeyEvent.VK_UP:
227 selectPreset(lsResult.getSelectedIndex() - 1);
228 break;
229 case KeyEvent.VK_PAGE_DOWN:
230 selectPreset(lsResult.getSelectedIndex() + 10);
231 break;
232 case KeyEvent.VK_PAGE_UP:
233 selectPreset(lsResult.getSelectedIndex() - 10);
234 break;
235 case KeyEvent.VK_HOME:
236 selectPreset(0);
237 break;
238 case KeyEvent.VK_END:
239 selectPreset(lsResultModel.getSize());
240 break;
241 }
242 }
243 });
244 add(edSearchText, BorderLayout.NORTH);
245
246 lsResult = new JList<>();
247 lsResult.setModel(lsResultModel);
248 lsResult.setCellRenderer(new ResultListCellRenderer());
249 lsResult.addMouseListener(new MouseAdapter() {
250 @Override
251 public void mouseClicked(MouseEvent e) {
252 if (e.getClickCount()>1) {
253 if (dblClickListener!=null)
254 dblClickListener.actionPerformed(null);
255 } else {
256 if (clickListener!=null)
257 clickListener.actionPerformed(null);
258 }
259 }
260 });
261 add(new JScrollPane(lsResult), BorderLayout.CENTER);
262
263 JPanel pnChecks = new JPanel();
264 pnChecks.setLayout(new BoxLayout(pnChecks, BoxLayout.Y_AXIS));
265
266 if (displayOnlyApplicable) {
267 ckOnlyApplicable = new JCheckBox();
268 ckOnlyApplicable.setText(tr("Show only applicable to selection"));
269 pnChecks.add(ckOnlyApplicable);
270 ckOnlyApplicable.addItemListener(new ItemListener() {
271 @Override
272 public void itemStateChanged(ItemEvent e) {
273 filterPresets();
274 }
275 });
276 }
277
278 if (displaySearchInTags) {
279 ckSearchInTags = new JCheckBox();
280 ckSearchInTags.setText(tr("Search in tags"));
281 ckSearchInTags.setSelected(SEARCH_IN_TAGS.get());
282 ckSearchInTags.addItemListener(new ItemListener() {
283 @Override
284 public void itemStateChanged(ItemEvent e) {
285 filterPresets();
286 }
287 });
288 pnChecks.add(ckSearchInTags);
289 }
290
291 add(pnChecks, BorderLayout.SOUTH);
292
293 setPreferredSize(new Dimension(400, 300));
294 filterPresets();
295 JPopupMenu popupMenu = new JPopupMenu();
296 popupMenu.add(new AbstractAction(tr("Add toolbar button")) {
297 @Override
298 public void actionPerformed(ActionEvent ae) {
299 String res = getSelectedPreset().getToolbarString();
300 Main.toolbar.addCustomButton(res, -1, false);
301 }
302 });
303 lsResult.addMouseListener(new PopupMenuLauncher(popupMenu));
304 }
305
306 private void selectPreset(int newIndex) {
307 if (newIndex < 0) {
308 newIndex = 0;
309 }
310 if (newIndex > lsResultModel.getSize() - 1) {
311 newIndex = lsResultModel.getSize() - 1;
312 }
313 lsResult.setSelectedIndex(newIndex);
314 lsResult.ensureIndexIsVisible(newIndex);
315 }
316
317 /**
318 * Search expression can be in form: "group1/group2/name" where names can contain multiple words
319 */
320 private void filterPresets() {
321 //TODO Save favorites to file
322 String text = edSearchText.getText().toLowerCase();
323
324 String[] groupWords;
325 String[] nameWords;
326
327 if (text.contains("/")) {
328 groupWords = text.substring(0, text.lastIndexOf('/')).split("[\\s/]");
329 nameWords = text.substring(text.indexOf('/') + 1).split("\\s");
330 } else {
331 groupWords = null;
332 nameWords = text.split("\\s");
333 }
334
335 boolean onlyApplicable = ckOnlyApplicable != null && ckOnlyApplicable.isSelected();
336 boolean inTags = ckSearchInTags != null && ckSearchInTags.isSelected();
337
338 List<PresetClassification> result = new ArrayList<>();
339 PRESET_LOOP:
340 for (PresetClassification presetClasification: classifications) {
341 TaggingPreset preset = presetClasification.preset;
342 presetClasification.classification = 0;
343
344 if (onlyApplicable && preset.types != null) {
345 boolean found = false;
346 for (TaggingPresetType type: preset.types) {
347 if (getTypesInSelection().contains(type)) {
348 found = true;
349 break;
350 }
351 }
352 if (!found) {
353 continue;
354 }
355 }
356
357 if (groupWords != null && presetClasification.isMatchingGroup(groupWords) == 0) {
358 continue PRESET_LOOP;
359 }
360
361 int matchName = presetClasification.isMatchingName(nameWords);
362
363 if (matchName == 0) {
364 if (groupWords == null) {
365 int groupMatch = presetClasification.isMatchingGroup(nameWords);
366 if (groupMatch > 0) {
367 presetClasification.classification = CLASSIFICATION_GROUP_MATCH + groupMatch;
368 }
369 }
370 if (presetClasification.classification == 0 && inTags) {
371 int tagsMatch = presetClasification.isMatchingTags(nameWords);
372 if (tagsMatch > 0) {
373 presetClasification.classification = CLASSIFICATION_TAGS_MATCH + tagsMatch;
374 }
375 }
376 } else {
377 presetClasification.classification = CLASSIFICATION_NAME_MATCH + matchName;
378 }
379
380 if (presetClasification.classification > 0) {
381 presetClasification.classification += presetClasification.favoriteIndex;
382 result.add(presetClasification);
383 }
384 }
385
386 Collections.sort(result);
387 lsResultModel.setPresets(result);
388
389 }
390
391 private EnumSet<TaggingPresetType> getTypesInSelection() {
392 if (typesInSelectionDirty) {
393 synchronized (typesInSelection) {
394 typesInSelectionDirty = false;
395 typesInSelection.clear();
396 if (Main.main==null || Main.main.getCurrentDataSet() == null) return typesInSelection;
397 for (OsmPrimitive primitive : Main.main.getCurrentDataSet().getSelected()) {
398 if (primitive instanceof Node) {
399 typesInSelection.add(TaggingPresetType.NODE);
400 } else if (primitive instanceof Way) {
401 typesInSelection.add(TaggingPresetType.WAY);
402 if (((Way) primitive).isClosed()) {
403 typesInSelection.add(TaggingPresetType.CLOSEDWAY);
404 }
405 } else if (primitive instanceof Relation) {
406 typesInSelection.add(TaggingPresetType.RELATION);
407 }
408 }
409 }
410 }
411 return typesInSelection;
412 }
413
414 @Override
415 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
416 typesInSelectionDirty = true;
417 }
418
419 public void init() {
420 if (ckOnlyApplicable != null) {
421 ckOnlyApplicable.setEnabled(!getTypesInSelection().isEmpty());
422 ckOnlyApplicable.setSelected(!getTypesInSelection().isEmpty() && ONLY_APPLICABLE.get());
423 }
424 edSearchText.setText("");
425 filterPresets();
426 }
427
428 public void init(Collection<TaggingPreset> presets) {
429 classifications.clear();
430 loadPresets(presets);
431 init();
432 }
433
434 public void clearSelection() {
435 lsResult.getSelectionModel().clearSelection();
436 }
437
438 /**
439 * Save checkbox values in preferences for future reuse
440 */
441 public void savePreferences() {
442 if (ckSearchInTags != null) {
443 SEARCH_IN_TAGS.put(ckSearchInTags.isSelected());
444 }
445 if (ckOnlyApplicable != null && ckOnlyApplicable.isEnabled()) {
446 ONLY_APPLICABLE.put(ckOnlyApplicable.isSelected());
447 }
448 }
449
450 /**
451 * Determines, which preset is selected at the current moment
452 * @return selected preset (as action)
453 */
454 public TaggingPreset getSelectedPreset() {
455 List<PresetClassification> presets = lsResultModel.getPresets();
456 if (presets.isEmpty()) return null;
457 int idx = lsResult.getSelectedIndex();
458 if (idx == -1) {
459 idx = 0;
460 }
461 TaggingPreset preset = presets.get(idx).preset;
462 for (PresetClassification pc: classifications) {
463 if (pc.preset == preset) {
464 pc.favoriteIndex = CLASSIFICATION_IN_FAVORITES;
465 } else if (pc.favoriteIndex > 0) {
466 pc.favoriteIndex--;
467 }
468 }
469 return preset;
470 }
471
472 private void loadPresets(Collection<TaggingPreset> presets) {
473 for (TaggingPreset preset: presets) {
474 if (preset instanceof TaggingPresetSeparator || preset instanceof TaggingPresetMenu) {
475 continue;
476 }
477 classifications.add(new PresetClassification(preset));
478 }
479 }
480
481 public void setSelectedPreset(TaggingPreset p) {
482 lsResult.setSelectedValue(p, true);
483 }
484
485 public int getItemCount() {
486 return lsResultModel.getSize();
487 }
488
489 public void setDblClickListener(ActionListener dblClickListener) {
490 this.dblClickListener = dblClickListener;
491 }
492
493 public void setClickListener(ActionListener clickListener) {
494 this.clickListener = clickListener;
495 }
496
497 public void addSelectionListener(final ActionListener selectListener) {
498 lsResult.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
499 @Override
500 public void valueChanged(ListSelectionEvent e) {
501 if (!e.getValueIsAdjusting())
502 selectListener.actionPerformed(null);
503 }
504 });
505 }
506}
Note: See TracBrowser for help on using the repository browser.