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

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

see #8465 - enable and fix more warnings

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