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

Last change on this file since 3388 was 3388, checked in by jttt, 14 years ago

Add tagging preset search dialog

  • Property svn:eol-style set to native
File size: 37.4 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.gui.tagging;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.tools.I18n.trc;
6import static org.openstreetmap.josm.tools.I18n.trn;
7
8import java.awt.Component;
9import java.awt.Container;
10import java.awt.GridBagLayout;
11import java.awt.Image;
12import java.awt.event.ActionEvent;
13import java.io.BufferedReader;
14import java.io.File;
15import java.io.IOException;
16import java.io.InputStream;
17import java.io.InputStreamReader;
18import java.io.Reader;
19import java.io.UnsupportedEncodingException;
20import java.util.Arrays;
21import java.util.Collection;
22import java.util.EnumSet;
23import java.util.HashMap;
24import java.util.LinkedHashMap;
25import java.util.LinkedList;
26import java.util.List;
27import java.util.TreeSet;
28
29import javax.swing.AbstractAction;
30import javax.swing.Action;
31import javax.swing.ImageIcon;
32import javax.swing.JComboBox;
33import javax.swing.JComponent;
34import javax.swing.JLabel;
35import javax.swing.JOptionPane;
36import javax.swing.JPanel;
37import javax.swing.JTextField;
38import javax.xml.transform.stream.StreamSource;
39
40import org.openstreetmap.josm.Main;
41import org.openstreetmap.josm.command.ChangePropertyCommand;
42import org.openstreetmap.josm.command.Command;
43import org.openstreetmap.josm.command.SequenceCommand;
44import org.openstreetmap.josm.data.osm.Node;
45import org.openstreetmap.josm.data.osm.OsmPrimitive;
46import org.openstreetmap.josm.data.osm.OsmUtils;
47import org.openstreetmap.josm.data.osm.Relation;
48import org.openstreetmap.josm.data.osm.Way;
49import org.openstreetmap.josm.gui.ExtendedDialog;
50import org.openstreetmap.josm.gui.MapView;
51import org.openstreetmap.josm.gui.QuadStateCheckBox;
52import org.openstreetmap.josm.gui.layer.Layer;
53import org.openstreetmap.josm.gui.layer.OsmDataLayer;
54import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
55import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionItemPritority;
56import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;
57import org.openstreetmap.josm.io.MirroredInputStream;
58import org.openstreetmap.josm.tools.GBC;
59import org.openstreetmap.josm.tools.ImageProvider;
60import org.openstreetmap.josm.tools.UrlLabel;
61import org.openstreetmap.josm.tools.XmlObjectParser;
62import org.xml.sax.SAXException;
63
64/**
65 * This class read encapsulate one tagging preset. A class method can
66 * read in all predefined presets, either shipped with JOSM or that are
67 * in the config directory.
68 *
69 * It is also able to construct dialogs out of preset definitions.
70 */
71public class TaggingPreset extends AbstractAction implements MapView.LayerChangeListener {
72
73 public enum PresetType {
74 NODE("Mf_node"), WAY("Mf_way"), RELATION("Mf_relation"), CLOSEDWAY("Mf_closedway");
75
76 private final String iconName;
77
78 PresetType(String iconName) {
79 this.iconName = iconName;
80 }
81
82 public String getIconName() {
83 return iconName;
84 }
85
86 public String getName() {
87 return name().toLowerCase();
88 }
89
90 }
91
92 public TaggingPresetMenu group = null;
93 public String name;
94 public String name_context;
95 public String locale_name;
96 public final static String OPTIONAL_TOOLTIP_TEXT = "Optional tooltip text";
97 private static File zipIcons = null;
98
99 public static abstract class Item {
100 protected void initAutoCompletionField(AutoCompletingTextField field, String key) {
101 OsmDataLayer layer = Main.main.getEditLayer();
102 if (layer == null) return;
103 AutoCompletionList list = new AutoCompletionList();
104 Main.main.getEditLayer().data.getAutoCompletionManager().populateWithTagValues(list, key);
105 field.setAutoCompletionList(list);
106 }
107
108 public boolean focus = false;
109 abstract boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel);
110 abstract void addCommands(Collection<OsmPrimitive> sel, List<Command> cmds);
111 boolean requestFocusInWindow() {return false;}
112 }
113
114 public static class Usage {
115 TreeSet<String> values;
116 boolean hadKeys = false;
117 boolean hadEmpty = false;
118 public boolean hasUniqueValue() {
119 return values.size() == 1 && !hadEmpty;
120 }
121
122 public boolean unused() {
123 return values.size() == 0;
124 }
125 public String getFirst() {
126 return values.first();
127 }
128
129 public boolean hadKeys() {
130 return hadKeys;
131 }
132 }
133
134 public static final String DIFFERENT = tr("<different>");
135
136 static Usage determineTextUsage(Collection<OsmPrimitive> sel, String key) {
137 Usage returnValue = new Usage();
138 returnValue.values = new TreeSet<String>();
139 for (OsmPrimitive s : sel) {
140 String v = s.get(key);
141 if (v != null) {
142 returnValue.values.add(v);
143 } else {
144 returnValue.hadEmpty = true;
145 }
146 returnValue.hadKeys = ! returnValue.values.isEmpty() | returnValue.hadEmpty;
147 }
148 return returnValue;
149 }
150
151 static Usage determineBooleanUsage(Collection<OsmPrimitive> sel, String key) {
152
153 Usage returnValue = new Usage();
154 returnValue.values = new TreeSet<String>();
155 for (OsmPrimitive s : sel) {
156 String booleanValue = OsmUtils.getNamedOsmBoolean(s.get(key));
157 if (booleanValue != null) {
158 returnValue.values.add(booleanValue);
159 }
160 }
161 return returnValue;
162 }
163
164 public static class Text extends Item {
165
166 public String key;
167 public String text;
168 public String locale_text;
169 public String text_context;
170 public String default_;
171 public String originalValue;
172 public boolean use_last_as_default = false;
173 public boolean delete_if_empty = false;
174
175 private JComponent value;
176
177 @Override public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
178
179 // find out if our key is already used in the selection.
180 Usage usage = determineTextUsage(sel, key);
181 AutoCompletingTextField textField = new AutoCompletingTextField();
182 initAutoCompletionField(textField, key);
183 if (usage.unused()){
184 if (use_last_as_default && lastValue.containsKey(key)) {
185 textField.setText(lastValue.get(key));
186 } else {
187 textField.setText(default_);
188 }
189 value = textField;
190 originalValue = null;
191 } else if (usage.hasUniqueValue()) {
192 // all objects use the same value
193 textField.setText(usage.getFirst());
194 value = textField;
195 originalValue = usage.getFirst();
196 } else {
197 // the objects have different values
198 JComboBox comboBox = new JComboBox(usage.values.toArray());
199 comboBox.setEditable(true);
200 comboBox.setEditor(textField);
201 comboBox.getEditor().setItem(DIFFERENT);
202 value=comboBox;
203 originalValue = DIFFERENT;
204 }
205 if(locale_text == null) {
206 if (text != null) {
207 if(text_context != null) {
208 locale_text = trc(text_context, text);
209 } else {
210 locale_text = tr(text);
211 }
212 }
213 }
214 p.add(new JLabel(locale_text+":"), GBC.std().insets(0,0,10,0));
215 p.add(value, GBC.eol().fill(GBC.HORIZONTAL));
216 return true;
217 }
218
219 @Override public void addCommands(Collection<OsmPrimitive> sel, List<Command> cmds) {
220
221 // return if unchanged
222 String v = (value instanceof JComboBox) ?
223 ((JComboBox)value).getEditor().getItem().toString() :
224 ((JTextField)value).getText();
225
226 if (use_last_as_default) {
227 lastValue.put(key, v);
228 }
229 if (v.equals(originalValue) || (originalValue == null && v.length() == 0)) return;
230
231 if (delete_if_empty && v.length() == 0) {
232 v = null;
233 }
234 cmds.add(new ChangePropertyCommand(sel, key, v));
235 }
236 @Override boolean requestFocusInWindow() {return value.requestFocusInWindow();}
237 }
238
239 public static class Check extends Item {
240
241 public String key;
242 public String text;
243 public String text_context;
244 public String locale_text;
245 public String value_on = OsmUtils.trueval;
246 public String value_off = OsmUtils.falseval;
247 public boolean default_ = false; // only used for tagless objects
248 public boolean use_last_as_default = false;
249
250 private QuadStateCheckBox check;
251 private QuadStateCheckBox.State initialState;
252 private boolean def;
253
254 @Override public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
255
256 // find out if our key is already used in the selection.
257 Usage usage = determineBooleanUsage(sel, key);
258 def = default_;
259
260 if(locale_text == null) {
261 if(text_context != null) {
262 locale_text = trc(text_context, text);
263 } else {
264 locale_text = tr(text);
265 }
266 }
267
268 String oneValue = null;
269 for (String s : usage.values) {
270 oneValue = s;
271 }
272 if (usage.values.size() < 2 && (oneValue == null || value_on.equals(oneValue) || value_off.equals(oneValue))) {
273 if(def)
274 {
275 for (OsmPrimitive s : sel)
276 if(s.hasKeys()) {
277 def = false;
278 }
279 }
280
281 // all selected objects share the same value which is either true or false or unset,
282 // we can display a standard check box.
283 initialState = value_on.equals(oneValue) ?
284 QuadStateCheckBox.State.SELECTED :
285 value_off.equals(oneValue) ?
286 QuadStateCheckBox.State.NOT_SELECTED :
287 def ? QuadStateCheckBox.State.SELECTED
288 : QuadStateCheckBox.State.UNSET;
289 check = new QuadStateCheckBox(locale_text, initialState,
290 new QuadStateCheckBox.State[] {
291 QuadStateCheckBox.State.SELECTED,
292 QuadStateCheckBox.State.NOT_SELECTED,
293 QuadStateCheckBox.State.UNSET });
294 } else {
295 def = false;
296 // the objects have different values, or one or more objects have something
297 // else than true/false. we display a quad-state check box
298 // in "partial" state.
299 initialState = QuadStateCheckBox.State.PARTIAL;
300 check = new QuadStateCheckBox(locale_text, QuadStateCheckBox.State.PARTIAL,
301 new QuadStateCheckBox.State[] {
302 QuadStateCheckBox.State.PARTIAL,
303 QuadStateCheckBox.State.SELECTED,
304 QuadStateCheckBox.State.NOT_SELECTED,
305 QuadStateCheckBox.State.UNSET });
306 }
307 p.add(check, GBC.eol().fill(GBC.HORIZONTAL));
308 return true;
309 }
310
311 @Override public void addCommands(Collection<OsmPrimitive> sel, List<Command> cmds) {
312 // if the user hasn't changed anything, don't create a command.
313 if (check.getState() == initialState && !def) return;
314
315 // otherwise change things according to the selected value.
316 cmds.add(new ChangePropertyCommand(sel, key,
317 check.getState() == QuadStateCheckBox.State.SELECTED ? value_on :
318 check.getState() == QuadStateCheckBox.State.NOT_SELECTED ? value_off :
319 null));
320 }
321 @Override boolean requestFocusInWindow() {return check.requestFocusInWindow();}
322 }
323
324 public static class Combo extends Item {
325
326 public String key;
327 public String text;
328 public String text_context;
329 public String locale_text;
330 public String values;
331 public String values_context;
332 public String display_values;
333 public String locale_display_values;
334 public String default_;
335 public boolean delete_if_empty = false;
336 public boolean editable = true;
337 public boolean use_last_as_default = false;
338
339 private JComboBox combo;
340 private LinkedHashMap<String,String> lhm;
341 private Usage usage;
342 private String originalValue;
343
344 @Override public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
345
346 // find out if our key is already used in the selection.
347 usage = determineTextUsage(sel, key);
348
349 String[] value_array = values.split(",");
350 String[] display_array;
351
352 if(locale_display_values != null) {
353 display_array = locale_display_values.split(",");
354 } else if(display_values != null) {
355 display_array = display_values.split(",");
356 } else {
357 display_array = value_array;
358 }
359
360 if (display_array.length != value_array.length) {
361 System.err.println(tr("Broken tagging preset \"{0}-{1}\" - number of items in display_values must be the same as in values", key, text));
362 display_array = value_array;
363 }
364
365 lhm = new LinkedHashMap<String,String>();
366 if (!usage.hasUniqueValue() && !usage.unused()){
367 lhm.put(DIFFERENT, DIFFERENT);
368 }
369 for (int i=0; i<value_array.length; i++) {
370 lhm.put(value_array[i], (locale_display_values == null)
371 ? (values_context == null ? tr(display_array[i])
372 : tr(values_context, display_array[i])) : display_array[i]);
373 }
374 if(!usage.unused()){
375 for (String s : usage.values) {
376 if (!lhm.containsKey(s)) {
377 lhm.put(s, s);
378 }
379 }
380 }
381 if (default_ != null && !lhm.containsKey(default_)) {
382 lhm.put(default_, default_);
383 }
384 if(!lhm.containsKey("")) {
385 lhm.put("", "");
386 }
387
388 combo = new JComboBox(lhm.values().toArray());
389 combo.setEditable(editable);
390 combo.setMaximumRowCount(13);
391 AutoCompletingTextField tf = new AutoCompletingTextField();
392 initAutoCompletionField(tf, key);
393 tf.getAutoCompletionList().add(Arrays.asList(display_array), AutoCompletionItemPritority.IS_IN_STANDARD);
394 combo.setEditor(tf);
395
396 if (usage.hasUniqueValue() && !usage.unused()){
397 originalValue=usage.getFirst();
398 combo.setSelectedItem(lhm.get(originalValue));
399 }
400 // use default only in case it is a totally new entry
401 else if(default_ != null && !usage.hadKeys()) {
402 combo.setSelectedItem(default_);
403 originalValue=DIFFERENT;
404 }
405 else if(usage.unused()){
406 combo.setSelectedItem("");
407 originalValue="";
408 }
409 else{
410 combo.setSelectedItem(DIFFERENT);
411 originalValue=DIFFERENT;
412 }
413
414 if(locale_text == null) {
415 if(text_context != null) {
416 locale_text = trc(text_context, text);
417 } else {
418 locale_text = tr(text);
419 }
420 }
421 p.add(new JLabel(locale_text+":"), GBC.std().insets(0,0,10,0));
422 p.add(combo, GBC.eol().fill(GBC.HORIZONTAL));
423 return true;
424 }
425 @Override public void addCommands(Collection<OsmPrimitive> sel, List<Command> cmds) {
426 Object obj = combo.getSelectedItem();
427 String display = (obj == null) ? null : obj.toString();
428 String value = null;
429 if(display == null && combo.isEditable()) {
430 display = combo.getEditor().getItem().toString();
431 }
432
433 if (display != null)
434 {
435 for (String key : lhm.keySet()) {
436 String k = lhm.get(key);
437 if (k != null && k.equals(display)) {
438 value=key;
439 }
440 }
441 if(value == null) {
442 value = display;
443 }
444 } else {
445 value = "";
446 }
447
448 // no change if same as before
449 if (value.equals(originalValue) || (originalValue == null && value.length() == 0)) return;
450
451 if (delete_if_empty && value.length() == 0) {
452 value = null;
453 }
454 cmds.add(new ChangePropertyCommand(sel, key, value));
455 }
456 @Override boolean requestFocusInWindow() {return combo.requestFocusInWindow();}
457 }
458
459 public static class Label extends Item {
460 public String text;
461 public String text_context;
462 public String locale_text;
463
464 @Override public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
465 if(locale_text == null) {
466 if(text_context != null) {
467 locale_text = trc(text_context, text);
468 } else {
469 locale_text = tr(text);
470 }
471 }
472 p.add(new JLabel(locale_text), GBC.eol());
473 return false;
474 }
475 @Override public void addCommands(Collection<OsmPrimitive> sel, List<Command> cmds) {}
476 }
477
478 public static class Link extends Item {
479 public String href;
480 public String text;
481 public String text_context;
482 public String locale_text;
483 public String locale_href;
484
485 @Override public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
486 if(locale_text == null) {
487 if(text == null) {
488 locale_text = tr("More information about this feature");
489 } else if(text_context != null) {
490 locale_text = trc(text_context, text);
491 } else {
492 locale_text = tr(text);
493 }
494 }
495 String url = locale_href;
496 if (url == null) {
497 url = href;
498 }
499 if (url != null) {
500 p.add(new UrlLabel(url, locale_text), GBC.eol().anchor(GBC.WEST));
501 }
502 return false;
503 }
504 @Override public void addCommands(Collection<OsmPrimitive> sel, List<Command> cmds) {}
505 }
506
507 public static class Role {
508 public EnumSet<PresetType> types;
509 public String key;
510 public String text;
511 public String text_context;
512 public String locale_text;
513
514 public boolean required=false;
515 public long count = 0;
516
517 public void setType(String types) throws SAXException {
518 this.types = TaggingPreset.getType(types);
519 }
520
521 public void setRequisite(String str) throws SAXException {
522 if("required".equals(str)) {
523 required = true;
524 } else if(!"optional".equals(str))
525 throw new SAXException(tr("Unknown requisite: {0}", str));
526 }
527
528 /* return either argument, the highest possible value or the lowest
529 allowed value */
530 public long getValidCount(long c)
531 {
532 if(count > 0 && !required)
533 return c != 0 ? count : 0;
534 else if(count > 0)
535 return count;
536 else if(!required)
537 return c != 0 ? c : 0;
538 else
539 return c != 0 ? c : 1;
540 }
541 public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
542 String cstring;
543 if(count > 0 && !required) {
544 cstring = "0,"+String.valueOf(count);
545 } else if(count > 0) {
546 cstring = String.valueOf(count);
547 } else if(!required) {
548 cstring = "0-...";
549 } else {
550 cstring = "1-...";
551 }
552 if(locale_text == null) {
553 if (text != null) {
554 if(text_context != null) {
555 locale_text = trc(text_context, text);
556 } else {
557 locale_text = tr(text);
558 }
559 }
560 }
561 p.add(new JLabel(locale_text+":"), GBC.std().insets(0,0,10,0));
562 p.add(new JLabel(key), GBC.std().insets(0,0,10,0));
563 p.add(new JLabel(cstring), types == null ? GBC.eol() : GBC.std().insets(0,0,10,0));
564 if(types != null){
565 JPanel pp = new JPanel();
566 for(PresetType t : types) {
567 pp.add(new JLabel(ImageProvider.get(t.getIconName())));
568 }
569 p.add(pp, GBC.eol());
570 }
571 return true;
572 }
573 }
574
575 public static class Roles extends Item {
576 public List<Role> roles = new LinkedList<Role>();
577 @Override public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
578 p.add(new JLabel(" "), GBC.eol()); // space
579 if(roles.size() > 0)
580 {
581 JPanel proles = new JPanel(new GridBagLayout());
582 proles.add(new JLabel(tr("Available roles")), GBC.std().insets(0,0,10,0));
583 proles.add(new JLabel(tr("role")), GBC.std().insets(0,0,10,0));
584 proles.add(new JLabel(tr("count")), GBC.std().insets(0,0,10,0));
585 proles.add(new JLabel(tr("elements")), GBC.eol());
586 for (Role i : roles) {
587 i.addToPanel(proles, sel);
588 }
589 p.add(proles, GBC.eol());
590 }
591 return false;
592 }
593 @Override public void addCommands(Collection<OsmPrimitive> sel, List<Command> cmds) {}
594 }
595
596 public static class Optional extends Item {
597 // TODO: Draw a box around optional stuff
598 @Override public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
599 p.add(new JLabel(" "), GBC.eol()); // space
600 p.add(new JLabel(tr("Optional Attributes:")), GBC.eol());
601 p.add(new JLabel(" "), GBC.eol()); // space
602 return false;
603 }
604 @Override public void addCommands(Collection<OsmPrimitive> sel, List<Command> cmds) {}
605 }
606
607 public static class Space extends Item {
608 @Override public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
609 p.add(new JLabel(" "), GBC.eol()); // space
610 return false;
611 }
612 @Override public void addCommands(Collection<OsmPrimitive> sel, List<Command> cmds) {}
613 }
614
615 public static class Key extends Item {
616 public String key;
617 public String value;
618
619 @Override public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) { return false; }
620 @Override public void addCommands(Collection<OsmPrimitive> sel, List<Command> cmds) {
621 cmds.add(new ChangePropertyCommand(sel, key, value != null && !value.equals("") ? value : null));
622 }
623 }
624
625 /**
626 * The types as preparsed collection.
627 */
628 public EnumSet<PresetType> types;
629 public List<Item> data = new LinkedList<Item>();
630 private static HashMap<String,String> lastValue = new HashMap<String,String>();
631
632 /**
633 * Create an empty tagging preset. This will not have any items and
634 * will be an empty string as text. createPanel will return null.
635 * Use this as default item for "do not select anything".
636 */
637 public TaggingPreset() {
638 MapView.addLayerChangeListener(this);
639 updateEnabledState();
640 }
641
642 /**
643 * Change the display name without changing the toolbar value.
644 */
645 public void setDisplayName() {
646 putValue(Action.NAME, getName());
647 putValue("toolbar", "tagging_" + getRawName());
648 }
649
650 public String getLocaleName() {
651 if(locale_name == null) {
652 if(name_context != null) {
653 locale_name = trc(name_context, name);
654 } else {
655 locale_name = tr(name);
656 }
657 }
658 return locale_name;
659 }
660
661 public String getName() {
662 return group != null ? group.getName() + "/" + getLocaleName() : getLocaleName();
663 }
664 public String getRawName() {
665 return group != null ? group.getRawName() + "/" + name : name;
666 }
667 /**
668 * Called from the XML parser to set the icon
669 *
670 * FIXME for Java 1.6 - use 24x24 icons for LARGE_ICON_KEY (button bar)
671 * and the 16x16 icons for SMALL_ICON.
672 */
673 public void setIcon(String iconName) {
674 Collection<String> s = Main.pref.getCollection("taggingpreset.icon.sources", null);
675 ImageIcon icon = ImageProvider.getIfAvailable(s, "presets", null, iconName, zipIcons);
676 if (icon == null)
677 {
678 System.out.println("Could not get presets icon " + iconName);
679 icon = new ImageIcon(iconName);
680 }
681 if (Math.max(icon.getIconHeight(), icon.getIconWidth()) != 16) {
682 icon = new ImageIcon(icon.getImage().getScaledInstance(16, 16, Image.SCALE_SMOOTH));
683 }
684 putValue(Action.SMALL_ICON, icon);
685 }
686
687 /**
688 * Called from the XML parser to set the types this preset affects
689 */
690
691 static public EnumSet<PresetType> getType(String types) throws SAXException {
692 EnumSet<PresetType> result = EnumSet.noneOf(PresetType.class);
693 for (String type : Arrays.asList(types.split(","))) {
694 try {
695 PresetType presetType = PresetType.valueOf(type.toUpperCase());
696 result.add(presetType);
697 } catch (IllegalArgumentException e) {
698 throw new SAXException(tr("Unknown type: {0}", type));
699 }
700 }
701 return result;
702 }
703
704 public void setType(String types) throws SAXException {
705 this.types = getType(types);
706 }
707
708 public static List<TaggingPreset> readAll(Reader in, boolean validate) throws SAXException {
709 XmlObjectParser parser = new XmlObjectParser();
710 parser.mapOnStart("item", TaggingPreset.class);
711 parser.mapOnStart("separator", TaggingPresetSeparator.class);
712 parser.mapBoth("group", TaggingPresetMenu.class);
713 parser.map("text", Text.class);
714 parser.map("link", Link.class);
715 parser.mapOnStart("optional", Optional.class);
716 parser.mapOnStart("roles", Roles.class);
717 parser.map("role", Role.class);
718 parser.map("check", Check.class);
719 parser.map("combo", Combo.class);
720 parser.map("label", Label.class);
721 parser.map("space", Space.class);
722 parser.map("key", Key.class);
723 LinkedList<TaggingPreset> all = new LinkedList<TaggingPreset>();
724 TaggingPresetMenu lastmenu = null;
725 Roles lastrole = null;
726
727 if (validate) {
728 parser.startWithValidation(in, "http://josm.openstreetmap.de/tagging-preset-1.0", new StreamSource(TaggingPreset.class.getResourceAsStream("tagging-preset.xsd")));
729 } else {
730 parser.start(in);
731 }
732 while(parser.hasNext()) {
733 Object o = parser.next();
734 if (o instanceof TaggingPresetMenu) {
735 TaggingPresetMenu tp = (TaggingPresetMenu) o;
736 if(tp == lastmenu) {
737 lastmenu = tp.group;
738 } else
739 {
740 tp.group = lastmenu;
741 tp.setDisplayName();
742 lastmenu = tp;
743 all.add(tp);
744
745 }
746 lastrole = null;
747 } else if (o instanceof TaggingPresetSeparator) {
748 TaggingPresetSeparator tp = (TaggingPresetSeparator) o;
749 tp.group = lastmenu;
750 all.add(tp);
751 lastrole = null;
752 } else if (o instanceof TaggingPreset) {
753 TaggingPreset tp = (TaggingPreset) o;
754 tp.group = lastmenu;
755 tp.setDisplayName();
756 all.add(tp);
757 lastrole = null;
758 } else {
759 if(all.size() != 0) {
760 if(o instanceof Roles) {
761 all.getLast().data.add((Item)o);
762 lastrole = (Roles) o;
763 }
764 else if(o instanceof Role) {
765 if(lastrole == null)
766 throw new SAXException(tr("Preset role element without parent"));
767 lastrole.roles.add((Role) o);
768 }
769 else {
770 all.getLast().data.add((Item)o);
771 lastrole = null;
772 }
773 } else
774 throw new SAXException(tr("Preset sub element without parent"));
775 }
776 }
777 return all;
778 }
779
780 public static Collection<TaggingPreset> readAll(String source, boolean validate) throws SAXException, IOException {
781 MirroredInputStream s = new MirroredInputStream(source);
782 InputStream zip = s.getZipEntry("xml","preset");
783 if(zip != null) {
784 zipIcons = s.getFile();
785 }
786 InputStreamReader r;
787 try
788 {
789 r = new InputStreamReader(zip == null ? s : zip, "UTF-8");
790 }
791 catch (UnsupportedEncodingException e)
792 {
793 r = new InputStreamReader(zip == null ? s: zip);
794 }
795 return TaggingPreset.readAll(new BufferedReader(r), validate);
796 }
797
798 public static Collection<TaggingPreset> readAll(Collection<String> sources, boolean validate) {
799 LinkedList<TaggingPreset> allPresets = new LinkedList<TaggingPreset>();
800 for(String source : sources) {
801 try {
802 allPresets.addAll(TaggingPreset.readAll(source, validate));
803 } catch (IOException e) {
804 e.printStackTrace();
805 JOptionPane.showMessageDialog(
806 Main.parent,
807 tr("Could not read tagging preset source: {0}",source),
808 tr("Error"),
809 JOptionPane.ERROR_MESSAGE
810 );
811 } catch (SAXException e) {
812 System.err.println(e.getMessage());
813 System.err.println(source);
814 e.printStackTrace();
815 JOptionPane.showMessageDialog(
816 Main.parent,
817 tr("Error parsing {0}: ", source)+e.getMessage(),
818 tr("Error"),
819 JOptionPane.ERROR_MESSAGE
820 );
821 }
822 zipIcons = null;
823 }
824 return allPresets;
825 }
826
827 public static LinkedList<String> getPresetSources() {
828 LinkedList<String> sources = new LinkedList<String>();
829
830 if(Main.pref.getBoolean("taggingpreset.enable-defaults", true)) {
831 sources.add("resource://data/defaultpresets.xml");
832 }
833 sources.addAll(Main.pref.getCollection("taggingpreset.sources", new LinkedList<String>()));
834 return sources;
835 }
836
837 public static Collection<TaggingPreset> readFromPreferences(boolean validate) {
838 return readAll(getPresetSources(), validate);
839 }
840
841 private static class PresetPanel extends JPanel {
842 boolean hasElements = false;
843 PresetPanel()
844 {
845 super(new GridBagLayout());
846 }
847 }
848
849 public PresetPanel createPanel(Collection<OsmPrimitive> selected) {
850 if (data == null)
851 return null;
852 PresetPanel p = new PresetPanel();
853 LinkedList<Item> l = new LinkedList<Item>();
854 if(types != null){
855 JPanel pp = new JPanel();
856 for(PresetType t : types){
857 JLabel la = new JLabel(ImageProvider.get(t.getIconName()));
858 la.setToolTipText(tr("Elements of type {0} are supported.", tr(t.getName())));
859 pp.add(la);
860 }
861 p.add(pp, GBC.eol());
862 }
863
864 JPanel items = new JPanel(new GridBagLayout());
865 for (Item i : data){
866 if(i instanceof Link) {
867 l.add(i);
868 } else {
869 if(i.addToPanel(items, selected)) {
870 p.hasElements = true;
871 }
872 }
873 }
874 p.add(items, GBC.eol().fill());
875 if (selected.size() == 0) {
876 setEnabledRec(items, false);
877 }
878
879 for(Item link : l) {
880 link.addToPanel(p, selected);
881 }
882
883 return p;
884 }
885
886 /**
887 * setEnabled() does not propagate to child elements, so we need this workaround.
888 */
889 static void setEnabledRec(Container root, boolean enabled) {
890 root.setEnabled(enabled);
891 Component children[] = root.getComponents();
892 for(int i = 0; i < children.length; i++) {
893 if(children[i] instanceof Container) {
894 setEnabledRec((Container)children[i], enabled);
895 } else {
896 children[i].setEnabled(enabled);
897 }
898 }
899 }
900
901 public boolean isShowable()
902 {
903 for(Item i : data)
904 {
905 if(!(i instanceof Optional || i instanceof Space || i instanceof Key))
906 return true;
907 }
908 return false;
909 }
910
911 public void actionPerformed(ActionEvent e) {
912 if (Main.main == null) return;
913 if (Main.main.getCurrentDataSet() == null) return;
914 Collection<OsmPrimitive> sel = createSelection(Main.main.getCurrentDataSet().getSelected());
915 PresetPanel p = createPanel(sel);
916 if (p == null)
917 return;
918
919 int answer = 1;
920 if (p.getComponentCount() != 0 && (sel.size() == 0 || p.hasElements)) {
921 String title = trn("Change {0} object", "Change {0} objects", sel.size(), sel.size());
922 if(sel.size() == 0) {
923 if(originalSelectionEmpty) {
924 title = tr("Nothing selected!");
925 } else {
926 title = tr("Selection unsuitable!");
927 }
928 }
929
930 class PresetDialog extends ExtendedDialog {
931 public PresetDialog(Component content, String title, boolean disableApply) {
932 super(Main.parent,
933 title,
934 new String[] { tr("Apply Preset"), tr("Cancel") },
935 true);
936 contentConstraints = GBC.eol().fill().insets(5,10,5,0);
937 setButtonIcons(new String[] {"ok.png", "cancel.png" });
938 setContent(content);
939 setupDialog();
940 buttons.get(0).setEnabled(!disableApply);
941 buttons.get(0).setToolTipText(title);
942 setVisible(true);
943 }
944 }
945
946 answer = new PresetDialog(p, title, (sel.size() == 0)).getValue();
947 }
948 if (sel.size() != 0 && answer == 1) {
949 Command cmd = createCommand(sel);
950 if (cmd != null) {
951 Main.main.undoRedo.add(cmd);
952 }
953 }
954 Main.main.getCurrentDataSet().setSelected(Main.main.getCurrentDataSet().getSelected()); // force update
955 }
956
957 /**
958 * True whenever the original selection given into createSelection was empty
959 */
960 private boolean originalSelectionEmpty = false;
961
962 /**
963 * Removes all unsuitable OsmPrimitives from the given list
964 * @param participants List of possibile OsmPrimitives to tag
965 * @return Cleaned list with suitable OsmPrimitives only
966 */
967 private Collection<OsmPrimitive> createSelection(Collection<OsmPrimitive> participants) {
968 originalSelectionEmpty = participants.size() == 0;
969 Collection<OsmPrimitive> sel = new LinkedList<OsmPrimitive>();
970 for (OsmPrimitive osm : participants)
971 {
972 if (types != null)
973 {
974 if(osm instanceof Relation)
975 {
976 if(!types.contains(PresetType.RELATION)) {
977 continue;
978 }
979 }
980 else if(osm instanceof Node)
981 {
982 if(!types.contains(PresetType.NODE)) {
983 continue;
984 }
985 }
986 else if(osm instanceof Way)
987 {
988 if(!types.contains(PresetType.WAY) &&
989 !(types.contains(PresetType.CLOSEDWAY) && ((Way)osm).isClosed())) {
990 continue;
991 }
992 }
993 }
994 sel.add(osm);
995 }
996 return sel;
997 }
998
999 private Command createCommand(Collection<OsmPrimitive> sel) {
1000 List<Command> cmds = new LinkedList<Command>();
1001 for (Item i : data) {
1002 i.addCommands(sel, cmds);
1003 }
1004 if (cmds.size() == 0)
1005 return null;
1006 else if (cmds.size() == 1)
1007 return cmds.get(0);
1008 else
1009 return new SequenceCommand(tr("Change Properties"), cmds);
1010 }
1011
1012 protected void updateEnabledState() {
1013 setEnabled(Main.main != null && Main.main.getCurrentDataSet() != null);
1014 }
1015
1016 public void activeLayerChange(Layer oldLayer, Layer newLayer) {
1017 updateEnabledState();
1018 }
1019
1020 public void layerAdded(Layer newLayer) {
1021 updateEnabledState();
1022 }
1023
1024 public void layerRemoved(Layer oldLayer) {
1025 updateEnabledState();
1026 }
1027}
Note: See TracBrowser for help on using the repository browser.