source: josm/trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/KeyedItem.java@ 17829

Last change on this file since 17829 was 17610, checked in by simon04, 3 years ago

see #18949 - Tagging presets: value_template="..." for <text>

Use value_template to generate the value automatically based on other <text> values of this preset. For instance, "Bus {ref}: {from} → {to}" can be used to generate the name of a bus route relation.

File size: 8.0 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.tagging.presets.items;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.util.Collection;
7import java.util.EnumSet;
8import java.util.HashMap;
9import java.util.Map;
10import java.util.NoSuchElementException;
11import java.util.SortedSet;
12import java.util.TreeSet;
13
14import javax.swing.JPopupMenu;
15
16import org.openstreetmap.josm.data.osm.OsmPrimitive;
17import org.openstreetmap.josm.data.osm.OsmUtils;
18import org.openstreetmap.josm.data.osm.Tag;
19import org.openstreetmap.josm.data.preferences.BooleanProperty;
20import org.openstreetmap.josm.gui.dialogs.properties.HelpTagAction;
21import org.openstreetmap.josm.gui.dialogs.properties.TaginfoAction;
22import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem;
23
24/**
25 * Preset item associated to an OSM key.
26 */
27public abstract class KeyedItem extends TextItem {
28
29 /** Translation of "&lt;different&gt;". Use in combo boxes to display an entry matching several different values. */
30 protected static final String DIFFERENT = tr("<different>");
31
32 protected static final BooleanProperty PROP_FILL_DEFAULT = new BooleanProperty("taggingpreset.fill-default-for-tagged-primitives", false);
33
34 /** Last value of each key used in presets, used for prefilling corresponding fields */
35 static final Map<String, String> LAST_VALUES = new HashMap<>();
36
37 /** This specifies the property key that will be modified by the item. */
38 public String key; // NOSONAR
39 /**
40 * Allows to change the matching process, i.e., determining whether the tags of an OSM object fit into this preset.
41 * If a preset fits then it is linked in the Tags/Membership dialog.<ul>
42 * <li>none: neutral, i.e., do not consider this item for matching</li>
43 * <li>key: positive if key matches, neutral otherwise</li>
44 * <li>key!: positive if key matches, negative otherwise</li>
45 * <li>keyvalue: positive if key and value matches, neutral otherwise</li>
46 * <li>keyvalue!: positive if key and value matches, negative otherwise</li></ul>
47 * Note that for a match, at least one positive and no negative is required.
48 * Default is "keyvalue!" for {@link Key} and "none" for {@link Text}, {@link Combo}, {@link MultiSelect} and {@link Check}.
49 */
50 public String match = getDefaultMatch().getValue(); // NOSONAR
51
52 /**
53 * Enum denoting how a match (see {@link TaggingPresetItem#matches}) is performed.
54 */
55 protected enum MatchType {
56
57 /** Neutral, i.e., do not consider this item for matching. */
58 NONE("none"),
59 /** Positive if key matches, neutral otherwise. */
60 KEY("key"),
61 /** Positive if key matches, negative otherwise. */
62 KEY_REQUIRED("key!"),
63 /** Positive if key and value matches, neutral otherwise. */
64 KEY_VALUE("keyvalue"),
65 /** Positive if key and value matches, negative otherwise. */
66 KEY_VALUE_REQUIRED("keyvalue!");
67
68 private final String value;
69
70 MatchType(String value) {
71 this.value = value;
72 }
73
74 /**
75 * Replies the associated textual value.
76 * @return the associated textual value
77 */
78 public String getValue() {
79 return value;
80 }
81
82 /**
83 * Determines the {@code MatchType} for the given textual value.
84 * @param type the textual value
85 * @return the {@code MatchType} for the given textual value
86 */
87 public static MatchType ofString(String type) {
88 for (MatchType i : EnumSet.allOf(MatchType.class)) {
89 if (i.getValue().equals(type))
90 return i;
91 }
92 throw new IllegalArgumentException(type + " is not allowed");
93 }
94 }
95
96 /**
97 * Usage information on a key
98 *
99 * TODO merge with {@link org.openstreetmap.josm.data.osm.TagCollection}
100 */
101 public static class Usage {
102 /**
103 * A set of values that were used for this key.
104 */
105 public final SortedSet<String> values = new TreeSet<>(); // NOSONAR
106 private boolean hadKeys;
107 private boolean hadEmpty;
108
109 /**
110 * Check if there is exactly one value for this key.
111 * @return <code>true</code> if there was exactly one value.
112 */
113 public boolean hasUniqueValue() {
114 return values.size() == 1 && !hadEmpty;
115 }
116
117 /**
118 * Check if this key was not used in any primitive
119 * @return <code>true</code> if it was unused.
120 */
121 public boolean unused() {
122 return values.isEmpty();
123 }
124
125 /**
126 * Get the first value available.
127 * @return The first value
128 * @throws NoSuchElementException if there is no such value.
129 */
130 public String getFirst() {
131 return values.first();
132 }
133
134 /**
135 * Check if we encountered any primitive that had any keys
136 * @return <code>true</code> if any of the primitives had any tags.
137 */
138 public boolean hadKeys() {
139 return hadKeys;
140 }
141 }
142
143 /**
144 * Computes the tag usage for the given key from the given primitives
145 * @param sel the primitives
146 * @param key the key
147 * @return the tag usage
148 */
149 public static Usage determineTextUsage(Collection<OsmPrimitive> sel, String key) {
150 Usage returnValue = new Usage();
151 for (OsmPrimitive s : sel) {
152 String v = s.get(key);
153 if (v != null) {
154 returnValue.values.add(v);
155 } else {
156 returnValue.hadEmpty = true;
157 }
158 if (s.hasKeys()) {
159 returnValue.hadKeys = true;
160 }
161 }
162 return returnValue;
163 }
164
165 protected static Usage determineBooleanUsage(Collection<OsmPrimitive> sel, String key) {
166 Usage returnValue = new Usage();
167 for (OsmPrimitive s : sel) {
168 String booleanValue = OsmUtils.getNamedOsmBoolean(s.get(key));
169 if (booleanValue != null) {
170 returnValue.values.add(booleanValue);
171 }
172 }
173 return returnValue;
174 }
175
176 /**
177 * Determines whether key or key+value are required.
178 * @return whether key or key+value are required
179 */
180 public boolean isKeyRequired() {
181 final MatchType type = MatchType.ofString(match);
182 return MatchType.KEY_REQUIRED == type || MatchType.KEY_VALUE_REQUIRED == type;
183 }
184
185 /**
186 * Returns the default match.
187 * @return the default match
188 */
189 public abstract MatchType getDefaultMatch();
190
191 /**
192 * Returns the list of values.
193 * @return the list of values
194 */
195 public abstract Collection<String> getValues();
196
197 protected String getKeyTooltipText() {
198 return tr("This corresponds to the key ''{0}''", key);
199 }
200
201 @Override
202 public Boolean matches(Map<String, String> tags) {
203 switch (MatchType.ofString(match)) {
204 case NONE:
205 return null;
206 case KEY:
207 return tags.containsKey(key) ? Boolean.TRUE : null;
208 case KEY_REQUIRED:
209 return tags.containsKey(key);
210 case KEY_VALUE:
211 return tags.containsKey(key) && getValues().contains(tags.get(key)) ? Boolean.TRUE : null;
212 case KEY_VALUE_REQUIRED:
213 return tags.containsKey(key) && getValues().contains(tags.get(key));
214 default:
215 throw new IllegalStateException();
216 }
217 }
218
219 protected JPopupMenu getPopupMenu() {
220 Tag tag = new Tag(key, null);
221 JPopupMenu popupMenu = new JPopupMenu();
222 popupMenu.add(tr("Key: {0}", key)).setEnabled(false);
223 popupMenu.add(new HelpTagAction(() -> tag));
224 TaginfoAction taginfoAction = new TaginfoAction(() -> tag, () -> null);
225 popupMenu.add(taginfoAction.toTagHistoryAction());
226 popupMenu.add(taginfoAction);
227 return popupMenu;
228 }
229
230 @Override
231 public String toString() {
232 return "KeyedItem [key=" + key + ", text=" + text
233 + ", text_context=" + text_context + ", match=" + match
234 + ']';
235 }
236}
Note: See TracBrowser for help on using the repository browser.