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

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

fix #7606 - Values in presets get translated

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