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

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

close bug with preset initialisation. Fix by Michel Marti

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