source: josm/trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetItems.java@ 7532

Last change on this file since 7532 was 7532, checked in by Don-vip, 10 years ago

fix #8664, fix #10510 - possibility to add icon to preset labels

File size: 58.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.tagging;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.tools.I18n.trc;
6
7import java.awt.Component;
8import java.awt.Dimension;
9import java.awt.Font;
10import java.awt.GridBagLayout;
11import java.awt.GridLayout;
12import java.awt.event.ActionEvent;
13import java.awt.event.ActionListener;
14import java.awt.event.MouseAdapter;
15import java.awt.event.MouseEvent;
16import java.io.File;
17import java.lang.reflect.Method;
18import java.lang.reflect.Modifier;
19import java.text.NumberFormat;
20import java.text.ParseException;
21import java.util.ArrayList;
22import java.util.Arrays;
23import java.util.Collection;
24import java.util.Collections;
25import java.util.EnumSet;
26import java.util.HashMap;
27import java.util.LinkedHashMap;
28import java.util.LinkedList;
29import java.util.List;
30import java.util.Map;
31import java.util.TreeSet;
32
33import javax.swing.ButtonGroup;
34import javax.swing.Icon;
35import javax.swing.ImageIcon;
36import javax.swing.JButton;
37import javax.swing.JComponent;
38import javax.swing.JLabel;
39import javax.swing.JList;
40import javax.swing.JPanel;
41import javax.swing.JScrollPane;
42import javax.swing.JSeparator;
43import javax.swing.JToggleButton;
44import javax.swing.ListCellRenderer;
45import javax.swing.ListModel;
46
47import org.openstreetmap.josm.Main;
48import org.openstreetmap.josm.actions.search.SearchCompiler;
49import org.openstreetmap.josm.data.osm.OsmPrimitive;
50import org.openstreetmap.josm.data.osm.OsmUtils;
51import org.openstreetmap.josm.data.osm.Tag;
52import org.openstreetmap.josm.data.preferences.BooleanProperty;
53import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
54import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionItemPriority;
55import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;
56import org.openstreetmap.josm.gui.widgets.JosmComboBox;
57import org.openstreetmap.josm.gui.widgets.JosmTextField;
58import org.openstreetmap.josm.gui.widgets.QuadStateCheckBox;
59import org.openstreetmap.josm.gui.widgets.UrlLabel;
60import org.openstreetmap.josm.tools.GBC;
61import org.openstreetmap.josm.tools.ImageProvider;
62import org.openstreetmap.josm.tools.Predicate;
63import org.openstreetmap.josm.tools.Utils;
64import org.xml.sax.SAXException;
65
66/**
67 * Class that contains all subtypes of TaggingPresetItem, static supplementary data, types and methods
68 * @since 6068
69 */
70public final class TaggingPresetItems {
71 private TaggingPresetItems() {
72 }
73
74 private static int auto_increment_selected = 0;
75 /** Translatation of "<different>". Use in combo boxes to display en entry matching several different values. */
76 public static final String DIFFERENT = tr("<different>");
77
78 private static final BooleanProperty PROP_FILL_DEFAULT = new BooleanProperty("taggingpreset.fill-default-for-tagged-primitives", false);
79
80 // cache the parsing of types using a LRU cache (http://java-planet.blogspot.com/2005/08/how-to-set-up-simple-lru-cache-using.html)
81 private static final Map<String,EnumSet<TaggingPresetType>> TYPE_CACHE = new LinkedHashMap<>(16, 1.1f, true);
82
83 /**
84 * Last value of each key used in presets, used for prefilling corresponding fields
85 */
86 private static final Map<String,String> LAST_VALUES = new HashMap<>();
87
88 public static class PresetListEntry {
89 public String value;
90 /** The context used for translating {@link #value} */
91 public String value_context;
92 public String display_value;
93 public String short_description;
94 /** The location of icon file to display */
95 public String icon;
96 /** The size of displayed icon. If not set, default is size from icon file */
97 public String icon_size;
98 /** The localized version of {@link #display_value}. */
99 public String locale_display_value;
100 /** The localized version of {@link #short_description}. */
101 public String locale_short_description;
102 private final File zipIcons = TaggingPresetReader.getZipIcons();
103
104 // Cached size (currently only for Combo) to speed up preset dialog initialization
105 private int prefferedWidth = -1;
106 private int prefferedHeight = -1;
107
108 public String getListDisplay() {
109 if (value.equals(DIFFERENT))
110 return "<b>"+DIFFERENT.replaceAll("<", "&lt;").replaceAll(">", "&gt;")+"</b>";
111
112 if (value.isEmpty())
113 return "&nbsp;";
114
115 final StringBuilder res = new StringBuilder("<b>");
116 res.append(getDisplayValue(true));
117 res.append("</b>");
118 if (getShortDescription(true) != null) {
119 // wrap in table to restrict the text width
120 res.append("<div style=\"width:300px; padding:0 0 5px 5px\">");
121 res.append(getShortDescription(true));
122 res.append("</div>");
123 }
124 return res.toString();
125 }
126
127 /**
128 * Returns the entry icon, if any.
129 * @return the entry icon, or {@code null}
130 */
131 public ImageIcon getIcon() {
132 return icon == null ? null : loadImageIcon(icon, zipIcons, parseInteger(icon_size));
133 }
134
135 /**
136 * Construxts a new {@code PresetListEntry}, uninitialized.
137 */
138 public PresetListEntry() {
139 }
140
141 public PresetListEntry(String value) {
142 this.value = value;
143 }
144
145 public String getDisplayValue(boolean translated) {
146 return translated
147 ? Utils.firstNonNull(locale_display_value, tr(display_value), trc(value_context, value))
148 : Utils.firstNonNull(display_value, value);
149 }
150
151 public String getShortDescription(boolean translated) {
152 return translated
153 ? Utils.firstNonNull(locale_short_description, tr(short_description))
154 : short_description;
155 }
156
157 // toString is mainly used to initialize the Editor
158 @Override
159 public String toString() {
160 if (value.equals(DIFFERENT))
161 return DIFFERENT;
162 return getDisplayValue(true).replaceAll("<.*>", ""); // remove additional markup, e.g. <br>
163 }
164 }
165
166 public static class Role {
167 public EnumSet<TaggingPresetType> types;
168 public String key;
169 /** The text to display */
170 public String text;
171 /** The context used for translating {@link #text} */
172 public String text_context;
173 /** The localized version of {@link #text}. */
174 public String locale_text;
175 public SearchCompiler.Match memberExpression;
176
177 public boolean required = false;
178 private long count = 0;
179
180 public void setType(String types) throws SAXException {
181 this.types = getType(types);
182 }
183
184 public void setRequisite(String str) throws SAXException {
185 if("required".equals(str)) {
186 required = true;
187 } else if(!"optional".equals(str))
188 throw new SAXException(tr("Unknown requisite: {0}", str));
189 }
190
191 public void setMember_expression(String member_expression) throws SAXException {
192 try {
193 this.memberExpression = SearchCompiler.compile(member_expression, true, true);
194 } catch (SearchCompiler.ParseError ex) {
195 throw new SAXException(tr("Illegal member expression: {0}", ex.getMessage()), ex);
196 }
197 }
198
199 public void setCount(String count) {
200 this.count = Long.parseLong(count);
201 }
202
203 /**
204 * Return either argument, the highest possible value or the lowest allowed value
205 */
206 public long getValidCount(long c) {
207 if (count > 0 && !required)
208 return c != 0 ? count : 0;
209 else if (count > 0)
210 return count;
211 else if (!required)
212 return c != 0 ? c : 0;
213 else
214 return c != 0 ? c : 1;
215 }
216
217 public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) {
218 String cstring;
219 if (count > 0 && !required) {
220 cstring = "0,"+count;
221 } else if(count > 0) {
222 cstring = String.valueOf(count);
223 } else if(!required) {
224 cstring = "0-...";
225 } else {
226 cstring = "1-...";
227 }
228 if (locale_text == null) {
229 locale_text = getLocaleText(text, text_context, null);
230 }
231 p.add(new JLabel(locale_text+":"), GBC.std().insets(0,0,10,0));
232 p.add(new JLabel(key), GBC.std().insets(0,0,10,0));
233 p.add(new JLabel(cstring), types == null ? GBC.eol() : GBC.std().insets(0,0,10,0));
234 if (types != null) {
235 JPanel pp = new JPanel();
236 for(TaggingPresetType t : types) {
237 pp.add(new JLabel(ImageProvider.get(t.getIconName())));
238 }
239 p.add(pp, GBC.eol());
240 }
241 return true;
242 }
243 }
244
245 /**
246 * Enum denoting how a match (see {@link TaggingPresetItem#matches}) is performed.
247 */
248 public static enum MatchType {
249
250 /** Neutral, i.e., do not consider this item for matching. */
251 NONE("none"),
252 /** Positive if key matches, neutral otherwise. */
253 KEY("key"),
254 /** Positive if key matches, negative otherwise. */
255 KEY_REQUIRED("key!"),
256 /** Positive if key and value matches, negative otherwise. */
257 KEY_VALUE("keyvalue");
258
259 private final String value;
260
261 private MatchType(String value) {
262 this.value = value;
263 }
264
265 /**
266 * Replies the associated textual value.
267 * @return the associated textual value
268 */
269 public String getValue() {
270 return value;
271 }
272
273 /**
274 * Determines the {@code MatchType} for the given textual value.
275 * @param type the textual value
276 * @return the {@code MatchType} for the given textual value
277 */
278 public static MatchType ofString(String type) {
279 for (MatchType i : EnumSet.allOf(MatchType.class)) {
280 if (i.getValue().equals(type))
281 return i;
282 }
283 throw new IllegalArgumentException(type + " is not allowed");
284 }
285 }
286
287 public static class Usage {
288 TreeSet<String> values;
289 boolean hadKeys = false;
290 boolean hadEmpty = false;
291
292 public boolean hasUniqueValue() {
293 return values.size() == 1 && !hadEmpty;
294 }
295
296 public boolean unused() {
297 return values.isEmpty();
298 }
299
300 public String getFirst() {
301 return values.first();
302 }
303
304 public boolean hadKeys() {
305 return hadKeys;
306 }
307 }
308
309 /**
310 * A tagging preset item displaying a localizable text.
311 * @since 6190
312 */
313 public abstract static class TaggingPresetTextItem extends TaggingPresetItem {
314
315 /** The text to display */
316 public String text;
317
318 /** The context used for translating {@link #text} */
319 public String text_context;
320
321 /** The localized version of {@link #text} */
322 public String locale_text;
323
324 protected final void initializeLocaleText(String defaultText) {
325 if (locale_text == null) {
326 locale_text = getLocaleText(text, text_context, defaultText);
327 }
328 }
329
330 @Override
331 void addCommands(List<Tag> changedTags) {
332 }
333
334 protected String fieldsToString() {
335 return (text != null ? "text=" + text + ", " : "")
336 + (text_context != null ? "text_context=" + text_context + ", " : "")
337 + (locale_text != null ? "locale_text=" + locale_text : "");
338 }
339
340 @Override
341 public String toString() {
342 return getClass().getSimpleName() + " [" + fieldsToString() + "]";
343 }
344 }
345
346 /**
347 * Label type.
348 */
349 public static class Label extends TaggingPresetTextItem {
350
351 /** The location of icon file to display (optional) */
352 public String icon;
353 /** The size of displayed icon. If not set, default is 16px */
354 public String icon_size;
355
356 @Override
357 public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) {
358 initializeLocaleText(null);
359 addLabel(p, getIcon(), locale_text);
360 return true;
361 }
362
363 /**
364 * Adds a new {@code JLabel} to the given panel.
365 * @param p The panel
366 * @param icon the icon (optional, can be null)
367 * @param label The text label
368 */
369 public static void addLabel(JPanel p, Icon icon, String label) {
370 p.add(new JLabel(label, icon, JLabel.LEADING), GBC.eol().fill(GBC.HORIZONTAL));
371 }
372
373 /**
374 * Returns the label icon, if any.
375 * @return the label icon, or {@code null}
376 */
377 public ImageIcon getIcon() {
378 Integer size = parseInteger(icon_size);
379 return icon == null ? null : loadImageIcon(icon, TaggingPresetReader.getZipIcons(), size != null ? size : 16);
380 }
381 }
382
383 /**
384 * Hyperlink type.
385 */
386 public static class Link extends TaggingPresetTextItem {
387
388 /** The link to display. */
389 public String href;
390
391 /** The localized version of {@link #href}. */
392 public String locale_href;
393
394 @Override
395 public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) {
396 initializeLocaleText(tr("More information about this feature"));
397 String url = locale_href;
398 if (url == null) {
399 url = href;
400 }
401 if (url != null) {
402 p.add(new UrlLabel(url, locale_text, 2), GBC.eol().insets(0, 10, 0, 0).fill(GBC.HORIZONTAL));
403 }
404 return false;
405 }
406
407 @Override
408 protected String fieldsToString() {
409 return super.fieldsToString()
410 + (href != null ? "href=" + href + ", " : "")
411 + (locale_href != null ? "locale_href=" + locale_href + ", " : "");
412 }
413 }
414
415 public static class PresetLink extends TaggingPresetItem {
416
417 public String preset_name = "";
418
419 @Override
420 boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) {
421 final String presetName = preset_name;
422 final TaggingPreset t = Utils.filter(TaggingPresets.getTaggingPresets(), new Predicate<TaggingPreset>() {
423 @Override
424 public boolean evaluate(TaggingPreset object) {
425 return presetName.equals(object.name);
426 }
427 }).iterator().next();
428 if (t == null) return false;
429 JLabel lbl = new PresetLabel(t);
430 lbl.addMouseListener(new MouseAdapter() {
431 @Override
432 public void mouseClicked(MouseEvent arg0) {
433 t.actionPerformed(null);
434 }
435 });
436 p.add(lbl, GBC.eol().fill(GBC.HORIZONTAL));
437 return false;
438 }
439
440 @Override
441 void addCommands(List<Tag> changedTags) {
442 }
443 }
444
445 public static class Roles extends TaggingPresetItem {
446
447 public final List<Role> roles = new LinkedList<>();
448
449 @Override
450 public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) {
451 p.add(new JLabel(" "), GBC.eol()); // space
452 if (!roles.isEmpty()) {
453 JPanel proles = new JPanel(new GridBagLayout());
454 proles.add(new JLabel(tr("Available roles")), GBC.std().insets(0, 0, 10, 0));
455 proles.add(new JLabel(tr("role")), GBC.std().insets(0, 0, 10, 0));
456 proles.add(new JLabel(tr("count")), GBC.std().insets(0, 0, 10, 0));
457 proles.add(new JLabel(tr("elements")), GBC.eol());
458 for (Role i : roles) {
459 i.addToPanel(proles, sel);
460 }
461 p.add(proles, GBC.eol());
462 }
463 return false;
464 }
465
466 @Override
467 public void addCommands(List<Tag> changedTags) {
468 }
469 }
470
471 public static class Optional extends TaggingPresetTextItem {
472
473 // TODO: Draw a box around optional stuff
474 @Override
475 public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) {
476 initializeLocaleText(tr("Optional Attributes:"));
477 p.add(new JLabel(" "), GBC.eol()); // space
478 p.add(new JLabel(locale_text), GBC.eol());
479 p.add(new JLabel(" "), GBC.eol()); // space
480 return false;
481 }
482 }
483
484 /**
485 * Horizontal separator type.
486 */
487 public static class Space extends TaggingPresetItem {
488
489 @Override
490 public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) {
491 p.add(new JLabel(" "), GBC.eol()); // space
492 return false;
493 }
494
495 @Override
496 public void addCommands(List<Tag> changedTags) {
497 }
498
499 @Override
500 public String toString() {
501 return "Space";
502 }
503 }
504
505 /**
506 * Class used to represent a {@link JSeparator} inside tagging preset window.
507 * @since 6198
508 */
509 public static class ItemSeparator extends TaggingPresetItem {
510
511 @Override
512 public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) {
513 p.add(new JSeparator(), GBC.eol().fill(GBC.HORIZONTAL).insets(0, 5, 0, 5));
514 return false;
515 }
516
517 @Override
518 public void addCommands(List<Tag> changedTags) {
519 }
520
521 @Override
522 public String toString() {
523 return "ItemSeparator";
524 }
525 }
526
527 /**
528 * Preset item associated to an OSM key.
529 */
530 public abstract static class KeyedItem extends TaggingPresetItem {
531
532 public String key;
533 /** The text to display */
534 public String text;
535 /** The context used for translating {@link #text} */
536 public String text_context;
537 public String match = getDefaultMatch().getValue();
538
539 public abstract MatchType getDefaultMatch();
540 public abstract Collection<String> getValues();
541
542 @Override
543 Boolean matches(Map<String, String> tags) {
544 switch (MatchType.ofString(match)) {
545 case NONE:
546 return null;
547 case KEY:
548 return tags.containsKey(key) ? true : null;
549 case KEY_REQUIRED:
550 return tags.containsKey(key);
551 case KEY_VALUE:
552 return tags.containsKey(key) && (getValues().contains(tags.get(key)));
553 default:
554 throw new IllegalStateException();
555 }
556 }
557
558 @Override
559 public String toString() {
560 return "KeyedItem [key=" + key + ", text=" + text
561 + ", text_context=" + text_context + ", match=" + match
562 + "]";
563 }
564 }
565
566 /**
567 * Invisible type allowing to hardcode an OSM key/value from the preset definition.
568 */
569 public static class Key extends KeyedItem {
570
571 /** The hardcoded value for key */
572 public String value;
573
574 @Override
575 public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) {
576 return false;
577 }
578
579 @Override
580 public void addCommands(List<Tag> changedTags) {
581 changedTags.add(new Tag(key, value));
582 }
583
584 @Override
585 public MatchType getDefaultMatch() {
586 return MatchType.KEY_VALUE;
587 }
588
589 @Override
590 public Collection<String> getValues() {
591 return Collections.singleton(value);
592 }
593
594 @Override
595 public String toString() {
596 return "Key [key=" + key + ", value=" + value + ", text=" + text
597 + ", text_context=" + text_context + ", match=" + match
598 + "]";
599 }
600 }
601
602 /**
603 * Text field type.
604 */
605 public static class Text extends KeyedItem {
606
607 /** The localized version of {@link #text}. */
608 public String locale_text;
609 public String default_;
610 public String originalValue;
611 public String use_last_as_default = "false";
612 public String auto_increment;
613 public String length;
614 public String alternative_autocomplete_keys;
615
616 private JComponent value;
617
618 @Override
619 public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) {
620
621 // find out if our key is already used in the selection.
622 Usage usage = determineTextUsage(sel, key);
623 AutoCompletingTextField textField = new AutoCompletingTextField();
624 if (alternative_autocomplete_keys != null) {
625 initAutoCompletionField(textField, (key + "," + alternative_autocomplete_keys).split(","));
626 } else {
627 initAutoCompletionField(textField, key);
628 }
629 textField.setHint(key);
630 if (length != null && !length.isEmpty()) {
631 textField.setMaxChars(Integer.valueOf(length));
632 }
633 if (usage.unused()){
634 if (auto_increment_selected != 0 && auto_increment != null) {
635 try {
636 textField.setText(Integer.toString(Integer.parseInt(LAST_VALUES.get(key)) + auto_increment_selected));
637 } catch (NumberFormatException ex) {
638 // Ignore - cannot auto-increment if last was non-numeric
639 }
640 }
641 else if (!usage.hadKeys() || PROP_FILL_DEFAULT.get() || "force".equals(use_last_as_default)) {
642 // selected osm primitives are untagged or filling default values feature is enabled
643 if (!"false".equals(use_last_as_default) && LAST_VALUES.containsKey(key) && !presetInitiallyMatches) {
644 textField.setText(LAST_VALUES.get(key));
645 } else {
646 textField.setText(default_);
647 }
648 } else {
649 // selected osm primitives are tagged and filling default values feature is disabled
650 textField.setText("");
651 }
652 value = textField;
653 originalValue = null;
654 } else if (usage.hasUniqueValue()) {
655 // all objects use the same value
656 textField.setText(usage.getFirst());
657 value = textField;
658 originalValue = usage.getFirst();
659 } else {
660 // the objects have different values
661 JosmComboBox<String> comboBox = new JosmComboBox<>(usage.values.toArray(new String[0]));
662 comboBox.setEditable(true);
663 comboBox.setEditor(textField);
664 comboBox.getEditor().setItem(DIFFERENT);
665 value=comboBox;
666 originalValue = DIFFERENT;
667 }
668 if (locale_text == null) {
669 locale_text = getLocaleText(text, text_context, null);
670 }
671
672 // if there's an auto_increment setting, then wrap the text field
673 // into a panel, appending a number of buttons.
674 // auto_increment has a format like -2,-1,1,2
675 // the text box being the first component in the panel is relied
676 // on in a rather ugly fashion further down.
677 if (auto_increment != null) {
678 ButtonGroup bg = new ButtonGroup();
679 JPanel pnl = new JPanel(new GridBagLayout());
680 pnl.add(value, GBC.std().fill(GBC.HORIZONTAL));
681
682 // first, one button for each auto_increment value
683 for (final String ai : auto_increment.split(",")) {
684 JToggleButton aibutton = new JToggleButton(ai);
685 aibutton.setToolTipText(tr("Select auto-increment of {0} for this field", ai));
686 aibutton.setMargin(new java.awt.Insets(0,0,0,0));
687 aibutton.setFocusable(false);
688 bg.add(aibutton);
689 try {
690 // TODO there must be a better way to parse a number like "+3" than this.
691 final int buttonvalue = (NumberFormat.getIntegerInstance().parse(ai.replace("+", ""))).intValue();
692 if (auto_increment_selected == buttonvalue) aibutton.setSelected(true);
693 aibutton.addActionListener(new ActionListener() {
694 @Override
695 public void actionPerformed(ActionEvent e) {
696 auto_increment_selected = buttonvalue;
697 }
698 });
699 pnl.add(aibutton, GBC.std());
700 } catch (ParseException x) {
701 Main.error("Cannot parse auto-increment value of '" + ai + "' into an integer");
702 }
703 }
704
705 // an invisible toggle button for "release" of the button group
706 final JToggleButton clearbutton = new JToggleButton("X");
707 clearbutton.setVisible(false);
708 clearbutton.setFocusable(false);
709 bg.add(clearbutton);
710 // and its visible counterpart. - this mechanism allows us to
711 // have *no* button selected after the X is clicked, instead
712 // of the X remaining selected
713 JButton releasebutton = new JButton("X");
714 releasebutton.setToolTipText(tr("Cancel auto-increment for this field"));
715 releasebutton.setMargin(new java.awt.Insets(0,0,0,0));
716 releasebutton.setFocusable(false);
717 releasebutton.addActionListener(new ActionListener() {
718 @Override
719 public void actionPerformed(ActionEvent e) {
720 auto_increment_selected = 0;
721 clearbutton.setSelected(true);
722 }
723 });
724 pnl.add(releasebutton, GBC.eol());
725 value = pnl;
726 }
727 p.add(new JLabel(locale_text+":"), GBC.std().insets(0,0,10,0));
728 p.add(value, GBC.eol().fill(GBC.HORIZONTAL));
729 return true;
730 }
731
732 private static String getValue(Component comp) {
733 if (comp instanceof JosmComboBox) {
734 return ((JosmComboBox<?>) comp).getEditor().getItem().toString();
735 } else if (comp instanceof JosmTextField) {
736 return ((JosmTextField) comp).getText();
737 } else if (comp instanceof JPanel) {
738 return getValue(((JPanel)comp).getComponent(0));
739 } else {
740 return null;
741 }
742 }
743
744 @Override
745 public void addCommands(List<Tag> changedTags) {
746
747 // return if unchanged
748 String v = getValue(value);
749 if (v == null) {
750 Main.error("No 'last value' support for component " + value);
751 return;
752 }
753
754 v = Tag.removeWhiteSpaces(v);
755
756 if (!"false".equals(use_last_as_default) || auto_increment != null) {
757 LAST_VALUES.put(key, v);
758 }
759 if (v.equals(originalValue) || (originalValue == null && v.length() == 0))
760 return;
761
762 changedTags.add(new Tag(key, v));
763 }
764
765 @Override
766 boolean requestFocusInWindow() {
767 return value.requestFocusInWindow();
768 }
769
770 @Override
771 public MatchType getDefaultMatch() {
772 return MatchType.NONE;
773 }
774
775 @Override
776 public Collection<String> getValues() {
777 if (default_ == null || default_.isEmpty())
778 return Collections.emptyList();
779 return Collections.singleton(default_);
780 }
781 }
782
783 /**
784 * A group of {@link Check}s.
785 * @since 6114
786 */
787 public static class CheckGroup extends TaggingPresetItem {
788
789 /**
790 * Number of columns (positive integer)
791 */
792 public String columns;
793
794 /**
795 * List of checkboxes
796 */
797 public final List<Check> checks = new LinkedList<>();
798
799 @Override
800 boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) {
801 Integer cols = Integer.valueOf(columns);
802 int rows = (int) Math.ceil(checks.size()/cols.doubleValue());
803 JPanel panel = new JPanel(new GridLayout(rows, cols));
804
805 for (Check check : checks) {
806 check.addToPanel(panel, sel, presetInitiallyMatches);
807 }
808
809 p.add(panel, GBC.eol());
810 return false;
811 }
812
813 @Override
814 void addCommands(List<Tag> changedTags) {
815 for (Check check : checks) {
816 check.addCommands(changedTags);
817 }
818 }
819
820 @Override
821 public String toString() {
822 return "CheckGroup [columns=" + columns + "]";
823 }
824 }
825
826 /**
827 * Checkbox type.
828 */
829 public static class Check extends KeyedItem {
830
831 /** The localized version of {@link #text}. */
832 public String locale_text;
833 /** the value to set when checked (default is "yes") */
834 public String value_on = OsmUtils.trueval;
835 /** the value to set when unchecked (default is "no") */
836 public String value_off = OsmUtils.falseval;
837 /** whether the off value is disabled in the dialog, i.e., only unset or yes are provided */
838 public boolean disable_off = false;
839 /** ticked on/off (default is "off") */
840 public boolean default_ = false; // only used for tagless objects
841
842 private QuadStateCheckBox check;
843 private QuadStateCheckBox.State initialState;
844 private boolean def;
845
846 @Override
847 public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) {
848
849 // find out if our key is already used in the selection.
850 final Usage usage = determineBooleanUsage(sel, key);
851 final String oneValue = usage.values.isEmpty() ? null : usage.values.last();
852 def = default_;
853
854 if (locale_text == null) {
855 locale_text = getLocaleText(text, text_context, null);
856 }
857
858 if (usage.values.size() < 2 && (oneValue == null || value_on.equals(oneValue) || value_off.equals(oneValue))) {
859 if (def && !PROP_FILL_DEFAULT.get()) {
860 // default is set and filling default values feature is disabled - check if all primitives are untagged
861 for (OsmPrimitive s : sel)
862 if (s.hasKeys()) {
863 def = false;
864 }
865 }
866
867 // all selected objects share the same value which is either true or false or unset,
868 // we can display a standard check box.
869 initialState = value_on.equals(oneValue)
870 ? QuadStateCheckBox.State.SELECTED
871 : value_off.equals(oneValue)
872 ? QuadStateCheckBox.State.NOT_SELECTED
873 : def
874 ? QuadStateCheckBox.State.SELECTED
875 : QuadStateCheckBox.State.UNSET;
876 } else {
877 def = false;
878 // the objects have different values, or one or more objects have something
879 // else than true/false. we display a quad-state check box
880 // in "partial" state.
881 initialState = QuadStateCheckBox.State.PARTIAL;
882 }
883
884 final List<QuadStateCheckBox.State> allowedStates = new ArrayList<>(4);
885 if (QuadStateCheckBox.State.PARTIAL.equals(initialState))
886 allowedStates.add(QuadStateCheckBox.State.PARTIAL);
887 allowedStates.add(QuadStateCheckBox.State.SELECTED);
888 if (!disable_off || value_off.equals(oneValue))
889 allowedStates.add(QuadStateCheckBox.State.NOT_SELECTED);
890 allowedStates.add(QuadStateCheckBox.State.UNSET);
891 check = new QuadStateCheckBox(locale_text, initialState,
892 allowedStates.toArray(new QuadStateCheckBox.State[allowedStates.size()]));
893
894 p.add(check, GBC.eol().fill(GBC.HORIZONTAL));
895 return true;
896 }
897
898 @Override
899 public void addCommands(List<Tag> changedTags) {
900 // if the user hasn't changed anything, don't create a command.
901 if (check.getState() == initialState && !def) return;
902
903 // otherwise change things according to the selected value.
904 changedTags.add(new Tag(key,
905 check.getState() == QuadStateCheckBox.State.SELECTED ? value_on :
906 check.getState() == QuadStateCheckBox.State.NOT_SELECTED ? value_off :
907 null));
908 }
909
910 @Override
911 boolean requestFocusInWindow() {return check.requestFocusInWindow();}
912
913 @Override
914 public MatchType getDefaultMatch() {
915 return MatchType.NONE;
916 }
917
918 @Override
919 public Collection<String> getValues() {
920 return disable_off ? Arrays.asList(value_on) : Arrays.asList(value_on, value_off);
921 }
922
923 @Override
924 public String toString() {
925 return "Check ["
926 + (locale_text != null ? "locale_text=" + locale_text + ", " : "")
927 + (value_on != null ? "value_on=" + value_on + ", " : "")
928 + (value_off != null ? "value_off=" + value_off + ", " : "")
929 + "default_=" + default_ + ", "
930 + (check != null ? "check=" + check + ", " : "")
931 + (initialState != null ? "initialState=" + initialState
932 + ", " : "") + "def=" + def + "]";
933 }
934 }
935
936 /**
937 * Abstract superclass for combo box and multi-select list types.
938 */
939 public abstract static class ComboMultiSelect extends KeyedItem {
940
941 /** The localized version of {@link #text}. */
942 public String locale_text;
943 public String values;
944 public String values_from;
945 /** The context used for translating {@link #values} */
946 public String values_context;
947 public String display_values;
948 /** The localized version of {@link #display_values}. */
949 public String locale_display_values;
950 public String short_descriptions;
951 /** The localized version of {@link #short_descriptions}. */
952 public String locale_short_descriptions;
953 public String default_;
954 public String delimiter = ";";
955 public String use_last_as_default = "false";
956 /** whether to use values for search via {@link TaggingPresetSelector} */
957 public String values_searchable = "false";
958
959 protected JComponent component;
960 protected final Map<String, PresetListEntry> lhm = new LinkedHashMap<>();
961 private boolean initialized = false;
962 protected Usage usage;
963 protected Object originalValue;
964
965 protected abstract Object getSelectedItem();
966 protected abstract void addToPanelAnchor(JPanel p, String def, boolean presetInitiallyMatches);
967
968 protected char getDelChar() {
969 return delimiter.isEmpty() ? ';' : delimiter.charAt(0);
970 }
971
972 @Override
973 public Collection<String> getValues() {
974 initListEntries();
975 return lhm.keySet();
976 }
977
978 public Collection<String> getDisplayValues() {
979 initListEntries();
980 return Utils.transform(lhm.values(), new Utils.Function<PresetListEntry, String>() {
981 @Override
982 public String apply(PresetListEntry x) {
983 return x.getDisplayValue(true);
984 }
985 });
986 }
987
988 @Override
989 public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) {
990
991 initListEntries();
992
993 // find out if our key is already used in the selection.
994 usage = determineTextUsage(sel, key);
995 if (!usage.hasUniqueValue() && !usage.unused()) {
996 lhm.put(DIFFERENT, new PresetListEntry(DIFFERENT));
997 }
998
999 p.add(new JLabel(tr("{0}:", locale_text)), GBC.std().insets(0, 0, 10, 0));
1000 addToPanelAnchor(p, default_, presetInitiallyMatches);
1001
1002 return true;
1003
1004 }
1005
1006 private void initListEntries() {
1007 if (initialized) {
1008 lhm.remove(DIFFERENT); // possibly added in #addToPanel
1009 return;
1010 } else if (lhm.isEmpty()) {
1011 initListEntriesFromAttributes();
1012 } else {
1013 if (values != null) {
1014 Main.warn(tr("Warning in tagging preset \"{0}-{1}\": "
1015 + "Ignoring ''{2}'' attribute as ''{3}'' elements are given.",
1016 key, text, "values", "list_entry"));
1017 }
1018 if (display_values != null || locale_display_values != null) {
1019 Main.warn(tr("Warning in tagging preset \"{0}-{1}\": "
1020 + "Ignoring ''{2}'' attribute as ''{3}'' elements are given.",
1021 key, text, "display_values", "list_entry"));
1022 }
1023 if (short_descriptions != null || locale_short_descriptions != null) {
1024 Main.warn(tr("Warning in tagging preset \"{0}-{1}\": "
1025 + "Ignoring ''{2}'' attribute as ''{3}'' elements are given.",
1026 key, text, "short_descriptions", "list_entry"));
1027 }
1028 for (PresetListEntry e : lhm.values()) {
1029 if (e.value_context == null) {
1030 e.value_context = values_context;
1031 }
1032 }
1033 }
1034 if (locale_text == null) {
1035 locale_text = getLocaleText(text, text_context, null);
1036 }
1037 initialized = true;
1038 }
1039
1040 private String[] initListEntriesFromAttributes() {
1041 char delChar = getDelChar();
1042
1043 String[] value_array = null;
1044
1045 if (values_from != null) {
1046 String[] class_method = values_from.split("#");
1047 if (class_method != null && class_method.length == 2) {
1048 try {
1049 Method method = Class.forName(class_method[0]).getMethod(class_method[1]);
1050 // Check method is public static String[] methodName()
1051 int mod = method.getModifiers();
1052 if (Modifier.isPublic(mod) && Modifier.isStatic(mod)
1053 && method.getReturnType().equals(String[].class) && method.getParameterTypes().length == 0) {
1054 value_array = (String[]) method.invoke(null);
1055 } else {
1056 Main.error(tr("Broken tagging preset \"{0}-{1}\" - Java method given in ''values_from'' is not \"{2}\"", key, text,
1057 "public static String[] methodName()"));
1058 }
1059 } catch (Exception e) {
1060 Main.error(tr("Broken tagging preset \"{0}-{1}\" - Java method given in ''values_from'' threw {2} ({3})", key, text,
1061 e.getClass().getName(), e.getMessage()));
1062 }
1063 }
1064 }
1065
1066 if (value_array == null) {
1067 value_array = splitEscaped(delChar, values);
1068 }
1069
1070 final String displ = Utils.firstNonNull(locale_display_values, display_values);
1071 String[] display_array = displ == null ? value_array : splitEscaped(delChar, displ);
1072
1073 final String descr = Utils.firstNonNull(locale_short_descriptions, short_descriptions);
1074 String[] short_descriptions_array = descr == null ? null : splitEscaped(delChar, descr);
1075
1076 if (display_array.length != value_array.length) {
1077 Main.error(tr("Broken tagging preset \"{0}-{1}\" - number of items in ''display_values'' must be the same as in ''values''", key, text));
1078 display_array = value_array;
1079 }
1080
1081 if (short_descriptions_array != null && short_descriptions_array.length != value_array.length) {
1082 Main.error(tr("Broken tagging preset \"{0}-{1}\" - number of items in ''short_descriptions'' must be the same as in ''values''", key, text));
1083 short_descriptions_array = null;
1084 }
1085
1086 for (int i = 0; i < value_array.length; i++) {
1087 final PresetListEntry e = new PresetListEntry(value_array[i]);
1088 e.locale_display_value = locale_display_values != null
1089 ? display_array[i]
1090 : trc(values_context, fixPresetString(display_array[i]));
1091 if (short_descriptions_array != null) {
1092 e.locale_short_description = locale_short_descriptions != null
1093 ? short_descriptions_array[i]
1094 : tr(fixPresetString(short_descriptions_array[i]));
1095 }
1096 lhm.put(value_array[i], e);
1097 display_array[i] = e.getDisplayValue(true);
1098 }
1099
1100 return display_array;
1101 }
1102
1103 protected String getDisplayIfNull() {
1104 return null;
1105 }
1106
1107 @Override
1108 public void addCommands(List<Tag> changedTags) {
1109 Object obj = getSelectedItem();
1110 String display = (obj == null) ? null : obj.toString();
1111 String value = null;
1112 if (display == null) {
1113 display = getDisplayIfNull();
1114 }
1115
1116 if (display != null) {
1117 for (String val : lhm.keySet()) {
1118 String k = lhm.get(val).toString();
1119 if (k != null && k.equals(display)) {
1120 value = val;
1121 break;
1122 }
1123 }
1124 if (value == null) {
1125 value = display;
1126 }
1127 } else {
1128 value = "";
1129 }
1130 value = Tag.removeWhiteSpaces(value);
1131
1132 // no change if same as before
1133 if (originalValue == null) {
1134 if (value.length() == 0)
1135 return;
1136 } else if (value.equals(originalValue.toString()))
1137 return;
1138
1139 if (!"false".equals(use_last_as_default)) {
1140 LAST_VALUES.put(key, value);
1141 }
1142 changedTags.add(new Tag(key, value));
1143 }
1144
1145 public void addListEntry(PresetListEntry e) {
1146 lhm.put(e.value, e);
1147 }
1148
1149 public void addListEntries(Collection<PresetListEntry> e) {
1150 for (PresetListEntry i : e) {
1151 addListEntry(i);
1152 }
1153 }
1154
1155 @Override
1156 boolean requestFocusInWindow() {
1157 return component.requestFocusInWindow();
1158 }
1159
1160 private static final ListCellRenderer<PresetListEntry> RENDERER = new ListCellRenderer<PresetListEntry>() {
1161
1162 JLabel lbl = new JLabel();
1163
1164 @Override
1165 public Component getListCellRendererComponent(
1166 JList<? extends PresetListEntry> list,
1167 PresetListEntry item,
1168 int index,
1169 boolean isSelected,
1170 boolean cellHasFocus) {
1171
1172 // Only return cached size, item is not shown
1173 if (!list.isShowing() && item.prefferedWidth != -1 && item.prefferedHeight != -1) {
1174 if (index == -1) {
1175 lbl.setPreferredSize(new Dimension(item.prefferedWidth, 10));
1176 } else {
1177 lbl.setPreferredSize(new Dimension(item.prefferedWidth, item.prefferedHeight));
1178 }
1179 return lbl;
1180 }
1181
1182 lbl.setPreferredSize(null);
1183
1184
1185 if (isSelected) {
1186 lbl.setBackground(list.getSelectionBackground());
1187 lbl.setForeground(list.getSelectionForeground());
1188 } else {
1189 lbl.setBackground(list.getBackground());
1190 lbl.setForeground(list.getForeground());
1191 }
1192
1193 lbl.setOpaque(true);
1194 lbl.setFont(lbl.getFont().deriveFont(Font.PLAIN));
1195 lbl.setText("<html>" + item.getListDisplay() + "</html>");
1196 lbl.setIcon(item.getIcon());
1197 lbl.setEnabled(list.isEnabled());
1198
1199 // Cache size
1200 item.prefferedWidth = lbl.getPreferredSize().width;
1201 item.prefferedHeight = lbl.getPreferredSize().height;
1202
1203 // We do not want the editor to have the maximum height of all
1204 // entries. Return a dummy with bogus height.
1205 if (index == -1) {
1206 lbl.setPreferredSize(new Dimension(lbl.getPreferredSize().width, 10));
1207 }
1208 return lbl;
1209 }
1210 };
1211
1212 protected ListCellRenderer<PresetListEntry> getListCellRenderer() {
1213 return RENDERER;
1214 }
1215
1216 @Override
1217 public MatchType getDefaultMatch() {
1218 return MatchType.NONE;
1219 }
1220 }
1221
1222 /**
1223 * Combobox type.
1224 */
1225 public static class Combo extends ComboMultiSelect {
1226
1227 public boolean editable = true;
1228 protected JosmComboBox<PresetListEntry> combo;
1229 public String length;
1230
1231 /**
1232 * Constructs a new {@code Combo}.
1233 */
1234 public Combo() {
1235 delimiter = ",";
1236 }
1237
1238 @Override
1239 protected void addToPanelAnchor(JPanel p, String def, boolean presetInitiallyMatches) {
1240 if (!usage.unused()) {
1241 for (String s : usage.values) {
1242 if (!lhm.containsKey(s)) {
1243 lhm.put(s, new PresetListEntry(s));
1244 }
1245 }
1246 }
1247 if (def != null && !lhm.containsKey(def)) {
1248 lhm.put(def, new PresetListEntry(def));
1249 }
1250 lhm.put("", new PresetListEntry(""));
1251
1252 combo = new JosmComboBox<>(lhm.values().toArray(new PresetListEntry[0]));
1253 component = combo;
1254 combo.setRenderer(getListCellRenderer());
1255 combo.setEditable(editable);
1256 combo.reinitialize(lhm.values());
1257 AutoCompletingTextField tf = new AutoCompletingTextField();
1258 initAutoCompletionField(tf, key);
1259 tf.setHint(key);
1260 if (length != null && !length.isEmpty()) {
1261 tf.setMaxChars(Integer.valueOf(length));
1262 }
1263 AutoCompletionList acList = tf.getAutoCompletionList();
1264 if (acList != null) {
1265 acList.add(getDisplayValues(), AutoCompletionItemPriority.IS_IN_STANDARD);
1266 }
1267 combo.setEditor(tf);
1268
1269 if (usage.hasUniqueValue()) {
1270 // all items have the same value (and there were no unset items)
1271 originalValue = lhm.get(usage.getFirst());
1272 combo.setSelectedItem(originalValue);
1273 } else if (def != null && usage.unused()) {
1274 // default is set and all items were unset
1275 if (!usage.hadKeys() || PROP_FILL_DEFAULT.get() || "force".equals(use_last_as_default)) {
1276 // selected osm primitives are untagged or filling default feature is enabled
1277 combo.setSelectedItem(lhm.get(def).getDisplayValue(true));
1278 } else {
1279 // selected osm primitives are tagged and filling default feature is disabled
1280 combo.setSelectedItem("");
1281 }
1282 originalValue = lhm.get(DIFFERENT);
1283 } else if (usage.unused()) {
1284 // all items were unset (and so is default)
1285 originalValue = lhm.get("");
1286 if ("force".equals(use_last_as_default) && LAST_VALUES.containsKey(key) && !presetInitiallyMatches) {
1287 combo.setSelectedItem(lhm.get(LAST_VALUES.get(key)));
1288 } else {
1289 combo.setSelectedItem(originalValue);
1290 }
1291 } else {
1292 originalValue = lhm.get(DIFFERENT);
1293 combo.setSelectedItem(originalValue);
1294 }
1295 p.add(combo, GBC.eol().fill(GBC.HORIZONTAL));
1296
1297 }
1298
1299 @Override
1300 protected Object getSelectedItem() {
1301 return combo.getSelectedItem();
1302
1303 }
1304
1305 @Override
1306 protected String getDisplayIfNull() {
1307 if (combo.isEditable())
1308 return combo.getEditor().getItem().toString();
1309 else
1310 return null;
1311 }
1312 }
1313
1314 /**
1315 * Multi-select list type.
1316 */
1317 public static class MultiSelect extends ComboMultiSelect {
1318
1319 public long rows = -1;
1320 protected ConcatenatingJList list;
1321
1322 @Override
1323 protected void addToPanelAnchor(JPanel p, String def, boolean presetInitiallyMatches) {
1324 list = new ConcatenatingJList(delimiter, lhm.values().toArray(new PresetListEntry[0]));
1325 component = list;
1326 ListCellRenderer<PresetListEntry> renderer = getListCellRenderer();
1327 list.setCellRenderer(renderer);
1328
1329 if (usage.hasUniqueValue() && !usage.unused()) {
1330 originalValue = usage.getFirst();
1331 list.setSelectedItem(originalValue);
1332 } else if (def != null && !usage.hadKeys() || PROP_FILL_DEFAULT.get() || "force".equals(use_last_as_default)) {
1333 originalValue = DIFFERENT;
1334 list.setSelectedItem(def);
1335 } else if (usage.unused()) {
1336 originalValue = null;
1337 list.setSelectedItem(originalValue);
1338 } else {
1339 originalValue = DIFFERENT;
1340 list.setSelectedItem(originalValue);
1341 }
1342
1343 JScrollPane sp = new JScrollPane(list);
1344 // if a number of rows has been specified in the preset,
1345 // modify preferred height of scroll pane to match that row count.
1346 if (rows != -1) {
1347 double height = renderer.getListCellRendererComponent(list,
1348 new PresetListEntry("x"), 0, false, false).getPreferredSize().getHeight() * rows;
1349 sp.setPreferredSize(new Dimension((int) sp.getPreferredSize().getWidth(), (int) height));
1350 }
1351 p.add(sp, GBC.eol().fill(GBC.HORIZONTAL));
1352 }
1353
1354 @Override
1355 protected Object getSelectedItem() {
1356 return list.getSelectedItem();
1357 }
1358
1359 @Override
1360 public void addCommands(List<Tag> changedTags) {
1361 // Do not create any commands if list has been disabled because of an unknown value (fix #8605)
1362 if (list.isEnabled()) {
1363 super.addCommands(changedTags);
1364 }
1365 }
1366 }
1367
1368 /**
1369 * Class that allows list values to be assigned and retrieved as a comma-delimited
1370 * string (extracted from TaggingPreset)
1371 */
1372 private static class ConcatenatingJList extends JList<PresetListEntry> {
1373 private String delimiter;
1374 public ConcatenatingJList(String del, PresetListEntry[] o) {
1375 super(o);
1376 delimiter = del;
1377 }
1378
1379 public void setSelectedItem(Object o) {
1380 if (o == null) {
1381 clearSelection();
1382 } else {
1383 String s = o.toString();
1384 TreeSet<String> parts = new TreeSet<>(Arrays.asList(s.split(delimiter)));
1385 ListModel<PresetListEntry> lm = getModel();
1386 int[] intParts = new int[lm.getSize()];
1387 int j = 0;
1388 for (int i = 0; i < lm.getSize(); i++) {
1389 if (parts.contains((lm.getElementAt(i).value))) {
1390 intParts[j++]=i;
1391 }
1392 }
1393 setSelectedIndices(Arrays.copyOf(intParts, j));
1394 // check if we have actually managed to represent the full
1395 // value with our presets. if not, cop out; we will not offer
1396 // a selection list that threatens to ruin the value.
1397 setEnabled(Utils.join(delimiter, parts).equals(getSelectedItem()));
1398 }
1399 }
1400
1401 public String getSelectedItem() {
1402 ListModel<PresetListEntry> lm = getModel();
1403 int[] si = getSelectedIndices();
1404 StringBuilder builder = new StringBuilder();
1405 for (int i=0; i<si.length; i++) {
1406 if (i>0) {
1407 builder.append(delimiter);
1408 }
1409 builder.append(lm.getElementAt(si[i]).value);
1410 }
1411 return builder.toString();
1412 }
1413 }
1414
1415 public static EnumSet<TaggingPresetType> getType(String types) throws SAXException {
1416 if (TYPE_CACHE.containsKey(types))
1417 return TYPE_CACHE.get(types);
1418 EnumSet<TaggingPresetType> result = EnumSet.noneOf(TaggingPresetType.class);
1419 for (String type : Arrays.asList(types.split(","))) {
1420 try {
1421 TaggingPresetType presetType = TaggingPresetType.fromString(type);
1422 result.add(presetType);
1423 } catch (IllegalArgumentException e) {
1424 throw new SAXException(tr("Unknown type: {0}", type), e);
1425 }
1426 }
1427 TYPE_CACHE.put(types, result);
1428 return result;
1429 }
1430
1431 static String fixPresetString(String s) {
1432 return s == null ? s : s.replaceAll("'","''");
1433 }
1434
1435 private static String getLocaleText(String text, String text_context, String defaultText) {
1436 if (text == null) {
1437 return defaultText;
1438 } else if (text_context != null) {
1439 return trc(text_context, fixPresetString(text));
1440 } else {
1441 return tr(fixPresetString(text));
1442 }
1443 }
1444
1445 /**
1446 * allow escaped comma in comma separated list:
1447 * "A\, B\, C,one\, two" --&gt; ["A, B, C", "one, two"]
1448 * @param delimiter the delimiter, e.g. a comma. separates the entries and
1449 * must be escaped within one entry
1450 * @param s the string
1451 */
1452 private static String[] splitEscaped(char delimiter, String s) {
1453 if (s == null)
1454 return new String[0];
1455 List<String> result = new ArrayList<>();
1456 boolean backslash = false;
1457 StringBuilder item = new StringBuilder();
1458 for (int i=0; i<s.length(); i++) {
1459 char ch = s.charAt(i);
1460 if (backslash) {
1461 item.append(ch);
1462 backslash = false;
1463 } else if (ch == '\\') {
1464 backslash = true;
1465 } else if (ch == delimiter) {
1466 result.add(item.toString());
1467 item.setLength(0);
1468 } else {
1469 item.append(ch);
1470 }
1471 }
1472 if (item.length() > 0) {
1473 result.add(item.toString());
1474 }
1475 return result.toArray(new String[result.size()]);
1476 }
1477
1478 static Usage determineTextUsage(Collection<OsmPrimitive> sel, String key) {
1479 Usage returnValue = new Usage();
1480 returnValue.values = new TreeSet<>();
1481 for (OsmPrimitive s : sel) {
1482 String v = s.get(key);
1483 if (v != null) {
1484 returnValue.values.add(v);
1485 } else {
1486 returnValue.hadEmpty = true;
1487 }
1488 if(s.hasKeys()) {
1489 returnValue.hadKeys = true;
1490 }
1491 }
1492 return returnValue;
1493 }
1494
1495 static Usage determineBooleanUsage(Collection<OsmPrimitive> sel, String key) {
1496
1497 Usage returnValue = new Usage();
1498 returnValue.values = new TreeSet<>();
1499 for (OsmPrimitive s : sel) {
1500 String booleanValue = OsmUtils.getNamedOsmBoolean(s.get(key));
1501 if (booleanValue != null) {
1502 returnValue.values.add(booleanValue);
1503 }
1504 }
1505 return returnValue;
1506 }
1507
1508 protected static ImageIcon loadImageIcon(String iconName, File zipIcons, Integer maxSize) {
1509 final Collection<String> s = Main.pref.getCollection("taggingpreset.icon.sources", null);
1510 ImageProvider imgProv = new ImageProvider(iconName).setDirs(s).setId("presets").setArchive(zipIcons).setOptional(true);
1511 if (maxSize != null) {
1512 imgProv.setMaxSize(maxSize);
1513 }
1514 return imgProv.get();
1515 }
1516
1517 protected static Integer parseInteger(String str) {
1518 if (str == null || str.isEmpty())
1519 return null;
1520 try {
1521 return Integer.parseInt(str);
1522 } catch (Exception e) {
1523 if (Main.isTraceEnabled()) {
1524 Main.trace(e.getMessage());
1525 }
1526 }
1527 return null;
1528 }
1529}
Note: See TracBrowser for help on using the repository browser.