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

Last change on this file since 1339 was 1339, checked in by stoecker, 15 years ago

fixed #2072

  • Property svn:eol-style set to native
File size: 23.8 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.gui.tagging;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.tools.I18n.trn;
6
7import java.awt.GridBagLayout;
8import java.awt.Image;
9import java.awt.event.ActionEvent;
10import java.awt.event.ActionListener;
11import java.io.BufferedReader;
12import java.io.InputStreamReader;
13import java.io.IOException;
14import java.io.Reader;
15import java.io.UnsupportedEncodingException;
16import java.util.Collection;
17import java.util.HashMap;
18import java.util.HashSet;
19import java.util.LinkedHashMap;
20import java.util.LinkedList;
21import java.util.List;
22import java.util.Set;
23
24import javax.swing.AbstractAction;
25import javax.swing.Action;
26import javax.swing.ImageIcon;
27import javax.swing.JButton;
28import javax.swing.JComboBox;
29import javax.swing.JComponent;
30import javax.swing.JLabel;
31import javax.swing.JOptionPane;
32import javax.swing.JPanel;
33import javax.swing.JTextField;
34
35import org.openstreetmap.josm.Main;
36import org.openstreetmap.josm.command.ChangePropertyCommand;
37import org.openstreetmap.josm.command.Command;
38import org.openstreetmap.josm.command.SequenceCommand;
39import org.openstreetmap.josm.data.osm.OsmPrimitive;
40import org.openstreetmap.josm.data.osm.OsmUtils;
41import org.openstreetmap.josm.io.MirroredInputStream;
42import org.openstreetmap.josm.gui.QuadStateCheckBox;
43import org.openstreetmap.josm.gui.tagging.TaggingPresetMenu;
44import org.openstreetmap.josm.gui.tagging.TaggingPresetSeparator;
45import org.openstreetmap.josm.tools.GBC;
46import org.openstreetmap.josm.tools.ImageProvider;
47import org.openstreetmap.josm.tools.OpenBrowser;
48import org.openstreetmap.josm.tools.XmlObjectParser;
49import org.xml.sax.SAXException;
50
51/**
52 * This class read encapsulate one tagging preset. A class method can
53 * read in all predefined presets, either shipped with JOSM or that are
54 * in the config directory.
55 *
56 * It is also able to construct dialogs out of preset definitions.
57 */
58public class TaggingPreset extends AbstractAction {
59
60 public TaggingPresetMenu group = null;
61 public String name;
62
63 public static abstract class Item {
64 public boolean focus = false;
65 abstract void addToPanel(JPanel p, Collection<OsmPrimitive> sel);
66 abstract void addCommands(Collection<OsmPrimitive> sel, List<Command> cmds);
67 boolean requestFocusInWindow() {return false;}
68 }
69
70 public static class Usage {
71 Set<String> values;
72 Boolean hadKeys = false;
73 Boolean hadEmpty = false;
74 public Boolean allSimilar()
75 {
76 return values.size() == 1 && !hadEmpty;
77 }
78 public Boolean unused()
79 {
80 return values.size() == 0;
81 }
82 public String getFirst()
83 {
84 return (String)(values.toArray()[0]);
85 }
86 public Boolean hadKeys()
87 {
88 return hadKeys;
89 }
90 }
91
92 public static final String DIFFERENT = tr("<different>");
93
94 static Usage determineTextUsage(Collection<OsmPrimitive> sel, String key) {
95 Usage returnValue = new Usage();
96 returnValue.values = new HashSet<String>();
97 for (OsmPrimitive s : sel) {
98 String v = s.get(key);
99 if (v != null)
100 returnValue.values.add(v);
101 else
102 returnValue.hadEmpty = true;
103 if(s.keys != null && s.keys.size() > 0)
104 returnValue.hadKeys = true;
105 }
106 return returnValue;
107 }
108
109 static Usage determineBooleanUsage(Collection<OsmPrimitive> sel, String key) {
110
111 Usage returnValue = new Usage();
112 returnValue.values = new HashSet<String>();
113 for (OsmPrimitive s : sel) {
114 returnValue.values.add(OsmUtils.getNamedOsmBoolean(s.get(key)));
115 }
116 return returnValue;
117 }
118
119 public static class Text extends Item {
120
121 public String key;
122 public String text;
123 public String locale_text;
124 public String default_;
125 public String originalValue;
126 public boolean use_last_as_default = false;
127 public boolean delete_if_empty = false;
128
129 private JComponent value;
130
131 @Override public void addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
132
133 // find out if our key is already used in the selection.
134 Usage usage = determineTextUsage(sel, key);
135 if (usage.unused())
136 {
137 value = new JTextField();
138 if (use_last_as_default && lastValue.containsKey(key)) {
139 ((JTextField)value).setText(lastValue.get(key));
140 } else {
141 ((JTextField)value).setText(default_);
142 }
143 originalValue = null;
144 } else if (usage.allSimilar()) {
145 // all objects use the same value
146 value = new JTextField();
147 for (String s : usage.values) ((JTextField) value).setText(s);
148 originalValue = ((JTextField)value).getText();
149 } else {
150 // the objects have different values
151 value = new JComboBox(usage.values.toArray());
152 ((JComboBox)value).setEditable(true);
153 ((JComboBox)value).getEditor().setItem(DIFFERENT);
154 originalValue = DIFFERENT;
155 }
156 if(locale_text == null)
157 locale_text = tr(text);
158 p.add(new JLabel(locale_text+":"), GBC.std().insets(0,0,10,0));
159 p.add(value, GBC.eol().fill(GBC.HORIZONTAL));
160 }
161
162 @Override public void addCommands(Collection<OsmPrimitive> sel, List<Command> cmds) {
163
164 // return if unchanged
165 String v = (value instanceof JComboBox) ?
166 ((JComboBox)value).getEditor().getItem().toString() :
167 ((JTextField)value).getText();
168
169 if (use_last_as_default) lastValue.put(key, v);
170 if (v.equals(originalValue) || (originalValue == null && v.length() == 0)) return;
171
172 if (delete_if_empty && v.length() == 0)
173 v = null;
174 cmds.add(new ChangePropertyCommand(sel, key, v));
175 }
176 @Override boolean requestFocusInWindow() {return value.requestFocusInWindow();}
177 }
178
179 public static class Check extends Item {
180
181 public String key;
182 public String text;
183 public String locale_text;
184 public boolean default_ = false; // only used for tagless objects
185 public boolean use_last_as_default = false;
186
187 private QuadStateCheckBox check;
188 private QuadStateCheckBox.State initialState;
189
190 @Override public void addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
191
192 // find out if our key is already used in the selection.
193 Usage usage = determineBooleanUsage(sel, key);
194
195 if(locale_text == null)
196 locale_text = tr(text);
197
198 String oneValue = null;
199 for (String s : usage.values) oneValue = s;
200 if (usage.values.size() < 2 && (oneValue == null || OsmUtils.trueval.equals(oneValue) || OsmUtils.falseval.equals(oneValue))) {
201 if(default_)
202 {
203 for (OsmPrimitive s : sel)
204 if(s.keys != null && s.keys.size() > 0) default_ = false;
205 }
206
207 // all selected objects share the same value which is either true or false or unset,
208 // we can display a standard check box.
209 initialState = OsmUtils.trueval.equals(oneValue) ?
210 QuadStateCheckBox.State.SELECTED :
211 OsmUtils.falseval.equals(oneValue) ?
212 QuadStateCheckBox.State.NOT_SELECTED :
213 default_ ? QuadStateCheckBox.State.SELECTED
214 : QuadStateCheckBox.State.UNSET;
215 check = new QuadStateCheckBox(locale_text, initialState,
216 new QuadStateCheckBox.State[] {
217 QuadStateCheckBox.State.SELECTED,
218 QuadStateCheckBox.State.NOT_SELECTED,
219 QuadStateCheckBox.State.UNSET });
220 } else {
221 // the objects have different values, or one or more objects have something
222 // else than true/false. we display a quad-state check box
223 // in "partial" state.
224 initialState = QuadStateCheckBox.State.PARTIAL;
225 check = new QuadStateCheckBox(locale_text, QuadStateCheckBox.State.PARTIAL,
226 new QuadStateCheckBox.State[] {
227 QuadStateCheckBox.State.PARTIAL,
228 QuadStateCheckBox.State.SELECTED,
229 QuadStateCheckBox.State.NOT_SELECTED,
230 QuadStateCheckBox.State.UNSET });
231 }
232 p.add(check, GBC.eol().fill(GBC.HORIZONTAL));
233 }
234
235 @Override public void addCommands(Collection<OsmPrimitive> sel, List<Command> cmds) {
236 // if the user hasn't changed anything, don't create a command.
237 if (check.getState() == initialState) return;
238
239 // otherwise change things according to the selected value.
240 cmds.add(new ChangePropertyCommand(sel, key,
241 check.getState() == QuadStateCheckBox.State.SELECTED ? OsmUtils.trueval :
242 check.getState() == QuadStateCheckBox.State.NOT_SELECTED ? OsmUtils.falseval :
243 null));
244 }
245 @Override boolean requestFocusInWindow() {return check.requestFocusInWindow();}
246 }
247
248 public static class Combo extends Item {
249
250 public String key;
251 public String text;
252 public String locale_text;
253 public String values;
254 public String display_values;
255 public String locale_display_values;
256 public String default_;
257 public boolean delete_if_empty = false;
258 public boolean editable = true;
259 public boolean use_last_as_default = false;
260
261 private JComboBox combo;
262 private LinkedHashMap<String,String> lhm;
263 private Usage usage;
264 private String originalValue;
265
266 @Override public void addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
267
268 // find out if our key is already used in the selection.
269 usage = determineTextUsage(sel, key);
270
271 String[] value_array = values.split(",");
272 String[] display_array;
273 if(locale_display_values != null)
274 display_array = locale_display_values.split(",");
275 else if(display_values != null)
276 display_array = display_values.split(",");
277 else
278 display_array = value_array;
279
280 lhm = new LinkedHashMap<String,String>();
281 if (!usage.allSimilar() && !usage.unused())
282 {
283 lhm.put(DIFFERENT, DIFFERENT);
284 }
285 for (int i=0; i<value_array.length; i++) {
286 lhm.put(value_array[i],
287 (locale_display_values == null) ?
288 tr(display_array[i]) : display_array[i]);
289 }
290 if(!usage.unused())
291 {
292 for (String s : usage.values) {
293 if (!lhm.containsKey(s)) lhm.put(s, s);
294 }
295 }
296 if (default_ != null && !lhm.containsKey(default_)) lhm.put(default_, default_);
297 if(!lhm.containsKey("")) lhm.put("", "");
298
299 combo = new JComboBox(lhm.values().toArray());
300 combo.setEditable(editable);
301 if (usage.allSimilar() && !usage.unused())
302 {
303 originalValue=usage.getFirst();
304 combo.setSelectedItem(lhm.get(originalValue));
305 }
306 // use default only in case it is a totally new entry
307 else if(default_ != null && !usage.hadKeys())
308 {
309 combo.setSelectedItem(default_);
310 originalValue=DIFFERENT;
311 }
312 else if(usage.unused())
313 {
314 combo.setSelectedItem("");
315 originalValue="";
316 }
317 else
318 {
319 combo.setSelectedItem(DIFFERENT);
320 originalValue=DIFFERENT;
321 }
322
323 if(locale_text == null)
324 locale_text = tr(text);
325 p.add(new JLabel(locale_text+":"), GBC.std().insets(0,0,10,0));
326 p.add(combo, GBC.eol().fill(GBC.HORIZONTAL));
327 }
328 @Override public void addCommands(Collection<OsmPrimitive> sel, List<Command> cmds) {
329 Object obj = combo.getSelectedItem();
330 String display = (obj == null) ? null : obj.toString();
331 String value = null;
332 if(display == null && combo.isEditable())
333 display = combo.getEditor().getItem().toString();
334
335 if (display != null)
336 {
337 for (String key : lhm.keySet()) {
338 String k = lhm.get(key);
339 if (k != null && k.equals(display)) value=key;
340 }
341 if(value == null)
342 value = display;
343 }
344 else
345 value = "";
346
347 // no change if same as before
348 if (value.equals(originalValue) || (originalValue == null && (value == null || value.length() == 0))) return;
349
350 if (delete_if_empty && value != null && value.length() == 0)
351 value = null;
352 cmds.add(new ChangePropertyCommand(sel, key, value));
353 }
354 @Override boolean requestFocusInWindow() {return combo.requestFocusInWindow();}
355 }
356
357 public static class Label extends Item {
358 public String text;
359 public String locale_text;
360
361 @Override public void addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
362 if(locale_text == null)
363 locale_text = tr(text);
364 p.add(new JLabel(locale_text), GBC.eol());
365 }
366 @Override public void addCommands(Collection<OsmPrimitive> sel, List<Command> cmds) {}
367 }
368
369 public static class Link extends Item {
370 public String href;
371 public String text;
372 public String locale_text;
373 public String locale_href;
374
375 @Override public void addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
376 if(locale_text == null)
377 locale_text = text == null ? tr("Open map features in browser") : tr(text);
378 JButton b = new JButton(locale_text);
379 b.addActionListener(new ActionListener(){
380 public void actionPerformed(ActionEvent e) {
381 OpenBrowser.displayUrl(locale_href != null ? locale_href : href);
382 }
383 });
384 p.add(b, GBC.eol().anchor(GBC.EAST));
385 }
386 @Override public void addCommands(Collection<OsmPrimitive> sel, List<Command> cmds) {}
387 }
388
389 public static class Optional extends Item {
390 // TODO: Draw a box around optional stuff
391 @Override public void addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
392 p.add(new JLabel(" "), GBC.eol()); // space
393 p.add(new JLabel(tr("Optional Attributes:")), GBC.eol());
394 p.add(new JLabel(" "), GBC.eol()); // space
395 }
396 @Override public void addCommands(Collection<OsmPrimitive> sel, List<Command> cmds) {}
397 }
398
399 public static class Space extends Item {
400 @Override public void addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
401 p.add(new JLabel(" "), GBC.eol()); // space
402 }
403 @Override public void addCommands(Collection<OsmPrimitive> sel, List<Command> cmds) {}
404 }
405
406 public static class Key extends Item {
407 public String key;
408 public String value;
409
410 @Override public void addToPanel(JPanel p, Collection<OsmPrimitive> sel) { }
411 @Override public void addCommands(Collection<OsmPrimitive> sel, List<Command> cmds) {
412 cmds.add(new ChangePropertyCommand(sel, key, value != null && !value.equals("") ? value : null));
413 }
414 }
415
416 /**
417 * The types as preparsed collection.
418 */
419 public Collection<Class<?>> types;
420 public List<Item> data = new LinkedList<Item>();
421 private static HashMap<String,String> lastValue = new HashMap<String,String>();
422
423 /**
424 * Create an empty tagging preset. This will not have any items and
425 * will be an empty string as text. createPanel will return null.
426 * Use this as default item for "do not select anything".
427 */
428 public TaggingPreset() {}
429
430 /**
431 * Change the display name without changing the toolbar value.
432 */
433 public void setDisplayName() {
434 putValue(Action.NAME, getName());
435 putValue("toolbar", "tagging_" + getRawName());
436 putValue(SHORT_DESCRIPTION, (group != null ?
437 tr("Use preset ''{0}'' of group ''{1}''", tr(name), group.getName()) :
438 tr("Use preset ''{0}''", tr(name))));
439 }
440
441 public String getName() {
442 return group != null ? group.getName() + "/" + tr(name) : tr(name);
443 }
444 public String getRawName() {
445 return group != null ? group.getRawName() + "/" + name : name;
446 }
447 /**
448 * Called from the XML parser to set the icon
449 *
450 * FIXME for Java 1.6 - use 24x24 icons for LARGE_ICON_KEY (button bar)
451 * and the 16x16 icons for SMALL_ICON.
452 */
453 public void setIcon(String iconName) {
454 String s = Main.pref.get("taggingpreset.iconpaths");
455 ImageIcon icon = ImageProvider.getIfAvailable((s != null ? s.split(";") : null), "presets", null, iconName);
456 if (icon == null)
457 {
458 System.out.println("Could not get presets icon " + iconName);
459 icon = new ImageIcon(iconName);
460 }
461 if (Math.max(icon.getIconHeight(), icon.getIconWidth()) != 16)
462 icon = new ImageIcon(icon.getImage().getScaledInstance(16, 16, Image.SCALE_SMOOTH));
463 putValue(Action.SMALL_ICON, icon);
464 }
465
466 /**
467 * Called from the XML parser to set the types this preset affects
468 */
469 public void setType(String types) throws SAXException {
470 try {
471 for (String type : types.split(",")) {
472 type = Character.toUpperCase(type.charAt(0))+type.substring(1);
473 if (this.types == null)
474 this.types = new LinkedList<Class<?>>();
475 this.types.add(Class.forName("org.openstreetmap.josm.data.osm."+type));
476 }
477 } catch (ClassNotFoundException e) {
478 e.printStackTrace();
479 throw new SAXException(tr("Unknown type"));
480 }
481 }
482
483 public static List<TaggingPreset> readAll(Reader in) throws SAXException {
484 XmlObjectParser parser = new XmlObjectParser();
485 parser.mapOnStart("item", TaggingPreset.class);
486 parser.mapOnStart("separator", TaggingPresetSeparator.class);
487 parser.mapBoth("group", TaggingPresetMenu.class);
488 parser.map("text", Text.class);
489 parser.map("link", Link.class);
490 parser.mapOnStart("optional", Optional.class);
491 parser.map("check", Check.class);
492 parser.map("combo", Combo.class);
493 parser.map("label", Label.class);
494 parser.map("space", Space.class);
495 parser.map("key", Key.class);
496 LinkedList<TaggingPreset> all = new LinkedList<TaggingPreset>();
497 TaggingPresetMenu lastmenu = null;
498 parser.start(in);
499 while(parser.hasNext()) {
500 Object o = parser.next();
501 if (o instanceof TaggingPresetMenu) {
502 TaggingPresetMenu tp = (TaggingPresetMenu) o;
503 if(tp == lastmenu)
504 lastmenu = tp.group;
505 else
506 {
507 tp.setDisplayName();
508 tp.group = lastmenu;
509 lastmenu = tp;
510 all.add(tp);
511 Main.toolbar.register(tp);
512
513 }
514 } else if (o instanceof TaggingPresetSeparator) {
515 TaggingPresetSeparator tp = (TaggingPresetSeparator) o;
516 tp.group = lastmenu;
517 all.add(tp);
518 } else if (o instanceof TaggingPreset) {
519 TaggingPreset tp = (TaggingPreset) o;
520 tp.group = lastmenu;
521 tp.setDisplayName();
522 all.add(tp);
523 Main.toolbar.register(tp);
524 } else
525 all.getLast().data.add((Item)o);
526 }
527 return all;
528 }
529
530 public static Collection<TaggingPreset> readFromPreferences() {
531 LinkedList<TaggingPreset> allPresets = new LinkedList<TaggingPreset>();
532 String allTaggingPresets = Main.pref.get("taggingpreset.sources");
533
534 if (Main.pref.getBoolean("taggingpreset.enable-defaults", true))
535 {
536 allTaggingPresets = "resource://presets/presets.xml"
537 + (allTaggingPresets != null ? ";"+allTaggingPresets : "");
538 }
539
540 for(String source : allTaggingPresets.split(";"))
541 {
542 try {
543 MirroredInputStream s = new MirroredInputStream(source);
544 InputStreamReader r;
545 try
546 {
547 r = new InputStreamReader(s, "UTF-8");
548 }
549 catch (UnsupportedEncodingException e)
550 {
551 r = new InputStreamReader(s);
552 }
553 allPresets.addAll(TaggingPreset.readAll(new BufferedReader(r)));
554 } catch (IOException e) {
555 e.printStackTrace();
556 JOptionPane.showMessageDialog(Main.parent, tr("Could not read tagging preset source: {0}",source));
557 } catch (SAXException e) {
558 e.printStackTrace();
559 JOptionPane.showMessageDialog(Main.parent, tr("Error parsing {0}: ", source)+e.getMessage());
560 }
561 }
562 return allPresets;
563 }
564
565 public JPanel createPanel(Collection<OsmPrimitive> selected) {
566 if (data == null)
567 return null;
568 JPanel p = new JPanel(new GridBagLayout());
569
570 for (Item i : data)
571 i.addToPanel(p, selected);
572 return p;
573 }
574
575 public void actionPerformed(ActionEvent e) {
576 Collection<OsmPrimitive> sel = Main.ds.getSelected();
577 JPanel p = createPanel(sel);
578 if (p == null)
579 return;
580 int answer = JOptionPane.OK_OPTION;
581 if (p.getComponentCount() != 0) {
582 final JOptionPane optionPane = new JOptionPane(p, JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION){
583 @Override public void selectInitialValue() {
584 for (Item i : data) {
585 if (i.focus) {
586 i.requestFocusInWindow();
587 return;
588 }
589 }
590 }
591 };
592 optionPane.createDialog(Main.parent, trn("Change {0} object", "Change {0} objects", sel.size(), sel.size())).setVisible(true);
593 Object answerObj = optionPane.getValue();
594 if (answerObj == null || answerObj == JOptionPane.UNINITIALIZED_VALUE ||
595 (answerObj instanceof Integer && (Integer)answerObj != JOptionPane.OK_OPTION))
596 answer = JOptionPane.CANCEL_OPTION;
597 }
598 if (answer == JOptionPane.OK_OPTION) {
599 Command cmd = createCommand(Main.ds.getSelected());
600 if (cmd != null)
601 Main.main.undoRedo.add(cmd);
602 }
603 Main.ds.setSelected(Main.ds.getSelected()); // force update
604 }
605
606 private Command createCommand(Collection<OsmPrimitive> participants) {
607 Collection<OsmPrimitive> sel = new LinkedList<OsmPrimitive>();
608 for (OsmPrimitive osm : participants)
609 if (types == null || types.contains(osm.getClass()))
610 sel.add(osm);
611 if (sel.isEmpty())
612 return null;
613
614 List<Command> cmds = new LinkedList<Command>();
615 for (Item i : data)
616 i.addCommands(sel, cmds);
617 if (cmds.size() == 0)
618 return null;
619 else if (cmds.size() == 1)
620 return cmds.get(0);
621 else
622 return new SequenceCommand(tr("Change Properties"), cmds);
623 }
624}
Note: See TracBrowser for help on using the repository browser.