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

Last change on this file since 5429 was 5429, checked in by Don-vip, 12 years ago

fix #7917 - Control the number of items displayed at once in all comboboxes (20 by default, configurable with gui.combobox.maximum-row-count)

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