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

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

Fix #5893 Null Pointer Exception while loding custom presets at start - JOSM hangs on splash screen

  • Property svn:eol-style set to native
File size: 54.1 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 = values.split(",");
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 // allow escaped comma in comma separated list:
556 // "A\, B\, C,one\, two" --> ["A, B, C", "one, two"]
557 private static String[] splitEscaped(String s) {
558 String[] res = s.replaceAll("\\\\,", "\u0091").split(",");
559 for (int i=0; i<res.length; ++i) {
560 res[i] = res[i].replaceAll("\u0091", ",");
561 }
562 return res;
563 }
564
565 @Override public void addCommands(List<Tag> changedTags) {
566 Object obj = combo.getSelectedItem();
567 String display = (obj == null) ? null : obj.toString();
568 String value = null;
569 if(display == null && combo.isEditable()) {
570 display = combo.getEditor().getItem().toString();
571 }
572
573 if (display != null)
574 {
575 for (String key : lhm.keySet()) {
576 String k = lhm.get(key).toString();
577 if (k != null && k.equals(display)) {
578 value=key;
579 }
580 }
581 if(value == null) {
582 value = display;
583 }
584 } else {
585 value = "";
586 }
587
588 // no change if same as before
589 if (originalValue == null) {
590 if (value.length() == 0)
591 return;
592 } else if (value.equals(originalValue.toString()))
593 return;
594
595 if (delete_if_empty && value.length() == 0) {
596 value = null;
597 }
598 if (use_last_as_default) {
599 lastValue.put(key, value);
600 }
601 changedTags.add(new Tag(key, value));
602 }
603
604 public void setShort_description(String s) {
605 if (short_description_list == null) {
606 short_description_list = new ArrayList<String>();
607 }
608 short_description_list.add(tr(s));
609 }
610
611 @Override boolean requestFocusInWindow() {return combo.requestFocusInWindow();}
612 }
613
614 /**
615 * Class that allows list values to be assigned and retrived as a comma-delimited
616 * string.
617 */
618 public static class ConcatenatingJList extends JList {
619 private String delimiter;
620 public ConcatenatingJList(String del, Object[] o) {
621 super(o);
622 delimiter = del;
623 }
624 public void setSelectedItem(Object o) {
625 if (o == null) {
626 clearSelection();
627 } else {
628 String s = o.toString();
629 HashSet<String> parts = new HashSet<String>(Arrays.asList(s.split(delimiter)));
630 ListModel lm = getModel();
631 int[] intParts = new int[lm.getSize()];
632 int j = 0;
633 for (int i = 0; i < lm.getSize(); i++) {
634 if (parts.contains((((PresetListEntry)lm.getElementAt(i)).value))) {
635 intParts[j++]=i;
636 }
637 }
638 setSelectedIndices(Arrays.copyOf(intParts, j));
639 // check if we have acutally managed to represent the full
640 // value with our presets. if not, cop out; we will not offer
641 // a selection list that threatens to ruin the value.
642 setEnabled(s.equals(getSelectedItem()));
643 }
644 }
645 public String getSelectedItem() {
646 ListModel lm = getModel();
647 int[] si = getSelectedIndices();
648 StringBuilder builder = new StringBuilder();
649 for (int i=0; i<si.length; i++) {
650 if (i>0) {
651 builder.append(delimiter);
652 }
653 builder.append(((PresetListEntry)lm.getElementAt(si[i])).value);
654 }
655 return builder.toString();
656 }
657 }
658
659 public static class MultiSelect extends Item {
660
661 public String key;
662 public String text;
663 public String text_context;
664 public String locale_text;
665 public String values;
666 public String values_context;
667 public String display_values;
668 public String locale_display_values;
669 public String short_descriptions;
670 public String locale_short_descriptions;
671 public String default_;
672 public String delimiter = ";";
673 public boolean delete_if_empty = false;
674 public boolean use_last_as_default = false;
675 public boolean required = false;
676
677 private List<String> short_description_list;
678 private ConcatenatingJList list;
679 private Map<String, PresetListEntry> lhm;
680 private Usage usage;
681 private String originalValue;
682
683 @Override public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
684
685 // find out if our key is already used in the selection.
686 usage = determineTextUsage(sel, key);
687 String def = default_;
688
689 String[] value_array = values.split(delimiter);
690 String[] display_array;
691 String[] short_descriptions_array = null;
692
693 if (locale_display_values != null) {
694 display_array = splitEscaped(delimiter, locale_display_values);
695 } else if (display_values != null) {
696 display_array = splitEscaped(delimiter, display_values);
697 } else {
698 display_array = value_array;
699 }
700
701 if (locale_short_descriptions != null) {
702 short_descriptions_array = splitEscaped(delimiter, locale_short_descriptions);
703 } else if (short_descriptions != null) {
704 short_descriptions_array = splitEscaped(delimiter, short_descriptions);
705 } else if (short_description_list != null) {
706 short_descriptions_array = short_description_list.toArray(new String[0]);
707 }
708
709 if (use_last_as_default && def == null && lastValue.containsKey(key)) {
710 def = lastValue.get(key);
711 }
712
713 if (display_array.length != value_array.length) {
714 System.err.println(tr("Broken tagging preset \"{0}-{1}\" - number of items in ''display_values'' must be the same as in ''values''", key, text));
715 display_array = value_array;
716 }
717
718 if (short_descriptions_array != null && short_descriptions_array.length != value_array.length) {
719 System.err.println(tr("Broken tagging preset \"{0}-{1}\" - number of items in ''short_descriptions'' must be the same as in ''values''", key, text));
720 short_descriptions_array = null;
721 }
722
723 lhm = new LinkedHashMap<String, PresetListEntry>();
724 if (!usage.hasUniqueValue() && !usage.unused()) {
725 lhm.put(DIFFERENT, new PresetListEntry(DIFFERENT));
726 }
727 for (int i=0; i<value_array.length; i++) {
728 PresetListEntry e = new PresetListEntry(value_array[i]);
729 e.display_value = (locale_display_values == null)
730 ? (values_context == null ? tr(display_array[i])
731 : trc(values_context, display_array[i])) : display_array[i];
732 if (short_descriptions_array != null) {
733 e.short_description = locale_short_descriptions == null ? tr(short_descriptions_array[i])
734 : short_descriptions_array[i];
735 }
736 lhm.put(value_array[i], e);
737 }
738
739 list = new ConcatenatingJList(delimiter, lhm.values().toArray());
740 list.setCellRenderer(new PresetListCellRenderer());
741
742 if (usage.hasUniqueValue() && !usage.unused()) {
743 originalValue=usage.getFirst();
744 }
745 else if (def != null && !usage.hadKeys()) {
746 originalValue=def;
747 }
748 else if (usage.unused()) {
749 originalValue=null;
750 }
751 else {
752 originalValue=DIFFERENT;
753 }
754 list.setSelectedItem(originalValue);
755
756 if (locale_text == null) {
757 if(text_context != null) {
758 locale_text = trc(text_context, text);
759 } else {
760 locale_text = tr(text);
761 }
762 }
763 p.add(new JLabel(locale_text+":"), GBC.std().insets(0,0,10,0));
764 p.add(new JScrollPane(list), GBC.eol().fill(GBC.HORIZONTAL));
765 return true;
766 }
767
768 private static class PresetListCellRenderer implements ListCellRenderer {
769
770 HtmlPanel lbl;
771 JComponent dummy = new JComponent() {};
772
773 public PresetListCellRenderer() {
774 lbl = new HtmlPanel();
775 }
776
777 public Component getListCellRendererComponent(
778 JList list,
779 Object value,
780 int index,
781 boolean isSelected,
782 boolean cellHasFocus)
783 {
784 if (isSelected) {
785 lbl.setBackground(list.getSelectionBackground());
786 lbl.setForeground(list.getSelectionForeground());
787 } else {
788 lbl.setBackground(list.getBackground());
789 lbl.setForeground(list.getForeground());
790 }
791
792 PresetListEntry item = (PresetListEntry) value;
793 String s = item.getListDisplay();
794 lbl.setText(s);
795 lbl.setEnabled(list.isEnabled());
796 // We do not want the editor to have the maximum height of all
797 // entries. Return a dummy with bogus height.
798 if (index == -1) {
799 dummy.setPreferredSize(new Dimension(lbl.getPreferredSize().width, 10));
800 return dummy;
801 }
802 return lbl;
803 }
804 }
805
806 // allow escaped delimiter in comma separated list:
807 // "A\, B\, C,one\, two" --> ["A, B, C", "one, two"]
808 private static String[] splitEscaped(String delimiter, String s) {
809 String[] res = s.replaceAll("\\\\,", "\u0091").split(delimiter);
810 for (int i=0; i<res.length; ++i) {
811 res[i] = res[i].replaceAll("\u0091", delimiter);
812 }
813 return res;
814 }
815
816 @Override public void addCommands(List<Tag> changedTags) {
817 Object obj = list.getSelectedItem();
818 String display = (obj == null) ? null : obj.toString();
819 String value = null;
820
821 if (display != null)
822 {
823 for (String key : lhm.keySet()) {
824 String k = lhm.get(key).toString();
825 if (k != null && k.equals(display)) {
826 value=key;
827 }
828 }
829 if (value == null) {
830 value = display;
831 }
832 } else {
833 value = "";
834 }
835
836 // no change if same as before
837 if (originalValue == null) {
838 if (value.length() == 0)
839 return;
840 } else if (value.equals(originalValue.toString()))
841 return;
842
843 if (delete_if_empty && value.length() == 0) {
844 value = null;
845 }
846 if (use_last_as_default) {
847 lastValue.put(key, value);
848 }
849 changedTags.add(new Tag(key, value));
850 }
851
852 public void setShort_description(String s) {
853 if (short_description_list == null) {
854 short_description_list = new ArrayList<String>();
855 }
856 short_description_list.add(tr(s));
857 }
858
859 @Override boolean requestFocusInWindow() {return list.requestFocusInWindow();}
860 }
861
862 public static class Label extends Item {
863 public String text;
864 public String text_context;
865 public String locale_text;
866
867 @Override public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
868 if(locale_text == null) {
869 if(text_context != null) {
870 locale_text = trc(text_context, text);
871 } else {
872 locale_text = tr(text);
873 }
874 }
875 p.add(new JLabel(locale_text), GBC.eol());
876 return false;
877 }
878 @Override public void addCommands(List<Tag> changedTags) {}
879 }
880
881 public static class Link extends Item {
882 public String href;
883 public String text;
884 public String text_context;
885 public String locale_text;
886 public String locale_href;
887
888 @Override public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
889 if(locale_text == null) {
890 if(text == null) {
891 locale_text = tr("More information about this feature");
892 } else if(text_context != null) {
893 locale_text = trc(text_context, text);
894 } else {
895 locale_text = tr(text);
896 }
897 }
898 String url = locale_href;
899 if (url == null) {
900 url = href;
901 }
902 if (url != null) {
903 p.add(new UrlLabel(url, locale_text), GBC.eol().anchor(GBC.WEST));
904 }
905 return false;
906 }
907 @Override public void addCommands(List<Tag> changedTags) {}
908 }
909
910 public static class Role {
911 public EnumSet<PresetType> types;
912 public String key;
913 public String text;
914 public String text_context;
915 public String locale_text;
916
917 public boolean required=false;
918 public long count = 0;
919
920 public void setType(String types) throws SAXException {
921 this.types = TaggingPreset.getType(types);
922 }
923
924 public void setRequisite(String str) throws SAXException {
925 if("required".equals(str)) {
926 required = true;
927 } else if(!"optional".equals(str))
928 throw new SAXException(tr("Unknown requisite: {0}", str));
929 }
930
931 /* return either argument, the highest possible value or the lowest
932 allowed value */
933 public long getValidCount(long c)
934 {
935 if(count > 0 && !required)
936 return c != 0 ? count : 0;
937 else if(count > 0)
938 return count;
939 else if(!required)
940 return c != 0 ? c : 0;
941 else
942 return c != 0 ? c : 1;
943 }
944 public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
945 String cstring;
946 if(count > 0 && !required) {
947 cstring = "0,"+String.valueOf(count);
948 } else if(count > 0) {
949 cstring = String.valueOf(count);
950 } else if(!required) {
951 cstring = "0-...";
952 } else {
953 cstring = "1-...";
954 }
955 if(locale_text == null) {
956 if (text != null) {
957 if(text_context != null) {
958 locale_text = trc(text_context, text);
959 } else {
960 locale_text = tr(text);
961 }
962 }
963 }
964 p.add(new JLabel(locale_text+":"), GBC.std().insets(0,0,10,0));
965 p.add(new JLabel(key), GBC.std().insets(0,0,10,0));
966 p.add(new JLabel(cstring), types == null ? GBC.eol() : GBC.std().insets(0,0,10,0));
967 if(types != null){
968 JPanel pp = new JPanel();
969 for(PresetType t : types) {
970 pp.add(new JLabel(ImageProvider.get(t.getIconName())));
971 }
972 p.add(pp, GBC.eol());
973 }
974 return true;
975 }
976 }
977
978 public static class Roles extends Item {
979 public List<Role> roles = new LinkedList<Role>();
980 @Override public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
981 p.add(new JLabel(" "), GBC.eol()); // space
982 if(roles.size() > 0)
983 {
984 JPanel proles = new JPanel(new GridBagLayout());
985 proles.add(new JLabel(tr("Available roles")), GBC.std().insets(0,0,10,0));
986 proles.add(new JLabel(tr("role")), GBC.std().insets(0,0,10,0));
987 proles.add(new JLabel(tr("count")), GBC.std().insets(0,0,10,0));
988 proles.add(new JLabel(tr("elements")), GBC.eol());
989 for (Role i : roles) {
990 i.addToPanel(proles, sel);
991 }
992 p.add(proles, GBC.eol());
993 }
994 return false;
995 }
996 @Override public void addCommands(List<Tag> changedTags) {}
997 }
998
999 public static class Optional extends Item {
1000 // TODO: Draw a box around optional stuff
1001 @Override public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
1002 p.add(new JLabel(" "), GBC.eol()); // space
1003 p.add(new JLabel(tr("Optional Attributes:")), GBC.eol());
1004 p.add(new JLabel(" "), GBC.eol()); // space
1005 return false;
1006 }
1007 @Override public void addCommands(List<Tag> changedTags) {}
1008 }
1009
1010 public static class Space extends Item {
1011 @Override public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
1012 p.add(new JLabel(" "), GBC.eol()); // space
1013 return false;
1014 }
1015 @Override public void addCommands(List<Tag> changedTags) {}
1016 }
1017
1018 public static class Key extends Item {
1019 public String key;
1020 public String value;
1021
1022 @Override public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) { return false; }
1023 @Override public void addCommands(List<Tag> changedTags) {
1024 changedTags.add(new Tag(key, value != null && !value.equals("") ? value : null));
1025 }
1026 }
1027
1028 /**
1029 * The types as preparsed collection.
1030 */
1031 public EnumSet<PresetType> types;
1032 public List<Item> data = new LinkedList<Item>();
1033 private static HashMap<String,String> lastValue = new HashMap<String,String>();
1034
1035 /**
1036 * Create an empty tagging preset. This will not have any items and
1037 * will be an empty string as text. createPanel will return null.
1038 * Use this as default item for "do not select anything".
1039 */
1040 public TaggingPreset() {
1041 MapView.addLayerChangeListener(this);
1042 updateEnabledState();
1043 }
1044
1045 /**
1046 * Change the display name without changing the toolbar value.
1047 */
1048 public void setDisplayName() {
1049 putValue(Action.NAME, getName());
1050 putValue("toolbar", "tagging_" + getRawName());
1051 }
1052
1053 public String getLocaleName() {
1054 if(locale_name == null) {
1055 if(name_context != null) {
1056 locale_name = trc(name_context, name);
1057 } else {
1058 locale_name = tr(name);
1059 }
1060 }
1061 return locale_name;
1062 }
1063
1064 public String getName() {
1065 return group != null ? group.getName() + "/" + getLocaleName() : getLocaleName();
1066 }
1067 public String getRawName() {
1068 return group != null ? group.getRawName() + "/" + name : name;
1069 }
1070 /**
1071 * Called from the XML parser to set the icon
1072 *
1073 * FIXME for Java 1.6 - use 24x24 icons for LARGE_ICON_KEY (button bar)
1074 * and the 16x16 icons for SMALL_ICON.
1075 */
1076 public void setIcon(String iconName) {
1077 Collection<String> s = Main.pref.getCollection("taggingpreset.icon.sources", null);
1078 ImageIcon icon = ImageProvider.getIfAvailable(s, "presets", null, iconName, zipIcons);
1079 if (icon == null)
1080 {
1081 System.out.println("Could not get presets icon " + iconName);
1082 icon = new ImageIcon(iconName);
1083 }
1084 if (Math.max(icon.getIconHeight(), icon.getIconWidth()) != 16) {
1085 icon = new ImageIcon(icon.getImage().getScaledInstance(16, 16, Image.SCALE_SMOOTH));
1086 }
1087 putValue(Action.SMALL_ICON, icon);
1088 }
1089
1090 /**
1091 * Called from the XML parser to set the types this preset affects
1092 */
1093
1094 static public EnumSet<PresetType> getType(String types) throws SAXException {
1095 EnumSet<PresetType> result = EnumSet.noneOf(PresetType.class);
1096 for (String type : Arrays.asList(types.split(","))) {
1097 try {
1098 PresetType presetType = PresetType.valueOf(type.toUpperCase());
1099 result.add(presetType);
1100 } catch (IllegalArgumentException e) {
1101 throw new SAXException(tr("Unknown type: {0}", type));
1102 }
1103 }
1104 return result;
1105 }
1106
1107 public void setType(String types) throws SAXException {
1108 this.types = getType(types);
1109 }
1110
1111 public static List<TaggingPreset> readAll(Reader in, boolean validate) throws SAXException {
1112 XmlObjectParser parser = new XmlObjectParser();
1113 parser.mapOnStart("item", TaggingPreset.class);
1114 parser.mapOnStart("separator", TaggingPresetSeparator.class);
1115 parser.mapBoth("group", TaggingPresetMenu.class);
1116 parser.map("text", Text.class);
1117 parser.map("link", Link.class);
1118 parser.mapOnStart("optional", Optional.class);
1119 parser.mapOnStart("roles", Roles.class);
1120 parser.map("role", Role.class);
1121 parser.map("check", Check.class);
1122 parser.map("combo", Combo.class);
1123 parser.map("multiselect", MultiSelect.class);
1124 parser.map("label", Label.class);
1125 parser.map("space", Space.class);
1126 parser.map("key", Key.class);
1127 LinkedList<TaggingPreset> all = new LinkedList<TaggingPreset>();
1128 TaggingPresetMenu lastmenu = null;
1129 Roles lastrole = null;
1130
1131 if (validate) {
1132 parser.startWithValidation(in, "http://josm.openstreetmap.de/tagging-preset-1.0", "resource://data/tagging-preset.xsd");
1133 } else {
1134 parser.start(in);
1135 }
1136 while(parser.hasNext()) {
1137 Object o = parser.next();
1138 if (o instanceof TaggingPresetMenu) {
1139 TaggingPresetMenu tp = (TaggingPresetMenu) o;
1140 if(tp == lastmenu) {
1141 lastmenu = tp.group;
1142 } else
1143 {
1144 tp.group = lastmenu;
1145 tp.setDisplayName();
1146 lastmenu = tp;
1147 all.add(tp);
1148
1149 }
1150 lastrole = null;
1151 } else if (o instanceof TaggingPresetSeparator) {
1152 TaggingPresetSeparator tp = (TaggingPresetSeparator) o;
1153 tp.group = lastmenu;
1154 all.add(tp);
1155 lastrole = null;
1156 } else if (o instanceof TaggingPreset) {
1157 TaggingPreset tp = (TaggingPreset) o;
1158 tp.group = lastmenu;
1159 tp.setDisplayName();
1160 all.add(tp);
1161 lastrole = null;
1162 } else {
1163 if(all.size() != 0) {
1164 if(o instanceof Roles) {
1165 all.getLast().data.add((Item)o);
1166 lastrole = (Roles) o;
1167 }
1168 else if(o instanceof Role) {
1169 if(lastrole == null)
1170 throw new SAXException(tr("Preset role element without parent"));
1171 lastrole.roles.add((Role) o);
1172 }
1173 else {
1174 all.getLast().data.add((Item)o);
1175 lastrole = null;
1176 }
1177 } else
1178 throw new SAXException(tr("Preset sub element without parent"));
1179 }
1180 }
1181 return all;
1182 }
1183
1184 public static Collection<TaggingPreset> readAll(String source, boolean validate) throws SAXException, IOException {
1185 MirroredInputStream s = new MirroredInputStream(source);
1186 InputStream zip = s.getZipEntry("xml","preset");
1187 if(zip != null) {
1188 zipIcons = s.getFile();
1189 }
1190 InputStreamReader r;
1191 try
1192 {
1193 r = new InputStreamReader(zip == null ? s : zip, "UTF-8");
1194 }
1195 catch (UnsupportedEncodingException e)
1196 {
1197 r = new InputStreamReader(zip == null ? s: zip);
1198 }
1199 return TaggingPreset.readAll(new BufferedReader(r), validate);
1200 }
1201
1202 public static Collection<TaggingPreset> readAll(Collection<String> sources, boolean validate) {
1203 LinkedList<TaggingPreset> allPresets = new LinkedList<TaggingPreset>();
1204 for(String source : sources) {
1205 try {
1206 allPresets.addAll(TaggingPreset.readAll(source, validate));
1207 } catch (IOException e) {
1208 e.printStackTrace();
1209 JOptionPane.showMessageDialog(
1210 Main.parent,
1211 tr("Could not read tagging preset source: {0}",source),
1212 tr("Error"),
1213 JOptionPane.ERROR_MESSAGE
1214 );
1215 } catch (SAXException e) {
1216 System.err.println(e.getMessage());
1217 System.err.println(source);
1218 e.printStackTrace();
1219 JOptionPane.showMessageDialog(
1220 Main.parent,
1221 tr("Error parsing {0}: ", source)+e.getMessage(),
1222 tr("Error"),
1223 JOptionPane.ERROR_MESSAGE
1224 );
1225 }
1226 zipIcons = null;
1227 }
1228 return allPresets;
1229 }
1230
1231 public static LinkedList<String> getPresetSources() {
1232 LinkedList<String> sources = new LinkedList<String>();
1233
1234 for (SourceEntry e : (new PresetPrefMigration()).get()) {
1235 sources.add(e.url);
1236 }
1237
1238 return sources;
1239 }
1240
1241 public static Collection<TaggingPreset> readFromPreferences(boolean validate) {
1242 return readAll(getPresetSources(), validate);
1243 }
1244
1245 private static class PresetPanel extends JPanel {
1246 boolean hasElements = false;
1247 PresetPanel()
1248 {
1249 super(new GridBagLayout());
1250 }
1251 }
1252
1253 public PresetPanel createPanel(Collection<OsmPrimitive> selected) {
1254 if (data == null)
1255 return null;
1256 PresetPanel p = new PresetPanel();
1257 LinkedList<Item> l = new LinkedList<Item>();
1258 if(types != null){
1259 JPanel pp = new JPanel();
1260 for(PresetType t : types){
1261 JLabel la = new JLabel(ImageProvider.get(t.getIconName()));
1262 la.setToolTipText(tr("Elements of type {0} are supported.", tr(t.getName())));
1263 pp.add(la);
1264 }
1265 p.add(pp, GBC.eol());
1266 }
1267
1268 JPanel items = new JPanel(new GridBagLayout());
1269 for (Item i : data){
1270 if(i instanceof Link) {
1271 l.add(i);
1272 } else {
1273 if(i.addToPanel(items, selected)) {
1274 p.hasElements = true;
1275 }
1276 }
1277 }
1278 p.add(items, GBC.eol().fill());
1279 if (selected.size() == 0 && !supportsRelation()) {
1280 GuiHelper.setEnabledRec(items, false);
1281 }
1282
1283 for(Item link : l) {
1284 link.addToPanel(p, selected);
1285 }
1286
1287 return p;
1288 }
1289
1290 public boolean isShowable()
1291 {
1292 for(Item i : data)
1293 {
1294 if(!(i instanceof Optional || i instanceof Space || i instanceof Key))
1295 return true;
1296 }
1297 return false;
1298 }
1299
1300 public void actionPerformed(ActionEvent e) {
1301 if (Main.main == null) return;
1302 if (Main.main.getCurrentDataSet() == null) return;
1303
1304 Collection<OsmPrimitive> sel = Main.main.getCurrentDataSet().getSelected();
1305 int answer = showDialog(sel, supportsRelation());
1306
1307 if (sel.size() != 0 && answer == DIALOG_ANSWER_APPLY) {
1308 Command cmd = createCommand(sel, getChangedTags());
1309 if (cmd != null) {
1310 Main.main.undoRedo.add(cmd);
1311 }
1312 } else if (answer == DIALOG_ANSWER_NEW_RELATION) {
1313 List<Command> cmds = new ArrayList<Command>(2);
1314 final Relation r = new Relation();
1315 final Collection<RelationMember> members = new HashSet<RelationMember>();
1316 for(Tag t : getChangedTags()) {
1317 r.put(t.getKey(), t.getValue());
1318 }
1319 for(OsmPrimitive osm : sel) {
1320 RelationMember rm = new RelationMember("", osm);
1321 r.addMember(rm);
1322 members.add(rm);
1323 }
1324 SwingUtilities.invokeLater(new Runnable() {
1325 @Override
1326 public void run() {
1327 RelationEditor.getEditor(Main.main.getEditLayer(), r, members).setVisible(true);
1328 }
1329 });
1330 }
1331 Main.main.getCurrentDataSet().setSelected(Main.main.getCurrentDataSet().getSelected()); // force update
1332
1333 }
1334
1335 public int showDialog(Collection<OsmPrimitive> selection, final boolean showNewRelation) {
1336 Collection<OsmPrimitive> sel = createSelection(selection);
1337 PresetPanel p = createPanel(sel);
1338 if (p == null)
1339 return DIALOG_ANSWER_CANCEL;
1340
1341 int answer = 1;
1342 if (p.getComponentCount() != 0 && (sel.size() == 0 || p.hasElements)) {
1343 String title = trn("Change {0} object", "Change {0} objects", sel.size(), sel.size());
1344 if(sel.size() == 0) {
1345 if(originalSelectionEmpty) {
1346 title = tr("Nothing selected!");
1347 } else {
1348 title = tr("Selection unsuitable!");
1349 }
1350 }
1351
1352 class PresetDialog extends ExtendedDialog {
1353 public PresetDialog(Component content, String title, boolean disableApply) {
1354 super(Main.parent,
1355 title,
1356 showNewRelation?
1357 new String[] { tr("Apply Preset"), tr("New relation"), tr("Cancel") }:
1358 new String[] { tr("Apply Preset"), tr("Cancel") },
1359 true);
1360 contentInsets = new Insets(10,5,0,5);
1361 if (showNewRelation) {
1362 setButtonIcons(new String[] {"ok.png", "dialogs/addrelation.png", "cancel.png" });
1363 } else {
1364 setButtonIcons(new String[] {"ok.png", "cancel.png" });
1365 }
1366 setContent(content);
1367 setDefaultButton(1);
1368 setupDialog();
1369 buttons.get(0).setEnabled(!disableApply);
1370 buttons.get(0).setToolTipText(title);
1371 showDialog();
1372 }
1373 }
1374
1375 answer = new PresetDialog(p, title, (sel.size() == 0)).getValue();
1376 }
1377 if (!showNewRelation && answer == 2)
1378 return DIALOG_ANSWER_CANCEL;
1379 else
1380 return answer;
1381 }
1382
1383 /**
1384 * True whenever the original selection given into createSelection was empty
1385 */
1386 private boolean originalSelectionEmpty = false;
1387
1388 /**
1389 * Removes all unsuitable OsmPrimitives from the given list
1390 * @param participants List of possibile OsmPrimitives to tag
1391 * @return Cleaned list with suitable OsmPrimitives only
1392 */
1393 private Collection<OsmPrimitive> createSelection(Collection<OsmPrimitive> participants) {
1394 originalSelectionEmpty = participants.size() == 0;
1395 Collection<OsmPrimitive> sel = new LinkedList<OsmPrimitive>();
1396 for (OsmPrimitive osm : participants)
1397 {
1398 if (types != null)
1399 {
1400 if(osm instanceof Relation)
1401 {
1402 if(!types.contains(PresetType.RELATION)) {
1403 continue;
1404 }
1405 }
1406 else if(osm instanceof Node)
1407 {
1408 if(!types.contains(PresetType.NODE)) {
1409 continue;
1410 }
1411 }
1412 else if(osm instanceof Way)
1413 {
1414 if(!types.contains(PresetType.WAY) &&
1415 !(types.contains(PresetType.CLOSEDWAY) && ((Way)osm).isClosed())) {
1416 continue;
1417 }
1418 }
1419 }
1420 sel.add(osm);
1421 }
1422 return sel;
1423 }
1424
1425 public List<Tag> getChangedTags() {
1426 List<Tag> result = new ArrayList<Tag>();
1427 for (Item i: data) {
1428 i.addCommands(result);
1429 }
1430 return result;
1431 }
1432
1433 public static Command createCommand(Collection<OsmPrimitive> sel, List<Tag> changedTags) {
1434 List<Command> cmds = new ArrayList<Command>();
1435 for (Tag tag: changedTags) {
1436 if (!tag.getValue().isEmpty()) {
1437 cmds.add(new ChangePropertyCommand(sel, tag.getKey(), tag.getValue()));
1438 }
1439 }
1440
1441 if (cmds.size() == 0)
1442 return null;
1443 else if (cmds.size() == 1)
1444 return cmds.get(0);
1445 else
1446 return new SequenceCommand(tr("Change Properties"), cmds);
1447 }
1448
1449 private boolean supportsRelation() {
1450 return types == null || types.contains(PresetType.RELATION);
1451 }
1452
1453 protected void updateEnabledState() {
1454 setEnabled(Main.main != null && Main.main.getCurrentDataSet() != null);
1455 }
1456
1457 public void activeLayerChange(Layer oldLayer, Layer newLayer) {
1458 updateEnabledState();
1459 }
1460
1461 public void layerAdded(Layer newLayer) {
1462 updateEnabledState();
1463 }
1464
1465 public void layerRemoved(Layer oldLayer) {
1466 updateEnabledState();
1467 }
1468
1469 @Override
1470 public String toString() {
1471 return (types == null?"":types) + " " + name;
1472 }
1473}
Note: See TracBrowser for help on using the repository browser.