Ticket #21851: 21851.patch
File 21851.patch, 675.8 KB (added by , 3 years ago) |
---|
-
resources/images/in_dataset.svg
1 <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 <svg 3 version="1.1" 4 width="16px" 5 height="16px" 6 id="svg79288" 7 sodipodi:docname="in_dataset.svg" 8 inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)" 9 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" 10 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" 11 xmlns="http://www.w3.org/2000/svg" 12 xmlns:svg="http://www.w3.org/2000/svg"> 13 <defs 14 id="defs79292" /> 15 <sodipodi:namedview 16 id="namedview79290" 17 pagecolor="#ffffff" 18 bordercolor="#666666" 19 borderopacity="1.0" 20 inkscape:pageshadow="2" 21 inkscape:pageopacity="0.0" 22 inkscape:pagecheckerboard="0" 23 showgrid="true" 24 inkscape:zoom="89.875" 25 inkscape:cx="3.3769124" 26 inkscape:cy="8.0055633" 27 inkscape:window-width="3840" 28 inkscape:window-height="2085" 29 inkscape:window-x="0" 30 inkscape:window-y="0" 31 inkscape:window-maximized="1" 32 inkscape:current-layer="svg79288"> 33 <inkscape:grid 34 type="xygrid" 35 id="grid79357" 36 empspacing="8" 37 dotted="false" 38 originx="8" 39 originy="8" 40 spacingx="0.5" 41 spacingy="0.5" /> 42 </sodipodi:namedview> 43 <path 44 style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" 45 d="M 3,3 13,6 5,13 Z" 46 id="path80027" 47 sodipodi:nodetypes="cccc" /> 48 <path 49 style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#df421e;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" 50 d="m 11.5,4.5 h 3 v 3 h -3 z" 51 id="path79392" /> 52 <path 53 style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#df421e;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" 54 d="m 1.5,1.5 h 3 v 3 h -3 z" 55 id="path79392-7" /> 56 <path 57 style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#df421e;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" 58 d="m 3.5,11.5 h 3 v 3 h -3 z" 59 id="path79392-4" /> 60 </svg> -
resources/images/in_standard.svg
1 <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 <svg 3 inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)" 4 sodipodi:docname="in_standard.svg" 5 id="svg12" 6 version="1.1" 7 width="16" 8 viewBox="0 0 16 16" 9 height="16" 10 enable-background="new 0 0 96 96" 11 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" 12 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" 13 xmlns="http://www.w3.org/2000/svg" 14 xmlns:svg="http://www.w3.org/2000/svg" 15 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 16 xmlns:cc="http://creativecommons.org/ns#" 17 xmlns:dc="http://purl.org/dc/elements/1.1/"> 18 <metadata 19 id="metadata18"> 20 <rdf:RDF> 21 <cc:Work 22 rdf:about=""> 23 <dc:format>image/svg+xml</dc:format> 24 <dc:type 25 rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> 26 </cc:Work> 27 </rdf:RDF> 28 </metadata> 29 <defs 30 id="defs16" /> 31 <sodipodi:namedview 32 inkscape:current-layer="svg12" 33 inkscape:window-maximized="1" 34 inkscape:window-y="0" 35 inkscape:window-x="0" 36 inkscape:cy="9.5865506" 37 inkscape:cx="7.8530512" 38 inkscape:zoom="66.916666" 39 showgrid="true" 40 id="namedview14" 41 inkscape:window-height="2085" 42 inkscape:window-width="3840" 43 inkscape:pageshadow="2" 44 inkscape:pageopacity="0" 45 guidetolerance="10" 46 gridtolerance="10" 47 objecttolerance="10" 48 borderopacity="1" 49 bordercolor="#666666" 50 pagecolor="#ffffff" 51 inkscape:pagecheckerboard="0" 52 inkscape:lockguides="false"> 53 <inkscape:grid 54 id="grid843" 55 type="xygrid" 56 originx="0" 57 originy="0" 58 empspacing="4" /> 59 </sodipodi:namedview> 60 <text 61 xml:space="preserve" 62 style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:16px;line-height:125%;font-family:FreeSans;-inkscape-font-specification:FreeSans;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" 63 x="11.570769" 64 y="11.741786" 65 id="text12471"><tspan 66 sodipodi:role="line" 67 id="tspan12469" 68 x="11.570769" 69 y="11.741786"></tspan></text> 70 <text 71 xml:space="preserve" 72 style="font-size:13.3333px;line-height:125%;font-family:FreeSans;-inkscape-font-specification:FreeSans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#df421e;fill-opacity:1;stroke:#df421e;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" 73 x="8.0417976" 74 y="11.405445" 75 id="text59954"><tspan 76 sodipodi:role="line" 77 id="tspan59952" 78 x="8.0417976" 79 y="11.405445">§</tspan></text> 80 </svg> -
scripts/TagInfoExtract.java
28 28 import java.util.regex.Matcher; 29 29 import java.util.regex.Pattern; 30 30 import java.util.stream.Collectors; 31 import java.util.stream.Stream;32 31 33 32 import javax.imageio.ImageIO; 34 33 import javax.json.Json; … … 68 67 import org.openstreetmap.josm.gui.mappaint.styleelement.LineElement; 69 68 import org.openstreetmap.josm.gui.mappaint.styleelement.StyleElement; 70 69 import org.openstreetmap.josm.gui.preferences.map.TaggingPresetPreference; 70 import org.openstreetmap.josm.gui.tagging.presets.KeyedItem; 71 71 import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset; 72 72 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetReader; 73 73 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType; 74 import org.openstreetmap.josm.gui.tagging.presets.items.CheckGroup; 75 import org.openstreetmap.josm.gui.tagging.presets.items.KeyedItem; 74 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets; 76 75 import org.openstreetmap.josm.io.CachedFile; 77 76 import org.openstreetmap.josm.io.OsmTransferException; 78 77 import org.openstreetmap.josm.spi.preferences.Config; … … 166 165 Path baseDir = Paths.get(""); 167 166 Path imageDir = Paths.get("taginfo-img"); 168 167 String imageUrlPrefix; 169 CachedFile inputFile;168 String inputUrl; 170 169 Path outputFile; 171 170 boolean noexit; 172 171 … … 174 173 mode = Mode.valueOf(value.toUpperCase(Locale.ENGLISH)); 175 174 switch (mode) { 176 175 case MAPPAINT: 177 input File = new CachedFile("resource://styles/standard/elemstyles.mapcss");176 inputUrl = "resource://styles/standard/elemstyles.mapcss"; 178 177 break; 179 178 case PRESETS: 180 input File = new CachedFile("resource://data/defaultpresets.xml");179 inputUrl = "resource://data/defaultpresets.xml"; 181 180 break; 182 181 default: 183 input File= null;182 inputUrl = null; 184 183 } 185 184 } 186 185 187 186 void setInputFile(String value) { 188 input File = new CachedFile(value);187 inputUrl = value; 189 188 } 190 189 191 190 void setOutputFile(String value) { … … 254 253 255 254 @Override 256 255 void run() throws IOException, OsmTransferException, SAXException { 257 try (BufferedReader reader = options.inputFile.getContentReader()) { 258 Collection<TaggingPreset> presets = TaggingPresetReader.readAll(reader, true); 259 List<TagInfoTag> tags = convertPresets(presets, "", true); 260 Logging.info("Converting {0} internal presets", tags.size()); 261 writeJson("JOSM main presets", "Tags supported by the default presets in the OSM editor JOSM", tags); 262 } 256 TaggingPresets.testInitialize(options.inputUrl); 257 Collection<TaggingPreset> presets = TaggingPresets.getTaggingPresets(); 258 List<TagInfoTag> tags = convertPresets(presets, "", true); 259 Logging.info("Converting {0} internal presets", tags.size()); 260 writeJson("JOSM main presets", "Tags supported by the default presets in the OSM editor JOSM", tags); 263 261 } 264 262 265 263 List<TagInfoTag> convertPresets(Iterable<TaggingPreset> presets, String descriptionPrefix, boolean addImages) { … … 267 265 final Map<Tag, TagInfoTag> requiredTags = new LinkedHashMap<>(); 268 266 final Map<Tag, TagInfoTag> optionalTags = new LinkedHashMap<>(); 269 267 for (TaggingPreset preset : presets) { 270 preset.data.stream() 271 .flatMap(item -> item instanceof KeyedItem 272 ? Stream.of(((KeyedItem) item)) 273 : item instanceof CheckGroup 274 ? ((CheckGroup) item).checks.stream() 275 : Stream.empty()) 276 .forEach(item -> { 268 preset.getAllItems() 269 .forEach(i -> { 270 if (i instanceof KeyedItem) { 271 KeyedItem item = (KeyedItem) i; 277 272 for (String value : values(item)) { 278 Set<TagInfoTag.Type> types = TagInfoTag.Type.forPresetTypes(preset. types);273 Set<TagInfoTag.Type> types = TagInfoTag.Type.forPresetTypes(preset.getTypes()); 279 274 if (item.isKeyRequired()) { 280 275 fillTagsMap(requiredTags, item, value, preset.getName(), types, 281 276 descriptionPrefix + TagInfoTag.REQUIRED_FOR_COUNT + ": ", 282 addImages && preset. iconName != null ? options.findImageUrl(preset.iconName) : null);277 addImages && preset.getIconName() != null ? options.findImageUrl(preset.getIconName()) : null); 283 278 } else { 284 279 fillTagsMap(optionalTags, item, value, preset.getName(), types, 285 280 descriptionPrefix + TagInfoTag.OPTIONAL_FOR_COUNT + ": ", null); 286 281 } 287 282 } 288 }); 283 } 284 }); 289 285 } 290 286 tags.addAll(requiredTags.values()); 291 287 tags.addAll(optionalTags.values()); … … 294 290 295 291 private void fillTagsMap(Map<Tag, TagInfoTag> optionalTags, KeyedItem item, String value, 296 292 String presetName, Set<TagInfoTag.Type> types, String descriptionPrefix, String iconUrl) { 297 optionalTags.compute(new Tag(item. key, value), (osmTag, tagInfoTag) -> {293 optionalTags.compute(new Tag(item.getKey(), value), (osmTag, tagInfoTag) -> { 298 294 if (tagInfoTag == null) { 299 return new TagInfoTag(descriptionPrefix + presetName, item. key, value, types, iconUrl);295 return new TagInfoTag(descriptionPrefix + presetName, item.getKey(), value, types, iconUrl); 300 296 } else { 301 297 tagInfoTag.descriptions.add(presetName); 302 298 tagInfoTag.objectTypes.addAll(types); … … 325 321 } 326 322 try { 327 323 Logging.info("Loading {0}", source.url); 328 Collection<TaggingPreset> presets = TaggingPresetReader.readAll(source.url, false); 324 TaggingPresets.testInitialize(source.url); 325 Collection<TaggingPreset> presets = TaggingPresets.getTaggingPresets(); 329 326 final List<TagInfoTag> t = convertPresets(presets, source.title + " ", false); 330 327 Logging.info("Converted {0} presets of {1} to {2} tags", presets.size(), source.title, t.size()); 331 328 tags.addAll(t); … … 355 352 * @throws ParseException in case of parsing error 356 353 */ 357 354 private void parseStyleSheet() throws IOException, ParseException { 358 try (BufferedReader reader = options.inputFile.getContentReader()) {355 try (BufferedReader reader = new CachedFile(options.inputUrl).getContentReader()) { 359 356 MapCSSParser parser = new MapCSSParser(reader, MapCSSParser.LexicalState.DEFAULT); 360 357 styleSource = new MapCSSStyleSource(""); 361 358 styleSource.url = ""; -
src/org/openstreetmap/josm/actions/UploadAction.java
240 240 ChangesetUpdater.check(); 241 241 242 242 final UploadDialog dialog = UploadDialog.getUploadDialog(); 243 dialog.setUploadedPrimitives(apiData); 244 dialog.initLifeCycle(layer.getDataSet()); 243 dialog.initLifeCycle(layer.getDataSet(), apiData); 245 244 dialog.setVisible(true); 246 245 dialog.rememberUserInput(); 247 246 if (dialog.isCanceled()) { -
src/org/openstreetmap/josm/data/osm/DefaultNameFormatter.java
184 184 } 185 185 name.append(n); 186 186 } else { 187 preset. nameTemplate.appendText(name, (TemplateEngineDataProvider) node);187 preset.getNameTemplate().appendText(name, (TemplateEngineDataProvider) node); 188 188 } 189 189 if (node.isLatLonKnown() && Config.getPref().getBoolean("osm-primitives.showcoor")) { 190 190 name.append(" \u200E("); … … 252 252 253 253 name.append(n); 254 254 } else { 255 preset. nameTemplate.appendText(name, (TemplateEngineDataProvider) way);255 preset.getNameTemplate().appendText(name, (TemplateEngineDataProvider) way); 256 256 } 257 257 258 258 int nodesNo = way.getRealNodesCount(); … … 355 355 } 356 356 result.append(" (").append(relationName).append(", "); 357 357 } else { 358 preset. nameTemplate.appendText(result, (TemplateEngineDataProvider) relation);358 preset.getNameTemplate().appendText(result, (TemplateEngineDataProvider) relation); 359 359 result.append('('); 360 360 } 361 361 return result; -
src/org/openstreetmap/josm/data/osm/search/SearchCompiler.java
45 45 import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.MapCSSParser; 46 46 import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.ParseException; 47 47 import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset; 48 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetMenu;49 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetSeparator;50 48 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets; 51 49 import org.openstreetmap.josm.tools.AlphanumComparator; 52 50 import org.openstreetmap.josm.tools.CheckParameterUtil; … … 1911 1909 1912 1910 this.presets = TaggingPresets.getTaggingPresets() 1913 1911 .stream() 1914 .filter(preset -> !(preset instanceof TaggingPresetMenu || preset instanceof TaggingPresetSeparator))1912 .filter(preset -> preset.getClass() == TaggingPreset.class) 1915 1913 .filter(preset -> presetNameMatch(presetName, preset, matchStrictly)) 1916 1914 .collect(Collectors.toList()); 1917 1915 … … 1929 1927 if (matchStrictly) { 1930 1928 return name.equalsIgnoreCase(preset.getRawName()); 1931 1929 } 1932 1933 try { 1934 String groupSuffix = name.substring(0, name.length() - 2); // try to remove '/*' 1935 TaggingPresetMenu group = preset.group; 1936 1937 return group != null && groupSuffix.equalsIgnoreCase(group.getRawName()); 1938 } catch (StringIndexOutOfBoundsException ex) { 1939 Logging.trace(ex); 1940 return false; 1941 } 1930 return preset.nameMatchesGlob(name); 1942 1931 } 1943 1932 1944 1933 @Override -
src/org/openstreetmap/josm/data/tagging/ac/AutoCompItemCellRenderer.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.tagging.ac; 3 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 6 import java.awt.Color; 7 import java.awt.Component; 8 import java.awt.Font; 9 import java.util.Map; 10 11 import javax.swing.ImageIcon; 12 import javax.swing.JLabel; 13 import javax.swing.JList; 14 import javax.swing.ListCellRenderer; 15 16 import org.openstreetmap.josm.gui.widgets.JosmListCellRenderer; 17 import org.openstreetmap.josm.tools.ImageProvider; 18 import org.openstreetmap.josm.tools.ImageProvider.ImageSizes; 19 20 /** 21 * A custom list cell renderer for autocompletion items that colorizes and adds the value count to 22 * some items. 23 * <p> 24 * See also: {@link AutoCompletionPriority#compareTo} 25 */ 26 public class AutoCompItemCellRenderer extends JosmListCellRenderer<AutoCompletionItem> { 27 /** The color used to render items found in the dataset. */ 28 public static final Color BGCOLOR_1 = new Color(254, 226, 214); 29 /** The color used to render items found in the standard */ 30 public static final Color BGCOLOR_2 = new Color(235, 255, 177); 31 32 protected Map<String, Integer> map; 33 private static final ImageIcon iconEmpty = ImageProvider.getEmpty(ImageSizes.POPUPMENU); 34 private static final ImageIcon iconDataSet = ImageProvider.get("in_dataset", ImageSizes.POPUPMENU); 35 private static final ImageIcon iconStandard = ImageProvider.get("in_standard", ImageSizes.POPUPMENU); 36 37 /** 38 * Constructs the cell renderer. 39 * 40 * @param component The component the renderer is attached to. JComboBox or JList. 41 * @param renderer The L&F renderer. Usually obtained by calling {@code getRenderer()} on {@code component}. 42 * @param map A map from key to count. 43 */ 44 public AutoCompItemCellRenderer(Component component, ListCellRenderer<? super AutoCompletionItem> renderer, Map<String, Integer> map) { 45 super(component, renderer); 46 this.map = map; 47 } 48 49 @Override 50 public Component getListCellRendererComponent(JList<? extends AutoCompletionItem> list, AutoCompletionItem value, 51 int index, boolean isSelected, boolean cellHasFocus) { 52 Integer count = null; 53 if (value == null) 54 value = new AutoCompletionItem("", AutoCompletionPriority.IS_IN_STANDARD); 55 56 // if there is a value count add it to the text 57 if (map != null) { 58 String text = value.toString(); 59 count = map.get(text); 60 if (count != null) { 61 value = new AutoCompletionItem(tr("{0} ({1})", text, count), value.getPriority()); 62 } 63 } 64 65 JLabel l = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); 66 l.setIcon(iconEmpty); 67 if (value.getPriority().isInDataSet()) { 68 l.setIcon(iconDataSet); 69 } 70 if (value.getPriority().isInStandard()) { 71 l.setIcon(iconStandard); 72 } 73 l.setComponentOrientation(component.getComponentOrientation()); 74 if (count != null) { 75 l.setFont(l.getFont().deriveFont(Font.ITALIC + Font.BOLD)); 76 } 77 return l; 78 } 79 } -
src/org/openstreetmap/josm/data/validation/tests/MapCSSTagChecker.java
41 41 import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.ParseException; 42 42 import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.TokenMgrError; 43 43 import org.openstreetmap.josm.gui.progress.ProgressMonitor; 44 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetHandler; 44 45 import org.openstreetmap.josm.io.CachedFile; 45 46 import org.openstreetmap.josm.io.FileWatcher; 46 47 import org.openstreetmap.josm.io.UTFInputStreamReader; … … 150 151 } 151 152 152 153 /** 154 * Obtains all {@link TestError}s for all primitives in the data handler. 155 * @param handler the handler that holds the primitives to check 156 * @param includeOtherSeverity if {@code true}, errors of severity {@link Severity#OTHER} (info) will also be returned 157 * @return all errors, with or without those of "info" severity 158 */ 159 public synchronized Collection<TestError> checkTaggingPresetHandler(TaggingPresetHandler handler, boolean includeOtherSeverity) { 160 Collection<TestError> errors = new ArrayList<>(); 161 for (OsmPrimitive primitive : handler.getPrimitives()) { 162 errors.addAll(getErrorsForPrimitive(primitive, includeOtherSeverity)); 163 } 164 return errors; 165 } 166 167 /** 153 168 * Obtains all {@link TestError}s for the {@link OsmPrimitive} {@code p}. 154 169 * @param p The OSM primitive 155 170 * @param includeOtherSeverity if {@code true}, errors of severity {@link Severity#OTHER} (info) will also be returned -
src/org/openstreetmap/josm/data/validation/tests/OpeningHourTest.java
21 21 import org.openstreetmap.josm.data.preferences.sources.ValidatorPrefHelper; 22 22 import org.openstreetmap.josm.data.validation.Severity; 23 23 import org.openstreetmap.josm.data.validation.Test.TagTest; 24 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetHandler; 24 25 import org.openstreetmap.josm.data.validation.TestError; 25 26 import org.openstreetmap.josm.tools.GBC; 26 27 import org.openstreetmap.josm.tools.Utils; … … 148 149 } 149 150 } 150 151 152 /** 153 * Checks the tags of the given primitive and adds validation errors to the given list. 154 * @param handler the handler that holds the data to check 155 * @param errors The list to add validation errors to 156 * @since 17643 157 */ 158 public void checkTaggingPresetHandler(TaggingPresetHandler handler, Collection<TestError> errors) { 159 for (OsmPrimitive primitive : handler.getPrimitives()) { 160 for (String key : KEYS_TO_CHECK) { 161 errors.addAll(checkOpeningHourSyntax(key, primitive.get(key), primitive, Locale.getDefault())); 162 } 163 // COVID-19, a few additional values are permitted, see #19048, see https://wiki.openstreetmap.org/wiki/Key:opening_hours:covid19 164 final String keyCovid19 = "opening_hours:covid19"; 165 if (primitive.hasTag(keyCovid19) && !primitive.hasTag(keyCovid19, "same", "restricted", "open", "off")) { 166 errors.addAll(checkOpeningHourSyntax(keyCovid19, primitive.get(keyCovid19), primitive, Locale.getDefault())); 167 } 168 } 169 } 170 151 171 @Override 152 172 public void addGui(JPanel testPanel) { 153 173 super.addGui(testPanel); -
src/org/openstreetmap/josm/data/validation/tests/RelationChecker.java
30 30 import org.openstreetmap.josm.data.validation.Test; 31 31 import org.openstreetmap.josm.data.validation.TestError; 32 32 import org.openstreetmap.josm.gui.progress.ProgressMonitor; 33 import org.openstreetmap.josm.gui.tagging.presets.Item; 34 import org.openstreetmap.josm.gui.tagging.presets.KeyedItem; 35 import org.openstreetmap.josm.gui.tagging.presets.Role; 33 36 import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset; 34 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem;35 37 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetListener; 36 38 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType; 39 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetUtils; 37 40 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets; 38 import org.openstreetmap.josm.gui.tagging.presets.items.KeyedItem;39 import org.openstreetmap.josm.gui.tagging.presets.items.Roles;40 import org.openstreetmap.josm.gui.tagging.presets.items.Roles.Role;41 import org.openstreetmap.josm.tools.SubclassFilteredCollection;42 41 import org.openstreetmap.josm.tools.Utils; 43 42 44 43 /** … … 106 105 return; 107 106 } 108 107 for (TaggingPreset p : TaggingPresets.getTaggingPresets()) { 109 if (p. data.stream().anyMatch(i -> i instanceof Roles)) {108 if (p.getAllItems().stream().anyMatch(i -> i instanceof Role)) { 110 109 relationpresets.add(p); 111 110 } 112 111 } … … 175 174 return map; 176 175 } 177 176 178 // return Roles grouped by key 179 private static Map<Role, String> buildAllRoles(Relation n) { 177 /** 178 * Returns all roles with preset name 179 * 180 * @param rel the relation 181 * @return all roles in all matching presets 182 */ 183 private static Map<Role, String> buildAllRoles(Relation rel) { 180 184 Map<Role, String> allroles = new LinkedHashMap<>(); 181 185 182 186 for (TaggingPreset p : relationpresets) { 183 final boolean matches = TaggingPresetItem.matches(Utils.filteredCollection(p.data, KeyedItem.class), n.getKeys());184 final SubclassFilteredCollection<TaggingPresetItem, Roles> roles = Utils.filteredCollection(p.data, Roles.class);185 if (matches && !roles.isEmpty()) {186 for (Role role: roles.iterator().next().roles) {187 allroles.put(role, p. name);187 List<Item> items = p.getAllItems(); 188 final boolean matches = TaggingPresetUtils.matches(Utils.filteredCollection(items, KeyedItem.class), rel.getKeys()); 189 if (matches) { 190 for (Role role: p.getAllRoles()) { 191 allroles.put(role, p.getBaseName()); 188 192 } 189 193 } 190 194 } … … 191 195 return allroles; 192 196 } 193 197 194 private static boolean checkMemberType(Role r, RelationMember member) {195 if (r.types != null) {196 switch (member.getDisplayType()) {197 case NODE:198 return r.types.contains(TaggingPresetType.NODE);199 case CLOSEDWAY:200 return r.types.contains(TaggingPresetType.CLOSEDWAY);201 case WAY:202 return r.types.contains(TaggingPresetType.WAY);203 case MULTIPOLYGON:204 return r.types.contains(TaggingPresetType.MULTIPOLYGON);205 case RELATION:206 return r.types.contains(TaggingPresetType.RELATION);207 default: // not matching type208 return false;209 }210 } else {211 // if no types specified, then test is passed212 return true;213 }214 }215 216 198 /** 217 199 * get all role definition for specified key and check, if some definition matches 218 200 * … … 226 208 String role = member.getRole(); 227 209 String name = null; 228 210 // Set of all accepted types in preset 229 Collection<TaggingPresetType> types = EnumSet.noneOf(TaggingPresetType.class);211 EnumSet<TaggingPresetType> types = EnumSet.noneOf(TaggingPresetType.class); 230 212 TestError possibleMatchError = null; 231 213 // iterate through all of the role definition within preset 232 214 // and look for any matching definition … … 236 218 continue; 237 219 } 238 220 name = e.getValue(); 239 types.addAll(r. types);240 if ( checkMemberType(r, member)) {221 types.addAll(r.getTypes()); 222 if (r.appliesTo(member.getDisplayType())) { 241 223 // member type accepted by role definition 242 if (r. memberExpression== null) {224 if (r.getMemberExpression() == null) { 243 225 // no member expression - so all requirements met 244 226 return true; 245 227 } else { … … 251 233 return true; 252 234 } else { 253 235 // verify expression 254 if (r. memberExpression.match(primitive)) {236 if (r.getMemberExpression().match(primitive)) { 255 237 return true; 256 238 } else { 257 239 // possible match error … … 261 243 possibleMatchError = TestError.builder(this, Severity.WARNING, WRONG_ROLE) 262 244 .message(ROLE_VERIF_PROBLEM_MSG, 263 245 marktr("Role of relation member does not match template expression ''{0}'' in preset {1}"), 264 r. memberExpression, name)246 r.getMemberExpression(), name) 265 247 .primitives(member.getMember().isUsable() ? member.getMember() : n) 266 248 .build(); 267 249 } … … 268 250 } 269 251 } 270 252 } else if (OsmPrimitiveType.RELATION == member.getType() && !member.getMember().isUsable() 271 && r. types.contains(TaggingPresetType.MULTIPOLYGON)) {253 && r.appliesTo(TaggingPresetType.MULTIPOLYGON)) { 272 254 // if relation is incomplete we cannot verify if it's a multipolygon - so we just skip it 273 255 return true; 274 256 } … … 275 257 } 276 258 277 259 if (name == null) { 278 return true;260 return true; 279 261 } else if (possibleMatchError != null) { 280 262 // if any error found, then assume that member type was correct 281 263 // and complain about not matching the memberExpression … … 318 300 319 301 // verify role counts based on whole role sets 320 302 for (Role r: allroles.keySet()) { 321 String keyname = r. key;303 String keyname = r.getKey(); 322 304 if (keyname.isEmpty()) { 323 305 keyname = tr("<empty>"); 324 306 } 325 checkRoleCounts(n, r, keyname, map.get(r. key));307 checkRoleCounts(n, r, keyname, map.get(r.getKey())); 326 308 } 327 309 if ("network".equals(n.get("type")) && !"bicycle".equals(n.get("route"))) { 328 310 return; … … 331 313 for (String key : map.keySet()) { 332 314 if (allroles.keySet().stream().noneMatch(role -> role.isRole(key))) { 333 315 String templates = allroles.keySet().stream() 334 .map(r -> r. key)316 .map(r -> r.getKey()) 335 317 .map(r -> Utils.isEmpty(r) ? tr("<empty>") : r) 336 318 .distinct() 337 319 .collect(Collectors.joining("/")); -
src/org/openstreetmap/josm/data/validation/tests/TagChecker.java
55 55 import org.openstreetmap.josm.data.validation.TestError; 56 56 import org.openstreetmap.josm.data.validation.util.Entities; 57 57 import org.openstreetmap.josm.gui.progress.ProgressMonitor; 58 import org.openstreetmap.josm.gui.tagging.presets.Item; 59 import org.openstreetmap.josm.gui.tagging.presets.KeyedItem; 58 60 import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset; 59 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem;60 61 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetListener; 61 62 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType; 63 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetUtils; 62 64 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets; 63 import org.openstreetmap.josm.gui.tagging.presets.items.Check;64 import org.openstreetmap.josm.gui.tagging.presets.items.CheckGroup;65 import org.openstreetmap.josm.gui.tagging.presets.items.KeyedItem;66 65 import org.openstreetmap.josm.gui.widgets.EditableList; 67 66 import org.openstreetmap.josm.io.CachedFile; 68 67 import org.openstreetmap.josm.spi.preferences.Config; … … 90 89 private static volatile HashSet<String> additionalPresetsValueData; 91 90 /** often used tags which are not in presets */ 92 91 private static final MultiMap<String, String> oftenUsedTags = new MultiMap<>(); 93 private static final Map<TaggingPreset, List< TaggingPresetItem>> presetIndex = new LinkedHashMap<>();92 private static final Map<TaggingPreset, List<Item>> presetIndex = new LinkedHashMap<>(); 94 93 95 94 private static final Pattern UNWANTED_NON_PRINTING_CONTROL_CHARACTERS = Pattern.compile( 96 95 "[\\x00-\\x09\\x0B\\x0C\\x0E-\\x1F\\x7F\\u200e-\\u200f\\u202a-\\u202e]"); … … 388 387 if (!presets.isEmpty()) { 389 388 initAdditionalPresetsValueData(); 390 389 for (TaggingPreset p : presets) { 391 List< TaggingPresetItem> minData = new ArrayList<>();392 for ( TaggingPresetItem i : p.data) {390 List<Item> minData = new ArrayList<>(); 391 for (Item i : p.getAllItems()) { 393 392 if (i instanceof KeyedItem) { 394 if (!"none".equals(((KeyedItem) i). match))393 if (!"none".equals(((KeyedItem) i).getMatchType())) 395 394 minData.add(i); 396 395 addPresetValue((KeyedItem) i); 397 } else if (i instanceof CheckGroup) {398 for (Check c : ((CheckGroup) i).checks) {399 addPresetValue(c);400 }401 396 } 402 397 } 403 398 if (!minData.isEmpty()) { … … 416 411 } 417 412 418 413 private static void addPresetValue(KeyedItem ky) { 419 if (ky. key!= null && ky.getValues() != null) {420 addToKeyDictionary(ky. key);414 if (ky.getKey() != null && ky.getValues() != null) { 415 addToKeyDictionary(ky.getKey()); 421 416 } 422 417 } 423 418 … … 660 655 EnumSet<TaggingPresetType> presetTypes = EnumSet.of(presetType); 661 656 662 657 Collection<TaggingPreset> matchingPresets = presetIndex.entrySet().stream() 663 .filter(e -> TaggingPreset Item.matches(e.getValue(), tags))658 .filter(e -> TaggingPresetUtils.matches(e.getValue(), tags)) 664 659 .map(Entry::getKey) 665 660 .collect(Collectors.toCollection(LinkedHashSet::new)); 666 661 Collection<TaggingPreset> matchingPresetsOK = matchingPresets.stream().filter( … … 670 665 671 666 for (TaggingPreset tp : matchingPresetsKO) { 672 667 // Potential error, unless matching tags are all known by a supported preset 673 Map<String, String> matchingTags = tp. data.stream()668 Map<String, String> matchingTags = tp.getAllItems().stream() 674 669 .filter(i -> Boolean.TRUE.equals(i.matches(tags))) 675 .filter(i -> i instanceof KeyedItem).map(i -> ((KeyedItem) i). key)670 .filter(i -> i instanceof KeyedItem).map(i -> ((KeyedItem) i).getKey()) 676 671 .collect(Collectors.toMap(k -> k, tags::get)); 677 672 if (matchingPresetsOK.stream().noneMatch( 678 673 tp2 -> matchingTags.entrySet().stream().allMatch( 679 e -> tp2. data.stream().anyMatch(680 i -> i instanceof KeyedItem && ((KeyedItem) i). key.equals(e.getKey()))))) {674 e -> tp2.getAllItems().stream().anyMatch( 675 i -> i instanceof KeyedItem && ((KeyedItem) i).getKey().equals(e.getKey()))))) { 681 676 errors.add(TestError.builder(this, Severity.OTHER, INVALID_PRESETS_TYPE) 682 677 .message(tr("Object type not in preset"), 683 678 marktr("Object type {0} is not supported by tagging preset: {1}"), -
src/org/openstreetmap/josm/gui/dialogs/properties/PresetListPanel.java
11 11 import javax.swing.JLabel; 12 12 import javax.swing.JPanel; 13 13 14 import org.openstreetmap.josm.data.osm.DataSet;15 import org.openstreetmap.josm.data.osm.OsmPrimitive;16 14 import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset; 15 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetDialog; 17 16 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetHandler; 18 17 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetLabel; 19 18 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType; … … 26 25 public class PresetListPanel extends JPanel { 27 26 28 27 static final class LabelMouseAdapter extends MouseAdapter { 29 private final TaggingPreset t;30 private final TaggingPresetHandler presetHandler;28 private final TaggingPreset preset; 29 private final TaggingPresetHandler handler; 31 30 32 LabelMouseAdapter(TaggingPreset t, TaggingPresetHandler presetHandler) {33 this. t =t;34 this. presetHandler = presetHandler;31 LabelMouseAdapter(TaggingPreset preset, TaggingPresetHandler handler) { 32 this.preset = preset; 33 this.handler = handler; 35 34 } 36 35 37 36 @Override 38 37 public void mouseClicked(MouseEvent e) { 39 Collection<OsmPrimitive> selection = t.createSelection(presetHandler.getSelection()); 40 if (selection.isEmpty()) 41 return; 42 int answer = t.showDialog(selection, false); 43 DataSet ds = selection.iterator().next().getDataSet(); 44 boolean locked = ds != null && ds.isLocked(); 45 46 if (answer == TaggingPreset.DIALOG_ANSWER_APPLY && !locked) { 47 presetHandler.updateTags(t.getChangedTags()); 48 } 38 TaggingPresetDialog.showAndApply(preset, handler, false); 49 39 } 50 40 } 51 41 -
src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesDialog.java
97 97 import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent; 98 98 import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener; 99 99 import org.openstreetmap.josm.gui.layer.OsmDataLayer; 100 import org.openstreetmap.josm.gui.tagging.presets. TaggingPreset;100 import org.openstreetmap.josm.gui.tagging.presets.DataSetTaggingPresetHandler; 101 101 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetHandler; 102 102 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetListener; 103 103 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType; … … 980 980 static final class TaggingPresetCommandHandler implements TaggingPresetHandler { 981 981 @Override 982 982 public void updateTags(List<Tag> tags) { 983 Command command = TaggingPreset.createCommand(getSelection(), tags);983 Command command = DataSetTaggingPresetHandler.createCommand(getPrimitives(), tags); 984 984 if (command != null) { 985 985 UndoRedoHandler.getInstance().add(command); 986 986 } … … 987 987 } 988 988 989 989 @Override 990 public Collection<OsmPrimitive> get Selection() {990 public Collection<OsmPrimitive> getPrimitives() { 991 991 return OsmDataManager.getInstance().getInProgressSelection(); 992 992 } 993 993 } -
src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java
10 10 import java.awt.FlowLayout; 11 11 import java.awt.GridBagConstraints; 12 12 import java.awt.GridBagLayout; 13 import java.awt.Insets; 13 14 import java.awt.Window; 14 15 import java.awt.datatransfer.Clipboard; 15 16 import java.awt.datatransfer.FlavorListener; 16 17 import java.awt.event.ActionEvent; 17 import java.awt.event.FocusAdapter;18 import java.awt.event.FocusEvent;19 18 import java.awt.event.InputEvent; 20 19 import java.awt.event.KeyEvent; 21 20 import java.awt.event.MouseAdapter; … … 27 26 import java.util.Collection; 28 27 import java.util.Collections; 29 28 import java.util.EnumSet; 29 import java.util.HashMap; 30 30 import java.util.List; 31 import java.util.Map; 31 32 import java.util.Set; 32 33 import java.util.stream.Collectors; 33 34 … … 35 36 import javax.swing.BorderFactory; 36 37 import javax.swing.InputMap; 37 38 import javax.swing.JButton; 39 import javax.swing.JCheckBox; 38 40 import javax.swing.JComponent; 39 41 import javax.swing.JLabel; 40 42 import javax.swing.JMenuItem; … … 45 47 import javax.swing.JSplitPane; 46 48 import javax.swing.JTabbedPane; 47 49 import javax.swing.JTable; 50 import javax.swing.JTextField; 48 51 import javax.swing.JToolBar; 49 52 import javax.swing.KeyStroke; 50 53 … … 53 56 import org.openstreetmap.josm.command.Command; 54 57 import org.openstreetmap.josm.data.UndoRedoHandler; 55 58 import org.openstreetmap.josm.data.UndoRedoHandler.CommandQueueListener; 59 import org.openstreetmap.josm.data.osm.DataSet; 56 60 import org.openstreetmap.josm.data.osm.DefaultNameFormatter; 57 61 import org.openstreetmap.josm.data.osm.OsmPrimitive; 58 62 import org.openstreetmap.josm.data.osm.Relation; 59 63 import org.openstreetmap.josm.data.osm.RelationMember; 60 64 import org.openstreetmap.josm.data.osm.Tag; 65 import org.openstreetmap.josm.data.tagging.ac.AutoCompItemCellRenderer; 66 import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem; 67 import org.openstreetmap.josm.data.tagging.ac.AutoCompletionPriority; 61 68 import org.openstreetmap.josm.data.validation.tests.RelationChecker; 62 69 import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil; 63 70 import org.openstreetmap.josm.gui.MainApplication; … … 98 105 import org.openstreetmap.josm.gui.layer.OsmDataLayer; 99 106 import org.openstreetmap.josm.gui.tagging.TagEditorModel; 100 107 import org.openstreetmap.josm.gui.tagging.TagEditorPanel; 101 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField; 102 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList; 108 import org.openstreetmap.josm.gui.tagging.TagTable; 109 import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBox; 110 import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel; 103 111 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager; 112 import org.openstreetmap.josm.gui.tagging.ac.DefaultAutoCompListener; 104 113 import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset; 105 114 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetHandler; 106 115 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType; … … 108 117 import org.openstreetmap.josm.gui.util.WindowGeometry; 109 118 import org.openstreetmap.josm.spi.preferences.Config; 110 119 import org.openstreetmap.josm.tools.CheckParameterUtil; 120 import org.openstreetmap.josm.tools.GBC; 111 121 import org.openstreetmap.josm.tools.InputMapUtils; 112 122 import org.openstreetmap.josm.tools.Logging; 113 123 import org.openstreetmap.josm.tools.Shortcut; … … 118 128 * @since 343 119 129 */ 120 130 public class GenericRelationEditor extends RelationEditor implements CommandQueueListener { 131 private static final String PREF_LASTROLE = "relation.editor.generic.lastrole"; 132 private static final String PREF_USE_ROLE_FILTER = "relation.editor.use_role_filter"; 133 121 134 /** the tag table and its model */ 122 135 private final TagEditorPanel tagEditorPanel; 123 136 private final ReferringRelationsBrowser referrerBrowser; … … 131 144 private final SelectionTable selectionTable; 132 145 private final SelectionTableModel selectionTableModel; 133 146 134 private final AutoCompletingTextField tfRole; 147 private final AutoCompletionManager manager; 148 private final AutoCompComboBox<AutoCompletionItem> cbRole; 135 149 136 150 /** 137 151 * the menu item in the windows menu. Required to properly hide on dialog close. … … 172 186 173 187 private Component selectedTabPane; 174 188 private JTabbedPane tabbedPane; 189 private JCheckBox btnFilter = new JCheckBox(tr("Filter")); 175 190 176 191 /** 177 192 * Creates a new relation editor for the given relation. The relation will be saved if the user … … 189 204 setRememberWindowGeometry(getClass().getName() + ".geometry", 190 205 WindowGeometry.centerInWindow(MainApplication.getMainFrame(), new Dimension(700, 650))); 191 206 207 /** 208 * The data handler we pass to any preset dialog opened from here. 209 * <p> 210 * This handler creates a new relation and a new dataset because the validator assumes that 211 * the primitive to validate has a dataset. On read it copies the current state of the tag 212 * editor to the relation. On write the data goes to the tag table. 213 */ 192 214 final TaggingPresetHandler presetHandler = new TaggingPresetHandler() { 215 Relation relation = new Relation(); 216 DataSet ds = new DataSet(); 193 217 218 /* anon constructor */ { 219 ds.addPrimitive(relation); 220 } 221 194 222 @Override 195 223 public void updateTags(List<Tag> tags) { 196 224 tagEditorPanel.getModel().updateTags(tags); … … 197 225 } 198 226 199 227 @Override 200 public Collection<OsmPrimitive> getSelection() { 201 Relation relation = new Relation(); 228 public Collection<OsmPrimitive> getPrimitives() { 229 relation.setKeys(null); 230 // copy the current state of the tag table 202 231 tagEditorPanel.getModel().applyToPrimitive(relation); 203 232 return Collections.<OsmPrimitive>singletonList(relation); 204 233 } … … 212 241 selectionTableModel.register(); 213 242 referrerModel = new ReferringRelationsBrowserModel(relation); 214 243 244 manager = AutoCompletionManager.of(this.getLayer().data); 245 215 246 tagEditorPanel = new TagEditorPanel(relation, presetHandler); 247 TagTable tagTable = tagEditorPanel.getTable(); 216 248 populateModels(relation); 217 249 tagEditorPanel.getModel().ensureOneTag(); 218 250 251 // setting up the tag table 252 AutoCompComboBox<AutoCompletionItem> keyEditor = new AutoCompComboBox<>(); 253 AutoCompComboBox<AutoCompletionItem> valueEditor = new AutoCompComboBox<>(); 254 KeyAutoCompManager keyAutoCompManager = new KeyAutoCompManager(); 255 ValueAutoCompManager valueAutoCompManager = new ValueAutoCompManager(); 256 keyEditor.getEditorComponent().setMaxTextLength(256); 257 keyEditor.getEditorComponent().setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1)); 258 keyEditor.getEditorComponent().enableUndoRedo(false); 259 keyEditor.getEditorComponent().addAutoCompListener(keyAutoCompManager); 260 keyEditor.addPopupMenuListener(keyAutoCompManager); 261 keyEditor.putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE); 262 keyEditor.setRenderer(new AutoCompItemCellRenderer(keyEditor, keyEditor.getRenderer(), null)); 263 264 valueEditor.getEditorComponent().setMaxTextLength(-1); 265 valueEditor.getEditorComponent().setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1)); 266 valueEditor.getEditorComponent().enableUndoRedo(false); 267 valueEditor.getEditorComponent().addAutoCompListener(valueAutoCompManager); 268 valueEditor.addPopupMenuListener(valueAutoCompManager); 269 valueEditor.putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE); 270 valueEditor.setRenderer(new AutoCompItemCellRenderer(valueEditor, valueEditor.getRenderer(), null)); 271 272 tagTable.setRowHeight(keyEditor.getEditorComponent().getPreferredSize().height); 273 tagTable.setKeyEditor(keyEditor); 274 tagTable.setValueEditor(valueEditor); 275 219 276 // setting up the member table 220 memberTable = new MemberTable(getLayer(), getRelation(), memberTableModel); 277 AutoCompComboBox<AutoCompletionItem> cbRoleEditor = new AutoCompComboBox<>(); 278 RoleAutoCompManager roleAutoCompManager = new RoleAutoCompManager(); 279 cbRoleEditor.getEditorComponent().addAutoCompListener(roleAutoCompManager); 280 cbRoleEditor.addPopupMenuListener(roleAutoCompManager); 281 cbRoleEditor.getEditorComponent().enableUndoRedo(false); 282 Insets insets = cbRoleEditor.getEditorComponent().getInsets(); 283 cbRoleEditor.getEditorComponent().setBorder(BorderFactory.createEmptyBorder(0, insets.left, 0, insets.right)); 284 cbRoleEditor.setToolTipText(tr("Select a role for this relation member")); 285 cbRoleEditor.setRenderer(new AutoCompItemCellRenderer(cbRoleEditor, cbRoleEditor.getRenderer(), null)); 286 287 int height = cbRoleEditor.getEditorComponent().getPreferredSize().height; 288 memberTable = new MemberTable(getLayer(), cbRoleEditor, memberTableModel); 221 289 memberTable.addMouseListener(new MemberTableDblClickAdapter()); 290 memberTable.setRowHeight(height); 222 291 memberTableModel.addMemberModelListener(memberTable); 223 292 224 MemberRoleCellEditor ce = (MemberRoleCellEditor) memberTable.getColumnModel().getColumn(0).getCellEditor();225 293 selectionTable = new SelectionTable(selectionTableModel, memberTableModel); 226 selectionTable.setRowHeight( ce.getEditor().getPreferredSize().height);294 selectionTable.setRowHeight(height); 227 295 228 296 LeftButtonToolbar leftButtonToolbar = new LeftButtonToolbar(new RelationEditorActionAccess()); 229 tfRole = buildRoleTextField(this); 297 cbRole = new AutoCompComboBox<>(); 298 cbRole.getEditorComponent().addAutoCompListener(roleAutoCompManager); 299 cbRole.addPopupMenuListener(roleAutoCompManager); 300 cbRole.setText(Config.getPref().get(PREF_LASTROLE, "")); 301 cbRole.setToolTipText(tr("Select a role")); 302 cbRole.setRenderer(new AutoCompItemCellRenderer(cbRole, cbRole.getRenderer(), null)); 230 303 231 304 JSplitPane pane = buildSplitPane( 232 305 buildTagEditorPanel(tagEditorPanel), 233 buildMemberEditorPanel(leftButtonToolbar , new RelationEditorActionAccess()),306 buildMemberEditorPanel(leftButtonToolbar), 234 307 this); 235 308 pane.setPreferredSize(new Dimension(100, 100)); 236 309 … … 310 383 @Override 311 384 public void actionPerformed(ActionEvent e) { 312 385 super.actionPerformed(e); 313 tfRole.requestFocusInWindow();386 cbRole.requestFocusInWindow(); 314 387 } 315 388 }, "PASTE_MEMBERS", key, getRootPane(), memberTable, selectionTable); 316 389 } … … 446 519 } 447 520 448 521 /** 449 * builds the role text field450 * @param re relation editor451 * @return the role text field452 */453 protected static AutoCompletingTextField buildRoleTextField(final IRelationEditor re) {454 final AutoCompletingTextField tfRole = new AutoCompletingTextField(10);455 tfRole.setToolTipText(tr("Enter a role and apply it to the selected relation members"));456 tfRole.addFocusListener(new FocusAdapter() {457 @Override458 public void focusGained(FocusEvent e) {459 tfRole.selectAll();460 }461 });462 tfRole.setAutoCompletionList(new AutoCompletionList());463 tfRole.addFocusListener(464 new FocusAdapter() {465 @Override466 public void focusGained(FocusEvent e) {467 AutoCompletionList list = tfRole.getAutoCompletionList();468 if (list != null) {469 list.clear();470 AutoCompletionManager.of(re.getLayer().data).populateWithMemberRoles(list, re.getRelation());471 }472 }473 }474 );475 tfRole.setText(Config.getPref().get("relation.editor.generic.lastrole", ""));476 return tfRole;477 }478 479 /**480 522 * builds the panel for the relation member editor 481 523 * @param leftButtonToolbar left button toolbar 482 * @param editorAccess The relation editor483 524 * 484 525 * @return the panel for the relation member editor 485 526 */ 486 protected static JPanel buildMemberEditorPanel( 487 LeftButtonToolbar leftButtonToolbar, IRelationEditorActionAccess editorAccess) { 527 protected JPanel buildMemberEditorPanel(LeftButtonToolbar leftButtonToolbar) { 488 528 final JPanel pnl = new JPanel(new GridBagLayout()); 489 final JScrollPane scrollPane = new JScrollPane( editorAccess.getMemberTable());529 final JScrollPane scrollPane = new JScrollPane(memberTable); 490 530 491 531 GridBagConstraints gc = new GridBagConstraints(); 492 532 gc.gridx = 0; … … 518 558 pnl.add(scrollPane, gc); 519 559 520 560 // --- role editing 521 JPanel p3 = new JPanel(new FlowLayout(FlowLayout.LEFT)); 522 p3.add(new JLabel(tr("Apply Role:"))); 523 p3.add(editorAccess.getTextFieldRole()); 524 SetRoleAction setRoleAction = new SetRoleAction(editorAccess); 525 editorAccess.getMemberTableModel().getSelectionModel().addListSelectionListener(setRoleAction); 526 editorAccess.getTextFieldRole().getDocument().addDocumentListener(setRoleAction); 527 editorAccess.getTextFieldRole().addActionListener(setRoleAction); 528 editorAccess.getMemberTableModel().getSelectionModel().addListSelectionListener( 529 e -> editorAccess.getTextFieldRole().setEnabled(editorAccess.getMemberTable().getSelectedRowCount() > 0) 561 JPanel p3 = new JPanel(new GridBagLayout()); 562 GBC gbc = GBC.std().fill(GridBagConstraints.NONE); 563 JLabel lbl = new JLabel(tr("Role:")); 564 p3.add(lbl, gbc); 565 566 p3.add(cbRole, gbc.insets(3, 3, 0, 3).fill(GridBagConstraints.HORIZONTAL)); 567 568 SetRoleAction setRoleAction = new SetRoleAction(new RelationEditorActionAccess()); 569 memberTableModel.getSelectionModel().addListSelectionListener(setRoleAction); 570 cbRole.getEditorComponent().getDocument().addDocumentListener(setRoleAction); 571 cbRole.getEditorComponent().addActionListener(setRoleAction); 572 memberTableModel.getSelectionModel().addListSelectionListener( 573 e -> cbRole.setEnabled(memberTable.getSelectedRowCount() > 0) 530 574 ); 531 editorAccess.getTextFieldRole().setEnabled(editorAccess.getMemberTable().getSelectedRowCount() > 0); 575 cbRole.setEnabled(memberTable.getSelectedRowCount() > 0); 576 532 577 JButton btnApply = new JButton(setRoleAction); 533 btnApply.setPreferredSize(new Dimension(20, 20)); 578 int height = cbRole.getPreferredSize().height; 579 btnApply.setPreferredSize(new Dimension(height, height)); 534 580 btnApply.setText(""); 535 p3.add(btnApply );581 p3.add(btnApply, gbc.weight(0, 0).fill(GridBagConstraints.NONE)); 536 582 583 btnFilter.setToolTipText(tr("Filter suggestions based on context")); 584 btnFilter.setSelected(Config.getPref().getBoolean(PREF_USE_ROLE_FILTER, false)); 585 p3.add(btnFilter, gbc.span(GridBagConstraints.REMAINDER)); 586 587 // 588 537 589 gc.gridx = 1; 538 590 gc.gridy = 2; 539 591 gc.fill = GridBagConstraints.HORIZONTAL; … … 562 614 gc.anchor = GridBagConstraints.NORTHWEST; 563 615 gc.weightx = 0.0; 564 616 gc.weighty = 1.0; 565 pnl2.add(new ScrollViewport(buildSelectionControlButtonToolbar( editorAccess),617 pnl2.add(new ScrollViewport(buildSelectionControlButtonToolbar(new RelationEditorActionAccess()), 566 618 ScrollViewport.VERTICAL_DIRECTION), gc); 567 619 568 620 gc.gridx = 1; … … 570 622 gc.weightx = 1.0; 571 623 gc.weighty = 1.0; 572 624 gc.fill = GridBagConstraints.BOTH; 573 pnl2.add(buildSelectionTablePanel( editorAccess.getSelectionTable()), gc);625 pnl2.add(buildSelectionTablePanel(selectionTable), gc); 574 626 575 627 final JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); 576 628 splitPane.setLeftComponent(pnl); 577 629 splitPane.setRightComponent(pnl2); 578 630 splitPane.setOneTouchExpandable(false); 579 if (editorAccess.getEditor() instanceof Window) { 580 ((Window) editorAccess.getEditor()).addWindowListener(new WindowAdapter() { 581 @Override 582 public void windowOpened(WindowEvent e) { 583 // has to be called when the window is visible, otherwise no effect 584 splitPane.setDividerLocation(0.6); 585 } 586 }); 587 } 631 addWindowListener(new WindowAdapter() { 632 @Override 633 public void windowOpened(WindowEvent e) { 634 // has to be called when the window is visible, otherwise no effect 635 splitPane.setDividerLocation(0.6); 636 } 637 }); 588 638 589 639 JPanel pnl3 = new JPanel(new BorderLayout()); 590 640 pnl3.add(splitPane, BorderLayout.CENTER); … … 739 789 if (isVisible() == visible) { 740 790 return; 741 791 } 742 if (visible) {743 tagEditorPanel.initAutoCompletion(getLayer());744 }745 792 super.setVisible(visible); 746 793 Clipboard clipboard = ClipboardUtils.getClipboard(); 747 794 if (visible) { … … 754 801 clipboard.addFlavorListener(listener); 755 802 } 756 803 } else { 804 Config.getPref().put(PREF_LASTROLE, cbRole.getText()); 805 Config.getPref().putBoolean(PREF_USE_ROLE_FILTER, btnFilter.isSelected()); 806 757 807 // make sure all registered listeners are unregistered 758 808 // 759 809 memberTable.stopHighlighting(); … … 1039 1089 } 1040 1090 1041 1091 @Override 1042 public AutoCompletingTextField getTextFieldRole() {1043 return tfRole;1092 public JTextField getTextFieldRole() { 1093 return cbRole.getEditorComponent(); 1044 1094 } 1045 1046 1095 } 1047 1096 1048 1097 @Override … … 1054 1103 applyAction.updateEnabledState(); 1055 1104 } 1056 1105 } 1106 1107 private class KeyAutoCompManager extends DefaultAutoCompListener<AutoCompletionItem> { 1108 @Override 1109 protected void updateAutoCompModel(AutoCompComboBoxModel<AutoCompletionItem> model) { 1110 Map<String, AutoCompletionPriority> map; 1111 Map<String, String> keys = tagEditorPanel.getModel().getTags(); 1112 Map<String, String> matchKeys = btnFilter.isSelected() ? keys : null; 1113 1114 map = AutoCompletionManager.merge( 1115 manager.getKeysForRelation(matchKeys), 1116 manager.getPresetKeys(EnumSet.of(TaggingPresetType.RELATION), matchKeys) 1117 ); 1118 1119 model.replaceAllElements(map.entrySet().stream().filter(e -> !keys.containsKey(e.getKey())) 1120 .map(e -> new AutoCompletionItem(e.getKey(), e.getValue())) 1121 .sorted(AutoCompletionManager.ALPHABETIC_COMPARATOR).collect(Collectors.toList())); 1122 } 1123 } 1124 1125 private class ValueAutoCompManager extends DefaultAutoCompListener<AutoCompletionItem> { 1126 @Override 1127 protected void updateAutoCompModel(AutoCompComboBoxModel<AutoCompletionItem> model) { 1128 Map<String, AutoCompletionPriority> map; 1129 Map<String, String> keys = btnFilter.isSelected() ? tagEditorPanel.getModel().getTags() : null; 1130 String key = (String) tagEditorPanel.getModel().getValueAt(tagEditorPanel.getTable().getEditingRow(), 0); 1131 1132 map = AutoCompletionManager.merge( 1133 manager.getValuesForRelation(keys, key), 1134 manager.getPresetValues(EnumSet.of(TaggingPresetType.RELATION), keys, key) 1135 ); 1136 1137 model.replaceAllElements(map.entrySet().stream() 1138 .map(e -> new AutoCompletionItem(e.getKey(), e.getValue())) 1139 .sorted(AutoCompletionManager.ALPHABETIC_COMPARATOR).collect(Collectors.toList())); 1140 } 1141 } 1142 1143 /** 1144 * Returns the roles currently edited in the members table. 1145 * @param types the preset types to include, (node / way / relation ...) or null to include all types 1146 * @return the roles currently edited in the members table. 1147 */ 1148 private Map<String, AutoCompletionPriority> getCurrentRoles(Collection<TaggingPresetType> types) { 1149 Map<String, AutoCompletionPriority> map = new HashMap<>(); 1150 for (int i = 0; i < memberTableModel.getRowCount(); ++i) { 1151 RelationMember member = memberTableModel.getValue(i); 1152 if (types == null || types.contains(TaggingPresetType.forPrimitiveType(member.getDisplayType()))) { 1153 map.merge(member.getRole(), AutoCompletionPriority.IS_IN_SELECTION, AutoCompletionPriority::mergeWith); 1154 } 1155 } 1156 return map; 1157 } 1158 1159 private class RoleAutoCompManager extends DefaultAutoCompListener<AutoCompletionItem> { 1160 @Override 1161 protected void updateAutoCompModel(AutoCompComboBoxModel<AutoCompletionItem> model) { 1162 Map<String, AutoCompletionPriority> map; 1163 Map<String, String> keys = btnFilter.isSelected() ? tagEditorPanel.getModel().getTags() : null; 1164 1165 EnumSet<TaggingPresetType> selectedTypes = EnumSet.noneOf(TaggingPresetType.class); 1166 for (RelationMember member : memberTableModel.getSelectedMembers()) { 1167 selectedTypes.add(TaggingPresetType.forPrimitiveType(member.getDisplayType())); 1168 } 1169 1170 map = AutoCompletionManager.merge( 1171 manager.getRolesForRelation(keys, selectedTypes), 1172 manager.getPresetRoles(keys, selectedTypes), 1173 getCurrentRoles(selectedTypes) 1174 ); 1175 1176 // turn into AutoCompletionItems 1177 model.replaceAllElements(map.entrySet().stream() 1178 .map(e -> new AutoCompletionItem(e.getKey(), e.getValue())) 1179 .sorted(AutoCompletionManager.ALPHABETIC_COMPARATOR).collect(Collectors.toList())); 1180 } 1181 } 1057 1182 } -
src/org/openstreetmap/josm/gui/dialogs/relation/MemberRoleCellEditor.java
1 // License: GPL. For details, see LICENSE file.2 package org.openstreetmap.josm.gui.dialogs.relation;3 4 import java.awt.Component;5 6 import javax.swing.AbstractCellEditor;7 import javax.swing.BorderFactory;8 import javax.swing.CellEditor;9 import javax.swing.JTable;10 import javax.swing.table.TableCellEditor;11 12 import org.openstreetmap.josm.data.osm.Relation;13 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;14 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;15 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;16 17 /**18 * The {@link CellEditor} for the role cell in the table. Supports autocompletion.19 */20 public class MemberRoleCellEditor extends AbstractCellEditor implements TableCellEditor {21 private final AutoCompletingTextField editor;22 private final AutoCompletionManager autoCompletionManager;23 private final transient Relation relation;24 25 /** user input is matched against this list of auto completion items */26 private final AutoCompletionList autoCompletionList;27 28 /**29 * Constructs a new {@code MemberRoleCellEditor}.30 * @param autoCompletionManager the auto completion manager. Must not be null31 * @param relation the relation. Can be null32 * @since 1367533 */34 public MemberRoleCellEditor(AutoCompletionManager autoCompletionManager, Relation relation) {35 this.autoCompletionManager = autoCompletionManager;36 this.relation = relation;37 editor = new AutoCompletingTextField(0, false);38 editor.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));39 autoCompletionList = new AutoCompletionList();40 editor.setAutoCompletionList(autoCompletionList);41 }42 43 @Override44 public Component getTableCellEditorComponent(JTable table,45 Object value, boolean isSelected, int row, int column) {46 47 String role = (String) value;48 editor.setText(role);49 autoCompletionList.clear();50 autoCompletionManager.populateWithMemberRoles(autoCompletionList, relation);51 return editor;52 }53 54 @Override55 public Object getCellEditorValue() {56 return editor.getText();57 }58 59 /**60 * Returns the edit field for this cell editor.61 * @return the edit field for this cell editor62 */63 public AutoCompletingTextField getEditor() {64 return editor;65 }66 } -
src/org/openstreetmap/josm/gui/dialogs/relation/MemberTable.java
Property changes on: src/org/openstreetmap/josm/gui/dialogs/relation/MemberRoleCellEditor.java ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property
20 20 import javax.swing.SwingUtilities; 21 21 import javax.swing.event.ListSelectionEvent; 22 22 import javax.swing.event.ListSelectionListener; 23 import javax.swing.table.TableCellEditor; 23 24 24 25 import org.openstreetmap.josm.actions.AbstractShowHistoryAction; 25 26 import org.openstreetmap.josm.actions.AutoScaleAction; … … 27 28 import org.openstreetmap.josm.actions.HistoryInfoAction; 28 29 import org.openstreetmap.josm.actions.ZoomToAction; 29 30 import org.openstreetmap.josm.data.osm.OsmPrimitive; 30 import org.openstreetmap.josm.data.osm.Relation;31 31 import org.openstreetmap.josm.data.osm.RelationMember; 32 32 import org.openstreetmap.josm.data.osm.Way; 33 33 import org.openstreetmap.josm.gui.MainApplication; … … 41 41 import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent; 42 42 import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener; 43 43 import org.openstreetmap.josm.gui.layer.OsmDataLayer; 44 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;45 44 import org.openstreetmap.josm.gui.util.HighlightHelper; 46 45 import org.openstreetmap.josm.gui.widgets.OsmPrimitivesTable; 47 46 import org.openstreetmap.josm.spi.preferences.Config; … … 60 59 * constructor for relation member table 61 60 * 62 61 * @param layer the data layer of the relation. Must not be null 63 * @param r elation the relation. Can be null62 * @param roleCellEditor the role editor combobox 64 63 * @param model the table model 65 64 */ 66 public MemberTable(OsmDataLayer layer, Relation relation, MemberTableModel model) {67 super(model, new MemberTableColumnModel( AutoCompletionManager.of(layer.data), relation), model.getSelectionModel());65 public MemberTable(OsmDataLayer layer, TableCellEditor roleCellEditor, MemberTableModel model) { 66 super(model, new MemberTableColumnModel(roleCellEditor), model.getSelectionModel()); 68 67 setLayer(layer); 69 68 model.addMemberModelListener(this); 70 69 71 MemberRoleCellEditor ce = (MemberRoleCellEditor) getColumnModel().getColumn(0).getCellEditor(); 72 setRowHeight(ce.getEditor().getPreferredSize().height); 70 setRowHeight(roleCellEditor.getTableCellEditorComponent(this, "", false, 0, 0).getPreferredSize().height); 73 71 setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); 74 72 setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); 75 73 putClientProperty("terminateEditOnFocusLost", Boolean.TRUE); … … 83 81 if (!GraphicsEnvironment.isHeadless()) { 84 82 setTransferHandler(new MemberTransferHandler()); 85 83 setFillsViewportHeight(true); // allow drop on empty table 86 if (!GraphicsEnvironment.isHeadless()) { 87 setDragEnabled(true); 88 } 84 setDragEnabled(true); 89 85 setDropMode(DropMode.INSERT_ROWS); 90 86 } 91 87 } -
src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableColumnModel.java
4 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 5 6 6 import javax.swing.table.DefaultTableColumnModel; 7 import javax.swing.table.TableCellEditor; 7 8 import javax.swing.table.TableColumn; 8 9 9 import org.openstreetmap.josm.data.osm.Relation;10 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;11 12 10 /** 13 11 * This is the column model for the {@link MemberTable} 14 12 */ … … 16 14 17 15 /** 18 16 * Constructs a new {@code MemberTableColumnModel}. 19 * @param autoCompletionManager the auto completion manager. Must not be null 20 * @param relation the relation. Can be null 17 * @param roleCellEditor the role editor combobox 21 18 * @since 13675 22 19 */ 23 public MemberTableColumnModel( AutoCompletionManager autoCompletionManager, Relation relation) {20 public MemberTableColumnModel(TableCellEditor roleCellEditor) { 24 21 TableColumn col; 25 22 26 23 // column 0 - the member role … … 29 26 col.setResizable(true); 30 27 col.setPreferredWidth(100); 31 28 col.setCellRenderer(new MemberTableRoleCellRenderer()); 32 col.setCellEditor( new MemberRoleCellEditor(autoCompletionManager, relation));29 col.setCellEditor(roleCellEditor); 33 30 addColumn(col); 34 31 35 32 // column 1 - the member -
src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableModel.java
433 433 RelationMember getRelationMemberForPrimitive(final OsmPrimitive primitive) { 434 434 final Collection<TaggingPreset> presets = TaggingPresets.getMatchingPresets( 435 435 EnumSet.of(relation != null ? TaggingPresetType.forPrimitive(relation) : TaggingPresetType.RELATION), 436 presetHandler.get Selection().iterator().next().getKeys(), false);436 presetHandler.getPrimitives().iterator().next().getKeys(), false); 437 437 Collection<String> potentialRoles = presets.stream() 438 438 .map(tp -> tp.suggestRoleForOsmPrimitive(primitive)) 439 439 .filter(Objects::nonNull) -
src/org/openstreetmap/josm/gui/dialogs/relation/actions/IRelationEditorActionAccess.java
2 2 package org.openstreetmap.josm.gui.dialogs.relation.actions; 3 3 4 4 import javax.swing.Action; 5 import javax.swing.JTextField; 5 6 6 7 import org.openstreetmap.josm.gui.dialogs.relation.IRelationEditor; 7 8 import org.openstreetmap.josm.gui.dialogs.relation.MemberTable; … … 9 10 import org.openstreetmap.josm.gui.dialogs.relation.SelectionTable; 10 11 import org.openstreetmap.josm.gui.dialogs.relation.SelectionTableModel; 11 12 import org.openstreetmap.josm.gui.tagging.TagEditorModel; 12 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;13 13 14 14 /** 15 15 * This interface provides access to the relation editor for actions. … … 69 69 * Get the text field that is used to edit the role. 70 70 * @return The role text field. 71 71 */ 72 AutoCompletingTextField getTextFieldRole();72 JTextField getTextFieldRole(); 73 73 74 74 /** 75 75 * Tells the member table editor to stop editing and accept any partially edited value as the value of the editor. -
src/org/openstreetmap/josm/gui/dialogs/relation/actions/SavingAction.java
8 8 import java.util.List; 9 9 10 10 import javax.swing.JOptionPane; 11 import javax.swing.JTextField; 11 12 import javax.swing.SwingUtilities; 12 13 13 14 import org.openstreetmap.josm.command.AddCommand; … … 27 28 import org.openstreetmap.josm.gui.dialogs.relation.RelationDialogManager; 28 29 import org.openstreetmap.josm.gui.dialogs.relation.RelationEditor; 29 30 import org.openstreetmap.josm.gui.tagging.TagEditorModel; 30 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;31 31 import org.openstreetmap.josm.tools.ImageProvider; 32 32 import org.openstreetmap.josm.tools.Utils; 33 33 … … 38 38 abstract class SavingAction extends AbstractRelationEditorAction { 39 39 private static final long serialVersionUID = 1L; 40 40 41 protected final AutoCompletingTextField tfRole;41 protected final JTextField tfRole; 42 42 43 43 protected SavingAction(IRelationEditorActionAccess editorAccess, IRelationEditorUpdateOn... updateOn) { 44 44 super(editorAccess, updateOn); -
src/org/openstreetmap/josm/gui/dialogs/relation/actions/SetRoleAction.java
7 7 import java.awt.event.ActionEvent; 8 8 9 9 import javax.swing.JOptionPane; 10 import javax.swing.JTextField; 10 11 import javax.swing.event.DocumentEvent; 11 12 import javax.swing.event.DocumentListener; 12 13 13 14 import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil; 14 15 import org.openstreetmap.josm.gui.MainApplication; 15 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;16 16 import org.openstreetmap.josm.tools.ImageProvider; 17 17 import org.openstreetmap.josm.tools.Utils; 18 18 … … 23 23 public class SetRoleAction extends AbstractRelationEditorAction implements DocumentListener { 24 24 private static final long serialVersionUID = 1L; 25 25 26 private final transient AutoCompletingTextField tfRole;26 private final transient JTextField tfRole; 27 27 28 28 /** 29 29 * Constructs a new {@code SetRoleAction}. … … 32 32 public SetRoleAction(IRelationEditorActionAccess editorAccess) { 33 33 super(editorAccess); 34 34 this.tfRole = editorAccess.getTextFieldRole(); 35 putValue(SHORT_DESCRIPTION, tr(" Sets a role forthe selected members"));35 putValue(SHORT_DESCRIPTION, tr("Apply the role to the selected members")); 36 36 new ImageProvider("apply").getResource().attachImageIcon(this); 37 37 putValue(NAME, tr("Apply Role")); 38 38 updateEnabledState(); -
src/org/openstreetmap/josm/gui/io/UploadDialog.java
18 18 import java.beans.PropertyChangeListener; 19 19 import java.lang.Character.UnicodeBlock; 20 20 import java.util.ArrayList; 21 import java.util. Collections;21 import java.util.Arrays; 22 22 import java.util.HashMap; 23 23 import java.util.List; 24 24 import java.util.Locale; … … 43 43 import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction; 44 44 import org.openstreetmap.josm.gui.help.HelpUtil; 45 45 import org.openstreetmap.josm.gui.tagging.TagEditorPanel; 46 import org.openstreetmap.josm.gui.tagging.TagTable; 47 import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBox; 48 import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel; 49 import org.openstreetmap.josm.gui.tagging.ac.DefaultAutoCompListener; 46 50 import org.openstreetmap.josm.gui.util.GuiHelper; 47 51 import org.openstreetmap.josm.gui.util.MultiLineFlowLayout; 48 52 import org.openstreetmap.josm.gui.util.WindowGeometry; … … 88 92 /** the model keeping the state of the changeset tags */ 89 93 private final transient UploadDialogModel model = new UploadDialogModel(); 90 94 91 private transient DataSet dataSet;92 93 95 /** 94 96 * Constructs a new {@code UploadDialog}. 95 97 */ … … 141 143 pnlTagEditor = new TagEditorPanel(model, null, Changeset.MAX_CHANGESET_TAG_LENGTH); 142 144 pnlTagEditorBorder.add(pnlTagEditor, BorderLayout.CENTER); 143 145 146 // setting up the tag table 147 TagTable tagTable = pnlTagEditor.getTable(); 148 AutoCompComboBox<String> keyEditor = new AutoCompComboBox<>(); 149 AutoCompComboBox<String> valueEditor = new AutoCompComboBox<>(); 150 KeyAutoCompManager keyAutoCompManager = new KeyAutoCompManager(); 151 ValueAutoCompManager valueAutoCompManager = new ValueAutoCompManager(); 152 keyEditor.getEditorComponent().setMaxTextLength(Changeset.MAX_CHANGESET_TAG_LENGTH); 153 keyEditor.getEditorComponent().setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1)); 154 keyEditor.getEditorComponent().enableUndoRedo(false); 155 keyEditor.getEditorComponent().addAutoCompListener(keyAutoCompManager); 156 keyEditor.addPopupMenuListener(keyAutoCompManager); 157 keyEditor.putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE); 158 159 valueEditor.getEditorComponent().setMaxTextLength(Changeset.MAX_CHANGESET_TAG_LENGTH); 160 valueEditor.getEditorComponent().setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1)); 161 valueEditor.getEditorComponent().enableUndoRedo(false); 162 valueEditor.getEditorComponent().addAutoCompListener(valueAutoCompManager); 163 valueEditor.addPopupMenuListener(valueAutoCompManager); 164 valueEditor.putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE); 165 166 tagTable.setRowHeight(keyEditor.getEditorComponent().getPreferredSize().height); 167 tagTable.setKeyEditor(keyEditor); 168 tagTable.setValueEditor(valueEditor); 169 144 170 pnlChangesetManagement = new ChangesetManagementPanel(); 145 171 pnlUploadStrategySelectionPanel = new UploadStrategySelectionPanel(); 146 172 pnlSettings.add(pnlChangesetManagement, GBC.eop().fill(GridBagConstraints.HORIZONTAL)); … … 228 254 * this in the constructor because the dialog is a singleton. 229 255 * 230 256 * @param dataSet The Dataset we want to upload 257 * @param toUpload The primitves to upload 231 258 * @since 18173 232 259 */ 233 public void initLifeCycle(DataSet dataSet ) {260 public void initLifeCycle(DataSet dataSet, APIDataSet toUpload) { 234 261 Map<String, String> map = new HashMap<>(); 235 this.dataSet = dataSet;236 262 pnlBasicUploadSettings.initLifeCycle(map); 237 263 pnlChangesetManagement.initLifeCycle(); 238 264 model.clear(); 239 model.putAll(map); 240 model.putAll( this.dataSet); // overwrite with tags from the dataset265 model.putAll(map); // init with tags from history 266 model.putAll(dataSet); // overwrite with tags from the dataset 241 267 242 268 tpConfigPanels.setSelectedIndex(0); 243 pnlTagEditor.initAutoCompletion(MainApplication.getLayerManager().getEditLayer());244 269 pnlUploadStrategySelectionPanel.initFromPreferences(); 245 270 246 271 // update the summary … … 247 272 UploadParameterSummaryPanel sumPnl = pnlBasicUploadSettings.getUploadParameterSummaryPanel(); 248 273 sumPnl.setUploadStrategySpecification(pnlUploadStrategySelectionPanel.getUploadStrategySpecification()); 249 274 sumPnl.setCloseChangesetAfterNextUpload(pnlChangesetManagement.isCloseChangesetAfterUpload()); 250 }251 275 252 /**253 * Sets the collection of primitives to upload254 *255 * @param toUpload the dataset with the objects to upload. If null, assumes the empty256 * set of objects to upload257 *258 */259 public void setUploadedPrimitives(APIDataSet toUpload) {260 UploadParameterSummaryPanel sumPnl = pnlBasicUploadSettings.getUploadParameterSummaryPanel();261 if (toUpload == null) {262 if (pnlUploadedObjects != null) {263 List<OsmPrimitive> emptyList = Collections.emptyList();264 pnlUploadedObjects.setUploadedPrimitives(emptyList, emptyList, emptyList);265 sumPnl.setNumObjects(0);266 }267 return;268 }269 276 List<OsmPrimitive> l = toUpload.getPrimitives(); 270 277 pnlBasicUploadSettings.setUploadedPrimitives(l); 271 pnlUploadedObjects.setUploadedPrimitives( 272 toUpload.getPrimitivesToAdd(), 273 toUpload.getPrimitivesToUpdate(), 274 toUpload.getPrimitivesToDelete() 275 ); 278 pnlUploadedObjects.removeAll(); 279 pnlUploadedObjects.build(toUpload); 276 280 sumPnl.setNumObjects(l.size()); 277 281 pnlUploadStrategySelectionPanel.setNumUploadedObjects(l.size()); 278 282 } … … 512 516 } 513 517 } 514 518 519 private static class KeyAutoCompManager extends DefaultAutoCompListener<String> { 520 @Override 521 protected void updateAutoCompModel(AutoCompComboBoxModel<String> model) { 522 model.replaceAllElements(Arrays.asList("comment", "source", "review_requested", "created_by", "imagery_used", "locale")); 523 // FIXME add more tags from user upload history? 524 } 525 } 526 527 private class ValueAutoCompManager extends DefaultAutoCompListener<String> { 528 @Override 529 protected void updateAutoCompModel(AutoCompComboBoxModel<String> model) { 530 String key = (String) pnlTagEditor.getModel().getValueAt(pnlTagEditor.getTable().getEditingRow(), 0); 531 if ("comment".equals(key)) { 532 model.prefs(x ->x, x -> x).load(BasicUploadSettingsPanel.COMMENT_HISTORY_KEY); 533 return; 534 } 535 if ("source".equals(key)) { 536 model.prefs(x -> x, x -> x).load(BasicUploadSettingsPanel.SOURCE_HISTORY_KEY, BasicUploadSettingsPanel.getDefaultSources()); 537 return; 538 } 539 if ("review_requested".equals(key)) { 540 model.replaceAllElements(Arrays.asList("yes", "")); 541 return; 542 } 543 model.replaceAllElements(Arrays.asList("")); 544 } 545 } 546 515 547 /* -------------------------------------------------------------------------- */ 516 548 /* Interface PropertyChangeListener */ 517 549 /* -------------------------------------------------------------------------- */ … … 604 636 * @since 14251 605 637 */ 606 638 public void clean() { 607 setUploadedPrimitives(null); 608 dataSet = null; 639 pnlUploadedObjects.removeAll(); 609 640 } 610 641 } -
src/org/openstreetmap/josm/gui/io/UploadDialogModel.java
75 75 * @return the hashtags separated by ";" or null 76 76 */ 77 77 String findHashTags(String comment) { 78 String hash tags = String.join(";",78 String hashTags = String.join(";", 79 79 Arrays.stream(comment.split("\\s", -1)) 80 80 .map(s -> Utils.strip(s, ",;")) 81 81 .filter(s -> s.matches("#[a-zA-Z0-9][-_a-zA-Z0-9]+")) 82 82 .collect(Collectors.toList())); 83 return hash tags.isEmpty() ? null : hashtags;83 return hashTags.isEmpty() ? null : hashTags; 84 84 } 85 85 86 86 /** -
src/org/openstreetmap/josm/gui/io/UploadPrimitivesTask.java
401 401 // return to the upload dialog 402 402 // 403 403 toUpload.removeProcessed(processedPrimitives); 404 UploadDialog.getUploadDialog(). setUploadedPrimitives(toUpload);404 UploadDialog.getUploadDialog().initLifeCycle(null, toUpload); 405 405 UploadDialog.getUploadDialog().setVisible(true); 406 406 break; 407 407 } -
src/org/openstreetmap/josm/gui/io/UploadStrategySelectionPanel.java
69 69 70 70 protected JPanel buildUploadStrategyPanel() { 71 71 JPanel pnl = new JPanel(new GridBagLayout()); 72 pnl.setBorder(BorderFactory.createTitledBorder(tr("Please select theupload strategy:")));72 pnl.setBorder(BorderFactory.createTitledBorder(tr("Please select an upload strategy:"))); 73 73 ButtonGroup bgStrategies = new ButtonGroup(); 74 74 rbStrategy = new EnumMap<>(UploadStrategy.class); 75 75 lblNumRequests = new EnumMap<>(UploadStrategy.class); -
src/org/openstreetmap/josm/gui/io/UploadedObjectsSummaryPanel.java
1 1 // License: GPL. For details, see LICENSE file. 2 2 package org.openstreetmap.josm.gui.io; 3 3 4 import static org.openstreetmap.josm.tools.I18n.tr;5 4 import static org.openstreetmap.josm.tools.I18n.trn; 6 5 7 6 import java.awt.GridBagConstraints; … … 8 7 import java.awt.GridBagLayout; 9 8 import java.awt.event.MouseAdapter; 10 9 import java.awt.event.MouseEvent; 11 import java.util.ArrayList;12 10 import java.util.Collections; 13 11 import java.util.List; 14 import java.util.Optional;15 12 16 import javax.swing.AbstractListModel; 13 import javax.swing.BoxLayout; 14 import javax.swing.DefaultListModel; 17 15 import javax.swing.JLabel; 18 16 import javax.swing.JList; 19 17 import javax.swing.JPanel; … … 20 18 import javax.swing.JScrollPane; 21 19 22 20 import org.openstreetmap.josm.actions.AutoScaleAction; 21 import org.openstreetmap.josm.data.APIDataSet; 23 22 import org.openstreetmap.josm.data.osm.OsmPrimitive; 24 23 import org.openstreetmap.josm.gui.PrimitiveRenderer; 25 24 … … 28 27 * @since 2599 29 28 */ 30 29 public class UploadedObjectsSummaryPanel extends JPanel { 31 /** the list with the added primitives */ 32 private PrimitiveList lstAdd; 33 private JLabel lblAdd; 34 private JScrollPane spAdd; 35 /** the list with the updated primitives */ 36 private PrimitiveList lstUpdate; 37 private JLabel lblUpdate; 38 private JScrollPane spUpdate; 39 /** the list with the deleted primitives */ 40 private PrimitiveList lstDelete; 41 private JLabel lblDelete; 42 private JScrollPane spDelete; 30 /** 31 * Zooms to the primitive on double-click 32 */ 33 private static MouseAdapter mouseListener = new MouseAdapter() { 34 @Override 35 public void mouseClicked(MouseEvent evt) { 36 if (evt.getButton() == MouseEvent.BUTTON1 && evt.getClickCount() == 2) { 37 @SuppressWarnings("unchecked") 38 JList<OsmPrimitive> list = (JList<OsmPrimitive>) evt.getSource(); 39 int index = list.locationToIndex(evt.getPoint()); 40 AutoScaleAction.zoomTo(Collections.singleton(list.getModel().getElementAt(index))); 41 } 42 } 43 }; 43 44 44 45 /** 46 * A JLabel and a JList 47 */ 48 private static class ListPanel extends JPanel { 49 /** 50 * Constructor 51 * 52 * @param primitives the list of primitives 53 * @param singular the singular form of the label 54 * @param plural the plural form of the label 55 */ 56 ListPanel(List<OsmPrimitive> primitives, String singular, String plural) { 57 DefaultListModel<OsmPrimitive> model = new DefaultListModel<>(); 58 JList<OsmPrimitive> jList = new JList<>(model); 59 primitives.forEach(model::addElement); 60 jList.setCellRenderer(new PrimitiveRenderer()); 61 jList.addMouseListener(mouseListener); 62 JScrollPane scrollPane = new JScrollPane(jList); 63 JLabel label = new JLabel(trn(singular, plural, model.size(), model.size())); 64 label.setLabelFor(jList); 65 this.setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS)); 66 this.add(label); 67 this.add(scrollPane); 68 } 69 } 70 71 /** 45 72 * Constructs a new {@code UploadedObjectsSummaryPanel}. 46 73 */ 47 74 public UploadedObjectsSummaryPanel() { 48 build();75 super(new GridBagLayout()); 49 76 } 50 77 51 protected void build() {52 setLayout(new GridBagLayout());53 PrimitiveRenderer renderer = new PrimitiveRenderer();54 MouseAdapter mouseListener = new MouseAdapter() {55 @Override56 public void mouseClicked(MouseEvent evt) {57 if (evt.getButton() == MouseEvent.BUTTON1 && evt.getClickCount() == 2) {58 PrimitiveList list = (PrimitiveList) evt.getSource();59 int index = list.locationToIndex(evt.getPoint());60 AutoScaleAction.zoomTo(Collections.singleton(list.getModel().getElementAt(index)));61 }62 }63 };64 // initialize the three lists for uploaded primitives, but don't add them to the dialog yet, see setUploadedPrimitives()65 //66 lstAdd = new PrimitiveList();67 lstAdd.setCellRenderer(renderer);68 lstAdd.addMouseListener(mouseListener);69 lstAdd.setVisibleRowCount(Math.min(lstAdd.getModel().getSize(), 10));70 spAdd = new JScrollPane(lstAdd);71 lblAdd = new JLabel(tr("Objects to add:"));72 lblAdd.setLabelFor(lstAdd);73 74 lstUpdate = new PrimitiveList();75 lstUpdate.setCellRenderer(renderer);76 lstUpdate.addMouseListener(mouseListener);77 lstUpdate.setVisibleRowCount(Math.min(lstUpdate.getModel().getSize(), 10));78 spUpdate = new JScrollPane(lstUpdate);79 lblUpdate = new JLabel(tr("Objects to modify:"));80 lblUpdate.setLabelFor(lstUpdate);81 82 lstDelete = new PrimitiveList();83 lstDelete.setCellRenderer(renderer);84 lstDelete.addMouseListener(mouseListener);85 lstDelete.setVisibleRowCount(Math.min(lstDelete.getModel().getSize(), 10));86 spDelete = new JScrollPane(lstDelete);87 lblDelete = new JLabel(tr("Objects to delete:"));88 lblDelete.setLabelFor(lstDelete);89 }90 91 78 /** 92 * Sets the collections of primitives which will be uploaded79 * Builds the panel 93 80 * 94 * @param add the collection of primitives to add 95 * @param update the collection of primitives to update 96 * @param delete the collection of primitives to delete 81 * @param toUpload the primitives to upload 97 82 */ 98 public void setUploadedPrimitives(List<OsmPrimitive> add, List<OsmPrimitive> update, List<OsmPrimitive> delete) { 99 lstAdd.getPrimitiveListModel().setPrimitives(add); 100 lstUpdate.getPrimitiveListModel().setPrimitives(update); 101 lstDelete.getPrimitiveListModel().setPrimitives(delete); 102 103 GridBagConstraints gcLabel = new GridBagConstraints(); 104 gcLabel.fill = GridBagConstraints.HORIZONTAL; 105 gcLabel.weightx = 1.0; 106 gcLabel.weighty = 0.0; 107 gcLabel.anchor = GridBagConstraints.FIRST_LINE_START; 108 83 public void build(APIDataSet toUpload) { 109 84 GridBagConstraints gcList = new GridBagConstraints(); 110 85 gcList.fill = GridBagConstraints.BOTH; 111 86 gcList.weightx = 1.0; 112 87 gcList.weighty = 1.0; 113 88 gcList.anchor = GridBagConstraints.CENTER; 89 114 90 removeAll(); 115 int y = -1; 116 if (!add.isEmpty()) { 117 y++; 118 gcLabel.gridy = y; 119 lblAdd.setText(trn("{0} object to add:", "{0} objects to add:", add.size(), add.size())); 120 add(lblAdd, gcLabel); 121 y++; 122 gcList.gridy = y; 123 add(spAdd, gcList); 91 List<OsmPrimitive> list = toUpload.getPrimitivesToAdd(); 92 if (!list.isEmpty()) { 93 gcList.gridy++; 94 add(new ListPanel(list, "{0} object to add:", "{0} objects to add:"), gcList); 124 95 } 125 if (!update.isEmpty()) { 126 y++; 127 gcLabel.gridy = y; 128 lblUpdate.setText(trn("{0} object to modify:", "{0} objects to modify:", update.size(), update.size())); 129 add(lblUpdate, gcLabel); 130 y++; 131 gcList.gridy = y; 132 add(spUpdate, gcList); 96 list = toUpload.getPrimitivesToUpdate(); 97 if (!list.isEmpty()) { 98 gcList.gridy++; 99 add(new ListPanel(list, "{0} object to modify:", "{0} objects to modify:"), gcList); 133 100 } 134 if (!delete.isEmpty()) { 135 y++; 136 gcLabel.gridy = y; 137 lblDelete.setText(trn("{0} object to delete:", "{0} objects to delete:", delete.size(), delete.size())); 138 add(lblDelete, gcLabel); 139 y++; 140 gcList.gridy = y; 141 add(spDelete, gcList); 101 list = toUpload.getPrimitivesToDelete(); 102 if (!list.isEmpty()) { 103 gcList.gridy++; 104 add(new ListPanel(list, "{0} object to delete:", "{0} objects to delete:"), gcList); 142 105 } 143 106 revalidate(); 107 repaint(); 144 108 } 145 146 /**147 * Replies the number of objects to upload148 *149 * @return the number of objects to upload150 */151 public int getNumObjectsToUpload() {152 return lstAdd.getModel().getSize()153 + lstUpdate.getModel().getSize()154 + lstDelete.getModel().getSize();155 }156 157 /**158 * A simple list of OSM primitives.159 */160 static class PrimitiveList extends JList<OsmPrimitive> {161 /**162 * Constructs a new {@code PrimitiveList}.163 */164 PrimitiveList() {165 super(new PrimitiveListModel());166 }167 168 public PrimitiveListModel getPrimitiveListModel() {169 return (PrimitiveListModel) getModel();170 }171 }172 173 /**174 * A list model for a list of OSM primitives.175 */176 static class PrimitiveListModel extends AbstractListModel<OsmPrimitive> {177 private transient List<OsmPrimitive> primitives;178 179 /**180 * Constructs a new {@code PrimitiveListModel}.181 */182 PrimitiveListModel() {183 primitives = new ArrayList<>();184 }185 186 PrimitiveListModel(List<OsmPrimitive> primitives) {187 setPrimitives(primitives);188 }189 190 public void setPrimitives(List<OsmPrimitive> primitives) {191 this.primitives = Optional.ofNullable(primitives).orElseGet(ArrayList::new);192 fireContentsChanged(this, 0, getSize());193 }194 195 @Override196 public OsmPrimitive getElementAt(int index) {197 if (primitives == null) return null;198 return primitives.get(index);199 }200 201 @Override202 public int getSize() {203 if (primitives == null) return 0;204 return primitives.size();205 }206 }207 109 } -
src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java
1280 1280 @Override 1281 1281 public AbstractUploadDialog getUploadDialog() { 1282 1282 UploadDialog dialog = UploadDialog.getUploadDialog(); 1283 dialog. setUploadedPrimitives(new APIDataSet(data));1283 dialog.initLifeCycle(data, new APIDataSet(data)); 1284 1284 return dialog; 1285 1285 } 1286 1286 -
src/org/openstreetmap/josm/gui/preferences/ToolbarPreferences.java
23 23 import java.util.Arrays; 24 24 import java.util.Collection; 25 25 import java.util.Collections; 26 import java.util.HashSet; 26 27 import java.util.LinkedList; 27 28 import java.util.List; 28 29 import java.util.Map; 29 30 import java.util.Objects; 30 31 import java.util.Optional; 32 import java.util.Set; 31 33 import java.util.concurrent.ConcurrentHashMap; 32 34 33 35 import javax.swing.AbstractAction; … … 238 240 } 239 241 } 240 242 243 /** 244 * Parses strings into action definitions and back. 245 */ 241 246 public static class ActionParser { 242 247 private final Map<String, Action> actions; 243 248 private final StringBuilder result = new StringBuilder(); … … 274 279 } 275 280 276 281 /** 277 * Loads the action definition from its toolbar name. 282 * Parses an action definition from a string. 283 * 278 284 * @param actionName action toolbar name 279 285 * @return action definition or null 280 286 */ … … 353 359 } 354 360 } 355 361 362 /** 363 * Unparses an action definition 364 * 365 * @param action the given action 366 * @return the action as string 367 */ 356 368 @SuppressWarnings("unchecked") 357 369 public String saveAction(ActionDefinition action) { 358 370 result.setLength(0); … … 397 409 } 398 410 if (!first) { 399 411 result.append('}'); 412 } 400 413 } 401 }402 414 403 415 return result.toString(); 404 416 } … … 570 582 } 571 583 572 584 private final ToolbarPopupMenu popupMenu = new ToolbarPopupMenu(); 585 private boolean showInfoAboutMissingActions; 573 586 574 587 /** 575 588 * Key: Registered name (property "toolbar" of action). … … 577 590 */ 578 591 private final Map<String, Action> regactions = new ConcurrentHashMap<>(); 579 592 580 private final DefaultMutableTreeNode rootActionsNode = new DefaultMutableTreeNode(tr("Actions")); 581 593 /** the swing component for the toolbar */ 582 594 public final JToolBar control = new JToolBar(); 583 595 private final Map<Object, ActionDefinition> buttonActions = new ConcurrentHashMap<>(30); 584 private boolean showInfoAboutMissingActions;585 596 586 597 @Override 587 598 public PreferenceSetting createPreferenceSetting() { 588 return new Settings( rootActionsNode);599 return new Settings(loadActions(MainApplication.getMenu(), regactions)); 589 600 } 590 601 591 602 /** … … 1025 1036 TaggingPresets.addListener(this); 1026 1037 } 1027 1038 1028 private static void loadAction(DefaultMutableTreeNode node, MenuElement menu, Map<String, Action> actionsInMenu) { 1029 Object userObject = null; 1039 /** 1040 * Recursive part of {@link #loadActions}. 1041 * 1042 * @param node the parent node 1043 * @param seen accumulator for all seen actions 1044 * @param menu the menu to harvest 1045 */ 1046 private void loadAction(DefaultMutableTreeNode node, Set<Action> seen, MenuElement menu) { 1030 1047 MenuElement menuElement = menu; 1031 1048 if (menu.getSubElements().length > 0 && 1032 1049 menu.getSubElements()[0] instanceof JPopupMenu) { … … 1034 1051 } 1035 1052 for (MenuElement item : menuElement.getSubElements()) { 1036 1053 if (item instanceof JMenuItem) { 1054 DefaultMutableTreeNode newNode = null; 1037 1055 JMenuItem menuItem = (JMenuItem) item; 1038 1056 if (menuItem.getAction() != null) { 1039 1057 Action action = menuItem.getAction(); 1040 userObject = action; 1058 newNode = new DefaultMutableTreeNode(action); 1059 seen.add(action); 1041 1060 Object tb = action.getValue("toolbar"); 1042 1061 if (tb == null) { 1043 1062 Logging.info(tr("Toolbar action without name: {0}", … … 1049 1068 action.getClass().getName())); 1050 1069 } 1051 1070 continue; 1052 } else {1053 String toolbar = (String) tb;1054 Action r = actionsInMenu.get(toolbar);1055 if (r != null && r != action && !toolbar.startsWith(IMAGERY_PREFIX)) {1056 Logging.info(tr("Toolbar action {0} overwritten: {1} gets {2}",1057 toolbar, r.getClass().getName(), action.getClass().getName()));1058 }1059 actionsInMenu.put(toolbar, action);1060 1071 } 1061 1072 } else { 1062 userObject = menuItem.getText();1073 newNode = new DefaultMutableTreeNode(menuItem.getText()); 1063 1074 } 1075 node.add(newNode); 1076 loadAction(newNode, seen, item); 1064 1077 } 1065 DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(userObject);1066 node.add(newNode);1067 loadAction(newNode, item, actionsInMenu);1068 1078 } 1069 1079 } 1070 1080 1071 private void loadActions(Map<String, Action> actionsInMenu) { 1072 rootActionsNode.removeAllChildren(); 1073 loadAction(rootActionsNode, MainApplication.getMenu(), actionsInMenu); 1074 for (Map.Entry<String, Action> a : regactions.entrySet()) { 1075 if (actionsInMenu.get(a.getKey()) == null) { 1076 rootActionsNode.add(new DefaultMutableTreeNode(a.getValue())); 1081 /** 1082 * Builds a JTree root node of known actions. 1083 * <p> 1084 * Builds a {@link JTree} with the same structure as the given menu, then adds all registered actions 1085 * that are not already in the tree. 1086 * 1087 * @param menu the menu to scan for actions 1088 * @param registeredActions the registered actions 1089 * @return the root tree node of a JTree 1090 */ 1091 private DefaultMutableTreeNode loadActions(MenuElement menu, Map<String, Action> registeredActions) { 1092 final DefaultMutableTreeNode rootActionsNode = new DefaultMutableTreeNode(tr("Actions")); 1093 final HashSet<Action> seen = new HashSet<>(); 1094 1095 loadAction(rootActionsNode, seen, menu); 1096 registeredActions.forEach((key, action) -> { 1097 if (!seen.contains(action)) { 1098 rootActionsNode.add(new DefaultMutableTreeNode(action)); 1077 1099 } 1078 } 1100 }); 1079 1101 rootActionsNode.add(new DefaultMutableTreeNode(null)); 1102 return rootActionsNode; 1080 1103 } 1081 1104 1082 1105 private static final String[] deftoolbar = {"open", "save", "download", "upload", "|", … … 1088 1111 "tagginggroup_Facilities/Food+Drinks", "|", "tagginggroup_Man Made/Historic Places", "|", 1089 1112 "tagginggroup_Man Made/Man Made"}; 1090 1113 1114 /** 1115 * Returns the configured toolbar strings or {@link #deftoolbar default ones}. 1116 * @return the toolstring 1117 */ 1091 1118 public static Collection<String> getToolString() { 1092 1119 Collection<String> toolStr = Config.getPref().getList("toolbar", Arrays.asList(deftoolbar)); 1093 1120 if (Utils.isEmpty(toolStr)) { … … 1097 1124 } 1098 1125 1099 1126 private Collection<ActionDefinition> getDefinedActions() { 1100 Map<String, Action> actionsInMenu = new ConcurrentHashMap<>(); 1101 1102 loadActions(actionsInMenu); 1103 1104 Map<String, Action> allActions = new ConcurrentHashMap<>(regactions); 1105 allActions.putAll(actionsInMenu); 1106 ActionParser actionParser = new ActionParser(allActions); 1107 1127 ActionParser actionParser = new ActionParser(regactions); 1108 1128 Collection<ActionDefinition> result = new ArrayList<>(); 1109 1129 1110 1130 for (String s : getToolString()) { … … 1139 1159 Logging.info(tr("Registered toolbar action {0} overwritten: {1} gets {2}", 1140 1160 toolbar, r.getClass().getName(), action.getClass().getName())); 1141 1161 } 1142 }1143 if (toolbar != null) {1144 1162 regactions.put(toolbar, action); 1145 1163 } 1146 1164 return action; … … 1250 1268 sc = ((JosmAction) action.getAction()).getShortcut(); 1251 1269 if (sc.getAssignedKey() == KeyEvent.CHAR_UNDEFINED) { 1252 1270 sc = null; 1271 } 1253 1272 } 1254 }1255 1273 1256 1274 long paramCode = 0; 1257 1275 if (action.hasParameters()) { -
src/org/openstreetmap/josm/gui/preferences/map/TaggingPresetPreference.java
31 31 import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane.PreferencePanel; 32 32 import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane.ValidationListener; 33 33 import org.openstreetmap.josm.gui.preferences.SourceEditor; 34 import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;35 34 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetReader; 36 35 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets; 37 36 import org.openstreetmap.josm.spi.preferences.Config; … … 58 57 i++; 59 58 boolean canLoad = false; 60 59 try { 61 TaggingPresetReader.read All(source.url, false);60 TaggingPresetReader.read(source.url, false); 62 61 canLoad = true; 63 62 } catch (IOException e) { 64 63 Logging.log(Logging.LEVEL_WARN, tr("Could not read tagging preset source: {0}", source), e); … … 82 81 String errorMessage = null; 83 82 84 83 try { 85 TaggingPresetReader.read All(source.url, true);84 TaggingPresetReader.read(source.url, true); 86 85 } catch (IOException e) { 87 86 // Should not happen, but at least show message 88 87 String msg = tr("Could not read tagging preset source: {0}", source); … … 170 169 171 170 @Override 172 171 public void addGui(PreferenceTabbedPane gui) { 173 useValidator = new JCheckBox(tr("Run data validator on user input"), TaggingPreset .USE_VALIDATOR.get());174 sortMenu = new JCheckBox(tr("Sort presets menu alphabetically"), TaggingPresets.SORT_ MENU.get());172 useValidator = new JCheckBox(tr("Run data validator on user input"), TaggingPresets.USE_VALIDATOR.get()); 173 sortMenu = new JCheckBox(tr("Sort presets menu alphabetically"), TaggingPresets.SORT_VALUES.get()); 175 174 176 175 final JPanel panel = new JPanel(new GridBagLayout()); 177 176 panel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); … … 251 250 252 251 @Override 253 252 public boolean ok() { 254 TaggingPreset .USE_VALIDATOR.put(useValidator.isSelected());255 if (sources.finish() || TaggingPresets.SORT_ MENU.put(sortMenu.isSelected())) {253 TaggingPresets.USE_VALIDATOR.put(useValidator.isSelected()); 254 if (sources.finish() || TaggingPresets.SORT_VALUES.put(sortMenu.isSelected())) { 256 255 TaggingPresets.destroy(); 257 256 TaggingPresets.initialize(); 258 257 } -
src/org/openstreetmap/josm/gui/tagging/TagEditorModel.java
145 145 } 146 146 147 147 @Override 148 public Object getValueAt(int rowIndex, int columnIndex) { 149 if (rowIndex >= getRowCount()) 150 throw new IndexOutOfBoundsException("unexpected rowIndex: rowIndex=" + rowIndex); 151 152 return tags.get(rowIndex); 148 public Object getValueAt(int row, int col) { 149 if (row >= getRowCount()) 150 throw new IndexOutOfBoundsException("unexpected row: row=" + row); 151 if (col >= getColumnCount()) 152 throw new IndexOutOfBoundsException("unexpected col: col=" + col); 153 TagModel tag = get(row); 154 switch(col) { 155 case 0: 156 return tag.getName(); 157 case 1: 158 return tag.getValue(); 159 default: // Do nothing 160 } 161 return null; 153 162 } 154 163 155 164 @Override -
src/org/openstreetmap/josm/gui/tagging/TagEditorPanel.java
21 21 import org.openstreetmap.josm.gui.dialogs.properties.HelpAction; 22 22 import org.openstreetmap.josm.gui.dialogs.properties.HelpTagAction; 23 23 import org.openstreetmap.josm.gui.dialogs.properties.PresetListPanel; 24 import org.openstreetmap.josm.gui.layer.OsmDataLayer;25 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;26 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;27 24 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetHandler; 28 25 import org.openstreetmap.josm.spi.preferences.Config; 29 import org.openstreetmap.josm.tools.CheckParameterUtil;30 26 31 27 /** 32 28 * TagEditorPanel is a {@link JPanel} which can be embedded as UI component in … … 90 86 JButton btn = new JButton(action); 91 87 pnl.add(btn); 92 88 btn.setMargin(new Insets(0, 0, 0, 0)); 93 tagTable.addComponentNotStoppingCellEditing(btn);94 89 } 95 90 96 91 /** … … 147 142 * @param presetHandler tagging preset handler 148 143 */ 149 144 public TagEditorPanel(OsmPrimitive primitive, TaggingPresetHandler presetHandler) { 150 this(new TagEditorModel().forPrimitive(primitive), presetHandler, 0);145 this(new TagEditorModel().forPrimitive(primitive), presetHandler, -1); 151 146 } 152 147 153 148 /** … … 188 183 } 189 184 190 185 /** 191 * Initializes the auto completion infrastructure used in this 192 * tag editor panel. {@code layer} is the data layer from whose data set 193 * tag values are proposed as auto completion items. 194 * 195 * @param layer the data layer. Must not be null. 196 * @throws IllegalArgumentException if {@code layer} is null 186 * Returns the JTable 187 * @return the JTable 197 188 */ 198 public void initAutoCompletion(OsmDataLayer layer) { 199 CheckParameterUtil.ensureParameterNotNull(layer, "layer"); 200 201 AutoCompletionManager autocomplete = AutoCompletionManager.of(layer.data); 202 AutoCompletionList acList = new AutoCompletionList(); 203 204 TagCellEditor editor = (TagCellEditor) tagTable.getColumnModel().getColumn(0).getCellEditor(); 205 editor.setAutoCompletionManager(autocomplete); 206 editor.setAutoCompletionList(acList); 207 editor = (TagCellEditor) tagTable.getColumnModel().getColumn(1).getCellEditor(); 208 editor.setAutoCompletionManager(autocomplete); 209 editor.setAutoCompletionList(acList); 189 public TagTable getTable() { 190 return tagTable; 210 191 } 211 192 212 193 @Override -
src/org/openstreetmap/josm/gui/tagging/TagTable.java
5 5 6 6 import java.awt.Component; 7 7 import java.awt.Dimension; 8 import java.awt.KeyboardFocusManager;9 import java.awt.Window;10 8 import java.awt.event.ActionEvent; 9 import java.awt.event.ActionListener; 11 10 import java.awt.event.KeyEvent; 12 11 import java.beans.PropertyChangeEvent; 13 12 import java.beans.PropertyChangeListener; 14 13 import java.util.Collections; 15 import java.util.EventObject;16 import java.util.concurrent.CopyOnWriteArrayList;17 14 18 15 import javax.swing.AbstractAction; 19 16 import javax.swing.CellEditor; 17 import javax.swing.InputMap; 20 18 import javax.swing.JComponent; 21 19 import javax.swing.JTable; 22 20 import javax.swing.KeyStroke; … … 24 22 import javax.swing.SwingUtilities; 25 23 import javax.swing.event.ListSelectionEvent; 26 24 import javax.swing.event.ListSelectionListener; 27 import javax.swing.text.JTextComponent; 25 import javax.swing.table.DefaultTableCellRenderer; 26 import javax.swing.table.TableCellEditor; 28 27 29 28 import org.openstreetmap.josm.data.osm.Relation; 30 29 import org.openstreetmap.josm.data.osm.TagMap; 31 30 import org.openstreetmap.josm.gui.datatransfer.OsmTransferHandler; 32 31 import org.openstreetmap.josm.gui.tagging.TagEditorModel.EndEditListener; 33 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList; 34 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager; 32 import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBox; 35 33 import org.openstreetmap.josm.gui.widgets.JosmTable; 36 34 import org.openstreetmap.josm.tools.ImageProvider; 37 import org.openstreetmap.josm.tools.Logging;38 35 import org.openstreetmap.josm.tools.Utils; 39 36 40 37 /** … … 41 38 * This is the tabular editor component for OSM tags. 42 39 * @since 1762 43 40 */ 44 public class TagTable extends JosmTable implements EndEditListener { 45 /** the table cell editor used by this table */ 46 private TagCellEditor editor; 41 public class TagTable extends JosmTable implements ActionListener, EndEditListener { 47 42 private final TagEditorModel model; 48 43 private Component nextFocusComponent; 44 private final int LAST_COL = 1; 49 45 50 /** a list of components to which focus can be transferred without stopping 51 * cell editing this table. 52 */ 53 private final CopyOnWriteArrayList<Component> doNotStopCellEditingWhenFocused = new CopyOnWriteArrayList<>(); 54 private transient CellEditorRemover editorRemover; 46 /** the go to next cell action */ 47 private final SelectNextColumnCellAction nextAction = new SelectNextColumnCellAction(); 48 /** the go to previous cell action */ 49 private final SelectPreviousColumnCellAction previousAction = new SelectPreviousColumnCellAction(); 50 /** the delete action */ 51 private final DeleteAction deleteAction = new DeleteAction(); 52 /** the add action */ 53 private final AddAction addAction = new AddAction(); 54 /** the tag paste action */ 55 private final PasteAction pasteAction = new PasteAction(); 55 56 56 57 /** 57 * Action to be run when the user navigates to the next cell in the table, 58 * for instance by pressing TAB or ENTER. The action alters the standard 59 * navigation path from cell to cell: 60 * <ul> 61 * <li>it jumps over cells in the first column</li> 62 * <li>it automatically add a new empty row when the user leaves the 63 * last cell in the table</li> 64 * </ul> 58 * Action to be run when the user navigates to the next cell in the table, for instance by 59 * pressing TAB or ENTER. The action automatically adds a new empty row when the user leaves the 60 * last cell in the table. 65 61 */ 66 62 class SelectNextColumnCellAction extends AbstractAction { 67 63 @Override 68 64 public void actionPerformed(ActionEvent e) { 69 run();70 }71 72 public void run() {73 65 int col = getSelectedColumn(); 74 66 int row = getSelectedRow(); 75 if (getCellEditor() != null) {76 getCellEditor().stopCellEditing();77 }78 67 79 68 if (row == -1 && col == -1) { 80 69 requestFocusInCell(0, 0); 81 70 return; 82 71 } 72 endCellEditing(); 83 73 84 if (col == 0) {74 if (col < LAST_COL) { 85 75 col++; 86 } else if ( col == 1 && row < getRowCount()-1) {76 } else if (row < getRowCount() - 1) { 87 77 col = 0; 88 78 row++; 89 } else if (col == 1 && row == getRowCount()-1){90 // we are at the end. Append an empty row and move the focus to its second column91 String key = ( (TagModel) model.getValueAt(row, 0)).getName();79 } else { 80 // we are in the last cell. 81 String key = (String) model.getValueAt(row, 0); 92 82 if (!Utils.isStripEmpty(key)) { 83 // append an empty row 93 84 model.appendNewTag(); 94 85 col = 0; 95 86 row++; 96 87 } else { 88 // exit the table 97 89 clearSelection(); 98 90 if (nextFocusComponent != null) 99 91 nextFocusComponent.requestFocusInWindow(); … … 114 106 public void actionPerformed(ActionEvent e) { 115 107 int col = getSelectedColumn(); 116 108 int row = getSelectedRow(); 117 if (getCellEditor() != null) {118 getCellEditor().stopCellEditing();119 }120 109 110 endCellEditing(); 111 121 112 if (col <= 0 && row <= 0) { 122 113 // change nothing 123 } else if (col == 1) {114 } else if (col > 0) { 124 115 col--; 125 116 } else { 126 col = 1;117 col = LAST_COL; 127 118 row--; 128 119 } 129 120 requestFocusInCell(row, col); … … 131 122 } 132 123 133 124 /** 134 * Action to be run when the user invokes a delete action on the table, for 135 * instance by pressing DEL.125 * Action to be run when the user invokes a delete action on the table, for instance by pressing 126 * DEL or hitting the "delete" button in the {@link TagEditorPanel}. 136 127 * 137 * Depending on the shape on the current selection the action deletes individual 138 * values orentire tags from the model.128 * Depending on the shape on the current selection the action deletes individual values or 129 * entire tags from the model. 139 130 * 140 * If the current selection consists of cells in the second column only, the keys of141 * t he selected tags are set to the empty string.131 * If the current selection consists of cells in the key column only, the keys of the selected 132 * tags are set to the empty string. 142 133 * 143 * If the current selection consists of cell in the thirdcolumn only, the values of the134 * If the current selection consists of cell in the values column only, the values of the 144 135 * selected tags are set to the empty string. 145 136 * 146 * If the current selection consists of cells in the second and the third column,147 * the selected tags are removed from themodel.137 * If the current selection consists of entire rows, the selected tags are removed from the 138 * model. 148 139 * 149 * This action listens to the table selection. It becomes enabled when the selection 150 * is non-empty, otherwise it is disabled. 151 * 152 * 140 * This action listens to the table selection. It becomes enabled when the selection is 141 * non-empty, otherwise it is disabled. 153 142 */ 154 143 class DeleteAction extends AbstractAction implements ListSelectionListener { 155 144 … … 161 150 updateEnabledState(); 162 151 } 163 152 164 /**165 * delete a selection of tag names166 */167 protected void deleteTagNames() {168 int[] rows = getSelectedRows();169 model.deleteTagNames(rows);170 }171 172 /**173 * delete a selection of tag values174 */175 protected void deleteTagValues() {176 int[] rows = getSelectedRows();177 model.deleteTagValues(rows);178 }179 180 /**181 * delete a selection of tags182 */183 protected void deleteTags() {184 int[] rows = getSelectedRows();185 model.deleteTags(rows);186 }187 188 153 @Override 189 154 public void actionPerformed(ActionEvent e) { 190 if (!isEnabled()) 191 return; 192 switch(getSelectedColumnCount()) { 155 switch (getSelectedColumnCount()) { 193 156 case 1: 194 157 if (getSelectedColumn() == 0) { 195 deleteTagNames();158 model.deleteTagNames(getSelectedRows()); 196 159 } else if (getSelectedColumn() == 1) { 197 deleteTagValues();160 model.deleteTagValues(getSelectedRows()); 198 161 } 199 162 break; 200 163 case 2: 201 deleteTags();164 model.deleteTags(getSelectedRows()); 202 165 break; 203 166 default: // Do nothing 204 167 } 205 168 206 endCellEditing();207 208 169 if (model.getRowCount() == 0) { 209 170 model.ensureOneTag(); 210 171 requestFocusInCell(0, 0); … … 216 177 */ 217 178 @Override 218 179 public void valueChanged(ListSelectionEvent e) { 219 updateEnabledState(); 180 // when the user clicks on the "delete" button the table loses focus and unselects all 181 // cells which in turn would disable the action. the delay allows the action to execute 182 // before it gets disabled 183 SwingUtilities.invokeLater(this::updateEnabledState); 220 184 } 221 185 222 186 protected final void updateEnabledState() { … … 243 207 cEditor.stopCellEditing(); 244 208 } 245 209 final int rowIdx = model.getRowCount()-1; 246 if (rowIdx < 0 || !Utils.isStripEmpty(( (TagModel) model.getValueAt(rowIdx, 0)).getName())) {210 if (rowIdx < 0 || !Utils.isStripEmpty((String) model.getValueAt(rowIdx, 0))) { 247 211 model.appendNewTag(); 248 212 } 249 213 requestFocusInCell(model.getRowCount()-1, 0); … … 288 252 } 289 253 } 290 254 291 /** the delete action */292 private DeleteAction deleteAction;293 294 /** the add action */295 private AddAction addAction;296 297 /** the tag paste action */298 private PasteAction pasteAction;299 300 255 /** 301 256 * Returns the delete action. 302 257 * @return the delete action used by this table … … 322 277 } 323 278 324 279 /** 325 * initialize the table326 * @param maxCharacters maximum number of characters allowed for keys and values, 0 for unlimited327 */328 protected final void init(final int maxCharacters) {329 setAutoResizeMode(JTable.AUTO_RESIZE_OFF);330 setRowSelectionAllowed(true);331 setColumnSelectionAllowed(true);332 setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);333 334 // make ENTER behave like TAB335 //336 getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)337 .put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false), "selectNextColumnCell");338 339 // install custom navigation actions340 //341 getActionMap().put("selectNextColumnCell", new SelectNextColumnCellAction());342 getActionMap().put("selectPreviousColumnCell", new SelectPreviousColumnCellAction());343 344 // create a delete action. Installing this action in the input and action map345 // didn't work. We therefore handle delete requests in processKeyBindings(...)346 //347 deleteAction = new DeleteAction();348 349 // create the add action350 //351 addAction = new AddAction();352 getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)353 .put(KeyStroke.getKeyStroke(KeyEvent.VK_ADD, KeyEvent.CTRL_DOWN_MASK), "addTag");354 getActionMap().put("addTag", addAction);355 356 pasteAction = new PasteAction();357 358 // create the table cell editor and set it to key and value columns359 //360 TagCellEditor tmpEditor = new TagCellEditor(maxCharacters);361 setRowHeight(tmpEditor.getEditor().getPreferredSize().height);362 setTagCellEditor(tmpEditor);363 }364 365 /**366 280 * Creates a new tag table 367 281 * 368 282 * @param model the tag editor model 369 * @param maxCharacters maximum number of characters allowed for keys and values, 0for unlimited283 * @param maxCharacters maximum number of characters allowed for keys and values, -1 for unlimited 370 284 */ 371 285 public TagTable(TagEditorModel model, final int maxCharacters) { 372 super(model, new TagTableColumnModelBuilder(new TagCellRenderer(), tr("Key"), tr("Value"))286 super(model, new TagTableColumnModelBuilder(new DefaultTableCellRenderer(), tr("Key"), tr("Value")) 373 287 .setSelectionModel(model.getColumnSelectionModel()).build(), 374 288 model.getRowSelectionModel()); 289 375 290 this.model = model; 376 291 model.setEndEditListener(this); 377 init(maxCharacters);378 }379 292 380 @Override 381 public Dimension getPreferredSize() { 382 return getPreferredFullWidthSize(); 383 } 293 setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN); 294 setRowSelectionAllowed(true); 295 setColumnSelectionAllowed(true); 296 setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION); 297 putClientProperty("terminateEditOnFocusLost", Boolean.TRUE); 384 298 385 @Override 386 protected boolean processKeyBinding(KeyStroke ks, KeyEvent e, int condition, boolean pressed) { 299 InputMap im = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 387 300 388 // handle delete key 389 // 390 if (e.getKeyCode() == KeyEvent.VK_DELETE) { 391 if (isEditing() && getSelectedColumnCount() == 1 && getSelectedRowCount() == 1) 392 // if DEL was pressed and only the currently edited cell is selected, 393 // don't run the delete action. DEL is handled by the CellEditor as normal 394 // DEL in the text input. 395 // 396 return super.processKeyBinding(ks, e, condition, pressed); 397 getDeleteAction().actionPerformed(null); 398 } 399 return super.processKeyBinding(ks, e, condition, pressed); 301 // make ENTER behave like TAB (does not work for ComboBoxes) 302 im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false), "selectNextColumnCell"); 303 im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "delete"); 304 im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ADD, KeyEvent.CTRL_DOWN_MASK), "addTag"); 305 306 getActionMap().put("selectNextColumnCell", nextAction); 307 getActionMap().put("selectPreviousColumnCell", previousAction); 308 getActionMap().put("addTag", addAction); 309 getActionMap().put("delete", deleteAction); 400 310 } 401 311 402 312 /** 403 * Sets the editor autocompletion list404 * @param autoCompletionList autocompletion list313 * Sets a TableCellEditor for the keys column. 314 * @param editor the editor to set 405 315 */ 406 public void setAutoCompletionList(AutoCompletionList autoCompletionList) { 407 if (autoCompletionList == null) 408 return; 409 if (editor != null) { 410 editor.setAutoCompletionList(autoCompletionList); 411 } 316 public void setKeyEditor(TableCellEditor editor) { 317 if (editor instanceof AutoCompComboBox) 318 ((AutoCompComboBox<?>) editor).getActionMap().put("enterPressed", nextAction); 319 getColumnModel().getColumn(0).setCellEditor(editor); 412 320 } 413 321 414 322 /** 415 * Sets the autocompletion manager that should be used for editing the cells416 * @param autocomplete The {@link AutoCompletionManager}323 * Sets a TableCellEditor for the values column. 324 * @param editor the editor to set 417 325 */ 418 public void setAutoCompletionManager(AutoCompletionManager autocomplete) { 419 if (autocomplete == null) { 420 Logging.warn("argument autocomplete should not be null. Aborting."); 421 Logging.error(new Exception()); 422 return; 423 } 424 if (editor != null) { 425 editor.setAutoCompletionManager(autocomplete); 426 } 326 public void setValueEditor(TableCellEditor editor) { 327 if (editor instanceof AutoCompComboBox) 328 ((AutoCompComboBox<?>) editor).getActionMap().put("enterPressed", nextAction); 329 getColumnModel().getColumn(1).setCellEditor(editor); 427 330 } 428 331 429 /** 430 * Gets the {@link AutoCompletionList} the cell editor is synchronized with 431 * @return The list 432 */ 433 public AutoCompletionList getAutoCompletionList() { 434 if (editor != null) 435 return editor.getAutoCompletionList(); 436 else 437 return null; 332 @Override 333 public boolean getDragEnabled() { 334 // fix for comboboxes flashing when clicking the cell where the arrow button will be 335 // maybe a late focus request wants to focus the cb when the popup is already open? 336 // see: BasicTableUI#adjustSelection and mouseReleasedDND 337 return true; 438 338 } 439 339 340 @Override 341 public Dimension getPreferredSize() { 342 return getPreferredFullWidthSize(); 343 } 344 440 345 /** 441 346 * Sets the next component to request focus after navigation (with tab or enter). 442 347 * @param nextFocusComponent next component to request focus after navigation (with tab or enter) … … 446 351 } 447 352 448 353 /** 449 * Gets the editor that is used for the table cells450 * @return The editor that is used when the user wants to enter text into a cell451 */452 public TagCellEditor getTableCellEditor() {453 return editor;454 }455 456 /**457 * Inject a tag cell editor in the tag table458 *459 * @param editor tag cell editor460 */461 public void setTagCellEditor(TagCellEditor editor) {462 endCellEditing();463 this.editor = editor;464 getColumnModel().getColumn(0).setCellEditor(editor);465 getColumnModel().getColumn(1).setCellEditor(editor);466 }467 468 /**469 354 * Request the focus in a specific cell 470 355 * @param row The row index 471 356 * @param col The column index … … 475 360 editCellAt(row, col); 476 361 Component c = getEditorComponent(); 477 362 if (c != null) { 478 if (!c.requestFocusInWindow()) { 479 Logging.warn("Unable to request focus for " + c); 480 } 481 if (c instanceof JTextComponent) { 482 ((JTextComponent) c).selectAll(); 483 } 363 c.requestFocusInWindow(); 484 364 } 485 // there was a bug here - on older 1.6 Java versions Tab was not working486 // after such activation. In 1.7 it works OK,487 // previous solution of using awt.Robot was resetting mouse speed on Windows488 365 } 489 366 490 /**491 * Marks a component that may be focused without stopping the cell editing492 * @param component The component493 */494 public void addComponentNotStoppingCellEditing(Component component) {495 if (component == null) return;496 doNotStopCellEditingWhenFocused.addIfAbsent(component);497 }498 499 /**500 * Removes a component added with {@link #addComponentNotStoppingCellEditing(Component)}501 * @param component The component502 */503 public void removeComponentNotStoppingCellEditing(Component component) {504 if (component == null) return;505 doNotStopCellEditingWhenFocused.remove(component);506 }507 508 367 @Override 509 public boolean editCellAt(int row, int column, EventObject e) {510 511 // a snipped copied from the Java 1.5 implementation of JTable512 //513 if (cellEditor != null && !cellEditor.stopCellEditing())514 return false;515 516 if (row < 0 || row >= getRowCount() ||517 column < 0 || column >= getColumnCount())518 return false;519 520 if (!isCellEditable(row, column))521 return false;522 523 // make sure our custom implementation of CellEditorRemover is created524 if (editorRemover == null) {525 KeyboardFocusManager fm =526 KeyboardFocusManager.getCurrentKeyboardFocusManager();527 editorRemover = new CellEditorRemover(fm);528 fm.addPropertyChangeListener("permanentFocusOwner", editorRemover);529 }530 531 // delegate to the default implementation532 return super.editCellAt(row, column, e);533 }534 535 @Override536 368 public void endCellEditing() { 537 if (isEditing()) { 538 CellEditor cEditor = getCellEditor(); 539 if (cEditor != null) { 540 // First attempt to commit. If this does not work, cancel. 541 cEditor.stopCellEditing(); 369 TableCellEditor cEditor = getCellEditor(); 370 if (cEditor != null) { 371 // First attempt to commit. If this does not work, cancel. 372 if (!cEditor.stopCellEditing()) { 542 373 cEditor.cancelCellEditing(); 543 374 } 544 375 } … … 545 376 } 546 377 547 378 @Override 548 public void removeEditor() { 549 // make sure we unregister our custom implementation of CellEditorRemover 550 KeyboardFocusManager.getCurrentKeyboardFocusManager(). 551 removePropertyChangeListener("permanentFocusOwner", editorRemover); 552 editorRemover = null; 553 super.removeEditor(); 554 } 555 556 @Override 557 public void removeNotify() { 558 // make sure we unregister our custom implementation of CellEditorRemover 559 KeyboardFocusManager.getCurrentKeyboardFocusManager(). 560 removePropertyChangeListener("permanentFocusOwner", editorRemover); 561 editorRemover = null; 562 super.removeNotify(); 563 } 564 565 /** 566 * This is a custom implementation of the CellEditorRemover used in JTable 567 * to handle the client property <code>terminateEditOnFocusLost</code>. 568 * 569 * This implementation also checks whether focus is transferred to one of a list 570 * of dedicated components, see {@link TagTable#doNotStopCellEditingWhenFocused}. 571 * A typical example for such a component is a button in {@link TagEditorPanel} 572 * which isn't a child component of {@link TagTable} but which should respond to 573 * to focus transfer in a similar way to a child of TagTable. 574 * 575 */ 576 class CellEditorRemover implements PropertyChangeListener { 577 private final KeyboardFocusManager focusManager; 578 579 CellEditorRemover(KeyboardFocusManager fm) { 580 this.focusManager = fm; 379 public void actionPerformed(ActionEvent e) { 380 if ("enterPressed".equals(e.getActionCommand())) { 381 // make ENTER in combobox behave like TAB 382 nextAction.actionPerformed(e); 581 383 } 582 583 @Override584 public void propertyChange(PropertyChangeEvent ev) {585 if (!isEditing())586 return;587 588 Component c = focusManager.getPermanentFocusOwner();589 while (c != null) {590 if (c == TagTable.this)591 // focus remains inside the table592 return;593 if (doNotStopCellEditingWhenFocused.contains(c))594 // focus remains on one of the associated components595 return;596 else if (c instanceof Window) {597 if (c == SwingUtilities.getRoot(TagTable.this) && !getCellEditor().stopCellEditing()) {598 getCellEditor().cancelCellEditing();599 }600 break;601 }602 c = c.getParent();603 }604 }605 384 } 606 385 } -
src/org/openstreetmap/josm/gui/tagging/ac/AutoCompComboBox.java
1 1 // License: GPL. For details, see LICENSE file. 2 2 package org.openstreetmap.josm.gui.tagging.ac; 3 3 4 import java.awt.Component; 5 import java.awt.event.MouseEvent; 4 6 import java.awt.im.InputContext; 7 import java.util.EventObject; 5 8 import java.util.Locale; 6 9 7 10 import javax.swing.ComboBoxEditor; 11 import javax.swing.JTable; 12 import javax.swing.event.CellEditorListener; 13 import javax.swing.table.TableCellEditor; 8 14 15 import org.openstreetmap.josm.gui.util.CellEditorSupport; 9 16 import org.openstreetmap.josm.gui.widgets.JosmComboBox; 10 17 import org.openstreetmap.josm.tools.Logging; 11 18 … … 21 28 * @param <E> the type of the combobox entries 22 29 * @since 18173 23 30 */ 24 public class AutoCompComboBox<E> extends JosmComboBox<E> implements AutoCompListener {31 public class AutoCompComboBox<E> extends JosmComboBox<E> implements TableCellEditor, AutoCompListener { 25 32 26 33 /** force a different keyboard input locale for the editor */ 27 34 private boolean useFixedLocale; … … 45 52 setEditable(true); 46 53 getEditorComponent().setModel(model); 47 54 getEditorComponent().addAutoCompListener(this); 55 tableCellEditorSupport = new CellEditorSupport(this); 48 56 } 49 57 50 58 /** … … 91 99 // Save the text in case item is null, because setSelectedItem will erase it. 92 100 String savedText = getText(); 93 101 setSelectedItem(item); 94 setText(savedText); 102 if (item == null) 103 setText(savedText); 95 104 } 96 105 97 106 /** … … 140 149 return super.getInputContext(); 141 150 } 142 151 143 /** AutoCompListener Interface */ 152 /* ------------------------------------------------------------------------------------ */ 153 /* AutoCompListener interface */ 154 /* ------------------------------------------------------------------------------------ */ 144 155 145 156 @Override 146 157 public void autoCompBefore(AutoCompEvent e) { … … 150 161 public void autoCompPerformed(AutoCompEvent e) { 151 162 autocomplete(e.getItem()); 152 163 } 164 165 /* ------------------------------------------------------------------------------------ */ 166 /* TableCellEditor interface */ 167 /* ------------------------------------------------------------------------------------ */ 168 169 private transient CellEditorSupport tableCellEditorSupport; 170 private String originalValue; 171 172 @Override 173 public void addCellEditorListener(CellEditorListener l) { 174 tableCellEditorSupport.addCellEditorListener(l); 175 } 176 177 protected void rememberOriginalValue(String value) { 178 this.originalValue = value; 179 } 180 181 protected void restoreOriginalValue() { 182 setText(originalValue); 183 } 184 185 @Override 186 public void removeCellEditorListener(CellEditorListener l) { 187 tableCellEditorSupport.removeCellEditorListener(l); 188 } 189 190 @Override 191 public void cancelCellEditing() { 192 restoreOriginalValue(); 193 tableCellEditorSupport.fireEditingCanceled(); 194 } 195 196 @Override 197 public Object getCellEditorValue() { 198 return getText(); 199 } 200 201 /** 202 * Returns true if <code>anEvent</code> is <b>not</b> a <code>MouseEvent</code>. Otherwise, it 203 * returns true if the necessary number of clicks have occurred, and returns false otherwise. 204 * 205 * @param anEvent the event 206 * @return true if cell is ready for editing, false otherwise 207 * @see #shouldSelectCell 208 */ 209 @Override 210 public boolean isCellEditable(EventObject anEvent) { 211 if (anEvent instanceof MouseEvent) { 212 return ((MouseEvent) anEvent).getClickCount() >= 1; 213 } 214 return true; 215 } 216 217 @Override 218 public boolean shouldSelectCell(EventObject anEvent) { 219 if (anEvent instanceof MouseEvent) { 220 MouseEvent e = (MouseEvent) anEvent; 221 return e.getID() != MouseEvent.MOUSE_DRAGGED; 222 } 223 return true; 224 } 225 226 @Override 227 public boolean stopCellEditing() { 228 tableCellEditorSupport.fireEditingStopped(); 229 return true; 230 } 231 232 @Override 233 public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { 234 setText(value == null ? "" : value.toString()); 235 rememberOriginalValue(getText()); 236 return this; 237 } 153 238 } -
src/org/openstreetmap/josm/gui/tagging/ac/AutoCompListener.java
4 4 import java.util.EventListener; 5 5 6 6 /** 7 * The listener interface for receiving autoComp events. 8 * The class that is interested in processing an autoComp event 9 * implements this interface, and the object created with that 10 * class is registered with a component, using the component's 11 * <code>addAutoCompListener</code> method. When the autoComp event 12 * occurs, that object's <code>autoCompPerformed</code> method is 13 * invoked. 7 * The listener interface for receiving AutoCompEvent events. 8 * <p> 9 * The class that is interested in processing an {@link AutoCompEvent} implements this interface, 10 * and the object created with that class is registered with an autocompleting component using the 11 * autocompleting component's {@link AutoCompTextField#addAutoCompListener addAutoCompListener} 12 * method. 13 * <p> 14 * Before the autocompletion searches for candidates, the listener's {@code autoCompBefore} method 15 * is invoked. It can be used to initialize the {@link AutoCompComboBoxModel}. After the 16 * autocompletion occured the listener's {@code autoCompPerformed} method is invoked. It is used eg. 17 * for adjusting the selection of an {@link AutoCompComboBox} after its {@link AutoCompTextField} 18 * has autocompleted. 14 19 * 15 * @see AutoCompEvent16 20 * @since 18221 17 21 */ 18 22 public interface AutoCompListener extends EventListener { -
src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletionManager.java
6 6 import java.util.Collection; 7 7 import java.util.Collections; 8 8 import java.util.Comparator; 9 import java.util.EnumSet; 9 10 import java.util.HashMap; 10 import java.util.HashSet;11 11 import java.util.LinkedHashSet; 12 12 import java.util.List; 13 13 import java.util.Map; … … 16 16 import java.util.Set; 17 17 import java.util.function.Function; 18 18 import java.util.stream.Collectors; 19 import java.util.stream.Stream; 19 20 20 21 import org.openstreetmap.josm.data.osm.DataSet; 21 22 import org.openstreetmap.josm.data.osm.OsmPrimitive; … … 39 40 import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent; 40 41 import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent; 41 42 import org.openstreetmap.josm.gui.layer.OsmDataLayer; 43 import org.openstreetmap.josm.gui.tagging.presets.Item; 44 import org.openstreetmap.josm.gui.tagging.presets.KeyedItem; 45 import org.openstreetmap.josm.gui.tagging.presets.Role; 42 46 import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset; 47 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType; 43 48 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets; 44 import org.openstreetmap.josm.gui.tagging.presets.items.Roles.Role;45 49 import org.openstreetmap.josm.tools.CheckParameterUtil; 46 50 import org.openstreetmap.josm.tools.MultiMap; 47 51 import org.openstreetmap.josm.tools.Utils; 48 52 49 53 /** 50 * AutoCompletionManager holds a cache of keys with a list of 51 * possible auto completion values foreach key.52 * 54 * AutoCompletionManager holds a cache of keys with a list of possible auto completion values for 55 * each key. 56 * <p> 53 57 * Each DataSet can be assigned one AutoCompletionManager instance such that 54 58 * <ol> 55 59 * <li>any key used in a tag in the data set is part of the key list in the cache</li> … … 56 60 * <li>any value used in a tag for a specific key is part of the autocompletion list of this key</li> 57 61 * </ol> 58 62 * 59 * Building up auto completion lists should not 60 * slow down tabbing from input field to input field. Looping through the complete 61 * data set in order to build up the auto completion list for a specific input 62 * field is not efficient enough, hence this cache. 63 * 64 * TODO: respect the relation type for member role autocompletion 63 * Building up auto completion lists should not slow down tabbing from input field to input field. 64 * Looping through the complete data set in order to build up the auto completion list for a 65 * specific input field is not efficient enough, hence this cache. 65 66 */ 66 67 public class AutoCompletionManager implements DataSetListener { 67 68 … … 105 106 } 106 107 } 107 108 109 /** 110 * Compares two AutoCompletionItems alphabetically. 111 */ 112 public static final Comparator<AutoCompletionItem> ALPHABETIC_COMPARATOR = 113 (ac1, ac2) -> String.CASE_INSENSITIVE_ORDER.compare(ac1.getValue(), ac2.getValue()); 114 108 115 /** If the dirty flag is set true, a rebuild is necessary. */ 109 116 protected boolean dirty; 110 117 /** The data set that is managed */ … … 115 122 * only accessed by getTagCache(), rebuild() and cachePrimitiveTags() 116 123 * use getTagCache() accessor 117 124 */ 118 protected MultiMap<String, String> tagCache;125 protected final MultiMap<String, String> TAG_CACHE = new MultiMap<>(); 119 126 120 127 /** 121 * the same as tagCache but for the preset keys and values can be accessed directly122 */123 static final MultiMap<String, String> PRESET_TAG_CACHE = new MultiMap<>();124 125 /**126 128 * Cache for tags that have been entered by the user. 127 129 */ 128 130 static final Set<UserInputTag> USER_INPUT_TAG_CACHE = new LinkedHashSet<>(); 129 131 130 132 /** 131 * the cached list of member roles 132 * only accessed by getRoleCache(), rebuild() and cacheRelationMemberRoles() 133 * use getRoleCache() accessor 133 * The cached relations by {@link #getRelationType(Map) relation type}. 134 134 */ 135 protected Set<String> roleCache;135 protected final MultiMap<String, Relation> RELATION_CACHE = new MultiMap<>(); 136 136 137 /**138 * the same as roleCache but for the preset roles can be accessed directly139 */140 static final Set<String> PRESET_ROLE_CACHE = new HashSet<>();141 142 137 private static final Map<DataSet, AutoCompletionManager> INSTANCES = new HashMap<>(); 143 138 139 private static final Map<String, String> EMPTY_MAP = Collections.emptyMap(); 140 144 141 /** 145 142 * Constructs a new {@code AutoCompletionManager}. 146 143 * @param ds data set … … 156 153 rebuild(); 157 154 dirty = false; 158 155 } 159 return tagCache;156 return TAG_CACHE; 160 157 } 161 158 162 protected Set<String> getRoleCache() {159 protected MultiMap<String, Relation> getRelationCache() { 163 160 if (dirty) { 164 161 rebuild(); 165 162 dirty = false; 166 163 } 167 return roleCache;164 return RELATION_CACHE; 168 165 } 169 166 170 167 /** … … 171 168 * initializes the cache from the primitives in the dataset 172 169 */ 173 170 protected void rebuild() { 174 tagCache = new MultiMap<>();175 roleCache = new HashSet<>();171 TAG_CACHE.clear(); 172 RELATION_CACHE.clear(); 176 173 cachePrimitives(ds.allNonDeletedCompletePrimitives()); 177 174 } 178 175 … … 180 177 for (OsmPrimitive primitive : primitives) { 181 178 cachePrimitiveTags(primitive); 182 179 if (primitive instanceof Relation) { 183 cacheRelationMemberRoles((Relation) primitive); 180 Relation rel = (Relation) primitive; 181 RELATION_CACHE.put(getRelationType(rel.getKeys()), rel); 184 182 } 185 183 } 186 184 } … … 192 190 * @param primitive an OSM primitive 193 191 */ 194 192 protected void cachePrimitiveTags(OsmPrimitive primitive) { 195 primitive.visitKeys((p, key, value) -> tagCache.put(key, value));193 primitive.visitKeys((p, key, value) -> TAG_CACHE.put(key, value)); 196 194 } 197 195 198 196 /** 199 * Caches all member roles of the relation <code>relation</code> 197 * Returns the relation type. 198 * <p> 199 * This is used to categorize the relations in the dataset. A relation with the keys: 200 * <ul> 201 * <li>type=route 202 * <li>route=hiking 203 * </ul> 204 * will return a relation type of {@code "route.hiking"}. 200 205 * 201 * @param relation the relation 206 * @param tags the tags on the relation 207 * @return the relation type or {@code ""} 202 208 */ 203 pr otected void cacheRelationMemberRoles(Relation relation) {204 for (RelationMember m: relation.getMembers()) {205 if (m.hasRole()) {206 roleCache.add(m.getRole());207 }208 }209 private String getRelationType(Map<String, String> tags) { 210 String type = tags.get("type"); 211 if (type == null) return ""; 212 String subtype = tags.get(type); 213 if (subtype == null) return type; 214 return type + "." + subtype; 209 215 } 210 216 211 217 /** 218 * Construct a role out of a relation member 219 * 220 * @param member the relation member 221 * @return the Role 222 */ 223 protected Role mkRole(RelationMember member) { 224 return new Role(member.getRole(), EnumSet.of(TaggingPresetType.forPrimitiveType(member.getDisplayType()))); 225 } 226 227 /** 212 228 * Remembers user input for the given key/value. 213 229 * @param key Tag key 214 230 * @param value Tag value … … 259 275 } 260 276 261 277 /** 262 * Replies the list of member roles 278 * Returns a collection of all member roles in the dataset. 279 * <p> 280 * Member roles are distinct on role name and primitive type they apply to. So there will be a 281 * role "platform" for nodes and a role "platform" for ways. 263 282 * 264 * @return the listof member roles283 * @return the collection of member roles 265 284 */ 266 public List<String> getMemberRoles() { 267 return new ArrayList<>(getRoleCache()); 285 public Set<Role> getAllMemberRoles() { 286 return getRelationCache().getAllValues().stream() 287 .flatMap(rel -> rel.getMembers().stream()).map(r -> mkRole(r)).collect(Collectors.toSet()); 268 288 } 269 289 270 290 /** 291 * Returns a collection of all roles in the dataset for one relation type. 292 * <p> 293 * Member roles are distinct on role name and primitive type they apply to. So there will be a 294 * role "platform" for nodes and a role "platform" for ways. 295 * 296 * @param relationType the {@link #getRelationType(Map) relation type} 297 * @return the collection of member roles 298 */ 299 public Set<Role> getMemberRoles(String relationType) { 300 Set<Relation> relations = getRelationCache().get(relationType); 301 if (relations == null) 302 return Collections.emptySet(); 303 return relations.stream().flatMap(rel -> rel.getMembers().stream()).map(r -> mkRole(r)).collect(Collectors.toSet()); 304 } 305 306 /** 271 307 * Populates the {@link AutoCompletionList} with the currently cached member roles. 272 308 * 273 309 * @param list the list to populate 274 310 */ 275 311 public void populateWithMemberRoles(AutoCompletionList list) { 276 list.add(TaggingPresets.getPresetRoles(), AutoCompletionPriority.IS_IN_STANDARD); 277 list.add(getRoleCache(), AutoCompletionPriority.IS_IN_DATASET); 312 list.add(TaggingPresets.getPresetRoles().stream().map(r -> r.getKey()) 313 .collect(Collectors.toList()), AutoCompletionPriority.IS_IN_STANDARD); 314 list.add(getAllMemberRoles().stream().map(role -> role.getKey()) 315 .collect(Collectors.toSet()), AutoCompletionPriority.IS_IN_DATASET); 278 316 } 279 317 280 318 /** … … 292 330 Collection<TaggingPreset> presets = r != null ? TaggingPresets.getMatchingPresets(null, r.getKeys(), false) : Collections.emptyList(); 293 331 if (r != null && !Utils.isEmpty(presets)) { 294 332 for (TaggingPreset tp : presets) { 295 if (tp.roles != null) { 296 list.add(Utils.transform(tp.roles.roles, (Function<Role, String>) x -> x.key), AutoCompletionPriority.IS_IN_STANDARD); 297 } 333 list.add(Utils.transform(tp.getAllRoles(), 334 (Function<Item, String>) x -> ((Role) x).getKey()), AutoCompletionPriority.IS_IN_STANDARD); 298 335 } 299 336 list.add(r.getMemberRoles(), AutoCompletionPriority.IS_IN_DATASET); 300 337 } else { … … 303 340 } 304 341 305 342 /** 343 * Merges two or more {@code Map<String, AutoCompletionPriority>}. The result will have the 344 * priorities merged. 345 * 346 * @param maps two or more maps to merge 347 * @return the merged map 348 */ 349 @SafeVarargs 350 public static final Map<String, AutoCompletionPriority> merge(Map<String, AutoCompletionPriority>... maps) { 351 return Stream.of(maps).flatMap(m -> m.entrySet().stream()) 352 .collect(Collectors.toMap(Entry::getKey, Entry::getValue, AutoCompletionPriority::mergeWith)); 353 } 354 355 /** 356 * Returns key suggestions for a given relation type. 357 * <p> 358 * Returns all keys in the dataset used on a given {@link #getRelationType(Map) relation type}. 359 * 360 * @param tags current tags in the tag editor panel, used to determine the relation type 361 * @return the suggestions 362 */ 363 public Map<String, AutoCompletionPriority> getKeysForRelation(Map<String, String> tags) { 364 Map<String, AutoCompletionPriority> map = new HashMap<>(); 365 Set<Relation> relations = tags != null ? getRelationCache().get(getRelationType(tags)) : getRelationCache().getAllValues(); 366 if (relations == null) 367 return map; 368 return relations.stream().flatMap(rel -> rel.getKeys().entrySet().stream()).map(e -> e.getKey()) 369 .collect(Collectors.toMap(k -> k, v -> AutoCompletionPriority.IS_IN_DATASET, AutoCompletionPriority::mergeWith)); 370 } 371 372 /** 373 * Returns value suggestions for a given relation type and key. 374 * <p> 375 * Returns all values in the dataset used with a given key on a given 376 * {@link #getRelationType(Map) relation type}. 377 * 378 * @param tags current tags in the tag editor panel, used to determine the relation type 379 * @param key the key to get values for 380 * @return the suggestions 381 */ 382 public Map<String, AutoCompletionPriority> getValuesForRelation(Map<String, String> tags, String key) { 383 Map<String, AutoCompletionPriority> map = new HashMap<>(); 384 Set<Relation> relations = tags != null ? getRelationCache().get(getRelationType(tags)) : getRelationCache().getAllValues(); 385 if (relations == null) 386 return map; 387 return relations.stream().map(rel -> rel.get(key)).filter(e -> e != null) 388 .collect(Collectors.toMap(k -> k, v -> AutoCompletionPriority.IS_IN_DATASET, AutoCompletionPriority::mergeWith)); 389 } 390 391 /** 392 * Returns role suggestions for a given relation type. 393 * <p> 394 * Returns all roles in the dataset for a given {@link TaggingPresetType role type} used with a given 395 * {@link #getRelationType(Map) relation type}. 396 * 397 * @param tags current tags in the tag editor panel, used to determine the relation type 398 * @param roleTypes all roles returned will match all of the types in this set. 399 * @return the suggestions 400 */ 401 public Map<String, AutoCompletionPriority> getRolesForRelation(Map<String, String> tags, EnumSet<TaggingPresetType> roleTypes) { 402 Map<String, AutoCompletionPriority> map = new HashMap<>(); 403 Set<Relation> relations = tags != null ? getRelationCache().get(getRelationType(tags)) : getRelationCache().getAllValues(); 404 if (relations == null) 405 return map; 406 return relations.stream().flatMap(rel -> rel.getMembers().stream()) 407 .map(member -> mkRole(member)).filter(role -> role.appliesToAll(roleTypes)) 408 .collect(Collectors.toMap(k -> k.getKey(), v -> AutoCompletionPriority.IS_IN_DATASET, AutoCompletionPriority::mergeWith)); 409 } 410 411 /** 412 * Returns all presets of type {@code types} matched by {@code tags}. 413 * 414 * @param types the preset types to include, (node / way / relation ...) or null to include all types 415 * @param tags match presets using these tags or null to match all presets 416 * @return the matched presets 417 */ 418 private Collection<TaggingPreset> getPresets(Collection<TaggingPresetType> types, Map<String, String> tags) { 419 if (tags == null) 420 tags = EMPTY_MAP; 421 422 Collection<TaggingPreset> presets = TaggingPresets.getMatchingPresets(types, tags, false); 423 if (presets.isEmpty()) { 424 presets = TaggingPresets.getTaggingPresets(); 425 } 426 return presets; 427 } 428 429 /** 430 * Returns all keys found in the presets matched by {@code tags}. 431 * 432 * @param types the preset types to include, (node / way / relation ...) or null to include all types 433 * @param tags match presets using these tags or null to match all presets 434 * @return the suggested keys 435 * @since xxx 436 */ 437 public Map<String, AutoCompletionPriority> getPresetKeys(Collection<TaggingPresetType> types, Map<String, String> tags) { 438 Map<String, AutoCompletionPriority> map = new HashMap<>(); 439 440 for (TaggingPreset preset : getPresets(types, tags)) { 441 for (Item item : preset.getAllItems()) { 442 if (item instanceof KeyedItem) { 443 map.merge(((KeyedItem) item).getKey(), AutoCompletionPriority.IS_IN_STANDARD, AutoCompletionPriority::mergeWith); 444 } 445 } 446 } 447 return map; 448 } 449 450 /** 451 * Returns all values for {@code key} found in the presets matched by {@code tags}. 452 * 453 * @param types the preset types to include, (node / way / relation ...) or null to include all types 454 * @param tags match presets using these tags or null to match all presets 455 * @param key the key to return values for 456 * @return the suggested values 457 * @since xxx 458 */ 459 public Map<String, AutoCompletionPriority> getPresetValues(Collection<TaggingPresetType> types, Map<String, String> tags, String key) { 460 Map<String, AutoCompletionPriority> map = new HashMap<>(); 461 462 for (TaggingPreset preset : getPresets(types, tags)) { 463 for (Item item : preset.getAllItems()) { 464 if (item instanceof KeyedItem) { 465 KeyedItem keyedItem = (KeyedItem) item; 466 if (keyedItem.getKey().equals(key)) { 467 for (String value : keyedItem.getValues()) { 468 map.merge(value, AutoCompletionPriority.IS_IN_STANDARD, AutoCompletionPriority::mergeWith); 469 } 470 } 471 } 472 } 473 } 474 return map; 475 } 476 477 /** 478 * Returns all roles found in the presets matched by {@code tags}. 479 * 480 * @param tags match presets using these tags or null to match all presets 481 * @param roleTypes the role types to include, (node / way / relation ...) or null to include all types 482 * @return the suggested roles 483 * @since xxx 484 */ 485 public Map<String, AutoCompletionPriority> getPresetRoles(Map<String, String> tags, Collection<TaggingPresetType> roleTypes) { 486 Map<String, AutoCompletionPriority> map = new HashMap<>(); 487 488 for (TaggingPreset preset : getPresets(EnumSet.of(TaggingPresetType.RELATION), tags)) { 489 for (Role role : preset.getAllRoles()) { 490 if (role.appliesToAll(roleTypes)) 491 map.merge(role.getKey(), AutoCompletionPriority.IS_IN_STANDARD, AutoCompletionPriority::mergeWith); 492 } 493 } 494 return map; 495 } 496 497 /** 498 * Returns all cached {@link AutoCompletionItem}s for given keys. 499 * 500 * @param keys retrieve the items for these keys 501 * @return the currently cached items, sorted by priority and alphabet 502 * @since 18221 503 */ 504 public List<AutoCompletionItem> getAllValuesForKeys(List<String> keys) { 505 Map<String, AutoCompletionPriority> map = new HashMap<>(); 506 507 for (String key : keys) { 508 for (String value : TaggingPresets.getPresetValues(key)) { 509 map.merge(value, AutoCompletionPriority.IS_IN_STANDARD, AutoCompletionPriority::mergeWith); 510 } 511 for (String value : getDataValues(key)) { 512 map.merge(value, AutoCompletionPriority.IS_IN_DATASET, AutoCompletionPriority::mergeWith); 513 } 514 for (String value : getUserInputValues(key)) { 515 map.merge(value, AutoCompletionPriority.UNKNOWN, AutoCompletionPriority::mergeWith); 516 } 517 } 518 return map.entrySet().stream().map(e -> new AutoCompletionItem(e.getKey(), e.getValue())) 519 .sorted(ALPHABETIC_COMPARATOR).collect(Collectors.toList()); 520 } 521 522 /** 306 523 * Populates the an {@link AutoCompletionList} with the currently cached tag keys 307 524 * 308 525 * @param list the list to populate … … 345 562 } 346 563 347 564 /** 348 * Returns all cached {@link AutoCompletionItem}s for given keys.349 *350 * @param keys retrieve the items for these keys351 * @return the currently cached items, sorted by priority and alphabet352 * @since 18221353 */354 public List<AutoCompletionItem> getAllForKeys(List<String> keys) {355 Map<String, AutoCompletionPriority> map = new HashMap<>();356 357 for (String key : keys) {358 for (String value : TaggingPresets.getPresetValues(key)) {359 map.merge(value, AutoCompletionPriority.IS_IN_STANDARD, AutoCompletionPriority::mergeWith);360 }361 for (String value : getDataValues(key)) {362 map.merge(value, AutoCompletionPriority.IS_IN_DATASET, AutoCompletionPriority::mergeWith);363 }364 for (String value : getUserInputValues(key)) {365 map.merge(value, AutoCompletionPriority.UNKNOWN, AutoCompletionPriority::mergeWith);366 }367 }368 return map.entrySet().stream().map(e -> new AutoCompletionItem(e.getKey(), e.getValue())).sorted().collect(Collectors.toList());369 }370 371 /**372 565 * Returns the currently cached tag keys. 373 566 * @return a set of tag keys 374 567 * @since 12859 … … 502 695 ds.removeDataSetListener(AutoCompletionManager.this); 503 696 MainApplication.getLayerManager().removeLayerChangeListener(this); 504 697 dirty = true; 505 tagCache = null;506 roleCache = null;698 TAG_CACHE.clear(); 699 RELATION_CACHE.clear(); 507 700 ds = null; 508 701 } 509 702 } -
src/org/openstreetmap/josm/gui/tagging/ac/DefaultAutoCompListener.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.ac; 3 4 import javax.swing.event.PopupMenuEvent; 5 import javax.swing.event.PopupMenuListener; 6 7 /** 8 * A default autocompletion listener. 9 * @param <E> the type of the {@code AutoCompComboBox<E>} or {@code AutoCompTextField<E>} 10 */ 11 public class DefaultAutoCompListener<E> implements AutoCompListener, PopupMenuListener { 12 protected void updateAutoCompModel(AutoCompComboBoxModel<E> model) { 13 } 14 15 @Override 16 public void autoCompBefore(AutoCompEvent e) { 17 AutoCompTextField<E> tf = toTextField(e); 18 String savedText = tf.getText(); 19 updateAutoCompModel(tf.getModel()); 20 tf.setText(savedText); 21 } 22 23 @Override 24 public void autoCompPerformed(AutoCompEvent e) { 25 } 26 27 @Override 28 public void popupMenuWillBecomeVisible(PopupMenuEvent e) { 29 AutoCompComboBox<E> cb = toComboBox(e); 30 String savedText = cb.getText(); 31 updateAutoCompModel(cb.getModel()); 32 cb.setText(savedText); 33 } 34 35 @Override 36 public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { 37 } 38 39 @Override 40 public void popupMenuCanceled(PopupMenuEvent e) { 41 } 42 43 /** 44 * Returns the AutoCompTextField that sent the request. 45 * @param e The AutoCompEvent 46 * @return the AutoCompTextField 47 */ 48 @SuppressWarnings("unchecked") 49 public AutoCompTextField<E> toTextField(AutoCompEvent e) { 50 return (AutoCompTextField<E>) e.getSource(); 51 } 52 53 /** 54 * Returns the AutoCompComboBox that sent the request. 55 * @param e The AutoCompEvent 56 * @return the AutoCompComboBox 57 */ 58 @SuppressWarnings("unchecked") 59 public AutoCompComboBox<E> toComboBox(PopupMenuEvent e) { 60 return (AutoCompComboBox<E>) e.getSource(); 61 } 62 } -
src/org/openstreetmap/josm/gui/tagging/presets/Check.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import java.util.ArrayList; 5 import java.util.Arrays; 6 import java.util.Collection; 7 import java.util.List; 8 import java.util.Map; 9 10 import javax.swing.JPanel; 11 12 import org.openstreetmap.josm.data.osm.OsmPrimitive; 13 import org.openstreetmap.josm.data.osm.OsmUtils; 14 import org.openstreetmap.josm.data.osm.Tag; 15 import org.openstreetmap.josm.gui.widgets.IconTextCheckBox; 16 import org.openstreetmap.josm.gui.widgets.QuadStateCheckBox; 17 import org.openstreetmap.josm.tools.GBC; 18 19 /** 20 * Checkbox type. 21 */ 22 final class Check extends KeyedItem { 23 24 /** the value to set when checked (default is "yes") */ 25 private final String valueOn; 26 /** the value to set when unchecked (default is "no") */ 27 private final String valueOff; 28 /** whether the off value is disabled in the dialog, i.e., only unset or yes are provided */ 29 private final boolean disableOff; 30 /** "on" or "off" or unset (default is unset) */ 31 private final String default_; // only used for tagless objects 32 33 /** 34 * Private constructor. Use {@link #fromXML} instead. 35 * @param attributes the XML attributes 36 * @throws IllegalArgumentException on illegal attributes 37 */ 38 private Check(Map<String, String> attributes) throws IllegalArgumentException { 39 super(attributes); 40 valueOn = attributes.getOrDefault("value_on", OsmUtils.TRUE_VALUE); 41 valueOff = attributes.getOrDefault("value_off", OsmUtils.FALSE_VALUE); 42 disableOff = Boolean.parseBoolean(attributes.get("disable_off")); 43 default_ = attributes.get("default"); 44 } 45 46 /** 47 * Create this class from an XML element's attributes. 48 * @param attributes the XML attributes 49 * @return the new instance 50 * @throws IllegalArgumentException on invalid attributes 51 */ 52 public static Check fromXML(Map<String, String> attributes) throws IllegalArgumentException { 53 return new Check(attributes); 54 } 55 56 @Override 57 boolean addToPanel(JPanel p, TaggingPresetInstance support) { 58 59 // find out if our key is already used in the selection. 60 final Usage usage = Usage.determineBooleanUsage(support.getSelected(), key); 61 final String oneValue = usage.map.isEmpty() ? null : usage.map.lastKey(); 62 QuadStateCheckBox.State initialState; 63 Boolean def = "on".equals(default_) ? Boolean.TRUE : "off".equals(default_) ? Boolean.FALSE : null; 64 65 if (usage.map.size() < 2 && (oneValue == null || valueOn.equals(oneValue) || valueOff.equals(oneValue))) { 66 if (def != null && !PROP_FILL_DEFAULT.get()) { 67 // default is set and filling default values feature is disabled - check if all primitives are untagged 68 for (OsmPrimitive s : support.getSelected()) { 69 if (s.hasKeys()) { 70 def = null; 71 } 72 } 73 } 74 75 // all selected objects share the same value which is either true or false or unset, 76 // we can display a standard check box. 77 initialState = valueOn.equals(oneValue) || Boolean.TRUE.equals(def) 78 ? QuadStateCheckBox.State.SELECTED 79 : valueOff.equals(oneValue) || Boolean.FALSE.equals(def) 80 ? QuadStateCheckBox.State.NOT_SELECTED 81 : QuadStateCheckBox.State.UNSET; 82 83 } else { 84 def = null; 85 // the objects have different values, or one or more objects have something 86 // else than true/false. we display a quad-state check box 87 // in "partial" state. 88 initialState = QuadStateCheckBox.State.PARTIAL; 89 } 90 91 final List<QuadStateCheckBox.State> allowedStates = new ArrayList<>(4); 92 if (QuadStateCheckBox.State.PARTIAL == initialState) 93 allowedStates.add(QuadStateCheckBox.State.PARTIAL); 94 allowedStates.add(QuadStateCheckBox.State.SELECTED); 95 if (!disableOff || valueOff.equals(oneValue)) 96 allowedStates.add(QuadStateCheckBox.State.NOT_SELECTED); 97 allowedStates.add(QuadStateCheckBox.State.UNSET); 98 99 QuadStateCheckBox check; 100 check = new QuadStateCheckBox(icon == null ? localeText : null, initialState, 101 allowedStates.toArray(new QuadStateCheckBox.State[0])); 102 check.setPropertyText(key); 103 check.setState(check.getState()); // to update the tooltip text 104 check.setComponentPopupMenu(getPopupMenu()); 105 106 if (icon != null) { 107 JPanel checkPanel = IconTextCheckBox.wrap(check, localeText, getIcon()); 108 checkPanel.applyComponentOrientation(support.getDefaultComponentOrientation()); 109 p.add(checkPanel, GBC.eol()); // Do not fill, see #15104 110 } else { 111 check.applyComponentOrientation(support.getDefaultComponentOrientation()); 112 p.add(check, GBC.eol()); // Do not fill, see #15104 113 } 114 Instance instance = new Instance(check, initialState, def); 115 support.putInstance(this, instance); 116 check.addChangeListener(l -> support.fireItemValueModified(instance, key, instance.getValue())); 117 return true; 118 } 119 120 class Instance extends Item.Instance { 121 private QuadStateCheckBox checkbox; 122 private QuadStateCheckBox.State originalState; 123 private Boolean def; 124 125 Instance(QuadStateCheckBox checkbox, QuadStateCheckBox.State originalState, Boolean def) { 126 this.checkbox = checkbox; 127 this.originalState = originalState; 128 this.def = def; 129 } 130 131 @Override 132 public void addCommands(List<Tag> changedTags) { 133 // if the user hasn't changed anything, don't create a command. 134 if (def == null && (checkbox.getState() == originalState)) return; 135 136 // otherwise change things according to the selected value. 137 changedTags.add(new Tag(key, getValue())); 138 } 139 140 private String getValue() { 141 return checkbox.getState() == QuadStateCheckBox.State.SELECTED ? valueOn : 142 checkbox.getState() == QuadStateCheckBox.State.NOT_SELECTED ? valueOff : 143 null; 144 } 145 } 146 147 @Override 148 MatchType getDefaultMatch() { 149 return MatchType.NONE; 150 } 151 152 @Override 153 public Collection<String> getValues() { 154 return disableOff ? Arrays.asList(valueOn) : Arrays.asList(valueOn, valueOff); 155 } 156 157 @Override 158 public String toString() { 159 return "Check [key=" + key + ", text=" + text + ", " 160 + (localeText != null ? "locale_text=" + localeText + ", " : "") 161 + (valueOn != null ? "value_on=" + valueOn + ", " : "") 162 + (valueOff != null ? "value_off=" + valueOff + ", " : "") 163 + "default_=" + default_ + ']'; 164 } 165 } -
src/org/openstreetmap/josm/gui/tagging/presets/CheckGroup.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import java.awt.GridBagConstraints; 5 import java.awt.GridLayout; 6 import java.util.Map; 7 8 import javax.swing.JLabel; 9 import javax.swing.JPanel; 10 11 import org.openstreetmap.josm.tools.GBC; 12 13 /** 14 * A group of {@link Check}s. 15 * @since 6114 16 */ 17 final class CheckGroup extends Container { 18 /** 19 * Number of columns (positive integer) 20 */ 21 private final int columns; 22 23 /** 24 * Private constructor. Use {@link #fromXML} instead. 25 * @param attributes the XML attributes 26 * @throws IllegalArgumentException on illegal attributes 27 */ 28 private CheckGroup(Map<String, String> attributes) throws IllegalArgumentException { 29 super(attributes); 30 columns = Integer.parseInt(attributes.getOrDefault("columns", "1")); 31 } 32 33 /** 34 * Create this class from an XML element's attributes. 35 * @param attributes the XML attributes 36 * @return the new instance 37 * @throws IllegalArgumentException on illegal attributes 38 */ 39 public static CheckGroup fromXML(Map<String, String> attributes) throws IllegalArgumentException { 40 return new CheckGroup(attributes); 41 } 42 43 @Override 44 boolean addToPanel(JPanel p, TaggingPresetInstance support) { 45 int rows = (int) Math.ceil(items.size() / ((double) columns)); 46 JPanel panel = new JPanel(new GridLayout(rows, columns)); 47 addBorder(panel); 48 49 for (Item item : items) { 50 item.addToPanel(panel, support); 51 } 52 // fill remaining cells, see #20792 53 for (int i = items.size(); i < rows * columns; i++) { 54 panel.add(new JLabel()); 55 } 56 57 panel.applyComponentOrientation(support.getDefaultComponentOrientation()); 58 p.add(panel, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); 59 return false; 60 } 61 62 @Override 63 public String toString() { 64 return "CheckGroup [columns=" + columns + ']'; 65 } 66 } -
src/org/openstreetmap/josm/gui/tagging/presets/Chunk.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import java.util.Map; 5 6 /** 7 * A collection of items to be inserted in place of a {@link Reference}. 8 */ 9 class Chunk extends Sequence { 10 private final String id; 11 12 /** 13 * Constructor. 14 * @param attributes the XML attributes 15 * @throws IllegalArgumentException on illegal attributes 16 */ 17 Chunk(Map<String, String> attributes) throws IllegalArgumentException { 18 super(attributes); 19 id = attributes.get("id"); 20 } 21 22 /** 23 * Create a {@code Chunk} from an XML element's attributes. 24 * @param attributes the XML attributes 25 * @return the {@code Chunk} 26 * @throws IllegalArgumentException on invalid attributes 27 */ 28 public static Chunk fromXML(Map<String, String> attributes) throws IllegalArgumentException { 29 return new Chunk(attributes); 30 } 31 32 @Override 33 void fixup(Map<String, Chunk> chunks, Item parent) { 34 super.fixup(chunks, parent); 35 chunks.put(getId(), this); 36 } 37 38 /** 39 * Returns the chunk id. 40 * @return the chunk id 41 */ 42 public String getId() { 43 return id; 44 } 45 46 @Override 47 public String toString() { 48 return "Chunk [id=" + id + "]"; 49 } 50 } -
src/org/openstreetmap/josm/gui/tagging/presets/CloneTaggingPresetHandler.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import java.util.Collection; 5 import java.util.List; 6 import java.util.Map; 7 import java.util.stream.Collectors; 8 9 import org.openstreetmap.josm.command.ChangePropertyCommand; 10 import org.openstreetmap.josm.data.osm.DataSet; 11 import org.openstreetmap.josm.data.osm.FilterModel; 12 import org.openstreetmap.josm.data.osm.INode; 13 import org.openstreetmap.josm.data.osm.IRelation; 14 import org.openstreetmap.josm.data.osm.IWay; 15 import org.openstreetmap.josm.data.osm.OsmPrimitive; 16 import org.openstreetmap.josm.data.osm.Tag; 17 import org.openstreetmap.josm.tools.SubclassFilteredCollection; 18 19 /** 20 * A handler that clones a selection into a new dataset. 21 * <p> 22 * Use this to apply temporary edits, eg. for the validator. 23 */ 24 public class CloneTaggingPresetHandler implements TaggingPresetHandler { 25 final DataSet ds = new DataSet(); 26 final Collection<OsmPrimitive> selection; 27 28 /** 29 * Constructor 30 * @param selection the selection of primitives to edit 31 */ 32 public CloneTaggingPresetHandler(Collection<OsmPrimitive> selection) { 33 Collection<OsmPrimitive> dependend = FilterModel.getAffectedPrimitives(selection); 34 Map<OsmPrimitive, OsmPrimitive> clonedMap = ds.clonePrimitives( 35 new SubclassFilteredCollection<>(dependend, INode.class::isInstance), 36 new SubclassFilteredCollection<>(dependend, IWay.class::isInstance), 37 new SubclassFilteredCollection<>(dependend, IRelation.class::isInstance) 38 ); 39 this.selection = selection.stream().map(p -> clonedMap.get(p)).collect(Collectors.toList()); 40 } 41 42 @Override 43 public void updateTags(List<Tag> changedTags) { 44 // we don't care about undo 45 for (Tag tag : changedTags) { 46 new ChangePropertyCommand(selection, tag.getKey(), tag.getValue()).executeCommand(); 47 } 48 } 49 50 @Override 51 public Collection<OsmPrimitive> getPrimitives() { 52 return selection; 53 } 54 } -
src/org/openstreetmap/josm/gui/tagging/presets/Combo.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 6 import java.awt.Color; 7 import java.awt.Cursor; 8 import java.awt.Dimension; 9 import java.awt.GridBagConstraints; 10 import java.awt.Insets; 11 import java.awt.event.ActionEvent; 12 import java.awt.event.ActionListener; 13 import java.awt.event.ComponentAdapter; 14 import java.awt.event.ComponentEvent; 15 import java.util.Arrays; 16 import java.util.Comparator; 17 import java.util.Map; 18 19 import javax.swing.AbstractAction; 20 import javax.swing.JButton; 21 import javax.swing.JColorChooser; 22 import javax.swing.JComponent; 23 import javax.swing.JLabel; 24 import javax.swing.JPanel; 25 26 import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem; 27 import org.openstreetmap.josm.data.tagging.ac.AutoCompletionPriority; 28 import org.openstreetmap.josm.gui.MainApplication; 29 import org.openstreetmap.josm.gui.mappaint.mapcss.CSSColors; 30 import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxEditor; 31 import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel; 32 import org.openstreetmap.josm.gui.tagging.ac.AutoCompTextField; 33 import org.openstreetmap.josm.gui.widgets.JosmComboBox; 34 import org.openstreetmap.josm.gui.widgets.OrientationAction; 35 import org.openstreetmap.josm.tools.ColorHelper; 36 import org.openstreetmap.josm.tools.GBC; 37 38 /** 39 * Combobox type. 40 */ 41 final class Combo extends ComboMultiSelect { 42 43 /** 44 * Whether the combo box is editable, which means that the user can add other values as text. 45 * Default is {@code true}. If {@code false} it is readonly, which means that the user can only select an item in the list. 46 */ 47 private final boolean editable; 48 49 /** 50 * Private constructor. Use {@link #fromXML} instead. 51 * @param attributes the XML attributes 52 * @throws IllegalArgumentException on illegal attributes 53 */ 54 private Combo(Map<String, String> attributes) throws IllegalArgumentException { 55 super(attributes); 56 editable = Boolean.parseBoolean(attributes.getOrDefault("editable", "true")); 57 } 58 59 /** 60 * Create this class from an XML element's attributes. 61 * @param attributes the XML attributes 62 * @return the new instance 63 * @throws IllegalArgumentException on invalid attributes 64 */ 65 public static Combo fromXML(Map<String, String> attributes) throws IllegalArgumentException { 66 return new Combo(attributes); 67 } 68 69 static class ComponentListener extends ComponentAdapter { 70 JosmComboBox<PresetListEntry.Instance> combobox; 71 72 ComponentListener(JosmComboBox<PresetListEntry.Instance> combobox) { 73 this.combobox = combobox; 74 } 75 76 @Override 77 public void componentResized(ComponentEvent e) { 78 // Make multi-line JLabels the correct size 79 // Only needed if there is any short_description 80 JComponent component = (JComponent) e.getSource(); 81 int width = component.getWidth(); 82 if (width == 0) 83 width = 200; 84 Insets insets = component.getInsets(); 85 width -= insets.left + insets.right + 10; 86 PresetListEntry.CellRenderer renderer = (PresetListEntry.CellRenderer) combobox.getRenderer(); 87 renderer.setWidth(width); 88 combobox.setRenderer(null); // needed to make prop change fire 89 combobox.setRenderer(renderer); 90 } 91 } 92 93 @Override 94 String getDefaultDelimiter() { 95 return ","; 96 } 97 98 private void addEntry(AutoCompComboBoxModel<PresetListEntry.Instance> model, PresetListEntry.Instance instance) { 99 if (!seenValues.containsKey(instance.getValue())) { 100 model.addElement(instance); 101 seenValues.put(instance.getValue(), instance); 102 } 103 } 104 105 @Override 106 boolean addToPanel(JPanel p, TaggingPresetInstance support) { 107 Usage usage = Usage.determineTextUsage(support.getSelected(), key); 108 seenValues.clear(); 109 110 // init the model 111 AutoCompComboBoxModel<PresetListEntry.Instance> dropDownModel = 112 new AutoCompComboBoxModel<>(Comparator.<PresetListEntry.Instance>naturalOrder()); 113 JosmComboBox<PresetListEntry.Instance> combobox = new JosmComboBox<>(dropDownModel); 114 Instance instance = new Instance(combobox, usage); 115 116 if (!usage.hasUniqueValue() && !usage.unused()) { 117 addEntry(dropDownModel, PresetListEntry.ENTRY_DIFFERENT.newInstance(instance)); 118 } 119 presetListEntries.forEach(e -> addEntry(dropDownModel, e.newInstance(instance))); 120 if (default_ != null) { 121 addEntry(dropDownModel, new PresetListEntry(this, default_).newInstance(instance)); 122 } 123 addEntry(dropDownModel, PresetListEntry.ENTRY_EMPTY.newInstance(instance)); 124 125 usage.map.forEach((value, count) -> { 126 addEntry(dropDownModel, new PresetListEntry(this, value).newInstance(instance)); 127 }); 128 129 AutoCompComboBoxEditor<AutoCompletionItem> editor = new AutoCompComboBoxEditor<>(); 130 combobox.setEditor(editor); 131 132 // The default behaviour of JComboBox is to size the editor according to the tallest item in 133 // the dropdown list. We don't want that to happen because we want to show taller items in 134 // the list than in the editor. We can't use 135 // {@code combobox.setPrototypeDisplayValue(PresetListEntry.ENTRY_EMPTY);} because that would 136 // set a fixed cell height in JList. 137 combobox.setPreferredHeight(combobox.getPreferredSize().height); 138 139 // a custom cell renderer capable of displaying a short description text along with the 140 // value 141 combobox.setRenderer(new PresetListEntry.CellRenderer(combobox, combobox.getRenderer(), 200)); 142 combobox.setEditable(editable); 143 144 AutoCompComboBoxModel<AutoCompletionItem> autoCompModel; 145 autoCompModel = new AutoCompComboBoxModel<>(Comparator.<AutoCompletionItem>naturalOrder()); 146 TaggingPresetUtils.getAllForKeys(Arrays.asList(key)).forEach(autoCompModel::addElement); 147 getDisplayValues().forEach(s -> autoCompModel.addElement(new AutoCompletionItem(s, AutoCompletionPriority.IS_IN_STANDARD))); 148 149 AutoCompTextField<AutoCompletionItem> tf = editor.getEditorComponent(); 150 tf.setModel(autoCompModel); 151 152 if (Item.DISPLAY_KEYS_AS_HINT.get()) { 153 combobox.setHint(key); 154 } 155 if (length > 0) { 156 tf.setMaxTextLength(length); 157 } 158 159 support.putInstance(this, instance); 160 161 JLabel label = addLabel(p); 162 163 if (key != null && ("colour".equals(key) || key.startsWith("colour:") || key.endsWith(":colour"))) { 164 p.add(combobox, GBC.std().fill(GridBagConstraints.HORIZONTAL)); 165 JButton button = new JButton(new ChooseColorAction(instance)); 166 button.setOpaque(true); 167 button.setBorderPainted(false); 168 Dimension size = combobox.getPreferredSize(); 169 button.setPreferredSize(new Dimension(size.height, size.height)); 170 button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); 171 p.add(button, GBC.eol()); 172 ActionListener updateColor = ignore -> button.setBackground(instance.getColor()); 173 updateColor.actionPerformed(null); 174 combobox.addActionListener(updateColor); 175 } else { 176 p.add(combobox, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); 177 } 178 179 String initialValue = instance.getInitialValue(usage, support); 180 PresetListEntry.Instance selItem = instance.find(initialValue); 181 if (selItem != null) { 182 combobox.setSelectedItem(selItem); 183 } else { 184 combobox.setText(initialValue); 185 } 186 187 combobox.addActionListener(l -> support.fireItemValueModified(instance, key, instance.getSelectedItem().getValue())); 188 combobox.addComponentListener(new ComponentListener(combobox)); 189 190 label.setLabelFor(combobox); 191 combobox.setToolTipText(getKeyTooltipText()); 192 combobox.applyComponentOrientation(OrientationAction.getValueOrientation(key)); 193 194 seenValues.clear(); 195 return true; 196 } 197 198 class Instance extends ComboMultiSelect.Instance { 199 JosmComboBox<PresetListEntry.Instance> combobox; 200 201 Instance(JosmComboBox<PresetListEntry.Instance> combobox, Usage usage) { 202 super(usage); 203 this.combobox = combobox; 204 } 205 206 /** 207 * Returns the value selected in the combobox or a synthetic value if a multiselect. 208 * 209 * @return the value 210 */ 211 @Override 212 PresetListEntry.Instance getSelectedItem() { 213 Object sel = combobox.getSelectedItem(); 214 if (sel instanceof PresetListEntry.Instance) 215 // selected from the dropdown 216 return (PresetListEntry.Instance) sel; 217 if (sel instanceof String) { 218 // free edit. If the free edit corresponds to a known entry, use that entry. This is 219 // to avoid that we write a display_value to the tag's value, eg. if the user did an 220 // undo. 221 PresetListEntry.Instance selItem = find((String) sel); 222 if (selItem != null) 223 return selItem; 224 return new PresetListEntry(Combo.this, (String) sel).newInstance(this); 225 } 226 return PresetListEntry.ENTRY_EMPTY.newInstance(this); 227 } 228 229 /** 230 * Finds the PresetListEntry that matches value. 231 * <p> 232 * Looks in the model of the combobox for an element whose {@code value} matches {@code value}. 233 * 234 * @param value The value to match. 235 * @return The entry or null 236 */ 237 PresetListEntry.Instance find(String value) { 238 return combobox.getModel().asCollection().stream().filter(o -> o.getValue().equals(value)).findAny().orElse(null); 239 } 240 241 void setColor(Color color) { 242 if (color != null) { 243 combobox.setSelectedItem(ColorHelper.color2html(color)); 244 } 245 } 246 247 Color getColor() { 248 String colorString = getSelectedItem().getValue(); 249 return colorString.startsWith("#") 250 ? ColorHelper.html2color(colorString) 251 : CSSColors.get(colorString); 252 } 253 } 254 255 static class ChooseColorAction extends AbstractAction { 256 private Instance instance; 257 258 ChooseColorAction(Instance instance) { 259 this.instance = instance; 260 putValue(SHORT_DESCRIPTION, tr("Choose a color")); 261 } 262 263 @Override 264 public void actionPerformed(ActionEvent e) { 265 Color color = instance.getColor(); 266 color = JColorChooser.showDialog(MainApplication.getMainPanel(), tr("Choose a color"), color); 267 instance.setColor(color); 268 } 269 } 270 } -
src/org/openstreetmap/josm/gui/tagging/presets/ComboMultiSelect.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 6 import java.lang.reflect.Method; 7 import java.lang.reflect.Modifier; 8 import java.util.ArrayList; 9 import java.util.Arrays; 10 import java.util.Collection; 11 import java.util.Collections; 12 import java.util.HashMap; 13 import java.util.HashSet; 14 import java.util.List; 15 import java.util.Map; 16 import java.util.Set; 17 import java.util.stream.Collectors; 18 19 import javax.swing.JLabel; 20 import javax.swing.JPanel; 21 22 import org.openstreetmap.josm.data.osm.Tag; 23 import org.openstreetmap.josm.gui.widgets.OrientationAction; 24 import org.openstreetmap.josm.tools.AlphanumComparator; 25 import org.openstreetmap.josm.tools.GBC; 26 import org.openstreetmap.josm.tools.Logging; 27 28 /** 29 * Abstract superclass for combo box and multi-select list types. 30 */ 31 public abstract class ComboMultiSelect extends KeyedItem { 32 /** The context used for translating values */ 33 final String valuesContext; 34 /** Disabled internationalisation for value to avoid mistakes, see #11696 */ 35 final boolean valuesNoI18n; 36 /** The default value for the item. If not specified, the current value of the key is chosen as default (if applicable).*/ 37 final String default_; 38 /** Whether to sort the values, defaults to true. */ 39 private final boolean valuesSort; 40 /** Whether to offer display values for search via {@link TaggingPresetSelector} */ 41 private final boolean valuesSearchable; 42 /** 43 * The character that separates values. 44 * In case of {@link Combo} the default is comma. 45 */ 46 final char delimiter; 47 48 /** 49 * The standard entries in the combobox dropdown or multiselect list. These entries are defined 50 * in {@code defaultpresets.xml} (or in other custom preset files). 51 */ 52 final List<PresetListEntry> presetListEntries = new ArrayList<>(); 53 54 /** Helps avoid duplicate list entries */ 55 final Map<String, PresetListEntry.Instance> seenValues = new HashMap<>(); 56 57 private Set<String> valuesSet; 58 private Set<String> displayValuesSet = new HashSet<>(); 59 60 /** 61 * Constructor. 62 * @param attributes the XML attributes 63 * @throws IllegalArgumentException on illegal attributes 64 */ 65 ComboMultiSelect(Map<String, String> attributes) throws IllegalArgumentException { 66 super(attributes); 67 valuesContext = attributes.get("values_context"); 68 valuesNoI18n = TaggingPresetUtils.parseBoolean(attributes.get("values_no_i18n")); 69 valuesSort = TaggingPresetUtils.parseBoolean(attributes.getOrDefault("values_sort", "on")); 70 valuesSearchable = TaggingPresetUtils.parseBoolean(attributes.get("values_searchable")); 71 default_ = attributes.get("default"); 72 delimiter = attributes.getOrDefault("delimiter", getDefaultDelimiter()).charAt(0); 73 74 initPresetListEntries(attributes); 75 } 76 77 @Override 78 void endElement() { 79 super.endElement(); 80 if (valuesSort && TaggingPresets.SORT_VALUES.get()) { 81 Collections.sort(presetListEntries, 82 (a, b) -> AlphanumComparator.getInstance().compare(a.getDisplayValue(this), b.getDisplayValue(this))); 83 } 84 valuesSet = presetListEntries.stream().map(PresetListEntry::getValue).collect(Collectors.toSet()); 85 if (valuesSearchable) { 86 displayValuesSet = presetListEntries.stream().map(e -> e.getDisplayValue(this)).collect(Collectors.toSet()); 87 } 88 } 89 90 @Override 91 public Set<String> getValues() { 92 return valuesSet; 93 } 94 95 /** 96 * Returns the values to display. 97 * @return the values to display 98 */ 99 public Set<String> getDisplayValues() { 100 return displayValuesSet; 101 } 102 103 /** 104 * Returns the default delimiter used in multi-value attributes. 105 * @return the default delimiter 106 */ 107 abstract String getDefaultDelimiter(); 108 109 /** 110 * Adds the label to the panel 111 * 112 * @param p the panel 113 * @return the label 114 */ 115 JLabel addLabel(JPanel p) { 116 final JLabel label = new JLabel(tr("{0}:", localeText)); 117 addIcon(label); 118 label.setToolTipText(getKeyTooltipText()); 119 label.setComponentPopupMenu(getPopupMenu()); 120 label.applyComponentOrientation(OrientationAction.getDefaultComponentOrientation()); 121 p.add(label, GBC.std().insets(0, 0, 10, 0)); 122 return label; 123 } 124 125 private List<String> getValuesFromCode(String valuesFrom) { 126 // get the values from a Java function 127 String[] classMethod = valuesFrom.split("#", -1); 128 if (classMethod.length == 2) { 129 try { 130 Method method = Class.forName(classMethod[0]).getMethod(classMethod[1]); 131 // ComboMultiSelect method is public static String[] methodName() 132 int mod = method.getModifiers(); 133 if (Modifier.isPublic(mod) && Modifier.isStatic(mod) 134 && method.getReturnType().equals(String[].class) && method.getParameterTypes().length == 0) { 135 return Arrays.asList((String[]) method.invoke(null)); 136 } else { 137 Logging.error(tr("Broken tagging preset \"{0}-{1}\" - Java method given in ''values_from'' is not \"{2}\"", key, text, 138 "public static String[] methodName()")); 139 } 140 } catch (ReflectiveOperationException e) { 141 Logging.error(tr("Broken tagging preset \"{0}-{1}\" - Java method given in ''values_from'' threw {2} ({3})", key, text, 142 e.getClass().getName(), e.getMessage())); 143 Logging.debug(e); 144 } 145 } 146 return null; // NOSONAR 147 } 148 149 /** 150 * Checks if list {@code a} is either null or the same length as list {@code b}. 151 * 152 * @param a The list to check 153 * @param b The other list 154 * @param name The name of the list for error reporting 155 * @return {@code a} if both lists have the same length or {@code null} 156 */ 157 private List<String> checkListsSameLength(List<String> a, List<String> b, String name) { 158 if (a != null && a.size() != b.size()) { 159 Logging.error(tr("Broken tagging preset \"{0}-{1}\" - number of items in ''{2}'' must be the same as in ''values''", 160 key, text, name)); 161 Logging.error(tr("Detailed information: {0} <> {1}", a, b)); 162 return null; // NOSONAR 163 } 164 return a; 165 } 166 167 private void initPresetListEntries(Map<String, String> attributes) { 168 /** 169 * A list of entries. 170 * The list has to be separated by commas (for the {@link Combo} box) or by the specified delimiter (for the {@link MultiSelect}). 171 * If a value contains the delimiter, the delimiter may be escaped with a backslash. 172 * If a value contains a backslash, it must also be escaped with a backslash. */ 173 final String values = attributes.get("values"); 174 /** 175 * To use instead of {@link #values} if the list of values has to be obtained with a Java method of this form: 176 * <p>{@code public static String[] getValues();}<p> 177 * The value must be: {@code full.package.name.ClassName#methodName}. 178 */ 179 final String valuesFrom = attributes.get("values_from"); 180 /** 181 * A list of entries that is displayed to the user. 182 * Must be the same number and order of entries as {@link #values} and editable must be false or not specified. 183 * For the delimiter character and escaping, see the remarks at {@link #values}. 184 */ 185 final String displayValues = attributes.get("display_values"); 186 /** The localized version of {@link #displayValues}. */ 187 final String localeDisplayValues = attributes.get("locale_display_values"); 188 /** 189 * A delimiter-separated list of texts to be displayed below each {@code display_value}. 190 * (Only if it is not possible to describe the entry in 2-3 words.) 191 * Instead of comma separated list instead using {@link #values}, {@link #displayValues} and {@link #shortDescriptions}, 192 * the following form is also supported:<p> 193 * {@code <list_entry value="" display_value="" short_description="" icon="" icon_size="" />} 194 */ 195 final String shortDescriptions = attributes.get("short_descriptions"); 196 /** The localized version of {@link #shortDescriptions}. */ 197 final String localeShortDescriptions = attributes.get("locale_short_descriptions"); 198 199 List<String> valueList = null; 200 List<String> displayList = null; 201 List<String> localeDisplayList = null; 202 203 if (valuesFrom != null) { 204 valueList = getValuesFromCode(valuesFrom); 205 } 206 207 if (valueList == null) { 208 // get from {@code values} attribute 209 valueList = TaggingPresetUtils.splitEscaped(delimiter, values); 210 } 211 if (valueList == null) { 212 return; 213 } 214 215 if (!valuesNoI18n) { 216 localeDisplayList = TaggingPresetUtils.splitEscaped(delimiter, localeDisplayValues); 217 displayList = TaggingPresetUtils.splitEscaped(delimiter, displayValues); 218 } 219 List<String> localeShortDescriptionsList = TaggingPresetUtils.splitEscaped(delimiter, localeShortDescriptions); 220 List<String> shortDescriptionsList = TaggingPresetUtils.splitEscaped(delimiter, shortDescriptions); 221 222 displayList = checkListsSameLength(displayList, valueList, "display_values"); 223 localeDisplayList = checkListsSameLength(localeDisplayList, valueList, "locale_display_values"); 224 shortDescriptionsList = checkListsSameLength(shortDescriptionsList, valueList, "short_descriptions"); 225 localeShortDescriptionsList = checkListsSameLength(localeShortDescriptionsList, valueList, "locale_short_descriptions"); 226 227 for (int i = 0; i < valueList.size(); i++) { 228 Map<String, String> attribs = new HashMap<>(); 229 attribs.put("value", valueList.get(i)); 230 if (displayList != null) 231 attribs.put("display_value", displayList.get(i)); 232 if (localeDisplayList != null) 233 attribs.put("locale_display_value", localeDisplayList.get(i)); 234 if (shortDescriptionsList != null) 235 attribs.put("short_description", shortDescriptionsList.get(i)); 236 if (localeShortDescriptionsList != null) 237 attribs.put("locale_short_description", localeShortDescriptionsList.get(i)); 238 addItem(PresetListEntry.fromXML(attribs)); 239 } 240 } 241 242 abstract class Instance extends Item.Instance { 243 /** 244 * Used to determine if the user has edited the value. This is not the same as the initial value 245 * shown in the component. The original value is the state of the data before the edit. The initial 246 * value may already be an edit suggested by the software. 247 */ 248 String originalValue; 249 Usage usage; 250 251 Instance(Usage usage) { 252 this.usage = usage; 253 } 254 255 ComboMultiSelect getTemplate() { 256 return ComboMultiSelect.this; 257 } 258 259 @Override 260 public void addCommands(List<Tag> changedTags) { 261 String value = getSelectedItem().getValue(); 262 263 // no change if same as before 264 if (value.equals(originalValue)) 265 return; 266 changedTags.add(new Tag(key, value)); 267 268 if (isUseLastAsDefault()) { 269 LAST_VALUES.put(key, value); 270 } 271 } 272 273 /** 274 * Returns the value selected in the combobox or a synthetic value if a multiselect. 275 * 276 * @return the value 277 */ 278 abstract PresetListEntry.Instance getSelectedItem(); 279 280 /** 281 * Returns the initial value to use for this preset. 282 * <p> 283 * The initial value is the value shown in the control when the preset dialog opens. For a 284 * discussion of all the options see the enclosed tickets. 285 * 286 * @param usage The key Usage 287 * @param support The support 288 * @return The initial value to use. 289 * 290 * @see "https://josm.openstreetmap.de/ticket/5564" 291 * @see "https://josm.openstreetmap.de/ticket/12733" 292 * @see "https://josm.openstreetmap.de/ticket/17324" 293 */ 294 String getInitialValue(Usage usage, TaggingPresetInstance support) { 295 String initialValue = null; 296 originalValue = ""; 297 298 if (usage.hasUniqueValue()) { 299 // all selected primitives have the same not empty value for this key 300 initialValue = usage.getFirst(); 301 originalValue = initialValue; 302 } else if (!usage.unused()) { 303 // at least one primitive has a value for this key (but not all have the same one) 304 initialValue = DIFFERENT; 305 originalValue = initialValue; 306 } else if (!usage.hadKeys() || isForceUseLastAsDefault() || PROP_FILL_DEFAULT.get()) { 307 // at this point no primitive had any value for this key 308 if (!support.isPresetInitiallyMatches() && isUseLastAsDefault() && LAST_VALUES.containsKey(key)) { 309 initialValue = LAST_VALUES.get(key); 310 } else { 311 initialValue = default_; 312 } 313 } 314 return initialValue != null ? initialValue : ""; 315 } 316 } 317 318 /** 319 * Adds a preset list entry. 320 * @param e list entry to add 321 */ 322 @Override 323 void addItem(Item e) { 324 if (e instanceof PresetListEntry) 325 presetListEntries.add((PresetListEntry) e); 326 } 327 328 /** 329 * Adds a collection of preset list entries. 330 * @param e list entries to add 331 */ 332 void addListEntries(Collection<PresetListEntry> e) { 333 for (PresetListEntry i : e) { 334 addItem(i); 335 } 336 } 337 338 @Override 339 MatchType getDefaultMatch() { 340 return MatchType.NONE; 341 } 342 } -
src/org/openstreetmap/josm/gui/tagging/presets/Container.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import java.awt.GridBagConstraints; 5 import java.awt.GridBagLayout; 6 import java.util.Map; 7 8 import javax.swing.BorderFactory; 9 import javax.swing.JPanel; 10 import javax.swing.border.Border; 11 import javax.swing.border.CompoundBorder; 12 13 import org.openstreetmap.josm.tools.GBC; 14 15 /** 16 * A sequence of {@link Item}s in a panel. The panel may have a title and border. 17 */ 18 public class Container extends Sequence { 19 /** The text to display */ 20 final String text; 21 /** The context used for translating {@link #text} */ 22 final String textContext; 23 /** The localized version of {@link #text} */ 24 final String localeText; 25 26 /** 27 * Constructor. 28 * @param attributes the XML attributes 29 * @throws IllegalArgumentException on illegal attributes 30 */ 31 Container(Map<String, String> attributes) throws IllegalArgumentException { 32 super(attributes); 33 String t = attributes.get("text"); 34 text = t != null ? t : getDefaultText(); 35 textContext = attributes.get("text_context"); 36 localeText = TaggingPresetUtils.buildLocaleString(attributes.get("locale_text"), text, textContext); 37 } 38 39 /** 40 * Create this class from an XML element's attributes. 41 * @param attributes the XML attributes 42 * @return the new instance 43 * @throws IllegalArgumentException on illegal attributes 44 */ 45 public static Container fromXML(Map<String, String> attributes) throws IllegalArgumentException { 46 return new Container(attributes); 47 } 48 49 String getDefaultText() { 50 return null; 51 } 52 53 void addBorder(JPanel panel) { 54 Border margin = BorderFactory.createEmptyBorder(10, 0, 0, 0); 55 if (localeText != null) { 56 Border border = BorderFactory.createTitledBorder(localeText); 57 Border padding = BorderFactory.createEmptyBorder(10, 10, 10, 10); 58 margin = new CompoundBorder(margin, new CompoundBorder(border, padding)); 59 } 60 panel.setBorder(margin); 61 } 62 63 JPanel getPanel() { 64 JPanel panel = new JPanel(new GridBagLayout()); 65 addBorder(panel); 66 return panel; 67 } 68 69 @Override 70 boolean addToPanel(JPanel p, TaggingPresetInstance support) { 71 if (items.isEmpty()) 72 // do not add an empty panel 73 return false; 74 JPanel panel = getPanel(); 75 super.addToPanel(panel, support); 76 panel.applyComponentOrientation(support.getDefaultComponentOrientation()); 77 p.add(panel, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); 78 return false; 79 } 80 81 @Override 82 public String toString() { 83 return "Container"; 84 } 85 } -
src/org/openstreetmap/josm/gui/tagging/presets/DataSetTaggingPresetHandler.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 6 import java.util.Collection; 7 import java.util.List; 8 9 import org.openstreetmap.josm.command.ChangePropertyCommand; 10 import org.openstreetmap.josm.command.Command; 11 import org.openstreetmap.josm.command.SequenceCommand; 12 import org.openstreetmap.josm.data.UndoRedoHandler; 13 import org.openstreetmap.josm.data.osm.DataSet; 14 import org.openstreetmap.josm.data.osm.OsmPrimitive; 15 import org.openstreetmap.josm.data.osm.Tag; 16 import org.openstreetmap.josm.tools.StreamUtils; 17 18 /** 19 * A TaggingPresetHandler that operates on a DataSet. 20 */ 21 public class DataSetTaggingPresetHandler implements TaggingPresetHandler { 22 Collection<OsmPrimitive> selection; 23 24 DataSetTaggingPresetHandler(DataSet dataSet) { 25 this.selection = dataSet.getSelected(); 26 } 27 28 /** 29 * Constructor 30 * @param selection the selection of primitives to edit 31 */ 32 public DataSetTaggingPresetHandler(Collection<OsmPrimitive> selection) { 33 this.selection = selection; 34 } 35 36 @Override 37 public void updateTags(List<Tag> changedTags) { 38 Command cmd = createCommand(selection, changedTags); 39 if (cmd != null) { 40 UndoRedoHandler.getInstance().add(cmd); 41 } 42 } 43 44 @Override 45 public Collection<OsmPrimitive> getPrimitives() { 46 return selection; 47 } 48 49 /** 50 * Create a command to change the given list of tags. 51 * @param sel The primitives to change the tags for 52 * @param changedTags The tags to change 53 * @return A command that changes the tags. 54 */ 55 public static Command createCommand(Collection<OsmPrimitive> sel, List<Tag> changedTags) { 56 List<Command> cmds = changedTags.stream() 57 .map(tag -> new ChangePropertyCommand(sel, tag.getKey(), tag.getValue())) 58 .filter(cmd -> cmd.getObjectsNumber() > 0) 59 .collect(StreamUtils.toUnmodifiableList()); 60 return cmds.isEmpty() ? null : SequenceCommand.wrapIfNeeded(tr("Change Tags"), cmds); 61 } 62 } -
src/org/openstreetmap/josm/gui/tagging/presets/Item.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import java.util.List; 5 import java.util.Map; 6 import java.util.function.Predicate; 7 8 import javax.swing.JMenu; 9 import javax.swing.JPanel; 10 11 import org.openstreetmap.josm.data.osm.OsmPrimitive; 12 import org.openstreetmap.josm.data.osm.Tag; 13 import org.openstreetmap.josm.data.preferences.BooleanProperty; 14 15 /** 16 * Template class for preset dialog construction. 17 * <p> 18 * This class and all its subclasses are immutable. They basically are in-memory representations of 19 * a {@code presets.xml} file. Their main use is as templates to 20 * {@link #addToPanel create the swing components} of a preset dialog. 21 * <p> 22 * To every Item class there is a companion class {@link Instance} that holds all mutable data. An 23 * Instance is created along with every active component and registered with the preset 24 * {@link TaggingPresetInstance preset instance}. The preset dialog calls the registered item 25 * instances {@link Instance#addCommands to save the user edits}. 26 * <p> 27 * All data access goes through the {@link TaggingPresetHandler}. By plugging in a different 28 * handler, the preset dialog can edit the JOSM {@code DataSet} or any other key/value data store. 29 * 30 * @since 6068 31 */ 32 public abstract class Item { 33 /** 34 * Display OSM keys as {@linkplain org.openstreetmap.josm.gui.widgets.OsmIdTextField#setHint hint} 35 */ 36 static final BooleanProperty DISPLAY_KEYS_AS_HINT = new BooleanProperty("taggingpreset.display-keys-as-hint", true); 37 38 /** 39 * Constructor. 40 * @param attributes the XML attributes 41 * @throws IllegalArgumentException on illegal attributes 42 */ 43 Item(Map<String, String> attributes) throws IllegalArgumentException { 44 } 45 46 /** 47 * Companion class to hold mutable data. 48 * <p> 49 * An instance of this class will be created by any template class that needs mutable instance 50 * data. 51 */ 52 abstract static class Instance { 53 /** 54 * Called by {@link TaggingPreset#getChangedTags} to collect changes. If the value in this 55 * item was changed by the user, this method should add its key and value to the list. 56 * 57 * @param changedTags The list to add to. 58 */ 59 public void addCommands(List<Tag> changedTags) { 60 } 61 } 62 63 /** 64 * for use of {@link TaggingPresetReader} only 65 * @param s the content to set 66 */ 67 void setContent(String s) { 68 } 69 70 /** 71 * for use of {@link TaggingPresetReader} only 72 */ 73 void endElement() { 74 } 75 76 /** 77 * Adds an item to the container 78 * @param item the item to add 79 */ 80 void addItem(Item item) { 81 // nothing to do as I'm no container 82 } 83 84 /** 85 * Called before item is removed. 86 * <p> 87 * Use to remove listeners, etc. 88 */ 89 void destroy() { 90 } 91 92 /** 93 * Creates the Swing components for this preset item and adds them to the panel. 94 * 95 * @param p The panel where components must be added 96 * @param instance The preset instance 97 * @return {@code true} if this item adds semantic tagging elements, {@code false} otherwise. 98 */ 99 boolean addToPanel(JPanel p, TaggingPresetInstance instance) { 100 return false; 101 } 102 103 /** 104 * Adds this item to the menu if it is a preset item. 105 * <p> 106 * This is overridden in {@link TaggingPreset} and descendants. 107 * @param parentMenu the parent menu 108 */ 109 public void addToMenu(JMenu parentMenu) { 110 } 111 112 /** 113 * When this function is called, the item should add itself to the list if it satisfies the 114 * predicate. If the item is a sequence it should also ask its children to do the same. 115 * 116 * @param list the list to add to 117 * @param p a predicate all added items must satisfy 118 * @param followReferences whether to follow references or not 119 */ 120 void addToItemList(List<Item> list, Predicate<Item> p, boolean followReferences) { 121 if (p.test(this)) 122 list.add(this); 123 } 124 125 /** 126 * When this function is called, the item should add itself to the list if it is an instance of 127 * {@code type}. If the item is a sequence it should also ask its children to do the same. 128 * 129 * @param <E> the type 130 * @param list the list to add to 131 * @param type the type 132 * @param followReferences whether to follow references or not 133 */ 134 <E> void addToItemList(List<E> list, Class<E> type, boolean followReferences) { 135 if (type.isInstance(this)) 136 list.add(type.cast(this)); 137 } 138 139 /** 140 * Various fixups after the whole xml file has been read. 141 * <p> 142 * If you are a chunk, add yourself to the map. If you are a reference, save the map for later. 143 * 144 * @param chunks the chunks map 145 * @param parent the parent item 146 */ 147 void fixup(Map<String, Chunk> chunks, Item parent) { 148 } 149 150 /** 151 * Tests whether the tags match this item. 152 * Note that for a match, at least one positive and no negative is required. 153 * @param tags the tags of an {@link OsmPrimitive} 154 * @return {@code true} if matches (positive), {@code null} if neutral, {@code false} if mismatches (negative). 155 */ 156 public Boolean matches(Map<String, String> tags) { 157 return null; // NOSONAR 158 } 159 } -
src/org/openstreetmap/josm/gui/tagging/presets/ItemFactory.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 6 import java.text.MessageFormat; 7 import java.util.HashMap; 8 import java.util.Map; 9 10 import org.openstreetmap.josm.tools.TextTagParser; 11 12 /** 13 * A factory for preset items. 14 */ 15 public final class ItemFactory { 16 @FunctionalInterface 17 private interface FromXML { 18 Item apply(Map<String, String> attributes) throws IllegalArgumentException; 19 } 20 21 /** The map from XML element name to template class constructor. */ 22 private static Map<String, FromXML> fromXML = buildMap(); 23 24 private ItemFactory() { 25 } 26 27 /** 28 * The factory method. 29 * 30 * @param localname build a preset item of this kind 31 * @param attributes the attributes of the item 32 * @return the item 33 */ 34 public static Item build(String localname, Map<String, String> attributes) { 35 FromXML f = fromXML.get(localname); 36 Item item = null; 37 if (f != null) 38 item = f.apply(attributes); 39 if (item == null) 40 throw new IllegalArgumentException(tr("Unknown element {0}", localname)); 41 return item; 42 } 43 44 /** 45 * A convenience factory method to ease testing. 46 * 47 * @param text The text describing the item in message format, eg. 48 * {@code key key=highway value=primary} or {@code checkgroup columns=4} 49 * @param objects parameters to message format 50 * @return a new item 51 */ 52 public static Item build(String text, Object... objects) { 53 final String[] arr = MessageFormat.format(text, objects).split("\\s+", 2); 54 String localname = arr[0]; 55 Map<String, String> attributes = new HashMap<>(); 56 if (arr.length > 1) 57 attributes = TextTagParser.readTagsFromText(arr[1]); 58 return build(localname, attributes); 59 } 60 61 private static Map<String, FromXML> buildMap() { 62 // CHECKSTYLE.OFF: SingleSpaceSeparator 63 Map<String, FromXML> map = new HashMap<>(); 64 map.put("chunk", (FromXML) Chunk::fromXML); 65 map.put("reference", (FromXML) Reference::fromXML); 66 67 map.put("presets", (FromXML) Root::fromXML); 68 map.put("group", (FromXML) TaggingPresetMenu::fromXML); 69 map.put("item", (FromXML) TaggingPreset::fromXML); 70 map.put("separator", (FromXML) TaggingPresetSeparator::fromXML); 71 72 map.put("check", (FromXML) Check::fromXML); 73 map.put("checkgroup", (FromXML) CheckGroup::fromXML); 74 map.put("combo", (FromXML) Combo::fromXML); 75 map.put("container", (FromXML) Container::fromXML); 76 map.put("item_separator", (FromXML) ItemSeparator::fromXML); 77 map.put("key", (FromXML) Key::fromXML); 78 map.put("label", (FromXML) Label::fromXML); 79 map.put("link", (FromXML) Link::fromXML); 80 map.put("list_entry", (FromXML) PresetListEntry::fromXML); 81 map.put("multiselect", (FromXML) MultiSelect::fromXML); 82 map.put("optional", (FromXML) Optional::fromXML); 83 map.put("preset_link", (FromXML) PresetLink::fromXML); 84 map.put("role", (FromXML) Role::fromXML); 85 map.put("roles", (FromXML) Roles::fromXML); 86 map.put("sequence", (FromXML) Sequence::fromXML); 87 map.put("space", (FromXML) Space::fromXML); 88 map.put("text", (FromXML) Text::fromXML); 89 // CHECKSTYLE.ON: SingleSpaceSeparator 90 return map; 91 } 92 93 /** 94 * Prepares an attribute map like the one used by the XML parser. 95 * 96 * @param attributes the attributes to set eg. ("key", "highway", "value", "primary") 97 * @return a map suitable to be passed to {@code fromXML}. 98 * @throws IllegalArgumentException on error in the attributes 99 */ 100 static Map<String, String> attributesToMap(String... attributes) throws IllegalArgumentException { 101 if (attributes.length % 2 != 0) 102 throw new IllegalArgumentException(); 103 Map<String, String> map = new HashMap<>(); 104 for (int i = 0; i < attributes.length; i += 2) { 105 map.put(attributes[i], attributes[i + 1]); 106 } 107 return map; 108 } 109 } -
src/org/openstreetmap/josm/gui/tagging/presets/ItemSeparator.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import java.util.Map; 5 6 import javax.swing.JPanel; 7 import javax.swing.JSeparator; 8 9 import org.openstreetmap.josm.tools.GBC; 10 11 /** 12 * Class used to represent a {@link JSeparator} inside tagging preset window. 13 * @since 6198 14 */ 15 final class ItemSeparator extends Item { 16 17 private ItemSeparator(Map<String, String> attributes) throws IllegalArgumentException { 18 super(attributes); 19 } 20 21 /** 22 * Create this class from an XML element's attributes. 23 * @param attributes the XML attributes (ignored) 24 * @return the new instance 25 * @throws IllegalArgumentException on invalid attributes 26 */ 27 public static ItemSeparator fromXML(Map<String, String> attributes) throws IllegalArgumentException { 28 return new ItemSeparator(attributes); 29 } 30 31 @Override 32 boolean addToPanel(JPanel p, TaggingPresetInstance support) { 33 p.add(new JSeparator(), GBC.eol().fill(GBC.HORIZONTAL).insets(0, 5, 0, 5)); 34 return false; 35 } 36 37 @Override 38 public String toString() { 39 return "ItemSeparator"; 40 } 41 } -
src/org/openstreetmap/josm/gui/tagging/presets/Key.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import java.util.Collection; 5 import java.util.Collections; 6 import java.util.List; 7 import java.util.Map; 8 9 import javax.swing.JPanel; 10 11 import org.openstreetmap.josm.data.osm.Tag; 12 13 /** 14 * Invisible type allowing to hardcode an OSM key/value from the preset definition. 15 */ 16 public final class Key extends KeyedItem { 17 18 /** The hardcoded value for key */ 19 private final String value; 20 21 /** 22 * Private constructor. Use {@link #fromXML} instead. 23 * @param attributes the XML attributes 24 * @throws IllegalArgumentException on illegal attributes 25 */ 26 private Key(Map<String, String> attributes) throws IllegalArgumentException { 27 super(attributes); 28 value = attributes.get("value"); 29 } 30 31 /** 32 * Create a {@code Key} from an XML element's attributes. 33 * @param attributes the XML attributes 34 * @return the {@code Key} 35 * @throws IllegalArgumentException on invalid attributes 36 */ 37 public static Key fromXML(Map<String, String> attributes) throws IllegalArgumentException { 38 return new Key(attributes); 39 } 40 41 /** 42 * Returns the value 43 * @return the value 44 */ 45 public String getValue() { 46 return value; 47 } 48 49 @Override 50 boolean addToPanel(JPanel p, TaggingPresetInstance support) { 51 support.putInstance(this, new Instance()); 52 return false; 53 } 54 55 class Instance extends Item.Instance { 56 @Override 57 public void addCommands(List<Tag> changedTags) { 58 changedTags.add(asTag()); 59 } 60 } 61 62 /** 63 * Returns the {@link Tag} set by this item 64 * @return the tag 65 */ 66 Tag asTag() { 67 return new Tag(key, value); 68 } 69 70 @Override 71 MatchType getDefaultMatch() { 72 return MatchType.KEY_VALUE_REQUIRED; 73 } 74 75 @Override 76 public Collection<String> getValues() { 77 return Collections.singleton(value); 78 } 79 80 @Override 81 public String toString() { 82 return "Key [key=" + key + ", value=" + value + ", text=" + text 83 + ", text_context=" + textContext + ", match=" + getMatchType() 84 + ']'; 85 } 86 } -
src/org/openstreetmap/josm/gui/tagging/presets/KeyedItem.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 6 import java.util.Collection; 7 import java.util.EnumSet; 8 import java.util.HashMap; 9 import java.util.Map; 10 11 import javax.swing.JPopupMenu; 12 13 import org.openstreetmap.josm.data.osm.Tag; 14 import org.openstreetmap.josm.data.preferences.BooleanProperty; 15 import org.openstreetmap.josm.gui.dialogs.properties.HelpTagAction; 16 import org.openstreetmap.josm.gui.dialogs.properties.TaginfoAction; 17 18 /** 19 * Preset item associated to an OSM key. 20 */ 21 public abstract class KeyedItem extends TextItem { 22 23 /** Last value of each key used in presets, used for prefilling corresponding fields */ 24 static final Map<String, String> LAST_VALUES = new HashMap<>(); 25 /** True if the default value should also be set on primitives that already have tags. */ 26 static final BooleanProperty PROP_FILL_DEFAULT = new BooleanProperty("taggingpreset.fill-default-for-tagged-primitives", false); 27 /** The constant value {@code "<different>"}. */ 28 static final String DIFFERENT = "<different>"; 29 /** Translation of {@code "<different>"}. */ 30 static final String DIFFERENT_I18N = tr(DIFFERENT); 31 32 /** This specifies the property key that will be modified by the item. */ 33 final String key; 34 /** The length of the text box (number of characters allowed). */ 35 final int length; 36 /** 37 * Whether the last value is used as default. 38 * <ul> 39 * <li>false = 0: do not use the last value as default 40 * <li>true = 1: use the last value as default for primitives without any tag 41 * <li>force = 2: use the last value as default for all primitives. 42 * </ul> 43 * Default is "false". 44 */ 45 final int useLastAsDefault; 46 /** 47 * Allows to change the matching process, i.e., determining whether the tags of an OSM object fit into this preset. 48 * If a preset fits then it is linked in the Tags/Membership dialog.<ul> 49 * <li>none: neutral, i.e., do not consider this item for matching</li> 50 * <li>key: positive if key matches, neutral otherwise</li> 51 * <li>key!: positive if key matches, negative otherwise</li> 52 * <li>keyvalue: positive if key and value matches, neutral otherwise</li> 53 * <li>keyvalue!: positive if key and value matches, negative otherwise</li></ul> 54 * Note that for a match, at least one positive and no negative is required. 55 * Default is "keyvalue!" for {@link Key} and "none" for {@link Text}, {@link Combo}, {@link MultiSelect} and {@link Check}. 56 */ 57 final MatchType matchType; 58 59 /** 60 * Constructor. 61 * @param attributes the XML attributes 62 * @throws IllegalArgumentException on illegal attributes 63 */ 64 KeyedItem(Map<String, String> attributes) throws IllegalArgumentException { 65 super(attributes); 66 key = attributes.get("key"); 67 useLastAsDefault = getUseLastAsDefault(attributes.get("use_last_as_default")); 68 length = Integer.parseInt(attributes.getOrDefault("length", "0")); 69 matchType = setMatchType(attributes.get("match")); 70 } 71 72 MatchType setMatchType(String v) { 73 if (v != null) 74 return MatchType.ofString(v); 75 return MatchType.ofString(getDefaultMatch().getValue()); 76 } 77 78 /** 79 * Enum denoting how a match (see {@link Item#matches}) is performed. 80 */ 81 enum MatchType { 82 83 /** Neutral, i.e., do not consider this item for matching. */ 84 NONE("none"), 85 /** Positive if key matches, neutral otherwise. */ 86 KEY("key"), 87 /** Positive if key matches, negative otherwise. */ 88 KEY_REQUIRED("key!"), 89 /** Positive if key and value matches, neutral otherwise. */ 90 KEY_VALUE("keyvalue"), 91 /** Positive if key and value matches, negative otherwise. */ 92 KEY_VALUE_REQUIRED("keyvalue!"); 93 94 private final String value; 95 96 MatchType(String value) { 97 this.value = value; 98 } 99 100 /** 101 * Replies the associated textual value. 102 * @return the associated textual value 103 */ 104 public String getValue() { 105 return value; 106 } 107 108 @Override 109 public String toString() { 110 return value; 111 } 112 113 /** 114 * Determines the {@code MatchType} for the given textual value. 115 * @param type the textual value 116 * @return the {@code MatchType} for the given textual value 117 */ 118 public static MatchType ofString(String type) { 119 for (MatchType i : EnumSet.allOf(MatchType.class)) { 120 if (i.getValue().equals(type)) 121 return i; 122 } 123 throw new IllegalArgumentException(type + " is not allowed"); 124 } 125 } 126 127 /** 128 * Returns the key 129 * @return the key 130 */ 131 public String getKey() { 132 return key; 133 } 134 135 /** 136 * Returns the match type. 137 * @return the match type 138 */ 139 public String getMatchType() { 140 return matchType.toString(); 141 } 142 143 /** 144 * Determines whether key or key+value are required. 145 * @return whether key or key+value are required 146 */ 147 public boolean isKeyRequired() { 148 return MatchType.KEY_REQUIRED == matchType || MatchType.KEY_VALUE_REQUIRED == matchType; 149 } 150 151 /** 152 * Returns the default match. 153 * @return the default match 154 */ 155 abstract MatchType getDefaultMatch(); 156 157 /** 158 * Returns the list of values. 159 * @return the list of values 160 */ 161 public abstract Collection<String> getValues(); 162 163 String getKeyTooltipText() { 164 return tr("This corresponds to the key ''{0}''", key); 165 } 166 167 @Override 168 public Boolean matches(Map<String, String> tags) { 169 switch (matchType) { 170 case NONE: 171 return null; // NOSONAR 172 case KEY: 173 return tags.containsKey(key) ? Boolean.TRUE : null; 174 case KEY_REQUIRED: 175 return tags.containsKey(key); 176 case KEY_VALUE: 177 return tags.containsKey(key) && getValues().contains(tags.get(key)) ? Boolean.TRUE : null; 178 case KEY_VALUE_REQUIRED: 179 return tags.containsKey(key) && getValues().contains(tags.get(key)); 180 default: 181 throw new IllegalStateException(); 182 } 183 } 184 185 /** 186 * Sets whether the last value is used as default. 187 * @param v Using "force" (2) enforces this behaviour also for already tagged objects. Default is "false" (0). 188 * @return the value as int 189 */ 190 private int getUseLastAsDefault(String v) { 191 if ("force".equals(v)) { 192 return 2; 193 } else if ("true".equals(v)) { 194 return 1; 195 } else { 196 return 0; 197 } 198 } 199 200 /** 201 * Returns true if the last entered value should be used as default. 202 * <p> 203 * Note: never used in {@code defaultpresets.xml}. 204 * 205 * @return true if the last entered value should be used as default. 206 */ 207 boolean isUseLastAsDefault() { 208 return useLastAsDefault > 0; 209 } 210 211 /** 212 * Returns true if the last entered value should be used as default also on primitives that 213 * already have tags. 214 * <p> 215 * Note: used for {@code addr:*} tags in {@code defaultpresets.xml}. 216 * 217 * @return true if see above 218 */ 219 boolean isForceUseLastAsDefault() { 220 return useLastAsDefault == 2; 221 } 222 223 JPopupMenu getPopupMenu() { 224 Tag tag = new Tag(key, null); 225 JPopupMenu popupMenu = new JPopupMenu(); 226 popupMenu.add(tr("Key: {0}", key)).setEnabled(false); 227 popupMenu.add(new HelpTagAction(() -> tag)); 228 TaginfoAction taginfoAction = new TaginfoAction(() -> tag, () -> null); 229 popupMenu.add(taginfoAction.toTagHistoryAction()); 230 popupMenu.add(taginfoAction); 231 return popupMenu; 232 } 233 234 @Override 235 public String toString() { 236 return "KeyedItem [key=" + key + ", text=" + text 237 + ", text_context=" + textContext + ", match=" + matchType 238 + ']'; 239 } 240 } -
src/org/openstreetmap/josm/gui/tagging/presets/Label.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import java.util.Map; 5 6 import javax.swing.JLabel; 7 import javax.swing.JPanel; 8 9 import org.openstreetmap.josm.tools.GBC; 10 11 /** 12 * Label type. 13 */ 14 final class Label extends TextItem { 15 16 /** 17 * Private constructor. Use {@link #fromXML} instead. 18 * @param attributes the XML attributes 19 * @throws IllegalArgumentException on illegal attributes 20 */ 21 private Label(Map<String, String> attributes) throws IllegalArgumentException { 22 super(attributes); 23 } 24 25 /** 26 * Create a {@code Label} from an XML element's attributes. 27 * @param attributes the XML attributes 28 * @return the {@code Label} 29 * @throws IllegalArgumentException on illegal attributes 30 */ 31 public static Label fromXML(Map<String, String> attributes) throws IllegalArgumentException { 32 return new Label(attributes); 33 } 34 35 @Override 36 boolean addToPanel(JPanel p, TaggingPresetInstance support) { 37 JLabel label = new JLabel(localeText); 38 addIcon(label); 39 label.applyComponentOrientation(support.getDefaultComponentOrientation()); 40 p.add(label, GBC.eol().fill(GBC.HORIZONTAL)); 41 return true; 42 } 43 } -
src/org/openstreetmap/josm/gui/tagging/presets/Link.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 6 import java.awt.event.MouseEvent; 7 import java.util.Arrays; 8 import java.util.Map; 9 import java.util.Optional; 10 11 import javax.swing.JPanel; 12 import javax.swing.SwingUtilities; 13 14 import org.openstreetmap.josm.gui.dialogs.properties.HelpAction; 15 import org.openstreetmap.josm.gui.widgets.UrlLabel; 16 import org.openstreetmap.josm.spi.preferences.Config; 17 import org.openstreetmap.josm.tools.GBC; 18 import org.openstreetmap.josm.tools.LanguageInfo; 19 20 /** 21 * Hyperlink type. 22 * @since 8863 23 */ 24 public final class Link extends TextItem { 25 26 /** The link to display. */ 27 private final String href; 28 /** The localized version of {@link #href}. */ 29 private final String localeHref; 30 /** The OSM wiki page to display. */ 31 private final String wiki; 32 33 /** 34 * Private constructor. Use {@link #fromXML} instead. 35 * @param attributes the XML attributes 36 * @throws IllegalArgumentException on attribute error 37 */ 38 private Link(Map<String, String> attributes) throws IllegalArgumentException { 39 super(attributes); 40 href = attributes.get("href"); 41 localeHref = attributes.get("locale_href"); 42 wiki = attributes.get("wiki"); 43 } 44 45 /** 46 * Create a {@code Link} from an XML element's attributes. 47 * @param attributes the XML attributes 48 * @return the {@code Link} 49 * @throws IllegalArgumentException on invalid attributes 50 */ 51 public static Link fromXML(Map<String, String> attributes) throws IllegalArgumentException { 52 return new Link(attributes); 53 } 54 55 @Override 56 String getDefaultText() { 57 return tr("More information about this feature"); 58 } 59 60 @Override 61 boolean addToPanel(JPanel p, TaggingPresetInstance support) { 62 UrlLabel label = buildUrlLabel(); 63 if (label != null) { 64 label.applyComponentOrientation(support.getDefaultComponentOrientation()); 65 p.add(label, GBC.eol().insets(0, 10, 0, 0).fill(GBC.HORIZONTAL)); 66 } 67 return false; 68 } 69 70 private UrlLabel buildUrlLabel() { 71 final String url = getUrl(); 72 if (wiki != null) { 73 UrlLabel urlLabel = new UrlLabel(url, localeText, 2) { 74 @Override 75 public void mouseClicked(MouseEvent e) { 76 if (SwingUtilities.isLeftMouseButton(e)) { 77 // Open localized page if exists 78 HelpAction.displayHelp(Arrays.asList( 79 LanguageInfo.getWikiLanguagePrefix(LanguageInfo.LocaleType.OSM_WIKI) + wiki, 80 wiki)); 81 } else { 82 super.mouseClicked(e); 83 } 84 } 85 }; 86 addIcon(urlLabel); 87 return urlLabel; 88 } else if (href != null || localeHref != null) { 89 UrlLabel urlLabel = new UrlLabel(url, localeText, 2); 90 addIcon(urlLabel); 91 return urlLabel; 92 } 93 return null; 94 } 95 96 /** 97 * Returns the link URL. 98 * @return the link URL 99 * @since 15423 100 */ 101 public String getUrl() { 102 if (wiki != null) { 103 return Config.getUrls().getOSMWiki() + "/wiki/" + wiki; 104 } else if (href != null || localeHref != null) { 105 return Optional.ofNullable(localeHref).orElse(href); 106 } 107 return null; 108 } 109 110 @Override 111 String fieldsToString() { 112 return super.fieldsToString() 113 + (wiki != null ? "wiki=" + wiki + ", " : "") 114 + (href != null ? "href=" + href + ", " : "") 115 + (localeHref != null ? "locale_href=" + localeHref + ", " : ""); 116 } 117 } -
src/org/openstreetmap/josm/gui/tagging/presets/MultiSelect.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import java.awt.Dimension; 5 import java.awt.GridBagConstraints; 6 import java.awt.Insets; 7 import java.awt.Rectangle; 8 import java.util.Map; 9 import java.util.stream.Collectors; 10 11 import javax.swing.DefaultListModel; 12 import javax.swing.JLabel; 13 import javax.swing.JList; 14 import javax.swing.JPanel; 15 import javax.swing.JScrollPane; 16 17 import org.openstreetmap.josm.gui.widgets.OrientationAction; 18 import org.openstreetmap.josm.tools.GBC; 19 import org.openstreetmap.josm.tools.Logging; 20 21 /** 22 * Multi-select list type. 23 */ 24 final class MultiSelect extends ComboMultiSelect { 25 /** 26 * Number of rows to display (positive integer, optional). 27 */ 28 private final int rows; 29 30 /** 31 * Private constructor. Use {@link #fromXML} instead. 32 * @param attributes the XML attributes 33 * @throws IllegalArgumentException on illegal attributes 34 */ 35 private MultiSelect(Map<String, String> attributes) throws IllegalArgumentException { 36 super(attributes); 37 rows = Integer.parseInt(attributes.getOrDefault("rows", "0")); 38 } 39 40 /** 41 * Create this class from an XML element's attributes. 42 * @param attributes the XML attributes 43 * @return the new instance 44 * @throws IllegalArgumentException on invalid attributes 45 */ 46 public static MultiSelect fromXML(Map<String, String> attributes) throws IllegalArgumentException { 47 return new MultiSelect(attributes); 48 } 49 50 @Override 51 String getDefaultDelimiter() { 52 return ";"; 53 } 54 55 private void addEntry(DefaultListModel<PresetListEntry.Instance> model, PresetListEntry.Instance instance) { 56 if (!seenValues.containsKey(instance.getValue())) { 57 model.addElement(instance); 58 seenValues.put(instance.getValue(), instance); 59 } 60 } 61 62 @Override 63 boolean addToPanel(JPanel p, TaggingPresetInstance support) { 64 Usage usage = Usage.determineTextUsage(support.getSelected(), key); 65 seenValues.clear(); 66 67 DefaultListModel<PresetListEntry.Instance> model = new DefaultListModel<>(); 68 JList<PresetListEntry.Instance> list = new JList<>(model); 69 Instance instance = new Instance(list, usage); 70 71 // disable if the selected primitives have different values 72 list.setEnabled(usage.hasUniqueValue() || usage.unused()); 73 74 // Add values from the preset. 75 presetListEntries.forEach(e -> addEntry(model, e.newInstance(instance))); 76 77 support.putInstance(this, instance); 78 String initialValue = instance.getInitialValue(usage, support); 79 Logging.info("initialValue = {0}", initialValue); 80 81 // Add all values used in the selected primitives. This also adds custom values and makes 82 // sure we won't lose them. 83 usage.splitValues(); 84 for (String value: usage.map.keySet()) { 85 addEntry(model, new PresetListEntry(this, value).newInstance(instance)); 86 } 87 88 // Select the values in the initial value. 89 if (!initialValue.isEmpty() && !DIFFERENT.equals(initialValue)) { 90 for (String value : initialValue.split(";", -1)) { 91 PresetListEntry.Instance pi = new PresetListEntry(this, value).newInstance(instance); 92 addEntry(model, pi); 93 int i = model.indexOf(pi); 94 list.addSelectionInterval(i, i); 95 Logging.info("selecting: {0} at index {1}", value, i); 96 } 97 } 98 99 PresetListEntry.CellRenderer renderer = new PresetListEntry.CellRenderer(list, list.getCellRenderer(), 200); 100 list.setCellRenderer(renderer); 101 JLabel label = addLabel(p); 102 label.setLabelFor(list); 103 JScrollPane sp = new JScrollPane(list); 104 105 if (rows > 0) { 106 list.setVisibleRowCount(rows); 107 // setVisibleRowCount() only works when all cells have the same height, but sometimes we 108 // have icons of different sizes. Calculate the size of the first {@code rows} entries 109 // and size the scrollpane accordingly. 110 Rectangle r = list.getCellBounds(0, Math.min(rows, model.size() - 1)); 111 if (r != null) { 112 Insets insets = list.getInsets(); 113 r.width += insets.left + insets.right; 114 r.height += insets.top + insets.bottom; 115 insets = sp.getInsets(); 116 r.width += insets.left + insets.right; 117 r.height += insets.top + insets.bottom; 118 sp.setPreferredSize(new Dimension(r.width, r.height)); 119 } 120 } 121 p.add(sp, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); 122 123 list.addListSelectionListener(l -> support.fireItemValueModified(instance, key, instance.getSelectedItem().getValue())); 124 list.setToolTipText(getKeyTooltipText()); 125 list.applyComponentOrientation(OrientationAction.getValueOrientation(key)); 126 127 seenValues.clear(); 128 return true; 129 } 130 131 class Instance extends ComboMultiSelect.Instance { 132 JList<PresetListEntry.Instance> list; 133 134 Instance(JList<PresetListEntry.Instance> list, Usage usage) { 135 super(usage); 136 this.list = list; 137 } 138 139 @Override 140 PresetListEntry.Instance getSelectedItem() { 141 return new PresetListEntry(MultiSelect.this, list.getSelectedValuesList() 142 .stream().map(e -> e.getValue()).distinct().sorted() 143 .collect(Collectors.joining(";"))).newInstance(this); 144 } 145 } 146 } -
src/org/openstreetmap/josm/gui/tagging/presets/Optional.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 6 import java.awt.GridBagLayout; 7 import java.util.Map; 8 9 import javax.swing.JPanel; 10 11 /** 12 * Used to group optional attributes. 13 * @since 8863 14 */ 15 final class Optional extends Container { 16 /** 17 * Private constructor. 18 * @param attributes the XML attributes 19 * @throws IllegalArgumentException on illegal attributes 20 */ 21 private Optional(Map<String, String> attributes) throws IllegalArgumentException { 22 super(attributes); 23 } 24 25 /** 26 * Create this class from an XML element's attributes. 27 * @param attributes the XML attributes 28 * @return the new instance 29 * @throws IllegalArgumentException on invalid attributes 30 */ 31 public static Optional fromXML(Map<String, String> attributes) throws IllegalArgumentException { 32 return new Optional(attributes); 33 } 34 35 @Override 36 String getDefaultText() { 37 return tr("Optional Attributes:"); 38 } 39 40 @Override 41 JPanel getPanel() { 42 JPanel panel = new JPanel(new GridBagLayout()); 43 addBorder(panel); 44 return panel; 45 } 46 47 @Override 48 public String toString() { 49 return "Optional"; 50 } 51 } -
src/org/openstreetmap/josm/gui/tagging/presets/PresetLink.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 6 import java.awt.event.MouseAdapter; 7 import java.awt.event.MouseEvent; 8 import java.util.Map; 9 10 import javax.swing.JLabel; 11 import javax.swing.JPanel; 12 13 import org.openstreetmap.josm.tools.GBC; 14 15 /** 16 * Adds a link to another preset. 17 * @since 8863 18 */ 19 final class PresetLink extends TextItem { 20 21 /** The exact name of the preset to link to. Required. */ 22 private final String presetName; 23 24 /** 25 * Private constructor. Use {@link #fromXML} instead. 26 * @param attributes the XML attributes 27 * @throws IllegalArgumentException on illegal attributes 28 */ 29 private PresetLink(Map<String, String> attributes) throws IllegalArgumentException { 30 super(attributes); 31 presetName = attributes.get("preset_name"); 32 if (presetName == null) 33 throw new IllegalArgumentException("attribute preset_name is required"); 34 } 35 36 /** 37 * Create a {@code PresetLink} from an XML element's attributes. 38 * @param attributes the XML attributes 39 * @return the {@code PresetLink} 40 * @throws IllegalArgumentException on illegal attributes 41 */ 42 public static PresetLink fromXML(Map<String, String> attributes) throws IllegalArgumentException { 43 return new PresetLink(attributes); 44 } 45 46 static final class TaggingPresetMouseAdapter extends MouseAdapter { 47 private final TaggingPreset preset; 48 private final TaggingPresetHandler handler; 49 50 TaggingPresetMouseAdapter(TaggingPreset preset, TaggingPresetHandler handler) { 51 this.preset = preset; 52 this.handler = handler; 53 } 54 55 @Override 56 public void mouseClicked(MouseEvent e) { 57 TaggingPresetDialog.showAndApply(preset, handler, false); 58 } 59 } 60 61 @Override 62 protected String getDefaultText() { 63 return tr("Edit also …"); 64 } 65 66 /** 67 * Creates a label to be inserted above this link 68 * @return a label 69 */ 70 JLabel createLabel() { 71 return new JLabel(localeText); 72 } 73 74 @Override 75 boolean addToPanel(JPanel p, TaggingPresetInstance support) { 76 for (TaggingPreset preset : TaggingPresets.getTaggingPresets()) { 77 if (presetName.equals(preset.getBaseName())) { 78 JLabel lbl = new TaggingPresetLabel(preset); 79 lbl.addMouseListener(new TaggingPresetMouseAdapter(preset, support.getHandler())); 80 lbl.applyComponentOrientation(support.getDefaultComponentOrientation()); 81 p.add(lbl, GBC.eol().fill(GBC.HORIZONTAL)); 82 break; 83 } 84 } 85 return false; 86 } 87 88 @Override 89 public String toString() { 90 return "PresetLink [preset_name=" + presetName + ']'; 91 } 92 } -
src/org/openstreetmap/josm/gui/tagging/presets/PresetListEntry.java
1 // License: GPL. For details, see LICENSE file. 2 3 package org.openstreetmap.josm.gui.tagging.presets; 4 5 import static org.openstreetmap.josm.tools.I18n.tr; 6 import static org.openstreetmap.josm.tools.I18n.trc; 7 8 import java.awt.Component; 9 import java.awt.Font; 10 import java.util.Map; 11 import java.util.Objects; 12 13 import javax.swing.ImageIcon; 14 import javax.swing.JLabel; 15 import javax.swing.JList; 16 import javax.swing.JPanel; 17 import javax.swing.ListCellRenderer; 18 19 import org.openstreetmap.josm.gui.widgets.JosmListCellRenderer; 20 import org.openstreetmap.josm.tools.AlphanumComparator; 21 import org.openstreetmap.josm.tools.Utils; 22 23 /** 24 * Preset list entry. 25 * <p> 26 * Used for controls that offer a list of items to choose from like {@link Combo} and 27 * {@link MultiSelect}. 28 */ 29 class PresetListEntry extends Item { 30 /** Used to display an entry matching several different values. */ 31 static final PresetListEntry ENTRY_DIFFERENT = new PresetListEntry(null, KeyedItem.DIFFERENT); 32 /** Used to display an empty entry used to clear values. */ 33 static final PresetListEntry ENTRY_EMPTY = new PresetListEntry(null, ""); 34 35 /** 36 * This is the value that is going to be written to the tag on the selected primitive(s). Except 37 * when the value is {@code "<different>"}, which is never written, or the value is empty, which 38 * deletes the tag. {@code value} is never translated. 39 */ 40 private final String value; 41 /** Text displayed to the user instead of {@link #value}. */ 42 private final String displayValue; 43 /** Text to be displayed below {@link #displayValue} in the combobox list. */ 44 private final String shortDescription; 45 /** The location of icon file to display */ 46 private final String icon; 47 /** The size of displayed icon. If not set, default is size from icon file */ 48 private final int iconSize; 49 50 /** The localized version of {@link #displayValue}. */ 51 private final String localeDisplayValue; 52 /** The finallocalized version of {@link #shortDescription}. */ 53 private final String localeShortDescription; 54 /** Tha cached icon */ 55 private ImageIcon cachedIcon; 56 57 /** 58 * Private constructor. Use {@link #fromXML} instead. 59 * @param attributes the XML attributes 60 * @throws IllegalArgumentException on attribute error 61 */ 62 private PresetListEntry(Map<String, String> attributes) throws IllegalArgumentException { 63 super(attributes); 64 value = attributes.get("value"); 65 displayValue = attributes.get("display_value"); 66 localeDisplayValue = attributes.get("locale_display_value"); 67 shortDescription = attributes.get("short_description"); 68 localeShortDescription = attributes.get("locale_short_description"); 69 icon = attributes.get("icon"); 70 iconSize = Integer.parseInt(attributes.getOrDefault("icon_size", "0")); 71 } 72 73 /** 74 * Create this class from an XML element's attributes. 75 * @param attributes the XML attributes 76 * @return the new instance 77 * @throws IllegalArgumentException on invalid attributes 78 */ 79 public static PresetListEntry fromXML(Map<String, String> attributes) throws IllegalArgumentException { 80 return new PresetListEntry(attributes); 81 } 82 83 /** 84 * Convenience constructor. Constructs a new {@code PresetListEntry}, initialized with a value. 85 * 86 * @param value value 87 * @param cms the ComboMultiSelect 88 */ 89 PresetListEntry(ComboMultiSelect cms, String value) { 90 super(ItemFactory.attributesToMap()); 91 this.value = value; 92 this.displayValue = value; 93 this.localeDisplayValue = value; 94 this.shortDescription = ""; 95 this.localeShortDescription = ""; 96 this.icon = null; 97 this.iconSize = 0; 98 } 99 100 /** 101 * Returns the value 102 * @return the value 103 */ 104 String getValue() { 105 return value; 106 } 107 108 /** 109 * Returns the entry icon, if any. 110 * @return the entry icon, or {@code null} 111 */ 112 ImageIcon getIcon() { 113 if (icon != null && cachedIcon == null) { 114 cachedIcon = TaggingPresetUtils.loadImageIcon(icon, TaggingPresetReader.getZipIcons(), iconSize); 115 } 116 return cachedIcon; 117 } 118 119 /** 120 * Returns the contents displayed in the current item view. 121 * @param cms the ComboMultiSelect 122 * @return the value to display 123 */ 124 String getDisplayValue(ComboMultiSelect cms) { 125 if (cms.valuesNoI18n) { 126 return Utils.firstNonNull(PresetListEntry.this.value, " "); 127 } 128 return Utils.firstNonNull( 129 localeDisplayValue, 130 tr(displayValue), 131 trc(cms.valuesContext, value), 132 " " 133 ); 134 } 135 136 /** 137 * Returns the short description to display. 138 * @return the short description to display 139 */ 140 String getShortDescription() { 141 return Utils.firstNonNull( 142 localeShortDescription, 143 tr(shortDescription), 144 "" 145 ); 146 } 147 148 /** 149 * Returns the tooltip for this entry. 150 * @param key the tag key 151 * @return the tooltip 152 */ 153 String getToolTipText(String key) { 154 if (this.equals(ENTRY_DIFFERENT)) { 155 return tr("Keeps the original values of the selected objects unchanged."); 156 } 157 if (value != null && !value.isEmpty()) { 158 return tr("Sets the key ''{0}'' to the value ''{1}''.", key, value); 159 } 160 return tr("Clears the key ''{0}''.", key); 161 } 162 163 @Override 164 boolean addToPanel(JPanel p, TaggingPresetInstance support) { 165 return false; 166 } 167 168 /** 169 * Creates a new instance 170 * @param cmsInstance The ComboMultiSelect.Instance 171 * @return the new instance 172 */ 173 Instance newInstance(ComboMultiSelect.Instance cmsInstance) { 174 return new Instance(cmsInstance); 175 } 176 177 class Instance implements Comparable<Instance> { 178 private String displayValue; 179 private String shortDescription; 180 private String toolTip; 181 private ImageIcon icon; 182 private Usage usage; 183 184 Instance(ComboMultiSelect.Instance cmsInstance) { 185 this.usage = cmsInstance.usage; 186 this.icon = PresetListEntry.this.getIcon(); 187 ComboMultiSelect cms = cmsInstance.getTemplate(); 188 this.toolTip = PresetListEntry.this.getToolTipText(cms.getKey()); 189 190 displayValue = getDisplayValue(cms); 191 shortDescription = getShortDescription(); 192 } 193 194 Instance(ComboMultiSelect.Instance cmsInstance, String value) { 195 this.usage = cmsInstance.usage; 196 this.displayValue = value; 197 } 198 199 String getValue() { 200 return PresetListEntry.this.getValue(); 201 } 202 203 @Override 204 public int compareTo(Instance o) { 205 return AlphanumComparator.getInstance().compare(this.displayValue, o.displayValue); 206 } 207 208 // toString is mainly used to initialize the Editor 209 @Override 210 public String toString() { 211 if (this.getValue().equals(KeyedItem.DIFFERENT)) 212 return displayValue; 213 return displayValue.replaceAll("\\s*<.*>\\s*", " "); // remove additional markup, e.g. <br> 214 } 215 216 @Override 217 public boolean equals(Object o) { 218 if (this == o) return true; 219 if (o == null || getClass() != o.getClass()) return false; 220 PresetListEntry.Instance that = (PresetListEntry.Instance) o; 221 return Objects.equals(getValue(), that.getValue()); 222 } 223 224 @Override 225 public int hashCode() { 226 return Objects.hash(getValue()); 227 } 228 229 /** 230 * Returns how many selected primitives had this value set. 231 * @return see above 232 */ 233 int getCount() { 234 Integer count = usage.map.get(value); 235 return count == null ? 0 : count; 236 } 237 238 /** 239 * Returns the contents displayed in the dropdown list. 240 * 241 * This is the contents that would be displayed in the current view plus a short description to 242 * aid the user. The whole contents is wrapped to {@code width}. 243 * 244 * @param width the width in px 245 * @return HTML formatted contents 246 */ 247 String getListDisplay(int width) { 248 Integer count = getCount(); 249 String result = displayValue; 250 251 if (count > 0 && usage.getSelectedCount() > 1) { 252 result = tr("{0} ({1})", displayValue, count); 253 } 254 255 if (this.getValue().equals(KeyedItem.DIFFERENT)) { 256 return "<html><b>" + Utils.escapeReservedCharactersHTML(displayValue) + "</b></html>"; 257 } 258 259 if (shortDescription.isEmpty()) { 260 // avoids a collapsed list entry if value == "" 261 if (result.isEmpty()) { 262 return " "; 263 } 264 return result; 265 } 266 267 // RTL not supported in HTML. See: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4866977 268 return String.format("<html><div style=\"width: %d\"><b>%s</b><p style=\"padding-left: 10\">%s</p></div></html>", 269 width, 270 result, 271 Utils.escapeReservedCharactersHTML(shortDescription)); 272 } 273 } 274 275 /** 276 * A list cell renderer that paints a short text in the current value pane and and a longer text 277 * in the dropdown list. 278 */ 279 static class CellRenderer extends JosmListCellRenderer<PresetListEntry.Instance> { 280 int width; 281 282 CellRenderer(Component component, ListCellRenderer<? super PresetListEntry.Instance> renderer, int width) { 283 super(component, renderer); 284 setWidth(width); 285 } 286 287 /** 288 * Sets the width to format the dropdown list to 289 * 290 * Note: This is not the width of the list, but the width to which we format any multi-line 291 * label in the list. We cannot use the list's width because at the time the combobox 292 * measures its items, it is not guaranteed that the list is already sized, the combobox may 293 * not even be layed out yet. Set this to {@code combobox.getWidth()} 294 * 295 * @param width the width 296 */ 297 public void setWidth(int width) { 298 if (width <= 0) 299 width = 200; 300 this.width = width - 20; 301 } 302 303 @Override 304 public JLabel getListCellRendererComponent( 305 JList<? extends PresetListEntry.Instance> list, PresetListEntry.Instance value, 306 int index, boolean isSelected, boolean cellHasFocus) { 307 308 JLabel l = (JLabel) renderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); 309 l.setComponentOrientation(component.getComponentOrientation()); 310 if (index != -1) { 311 // index -1 is set when measuring the size of the cell and when painting the 312 // editor-ersatz of a readonly combobox. fixes #6157 313 l.setText(value.getListDisplay(width)); 314 } 315 if (value.getCount() > 0) { 316 l.setFont(l.getFont().deriveFont(Font.ITALIC + Font.BOLD)); 317 } 318 l.setIcon(value.icon); 319 l.setToolTipText(value.toolTip); 320 return l; 321 } 322 } 323 } -
src/org/openstreetmap/josm/gui/tagging/presets/ReadOnlyTaggingPresetHandler.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import java.util.Collection; 5 import java.util.List; 6 7 import org.openstreetmap.josm.data.osm.OsmPrimitive; 8 import org.openstreetmap.josm.data.osm.Tag; 9 10 /** 11 * A Read-Only TaggingPresetHandler that operates on a collection of primitives. 12 * <p> 13 * For testing purposes. 14 */ 15 public class ReadOnlyTaggingPresetHandler implements TaggingPresetHandler { 16 Collection<OsmPrimitive> selection; 17 18 /** 19 * Constructor 20 * @param selection the selection of primitives to edit 21 */ 22 public ReadOnlyTaggingPresetHandler(Collection<OsmPrimitive> selection) { 23 this.selection = selection; 24 } 25 26 @Override 27 public void updateTags(List<Tag> changedTags) { 28 } 29 30 @Override 31 public Collection<OsmPrimitive> getPrimitives() { 32 return selection; 33 } 34 } -
src/org/openstreetmap/josm/gui/tagging/presets/Reference.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 6 import java.util.List; 7 import java.util.Map; 8 import java.util.function.Predicate; 9 10 import javax.swing.JPanel; 11 12 /** 13 * A reference to be satisfied by a {@link Chunk} 14 */ 15 final class Reference extends Item { 16 private final String ref; 17 private Map<String, Chunk> chunks; 18 19 /** 20 * Private constructor. 21 * @param attributes the XML attributes 22 * @throws IllegalArgumentException on illegal attributes 23 */ 24 private Reference(Map<String, String> attributes) throws IllegalArgumentException { 25 super(attributes); 26 ref = attributes.get("ref"); 27 } 28 29 @Override 30 void fixup(Map<String, Chunk> chunks, Item parent) { 31 super.fixup(chunks, parent); 32 this.chunks = chunks; 33 } 34 35 /** 36 * Create a {@code Reference} from an XML element's attributes. 37 * @param attributes the XML attributes 38 * @return the {@code Reference} 39 * @throws IllegalArgumentException on invalid attributes 40 */ 41 public static Reference fromXML(Map<String, String> attributes) throws IllegalArgumentException { 42 return new Reference(attributes); 43 } 44 45 @Override 46 void destroy() { 47 chunks = null; 48 super.destroy(); 49 } 50 51 private Chunk getChunk() { 52 Chunk chunk = chunks.get(ref); 53 if (chunk == null) 54 throw new IllegalArgumentException(tr("Reference to undefined chunk: {0}", ref)); 55 return chunk; 56 } 57 58 @Override 59 void addToItemList(List<Item> list, Predicate<Item> p, boolean followReferences) { 60 super.addToItemList(list, p, followReferences); 61 if (followReferences) 62 getChunk().addToItemList(list, p, followReferences); 63 } 64 65 @Override 66 <E> void addToItemList(List<E> list, Class<E> type, boolean followReferences) { 67 super.addToItemList(list, type, followReferences); 68 if (followReferences) 69 getChunk().addToItemList(list, type, followReferences); 70 } 71 72 @Override 73 boolean addToPanel(JPanel p, TaggingPresetInstance support) { 74 return getChunk().addToPanel(p, support); 75 } 76 77 @Override 78 public Boolean matches(Map<String, String> tags) { 79 return getChunk().matches(tags); 80 } 81 82 @Override 83 public String toString() { 84 return "Reference [ref=" + ref + "]"; 85 } 86 } -
src/org/openstreetmap/josm/gui/tagging/presets/Role.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 6 import java.util.Collection; 7 import java.util.EnumSet; 8 import java.util.Map; 9 import java.util.Set; 10 11 import javax.swing.JLabel; 12 import javax.swing.JPanel; 13 14 import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 15 import org.openstreetmap.josm.data.osm.search.SearchCompiler; 16 import org.openstreetmap.josm.data.osm.search.SearchParseError; 17 import org.openstreetmap.josm.data.osm.search.SearchSetting; 18 import org.openstreetmap.josm.tools.GBC; 19 import org.openstreetmap.josm.tools.ImageProvider; 20 21 /** 22 * The <code>role</code> element in tagging preset definition. 23 * 24 * Information on a certain role, which is expected for the relation members. 25 */ 26 public class Role extends TextItem { 27 /** the right margin */ 28 private static final int right = 10; 29 30 /** Role name used in a relation */ 31 private final String key; 32 /** Presets types expected for this role */ 33 private final EnumSet<TaggingPresetType> types; 34 /** Is the role name a regular expression */ 35 private final boolean regexp; 36 /** How often must the element appear */ 37 private final int count; 38 /** An expression (cf. search dialog) for objects of this role */ 39 private final SearchCompiler.Match memberExpression; 40 /** Is this role required at least once in the relation? */ 41 private final boolean required; 42 43 /** 44 * Private constructor. Use {@link #fromXML} instead. 45 * @param attributes the XML attributes 46 * @throws IllegalArgumentException on illegal attributes 47 */ 48 private Role(Map<String, String> attributes) throws IllegalArgumentException { 49 super(attributes); 50 key = attributes.get("key"); 51 types = TaggingPresetType.getOrDefault(attributes.get("type"), EnumSet.noneOf(TaggingPresetType.class)); 52 regexp = Boolean.parseBoolean(attributes.getOrDefault("regexp", "false")); 53 count = Integer.parseInt(attributes.getOrDefault("count", "0")); 54 memberExpression = parseSearchExpression(attributes.get("member_expression")); 55 required = parseRequisite(attributes.getOrDefault("requisite", "optional")); 56 } 57 58 /** 59 * Convenience constructor (also for testing purposes) 60 * @param key the key 61 * @param types the tagging preset types this role applies to 62 */ 63 public Role(String key, Set<TaggingPresetType> types) { 64 super(ItemFactory.attributesToMap()); 65 this.key = key; 66 this.types = EnumSet.copyOf(types); 67 this.regexp = false; 68 this.count = 0; 69 this.memberExpression = null; 70 this.required = false; 71 } 72 73 /** 74 * Create this class from an XML element's attributes. 75 * @param attributes the XML attributes 76 * @return the new instance 77 * @throws IllegalArgumentException on invalid attributes 78 */ 79 public static Role fromXML(Map<String, String> attributes) throws IllegalArgumentException { 80 return new Role(attributes); 81 } 82 83 /** 84 * Returns the key 85 * @return the key 86 */ 87 public String getKey() { 88 return key; 89 } 90 91 /** 92 * Returns the member expression 93 * @return the member expression 94 */ 95 public SearchCompiler.Match getMemberExpression() { 96 return memberExpression; 97 } 98 99 /** 100 * Sets whether this role is required at least once in the relation. 101 * @param str "required" or "optional" 102 * @return true if required 103 * @throws IllegalArgumentException if str is neither "required" or "optional" 104 */ 105 private boolean parseRequisite(String str) throws IllegalArgumentException { 106 if ("required".equals(str)) { 107 return true; 108 } else if ("optional".equals(str)) { 109 return false; 110 } 111 throw new IllegalArgumentException(tr("Unknown requisite: {0}", str)); 112 } 113 114 /** 115 * Sets an expression (cf. search dialog) for objects of this role 116 * @param memberExpression an expression (cf. search dialog) for objects of this role 117 * @return the match expression 118 * @throws IllegalArgumentException in case of parsing error 119 */ 120 private SearchCompiler.Match parseSearchExpression(String memberExpression) throws IllegalArgumentException { 121 if (memberExpression == null) 122 return null; 123 try { 124 final SearchSetting searchSetting = new SearchSetting(); 125 searchSetting.text = memberExpression; 126 searchSetting.caseSensitive = true; 127 searchSetting.regexSearch = true; 128 return SearchCompiler.compile(searchSetting); 129 } catch (SearchParseError ex) { 130 throw new IllegalArgumentException(tr("Illegal member expression: {0}", ex.getMessage()), ex); 131 } 132 } 133 134 /** 135 * Return either argument, the highest possible value or the lowest allowed value. Used by 136 * relation checker. 137 * @param c count 138 * @return the highest possible value or the lowest allowed value 139 * @see #required 140 */ 141 public long getValidCount(long c) { 142 if (count > 0 && !required) 143 return c != 0 ? count : 0; 144 else if (count > 0) 145 return count; 146 else if (!required) 147 return c != 0 ? c : 0; 148 else 149 return c != 0 ? c : 1; 150 } 151 152 /** 153 * Returns the preset types that this role may be applied to 154 * @return the preset types 155 */ 156 public EnumSet<TaggingPresetType> getTypes() { 157 return EnumSet.copyOf(types); 158 } 159 160 /** 161 * Returns true if this role may be applied to the given preset type, eg. node / way ... 162 * 163 * @param presetType The preset type 164 * @return true if this role may be applied to the given preset type. 165 */ 166 public boolean appliesTo(TaggingPresetType presetType) { 167 return types.contains(presetType); 168 } 169 170 /** 171 * Returns true if this role may be applied to the given primitive type, eg. node / way ... 172 * 173 * @param primitiveType The OSM primitive type 174 * @return true if this role may be applied to the given primitive type. 175 */ 176 public boolean appliesTo(OsmPrimitiveType primitiveType) { 177 return types.contains(TaggingPresetType.forPrimitiveType(primitiveType)); 178 } 179 180 /** 181 * Returns true if this role may be applied to all of the given preset types. 182 * <p> 183 * Returns true if {@code role.types} contains all elements of {@code types}. 184 * 185 * @param presetTypes The preset types. 186 * @return true if this role may be applied to all of the given preset types. 187 */ 188 public boolean appliesToAll(Collection<TaggingPresetType> presetTypes) { 189 return types.containsAll(presetTypes); 190 } 191 192 /** 193 * Role if the given role matches this class (required to check regexp role types) 194 * @param role role to check 195 * @return <code>true</code> if role matches 196 * @since 11989 197 */ 198 public boolean isRole(String role) { 199 if (regexp && role != null) { // pass null through, it will anyway fail 200 return role.matches(this.key); 201 } 202 return this.key.equals(role); 203 } 204 205 /** 206 * Adds this role to the given panel. 207 * @param p panel where to add this role 208 * @return {@code true} 209 */ 210 @Override 211 boolean addToPanel(JPanel p, TaggingPresetInstance support) { 212 GBC std = GBC.std().insets(0, 0, right, 0); 213 GBC eol = GBC.eol().insets(0, 0, right, 0); 214 215 String cstring; 216 if (count > 0 && !required) { 217 cstring = "0,"+count; 218 } else if (count > 0) { 219 cstring = String.valueOf(count); 220 } else if (!required) { 221 cstring = "0-..."; 222 } else { 223 cstring = "1-..."; 224 } 225 p.add(new JLabel(localeText+':'), std); 226 p.add(new JLabel(key), std); 227 p.add(new JLabel(cstring), std); 228 229 JPanel typesPanel = new JPanel(); 230 for (TaggingPresetType t : types) { 231 typesPanel.add(new JLabel(ImageProvider.get(t.getIconName()))); 232 } 233 p.add(typesPanel, eol); 234 return true; 235 } 236 237 @Override 238 public int hashCode() { 239 final int prime = 31; 240 int result = 1; 241 result = prime * result + ((key == null) ? 0 : key.hashCode()); 242 result = prime * result + ((types == null) ? 0 : types.hashCode()); 243 return result; 244 } 245 246 @Override 247 public boolean equals(Object obj) { 248 if (this == obj) 249 return true; 250 if (obj == null) 251 return false; 252 if (getClass() != obj.getClass()) 253 return false; 254 Role other = (Role) obj; 255 if (key == null) { 256 if (other.key != null) 257 return false; 258 } else if (!key.equals(other.key)) 259 return false; 260 if (!types.equals(other.types)) 261 return false; 262 return true; 263 } 264 265 @Override 266 public String toString() { 267 return "Role [key=" + key + ", text=" + text + ']'; 268 } 269 } -
src/org/openstreetmap/josm/gui/tagging/presets/Roles.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 6 import java.awt.GridBagConstraints; 7 import java.awt.GridBagLayout; 8 import java.util.Map; 9 10 import javax.swing.JLabel; 11 import javax.swing.JPanel; 12 13 import org.openstreetmap.josm.tools.GBC; 14 15 /** 16 * The <code>roles</code> element in tagging presets definition. 17 * <p> 18 * A list of {@link Role} elements. Describes the roles that are expected for 19 * the members of a relation. 20 * <p> 21 * Used for data validation, auto completion, among others. 22 */ 23 final class Roles extends Container { 24 /** the right margin */ 25 private static final int right = 10; 26 27 /** 28 * Private constructor. Use {@link #fromXML} instead. 29 * @param attributes the XML attributes 30 * @throws IllegalArgumentException on illegal attributes 31 */ 32 private Roles(Map<String, String> attributes) throws IllegalArgumentException { 33 super(attributes); 34 } 35 36 /** 37 * Create this class from an XML element's attributes. 38 * @param attributes the XML attributes 39 * @return the new instance 40 * @throws IllegalArgumentException on invalid attributes 41 */ 42 public static Roles fromXML(Map<String, String> attributes) throws IllegalArgumentException { 43 return new Roles(attributes); 44 } 45 46 @Override 47 String getDefaultText() { 48 return tr("Roles:"); 49 } 50 51 @Override 52 JPanel getPanel() { 53 GBC std = GBC.std().insets(0, 0, right, 10); 54 GBC eol = GBC.eol().insets(0, 0, right, 10).fill(GridBagConstraints.HORIZONTAL); 55 56 JPanel panel = new JPanel(new GridBagLayout()); 57 panel.setAlignmentX(0); 58 addBorder(panel); 59 panel.add(new JLabel(tr("Available roles")), std); 60 panel.add(new JLabel(tr("role")), std); 61 panel.add(new JLabel(tr("count")), std); 62 panel.add(new JLabel(tr("elements")), eol); 63 return panel; 64 } 65 66 @Override 67 public String toString() { 68 return "Roles"; 69 } 70 } -
src/org/openstreetmap/josm/gui/tagging/presets/Root.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import java.util.Map; 5 6 import javax.swing.JPanel; 7 8 /** 9 * The XML root element. Corresponds to {@code <presets>}. 10 */ 11 public class Root extends Sequence { 12 /** The url of the XML resource. */ 13 String url; 14 15 final String author; 16 final String version; 17 final String description; 18 final String shortDescription; 19 final String link; 20 final String iconName; 21 final String baseLanguage; 22 23 /** 24 * Constructor. 25 * @param attributes the XML attributes 26 * @throws IllegalArgumentException on illegal attributes 27 */ 28 Root(Map<String, String> attributes) throws IllegalArgumentException { 29 super(attributes); 30 author = attributes.get("author"); 31 version = attributes.get("version"); 32 description = attributes.get("description"); 33 shortDescription = attributes.get("shortdescription"); 34 link = attributes.get("link"); 35 iconName = attributes.get("icon"); 36 baseLanguage = attributes.get("baselanguage"); 37 } 38 39 /** 40 * Create a {@code Root} 41 * @param attributes the XML attributes 42 * @return the {@code Chunk} 43 * @throws IllegalArgumentException on invalid attributes 44 */ 45 public static Root fromXML(Map<String, String> attributes) throws IllegalArgumentException { 46 return new Root(attributes); 47 } 48 49 @Override 50 boolean addToPanel(JPanel p, TaggingPresetInstance support) { 51 return false; 52 } 53 54 @Override 55 public String toString() { 56 return "Root [" + url + "]"; 57 } 58 } -
src/org/openstreetmap/josm/gui/tagging/presets/Sequence.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import java.util.LinkedList; 5 import java.util.List; 6 import java.util.Map; 7 import java.util.function.Predicate; 8 9 import javax.swing.JMenu; 10 import javax.swing.JPanel; 11 12 /** 13 * A sequence of items {@link Item}s. 14 */ 15 public class Sequence extends Item { 16 /** The list of items in the sequence. */ 17 final List<Item> items = new LinkedList<>(); 18 19 /** 20 * Constructor. 21 * @param attributes the XML attributes 22 * @throws IllegalArgumentException on illegal attributes 23 */ 24 Sequence(Map<String, String> attributes) throws IllegalArgumentException { 25 super(attributes); 26 } 27 28 /** 29 * Create this class from an XML element's attributes. 30 * @param attributes the XML attributes 31 * @return the new instance 32 * @throws IllegalArgumentException on illegal attributes 33 */ 34 public static Sequence fromXML(Map<String, String> attributes) throws IllegalArgumentException { 35 return new Sequence(attributes); 36 } 37 38 /** 39 * Adds an item to the container 40 * @param item the item to add 41 */ 42 @Override 43 public void addItem(Item item) { 44 items.add(item); 45 } 46 47 @Override 48 void destroy() { 49 super.destroy(); 50 items.forEach(item -> item.destroy()); 51 } 52 53 @Override 54 boolean addToPanel(JPanel p, TaggingPresetInstance instance) { 55 items.forEach(item -> item.addToPanel(p, instance)); 56 return false; 57 } 58 59 @Override 60 public void addToMenu(JMenu parentMenu) { 61 items.forEach(item -> item.addToMenu(parentMenu)); 62 } 63 64 /** 65 * Returns all items in this sequence. Convenience function. 66 * 67 * @return the list of all items 68 */ 69 public List<Item> getAllItems() { 70 return getAllItems(true); 71 } 72 73 /** 74 * Returns all items in this sequence. 75 * <p> 76 * Use the followReference mode when getting all items inside a preset. Do not follow 77 * references when getting all items inside a root element because you would get all items in 78 * chunks many times over. 79 * 80 * @param followReferences whether to follow references or not 81 * @return the list of all items 82 */ 83 public List<Item> getAllItems(boolean followReferences) { 84 List<Item> result = new LinkedList<>(); 85 items.forEach(item -> item.addToItemList(result, i -> true, followReferences)); 86 return result; 87 } 88 89 /** 90 * Returns all items in this sequence that satisfy a predicate. 91 * @param p the predicate all items must satisfy 92 * @param followReferences whether to follow references or not 93 * @return the list of all items 94 */ 95 public List<Item> getAllItems(Predicate<Item> p, boolean followReferences) { 96 List<Item> list = new LinkedList<>(); 97 items.forEach(item -> item.addToItemList(list, p, followReferences)); 98 return list; 99 } 100 101 /** 102 * Returns all items of a type in this sequence. 103 * @param <E> the type 104 * @param type the type 105 * @param followReferences whether to follow references or not 106 * @return the list of all items 107 */ 108 public <E> List<E> getAllItems(Class<E> type, boolean followReferences) { 109 List<E> list = new LinkedList<>(); 110 items.forEach(item -> item.addToItemList(list, type, followReferences)); 111 return list; 112 } 113 114 /** 115 * Returns all roles in this sequence. Convenience function. 116 * 117 * @return the list of all roles 118 */ 119 public List<Role> getAllRoles() { 120 return getAllRoles(true); 121 } 122 123 /** 124 * Returns all roles in this sequence. 125 * @param followReferences whether to follow references or not 126 * @return the list of all roles 127 */ 128 public List<Role> getAllRoles(boolean followReferences) { 129 return getAllItems(Role.class, followReferences); 130 } 131 132 @Override 133 public void addToItemList(List<Item> list, Predicate<Item> p, boolean followReferences) { 134 super.addToItemList(list, p, followReferences); 135 items.forEach(item -> item.addToItemList(list, p, followReferences)); 136 } 137 138 @Override 139 <E> void addToItemList(List<E> list, Class<E> type, boolean followReferences) { 140 super.addToItemList(list, type, followReferences); 141 items.forEach(item -> item.addToItemList(list, type, followReferences)); 142 } 143 144 @Override 145 void fixup(Map<String, Chunk> chunks, Item parent) { 146 items.forEach(item -> item.fixup(chunks, parent)); 147 } 148 149 @Override 150 public Boolean matches(Map<String, String> tags) { 151 for (Item item : items) { 152 if (Boolean.TRUE.equals(item.matches(tags))) { 153 return Boolean.TRUE; 154 } 155 } 156 return null; 157 } 158 159 @Override 160 public String toString() { 161 return "Collection"; 162 } 163 } -
src/org/openstreetmap/josm/gui/tagging/presets/Space.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import java.util.Map; 5 6 import javax.swing.JLabel; 7 import javax.swing.JPanel; 8 9 import org.openstreetmap.josm.tools.GBC; 10 11 /** 12 * A horizontal spacer. 13 */ 14 final class Space extends Item { 15 private Space(Map<String, String> attributes) throws IllegalArgumentException { 16 super(attributes); 17 } 18 19 /** 20 * Create this class from an XML element's attributes. 21 * @param attributes the XML attributes (ignored) 22 * @return the new instance 23 * @throws IllegalArgumentException on invalid attributes 24 */ 25 public static Space fromXML(Map<String, String> attributes) throws IllegalArgumentException { 26 return new Space(attributes); 27 } 28 29 @Override 30 boolean addToPanel(JPanel p, TaggingPresetInstance support) { 31 p.add(new JLabel(" "), GBC.eol()); // space 32 return false; 33 } 34 35 @Override 36 public String toString() { 37 return "Space"; 38 } 39 } -
src/org/openstreetmap/josm/gui/tagging/presets/TaggingPreset.java
2 2 package org.openstreetmap.josm.gui.tagging.presets; 3 3 4 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 import static org.openstreetmap.josm.tools.I18n.trc;6 import static org.openstreetmap.josm.tools.I18n.trn;7 5 8 import java.awt.Component;9 6 import java.awt.ComponentOrientation; 10 import java.awt.Dimension;11 7 import java.awt.GridBagLayout; 12 import java.awt.Insets;13 8 import java.awt.event.ActionEvent; 14 import java.io.File;15 9 import java.util.ArrayList; 16 10 import java.util.Collection; 11 import java.util.Collections; 17 12 import java.util.EnumSet; 18 import java.util. LinkedHashSet;13 import java.util.HashMap; 19 14 import java.util.List; 20 15 import java.util.Map; 21 16 import java.util.Objects; 22 17 import java.util.Set; 23 import java.util.concurrent.CompletableFuture;24 18 import java.util.function.Predicate; 25 19 import java.util.stream.Collectors; 26 20 27 21 import javax.swing.AbstractAction; 28 22 import javax.swing.Action; 29 import javax.swing.ImageIcon;30 23 import javax.swing.JLabel; 31 import javax.swing.JOptionPane; 24 import javax.swing.JMenu; 25 import javax.swing.JMenuItem; 32 26 import javax.swing.JPanel; 33 27 import javax.swing.JToggleButton; 34 import javax.swing.SwingUtilities;35 28 36 import org.openstreetmap.josm.actions.AdaptableAction;37 import org.openstreetmap.josm.actions.CreateMultipolygonAction;38 import org.openstreetmap.josm.command.ChangePropertyCommand;39 import org.openstreetmap.josm.command.Command;40 import org.openstreetmap.josm.command.SequenceCommand;41 import org.openstreetmap.josm.data.UndoRedoHandler;42 29 import org.openstreetmap.josm.data.osm.DataSet; 43 30 import org.openstreetmap.josm.data.osm.IPrimitive; 44 import org.openstreetmap.josm.data.osm.OsmData;45 31 import org.openstreetmap.josm.data.osm.OsmDataManager; 46 32 import org.openstreetmap.josm.data.osm.OsmPrimitive; 47 import org.openstreetmap.josm.data.osm.Relation;48 import org.openstreetmap.josm.data.osm.RelationMember;49 33 import org.openstreetmap.josm.data.osm.Tag; 50 34 import org.openstreetmap.josm.data.osm.Tagged; 51 import org.openstreetmap.josm.data.osm.Way;52 import org.openstreetmap.josm.data.osm.search.SearchCompiler;53 35 import org.openstreetmap.josm.data.osm.search.SearchCompiler.Match; 54 import org.openstreetmap.josm.data.osm.search.SearchParseError;55 import org.openstreetmap.josm.data.preferences.BooleanProperty;56 import org.openstreetmap.josm.gui.ExtendedDialog;57 36 import org.openstreetmap.josm.gui.MainApplication; 58 import org.openstreetmap.josm.gui.Notification;59 import org.openstreetmap.josm.gui.dialogs.relation.RelationEditor;60 import org.openstreetmap.josm.gui.dialogs.relation.sort.RelationSorter;61 import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent;62 import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;63 37 import org.openstreetmap.josm.gui.preferences.ToolbarPreferences; 64 import org.openstreetmap.josm.gui.tagging.presets.items.Key;65 import org.openstreetmap.josm.gui.tagging.presets.items.Link;66 import org.openstreetmap.josm.gui.tagging.presets.items.Optional;67 import org.openstreetmap.josm.gui.tagging.presets.items.PresetLink;68 import org.openstreetmap.josm.gui.tagging.presets.items.Roles;69 import org.openstreetmap.josm.gui.tagging.presets.items.Space;70 38 import org.openstreetmap.josm.gui.util.GuiHelper; 71 39 import org.openstreetmap.josm.tools.GBC; 72 40 import org.openstreetmap.josm.tools.ImageProvider; 73 import org.openstreetmap.josm.tools.ImageResource;74 import org.openstreetmap.josm.tools.Logging;75 import org.openstreetmap.josm.tools.Pair;76 import org.openstreetmap.josm.tools.StreamUtils;77 41 import org.openstreetmap.josm.tools.Utils; 78 import org.openstreetmap.josm.tools.template_engine.ParseError;79 42 import org.openstreetmap.josm.tools.template_engine.TemplateEntry; 80 import org.openstreetmap.josm.tools.template_engine.TemplateParser;81 import org.xml.sax.SAXException;82 43 83 44 /** 84 * This class read encapsulate one tagging preset. A class method can 85 * read in all predefined presets, either shipped with JOSM or that are 86 * in the config directory. 45 * A template class to build preset dialogs. 46 * <p> 47 * This class is an immutable template class mainly used to build Swing dialogs. It also creates 48 * menu and toolbar entries. 49 * <p> 50 * This class is immutable and uses the companion class {@link TaggingPresetInstance} to store 51 * instance data. 87 52 * 88 * It is also able to construct dialogs out of preset definitions.89 53 * @since 294 90 54 */ 91 public class TaggingPreset extends AbstractAction implements ActiveLayerChangeListener, AdaptableAction, Predicate<IPrimitive> { 92 93 /** The user pressed the "Apply" button */ 94 public static final int DIALOG_ANSWER_APPLY = 1; 95 /** The user pressed the "New Relation" button */ 96 public static final int DIALOG_ANSWER_NEW_RELATION = 2; 97 /** The user pressed the "Cancel" button */ 98 public static final int DIALOG_ANSWER_CANCEL = 3; 99 100 /** The action key for optional tooltips */ 101 public static final String OPTIONAL_TOOLTIP_TEXT = "Optional tooltip text"; 102 103 /** Prefix of preset icon loading failure error message */ 104 public static final String PRESET_ICON_ERROR_MSG_PREFIX = "Could not get presets icon "; 105 55 public class TaggingPreset extends TaggingPresetBase implements Predicate<IPrimitive> { 56 /** Show the preset name and icon in the dialog if true */ 57 private final boolean presetNameLabel; 58 /** The types this preset applies to. */ 59 private final Set<TaggingPresetType> types; 106 60 /** 107 * Defines whether the validator should be active in the preset dialog108 * @see TaggingPresetValidation109 */110 public static final BooleanProperty USE_VALIDATOR = new BooleanProperty("taggingpreset.validator", false);111 112 /**113 * The preset group this preset belongs to.114 */115 public TaggingPresetMenu group;116 117 /**118 * The name of the tagging preset.119 * @see #getRawName()120 */121 public String name;122 /**123 * The icon name assigned to this preset.124 */125 public String iconName;126 /**127 * Translation context for name128 */129 public String name_context;130 /**131 * A cache for the local name. Should never be accessed directly.132 * @see #getLocaleName()133 */134 public String locale_name;135 /**136 * Show the preset name if true137 */138 public boolean preset_name_label;139 140 /**141 * The types as preparsed collection.142 */143 public transient Set<TaggingPresetType> types;144 /**145 * The list of preset items146 */147 public final transient List<TaggingPresetItem> data = new ArrayList<>(2);148 /**149 * The roles for this relation (if we are editing a relation). See:150 * <a href="https://josm.openstreetmap.de/wiki/TaggingPresets#Tags">JOSM wiki</a>151 */152 public transient Roles roles;153 /**154 61 * The name_template custom name formatter. See: 155 62 * <a href="https://josm.openstreetmap.de/wiki/TaggingPresets#Attributes">JOSM wiki</a> 156 63 */ 157 p ublic transientTemplateEntry nameTemplate;64 private final TemplateEntry nameTemplate; 158 65 /** The name_template_filter */ 159 p ublic transientMatch nameTemplateFilter;66 private final Match nameTemplateFilter; 160 67 /** The match_expression */ 161 p ublic transientMatch matchExpression;68 private final Match matchExpression; 162 69 163 70 /** 164 * True whenever the original selection given into createSelection was empty 71 * A store to persist information from one invocation of this preset's dialog to the next. 72 * <p> 73 * Example: The autoincrement setting of the street address preset. 165 74 */ 166 private boolean originalSelectionEmpty;75 Map<String, Object> properties = new HashMap<>(); 167 76 168 /** The completable future task of asynchronous icon loading */169 private CompletableFuture<Void> iconFuture;170 171 /** Support functions */172 protected TaggingPresetItemGuiSupport itemGuiSupport;173 174 77 /** 175 78 * Create an empty tagging preset. This will not have any items and 176 79 * will be an empty string as text. createPanel will return null. 177 80 * Use this as default item for "do not select anything". 81 * 82 * @param attributes the XML attributes 83 * @throws IllegalArgumentException on invalid attributes 178 84 */ 179 public TaggingPreset() { 180 updateEnabledState(); 181 } 85 TaggingPreset(Map<String, String> attributes) throws IllegalArgumentException { 86 super(attributes); 182 87 183 /** 184 * Change the display name without changing the toolbar value. 185 */ 186 public void setDisplayName() { 187 putValue(Action.NAME, getName()); 188 putValue("toolbar", "tagging_" + getRawName()); 189 putValue(OPTIONAL_TOOLTIP_TEXT, group != null ? 190 tr("Use preset ''{0}'' of group ''{1}''", getLocaleName(), group.getName()) : 191 tr("Use preset ''{0}''", getLocaleName())); 88 presetNameLabel = TaggingPresetUtils.parseBoolean(attributes.getOrDefault("preset_name_label", "false")); 89 types = TaggingPresetType.getOrDefault(attributes.get("type"), EnumSet.allOf(TaggingPresetType.class)); 90 nameTemplate = TaggingPresetUtils.parseTemplate(attributes.get("name_template")); 91 nameTemplateFilter = TaggingPresetUtils.parseSearchExpression(attributes.get("name_template_filter")); 92 matchExpression = TaggingPresetUtils.parseSearchExpression(attributes.get("match_expression")); 192 93 } 193 94 194 95 /** 195 * Gets the localized version of the name 196 * @return The name that should be displayed to the user. 96 * Create a {@code TaggingPreset} from an XML element's attributes. 97 * @param attributes the XML attributes 98 * @return the {@code TaggingPreset} 99 * @throws IllegalArgumentException on invalid attributes 197 100 */ 198 public String getLocaleName() { 199 if (locale_name == null) { 200 if (name_context != null) { 201 locale_name = trc(name_context, TaggingPresetItem.fixPresetString(name)); 202 } else { 203 locale_name = tr(TaggingPresetItem.fixPresetString(name)); 204 } 205 } 206 return locale_name; 101 public static TaggingPreset fromXML(Map<String, String> attributes) throws IllegalArgumentException { 102 return new TaggingPreset(attributes); 207 103 } 208 104 209 /** 210 * Returns the translated name of this preset, prefixed with the group names it belongs to. 211 * @return the translated name of this preset, prefixed with the group names it belongs to 212 */ 213 public String getName() { 214 return group != null ? group.getName() + '/' + getLocaleName() : getLocaleName(); 105 @Override 106 void fixup(Map<String, Chunk> chunks, Item parent) { 107 super.fixup(chunks, parent); 108 action = new TaggingPresetAction(); 109 iconFuture = TaggingPresetUtils.loadIcon(iconName, action); 215 110 } 216 111 217 /** 218 * Returns the non translated name of this preset, prefixed with the (non translated) group names it belongs to. 219 * @return the non translated name of this preset, prefixed with the (non translated) group names it belongs to 220 */ 221 public String getRawName() { 222 return group != null ? group.getRawName() + '/' + name : name; 112 @Override 113 public void addToMenu(JMenu parentMenu) { 114 JMenuItem menuItem = new JMenuItem(getAction()); 115 menuItem.setText(getLocaleName()); 116 parentMenu.add(menuItem); 223 117 } 224 118 225 119 /** 226 * Returns the preset icon (16px). 227 * @return The preset icon, or {@code null} if none defined 228 * @since 6403 120 * Returns whether the preset name should be shown in the dialog 121 * @return whether the preset name should be shown in the dialog 229 122 */ 230 public final ImageIcon getIcon() {231 return getIcon(Action.SMALL_ICON);123 public boolean getPresetNameLabel() { 124 return presetNameLabel; 232 125 } 233 126 234 127 /** 235 * Returns the preset icon (16 or 24px). 236 * @param key Key determining icon size: {@code Action.SMALL_ICON} for 16x, {@code Action.LARGE_ICON_KEY} for 24px 237 * @return The preset icon, or {@code null} if none defined 238 * @since 10849 128 * Returns the primitive types this preset applies to 129 * @return the set of types 239 130 */ 240 public final ImageIcon getIcon(String key) { 241 Object icon = getValue(key); 242 if (icon instanceof ImageIcon) { 243 return (ImageIcon) icon; 244 } 245 return null; 131 public Set<TaggingPresetType> getTypes() { 132 return Collections.unmodifiableSet(types); 246 133 } 247 134 248 135 /** 249 * Returns the {@link ImageResource} attached to this preset, if any. 250 * @return the {@code ImageResource} attached to this preset, or {@code null} 251 * @since 16060 136 * Returns the name template 137 * @return the name template 252 138 */ 253 public final ImageResource getImageResource() {254 return ImageResource.getAttachedImageResource(this);139 public TemplateEntry getNameTemplate() { 140 return nameTemplate; 255 141 } 256 142 257 143 /** 258 * Called from the XML parser to set the icon. 259 * The loading task is performed in the background in order to speedup startup. 260 * @param iconName icon name 144 * Returns the name template filter 145 * @return the name template filter 261 146 */ 262 public void setIcon(final String iconName) { 263 this.iconName = iconName; 264 if (iconName == null || !TaggingPresetReader.isLoadIcons()) { 265 return; 266 } 267 File arch = TaggingPresetReader.getZipIcons(); 268 final Collection<String> s = TaggingPresets.ICON_SOURCES.get(); 269 this.iconFuture = new CompletableFuture<>(); 270 new ImageProvider(iconName) 271 .setDirs(s) 272 .setId("presets") 273 .setArchive(arch) 274 .setOptional(true) 275 .getResourceAsync(result -> { 276 if (result != null) { 277 GuiHelper.runInEDT(() -> { 278 try { 279 result.attachImageIcon(this, true); 280 } catch (IllegalArgumentException e) { 281 Logging.warn(toString() + ": " + PRESET_ICON_ERROR_MSG_PREFIX + iconName); 282 Logging.warn(e); 283 } finally { 284 iconFuture.complete(null); 285 } 286 }); 287 } else { 288 Logging.warn(toString() + ": " + PRESET_ICON_ERROR_MSG_PREFIX + iconName); 289 iconFuture.complete(null); 290 } 291 }); 147 public Match getNameTemplateFilter() { 148 return nameTemplateFilter; 292 149 } 293 150 294 151 /** 295 * Called from the XML parser to set the types this preset affects. 296 * @param types comma-separated primitive types ("node", "way", "relation" or "closedway") 297 * @throws SAXException if any SAX error occurs 298 * @see TaggingPresetType#fromString 152 * Creates a panel for this preset. This includes general information such as name and supported 153 * {@link TaggingPresetType types}. This includes the elements from the individual 154 * {@link Item items}. 155 * @param p the panel to add to 156 * @param support the support 157 * @return true if any elements where added 299 158 */ 300 public void setType(String types) throws SAXException { 301 this.types = TaggingPresetItem.getType(types); 302 } 159 @Override 160 boolean addToPanel(JPanel p, TaggingPresetInstance support) { 161 Collection<OsmPrimitive> selected = support.getSelected(); 162 boolean hasElements = false; 303 163 304 /**305 * Sets the name_template custom name formatter.306 *307 * @param template The format template308 * @throws SAXException on template parse error309 * @see <a href="https://josm.openstreetmap.de/wiki/TaggingPresets#name_templatedetails">JOSM wiki</a>310 */311 public void setName_template(String template) throws SAXException {312 try {313 this.nameTemplate = new TemplateParser(template).parse();314 } catch (ParseError e) {315 Logging.error("Error while parsing " + template + ": " + e.getMessage());316 throw new SAXException(e);317 }318 }319 320 /**321 * Sets the name_template_filter.322 *323 * @param filter The search pattern324 * @throws SAXException on search patern parse error325 * @see <a href="https://josm.openstreetmap.de/wiki/TaggingPresets#name_templatedetails">JOSM wiki</a>326 */327 public void setName_template_filter(String filter) throws SAXException {328 try {329 this.nameTemplateFilter = SearchCompiler.compile(filter);330 } catch (SearchParseError e) {331 Logging.error("Error while parsing" + filter + ": " + e.getMessage());332 throw new SAXException(e);333 }334 }335 336 /**337 * Sets the match_expression additional criteria for matching primitives.338 *339 * @param filter The search pattern340 * @throws SAXException on search patern parse error341 * @see <a href="https://josm.openstreetmap.de/wiki/TaggingPresets#Attributes">JOSM wiki</a>342 */343 public void setMatch_expression(String filter) throws SAXException {344 try {345 this.matchExpression = SearchCompiler.compile(filter);346 } catch (SearchParseError e) {347 Logging.error("Error while parsing" + filter + ": " + e.getMessage());348 throw new SAXException(e);349 }350 }351 352 private static class PresetPanel extends JPanel {353 private boolean hasElements;354 355 PresetPanel() {356 super(new GridBagLayout());357 }358 }359 360 /**361 * Creates a panel for this preset. This includes general information such as name and supported {@link TaggingPresetType types}.362 * This includes the elements from the individual {@link TaggingPresetItem items}.363 *364 * @param selected the selected primitives365 * @return the newly created panel366 */367 public PresetPanel createPanel(Collection<OsmPrimitive> selected) {368 PresetPanel p = new PresetPanel();369 370 164 final JPanel pp = new JPanel(); 371 if (types != null) { 372 for (TaggingPresetType t : types) { 373 JLabel la = new JLabel(ImageProvider.get(t.getIconName())); 374 la.setToolTipText(tr("Elements of type {0} are supported.", tr(t.getName()))); 375 pp.add(la); 376 } 165 for (TaggingPresetType t : types) { 166 JLabel la = new JLabel(ImageProvider.get(t.getIconName())); 167 la.setToolTipText(tr("Elements of type {0} are supported.", tr(t.getName()))); 168 pp.add(la); 377 169 } 378 final List<Tag> directlyAppliedTags = Utils.filteredCollection( data, Key.class).stream()170 final List<Tag> directlyAppliedTags = Utils.filteredCollection(items, Key.class).stream() 379 171 .map(Key::asTag) 380 172 .collect(Collectors.toList()); 381 173 if (!directlyAppliedTags.isEmpty()) { … … 388 180 pp.add(validationLabel); 389 181 390 182 final int count = pp.getComponentCount(); 391 if (preset _name_label) {183 if (presetNameLabel) { 392 184 p.add(new JLabel(getIcon(Action.LARGE_ICON_KEY)), GBC.std(0, 0).span(1, count > 0 ? 2 : 1).insets(0, 0, 5, 0)); 393 185 } 394 186 if (count > 0) { 395 187 p.add(pp, GBC.std(1, 0).span(GBC.REMAINDER)); 396 188 } 397 if (preset _name_label) {189 if (presetNameLabel) { 398 190 p.add(new JLabel(getName()), GBC.std(1, count > 0 ? 1 : 0).insets(5, 0, 0, 0).span(GBC.REMAINDER).fill(GBC.HORIZONTAL)); 399 191 } 400 192 401 boolean presetInitiallyMatches = !selected.isEmpty() && selected.stream().allMatch(this);402 itemGuiSupport = TaggingPresetItemGuiSupport.create(presetInitiallyMatches, selected, this::getChangedTags);403 404 193 JPanel itemPanel = new JPanel(new GridBagLayout()) { 405 194 /** 406 195 * This hack allows the items to have their own orientation. … … 419 208 } 420 209 }; 421 210 JPanel linkPanel = new JPanel(new GridBagLayout()); 422 TaggingPresetItem previous = null;423 for ( TaggingPresetItem i : data) {211 Item previous = null; 212 for (Item i : items) { 424 213 if (i instanceof Link) { 425 i.addToPanel(linkPanel, itemGuiSupport);426 p.hasElements = true;214 i.addToPanel(linkPanel, support); 215 hasElements = true; 427 216 } else { 428 217 if (i instanceof PresetLink) { 429 218 PresetLink link = (PresetLink) i; 430 if (!(previous instanceof PresetLink && Objects.equals(((PresetLink) previous). text, link.text))) {219 if (!(previous instanceof PresetLink && Objects.equals(((PresetLink) previous).getText(), link.getText()))) { 431 220 itemPanel.add(link.createLabel(), GBC.eol().insets(0, 8, 0, 0)); 432 221 } 433 222 } 434 if (i.addToPanel(itemPanel, itemGuiSupport)) {435 p.hasElements = true;223 if (i.addToPanel(itemPanel, support)) { 224 hasElements = true; 436 225 } 437 226 } 438 227 previous = i; … … 444 233 GuiHelper.setEnabledRec(itemPanel, false); 445 234 } 446 235 447 if (selected.size() == 1 && USE_VALIDATOR.get()) { 448 itemGuiSupport.addListener((source, key, newValue) -> 449 TaggingPresetValidation.validateAsync(selected.iterator().next(), validationLabel, getChangedTags())); 236 if (selected.size() == 1 && TaggingPresets.USE_VALIDATOR.get()) { 237 support.addListener((source, key, newValue) -> { 238 TaggingPresetHandler handler = new CloneTaggingPresetHandler(selected); 239 handler.updateTags(support.getChangedTags()); 240 TaggingPresetValidation.validateAsync(handler, validationLabel); 241 }); 450 242 } 451 243 452 // "Add toolbar button"244 // add the "pin" button 453 245 JToggleButton tb = new JToggleButton(new ToolbarButtonAction()); 454 246 tb.setFocusable(false); 455 247 p.add(tb, GBC.std(1, 0).anchor(GBC.LINE_END)); 456 248 457 249 // Trigger initial updates once and only once 458 itemGuiSupport.setEnabled(true); 459 itemGuiSupport.fireItemValueModified(null, null, null); 460 461 return p; 250 support.setEnabled(true); 251 support.fireItemValueModified(null, null, null); 252 return hasElements; 462 253 } 463 254 464 255 /** … … 467 258 * @return {@code true} if a dialog can be shown for this preset 468 259 */ 469 260 public boolean isShowable() { 470 return data.stream().anyMatch(i -> !(i instanceof Optional || i instanceof Space || i instanceof Key));261 return getAllItems(i -> !(i instanceof Container || i instanceof Space || i instanceof Key), true).size() > 0; 471 262 } 472 263 473 264 /** 474 265 * Suggests a relation role for this primitive 266 * <p> 267 * Suggests a role when the primitive is added to a relation. 475 268 * 476 269 * @param osm The primitive 477 270 * @return the suggested role or null 478 271 */ 479 272 public String suggestRoleForOsmPrimitive(OsmPrimitive osm) { 480 if (roles != null && osm != null) { 481 return roles.roles.stream() 482 .filter(i -> i.memberExpression != null && i.memberExpression.match(osm)) 483 .filter(i -> Utils.isEmpty(i.types) || i.types.contains(TaggingPresetType.forPrimitive(osm))) 484 .findFirst() 485 .map(i -> i.key) 486 .orElse(null); 487 } 488 return null; 273 if (osm == null) 274 return null; 275 return getAllRoles().stream() 276 .filter(role -> role.getMemberExpression() != null && role.getMemberExpression().match(osm)) 277 .filter(role -> role.appliesTo(TaggingPresetType.forPrimitive(osm))) 278 .findFirst() 279 .map(i -> i.getKey()) 280 .orElse(null); 489 281 } 490 282 491 @Override492 public void actionPerformed(ActionEvent e) {493 DataSet ds = OsmDataManager.getInstance().getEditDataSet();494 if (ds == null) {495 return;496 }497 showAndApply(ds.getSelected());498 }499 500 283 /** 501 * {@linkplain #showDialog Show preset dialog}, apply changes 502 * @param primitives the primitives 503 */ 504 public void showAndApply(Collection<OsmPrimitive> primitives) { 505 // Display dialog even if no data layer (used by preset-tagging-tester plugin) 506 Collection<OsmPrimitive> sel = createSelection(primitives); 507 int answer = showDialog(sel, supportsRelation()); 508 509 if (!sel.isEmpty() && answer == DIALOG_ANSWER_APPLY) { 510 Command cmd = createCommand(sel, getChangedTags()); 511 if (cmd != null) { 512 UndoRedoHandler.getInstance().add(cmd); 513 } 514 } else if (answer == DIALOG_ANSWER_NEW_RELATION) { 515 Relation calculated = null; 516 if (getChangedTags().stream().anyMatch(t -> "boundary".equals(t.get("type")) || "multipolygon".equals(t.get("type")))) { 517 Collection<Way> ways = Utils.filteredCollection(primitives, Way.class); 518 Pair<Relation, Relation> res = CreateMultipolygonAction.createMultipolygonRelation(ways, true); 519 if (res != null) { 520 calculated = res.b; 521 } 522 } 523 final Relation r = calculated != null ? calculated : new Relation(); 524 final Collection<RelationMember> members = new LinkedHashSet<>(r.getMembers()); 525 for (Tag t : getChangedTags()) { 526 r.put(t.getKey(), t.getValue()); 527 } 528 for (OsmPrimitive osm : primitives) { 529 if (r == calculated && osm instanceof Way) 530 continue; 531 String role = suggestRoleForOsmPrimitive(osm); 532 RelationMember rm = new RelationMember(role == null ? "" : role, osm); 533 r.addMember(rm); 534 members.add(rm); 535 } 536 if (r.isMultipolygon() && r != calculated) { 537 r.setMembers(RelationSorter.sortMembersByConnectivity(r.getMembers())); 538 } 539 SwingUtilities.invokeLater(() -> RelationEditor.getEditor( 540 MainApplication.getLayerManager().getEditLayer(), r, members).setVisible(true)); 541 } 542 if (!primitives.isEmpty()) { 543 DataSet ds = primitives.iterator().next().getDataSet(); 544 ds.setSelected(primitives); // force update 545 } 546 } 547 548 private static class PresetDialog extends ExtendedDialog { 549 550 /** 551 * Constructs a new {@code PresetDialog}. 552 * @param content the content that will be displayed in this dialog 553 * @param title the text that will be shown in the window titlebar 554 * @param icon the image to be displayed as the icon for this window 555 * @param disableApply whether to disable "Apply" button 556 * @param showNewRelation whether to display "New relation" button 557 */ 558 PresetDialog(Component content, String title, ImageIcon icon, boolean disableApply, boolean showNewRelation) { 559 super(MainApplication.getMainFrame(), title, 560 showNewRelation ? 561 (new String[] {tr("Apply Preset"), tr("New relation"), tr("Cancel")}) : 562 (new String[] {tr("Apply Preset"), tr("Cancel")}), 563 true); 564 if (icon != null) 565 setIconImage(icon.getImage()); 566 contentInsets = new Insets(10, 5, 0, 5); 567 if (showNewRelation) { 568 setButtonIcons("ok", "data/relation", "cancel"); 569 } else { 570 setButtonIcons("ok", "cancel"); 571 } 572 configureContextsensitiveHelp("/Menu/Presets", true); 573 setContent(content); 574 setDefaultButton(1); 575 setupDialog(); 576 buttons.get(0).setEnabled(!disableApply); 577 buttons.get(0).setToolTipText(title); 578 // Prevent dialogs of being too narrow (fix #6261) 579 Dimension d = getSize(); 580 if (d.width < 350) { 581 d.width = 350; 582 setSize(d); 583 } 584 super.showDialog(); 585 } 586 } 587 588 /** 589 * Shows the preset dialog. 590 * @param sel selection 591 * @param showNewRelation whether to display "New relation" button 592 * @return the user choice after the dialog has been closed 593 */ 594 public int showDialog(Collection<OsmPrimitive> sel, boolean showNewRelation) { 595 PresetPanel p = createPanel(sel); 596 597 int answer = 1; 598 boolean canCreateRelation = types == null || types.contains(TaggingPresetType.RELATION); 599 if (originalSelectionEmpty && !canCreateRelation) { 600 new Notification( 601 tr("The preset <i>{0}</i> cannot be applied since nothing has been selected!", getLocaleName())) 602 .setIcon(JOptionPane.WARNING_MESSAGE) 603 .show(); 604 return DIALOG_ANSWER_CANCEL; 605 } else if (sel.isEmpty() && !canCreateRelation) { 606 new Notification( 607 tr("The preset <i>{0}</i> cannot be applied since the selection is unsuitable!", getLocaleName())) 608 .setIcon(JOptionPane.WARNING_MESSAGE) 609 .show(); 610 return DIALOG_ANSWER_CANCEL; 611 } else if (p.getComponentCount() != 0 && (sel.isEmpty() || p.hasElements)) { 612 int size = sel.size(); 613 String title = trn("Change {0} object", "Change {0} objects", size, size); 614 if (!showNewRelation && size == 0) { 615 if (originalSelectionEmpty) { 616 title = tr("Nothing selected!"); 617 } else { 618 title = tr("Selection unsuitable!"); 619 } 620 } 621 622 boolean disableApply = size == 0; 623 if (!disableApply) { 624 OsmData<?, ?, ?, ?> ds = sel.iterator().next().getDataSet(); 625 disableApply = ds != null && ds.isLocked(); 626 } 627 answer = new PresetDialog(p, title, preset_name_label ? null : (ImageIcon) getValue(Action.SMALL_ICON), 628 disableApply, showNewRelation).getValue(); 629 } 630 if (!showNewRelation && answer == 2) 631 return DIALOG_ANSWER_CANCEL; 632 else 633 return answer; 634 } 635 636 /** 637 * Removes all unsuitable OsmPrimitives from the given list 638 * @param participants List of possible OsmPrimitives to tag 639 * @return Cleaned list with suitable OsmPrimitives only 640 */ 641 public Collection<OsmPrimitive> createSelection(Collection<OsmPrimitive> participants) { 642 originalSelectionEmpty = participants.isEmpty(); 643 return participants.stream().filter(this::typeMatches).collect(Collectors.toList()); 644 } 645 646 /** 647 * Gets a list of tags that are set by this preset. 284 * Gets the current (edited) state of the tags. 285 * @param support the support 648 286 * @return The list of tags. 649 287 */ 650 public List<Tag> getChangedTags() { 651 List<Tag> result = new ArrayList<>(); 652 data.forEach(i -> i.addCommands(result)); 653 return result; 288 public List<Tag> getChangedTags(TaggingPresetInstance support) { 289 return support.getChangedTags(); 654 290 } 655 291 656 292 /** 657 * Create a command to change the given list of tags. 658 * @param sel The primitives to change the tags for 659 * @param changedTags The tags to change 660 * @return A command that changes the tags. 293 * Return true if this preset applies to relations 294 * @return true if this preset applies to relations 661 295 */ 662 public static Command createCommand(Collection<OsmPrimitive> sel, List<Tag> changedTags) { 663 List<Command> cmds = changedTags.stream() 664 .map(tag -> new ChangePropertyCommand(sel, tag.getKey(), tag.getValue())) 665 .filter(cmd -> cmd.getObjectsNumber() > 0) 666 .collect(StreamUtils.toUnmodifiableList()); 667 return cmds.isEmpty() ? null : SequenceCommand.wrapIfNeeded(tr("Change Tags"), cmds); 296 public boolean supportsRelation() { 297 return types.contains(TaggingPresetType.RELATION); 668 298 } 669 299 670 private boolean supportsRelation() {671 return types == null || types.contains(TaggingPresetType.RELATION);672 }673 674 protected final void updateEnabledState() {675 setEnabled(OsmDataManager.getInstance().getEditDataSet() != null);676 }677 678 300 @Override 679 public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) {680 updateEnabledState();681 }682 683 @Override684 301 public String toString() { 685 return (types == null ? "" : types.toString()) + ' ' + name;302 return "TaggingPreset " + types.toString() + " " + getName(); 686 303 } 687 304 688 305 /** … … 701 318 * @return <code>true</code> if all types match. 702 319 */ 703 320 public boolean typeMatches(Collection<TaggingPresetType> t) { 704 return t == null || types == null || types.containsAll(t);321 return t == null || types.containsAll(t); 705 322 } 706 323 707 324 /** 708 325 * Determines whether this preset matches the given primitive, i.e., 709 * whether the {@link #typeMatches(Collection) type matches} and the {@link TaggingPresetItem#matches(Map) tags match}.326 * whether the {@link #typeMatches(Collection) type matches} and the {@link Item#matches(Map) tags match}. 710 327 * 711 328 * @param p the primitive 712 329 * @return {@code true} if this preset matches the primitive … … 721 338 * Determines whether this preset matches the parameters. 722 339 * 723 340 * @param t the preset types to include, see {@link #typeMatches(Collection)} 724 * @param tags the tags to perform matching on, see {@link TaggingPresetItem#matches(Map)}341 * @param tags the tags to perform matching on, see {@link Item#matches(Map)} 725 342 * @param onlyShowable whether the preset must be {@link #isShowable() showable} 726 343 * @return {@code true} if this preset matches the parameters. 727 344 */ … … 731 348 } else if (matchExpression != null && !matchExpression.match(Tagged.ofMap(tags))) { 732 349 return false; 733 350 } else { 734 return TaggingPreset Item.matches(data, tags);351 return TaggingPresetUtils.matches(items, tags); 735 352 } 736 353 } 737 354 738 355 /** 739 * A ction that adds or removes the button on main toolbar356 * An action that opens the preset dialog. 740 357 */ 358 public class TaggingPresetAction extends TaggingPresetBase.TaggingPresetBaseAction { 359 TaggingPresetAction() { 360 super(); 361 putValue(Action.NAME, getName()); 362 putValue("toolbar", "tagging_" + getRawName()); 363 putValue(OPTIONAL_TOOLTIP_TEXT, tr("Use preset ''{0}''", getLocaleFullName())); 364 MainApplication.getLayerManager().addActiveLayerChangeListener(this); 365 } 366 367 @Override 368 public void actionPerformed(ActionEvent e) { 369 DataSet dataSet = OsmDataManager.getInstance().getEditDataSet(); 370 if (dataSet == null) { 371 return; 372 } 373 Collection<OsmPrimitive> filtered = dataSet.getSelected().stream() 374 .filter(TaggingPreset.this::typeMatches).collect(Collectors.toList()); 375 TaggingPresetDialog.showAndApply(TaggingPreset.this, new DataSetTaggingPresetHandler(filtered), supportsRelation()); 376 } 377 } 378 379 /** 380 * Action that "pins" the preset to the main toolbar 381 */ 741 382 public class ToolbarButtonAction extends AbstractAction { 742 383 private final int toolbarIndex; 743 384 … … 755 396 756 397 @Override 757 398 public void actionPerformed(ActionEvent ae) { 758 String res = getToolbarString(); 759 MainApplication.getToolbar().addCustomButton(res, toolbarIndex, true); 399 MainApplication.getToolbar().addCustomButton(getToolbarString(), toolbarIndex, true); 760 400 } 761 401 } 762 763 /**764 * Gets a string describing this preset that can be used for the toolbar765 * @return A String that can be passed on to the toolbar766 * @see ToolbarPreferences#addCustomButton(String, int, boolean)767 */768 public String getToolbarString() {769 ToolbarPreferences.ActionParser actionParser = new ToolbarPreferences.ActionParser(null);770 return actionParser.saveAction(new ToolbarPreferences.ActionDefinition(this));771 }772 773 /**774 * Returns the completable future task that performs icon loading, if any.775 * @return the completable future task that performs icon loading, or null776 * @since 14449777 */778 public CompletableFuture<Void> getIconLoadingTask() {779 return iconFuture;780 }781 782 402 } -
src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetBase.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import java.util.Map; 5 import java.util.concurrent.CompletableFuture; 6 7 import javax.swing.AbstractAction; 8 import javax.swing.Action; 9 import javax.swing.ImageIcon; 10 11 import org.openstreetmap.josm.actions.AdaptableAction; 12 import org.openstreetmap.josm.data.osm.OsmDataManager; 13 import org.openstreetmap.josm.gui.MainApplication; 14 import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent; 15 import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener; 16 import org.openstreetmap.josm.gui.preferences.ToolbarPreferences; 17 import org.openstreetmap.josm.tools.ImageResource; 18 19 /** 20 * Base class for templates to build preset menus. 21 * <p> 22 * This class is an immutable template class mainly used to build menues, toolbars and preset lists. 23 * 24 * @since xxx 25 */ 26 public abstract class TaggingPresetBase extends Sequence { 27 /** The action key for optional tooltips */ 28 public static final String OPTIONAL_TOOLTIP_TEXT = "Optional tooltip text"; 29 /** Prefix of preset icon loading failure error message */ 30 public static final String PRESET_ICON_ERROR_MSG_PREFIX = "Could not get presets icon "; 31 32 /** 33 * The name of the tagging preset. 34 * @see #getRawName() 35 */ 36 private final String name; 37 /** Translation context for name */ 38 private final String nameContext; 39 /** 40 * A cache for the local name. Should never be accessed directly. 41 * @see #getLocaleName() 42 */ 43 private final String localeName; 44 /** The icon name assigned to this preset. */ 45 final String iconName; 46 47 /** The english full name of this preset, eg. {@code Highways/Streets/Motorway} */ 48 String fullName; 49 /** The localized full name of this preset, eg. {@code Straßen/Straßen/Autobahn} */ 50 String localeFullName; 51 /** The english group name of this preset, eg. {@code Highways/Streets} */ 52 String groupName; 53 /** The localized group name of this preset, eg. {@code Straßen/Straßen} */ 54 String localeGroupName; 55 56 TaggingPresetBaseAction action; 57 /** The completable future task of asynchronous icon loading. Used for testing. */ 58 CompletableFuture<ImageResource> iconFuture; 59 60 /** 61 * Create an empty tagging preset. This will not have any items and 62 * will be an empty string as text. createPanel will return null. 63 * Use this as default item for "do not select anything". 64 * 65 * @param attributes the XML attributes 66 * @throws IllegalArgumentException on invalid attributes 67 */ 68 TaggingPresetBase(Map<String, String> attributes) throws IllegalArgumentException { 69 super(attributes); 70 71 name = attributes.get("name"); 72 nameContext = attributes.get("name_context"); 73 localeName = TaggingPresetUtils.buildLocaleString(attributes.get("locale_name"), name, nameContext); 74 iconName = attributes.get("icon"); 75 } 76 77 @Override 78 void fixup(Map<String, Chunk> chunks, Item parent) { 79 if (parent instanceof TaggingPresetBase) { 80 TaggingPresetBase p = (TaggingPresetBase) parent; 81 groupName = p.fullName; 82 localeGroupName = p.localeFullName; 83 fullName = p.fullName + "/" + name; 84 localeFullName = p.localeFullName + "/" + localeName; 85 } else { 86 groupName = ""; 87 localeGroupName = ""; 88 fullName = name; 89 localeFullName = localeName; 90 } 91 super.fixup(chunks, this); 92 } 93 94 @Override 95 void destroy() { 96 super.destroy(); 97 action.removeListener(); 98 action = null; 99 iconFuture = null; 100 } 101 102 /** 103 * Returns the untranslated name. eg. {@code Motorway} 104 * 105 * @return the name 106 */ 107 public String getBaseName() { 108 return name; 109 } 110 111 /** 112 * Returns the localized version of the name. eg. {@code Autobahn} 113 * 114 * @return The name that should be displayed to the user. 115 */ 116 public String getLocaleName() { 117 return localeName; 118 } 119 120 /** 121 * Returns the localized full name of this preset, eg. {@code Wege/Staßen/Autobahn} 122 * @return the localized full name 123 */ 124 public String getName() { 125 return localeFullName; 126 } 127 128 /** 129 * Returns the localized full name of this preset, eg. {@code Straßen/Straßen/Autobahn} 130 * @return the localized full name 131 */ 132 public String getLocaleFullName() { 133 return localeFullName; 134 } 135 136 /** 137 * Returns the full name of this preset, in English, eg. {@code Highways/Streets/Motorway} 138 * @return the full name 139 */ 140 public String getRawName() { 141 return fullName; 142 } 143 144 /** 145 * Returns the group name of this preset, in English, eg. {@code Highways/Streets} 146 * @return the group name 147 */ 148 public String getGroupName() { 149 return groupName; 150 } 151 152 /** 153 * Returns the localized group name of this preset, eg. {@code Straßen/Straßen} 154 * @return the localized group name 155 */ 156 public String getLocaleGroupName() { 157 return localeGroupName; 158 } 159 160 /** 161 * Returns the preset icon (16 or 24px). 162 * @param size Key determining icon size: {@code Action.SMALL_ICON} for 16x, {@code Action.LARGE_ICON_KEY} for 24px 163 * @return The preset icon, or {@code null} if none defined 164 * @since 10849 165 */ 166 public final ImageIcon getIcon(String size) { 167 Object icon = getAction().getValue(size); 168 if (icon instanceof ImageIcon) { 169 return (ImageIcon) icon; 170 } 171 return null; 172 } 173 174 /** 175 * Returns the preset icon (16px). 176 * @return The preset icon, or {@code null} if none defined 177 * @since 6403 178 */ 179 public final ImageIcon getIcon() { 180 return getIcon(Action.SMALL_ICON); 181 } 182 183 /** 184 * Returns the icon name 185 * @return the icon name 186 */ 187 public String getIconName() { 188 return iconName; 189 } 190 191 /** 192 * Returns the Action associated with this preset. 193 * @return the Action 194 */ 195 public AbstractAction getAction() { 196 return action; 197 } 198 199 /** 200 * Gets a string describing this preset that can be used for the toolbar 201 * @return A String that can be passed on to the toolbar 202 * @see ToolbarPreferences#addCustomButton(String, int, boolean) 203 */ 204 public String getToolbarString() { 205 ToolbarPreferences.ActionParser actionParser = new ToolbarPreferences.ActionParser(null); 206 return actionParser.saveAction(new ToolbarPreferences.ActionDefinition(getAction())); 207 } 208 209 /** 210 * Returns true if this preset's fullName matches the glob. 211 * @param glob the name to match. "/*" matches all names 212 * @return true if this preset's fullName matches the glob. 213 */ 214 public boolean nameMatchesGlob(String glob) { 215 if (glob.endsWith("/*")) { 216 glob = glob.substring(0, glob.length() - 2); 217 return glob.equalsIgnoreCase(groupName); 218 } 219 return glob.equalsIgnoreCase(fullName); 220 } 221 222 /** 223 * An action that opens the preset dialog. 224 */ 225 abstract static class TaggingPresetBaseAction extends AbstractAction implements ActiveLayerChangeListener, AdaptableAction { 226 TaggingPresetBaseAction() { 227 updateEnabledState(); 228 } 229 230 @Override 231 public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) { 232 updateEnabledState(); 233 } 234 235 final void updateEnabledState() { 236 setEnabled(OsmDataManager.getInstance().getEditDataSet() != null); 237 } 238 239 final void removeListener() { 240 try { 241 MainApplication.getLayerManager().removeActiveLayerChangeListener(this); 242 } catch (IllegalArgumentException e) { 243 // the test rig always cleans out the layers before we get a chance to unregister 244 return; // there must be at east one statement 245 } 246 } 247 } 248 } -
src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetDialog.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 import static org.openstreetmap.josm.tools.I18n.trn; 6 7 import java.awt.Component; 8 import java.awt.Dimension; 9 import java.awt.GridBagLayout; 10 import java.awt.Insets; 11 import java.util.Collection; 12 import java.util.LinkedHashSet; 13 14 import javax.swing.Action; 15 import javax.swing.ImageIcon; 16 import javax.swing.JOptionPane; 17 import javax.swing.JPanel; 18 import javax.swing.SwingUtilities; 19 20 import org.openstreetmap.josm.actions.CreateMultipolygonAction; 21 import org.openstreetmap.josm.data.osm.DataSet; 22 import org.openstreetmap.josm.data.osm.OsmData; 23 import org.openstreetmap.josm.data.osm.OsmDataManager; 24 import org.openstreetmap.josm.data.osm.OsmPrimitive; 25 import org.openstreetmap.josm.data.osm.Relation; 26 import org.openstreetmap.josm.data.osm.RelationMember; 27 import org.openstreetmap.josm.data.osm.Tag; 28 import org.openstreetmap.josm.data.osm.Way; 29 import org.openstreetmap.josm.gui.ExtendedDialog; 30 import org.openstreetmap.josm.gui.MainApplication; 31 import org.openstreetmap.josm.gui.Notification; 32 import org.openstreetmap.josm.gui.dialogs.relation.RelationEditor; 33 import org.openstreetmap.josm.gui.dialogs.relation.sort.RelationSorter; 34 import org.openstreetmap.josm.tools.Pair; 35 import org.openstreetmap.josm.tools.Utils; 36 37 /** 38 * A tagging preset dialog. 39 */ 40 public class TaggingPresetDialog extends ExtendedDialog { 41 /** The user pressed the "Apply" button */ 42 private static final int DIALOG_ANSWER_APPLY = 1; 43 /** The user pressed the "New Relation" button */ 44 private static final int DIALOG_ANSWER_NEW_RELATION = 2; 45 /** The user pressed the "Cancel" button */ 46 private static final int DIALOG_ANSWER_CANCEL = 3; 47 48 /** 49 * Constructs a new {@code PresetDialog}. 50 * @param content the content that will be displayed in this dialog 51 * @param title the text that will be shown in the window titlebar 52 * @param icon the image to be displayed as the icon for this window 53 * @param disableApply whether to disable "Apply" button 54 * @param showNewRelation whether to display "New relation" button 55 */ 56 TaggingPresetDialog(Component content, String title, ImageIcon icon, boolean disableApply, boolean showNewRelation) { 57 super(MainApplication.getMainFrame(), title, 58 showNewRelation ? 59 (new String[] {tr("Apply Preset"), tr("New relation"), tr("Cancel")}) : 60 (new String[] {tr("Apply Preset"), tr("Cancel")}), 61 true); 62 if (icon != null) 63 setIconImage(icon.getImage()); 64 contentInsets = new Insets(10, 10, 0, 10); 65 if (showNewRelation) { 66 setButtonIcons("ok", "data/relation", "cancel"); 67 } else { 68 setButtonIcons("ok", "cancel"); 69 } 70 configureContextsensitiveHelp("/Menu/Presets", true); 71 setContent(content); 72 setDefaultButton(1); 73 setupDialog(); 74 buttons.get(0).setEnabled(!disableApply); 75 buttons.get(0).setToolTipText(title); 76 // Prevent dialogs of being too narrow (fix #6261) 77 Dimension d = getSize(); 78 if (d.width < 350) { 79 d.width = 350; 80 setSize(d); 81 } 82 super.showDialog(); 83 } 84 85 /** 86 * Shows the preset dialog and applies changes 87 * 88 * @param preset the tagging preser 89 * @param handler the tagging preset handler 90 * @param showNewRelation if true adds a "new relation" button 91 */ 92 public static void showAndApply(TaggingPreset preset, TaggingPresetHandler handler, boolean showNewRelation) { 93 TaggingPresetInstance instance = TaggingPresetInstance.create(preset, handler); 94 95 // get unfiltered selection 96 DataSet dataSet = OsmDataManager.getInstance().getEditDataSet(); 97 if (dataSet == null) { 98 return; 99 } 100 Collection<OsmPrimitive> selected = dataSet.getSelected(); 101 Collection<OsmPrimitive> filtered = instance.getSelected(); 102 103 JPanel p = new JPanel(new GridBagLayout()); 104 boolean hasElements = preset.addToPanel(p, instance); 105 boolean originalSelectionEmpty = selected.isEmpty(); 106 107 int answer = 1; 108 boolean canCreateRelation = preset.supportsRelation(); 109 if (originalSelectionEmpty && !canCreateRelation) { 110 new Notification( 111 tr("The preset <i>{0}</i> cannot be applied since nothing has been selected!", preset.getLocaleName())) 112 .setIcon(JOptionPane.WARNING_MESSAGE) 113 .show(); 114 answer = DIALOG_ANSWER_CANCEL; 115 } else if (filtered.isEmpty() && !canCreateRelation) { 116 new Notification( 117 tr("The preset <i>{0}</i> cannot be applied since the selection is unsuitable!", preset.getLocaleName())) 118 .setIcon(JOptionPane.WARNING_MESSAGE) 119 .show(); 120 answer = DIALOG_ANSWER_CANCEL; 121 } else if (p.getComponentCount() != 0 && (filtered.isEmpty() || hasElements)) { 122 int size = filtered.size(); 123 String title = trn("Change {0} object", "Change {0} objects", size, size); 124 if (!showNewRelation && size == 0) { 125 if (originalSelectionEmpty) { 126 title = tr("Nothing selected!"); 127 } else { 128 title = tr("Selection unsuitable!"); 129 } 130 } 131 132 boolean disableApply = size == 0; 133 if (!disableApply) { 134 OsmData<?, ?, ?, ?> ds = filtered.iterator().next().getDataSet(); 135 disableApply = ds != null && ds.isLocked(); 136 } 137 138 // finally show the dialog 139 answer = new TaggingPresetDialog(p, title, preset.getPresetNameLabel() ? null : preset.getIcon(Action.SMALL_ICON), 140 disableApply, showNewRelation).getValue(); 141 } 142 if (!showNewRelation && answer == DIALOG_ANSWER_NEW_RELATION) 143 answer = DIALOG_ANSWER_CANCEL; 144 145 if (!handler.getPrimitives().isEmpty() && answer == DIALOG_ANSWER_APPLY) { 146 // write the changed tags 147 handler.updateTags(preset.getChangedTags(instance)); 148 } else if (answer == DIALOG_ANSWER_NEW_RELATION) { 149 Relation calculated = null; 150 if (preset.getChangedTags(instance).stream() 151 .anyMatch(t -> "boundary".equals(t.get("type")) || "multipolygon".equals(t.get("type")))) { 152 Collection<Way> ways = Utils.filteredCollection(selected, Way.class); 153 Pair<Relation, Relation> res = CreateMultipolygonAction.createMultipolygonRelation(ways, true); 154 if (res != null) { 155 calculated = res.b; 156 } 157 } 158 final Relation r = calculated != null ? calculated : new Relation(); 159 final Collection<RelationMember> members = new LinkedHashSet<>(r.getMembers()); 160 for (Tag t : preset.getChangedTags(instance)) { 161 r.put(t.getKey(), t.getValue()); 162 } 163 for (OsmPrimitive osm : selected) { 164 if (r == calculated && osm instanceof Way) 165 continue; 166 String role = preset.suggestRoleForOsmPrimitive(osm); 167 RelationMember rm = new RelationMember(role == null ? "" : role, osm); 168 r.addMember(rm); 169 members.add(rm); 170 } 171 if (r.isMultipolygon() && r != calculated) { 172 r.setMembers(RelationSorter.sortMembersByConnectivity(r.getMembers())); 173 } 174 SwingUtilities.invokeLater(() -> RelationEditor.getEditor( 175 MainApplication.getLayerManager().getEditLayer(), r, members).setVisible(true)); 176 } 177 if (!selected.isEmpty()) { 178 DataSet ds = selected.iterator().next().getDataSet(); 179 // check for null because if we were called from the relation editor, we are editing a 180 // copy that is not in the dataset. 181 if (ds != null) 182 ds.setSelected(selected); // force update 183 } 184 } 185 } -
src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetHandler.java
8 8 import org.openstreetmap.josm.data.osm.Tag; 9 9 10 10 /** 11 * This interface needs to be implemented in order to display a tagging preset. It allows the preset dialog to query the primitives it should12 * be displayed for and modify them.11 * This bidirectional interface is the connection between a preset dialog and the backing store. The 12 * data store can be a JOSM {@code DataSet} or any other key/value store, eg. a {@code TagTable}. 13 13 */ 14 14 public interface TaggingPresetHandler { 15 15 16 /** 16 * Gets the selection the preset should be applied to. 17 * Returns the set of primitives to operate on. 18 * <p> 19 * This is called by the preset dialog to obtain initial values for the edit controls and by the 20 * {@link #updateTags} method. The returned set should not change between invocations. 21 * 17 22 * @return A collection of primitives. 18 23 */ 19 Collection<OsmPrimitive> get Selection();24 Collection<OsmPrimitive> getPrimitives(); 20 25 21 26 /** 22 * Update the given tags on the selection. 23 * @param tags The tags to update. 27 * Update the given tags on the primitives. 28 * <p> 29 * This method writes the tags in the given list to the primitives returned by 30 * {@link #getPrimitives}. 31 * 32 * @param tags The tags to write. 24 33 */ 25 34 void updateTags(List<Tag> tags); 26 35 } -
src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetInstance.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import java.awt.ComponentOrientation; 5 import java.util.ArrayList; 6 import java.util.Arrays; 7 import java.util.Collection; 8 import java.util.HashMap; 9 import java.util.List; 10 import java.util.Map; 11 12 import org.openstreetmap.josm.data.osm.OsmPrimitive; 13 import org.openstreetmap.josm.data.osm.Tag; 14 import org.openstreetmap.josm.data.osm.Tagged; 15 import org.openstreetmap.josm.data.osm.search.SearchCompiler; 16 import org.openstreetmap.josm.gui.widgets.OrientationAction; 17 import org.openstreetmap.josm.tools.ListenerList; 18 import org.openstreetmap.josm.tools.Utils; 19 import org.openstreetmap.josm.tools.template_engine.TemplateEngineDataProvider; 20 21 /** 22 * Companion class to TaggingPreset to hold instance data. 23 * <p> 24 * The {@link TaggingPreset} class is an immutable template class mainly used to build Swing 25 * dialogs. But being immutable it cannot hold any of the data the user entered in the dialog. An 26 * instance of this class is created along with every preset dialog. 27 * 28 * @since 17609 29 */ 30 public final class TaggingPresetInstance implements TemplateEngineDataProvider { 31 /** The preset template that created this instance. */ 32 private final TaggingPreset preset; 33 /** The current TaggingPresetHandler */ 34 private final TaggingPresetHandler handler; 35 /** The map from Item to Item.Instance. */ 36 private final Map<Item, Item.Instance> instances = new HashMap<>(); 37 /** True if all selected primitives matched this preset at the moment the dialog was openend. */ 38 private final boolean presetInitiallyMatches; 39 /** Data change listeners */ 40 private final ListenerList<ChangeListener> listeners = ListenerList.create(); 41 /** whether to fire data changed events or not */ 42 private boolean enabled; 43 44 private TaggingPresetInstance(TaggingPreset preset, TaggingPresetHandler handler) { 45 this.preset = preset; 46 this.handler = handler; 47 Collection<OsmPrimitive> selected = handler.getPrimitives(); 48 this.presetInitiallyMatches = !selected.isEmpty() && selected.stream().allMatch(preset); 49 } 50 51 /** 52 * Creates a new {@code TaggingPresetInstance} 53 * 54 * @param preset the preset 55 * @param handler the preset handler 56 * @return the new {@code TaggingPresetInstance} 57 */ 58 public static TaggingPresetInstance create(TaggingPreset preset, TaggingPresetHandler handler) { 59 return new TaggingPresetInstance(preset, handler); 60 } 61 62 /** 63 * Creates a new {@code TaggingPresetInstance} for testing purposes 64 * 65 * @param selected the selected primitives 66 * @return the new {@code TaggingPresetInstance} 67 */ 68 public static TaggingPresetInstance createTest(OsmPrimitive... selected) { 69 return new TaggingPresetInstance( 70 (TaggingPreset) ItemFactory.build("item"), 71 new ReadOnlyTaggingPresetHandler(Arrays.asList(selected)) 72 ); 73 } 74 75 /** 76 * Returns the preset 77 * @return the preset 78 */ 79 public TaggingPreset getPreset() { 80 return preset; 81 } 82 83 /** 84 * Returns the handler 85 * @return the handler 86 */ 87 public TaggingPresetHandler getHandler() { 88 return handler; 89 } 90 91 /** 92 * Return the selected primitives. 93 * @return the selected primitives 94 */ 95 public Collection<OsmPrimitive> getSelected() { 96 return handler.getPrimitives(); 97 } 98 99 /** 100 * Return the instance for the item 101 * @param item the item 102 * @return the instance 103 */ 104 public Item.Instance getInstance(Item item) { 105 return instances.get(item); 106 } 107 108 /** 109 * Returns the preset properties 110 * @return the preset properties 111 */ 112 public Map<String, Object> getPresetProperties() { 113 return preset.properties; 114 } 115 116 /** 117 * Returns whether firing of events is enabled 118 * 119 * @return true if firing of events is enabled 120 */ 121 public boolean isEnabled() { 122 return enabled; 123 } 124 125 /** 126 * Enables or disables the firing of events 127 * 128 * @param enabled fires if true 129 * @return the old state of enabled 130 */ 131 public boolean setEnabled(boolean enabled) { 132 boolean oldEnabled = this.enabled; 133 this.enabled = enabled; 134 return oldEnabled; 135 } 136 137 /** 138 * Registers the instance of the preset item. 139 * <p> 140 * All presets have to register an instance if the item is editable. 141 * 142 * @param item the preset item 143 * @param instance the instance 144 */ 145 public void putInstance(Item item, Item.Instance instance) { 146 instances.put(item, instance); 147 } 148 149 /** 150 * Interface to notify listeners that a preset item input as changed. 151 * @since 17610 152 */ 153 public interface ChangeListener { 154 /** 155 * Notifies this listener that a preset item input as changed. 156 * @param source the source of this event 157 * @param key the tag key 158 * @param newValue the new tag value 159 */ 160 void itemValueModified(Item.Instance source, String key, String newValue); 161 } 162 163 /** 164 * Returns true if all selected primitives matched this preset (before opening the dialog). 165 * <p> 166 * This usually means that the preset dialog was opened from the Tags / Memberships panel as 167 * opposed to being opened from the menu or toolbar or the preset search dialog. 168 * 169 * @return true if the preset initially matched 170 */ 171 public boolean isPresetInitiallyMatches() { 172 return presetInitiallyMatches; 173 } 174 175 /** 176 * Gets the current (edited) state of the tags. 177 * @return The list of tags. 178 */ 179 public List<Tag> getChangedTags() { 180 List<Tag> result = new ArrayList<>(); 181 instances.forEach((item, instance) -> instance.addCommands(result)); 182 return result; 183 } 184 185 /** 186 * Get tags with values as currently shown in the dialog. 187 * If exactly one primitive is selected, get all tags of it, then 188 * overwrite with the current values shown in the dialog. 189 * Else get only the tags shown in the dialog. 190 * @return Tags 191 */ 192 public Tagged getTagged() { 193 if (getSelected().size() != 1) { 194 return Tagged.ofTags(getChangedTags()); 195 } 196 // if there is only one primitive selected, get its tags 197 Tagged tagged = Tagged.ofMap(getSelected().iterator().next().getKeys()); 198 // update changed tags 199 getChangedTags().forEach(tag -> tagged.put(tag)); 200 return tagged; 201 } 202 203 @Override 204 public Collection<String> getTemplateKeys() { 205 return getTagged().keySet(); 206 } 207 208 @Override 209 public Object getTemplateValue(String key, boolean special) { 210 String value = getTagged().get(key); 211 return Utils.isEmpty(value) ? null : value; 212 } 213 214 /** 215 * Returns the default component orientation by the user's locale 216 * 217 * @return the default component orientation 218 */ 219 public ComponentOrientation getDefaultComponentOrientation() { 220 return OrientationAction.getDefaultComponentOrientation(); 221 } 222 223 @Override 224 public boolean evaluateCondition(SearchCompiler.Match condition) { 225 return condition.match(getTagged()); 226 } 227 228 /** 229 * Adds a new change listener 230 * @param listener the listener to add 231 */ 232 public void addListener(ChangeListener listener) { 233 listeners.addListener(listener); 234 } 235 236 /** 237 * Notifies all listeners that a preset item input as changed. 238 * @param source the source of this event 239 * @param key the tag key 240 * @param newValue the new tag value 241 */ 242 public void fireItemValueModified(Item.Instance source, String key, String newValue) { 243 if (enabled) 244 listeners.fireEvent(e -> e.itemValueModified(source, key, newValue)); 245 } 246 } -
src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItem.java
1 // License: GPL. For details, see LICENSE file.2 package org.openstreetmap.josm.gui.tagging.presets;3 4 import static org.openstreetmap.josm.tools.I18n.tr;5 import static org.openstreetmap.josm.tools.I18n.trc;6 7 import java.io.File;8 import java.util.Arrays;9 import java.util.Collection;10 import java.util.Collections;11 import java.util.EnumSet;12 import java.util.List;13 import java.util.Map;14 import java.util.Set;15 16 import javax.swing.ImageIcon;17 import javax.swing.JPanel;18 19 import org.openstreetmap.josm.data.osm.DataSet;20 import org.openstreetmap.josm.data.osm.OsmDataManager;21 import org.openstreetmap.josm.data.osm.OsmPrimitive;22 import org.openstreetmap.josm.data.osm.Tag;23 import org.openstreetmap.josm.data.preferences.BooleanProperty;24 import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem;25 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;26 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;27 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;28 import org.openstreetmap.josm.gui.util.LruCache;29 import org.openstreetmap.josm.tools.ImageProvider;30 import org.openstreetmap.josm.tools.Logging;31 import org.openstreetmap.josm.tools.Utils;32 import org.xml.sax.SAXException;33 34 /**35 * Class that represents single part of a preset - one field or text label that is shown to user36 * @since 606837 */38 public abstract class TaggingPresetItem {39 40 // cache the parsing of types using a LRU cache41 private static final Map<String, Set<TaggingPresetType>> TYPE_CACHE = new LruCache<>(16);42 /**43 * Display OSM keys as {@linkplain org.openstreetmap.josm.gui.widgets.OsmIdTextField#setHint hint}44 */45 protected static final BooleanProperty DISPLAY_KEYS_AS_HINT = new BooleanProperty("taggingpreset.display-keys-as-hint", true);46 47 protected void initAutoCompletionField(AutoCompletingTextField field, String... key) {48 initAutoCompletionField(field, Arrays.asList(key));49 }50 51 protected void initAutoCompletionField(AutoCompletingTextField field, List<String> keys) {52 DataSet data = OsmDataManager.getInstance().getEditDataSet();53 if (data == null) {54 return;55 }56 AutoCompletionList list = new AutoCompletionList();57 AutoCompletionManager.of(data).populateWithTagValues(list, keys);58 field.setAutoCompletionList(list);59 }60 61 /**62 * Returns all cached {@link AutoCompletionItem}s for given keys.63 *64 * @param keys retrieve the items for these keys65 * @return the currently cached items, sorted by priority and alphabet66 * @since 1822167 */68 protected List<AutoCompletionItem> getAllForKeys(List<String> keys) {69 DataSet data = OsmDataManager.getInstance().getEditDataSet();70 if (data == null) {71 return Collections.emptyList();72 }73 return AutoCompletionManager.of(data).getAllForKeys(keys);74 }75 76 /**77 * Called by {@link TaggingPreset#createPanel} during tagging preset panel creation.78 * All components defining this tagging preset item must be added to given panel.79 *80 * @param p The panel where components must be added81 * @param support supporting class for creating the GUI82 * @return {@code true} if this item adds semantic tagging elements, {@code false} otherwise.83 */84 protected abstract boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support);85 86 /**87 * Adds the new tags to apply to selected OSM primitives when the preset holding this item is applied.88 * @param changedTags The list of changed tags to modify if needed89 */90 protected abstract void addCommands(List<Tag> changedTags);91 92 /**93 * Tests whether the tags match this item.94 * Note that for a match, at least one positive and no negative is required.95 * @param tags the tags of an {@link OsmPrimitive}96 * @return {@code true} if matches (positive), {@code null} if neutral, {@code false} if mismatches (negative).97 */98 public Boolean matches(Map<String, String> tags) {99 return null; // NOSONAR100 }101 102 protected static Set<TaggingPresetType> getType(String types) throws SAXException {103 if (Utils.isEmpty(types)) {104 throw new SAXException(tr("Unknown type: {0}", types));105 }106 if (TYPE_CACHE.containsKey(types))107 return TYPE_CACHE.get(types);108 Set<TaggingPresetType> result = EnumSet.noneOf(TaggingPresetType.class);109 for (String type : types.split(",", -1)) {110 try {111 TaggingPresetType presetType = TaggingPresetType.fromString(type);112 if (presetType != null) {113 result.add(presetType);114 }115 } catch (IllegalArgumentException e) {116 throw new SAXException(tr("Unknown type: {0}", type), e);117 }118 }119 TYPE_CACHE.put(types, result);120 return result;121 }122 123 protected static String fixPresetString(String s) {124 return s == null ? s : s.replace("'", "''");125 }126 127 protected static String getLocaleText(String text, String textContext, String defaultText) {128 if (text == null) {129 return defaultText;130 } else if (textContext != null) {131 return trc(textContext, fixPresetString(text));132 } else {133 return tr(fixPresetString(text));134 }135 }136 137 protected static Integer parseInteger(String str) {138 if (Utils.isEmpty(str))139 return null;140 try {141 return Integer.valueOf(str);142 } catch (NumberFormatException e) {143 Logging.trace(e);144 }145 return null;146 }147 148 /**149 * Loads a tagging preset icon150 * @param iconName the icon name151 * @param zipIcons zip file where the image is located152 * @param maxSize maximum image size (or null)153 * @return the requested image or null if the request failed154 */155 public static ImageIcon loadImageIcon(String iconName, File zipIcons, Integer maxSize) {156 final Collection<String> s = TaggingPresets.ICON_SOURCES.get();157 ImageProvider imgProv = new ImageProvider(iconName).setDirs(s).setId("presets").setArchive(zipIcons).setOptional(true);158 if (maxSize != null && maxSize > 0) {159 imgProv.setMaxSize(maxSize);160 }161 return imgProv.get();162 }163 164 /**165 * Determine whether the given preset items match the tags166 * @param data the preset items167 * @param tags the tags to match168 * @return whether the given preset items match the tags169 * @since 9932170 */171 public static boolean matches(Iterable<? extends TaggingPresetItem> data, Map<String, String> tags) {172 boolean atLeastOnePositiveMatch = false;173 for (TaggingPresetItem item : data) {174 Boolean m = item.matches(tags);175 if (m != null && !m)176 return false;177 else if (m != null) {178 atLeastOnePositiveMatch = true;179 }180 }181 return atLeastOnePositiveMatch;182 }183 } -
src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItemGuiSupport.java
Property changes on: src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItem.java ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property
1 // License: GPL. For details, see LICENSE file.2 package org.openstreetmap.josm.gui.tagging.presets;3 4 import java.awt.ComponentOrientation;5 import java.util.Arrays;6 import java.util.Collection;7 import java.util.Collections;8 import java.util.function.Supplier;9 10 import org.openstreetmap.josm.data.osm.OsmPrimitive;11 import org.openstreetmap.josm.data.osm.Tag;12 import org.openstreetmap.josm.data.osm.Tagged;13 import org.openstreetmap.josm.data.osm.search.SearchCompiler;14 import org.openstreetmap.josm.gui.widgets.OrientationAction;15 import org.openstreetmap.josm.tools.ListenerList;16 import org.openstreetmap.josm.tools.Utils;17 import org.openstreetmap.josm.tools.template_engine.TemplateEngineDataProvider;18 19 /**20 * Supporting class for creating the GUI for a preset item.21 *22 * @since 1760923 */24 public final class TaggingPresetItemGuiSupport implements TemplateEngineDataProvider {25 26 private final Collection<OsmPrimitive> selected;27 /** True if all selected primitives matched this preset at the moment it was openend. */28 private final boolean presetInitiallyMatches;29 private final Supplier<Collection<Tag>> changedTagsSupplier;30 private final ListenerList<ChangeListener> listeners = ListenerList.create();31 32 /** whether to fire events or not */33 private boolean enabled;34 35 /**36 * Returns whether firing of events is enabled37 *38 * @return true if firing of events is enabled39 */40 public boolean isEnabled() {41 return enabled;42 }43 44 /**45 * Enables or disables the firing of events46 *47 * @param enabled fires if true48 * @return the old state of enabled49 */50 public boolean setEnabled(boolean enabled) {51 boolean oldEnabled = this.enabled;52 this.enabled = enabled;53 return oldEnabled;54 }55 56 /**57 * Interface to notify listeners that a preset item input as changed.58 * @since 1761059 */60 public interface ChangeListener {61 /**62 * Notifies this listener that a preset item input as changed.63 * @param source the source of this event64 * @param key the tag key65 * @param newValue the new tag value66 */67 void itemValueModified(TaggingPresetItem source, String key, String newValue);68 }69 70 private TaggingPresetItemGuiSupport(71 boolean presetInitiallyMatches, Collection<OsmPrimitive> selected, Supplier<Collection<Tag>> changedTagsSupplier) {72 this.selected = selected;73 this.presetInitiallyMatches = presetInitiallyMatches;74 this.changedTagsSupplier = changedTagsSupplier;75 }76 77 /**78 * Returns the selected primitives79 *80 * @return the selected primitives81 */82 public Collection<OsmPrimitive> getSelected() {83 return selected;84 }85 86 /**87 * Returns true if all selected primitives matched this preset (before opening the dialog).88 * <p>89 * This usually means that the preset dialog was opened from the Tags / Memberships panel as90 * opposed to being opened by selection from the menu or toolbar or the search.91 *92 * @return true if the preset initially matched93 */94 public boolean isPresetInitiallyMatches() {95 return presetInitiallyMatches;96 }97 98 /**99 * Creates a new {@code TaggingPresetItemGuiSupport}100 *101 * @param presetInitiallyMatches whether the preset initially matched102 * @param selected the selected primitives103 * @param changedTagsSupplier the changed tags104 * @return the new {@code TaggingPresetItemGuiSupport}105 */106 public static TaggingPresetItemGuiSupport create(107 boolean presetInitiallyMatches, Collection<OsmPrimitive> selected, Supplier<Collection<Tag>> changedTagsSupplier) {108 return new TaggingPresetItemGuiSupport(presetInitiallyMatches, selected, changedTagsSupplier);109 }110 111 /**112 * Creates a new {@code TaggingPresetItemGuiSupport}113 *114 * @param presetInitiallyMatches whether the preset initially matched115 * @param selected the selected primitives116 * @return the new {@code TaggingPresetItemGuiSupport}117 */118 public static TaggingPresetItemGuiSupport create(119 boolean presetInitiallyMatches, OsmPrimitive... selected) {120 return new TaggingPresetItemGuiSupport(presetInitiallyMatches, Arrays.asList(selected), Collections::emptyList);121 }122 123 /**124 * Get tags with values as currently shown in the dialog.125 * If exactly one primitive is selected, get all tags of it, then126 * overwrite with the current values shown in the dialog.127 * Else get only the tags shown in the dialog.128 * @return Tags129 */130 public Tagged getTagged() {131 if (selected.size() != 1) {132 return Tagged.ofTags(changedTagsSupplier.get());133 }134 // if there is only one primitive selected, get its tags135 Tagged tagged = Tagged.ofMap(selected.iterator().next().getKeys());136 // update changed tags137 changedTagsSupplier.get().forEach(tag -> tagged.put(tag));138 return tagged;139 }140 141 @Override142 public Collection<String> getTemplateKeys() {143 return getTagged().keySet();144 }145 146 @Override147 public Object getTemplateValue(String key, boolean special) {148 String value = getTagged().get(key);149 return Utils.isEmpty(value) ? null : value;150 }151 152 /**153 * Returns the default component orientation by the user's locale154 *155 * @return the default component orientation156 */157 public ComponentOrientation getDefaultComponentOrientation() {158 return OrientationAction.getDefaultComponentOrientation();159 }160 161 @Override162 public boolean evaluateCondition(SearchCompiler.Match condition) {163 return condition.match(getTagged());164 }165 166 /**167 * Adds a new change listener168 * @param listener the listener to add169 */170 public void addListener(ChangeListener listener) {171 listeners.addListener(listener);172 }173 174 /**175 * Notifies all listeners that a preset item input as changed.176 * @param source the source of this event177 * @param key the tag key178 * @param newValue the new tag value179 */180 public void fireItemValueModified(TaggingPresetItem source, String key, String newValue) {181 if (enabled)182 listeners.fireEvent(e -> e.itemValueModified(source, key, newValue));183 }184 } -
src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetLabel.java
12 12 13 13 /** 14 14 * A hyperlink {@link JLabel}. 15 * 15 * 16 16 * To indicate that the user can click on the text, it displays an appropriate 17 17 * mouse cursor and dotted underline when the mouse is inside the hover area. 18 18 */ -
src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetMenu.java
8 8 import java.awt.Point; 9 9 import java.awt.PointerInfo; 10 10 import java.awt.event.ActionEvent; 11 import java.io.Serializable; 12 import java.util.ArrayList; 13 import java.util.Comparator; 14 import java.util.List; 11 import java.util.Map; 15 12 import java.util.Objects; 16 13 17 14 import javax.swing.Action; 18 15 import javax.swing.JMenu; 19 import javax.swing.JMenuItem;20 16 import javax.swing.JPopupMenu; 21 import javax.swing.JSeparator;22 17 23 18 import org.openstreetmap.josm.gui.MainApplication; 24 19 import org.openstreetmap.josm.gui.MainFrame; 25 import org.openstreetmap.josm. tools.AlphanumComparator;20 import org.openstreetmap.josm.gui.MenuScroller; 26 21 import org.openstreetmap.josm.tools.Logging; 27 22 28 23 /** … … 30 25 * <p> 31 26 * Used, to create the nested directory structure in the preset main menu entry. 32 27 */ 33 public class TaggingPresetMenu extends TaggingPreset { 34 public JMenu menu; // set by TaggingPresets 35 36 private static class PresetTextComparator implements Comparator<JMenuItem>, Serializable { 37 private static final long serialVersionUID = 1L; 38 @Override 39 public int compare(JMenuItem o1, JMenuItem o2) { 40 if (MainApplication.getMenu().presetSearchAction.equals(o1.getAction())) 41 return -1; 42 else if (MainApplication.getMenu().presetSearchAction.equals(o2.getAction())) 43 return 1; 44 else 45 return AlphanumComparator.getInstance().compare(o1.getText(), o2.getText()); 46 } 28 public class TaggingPresetMenu extends TaggingPresetBase { 29 TaggingPresetMenu(Map<String, String> attributes) throws IllegalArgumentException { 30 super(attributes); 47 31 } 48 32 49 33 /** 50 * {@code TaggingPresetMenu} are considered equivalent if (and only if) their {@link #getRawName()} match. 34 * Create a {@code TaggingPresetMenu} from an XML element's attributes. 35 * @param attributes the XML attributes 36 * @return the {@code TaggingPresetMenu} 37 * @throws IllegalArgumentException on attribute errors 51 38 */ 52 @Override 53 public boolean equals(Object o) { 54 if (this == o) return true; 55 if (o == null || getClass() != o.getClass()) return false; 56 TaggingPresetMenu that = (TaggingPresetMenu) o; 57 return Objects.equals(getRawName(), that.getRawName()); 39 public static TaggingPresetMenu fromXML(Map<String, String> attributes) throws IllegalArgumentException { 40 return new TaggingPresetMenu(attributes); 58 41 } 59 42 60 43 @Override 61 public int hashCode() { 62 return Objects.hash(getRawName()); 44 void fixup(Map<String, Chunk> chunks, Item parent) { 45 super.fixup(chunks, parent); 46 action = new TaggingPresetMenuAction(); 47 iconFuture = TaggingPresetUtils.loadIcon(iconName, action); 63 48 } 64 49 65 50 @Override 66 public void setDisplayName() { 67 putValue(Action.NAME, getName()); 68 /** Tooltips should be shown for the toolbar buttons, but not in the menu. */ 69 putValue(OPTIONAL_TOOLTIP_TEXT, group != null ? 70 tr("Preset group {1} / {0}", getLocaleName(), group.getName()) : 71 tr("Preset group {0}", getLocaleName())); 72 putValue("toolbar", "tagginggroup_" + getRawName()); 73 } 51 public void addToMenu(JMenu parentMenu) { 52 JMenu subMenu = new JMenu(getAction()); 53 subMenu.setText(getLocaleName()); 54 parentMenu.add(subMenu); 74 55 75 private static Component copyMenuComponent(Component menuComponent) { 76 if (menuComponent instanceof JMenu) { 77 JMenu menu = (JMenu) menuComponent; 78 JMenu result = new JMenu(menu.getAction()); 79 for (Component item:menu.getMenuComponents()) { 80 result.add(copyMenuComponent(item)); 81 } 82 result.setText(menu.getText()); 83 return result; 84 } else if (menuComponent instanceof JMenuItem) { 85 JMenuItem menuItem = (JMenuItem) menuComponent; 86 JMenuItem result = new JMenuItem(menuItem.getAction()); 87 result.setText(menuItem.getText()); 88 return result; 89 } else if (menuComponent instanceof JSeparator) { 90 return new JSeparator(); 91 } else { 92 return menuComponent; 56 for (Item item : items) { 57 item.addToMenu(subMenu); 93 58 } 59 if (subMenu.getItemCount() >= TaggingPresets.MIN_ELEMENTS_FOR_SCROLLER.get()) { 60 MenuScroller.setScrollerFor(subMenu); 61 } 94 62 } 95 63 96 64 @Override 97 public void actionPerformed(ActionEvent e) { 98 Object s = e.getSource(); 99 if (menu != null && s instanceof Component) { 100 JPopupMenu pm = new JPopupMenu(getName()); 101 for (Component c : menu.getMenuComponents()) { 102 pm.add(copyMenuComponent(c)); 103 } 104 try { 105 PointerInfo pointerInfo = MouseInfo.getPointerInfo(); 106 if (pointerInfo != null) { 107 Point p = pointerInfo.getLocation(); 108 MainFrame parent = MainApplication.getMainFrame(); 109 if (parent.isShowing()) { 110 pm.show(parent, p.x-parent.getX(), p.y-parent.getY()); 111 } 112 } 113 } catch (SecurityException ex) { 114 Logging.log(Logging.LEVEL_ERROR, "Unable to get mouse pointer info", ex); 115 } 116 } 65 public String toString() { 66 return "TaggingPresetMenu " + getName(); 117 67 } 118 68 119 69 /** 120 * Sorts the menu items using the translated item text70 * {@code TaggingPresetMenu} are considered equivalent if (and only if) their {@link #getRawName()} match. 121 71 */ 122 public void sortMenu() { 123 TaggingPresetMenu.sortMenu(this.menu); 72 @Override 73 public boolean equals(Object o) { 74 if (this == o) return true; 75 if (o == null || getClass() != o.getClass()) return false; 76 TaggingPresetMenu that = (TaggingPresetMenu) o; 77 return Objects.equals(getRawName(), that.getRawName()); 124 78 } 125 79 80 @Override 81 public int hashCode() { 82 return Objects.hash(getRawName()); 83 } 84 126 85 /** 127 * Sorts the menu items using the translated item text 128 * @param menu menu to sort 86 * An action that opens this menu as a popup in the toolbar. 129 87 */ 130 public static void sortMenu(JMenu menu) { 131 Component[] items = menu.getMenuComponents(); 132 PresetTextComparator comp = new PresetTextComparator(); 133 List<JMenuItem> sortarray = new ArrayList<>(); 134 int lastSeparator = 0; 135 for (int i = 0; i < items.length; i++) { 136 Object item = items[i]; 137 if (item instanceof JMenu) { 138 sortMenu((JMenu) item); 139 } 140 if (item instanceof JMenuItem) { 141 sortarray.add((JMenuItem) item); 142 if (i == items.length-1) { 143 handleMenuItem(menu, comp, sortarray, lastSeparator); 144 sortarray = new ArrayList<>(); 145 lastSeparator = 0; 146 } 147 } else if (item instanceof JSeparator) { 148 handleMenuItem(menu, comp, sortarray, lastSeparator); 149 sortarray = new ArrayList<>(); 150 lastSeparator = i; 151 } 88 public class TaggingPresetMenuAction extends TaggingPresetBase.TaggingPresetBaseAction { 89 TaggingPresetMenuAction() { 90 super(); 91 putValue(Action.NAME, getName()); 92 putValue("toolbar", "tagginggroup_" + getRawName()); 93 /** Tooltips should be shown for the toolbar buttons, but not in the menu. */ 94 putValue(OPTIONAL_TOOLTIP_TEXT, tr("Preset group ''{0}''", getLocaleFullName())); 95 MainApplication.getLayerManager().addActiveLayerChangeListener(this); 152 96 } 153 }154 97 155 private static void handleMenuItem(JMenu menu, PresetTextComparator comp, List<JMenuItem> sortarray, int lastSeparator) { 156 sortarray.sort(comp); 157 int pos = 0; 158 for (JMenuItem menuItem : sortarray) { 159 int oldPos; 160 if (lastSeparator == 0) { 161 oldPos = pos; 162 } else { 163 oldPos = pos+lastSeparator+1; 98 @Override 99 public void actionPerformed(ActionEvent e) { 100 if (e.getSource() instanceof Component) { 101 JMenu menu = new JMenu(); 102 for (Item item : items) { 103 item.addToMenu(menu); 104 } 105 JPopupMenu popupMenu = new JPopupMenu(getName()); 106 for (Component menuItem : menu.getMenuComponents()) { 107 popupMenu.add(menuItem); 108 } 109 try { 110 PointerInfo pointerInfo = MouseInfo.getPointerInfo(); 111 if (pointerInfo != null) { 112 Point p = pointerInfo.getLocation(); 113 MainFrame parent = MainApplication.getMainFrame(); 114 if (parent.isShowing()) { 115 popupMenu.show(parent, p.x-parent.getX(), p.y-parent.getY()); 116 } 117 } 118 } catch (SecurityException ex) { 119 Logging.log(Logging.LEVEL_ERROR, "Unable to get mouse pointer info", ex); 120 } 164 121 } 165 menu.add(menuItem, oldPos);166 pos++;167 122 } 168 123 } 169 124 } -
src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetNameTemplateList.java
39 39 Logging.debug("Building list of presets with name template"); 40 40 presetsWithPattern.clear(); 41 41 for (TaggingPreset tp : TaggingPresets.getTaggingPresets()) { 42 if (tp. nameTemplate!= null) {42 if (tp.getNameTemplate() != null) { 43 43 presetsWithPattern.add(tp); 44 44 } 45 45 } … … 56 56 for (TaggingPreset t : presetsWithPattern) { 57 57 Collection<TaggingPresetType> type = EnumSet.of(TaggingPresetType.forPrimitive(primitive)); 58 58 if (t.typeMatches(type)) { 59 if (t. nameTemplateFilter!= null) {60 if (t. nameTemplateFilter.match(primitive))59 if (t.getNameTemplateFilter() != null) { 60 if (t.getNameTemplateFilter().match(primitive)) 61 61 return t; 62 62 } else if (t.matches(type, primitive.getKeys(), false)) { 63 63 return t; -
src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetReader.java
9 9 import java.io.InputStream; 10 10 import java.io.InputStreamReader; 11 11 import java.io.Reader; 12 import java.util.ArrayDeque; 12 import java.net.URI; 13 import java.net.URISyntaxException; 13 14 import java.util.ArrayList; 14 15 import java.util.Collection; 15 import java.util.Deque;16 16 import java.util.HashMap; 17 import java.util.Iterator;18 import java.util.LinkedHashSet;19 import java.util.LinkedList;20 import java.util.List;21 17 import java.util.Map; 22 18 import java.util.Set; 19 import java.util.Stack; 23 20 24 21 import javax.swing.JOptionPane; 22 import javax.xml.parsers.ParserConfigurationException; 23 import javax.xml.transform.stream.StreamSource; 24 import javax.xml.validation.Schema; 25 import javax.xml.validation.SchemaFactory; 26 import javax.xml.validation.ValidatorHandler; 25 27 26 28 import org.openstreetmap.josm.data.preferences.sources.PresetPrefHelper; 27 29 import org.openstreetmap.josm.gui.MainApplication; 28 import org.openstreetmap.josm.gui.tagging.presets.items.Check;29 import org.openstreetmap.josm.gui.tagging.presets.items.CheckGroup;30 import org.openstreetmap.josm.gui.tagging.presets.items.Combo;31 import org.openstreetmap.josm.gui.tagging.presets.items.ComboMultiSelect;32 import org.openstreetmap.josm.gui.tagging.presets.items.ItemSeparator;33 import org.openstreetmap.josm.gui.tagging.presets.items.Key;34 import org.openstreetmap.josm.gui.tagging.presets.items.Label;35 import org.openstreetmap.josm.gui.tagging.presets.items.Link;36 import org.openstreetmap.josm.gui.tagging.presets.items.MultiSelect;37 import org.openstreetmap.josm.gui.tagging.presets.items.Optional;38 import org.openstreetmap.josm.gui.tagging.presets.items.PresetLink;39 import org.openstreetmap.josm.gui.tagging.presets.items.PresetListEntry;40 import org.openstreetmap.josm.gui.tagging.presets.items.Roles;41 import org.openstreetmap.josm.gui.tagging.presets.items.Roles.Role;42 import org.openstreetmap.josm.gui.tagging.presets.items.Space;43 import org.openstreetmap.josm.gui.tagging.presets.items.Text;44 30 import org.openstreetmap.josm.io.CachedFile; 45 31 import org.openstreetmap.josm.io.NetworkManager; 46 32 import org.openstreetmap.josm.io.UTFInputStreamReader; 47 33 import org.openstreetmap.josm.spi.preferences.Config; 48 34 import org.openstreetmap.josm.tools.I18n; 35 import org.openstreetmap.josm.tools.JosmRuntimeException; 36 import org.openstreetmap.josm.tools.LanguageInfo; 49 37 import org.openstreetmap.josm.tools.Logging; 50 38 import org.openstreetmap.josm.tools.Stopwatch; 51 39 import org.openstreetmap.josm.tools.Utils; 52 import org.openstreetmap.josm.tools.XmlObjectParser; 40 import org.openstreetmap.josm.tools.XmlParsingException; 41 import org.openstreetmap.josm.tools.XmlUtils; 42 import org.xml.sax.Attributes; 43 import org.xml.sax.ContentHandler; 44 import org.xml.sax.InputSource; 45 import org.xml.sax.Locator; 46 import org.xml.sax.SAXParseException; 53 47 import org.xml.sax.SAXException; 48 import org.xml.sax.XMLReader; 49 import org.xml.sax.helpers.DefaultHandler; 50 import org.xml.sax.helpers.XMLFilterImpl; 54 51 55 52 /** 56 * The tagging presets reader. 57 * @since 6068 53 * The tagging presets XML file parser. 54 * <p> 55 * Parses an XML file and builds an in-memory tree of template classes. The template classes are 56 * then used to create preset dialogs and menus. 57 * 58 * @since xxx 58 59 */ 59 60 public final class TaggingPresetReader { 60 61 … … 77 78 */ 78 79 public static final String SCHEMA_SOURCE = "resource://data/tagging-preset.xsd"; 79 80 80 private static volatileFile zipIcons;81 private static volatileboolean loadIcons = true;81 private static File zipIcons; 82 private static boolean loadIcons = true; 82 83 83 /**84 * Holds a reference to a chunk of items/objects.85 */86 public static class Chunk {87 /** The chunk id, can be referenced later */88 p ublic String id;84 private static class Parser extends DefaultHandler { 85 private Root root; 86 private Stack<Item> stack = new Stack<>(); 87 private StringBuilder characters; 88 private Locator locator; 89 private final String lang = LanguageInfo.getLanguageCodeXML(); 89 90 91 public Root getRoot() { 92 return root; 93 } 94 90 95 @Override 91 public String toString() {92 return "Chunk [id=" + id + ']';96 public void setDocumentLocator(Locator locator) { 97 this.locator = locator; 93 98 } 94 }95 99 96 /** 97 * Holds a reference to an earlier item/object. 98 */ 99 public static class Reference { 100 /** Reference matching a chunk id defined earlier **/ 101 public String ref; 100 private Map<String, String> getAttributes(Attributes a) { 101 Map<String, String> attributes = new HashMap<>(); 102 for (int i = 0; i < a.getLength(); ++i) { 103 String name = a.getLocalName(i); 104 if (name.startsWith(lang)) { 105 name = "locale_" + name.substring(lang.length()); 106 } 107 attributes.put(a.getLocalName(i), a.getValue(i)); 108 } 109 return attributes; 110 } 102 111 103 112 @Override 104 public String toString() { 105 return "Reference [ref=" + ref + ']'; 113 public void startElement(String ns, String lname, String qname, Attributes a) throws SAXException { 114 Item item = null; 115 Map<String, String> attributes = getAttributes(a); 116 117 try { 118 item = ItemFactory.build(lname, attributes); 119 if (item instanceof Root) { 120 Root root = (Root) item; 121 if (this.root == null) { 122 root.url = locator.getSystemId(); 123 this.root = root; 124 } 125 } 126 if (stack.size() > 0) { 127 // add this item to the parent 128 // do not put this into the constructor of Item or Chunks will not work 129 stack.peek().addItem(item); 130 } 131 } catch (IllegalArgumentException e) { 132 throwException(e); 133 } 134 stack.push(item); 106 135 } 107 }108 136 109 static class HashSetWithLast<E> extends LinkedHashSet<E> { 110 private static final long serialVersionUID = 1L; 111 protected transient E last; 137 @Override 138 public void endElement(String ns, String lname, String qname) throws SAXException { 139 try { 140 Item item = stack.peek(); 141 if (characters != null) 142 item.setContent(characters.toString().trim()); 143 item.endElement(); 144 } catch (IllegalArgumentException e) { 145 throwException(e); 146 } 147 stack.pop(); 148 characters = null; 149 } 112 150 113 151 @Override 114 public boolean add(E e) { 115 last = e; 116 return super.add(e); 152 public void characters(char[] ch, int start, int length) { 153 if (characters == null) 154 characters = new StringBuilder(64); // lazily get a builder 155 characters.append(ch, start, length); 117 156 } 118 157 119 158 /** 120 * Returns the last inserted element. 121 * @return the last inserted element 159 * Rethrows an exception and adds location information 160 * @param e the exception without location information 161 * @throws XmlParsingException the exception with location information 122 162 */ 123 p ublic E getLast(){124 return last;163 private void throwException(Exception e) throws XmlParsingException { 164 throw new XmlParsingException(e).rememberLocation(locator); 125 165 } 126 }127 166 128 /** 129 * Returns the set of preset source URLs. 130 * @return The set of preset source URLs. 131 */ 132 public static Set<String> getPresetSources() { 133 return new PresetPrefHelper().getActiveUrls(); 134 } 167 @Override 168 public void error(SAXParseException e) throws SAXException { 169 throwException(e); 170 } 135 171 136 private static XmlObjectParser buildParser() { 137 XmlObjectParser parser = new XmlObjectParser(); 138 parser.mapOnStart("item", TaggingPreset.class); 139 parser.mapOnStart("separator", TaggingPresetSeparator.class); 140 parser.mapBoth("group", TaggingPresetMenu.class); 141 parser.map("text", Text.class); 142 parser.map("link", Link.class); 143 parser.map("preset_link", PresetLink.class); 144 parser.mapOnStart("optional", Optional.class); 145 parser.mapOnStart("roles", Roles.class); 146 parser.map("role", Role.class); 147 parser.mapBoth("checkgroup", CheckGroup.class); 148 parser.map("check", Check.class); 149 parser.map("combo", Combo.class); 150 parser.map("multiselect", MultiSelect.class); 151 parser.map("label", Label.class); 152 parser.map("space", Space.class); 153 parser.map("key", Key.class); 154 parser.map("list_entry", PresetListEntry.class); 155 parser.map("item_separator", ItemSeparator.class); 156 parser.mapBoth("chunk", Chunk.class); 157 parser.map("reference", Reference.class); 158 return parser; 172 @Override 173 public void fatalError(SAXParseException e) throws SAXException { 174 throwException(e); 175 } 159 176 } 160 177 161 /** 162 * Reads all tagging presets from the input reader. 163 * @param in The input reader 164 * @param validate if {@code true}, XML validation will be performed 165 * @return collection of tagging presets 166 * @throws SAXException if any XML error occurs 167 */ 168 public static Collection<TaggingPreset> readAll(Reader in, boolean validate) throws SAXException { 169 return readAll(in, validate, new HashSetWithLast<TaggingPreset>()); 178 private static void start(final Reader in, final ContentHandler contentHandler, String url) throws SAXException, IOException { 179 try { 180 XMLReader reader = XmlUtils.newSafeSAXParser().getXMLReader(); 181 reader.setContentHandler(contentHandler); 182 try { 183 // better performance on big files like defaultpresets.xml 184 reader.setProperty("http://apache.org/xml/properties/input-buffer-size", 8 * 1024); 185 // enable xinclude 186 reader.setFeature("http://apache.org/xml/features/xinclude", true); 187 // do not set xml:base, it doesn't validate 188 reader.setFeature("http://apache.org/xml/features/xinclude/fixup-base-uris", false); 189 // Do not load external DTDs (fix #8191) 190 reader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); 191 } catch (SAXException e) { 192 // Exception very unlikely to happen, so no need to translate this 193 Logging.log(Logging.LEVEL_ERROR, "Cannot set property or feature on SAX reader:", e); 194 } 195 InputSource is = new InputSource(in); 196 is.setSystemId(url); 197 reader.parse(is); 198 } catch (ParserConfigurationException e) { 199 throw new JosmRuntimeException(e); 200 } 170 201 } 171 202 172 203 /** 173 * Reads all tagging presets from the input reader. 174 * @param in The input reader 175 * @param validate if {@code true}, XML validation will be performed 176 * @param all the accumulator for parsed tagging presets 177 * @return the accumulator 178 * @throws SAXException if any XML error occurs 204 * This filter adds the default namespace 205 * {@code http://josm.openstreetmap.de/tagging-preset-1.0} to all elements that have none. 179 206 */ 180 static Collection<TaggingPreset> readAll(Reader in, boolean validate, HashSetWithLast<TaggingPreset> all) throws SAXException{181 XmlObjectParser parser = buildParser();207 private static class AddNamespaceFilter extends XMLFilterImpl { 208 private final String namespace; 182 209 183 /** to detect end of {@code <checkgroup>} */ 184 CheckGroup lastcheckgroup = null; 185 /** to detect end of {@code <group>} */ 186 TaggingPresetMenu lastmenu = null; 187 /** to detect end of reused {@code <group>} */ 188 TaggingPresetMenu lastmenuOriginal = null; 189 Roles lastrole = null; 190 final List<Check> checks = new LinkedList<>(); 191 final List<PresetListEntry> listEntries = new LinkedList<>(); 192 final Map<String, List<Object>> byId = new HashMap<>(); 193 final Deque<String> lastIds = new ArrayDeque<>(); 194 /** lastIdIterators contains non empty iterators of items to be handled before obtaining the next item from the XML parser */ 195 final Deque<Iterator<Object>> lastIdIterators = new ArrayDeque<>(); 210 AddNamespaceFilter(String namespace) { 211 this.namespace = namespace; 212 } 196 213 197 if (validate) { 198 parser.startWithValidation(in, NAMESPACE, SCHEMA_SOURCE); 199 } else { 200 parser.start(in); 201 } 202 while (parser.hasNext() || !lastIdIterators.isEmpty()) { 203 final Object o; 204 if (!lastIdIterators.isEmpty()) { 205 // obtain elements from lastIdIterators with higher priority 206 o = lastIdIterators.peek().next(); 207 if (!lastIdIterators.peek().hasNext()) { 208 // remove iterator if is empty 209 lastIdIterators.pop(); 210 } 214 @Override 215 public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException { 216 if ("".equals(uri)) { 217 super.startElement(namespace, localName, qName, atts); 211 218 } else { 212 o = parser.next();219 super.startElement(uri, localName, qName, atts); 213 220 } 214 Logging.trace("Preset object: {0}", o);215 if (o instanceof Chunk) {216 if (!lastIds.isEmpty() && ((Chunk) o).id.equals(lastIds.peek())) {217 // pop last id on end of object, don't process further218 lastIds.pop();219 ((Chunk) o).id = null;220 } else {221 // if preset item contains an id, store a mapping for later usage222 String lastId = ((Chunk) o).id;223 lastIds.push(lastId);224 byId.put(lastId, new ArrayList<>());225 }226 continue;227 } else if (!lastIds.isEmpty()) {228 // add object to mapping for later usage229 byId.get(lastIds.peek()).add(o);230 continue;231 }232 if (o instanceof Reference) {233 // if o is a reference, obtain the corresponding objects from the mapping,234 // and iterate over those before consuming the next element from parser.235 final String ref = ((Reference) o).ref;236 if (byId.get(ref) == null) {237 throw new SAXException(tr("Reference {0} is being used before it was defined", ref));238 }239 Iterator<Object> it = byId.get(ref).iterator();240 if (it.hasNext()) {241 lastIdIterators.push(it);242 if (lastIdIterators.size() > 100) {243 throw new SAXException(tr("Reference stack for {0} is too large", ref));244 }245 } else {246 Logging.warn("Ignoring reference '"+ref+"' denoting an empty chunk");247 }248 continue;249 }250 if (!(o instanceof TaggingPresetItem) && !checks.isEmpty()) {251 all.getLast().data.addAll(checks);252 checks.clear();253 }254 if (o instanceof TaggingPresetMenu) {255 TaggingPresetMenu tp = (TaggingPresetMenu) o;256 if (tp == lastmenu || tp == lastmenuOriginal) {257 lastmenu = tp.group;258 } else {259 tp.group = lastmenu;260 if (all.contains(tp)) {261 lastmenuOriginal = tp;262 tp = (TaggingPresetMenu) all.stream().filter(tp::equals).findFirst().orElse(tp);263 lastmenuOriginal.group = null;264 } else {265 tp.setDisplayName();266 all.add(tp);267 lastmenuOriginal = null;268 }269 lastmenu = tp;270 }271 lastrole = null;272 } else if (o instanceof TaggingPresetSeparator) {273 TaggingPresetSeparator tp = (TaggingPresetSeparator) o;274 tp.group = lastmenu;275 all.add(tp);276 lastrole = null;277 } else if (o instanceof TaggingPreset) {278 TaggingPreset tp = (TaggingPreset) o;279 tp.group = lastmenu;280 tp.setDisplayName();281 all.add(tp);282 lastrole = null;283 } else {284 if (!all.isEmpty()) {285 if (o instanceof Roles) {286 all.getLast().data.add((TaggingPresetItem) o);287 if (all.getLast().roles != null) {288 throw new SAXException(tr("Roles cannot appear more than once"));289 }290 all.getLast().roles = (Roles) o;291 lastrole = (Roles) o;292 // #16458 - Make sure we don't duplicate role entries if used in a chunk/reference293 lastrole.roles.clear();294 } else if (o instanceof Role) {295 if (lastrole == null)296 throw new SAXException(tr("Preset role element without parent"));297 lastrole.roles.add((Role) o);298 } else if (o instanceof Check) {299 if (lastcheckgroup != null) {300 checks.add((Check) o);301 } else {302 all.getLast().data.add((TaggingPresetItem) o);303 }304 } else if (o instanceof PresetListEntry) {305 listEntries.add((PresetListEntry) o);306 } else if (o instanceof CheckGroup) {307 CheckGroup cg = (CheckGroup) o;308 if (cg == lastcheckgroup) {309 lastcheckgroup = null;310 all.getLast().data.add(cg);311 // Make sure list of checks is empty to avoid adding checks several times312 // when used in chunks (fix #10801)313 cg.checks.clear();314 cg.checks.addAll(checks);315 checks.clear();316 } else {317 lastcheckgroup = cg;318 }319 } else {320 if (!checks.isEmpty()) {321 all.getLast().data.addAll(checks);322 checks.clear();323 }324 all.getLast().data.add((TaggingPresetItem) o);325 if (o instanceof ComboMultiSelect) {326 ((ComboMultiSelect) o).addListEntries(listEntries);327 } else if (o instanceof Key && ((Key) o).value == null) {328 ((Key) o).value = ""; // Fix #8530329 }330 listEntries.clear();331 lastrole = null;332 }333 } else334 throw new SAXException(tr("Preset sub element without parent"));335 }336 221 } 337 if (!all.isEmpty() && !checks.isEmpty()) {338 all.getLast().data.addAll(checks);339 checks.clear();340 }341 return all;342 222 } 343 223 344 224 /** 345 * Reads all tagging presets from the given source. 346 * @param source a given filename, URL or internal resource 347 * @param validate if {@code true}, XML validation will be performed 348 * @return collection of tagging presets 349 * @throws SAXException if any XML error occurs 350 * @throws IOException if any I/O error occurs 225 * Add validation filters to a parser 226 * 227 * @param parser the parser without validation 228 * @param namespace default namespace 229 * @param schemaUrl URL of XSD schema 230 * @return the new parser with validation 231 * @throws SAXException if any XML or I/O error occurs 351 232 */ 352 public static Collection<TaggingPreset> readAll(String source, boolean validate) throws SAXException, IOException { 353 return readAll(source, validate, new HashSetWithLast<TaggingPreset>()); 233 public static ContentHandler buildParserWithValidation(Parser parser, String namespace, String schemaUrl) throws SAXException { 234 SchemaFactory factory = XmlUtils.newXmlSchemaFactory(); 235 try (CachedFile cf = new CachedFile(schemaUrl); 236 InputStream mis = cf.getInputStream()) { 237 Schema schema = factory.newSchema(new StreamSource(mis)); 238 ValidatorHandler validator = schema.newValidatorHandler(); 239 validator.setContentHandler(parser); 240 validator.setErrorHandler(parser); 241 242 AddNamespaceFilter filter = new AddNamespaceFilter(namespace); 243 filter.setContentHandler(validator); 244 return filter; 245 } catch (IOException e) { 246 throw new SAXException(tr("Failed to load XML schema."), e); 247 } 354 248 } 355 249 356 250 /** 357 * Reads all tagging presets from the given source. 358 * @param source a given filename, URL or internal resource 251 * Reads all tagging presets from the given XML resource. 252 * 253 * @param url a given filename, URL or internal resource 359 254 * @param validate if {@code true}, XML validation will be performed 360 * @param all the accumulator for parsed tagging presets 361 * @return the accumulator 255 * @return the root element of the resource 362 256 * @throws SAXException if any XML error occurs 363 257 * @throws IOException if any I/O error occurs 364 258 */ 365 static Collection<TaggingPreset> readAll(String source, boolean validate, HashSetWithLast<TaggingPreset> all) 366 throws SAXException, IOException { 367 Collection<TaggingPreset> tp; 368 Logging.debug("Reading presets from {0}", source); 259 public static Root read(String url, boolean validate) throws SAXException, IOException { 260 Logging.debug("Reading presets from {0}", url); 369 261 Stopwatch stopwatch = Stopwatch.createStarted(); 262 Parser parser = new Parser(); 263 370 264 try ( 371 CachedFile cf = new CachedFile( source).setHttpAccept(PRESET_MIME_TYPES);265 CachedFile cf = new CachedFile(url); 372 266 // zip may be null, but Java 7 allows it: https://blogs.oracle.com/darcy/entry/project_coin_null_try_with 373 267 InputStream zip = cf.findZipEntryInputStream("xml", "preset") 374 268 ) { 269 cf.setHttpAccept(PRESET_MIME_TYPES); 375 270 if (zip != null) { 376 271 zipIcons = cf.getFile(); 377 272 I18n.addTexts(zipIcons); 378 273 } 379 274 try (InputStreamReader r = UTFInputStreamReader.create(zip == null ? cf.getInputStream() : zip)) { 380 tp = readAll(new BufferedReader(r), validate, all); 275 ContentHandler handler = parser; 276 if (validate) { 277 handler = buildParserWithValidation((Parser) handler, NAMESPACE, SCHEMA_SOURCE); 278 } 279 start(new BufferedReader(r), handler, url); 381 280 } 382 281 } 282 283 Root patchRoot = readPatchFile(url, validate); 284 if (patchRoot != null) 285 parser.getRoot().items.addAll(patchRoot.items); 286 383 287 Logging.debug(stopwatch.toString("Reading presets")); 384 return tp;288 return parser.getRoot(); 385 289 } 386 290 387 291 /** 388 * Reads all tagging presets from the given sources. 292 * Try to read a .local preset patch file. 293 * <p> 294 * A preset patch file has the same structure as the {@code defaultpresets.xml} file. All items 295 * in the root of the preset patch file will be appended to the root of the respective presets 296 * file. Chunks in the preset patch file will replace chunks with the same {@code id} in the 297 * presets file. The patch file must be placed in the {@code josmdir://} and have the same 298 * filename and extension with an added extension of {@code .local} eg. 299 * {@code <josmdir>/defaultpresets.xml.local} 300 * 301 * @param url a given filename, URL or internal resource 302 * @param validate if {@code true}, XML validation will be performed 303 * @return the root element of the resource 304 * @throws SAXException if any XML error occurs 305 * @throws IOException if any I/O error occurs 306 */ 307 308 static Root readPatchFile(String url, boolean validate) throws SAXException, IOException { 309 try { 310 URI uri = new URI(url); 311 String fileName = new File(uri.getPath()).getName(); 312 url = "josmdir://" + fileName + ".local"; 313 314 Parser parser = new Parser(); 315 try (CachedFile cf = new CachedFile(url)) { 316 Logging.debug("Reading local preset patches from {0}", cf.getFile().toPath()); 317 try (InputStreamReader r = UTFInputStreamReader.create(cf.getInputStream())) { 318 ContentHandler handler = parser; 319 if (validate) { 320 handler = buildParserWithValidation(parser, NAMESPACE, SCHEMA_SOURCE); 321 } 322 start(new BufferedReader(r), handler, url); 323 } 324 } 325 return parser.getRoot(); 326 } catch (URISyntaxException e) { 327 Logging.error("readPatchFile: cannot parse url {0}", url); 328 return null; 329 } catch (IOException e) { 330 return null; // there is no local patch file, do nothing 331 } 332 } 333 334 /** 335 * Reads all tagging presets from the given XML resources. Convenience function. 336 * 389 337 * @param sources Collection of tagging presets sources. 390 338 * @param validate if {@code true}, presets will be validated against XML schema 391 * @return Collection of all presets successfully read339 * @return the root elements of the XML resources 392 340 */ 393 public static Collection< TaggingPreset> readAll(Collection<String> sources, boolean validate) {341 public static Collection<Root> readAll(Collection<String> sources, boolean validate) { 394 342 return readAll(sources, validate, true); 395 343 } 396 344 397 345 /** 398 * Reads all tagging presets from the given sources. 346 * Reads all tagging presets from the given XML resources. 347 * 399 348 * @param sources Collection of tagging presets sources. 400 349 * @param validate if {@code true}, presets will be validated against XML schema 401 350 * @param displayErrMsg if {@code true}, a blocking error message is displayed in case of I/O exception. 402 * @return Collection of all presets successfully read351 * @return the root elements of the XML resources 403 352 */ 404 public static Collection< TaggingPreset> readAll(Collection<String> sources, boolean validate, boolean displayErrMsg) {405 HashSetWithLast<TaggingPreset> allPresets = new HashSetWithLast<>();353 public static Collection<Root> readAll(Collection<String> sources, boolean validate, boolean displayErrMsg) { 354 Collection<Root> result = new ArrayList<>(); 406 355 for (String source : sources) { 407 356 try { 408 re adAll(source, validate, allPresets);357 result.add(read(source, validate)); 409 358 } catch (IOException e) { 410 359 Logging.log(Logging.LEVEL_ERROR, e); 411 360 Logging.error(source); … … 434 383 } 435 384 } 436 385 } 437 return allPresets;386 return result; 438 387 } 439 388 440 389 /** … … 443 392 * @param displayErrMsg if {@code true}, a blocking error message is displayed in case of I/O exception. 444 393 * @return Collection of all presets successfully read 445 394 */ 446 public static Collection< TaggingPreset> readFromPreferences(boolean validate, boolean displayErrMsg) {395 public static Collection<Root> readFromPreferences(boolean validate, boolean displayErrMsg) { 447 396 return readAll(getPresetSources(), validate, displayErrMsg); 448 397 } 449 398 450 399 /** 400 * Returns the set of preset source URLs. 401 * @return The set of preset source URLs. 402 */ 403 public static Set<String> getPresetSources() { 404 return new PresetPrefHelper().getActiveUrls(); 405 } 406 407 /** 451 408 * Returns the zip file where the icons are located 452 409 * @return the zip file where the icons are located 453 410 */ … … 471 428 TaggingPresetReader.loadIcons = loadIcons; 472 429 } 473 430 474 private TaggingPresetReader() { 475 // Hide default constructor for utils classes 476 } 431 // fix checkstyle HideUtilityClassConstructor 432 private TaggingPresetReader() {} 477 433 } -
src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetSearchDialog.java
54 54 if (buttonIndex == 0) { 55 55 TaggingPreset preset = selector.getSelectedPresetAndUpdateClassification(); 56 56 if (preset != null) { 57 preset. actionPerformed(null);57 preset.getAction().actionPerformed(null); 58 58 } 59 59 } 60 60 selector.savePreferences(); -
src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetSelector.java
23 23 import javax.swing.Action; 24 24 import javax.swing.BoxLayout; 25 25 import javax.swing.DefaultListCellRenderer; 26 import javax.swing.Icon;27 26 import javax.swing.JCheckBox; 28 27 import javax.swing.JLabel; 29 28 import javax.swing.JList; … … 39 38 import org.openstreetmap.josm.data.osm.OsmPrimitive; 40 39 import org.openstreetmap.josm.data.preferences.BooleanProperty; 41 40 import org.openstreetmap.josm.gui.MainApplication; 42 import org.openstreetmap.josm.gui.tagging.presets.items.ComboMultiSelect;43 import org.openstreetmap.josm.gui.tagging.presets.items.Key;44 import org.openstreetmap.josm.gui.tagging.presets.items.KeyedItem;45 import org.openstreetmap.josm.gui.tagging.presets.items.Roles;46 import org.openstreetmap.josm.gui.tagging.presets.items.Roles.Role;47 41 import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher; 48 42 import org.openstreetmap.josm.gui.widgets.SearchTextResultListPanel; 49 43 import org.openstreetmap.josm.tools.Destroyable; … … 77 71 boolean isSelected, boolean cellHasFocus) { 78 72 JLabel result = (JLabel) def.getListCellRendererComponent(list, tp, index, isSelected, cellHasFocus); 79 73 result.setText(tp.getName()); 80 result.setIcon( (Icon) tp.getValue(Action.SMALL_ICON));74 result.setIcon(tp.getIcon(Action.SMALL_ICON)); 81 75 return result; 82 76 } 83 77 } … … 86 80 * Computes the match ration of a {@link TaggingPreset} wrt. a searchString. 87 81 */ 88 82 public static class PresetClassification implements Comparable<PresetClassification> { 83 /** The preset to classify */ 89 84 public final TaggingPreset preset; 85 /** how well this preset matches */ 90 86 public int classification; 87 /** where to put it in the list */ 91 88 public int favoriteIndex; 92 89 private final Collection<String> groups; 93 90 private final Collection<String> names; … … 98 95 Set<String> groupSet = new HashSet<>(); 99 96 Set<String> nameSet = new HashSet<>(); 100 97 Set<String> tagSet = new HashSet<>(); 101 TaggingPreset group = preset.group; 102 while (group != null) { 103 addLocaleNames(groupSet, group); 104 group = group.group; 98 99 for (String name : preset.getLocaleGroupName().split("/", -1)) { 100 addLocaleNames(groupSet, name); 105 101 } 106 addLocaleNames(nameSet, preset );107 for ( TaggingPresetItem item: preset.data) {102 addLocaleNames(nameSet, preset.getLocaleName()); 103 for (Item item: preset.getAllItems()) { 108 104 if (item instanceof KeyedItem) { 109 tagSet.add(((KeyedItem) item).key); 105 tagSet.add(((KeyedItem) item).getKey()); 106 tagSet.addAll(((KeyedItem) item).getValues()); 110 107 if (item instanceof ComboMultiSelect) { 111 final ComboMultiSelect cms = (ComboMultiSelect) item; 112 if (cms.values_searchable) { 113 tagSet.addAll(cms.getDisplayValues()); 114 } 108 tagSet.addAll(((ComboMultiSelect) item).getDisplayValues()); 115 109 } 116 if (item instanceof Key && ((Key) item).value != null) { 117 tagSet.add(((Key) item).value); 118 } 119 } else if (item instanceof Roles) { 120 for (Role role : ((Roles) item).roles) { 121 tagSet.add(role.key); 122 } 110 } else if (item instanceof Role) { 111 tagSet.add(((Role) item).getKey()); 123 112 } 124 113 } 125 114 this.groups = Utils.toUnmodifiableList(groupSet); … … 127 116 this.tags = Utils.toUnmodifiableList(tagSet); 128 117 } 129 118 130 private static void addLocaleNames(Collection<String> collection, TaggingPreset preset) { 131 String locName = preset.getLocaleName(); 119 private static void addLocaleNames(Collection<String> collection, String locName) { 132 120 if (locName != null) { 133 121 Collections.addAll(collection, locName.toLowerCase(Locale.ENGLISH).split("\\s", -1)); 134 122 } … … 276 264 277 265 private final List<PresetClassification> classifications = new ArrayList<>(); 278 266 267 /** 268 * Returns a list of presets that match the search criteria. 269 * 270 * @param searchText the search text 271 * @param onlyApplicable only consider presets applicable to {@code presetTypes} 272 * @param inTags if true also search for words in tags 273 * @param presetTypes these preset types 274 * @param selectedPrimitives consider relation presets that can use the selected primitives as roles 275 * @return the list of presets 276 */ 279 277 public List<PresetClassification> getMatchingPresets(String searchText, boolean onlyApplicable, boolean inTags, 280 278 Set<TaggingPresetType> presetTypes, final Collection<? extends OsmPrimitive> selectedPrimitives) { 281 279 final String[] groupWords; … … 292 290 return getMatchingPresets(groupWords, nameWords, onlyApplicable, inTags, presetTypes, selectedPrimitives); 293 291 } 294 292 293 /** 294 * Returns a list of presets that match the search criteria. 295 * @param groupWords search for these preset groups 296 * @param nameWords search for these preset names 297 * @param onlyApplicable only consider presets applicable to {@code presetTypes} 298 * @param inTags if true also search for words in tags 299 * @param presetTypes these preset types 300 * @param selectedPrimitives consider relation presets that can use the selected primitives as roles 301 * @return the list of presets 302 */ 295 303 public List<PresetClassification> getMatchingPresets(String[] groupWords, String[] nameWords, boolean onlyApplicable, 296 304 boolean inTags, Set<TaggingPresetType> presetTypes, final Collection<? extends OsmPrimitive> selectedPrimitives) { 297 305 … … 303 311 if (onlyApplicable) { 304 312 boolean suitable = preset.typeMatches(presetTypes); 305 313 306 if (!suitable && preset.types.contains(TaggingPresetType.RELATION) 307 && preset.roles != null && !preset.roles.roles.isEmpty()) { 308 suitable = preset.roles.roles.stream().anyMatch( 309 object -> object.memberExpression != null && selectedPrimitives.stream().anyMatch(object.memberExpression)); 314 if (!suitable && preset.getTypes().contains(TaggingPresetType.RELATION)) { 315 suitable = preset.getAllRoles().stream().anyMatch( 316 role -> role.getMemberExpression() != null && selectedPrimitives.stream().anyMatch(role.getMemberExpression())); 310 317 // keep the preset to allow the creation of new relations 311 318 } 312 319 if (!suitable) { … … 360 367 */ 361 368 public void loadPresets(Collection<TaggingPreset> presets) { 362 369 for (TaggingPreset preset : presets) { 363 if (preset instanceof TaggingPresetSeparator || preset instanceof TaggingPresetMenu) {364 c ontinue;370 if (preset.getClass() == TaggingPreset.class) { 371 classifications.add(new PresetClassification(preset)); 365 372 } 366 classifications.add(new PresetClassification(preset));367 373 } 368 374 } 369 375 -
src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetSeparator.java
1 1 // License: GPL. For details, see LICENSE file. 2 2 package org.openstreetmap.josm.gui.tagging.presets; 3 3 4 import java.util.Map; 5 6 import javax.swing.JMenu; 7 import javax.swing.JSeparator; 8 4 9 /** 5 * Class used to represent a {@link javax.swing.JSeparator} inside tagging preset menu.10 * Class used to represent a {@link JSeparator} inside tagging preset menu. 6 11 * @since 895 7 12 */ 8 public class TaggingPresetSeparator extends TaggingPreset { 13 final class TaggingPresetSeparator extends Item { 14 15 private TaggingPresetSeparator(Map<String, String> attributes) throws IllegalArgumentException { 16 super(attributes); 17 } 18 19 /** 20 * Create a {@code TaggingPresetSeparator} from an XML element's attributes. 21 * @param attributes the XML attributes 22 * @return the {@code TaggingPresetSeparator} 23 * @throws IllegalArgumentException on attribute errors 24 */ 25 public static TaggingPresetSeparator fromXML(Map<String, String> attributes) throws IllegalArgumentException { 26 return new TaggingPresetSeparator(attributes); 27 } 28 9 29 @Override 10 public void setDisplayName() {11 // Do nothing30 public void addToMenu(JMenu parentMenu) { 31 parentMenu.add(new JSeparator()); 12 32 } 33 34 @Override 35 public String toString() { 36 return "TaggingPresetSeparator"; 37 } 13 38 } -
src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetType.java
1 1 // License: GPL. For details, see LICENSE file. 2 2 package org.openstreetmap.josm.gui.tagging.presets; 3 3 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 4 6 import java.util.Arrays; 7 import java.util.EnumSet; 8 import java.util.Map; 5 9 6 10 import org.openstreetmap.josm.data.osm.IPrimitive; 7 11 import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 12 import org.openstreetmap.josm.gui.util.LruCache; 8 13 9 14 /** 10 15 * Enumeration of OSM primitive types associated with names and icons … … 24 29 private final String iconName; 25 30 private final String name; 26 31 32 /** LRU cache for the parsing of types */ 33 private static final Map<String, EnumSet<TaggingPresetType>> TYPE_CACHE = new LruCache<>(16); 34 27 35 TaggingPresetType(String iconName, String name) { 28 36 this.iconName = iconName + ".svg"; 29 37 this.name = name; … … 83 91 .filter(t -> t.getName().equals(type)) 84 92 .findFirst().orElse(null); 85 93 } 94 95 /** 96 * Returns a set of types 97 * 98 * @param types the types as comma-separated string. eg. "node,way,relation" 99 * @param default_ the default value, returned if {@code types} is null 100 * @return the types as set 101 * @throws IllegalArgumentException on input error 102 */ 103 public static EnumSet<TaggingPresetType> getOrDefault(String types, EnumSet<TaggingPresetType> default_) throws IllegalArgumentException { 104 if (types == null) 105 return default_; 106 if (types.isEmpty()) 107 throw new IllegalArgumentException(tr("Unknown type: {0}", types)); 108 if (TYPE_CACHE.containsKey(types)) 109 return TYPE_CACHE.get(types); 110 111 EnumSet<TaggingPresetType> result = EnumSet.noneOf(TaggingPresetType.class); 112 for (String type : types.split(",", -1)) { 113 try { 114 TaggingPresetType presetType = fromString(type); 115 if (presetType != null) { 116 result.add(presetType); 117 } 118 } catch (IllegalArgumentException e) { 119 throw new IllegalArgumentException(tr("Unknown type: {0}", type), e); 120 } 121 } 122 TYPE_CACHE.put(types, result); 123 return result; 124 } 86 125 } -
src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetUtils.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 import static org.openstreetmap.josm.tools.I18n.trc; 6 7 import java.awt.Component; 8 import java.io.File; 9 import java.io.Serializable; 10 import java.util.ArrayList; 11 import java.util.Collection; 12 import java.util.Collections; 13 import java.util.Comparator; 14 import java.util.List; 15 import java.util.Map; 16 import java.util.concurrent.CompletableFuture; 17 import java.util.concurrent.ExecutionException; 18 import java.util.concurrent.TimeUnit; 19 import java.util.concurrent.TimeoutException; 20 21 import javax.swing.AbstractAction; 22 import javax.swing.ImageIcon; 23 import javax.swing.JMenu; 24 import javax.swing.JMenuItem; 25 import javax.swing.JSeparator; 26 import javax.swing.SwingUtilities; 27 28 import org.openstreetmap.josm.data.osm.DataSet; 29 import org.openstreetmap.josm.data.osm.OsmDataManager; 30 import org.openstreetmap.josm.data.osm.search.SearchCompiler; 31 import org.openstreetmap.josm.data.osm.search.SearchParseError; 32 import org.openstreetmap.josm.data.osm.search.SearchCompiler.Match; 33 import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem; 34 import org.openstreetmap.josm.gui.MainApplication; 35 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager; 36 import org.openstreetmap.josm.tools.AlphanumComparator; 37 import org.openstreetmap.josm.tools.ImageProvider; 38 import org.openstreetmap.josm.tools.ImageResource; 39 import org.openstreetmap.josm.tools.Logging; 40 import org.openstreetmap.josm.tools.template_engine.ParseError; 41 import org.openstreetmap.josm.tools.template_engine.TemplateEntry; 42 import org.openstreetmap.josm.tools.template_engine.TemplateParser; 43 44 /** 45 * Utility class for tagging presets. 46 */ 47 public final class TaggingPresetUtils { 48 49 private TaggingPresetUtils() {} 50 51 /** 52 * Replaces ' with '' 53 * @param s input 54 * @return output 55 */ 56 public static String fixPresetString(String s) { 57 return s == null ? s : s.replace("'", "''"); 58 } 59 60 /** 61 * Parse and compile a template. 62 * 63 * @param pattern The template pattern. 64 * @return the compiled template 65 * @throws IllegalArgumentException If an error occured while parsing. 66 */ 67 public static TemplateEntry parseTemplate(String pattern) throws IllegalArgumentException { // NOPMD 68 if (pattern == null) 69 return null; 70 try { 71 return new TemplateParser(pattern).parse(); 72 } catch (ParseError e) { 73 Logging.error("Error while parsing " + pattern + ": " + e.getMessage()); 74 throw new IllegalArgumentException(e); 75 } 76 } 77 78 /** 79 * Sets the match_expression additional criteria for matching primitives. 80 * 81 * @param filter The search pattern 82 * @return the compiled expression 83 84 * @throws IllegalArgumentException on search pattern parse error 85 * @see <a href="https://josm.openstreetmap.de/wiki/TaggingPresets#Attributes">JOSM wiki</a> 86 */ 87 88 public static Match parseSearchExpression(String filter) throws IllegalArgumentException { 89 if (filter == null) 90 return null; 91 try { 92 return SearchCompiler.compile(filter); 93 } catch (SearchParseError e) { 94 Logging.error("Error while parsing" + filter + ": " + e.getMessage()); 95 throw new IllegalArgumentException(e); 96 } 97 } 98 99 /** 100 * Loads the icon asynchronously and puts it on the action. 101 * <p> 102 * The image resource is loaded in the background, and then the EDT is invoked to put the icon 103 * on the action. 104 * @param iconName the iconname 105 * @param action the action where to put the icon 106 * 107 * @return a future completed when the icon is put on the action 108 */ 109 static CompletableFuture<ImageResource> loadIcon(String iconName, AbstractAction action) { 110 if (action != null && iconName != null && TaggingPresetReader.isLoadIcons()) { 111 return new ImageProvider(iconName) 112 .setDirs(TaggingPresets.ICON_SOURCES.get()) 113 .setId("presets") 114 .setArchive(TaggingPresetReader.getZipIcons()) 115 .setOptional(true) 116 .getResourceFuture() 117 .thenCompose((imageResource) -> { 118 CompletableFuture<ImageResource> future = new CompletableFuture<>(); 119 SwingUtilities.invokeLater(() -> { 120 if (imageResource != null) 121 imageResource.attachImageIcon(action, true); 122 future.complete(imageResource); 123 }); 124 return future; 125 }); 126 } 127 return CompletableFuture.<ImageResource>completedFuture(null); 128 } 129 130 /** 131 * Loads a tagging preset icon 132 * @param iconName the icon name 133 * @param zipIcons zip file where the image is located 134 * @param maxSize maximum image size (or null) 135 * @return the requested image or null if the request failed 136 */ 137 public static ImageIcon loadImageIcon(String iconName, File zipIcons, Integer maxSize) { 138 final Collection<String> s = TaggingPresets.ICON_SOURCES.get(); 139 ImageProvider imgProv = new ImageProvider(iconName).setDirs(s).setId("presets").setArchive(zipIcons).setOptional(true); 140 if (maxSize != null && maxSize > 0) { 141 imgProv.setMaxSize(maxSize); 142 } 143 return imgProv.get(); 144 } 145 146 /** 147 * Localizes the preset name 148 * 149 * @param localeText the locale name from the attributes 150 * @param text the unlocalized name 151 * @param textContext the localization context 152 * @return The name that should be displayed to the user. 153 */ 154 public static String buildLocaleString(String localeText, String text, String textContext) { 155 if (localeText != null) 156 return localeText; 157 text = fixPresetString(text); 158 if (textContext != null) { 159 return trc(textContext, text); 160 } else { 161 return tr(text); 162 } 163 } 164 165 /** 166 * Determine whether the given preset items match the tags 167 * @param data the preset items 168 * @param tags the tags to match 169 * @return whether the given preset items match the tags 170 * @since 9932 171 */ 172 public static boolean matches(Iterable<? extends Item> data, Map<String, String> tags) { 173 boolean atLeastOnePositiveMatch = false; 174 for (Item item : data) { 175 Boolean m = item.matches(tags); 176 if (m != null && !m) 177 return false; 178 else if (m != null) { 179 atLeastOnePositiveMatch = true; 180 } 181 } 182 return atLeastOnePositiveMatch; 183 } 184 185 /** 186 * allow escaped comma in comma separated list: 187 * "A\, B\, C,one\, two" --> ["A, B, C", "one, two"] 188 * @param delimiter the delimiter, e.g. a comma. separates the entries and 189 * must be escaped within one entry 190 * @param s the string 191 * @return splitted items 192 */ 193 public static List<String> splitEscaped(char delimiter, String s) { 194 if (s == null) 195 return null; // NOSONAR 196 197 List<String> result = new ArrayList<>(); 198 boolean backslash = false; 199 StringBuilder item = new StringBuilder(); 200 for (int i = 0; i < s.length(); i++) { 201 char ch = s.charAt(i); 202 if (backslash) { 203 item.append(ch); 204 backslash = false; 205 } else if (ch == '\\') { 206 backslash = true; 207 } else if (ch == delimiter) { 208 result.add(item.toString()); 209 item.setLength(0); 210 } else { 211 item.append(ch); 212 } 213 } 214 if (item.length() > 0) { 215 result.add(item.toString()); 216 } 217 return result; 218 } 219 220 /** 221 * Returns all cached {@link AutoCompletionItem}s for given keys. 222 * 223 * @param keys retrieve the items for these keys 224 * @return the currently cached items, sorted by priority and alphabet 225 * @since 18221 226 */ 227 public static List<AutoCompletionItem> getAllForKeys(List<String> keys) { 228 DataSet data = OsmDataManager.getInstance().getEditDataSet(); 229 if (data == null) { 230 return Collections.emptyList(); 231 } 232 return AutoCompletionManager.of(data).getAllValuesForKeys(keys); 233 } 234 235 /** 236 * Parse a {@code String} into an {@code boolean}. 237 * @param s the string to parse 238 * @return the int 239 */ 240 public static boolean parseBoolean(String s) { 241 return s != null 242 && !"0".equals(s) 243 && !s.startsWith("off") 244 && !s.startsWith("false") 245 && !s.startsWith("no"); 246 } 247 248 /** 249 * Wait for all preset icons to load 250 * @param presets presets collection 251 * @param timeout timeout in seconds 252 * @throws InterruptedException if any thread is interrupted 253 * @throws ExecutionException if any thread throws 254 * @throws TimeoutException on timeout 255 */ 256 public static void waitForIconsLoaded(Collection<TaggingPreset> presets, long timeout) 257 throws InterruptedException, ExecutionException, TimeoutException { 258 259 @SuppressWarnings("unchecked") 260 CompletableFuture<ImageResource>[] futures = 261 presets.stream().map(tp -> tp.iconFuture).toArray(CompletableFuture[]::new); 262 263 CompletableFuture.allOf(futures).get(timeout, TimeUnit.SECONDS); 264 } 265 266 /** 267 * Sorts the menu items using the translated item text 268 * @param menu menu to sort 269 */ 270 public static void sortMenu(JMenu menu) { 271 Component[] items = menu.getMenuComponents(); 272 PresetTextComparator comp = new PresetTextComparator(); 273 List<JMenuItem> sortarray = new ArrayList<>(); 274 int lastSeparator = 0; 275 for (int i = 0; i < items.length; i++) { 276 Object item = items[i]; 277 if (item instanceof JMenu) { 278 sortMenu((JMenu) item); 279 } 280 if (item instanceof JMenuItem) { 281 sortarray.add((JMenuItem) item); 282 if (i == items.length-1) { 283 handleMenuItem(menu, comp, sortarray, lastSeparator); 284 sortarray = new ArrayList<>(); 285 lastSeparator = 0; 286 } 287 } else if (item instanceof JSeparator) { 288 handleMenuItem(menu, comp, sortarray, lastSeparator); 289 sortarray = new ArrayList<>(); 290 lastSeparator = i; 291 } 292 } 293 } 294 295 private static void handleMenuItem(JMenu menu, PresetTextComparator comp, List<JMenuItem> sortarray, int lastSeparator) { 296 sortarray.sort(comp); 297 int pos = 0; 298 for (JMenuItem menuItem : sortarray) { 299 int oldPos; 300 if (lastSeparator == 0) { 301 oldPos = pos; 302 } else { 303 oldPos = pos+lastSeparator+1; 304 } 305 menu.add(menuItem, oldPos); 306 pos++; 307 } 308 } 309 310 private static class PresetTextComparator implements Comparator<JMenuItem>, Serializable { 311 private static final long serialVersionUID = 1L; 312 @Override 313 public int compare(JMenuItem o1, JMenuItem o2) { 314 if (MainApplication.getMenu().presetSearchAction.equals(o1.getAction())) 315 return -1; 316 else if (MainApplication.getMenu().presetSearchAction.equals(o2.getAction())) 317 return 1; 318 else 319 return AlphanumComparator.getInstance().compare(o1.getText(), o2.getText()); 320 } 321 } 322 } -
src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetValidation.java
1 1 // License: GPL. For details, see LICENSE file. 2 2 package org.openstreetmap.josm.gui.tagging.presets; 3 3 4 import static java.util.Collections.singleton;5 4 import static org.openstreetmap.josm.tools.I18n.tr; 6 5 7 6 import java.util.ArrayList; 8 7 import java.util.Arrays; 9 import java.util.Collection;10 8 import java.util.List; 11 9 12 10 import javax.swing.JLabel; 13 11 14 import org.openstreetmap.josm.command.Command;15 import org.openstreetmap.josm.data.osm.DataSet;16 import org.openstreetmap.josm.data.osm.FilterModel;17 import org.openstreetmap.josm.data.osm.INode;18 import org.openstreetmap.josm.data.osm.IRelation;19 import org.openstreetmap.josm.data.osm.IWay;20 import org.openstreetmap.josm.data.osm.OsmPrimitive;21 import org.openstreetmap.josm.data.osm.Tag;22 12 import org.openstreetmap.josm.data.preferences.sources.ValidatorPrefHelper; 23 13 import org.openstreetmap.josm.data.validation.OsmValidator; 24 14 import org.openstreetmap.josm.data.validation.TestError; … … 27 17 import org.openstreetmap.josm.gui.MainApplication; 28 18 import org.openstreetmap.josm.gui.util.GuiHelper; 29 19 import org.openstreetmap.josm.tools.Logging; 30 import org.openstreetmap.josm.tools.SubclassFilteredCollection;31 20 import org.openstreetmap.josm.tools.Utils; 32 21 33 22 /** 34 23 * Validates the preset user input a the given primitive. 35 24 */ 36 interface TaggingPresetValidation {25 public interface TaggingPresetValidation { 37 26 38 27 /** 39 28 * Asynchronously validates the user input for the given primitive. 40 * @param original the primitive29 * @param handler the handler that holds the primitives to check 41 30 * @param validationLabel the label for validation errors 42 * @param changedTags the list of tags that are set by this preset43 31 */ 44 static void validateAsync(OsmPrimitive original, JLabel validationLabel, List<Tag> changedTags) { 45 OsmPrimitive primitive = applyChangedTags(original, changedTags); 46 MainApplication.worker.execute(() -> validate(primitive, validationLabel)); 32 static void validateAsync(TaggingPresetHandler handler, JLabel validationLabel) { 33 MainApplication.worker.execute(() -> validate(handler, validationLabel)); 47 34 } 48 35 49 36 /** 50 37 * Validates the user input for the given primitive. 51 * @param primitive the primitive38 * @param handler the handler that holds the primitives to check 52 39 * @param validationLabel the label for validation errors 53 40 */ 54 static void validate( OsmPrimitive primitive, JLabel validationLabel) {41 static void validate(TaggingPresetHandler handler, JLabel validationLabel) { 55 42 try { 56 43 MapCSSTagChecker mapCSSTagChecker = OsmValidator.getTest(MapCSSTagChecker.class); 57 44 OpeningHourTest openingHourTest = OsmValidator.getTest(OpeningHourTest.class); … … 58 45 OsmValidator.initializeTests(Arrays.asList(mapCSSTagChecker, openingHourTest)); 59 46 60 47 List<TestError> errors = new ArrayList<>(); 61 openingHourTest. addErrorsForPrimitive(primitive, errors);62 errors.addAll(mapCSSTagChecker. getErrorsForPrimitive(primitive, ValidatorPrefHelper.PREF_OTHER.get()));48 openingHourTest.checkTaggingPresetHandler(handler, errors); 49 errors.addAll(mapCSSTagChecker.checkTaggingPresetHandler(handler, ValidatorPrefHelper.PREF_OTHER.get())); 63 50 64 51 boolean visible = !errors.isEmpty(); 65 52 String toolTipText = "<html>" + Utils.joinAsHtmlUnorderedList(Utils.transform(errors, e -> … … 69 56 validationLabel.setToolTipText(toolTipText); 70 57 }); 71 58 } catch (Exception e) { 72 Logging.warn("Failed to validate {0}", primitive);59 Logging.warn("Failed to validate {0}", handler.getPrimitives().iterator().next()); 73 60 Logging.warn(e); 74 } finally {75 primitive.getDataSet().clear();76 61 } 77 62 } 78 79 static OsmPrimitive applyChangedTags(OsmPrimitive original, List<Tag> changedTags) {80 DataSet ds = new DataSet();81 Collection<OsmPrimitive> primitives = FilterModel.getAffectedPrimitives(singleton(original));82 OsmPrimitive primitive = ds.clonePrimitives(83 new SubclassFilteredCollection<>(primitives, INode.class::isInstance),84 new SubclassFilteredCollection<>(primitives, IWay.class::isInstance),85 new SubclassFilteredCollection<>(primitives, IRelation.class::isInstance))86 .get(original);87 Command command = TaggingPreset.createCommand(singleton(primitive), changedTags);88 if (command != null) {89 command.executeCommand();90 }91 return primitive;92 }93 63 } -
src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresets.java
3 3 4 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 5 6 import java. util.ArrayList;6 import java.io.IOException; 7 7 import java.util.Collection; 8 8 import java.util.Collections; 9 9 import java.util.HashMap; 10 10 import java.util.HashSet; 11 import java.util.LinkedHashMap; 12 import java.util.LinkedList; 13 import java.util.List; 11 14 import java.util.Map; 12 15 import java.util.Set; 16 import java.util.function.Predicate; 13 17 14 18 import javax.swing.JMenu; 15 import javax.swing.JMenuItem;16 import javax.swing.JSeparator;17 19 18 20 import org.openstreetmap.josm.actions.PreferencesAction; 19 21 import org.openstreetmap.josm.data.osm.IPrimitive; … … 22 24 import org.openstreetmap.josm.data.preferences.ListProperty; 23 25 import org.openstreetmap.josm.gui.MainApplication; 24 26 import org.openstreetmap.josm.gui.MainMenu; 25 import org.openstreetmap.josm.gui.MenuScroller;26 27 import org.openstreetmap.josm.gui.preferences.ToolbarPreferences; 27 28 import org.openstreetmap.josm.gui.preferences.map.TaggingPresetPreference; 28 import org.openstreetmap.josm.gui.tagging.presets.items.CheckGroup;29 import org.openstreetmap.josm.gui.tagging.presets.items.KeyedItem;30 import org.openstreetmap.josm.gui.tagging.presets.items.Roles;31 import org.openstreetmap.josm.gui.tagging.presets.items.Roles.Role;32 import org.openstreetmap.josm.tools.Logging;33 29 import org.openstreetmap.josm.tools.MultiMap; 34 30 import org.openstreetmap.josm.tools.SubclassFilteredCollection; 31 import org.xml.sax.SAXException; 35 32 36 33 /** 37 34 * Class holding Tagging Presets and allowing to manage them. … … 39 36 */ 40 37 public final class TaggingPresets { 41 38 42 /** The collection of tagging presets */43 private static final Collection< TaggingPreset> taggingPresets = new ArrayList<>();39 /** The root elements of all XML files */ 40 private static final Collection<Root> rootElements = new LinkedList<>(); 44 41 45 /** cache for key/value pairs found in the preset */ 42 /** caches the tags in all presets key->values */ 43 private static final Map<String, TaggingPreset> PRESET_CACHE = new LinkedHashMap<>(); 44 /** caches the tags in all presets key->values */ 46 45 private static final MultiMap<String, String> PRESET_TAG_CACHE = new MultiMap<>(); 47 /** cache for roles found in the preset*/48 private static final Set< String> PRESET_ROLE_CACHE = new HashSet<>();46 /** caches the roles in all presets key -> role */ 47 private static final Set<Role> PRESET_ROLE_CACHE = new HashSet<>(); 49 48 50 49 /** The collection of listeners */ 51 private static final Collection<TaggingPresetListener> listeners = new ArrayList<>(); 50 private static final Collection<TaggingPresetListener> listeners = new LinkedList<>(); 51 /** Custom icon sources */ 52 public static final ListProperty ICON_SOURCES = new ListProperty("taggingpreset.icon.sources", null); 52 53 /** 53 * Sort presets menu alphabetically 54 * Defines whether the validator should be active in the preset dialog 55 * @see TaggingPresetValidation 54 56 */ 55 public static final BooleanProperty SORT_MENU = new BooleanProperty("taggingpreset.sortvalues", true); 56 /** 57 * Custom icon sources 58 */ 59 public static final ListProperty ICON_SOURCES = new ListProperty("taggingpreset.icon.sources", null); 60 private static final IntegerProperty MIN_ELEMENTS_FOR_SCROLLER = new IntegerProperty("taggingpreset.min-elements-for-scroller", 15); 57 public static final BooleanProperty USE_VALIDATOR = new BooleanProperty("taggingpreset.validator", false); 58 /** Sort preset values alphabetically in combos and menus */ 59 public static final BooleanProperty SORT_VALUES = new BooleanProperty("taggingpreset.sortvalues", true); 60 /** No. of items a menu must have before using a scroller */ 61 public static final IntegerProperty MIN_ELEMENTS_FOR_SCROLLER = new IntegerProperty("taggingpreset.min-elements-for-scroller", 15); 61 62 62 63 private TaggingPresets() { 63 64 // Hide constructor for utility classes … … 64 65 } 65 66 66 67 /** 67 * Initializes tagging presets from preferences.68 * Standard initialization during app startup. Obeys users prefs. 68 69 */ 70 public static void initialize() { 71 readFromPreferences(); 72 initializeMenus(); 73 } 74 75 /** 76 * Initializes tagging presets from user preferences. 77 */ 69 78 public static void readFromPreferences() { 70 taggingPresets.clear(); 71 taggingPresets.addAll(TaggingPresetReader.readFromPreferences(false, false)); 72 cachePresets(taggingPresets); 79 TaggingPresetReader.readFromPreferences(false, false).forEach(TaggingPresets::addRoot); 80 listeners.forEach(TaggingPresetListener::taggingPresetsModified); 73 81 } 74 82 75 83 /** 76 * Initialize the tagging presets (load and may display error) 84 * Deterministic initialization during test. 85 * 86 * @param presetsUrl the url of the presets file to load for testing 87 * @throws SAXException in case of parser errors 88 * @throws IOException if the url was not found 77 89 */ 78 public static void initialize() { 90 public static void testInitialize(String presetsUrl) throws SAXException, IOException { 91 addRoot(TaggingPresetReader.read(presetsUrl, false)); 92 listeners.forEach(TaggingPresetListener::taggingPresetsModified); 93 } 94 95 /** 96 * Add a new root element 97 * @param root the new root element 98 */ 99 public static void addRoot(Root root) { 100 Map<String, Chunk> chunks = new HashMap<>(); 101 root.fixup(chunks, root); 102 rootElements.add(root); 103 cachePresets(root); 104 } 105 106 /** 107 * Initializes the preset menu and toolbar. 108 * <p> 109 * Builds the tagging presets menu and registers all preset actions with the application 110 * toolbar. 111 */ 112 public static void initializeMenus() { 79 113 MainMenu mainMenu = MainApplication.getMenu(); 80 114 JMenu presetsMenu = mainMenu.presetsMenu; 81 115 if (presetsMenu.getComponentCount() == 0) { … … 86 120 presetsMenu.addSeparator(); 87 121 } 88 122 89 readFromPreferences(); 90 for (TaggingPreset tp: taggingPresets) { 91 if (!(tp instanceof TaggingPresetSeparator)) { 92 MainApplication.getToolbar().register(tp); 93 MainApplication.getLayerManager().addActiveLayerChangeListener(tp); 94 } 95 } 96 if (taggingPresets.isEmpty()) { 123 // register all presets with the application toolbar 124 ToolbarPreferences toolBar = MainApplication.getToolbar(); 125 getAllItems(TaggingPresetBase.class).forEach(tp -> toolBar.register(tp.getAction())); 126 toolBar.refreshToolbarControl(); 127 128 // add presets and groups to the presets menu 129 if (rootElements.isEmpty()) { 97 130 presetsMenu.setVisible(false); 98 131 } else { 99 Map<TaggingPresetMenu, JMenu> submenus = new HashMap<>(); 100 for (final TaggingPreset p : taggingPresets) { 101 JMenu m = p.group != null ? submenus.get(p.group) : presetsMenu; 102 if (m == null && p.group != null) { 103 Logging.error("No tagging preset submenu for " + p.group); 104 } else if (m == null) { 105 Logging.error("No tagging preset menu. Tagging preset " + p + " won't be available there"); 106 } else if (p instanceof TaggingPresetSeparator) { 107 m.add(new JSeparator()); 108 } else if (p instanceof TaggingPresetMenu) { 109 JMenu submenu = new JMenu(p); 110 submenu.setText(p.getLocaleName()); 111 ((TaggingPresetMenu) p).menu = submenu; 112 submenus.put((TaggingPresetMenu) p, submenu); 113 m.add(submenu); 114 } else { 115 JMenuItem mi = new JMenuItem(p); 116 mi.setText(p.getLocaleName()); 117 m.add(mi); 118 } 132 rootElements.forEach(e -> e.addToMenu(presetsMenu)); 133 if (TaggingPresets.SORT_VALUES.get()) { 134 TaggingPresetUtils.sortMenu(presetsMenu); 119 135 } 120 for (JMenu submenu : submenus.values()) {121 if (submenu.getItemCount() >= MIN_ELEMENTS_FOR_SCROLLER.get()) {122 MenuScroller.setScrollerFor(submenu);123 }124 }125 136 } 126 if (SORT_MENU.get()) {127 TaggingPresetMenu.sortMenu(presetsMenu);128 }129 listeners.forEach(TaggingPresetListener::taggingPresetsModified);130 137 } 131 138 132 139 // Cannot implement Destroyable since this is static … … 137 144 * @since 15582 138 145 */ 139 146 public static void destroy() { 147 unInitializeMenus(); 148 cleanUp(); 149 } 150 151 static void unInitializeMenus() { 140 152 ToolbarPreferences toolBar = MainApplication.getToolbar(); 141 for (TaggingPreset tp: taggingPresets) { 142 toolBar.unregister(tp); 143 if (!(tp instanceof TaggingPresetSeparator)) { 144 MainApplication.getLayerManager().removeActiveLayerChangeListener(tp); 145 } 146 } 147 taggingPresets.clear(); 153 if (toolBar != null) 154 getAllItems(TaggingPresetBase.class).forEach(tp -> toolBar.unregister(tp.getAction())); 155 MainMenu menu = MainApplication.getMenu(); 156 if (menu != null) 157 menu.presetsMenu.removeAll(); 158 } 159 160 static void cleanUp() { 161 PRESET_CACHE.clear(); 148 162 PRESET_TAG_CACHE.clear(); 149 163 PRESET_ROLE_CACHE.clear(); 150 MainApplication.getMenu().presetsMenu.removeAll(); 164 rootElements.forEach(Item::destroy); 165 rootElements.clear(); 151 166 } 152 167 153 168 /** 154 * Initialize the cache for presets. This is done only once. 155 * @param presets Tagging presets to cache 169 * Initialize the cache with presets. 170 * 171 * @param root the root of the xml file 156 172 */ 157 public static void cachePresets(Collection<TaggingPreset> presets) { 158 for (final TaggingPreset p : presets) { 159 for (TaggingPresetItem item : p.data) { 160 cachePresetItem(p, item); 173 static void cachePresets(Root root) { 174 root.getAllItems(TaggingPreset.class, false).forEach(tp -> PRESET_CACHE.put(tp.fullName, tp)); 175 PRESET_ROLE_CACHE.addAll(root.getAllItems(Role.class, false)); 176 root.getAllItems(KeyedItem.class, false).forEach(item -> { 177 if (item.key != null && item.getValues() != null) { 178 PRESET_TAG_CACHE.putAll(item.key, item.getValues()); 161 179 } 162 } 180 }); 163 181 } 164 182 165 private static void cachePresetItem(TaggingPreset p, TaggingPresetItem item) { 166 if (item instanceof KeyedItem) { 167 KeyedItem ki = (KeyedItem) item; 168 if (ki.key != null && ki.getValues() != null) { 169 PRESET_TAG_CACHE.putAll(ki.key, ki.getValues()); 170 } 171 } else if (item instanceof Roles) { 172 Roles r = (Roles) item; 173 for (Role i : r.roles) { 174 if (i.key != null) { 175 PRESET_ROLE_CACHE.add(i.key); 176 } 177 } 178 } else if (item instanceof CheckGroup) { 179 for (KeyedItem check : ((CheckGroup) item).checks) { 180 cachePresetItem(p, check); 181 } 182 } 183 /** 184 * Returns all items that satisfy a given predicate. 185 * @param p the predicate all items must satisfy 186 * @return the items that satisfy the predicate 187 */ 188 public static List<Item> getAllItems(Predicate<Item> p) { 189 List<Item> list = new LinkedList<>(); 190 rootElements.forEach(r -> r.addToItemList(list, p, false)); 191 return list; 183 192 } 184 193 185 194 /** 186 * Replies a new collection containing all tagging presets. 187 * @return a new collection containing all tagging presets. Empty if presets are not initialized (never null) 195 * Returns all items of a type. 196 * @param <E> the type 197 * @param type the type 198 * @return the list of all items 188 199 */ 200 public static <E> List<E> getAllItems(Class<E> type) { 201 List<E> list = new LinkedList<>(); 202 rootElements.forEach(r -> r.addToItemList(list, type, false)); 203 return list; 204 } 205 206 /** 207 * Returns all tagging presets. 208 * @return all tagging presets 209 */ 189 210 public static Collection<TaggingPreset> getTaggingPresets() { 190 return Collections.unmodifiableCollection( taggingPresets);211 return Collections.unmodifiableCollection(PRESET_CACHE.values()); 191 212 } 192 213 193 214 /** 194 * Re plies a set of all roles in the tagging presets.195 * @return a set of all roles in the tagging presets.215 * Returns every role found in any preset. 216 * @return the roles 196 217 */ 197 public static Set<String> getPresetRoles() {198 return Collections.unmodifiable Set(PRESET_ROLE_CACHE);218 public static Collection<Role> getPresetRoles() { 219 return Collections.unmodifiableCollection(PRESET_ROLE_CACHE); 199 220 } 200 221 201 222 /** 202 * Re plies a set of all keys in thetagging presets.203 * @return a set of all keys in the tagging presets.223 * Returns all keys seen in all tagging presets. 224 * @return the set of all keys 204 225 */ 205 226 public static Set<String> getPresetKeys() { 206 227 return Collections.unmodifiableSet(PRESET_TAG_CACHE.keySet()); … … 207 228 } 208 229 209 230 /** 210 * Return set of values for a key in the tagging presets231 * Returns all values seen in all presets for this key. 211 232 * @param key the key 212 * @return set of values for a key in the tagging presets233 * @return the set of all values 213 234 */ 214 235 public static Set<String> getPresetValues(String key) { 215 236 Set<String> values = PRESET_TAG_CACHE.get(key); … … 232 253 * Replies a new collection of all presets matching the parameters. 233 254 * 234 255 * @param t the preset types to include 235 * @param tags the tags to perform matching on, see {@link TaggingPresetItem#matches(Map)}256 * @param tags the tags to perform matching on, see {@link Item#matches(Map)} 236 257 * @param onlyShowable whether only {@link TaggingPreset#isShowable() showable} presets should be returned 237 258 * @return a new collection of all presets matching the parameters. 238 259 * @see TaggingPreset#matches(Collection, Map, boolean) … … 256 277 } 257 278 258 279 /** 259 * Adds a list of tagging presets to the current list.260 * @param presets The tagging presets to add261 */262 public static void addTaggingPresets(Collection<TaggingPreset> presets) {263 if (presets != null && taggingPresets.addAll(presets)) {264 listeners.forEach(TaggingPresetListener::taggingPresetsModified);265 }266 }267 268 /**269 280 * Adds a tagging preset listener. 270 281 * @param listener The listener to add 271 282 */ -
src/org/openstreetmap/josm/gui/tagging/presets/Text.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 6 import java.util.ArrayList; 7 import java.awt.Color; 8 import java.awt.GridBagLayout; 9 import java.awt.Insets; 10 import java.text.NumberFormat; 11 import java.text.ParseException; 12 import java.util.Collection; 13 import java.util.Collections; 14 import java.util.List; 15 import java.util.Map; 16 17 import javax.swing.AbstractButton; 18 import javax.swing.BorderFactory; 19 import javax.swing.ButtonGroup; 20 import javax.swing.JButton; 21 import javax.swing.JComponent; 22 import javax.swing.JLabel; 23 import javax.swing.JPanel; 24 import javax.swing.JToggleButton; 25 26 import org.openstreetmap.josm.data.osm.Tag; 27 import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem; 28 import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxEditor; 29 import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel; 30 import org.openstreetmap.josm.gui.tagging.ac.AutoCompTextField; 31 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager; 32 import org.openstreetmap.josm.gui.util.DocumentAdapter; 33 import org.openstreetmap.josm.gui.widgets.JosmComboBox; 34 import org.openstreetmap.josm.gui.widgets.OrientationAction; 35 import org.openstreetmap.josm.tools.GBC; 36 import org.openstreetmap.josm.tools.Logging; 37 import org.openstreetmap.josm.tools.Utils; 38 import org.openstreetmap.josm.tools.template_engine.TemplateEntry; 39 40 /** 41 * Text field type. 42 */ 43 final class Text extends KeyedItem { 44 /** 45 * May contain a comma separated list of integer increments or decrements, e.g. "-2,-1,+1,+2". 46 * A button will be shown next to the text field for each value, allowing the user to select auto-increment with the given stepping. 47 * Auto-increment only happens if the user selects it. There is also a button to deselect auto-increment. 48 * Default is no auto-increment. Mutually exclusive with {@link KeyedItem#useLastAsDefault}. 49 */ 50 private final String autoIncrement; 51 /** A comma separated list of alternative keys to use for autocompletion. */ 52 private final String alternativeAutocompleteKeys; 53 /** A value template */ 54 private final TemplateEntry valueTemplate; 55 /** The default value for the item. If not specified, the current value of the key is chosen as 56 * default (if applicable). Defaults to "". */ 57 private final String default_; 58 59 /** 60 * Private constructor. Use {@link #fromXML} instead. 61 * @param attributes the XML attributes 62 * @throws IllegalArgumentException on illegal attributes 63 */ 64 private Text(Map<String, String> attributes) throws IllegalArgumentException { 65 super(attributes); 66 valueTemplate = TaggingPresetUtils.parseTemplate(attributes.get("value_template")); 67 autoIncrement = attributes.get("auto_increment"); 68 alternativeAutocompleteKeys = attributes.get("alternative_autocomplete_keys"); 69 default_ = attributes.get("default"); 70 } 71 72 /** 73 * Create a {@code Text} from an XML element's attributes. 74 * @param attributes the XML attributes 75 * @return the {@code Text} 76 * @throws IllegalArgumentException on invalid attributes 77 */ 78 public static Text fromXML(Map<String, String> attributes) throws IllegalArgumentException { 79 return new Text(attributes); 80 } 81 82 @Override 83 boolean addToPanel(JPanel p, TaggingPresetInstance support) { 84 AutoCompComboBoxModel<AutoCompletionItem> model = new AutoCompComboBoxModel<>(); 85 List<String> keys = new ArrayList<>(); 86 keys.add(key); 87 if (alternativeAutocompleteKeys != null) { 88 for (String k : alternativeAutocompleteKeys.split(",", -1)) { 89 keys.add(k); 90 } 91 } 92 TaggingPresetUtils.getAllForKeys(keys).forEach(model::addElement); 93 94 AutoCompTextField<AutoCompletionItem> textField; 95 AutoCompComboBoxEditor<AutoCompletionItem> editor = null; 96 97 // find out if our key is already used in the selection. 98 Usage usage = Usage.determineTextUsage(support.getSelected(), key); 99 100 JComponent component; 101 if (usage.unused() || usage.hasUniqueValue()) { 102 textField = new AutoCompTextField<>(); 103 component = textField; 104 } else { 105 // The selected primitives have different values for this key. <b>Note:</b> this 106 // cannot be an AutoCompComboBox because the values in the dropdown are different from 107 // those we autocomplete on. 108 JosmComboBox<String> comboBox = new JosmComboBox<>(); 109 comboBox.getModel().addAllElements(usage.map.keySet()); 110 comboBox.setEditable(true); 111 editor = new AutoCompComboBoxEditor<>(); 112 comboBox.setEditor(editor); 113 comboBox.getEditor().setItem(DIFFERENT_I18N); 114 textField = editor.getEditorComponent(); 115 component = comboBox; 116 } 117 textField.setModel(model); 118 119 if (length > 0) { 120 textField.setMaxTextLength(length); 121 } 122 if (Item.DISPLAY_KEYS_AS_HINT.get()) { 123 textField.setHint(key); 124 } 125 126 Instance instance = new Instance(textField, support); 127 support.putInstance(this, instance); 128 instance.setInitialValue(usage, support); 129 130 instance.setupListeners(textField, support); 131 132 // if there's an auto_increment setting, then wrap the text field 133 // into a panel, appending a number of buttons. 134 // auto_increment has a format like -2,-1,1,2 135 // the text box being the first component in the panel is relied 136 // on in a rather ugly fashion further down. 137 if (autoIncrement != null) { 138 int autoIncrementSelected = getAutoIncrement(support); 139 ButtonGroup bg = new ButtonGroup(); 140 JPanel pnl = new JPanel(new GridBagLayout()); 141 pnl.add(component, GBC.std().fill(GBC.HORIZONTAL)); 142 143 // first, one button for each auto_increment value 144 for (final String ai : autoIncrement.split(",", -1)) { 145 JToggleButton aibutton = new JToggleButton(ai); 146 aibutton.setToolTipText(tr("Select auto-increment of {0} for this field", ai)); 147 aibutton.setMargin(new Insets(0, 0, 0, 0)); 148 aibutton.setFocusable(false); 149 saveHorizontalSpace(aibutton); 150 bg.add(aibutton); 151 try { 152 // TODO there must be a better way to parse a number like "+3" than this. 153 final int buttonvalue = NumberFormat.getIntegerInstance().parse(ai.replace("+", "")).intValue(); 154 if (autoIncrementSelected == buttonvalue) aibutton.setSelected(true); 155 aibutton.addActionListener(e -> setAutoIncrement(support, buttonvalue)); 156 pnl.add(aibutton, GBC.std()); 157 } catch (ParseException ex) { 158 Logging.error("Cannot parse auto-increment value of '" + ai + "' into an integer"); 159 } 160 } 161 162 // an invisible toggle button for "release" of the button group 163 final JToggleButton clearbutton = new JToggleButton("X"); 164 clearbutton.setVisible(false); 165 clearbutton.setFocusable(false); 166 bg.add(clearbutton); 167 // and its visible counterpart. - this mechanism allows us to 168 // have *no* button selected after the X is clicked, instead 169 // of the X remaining selected 170 JButton releasebutton = new JButton("X"); 171 releasebutton.setToolTipText(tr("Cancel auto-increment for this field")); 172 releasebutton.setMargin(new Insets(0, 0, 0, 0)); 173 releasebutton.setFocusable(false); 174 releasebutton.addActionListener(e -> { 175 setAutoIncrement(support, 0); 176 clearbutton.setSelected(true); 177 }); 178 saveHorizontalSpace(releasebutton); 179 pnl.add(releasebutton, GBC.eol()); 180 component = pnl; 181 } 182 183 final JLabel label = new JLabel(tr("{0}:", localeText)); 184 addIcon(label); 185 label.setToolTipText(getKeyTooltipText()); 186 label.setComponentPopupMenu(getPopupMenu()); 187 label.setLabelFor(component); 188 p.add(label, GBC.std().insets(0, 0, 10, 0)); 189 p.add(component, GBC.eol().fill(GBC.HORIZONTAL)); 190 label.applyComponentOrientation(support.getDefaultComponentOrientation()); 191 component.setToolTipText(getKeyTooltipText()); 192 component.applyComponentOrientation(OrientationAction.getNamelikeOrientation(key)); 193 194 return true; 195 } 196 197 private int getAutoIncrement(TaggingPresetInstance support) { 198 return (Integer) support.getPresetProperties().getOrDefault("autoincrement." + key, 0); 199 } 200 201 private void setAutoIncrement(TaggingPresetInstance support, int i) { 202 support.getPresetProperties().put("autoincrement." + key, i); 203 } 204 205 private static void saveHorizontalSpace(AbstractButton button) { 206 Insets insets = button.getBorder().getBorderInsets(button); 207 // Ensure the current look&feel does not waste horizontal space (as seen in Nimbus & Aqua) 208 if (insets != null && insets.left+insets.right > insets.top+insets.bottom) { 209 int min = Math.min(insets.top, insets.bottom); 210 button.setBorder(BorderFactory.createEmptyBorder(insets.top, min, insets.bottom, min)); 211 } 212 } 213 214 @Override 215 MatchType getDefaultMatch() { 216 return MatchType.NONE; 217 } 218 219 @Override 220 public Collection<String> getValues() { 221 if (Utils.isEmpty(default_)) 222 return Collections.emptyList(); 223 return Collections.singleton(default_); 224 } 225 226 class Instance extends Item.Instance { 227 private AutoCompTextField<AutoCompletionItem> textField; 228 private String originalValue; 229 private Integer autoIncrementSelected; 230 231 Instance(AutoCompTextField<AutoCompletionItem> textField, TaggingPresetInstance support) { 232 this.textField = textField; 233 this.autoIncrementSelected = (Integer) support.getPresetProperties().getOrDefault("autoincrement." + key, 0); 234 } 235 236 @Override 237 public void addCommands(List<Tag> changedTags) { 238 // return if unchanged 239 String v = textField.getText(); 240 if (v == null) { 241 Logging.error("No 'last value' support for component " + textField); 242 return; 243 } 244 245 v = Utils.removeWhiteSpaces(v); 246 247 if (isUseLastAsDefault() || autoIncrement != null) { 248 LAST_VALUES.put(key, v); 249 } 250 if (v.equals(originalValue) || (originalValue == null && v.isEmpty())) 251 return; 252 253 changedTags.add(new Tag(key, v)); 254 AutoCompletionManager.rememberUserInput(key, v, true); 255 } 256 257 private void setInitialValue(Usage usage, TaggingPresetInstance support) { 258 if (usage.unused()) { 259 if (autoIncrementSelected != 0 && autoIncrement != null) { 260 try { 261 textField.setText(Integer.toString(Integer.parseInt( 262 LAST_VALUES.get(key)) + autoIncrementSelected)); 263 } catch (NumberFormatException ex) { 264 // Ignore - cannot auto-increment if last was non-numeric 265 Logging.trace(ex); 266 } 267 } else if (!usage.hadKeys() || PROP_FILL_DEFAULT.get() || isForceUseLastAsDefault()) { 268 // selected osm primitives are untagged or filling default values feature is enabled 269 if (!support.isPresetInitiallyMatches() && isUseLastAsDefault() && LAST_VALUES.containsKey(key)) { 270 textField.setText(LAST_VALUES.get(key)); 271 } else { 272 textField.setText(default_); 273 } 274 } else { 275 // selected osm primitives are tagged and filling default values feature is disabled 276 textField.setText(""); 277 } 278 originalValue = null; 279 } else if (usage.hasUniqueValue()) { 280 // all objects use the same value 281 textField.setText(usage.getFirst()); 282 originalValue = usage.getFirst(); 283 } else { 284 originalValue = DIFFERENT_I18N; 285 } 286 } 287 288 private void setupListeners(AutoCompTextField<AutoCompletionItem> textField, TaggingPresetInstance support) { 289 // value_templates don't work well with multiple selected items because, 290 // as the command queue is currently implemented, we can only save 291 // the same value to all selected primitives, which is probably not 292 // what you want. 293 if (valueTemplate == null || support.getSelected().size() > 1) { // only fire on normal fields 294 textField.getDocument().addDocumentListener(DocumentAdapter.create(ignore -> 295 support.fireItemValueModified(this, key, textField.getText()))); 296 } else { // only listen on calculated fields 297 support.addListener((source, key, newValue) -> { 298 String valueTemplateText = valueTemplate.getText(support); 299 Logging.trace("Evaluating value_template {0} for key {1} from {2} with new value {3} => {4}", 300 valueTemplate, key, source, newValue, valueTemplateText); 301 textField.setText(valueTemplateText); 302 if (originalValue != null && !originalValue.equals(valueTemplateText)) { 303 textField.setForeground(Color.RED); 304 } else { 305 textField.setForeground(Color.BLUE); 306 } 307 }); 308 } 309 } 310 } 311 } -
src/org/openstreetmap/josm/gui/tagging/presets/TextItem.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import java.util.Map; 5 6 import javax.swing.ImageIcon; 7 import javax.swing.JLabel; 8 import javax.swing.SwingConstants; 9 10 /** 11 * A tagging preset item displaying a localizable text. 12 * @since 6190 13 */ 14 abstract class TextItem extends Item { 15 16 /** The text to display */ 17 final String text; 18 /** The context used for translating {@link #text} */ 19 final String textContext; 20 /** The localized version of {@link #text} */ 21 final String localeText; 22 /** The location of icon file to display */ 23 final String icon; 24 /** The size of displayed icon. If not set, default is 16px */ 25 final int iconSize; 26 27 /** 28 * Constructor. 29 * @param attributes the XML attributes 30 * @throws IllegalArgumentException on illegal attributes 31 */ 32 TextItem(Map<String, String> attributes) throws IllegalArgumentException { 33 super(attributes); 34 String v = attributes.get("text"); 35 text = v != null ? v : getDefaultText(); 36 textContext = attributes.get("text_context"); 37 icon = attributes.get("icon"); 38 iconSize = Integer.parseInt(attributes.getOrDefault("icon_size", "16")); 39 localeText = TaggingPresetUtils.buildLocaleString(attributes.get("locale_text"), text, textContext); 40 } 41 42 /** 43 * Returns the text 44 * @return teh text 45 */ 46 String getText() { 47 return text; 48 } 49 50 String getDefaultText() { 51 return null; 52 } 53 54 String fieldsToString() { 55 return (text != null ? "text=" + text + ", " : "") 56 + (textContext != null ? "text_context=" + textContext + ", " : "") 57 + (localeText != null ? "locale_text=" + localeText : ""); 58 } 59 60 /** 61 * Defines the label icon from this entry's icon 62 * @param label the component 63 * @since 17605 64 */ 65 void addIcon(JLabel label) { 66 label.setIcon(getIcon()); 67 label.setHorizontalAlignment(SwingConstants.LEADING); 68 } 69 70 /** 71 * Returns the entry icon, if any. 72 * @return the entry icon, or {@code null} 73 * @since 17605 74 */ 75 ImageIcon getIcon() { 76 return icon == null ? null : TaggingPresetUtils.loadImageIcon(icon, TaggingPresetReader.getZipIcons(), iconSize); 77 } 78 79 @Override 80 public String toString() { 81 return getClass().getSimpleName() + " [" + fieldsToString() + ']'; 82 } 83 } -
src/org/openstreetmap/josm/gui/tagging/presets/Usage.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import java.util.Collection; 5 import java.util.NoSuchElementException; 6 import java.util.SortedMap; 7 import java.util.TreeMap; 8 9 import org.openstreetmap.josm.data.osm.OsmPrimitive; 10 import org.openstreetmap.josm.data.osm.OsmUtils; 11 12 /** 13 * Usage information on a key 14 * 15 * TODO merge with {@link org.openstreetmap.josm.data.osm.TagCollection} 16 */ 17 public class Usage { 18 /** Usage count for all values used for this key */ 19 public final SortedMap<String, Integer> map = new TreeMap<>(); 20 private boolean hadKeys; 21 private boolean hadEmpty; 22 private int selectedCount; 23 24 /** 25 * Computes the tag usage for the given key from the given primitives 26 * 27 * @param sel the primitives 28 * @param key the key 29 * @return the tag usage 30 */ 31 public static Usage determineTextUsage(Collection<OsmPrimitive> sel, String key) { 32 Usage returnValue = new Usage(); 33 returnValue.selectedCount = sel.size(); 34 for (OsmPrimitive s : sel) { 35 String v = s.get(key); 36 if (v != null) { 37 returnValue.map.merge(v, 1, Integer::sum); 38 } else { 39 returnValue.hadEmpty = true; 40 } 41 if (s.hasKeys()) { 42 returnValue.hadKeys = true; 43 } 44 } 45 return returnValue; 46 } 47 48 /** 49 * Computes the tag usage for the given key if the key has a boolean value 50 * 51 * @param sel the primitives 52 * @param key the key 53 * @return the tag usage 54 */ 55 public static Usage determineBooleanUsage(Collection<OsmPrimitive> sel, String key) { 56 Usage returnValue = new Usage(); 57 returnValue.selectedCount = sel.size(); 58 for (OsmPrimitive s : sel) { 59 String booleanValue = OsmUtils.getNamedOsmBoolean(s.get(key)); 60 if (booleanValue != null) { 61 returnValue.map.merge(booleanValue, 1, Integer::sum); 62 } 63 } 64 return returnValue; 65 } 66 67 /** 68 * Check if there is exactly one value for this key. 69 * @return <code>true</code> if there was exactly one value. 70 */ 71 public boolean hasUniqueValue() { 72 return map.size() == 1 && !hadEmpty; 73 } 74 75 /** 76 * Check if this key was not used in any primitive 77 * @return <code>true</code> if it was unused. 78 */ 79 public boolean unused() { 80 return map.isEmpty(); 81 } 82 83 /** 84 * Get the first value available. 85 * @return The first value 86 * @throws NoSuchElementException if there is no such value. 87 */ 88 public String getFirst() { 89 return map.firstKey(); 90 } 91 92 /** 93 * Check if we encountered any primitive that had any keys 94 * @return <code>true</code> if any of the primitives had any tags. 95 */ 96 public boolean hadKeys() { 97 return hadKeys; 98 } 99 100 /** 101 * Returns the number of primitives selected. 102 * @return the number of primitives selected. 103 */ 104 public int getSelectedCount() { 105 return selectedCount; 106 } 107 108 /** 109 * Splits multiple values and adds their usage counts as single value. 110 * <p> 111 * A value of {@code regional;pizza} will increment the count of {@code regional} and of 112 * {@code pizza}. 113 */ 114 public void splitValues() { 115 SortedMap<String, Integer> copy = new TreeMap<>(map); 116 copy.forEach((value, count) -> { 117 map.remove(value); 118 for (String v : value.split(";", -1)) { 119 map.merge(v, count, Integer::sum); 120 } 121 }); 122 } 123 } -
src/org/openstreetmap/josm/gui/tagging/presets/items/Check.java
1 // License: GPL. For details, see LICENSE file.2 package org.openstreetmap.josm.gui.tagging.presets.items;3 4 import java.util.ArrayList;5 import java.util.Arrays;6 import java.util.Collection;7 import java.util.List;8 9 import javax.swing.JPanel;10 11 import org.openstreetmap.josm.data.osm.OsmPrimitive;12 import org.openstreetmap.josm.data.osm.OsmUtils;13 import org.openstreetmap.josm.data.osm.Tag;14 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;15 import org.openstreetmap.josm.gui.widgets.IconTextCheckBox;16 import org.openstreetmap.josm.gui.widgets.QuadStateCheckBox;17 import org.openstreetmap.josm.tools.GBC;18 19 /**20 * Checkbox type.21 */22 public class Check extends KeyedItem {23 24 /** the value to set when checked (default is "yes") */25 public String value_on = OsmUtils.TRUE_VALUE; // NOSONAR26 /** the value to set when unchecked (default is "no") */27 public String value_off = OsmUtils.FALSE_VALUE; // NOSONAR28 /** whether the off value is disabled in the dialog, i.e., only unset or yes are provided */29 public boolean disable_off; // NOSONAR30 /** "on" or "off" or unset (default is unset) */31 public String default_; // only used for tagless objects // NOSONAR32 33 private QuadStateCheckBox check;34 private QuadStateCheckBox.State initialState;35 private Boolean def;36 37 @Override38 public boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support) {39 40 // find out if our key is already used in the selection.41 final Usage usage = determineBooleanUsage(support.getSelected(), key);42 final String oneValue = usage.map.isEmpty() ? null : usage.map.lastKey();43 def = "on".equals(default_) ? Boolean.TRUE : "off".equals(default_) ? Boolean.FALSE : null;44 45 initializeLocaleText(null);46 47 if (usage.map.size() < 2 && (oneValue == null || value_on.equals(oneValue) || value_off.equals(oneValue))) {48 if (def != null && !PROP_FILL_DEFAULT.get()) {49 // default is set and filling default values feature is disabled - check if all primitives are untagged50 for (OsmPrimitive s : support.getSelected()) {51 if (s.hasKeys()) {52 def = null;53 }54 }55 }56 57 // all selected objects share the same value which is either true or false or unset,58 // we can display a standard check box.59 initialState = value_on.equals(oneValue) || Boolean.TRUE.equals(def)60 ? QuadStateCheckBox.State.SELECTED61 : value_off.equals(oneValue) || Boolean.FALSE.equals(def)62 ? QuadStateCheckBox.State.NOT_SELECTED63 : QuadStateCheckBox.State.UNSET;64 65 } else {66 def = null;67 // the objects have different values, or one or more objects have something68 // else than true/false. we display a quad-state check box69 // in "partial" state.70 initialState = QuadStateCheckBox.State.PARTIAL;71 }72 73 final List<QuadStateCheckBox.State> allowedStates = new ArrayList<>(4);74 if (QuadStateCheckBox.State.PARTIAL == initialState)75 allowedStates.add(QuadStateCheckBox.State.PARTIAL);76 allowedStates.add(QuadStateCheckBox.State.SELECTED);77 if (!disable_off || value_off.equals(oneValue))78 allowedStates.add(QuadStateCheckBox.State.NOT_SELECTED);79 allowedStates.add(QuadStateCheckBox.State.UNSET);80 check = new QuadStateCheckBox(icon == null ? locale_text : null, initialState,81 allowedStates.toArray(new QuadStateCheckBox.State[0]));82 check.setPropertyText(key);83 check.setState(check.getState()); // to update the tooltip text84 check.setComponentPopupMenu(getPopupMenu());85 86 if (icon != null) {87 JPanel checkPanel = IconTextCheckBox.wrap(check, locale_text, getIcon());88 checkPanel.applyComponentOrientation(support.getDefaultComponentOrientation());89 p.add(checkPanel, GBC.eol()); // Do not fill, see #1510490 } else {91 check.applyComponentOrientation(support.getDefaultComponentOrientation());92 p.add(check, GBC.eol()); // Do not fill, see #1510493 }94 check.addChangeListener(l -> support.fireItemValueModified(this, key, getValue()));95 return true;96 }97 98 @Override99 public void addCommands(List<Tag> changedTags) {100 // if the user hasn't changed anything, don't create a command.101 if (def == null && check.getState() == initialState) return;102 103 // otherwise change things according to the selected value.104 changedTags.add(new Tag(key, getValue()));105 }106 107 protected String getValue() {108 return check.getState() == QuadStateCheckBox.State.SELECTED ? value_on :109 check.getState() == QuadStateCheckBox.State.NOT_SELECTED ? value_off :110 null;111 }112 113 @Override114 public MatchType getDefaultMatch() {115 return MatchType.NONE;116 }117 118 @Override119 public Collection<String> getValues() {120 return disable_off ? Arrays.asList(value_on) : Arrays.asList(value_on, value_off);121 }122 123 @Override124 public String toString() {125 return "Check [key=" + key + ", text=" + text + ", "126 + (locale_text != null ? "locale_text=" + locale_text + ", " : "")127 + (value_on != null ? "value_on=" + value_on + ", " : "")128 + (value_off != null ? "value_off=" + value_off + ", " : "")129 + "default_=" + default_ + ", "130 + (check != null ? "check=" + check + ", " : "")131 + (initialState != null ? "initialState=" + initialState132 + ", " : "") + "def=" + def + ']';133 }134 } -
src/org/openstreetmap/josm/gui/tagging/presets/items/CheckGroup.java
1 // License: GPL. For details, see LICENSE file.2 package org.openstreetmap.josm.gui.tagging.presets.items;3 4 import java.awt.GridLayout;5 import java.util.LinkedList;6 import java.util.List;7 import java.util.Map;8 9 import javax.swing.JLabel;10 import javax.swing.JPanel;11 12 import org.openstreetmap.josm.data.osm.Tag;13 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem;14 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;15 import org.openstreetmap.josm.tools.GBC;16 17 /**18 * A group of {@link Check}s.19 * @since 611420 */21 public class CheckGroup extends TaggingPresetItem {22 23 /**24 * Number of columns (positive integer)25 */26 public short columns = 1; // NOSONAR27 28 /**29 * List of checkboxes30 */31 public final List<Check> checks = new LinkedList<>();32 33 @Override34 public boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support) {35 int rows = (int) Math.ceil(checks.size() / ((double) columns));36 JPanel panel = new JPanel(new GridLayout(rows, columns));37 38 int i = 0;39 for (Check check : checks) {40 check.addToPanel(panel, support);41 i++;42 }43 for (; i < rows * columns; i++) {44 // fill remaining cells, see #2079245 panel.add(new JLabel());46 }47 48 panel.applyComponentOrientation(support.getDefaultComponentOrientation());49 p.add(panel, GBC.eol());50 return false;51 }52 53 @Override54 public void addCommands(List<Tag> changedTags) {55 for (Check check : checks) {56 check.addCommands(changedTags);57 }58 }59 60 @Override61 public Boolean matches(Map<String, String> tags) {62 for (Check check : checks) {63 if (Boolean.TRUE.equals(check.matches(tags))) {64 return Boolean.TRUE;65 }66 }67 return null;68 }69 70 @Override71 public String toString() {72 return "CheckGroup [columns=" + columns + ']';73 }74 } -
src/org/openstreetmap/josm/gui/tagging/presets/items/Combo.java
1 // License: GPL. For details, see LICENSE file.2 package org.openstreetmap.josm.gui.tagging.presets.items;3 4 import static org.openstreetmap.josm.tools.I18n.tr;5 6 import java.awt.Color;7 import java.awt.Cursor;8 import java.awt.Insets;9 import java.awt.event.ActionEvent;10 import java.awt.event.ActionListener;11 import java.awt.event.ComponentAdapter;12 import java.awt.event.ComponentEvent;13 import java.util.Arrays;14 import java.util.Comparator;15 16 import javax.swing.AbstractAction;17 import javax.swing.JButton;18 import javax.swing.JColorChooser;19 import javax.swing.JComponent;20 import javax.swing.JLabel;21 import javax.swing.JPanel;22 23 import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem;24 import org.openstreetmap.josm.data.tagging.ac.AutoCompletionPriority;25 import org.openstreetmap.josm.gui.MainApplication;26 import org.openstreetmap.josm.gui.mappaint.mapcss.CSSColors;27 import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxEditor;28 import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel;29 import org.openstreetmap.josm.gui.tagging.ac.AutoCompTextField;30 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem;31 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;32 import org.openstreetmap.josm.gui.widgets.JosmComboBox;33 import org.openstreetmap.josm.gui.widgets.OrientationAction;34 import org.openstreetmap.josm.tools.ColorHelper;35 import org.openstreetmap.josm.tools.GBC;36 37 /**38 * Combobox type.39 */40 public class Combo extends ComboMultiSelect {41 42 /**43 * Whether the combo box is editable, which means that the user can add other values as text.44 * Default is {@code true}. If {@code false} it is readonly, which means that the user can only select an item in the list.45 */46 public boolean editable = true; // NOSONAR47 /** The length of the combo box (number of characters allowed). */48 public int length; // NOSONAR49 50 protected JosmComboBox<PresetListEntry> combobox;51 protected AutoCompComboBoxModel<PresetListEntry> dropDownModel;52 protected AutoCompComboBoxModel<AutoCompletionItem> autoCompModel;53 54 class ComponentListener extends ComponentAdapter {55 @Override56 public void componentResized(ComponentEvent e) {57 // Make multi-line JLabels the correct size58 // Only needed if there is any short_description59 JComponent component = (JComponent) e.getSource();60 int width = component.getWidth();61 if (width == 0)62 width = 200;63 Insets insets = component.getInsets();64 width -= insets.left + insets.right + 10;65 ComboMultiSelectListCellRenderer renderer = (ComboMultiSelectListCellRenderer) combobox.getRenderer();66 renderer.setWidth(width);67 combobox.setRenderer(null); // needed to make prop change fire68 combobox.setRenderer(renderer);69 }70 }71 72 /**73 * Constructs a new {@code Combo}.74 */75 public Combo() {76 delimiter = ',';77 }78 79 private void addEntry(PresetListEntry entry) {80 if (!seenValues.containsKey(entry.value)) {81 dropDownModel.addElement(entry);82 seenValues.put(entry.value, entry);83 }84 }85 86 @Override87 protected boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support) {88 initializeLocaleText(null);89 usage = determineTextUsage(support.getSelected(), key);90 seenValues.clear();91 // get the standard values from the preset definition92 initListEntries();93 94 // init the model95 dropDownModel = new AutoCompComboBoxModel<>(Comparator.<PresetListEntry>naturalOrder());96 97 if (!usage.hasUniqueValue() && !usage.unused()) {98 addEntry(PresetListEntry.ENTRY_DIFFERENT);99 }100 presetListEntries.forEach(this::addEntry);101 if (default_ != null) {102 addEntry(new PresetListEntry(default_, this));103 }104 addEntry(PresetListEntry.ENTRY_EMPTY);105 106 usage.map.forEach((value, count) -> {107 addEntry(new PresetListEntry(value, this));108 });109 110 combobox = new JosmComboBox<>(dropDownModel);111 AutoCompComboBoxEditor<AutoCompletionItem> editor = new AutoCompComboBoxEditor<>();112 combobox.setEditor(editor);113 114 // The default behaviour of JComboBox is to size the editor according to the tallest item in115 // the dropdown list. We don't want that to happen because we want to show taller items in116 // the list than in the editor. We can't use117 // {@code combobox.setPrototypeDisplayValue(PresetListEntry.ENTRY_EMPTY);} because that would118 // set a fixed cell height in JList.119 combobox.setPreferredHeight(combobox.getPreferredSize().height);120 121 // a custom cell renderer capable of displaying a short description text along with the122 // value123 combobox.setRenderer(new ComboMultiSelectListCellRenderer(combobox, combobox.getRenderer(), 200, key));124 combobox.setEditable(editable);125 126 autoCompModel = new AutoCompComboBoxModel<>(Comparator.<AutoCompletionItem>naturalOrder());127 getAllForKeys(Arrays.asList(key)).forEach(autoCompModel::addElement);128 getDisplayValues().forEach(s -> autoCompModel.addElement(new AutoCompletionItem(s, AutoCompletionPriority.IS_IN_STANDARD)));129 130 AutoCompTextField<AutoCompletionItem> tf = editor.getEditorComponent();131 tf.setModel(autoCompModel);132 133 if (TaggingPresetItem.DISPLAY_KEYS_AS_HINT.get()) {134 combobox.setHint(key);135 }136 if (length > 0) {137 tf.setMaxTextLength(length);138 }139 140 JLabel label = addLabel(p);141 142 if (key != null && ("colour".equals(key) || key.startsWith("colour:") || key.endsWith(":colour"))) {143 p.add(combobox, GBC.std().fill(GBC.HORIZONTAL)); // NOSONAR144 JButton button = new JButton(new ChooseColorAction());145 button.setOpaque(true);146 button.setBorderPainted(false);147 button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));148 p.add(button, GBC.eol().fill(GBC.VERTICAL)); // NOSONAR149 ActionListener updateColor = ignore -> button.setBackground(getColor());150 updateColor.actionPerformed(null);151 combobox.addActionListener(updateColor);152 } else {153 p.add(combobox, GBC.eol().fill(GBC.HORIZONTAL)); // NOSONAR154 }155 156 String initialValue = getInitialValue(usage, support);157 PresetListEntry selItem = find(initialValue);158 if (selItem != null) {159 combobox.setSelectedItem(selItem);160 } else {161 combobox.setText(initialValue);162 }163 164 combobox.addActionListener(l -> support.fireItemValueModified(this, key, getSelectedItem().value));165 combobox.addComponentListener(new ComponentListener());166 167 label.setLabelFor(combobox);168 combobox.setToolTipText(getKeyTooltipText());169 combobox.applyComponentOrientation(OrientationAction.getValueOrientation(key));170 171 return true;172 }173 174 /**175 * Finds the PresetListEntry that matches value.176 * <p>177 * Looks in the model for an element whose {@code value} matches {@code value}.178 *179 * @param value The value to match.180 * @return The entry or null181 */182 private PresetListEntry find(String value) {183 return dropDownModel.asCollection().stream().filter(o -> o.value.equals(value)).findAny().orElse(null);184 }185 186 class ChooseColorAction extends AbstractAction {187 ChooseColorAction() {188 putValue(SHORT_DESCRIPTION, tr("Choose a color"));189 }190 191 @Override192 public void actionPerformed(ActionEvent e) {193 Color color = getColor();194 color = JColorChooser.showDialog(MainApplication.getMainPanel(), tr("Choose a color"), color);195 setColor(color);196 }197 }198 199 protected void setColor(Color color) {200 if (color != null) {201 combobox.setSelectedItem(ColorHelper.color2html(color));202 }203 }204 205 protected Color getColor() {206 String colorString = getSelectedItem().value;207 return colorString.startsWith("#")208 ? ColorHelper.html2color(colorString)209 : CSSColors.get(colorString);210 }211 212 @Override213 protected PresetListEntry getSelectedItem() {214 Object sel = combobox.getSelectedItem();215 if (sel instanceof PresetListEntry)216 // selected from the dropdown217 return (PresetListEntry) sel;218 if (sel instanceof String) {219 // free edit. If the free edit corresponds to a known entry, use that entry. This is220 // to avoid that we write a display_value to the tag's value, eg. if the user did an221 // undo.222 PresetListEntry selItem = dropDownModel.find((String) sel);223 if (selItem != null)224 return selItem;225 return new PresetListEntry((String) sel, this);226 }227 return PresetListEntry.ENTRY_EMPTY;228 }229 } -
src/org/openstreetmap/josm/gui/tagging/presets/items/ComboMultiSelect.java
1 // License: GPL. For details, see LICENSE file.2 package org.openstreetmap.josm.gui.tagging.presets.items;3 4 import static org.openstreetmap.josm.tools.I18n.tr;5 6 import java.awt.Component;7 import java.awt.Font;8 import java.lang.reflect.Method;9 import java.lang.reflect.Modifier;10 import java.util.ArrayList;11 import java.util.Arrays;12 import java.util.Collection;13 import java.util.Collections;14 import java.util.List;15 import java.util.Map;16 import java.util.TreeMap;17 import java.util.stream.Collectors;18 19 import javax.swing.JLabel;20 import javax.swing.JList;21 import javax.swing.JPanel;22 import javax.swing.ListCellRenderer;23 24 import org.openstreetmap.josm.data.osm.Tag;25 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;26 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetSelector;27 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;28 import org.openstreetmap.josm.gui.widgets.JosmListCellRenderer;29 import org.openstreetmap.josm.gui.widgets.OrientationAction;30 import org.openstreetmap.josm.tools.AlphanumComparator;31 import org.openstreetmap.josm.tools.GBC;32 import org.openstreetmap.josm.tools.Logging;33 34 /**35 * Abstract superclass for combo box and multi-select list types.36 */37 public abstract class ComboMultiSelect extends KeyedItem {38 39 /**40 * A list of entries.41 * The list has to be separated by commas (for the {@link Combo} box) or by the specified delimiter (for the {@link MultiSelect}).42 * If a value contains the delimiter, the delimiter may be escaped with a backslash.43 * If a value contains a backslash, it must also be escaped with a backslash. */44 public String values; // NOSONAR45 /**46 * To use instead of {@link #values} if the list of values has to be obtained with a Java method of this form:47 * <p>{@code public static String[] getValues();}<p>48 * The value must be: {@code full.package.name.ClassName#methodName}.49 */50 public String values_from; // NOSONAR51 /** The context used for translating {@link #values} */52 public String values_context; // NOSONAR53 /** Disabled internationalisation for value to avoid mistakes, see #11696 */54 public boolean values_no_i18n; // NOSONAR55 /** Whether to sort the values, defaults to true. */56 public boolean values_sort = true; // NOSONAR57 /**58 * A list of entries that is displayed to the user.59 * Must be the same number and order of entries as {@link #values} and editable must be false or not specified.60 * For the delimiter character and escaping, see the remarks at {@link #values}.61 */62 public String display_values; // NOSONAR63 /** The localized version of {@link #display_values}. */64 public String locale_display_values; // NOSONAR65 /**66 * A delimiter-separated list of texts to be displayed below each {@code display_value}.67 * (Only if it is not possible to describe the entry in 2-3 words.)68 * Instead of comma separated list instead using {@link #values}, {@link #display_values} and {@link #short_descriptions},69 * the following form is also supported:<p>70 * {@code <list_entry value="" display_value="" short_description="" icon="" icon_size="" />}71 */72 public String short_descriptions; // NOSONAR73 /** The localized version of {@link #short_descriptions}. */74 public String locale_short_descriptions; // NOSONAR75 /** The default value for the item. If not specified, the current value of the key is chosen as default (if applicable).*/76 public String default_; // NOSONAR77 /**78 * The character that separates values.79 * In case of {@link Combo} the default is comma.80 * In case of {@link MultiSelect} the default is semicolon and this will also be used to separate selected values in the tag.81 */82 public char delimiter = ';'; // NOSONAR83 /** whether the last value is used as default.84 * Using "force" (2) enforces this behaviour also for already tagged objects. Default is "false" (0).*/85 public byte use_last_as_default; // NOSONAR86 /** whether to use values for search via {@link TaggingPresetSelector} */87 public boolean values_searchable; // NOSONAR88 89 /**90 * The standard entries in the combobox dropdown or multiselect list. These entries are defined91 * in {@code defaultpresets.xml} (or in other custom preset files).92 */93 protected final List<PresetListEntry> presetListEntries = new ArrayList<>();94 /** Helps avoid duplicate list entries */95 protected final Map<String, PresetListEntry> seenValues = new TreeMap<>();96 protected Usage usage;97 /** Used to see if the user edited the value. */98 protected String originalValue;99 100 /**101 * A list cell renderer that paints a short text in the current value pane and and a longer text102 * in the dropdown list.103 */104 static class ComboMultiSelectListCellRenderer extends JosmListCellRenderer<PresetListEntry> {105 int width;106 private String key;107 108 ComboMultiSelectListCellRenderer(Component component, ListCellRenderer<? super PresetListEntry> renderer, int width, String key) {109 super(component, renderer);110 this.key = key;111 setWidth(width);112 }113 114 /**115 * Sets the width to format the dropdown list to116 *117 * Note: This is not the width of the list, but the width to which we format any multi-line118 * label in the list. We cannot use the list's width because at the time the combobox119 * measures its items, it is not guaranteed that the list is already sized, the combobox may120 * not even be layed out yet. Set this to {@code combobox.getWidth()}121 *122 * @param width the width123 */124 public void setWidth(int width) {125 if (width <= 0)126 width = 200;127 this.width = width - 20;128 }129 130 @Override131 public JLabel getListCellRendererComponent(132 JList<? extends PresetListEntry> list, PresetListEntry value, int index, boolean isSelected, boolean cellHasFocus) {133 134 JLabel l = (JLabel) renderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);135 l.setComponentOrientation(component.getComponentOrientation());136 if (index != -1) {137 // index -1 is set when measuring the size of the cell and when painting the138 // editor-ersatz of a readonly combobox. fixes #6157139 l.setText(value.getListDisplay(width));140 }141 if (value.getCount() > 0) {142 l.setFont(l.getFont().deriveFont(Font.ITALIC + Font.BOLD));143 }144 l.setIcon(value.getIcon());145 l.setToolTipText(value.getToolTipText(key));146 return l;147 }148 }149 150 /**151 * allow escaped comma in comma separated list:152 * "A\, B\, C,one\, two" --> ["A, B, C", "one, two"]153 * @param delimiter the delimiter, e.g. a comma. separates the entries and154 * must be escaped within one entry155 * @param s the string156 * @return splitted items157 */158 public static List<String> splitEscaped(char delimiter, String s) {159 if (s == null)160 return null; // NOSONAR161 162 List<String> result = new ArrayList<>();163 boolean backslash = false;164 StringBuilder item = new StringBuilder();165 for (int i = 0; i < s.length(); i++) {166 char ch = s.charAt(i);167 if (backslash) {168 item.append(ch);169 backslash = false;170 } else if (ch == '\\') {171 backslash = true;172 } else if (ch == delimiter) {173 result.add(item.toString());174 item.setLength(0);175 } else {176 item.append(ch);177 }178 }179 if (item.length() > 0) {180 result.add(item.toString());181 }182 return result;183 }184 185 /**186 * Returns the value selected in the combobox or a synthetic value if a multiselect.187 *188 * @return the value189 */190 protected abstract PresetListEntry getSelectedItem();191 192 @Override193 public Collection<String> getValues() {194 initListEntries();195 return presetListEntries.stream().map(x -> x.value).collect(Collectors.toSet());196 }197 198 /**199 * Returns the values to display.200 * @return the values to display201 */202 public Collection<String> getDisplayValues() {203 initListEntries();204 return presetListEntries.stream().map(PresetListEntry::getDisplayValue).collect(Collectors.toList());205 }206 207 /**208 * Adds the label to the panel209 *210 * @param p the panel211 * @return the label212 */213 protected JLabel addLabel(JPanel p) {214 final JLabel label = new JLabel(tr("{0}:", locale_text));215 addIcon(label);216 label.setToolTipText(getKeyTooltipText());217 label.setComponentPopupMenu(getPopupMenu());218 label.applyComponentOrientation(OrientationAction.getDefaultComponentOrientation());219 p.add(label, GBC.std().insets(0, 0, 10, 0));220 return label;221 }222 223 protected void initListEntries() {224 if (presetListEntries.isEmpty()) {225 initListEntriesFromAttributes();226 }227 }228 229 private List<String> getValuesFromCode(String valuesFrom) {230 // get the values from a Java function231 String[] classMethod = valuesFrom.split("#", -1);232 if (classMethod.length == 2) {233 try {234 Method method = Class.forName(classMethod[0]).getMethod(classMethod[1]);235 // Check method is public static String[] methodName()236 int mod = method.getModifiers();237 if (Modifier.isPublic(mod) && Modifier.isStatic(mod)238 && method.getReturnType().equals(String[].class) && method.getParameterTypes().length == 0) {239 return Arrays.asList((String[]) method.invoke(null));240 } else {241 Logging.error(tr("Broken tagging preset \"{0}-{1}\" - Java method given in ''values_from'' is not \"{2}\"", key, text,242 "public static String[] methodName()"));243 }244 } catch (ReflectiveOperationException e) {245 Logging.error(tr("Broken tagging preset \"{0}-{1}\" - Java method given in ''values_from'' threw {2} ({3})", key, text,246 e.getClass().getName(), e.getMessage()));247 Logging.debug(e);248 }249 }250 return null; // NOSONAR251 }252 253 /**254 * Checks if list {@code a} is either null or the same length as list {@code b}.255 *256 * @param a The list to check257 * @param b The other list258 * @param name The name of the list for error reporting259 * @return {@code a} if both lists have the same length or {@code null}260 */261 private List<String> checkListsSameLength(List<String> a, List<String> b, String name) {262 if (a != null && a.size() != b.size()) {263 Logging.error(tr("Broken tagging preset \"{0}-{1}\" - number of items in ''{2}'' must be the same as in ''values''",264 key, text, name));265 Logging.error(tr("Detailed information: {0} <> {1}", a, b));266 return null; // NOSONAR267 }268 return a;269 }270 271 protected void initListEntriesFromAttributes() {272 List<String> valueList = null;273 List<String> displayList = null;274 List<String> localeDisplayList = null;275 276 if (values_from != null) {277 valueList = getValuesFromCode(values_from);278 }279 280 if (valueList == null) {281 // get from {@code values} attribute282 valueList = splitEscaped(delimiter, values);283 }284 if (valueList == null) {285 return;286 }287 288 if (!values_no_i18n) {289 localeDisplayList = splitEscaped(delimiter, locale_display_values);290 displayList = splitEscaped(delimiter, display_values);291 }292 List<String> localeShortDescriptionsList = splitEscaped(delimiter, locale_short_descriptions);293 List<String> shortDescriptionsList = splitEscaped(delimiter, short_descriptions);294 295 displayList = checkListsSameLength(displayList, valueList, "display_values");296 localeDisplayList = checkListsSameLength(localeDisplayList, valueList, "locale_display_values");297 shortDescriptionsList = checkListsSameLength(shortDescriptionsList, valueList, "short_descriptions");298 localeShortDescriptionsList = checkListsSameLength(localeShortDescriptionsList, valueList, "locale_short_descriptions");299 300 for (int i = 0; i < valueList.size(); i++) {301 final PresetListEntry e = new PresetListEntry(valueList.get(i), this);302 if (displayList != null)303 e.display_value = displayList.get(i);304 if (localeDisplayList != null)305 e.locale_display_value = localeDisplayList.get(i);306 if (shortDescriptionsList != null)307 e.short_description = shortDescriptionsList.get(i);308 if (localeShortDescriptionsList != null)309 e.locale_short_description = localeShortDescriptionsList.get(i);310 addListEntry(e);311 }312 313 if (values_sort && TaggingPresets.SORT_MENU.get()) {314 Collections.sort(presetListEntries, (a, b) -> AlphanumComparator.getInstance().compare(a.getDisplayValue(), b.getDisplayValue()));315 }316 }317 318 /**319 * Returns the initial value to use for this preset.320 * <p>321 * The initial value is the value shown in the control when the preset dialog opens. For a322 * discussion of all the options see the enclosed tickets.323 *324 * @param usage The key Usage325 * @param support The support326 * @return The initial value to use.327 *328 * @see "https://josm.openstreetmap.de/ticket/5564"329 * @see "https://josm.openstreetmap.de/ticket/12733"330 * @see "https://josm.openstreetmap.de/ticket/17324"331 */332 protected String getInitialValue(Usage usage, TaggingPresetItemGuiSupport support) {333 String initialValue = null;334 originalValue = "";335 336 if (usage.hasUniqueValue()) {337 // all selected primitives have the same not empty value for this key338 initialValue = usage.getFirst();339 originalValue = initialValue;340 } else if (!usage.unused()) {341 // at least one primitive has a value for this key (but not all have the same one)342 initialValue = DIFFERENT;343 originalValue = initialValue;344 } else if (!usage.hadKeys() || isForceUseLastAsDefault() || PROP_FILL_DEFAULT.get()) {345 // at this point no primitive had any value for this key346 if (!support.isPresetInitiallyMatches() && isUseLastAsDefault() && LAST_VALUES.containsKey(key)) {347 initialValue = LAST_VALUES.get(key);348 } else {349 initialValue = default_;350 }351 }352 return initialValue != null ? initialValue : "";353 }354 355 @Override356 public void addCommands(List<Tag> changedTags) {357 String value = getSelectedItem().value;358 359 // no change if same as before360 if (value.equals(originalValue))361 return;362 changedTags.add(new Tag(key, value));363 364 if (isUseLastAsDefault()) {365 LAST_VALUES.put(key, value);366 }367 }368 369 /**370 * Sets whether the last value is used as default.371 * @param v Using "force" (2) enforces this behaviour also for already tagged objects. Default is "false" (0).372 */373 public void setUse_last_as_default(String v) { // NOPMD374 if ("force".equals(v)) {375 use_last_as_default = 2;376 } else if ("true".equals(v)) {377 use_last_as_default = 1;378 } else {379 use_last_as_default = 0;380 }381 }382 383 /**384 * Returns true if the last entered value should be used as default.385 * <p>386 * Note: never used in {@code defaultpresets.xml}.387 *388 * @return true if the last entered value should be used as default.389 */390 protected boolean isUseLastAsDefault() {391 return use_last_as_default > 0;392 }393 394 /**395 * Returns true if the last entered value should be used as default also on primitives that396 * already have tags.397 * <p>398 * Note: used for {@code addr:*} tags in {@code defaultpresets.xml}.399 *400 * @return true if see above401 */402 protected boolean isForceUseLastAsDefault() {403 return use_last_as_default == 2;404 }405 406 /**407 * Adds a preset list entry.408 * @param e list entry to add409 */410 public void addListEntry(PresetListEntry e) {411 presetListEntries.add(e);412 // we need to fix the entries because the XML Parser413 // {@link org.openstreetmap.josm.tools.XmlObjectParser.Parser#startElement} has used the414 // default standard constructor for {@link PresetListEntry} if the list entry was defined415 // using XML {@code <list_entry>}.416 e.cms = this;417 }418 419 /**420 * Adds a collection of preset list entries.421 * @param e list entries to add422 */423 public void addListEntries(Collection<PresetListEntry> e) {424 for (PresetListEntry i : e) {425 addListEntry(i);426 }427 }428 429 @Override430 public MatchType getDefaultMatch() {431 return MatchType.NONE;432 }433 } -
src/org/openstreetmap/josm/gui/tagging/presets/items/ItemSeparator.java
1 // License: GPL. For details, see LICENSE file.2 package org.openstreetmap.josm.gui.tagging.presets.items;3 4 import java.util.List;5 6 import javax.swing.JPanel;7 import javax.swing.JSeparator;8 9 import org.openstreetmap.josm.data.osm.Tag;10 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem;11 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;12 import org.openstreetmap.josm.tools.GBC;13 14 /**15 * Class used to represent a {@link JSeparator} inside tagging preset window.16 * @since 619817 */18 public class ItemSeparator extends TaggingPresetItem {19 20 @Override21 public boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support) {22 p.add(new JSeparator(), GBC.eol().fill(GBC.HORIZONTAL).insets(0, 5, 0, 5));23 return false;24 }25 26 @Override27 public void addCommands(List<Tag> changedTags) {28 // Do nothing29 }30 31 @Override32 public String toString() {33 return "ItemSeparator";34 }35 } -
src/org/openstreetmap/josm/gui/tagging/presets/items/Key.java
1 // License: GPL. For details, see LICENSE file.2 package org.openstreetmap.josm.gui.tagging.presets.items;3 4 import java.util.Collection;5 import java.util.Collections;6 import java.util.List;7 8 import javax.swing.JPanel;9 10 import org.openstreetmap.josm.data.osm.Tag;11 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;12 13 /**14 * Invisible type allowing to hardcode an OSM key/value from the preset definition.15 */16 public class Key extends KeyedItem {17 18 /** The hardcoded value for key */19 public String value; // NOSONAR20 21 @Override22 public boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support) {23 return false;24 }25 26 @Override27 public void addCommands(List<Tag> changedTags) {28 changedTags.add(asTag());29 }30 31 /**32 * Returns the {@link Tag} set by this item33 * @return the tag34 */35 public Tag asTag() {36 return new Tag(key, value);37 }38 39 @Override40 public MatchType getDefaultMatch() {41 return MatchType.KEY_VALUE_REQUIRED;42 }43 44 @Override45 public Collection<String> getValues() {46 return Collections.singleton(value);47 }48 49 @Override50 public String toString() {51 return "Key [key=" + key + ", value=" + value + ", text=" + text52 + ", text_context=" + text_context + ", match=" + match53 + ']';54 }55 } -
src/org/openstreetmap/josm/gui/tagging/presets/items/KeyedItem.java
1 // License: GPL. For details, see LICENSE file.2 package org.openstreetmap.josm.gui.tagging.presets.items;3 4 import static org.openstreetmap.josm.tools.I18n.tr;5 6 import java.util.Collection;7 import java.util.EnumSet;8 import java.util.HashMap;9 import java.util.Map;10 import java.util.SortedMap;11 import java.util.NoSuchElementException;12 import java.util.TreeMap;13 14 import javax.swing.JPopupMenu;15 16 import org.openstreetmap.josm.data.osm.OsmPrimitive;17 import org.openstreetmap.josm.data.osm.OsmUtils;18 import org.openstreetmap.josm.data.osm.Tag;19 import org.openstreetmap.josm.data.preferences.BooleanProperty;20 import org.openstreetmap.josm.gui.dialogs.properties.HelpTagAction;21 import org.openstreetmap.josm.gui.dialogs.properties.TaginfoAction;22 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem;23 24 /**25 * Preset item associated to an OSM key.26 */27 public abstract class KeyedItem extends TextItem {28 29 /** The constant value {@code "<different>"}. */30 protected static final String DIFFERENT = "<different>";31 /** Translation of {@code "<different>"}. */32 protected static final String DIFFERENT_I18N = tr(DIFFERENT);33 34 /** True if the default value should also be set on primitives that already have tags. */35 protected static final BooleanProperty PROP_FILL_DEFAULT = new BooleanProperty("taggingpreset.fill-default-for-tagged-primitives", false);36 37 /** Last value of each key used in presets, used for prefilling corresponding fields */38 static final Map<String, String> LAST_VALUES = new HashMap<>();39 40 /** This specifies the property key that will be modified by the item. */41 public String key; // NOSONAR42 /**43 * Allows to change the matching process, i.e., determining whether the tags of an OSM object fit into this preset.44 * If a preset fits then it is linked in the Tags/Membership dialog.<ul>45 * <li>none: neutral, i.e., do not consider this item for matching</li>46 * <li>key: positive if key matches, neutral otherwise</li>47 * <li>key!: positive if key matches, negative otherwise</li>48 * <li>keyvalue: positive if key and value matches, neutral otherwise</li>49 * <li>keyvalue!: positive if key and value matches, negative otherwise</li></ul>50 * Note that for a match, at least one positive and no negative is required.51 * Default is "keyvalue!" for {@link Key} and "none" for {@link Text}, {@link Combo}, {@link MultiSelect} and {@link Check}.52 */53 public String match = getDefaultMatch().getValue(); // NOSONAR54 55 /**56 * Enum denoting how a match (see {@link TaggingPresetItem#matches}) is performed.57 */58 protected enum MatchType {59 60 /** Neutral, i.e., do not consider this item for matching. */61 NONE("none"),62 /** Positive if key matches, neutral otherwise. */63 KEY("key"),64 /** Positive if key matches, negative otherwise. */65 KEY_REQUIRED("key!"),66 /** Positive if key and value matches, neutral otherwise. */67 KEY_VALUE("keyvalue"),68 /** Positive if key and value matches, negative otherwise. */69 KEY_VALUE_REQUIRED("keyvalue!");70 71 private final String value;72 73 MatchType(String value) {74 this.value = value;75 }76 77 /**78 * Replies the associated textual value.79 * @return the associated textual value80 */81 public String getValue() {82 return value;83 }84 85 /**86 * Determines the {@code MatchType} for the given textual value.87 * @param type the textual value88 * @return the {@code MatchType} for the given textual value89 */90 public static MatchType ofString(String type) {91 for (MatchType i : EnumSet.allOf(MatchType.class)) {92 if (i.getValue().equals(type))93 return i;94 }95 throw new IllegalArgumentException(type + " is not allowed");96 }97 }98 99 /**100 * Usage information on a key101 *102 * TODO merge with {@link org.openstreetmap.josm.data.osm.TagCollection}103 */104 public static class Usage {105 /** Usage count for all values used for this key */106 public final SortedMap<String, Integer> map = new TreeMap<>();107 private boolean hadKeys;108 private boolean hadEmpty;109 private int selectedCount;110 111 /**112 * Check if there is exactly one value for this key.113 * @return <code>true</code> if there was exactly one value.114 */115 public boolean hasUniqueValue() {116 return map.size() == 1 && !hadEmpty;117 }118 119 /**120 * Check if this key was not used in any primitive121 * @return <code>true</code> if it was unused.122 */123 public boolean unused() {124 return map.isEmpty();125 }126 127 /**128 * Get the first value available.129 * @return The first value130 * @throws NoSuchElementException if there is no such value.131 */132 public String getFirst() {133 return map.firstKey();134 }135 136 /**137 * Check if we encountered any primitive that had any keys138 * @return <code>true</code> if any of the primitives had any tags.139 */140 public boolean hadKeys() {141 return hadKeys;142 }143 144 /**145 * Returns the number of primitives selected.146 * @return the number of primitives selected.147 */148 public int getSelectedCount() {149 return selectedCount;150 }151 152 /**153 * Splits multiple values and adds their usage counts as single value.154 * <p>155 * A value of {@code regional;pizza} will increment the count of {@code regional} and of156 * {@code pizza}.157 * @param delimiter The delimiter used for splitting.158 * @return A new usage object with the new counts.159 */160 public Usage splitValues(String delimiter) {161 Usage usage = new Usage();162 usage.hadEmpty = hadEmpty;163 usage.hadKeys = hadKeys;164 usage.selectedCount = selectedCount;165 map.forEach((value, count) -> {166 for (String v : value.split(String.valueOf(delimiter), -1)) {167 usage.map.merge(v, count, Integer::sum);168 }169 });170 return usage;171 }172 }173 174 /**175 * Computes the tag usage for the given key from the given primitives176 * @param sel the primitives177 * @param key the key178 * @return the tag usage179 */180 public static Usage determineTextUsage(Collection<OsmPrimitive> sel, String key) {181 Usage returnValue = new Usage();182 returnValue.selectedCount = sel.size();183 for (OsmPrimitive s : sel) {184 String v = s.get(key);185 if (v != null) {186 returnValue.map.merge(v, 1, Integer::sum);187 } else {188 returnValue.hadEmpty = true;189 }190 if (s.hasKeys()) {191 returnValue.hadKeys = true;192 }193 }194 return returnValue;195 }196 197 protected static Usage determineBooleanUsage(Collection<OsmPrimitive> sel, String key) {198 Usage returnValue = new Usage();199 returnValue.selectedCount = sel.size();200 for (OsmPrimitive s : sel) {201 String booleanValue = OsmUtils.getNamedOsmBoolean(s.get(key));202 if (booleanValue != null) {203 returnValue.map.merge(booleanValue, 1, Integer::sum);204 }205 }206 return returnValue;207 }208 209 /**210 * Determines whether key or key+value are required.211 * @return whether key or key+value are required212 */213 public boolean isKeyRequired() {214 final MatchType type = MatchType.ofString(match);215 return MatchType.KEY_REQUIRED == type || MatchType.KEY_VALUE_REQUIRED == type;216 }217 218 /**219 * Returns the default match.220 * @return the default match221 */222 public abstract MatchType getDefaultMatch();223 224 /**225 * Returns the list of values.226 * @return the list of values227 */228 public abstract Collection<String> getValues();229 230 protected String getKeyTooltipText() {231 return tr("This corresponds to the key ''{0}''", key);232 }233 234 @Override235 public Boolean matches(Map<String, String> tags) {236 switch (MatchType.ofString(match)) {237 case NONE:238 return null; // NOSONAR239 case KEY:240 return tags.containsKey(key) ? Boolean.TRUE : null;241 case KEY_REQUIRED:242 return tags.containsKey(key);243 case KEY_VALUE:244 return tags.containsKey(key) && getValues().contains(tags.get(key)) ? Boolean.TRUE : null;245 case KEY_VALUE_REQUIRED:246 return tags.containsKey(key) && getValues().contains(tags.get(key));247 default:248 throw new IllegalStateException();249 }250 }251 252 protected JPopupMenu getPopupMenu() {253 Tag tag = new Tag(key, null);254 JPopupMenu popupMenu = new JPopupMenu();255 popupMenu.add(tr("Key: {0}", key)).setEnabled(false);256 popupMenu.add(new HelpTagAction(() -> tag));257 TaginfoAction taginfoAction = new TaginfoAction(() -> tag, () -> null);258 popupMenu.add(taginfoAction.toTagHistoryAction());259 popupMenu.add(taginfoAction);260 return popupMenu;261 }262 263 @Override264 public String toString() {265 return "KeyedItem [key=" + key + ", text=" + text266 + ", text_context=" + text_context + ", match=" + match267 + ']';268 }269 } -
src/org/openstreetmap/josm/gui/tagging/presets/items/Label.java
1 // License: GPL. For details, see LICENSE file.2 package org.openstreetmap.josm.gui.tagging.presets.items;3 4 import javax.swing.JLabel;5 import javax.swing.JPanel;6 7 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;8 import org.openstreetmap.josm.tools.GBC;9 10 /**11 * Label type.12 */13 public class Label extends TextItem {14 15 @Override16 public boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support) {17 initializeLocaleText(null);18 JLabel label = new JLabel(locale_text);19 addIcon(label);20 label.applyComponentOrientation(support.getDefaultComponentOrientation());21 p.add(label, GBC.eol().fill(GBC.HORIZONTAL));22 return true;23 }24 25 } -
src/org/openstreetmap/josm/gui/tagging/presets/items/Link.java
1 // License: GPL. For details, see LICENSE file.2 package org.openstreetmap.josm.gui.tagging.presets.items;3 4 import static org.openstreetmap.josm.tools.I18n.tr;5 6 import java.awt.event.MouseEvent;7 import java.util.Arrays;8 import java.util.Optional;9 10 import javax.swing.JPanel;11 import javax.swing.SwingUtilities;12 13 import org.openstreetmap.josm.gui.dialogs.properties.HelpAction;14 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;15 import org.openstreetmap.josm.gui.widgets.UrlLabel;16 import org.openstreetmap.josm.spi.preferences.Config;17 import org.openstreetmap.josm.tools.GBC;18 import org.openstreetmap.josm.tools.LanguageInfo;19 20 /**21 * Hyperlink type.22 * @since 886323 */24 public class Link extends TextItem {25 26 /** The OSM wiki page to display. */27 public String wiki; // NOSONAR28 29 /** The link to display. */30 public String href; // NOSONAR31 32 /** The localized version of {@link #href}. */33 public String locale_href; // NOSONAR34 35 @Override36 public boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support) {37 initializeLocaleText(tr("More information about this feature"));38 UrlLabel label = buildUrlLabel();39 if (label != null) {40 label.applyComponentOrientation(support.getDefaultComponentOrientation());41 p.add(label, GBC.eol().insets(0, 10, 0, 0).fill(GBC.HORIZONTAL));42 }43 return false;44 }45 46 protected UrlLabel buildUrlLabel() {47 final String url = getUrl();48 if (wiki != null) {49 UrlLabel urlLabel = new UrlLabel(url, locale_text, 2) {50 @Override51 public void mouseClicked(MouseEvent e) {52 if (SwingUtilities.isLeftMouseButton(e)) {53 // Open localized page if exists54 HelpAction.displayHelp(Arrays.asList(55 LanguageInfo.getWikiLanguagePrefix(LanguageInfo.LocaleType.OSM_WIKI) + wiki,56 wiki));57 } else {58 super.mouseClicked(e);59 }60 }61 };62 addIcon(urlLabel);63 return urlLabel;64 } else if (href != null || locale_href != null) {65 UrlLabel urlLabel = new UrlLabel(url, locale_text, 2);66 addIcon(urlLabel);67 return urlLabel;68 }69 return null;70 }71 72 /**73 * Returns the link URL.74 * @return the link URL75 * @since 1542376 */77 public String getUrl() {78 if (wiki != null) {79 return Config.getUrls().getOSMWiki() + "/wiki/" + wiki;80 } else if (href != null || locale_href != null) {81 return Optional.ofNullable(locale_href).orElse(href);82 }83 return null;84 }85 86 @Override87 protected String fieldsToString() {88 return super.fieldsToString()89 + (wiki != null ? "wiki=" + wiki + ", " : "")90 + (href != null ? "href=" + href + ", " : "")91 + (locale_href != null ? "locale_href=" + locale_href + ", " : "");92 }93 } -
src/org/openstreetmap/josm/gui/tagging/presets/items/MultiSelect.java
1 // License: GPL. For details, see LICENSE file.2 package org.openstreetmap.josm.gui.tagging.presets.items;3 4 import java.awt.Dimension;5 import java.awt.Insets;6 import java.awt.Rectangle;7 import java.util.stream.Collectors;8 9 import javax.swing.DefaultListModel;10 import javax.swing.JLabel;11 import javax.swing.JList;12 import javax.swing.JPanel;13 import javax.swing.JScrollPane;14 15 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;16 import org.openstreetmap.josm.gui.widgets.OrientationAction;17 import org.openstreetmap.josm.tools.GBC;18 19 /**20 * Multi-select list type.21 */22 public class MultiSelect extends ComboMultiSelect {23 24 /**25 * Number of rows to display (positive integer, optional).26 */27 public short rows; // NOSONAR28 29 /** The model for the JList */30 protected final DefaultListModel<PresetListEntry> model = new DefaultListModel<>();31 /** The swing component */32 protected final JList<PresetListEntry> list = new JList<>(model);33 34 private void addEntry(PresetListEntry entry) {35 if (!seenValues.containsKey(entry.value)) {36 model.addElement(entry);37 seenValues.put(entry.value, entry);38 }39 }40 41 @Override42 protected boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support) {43 initializeLocaleText(null);44 usage = determineTextUsage(support.getSelected(), key);45 seenValues.clear();46 initListEntries();47 48 model.clear();49 // disable if the selected primitives have different values50 list.setEnabled(usage.hasUniqueValue() || usage.unused());51 String initialValue = getInitialValue(usage, support);52 53 // Add values from the preset.54 presetListEntries.forEach(this::addEntry);55 56 // Add all values used in the selected primitives. This also adds custom values and makes57 // sure we won't lose them.58 usage = usage.splitValues(String.valueOf(delimiter));59 for (String value: usage.map.keySet()) {60 addEntry(new PresetListEntry(value, this));61 }62 63 // Select the values in the initial value.64 if (!initialValue.isEmpty() && !DIFFERENT.equals(initialValue)) {65 for (String value : initialValue.split(String.valueOf(delimiter), -1)) {66 PresetListEntry e = new PresetListEntry(value, this);67 addEntry(e);68 int i = model.indexOf(e);69 list.addSelectionInterval(i, i);70 }71 }72 73 ComboMultiSelectListCellRenderer renderer = new ComboMultiSelectListCellRenderer(list, list.getCellRenderer(), 200, key);74 list.setCellRenderer(renderer);75 JLabel label = addLabel(p);76 label.setLabelFor(list);77 JScrollPane sp = new JScrollPane(list);78 79 if (rows > 0) {80 list.setVisibleRowCount(rows);81 // setVisibleRowCount() only works when all cells have the same height, but sometimes we82 // have icons of different sizes. Calculate the size of the first {@code rows} entries83 // and size the scrollpane accordingly.84 Rectangle r = list.getCellBounds(0, Math.min(rows, model.size() - 1));85 if (r != null) {86 Insets insets = list.getInsets();87 r.width += insets.left + insets.right;88 r.height += insets.top + insets.bottom;89 insets = sp.getInsets();90 r.width += insets.left + insets.right;91 r.height += insets.top + insets.bottom;92 sp.setPreferredSize(new Dimension(r.width, r.height));93 }94 }95 p.add(sp, GBC.eol().fill(GBC.HORIZONTAL)); // NOSONAR96 97 list.addListSelectionListener(l -> support.fireItemValueModified(this, key, getSelectedItem().value));98 list.setToolTipText(getKeyTooltipText());99 list.applyComponentOrientation(OrientationAction.getValueOrientation(key));100 101 return true;102 }103 104 @Override105 protected PresetListEntry getSelectedItem() {106 return new PresetListEntry(list.getSelectedValuesList()107 .stream().map(e -> e.value).distinct().sorted().collect(Collectors.joining(String.valueOf(delimiter))), this);108 }109 } -
src/org/openstreetmap/josm/gui/tagging/presets/items/Optional.java
1 // License: GPL. For details, see LICENSE file.2 package org.openstreetmap.josm.gui.tagging.presets.items;3 4 import static org.openstreetmap.josm.tools.I18n.tr;5 6 import javax.swing.JLabel;7 import javax.swing.JPanel;8 9 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;10 import org.openstreetmap.josm.tools.GBC;11 12 /**13 * Used to group optional attributes.14 * @since 886315 */16 public class Optional extends TextItem {17 18 // TODO: Draw a box around optional stuff19 @Override20 public boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support) {21 initializeLocaleText(tr("Optional Attributes:"));22 JLabel label = new JLabel(locale_text);23 label.applyComponentOrientation(support.getDefaultComponentOrientation());24 p.add(new JLabel(" "), GBC.eol()); // space25 p.add(label, GBC.eol());26 p.add(new JLabel(" "), GBC.eol()); // space27 return false;28 }29 } -
src/org/openstreetmap/josm/gui/tagging/presets/items/PresetLink.java
1 // License: GPL. For details, see LICENSE file.2 package org.openstreetmap.josm.gui.tagging.presets.items;3 4 import static org.openstreetmap.josm.tools.I18n.tr;5 6 import java.awt.event.MouseAdapter;7 import java.awt.event.MouseEvent;8 import java.util.Collection;9 import java.util.List;10 import java.util.Optional;11 12 import javax.swing.JLabel;13 import javax.swing.JPanel;14 15 import org.openstreetmap.josm.data.osm.OsmPrimitive;16 import org.openstreetmap.josm.data.osm.Tag;17 import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;18 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;19 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetLabel;20 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;21 import org.openstreetmap.josm.tools.GBC;22 23 /**24 * Adds a link to another preset.25 * @since 886326 */27 public class PresetLink extends TextItem {28 29 static final class TaggingPresetMouseAdapter extends MouseAdapter {30 private final TaggingPreset t;31 private final Collection<OsmPrimitive> sel;32 33 TaggingPresetMouseAdapter(TaggingPreset t, Collection<OsmPrimitive> sel) {34 this.t = t;35 this.sel = sel;36 }37 38 @Override39 public void mouseClicked(MouseEvent e) {40 t.showAndApply(sel);41 }42 }43 44 /** The exact name of the preset to link to. Required. */45 public String preset_name = ""; // NOSONAR46 47 /**48 * Creates a label to be inserted aboive this link49 * @return a label50 */51 public JLabel createLabel() {52 initializeLocaleText(tr("Edit also …"));53 return new JLabel(locale_text);54 }55 56 @Override57 public boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support) {58 final String presetName = preset_name;59 Optional<TaggingPreset> found = TaggingPresets.getTaggingPresets().stream().filter(preset -> presetName.equals(preset.name)).findFirst();60 if (found.isPresent()) {61 TaggingPreset t = found.get();62 JLabel lbl = new TaggingPresetLabel(t);63 lbl.addMouseListener(new TaggingPresetMouseAdapter(t, support.getSelected()));64 lbl.applyComponentOrientation(support.getDefaultComponentOrientation());65 p.add(lbl, GBC.eol().fill(GBC.HORIZONTAL));66 }67 return false;68 }69 70 @Override71 public void addCommands(List<Tag> changedTags) {72 // Do nothing73 }74 75 @Override76 public String toString() {77 return "PresetLink [preset_name=" + preset_name + ']';78 }79 } -
src/org/openstreetmap/josm/gui/tagging/presets/items/PresetListEntry.java
1 // License: GPL. For details, see LICENSE file.2 3 package org.openstreetmap.josm.gui.tagging.presets.items;4 5 import static org.openstreetmap.josm.tools.I18n.tr;6 import static org.openstreetmap.josm.tools.I18n.trc;7 8 import java.util.Objects;9 10 import javax.swing.ImageIcon;11 12 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem;13 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetReader;14 import org.openstreetmap.josm.tools.AlphanumComparator;15 import org.openstreetmap.josm.tools.Utils;16 17 /**18 * Preset list entry.19 * <p>20 * Used for controls that offer a list of items to choose from like {@link Combo} and21 * {@link MultiSelect}.22 */23 public class PresetListEntry implements Comparable<PresetListEntry> {24 /** Used to display an entry matching several different values. */25 protected static final PresetListEntry ENTRY_DIFFERENT = new PresetListEntry(KeyedItem.DIFFERENT, null);26 /** Used to display an empty entry used to clear values. */27 protected static final PresetListEntry ENTRY_EMPTY = new PresetListEntry("", null);28 29 /**30 * This is the value that is going to be written to the tag on the selected primitive(s). Except31 * when the value is {@code "<different>"}, which is never written, or the value is empty, which32 * deletes the tag. {@code value} is never translated.33 */34 public String value; // NOSONAR35 /** The ComboMultiSelect that displays the list */36 public ComboMultiSelect cms; // NOSONAR37 /** Text displayed to the user instead of {@link #value}. */38 public String display_value; // NOSONAR39 /** Text to be displayed below {@link #display_value} in the combobox list. */40 public String short_description; // NOSONAR41 /** The location of icon file to display */42 public String icon; // NOSONAR43 /** The size of displayed icon. If not set, default is size from icon file */44 public short icon_size; // NOSONAR45 /** The localized version of {@link #display_value}. */46 public String locale_display_value; // NOSONAR47 /** The localized version of {@link #short_description}. */48 public String locale_short_description; // NOSONAR49 50 private String cachedDisplayValue;51 private String cachedShortDescription;52 private ImageIcon cachedIcon;53 54 /**55 * Constructs a new {@code PresetListEntry}, uninitialized.56 *57 * Public default constructor is needed by {@link org.openstreetmap.josm.tools.XmlObjectParser.Parser#startElement}58 */59 public PresetListEntry() {60 }61 62 /**63 * Constructs a new {@code PresetListEntry}, initialized with a value and64 * {@link ComboMultiSelect} context.65 *66 * @param value value67 * @param cms the ComboMultiSelect68 */69 public PresetListEntry(String value, ComboMultiSelect cms) {70 this.value = value;71 this.cms = cms;72 }73 74 /**75 * Returns the contents displayed in the dropdown list.76 *77 * This is the contents that would be displayed in the current view plus a short description to78 * aid the user. The whole contents is wrapped to {@code width}.79 *80 * @param width the width in px81 * @return HTML formatted contents82 */83 public String getListDisplay(int width) {84 String displayValue = getDisplayValue();85 Integer count = getCount();86 87 if (count > 0 && cms.usage.getSelectedCount() > 1) {88 displayValue = tr("{0} ({1})", displayValue, count);89 }90 91 if (this.equals(ENTRY_DIFFERENT)) {92 return "<html><b>" + Utils.escapeReservedCharactersHTML(displayValue) + "</b></html>";93 }94 95 String shortDescription = getShortDescription();96 97 if (shortDescription.isEmpty()) {98 // avoids a collapsed list entry if value == ""99 if (displayValue.isEmpty()) {100 return " ";101 }102 return displayValue;103 }104 105 // RTL not supported in HTML. See: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4866977106 return String.format("<html><div style=\"width: %d\"><b>%s</b><p style=\"padding-left: 10\">%s</p></div></html>",107 width,108 displayValue,109 Utils.escapeReservedCharactersHTML(shortDescription));110 }111 112 /**113 * Returns the entry icon, if any.114 * @return the entry icon, or {@code null}115 */116 public ImageIcon getIcon() {117 if (icon != null && cachedIcon == null) {118 cachedIcon = TaggingPresetItem.loadImageIcon(icon, TaggingPresetReader.getZipIcons(), (int) icon_size);119 }120 return cachedIcon;121 }122 123 /**124 * Returns the contents displayed in the current item view.125 * @return the value to display126 */127 public String getDisplayValue() {128 if (cachedDisplayValue == null) {129 if (cms != null && cms.values_no_i18n) {130 cachedDisplayValue = Utils.firstNonNull(value, " ");131 } else {132 cachedDisplayValue = Utils.firstNonNull(133 locale_display_value, tr(display_value), trc(cms == null ? null : cms.values_context, value), " ");134 }135 }136 return cachedDisplayValue;137 }138 139 /**140 * Returns the short description to display.141 * @return the short description to display142 */143 public String getShortDescription() {144 if (cachedShortDescription == null) {145 cachedShortDescription = Utils.firstNonNull(locale_short_description, tr(short_description), "");146 }147 return cachedShortDescription;148 }149 150 /**151 * Returns the tooltip for this entry.152 * @param key the tag key153 * @return the tooltip154 */155 public String getToolTipText(String key) {156 if (this.equals(ENTRY_DIFFERENT)) {157 return tr("Keeps the original values of the selected objects unchanged.");158 }159 if (value != null && !value.isEmpty()) {160 return tr("Sets the key ''{0}'' to the value ''{1}''.", key, value);161 }162 return tr("Clears the key ''{0}''.", key);163 }164 165 // toString is mainly used to initialize the Editor166 @Override167 public String toString() {168 if (this.equals(ENTRY_DIFFERENT))169 return getDisplayValue();170 return getDisplayValue().replaceAll("\\s*<.*>\\s*", " "); // remove additional markup, e.g. <br>171 }172 173 @Override174 public boolean equals(Object o) {175 if (this == o) return true;176 if (o == null || getClass() != o.getClass()) return false;177 PresetListEntry that = (PresetListEntry) o;178 return Objects.equals(value, that.value);179 }180 181 @Override182 public int hashCode() {183 return Objects.hash(value);184 }185 186 /**187 * Returns how many selected primitives had this value set.188 * @return see above189 */190 public int getCount() {191 Integer count = cms == null || cms.usage == null ? null : cms.usage.map.get(value);192 return count == null ? 0 : count;193 }194 195 @Override196 public int compareTo(PresetListEntry o) {197 return AlphanumComparator.getInstance().compare(this.value, o.value);198 }199 } -
src/org/openstreetmap/josm/gui/tagging/presets/items/Roles.java
1 // License: GPL. For details, see LICENSE file.2 package org.openstreetmap.josm.gui.tagging.presets.items;3 4 import static org.openstreetmap.josm.tools.I18n.tr;5 6 import java.awt.GridBagLayout;7 import java.util.ArrayList;8 import java.util.List;9 import java.util.Set;10 11 import javax.swing.JLabel;12 import javax.swing.JPanel;13 14 import org.openstreetmap.josm.data.osm.Tag;15 import org.openstreetmap.josm.data.osm.search.SearchCompiler;16 import org.openstreetmap.josm.data.osm.search.SearchParseError;17 import org.openstreetmap.josm.data.osm.search.SearchSetting;18 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem;19 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;20 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType;21 import org.openstreetmap.josm.tools.GBC;22 import org.openstreetmap.josm.tools.ImageProvider;23 import org.xml.sax.SAXException;24 25 /**26 * The <code>roles</code> element in tagging presets definition.27 * <p>28 * A list of {@link Role} elements. Describes the roles that are expected for29 * the members of a relation.30 * <p>31 * Used for data validation, auto completion, among others.32 */33 public class Roles extends TaggingPresetItem {34 35 /**36 * The <code>role</code> element in tagging preset definition.37 *38 * Information on a certain role, which is expected for the relation members.39 */40 public static class Role {41 /** Presets types expected for this role */42 public Set<TaggingPresetType> types; // NOSONAR43 /** Role name used in a relation */44 public String key; // NOSONAR45 /** Is the role name a regular expression */46 public boolean regexp; // NOSONAR47 /** The text to display */48 public String text; // NOSONAR49 /** The context used for translating {@link #text} */50 public String text_context; // NOSONAR51 /** The localized version of {@link #text}. */52 public String locale_text; // NOSONAR53 /** An expression (cf. search dialog) for objects of this role */54 public SearchCompiler.Match memberExpression; // NOSONAR55 /** Is this role required at least once in the relation? */56 public boolean required; // NOSONAR57 /** How often must the element appear */58 private short count;59 60 /**61 * Sets the presets types expected for this role.62 * @param types comma-separated set of expected types63 * @throws SAXException if an unknown type is detected64 */65 public void setType(String types) throws SAXException {66 this.types = getType(types);67 }68 69 /**70 * Sets whether this role is required at least once in the relation.71 * @param str "required" or "optional"72 * @throws SAXException if str is neither "required" or "optional"73 */74 public void setRequisite(String str) throws SAXException {75 if ("required".equals(str)) {76 required = true;77 } else if (!"optional".equals(str))78 throw new SAXException(tr("Unknown requisite: {0}", str));79 }80 81 /**82 * Sets whether the role name is a regular expression.83 * @param str "true" or "false"84 * @throws SAXException if str is neither "true" or "false"85 */86 public void setRegexp(String str) throws SAXException {87 if ("true".equals(str)) {88 regexp = true;89 } else if (!"false".equals(str))90 throw new SAXException(tr("Unknown regexp value: {0}", str));91 }92 93 /**94 * Sets an expression (cf. search dialog) for objects of this role95 * @param memberExpression an expression (cf. search dialog) for objects of this role96 * @throws SAXException in case of parsing error97 */98 public void setMember_expression(String memberExpression) throws SAXException {99 try {100 final SearchSetting searchSetting = new SearchSetting();101 searchSetting.text = memberExpression;102 searchSetting.caseSensitive = true;103 searchSetting.regexSearch = true;104 this.memberExpression = SearchCompiler.compile(searchSetting);105 } catch (SearchParseError ex) {106 throw new SAXException(tr("Illegal member expression: {0}", ex.getMessage()), ex);107 }108 }109 110 /**111 * Sets how often must the element appear.112 * @param count how often must the element appear113 */114 public void setCount(String count) {115 this.count = Short.parseShort(count);116 }117 118 /**119 * Return either argument, the highest possible value or the lowest allowed value120 * @param c count121 * @return the highest possible value or the lowest allowed value122 * @see #required123 */124 public long getValidCount(long c) {125 if (count > 0 && !required)126 return c != 0 ? count : 0;127 else if (count > 0)128 return count;129 else if (!required)130 return c != 0 ? c : 0;131 else132 return c != 0 ? c : 1;133 }134 135 /**136 * Check if the given role matches this class (required to check regexp role types)137 * @param role role to check138 * @return <code>true</code> if role matches139 * @since 11989140 */141 public boolean isRole(String role) {142 if (regexp && role != null) { // pass null through, it will anyway fail143 return role.matches(this.key);144 }145 return this.key.equals(role);146 }147 148 /**149 * Adds this role to the given panel.150 * @param p panel where to add this role151 * @return {@code true}152 */153 public boolean addToPanel(JPanel p) {154 String cstring;155 if (count > 0 && !required) {156 cstring = "0,"+count;157 } else if (count > 0) {158 cstring = String.valueOf(count);159 } else if (!required) {160 cstring = "0-...";161 } else {162 cstring = "1-...";163 }164 if (locale_text == null) {165 locale_text = getLocaleText(text, text_context, null);166 }167 p.add(new JLabel(locale_text+':'), GBC.std().insets(0, 0, 10, 0));168 p.add(new JLabel(key), GBC.std().insets(0, 0, 10, 0));169 p.add(new JLabel(cstring), types == null ? GBC.eol() : GBC.std().insets(0, 0, 10, 0));170 if (types != null) {171 JPanel pp = new JPanel();172 for (TaggingPresetType t : types) {173 pp.add(new JLabel(ImageProvider.get(t.getIconName())));174 }175 p.add(pp, GBC.eol());176 }177 return true;178 }179 180 @Override181 public String toString() {182 return "Role [key=" + key + ", text=" + text + ']';183 }184 }185 186 /**187 * List of {@link Role} elements.188 */189 public final List<Role> roles = new ArrayList<>(2);190 191 @Override192 public boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support) {193 p.add(new JLabel(" "), GBC.eol()); // space194 if (!roles.isEmpty()) {195 JPanel proles = new JPanel(new GridBagLayout());196 proles.add(new JLabel(tr("Available roles")), GBC.std().insets(0, 0, 10, 0));197 proles.add(new JLabel(tr("role")), GBC.std().insets(0, 0, 10, 0));198 proles.add(new JLabel(tr("count")), GBC.std().insets(0, 0, 10, 0));199 proles.add(new JLabel(tr("elements")), GBC.eol());200 for (Role i : roles) {201 i.addToPanel(proles);202 }203 proles.applyComponentOrientation(support.getDefaultComponentOrientation());204 p.add(proles, GBC.eol());205 }206 return false;207 }208 209 @Override210 public void addCommands(List<Tag> changedTags) {211 // Do nothing212 }213 214 @Override215 public String toString() {216 return "Roles [roles=" + roles + ']';217 }218 } -
src/org/openstreetmap/josm/gui/tagging/presets/items/Space.java
1 // License: GPL. For details, see LICENSE file.2 package org.openstreetmap.josm.gui.tagging.presets.items;3 4 import java.util.List;5 6 import javax.swing.JLabel;7 import javax.swing.JPanel;8 9 import org.openstreetmap.josm.data.osm.Tag;10 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem;11 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;12 import org.openstreetmap.josm.tools.GBC;13 14 /**15 * Horizontal separator type.16 */17 public class Space extends TaggingPresetItem {18 19 @Override20 public boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support) {21 p.add(new JLabel(" "), GBC.eol()); // space22 return false;23 }24 25 @Override26 public void addCommands(List<Tag> changedTags) {27 // Do nothing28 }29 30 @Override31 public String toString() {32 return "Space";33 }34 } -
src/org/openstreetmap/josm/gui/tagging/presets/items/Text.java
1 // License: GPL. For details, see LICENSE file.2 package org.openstreetmap.josm.gui.tagging.presets.items;3 4 import static org.openstreetmap.josm.tools.I18n.tr;5 6 import java.awt.Color;7 import java.awt.Component;8 import java.awt.GridBagLayout;9 import java.awt.Insets;10 import java.text.NumberFormat;11 import java.text.ParseException;12 import java.util.ArrayList;13 import java.util.Collection;14 import java.util.Collections;15 import java.util.List;16 17 import javax.swing.AbstractButton;18 import javax.swing.BorderFactory;19 import javax.swing.ButtonGroup;20 import javax.swing.JButton;21 import javax.swing.JComponent;22 import javax.swing.JLabel;23 import javax.swing.JPanel;24 import javax.swing.JToggleButton;25 26 import org.openstreetmap.josm.data.osm.Tag;27 import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem;28 import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxEditor;29 import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel;30 import org.openstreetmap.josm.gui.tagging.ac.AutoCompTextField;31 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;32 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem;33 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;34 import org.openstreetmap.josm.gui.util.DocumentAdapter;35 import org.openstreetmap.josm.gui.widgets.JosmComboBox;36 import org.openstreetmap.josm.gui.widgets.JosmTextField;37 import org.openstreetmap.josm.gui.widgets.OrientationAction;38 import org.openstreetmap.josm.tools.GBC;39 import org.openstreetmap.josm.tools.Logging;40 import org.openstreetmap.josm.tools.Utils;41 import org.openstreetmap.josm.tools.template_engine.ParseError;42 import org.openstreetmap.josm.tools.template_engine.TemplateEntry;43 import org.openstreetmap.josm.tools.template_engine.TemplateParser;44 import org.xml.sax.SAXException;45 46 /**47 * Text field type.48 */49 public class Text extends KeyedItem {50 51 private static int auto_increment_selected; // NOSONAR52 53 /** The default value for the item. If not specified, the current value of the key is chosen as default (if applicable). Defaults to "". */54 public String default_; // NOSONAR55 /** The original value */56 public String originalValue; // NOSONAR57 /** whether the last value is used as default. Using "force" enforces this behaviour also for already tagged objects. Default is "false".*/58 public String use_last_as_default = "false"; // NOSONAR59 /**60 * May contain a comma separated list of integer increments or decrements, e.g. "-2,-1,+1,+2".61 * A button will be shown next to the text field for each value, allowing the user to select auto-increment with the given stepping.62 * Auto-increment only happens if the user selects it. There is also a button to deselect auto-increment.63 * Default is no auto-increment. Mutually exclusive with {@link #use_last_as_default}.64 */65 public String auto_increment; // NOSONAR66 /** The length of the text box (number of characters allowed). */67 public short length; // NOSONAR68 /** A comma separated list of alternative keys to use for autocompletion. */69 public String alternative_autocomplete_keys; // NOSONAR70 71 private JComponent value;72 private transient TemplateEntry valueTemplate;73 74 @Override75 public boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support) {76 77 AutoCompComboBoxModel<AutoCompletionItem> model = new AutoCompComboBoxModel<>();78 List<String> keys = new ArrayList<>();79 keys.add(key);80 if (alternative_autocomplete_keys != null) {81 for (String k : alternative_autocomplete_keys.split(",", -1)) {82 keys.add(k);83 }84 }85 getAllForKeys(keys).forEach(model::addElement);86 87 AutoCompTextField<AutoCompletionItem> textField;88 AutoCompComboBoxEditor<AutoCompletionItem> editor = null;89 90 // find out if our key is already used in the selection.91 Usage usage = determineTextUsage(support.getSelected(), key);92 93 if (usage.unused() || usage.hasUniqueValue()) {94 textField = new AutoCompTextField<>();95 } else {96 editor = new AutoCompComboBoxEditor<>();97 textField = editor.getEditorComponent();98 }99 textField.setModel(model);100 value = textField;101 102 if (length > 0) {103 textField.setMaxTextLength(length);104 }105 if (TaggingPresetItem.DISPLAY_KEYS_AS_HINT.get()) {106 textField.setHint(key);107 }108 if (usage.unused()) {109 if (auto_increment_selected != 0 && auto_increment != null) {110 try {111 textField.setText(Integer.toString(Integer.parseInt(112 LAST_VALUES.get(key)) + auto_increment_selected));113 } catch (NumberFormatException ex) {114 // Ignore - cannot auto-increment if last was non-numeric115 Logging.trace(ex);116 }117 } else if (!usage.hadKeys() || PROP_FILL_DEFAULT.get() || "force".equals(use_last_as_default)) {118 // selected osm primitives are untagged or filling default values feature is enabled119 if (!support.isPresetInitiallyMatches() && !"false".equals(use_last_as_default) && LAST_VALUES.containsKey(key)) {120 textField.setText(LAST_VALUES.get(key));121 } else {122 textField.setText(default_);123 }124 } else {125 // selected osm primitives are tagged and filling default values feature is disabled126 textField.setText("");127 }128 value = textField;129 originalValue = null;130 } else if (usage.hasUniqueValue()) {131 // all objects use the same value132 textField.setText(usage.getFirst());133 value = textField;134 originalValue = usage.getFirst();135 }136 if (editor != null) {137 // The selected primitives have different values for this key. <b>Note:</b> this138 // cannot be an AutoCompComboBox because the values in the dropdown are different from139 // those we autocomplete on.140 JosmComboBox<String> comboBox = new JosmComboBox<>();141 comboBox.getModel().addAllElements(usage.map.keySet());142 comboBox.setEditable(true);143 comboBox.setEditor(editor);144 comboBox.getEditor().setItem(DIFFERENT_I18N);145 value = comboBox;146 originalValue = DIFFERENT_I18N;147 }148 initializeLocaleText(null);149 150 setupListeners(textField, support);151 152 // if there's an auto_increment setting, then wrap the text field153 // into a panel, appending a number of buttons.154 // auto_increment has a format like -2,-1,1,2155 // the text box being the first component in the panel is relied156 // on in a rather ugly fashion further down.157 if (auto_increment != null) {158 ButtonGroup bg = new ButtonGroup();159 JPanel pnl = new JPanel(new GridBagLayout());160 pnl.add(value, GBC.std().fill(GBC.HORIZONTAL));161 162 // first, one button for each auto_increment value163 for (final String ai : auto_increment.split(",", -1)) {164 JToggleButton aibutton = new JToggleButton(ai);165 aibutton.setToolTipText(tr("Select auto-increment of {0} for this field", ai));166 aibutton.setMargin(new Insets(0, 0, 0, 0));167 aibutton.setFocusable(false);168 saveHorizontalSpace(aibutton);169 bg.add(aibutton);170 try {171 // TODO there must be a better way to parse a number like "+3" than this.172 final int buttonvalue = NumberFormat.getIntegerInstance().parse(ai.replace("+", "")).intValue();173 if (auto_increment_selected == buttonvalue) aibutton.setSelected(true);174 aibutton.addActionListener(e -> auto_increment_selected = buttonvalue);175 pnl.add(aibutton, GBC.std());176 } catch (ParseException ex) {177 Logging.error("Cannot parse auto-increment value of '" + ai + "' into an integer");178 }179 }180 181 // an invisible toggle button for "release" of the button group182 final JToggleButton clearbutton = new JToggleButton("X");183 clearbutton.setVisible(false);184 clearbutton.setFocusable(false);185 bg.add(clearbutton);186 // and its visible counterpart. - this mechanism allows us to187 // have *no* button selected after the X is clicked, instead188 // of the X remaining selected189 JButton releasebutton = new JButton("X");190 releasebutton.setToolTipText(tr("Cancel auto-increment for this field"));191 releasebutton.setMargin(new Insets(0, 0, 0, 0));192 releasebutton.setFocusable(false);193 releasebutton.addActionListener(e -> {194 auto_increment_selected = 0;195 clearbutton.setSelected(true);196 });197 saveHorizontalSpace(releasebutton);198 pnl.add(releasebutton, GBC.eol());199 value = pnl;200 }201 final JLabel label = new JLabel(tr("{0}:", locale_text));202 addIcon(label);203 label.setToolTipText(getKeyTooltipText());204 label.setComponentPopupMenu(getPopupMenu());205 label.setLabelFor(value);206 p.add(label, GBC.std().insets(0, 0, 10, 0));207 p.add(value, GBC.eol().fill(GBC.HORIZONTAL));208 label.applyComponentOrientation(support.getDefaultComponentOrientation());209 value.setToolTipText(getKeyTooltipText());210 value.applyComponentOrientation(OrientationAction.getNamelikeOrientation(key));211 return true;212 }213 214 private static void saveHorizontalSpace(AbstractButton button) {215 Insets insets = button.getBorder().getBorderInsets(button);216 // Ensure the current look&feel does not waste horizontal space (as seen in Nimbus & Aqua)217 if (insets != null && insets.left+insets.right > insets.top+insets.bottom) {218 int min = Math.min(insets.top, insets.bottom);219 button.setBorder(BorderFactory.createEmptyBorder(insets.top, min, insets.bottom, min));220 }221 }222 223 private static String getValue(Component comp) {224 if (comp instanceof JosmComboBox) {225 return ((JosmComboBox<?>) comp).getEditorItemAsString();226 } else if (comp instanceof JosmTextField) {227 return ((JosmTextField) comp).getText();228 } else if (comp instanceof JPanel) {229 return getValue(((JPanel) comp).getComponent(0));230 } else {231 return null;232 }233 }234 235 @Override236 public void addCommands(List<Tag> changedTags) {237 238 // return if unchanged239 String v = getValue(value);240 if (v == null) {241 Logging.error("No 'last value' support for component " + value);242 return;243 }244 245 v = Utils.removeWhiteSpaces(v);246 247 if (!"false".equals(use_last_as_default) || auto_increment != null) {248 LAST_VALUES.put(key, v);249 }250 if (v.equals(originalValue) || (originalValue == null && v.isEmpty()))251 return;252 253 changedTags.add(new Tag(key, v));254 AutoCompletionManager.rememberUserInput(key, v, true);255 }256 257 @Override258 public MatchType getDefaultMatch() {259 return MatchType.NONE;260 }261 262 @Override263 public Collection<String> getValues() {264 if (Utils.isEmpty(default_))265 return Collections.emptyList();266 return Collections.singleton(default_);267 }268 269 /**270 * Set the value template.271 * @param pattern The value_template pattern.272 * @throws SAXException If an error occured while parsing.273 */274 public void setValue_template(String pattern) throws SAXException { // NOPMD275 try {276 this.valueTemplate = new TemplateParser(pattern).parse();277 } catch (ParseError e) {278 Logging.error("Error while parsing " + pattern + ": " + e.getMessage());279 throw new SAXException(e);280 }281 }282 283 private void setupListeners(AutoCompTextField<AutoCompletionItem> textField, TaggingPresetItemGuiSupport support) {284 // value_templates don't work well with multiple selected items because,285 // as the command queue is currently implemented, we can only save286 // the same value to all selected primitives, which is probably not287 // what you want.288 if (valueTemplate == null || support.getSelected().size() > 1) { // only fire on normal fields289 textField.getDocument().addDocumentListener(DocumentAdapter.create(ignore ->290 support.fireItemValueModified(this, key, textField.getText())));291 } else { // only listen on calculated fields292 support.addListener((source, key, newValue) -> {293 String valueTemplateText = valueTemplate.getText(support);294 Logging.trace("Evaluating value_template {0} for key {1} from {2} with new value {3} => {4}",295 valueTemplate, key, source, newValue, valueTemplateText);296 textField.setText(valueTemplateText);297 if (originalValue != null && !originalValue.equals(valueTemplateText)) {298 textField.setForeground(Color.RED);299 } else {300 textField.setForeground(Color.BLUE);301 }302 });303 }304 }305 } -
src/org/openstreetmap/josm/gui/tagging/presets/items/TextItem.java
1 // License: GPL. For details, see LICENSE file.2 package org.openstreetmap.josm.gui.tagging.presets.items;3 4 import java.util.List;5 6 import org.openstreetmap.josm.data.osm.Tag;7 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem;8 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetReader;9 10 import javax.swing.ImageIcon;11 import javax.swing.JLabel;12 import javax.swing.SwingConstants;13 14 /**15 * A tagging preset item displaying a localizable text.16 * @since 619017 */18 public abstract class TextItem extends TaggingPresetItem {19 20 /** The text to display */21 public String text; // NOSONAR22 23 /** The context used for translating {@link #text} */24 public String text_context; // NOSONAR25 26 /** The localized version of {@link #text} */27 public String locale_text; // NOSONAR28 29 /** The location of icon file to display */30 public String icon; // NOSONAR31 /** The size of displayed icon. If not set, default is 16px */32 public short icon_size = 16; // NOSONAR33 34 protected final void initializeLocaleText(String defaultText) {35 if (locale_text == null) {36 locale_text = getLocaleText(text, text_context, defaultText);37 }38 }39 40 @Override41 public void addCommands(List<Tag> changedTags) {42 // Do nothing43 }44 45 protected String fieldsToString() {46 return (text != null ? "text=" + text + ", " : "")47 + (text_context != null ? "text_context=" + text_context + ", " : "")48 + (locale_text != null ? "locale_text=" + locale_text : "");49 }50 51 /**52 * Defines the label icon from this entry's icon53 * @param label the component54 * @since 1760555 */56 protected void addIcon(JLabel label) {57 label.setIcon(getIcon());58 label.setHorizontalAlignment(SwingConstants.LEADING);59 }60 61 /**62 * Returns the entry icon, if any.63 * @return the entry icon, or {@code null}64 * @since 1760565 */66 public ImageIcon getIcon() {67 return icon == null ? null : loadImageIcon(icon, TaggingPresetReader.getZipIcons(), (int) icon_size);68 }69 70 @Override71 public String toString() {72 return getClass().getSimpleName() + " [" + fieldsToString() + ']';73 }74 } -
src/org/openstreetmap/josm/gui/widgets/JosmComboBox.java
59 59 /** greyed text to display in the editor when the selected value is empty */ 60 60 private String hint; 61 61 62 private boolean fakeWidth; 63 62 64 /** 63 65 * Creates a {@code JosmComboBox} with a {@link JosmComboBoxModel} data model. 64 66 * The default data model is an empty list of objects. … … 302 304 } 303 305 304 306 /** 307 * Make popup wider than combobox. 308 */ 309 @Override 310 public Dimension getSize() { 311 Dimension dim = super.getSize(); 312 if (fakeWidth) 313 dim.width = Math.max(getPreferredSize().width, dim.width); 314 return dim; 315 } 316 317 /** 318 * Helper to make popup wider than combobox. 319 */ 320 @Override 321 public void doLayout() { 322 try { 323 fakeWidth = false; 324 super.doLayout(); 325 } finally { 326 fakeWidth = true; 327 } 328 } 329 330 /** 305 331 * Get the dropdown list component 306 332 * 307 333 * @return the list or null … … 392 418 int freeAbove = bounds.y - screenBounds.y; 393 419 int freeBelow = (screenBounds.y + screenBounds.height) - (bounds.y + bounds.height); 394 420 395 try { 396 // First try an implementation-dependent method to get the exact number. 397 @SuppressWarnings("unchecked") 398 JList<E> jList = getList(); 399 421 int maxRowCount = 8; // default 422 @SuppressWarnings("unchecked") 423 JList<E> jList = getList(); 424 if (jList != null) { 400 425 // Calculate the free space available on screen 401 426 Insets insets = jList.getInsets(); 402 427 // A small fudge factor that accounts for the displacement of the popup relative to the … … 403 428 // combobox and the popup shadow. 404 429 int fudge = 4; 405 430 int free = Math.max(freeAbove, freeBelow) - (insets.top + insets.bottom) - fudge; 406 if (jList.getParent() instanceof JScrollPane) { 407 JScrollPane scroller = (JScrollPane) jList.getParent(); 431 Component parent = getParent(); 432 if (parent instanceof JScrollPane) { 433 JScrollPane scroller = (JScrollPane) parent; 408 434 Border border = scroller.getViewportBorder(); 409 435 if (border != null) { 410 436 insets = border.getBorderInsets(null); … … 427 453 if (h >= free) 428 454 break; 429 455 } 430 setMaximumRowCount(i); 431 // Logging.debug("free = {0}, h = {1}, i = {2}, bounds = {3}, screenBounds = {4}", free, h, i, bounds, screenBounds); 432 } catch (Exception ex) { 433 setMaximumRowCount(8); // the default 456 maxRowCount = i; 434 457 } 458 // Logging.debug("free = {0}, h = {1}, i = {2}, bounds = {3}, screenBounds = {4}", free, h, i, bounds, screenBounds); 459 setMaximumRowCount(maxRowCount); 435 460 } 436 461 437 462 @Override -
src/org/openstreetmap/josm/gui/widgets/JosmComboBoxModel.java
205 205 } 206 206 207 207 /** 208 * Replaces all current elements with elements from the collection. 209 * <p> 210 * This is the same as {@link #removeAllElements} followed by {@link #addAllElements} but 211 * minimizes event firing and tries to keep the current selection. Use this when all elements 212 * are reinitialized programmatically like in an {@code autoCompBefore} event. 213 * 214 * @param newElements The new elements. 215 */ 216 public void replaceAllElements(Collection<E> newElements) { 217 Object oldSelected = selected; 218 int index0 = elements.size(); 219 elements.clear(); 220 newElements.forEach(e -> doAddElement(e)); 221 int index1 = elements.size(); 222 int index2 = Math.min(index0, index1); 223 if (0 < index2) { 224 fireContentsChanged(this, 0, index2 - 1); 225 } 226 if (index2 < index0) { 227 fireIntervalRemoved(this, index2, index0 - 1); 228 } 229 if (index2 < index1) { 230 fireIntervalAdded(this, index2, index1 - 1); 231 } 232 // re-select the old selection if possible 233 int index = elements.indexOf(oldSelected); 234 setSelectedItem(index == -1 ? null : getElementAt(index)); 235 } 236 237 /** 208 238 * Adds an element to the top of the list. 209 239 * <p> 210 240 * If the element is already in the model, moves it to the top. If the model gets too big, -
src/org/openstreetmap/josm/gui/widgets/JosmTextField.java
42 42 */ 43 43 public class JosmTextField extends JTextField implements Destroyable, ComponentListener, FocusListener, PropertyChangeListener { 44 44 45 private finalPopupMenuLauncher launcher;45 private PopupMenuLauncher launcher; 46 46 private String hint; 47 47 private Icon icon; 48 48 private Point iconPos; … … 241 241 } 242 242 243 243 /** 244 * Enables / disables the undo / redo functionality. 245 * @param undoRedo enable if true 246 */ 247 public void enableUndoRedo(boolean undoRedo) { 248 launcher = TextContextualPopupMenu.enableMenuFor(this, undoRedo); 249 } 250 251 /** 244 252 * Empties the internal undo manager. 245 253 * @since 14977 246 254 */ -
src/org/openstreetmap/josm/tools/ImageProvider.java
725 725 } 726 726 727 727 /** 728 * Returns a Future for the ImageResource. 729 * 730 * This method returns immediately and loads the image asynchronously. 731 * 732 * @return the future of the requested image 733 * @since xxx 734 */ 735 public CompletableFuture<ImageResource> getResourceFuture() { 736 return isRemote() 737 ? CompletableFuture.supplyAsync(this::getResource, IMAGE_FETCHER) 738 : CompletableFuture.completedFuture(getResource()); 739 } 740 741 /** 728 742 * Load an image with a given file name. 729 743 * 730 744 * @param subdir subdirectory the image lies in -
src/org/openstreetmap/josm/tools/MultiMap.java
124 124 } 125 125 126 126 /** 127 * Like getValues, but returns all values for all keys. 128 * @return the set of all values or an empty set 129 */ 130 public Set<B> getAllValues() { 131 return map.entrySet().stream().flatMap(e -> e.getValue().stream()).collect(Collectors.toSet()); 132 } 133 134 /** 127 135 * Returns {@code true} if this map contains no key-value mappings. 128 136 * @return {@code true} if this map contains no key-value mappings 129 137 * @see Map#isEmpty() -
src/org/openstreetmap/josm/tools/OsmPrimitiveImageProvider.java
22 22 import org.openstreetmap.josm.gui.mappaint.styleelement.MapImage; 23 23 import org.openstreetmap.josm.gui.mappaint.styleelement.NodeElement; 24 24 import org.openstreetmap.josm.gui.mappaint.styleelement.StyleElement; 25 import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;26 25 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets; 27 26 28 27 /** … … 56 55 // Check if the presets have icons for nodes/relations. 57 56 if (primitive.isTagged() && (!options.contains(Options.NO_WAY_PRESETS) || OsmPrimitiveType.WAY != primitive.getType())) { 58 57 final Optional<ImageResource> icon = TaggingPresets.getMatchingPresets(primitive).stream() 59 .sorted(Comparator.comparing(p -> (p.iconName != null && p.iconName.contains("multipolygon")) 60 || Utils.isEmpty(p.types) ? Integer.MAX_VALUE : p.types.size())) 61 .map(TaggingPreset::getImageResource) 58 .sorted(Comparator.comparing(p -> 59 ((p.getIconName() != null && p.getIconName().contains("multipolygon")) || Utils.isEmpty(p.getTypes())) 60 ? Integer.MAX_VALUE : p.getTypes().size())) 61 .map(tp -> ImageResource.getAttachedImageResource(tp.getAction())) 62 62 .filter(Objects::nonNull) 63 63 .findFirst(); 64 64 if (icon.isPresent()) { -
src/org/openstreetmap/josm/tools/XmlParsingException.java
5 5 6 6 import org.xml.sax.Locator; 7 7 import org.xml.sax.SAXException; 8 import org.xml.sax.helpers.LocatorImpl; 8 9 9 10 /** 10 11 * An exception thrown during XML parsing, with known line and column. … … 11 12 * @since 6906 12 13 */ 13 14 public class XmlParsingException extends SAXException { 14 private int columnNumber; 15 private int lineNumber; 15 private LocatorImpl locator = new LocatorImpl(); 16 16 17 17 /** 18 18 * Constructs a new {@code XmlParsingException}. … … 46 46 */ 47 47 public XmlParsingException rememberLocation(Locator locator) { 48 48 if (locator != null) { 49 this.columnNumber = locator.getColumnNumber(); 50 this.lineNumber = locator.getLineNumber(); 49 this.locator = new LocatorImpl(locator); 51 50 } 52 51 return this; 53 52 } … … 55 54 @Override 56 55 public String getMessage() { 57 56 String msg = super.getMessage(); 58 if ( lineNumber == 0 && columnNumber== 0)57 if (getLineNumber() == 0 && getColumnNumber() == 0) 59 58 return msg; 60 59 if (msg == null) { 61 60 msg = getClass().getName(); 62 61 } 63 return msg + ' ' + tr("(at line {0}, column {1})", lineNumber, columnNumber); 62 return msg + ' ' + tr("(in {0} at line {1}, column {2})", 63 locator.getSystemId(), getLineNumber(), getColumnNumber()); 64 64 } 65 65 66 66 /** … … 68 68 * @return the column number where the exception occurred 69 69 */ 70 70 public int getColumnNumber() { 71 return columnNumber;71 return locator.getColumnNumber(); 72 72 } 73 73 74 74 /** … … 76 76 * @return the line number where the exception occurred 77 77 */ 78 78 public int getLineNumber() { 79 return l ineNumber;79 return locator.getLineNumber(); 80 80 } 81 81 } -
test/functional/org/openstreetmap/josm/tools/ImageProviderTest.java
6 6 import static org.junit.jupiter.api.Assertions.assertFalse; 7 7 import static org.junit.jupiter.api.Assertions.assertNotNull; 8 8 import static org.openstreetmap.josm.gui.mappaint.MapCSSRendererTest.assertImageEquals; 9 import static org.openstreetmap.josm.gui.tagging.presets.ItemFactory.build; 9 10 10 11 import java.awt.Dimension; 11 12 import java.awt.Image; … … 14 15 import java.awt.image.BufferedImage; 15 16 import java.io.File; 16 17 import java.io.IOException; 17 import java.util.Arrays;18 18 import java.util.Collections; 19 19 import java.util.List; 20 20 import java.util.function.UnaryOperator; … … 34 34 import org.openstreetmap.josm.TestUtils; 35 35 import org.openstreetmap.josm.data.coor.LatLon; 36 36 import org.openstreetmap.josm.data.osm.Node; 37 import org.openstreetmap.josm.gui.tagging.presets.Key; 38 import org.openstreetmap.josm.gui.tagging.presets.Root; 37 39 import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset; 38 40 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets; 39 import org.openstreetmap.josm.gui.tagging.presets.items.Key;40 41 import org.openstreetmap.josm.testutils.JOSMTestRules; 41 42 import org.xml.sax.SAXException; 42 43 … … 134 135 */ 135 136 @Test 136 137 void testTicket19551() throws SAXException { 137 TaggingPreset badPreset = new TaggingPreset(); 138 badPreset.setType("node,way,relation,closedway"); 139 Key key = new Key(); 140 key.key = "amenity"; 141 key.value = "fuel"; 142 badPreset.data.add(key); 143 TaggingPreset goodPreset = new TaggingPreset(); 144 goodPreset.setType("node,way,relation,closedway"); 145 goodPreset.data.add(key); 146 goodPreset.iconName = "stop"; 147 TaggingPresets.addTaggingPresets(Arrays.asList(goodPreset, badPreset)); 138 Root root = (Root) build("root"); 139 Key key = (Key) build("key", "key", "amenity", "value", "fuel"); 140 141 TaggingPreset badPreset = (TaggingPreset) build("item", "type", "node,way,relation,closedway"); 142 badPreset.getAllItems().add(key); 143 root.addItem(badPreset); 144 145 TaggingPreset goodPreset = (TaggingPreset) build("item", "type", "node,way,relation,closedway", "icon_name", "stop"); 146 goodPreset.getAllItems().add(key); 147 root.addItem(goodPreset); 148 149 TaggingPresets.addRoot(root); 148 150 Node node = new Node(LatLon.ZERO); 149 151 node.put("amenity", "fuel"); 150 152 assertDoesNotThrow(() -> OsmPrimitiveImageProvider.getResource(node, Collections.emptyList())); -
test/unit/org/openstreetmap/josm/data/osm/DefaultNameFormatterTest.java
16 16 17 17 import org.junit.jupiter.api.Test; 18 18 import org.openstreetmap.josm.TestUtils; 19 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetReader;20 19 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets; 21 20 import org.openstreetmap.josm.io.IllegalDataException; 22 21 import org.openstreetmap.josm.io.OsmReader; … … 52 51 @Test 53 52 @SuppressFBWarnings(value = "ITA_INEFFICIENT_TO_ARRAY") 54 53 void testTicket9632() throws IllegalDataException, IOException, SAXException { 54 55 DefaultNameFormatter f = DefaultNameFormatter.getInstance(); 56 55 57 String source = "presets/Presets_BicycleJunction-preset.xml"; 56 58 wireMockServer.stubFor(get(urlEqualTo("/" + source)) 57 59 .willReturn(aResponse() … … 58 60 .withStatus(200) 59 61 .withHeader("Content-Type", "text/xml") 60 62 .withBodyFile(source))); 61 TaggingPresets.addTaggingPresets(TaggingPresetReader.readAll(wireMockServer.url(source), true));62 63 63 Comparator<IRelation<?>> comparator = DefaultNameFormatter.getInstance().getRelationComparator(); 64 TaggingPresets.testInitialize(wireMockServer.url(source)); 65 Comparator<IRelation<?>> comparator = f.getRelationComparator(); 64 66 65 67 try (InputStream is = TestUtils.getRegressionDataStream(9632, "data.osm.zip")) { 66 68 DataSet ds = OsmReader.parseDataSet(is, null); 67 69 70 // CHECKSTYLE.OFF: SingleSpaceSeparator 71 // CHECKSTYLE.OFF: ParenPad 72 68 73 // Test with 3 known primitives causing the problem. Correct order is p1, p3, p2 with this preset 69 74 Relation p1 = (Relation) ds.getPrimitiveById(2983382, OsmPrimitiveType.RELATION); 70 Relation p2 = (Relation) ds.getPrimitiveById( 550315, OsmPrimitiveType.RELATION);71 Relation p3 = (Relation) ds.getPrimitiveById( 167042, OsmPrimitiveType.RELATION);75 Relation p2 = (Relation) ds.getPrimitiveById( 550315, OsmPrimitiveType.RELATION); 76 Relation p3 = (Relation) ds.getPrimitiveById( 167042, OsmPrimitiveType.RELATION); 72 77 73 // route_master ("Bus 453", 6 members) 74 System.out.println("p1: "+DefaultNameFormatter.getInstance().format(p1)+" - "+p1); 75 // TMC ("A 6 Kaiserslautern - Mannheim [negative]", 123 members) 76 System.out.println("p2: "+DefaultNameFormatter.getInstance().format(p2)+" - "+p2); 77 // route(lcn Sal Salier-Radweg(412 members) 78 System.out.println("p3: "+DefaultNameFormatter.getInstance().format(p3)+" - "+p3); 78 assertEquals("route_master (\"Bus 453\", 6 members)", f.format(p1)); 79 assertEquals("TMC (\"A 6 Kaiserslautern - Mannheim [negative]\", 123 members)", f.format(p2)); 80 assertEquals("route(lcn Sal Salier-Radweg(412 members)", f.format(p3)); 79 81 80 // CHECKSTYLE.OFF: SingleSpaceSeparator 81 assertEquals(comparator.compare(p1, p2), -1); // p1 < p2 82 assertEquals(comparator.compare(p2, p1), 1); // p2 > p1 82 assertEquals(-1, comparator.compare(p1, p2)); // p1 < p2 83 assertEquals( 1, comparator.compare(p2, p1)); // p2 > p1 84 assertEquals(-1, comparator.compare(p1, p3)); // p1 < p3 85 assertEquals( 1, comparator.compare(p3, p1)); // p3 > p1 86 assertEquals( 1, comparator.compare(p2, p3)); // p2 > p3 87 assertEquals(-1, comparator.compare(p3, p2)); // p3 < p2 83 88 84 assertEquals(comparator.compare(p1, p3), -1); // p1 < p385 assertEquals(comparator.compare(p3, p1), 1); // p3 > p186 assertEquals(comparator.compare(p2, p3), 1); // p2 > p387 assertEquals(comparator.compare(p3, p2), -1); // p3 < p288 89 // CHECKSTYLE.ON: SingleSpaceSeparator 90 // CHECKSTYLE.ON: ParenPad 89 91 90 92 Relation[] relations = new ArrayList<>(ds.getRelations()).toArray(new Relation[0]); 91 93 -
test/unit/org/openstreetmap/josm/data/osm/search/SearchCompilerTest.java
7 7 import static org.junit.jupiter.api.Assertions.assertNotNull; 8 8 import static org.junit.jupiter.api.Assertions.assertThrows; 9 9 import static org.junit.jupiter.api.Assertions.assertTrue; 10 import static org.openstreetmap.josm.gui.tagging.presets.ItemFactory.build; 10 11 11 12 import java.lang.reflect.Field; 12 13 import java.nio.charset.StandardCharsets; … … 13 14 import java.nio.file.Files; 14 15 import java.nio.file.Paths; 15 16 import java.time.Instant; 16 import java.util.Arrays;17 17 import java.util.Collection; 18 import java.util.Collections;19 18 import java.util.Set; 20 19 21 20 import org.junit.Assert; … … 39 38 import org.openstreetmap.josm.data.osm.WayData; 40 39 import org.openstreetmap.josm.data.osm.search.SearchCompiler.ExactKeyValue; 41 40 import org.openstreetmap.josm.data.osm.search.SearchCompiler.Match; 41 import org.openstreetmap.josm.gui.tagging.presets.Key; 42 import org.openstreetmap.josm.gui.tagging.presets.Root; 42 43 import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset; 43 44 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetMenu; 44 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType;45 45 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets; 46 import org.openstreetmap.josm.gui.tagging.presets.items.Key;47 46 import org.openstreetmap.josm.testutils.annotations.BasicPreferences; 48 47 import org.openstreetmap.josm.tools.Logging; 49 48 … … 602 601 */ 603 602 @Test 604 603 void testPresetMultipleWords() { 605 TaggingPreset testPreset = new TaggingPreset(); 606 testPreset.name = "Test Combined Preset Name"; 607 testPreset.group = new TaggingPresetMenu(); 608 testPreset.group.name = "TestGroupName"; 604 TaggingPresetMenu testPresetMenu = newTaggingPresetMenu("TestGroupName"); 605 TaggingPreset testPreset = newTaggingPreset("Test Combined Preset Name"); 606 testPresetMenu.addItem(testPreset); 609 607 610 608 String combinedPresetname = testPreset.getRawName(); 611 609 SearchSetting settings = new SearchSetting(); … … 628 626 */ 629 627 @Test 630 628 void testPresetLookup() throws SearchParseError, NoSuchFieldException, IllegalAccessException { 631 TaggingPreset testPreset = new TaggingPreset(); 632 testPreset.name = "Test Preset Name"; 633 testPreset.group = new TaggingPresetMenu(); 634 testPreset.group.name = "Test Preset Group Name"; 629 Root root = newRoot(); 635 630 636 String query = "preset:" + 637 "\"" + testPreset.getRawName() + "\""; 631 TaggingPresetMenu group = newTaggingPresetMenu("Test Preset Group Name"); 632 root.addItem(group); 633 634 TaggingPreset testPreset = newTaggingPreset("Test Preset Name"); 635 group.addItem(testPreset); 636 637 TaggingPresets.readFromPreferences(); 638 TaggingPresets.addRoot(root); 639 640 String query = "preset:\"Test Preset Group Name/Test Preset Name\""; 638 641 SearchSetting settings = new SearchSetting(); 639 642 settings.text = query; 640 643 settings.mapCSSSearch = false; 641 644 642 // load presets and add the test preset643 TaggingPresets.readFromPreferences();644 TaggingPresets.addTaggingPresets(Collections.singletonList(testPreset));645 646 645 Match match = SearchCompiler.compile(settings); 647 646 648 647 // access the private field where all matching presets are stored … … 667 666 */ 668 667 @Test 669 668 void testPresetLookupWildcard() throws SearchParseError, NoSuchFieldException, IllegalAccessException { 670 TaggingPresetMenu group = new TaggingPresetMenu(); 671 group.name = "TestPresetGroup"; 669 Root root = newRoot(); 672 670 673 TaggingPreset testPreset1 = new TaggingPreset(); 674 testPreset1.name = "TestPreset1"; 675 testPreset1.group = group; 671 TaggingPresetMenu group = newTaggingPresetMenu("TestPresetGroup"); 672 root.addItem(group); 676 673 677 TaggingPreset testPreset2 = new TaggingPreset(); 678 testPreset2.name = "TestPreset2"; 679 testPreset2.group = group; 674 TaggingPreset testPreset1 = newTaggingPreset("TestPreset1"); 675 group.addItem(testPreset1); 680 676 681 TaggingPreset testPreset3 = new TaggingPreset(); 682 testPreset3.name = "TestPreset3"; 683 testPreset3.group = group; 677 TaggingPreset testPreset2 = newTaggingPreset("TestPreset2"); 678 group.addItem(testPreset2); 684 679 685 String query = "preset:" + "\"" + group.getRawName() + "/*\""; 680 TaggingPreset testPreset3 = newTaggingPreset("TestPreset3"); 681 group.addItem(testPreset3); 682 683 TaggingPresets.readFromPreferences(); 684 TaggingPresets.addRoot(root); 685 686 String query = "preset:\"TestPresetGroup/*\""; 686 687 SearchSetting settings = new SearchSetting(); 687 688 settings.text = query; 688 689 settings.mapCSSSearch = false; 689 690 690 TaggingPresets.readFromPreferences();691 TaggingPresets.addTaggingPresets(Arrays.asList(testPreset1, testPreset2, testPreset3));692 693 691 Match match = SearchCompiler.compile(settings); 694 692 695 693 // access the private field where all matching presets are stored … … 717 715 final String key = "test_key1"; 718 716 final String val = "test_val1"; 719 717 720 Key key1 = new Key(); 721 key1.key = key; 722 key1.value = val; 718 Root root = newRoot(); 723 719 724 TaggingPreset testPreset = new TaggingPreset(); 725 testPreset.name = presetName; 726 testPreset.types = Collections.singleton(TaggingPresetType.NODE); 727 testPreset.data.add(key1); 728 testPreset.group = new TaggingPresetMenu(); 729 testPreset.group.name = presetGroupName; 720 TaggingPresetMenu group = newTaggingPresetMenu(presetGroupName); 721 root.addItem(group); 730 722 723 TaggingPreset testPreset = (TaggingPreset) build("item name={0} type=node", presetName); 724 group.addItem(testPreset); 725 726 Key key1 = (Key) build("key key={0} value={1}", key, val); 727 testPreset.addItem(key1); 728 731 729 TaggingPresets.readFromPreferences(); 732 TaggingPresets.add TaggingPresets(Collections.singleton(testPreset));730 TaggingPresets.addRoot(root); 733 731 734 732 String query = "preset:" + "\"" + testPreset.getRawName() + "\""; 735 733 … … 763 761 } 764 762 } 765 763 764 private static Root newRoot() { 765 return (Root) build("presets"); 766 } 767 766 768 private static TaggingPreset newTaggingPreset(String name) { 767 TaggingPreset result = new TaggingPreset(); 768 result.name = name; 769 return result; 769 return (TaggingPreset) build("item name={0}", name); 770 770 } 771 771 772 private static TaggingPresetMenu newTaggingPresetMenu(String name) { 773 return (TaggingPresetMenu) build("group name={0}", name); 774 } 775 772 776 /** 773 777 * Search for {@code nodes:2}. 774 778 * @throws SearchParseError if an error has been encountered while compiling … … 843 847 844 848 /** 845 849 * Non-regression test for JOSM #21463 850 * @throws SearchParseError in case of parser error 846 851 */ 847 852 @Test 848 853 void testNonRegression21463() throws SearchParseError { -
test/unit/org/openstreetmap/josm/data/validation/tests/OpeningHourTestTest.java
9 9 import static org.junit.jupiter.api.Assertions.assertNotNull; 10 10 import static org.junit.jupiter.api.Assertions.assertTrue; 11 11 12 import java.io.IOException; 12 13 import java.util.Arrays; 13 import java.util.Collection;14 14 import java.util.LinkedHashSet; 15 15 import java.util.List; 16 16 import java.util.Locale; … … 24 24 import org.openstreetmap.josm.data.preferences.sources.ValidatorPrefHelper; 25 25 import org.openstreetmap.josm.data.validation.Severity; 26 26 import org.openstreetmap.josm.data.validation.TestError; 27 import org.openstreetmap.josm.gui.tagging.presets.Item; 28 import org.openstreetmap.josm.gui.tagging.presets.KeyedItem; 27 29 import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset; 28 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem; 29 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetReader; 30 import org.openstreetmap.josm.gui.tagging.presets.items.KeyedItem; 30 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets; 31 31 import org.openstreetmap.josm.testutils.annotations.BasicPreferences; 32 32 import org.openstreetmap.josm.testutils.annotations.I18n; 33 33 import org.openstreetmap.josm.tools.Logging; 34 34 import org.xml.sax.SAXException; 35 35 import org.junit.jupiter.api.BeforeEach; 36 36 import org.junit.jupiter.api.Test; 37 37 … … 216 216 * Tests that predefined values in presets are correct. 217 217 */ 218 218 @Test 219 void testPresetValues() { 220 final Collection<TaggingPreset> presets = TaggingPresetReader.readFromPreferences(false, false); 219 void testPresetValues() throws SAXException, IOException { 220 221 TaggingPresets.testInitialize("resource://data/defaultpresets.xml"); 221 222 final Set<Tag> values = new LinkedHashSet<>(); 222 for (final TaggingPreset p : presets) {223 for (final TaggingPresetItem i : p.data) {223 for (final TaggingPreset p : TaggingPresets.getTaggingPresets()) { 224 for (final Item i : p.getAllItems()) { 224 225 if (i instanceof KeyedItem && 225 Arrays.asList("opening_hours", "service_times", "collection_times").contains(((KeyedItem) i). key)) {226 Arrays.asList("opening_hours", "service_times", "collection_times").contains(((KeyedItem) i).getKey())) { 226 227 for (final String v : ((KeyedItem) i).getValues()) { 227 values.add(new Tag(((KeyedItem) i). key, v));228 values.add(new Tag(((KeyedItem) i).getKey(), v)); 228 229 } 229 230 } 230 231 } -
test/unit/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditorTest.java
20 20 import org.openstreetmap.josm.data.osm.Way; 21 21 import org.openstreetmap.josm.gui.layer.OsmDataLayer; 22 22 import org.openstreetmap.josm.gui.tagging.TagEditorPanel; 23 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;24 23 import org.openstreetmap.josm.testutils.JOSMTestRules; 25 24 import org.openstreetmap.josm.testutils.mockers.JOptionPaneSimpleMocker; 26 25 … … 123 122 OsmDataLayer layer = new OsmDataLayer(ds, "test", null); 124 123 IRelationEditor re = newRelationEditor(relation, layer); 125 124 126 AutoCompletingTextField tfRole = GenericRelationEditor.buildRoleTextField(re);127 assertNotNull(tfRole);128 129 125 TagEditorPanel tagEditorPanel = new TagEditorPanel(relation, null); 130 126 131 127 JPanel top = GenericRelationEditor.buildTagEditorPanel(tagEditorPanel); -
test/unit/org/openstreetmap/josm/gui/dialogs/relation/MemberTableModelTest.java
33 33 } 34 34 35 35 @Override 36 public Collection<OsmPrimitive> get Selection() {36 public Collection<OsmPrimitive> getPrimitives() { 37 37 return Collections.<OsmPrimitive>singleton(n); 38 38 } 39 39 }).getRelationMemberForPrimitive(n)); -
test/unit/org/openstreetmap/josm/gui/dialogs/relation/actions/AbstractRelationEditorActionTest.java
12 12 import org.openstreetmap.josm.data.osm.OsmPrimitive; 13 13 import org.openstreetmap.josm.data.osm.Relation; 14 14 import org.openstreetmap.josm.data.osm.Tag; 15 import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem; 15 16 import org.openstreetmap.josm.gui.dialogs.relation.GenericRelationEditorTest; 16 17 import org.openstreetmap.josm.gui.dialogs.relation.IRelationEditor; 17 18 import org.openstreetmap.josm.gui.dialogs.relation.MemberTable; … … 20 21 import org.openstreetmap.josm.gui.dialogs.relation.SelectionTableModel; 21 22 import org.openstreetmap.josm.gui.layer.OsmDataLayer; 22 23 import org.openstreetmap.josm.gui.tagging.TagEditorModel; 23 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField; 24 import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBox; 25 import org.openstreetmap.josm.gui.tagging.ac.AutoCompEvent; 26 import org.openstreetmap.josm.gui.tagging.ac.AutoCompListener; 27 import org.openstreetmap.josm.gui.tagging.ac.AutoCompTextField; 24 28 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetHandler; 25 29 import org.openstreetmap.josm.testutils.JOSMTestRules; 26 30 … … 31 35 * @author Michael Zangl 32 36 */ 33 37 @Disabled 34 public abstract class AbstractRelationEditorActionTest {38 public abstract class AbstractRelationEditorActionTest implements AutoCompListener { 35 39 /** 36 40 * Platform for tooltips. 37 41 */ … … 46 50 private IRelationEditor editor; 47 51 private MemberTable memberTable; 48 52 private MemberTableModel memberTableModel; 49 private AutoComp letingTextFieldtfRole;53 private AutoCompTextField<AutoCompletionItem> tfRole; 50 54 private TagEditorModel tagModel; 51 55 52 56 protected final IRelationEditorActionAccess relationEditorAccess = new IRelationEditorActionAccess() { 53 57 54 58 @Override 55 public AutoComp letingTextFieldgetTextFieldRole() {59 public AutoCompTextField<AutoCompletionItem> getTextFieldRole() { 56 60 return tfRole; 57 61 } 58 62 … … 102 106 } 103 107 104 108 @Override 105 public Collection<OsmPrimitive> get Selection() {109 public Collection<OsmPrimitive> getPrimitives() { 106 110 return Collections.<OsmPrimitive>singleton(orig); 107 111 } 108 112 }); … … 109 113 selectionTableModel = new SelectionTableModel(layer); 110 114 selectionTable = new SelectionTable(selectionTableModel, memberTableModel); 111 115 editor = GenericRelationEditorTest.newRelationEditor(orig, layer); 112 tfRole = new AutoComp letingTextField();116 tfRole = new AutoCompTextField<>(); 113 117 tagModel = new TagEditorModel(); 114 memberTable = new MemberTable(layer, editor.getRelation(), memberTableModel);118 memberTable = new MemberTable(layer, new AutoCompComboBox<String>(), memberTableModel); 115 119 } 120 121 @Override 122 public void autoCompBefore(AutoCompEvent e) { 123 } 124 125 @Override 126 public void autoCompPerformed(AutoCompEvent e) { 127 } 116 128 } -
test/unit/org/openstreetmap/josm/gui/preferences/map/TaggingPresetPreferenceTestIT.java
14 14 import java.util.List; 15 15 import java.util.Locale; 16 16 import java.util.Set; 17 import java.util.concurrent.ExecutionException; 18 import java.util.concurrent.TimeoutException; 17 19 18 20 import org.junit.jupiter.api.BeforeAll; 19 21 import org.junit.jupiter.api.extension.RegisterExtension; … … 22 24 import org.openstreetmap.josm.TestUtils; 23 25 import org.openstreetmap.josm.data.preferences.sources.ExtendedSourceEntry; 24 26 import org.openstreetmap.josm.gui.preferences.AbstractExtendedSourceEntryTestCase; 27 import org.openstreetmap.josm.gui.tagging.presets.Link; 25 28 import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset; 26 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetReader; 27 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetsTest; 28 import org.openstreetmap.josm.gui.tagging.presets.items.Link; 29 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetUtils; 30 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets; 29 31 import org.openstreetmap.josm.spi.preferences.Config; 30 32 import org.openstreetmap.josm.testutils.JOSMTestRules; 31 33 import org.openstreetmap.josm.tools.HttpClient; … … 108 110 109 111 private void testPresets(Set<String> messages, ExtendedSourceEntry source, List<String> ignoredErrors) 110 112 throws SAXException, IOException { 111 Collection<TaggingPreset> presets = TaggingPresetReader.readAll(source.url, true); 113 TaggingPresets.testInitialize(source.url); 114 Collection<TaggingPreset> presets = TaggingPresets.getTaggingPresets(); 112 115 assertFalse(presets.isEmpty()); 113 TaggingPresetsTest.waitForIconLoading(presets); 116 try { 117 TaggingPresetUtils.waitForIconsLoaded(presets, 30); 118 } catch (InterruptedException | ExecutionException | TimeoutException e1) { 119 addOrIgnoreError(source, messages, e1.getMessage(), ignoredErrors); 120 } 114 121 // check that links are correct and not redirections 115 presets.parallelStream().flatMap(x -> x. data.stream().filter(i -> i instanceof Link).map(i -> ((Link) i).getUrl())).forEach(u-> {122 presets.parallelStream().flatMap(x -> x.getAllItems(Link.class, false).stream()).map(link -> link.getUrl()).forEach(url -> { 116 123 try { 117 Response cr = HttpClient.create(new URL(u ), "HEAD").setMaxRedirects(-1).connect();124 Response cr = HttpClient.create(new URL(url), "HEAD").setMaxRedirects(-1).connect(); 118 125 final int code = cr.getResponseCode(); 119 126 if (HttpClient.isRedirect(code)) { 120 127 addOrIgnoreError(source, messages, 121 "Found HTTP redirection for " + u + " -> " + code + " -> " + cr.getHeaderField("Location"), ignoredErrors);128 "Found HTTP redirection for " + url + " -> " + code + " -> " + cr.getHeaderField("Location"), ignoredErrors); 122 129 } else if (code >= 400) { 123 addOrIgnoreError(source, messages, "Found HTTP error for " + u + " -> " + code, ignoredErrors);130 addOrIgnoreError(source, messages, "Found HTTP error for " + url + " -> " + code, ignoredErrors); 124 131 } 125 132 } catch (IOException e) { 126 Logging.error(e);133 addOrIgnoreError(source, messages, e.getMessage(), ignoredErrors); 127 134 } 128 135 }); 129 Collection<String> errorsAndWarnings = Logging.getLastErrorAndWarnings();130 boolean error = false;131 for (String message : errorsAndWarnings) {132 if (message.contains(TaggingPreset.PRESET_ICON_ERROR_MSG_PREFIX)) {133 error = true;134 addOrIgnoreError(source, messages, message, ignoredErrors);135 }136 }137 if (error) {138 Logging.clearLastErrorAndWarnings();139 }140 136 } 141 137 142 138 void addOrIgnoreError(ExtendedSourceEntry source, Set<String> messages, String message, List<String> ignoredErrors) { -
test/unit/org/openstreetmap/josm/gui/tagging/presets/CheckGroupTest.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import static org.junit.jupiter.api.Assertions.assertEquals; 5 import static org.junit.jupiter.api.Assertions.assertFalse; 6 import static org.junit.jupiter.api.Assertions.assertTrue; 7 8 import javax.swing.JPanel; 9 10 import org.junit.jupiter.api.BeforeAll; 11 import org.junit.jupiter.api.Test; 12 import org.openstreetmap.josm.JOSMFixture; 13 14 /** 15 * Unit tests of {@link CheckGroup} class. 16 */ 17 class CheckGroupTest { 18 19 /** 20 * Setup test. 21 */ 22 @BeforeAll 23 public static void setUp() { 24 JOSMFixture.createUnitTestFixture().init(); 25 } 26 27 /** 28 * Unit test for {@link CheckGroup#addToPanel}. 29 */ 30 @Test 31 void testAddToPanel() { 32 CheckGroup cg = (CheckGroup) ItemFactory.build("checkgroup"); 33 JPanel p = new JPanel(); 34 assertEquals(0, p.getComponentCount()); 35 assertFalse(cg.addToPanel(p, TaggingPresetInstance.createTest())); 36 assertTrue(p.getComponentCount() > 0); 37 } 38 } -
test/unit/org/openstreetmap/josm/gui/tagging/presets/CheckTest.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import static org.junit.jupiter.api.Assertions.assertEquals; 5 import static org.junit.jupiter.api.Assertions.assertTrue; 6 7 import javax.swing.JPanel; 8 9 import org.junit.jupiter.api.BeforeAll; 10 import org.junit.jupiter.api.Test; 11 import org.openstreetmap.josm.JOSMFixture; 12 13 /** 14 * Unit tests of {@link Check} class. 15 */ 16 class CheckTest { 17 18 /** 19 * Setup test. 20 */ 21 @BeforeAll 22 public static void setUp() { 23 JOSMFixture.createUnitTestFixture().init(true); 24 } 25 26 /** 27 * Unit test for {@link Check#addToPanel}. 28 */ 29 @Test 30 void testAddToPanel() { 31 JPanel p = new JPanel(); 32 assertEquals(0, p.getComponentCount()); 33 assertTrue(ItemFactory.build("check").addToPanel(p, TaggingPresetInstance.createTest())); 34 assertTrue(p.getComponentCount() > 0); 35 } 36 } -
test/unit/org/openstreetmap/josm/gui/tagging/presets/ComboTest.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import static org.junit.jupiter.api.Assertions.assertEquals; 5 import static org.junit.jupiter.api.Assertions.assertTrue; 6 7 import java.awt.Color; 8 9 import javax.swing.JPanel; 10 11 import org.junit.jupiter.api.BeforeAll; 12 import org.junit.jupiter.api.Test; 13 import org.openstreetmap.josm.JOSMFixture; 14 import org.openstreetmap.josm.data.osm.OsmUtils; 15 import org.openstreetmap.josm.tools.I18n; 16 17 /** 18 * Unit tests of {@link Combo} class. 19 */ 20 class ComboTest { 21 22 /** 23 * Setup test. 24 */ 25 @BeforeAll 26 public static void setUp() { 27 JOSMFixture.createUnitTestFixture().init(true); 28 I18n.init(); 29 I18n.set("de"); 30 } 31 32 /** 33 * Unit test for {@link Combo#addToPanel}. 34 */ 35 @Test 36 void testAddToPanel() { 37 JPanel p = new JPanel(); 38 assertEquals(0, p.getComponentCount()); 39 assertTrue(ItemFactory.build("check").addToPanel(p, TaggingPresetInstance.createTest())); 40 assertTrue(p.getComponentCount() > 0); 41 } 42 43 void is(String expected, Combo combo, TaggingPresetInstance presetInstance) { 44 JPanel p = new JPanel(); 45 combo.addToPanel(p, presetInstance); 46 Combo.Instance instance = (Combo.Instance) presetInstance.getInstance(combo); 47 assertEquals(expected, instance.getSelectedItem().getValue()); 48 } 49 50 /** 51 * Unit test for {@link ComboMultiSelect#useLastAsDefault} and {@link ComboMultiSelect.Instance#getInitialValue} 52 */ 53 @Test 54 void testUseLastAsDefault() { 55 KeyedItem.LAST_VALUES.clear(); 56 KeyedItem.LAST_VALUES.put("addr:country", "AT"); 57 Combo.PROP_FILL_DEFAULT.put(false); 58 59 TaggingPresetInstance way = TaggingPresetInstance.createTest(OsmUtils.createPrimitive("way")); 60 TaggingPresetInstance wayTagged = TaggingPresetInstance.createTest(OsmUtils.createPrimitive("way highway=residential")); 61 TaggingPresetInstance wayAT = TaggingPresetInstance.createTest(OsmUtils.createPrimitive("way addr:country=AT")); 62 TaggingPresetInstance waySI = TaggingPresetInstance.createTest(OsmUtils.createPrimitive("way addr:country=SI")); 63 TaggingPresetInstance waysATSI = TaggingPresetInstance.createTest( 64 OsmUtils.createPrimitive("way addr:country=AT"), OsmUtils.createPrimitive("way addr:country=SI")); 65 66 String desc = "combo key=addr:country values_from=java.util.Locale#getISOCountries"; 67 Combo combo = (Combo) ItemFactory.build(desc); 68 combo.endElement(); 69 is("", combo, way); 70 is("", combo, wayTagged); 71 is("AT", combo, wayAT); 72 is("SI", combo, waySI); 73 is(Combo.DIFFERENT, combo, waysATSI); 74 75 combo = (Combo) ItemFactory.build(desc + " default=AT"); 76 combo.endElement(); 77 is("AT", combo, way); 78 is("", combo, wayTagged); 79 is("AT", combo, wayAT); 80 is("SI", combo, waySI); 81 is(Combo.DIFFERENT, combo, waysATSI); 82 83 Combo.PROP_FILL_DEFAULT.put(true); 84 85 is("AT", combo, way); 86 is("AT", combo, wayTagged); 87 is("AT", combo, wayAT); 88 is("SI", combo, waySI); 89 is(Combo.DIFFERENT, combo, waysATSI); 90 91 Combo.PROP_FILL_DEFAULT.put(false); 92 93 combo = (Combo) ItemFactory.build(desc + " use_last_as_default=true"); 94 combo.endElement(); 95 96 is("AT", combo, way); 97 is("", combo, wayTagged); 98 is("AT", combo, wayAT); 99 is("SI", combo, waySI); 100 is(Combo.DIFFERENT, combo, waysATSI); 101 102 combo = (Combo) ItemFactory.build(desc + " use_last_as_default=force"); 103 combo.endElement(); 104 105 is("AT", combo, way); 106 is("AT", combo, wayTagged); 107 is("AT", combo, wayAT); 108 is("SI", combo, waySI); 109 is(Combo.DIFFERENT, combo, waysATSI); 110 111 KeyedItem.LAST_VALUES.clear(); 112 } 113 114 @Test 115 void testColor() { 116 TaggingPresetInstance presetInstance = TaggingPresetInstance.createTest(); 117 Combo combo = (Combo) ItemFactory.build("combo key=colour values=red;green;blue;black values_context=color delimiter=;"); 118 combo.endElement(); 119 combo.addToPanel(new JPanel(), presetInstance); 120 Combo.Instance comboInstance = (Combo.Instance) presetInstance.getInstance(combo); 121 122 assertEquals(5, comboInstance.combobox.getItemCount()); 123 124 PresetListEntry.Instance i = comboInstance.find("red"); 125 comboInstance.combobox.setSelectedItem(i); 126 assertEquals("red", comboInstance.getSelectedItem().getValue()); 127 assertEquals("Rot", comboInstance.getSelectedItem().toString()); 128 assertEquals(new Color(0xFF0000), comboInstance.getColor()); 129 130 i = comboInstance.find("green"); 131 comboInstance.combobox.setSelectedItem(i); 132 assertEquals("green", i.getValue()); 133 assertEquals("Grün", i.toString()); 134 assertEquals(new Color(0x008000), comboInstance.getColor()); 135 136 comboInstance.combobox.setSelectedItem("#135"); 137 assertEquals("#135", comboInstance.getSelectedItem().getValue()); 138 assertEquals(new Color(0x113355), comboInstance.getColor()); 139 140 comboInstance.combobox.setSelectedItem("#123456"); 141 assertEquals("#123456", comboInstance.getSelectedItem().getValue()); 142 assertEquals(new Color(0x123456), comboInstance.getColor()); 143 144 comboInstance.setColor(new Color(0x448822)); 145 assertEquals("#448822", comboInstance.getSelectedItem().getValue()); 146 } 147 } -
test/unit/org/openstreetmap/josm/gui/tagging/presets/ItemSeparatorTest.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import static org.junit.jupiter.api.Assertions.assertEquals; 5 import static org.junit.jupiter.api.Assertions.assertFalse; 6 import static org.junit.jupiter.api.Assertions.assertTrue; 7 8 import javax.swing.JPanel; 9 10 import org.junit.jupiter.api.BeforeAll; 11 import org.junit.jupiter.api.Test; 12 import org.openstreetmap.josm.JOSMFixture; 13 14 /** 15 * Unit tests of {@link ItemSeparator} class. 16 */ 17 class ItemSeparatorTest { 18 19 /** 20 * Setup test. 21 */ 22 @BeforeAll 23 public static void setUp() { 24 JOSMFixture.createUnitTestFixture().init(true); 25 } 26 27 /** 28 * Unit test for {@link ItemSeparator#addToPanel}. 29 */ 30 @Test 31 void testAddToPanel() { 32 JPanel p = new JPanel(); 33 assertEquals(0, p.getComponentCount()); 34 assertFalse(ItemFactory.build("item_separator").addToPanel(p, TaggingPresetInstance.createTest())); 35 assertTrue(p.getComponentCount() > 0); 36 } 37 } -
test/unit/org/openstreetmap/josm/gui/tagging/presets/KeyTest.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import static org.junit.jupiter.api.Assertions.assertEquals; 5 import static org.junit.jupiter.api.Assertions.assertFalse; 6 7 import javax.swing.JPanel; 8 9 import org.junit.jupiter.api.BeforeAll; 10 import org.junit.jupiter.api.Test; 11 import org.openstreetmap.josm.JOSMFixture; 12 13 /** 14 * Unit tests of {@link Key} class. 15 */ 16 class KeyTest { 17 18 /** 19 * Setup test. 20 */ 21 @BeforeAll 22 public static void setUp() { 23 JOSMFixture.createUnitTestFixture().init(); 24 } 25 26 /** 27 * Unit test for {@link Key#addToPanel}. 28 */ 29 @Test 30 void testAddToPanel() { 31 JPanel p = new JPanel(); 32 assertEquals(0, p.getComponentCount()); 33 assertFalse(ItemFactory.build("key").addToPanel(p, TaggingPresetInstance.createTest())); 34 assertEquals(0, p.getComponentCount()); 35 } 36 } -
test/unit/org/openstreetmap/josm/gui/tagging/presets/LabelTest.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import static org.junit.jupiter.api.Assertions.assertEquals; 5 import static org.junit.jupiter.api.Assertions.assertTrue; 6 7 import javax.swing.JPanel; 8 9 import org.junit.jupiter.api.BeforeAll; 10 import org.junit.jupiter.api.Test; 11 import org.openstreetmap.josm.JOSMFixture; 12 13 /** 14 * Unit tests of {@link Label} class. 15 */ 16 class LabelTest { 17 18 /** 19 * Setup test. 20 */ 21 @BeforeAll 22 public static void setUp() { 23 JOSMFixture.createUnitTestFixture().init(); 24 } 25 26 /** 27 * Unit test for {@link Label#addToPanel}. 28 */ 29 @Test 30 void testAddToPanel() { 31 JPanel p = new JPanel(); 32 assertEquals(0, p.getComponentCount()); 33 assertTrue(ItemFactory.build("label").addToPanel(p, TaggingPresetInstance.createTest())); 34 assertTrue(p.getComponentCount() > 0); 35 } 36 } -
test/unit/org/openstreetmap/josm/gui/tagging/presets/LinkTest.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import static org.junit.jupiter.api.Assertions.assertEquals; 5 import static org.junit.jupiter.api.Assertions.assertFalse; 6 import static org.junit.jupiter.api.Assertions.assertTrue; 7 import static org.openstreetmap.josm.gui.tagging.presets.ItemFactory.build; 8 9 import javax.swing.JPanel; 10 11 import org.junit.jupiter.api.BeforeAll; 12 import org.junit.jupiter.api.Test; 13 import org.openstreetmap.josm.JOSMFixture; 14 import org.openstreetmap.josm.spi.preferences.Config; 15 16 /** 17 * Unit tests of {@link Link} class. 18 */ 19 class LinkTest { 20 21 /** 22 * Setup test. 23 */ 24 @BeforeAll 25 public static void setUp() { 26 JOSMFixture.createUnitTestFixture().init(); 27 } 28 29 /** 30 * Unit test for {@link Link#addToPanel}. 31 */ 32 @Test 33 void testAddToPanel() { 34 JPanel p = new JPanel(); 35 Link l = (Link) build("link"); 36 assertFalse(l.addToPanel(p, TaggingPresetInstance.createTest())); 37 assertEquals(0, p.getComponentCount()); 38 39 p = new JPanel(); 40 l = (Link) build("link href=" + Config.getUrls().getJOSMWebsite()); 41 assertFalse(l.addToPanel(p, TaggingPresetInstance.createTest())); 42 assertTrue(p.getComponentCount() > 0); 43 44 p = new JPanel(); 45 l = (Link) build("link locale_href=" + Config.getUrls().getJOSMWebsite()); 46 assertFalse(l.addToPanel(p, TaggingPresetInstance.createTest())); 47 assertTrue(p.getComponentCount() > 0); 48 } 49 } -
test/unit/org/openstreetmap/josm/gui/tagging/presets/MultiSelectTest.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import static org.junit.jupiter.api.Assertions.assertEquals; 5 6 import javax.swing.JPanel; 7 8 import org.junit.jupiter.api.BeforeAll; 9 import org.junit.jupiter.api.Test; 10 import org.openstreetmap.josm.JOSMFixture; 11 12 /** 13 * Unit tests of {@link MultiSelect} class. 14 */ 15 class MultiSelectTest { 16 17 /** 18 * Setup test. 19 */ 20 @BeforeAll 21 public static void setUp() { 22 JOSMFixture.createUnitTestFixture().init(); 23 } 24 25 /** 26 * Unit test for {@link MultiSelect#addToPanel}. 27 */ 28 @Test 29 void testAddToPanel() { 30 JPanel p = new JPanel(); 31 assertEquals(0, p.getComponentCount()); 32 // cannot build a cms directly, only combo or multiselect 33 // assertTrue(p.getComponentCount() > 0); 34 } 35 } -
test/unit/org/openstreetmap/josm/gui/tagging/presets/OptionalTest.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import static org.junit.jupiter.api.Assertions.assertEquals; 5 import static org.junit.jupiter.api.Assertions.assertFalse; 6 import static org.junit.jupiter.api.Assertions.assertTrue; 7 8 import javax.swing.JPanel; 9 10 import org.junit.jupiter.api.BeforeAll; 11 import org.junit.jupiter.api.Test; 12 import org.openstreetmap.josm.JOSMFixture; 13 14 /** 15 * Unit tests of {@link Optional} class. 16 */ 17 class OptionalTest { 18 19 /** 20 * Setup test. 21 */ 22 @BeforeAll 23 public static void setUp() { 24 JOSMFixture.createUnitTestFixture().init(); 25 } 26 27 /** 28 * Unit test for {@link Optional#addToPanel}. 29 */ 30 @Test 31 void testAddToPanel() { 32 JPanel p = new JPanel(); 33 assertEquals(0, p.getComponentCount()); 34 assertFalse(ItemFactory.build("optional").addToPanel(p, TaggingPresetInstance.createTest())); 35 // do not add anything if there are no optional items 36 assertTrue(p.getComponentCount() == 0); 37 } 38 } -
test/unit/org/openstreetmap/josm/gui/tagging/presets/PresetClassificationsTest.java
5 5 import static org.junit.jupiter.api.Assertions.assertTrue; 6 6 7 7 import java.io.IOException; 8 import java.util.Collection;9 8 import java.util.Collections; 10 9 import java.util.EnumSet; 11 10 import java.util.List; … … 20 19 import org.openstreetmap.josm.data.osm.Way; 21 20 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetSelector.PresetClassification; 22 21 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetSelector.PresetClassifications; 22 import org.openstreetmap.josm.tools.Logging; 23 23 import org.xml.sax.SAXException; 24 24 25 25 /** … … 37 37 @BeforeAll 38 38 public static void setUp() throws IOException, SAXException { 39 39 JOSMFixture.createUnitTestFixture().init(); 40 final Collection<TaggingPreset> presets = TaggingPresetReader.readAll("resource://data/defaultpresets.xml", true);41 classifications.loadPresets( presets);40 TaggingPresets.testInitialize("resource://data/defaultpresets.xml"); 41 classifications.loadPresets(TaggingPresets.getTaggingPresets()); 42 42 } 43 43 44 44 private List<PresetClassification> getMatchingPresets(String searchText, OsmPrimitive w) { … … 47 47 } 48 48 49 49 private List<String> getMatchingPresetNames(String searchText, OsmPrimitive w) { 50 return getMatchingPresets(searchText, w).stream().map(x -> x.preset. name).collect(Collectors.toList());50 return getMatchingPresets(searchText, w).stream().map(x -> x.preset.getBaseName()).collect(Collectors.toList()); 51 51 } 52 52 53 53 /** … … 60 60 w.addNode(n1); 61 61 w.addNode(new Node()); 62 62 w.addNode(new Node()); 63 Logging.info(getMatchingPresetNames("building", w).toString()); 63 64 assertFalse(getMatchingPresetNames("building", w).contains("Building"), "unclosed way should not match building preset"); 64 65 w.addNode(n1); 66 Logging.info(getMatchingPresetNames("building", w).toString()); 65 67 assertTrue(getMatchingPresetNames("building", w).contains("Building"), "closed way should match building preset"); 66 68 } 67 69 -
test/unit/org/openstreetmap/josm/gui/tagging/presets/PresetLinkTest.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import static org.junit.jupiter.api.Assertions.assertEquals; 5 import static org.junit.jupiter.api.Assertions.assertFalse; 6 import static org.junit.jupiter.api.Assertions.assertTrue; 7 8 import javax.swing.JPanel; 9 10 import org.junit.jupiter.api.extension.RegisterExtension; 11 import org.junit.jupiter.api.Test; 12 import org.openstreetmap.josm.testutils.JOSMTestRules; 13 14 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 15 16 /** 17 * Unit tests of {@link PresetLink} class. 18 */ 19 class PresetLinkTest { 20 21 /** 22 * Setup test. 23 */ 24 @RegisterExtension 25 @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD") 26 public JOSMTestRules rule = new JOSMTestRules().presets(); 27 28 /** 29 * Unit test for {@link PresetLink#addToPanel}. 30 */ 31 @Test 32 void testAddToPanel() { 33 PresetLink l = (PresetLink) ItemFactory.build("preset_link preset_name=River"); 34 JPanel p = new JPanel(); 35 assertEquals(0, p.getComponentCount()); 36 assertFalse(l.addToPanel(p, TaggingPresetInstance.createTest())); 37 assertTrue(p.getComponentCount() > 0); 38 } 39 } -
test/unit/org/openstreetmap/josm/gui/tagging/presets/PresetListEntryTest.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; 5 import static org.junit.jupiter.api.Assertions.assertTrue; 6 7 import javax.swing.JPanel; 8 9 import org.junit.jupiter.api.BeforeAll; 10 import org.junit.jupiter.api.Test; 11 import org.openstreetmap.josm.JOSMFixture; 12 13 /** 14 * Unit tests of {@link PresetListEntry} class. 15 */ 16 class PresetListEntryTest { 17 18 /** 19 * Setup test. 20 */ 21 @BeforeAll 22 public static void setUp() { 23 JOSMFixture.createUnitTestFixture().init(true); 24 } 25 26 /** 27 * Non-regression test for ticket <a href="https://josm.openstreetmap.de/ticket/12416">#12416</a>. 28 */ 29 @Test 30 void testTicket12416() { 31 Combo combo = (Combo) ItemFactory.build("combo key=foo"); 32 PresetListEntry entry = (PresetListEntry) ItemFactory.build("list_entry value="); 33 combo.addItem(entry); 34 35 JPanel panel = new JPanel(); 36 TaggingPresetInstance instance = TaggingPresetInstance.createTest(); 37 combo.addToPanel(panel, instance); 38 assertTrue(entry.newInstance((Combo.Instance) instance.getInstance(combo)).getListDisplay(200).contains(" ")); 39 } 40 41 /** 42 * Non-regression test for ticket <a href="https://josm.openstreetmap.de/ticket/21550">#21550</a> 43 */ 44 @Test 45 void testTicket21550() { 46 Combo combo = (Combo) ItemFactory.build("combo key=foo"); 47 PresetListEntry entry = (PresetListEntry) ItemFactory.build("list_entry value="); 48 combo.addItem(entry); 49 50 JPanel panel = new JPanel(); 51 TaggingPresetInstance instance = TaggingPresetInstance.createTest(); 52 combo.addToPanel(panel, instance); 53 assertDoesNotThrow(entry.newInstance((Combo.Instance) instance.getInstance(combo))::getCount); 54 } 55 } -
test/unit/org/openstreetmap/josm/gui/tagging/presets/RolesTest.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import static org.junit.jupiter.api.Assertions.assertEquals; 5 import static org.junit.jupiter.api.Assertions.assertFalse; 6 import static org.junit.jupiter.api.Assertions.assertTrue; 7 8 import javax.swing.JPanel; 9 10 import org.junit.jupiter.api.BeforeAll; 11 import org.junit.jupiter.api.Test; 12 import org.openstreetmap.josm.JOSMFixture; 13 14 /** 15 * Unit tests of {@link Roles} class. 16 */ 17 class RolesTest { 18 19 /** 20 * Setup test. 21 */ 22 @BeforeAll 23 public static void setUp() { 24 JOSMFixture.createUnitTestFixture().init(); 25 } 26 27 /** 28 * Unit test for {@link Roles#addToPanel}. 29 */ 30 @Test 31 void testAddToPanel() { 32 JPanel p = new JPanel(); 33 assertEquals(0, p.getComponentCount()); 34 assertFalse(ItemFactory.build("roles").addToPanel(p, TaggingPresetInstance.createTest())); 35 // do not add anything if there are no roles 36 assertTrue(p.getComponentCount() == 0); 37 } 38 } -
test/unit/org/openstreetmap/josm/gui/tagging/presets/SpaceTest.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import static org.junit.jupiter.api.Assertions.assertEquals; 5 import static org.junit.jupiter.api.Assertions.assertFalse; 6 import static org.junit.jupiter.api.Assertions.assertTrue; 7 8 import javax.swing.JPanel; 9 10 import org.junit.jupiter.api.BeforeAll; 11 import org.junit.jupiter.api.Test; 12 import org.openstreetmap.josm.JOSMFixture; 13 14 /** 15 * Unit tests of {@link Space} class. 16 */ 17 class SpaceTest { 18 19 /** 20 * Setup test. 21 */ 22 @BeforeAll 23 public static void setUp() { 24 JOSMFixture.createUnitTestFixture().init(); 25 } 26 27 /** 28 * Unit test for {@link Space#addToPanel}. 29 */ 30 @Test 31 void testAddToPanel() { 32 JPanel p = new JPanel(); 33 assertEquals(0, p.getComponentCount()); 34 assertFalse(ItemFactory.build("space").addToPanel(p, TaggingPresetInstance.createTest())); 35 assertTrue(p.getComponentCount() > 0); 36 } 37 } -
test/unit/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetInstanceTest.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 5 import org.junit.jupiter.api.Test; 6 import org.junit.jupiter.api.extension.RegisterExtension; 7 import org.openstreetmap.josm.data.osm.OsmUtils; 8 import org.openstreetmap.josm.testutils.JOSMTestRules; 9 import org.openstreetmap.josm.tools.template_engine.TemplateEntry; 10 import org.openstreetmap.josm.tools.template_engine.TemplateParser; 11 12 import static org.junit.jupiter.api.Assertions.assertEquals; 13 14 /** 15 * Unit tests of {@link TaggingPresetInstance} 16 */ 17 class TaggingPresetInstanceTest { 18 19 /** 20 * Setup rule 21 */ 22 @RegisterExtension 23 @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD") 24 public JOSMTestRules test = new JOSMTestRules(); 25 26 /** 27 * Tests {@link TemplateEntry} evaluation 28 * @throws Exception in case something goes wrong 29 */ 30 @Test 31 void testTemplate() throws Exception { 32 TaggingPresetInstance instance = TaggingPresetInstance.createTest( 33 OsmUtils.createPrimitive("relation route=bus ref=42 name=xxx from=Foo to=Bar")); 34 TemplateEntry templateEntry = new TemplateParser("Bus {ref}: {from} -> {to}").parse(); 35 assertEquals("Bus 42: Foo -> Bar", templateEntry.getText(instance)); 36 templateEntry = new TemplateParser("?{route=train 'Train'|route=bus 'Bus'|'X'} {ref}: {from} -> {to}").parse(); 37 assertEquals("Bus 42: Foo -> Bar", templateEntry.getText(instance)); 38 } 39 } -
test/unit/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItemGuiSupportTest.java
1 // License: GPL. For details, see LICENSE file.2 package org.openstreetmap.josm.gui.tagging.presets;3 4 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;5 import org.junit.jupiter.api.Test;6 import org.junit.jupiter.api.extension.RegisterExtension;7 import org.openstreetmap.josm.data.osm.OsmPrimitive;8 import org.openstreetmap.josm.data.osm.OsmUtils;9 import org.openstreetmap.josm.data.osm.Tag;10 import org.openstreetmap.josm.testutils.JOSMTestRules;11 import org.openstreetmap.josm.tools.template_engine.TemplateEntry;12 import org.openstreetmap.josm.tools.template_engine.TemplateParser;13 14 import java.util.ArrayList;15 import java.util.Arrays;16 import java.util.Collection;17 import java.util.Collections;18 19 import static org.junit.jupiter.api.Assertions.assertEquals;20 21 /**22 * Unit tests of {@link TaggingPresetItemGuiSupport}23 */24 class TaggingPresetItemGuiSupportTest {25 26 /**27 * Setup rule28 */29 @RegisterExtension30 @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")31 public JOSMTestRules test = new JOSMTestRules();32 33 /**34 * Tests {@link TemplateEntry} evaluation35 */36 @Test37 void testTemplate() throws Exception {38 ArrayList<Tag> tags = new ArrayList<>(Arrays.asList(39 new Tag("route", "bus"),40 new Tag("name", "xxx"),41 new Tag("from", "Foo"),42 new Tag("to", "Bar")));43 Collection<OsmPrimitive> primitives = Collections.singleton(44 OsmUtils.createPrimitive("relation ref=42"));45 46 TaggingPresetItemGuiSupport support = TaggingPresetItemGuiSupport.create(false, primitives, () -> tags);47 TemplateEntry templateEntry = new TemplateParser("Bus {ref}: {from} -> {to}").parse();48 assertEquals("Bus 42: Foo -> Bar", templateEntry.getText(support));49 templateEntry = new TemplateParser("?{route=train 'Train'|route=bus 'Bus'|'X'} {ref}: {from} -> {to}").parse();50 assertEquals("Bus 42: Foo -> Bar", templateEntry.getText(support));51 }52 } -
test/unit/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetReaderTest.java
4 4 import static org.CustomMatchers.hasSize; 5 5 import static org.hamcrest.MatcherAssert.assertThat; 6 6 import static org.junit.jupiter.api.Assertions.assertEquals; 7 import static org.junit.jupiter.api.Assertions.assertTrue;8 7 import static org.junit.jupiter.api.Assertions.fail; 9 8 10 9 import java.io.IOException; … … 16 15 import org.junit.jupiter.api.Test; 17 16 import org.junit.jupiter.api.extension.RegisterExtension; 18 17 import org.openstreetmap.josm.TestUtils; 19 import org.openstreetmap.josm.gui.tagging.presets.items.Check;20 import org.openstreetmap.josm.gui.tagging.presets.items.Key;21 18 import org.openstreetmap.josm.testutils.JOSMTestRules; 22 19 import org.xml.sax.SAXException; 23 20 … … 27 24 * Unit tests of {@link TaggingPresetReader} class. 28 25 */ 29 26 class TaggingPresetReaderTest { 30 31 27 /** 32 * Setup rule28 * Setup test. 33 29 */ 34 30 @RegisterExtension 35 31 @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD") 36 public JOSMTestRules test= new JOSMTestRules();32 public JOSMTestRules rule = new JOSMTestRules(); 37 33 38 34 /** 39 35 * #8954 - last checkbox in the preset is not added … … 43 39 @Test 44 40 void testTicket8954() throws SAXException, IOException { 45 41 String presetfile = TestUtils.getRegressionDataFile(8954, "preset.xml"); 46 final Collection<TaggingPreset> presets = TaggingPresetReader.readAll(presetfile, false); 42 TaggingPresets.testInitialize(presetfile); 43 final Collection<TaggingPreset> presets = TaggingPresets.getTaggingPresets(); 47 44 Assert.assertEquals("Number of preset items", 1, presets.size()); 48 45 final TaggingPreset preset = presets.iterator().next(); 49 Assert.assertEquals("Number of entries", 1, preset. data.size());50 final TaggingPresetItem item = preset.data.get(0);46 Assert.assertEquals("Number of entries", 1, preset.getAllItems().size()); 47 final Item item = preset.getAllItems().get(0); 51 48 Assert.assertTrue("Entry is not checkbox", item instanceof Check); 49 TaggingPresets.cleanUp(); 52 50 } 53 51 54 52 /** … … 58 56 */ 59 57 @Test 60 58 void testNestedChunks() throws SAXException, IOException { 61 final Collection<TaggingPreset> presets = TaggingPresetReader.readAll(TestUtils.getTestDataRoot() + "preset_chunk.xml", true); 59 TaggingPresets.testInitialize(TestUtils.getTestDataRoot() + "preset_chunk.xml"); 60 final Collection<TaggingPreset> presets = TaggingPresets.getTaggingPresets(); 62 61 assertThat(presets, hasSize(1)); 63 62 final TaggingPreset abc = presets.iterator().next(); 64 assertTrue(abc.data.stream().allMatch(Key.class::isInstance)); 65 final List<String> keys = abc. data.stream().map(x -> ((Key) x).key).collect(Collectors.toList());63 64 final List<String> keys = abc.getAllItems(Key.class, true).stream().map(x -> x.getKey()).collect(Collectors.toList()); 66 65 assertEquals("[A1, A2, A3, B1, B2, B3, C1, C2, C3]", keys.toString()); 66 TaggingPresets.cleanUp(); 67 67 } 68 68 69 69 /** … … 74 74 @Test 75 75 void testExternalEntityResolving() throws IOException { 76 76 try { 77 TaggingPresetReader.read All(TestUtils.getTestDataRoot() + "preset_external_entity.xml", true);77 TaggingPresetReader.read(TestUtils.getTestDataRoot() + "preset_external_entity.xml", true); 78 78 fail("Reading a file with external entities should throw an SAXParseException!"); 79 79 } catch (SAXException e) { 80 80 String expected = "DOCTYPE is disallowed when the feature \"http://apache.org/xml/features/disallow-doctype-decl\" set to true."; 81 81 assertEquals(expected, e.getMessage()); 82 82 } 83 TaggingPresets.cleanUp(); 83 84 } 84 85 85 86 /** … … 91 92 @Test 92 93 void testReadDefaulPresets() throws SAXException, IOException { 93 94 String presetfile = "resource://data/defaultpresets.xml"; 94 final Collection<TaggingPreset> presets = TaggingPresetReader.readAll(presetfile, true); 95 TaggingPresets.testInitialize(presetfile); 96 final Collection<TaggingPreset> presets = TaggingPresets.getTaggingPresets(); 95 97 Assert.assertTrue("Default presets are empty", presets.size() > 0); 98 TaggingPresets.cleanUp(); 96 99 } 97 100 } -
test/unit/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetSelectorTest.java
28 28 */ 29 29 @Test 30 30 void testIsMatching() { 31 TaggingPreset preset = new TaggingPreset(); 32 preset.name = "estação de bombeiros"; // fire_station in brazilian portuguese 31 // fire_station in brazilian portuguese 32 Root root = (Root) ItemFactory.build("presets"); 33 TaggingPreset preset = (TaggingPreset) ItemFactory.build("item name=estação de bombeiros"); 34 root.addItem(preset); 35 TaggingPresets.addRoot(root); 36 33 37 PresetClassification pc = new PresetClassification(preset); 34 38 assertEquals(0, pc.isMatchingName("foo")); 35 39 assertTrue(pc.isMatchingName("estação") > 0); -
test/unit/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetTest.java
3 3 4 4 import static org.junit.jupiter.api.Assertions.assertFalse; 5 5 import static org.junit.jupiter.api.Assertions.assertTrue; 6 import static org.openstreetmap.josm.gui.tagging.presets.ItemFactory.build; 6 7 7 import java.util.EnumSet;8 9 8 import org.junit.jupiter.api.Test; 10 9 import org.junit.jupiter.api.extension.RegisterExtension; 11 10 import org.openstreetmap.josm.data.osm.IPrimitive; 12 11 import org.openstreetmap.josm.data.osm.OsmUtils; 13 import org.openstreetmap.josm.data.osm.search.SearchCompiler;14 12 import org.openstreetmap.josm.data.osm.search.SearchParseError; 15 import org.openstreetmap.josm.gui.tagging.presets.items.Key;16 13 import org.openstreetmap.josm.testutils.JOSMTestRules; 17 14 18 15 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; … … 35 32 */ 36 33 @Test 37 34 void test() throws SearchParseError { 38 Key key = new Key(); 39 key.key = "railway"; 40 key.value = "tram_stop"; 41 TaggingPreset preset = new TaggingPreset(); 42 preset.data.add(key); 35 Key key = (Key) build("key key=railway value=tram_stop"); 43 36 37 TaggingPreset preset = (TaggingPreset) build("item"); 38 preset.addItem(key); 44 39 assertFalse(preset.test(OsmUtils.createPrimitive("node foo=bar"))); 45 40 assertTrue(preset.test(OsmUtils.createPrimitive("node railway=tram_stop"))); 46 41 47 preset.types = EnumSet.of(TaggingPresetType.NODE); 42 preset = (TaggingPreset) build("item type=node"); 43 preset.addItem(key); 48 44 assertTrue(preset.test(OsmUtils.createPrimitive("node railway=tram_stop"))); 49 45 assertFalse(preset.test(OsmUtils.createPrimitive("way railway=tram_stop"))); 50 46 51 preset.matchExpression = SearchCompiler.compile("-public_transport"); 47 preset = (TaggingPreset) build("item type=node match_expression=-public_transport"); 48 preset.addItem(key); 52 49 assertTrue(preset.test(OsmUtils.createPrimitive("node railway=tram_stop"))); 53 50 assertFalse(preset.test(OsmUtils.createPrimitive("node railway=tram_stop public_transport=stop_position"))); 54 51 } -
test/unit/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetValidationTest.java
4 4 import static org.junit.jupiter.api.Assertions.assertEquals; 5 5 import static org.junit.jupiter.api.Assertions.assertTrue; 6 6 7 import java.util.Arrays; 7 import java.util.Collection; 8 import java.util.Collections; 8 9 import java.util.Locale; 9 10 10 11 import javax.swing.JLabel; … … 12 13 import org.junit.jupiter.api.BeforeEach; 13 14 import org.junit.jupiter.api.Test; 14 15 import org.junit.jupiter.api.extension.RegisterExtension; 15 import org.openstreetmap.josm.data.osm.DataSet;16 16 import org.openstreetmap.josm.data.osm.OsmPrimitive; 17 17 import org.openstreetmap.josm.data.osm.OsmUtils; 18 import org.openstreetmap.josm.data.osm.Tag;19 18 import org.openstreetmap.josm.data.validation.OsmValidator; 20 19 import org.openstreetmap.josm.testutils.JOSMTestRules; 21 20 … … 46 45 void testValidate() { 47 46 JLabel label = new JLabel(); 48 47 OsmPrimitive primitive = OsmUtils.createPrimitive("way incline=10m width=1mm opening_hours=\"Mo-Fr 8-10\""); 49 new DataSet(primitive);48 Collection<OsmPrimitive> selection = Collections.singletonList(primitive); 50 49 51 TaggingPresetValidation.validate( primitive, label);50 TaggingPresetValidation.validate(new ReadOnlyTaggingPresetHandler(selection), label); 52 51 53 52 // CHECKSTYLE.OFF: LineLength 54 53 assertTrue(label.isVisible()); … … 60 59 "<li>suspicious tag combination (incline on suspicious object)</li></ul>", label.getToolTipText()); 61 60 // CHECKSTYLE.ON: LineLength 62 61 } 63 64 /**65 * Tests {@link TaggingPresetValidation#applyChangedTags}66 */67 @Test68 void testApplyChangedTags() {69 OsmPrimitive primitive = OsmUtils.createPrimitive("way incline=10m width=1mm opening_hours=\"Mo-Fr 8-10\"");70 new DataSet(primitive);71 OsmPrimitive clone = TaggingPresetValidation.applyChangedTags(primitive, Arrays.asList(new Tag("incline", "20m")));72 assertEquals("20m", clone.get("incline"));73 assertEquals("1mm", clone.get("width"));74 }75 62 } -
test/unit/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetsTest.java
1 1 // License: GPL. For details, see LICENSE file. 2 2 package org.openstreetmap.josm.gui.tagging.presets; 3 3 4 import java.util.Collection;5 import java.util.Objects;6 import java.util.concurrent.ExecutionException;7 import java.util.concurrent.TimeUnit;8 import java.util.concurrent.TimeoutException;9 10 4 import org.junit.jupiter.api.Test; 11 5 import org.junit.jupiter.api.extension.RegisterExtension; 12 6 import org.openstreetmap.josm.testutils.JOSMTestRules; 13 import org.openstreetmap.josm.tools.Logging;14 7 15 8 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 16 9 import net.trajano.commons.testing.UtilityClassTestUtil; … … 35 28 void testUtilityClass() throws ReflectiveOperationException { 36 29 UtilityClassTestUtil.assertUtilityClassWellDefined(TaggingPresets.class); 37 30 } 38 39 /**40 * Wait for asynchronous icon loading41 * @param presets presets collection42 */43 public static void waitForIconLoading(Collection<TaggingPreset> presets) {44 presets.parallelStream().map(TaggingPreset::getIconLoadingTask).filter(Objects::nonNull).forEach(t -> {45 try {46 t.get(30, TimeUnit.SECONDS);47 } catch (InterruptedException | ExecutionException | TimeoutException e) {48 Logging.error(e);49 }50 });51 }52 31 } -
test/unit/org/openstreetmap/josm/gui/tagging/presets/TextTest.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.tagging.presets; 3 4 import static org.junit.jupiter.api.Assertions.assertEquals; 5 import static org.junit.jupiter.api.Assertions.assertTrue; 6 7 import javax.swing.JPanel; 8 9 import org.junit.jupiter.api.BeforeAll; 10 import org.junit.jupiter.api.Test; 11 import org.openstreetmap.josm.JOSMFixture; 12 13 /** 14 * Unit tests of {@link Text} class. 15 */ 16 class TextTest { 17 /** 18 * Setup test. 19 */ 20 @BeforeAll 21 public static void setUp() { 22 JOSMFixture.createUnitTestFixture().init(true); 23 } 24 25 /** 26 * Unit test for {@link Text#addToPanel}. 27 */ 28 @Test 29 void testAddToPanel() { 30 JPanel p = new JPanel(); 31 assertEquals(0, p.getComponentCount()); 32 assertTrue(ItemFactory.build("text").addToPanel(p, TaggingPresetInstance.createTest())); 33 assertTrue(p.getComponentCount() > 0); 34 } 35 } -
test/unit/org/openstreetmap/josm/gui/tagging/presets/items/CheckGroupTest.java
1 // License: GPL. For details, see LICENSE file.2 package org.openstreetmap.josm.gui.tagging.presets.items;3 4 import static org.junit.jupiter.api.Assertions.assertEquals;5 import static org.junit.jupiter.api.Assertions.assertFalse;6 import static org.junit.jupiter.api.Assertions.assertTrue;7 8 import javax.swing.JPanel;9 10 import org.junit.jupiter.api.BeforeAll;11 import org.junit.jupiter.api.Test;12 import org.openstreetmap.josm.JOSMFixture;13 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;14 15 /**16 * Unit tests of {@link CheckGroup} class.17 */18 class CheckGroupTest {19 20 /**21 * Setup test.22 */23 @BeforeAll24 public static void setUp() {25 JOSMFixture.createUnitTestFixture().init();26 }27 28 /**29 * Unit test for {@link CheckGroup#addToPanel}.30 */31 @Test32 void testAddToPanel() {33 CheckGroup cg = new CheckGroup();34 JPanel p = new JPanel();35 assertEquals(0, p.getComponentCount());36 assertFalse(cg.addToPanel(p, TaggingPresetItemGuiSupport.create(false)));37 assertTrue(p.getComponentCount() > 0);38 }39 } -
test/unit/org/openstreetmap/josm/gui/tagging/presets/items/CheckTest.java
Property changes on: test/unit/org/openstreetmap/josm/gui/tagging/presets/items/CheckGroupTest.java ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property
1 // License: GPL. For details, see LICENSE file.2 package org.openstreetmap.josm.gui.tagging.presets.items;3 4 import static org.junit.jupiter.api.Assertions.assertEquals;5 import static org.junit.jupiter.api.Assertions.assertTrue;6 7 import javax.swing.JPanel;8 9 import org.junit.jupiter.api.extension.RegisterExtension;10 import org.junit.jupiter.api.Test;11 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;12 import org.openstreetmap.josm.testutils.JOSMTestRules;13 14 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;15 16 /**17 * Unit tests of {@link Check} class.18 */19 class CheckTest {20 21 /**22 * Setup test.23 */24 @RegisterExtension25 @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")26 public JOSMTestRules test = new JOSMTestRules().main();27 28 /**29 * Unit test for {@link Check#addToPanel}.30 */31 @Test32 void testAddToPanel() {33 JPanel p = new JPanel();34 assertEquals(0, p.getComponentCount());35 assertTrue(new Check().addToPanel(p, TaggingPresetItemGuiSupport.create(false)));36 assertTrue(p.getComponentCount() > 0);37 }38 } -
test/unit/org/openstreetmap/josm/gui/tagging/presets/items/ComboTest.java
Property changes on: test/unit/org/openstreetmap/josm/gui/tagging/presets/items/CheckTest.java ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property
1 // License: GPL. For details, see LICENSE file.2 package org.openstreetmap.josm.gui.tagging.presets.items;3 4 import static org.junit.jupiter.api.Assertions.assertEquals;5 import static org.junit.jupiter.api.Assertions.assertTrue;6 7 import java.awt.Color;8 9 import javax.swing.JPanel;10 11 import org.junit.jupiter.api.Test;12 import org.junit.jupiter.api.extension.RegisterExtension;13 import org.openstreetmap.josm.data.osm.OsmPrimitive;14 import org.openstreetmap.josm.data.osm.OsmUtils;15 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;16 import org.openstreetmap.josm.testutils.JOSMTestRules;17 18 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;19 20 /**21 * Unit tests of {@link Combo} class.22 */23 class ComboTest {24 25 /**26 * Setup test.27 */28 @RegisterExtension29 @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")30 public JOSMTestRules test = new JOSMTestRules().preferences().main().i18n("de");31 32 /**33 * Unit test for {@link Combo#addToPanel}.34 */35 @Test36 void testAddToPanel() {37 JPanel p = new JPanel();38 assertEquals(0, p.getComponentCount());39 assertTrue(new Combo().addToPanel(p, TaggingPresetItemGuiSupport.create(false)));40 assertTrue(p.getComponentCount() > 0);41 }42 43 /**44 * Unit test for {@link ComboMultiSelect#use_last_as_default} and {@link ComboMultiSelect#getInitialValue}45 */46 @Test47 void testUseLastAsDefault() {48 Combo combo = new Combo();49 combo.key = "addr:country";50 combo.values_from = "java.util.Locale#getISOCountries";51 OsmPrimitive way = OsmUtils.createPrimitive("way");52 OsmPrimitive wayTagged = OsmUtils.createPrimitive("way highway=residential");53 OsmPrimitive wayAT = OsmUtils.createPrimitive("way addr:country=AT");54 OsmPrimitive waySI = OsmUtils.createPrimitive("way addr:country=SI");55 KeyedItem.LAST_VALUES.clear();56 KeyedItem.LAST_VALUES.put("addr:country", "AT");57 Combo.PROP_FILL_DEFAULT.put(false);58 combo.use_last_as_default = 0;59 60 combo.addToPanel(new JPanel(), TaggingPresetItemGuiSupport.create(false, way));61 assertEquals("", combo.getSelectedItem().value);62 combo.addToPanel(new JPanel(), TaggingPresetItemGuiSupport.create(false, wayTagged));63 assertEquals("", combo.getSelectedItem().value);64 combo.addToPanel(new JPanel(), TaggingPresetItemGuiSupport.create(false, wayAT));65 assertEquals("AT", combo.getSelectedItem().value);66 combo.addToPanel(new JPanel(), TaggingPresetItemGuiSupport.create(false, waySI));67 assertEquals("SI", combo.getSelectedItem().value);68 combo.addToPanel(new JPanel(), TaggingPresetItemGuiSupport.create(false, wayAT, waySI));69 assertEquals(Combo.DIFFERENT, combo.getSelectedItem().value);70 71 combo.default_ = "AT";72 combo.addToPanel(new JPanel(), TaggingPresetItemGuiSupport.create(false, way));73 assertEquals("AT", combo.getSelectedItem().value);74 combo.addToPanel(new JPanel(), TaggingPresetItemGuiSupport.create(false, wayTagged));75 assertEquals("", combo.getSelectedItem().value);76 combo.addToPanel(new JPanel(), TaggingPresetItemGuiSupport.create(false, wayAT));77 assertEquals("AT", combo.getSelectedItem().value);78 combo.addToPanel(new JPanel(), TaggingPresetItemGuiSupport.create(false, waySI));79 assertEquals("SI", combo.getSelectedItem().value);80 combo.addToPanel(new JPanel(), TaggingPresetItemGuiSupport.create(false, wayAT, waySI));81 assertEquals(Combo.DIFFERENT, combo.getSelectedItem().value);82 83 Combo.PROP_FILL_DEFAULT.put(true);84 combo.addToPanel(new JPanel(), TaggingPresetItemGuiSupport.create(false, way));85 assertEquals("AT", combo.getSelectedItem().value);86 combo.addToPanel(new JPanel(), TaggingPresetItemGuiSupport.create(false, wayTagged));87 assertEquals("AT", combo.getSelectedItem().value);88 combo.addToPanel(new JPanel(), TaggingPresetItemGuiSupport.create(false, wayAT));89 assertEquals("AT", combo.getSelectedItem().value);90 combo.addToPanel(new JPanel(), TaggingPresetItemGuiSupport.create(false, waySI));91 assertEquals("SI", combo.getSelectedItem().value);92 combo.addToPanel(new JPanel(), TaggingPresetItemGuiSupport.create(false, wayAT, waySI));93 assertEquals(Combo.DIFFERENT, combo.getSelectedItem().value);94 Combo.PROP_FILL_DEFAULT.put(false);95 combo.default_ = null;96 97 combo.use_last_as_default = 1; // untagged objects only98 combo.addToPanel(new JPanel(), TaggingPresetItemGuiSupport.create(false, way));99 assertEquals("AT", combo.getSelectedItem().value);100 combo.addToPanel(new JPanel(), TaggingPresetItemGuiSupport.create(false, wayTagged));101 assertEquals("", combo.getSelectedItem().value);102 combo.addToPanel(new JPanel(), TaggingPresetItemGuiSupport.create(false, wayAT));103 assertEquals("AT", combo.getSelectedItem().value);104 combo.addToPanel(new JPanel(), TaggingPresetItemGuiSupport.create(false, waySI));105 assertEquals("SI", combo.getSelectedItem().value);106 combo.addToPanel(new JPanel(), TaggingPresetItemGuiSupport.create(false, wayAT, waySI));107 assertEquals(Combo.DIFFERENT, combo.getSelectedItem().value);108 109 combo.use_last_as_default = 2; // "force" on tagged objects too110 combo.addToPanel(new JPanel(), TaggingPresetItemGuiSupport.create(false, way));111 assertEquals("AT", combo.getSelectedItem().value);112 combo.addToPanel(new JPanel(), TaggingPresetItemGuiSupport.create(false, wayTagged));113 assertEquals("AT", combo.getSelectedItem().value);114 combo.addToPanel(new JPanel(), TaggingPresetItemGuiSupport.create(false, wayAT));115 assertEquals("AT", combo.getSelectedItem().value);116 combo.addToPanel(new JPanel(), TaggingPresetItemGuiSupport.create(false, waySI));117 assertEquals("SI", combo.getSelectedItem().value);118 combo.addToPanel(new JPanel(), TaggingPresetItemGuiSupport.create(false, wayAT, waySI));119 assertEquals(Combo.DIFFERENT, combo.getSelectedItem().value);120 combo.use_last_as_default = 0;121 122 KeyedItem.LAST_VALUES.clear();123 }124 125 @Test126 void testColor() {127 Combo combo = new Combo();128 combo.key = "colour";129 combo.values = "red;green;blue;black";130 combo.values_context = "color";131 combo.delimiter = ';';132 combo.addToPanel(new JPanel(), TaggingPresetItemGuiSupport.create(false));133 assertEquals(5, combo.combobox.getItemCount());134 combo.presetListEntries.stream().filter(e -> "red".equals(e.value)).findFirst().ifPresent(combo.combobox::setSelectedItem);135 assertEquals("red", combo.getSelectedItem().value);136 assertEquals("Rot", combo.getSelectedItem().toString());137 assertEquals(new Color(0xFF0000), combo.getColor());138 combo.presetListEntries.stream().filter(e -> "green".equals(e.value)).findFirst().ifPresent(combo.combobox::setSelectedItem);139 assertEquals("green", combo.getSelectedItem().value);140 assertEquals("Grün", combo.getSelectedItem().toString());141 assertEquals(new Color(0x008000), combo.getColor());142 combo.combobox.setSelectedItem("#135");143 assertEquals("#135", combo.getSelectedItem().value);144 assertEquals(new Color(0x113355), combo.getColor());145 combo.combobox.setSelectedItem("#123456");146 assertEquals("#123456", combo.getSelectedItem().value);147 assertEquals(new Color(0x123456), combo.getColor());148 combo.setColor(new Color(0x448822));149 assertEquals("#448822", combo.getSelectedItem().value);150 }151 } -
test/unit/org/openstreetmap/josm/gui/tagging/presets/items/ItemSeparatorTest.java
Property changes on: test/unit/org/openstreetmap/josm/gui/tagging/presets/items/ComboTest.java ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property
1 // License: GPL. For details, see LICENSE file.2 package org.openstreetmap.josm.gui.tagging.presets.items;3 4 import static org.junit.jupiter.api.Assertions.assertEquals;5 import static org.junit.jupiter.api.Assertions.assertFalse;6 import static org.junit.jupiter.api.Assertions.assertTrue;7 8 import javax.swing.JPanel;9 10 import org.junit.jupiter.api.BeforeAll;11 import org.junit.jupiter.api.Test;12 import org.openstreetmap.josm.JOSMFixture;13 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;14 15 /**16 * Unit tests of {@link ItemSeparator} class.17 */18 class ItemSeparatorTest {19 20 /**21 * Setup test.22 */23 @BeforeAll24 public static void setUp() {25 JOSMFixture.createUnitTestFixture().init();26 }27 28 /**29 * Unit test for {@link ItemSeparator#addToPanel}.30 */31 @Test32 void testAddToPanel() {33 JPanel p = new JPanel();34 assertEquals(0, p.getComponentCount());35 assertFalse(new ItemSeparator().addToPanel(p, TaggingPresetItemGuiSupport.create(false)));36 assertTrue(p.getComponentCount() > 0);37 }38 } -
test/unit/org/openstreetmap/josm/gui/tagging/presets/items/KeyTest.java
Property changes on: test/unit/org/openstreetmap/josm/gui/tagging/presets/items/ItemSeparatorTest.java ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property
1 // License: GPL. For details, see LICENSE file.2 package org.openstreetmap.josm.gui.tagging.presets.items;3 4 import static org.junit.jupiter.api.Assertions.assertEquals;5 import static org.junit.jupiter.api.Assertions.assertFalse;6 7 import javax.swing.JPanel;8 9 import org.junit.jupiter.api.BeforeAll;10 import org.junit.jupiter.api.Test;11 import org.openstreetmap.josm.JOSMFixture;12 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;13 14 /**15 * Unit tests of {@link Key} class.16 */17 class KeyTest {18 19 /**20 * Setup test.21 */22 @BeforeAll23 public static void setUp() {24 JOSMFixture.createUnitTestFixture().init();25 }26 27 /**28 * Unit test for {@link Key#addToPanel}.29 */30 @Test31 void testAddToPanel() {32 JPanel p = new JPanel();33 assertEquals(0, p.getComponentCount());34 assertFalse(new Key().addToPanel(p, TaggingPresetItemGuiSupport.create(false)));35 assertEquals(0, p.getComponentCount());36 }37 } -
test/unit/org/openstreetmap/josm/gui/tagging/presets/items/LabelTest.java
Property changes on: test/unit/org/openstreetmap/josm/gui/tagging/presets/items/KeyTest.java ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property
1 // License: GPL. For details, see LICENSE file.2 package org.openstreetmap.josm.gui.tagging.presets.items;3 4 import static org.junit.jupiter.api.Assertions.assertEquals;5 import static org.junit.jupiter.api.Assertions.assertTrue;6 7 import javax.swing.JPanel;8 9 import org.junit.jupiter.api.BeforeAll;10 import org.junit.jupiter.api.Test;11 import org.openstreetmap.josm.JOSMFixture;12 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;13 14 /**15 * Unit tests of {@link Label} class.16 */17 class LabelTest {18 19 /**20 * Setup test.21 */22 @BeforeAll23 public static void setUp() {24 JOSMFixture.createUnitTestFixture().init();25 }26 27 /**28 * Unit test for {@link Label#addToPanel}.29 */30 @Test31 void testAddToPanel() {32 JPanel p = new JPanel();33 assertEquals(0, p.getComponentCount());34 assertTrue(new Label().addToPanel(p, TaggingPresetItemGuiSupport.create(false)));35 assertTrue(p.getComponentCount() > 0);36 }37 } -
test/unit/org/openstreetmap/josm/gui/tagging/presets/items/LinkTest.java
Property changes on: test/unit/org/openstreetmap/josm/gui/tagging/presets/items/LabelTest.java ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property
1 // License: GPL. For details, see LICENSE file.2 package org.openstreetmap.josm.gui.tagging.presets.items;3 4 import static org.junit.jupiter.api.Assertions.assertEquals;5 import static org.junit.jupiter.api.Assertions.assertFalse;6 import static org.junit.jupiter.api.Assertions.assertTrue;7 8 import javax.swing.JPanel;9 10 import org.junit.jupiter.api.BeforeAll;11 import org.junit.jupiter.api.Test;12 import org.openstreetmap.josm.JOSMFixture;13 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;14 import org.openstreetmap.josm.spi.preferences.Config;15 16 /**17 * Unit tests of {@link Link} class.18 */19 class LinkTest {20 21 /**22 * Setup test.23 */24 @BeforeAll25 public static void setUp() {26 JOSMFixture.createUnitTestFixture().init();27 }28 29 /**30 * Unit test for {@link Link#addToPanel}.31 */32 @Test33 void testAddToPanel() {34 Link l = new Link();35 JPanel p = new JPanel();36 assertEquals(0, p.getComponentCount());37 assertFalse(l.addToPanel(p, TaggingPresetItemGuiSupport.create(false)));38 assertEquals(0, p.getComponentCount());39 40 l.href = Config.getUrls().getJOSMWebsite();41 assertFalse(l.addToPanel(p, TaggingPresetItemGuiSupport.create(false)));42 assertTrue(p.getComponentCount() > 0);43 44 l.locale_href = Config.getUrls().getJOSMWebsite();45 assertFalse(l.addToPanel(p, TaggingPresetItemGuiSupport.create(false)));46 assertTrue(p.getComponentCount() > 0);47 }48 } -
test/unit/org/openstreetmap/josm/gui/tagging/presets/items/MultiSelectTest.java
Property changes on: test/unit/org/openstreetmap/josm/gui/tagging/presets/items/LinkTest.java ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property
1 // License: GPL. For details, see LICENSE file.2 package org.openstreetmap.josm.gui.tagging.presets.items;3 4 import static org.junit.jupiter.api.Assertions.assertEquals;5 import static org.junit.jupiter.api.Assertions.assertTrue;6 7 import javax.swing.JPanel;8 9 import org.junit.jupiter.api.BeforeAll;10 import org.junit.jupiter.api.Test;11 import org.openstreetmap.josm.JOSMFixture;12 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;13 14 /**15 * Unit tests of {@link MultiSelect} class.16 */17 class MultiSelectTest {18 19 /**20 * Setup test.21 */22 @BeforeAll23 public static void setUp() {24 JOSMFixture.createUnitTestFixture().init();25 }26 27 /**28 * Unit test for {@link MultiSelect#addToPanel}.29 */30 @Test31 void testAddToPanel() {32 JPanel p = new JPanel();33 assertEquals(0, p.getComponentCount());34 assertTrue(new MultiSelect().addToPanel(p, TaggingPresetItemGuiSupport.create(false)));35 assertTrue(p.getComponentCount() > 0);36 }37 } -
test/unit/org/openstreetmap/josm/gui/tagging/presets/items/OptionalTest.java
Property changes on: test/unit/org/openstreetmap/josm/gui/tagging/presets/items/MultiSelectTest.java ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property
1 // License: GPL. For details, see LICENSE file.2 package org.openstreetmap.josm.gui.tagging.presets.items;3 4 import static org.junit.jupiter.api.Assertions.assertEquals;5 import static org.junit.jupiter.api.Assertions.assertFalse;6 import static org.junit.jupiter.api.Assertions.assertTrue;7 8 import javax.swing.JPanel;9 10 import org.junit.jupiter.api.BeforeAll;11 import org.junit.jupiter.api.Test;12 import org.openstreetmap.josm.JOSMFixture;13 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;14 15 /**16 * Unit tests of {@link Optional} class.17 */18 class OptionalTest {19 20 /**21 * Setup test.22 */23 @BeforeAll24 public static void setUp() {25 JOSMFixture.createUnitTestFixture().init();26 }27 28 /**29 * Unit test for {@link Optional#addToPanel}.30 */31 @Test32 void testAddToPanel() {33 JPanel p = new JPanel();34 assertEquals(0, p.getComponentCount());35 assertFalse(new Optional().addToPanel(p, TaggingPresetItemGuiSupport.create(false)));36 assertTrue(p.getComponentCount() > 0);37 }38 } -
test/unit/org/openstreetmap/josm/gui/tagging/presets/items/PresetLinkTest.java
Property changes on: test/unit/org/openstreetmap/josm/gui/tagging/presets/items/OptionalTest.java ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property
1 // License: GPL. For details, see LICENSE file.2 package org.openstreetmap.josm.gui.tagging.presets.items;3 4 import static org.junit.jupiter.api.Assertions.assertEquals;5 import static org.junit.jupiter.api.Assertions.assertFalse;6 import static org.junit.jupiter.api.Assertions.assertTrue;7 8 import javax.swing.JPanel;9 10 import org.junit.jupiter.api.extension.RegisterExtension;11 import org.junit.jupiter.api.Test;12 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;13 import org.openstreetmap.josm.testutils.JOSMTestRules;14 15 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;16 17 /**18 * Unit tests of {@link PresetLink} class.19 */20 class PresetLinkTest {21 22 /**23 * Setup test.24 */25 @RegisterExtension26 @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")27 public JOSMTestRules rule = new JOSMTestRules().presets();28 29 /**30 * Unit test for {@link PresetLink#addToPanel}.31 */32 @Test33 void testAddToPanel() {34 PresetLink l = new PresetLink();35 l.preset_name = "River";36 JPanel p = new JPanel();37 assertEquals(0, p.getComponentCount());38 assertFalse(l.addToPanel(p, TaggingPresetItemGuiSupport.create(false)));39 assertTrue(p.getComponentCount() > 0);40 }41 } -
test/unit/org/openstreetmap/josm/gui/tagging/presets/items/PresetListEntryTest.java
Property changes on: test/unit/org/openstreetmap/josm/gui/tagging/presets/items/PresetLinkTest.java ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property
1 // License: GPL. For details, see LICENSE file.2 package org.openstreetmap.josm.gui.tagging.presets.items;3 4 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;5 import static org.junit.jupiter.api.Assertions.assertTrue;6 7 import org.junit.jupiter.api.BeforeAll;8 import org.junit.jupiter.api.Test;9 import org.openstreetmap.josm.JOSMFixture;10 11 /**12 * Unit tests of {@link PresetListEntry} class.13 */14 class PresetListEntryTest {15 16 /**17 * Setup test.18 */19 @BeforeAll20 public static void setUp() {21 JOSMFixture.createUnitTestFixture().init();22 }23 24 /**25 * Non-regression test for ticket <a href="https://josm.openstreetmap.de/ticket/12416">#12416</a>.26 */27 @Test28 void testTicket12416() {29 assertTrue(new PresetListEntry("", null).getListDisplay(200).contains(" "));30 }31 32 /**33 * Non-regression test for ticket <a href="https://josm.openstreetmap.de/ticket/21550">#21550</a>34 */35 @Test36 void testTicket21550() {37 final PresetListEntry entry = new PresetListEntry("", new Combo());38 assertDoesNotThrow(entry::getCount);39 }40 } -
test/unit/org/openstreetmap/josm/gui/tagging/presets/items/RolesTest.java
Property changes on: test/unit/org/openstreetmap/josm/gui/tagging/presets/items/PresetListEntryTest.java ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property
1 // License: GPL. For details, see LICENSE file.2 package org.openstreetmap.josm.gui.tagging.presets.items;3 4 import static org.junit.jupiter.api.Assertions.assertEquals;5 import static org.junit.jupiter.api.Assertions.assertFalse;6 import static org.junit.jupiter.api.Assertions.assertTrue;7 8 import javax.swing.JPanel;9 10 import org.junit.jupiter.api.BeforeAll;11 import org.junit.jupiter.api.Test;12 import org.openstreetmap.josm.JOSMFixture;13 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;14 15 /**16 * Unit tests of {@link Roles} class.17 */18 class RolesTest {19 20 /**21 * Setup test.22 */23 @BeforeAll24 public static void setUp() {25 JOSMFixture.createUnitTestFixture().init();26 }27 28 /**29 * Unit test for {@link Roles#addToPanel}.30 */31 @Test32 void testAddToPanel() {33 JPanel p = new JPanel();34 assertEquals(0, p.getComponentCount());35 assertFalse(new Roles().addToPanel(p, TaggingPresetItemGuiSupport.create(false)));36 assertTrue(p.getComponentCount() > 0);37 }38 } -
test/unit/org/openstreetmap/josm/gui/tagging/presets/items/SpaceTest.java
Property changes on: test/unit/org/openstreetmap/josm/gui/tagging/presets/items/RolesTest.java ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property
1 // License: GPL. For details, see LICENSE file.2 package org.openstreetmap.josm.gui.tagging.presets.items;3 4 import static org.junit.jupiter.api.Assertions.assertEquals;5 import static org.junit.jupiter.api.Assertions.assertFalse;6 import static org.junit.jupiter.api.Assertions.assertTrue;7 8 import javax.swing.JPanel;9 10 import org.junit.jupiter.api.BeforeAll;11 import org.junit.jupiter.api.Test;12 import org.openstreetmap.josm.JOSMFixture;13 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;14 15 /**16 * Unit tests of {@link Space} class.17 */18 class SpaceTest {19 20 /**21 * Setup test.22 */23 @BeforeAll24 public static void setUp() {25 JOSMFixture.createUnitTestFixture().init();26 }27 28 /**29 * Unit test for {@link Space#addToPanel}.30 */31 @Test32 void testAddToPanel() {33 JPanel p = new JPanel();34 assertEquals(0, p.getComponentCount());35 assertFalse(new Space().addToPanel(p, TaggingPresetItemGuiSupport.create(false)));36 assertTrue(p.getComponentCount() > 0);37 }38 } -
test/unit/org/openstreetmap/josm/gui/tagging/presets/items/TextTest.java
Property changes on: test/unit/org/openstreetmap/josm/gui/tagging/presets/items/SpaceTest.java ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property
1 // License: GPL. For details, see LICENSE file.2 package org.openstreetmap.josm.gui.tagging.presets.items;3 4 import static org.junit.jupiter.api.Assertions.assertEquals;5 import static org.junit.jupiter.api.Assertions.assertTrue;6 7 import javax.swing.JPanel;8 9 import org.junit.jupiter.api.extension.RegisterExtension;10 import org.junit.jupiter.api.Test;11 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;12 import org.openstreetmap.josm.testutils.JOSMTestRules;13 14 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;15 16 /**17 * Unit tests of {@link Text} class.18 */19 class TextTest {20 21 /**22 * Setup test.23 */24 @RegisterExtension25 @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")26 public JOSMTestRules test = new JOSMTestRules().main();27 28 /**29 * Unit test for {@link Text#addToPanel}.30 */31 @Test32 void testAddToPanel() {33 JPanel p = new JPanel();34 assertEquals(0, p.getComponentCount());35 assertTrue(new Text().addToPanel(p, TaggingPresetItemGuiSupport.create(false)));36 assertTrue(p.getComponentCount() > 0);37 }38 } -
test/unit/org/openstreetmap/josm/tools/OsmPrimitiveImageProviderTest.java
Property changes on: test/unit/org/openstreetmap/josm/gui/tagging/presets/items/TextTest.java ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property
7 7 8 8 import java.awt.Dimension; 9 9 import java.util.EnumSet; 10 import java.util.concurrent.ExecutionException; 11 import java.util.concurrent.TimeoutException; 10 12 11 13 import javax.swing.ImageIcon; 12 14 … … 16 18 import org.openstreetmap.josm.JOSMFixture; 17 19 import org.openstreetmap.josm.data.osm.Node; 18 20 import org.openstreetmap.josm.data.osm.OsmUtils; 21 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetUtils; 19 22 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets; 20 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetsTest;21 23 import org.openstreetmap.josm.testutils.JOSMTestRules; 22 24 import org.openstreetmap.josm.tools.OsmPrimitiveImageProvider.Options; 23 25 … … 45 47 46 48 /** 47 49 * Unit test of {@link OsmPrimitiveImageProvider#getResource}. 50 * @throws InterruptedException if any thread is interrupted 51 * @throws ExecutionException if any thread throws 52 * @throws TimeoutException on timeout 48 53 */ 49 54 @Test 50 void testGetResource() {51 TaggingPreset sTest.waitForIconLoading(TaggingPresets.getTaggingPresets());55 void testGetResource() throws InterruptedException, ExecutionException, TimeoutException { 56 TaggingPresetUtils.waitForIconsLoaded(TaggingPresets.getTaggingPresets(), 30); 52 57 53 58 final EnumSet<Options> noDefault = EnumSet.of(Options.NO_DEFAULT); 54 59 final Dimension iconSize = new Dimension(16, 16);