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

Last change on this file since 910 was 910, checked in by stoecker, 16 years ago

minor fix

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