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

Last change on this file since 3481 was 3481, checked in by stoecker, 14 years ago

add use_last_as_default for combos, fix long-time broken tagged/untagged detection for combo-box and textbox default value selection

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