source: josm/trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetSearchDialog.java@ 5170

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

cleanup svn:mime-type properties preventing Java sources from being viewed as such on Trac

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