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

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

removed duplicate preset icons, added better message for 500 server error. Run optipng over all icons.

  • 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 {
364 System.out.println("Could not get presets icon " + iconName);
365 icon = new ImageIcon(iconName);
366 }
367 if (Math.max(icon.getIconHeight(), icon.getIconWidth()) != 16)
368 icon = new ImageIcon(icon.getImage().getScaledInstance(16, 16, Image.SCALE_SMOOTH));
369 putValue(Action.SMALL_ICON, icon);
370 }
371
372 /**
373 * Called from the XML parser to set the types this preset affects
374 */
375 public void setType(String types) throws SAXException {
376 try {
377 for (String type : types.split(",")) {
378 type = Character.toUpperCase(type.charAt(0))+type.substring(1);
379 if (this.types == null)
380 this.types = new LinkedList<Class<?>>();
381 this.types.add(Class.forName("org.openstreetmap.josm.data.osm."+type));
382 }
383 } catch (ClassNotFoundException e) {
384 e.printStackTrace();
385 throw new SAXException(tr("Unknown type"));
386 }
387 }
388
389 public static List<TaggingPreset> readAll(Reader in) throws SAXException {
390 XmlObjectParser parser = new XmlObjectParser();
391 parser.mapOnStart("item", TaggingPreset.class);
392 parser.mapOnStart("separator", TaggingPresetSeparator.class);
393 parser.mapBoth("group", TaggingPresetMenu.class);
394 parser.map("text", Text.class);
395 parser.map("check", Check.class);
396 parser.map("combo", Combo.class);
397 parser.map("label", Label.class);
398 parser.map("key", Key.class);
399 LinkedList<TaggingPreset> all = new LinkedList<TaggingPreset>();
400 TaggingPresetMenu lastmenu = null;
401 parser.start(in);
402 while(parser.hasNext()) {
403 Object o = parser.next();
404 if (o instanceof TaggingPresetMenu) {
405 TaggingPresetMenu tp = (TaggingPresetMenu) o;
406 if(tp == lastmenu)
407 lastmenu = tp.group;
408 else
409 {
410 tp.setDisplayName();
411 tp.group = lastmenu;
412 lastmenu = tp;
413 all.add(tp);
414 Main.toolbar.register(tp);
415
416 }
417 } else if (o instanceof TaggingPresetSeparator) {
418 TaggingPresetSeparator tp = (TaggingPresetSeparator) o;
419 tp.group = lastmenu;
420 all.add(tp);
421 } else if (o instanceof TaggingPreset) {
422 TaggingPreset tp = (TaggingPreset) o;
423 tp.group = lastmenu;
424 tp.setDisplayName();
425 all.add(tp);
426 Main.toolbar.register(tp);
427 } else
428 all.getLast().data.add((Item)o);
429 }
430 return all;
431 }
432
433 public static Collection<TaggingPreset> readFromPreferences() {
434 LinkedList<TaggingPreset> allPresets = new LinkedList<TaggingPreset>();
435 String allTaggingPresets = Main.pref.get("taggingpreset.sources");
436
437 if (Main.pref.getBoolean("taggingpreset.enable-defaults", true))
438 {
439 allTaggingPresets = "resource://presets/presets.xml"
440 + (allTaggingPresets != null ? ";"+allTaggingPresets : "");
441 }
442
443 for(String source : allTaggingPresets.split(";"))
444 {
445 try {
446 MirroredInputStream s = new MirroredInputStream(source);
447 InputStreamReader r;
448 try
449 {
450 r = new InputStreamReader(s, "UTF-8");
451 }
452 catch (UnsupportedEncodingException e)
453 {
454 r = new InputStreamReader(s);
455 }
456 allPresets.addAll(TaggingPreset.readAll(new BufferedReader(r)));
457 } catch (IOException e) {
458 e.printStackTrace();
459 JOptionPane.showMessageDialog(Main.parent, tr("Could not read tagging preset source: {0}",source));
460 } catch (SAXException e) {
461 e.printStackTrace();
462 JOptionPane.showMessageDialog(Main.parent, tr("Error parsing {0}: ", source)+e.getMessage());
463 }
464 }
465 return allPresets;
466 }
467
468 public JPanel createPanel(Collection<OsmPrimitive> selected) {
469 if (data == null)
470 return null;
471 JPanel p = new JPanel(new GridBagLayout());
472
473 for (Item i : data)
474 i.addToPanel(p, selected);
475 return p;
476 }
477
478 public void actionPerformed(ActionEvent e) {
479 Collection<OsmPrimitive> sel = Main.ds.getSelected();
480 JPanel p = createPanel(sel);
481 if (p == null)
482 return;
483 int answer = JOptionPane.OK_OPTION;
484 if (p.getComponentCount() != 0) {
485 final JOptionPane optionPane = new JOptionPane(p, JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION){
486 @Override public void selectInitialValue() {
487 for (Item i : data) {
488 if (i.focus) {
489 i.requestFocusInWindow();
490 return;
491 }
492 }
493 }
494 };
495 optionPane.createDialog(Main.parent, trn("Change {0} object", "Change {0} objects", sel.size(), sel.size())).setVisible(true);
496 Object answerObj = optionPane.getValue();
497 if (answerObj == null || answerObj == JOptionPane.UNINITIALIZED_VALUE ||
498 (answerObj instanceof Integer && (Integer)answerObj != JOptionPane.OK_OPTION))
499 answer = JOptionPane.CANCEL_OPTION;
500 }
501 if (answer == JOptionPane.OK_OPTION) {
502 Command cmd = createCommand(Main.ds.getSelected());
503 if (cmd != null)
504 Main.main.undoRedo.add(cmd);
505 }
506 Main.ds.setSelected(Main.ds.getSelected()); // force update
507 }
508
509 private Command createCommand(Collection<OsmPrimitive> participants) {
510 Collection<OsmPrimitive> sel = new LinkedList<OsmPrimitive>();
511 for (OsmPrimitive osm : participants)
512 if (types == null || types.contains(osm.getClass()))
513 sel.add(osm);
514 if (sel.isEmpty())
515 return null;
516
517 List<Command> cmds = new LinkedList<Command>();
518 for (Item i : data)
519 i.addCommands(sel, cmds);
520 if (cmds.size() == 0)
521 return null;
522 else if (cmds.size() == 1)
523 return cmds.get(0);
524 else
525 return new SequenceCommand(tr("Change Properties"), cmds);
526 }
527}
Note: See TracBrowser for help on using the repository browser.