source: josm/trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPreset.java@ 5155

Last change on this file since 5155 was 5155, checked in by simon04, 12 years ago

fix #5933 - tagging presets: allow to change the matching process (match=none|key|key!|keyvalue), remove delete_if_empty, default defaults to "", adapted comments in defaultpresets.xml, refactoring of the matching process (removes some duplicate code and some magical arithmetic)

  • Property svn:eol-style set to native
File size: 60.0 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.gui.tagging;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.tools.I18n.trc;
6import static org.openstreetmap.josm.tools.I18n.trn;
7
8import java.awt.Component;
9import java.awt.Dimension;
10import java.awt.GridBagLayout;
11import java.awt.Image;
12import java.awt.Insets;
13import java.awt.event.ActionEvent;
14import java.io.BufferedReader;
15import java.io.File;
16import java.io.IOException;
17import java.io.InputStream;
18import java.io.InputStreamReader;
19import java.io.Reader;
20import java.io.UnsupportedEncodingException;
21import java.util.ArrayList;
22import java.util.Arrays;
23import java.util.Collection;
24import java.util.EnumSet;
25import java.util.HashMap;
26import java.util.HashSet;
27import java.util.LinkedHashMap;
28import java.util.LinkedList;
29import java.util.List;
30import java.util.Map;
31import java.util.TreeSet;
32
33import javax.swing.AbstractAction;
34import javax.swing.Action;
35import javax.swing.ImageIcon;
36import javax.swing.JComboBox;
37import javax.swing.JComponent;
38import javax.swing.JLabel;
39import javax.swing.JList;
40import javax.swing.JOptionPane;
41import javax.swing.JPanel;
42import javax.swing.JScrollPane;
43import javax.swing.JTextField;
44import javax.swing.ListCellRenderer;
45import javax.swing.ListModel;
46import javax.swing.SwingUtilities;
47
48import org.openstreetmap.josm.Main;
49import org.openstreetmap.josm.actions.search.SearchCompiler;
50import org.openstreetmap.josm.actions.search.SearchCompiler.Match;
51import org.openstreetmap.josm.command.ChangePropertyCommand;
52import org.openstreetmap.josm.command.Command;
53import org.openstreetmap.josm.command.SequenceCommand;
54import org.openstreetmap.josm.data.osm.Node;
55import org.openstreetmap.josm.data.osm.OsmPrimitive;
56import org.openstreetmap.josm.data.osm.OsmUtils;
57import org.openstreetmap.josm.data.osm.Relation;
58import org.openstreetmap.josm.data.osm.RelationMember;
59import org.openstreetmap.josm.data.osm.Tag;
60import org.openstreetmap.josm.data.osm.Way;
61import org.openstreetmap.josm.data.preferences.BooleanProperty;
62import org.openstreetmap.josm.gui.ExtendedDialog;
63import org.openstreetmap.josm.gui.MapView;
64import org.openstreetmap.josm.gui.QuadStateCheckBox;
65import org.openstreetmap.josm.gui.dialogs.relation.RelationEditor;
66import org.openstreetmap.josm.gui.layer.Layer;
67import org.openstreetmap.josm.gui.layer.OsmDataLayer;
68import org.openstreetmap.josm.gui.preferences.SourceEntry;
69import org.openstreetmap.josm.gui.preferences.map.TaggingPresetPreference.PresetPrefHelper;
70import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
71import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionItemPritority;
72import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;
73import org.openstreetmap.josm.gui.util.GuiHelper;
74import org.openstreetmap.josm.gui.widgets.HtmlPanel;
75import org.openstreetmap.josm.io.MirroredInputStream;
76import org.openstreetmap.josm.tools.GBC;
77import org.openstreetmap.josm.tools.ImageProvider;
78import org.openstreetmap.josm.tools.UrlLabel;
79import org.openstreetmap.josm.tools.XmlObjectParser;
80import org.openstreetmap.josm.tools.template_engine.ParseError;
81import org.openstreetmap.josm.tools.template_engine.TemplateEntry;
82import org.openstreetmap.josm.tools.template_engine.TemplateParser;
83import org.xml.sax.SAXException;
84
85/**
86 * This class read encapsulate one tagging preset. A class method can
87 * read in all predefined presets, either shipped with JOSM or that are
88 * in the config directory.
89 *
90 * It is also able to construct dialogs out of preset definitions.
91 */
92public class TaggingPreset extends AbstractAction implements MapView.LayerChangeListener {
93
94 public enum PresetType {
95 NODE(/* ICON */"Mf_node"), WAY(/* ICON */"Mf_way"), RELATION(/* ICON */"Mf_relation"), CLOSEDWAY(/* ICON */"Mf_closedway");
96
97 private final String iconName;
98
99 PresetType(String iconName) {
100 this.iconName = iconName;
101 }
102
103 public String getIconName() {
104 return iconName;
105 }
106
107 public String getName() {
108 return name().toLowerCase();
109 }
110
111 public static PresetType forPrimitive(OsmPrimitive p) {
112 return forPrimitiveType(p.getDisplayType());
113 }
114
115 public static PresetType forPrimitiveType(org.openstreetmap.josm.data.osm.OsmPrimitiveType type) {
116 switch (type) {
117 case NODE:
118 return NODE;
119 case WAY:
120 return WAY;
121 case CLOSEDWAY:
122 return CLOSEDWAY;
123 case RELATION:
124 return RELATION;
125 default:
126 throw new IllegalArgumentException();
127 }
128 }
129 }
130
131 /**
132 * Enum denoting how a match (see {@link Item#matches}) is performed.
133 */
134 private enum MatchType {
135
136 /**
137 * Neutral, i.e., do not consider this item for matching.
138 */
139 NONE("none"),
140 /**
141 * Positive if key matches, neutral otherwise.
142 */
143 KEY("key"),
144 /**
145 * Positive if key matches, negative otherwise.
146 */
147 KEY_REQUIRED("key!"),
148 /**
149 * Positive if key and value matches, negative otherwise.
150 */
151 KEY_VALUE("keyvalue");
152
153 private final String value;
154
155 private MatchType(String value) {
156 this.value = value;
157 }
158
159 public String getValue() {
160 return value;
161 }
162
163 public static MatchType ofString(String type) {
164 for (MatchType i : EnumSet.allOf(MatchType.class)) {
165 if (i.getValue().equals(type)) {
166 return i;
167 }
168 }
169 throw new IllegalArgumentException(type + " is not allowed");
170 }
171 }
172
173 public static final int DIALOG_ANSWER_APPLY = 1;
174 public static final int DIALOG_ANSWER_NEW_RELATION = 2;
175 public static final int DIALOG_ANSWER_CANCEL = 3;
176
177 public TaggingPresetMenu group = null;
178 public String name;
179 public String name_context;
180 public String locale_name;
181 public final static String OPTIONAL_TOOLTIP_TEXT = "Optional tooltip text";
182 private static File zipIcons = null;
183 private static final BooleanProperty PROP_FILL_DEFAULT = new BooleanProperty("taggingpreset.fill-default-for-tagged-primitives", false);
184
185 public static abstract class Item {
186
187 protected void initAutoCompletionField(AutoCompletingTextField field, String key) {
188 OsmDataLayer layer = Main.main.getEditLayer();
189 if (layer == null)
190 return;
191 AutoCompletionList list = new AutoCompletionList();
192 Main.main.getEditLayer().data.getAutoCompletionManager().populateWithTagValues(list, key);
193 field.setAutoCompletionList(list);
194 }
195
196 abstract boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel);
197
198 abstract void addCommands(List<Tag> changedTags);
199
200 boolean requestFocusInWindow() {
201 return false;
202 }
203
204 /**
205 * Tests whether the tags match this item.
206 * Note that for a match, at least one positive and no negative is required.
207 * @param tags the tags of an {@link OsmPrimitive}
208 * @return {@code true} if matches (positive), {@code null} if neutral, {@code false} if mismatches (negative).
209 */
210 abstract Boolean matches(Map<String, String> tags);
211 }
212
213 public static class Usage {
214 TreeSet<String> values;
215 boolean hadKeys = false;
216 boolean hadEmpty = false;
217 public boolean hasUniqueValue() {
218 return values.size() == 1 && !hadEmpty;
219 }
220
221 public boolean unused() {
222 return values.isEmpty();
223 }
224 public String getFirst() {
225 return values.first();
226 }
227
228 public boolean hadKeys() {
229 return hadKeys;
230 }
231 }
232
233 public static final String DIFFERENT = tr("<different>");
234
235 static Usage determineTextUsage(Collection<OsmPrimitive> sel, String key) {
236 Usage returnValue = new Usage();
237 returnValue.values = new TreeSet<String>();
238 for (OsmPrimitive s : sel) {
239 String v = s.get(key);
240 if (v != null) {
241 returnValue.values.add(v);
242 } else {
243 returnValue.hadEmpty = true;
244 }
245 if(s.hasKeys()) {
246 returnValue.hadKeys = true;
247 }
248 }
249 return returnValue;
250 }
251
252 static Usage determineBooleanUsage(Collection<OsmPrimitive> sel, String key) {
253
254 Usage returnValue = new Usage();
255 returnValue.values = new TreeSet<String>();
256 for (OsmPrimitive s : sel) {
257 String booleanValue = OsmUtils.getNamedOsmBoolean(s.get(key));
258 if (booleanValue != null) {
259 returnValue.values.add(booleanValue);
260 }
261 }
262 return returnValue;
263 }
264
265 protected static class PresetListEntry {
266 String value;
267 String display_value;
268 String short_description;
269
270 public String getListDisplay() {
271 if (value.equals(DIFFERENT))
272 return "<b>"+DIFFERENT.replaceAll("<", "&lt;").replaceAll(">", "&gt;")+"</b>";
273
274 if (value.equals(""))
275 return "&nbsp;";
276
277 StringBuilder res = new StringBuilder("<b>");
278 if (display_value != null) {
279 res.append(display_value);
280 } else {
281 res.append(value);
282 }
283 res.append("</b>");
284 if (short_description != null) {
285 // wrap in table to restrict the text width
286 res.append("<br><table><td width='232'>(").append(short_description).append(")</td></table>");
287 }
288 return res.toString();
289 }
290
291 public PresetListEntry(String value) {
292 this.value = value;
293 this.display_value = value;
294 }
295
296 public PresetListEntry(String value, String display_value) {
297 this.value = value;
298 this.display_value = display_value;
299 }
300
301 // toString is mainly used to initialize the Editor
302 @Override
303 public String toString() {
304 if (value.equals(DIFFERENT))
305 return DIFFERENT;
306 return display_value.replaceAll("<.*>", ""); // remove additional markup, e.g. <br>
307 }
308 }
309
310 public static class Text extends Item {
311
312 public String key;
313 public String text;
314 public String locale_text;
315 public String text_context;
316 public String default_;
317 public String originalValue;
318 public String use_last_as_default = "false";
319 public String match = MatchType.NONE.getValue();
320
321 private JComponent value;
322
323 @Override public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
324
325 // find out if our key is already used in the selection.
326 Usage usage = determineTextUsage(sel, key);
327 AutoCompletingTextField textField = new AutoCompletingTextField();
328 initAutoCompletionField(textField, key);
329 if (usage.unused()){
330 if (!usage.hadKeys() || PROP_FILL_DEFAULT.get() || "force".equals(use_last_as_default)) {
331 // selected osm primitives are untagged or filling default values feature is enabled
332 if (!"false".equals(use_last_as_default) && lastValue.containsKey(key)) {
333 textField.setText(lastValue.get(key));
334 } else {
335 textField.setText(default_);
336 }
337 } else {
338 // selected osm primitives are tagged and filling default values feature is disabled
339 textField.setText("");
340 }
341 value = textField;
342 originalValue = null;
343 } else if (usage.hasUniqueValue()) {
344 // all objects use the same value
345 textField.setText(usage.getFirst());
346 value = textField;
347 originalValue = usage.getFirst();
348 } else {
349 // the objects have different values
350 JComboBox comboBox = new JComboBox(usage.values.toArray());
351 comboBox.setEditable(true);
352 comboBox.setEditor(textField);
353 comboBox.getEditor().setItem(DIFFERENT);
354 value=comboBox;
355 originalValue = DIFFERENT;
356 }
357 if(locale_text == null) {
358 if (text != null) {
359 if(text_context != null) {
360 locale_text = trc(text_context, fixPresetString(text));
361 } else {
362 locale_text = tr(fixPresetString(text));
363 }
364 }
365 }
366 p.add(new JLabel(locale_text+":"), GBC.std().insets(0,0,10,0));
367 p.add(value, GBC.eol().fill(GBC.HORIZONTAL));
368 return true;
369 }
370
371 @Override
372 public void addCommands(List<Tag> changedTags) {
373
374 // return if unchanged
375 String v = (value instanceof JComboBox)
376 ? ((JComboBox) value).getEditor().getItem().toString()
377 : ((JTextField) value).getText();
378 v = v.trim();
379
380 if (!"false".equals(use_last_as_default)) {
381 lastValue.put(key, v);
382 }
383 if (v.equals(originalValue) || (originalValue == null && v.length() == 0))
384 return;
385
386 changedTags.add(new Tag(key, v));
387 }
388
389 @Override
390 boolean requestFocusInWindow() {
391 return value.requestFocusInWindow();
392 }
393
394 @Override
395 Boolean matches(Map<String, String> tags) {
396 switch (MatchType.ofString(match)) {
397 case NONE:
398 return null;
399 case KEY:
400 return tags.containsKey(key) ? true : null;
401 case KEY_REQUIRED:
402 return tags.containsKey(key);
403 default:
404 throw new IllegalArgumentException("key_value matching not supported for <text>: " + text);
405 }
406 }
407 }
408
409 public static class Check extends Item {
410
411 public String key;
412 public String text;
413 public String text_context;
414 public String locale_text;
415 public String value_on = OsmUtils.trueval;
416 public String value_off = OsmUtils.falseval;
417 public boolean default_ = false; // only used for tagless objects
418 public String match = MatchType.NONE.getValue();
419
420 private QuadStateCheckBox check;
421 private QuadStateCheckBox.State initialState;
422 private boolean def;
423
424 @Override public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
425
426 // find out if our key is already used in the selection.
427 Usage usage = determineBooleanUsage(sel, key);
428 def = default_;
429
430 if(locale_text == null) {
431 if(text_context != null) {
432 locale_text = trc(text_context, fixPresetString(text));
433 } else {
434 locale_text = tr(fixPresetString(text));
435 }
436 }
437
438 String oneValue = null;
439 for (String s : usage.values) {
440 oneValue = s;
441 }
442 if (usage.values.size() < 2 && (oneValue == null || value_on.equals(oneValue) || value_off.equals(oneValue))) {
443 if (def && !PROP_FILL_DEFAULT.get()) {
444 // default is set and filling default values feature is disabled - check if all primitives are untagged
445 for (OsmPrimitive s : sel)
446 if(s.hasKeys()) {
447 def = false;
448 }
449 }
450
451 // all selected objects share the same value which is either true or false or unset,
452 // we can display a standard check box.
453 initialState = value_on.equals(oneValue) ?
454 QuadStateCheckBox.State.SELECTED :
455 value_off.equals(oneValue) ?
456 QuadStateCheckBox.State.NOT_SELECTED :
457 def ? QuadStateCheckBox.State.SELECTED
458 : QuadStateCheckBox.State.UNSET;
459 check = new QuadStateCheckBox(locale_text, initialState,
460 new QuadStateCheckBox.State[] {
461 QuadStateCheckBox.State.SELECTED,
462 QuadStateCheckBox.State.NOT_SELECTED,
463 QuadStateCheckBox.State.UNSET });
464 } else {
465 def = false;
466 // the objects have different values, or one or more objects have something
467 // else than true/false. we display a quad-state check box
468 // in "partial" state.
469 initialState = QuadStateCheckBox.State.PARTIAL;
470 check = new QuadStateCheckBox(locale_text, QuadStateCheckBox.State.PARTIAL,
471 new QuadStateCheckBox.State[] {
472 QuadStateCheckBox.State.PARTIAL,
473 QuadStateCheckBox.State.SELECTED,
474 QuadStateCheckBox.State.NOT_SELECTED,
475 QuadStateCheckBox.State.UNSET });
476 }
477 p.add(check, GBC.eol().fill(GBC.HORIZONTAL));
478 return true;
479 }
480
481 @Override public void addCommands(List<Tag> changedTags) {
482 // if the user hasn't changed anything, don't create a command.
483 if (check.getState() == initialState && !def) return;
484
485 // otherwise change things according to the selected value.
486 changedTags.add(new Tag(key,
487 check.getState() == QuadStateCheckBox.State.SELECTED ? value_on :
488 check.getState() == QuadStateCheckBox.State.NOT_SELECTED ? value_off :
489 null));
490 }
491 @Override boolean requestFocusInWindow() {return check.requestFocusInWindow();}
492
493 @Override
494 Boolean matches(Map<String, String> tags) {
495 switch (MatchType.ofString(match)) {
496 case NONE:
497 return null;
498 case KEY:
499 return tags.containsKey(key) ? true : null;
500 case KEY_REQUIRED:
501 return tags.containsKey(key);
502 case KEY_VALUE:
503 return value_off.equals(tags.get(key)) || value_on.equals(tags.get(key));
504 default:
505 throw new IllegalStateException();
506 }
507 }
508 }
509
510 public static abstract class ComboMultiSelect extends Item {
511
512 public String key;
513 public String text;
514 public String text_context;
515 public String locale_text;
516 public String values;
517 public String values_context;
518 public String display_values;
519 public String locale_display_values;
520 public String short_descriptions;
521 public String locale_short_descriptions;
522 public String default_;
523 public String delimiter = ";";
524 public String use_last_as_default = "false";
525 public String match = MatchType.NONE.getValue();
526
527 protected List<String> short_description_list;
528 protected JComponent component;
529 protected Map<String, PresetListEntry> lhm;
530 protected Usage usage;
531 protected Object originalValue;
532
533 protected abstract Object getSelectedItem();
534 protected abstract void addToPanelAnchor(JPanel p, String def, String[] display_array);
535
536 protected char getDelChar() {
537 return delimiter.isEmpty() ? ';' : delimiter.charAt(0);
538 }
539
540 @Override
541 public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
542
543 // find out if our key is already used in the selection.
544 usage = determineTextUsage(sel, key);
545 String def = default_;
546
547 char delChar = getDelChar();
548
549 String[] value_array = splitEscaped(delChar, values);
550 String[] display_array;
551 String[] short_descriptions_array = null;
552
553 if (locale_display_values != null) {
554 display_array = splitEscaped(delChar, locale_display_values);
555 } else if (display_values != null) {
556 display_array = splitEscaped(delChar, display_values);
557 } else {
558 display_array = value_array;
559 }
560
561 if (locale_short_descriptions != null) {
562 short_descriptions_array = splitEscaped(delChar, locale_short_descriptions);
563 } else if (short_descriptions != null) {
564 short_descriptions_array = splitEscaped(delChar, short_descriptions);
565 } else if (short_description_list != null) {
566 short_descriptions_array = short_description_list.toArray(new String[0]);
567 }
568
569 if (!"false".equals(use_last_as_default) && def == null && lastValue.containsKey(key)) {
570 def = lastValue.get(key);
571 }
572
573 if (display_array.length != value_array.length) {
574 System.err.println(tr("Broken tagging preset \"{0}-{1}\" - number of items in ''display_values'' must be the same as in ''values''", key, text));
575 display_array = value_array;
576 }
577
578 if (short_descriptions_array != null && short_descriptions_array.length != value_array.length) {
579 System.err.println(tr("Broken tagging preset \"{0}-{1}\" - number of items in ''short_descriptions'' must be the same as in ''values''", key, text));
580 short_descriptions_array = null;
581 }
582
583 lhm = new LinkedHashMap<String, PresetListEntry>();
584 if (!usage.hasUniqueValue() && !usage.unused()) {
585 lhm.put(DIFFERENT, new PresetListEntry(DIFFERENT));
586 }
587 for (int i = 0; i < value_array.length; i++) {
588 PresetListEntry e = new PresetListEntry(value_array[i]);
589 e.display_value = (locale_display_values == null)
590 ? (values_context == null ? tr(fixPresetString(display_array[i]))
591 : trc(values_context, fixPresetString(display_array[i]))) : display_array[i];
592 if (short_descriptions_array != null) {
593 e.short_description = locale_short_descriptions == null ? tr(fixPresetString(short_descriptions_array[i]))
594 : fixPresetString(short_descriptions_array[i]);
595 }
596 lhm.put(value_array[i], e);
597 }
598
599 if (locale_text == null) {
600 if (text_context != null) {
601 locale_text = trc(text_context, fixPresetString(text));
602 } else {
603 locale_text = tr(fixPresetString(text));
604 }
605 }
606 p.add(new JLabel(locale_text + ":"), GBC.std().insets(0, 0, 10, 0));
607
608 addToPanelAnchor(p, def, display_array);
609
610 return true;
611
612 }
613
614 protected String getDisplayIfNull(String display) {
615 return display;
616 }
617
618 @Override
619 public void addCommands(List<Tag> changedTags) {
620 Object obj = getSelectedItem();
621 String display = (obj == null) ? null : obj.toString();
622 String value = null;
623 if (display == null) {
624 display = getDisplayIfNull(display);
625 }
626
627 if (display != null) {
628 for (String key : lhm.keySet()) {
629 String k = lhm.get(key).toString();
630 if (k != null && k.equals(display)) {
631 value = key;
632 }
633 }
634 if (value == null) {
635 value = display;
636 }
637 } else {
638 value = "";
639 }
640 value = value.trim();
641
642 // no change if same as before
643 if (originalValue == null) {
644 if (value.length() == 0)
645 return;
646 } else if (value.equals(originalValue.toString()))
647 return;
648
649 if (!"false".equals(use_last_as_default)) {
650 lastValue.put(key, value);
651 }
652 changedTags.add(new Tag(key, value));
653 }
654
655 public void setShort_description(String s) {
656 if (short_description_list == null) {
657 short_description_list = new ArrayList<String>();
658 }
659 short_description_list.add(tr(s));
660 }
661
662 @Override
663 boolean requestFocusInWindow() {
664 return component.requestFocusInWindow();
665 }
666
667 protected ListCellRenderer getListCellRenderer() {
668 return new ListCellRenderer() {
669
670 HtmlPanel lbl = new HtmlPanel();
671 JComponent dummy = new JComponent() {
672 };
673
674 public Component getListCellRendererComponent(
675 JList list,
676 Object value,
677 int index,
678 boolean isSelected,
679 boolean cellHasFocus) {
680 if (isSelected) {
681 lbl.setBackground(list.getSelectionBackground());
682 lbl.setForeground(list.getSelectionForeground());
683 } else {
684 lbl.setBackground(list.getBackground());
685 lbl.setForeground(list.getForeground());
686 }
687
688 PresetListEntry item = (PresetListEntry) value;
689 String s = item.getListDisplay();
690 lbl.setText(s);
691 lbl.setEnabled(list.isEnabled());
692 // We do not want the editor to have the maximum height of all
693 // entries. Return a dummy with bogus height.
694 if (index == -1) {
695 dummy.setPreferredSize(new Dimension(lbl.getPreferredSize().width, 10));
696 return dummy;
697 }
698 return lbl;
699 }
700 };
701 }
702
703 @Override
704 Boolean matches(Map<String, String> tags) {
705 switch (MatchType.ofString(match)) {
706 case NONE:
707 return null;
708 case KEY:
709 return tags.containsKey(key) ? true : null;
710 case KEY_REQUIRED:
711 return tags.containsKey(key);
712 case KEY_VALUE:
713 return tags.containsKey(key)
714 && Arrays.asList(splitEscaped(getDelChar(), values)).contains(tags.get(key));
715 default:
716 throw new IllegalStateException();
717 }
718 }
719 }
720
721 public static class Combo extends ComboMultiSelect {
722
723 public boolean editable = true;
724 protected JComboBox combo;
725
726 public Combo() {
727 delimiter = ",";
728 }
729
730 @Override
731 protected void addToPanelAnchor(JPanel p, String def, String[] display_array) {
732 if (!usage.unused()) {
733 for (String s : usage.values) {
734 if (!lhm.containsKey(s)) {
735 lhm.put(s, new PresetListEntry(s));
736 }
737 }
738 }
739 if (def != null && !lhm.containsKey(def)) {
740 lhm.put(def, new PresetListEntry(def));
741 }
742 lhm.put("", new PresetListEntry(""));
743
744 combo = new JComboBox(lhm.values().toArray());
745 component = combo;
746 combo.setRenderer(getListCellRenderer());
747 combo.setEditable(editable);
748 combo.setMaximumRowCount(13);
749 AutoCompletingTextField tf = new AutoCompletingTextField();
750 initAutoCompletionField(tf, key);
751 tf.getAutoCompletionList().add(Arrays.asList(display_array), AutoCompletionItemPritority.IS_IN_STANDARD);
752 combo.setEditor(tf);
753
754 if (usage.hasUniqueValue()) {
755 // all items have the same value (and there were no unset items)
756 originalValue = lhm.get(usage.getFirst());
757 combo.setSelectedItem(originalValue);
758 } else if (def != null && usage.unused()) {
759 // default is set and all items were unset
760 if (!usage.hadKeys() || PROP_FILL_DEFAULT.get() || "force".equals(use_last_as_default)) {
761 // selected osm primitives are untagged or filling default feature is enabled
762 combo.setSelectedItem(lhm.get(def).display_value);
763 } else {
764 // selected osm primitives are tagged and filling default feature is disabled
765 combo.setSelectedItem("");
766 }
767 originalValue = lhm.get(DIFFERENT);
768 } else if (usage.unused()) {
769 // all items were unset (and so is default)
770 originalValue = lhm.get("");
771 combo.setSelectedItem(originalValue);
772 } else {
773 originalValue = lhm.get(DIFFERENT);
774 combo.setSelectedItem(originalValue);
775 }
776 p.add(combo, GBC.eol().fill(GBC.HORIZONTAL));
777
778 }
779
780 @Override
781 protected Object getSelectedItem() {
782 return combo.getSelectedItem();
783
784 }
785
786 @Override
787 protected String getDisplayIfNull(String display) {
788 if (combo.isEditable())
789 return combo.getEditor().getItem().toString();
790 else
791 return display;
792
793 }
794 }
795
796 /**
797 * Class that allows list values to be assigned and retrieved as a comma-delimited
798 * string.
799 */
800 public static class ConcatenatingJList extends JList {
801 private String delimiter;
802 public ConcatenatingJList(String del, Object[] o) {
803 super(o);
804 delimiter = del;
805 }
806 public void setSelectedItem(Object o) {
807 if (o == null) {
808 clearSelection();
809 } else {
810 String s = o.toString();
811 HashSet<String> parts = new HashSet<String>(Arrays.asList(s.split(delimiter)));
812 ListModel lm = getModel();
813 int[] intParts = new int[lm.getSize()];
814 int j = 0;
815 for (int i = 0; i < lm.getSize(); i++) {
816 if (parts.contains((((PresetListEntry)lm.getElementAt(i)).value))) {
817 intParts[j++]=i;
818 }
819 }
820 setSelectedIndices(Arrays.copyOf(intParts, j));
821 // check if we have acutally managed to represent the full
822 // value with our presets. if not, cop out; we will not offer
823 // a selection list that threatens to ruin the value.
824 setEnabled(s.equals(getSelectedItem()));
825 }
826 }
827 public String getSelectedItem() {
828 ListModel lm = getModel();
829 int[] si = getSelectedIndices();
830 StringBuilder builder = new StringBuilder();
831 for (int i=0; i<si.length; i++) {
832 if (i>0) {
833 builder.append(delimiter);
834 }
835 builder.append(((PresetListEntry)lm.getElementAt(si[i])).value);
836 }
837 return builder.toString();
838 }
839 }
840
841 public static class MultiSelect extends ComboMultiSelect {
842
843 public long rows = -1;
844 protected ConcatenatingJList list;
845
846 @Override
847 protected void addToPanelAnchor(JPanel p, String def, String[] display_array) {
848 list = new ConcatenatingJList(delimiter, lhm.values().toArray());
849 component = list;
850 ListCellRenderer renderer = getListCellRenderer();
851 list.setCellRenderer(renderer);
852
853 if (usage.hasUniqueValue() && !usage.unused()) {
854 originalValue = usage.getFirst();
855 list.setSelectedItem(originalValue);
856 } else if (def != null && !usage.hadKeys() || PROP_FILL_DEFAULT.get() || "force".equals(use_last_as_default)) {
857 originalValue = DIFFERENT;
858 list.setSelectedItem(def);
859 } else if (usage.unused()) {
860 originalValue = null;
861 list.setSelectedItem(originalValue);
862 } else {
863 originalValue = DIFFERENT;
864 list.setSelectedItem(originalValue);
865 }
866
867 JScrollPane sp = new JScrollPane(list);
868 // if a number of rows has been specified in the preset,
869 // modify preferred height of scroll pane to match that row count.
870 if (rows != -1) {
871 double height = renderer.getListCellRendererComponent(list,
872 new PresetListEntry("x"), 0, false, false).getPreferredSize().getHeight() * rows;
873 sp.setPreferredSize(new Dimension((int) sp.getPreferredSize().getWidth(), (int) height));
874 }
875 p.add(sp, GBC.eol().fill(GBC.HORIZONTAL));
876
877
878 }
879
880 @Override
881 protected Object getSelectedItem() {
882 return list.getSelectedItem();
883 }
884 }
885
886 /**
887 * allow escaped comma in comma separated list:
888 * "A\, B\, C,one\, two" --> ["A, B, C", "one, two"]
889 * @param delimiter the delimiter, e.g. a comma. separates the entries and
890 * must be escaped within one entry
891 * @param s the string
892 */
893 private static String[] splitEscaped(char delemiter, String s) {
894 List<String> result = new ArrayList<String>();
895 boolean backslash = false;
896 StringBuilder item = new StringBuilder();
897 for (int i=0; i<s.length(); i++) {
898 char ch = s.charAt(i);
899 if (backslash) {
900 item.append(ch);
901 backslash = false;
902 } else if (ch == '\\') {
903 backslash = true;
904 } else if (ch == delemiter) {
905 result.add(item.toString());
906 item.setLength(0);
907 } else {
908 item.append(ch);
909 }
910 }
911 if (item.length() > 0) {
912 result.add(item.toString());
913 }
914 return result.toArray(new String[result.size()]);
915 }
916
917 public static class Label extends Item {
918
919 public String text;
920 public String text_context;
921 public String locale_text;
922
923 @Override
924 public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
925 if (locale_text == null) {
926 if (text_context != null) {
927 locale_text = trc(text_context, fixPresetString(text));
928 } else {
929 locale_text = tr(fixPresetString(text));
930 }
931 }
932 p.add(new JLabel(locale_text), GBC.eol());
933 return false;
934 }
935
936 @Override
937 public void addCommands(List<Tag> changedTags) {
938 }
939
940 @Override
941 Boolean matches(Map<String, String> tags) {
942 return null;
943 }
944 }
945
946 public static class Link extends Item {
947
948 public String href;
949 public String text;
950 public String text_context;
951 public String locale_text;
952 public String locale_href;
953
954 @Override
955 public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
956 if (locale_text == null) {
957 if (text == null) {
958 locale_text = tr("More information about this feature");
959 } else if (text_context != null) {
960 locale_text = trc(text_context, fixPresetString(text));
961 } else {
962 locale_text = tr(fixPresetString(text));
963 }
964 }
965 String url = locale_href;
966 if (url == null) {
967 url = href;
968 }
969 if (url != null) {
970 p.add(new UrlLabel(url, locale_text, 2), GBC.eol().anchor(GBC.WEST));
971 }
972 return false;
973 }
974
975 @Override
976 public void addCommands(List<Tag> changedTags) {
977 }
978
979 @Override
980 Boolean matches(Map<String, String> tags) {
981 return null;
982 }
983 }
984
985 public static class Role {
986 public EnumSet<PresetType> types;
987 public String key;
988 public String text;
989 public String text_context;
990 public String locale_text;
991
992 public boolean required = false;
993 public long count = 0;
994
995 public void setType(String types) throws SAXException {
996 this.types = TaggingPreset.getType(types);
997 }
998
999 public void setRequisite(String str) throws SAXException {
1000 if("required".equals(str)) {
1001 required = true;
1002 } else if(!"optional".equals(str))
1003 throw new SAXException(tr("Unknown requisite: {0}", str));
1004 }
1005
1006 /* return either argument, the highest possible value or the lowest
1007 allowed value */
1008 public long getValidCount(long c)
1009 {
1010 if(count > 0 && !required)
1011 return c != 0 ? count : 0;
1012 else if(count > 0)
1013 return count;
1014 else if(!required)
1015 return c != 0 ? c : 0;
1016 else
1017 return c != 0 ? c : 1;
1018 }
1019 public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
1020 String cstring;
1021 if(count > 0 && !required) {
1022 cstring = "0,"+String.valueOf(count);
1023 } else if(count > 0) {
1024 cstring = String.valueOf(count);
1025 } else if(!required) {
1026 cstring = "0-...";
1027 } else {
1028 cstring = "1-...";
1029 }
1030 if(locale_text == null) {
1031 if (text != null) {
1032 if(text_context != null) {
1033 locale_text = trc(text_context, fixPresetString(text));
1034 } else {
1035 locale_text = tr(fixPresetString(text));
1036 }
1037 }
1038 }
1039 p.add(new JLabel(locale_text+":"), GBC.std().insets(0,0,10,0));
1040 p.add(new JLabel(key), GBC.std().insets(0,0,10,0));
1041 p.add(new JLabel(cstring), types == null ? GBC.eol() : GBC.std().insets(0,0,10,0));
1042 if(types != null){
1043 JPanel pp = new JPanel();
1044 for(PresetType t : types) {
1045 pp.add(new JLabel(ImageProvider.get(t.getIconName())));
1046 }
1047 p.add(pp, GBC.eol());
1048 }
1049 return true;
1050 }
1051 }
1052
1053 public static class Roles extends Item {
1054
1055 public List<Role> roles = new LinkedList<Role>();
1056
1057 @Override
1058 public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
1059 p.add(new JLabel(" "), GBC.eol()); // space
1060 if (roles.size() > 0) {
1061 JPanel proles = new JPanel(new GridBagLayout());
1062 proles.add(new JLabel(tr("Available roles")), GBC.std().insets(0, 0, 10, 0));
1063 proles.add(new JLabel(tr("role")), GBC.std().insets(0, 0, 10, 0));
1064 proles.add(new JLabel(tr("count")), GBC.std().insets(0, 0, 10, 0));
1065 proles.add(new JLabel(tr("elements")), GBC.eol());
1066 for (Role i : roles) {
1067 i.addToPanel(proles, sel);
1068 }
1069 p.add(proles, GBC.eol());
1070 }
1071 return false;
1072 }
1073
1074 @Override
1075 public void addCommands(List<Tag> changedTags) {
1076 }
1077
1078 @Override
1079 Boolean matches(Map<String, String> tags) {
1080 return null;
1081 }
1082 }
1083
1084 public static class Optional extends Item {
1085
1086 // TODO: Draw a box around optional stuff
1087 @Override
1088 public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
1089 p.add(new JLabel(" "), GBC.eol()); // space
1090 p.add(new JLabel(tr("Optional Attributes:")), GBC.eol());
1091 p.add(new JLabel(" "), GBC.eol()); // space
1092 return false;
1093 }
1094
1095 @Override
1096 public void addCommands(List<Tag> changedTags) {
1097 }
1098
1099 @Override
1100 Boolean matches(Map<String, String> tags) {
1101 return null;
1102 }
1103 }
1104
1105 public static class Space extends Item {
1106
1107 @Override
1108 public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
1109 p.add(new JLabel(" "), GBC.eol()); // space
1110 return false;
1111 }
1112
1113 @Override
1114 public void addCommands(List<Tag> changedTags) {
1115 }
1116
1117 @Override
1118 Boolean matches(Map<String, String> tags) {
1119 return null;
1120 }
1121 }
1122
1123 public static class Key extends Item {
1124
1125 public String key;
1126 public String value;
1127 public String match = MatchType.KEY_VALUE.getValue();
1128
1129 @Override
1130 public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
1131 return false;
1132 }
1133
1134 @Override
1135 public void addCommands(List<Tag> changedTags) {
1136 changedTags.add(new Tag(key, value));
1137 }
1138
1139 @Override
1140 Boolean matches(Map<String, String> tags) {
1141 switch (MatchType.ofString(match)) {
1142 case NONE:
1143 return null;
1144 case KEY:
1145 return tags.containsKey(key) ? true : null;
1146 case KEY_REQUIRED:
1147 return tags.containsKey(key);
1148 case KEY_VALUE:
1149 return value.equals(tags.get(key));
1150 default:
1151 throw new IllegalStateException();
1152 }
1153 }
1154 }
1155
1156 /**
1157 * The types as preparsed collection.
1158 */
1159 public EnumSet<PresetType> types;
1160 public List<Item> data = new LinkedList<Item>();
1161 public TemplateEntry nameTemplate;
1162 public Match nameTemplateFilter;
1163 private static HashMap<String,String> lastValue = new HashMap<String,String>();
1164
1165 /**
1166 * Create an empty tagging preset. This will not have any items and
1167 * will be an empty string as text. createPanel will return null.
1168 * Use this as default item for "do not select anything".
1169 */
1170 public TaggingPreset() {
1171 MapView.addLayerChangeListener(this);
1172 updateEnabledState();
1173 }
1174
1175 /**
1176 * Change the display name without changing the toolbar value.
1177 */
1178 public void setDisplayName() {
1179 putValue(Action.NAME, getName());
1180 putValue("toolbar", "tagging_" + getRawName());
1181 putValue(OPTIONAL_TOOLTIP_TEXT, (group != null ?
1182 tr("Use preset ''{0}'' of group ''{1}''", getLocaleName(), group.getName()) :
1183 tr("Use preset ''{0}''", getLocaleName())));
1184 }
1185
1186 public String getLocaleName() {
1187 if(locale_name == null) {
1188 if(name_context != null) {
1189 locale_name = trc(name_context, fixPresetString(name));
1190 } else {
1191 locale_name = tr(fixPresetString(name));
1192 }
1193 }
1194 return locale_name;
1195 }
1196
1197 public String getName() {
1198 return group != null ? group.getName() + "/" + getLocaleName() : getLocaleName();
1199 }
1200 public String getRawName() {
1201 return group != null ? group.getRawName() + "/" + name : name;
1202 }
1203
1204 /*
1205 * Called from the XML parser to set the icon.
1206 * This task is performed in the background in order to speedup startup.
1207 *
1208 * FIXME for Java 1.6 - use 24x24 icons for LARGE_ICON_KEY (button bar)
1209 * and the 16x16 icons for SMALL_ICON.
1210 */
1211 public void setIcon(final String iconName) {
1212 final File zipIcons = this.zipIcons;
1213 Main.worker.submit(new Runnable() {
1214
1215 @Override
1216 public void run() {
1217 final Collection<String> s = Main.pref.getCollection("taggingpreset.icon.sources", null);
1218 ImageIcon icon = new ImageProvider(iconName).setDirs(s).setId("presets").setArchive(zipIcons).setOptional(true).get();
1219 if (icon == null) {
1220 System.out.println("Could not get presets icon " + iconName);
1221 icon = new ImageIcon(iconName);
1222 }
1223 if (Math.max(icon.getIconHeight(), icon.getIconWidth()) != 16) {
1224 icon = new ImageIcon(icon.getImage().getScaledInstance(16, 16, Image.SCALE_SMOOTH));
1225 }
1226 putValue(Action.SMALL_ICON, icon);
1227 }
1228 });
1229 }
1230
1231 // cache the parsing of types using a LRU cache (http://java-planet.blogspot.com/2005/08/how-to-set-up-simple-lru-cache-using.html)
1232 private static final Map<String,EnumSet<PresetType>> typeCache =
1233 new LinkedHashMap<String, EnumSet<PresetType>>(16, 1.1f, true);
1234
1235 static public EnumSet<PresetType> getType(String types) throws SAXException {
1236 if (typeCache.containsKey(types)) {
1237 return typeCache.get(types);
1238 }
1239 EnumSet<PresetType> result = EnumSet.noneOf(PresetType.class);
1240 for (String type : Arrays.asList(types.split(","))) {
1241 try {
1242 PresetType presetType = PresetType.valueOf(type.toUpperCase());
1243 result.add(presetType);
1244 } catch (IllegalArgumentException e) {
1245 throw new SAXException(tr("Unknown type: {0}", type));
1246 }
1247 }
1248 typeCache.put(types, result);
1249 return result;
1250 }
1251
1252 /*
1253 * Called from the XML parser to set the types this preset affects.
1254 */
1255 public void setType(String types) throws SAXException {
1256 this.types = getType(types);
1257 }
1258
1259 public void setName_template(String pattern) throws SAXException {
1260 try {
1261 this.nameTemplate = new TemplateParser(pattern).parse();
1262 } catch (ParseError e) {
1263 System.err.println("Error while parsing " + pattern + ": " + e.getMessage());
1264 throw new SAXException(e);
1265 }
1266 }
1267
1268 public void setName_template_filter(String filter) throws SAXException {
1269 try {
1270 this.nameTemplateFilter = SearchCompiler.compile(filter, false, false);
1271 } catch (org.openstreetmap.josm.actions.search.SearchCompiler.ParseError e) {
1272 System.err.println("Error while parsing" + filter + ": " + e.getMessage());
1273 throw new SAXException(e);
1274 }
1275 }
1276
1277
1278 public static List<TaggingPreset> readAll(Reader in, boolean validate) throws SAXException {
1279 XmlObjectParser parser = new XmlObjectParser();
1280 parser.mapOnStart("item", TaggingPreset.class);
1281 parser.mapOnStart("separator", TaggingPresetSeparator.class);
1282 parser.mapBoth("group", TaggingPresetMenu.class);
1283 parser.map("text", Text.class);
1284 parser.map("link", Link.class);
1285 parser.mapOnStart("optional", Optional.class);
1286 parser.mapOnStart("roles", Roles.class);
1287 parser.map("role", Role.class);
1288 parser.map("check", Check.class);
1289 parser.map("combo", Combo.class);
1290 parser.map("multiselect", MultiSelect.class);
1291 parser.map("label", Label.class);
1292 parser.map("space", Space.class);
1293 parser.map("key", Key.class);
1294 LinkedList<TaggingPreset> all = new LinkedList<TaggingPreset>();
1295 TaggingPresetMenu lastmenu = null;
1296 Roles lastrole = null;
1297
1298 if (validate) {
1299 parser.startWithValidation(in, "http://josm.openstreetmap.de/tagging-preset-1.0", "resource://data/tagging-preset.xsd");
1300 } else {
1301 parser.start(in);
1302 }
1303 while(parser.hasNext()) {
1304 Object o = parser.next();
1305 if (o instanceof TaggingPresetMenu) {
1306 TaggingPresetMenu tp = (TaggingPresetMenu) o;
1307 if(tp == lastmenu) {
1308 lastmenu = tp.group;
1309 } else
1310 {
1311 tp.group = lastmenu;
1312 tp.setDisplayName();
1313 lastmenu = tp;
1314 all.add(tp);
1315
1316 }
1317 lastrole = null;
1318 } else if (o instanceof TaggingPresetSeparator) {
1319 TaggingPresetSeparator tp = (TaggingPresetSeparator) o;
1320 tp.group = lastmenu;
1321 all.add(tp);
1322 lastrole = null;
1323 } else if (o instanceof TaggingPreset) {
1324 TaggingPreset tp = (TaggingPreset) o;
1325 tp.group = lastmenu;
1326 tp.setDisplayName();
1327 all.add(tp);
1328 lastrole = null;
1329 } else {
1330 if(all.size() != 0) {
1331 if(o instanceof Roles) {
1332 all.getLast().data.add((Item)o);
1333 lastrole = (Roles) o;
1334 }
1335 else if(o instanceof Role) {
1336 if(lastrole == null)
1337 throw new SAXException(tr("Preset role element without parent"));
1338 lastrole.roles.add((Role) o);
1339 }
1340 else {
1341 all.getLast().data.add((Item)o);
1342 lastrole = null;
1343 }
1344 } else
1345 throw new SAXException(tr("Preset sub element without parent"));
1346 }
1347 }
1348 return all;
1349 }
1350
1351 public static Collection<TaggingPreset> readAll(String source, boolean validate) throws SAXException, IOException {
1352 Collection<TaggingPreset> tp;
1353 MirroredInputStream s = new MirroredInputStream(source);
1354 try {
1355 InputStream zip = s.getZipEntry("xml","preset");
1356 if(zip != null) {
1357 zipIcons = s.getFile();
1358 }
1359 InputStreamReader r;
1360 try {
1361 r = new InputStreamReader(zip == null ? s : zip, "UTF-8");
1362 } catch (UnsupportedEncodingException e) {
1363 r = new InputStreamReader(zip == null ? s: zip);
1364 }
1365 try {
1366 tp = TaggingPreset.readAll(new BufferedReader(r), validate);
1367 } finally {
1368 r.close();
1369 }
1370 } finally {
1371 s.close();
1372 }
1373 return tp;
1374 }
1375
1376 public static Collection<TaggingPreset> readAll(Collection<String> sources, boolean validate) {
1377 LinkedList<TaggingPreset> allPresets = new LinkedList<TaggingPreset>();
1378 for(String source : sources) {
1379 try {
1380 allPresets.addAll(TaggingPreset.readAll(source, validate));
1381 } catch (IOException e) {
1382 e.printStackTrace();
1383 JOptionPane.showMessageDialog(
1384 Main.parent,
1385 tr("Could not read tagging preset source: {0}",source),
1386 tr("Error"),
1387 JOptionPane.ERROR_MESSAGE
1388 );
1389 } catch (SAXException e) {
1390 System.err.println(e.getMessage());
1391 System.err.println(source);
1392 e.printStackTrace();
1393 JOptionPane.showMessageDialog(
1394 Main.parent,
1395 tr("Error parsing {0}: ", source)+e.getMessage(),
1396 tr("Error"),
1397 JOptionPane.ERROR_MESSAGE
1398 );
1399 }
1400 zipIcons = null;
1401 }
1402 return allPresets;
1403 }
1404
1405 public static LinkedList<String> getPresetSources() {
1406 LinkedList<String> sources = new LinkedList<String>();
1407
1408 for (SourceEntry e : (new PresetPrefHelper()).get()) {
1409 sources.add(e.url);
1410 }
1411
1412 return sources;
1413 }
1414
1415 public static Collection<TaggingPreset> readFromPreferences(boolean validate) {
1416 return readAll(getPresetSources(), validate);
1417 }
1418
1419 private static class PresetPanel extends JPanel {
1420 boolean hasElements = false;
1421 PresetPanel()
1422 {
1423 super(new GridBagLayout());
1424 }
1425 }
1426
1427 public PresetPanel createPanel(Collection<OsmPrimitive> selected) {
1428 if (data == null)
1429 return null;
1430 PresetPanel p = new PresetPanel();
1431 LinkedList<Item> l = new LinkedList<Item>();
1432 if(types != null){
1433 JPanel pp = new JPanel();
1434 for(PresetType t : types){
1435 JLabel la = new JLabel(ImageProvider.get(t.getIconName()));
1436 la.setToolTipText(tr("Elements of type {0} are supported.", tr(t.getName())));
1437 pp.add(la);
1438 }
1439 p.add(pp, GBC.eol());
1440 }
1441
1442 JPanel items = new JPanel(new GridBagLayout());
1443 for (Item i : data){
1444 if(i instanceof Link) {
1445 l.add(i);
1446 } else {
1447 if(i.addToPanel(items, selected)) {
1448 p.hasElements = true;
1449 }
1450 }
1451 }
1452 p.add(items, GBC.eol().fill());
1453 if (selected.size() == 0 && !supportsRelation()) {
1454 GuiHelper.setEnabledRec(items, false);
1455 }
1456
1457 for(Item link : l) {
1458 link.addToPanel(p, selected);
1459 }
1460
1461 return p;
1462 }
1463
1464 public boolean isShowable()
1465 {
1466 for(Item i : data)
1467 {
1468 if(!(i instanceof Optional || i instanceof Space || i instanceof Key))
1469 return true;
1470 }
1471 return false;
1472 }
1473
1474 public void actionPerformed(ActionEvent e) {
1475 if (Main.main == null) return;
1476 if (Main.main.getCurrentDataSet() == null) return;
1477
1478 Collection<OsmPrimitive> sel = createSelection(Main.main.getCurrentDataSet().getSelected());
1479 int answer = showDialog(sel, supportsRelation());
1480
1481 if (sel.size() != 0 && answer == DIALOG_ANSWER_APPLY) {
1482 Command cmd = createCommand(sel, getChangedTags());
1483 if (cmd != null) {
1484 Main.main.undoRedo.add(cmd);
1485 }
1486 } else if (answer == DIALOG_ANSWER_NEW_RELATION) {
1487 final Relation r = new Relation();
1488 final Collection<RelationMember> members = new HashSet<RelationMember>();
1489 for(Tag t : getChangedTags()) {
1490 r.put(t.getKey(), t.getValue());
1491 }
1492 for(OsmPrimitive osm : Main.main.getCurrentDataSet().getSelected()) {
1493 RelationMember rm = new RelationMember("", osm);
1494 r.addMember(rm);
1495 members.add(rm);
1496 }
1497 SwingUtilities.invokeLater(new Runnable() {
1498 @Override
1499 public void run() {
1500 RelationEditor.getEditor(Main.main.getEditLayer(), r, members).setVisible(true);
1501 }
1502 });
1503 }
1504 Main.main.getCurrentDataSet().setSelected(Main.main.getCurrentDataSet().getSelected()); // force update
1505
1506 }
1507
1508 public int showDialog(Collection<OsmPrimitive> sel, final boolean showNewRelation) {
1509 PresetPanel p = createPanel(sel);
1510 if (p == null)
1511 return DIALOG_ANSWER_CANCEL;
1512
1513 int answer = 1;
1514 if (p.getComponentCount() != 0 && (sel.size() == 0 || p.hasElements)) {
1515 String title = trn("Change {0} object", "Change {0} objects", sel.size(), sel.size());
1516 if(sel.size() == 0) {
1517 if(originalSelectionEmpty) {
1518 title = tr("Nothing selected!");
1519 } else {
1520 title = tr("Selection unsuitable!");
1521 }
1522 }
1523
1524 class PresetDialog extends ExtendedDialog {
1525 public PresetDialog(Component content, String title, boolean disableApply) {
1526 super(Main.parent,
1527 title,
1528 showNewRelation?
1529 new String[] { tr("Apply Preset"), tr("New relation"), tr("Cancel") }:
1530 new String[] { tr("Apply Preset"), tr("Cancel") },
1531 true);
1532 contentInsets = new Insets(10,5,0,5);
1533 if (showNewRelation) {
1534 setButtonIcons(new String[] {"ok.png", "dialogs/addrelation.png", "cancel.png" });
1535 } else {
1536 setButtonIcons(new String[] {"ok.png", "cancel.png" });
1537 }
1538 setContent(content);
1539 setDefaultButton(1);
1540 setupDialog();
1541 buttons.get(0).setEnabled(!disableApply);
1542 buttons.get(0).setToolTipText(title);
1543 // Prevent dialogs of being too narrow (fix #6261)
1544 Dimension d = getSize();
1545 if (d.width < 350) {
1546 d.width = 350;
1547 setSize(d);
1548 }
1549 showDialog();
1550 }
1551 }
1552
1553 answer = new PresetDialog(p, title, (sel.size() == 0)).getValue();
1554 }
1555 if (!showNewRelation && answer == 2)
1556 return DIALOG_ANSWER_CANCEL;
1557 else
1558 return answer;
1559 }
1560
1561 /**
1562 * True whenever the original selection given into createSelection was empty
1563 */
1564 private boolean originalSelectionEmpty = false;
1565
1566 /**
1567 * Removes all unsuitable OsmPrimitives from the given list
1568 * @param participants List of possibile OsmPrimitives to tag
1569 * @return Cleaned list with suitable OsmPrimitives only
1570 */
1571 public Collection<OsmPrimitive> createSelection(Collection<OsmPrimitive> participants) {
1572 originalSelectionEmpty = participants.size() == 0;
1573 Collection<OsmPrimitive> sel = new LinkedList<OsmPrimitive>();
1574 for (OsmPrimitive osm : participants)
1575 {
1576 if (types != null)
1577 {
1578 if(osm instanceof Relation)
1579 {
1580 if(!types.contains(PresetType.RELATION) &&
1581 !(types.contains(PresetType.CLOSEDWAY) && ((Relation)osm).isMultipolygon())) {
1582 continue;
1583 }
1584 }
1585 else if(osm instanceof Node)
1586 {
1587 if(!types.contains(PresetType.NODE)) {
1588 continue;
1589 }
1590 }
1591 else if(osm instanceof Way)
1592 {
1593 if(!types.contains(PresetType.WAY) &&
1594 !(types.contains(PresetType.CLOSEDWAY) && ((Way)osm).isClosed())) {
1595 continue;
1596 }
1597 }
1598 }
1599 sel.add(osm);
1600 }
1601 return sel;
1602 }
1603
1604 public List<Tag> getChangedTags() {
1605 List<Tag> result = new ArrayList<Tag>();
1606 for (Item i: data) {
1607 i.addCommands(result);
1608 }
1609 return result;
1610 }
1611
1612 private static String fixPresetString(String s) {
1613 return s == null ? s : s.replaceAll("'","''");
1614 }
1615
1616 public static Command createCommand(Collection<OsmPrimitive> sel, List<Tag> changedTags) {
1617 List<Command> cmds = new ArrayList<Command>();
1618 for (Tag tag: changedTags) {
1619 cmds.add(new ChangePropertyCommand(sel, tag.getKey(), tag.getValue()));
1620 }
1621
1622 if (cmds.size() == 0)
1623 return null;
1624 else if (cmds.size() == 1)
1625 return cmds.get(0);
1626 else
1627 return new SequenceCommand(tr("Change Properties"), cmds);
1628 }
1629
1630 private boolean supportsRelation() {
1631 return types == null || types.contains(PresetType.RELATION);
1632 }
1633
1634 protected void updateEnabledState() {
1635 setEnabled(Main.main != null && Main.main.getCurrentDataSet() != null);
1636 }
1637
1638 public void activeLayerChange(Layer oldLayer, Layer newLayer) {
1639 updateEnabledState();
1640 }
1641
1642 public void layerAdded(Layer newLayer) {
1643 updateEnabledState();
1644 }
1645
1646 public void layerRemoved(Layer oldLayer) {
1647 updateEnabledState();
1648 }
1649
1650 @Override
1651 public String toString() {
1652 return (types == null?"":types) + " " + name;
1653 }
1654
1655 public boolean matches(Collection<PresetType> t, Map<String, String> tags) {
1656 if (!isShowable()) {
1657 return false;
1658 } else if (t != null && types != null && !types.containsAll(t)) {
1659 return false;
1660 }
1661 boolean atLeastOnePositiveMatch = false;
1662 for (Item item : data) {
1663 Boolean m = item.matches(tags);
1664 if (m != null && !m) {
1665 return false;
1666 } else if (m != null) {
1667 atLeastOnePositiveMatch = true;
1668 }
1669 }
1670 return atLeastOnePositiveMatch;
1671 }
1672}
Note: See TracBrowser for help on using the repository browser.