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

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

(hopefully) fix #7574 - NameTemplate stopped working

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