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

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

see #6964 - speedup presets initialization by some caching

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