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

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

see #7552 - presets: allow icons for individual combo items

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