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

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

see #8180 - Presets: Allow length of input box for text

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