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

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

code style - fix non-static access to static member

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