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

Last change on this file since 3861 was 3861, checked in by jttt, 13 years ago

Fix #5892 Escaping does not work for <combo> in presets

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