Ticket #21851: 21851.patch

File 21851.patch, 675.8 KB (added by marcello@…, 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

     
    2828import java.util.regex.Matcher;
    2929import java.util.regex.Pattern;
    3030import java.util.stream.Collectors;
    31 import java.util.stream.Stream;
    3231
    3332import javax.imageio.ImageIO;
    3433import javax.json.Json;
     
    6867import org.openstreetmap.josm.gui.mappaint.styleelement.LineElement;
    6968import org.openstreetmap.josm.gui.mappaint.styleelement.StyleElement;
    7069import org.openstreetmap.josm.gui.preferences.map.TaggingPresetPreference;
     70import org.openstreetmap.josm.gui.tagging.presets.KeyedItem;
    7171import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
    7272import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetReader;
    7373import 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;
     74import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
    7675import org.openstreetmap.josm.io.CachedFile;
    7776import org.openstreetmap.josm.io.OsmTransferException;
    7877import org.openstreetmap.josm.spi.preferences.Config;
     
    166165        Path baseDir = Paths.get("");
    167166        Path imageDir = Paths.get("taginfo-img");
    168167        String imageUrlPrefix;
    169         CachedFile inputFile;
     168        String inputUrl;
    170169        Path outputFile;
    171170        boolean noexit;
    172171
     
    174173            mode = Mode.valueOf(value.toUpperCase(Locale.ENGLISH));
    175174            switch (mode) {
    176175                case MAPPAINT:
    177                     inputFile = new CachedFile("resource://styles/standard/elemstyles.mapcss");
     176                    inputUrl = "resource://styles/standard/elemstyles.mapcss";
    178177                    break;
    179178                case PRESETS:
    180                     inputFile = new CachedFile("resource://data/defaultpresets.xml");
     179                    inputUrl = "resource://data/defaultpresets.xml";
    181180                    break;
    182181                default:
    183                     inputFile = null;
     182                    inputUrl = null;
    184183            }
    185184        }
    186185
    187186        void setInputFile(String value) {
    188             inputFile = new CachedFile(value);
     187            inputUrl = value;
    189188        }
    190189
    191190        void setOutputFile(String value) {
     
    254253
    255254        @Override
    256255        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);
    263261        }
    264262
    265263        List<TagInfoTag> convertPresets(Iterable<TaggingPreset> presets, String descriptionPrefix, boolean addImages) {
     
    267265            final Map<Tag, TagInfoTag> requiredTags = new LinkedHashMap<>();
    268266            final Map<Tag, TagInfoTag> optionalTags = new LinkedHashMap<>();
    269267            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;
    277272                            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());
    279274                                if (item.isKeyRequired()) {
    280275                                    fillTagsMap(requiredTags, item, value, preset.getName(), types,
    281276                                            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);
    283278                                } else {
    284279                                    fillTagsMap(optionalTags, item, value, preset.getName(), types,
    285280                                            descriptionPrefix + TagInfoTag.OPTIONAL_FOR_COUNT + ": ", null);
    286281                                }
    287282                            }
    288                         });
     283                        }
     284                    });
    289285            }
    290286            tags.addAll(requiredTags.values());
    291287            tags.addAll(optionalTags.values());
     
    294290
    295291        private void fillTagsMap(Map<Tag, TagInfoTag> optionalTags, KeyedItem item, String value,
    296292                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) -> {
    298294                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);
    300296                } else {
    301297                    tagInfoTag.descriptions.add(presetName);
    302298                    tagInfoTag.objectTypes.addAll(types);
     
    325321                }
    326322                try {
    327323                    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();
    329326                    final List<TagInfoTag> t = convertPresets(presets, source.title + " ", false);
    330327                    Logging.info("Converted {0} presets of {1} to {2} tags", presets.size(), source.title, t.size());
    331328                    tags.addAll(t);
     
    355352         * @throws ParseException in case of parsing error
    356353         */
    357354        private void parseStyleSheet() throws IOException, ParseException {
    358             try (BufferedReader reader = options.inputFile.getContentReader()) {
     355            try (BufferedReader reader = new CachedFile(options.inputUrl).getContentReader()) {
    359356                MapCSSParser parser = new MapCSSParser(reader, MapCSSParser.LexicalState.DEFAULT);
    360357                styleSource = new MapCSSStyleSource("");
    361358                styleSource.url = "";
  • src/org/openstreetmap/josm/actions/UploadAction.java

     
    240240        ChangesetUpdater.check();
    241241
    242242        final UploadDialog dialog = UploadDialog.getUploadDialog();
    243         dialog.setUploadedPrimitives(apiData);
    244         dialog.initLifeCycle(layer.getDataSet());
     243        dialog.initLifeCycle(layer.getDataSet(), apiData);
    245244        dialog.setVisible(true);
    246245        dialog.rememberUserInput();
    247246        if (dialog.isCanceled()) {
  • src/org/openstreetmap/josm/data/osm/DefaultNameFormatter.java

     
    184184                }
    185185                name.append(n);
    186186            } else {
    187                 preset.nameTemplate.appendText(name, (TemplateEngineDataProvider) node);
     187                preset.getNameTemplate().appendText(name, (TemplateEngineDataProvider) node);
    188188            }
    189189            if (node.isLatLonKnown() && Config.getPref().getBoolean("osm-primitives.showcoor")) {
    190190                name.append(" \u200E(");
     
    252252
    253253                name.append(n);
    254254            } else {
    255                 preset.nameTemplate.appendText(name, (TemplateEngineDataProvider) way);
     255                preset.getNameTemplate().appendText(name, (TemplateEngineDataProvider) way);
    256256            }
    257257
    258258            int nodesNo = way.getRealNodesCount();
     
    355355            }
    356356            result.append(" (").append(relationName).append(", ");
    357357        } else {
    358             preset.nameTemplate.appendText(result, (TemplateEngineDataProvider) relation);
     358            preset.getNameTemplate().appendText(result, (TemplateEngineDataProvider) relation);
    359359            result.append('(');
    360360        }
    361361        return result;
  • src/org/openstreetmap/josm/data/osm/search/SearchCompiler.java

     
    4545import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.MapCSSParser;
    4646import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.ParseException;
    4747import 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;
    5048import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
    5149import org.openstreetmap.josm.tools.AlphanumComparator;
    5250import org.openstreetmap.josm.tools.CheckParameterUtil;
     
    19111909
    19121910            this.presets = TaggingPresets.getTaggingPresets()
    19131911                    .stream()
    1914                     .filter(preset -> !(preset instanceof TaggingPresetMenu || preset instanceof TaggingPresetSeparator))
     1912                    .filter(preset -> preset.getClass() == TaggingPreset.class)
    19151913                    .filter(preset -> presetNameMatch(presetName, preset, matchStrictly))
    19161914                    .collect(Collectors.toList());
    19171915
     
    19291927            if (matchStrictly) {
    19301928                return name.equalsIgnoreCase(preset.getRawName());
    19311929            }
    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);
    19421931        }
    19431932
    19441933        @Override
  • src/org/openstreetmap/josm/data/tagging/ac/AutoCompItemCellRenderer.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.data.tagging.ac;
     3
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
     6import java.awt.Color;
     7import java.awt.Component;
     8import java.awt.Font;
     9import java.util.Map;
     10
     11import javax.swing.ImageIcon;
     12import javax.swing.JLabel;
     13import javax.swing.JList;
     14import javax.swing.ListCellRenderer;
     15
     16import org.openstreetmap.josm.gui.widgets.JosmListCellRenderer;
     17import org.openstreetmap.josm.tools.ImageProvider;
     18import 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 */
     26public 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&amp;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

     
    4141import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.ParseException;
    4242import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.TokenMgrError;
    4343import org.openstreetmap.josm.gui.progress.ProgressMonitor;
     44import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetHandler;
    4445import org.openstreetmap.josm.io.CachedFile;
    4546import org.openstreetmap.josm.io.FileWatcher;
    4647import org.openstreetmap.josm.io.UTFInputStreamReader;
     
    150151    }
    151152
    152153    /**
     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    /**
    153168     * Obtains all {@link TestError}s for the {@link OsmPrimitive} {@code p}.
    154169     * @param p The OSM primitive
    155170     * @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

     
    2121import org.openstreetmap.josm.data.preferences.sources.ValidatorPrefHelper;
    2222import org.openstreetmap.josm.data.validation.Severity;
    2323import org.openstreetmap.josm.data.validation.Test.TagTest;
     24import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetHandler;
    2425import org.openstreetmap.josm.data.validation.TestError;
    2526import org.openstreetmap.josm.tools.GBC;
    2627import org.openstreetmap.josm.tools.Utils;
     
    148149        }
    149150    }
    150151
     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
    151171    @Override
    152172    public void addGui(JPanel testPanel) {
    153173        super.addGui(testPanel);
  • src/org/openstreetmap/josm/data/validation/tests/RelationChecker.java

     
    3030import org.openstreetmap.josm.data.validation.Test;
    3131import org.openstreetmap.josm.data.validation.TestError;
    3232import org.openstreetmap.josm.gui.progress.ProgressMonitor;
     33import org.openstreetmap.josm.gui.tagging.presets.Item;
     34import org.openstreetmap.josm.gui.tagging.presets.KeyedItem;
     35import org.openstreetmap.josm.gui.tagging.presets.Role;
    3336import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
    34 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem;
    3537import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetListener;
    3638import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType;
     39import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetUtils;
    3740import 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;
    4241import org.openstreetmap.josm.tools.Utils;
    4342
    4443/**
     
    106105            return;
    107106        }
    108107        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)) {
    110109                relationpresets.add(p);
    111110            }
    112111        }
     
    175174        return map;
    176175    }
    177176
    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) {
    180184        Map<Role, String> allroles = new LinkedHashMap<>();
    181185
    182186        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());
    188192                }
    189193            }
    190194        }
     
    191195        return allroles;
    192196    }
    193197
    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 type
    208                 return false;
    209             }
    210         } else {
    211             // if no types specified, then test is passed
    212             return true;
    213         }
    214     }
    215 
    216198    /**
    217199     * get all role definition for specified key and check, if some definition matches
    218200     *
     
    226208        String role = member.getRole();
    227209        String name = null;
    228210        // Set of all accepted types in preset
    229         Collection<TaggingPresetType> types = EnumSet.noneOf(TaggingPresetType.class);
     211        EnumSet<TaggingPresetType> types = EnumSet.noneOf(TaggingPresetType.class);
    230212        TestError possibleMatchError = null;
    231213        // iterate through all of the role definition within preset
    232214        // and look for any matching definition
     
    236218                continue;
    237219            }
    238220            name = e.getValue();
    239             types.addAll(r.types);
    240             if (checkMemberType(r, member)) {
     221            types.addAll(r.getTypes());
     222            if (r.appliesTo(member.getDisplayType())) {
    241223                // member type accepted by role definition
    242                 if (r.memberExpression == null) {
     224                if (r.getMemberExpression() == null) {
    243225                    // no member expression - so all requirements met
    244226                    return true;
    245227                } else {
     
    251233                        return true;
    252234                    } else {
    253235                        // verify expression
    254                         if (r.memberExpression.match(primitive)) {
     236                        if (r.getMemberExpression().match(primitive)) {
    255237                            return true;
    256238                        } else {
    257239                            // possible match error
     
    261243                            possibleMatchError = TestError.builder(this, Severity.WARNING, WRONG_ROLE)
    262244                                    .message(ROLE_VERIF_PROBLEM_MSG,
    263245                                            marktr("Role of relation member does not match template expression ''{0}'' in preset {1}"),
    264                                             r.memberExpression, name)
     246                                            r.getMemberExpression(), name)
    265247                                    .primitives(member.getMember().isUsable() ? member.getMember() : n)
    266248                                    .build();
    267249                        }
     
    268250                    }
    269251                }
    270252            } else if (OsmPrimitiveType.RELATION == member.getType() && !member.getMember().isUsable()
    271                     && r.types.contains(TaggingPresetType.MULTIPOLYGON)) {
     253                    && r.appliesTo(TaggingPresetType.MULTIPOLYGON)) {
    272254                // if relation is incomplete we cannot verify if it's a multipolygon - so we just skip it
    273255                return true;
    274256            }
     
    275257        }
    276258
    277259        if (name == null) {
    278            return true;
     260            return true;
    279261        } else if (possibleMatchError != null) {
    280262            // if any error found, then assume that member type was correct
    281263            // and complain about not matching the memberExpression
     
    318300
    319301        // verify role counts based on whole role sets
    320302        for (Role r: allroles.keySet()) {
    321             String keyname = r.key;
     303            String keyname = r.getKey();
    322304            if (keyname.isEmpty()) {
    323305                keyname = tr("<empty>");
    324306            }
    325             checkRoleCounts(n, r, keyname, map.get(r.key));
     307            checkRoleCounts(n, r, keyname, map.get(r.getKey()));
    326308        }
    327309        if ("network".equals(n.get("type")) && !"bicycle".equals(n.get("route"))) {
    328310            return;
     
    331313        for (String key : map.keySet()) {
    332314            if (allroles.keySet().stream().noneMatch(role -> role.isRole(key))) {
    333315                String templates = allroles.keySet().stream()
    334                         .map(r -> r.key)
     316                        .map(r -> r.getKey())
    335317                        .map(r -> Utils.isEmpty(r) ? tr("<empty>") : r)
    336318                        .distinct()
    337319                        .collect(Collectors.joining("/"));
  • src/org/openstreetmap/josm/data/validation/tests/TagChecker.java

     
    5555import org.openstreetmap.josm.data.validation.TestError;
    5656import org.openstreetmap.josm.data.validation.util.Entities;
    5757import org.openstreetmap.josm.gui.progress.ProgressMonitor;
     58import org.openstreetmap.josm.gui.tagging.presets.Item;
     59import org.openstreetmap.josm.gui.tagging.presets.KeyedItem;
    5860import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
    59 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem;
    6061import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetListener;
    6162import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType;
     63import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetUtils;
    6264import 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;
    6665import org.openstreetmap.josm.gui.widgets.EditableList;
    6766import org.openstreetmap.josm.io.CachedFile;
    6867import org.openstreetmap.josm.spi.preferences.Config;
     
    9089    private static volatile HashSet<String> additionalPresetsValueData;
    9190    /** often used tags which are not in presets */
    9291    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<>();
    9493
    9594    private static final Pattern UNWANTED_NON_PRINTING_CONTROL_CHARACTERS = Pattern.compile(
    9695            "[\\x00-\\x09\\x0B\\x0C\\x0E-\\x1F\\x7F\\u200e-\\u200f\\u202a-\\u202e]");
     
    388387        if (!presets.isEmpty()) {
    389388            initAdditionalPresetsValueData();
    390389            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()) {
    393392                    if (i instanceof KeyedItem) {
    394                         if (!"none".equals(((KeyedItem) i).match))
     393                        if (!"none".equals(((KeyedItem) i).getMatchType()))
    395394                            minData.add(i);
    396395                        addPresetValue((KeyedItem) i);
    397                     } else if (i instanceof CheckGroup) {
    398                         for (Check c : ((CheckGroup) i).checks) {
    399                             addPresetValue(c);
    400                         }
    401396                    }
    402397                }
    403398                if (!minData.isEmpty()) {
     
    416411    }
    417412
    418413    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());
    421416        }
    422417    }
    423418
     
    660655            EnumSet<TaggingPresetType> presetTypes = EnumSet.of(presetType);
    661656
    662657            Collection<TaggingPreset> matchingPresets = presetIndex.entrySet().stream()
    663                     .filter(e -> TaggingPresetItem.matches(e.getValue(), tags))
     658                    .filter(e -> TaggingPresetUtils.matches(e.getValue(), tags))
    664659                    .map(Entry::getKey)
    665660                    .collect(Collectors.toCollection(LinkedHashSet::new));
    666661            Collection<TaggingPreset> matchingPresetsOK = matchingPresets.stream().filter(
     
    670665
    671666            for (TaggingPreset tp : matchingPresetsKO) {
    672667                // 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()
    674669                    .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())
    676671                    .collect(Collectors.toMap(k -> k, tags::get));
    677672                if (matchingPresetsOK.stream().noneMatch(
    678673                        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()))))) {
    681676                    errors.add(TestError.builder(this, Severity.OTHER, INVALID_PRESETS_TYPE)
    682677                            .message(tr("Object type not in preset"),
    683678                                    marktr("Object type {0} is not supported by tagging preset: {1}"),
  • src/org/openstreetmap/josm/gui/dialogs/properties/PresetListPanel.java

     
    1111import javax.swing.JLabel;
    1212import javax.swing.JPanel;
    1313
    14 import org.openstreetmap.josm.data.osm.DataSet;
    15 import org.openstreetmap.josm.data.osm.OsmPrimitive;
    1614import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
     15import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetDialog;
    1716import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetHandler;
    1817import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetLabel;
    1918import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType;
     
    2625public class PresetListPanel extends JPanel {
    2726
    2827    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;
    3130
    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;
    3534        }
    3635
    3736        @Override
    3837        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);
    4939        }
    5040    }
    5141
  • src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesDialog.java

     
    9797import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent;
    9898import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
    9999import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    100 import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
     100import org.openstreetmap.josm.gui.tagging.presets.DataSetTaggingPresetHandler;
    101101import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetHandler;
    102102import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetListener;
    103103import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType;
     
    980980    static final class TaggingPresetCommandHandler implements TaggingPresetHandler {
    981981        @Override
    982982        public void updateTags(List<Tag> tags) {
    983             Command command = TaggingPreset.createCommand(getSelection(), tags);
     983            Command command = DataSetTaggingPresetHandler.createCommand(getPrimitives(), tags);
    984984            if (command != null) {
    985985                UndoRedoHandler.getInstance().add(command);
    986986            }
     
    987987        }
    988988
    989989        @Override
    990         public Collection<OsmPrimitive> getSelection() {
     990        public Collection<OsmPrimitive> getPrimitives() {
    991991            return OsmDataManager.getInstance().getInProgressSelection();
    992992        }
    993993    }
  • src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java

     
    1010import java.awt.FlowLayout;
    1111import java.awt.GridBagConstraints;
    1212import java.awt.GridBagLayout;
     13import java.awt.Insets;
    1314import java.awt.Window;
    1415import java.awt.datatransfer.Clipboard;
    1516import java.awt.datatransfer.FlavorListener;
    1617import java.awt.event.ActionEvent;
    17 import java.awt.event.FocusAdapter;
    18 import java.awt.event.FocusEvent;
    1918import java.awt.event.InputEvent;
    2019import java.awt.event.KeyEvent;
    2120import java.awt.event.MouseAdapter;
     
    2726import java.util.Collection;
    2827import java.util.Collections;
    2928import java.util.EnumSet;
     29import java.util.HashMap;
    3030import java.util.List;
     31import java.util.Map;
    3132import java.util.Set;
    3233import java.util.stream.Collectors;
    3334
     
    3536import javax.swing.BorderFactory;
    3637import javax.swing.InputMap;
    3738import javax.swing.JButton;
     39import javax.swing.JCheckBox;
    3840import javax.swing.JComponent;
    3941import javax.swing.JLabel;
    4042import javax.swing.JMenuItem;
     
    4547import javax.swing.JSplitPane;
    4648import javax.swing.JTabbedPane;
    4749import javax.swing.JTable;
     50import javax.swing.JTextField;
    4851import javax.swing.JToolBar;
    4952import javax.swing.KeyStroke;
    5053
     
    5356import org.openstreetmap.josm.command.Command;
    5457import org.openstreetmap.josm.data.UndoRedoHandler;
    5558import org.openstreetmap.josm.data.UndoRedoHandler.CommandQueueListener;
     59import org.openstreetmap.josm.data.osm.DataSet;
    5660import org.openstreetmap.josm.data.osm.DefaultNameFormatter;
    5761import org.openstreetmap.josm.data.osm.OsmPrimitive;
    5862import org.openstreetmap.josm.data.osm.Relation;
    5963import org.openstreetmap.josm.data.osm.RelationMember;
    6064import org.openstreetmap.josm.data.osm.Tag;
     65import org.openstreetmap.josm.data.tagging.ac.AutoCompItemCellRenderer;
     66import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem;
     67import org.openstreetmap.josm.data.tagging.ac.AutoCompletionPriority;
    6168import org.openstreetmap.josm.data.validation.tests.RelationChecker;
    6269import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
    6370import org.openstreetmap.josm.gui.MainApplication;
     
    98105import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    99106import org.openstreetmap.josm.gui.tagging.TagEditorModel;
    100107import org.openstreetmap.josm.gui.tagging.TagEditorPanel;
    101 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
    102 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;
     108import org.openstreetmap.josm.gui.tagging.TagTable;
     109import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBox;
     110import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel;
    103111import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
     112import org.openstreetmap.josm.gui.tagging.ac.DefaultAutoCompListener;
    104113import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
    105114import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetHandler;
    106115import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType;
     
    108117import org.openstreetmap.josm.gui.util.WindowGeometry;
    109118import org.openstreetmap.josm.spi.preferences.Config;
    110119import org.openstreetmap.josm.tools.CheckParameterUtil;
     120import org.openstreetmap.josm.tools.GBC;
    111121import org.openstreetmap.josm.tools.InputMapUtils;
    112122import org.openstreetmap.josm.tools.Logging;
    113123import org.openstreetmap.josm.tools.Shortcut;
     
    118128 * @since 343
    119129 */
    120130public 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
    121134    /** the tag table and its model */
    122135    private final TagEditorPanel tagEditorPanel;
    123136    private final ReferringRelationsBrowser referrerBrowser;
     
    131144    private final SelectionTable selectionTable;
    132145    private final SelectionTableModel selectionTableModel;
    133146
    134     private final AutoCompletingTextField tfRole;
     147    private final AutoCompletionManager manager;
     148    private final AutoCompComboBox<AutoCompletionItem> cbRole;
    135149
    136150    /**
    137151     * the menu item in the windows menu. Required to properly hide on dialog close.
     
    172186
    173187    private Component selectedTabPane;
    174188    private JTabbedPane tabbedPane;
     189    private JCheckBox btnFilter = new JCheckBox(tr("Filter"));
    175190
    176191    /**
    177192     * Creates a new relation editor for the given relation. The relation will be saved if the user
     
    189204        setRememberWindowGeometry(getClass().getName() + ".geometry",
    190205                WindowGeometry.centerInWindow(MainApplication.getMainFrame(), new Dimension(700, 650)));
    191206
     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         */
    192214        final TaggingPresetHandler presetHandler = new TaggingPresetHandler() {
     215            Relation relation = new Relation();
     216            DataSet ds = new DataSet();
    193217
     218            /* anon constructor */ {
     219                ds.addPrimitive(relation);
     220            }
     221
    194222            @Override
    195223            public void updateTags(List<Tag> tags) {
    196224                tagEditorPanel.getModel().updateTags(tags);
     
    197225            }
    198226
    199227            @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
    202231                tagEditorPanel.getModel().applyToPrimitive(relation);
    203232                return Collections.<OsmPrimitive>singletonList(relation);
    204233            }
     
    212241        selectionTableModel.register();
    213242        referrerModel = new ReferringRelationsBrowserModel(relation);
    214243
     244        manager = AutoCompletionManager.of(this.getLayer().data);
     245
    215246        tagEditorPanel = new TagEditorPanel(relation, presetHandler);
     247        TagTable tagTable = tagEditorPanel.getTable();
    216248        populateModels(relation);
    217249        tagEditorPanel.getModel().ensureOneTag();
    218250
     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
    219276        // 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);
    221289        memberTable.addMouseListener(new MemberTableDblClickAdapter());
     290        memberTable.setRowHeight(height);
    222291        memberTableModel.addMemberModelListener(memberTable);
    223292
    224         MemberRoleCellEditor ce = (MemberRoleCellEditor) memberTable.getColumnModel().getColumn(0).getCellEditor();
    225293        selectionTable = new SelectionTable(selectionTableModel, memberTableModel);
    226         selectionTable.setRowHeight(ce.getEditor().getPreferredSize().height);
     294        selectionTable.setRowHeight(height);
    227295
    228296        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));
    230303
    231304        JSplitPane pane = buildSplitPane(
    232305                buildTagEditorPanel(tagEditorPanel),
    233                 buildMemberEditorPanel(leftButtonToolbar, new RelationEditorActionAccess()),
     306                buildMemberEditorPanel(leftButtonToolbar),
    234307                this);
    235308        pane.setPreferredSize(new Dimension(100, 100));
    236309
     
    310383                @Override
    311384                public void actionPerformed(ActionEvent e) {
    312385                    super.actionPerformed(e);
    313                     tfRole.requestFocusInWindow();
     386                    cbRole.requestFocusInWindow();
    314387                }
    315388            }, "PASTE_MEMBERS", key, getRootPane(), memberTable, selectionTable);
    316389        }
     
    446519    }
    447520
    448521    /**
    449      * builds the role text field
    450      * @param re relation editor
    451      * @return the role text field
    452      */
    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             @Override
    458             public void focusGained(FocusEvent e) {
    459                 tfRole.selectAll();
    460             }
    461         });
    462         tfRole.setAutoCompletionList(new AutoCompletionList());
    463         tfRole.addFocusListener(
    464                 new FocusAdapter() {
    465                     @Override
    466                     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     /**
    480522     * builds the panel for the relation member editor
    481523     * @param leftButtonToolbar left button toolbar
    482      * @param editorAccess The relation editor
    483524     *
    484525     * @return the panel for the relation member editor
    485526     */
    486     protected static JPanel buildMemberEditorPanel(
    487             LeftButtonToolbar leftButtonToolbar, IRelationEditorActionAccess editorAccess) {
     527    protected JPanel buildMemberEditorPanel(LeftButtonToolbar leftButtonToolbar) {
    488528        final JPanel pnl = new JPanel(new GridBagLayout());
    489         final JScrollPane scrollPane = new JScrollPane(editorAccess.getMemberTable());
     529        final JScrollPane scrollPane = new JScrollPane(memberTable);
    490530
    491531        GridBagConstraints gc = new GridBagConstraints();
    492532        gc.gridx = 0;
     
    518558        pnl.add(scrollPane, gc);
    519559
    520560        // --- 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)
    530574        );
    531         editorAccess.getTextFieldRole().setEnabled(editorAccess.getMemberTable().getSelectedRowCount() > 0);
     575        cbRole.setEnabled(memberTable.getSelectedRowCount() > 0);
     576
    532577        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));
    534580        btnApply.setText("");
    535         p3.add(btnApply);
     581        p3.add(btnApply, gbc.weight(0, 0).fill(GridBagConstraints.NONE));
    536582
     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
    537589        gc.gridx = 1;
    538590        gc.gridy = 2;
    539591        gc.fill = GridBagConstraints.HORIZONTAL;
     
    562614        gc.anchor = GridBagConstraints.NORTHWEST;
    563615        gc.weightx = 0.0;
    564616        gc.weighty = 1.0;
    565         pnl2.add(new ScrollViewport(buildSelectionControlButtonToolbar(editorAccess),
     617        pnl2.add(new ScrollViewport(buildSelectionControlButtonToolbar(new RelationEditorActionAccess()),
    566618                ScrollViewport.VERTICAL_DIRECTION), gc);
    567619
    568620        gc.gridx = 1;
     
    570622        gc.weightx = 1.0;
    571623        gc.weighty = 1.0;
    572624        gc.fill = GridBagConstraints.BOTH;
    573         pnl2.add(buildSelectionTablePanel(editorAccess.getSelectionTable()), gc);
     625        pnl2.add(buildSelectionTablePanel(selectionTable), gc);
    574626
    575627        final JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
    576628        splitPane.setLeftComponent(pnl);
    577629        splitPane.setRightComponent(pnl2);
    578630        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        });
    588638
    589639        JPanel pnl3 = new JPanel(new BorderLayout());
    590640        pnl3.add(splitPane, BorderLayout.CENTER);
     
    739789        if (isVisible() == visible) {
    740790            return;
    741791        }
    742         if (visible) {
    743             tagEditorPanel.initAutoCompletion(getLayer());
    744         }
    745792        super.setVisible(visible);
    746793        Clipboard clipboard = ClipboardUtils.getClipboard();
    747794        if (visible) {
     
    754801                clipboard.addFlavorListener(listener);
    755802            }
    756803        } else {
     804            Config.getPref().put(PREF_LASTROLE, cbRole.getText());
     805            Config.getPref().putBoolean(PREF_USE_ROLE_FILTER, btnFilter.isSelected());
     806
    757807            // make sure all registered listeners are unregistered
    758808            //
    759809            memberTable.stopHighlighting();
     
    10391089        }
    10401090
    10411091        @Override
    1042         public AutoCompletingTextField getTextFieldRole() {
    1043             return tfRole;
     1092        public JTextField getTextFieldRole() {
     1093            return cbRole.getEditorComponent();
    10441094        }
    1045 
    10461095    }
    10471096
    10481097    @Override
     
    10541103            applyAction.updateEnabledState();
    10551104        }
    10561105    }
     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    }
    10571182}
  • 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 null
    31      * @param relation the relation. Can be null
    32      * @since 13675
    33      */
    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     @Override
    44     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     @Override
    55     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 editor
    62      */
    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
     
    2020import javax.swing.SwingUtilities;
    2121import javax.swing.event.ListSelectionEvent;
    2222import javax.swing.event.ListSelectionListener;
     23import javax.swing.table.TableCellEditor;
    2324
    2425import org.openstreetmap.josm.actions.AbstractShowHistoryAction;
    2526import org.openstreetmap.josm.actions.AutoScaleAction;
     
    2728import org.openstreetmap.josm.actions.HistoryInfoAction;
    2829import org.openstreetmap.josm.actions.ZoomToAction;
    2930import org.openstreetmap.josm.data.osm.OsmPrimitive;
    30 import org.openstreetmap.josm.data.osm.Relation;
    3131import org.openstreetmap.josm.data.osm.RelationMember;
    3232import org.openstreetmap.josm.data.osm.Way;
    3333import org.openstreetmap.josm.gui.MainApplication;
     
    4141import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent;
    4242import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
    4343import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    44 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
    4544import org.openstreetmap.josm.gui.util.HighlightHelper;
    4645import org.openstreetmap.josm.gui.widgets.OsmPrimitivesTable;
    4746import org.openstreetmap.josm.spi.preferences.Config;
     
    6059     * constructor for relation member table
    6160     *
    6261     * @param layer the data layer of the relation. Must not be null
    63      * @param relation the relation. Can be null
     62     * @param roleCellEditor the role editor combobox
    6463     * @param model the table model
    6564     */
    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());
    6867        setLayer(layer);
    6968        model.addMemberModelListener(this);
    7069
    71         MemberRoleCellEditor ce = (MemberRoleCellEditor) getColumnModel().getColumn(0).getCellEditor();
    72         setRowHeight(ce.getEditor().getPreferredSize().height);
     70        setRowHeight(roleCellEditor.getTableCellEditorComponent(this, "", false, 0, 0).getPreferredSize().height);
    7371        setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
    7472        setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
    7573        putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
     
    8381        if (!GraphicsEnvironment.isHeadless()) {
    8482            setTransferHandler(new MemberTransferHandler());
    8583            setFillsViewportHeight(true); // allow drop on empty table
    86             if (!GraphicsEnvironment.isHeadless()) {
    87                 setDragEnabled(true);
    88             }
     84            setDragEnabled(true);
    8985            setDropMode(DropMode.INSERT_ROWS);
    9086        }
    9187    }
  • src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableColumnModel.java

     
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55
    66import javax.swing.table.DefaultTableColumnModel;
     7import javax.swing.table.TableCellEditor;
    78import javax.swing.table.TableColumn;
    89
    9 import org.openstreetmap.josm.data.osm.Relation;
    10 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
    11 
    1210/**
    1311 * This is the column model for the {@link MemberTable}
    1412 */
     
    1614
    1715    /**
    1816     * 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
    2118     * @since 13675
    2219     */
    23     public MemberTableColumnModel(AutoCompletionManager autoCompletionManager, Relation relation) {
     20    public MemberTableColumnModel(TableCellEditor roleCellEditor) {
    2421        TableColumn col;
    2522
    2623        // column 0 - the member role
     
    2926        col.setResizable(true);
    3027        col.setPreferredWidth(100);
    3128        col.setCellRenderer(new MemberTableRoleCellRenderer());
    32         col.setCellEditor(new MemberRoleCellEditor(autoCompletionManager, relation));
     29        col.setCellEditor(roleCellEditor);
    3330        addColumn(col);
    3431
    3532        // column 1 - the member
  • src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableModel.java

     
    433433    RelationMember getRelationMemberForPrimitive(final OsmPrimitive primitive) {
    434434        final Collection<TaggingPreset> presets = TaggingPresets.getMatchingPresets(
    435435                EnumSet.of(relation != null ? TaggingPresetType.forPrimitive(relation) : TaggingPresetType.RELATION),
    436                 presetHandler.getSelection().iterator().next().getKeys(), false);
     436                presetHandler.getPrimitives().iterator().next().getKeys(), false);
    437437        Collection<String> potentialRoles = presets.stream()
    438438                .map(tp -> tp.suggestRoleForOsmPrimitive(primitive))
    439439                .filter(Objects::nonNull)
  • src/org/openstreetmap/josm/gui/dialogs/relation/actions/IRelationEditorActionAccess.java

     
    22package org.openstreetmap.josm.gui.dialogs.relation.actions;
    33
    44import javax.swing.Action;
     5import javax.swing.JTextField;
    56
    67import org.openstreetmap.josm.gui.dialogs.relation.IRelationEditor;
    78import org.openstreetmap.josm.gui.dialogs.relation.MemberTable;
     
    910import org.openstreetmap.josm.gui.dialogs.relation.SelectionTable;
    1011import org.openstreetmap.josm.gui.dialogs.relation.SelectionTableModel;
    1112import org.openstreetmap.josm.gui.tagging.TagEditorModel;
    12 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
    1313
    1414/**
    1515 * This interface provides access to the relation editor for actions.
     
    6969     * Get the text field that is used to edit the role.
    7070     * @return The role text field.
    7171     */
    72     AutoCompletingTextField getTextFieldRole();
     72    JTextField getTextFieldRole();
    7373
    7474    /**
    7575     * 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

     
    88import java.util.List;
    99
    1010import javax.swing.JOptionPane;
     11import javax.swing.JTextField;
    1112import javax.swing.SwingUtilities;
    1213
    1314import org.openstreetmap.josm.command.AddCommand;
     
    2728import org.openstreetmap.josm.gui.dialogs.relation.RelationDialogManager;
    2829import org.openstreetmap.josm.gui.dialogs.relation.RelationEditor;
    2930import org.openstreetmap.josm.gui.tagging.TagEditorModel;
    30 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
    3131import org.openstreetmap.josm.tools.ImageProvider;
    3232import org.openstreetmap.josm.tools.Utils;
    3333
     
    3838abstract class SavingAction extends AbstractRelationEditorAction {
    3939    private static final long serialVersionUID = 1L;
    4040
    41     protected final AutoCompletingTextField tfRole;
     41    protected final JTextField tfRole;
    4242
    4343    protected SavingAction(IRelationEditorActionAccess editorAccess, IRelationEditorUpdateOn... updateOn) {
    4444        super(editorAccess, updateOn);
  • src/org/openstreetmap/josm/gui/dialogs/relation/actions/SetRoleAction.java

     
    77import java.awt.event.ActionEvent;
    88
    99import javax.swing.JOptionPane;
     10import javax.swing.JTextField;
    1011import javax.swing.event.DocumentEvent;
    1112import javax.swing.event.DocumentListener;
    1213
    1314import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
    1415import org.openstreetmap.josm.gui.MainApplication;
    15 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
    1616import org.openstreetmap.josm.tools.ImageProvider;
    1717import org.openstreetmap.josm.tools.Utils;
    1818
     
    2323public class SetRoleAction extends AbstractRelationEditorAction implements DocumentListener {
    2424    private static final long serialVersionUID = 1L;
    2525
    26     private final transient AutoCompletingTextField tfRole;
     26    private final transient JTextField tfRole;
    2727
    2828    /**
    2929     * Constructs a new {@code SetRoleAction}.
     
    3232    public SetRoleAction(IRelationEditorActionAccess editorAccess) {
    3333        super(editorAccess);
    3434        this.tfRole = editorAccess.getTextFieldRole();
    35         putValue(SHORT_DESCRIPTION, tr("Sets a role for the selected members"));
     35        putValue(SHORT_DESCRIPTION, tr("Apply the role to the selected members"));
    3636        new ImageProvider("apply").getResource().attachImageIcon(this);
    3737        putValue(NAME, tr("Apply Role"));
    3838        updateEnabledState();
  • src/org/openstreetmap/josm/gui/io/UploadDialog.java

     
    1818import java.beans.PropertyChangeListener;
    1919import java.lang.Character.UnicodeBlock;
    2020import java.util.ArrayList;
    21 import java.util.Collections;
     21import java.util.Arrays;
    2222import java.util.HashMap;
    2323import java.util.List;
    2424import java.util.Locale;
     
    4343import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
    4444import org.openstreetmap.josm.gui.help.HelpUtil;
    4545import org.openstreetmap.josm.gui.tagging.TagEditorPanel;
     46import org.openstreetmap.josm.gui.tagging.TagTable;
     47import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBox;
     48import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel;
     49import org.openstreetmap.josm.gui.tagging.ac.DefaultAutoCompListener;
    4650import org.openstreetmap.josm.gui.util.GuiHelper;
    4751import org.openstreetmap.josm.gui.util.MultiLineFlowLayout;
    4852import org.openstreetmap.josm.gui.util.WindowGeometry;
     
    8892    /** the model keeping the state of the changeset tags */
    8993    private final transient UploadDialogModel model = new UploadDialogModel();
    9094
    91     private transient DataSet dataSet;
    92 
    9395    /**
    9496     * Constructs a new {@code UploadDialog}.
    9597     */
     
    141143        pnlTagEditor = new TagEditorPanel(model, null, Changeset.MAX_CHANGESET_TAG_LENGTH);
    142144        pnlTagEditorBorder.add(pnlTagEditor, BorderLayout.CENTER);
    143145
     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
    144170        pnlChangesetManagement = new ChangesetManagementPanel();
    145171        pnlUploadStrategySelectionPanel = new UploadStrategySelectionPanel();
    146172        pnlSettings.add(pnlChangesetManagement, GBC.eop().fill(GridBagConstraints.HORIZONTAL));
     
    228254     * this in the constructor because the dialog is a singleton.
    229255     *
    230256     * @param dataSet The Dataset we want to upload
     257     * @param toUpload The primitves to upload
    231258     * @since 18173
    232259     */
    233     public void initLifeCycle(DataSet dataSet) {
     260    public void initLifeCycle(DataSet dataSet, APIDataSet toUpload) {
    234261        Map<String, String> map = new HashMap<>();
    235         this.dataSet = dataSet;
    236262        pnlBasicUploadSettings.initLifeCycle(map);
    237263        pnlChangesetManagement.initLifeCycle();
    238264        model.clear();
    239         model.putAll(map);          // init with tags from history
    240         model.putAll(this.dataSet); // overwrite with tags from the dataset
     265        model.putAll(map);     // init with tags from history
     266        model.putAll(dataSet); // overwrite with tags from the dataset
    241267
    242268        tpConfigPanels.setSelectedIndex(0);
    243         pnlTagEditor.initAutoCompletion(MainApplication.getLayerManager().getEditLayer());
    244269        pnlUploadStrategySelectionPanel.initFromPreferences();
    245270
    246271        // update the summary
     
    247272        UploadParameterSummaryPanel sumPnl = pnlBasicUploadSettings.getUploadParameterSummaryPanel();
    248273        sumPnl.setUploadStrategySpecification(pnlUploadStrategySelectionPanel.getUploadStrategySpecification());
    249274        sumPnl.setCloseChangesetAfterNextUpload(pnlChangesetManagement.isCloseChangesetAfterUpload());
    250     }
    251275
    252     /**
    253      * Sets the collection of primitives to upload
    254      *
    255      * @param toUpload the dataset with the objects to upload. If null, assumes the empty
    256      * set of objects to upload
    257      *
    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         }
    269276        List<OsmPrimitive> l = toUpload.getPrimitives();
    270277        pnlBasicUploadSettings.setUploadedPrimitives(l);
    271         pnlUploadedObjects.setUploadedPrimitives(
    272                 toUpload.getPrimitivesToAdd(),
    273                 toUpload.getPrimitivesToUpdate(),
    274                 toUpload.getPrimitivesToDelete()
    275         );
     278        pnlUploadedObjects.removeAll();
     279        pnlUploadedObjects.build(toUpload);
    276280        sumPnl.setNumObjects(l.size());
    277281        pnlUploadStrategySelectionPanel.setNumUploadedObjects(l.size());
    278282    }
     
    512516        }
    513517    }
    514518
     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
    515547    /* -------------------------------------------------------------------------- */
    516548    /* Interface PropertyChangeListener                                           */
    517549    /* -------------------------------------------------------------------------- */
     
    604636     * @since 14251
    605637     */
    606638    public void clean() {
    607         setUploadedPrimitives(null);
    608         dataSet = null;
     639        pnlUploadedObjects.removeAll();
    609640    }
    610641}
  • src/org/openstreetmap/josm/gui/io/UploadDialogModel.java

     
    7575     * @return the hashtags separated by ";" or null
    7676     */
    7777    String findHashTags(String comment) {
    78         String hashtags = String.join(";",
     78        String hashTags = String.join(";",
    7979            Arrays.stream(comment.split("\\s", -1))
    8080                .map(s -> Utils.strip(s, ",;"))
    8181                .filter(s -> s.matches("#[a-zA-Z0-9][-_a-zA-Z0-9]+"))
    8282                .collect(Collectors.toList()));
    83         return hashtags.isEmpty() ? null : hashtags;
     83        return hashTags.isEmpty() ? null : hashTags;
    8484    }
    8585
    8686    /**
  • src/org/openstreetmap/josm/gui/io/UploadPrimitivesTask.java

     
    401401                        // return to the upload dialog
    402402                        //
    403403                        toUpload.removeProcessed(processedPrimitives);
    404                         UploadDialog.getUploadDialog().setUploadedPrimitives(toUpload);
     404                        UploadDialog.getUploadDialog().initLifeCycle(null, toUpload);
    405405                        UploadDialog.getUploadDialog().setVisible(true);
    406406                        break;
    407407                    }
  • src/org/openstreetmap/josm/gui/io/UploadStrategySelectionPanel.java

     
    6969
    7070    protected JPanel buildUploadStrategyPanel() {
    7171        JPanel pnl = new JPanel(new GridBagLayout());
    72         pnl.setBorder(BorderFactory.createTitledBorder(tr("Please select the upload strategy:")));
     72        pnl.setBorder(BorderFactory.createTitledBorder(tr("Please select an upload strategy:")));
    7373        ButtonGroup bgStrategies = new ButtonGroup();
    7474        rbStrategy = new EnumMap<>(UploadStrategy.class);
    7575        lblNumRequests = new EnumMap<>(UploadStrategy.class);
  • src/org/openstreetmap/josm/gui/io/UploadedObjectsSummaryPanel.java

     
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.gui.io;
    33
    4 import static org.openstreetmap.josm.tools.I18n.tr;
    54import static org.openstreetmap.josm.tools.I18n.trn;
    65
    76import java.awt.GridBagConstraints;
     
    87import java.awt.GridBagLayout;
    98import java.awt.event.MouseAdapter;
    109import java.awt.event.MouseEvent;
    11 import java.util.ArrayList;
    1210import java.util.Collections;
    1311import java.util.List;
    14 import java.util.Optional;
    1512
    16 import javax.swing.AbstractListModel;
     13import javax.swing.BoxLayout;
     14import javax.swing.DefaultListModel;
    1715import javax.swing.JLabel;
    1816import javax.swing.JList;
    1917import javax.swing.JPanel;
     
    2018import javax.swing.JScrollPane;
    2119
    2220import org.openstreetmap.josm.actions.AutoScaleAction;
     21import org.openstreetmap.josm.data.APIDataSet;
    2322import org.openstreetmap.josm.data.osm.OsmPrimitive;
    2423import org.openstreetmap.josm.gui.PrimitiveRenderer;
    2524
     
    2827 * @since 2599
    2928 */
    3029public 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    };
    4344
    4445    /**
     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    /**
    4572     * Constructs a new {@code UploadedObjectsSummaryPanel}.
    4673     */
    4774    public UploadedObjectsSummaryPanel() {
    48         build();
     75        super(new GridBagLayout());
    4976    }
    5077
    51     protected void build() {
    52         setLayout(new GridBagLayout());
    53         PrimitiveRenderer renderer = new PrimitiveRenderer();
    54         MouseAdapter mouseListener = new MouseAdapter() {
    55             @Override
    56             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 
    9178    /**
    92      * Sets the collections of primitives which will be uploaded
     79     * Builds the panel
    9380     *
    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
    9782     */
    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) {
    10984        GridBagConstraints gcList = new GridBagConstraints();
    11085        gcList.fill = GridBagConstraints.BOTH;
    11186        gcList.weightx = 1.0;
    11287        gcList.weighty = 1.0;
    11388        gcList.anchor = GridBagConstraints.CENTER;
     89
    11490        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);
    12495        }
    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);
    133100        }
    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);
    142105        }
    143106        revalidate();
     107        repaint();
    144108    }
    145 
    146     /**
    147      * Replies the number of objects to upload
    148      *
    149      * @return the number of objects to upload
    150      */
    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         @Override
    196         public OsmPrimitive getElementAt(int index) {
    197             if (primitives == null) return null;
    198             return primitives.get(index);
    199         }
    200 
    201         @Override
    202         public int getSize() {
    203             if (primitives == null) return 0;
    204             return primitives.size();
    205         }
    206     }
    207109}
  • src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java

     
    12801280    @Override
    12811281    public AbstractUploadDialog getUploadDialog() {
    12821282        UploadDialog dialog = UploadDialog.getUploadDialog();
    1283         dialog.setUploadedPrimitives(new APIDataSet(data));
     1283        dialog.initLifeCycle(data, new APIDataSet(data));
    12841284        return dialog;
    12851285    }
    12861286
  • src/org/openstreetmap/josm/gui/preferences/ToolbarPreferences.java

     
    2323import java.util.Arrays;
    2424import java.util.Collection;
    2525import java.util.Collections;
     26import java.util.HashSet;
    2627import java.util.LinkedList;
    2728import java.util.List;
    2829import java.util.Map;
    2930import java.util.Objects;
    3031import java.util.Optional;
     32import java.util.Set;
    3133import java.util.concurrent.ConcurrentHashMap;
    3234
    3335import javax.swing.AbstractAction;
     
    238240        }
    239241    }
    240242
     243    /**
     244     * Parses strings into action definitions and back.
     245     */
    241246    public static class ActionParser {
    242247        private final Map<String, Action> actions;
    243248        private final StringBuilder result = new StringBuilder();
     
    274279        }
    275280
    276281        /**
    277          * Loads the action definition from its toolbar name.
     282         * Parses an action definition from a string.
     283         *
    278284         * @param actionName action toolbar name
    279285         * @return action definition or null
    280286         */
     
    353359            }
    354360        }
    355361
     362        /**
     363         * Unparses an action definition
     364         *
     365         * @param action the given action
     366         * @return the action as string
     367         */
    356368        @SuppressWarnings("unchecked")
    357369        public String saveAction(ActionDefinition action) {
    358370            result.setLength(0);
     
    397409                }
    398410                if (!first) {
    399411                    result.append('}');
     412                }
    400413            }
    401             }
    402414
    403415            return result.toString();
    404416        }
     
    570582    }
    571583
    572584    private final ToolbarPopupMenu popupMenu = new ToolbarPopupMenu();
     585    private boolean showInfoAboutMissingActions;
    573586
    574587    /**
    575588     * Key: Registered name (property "toolbar" of action).
     
    577590     */
    578591    private final Map<String, Action> regactions = new ConcurrentHashMap<>();
    579592
    580     private final DefaultMutableTreeNode rootActionsNode = new DefaultMutableTreeNode(tr("Actions"));
    581 
     593    /** the swing component for the toolbar */
    582594    public final JToolBar control = new JToolBar();
    583595    private final Map<Object, ActionDefinition> buttonActions = new ConcurrentHashMap<>(30);
    584     private boolean showInfoAboutMissingActions;
    585596
    586597    @Override
    587598    public PreferenceSetting createPreferenceSetting() {
    588         return new Settings(rootActionsNode);
     599        return new Settings(loadActions(MainApplication.getMenu(), regactions));
    589600    }
    590601
    591602    /**
     
    10251036        TaggingPresets.addListener(this);
    10261037    }
    10271038
    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) {
    10301047        MenuElement menuElement = menu;
    10311048        if (menu.getSubElements().length > 0 &&
    10321049                menu.getSubElements()[0] instanceof JPopupMenu) {
     
    10341051        }
    10351052        for (MenuElement item : menuElement.getSubElements()) {
    10361053            if (item instanceof JMenuItem) {
     1054                DefaultMutableTreeNode newNode = null;
    10371055                JMenuItem menuItem = (JMenuItem) item;
    10381056                if (menuItem.getAction() != null) {
    10391057                    Action action = menuItem.getAction();
    1040                     userObject = action;
     1058                    newNode = new DefaultMutableTreeNode(action);
     1059                    seen.add(action);
    10411060                    Object tb = action.getValue("toolbar");
    10421061                    if (tb == null) {
    10431062                        Logging.info(tr("Toolbar action without name: {0}",
     
    10491068                            action.getClass().getName()));
    10501069                        }
    10511070                        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);
    10601071                    }
    10611072                } else {
    1062                     userObject = menuItem.getText();
     1073                    newNode = new DefaultMutableTreeNode(menuItem.getText());
    10631074                }
     1075                node.add(newNode);
     1076                loadAction(newNode, seen, item);
    10641077            }
    1065             DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(userObject);
    1066             node.add(newNode);
    1067             loadAction(newNode, item, actionsInMenu);
    10681078        }
    10691079    }
    10701080
    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));
    10771099            }
    1078         }
     1100        });
    10791101        rootActionsNode.add(new DefaultMutableTreeNode(null));
     1102        return rootActionsNode;
    10801103    }
    10811104
    10821105    private static final String[] deftoolbar = {"open", "save", "download", "upload", "|",
     
    10881111    "tagginggroup_Facilities/Food+Drinks", "|", "tagginggroup_Man Made/Historic Places", "|",
    10891112    "tagginggroup_Man Made/Man Made"};
    10901113
     1114    /**
     1115     * Returns the configured toolbar strings or {@link #deftoolbar default ones}.
     1116     * @return the toolstring
     1117     */
    10911118    public static Collection<String> getToolString() {
    10921119        Collection<String> toolStr = Config.getPref().getList("toolbar", Arrays.asList(deftoolbar));
    10931120        if (Utils.isEmpty(toolStr)) {
     
    10971124    }
    10981125
    10991126    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);
    11081128        Collection<ActionDefinition> result = new ArrayList<>();
    11091129
    11101130        for (String s : getToolString()) {
     
    11391159                Logging.info(tr("Registered toolbar action {0} overwritten: {1} gets {2}",
    11401160                    toolbar, r.getClass().getName(), action.getClass().getName()));
    11411161            }
    1142         }
    1143         if (toolbar != null) {
    11441162            regactions.put(toolbar, action);
    11451163        }
    11461164        return action;
     
    12501268            sc = ((JosmAction) action.getAction()).getShortcut();
    12511269            if (sc.getAssignedKey() == KeyEvent.CHAR_UNDEFINED) {
    12521270                sc = null;
     1271            }
    12531272        }
    1254         }
    12551273
    12561274        long paramCode = 0;
    12571275        if (action.hasParameters()) {
  • src/org/openstreetmap/josm/gui/preferences/map/TaggingPresetPreference.java

     
    3131import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane.PreferencePanel;
    3232import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane.ValidationListener;
    3333import org.openstreetmap.josm.gui.preferences.SourceEditor;
    34 import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
    3534import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetReader;
    3635import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
    3736import org.openstreetmap.josm.spi.preferences.Config;
     
    5857                        i++;
    5958                        boolean canLoad = false;
    6059                        try {
    61                             TaggingPresetReader.readAll(source.url, false);
     60                            TaggingPresetReader.read(source.url, false);
    6261                            canLoad = true;
    6362                        } catch (IOException e) {
    6463                            Logging.log(Logging.LEVEL_WARN, tr("Could not read tagging preset source: {0}", source), e);
     
    8281                        String errorMessage = null;
    8382
    8483                        try {
    85                             TaggingPresetReader.readAll(source.url, true);
     84                            TaggingPresetReader.read(source.url, true);
    8685                        } catch (IOException e) {
    8786                            // Should not happen, but at least show message
    8887                            String msg = tr("Could not read tagging preset source: {0}", source);
     
    170169
    171170    @Override
    172171    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());
    175174
    176175        final JPanel panel = new JPanel(new GridBagLayout());
    177176        panel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
     
    251250
    252251    @Override
    253252    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())) {
    256255            TaggingPresets.destroy();
    257256            TaggingPresets.initialize();
    258257        }
  • src/org/openstreetmap/josm/gui/tagging/TagEditorModel.java

     
    145145    }
    146146
    147147    @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;
    153162    }
    154163
    155164    @Override
  • src/org/openstreetmap/josm/gui/tagging/TagEditorPanel.java

     
    2121import org.openstreetmap.josm.gui.dialogs.properties.HelpAction;
    2222import org.openstreetmap.josm.gui.dialogs.properties.HelpTagAction;
    2323import 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;
    2724import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetHandler;
    2825import org.openstreetmap.josm.spi.preferences.Config;
    29 import org.openstreetmap.josm.tools.CheckParameterUtil;
    3026
    3127/**
    3228 * TagEditorPanel is a {@link JPanel} which can be embedded as UI component in
     
    9086        JButton btn = new JButton(action);
    9187        pnl.add(btn);
    9288        btn.setMargin(new Insets(0, 0, 0, 0));
    93         tagTable.addComponentNotStoppingCellEditing(btn);
    9489    }
    9590
    9691    /**
     
    147142     * @param presetHandler tagging preset handler
    148143     */
    149144    public TagEditorPanel(OsmPrimitive primitive, TaggingPresetHandler presetHandler) {
    150         this(new TagEditorModel().forPrimitive(primitive), presetHandler, 0);
     145        this(new TagEditorModel().forPrimitive(primitive), presetHandler, -1);
    151146    }
    152147
    153148    /**
     
    188183    }
    189184
    190185    /**
    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
    197188     */
    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;
    210191    }
    211192
    212193    @Override
  • src/org/openstreetmap/josm/gui/tagging/TagTable.java

     
    55
    66import java.awt.Component;
    77import java.awt.Dimension;
    8 import java.awt.KeyboardFocusManager;
    9 import java.awt.Window;
    108import java.awt.event.ActionEvent;
     9import java.awt.event.ActionListener;
    1110import java.awt.event.KeyEvent;
    1211import java.beans.PropertyChangeEvent;
    1312import java.beans.PropertyChangeListener;
    1413import java.util.Collections;
    15 import java.util.EventObject;
    16 import java.util.concurrent.CopyOnWriteArrayList;
    1714
    1815import javax.swing.AbstractAction;
    1916import javax.swing.CellEditor;
     17import javax.swing.InputMap;
    2018import javax.swing.JComponent;
    2119import javax.swing.JTable;
    2220import javax.swing.KeyStroke;
     
    2422import javax.swing.SwingUtilities;
    2523import javax.swing.event.ListSelectionEvent;
    2624import javax.swing.event.ListSelectionListener;
    27 import javax.swing.text.JTextComponent;
     25import javax.swing.table.DefaultTableCellRenderer;
     26import javax.swing.table.TableCellEditor;
    2827
    2928import org.openstreetmap.josm.data.osm.Relation;
    3029import org.openstreetmap.josm.data.osm.TagMap;
    3130import org.openstreetmap.josm.gui.datatransfer.OsmTransferHandler;
    3231import 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;
     32import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBox;
    3533import org.openstreetmap.josm.gui.widgets.JosmTable;
    3634import org.openstreetmap.josm.tools.ImageProvider;
    37 import org.openstreetmap.josm.tools.Logging;
    3835import org.openstreetmap.josm.tools.Utils;
    3936
    4037/**
     
    4138 * This is the tabular editor component for OSM tags.
    4239 * @since 1762
    4340 */
    44 public class TagTable extends JosmTable implements EndEditListener {
    45     /** the table cell editor used by this table */
    46     private TagCellEditor editor;
     41public class TagTable extends JosmTable implements ActionListener, EndEditListener {
    4742    private final TagEditorModel model;
    4843    private Component nextFocusComponent;
     44    private final int LAST_COL = 1;
    4945
    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();
    5556
    5657    /**
    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.
    6561     */
    6662    class SelectNextColumnCellAction extends AbstractAction {
    6763        @Override
    6864        public void actionPerformed(ActionEvent e) {
    69             run();
    70         }
    71 
    72         public void run() {
    7365            int col = getSelectedColumn();
    7466            int row = getSelectedRow();
    75             if (getCellEditor() != null) {
    76                 getCellEditor().stopCellEditing();
    77             }
    7867
    7968            if (row == -1 && col == -1) {
    8069                requestFocusInCell(0, 0);
    8170                return;
    8271            }
     72            endCellEditing();
    8373
    84             if (col == 0) {
     74            if (col < LAST_COL) {
    8575                col++;
    86             } else if (col == 1 && row < getRowCount()-1) {
     76            } else if (row < getRowCount() - 1) {
    8777                col = 0;
    8878                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 column
    91                 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);
    9282                if (!Utils.isStripEmpty(key)) {
     83                    // append an empty row
    9384                    model.appendNewTag();
    9485                    col = 0;
    9586                    row++;
    9687                } else {
     88                    // exit the table
    9789                    clearSelection();
    9890                    if (nextFocusComponent != null)
    9991                        nextFocusComponent.requestFocusInWindow();
     
    114106        public void actionPerformed(ActionEvent e) {
    115107            int col = getSelectedColumn();
    116108            int row = getSelectedRow();
    117             if (getCellEditor() != null) {
    118                 getCellEditor().stopCellEditing();
    119             }
    120109
     110            endCellEditing();
     111
    121112            if (col <= 0 && row <= 0) {
    122113                // change nothing
    123             } else if (col == 1) {
     114            } else if (col > 0) {
    124115                col--;
    125116            } else {
    126                 col = 1;
     117                col = LAST_COL;
    127118                row--;
    128119            }
    129120            requestFocusInCell(row, col);
     
    131122    }
    132123
    133124    /**
    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}.
    136127     *
    137      * Depending on the shape on the current selection the action deletes individual
    138      * values or entire 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.
    139130     *
    140      * If the current selection consists of cells in the second column only, the keys of
    141      * the 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.
    142133     *
    143      * If the current selection consists of cell in the third column only, the values of the
     134     * If the current selection consists of cell in the values column only, the values of the
    144135     * selected tags are set to the empty string.
    145136     *
    146      *  If the current selection consists of cells in the second and the third column,
    147      *  the selected tags are removed from the model.
     137     * If the current selection consists of entire rows, the selected tags are removed from the
     138     * model.
    148139     *
    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.
    153142     */
    154143    class DeleteAction extends AbstractAction implements ListSelectionListener {
    155144
     
    161150            updateEnabledState();
    162151        }
    163152
    164         /**
    165          * delete a selection of tag names
    166          */
    167         protected void deleteTagNames() {
    168             int[] rows = getSelectedRows();
    169             model.deleteTagNames(rows);
    170         }
    171 
    172         /**
    173          * delete a selection of tag values
    174          */
    175         protected void deleteTagValues() {
    176             int[] rows = getSelectedRows();
    177             model.deleteTagValues(rows);
    178         }
    179 
    180         /**
    181          * delete a selection of tags
    182          */
    183         protected void deleteTags() {
    184             int[] rows = getSelectedRows();
    185             model.deleteTags(rows);
    186         }
    187 
    188153        @Override
    189154        public void actionPerformed(ActionEvent e) {
    190             if (!isEnabled())
    191                 return;
    192             switch(getSelectedColumnCount()) {
     155            switch (getSelectedColumnCount()) {
    193156            case 1:
    194157                if (getSelectedColumn() == 0) {
    195                     deleteTagNames();
     158                    model.deleteTagNames(getSelectedRows());
    196159                } else if (getSelectedColumn() == 1) {
    197                     deleteTagValues();
     160                    model.deleteTagValues(getSelectedRows());
    198161                }
    199162                break;
    200163            case 2:
    201                 deleteTags();
     164                model.deleteTags(getSelectedRows());
    202165                break;
    203166            default: // Do nothing
    204167            }
    205168
    206             endCellEditing();
    207 
    208169            if (model.getRowCount() == 0) {
    209170                model.ensureOneTag();
    210171                requestFocusInCell(0, 0);
     
    216177         */
    217178        @Override
    218179        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);
    220184        }
    221185
    222186        protected final void updateEnabledState() {
     
    243207                cEditor.stopCellEditing();
    244208            }
    245209            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))) {
    247211                model.appendNewTag();
    248212            }
    249213            requestFocusInCell(model.getRowCount()-1, 0);
     
    288252        }
    289253    }
    290254
    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 
    300255    /**
    301256     * Returns the delete action.
    302257     * @return the delete action used by this table
     
    322277    }
    323278
    324279    /**
    325      * initialize the table
    326      * @param maxCharacters maximum number of characters allowed for keys and values, 0 for unlimited
    327      */
    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 TAB
    335         //
    336         getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
    337         .put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false), "selectNextColumnCell");
    338 
    339         // install custom navigation actions
    340         //
    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 map
    345         // didn't work. We therefore handle delete requests in processKeyBindings(...)
    346         //
    347         deleteAction = new DeleteAction();
    348 
    349         // create the add action
    350         //
    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 columns
    359         //
    360         TagCellEditor tmpEditor = new TagCellEditor(maxCharacters);
    361         setRowHeight(tmpEditor.getEditor().getPreferredSize().height);
    362         setTagCellEditor(tmpEditor);
    363     }
    364 
    365     /**
    366280     * Creates a new tag table
    367281     *
    368282     * @param model the tag editor model
    369      * @param maxCharacters maximum number of characters allowed for keys and values, 0 for unlimited
     283     * @param maxCharacters maximum number of characters allowed for keys and values, -1 for unlimited
    370284     */
    371285    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"))
    373287                  .setSelectionModel(model.getColumnSelectionModel()).build(),
    374288              model.getRowSelectionModel());
     289
    375290        this.model = model;
    376291        model.setEndEditListener(this);
    377         init(maxCharacters);
    378     }
    379292
    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);
    384298
    385     @Override
    386     protected boolean processKeyBinding(KeyStroke ks, KeyEvent e, int condition, boolean pressed) {
     299        InputMap im = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
    387300
    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);
    400310    }
    401311
    402312    /**
    403      * Sets the editor autocompletion list
    404      * @param autoCompletionList autocompletion list
     313     * Sets a TableCellEditor for the keys column.
     314     * @param editor the editor to set
    405315     */
    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);
    412320    }
    413321
    414322    /**
    415      * Sets the autocompletion manager that should be used for editing the cells
    416      * @param autocomplete The {@link AutoCompletionManager}
     323     * Sets a TableCellEditor for the values column.
     324     * @param editor the editor to set
    417325     */
    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);
    427330    }
    428331
    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;
    438338    }
    439339
     340    @Override
     341    public Dimension getPreferredSize() {
     342        return getPreferredFullWidthSize();
     343    }
     344
    440345    /**
    441346     * Sets the next component to request focus after navigation (with tab or enter).
    442347     * @param nextFocusComponent next component to request focus after navigation (with tab or enter)
     
    446351    }
    447352
    448353    /**
    449      * Gets the editor that is used for the table cells
    450      * @return The editor that is used when the user wants to enter text into a cell
    451      */
    452     public TagCellEditor getTableCellEditor() {
    453         return editor;
    454     }
    455 
    456     /**
    457      * Inject a tag cell editor in the tag table
    458      *
    459      * @param editor tag cell editor
    460      */
    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     /**
    469354     * Request the focus in a specific cell
    470355     * @param row The row index
    471356     * @param col The column index
     
    475360        editCellAt(row, col);
    476361        Component c = getEditorComponent();
    477362        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();
    484364        }
    485         // there was a bug here - on older 1.6 Java versions Tab was not working
    486         // after such activation. In 1.7 it works OK,
    487         // previous solution of using awt.Robot was resetting mouse speed on Windows
    488365    }
    489366
    490     /**
    491      * Marks a component that may be focused without stopping the cell editing
    492      * @param component The component
    493      */
    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 component
    502      */
    503     public void removeComponentNotStoppingCellEditing(Component component) {
    504         if (component == null) return;
    505         doNotStopCellEditingWhenFocused.remove(component);
    506     }
    507 
    508367    @Override
    509     public boolean editCellAt(int row, int column, EventObject e) {
    510 
    511         // a snipped copied from the Java 1.5 implementation of JTable
    512         //
    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 created
    524         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 implementation
    532         return super.editCellAt(row, column, e);
    533     }
    534 
    535     @Override
    536368    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()) {
    542373                cEditor.cancelCellEditing();
    543374            }
    544375        }
     
    545376    }
    546377
    547378    @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);
    581383        }
    582 
    583         @Override
    584         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 table
    592                     return;
    593                 if (doNotStopCellEditingWhenFocused.contains(c))
    594                     // focus remains on one of the associated components
    595                     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         }
    605384    }
    606385}
  • src/org/openstreetmap/josm/gui/tagging/ac/AutoCompComboBox.java

     
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.gui.tagging.ac;
    33
     4import java.awt.Component;
     5import java.awt.event.MouseEvent;
    46import java.awt.im.InputContext;
     7import java.util.EventObject;
    58import java.util.Locale;
    69
    710import javax.swing.ComboBoxEditor;
     11import javax.swing.JTable;
     12import javax.swing.event.CellEditorListener;
     13import javax.swing.table.TableCellEditor;
    814
     15import org.openstreetmap.josm.gui.util.CellEditorSupport;
    916import org.openstreetmap.josm.gui.widgets.JosmComboBox;
    1017import org.openstreetmap.josm.tools.Logging;
    1118
     
    2128 * @param <E> the type of the combobox entries
    2229 * @since 18173
    2330 */
    24 public class AutoCompComboBox<E> extends JosmComboBox<E> implements AutoCompListener {
     31public class AutoCompComboBox<E> extends JosmComboBox<E> implements TableCellEditor, AutoCompListener {
    2532
    2633    /** force a different keyboard input locale for the editor */
    2734    private boolean useFixedLocale;
     
    4552        setEditable(true);
    4653        getEditorComponent().setModel(model);
    4754        getEditorComponent().addAutoCompListener(this);
     55        tableCellEditorSupport = new CellEditorSupport(this);
    4856    }
    4957
    5058    /**
     
    9199        // Save the text in case item is null, because setSelectedItem will erase it.
    92100        String savedText = getText();
    93101        setSelectedItem(item);
    94         setText(savedText);
     102        if (item == null)
     103            setText(savedText);
    95104    }
    96105
    97106    /**
     
    140149        return super.getInputContext();
    141150    }
    142151
    143     /** AutoCompListener Interface */
     152    /* ------------------------------------------------------------------------------------ */
     153    /* AutoCompListener interface                                                           */
     154    /* ------------------------------------------------------------------------------------ */
    144155
    145156    @Override
    146157    public void autoCompBefore(AutoCompEvent e) {
     
    150161    public void autoCompPerformed(AutoCompEvent e) {
    151162        autocomplete(e.getItem());
    152163    }
     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    }
    153238}
  • src/org/openstreetmap/josm/gui/tagging/ac/AutoCompListener.java

     
    44import java.util.EventListener;
    55
    66/**
    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.
    1419 *
    15  * @see AutoCompEvent
    1620 * @since 18221
    1721 */
    1822public interface AutoCompListener extends EventListener {
  • src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletionManager.java

     
    66import java.util.Collection;
    77import java.util.Collections;
    88import java.util.Comparator;
     9import java.util.EnumSet;
    910import java.util.HashMap;
    10 import java.util.HashSet;
    1111import java.util.LinkedHashSet;
    1212import java.util.List;
    1313import java.util.Map;
     
    1616import java.util.Set;
    1717import java.util.function.Function;
    1818import java.util.stream.Collectors;
     19import java.util.stream.Stream;
    1920
    2021import org.openstreetmap.josm.data.osm.DataSet;
    2122import org.openstreetmap.josm.data.osm.OsmPrimitive;
     
    3940import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent;
    4041import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent;
    4142import org.openstreetmap.josm.gui.layer.OsmDataLayer;
     43import org.openstreetmap.josm.gui.tagging.presets.Item;
     44import org.openstreetmap.josm.gui.tagging.presets.KeyedItem;
     45import org.openstreetmap.josm.gui.tagging.presets.Role;
    4246import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
     47import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType;
    4348import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
    44 import org.openstreetmap.josm.gui.tagging.presets.items.Roles.Role;
    4549import org.openstreetmap.josm.tools.CheckParameterUtil;
    4650import org.openstreetmap.josm.tools.MultiMap;
    4751import org.openstreetmap.josm.tools.Utils;
    4852
    4953/**
    50  * AutoCompletionManager holds a cache of keys with a list of
    51  * possible auto completion values for each key.
    52  *
     54 * AutoCompletionManager holds a cache of keys with a list of possible auto completion values for
     55 * each key.
     56 * <p>
    5357 * Each DataSet can be assigned one AutoCompletionManager instance such that
    5458 * <ol>
    5559 *   <li>any key used in a tag in the data set is part of the key list in the cache</li>
     
    5660 *   <li>any value used in a tag for a specific key is part of the autocompletion list of this key</li>
    5761 * </ol>
    5862 *
    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.
    6566 */
    6667public class AutoCompletionManager implements DataSetListener {
    6768
     
    105106        }
    106107    }
    107108
     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
    108115    /** If the dirty flag is set true, a rebuild is necessary. */
    109116    protected boolean dirty;
    110117    /** The data set that is managed */
     
    115122     * only accessed by getTagCache(), rebuild() and cachePrimitiveTags()
    116123     * use getTagCache() accessor
    117124     */
    118     protected MultiMap<String, String> tagCache;
     125    protected final MultiMap<String, String> TAG_CACHE = new MultiMap<>();
    119126
    120127    /**
    121      * the same as tagCache but for the preset keys and values can be accessed directly
    122      */
    123     static final MultiMap<String, String> PRESET_TAG_CACHE = new MultiMap<>();
    124 
    125     /**
    126128     * Cache for tags that have been entered by the user.
    127129     */
    128130    static final Set<UserInputTag> USER_INPUT_TAG_CACHE = new LinkedHashSet<>();
    129131
    130132    /**
    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}.
    134134     */
    135     protected Set<String> roleCache;
     135    protected final MultiMap<String, Relation> RELATION_CACHE = new MultiMap<>();
    136136
    137     /**
    138      * the same as roleCache but for the preset roles can be accessed directly
    139      */
    140     static final Set<String> PRESET_ROLE_CACHE = new HashSet<>();
    141 
    142137    private static final Map<DataSet, AutoCompletionManager> INSTANCES = new HashMap<>();
    143138
     139    private static final Map<String, String> EMPTY_MAP = Collections.emptyMap();
     140
    144141    /**
    145142     * Constructs a new {@code AutoCompletionManager}.
    146143     * @param ds data set
     
    156153            rebuild();
    157154            dirty = false;
    158155        }
    159         return tagCache;
     156        return TAG_CACHE;
    160157    }
    161158
    162     protected Set<String> getRoleCache() {
     159    protected MultiMap<String, Relation> getRelationCache() {
    163160        if (dirty) {
    164161            rebuild();
    165162            dirty = false;
    166163        }
    167         return roleCache;
     164        return RELATION_CACHE;
    168165    }
    169166
    170167    /**
     
    171168     * initializes the cache from the primitives in the dataset
    172169     */
    173170    protected void rebuild() {
    174         tagCache = new MultiMap<>();
    175         roleCache = new HashSet<>();
     171        TAG_CACHE.clear();
     172        RELATION_CACHE.clear();
    176173        cachePrimitives(ds.allNonDeletedCompletePrimitives());
    177174    }
    178175
     
    180177        for (OsmPrimitive primitive : primitives) {
    181178            cachePrimitiveTags(primitive);
    182179            if (primitive instanceof Relation) {
    183                 cacheRelationMemberRoles((Relation) primitive);
     180                Relation rel = (Relation) primitive;
     181                RELATION_CACHE.put(getRelationType(rel.getKeys()), rel);
    184182            }
    185183        }
    186184    }
     
    192190     * @param primitive an OSM primitive
    193191     */
    194192    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));
    196194    }
    197195
    198196    /**
    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"}.
    200205     *
    201      * @param relation the relation
     206     * @param tags the tags on the relation
     207     * @return the relation type or {@code ""}
    202208     */
    203     protected 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;
    209215    }
    210216
    211217    /**
     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    /**
    212228     * Remembers user input for the given key/value.
    213229     * @param key Tag key
    214230     * @param value Tag value
     
    259275    }
    260276
    261277    /**
    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.
    263282     *
    264      * @return the list of member roles
     283     * @return the collection of member roles
    265284     */
    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());
    268288    }
    269289
    270290    /**
     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    /**
    271307     * Populates the {@link AutoCompletionList} with the currently cached member roles.
    272308     *
    273309     * @param list the list to populate
    274310     */
    275311    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);
    278316    }
    279317
    280318    /**
     
    292330        Collection<TaggingPreset> presets = r != null ? TaggingPresets.getMatchingPresets(null, r.getKeys(), false) : Collections.emptyList();
    293331        if (r != null && !Utils.isEmpty(presets)) {
    294332            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);
    298335            }
    299336            list.add(r.getMemberRoles(), AutoCompletionPriority.IS_IN_DATASET);
    300337        } else {
     
    303340    }
    304341
    305342    /**
     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    /**
    306523     * Populates the an {@link AutoCompletionList} with the currently cached tag keys
    307524     *
    308525     * @param list the list to populate
     
    345562    }
    346563
    347564    /**
    348      * Returns all cached {@link AutoCompletionItem}s for given keys.
    349      *
    350      * @param keys retrieve the items for these keys
    351      * @return the currently cached items, sorted by priority and alphabet
    352      * @since 18221
    353      */
    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     /**
    372565     * Returns the currently cached tag keys.
    373566     * @return a set of tag keys
    374567     * @since 12859
     
    502695                    ds.removeDataSetListener(AutoCompletionManager.this);
    503696                    MainApplication.getLayerManager().removeLayerChangeListener(this);
    504697                    dirty = true;
    505                     tagCache = null;
    506                     roleCache = null;
     698                    TAG_CACHE.clear();
     699                    RELATION_CACHE.clear();
    507700                    ds = null;
    508701                }
    509702            }
  • src/org/openstreetmap/josm/gui/tagging/ac/DefaultAutoCompListener.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.tagging.ac;
     3
     4import javax.swing.event.PopupMenuEvent;
     5import 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 */
     11public 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.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import java.util.ArrayList;
     5import java.util.Arrays;
     6import java.util.Collection;
     7import java.util.List;
     8import java.util.Map;
     9
     10import javax.swing.JPanel;
     11
     12import org.openstreetmap.josm.data.osm.OsmPrimitive;
     13import org.openstreetmap.josm.data.osm.OsmUtils;
     14import org.openstreetmap.josm.data.osm.Tag;
     15import org.openstreetmap.josm.gui.widgets.IconTextCheckBox;
     16import org.openstreetmap.josm.gui.widgets.QuadStateCheckBox;
     17import org.openstreetmap.josm.tools.GBC;
     18
     19/**
     20 * Checkbox type.
     21 */
     22final 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.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import java.awt.GridBagConstraints;
     5import java.awt.GridLayout;
     6import java.util.Map;
     7
     8import javax.swing.JLabel;
     9import javax.swing.JPanel;
     10
     11import org.openstreetmap.josm.tools.GBC;
     12
     13/**
     14 * A group of {@link Check}s.
     15 * @since 6114
     16 */
     17final 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.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import java.util.Map;
     5
     6/**
     7 * A collection of items to be inserted in place of a {@link Reference}.
     8 */
     9class 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.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import java.util.Collection;
     5import java.util.List;
     6import java.util.Map;
     7import java.util.stream.Collectors;
     8
     9import org.openstreetmap.josm.command.ChangePropertyCommand;
     10import org.openstreetmap.josm.data.osm.DataSet;
     11import org.openstreetmap.josm.data.osm.FilterModel;
     12import org.openstreetmap.josm.data.osm.INode;
     13import org.openstreetmap.josm.data.osm.IRelation;
     14import org.openstreetmap.josm.data.osm.IWay;
     15import org.openstreetmap.josm.data.osm.OsmPrimitive;
     16import org.openstreetmap.josm.data.osm.Tag;
     17import 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 */
     24public 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.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
     6import java.awt.Color;
     7import java.awt.Cursor;
     8import java.awt.Dimension;
     9import java.awt.GridBagConstraints;
     10import java.awt.Insets;
     11import java.awt.event.ActionEvent;
     12import java.awt.event.ActionListener;
     13import java.awt.event.ComponentAdapter;
     14import java.awt.event.ComponentEvent;
     15import java.util.Arrays;
     16import java.util.Comparator;
     17import java.util.Map;
     18
     19import javax.swing.AbstractAction;
     20import javax.swing.JButton;
     21import javax.swing.JColorChooser;
     22import javax.swing.JComponent;
     23import javax.swing.JLabel;
     24import javax.swing.JPanel;
     25
     26import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem;
     27import org.openstreetmap.josm.data.tagging.ac.AutoCompletionPriority;
     28import org.openstreetmap.josm.gui.MainApplication;
     29import org.openstreetmap.josm.gui.mappaint.mapcss.CSSColors;
     30import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxEditor;
     31import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel;
     32import org.openstreetmap.josm.gui.tagging.ac.AutoCompTextField;
     33import org.openstreetmap.josm.gui.widgets.JosmComboBox;
     34import org.openstreetmap.josm.gui.widgets.OrientationAction;
     35import org.openstreetmap.josm.tools.ColorHelper;
     36import org.openstreetmap.josm.tools.GBC;
     37
     38/**
     39 * Combobox type.
     40 */
     41final 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.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
     6import java.lang.reflect.Method;
     7import java.lang.reflect.Modifier;
     8import java.util.ArrayList;
     9import java.util.Arrays;
     10import java.util.Collection;
     11import java.util.Collections;
     12import java.util.HashMap;
     13import java.util.HashSet;
     14import java.util.List;
     15import java.util.Map;
     16import java.util.Set;
     17import java.util.stream.Collectors;
     18
     19import javax.swing.JLabel;
     20import javax.swing.JPanel;
     21
     22import org.openstreetmap.josm.data.osm.Tag;
     23import org.openstreetmap.josm.gui.widgets.OrientationAction;
     24import org.openstreetmap.josm.tools.AlphanumComparator;
     25import org.openstreetmap.josm.tools.GBC;
     26import org.openstreetmap.josm.tools.Logging;
     27
     28/**
     29 * Abstract superclass for combo box and multi-select list types.
     30 */
     31public 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.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import java.awt.GridBagConstraints;
     5import java.awt.GridBagLayout;
     6import java.util.Map;
     7
     8import javax.swing.BorderFactory;
     9import javax.swing.JPanel;
     10import javax.swing.border.Border;
     11import javax.swing.border.CompoundBorder;
     12
     13import 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 */
     18public 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.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
     6import java.util.Collection;
     7import java.util.List;
     8
     9import org.openstreetmap.josm.command.ChangePropertyCommand;
     10import org.openstreetmap.josm.command.Command;
     11import org.openstreetmap.josm.command.SequenceCommand;
     12import org.openstreetmap.josm.data.UndoRedoHandler;
     13import org.openstreetmap.josm.data.osm.DataSet;
     14import org.openstreetmap.josm.data.osm.OsmPrimitive;
     15import org.openstreetmap.josm.data.osm.Tag;
     16import org.openstreetmap.josm.tools.StreamUtils;
     17
     18/**
     19 * A TaggingPresetHandler that operates on a DataSet.
     20 */
     21public 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.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import java.util.List;
     5import java.util.Map;
     6import java.util.function.Predicate;
     7
     8import javax.swing.JMenu;
     9import javax.swing.JPanel;
     10
     11import org.openstreetmap.josm.data.osm.OsmPrimitive;
     12import org.openstreetmap.josm.data.osm.Tag;
     13import 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 */
     32public 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.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
     6import java.text.MessageFormat;
     7import java.util.HashMap;
     8import java.util.Map;
     9
     10import org.openstreetmap.josm.tools.TextTagParser;
     11
     12/**
     13 * A factory for preset items.
     14 */
     15public 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.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import java.util.Map;
     5
     6import javax.swing.JPanel;
     7import javax.swing.JSeparator;
     8
     9import org.openstreetmap.josm.tools.GBC;
     10
     11/**
     12 * Class used to represent a {@link JSeparator} inside tagging preset window.
     13 * @since 6198
     14 */
     15final 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.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import java.util.Collection;
     5import java.util.Collections;
     6import java.util.List;
     7import java.util.Map;
     8
     9import javax.swing.JPanel;
     10
     11import org.openstreetmap.josm.data.osm.Tag;
     12
     13/**
     14 * Invisible type allowing to hardcode an OSM key/value from the preset definition.
     15 */
     16public 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.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
     6import java.util.Collection;
     7import java.util.EnumSet;
     8import java.util.HashMap;
     9import java.util.Map;
     10
     11import javax.swing.JPopupMenu;
     12
     13import org.openstreetmap.josm.data.osm.Tag;
     14import org.openstreetmap.josm.data.preferences.BooleanProperty;
     15import org.openstreetmap.josm.gui.dialogs.properties.HelpTagAction;
     16import org.openstreetmap.josm.gui.dialogs.properties.TaginfoAction;
     17
     18/**
     19 * Preset item associated to an OSM key.
     20 */
     21public 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.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import java.util.Map;
     5
     6import javax.swing.JLabel;
     7import javax.swing.JPanel;
     8
     9import org.openstreetmap.josm.tools.GBC;
     10
     11/**
     12 * Label type.
     13 */
     14final 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.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
     6import java.awt.event.MouseEvent;
     7import java.util.Arrays;
     8import java.util.Map;
     9import java.util.Optional;
     10
     11import javax.swing.JPanel;
     12import javax.swing.SwingUtilities;
     13
     14import org.openstreetmap.josm.gui.dialogs.properties.HelpAction;
     15import org.openstreetmap.josm.gui.widgets.UrlLabel;
     16import org.openstreetmap.josm.spi.preferences.Config;
     17import org.openstreetmap.josm.tools.GBC;
     18import org.openstreetmap.josm.tools.LanguageInfo;
     19
     20/**
     21 * Hyperlink type.
     22 * @since 8863
     23 */
     24public 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.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import java.awt.Dimension;
     5import java.awt.GridBagConstraints;
     6import java.awt.Insets;
     7import java.awt.Rectangle;
     8import java.util.Map;
     9import java.util.stream.Collectors;
     10
     11import javax.swing.DefaultListModel;
     12import javax.swing.JLabel;
     13import javax.swing.JList;
     14import javax.swing.JPanel;
     15import javax.swing.JScrollPane;
     16
     17import org.openstreetmap.josm.gui.widgets.OrientationAction;
     18import org.openstreetmap.josm.tools.GBC;
     19import org.openstreetmap.josm.tools.Logging;
     20
     21/**
     22 * Multi-select list type.
     23 */
     24final 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.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
     6import java.awt.GridBagLayout;
     7import java.util.Map;
     8
     9import javax.swing.JPanel;
     10
     11/**
     12 * Used to group optional attributes.
     13 * @since 8863
     14 */
     15final 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.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
     6import java.awt.event.MouseAdapter;
     7import java.awt.event.MouseEvent;
     8import java.util.Map;
     9
     10import javax.swing.JLabel;
     11import javax.swing.JPanel;
     12
     13import org.openstreetmap.josm.tools.GBC;
     14
     15/**
     16 * Adds a link to another preset.
     17 * @since 8863
     18 */
     19final 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
     3package org.openstreetmap.josm.gui.tagging.presets;
     4
     5import static org.openstreetmap.josm.tools.I18n.tr;
     6import static org.openstreetmap.josm.tools.I18n.trc;
     7
     8import java.awt.Component;
     9import java.awt.Font;
     10import java.util.Map;
     11import java.util.Objects;
     12
     13import javax.swing.ImageIcon;
     14import javax.swing.JLabel;
     15import javax.swing.JList;
     16import javax.swing.JPanel;
     17import javax.swing.ListCellRenderer;
     18
     19import org.openstreetmap.josm.gui.widgets.JosmListCellRenderer;
     20import org.openstreetmap.josm.tools.AlphanumComparator;
     21import 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 */
     29class 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.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import java.util.Collection;
     5import java.util.List;
     6
     7import org.openstreetmap.josm.data.osm.OsmPrimitive;
     8import 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 */
     15public 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.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
     6import java.util.List;
     7import java.util.Map;
     8import java.util.function.Predicate;
     9
     10import javax.swing.JPanel;
     11
     12/**
     13 * A reference to be satisfied by a {@link Chunk}
     14 */
     15final 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.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
     6import java.util.Collection;
     7import java.util.EnumSet;
     8import java.util.Map;
     9import java.util.Set;
     10
     11import javax.swing.JLabel;
     12import javax.swing.JPanel;
     13
     14import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
     15import org.openstreetmap.josm.data.osm.search.SearchCompiler;
     16import org.openstreetmap.josm.data.osm.search.SearchParseError;
     17import org.openstreetmap.josm.data.osm.search.SearchSetting;
     18import org.openstreetmap.josm.tools.GBC;
     19import 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 */
     26public 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.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
     6import java.awt.GridBagConstraints;
     7import java.awt.GridBagLayout;
     8import java.util.Map;
     9
     10import javax.swing.JLabel;
     11import javax.swing.JPanel;
     12
     13import 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 */
     23final 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.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import java.util.Map;
     5
     6import javax.swing.JPanel;
     7
     8/**
     9 * The XML root element.  Corresponds to {@code <presets>}.
     10 */
     11public 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.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import java.util.LinkedList;
     5import java.util.List;
     6import java.util.Map;
     7import java.util.function.Predicate;
     8
     9import javax.swing.JMenu;
     10import javax.swing.JPanel;
     11
     12/**
     13 * A sequence of items {@link Item}s.
     14 */
     15public 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.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import java.util.Map;
     5
     6import javax.swing.JLabel;
     7import javax.swing.JPanel;
     8
     9import org.openstreetmap.josm.tools.GBC;
     10
     11/**
     12 * A horizontal spacer.
     13 */
     14final 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

     
    22package org.openstreetmap.josm.gui.tagging.presets;
    33
    44import 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;
    75
    8 import java.awt.Component;
    96import java.awt.ComponentOrientation;
    10 import java.awt.Dimension;
    117import java.awt.GridBagLayout;
    12 import java.awt.Insets;
    138import java.awt.event.ActionEvent;
    14 import java.io.File;
    159import java.util.ArrayList;
    1610import java.util.Collection;
     11import java.util.Collections;
    1712import java.util.EnumSet;
    18 import java.util.LinkedHashSet;
     13import java.util.HashMap;
    1914import java.util.List;
    2015import java.util.Map;
    2116import java.util.Objects;
    2217import java.util.Set;
    23 import java.util.concurrent.CompletableFuture;
    2418import java.util.function.Predicate;
    2519import java.util.stream.Collectors;
    2620
    2721import javax.swing.AbstractAction;
    2822import javax.swing.Action;
    29 import javax.swing.ImageIcon;
    3023import javax.swing.JLabel;
    31 import javax.swing.JOptionPane;
     24import javax.swing.JMenu;
     25import javax.swing.JMenuItem;
    3226import javax.swing.JPanel;
    3327import javax.swing.JToggleButton;
    34 import javax.swing.SwingUtilities;
    3528
    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;
    4229import org.openstreetmap.josm.data.osm.DataSet;
    4330import org.openstreetmap.josm.data.osm.IPrimitive;
    44 import org.openstreetmap.josm.data.osm.OsmData;
    4531import org.openstreetmap.josm.data.osm.OsmDataManager;
    4632import org.openstreetmap.josm.data.osm.OsmPrimitive;
    47 import org.openstreetmap.josm.data.osm.Relation;
    48 import org.openstreetmap.josm.data.osm.RelationMember;
    4933import org.openstreetmap.josm.data.osm.Tag;
    5034import org.openstreetmap.josm.data.osm.Tagged;
    51 import org.openstreetmap.josm.data.osm.Way;
    52 import org.openstreetmap.josm.data.osm.search.SearchCompiler;
    5335import 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;
    5736import 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;
    6337import 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;
    7038import org.openstreetmap.josm.gui.util.GuiHelper;
    7139import org.openstreetmap.josm.tools.GBC;
    7240import 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;
    7741import org.openstreetmap.josm.tools.Utils;
    78 import org.openstreetmap.josm.tools.template_engine.ParseError;
    7942import org.openstreetmap.josm.tools.template_engine.TemplateEntry;
    80 import org.openstreetmap.josm.tools.template_engine.TemplateParser;
    81 import org.xml.sax.SAXException;
    8243
    8344/**
    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.
    8752 *
    88  * It is also able to construct dialogs out of preset definitions.
    8953 * @since 294
    9054 */
    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 
     55public 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;
    10660    /**
    107      * Defines whether the validator should be active in the preset dialog
    108      * @see TaggingPresetValidation
    109      */
    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 name
    128      */
    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 true
    137      */
    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 items
    146      */
    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     /**
    15461     * The name_template custom name formatter. See:
    15562     * <a href="https://josm.openstreetmap.de/wiki/TaggingPresets#Attributes">JOSM wiki</a>
    15663     */
    157     public transient TemplateEntry nameTemplate;
     64    private final TemplateEntry nameTemplate;
    15865    /** The name_template_filter */
    159     public transient Match nameTemplateFilter;
     66    private final Match nameTemplateFilter;
    16067    /** The match_expression */
    161     public transient Match matchExpression;
     68    private final Match matchExpression;
    16269
    16370    /**
    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.
    16574     */
    166     private boolean originalSelectionEmpty;
     75    Map<String, Object> properties = new HashMap<>();
    16776
    168     /** The completable future task of asynchronous icon loading */
    169     private CompletableFuture<Void> iconFuture;
    170 
    171     /** Support functions */
    172     protected TaggingPresetItemGuiSupport itemGuiSupport;
    173 
    17477    /**
    17578     * Create an empty tagging preset. This will not have any items and
    17679     * will be an empty string as text. createPanel will return null.
    17780     * Use this as default item for "do not select anything".
     81     *
     82     * @param attributes the XML attributes
     83     * @throws IllegalArgumentException on invalid attributes
    17884     */
    179     public TaggingPreset() {
    180         updateEnabledState();
    181     }
     85    TaggingPreset(Map<String, String> attributes) throws IllegalArgumentException {
     86        super(attributes);
    18287
    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"));
    19293    }
    19394
    19495    /**
    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
    197100     */
    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);
    207103    }
    208104
    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);
    215110    }
    216111
    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);
    223117    }
    224118
    225119    /**
    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
    229122     */
    230     public final ImageIcon getIcon() {
    231         return getIcon(Action.SMALL_ICON);
     123    public boolean getPresetNameLabel() {
     124        return presetNameLabel;
    232125    }
    233126
    234127    /**
    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
    239130     */
    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);
    246133    }
    247134
    248135    /**
    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
    252138     */
    253     public final ImageResource getImageResource() {
    254         return ImageResource.getAttachedImageResource(this);
     139    public TemplateEntry getNameTemplate() {
     140        return nameTemplate;
    255141    }
    256142
    257143    /**
    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
    261146     */
    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;
    292149    }
    293150
    294151    /**
    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
    299158     */
    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;
    303163
    304     /**
    305      * Sets the name_template custom name formatter.
    306      *
    307      * @param template The format template
    308      * @throws SAXException on template parse error
    309      * @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 pattern
    324      * @throws SAXException on search patern parse error
    325      * @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 pattern
    340      * @throws SAXException on search patern parse error
    341      * @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 primitives
    365      * @return the newly created panel
    366      */
    367     public PresetPanel createPanel(Collection<OsmPrimitive> selected) {
    368         PresetPanel p = new PresetPanel();
    369 
    370164        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);
    377169        }
    378         final List<Tag> directlyAppliedTags = Utils.filteredCollection(data, Key.class).stream()
     170        final List<Tag> directlyAppliedTags = Utils.filteredCollection(items, Key.class).stream()
    379171                .map(Key::asTag)
    380172                .collect(Collectors.toList());
    381173        if (!directlyAppliedTags.isEmpty()) {
     
    388180        pp.add(validationLabel);
    389181
    390182        final int count = pp.getComponentCount();
    391         if (preset_name_label) {
     183        if (presetNameLabel) {
    392184            p.add(new JLabel(getIcon(Action.LARGE_ICON_KEY)), GBC.std(0, 0).span(1, count > 0 ? 2 : 1).insets(0, 0, 5, 0));
    393185        }
    394186        if (count > 0) {
    395187            p.add(pp, GBC.std(1, 0).span(GBC.REMAINDER));
    396188        }
    397         if (preset_name_label) {
     189        if (presetNameLabel) {
    398190            p.add(new JLabel(getName()), GBC.std(1, count > 0 ? 1 : 0).insets(5, 0, 0, 0).span(GBC.REMAINDER).fill(GBC.HORIZONTAL));
    399191        }
    400192
    401         boolean presetInitiallyMatches = !selected.isEmpty() && selected.stream().allMatch(this);
    402         itemGuiSupport = TaggingPresetItemGuiSupport.create(presetInitiallyMatches, selected, this::getChangedTags);
    403 
    404193        JPanel itemPanel = new JPanel(new GridBagLayout()) {
    405194            /**
    406195             * This hack allows the items to have their own orientation.
     
    419208            }
    420209        };
    421210        JPanel linkPanel = new JPanel(new GridBagLayout());
    422         TaggingPresetItem previous = null;
    423         for (TaggingPresetItem i : data) {
     211        Item previous = null;
     212        for (Item i : items) {
    424213            if (i instanceof Link) {
    425                 i.addToPanel(linkPanel, itemGuiSupport);
    426                 p.hasElements = true;
     214                i.addToPanel(linkPanel, support);
     215                hasElements = true;
    427216            } else {
    428217                if (i instanceof PresetLink) {
    429218                    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()))) {
    431220                        itemPanel.add(link.createLabel(), GBC.eol().insets(0, 8, 0, 0));
    432221                    }
    433222                }
    434                 if (i.addToPanel(itemPanel, itemGuiSupport)) {
    435                     p.hasElements = true;
     223                if (i.addToPanel(itemPanel, support)) {
     224                    hasElements = true;
    436225                }
    437226            }
    438227            previous = i;
     
    444233            GuiHelper.setEnabledRec(itemPanel, false);
    445234        }
    446235
    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            });
    450242        }
    451243
    452         // "Add toolbar button"
     244        // add the "pin" button
    453245        JToggleButton tb = new JToggleButton(new ToolbarButtonAction());
    454246        tb.setFocusable(false);
    455247        p.add(tb, GBC.std(1, 0).anchor(GBC.LINE_END));
    456248
    457249        // 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;
    462253    }
    463254
    464255    /**
     
    467258     * @return {@code true} if a dialog can be shown for this preset
    468259     */
    469260    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;
    471262    }
    472263
    473264    /**
    474265     * Suggests a relation role for this primitive
     266     * <p>
     267     * Suggests a role when the primitive is added to a relation.
    475268     *
    476269     * @param osm The primitive
    477270     * @return the suggested role or null
    478271     */
    479272    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);
    489281    }
    490282
    491     @Override
    492     public void actionPerformed(ActionEvent e) {
    493         DataSet ds = OsmDataManager.getInstance().getEditDataSet();
    494         if (ds == null) {
    495             return;
    496         }
    497         showAndApply(ds.getSelected());
    498     }
    499 
    500283    /**
    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
    648286     * @return The list of tags.
    649287     */
    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();
    654290    }
    655291
    656292    /**
    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
    661295     */
    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);
    668298    }
    669299
    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 
    678300    @Override
    679     public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) {
    680         updateEnabledState();
    681     }
    682 
    683     @Override
    684301    public String toString() {
    685         return (types == null ? "" : types.toString()) + ' ' + name;
     302        return "TaggingPreset " + types.toString() + " " + getName();
    686303    }
    687304
    688305    /**
     
    701318     * @return <code>true</code> if all types match.
    702319     */
    703320    public boolean typeMatches(Collection<TaggingPresetType> t) {
    704         return t == null || types == null || types.containsAll(t);
     321        return t == null || types.containsAll(t);
    705322    }
    706323
    707324    /**
    708325     * 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}.
    710327     *
    711328     * @param p the primitive
    712329     * @return {@code true} if this preset matches the primitive
     
    721338     * Determines whether this preset matches the parameters.
    722339     *
    723340     * @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)}
    725342     * @param onlyShowable whether the preset must be {@link #isShowable() showable}
    726343     * @return {@code true} if this preset matches the parameters.
    727344     */
     
    731348        } else if (matchExpression != null && !matchExpression.match(Tagged.ofMap(tags))) {
    732349            return false;
    733350        } else {
    734             return TaggingPresetItem.matches(data, tags);
     351            return TaggingPresetUtils.matches(items, tags);
    735352        }
    736353    }
    737354
    738355    /**
    739      * Action that adds or removes the button on main toolbar
     356     * An action that opens the preset dialog.
    740357     */
     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     */
    741382    public class ToolbarButtonAction extends AbstractAction {
    742383        private final int toolbarIndex;
    743384
     
    755396
    756397        @Override
    757398        public void actionPerformed(ActionEvent ae) {
    758             String res = getToolbarString();
    759             MainApplication.getToolbar().addCustomButton(res, toolbarIndex, true);
     399            MainApplication.getToolbar().addCustomButton(getToolbarString(), toolbarIndex, true);
    760400        }
    761401    }
    762 
    763     /**
    764      * Gets a string describing this preset that can be used for the toolbar
    765      * @return A String that can be passed on to the toolbar
    766      * @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 null
    776      * @since 14449
    777      */
    778     public CompletableFuture<Void> getIconLoadingTask() {
    779         return iconFuture;
    780     }
    781 
    782402}
  • src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetBase.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import java.util.Map;
     5import java.util.concurrent.CompletableFuture;
     6
     7import javax.swing.AbstractAction;
     8import javax.swing.Action;
     9import javax.swing.ImageIcon;
     10
     11import org.openstreetmap.josm.actions.AdaptableAction;
     12import org.openstreetmap.josm.data.osm.OsmDataManager;
     13import org.openstreetmap.josm.gui.MainApplication;
     14import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent;
     15import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
     16import org.openstreetmap.josm.gui.preferences.ToolbarPreferences;
     17import 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 */
     26public 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.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5import static org.openstreetmap.josm.tools.I18n.trn;
     6
     7import java.awt.Component;
     8import java.awt.Dimension;
     9import java.awt.GridBagLayout;
     10import java.awt.Insets;
     11import java.util.Collection;
     12import java.util.LinkedHashSet;
     13
     14import javax.swing.Action;
     15import javax.swing.ImageIcon;
     16import javax.swing.JOptionPane;
     17import javax.swing.JPanel;
     18import javax.swing.SwingUtilities;
     19
     20import org.openstreetmap.josm.actions.CreateMultipolygonAction;
     21import org.openstreetmap.josm.data.osm.DataSet;
     22import org.openstreetmap.josm.data.osm.OsmData;
     23import org.openstreetmap.josm.data.osm.OsmDataManager;
     24import org.openstreetmap.josm.data.osm.OsmPrimitive;
     25import org.openstreetmap.josm.data.osm.Relation;
     26import org.openstreetmap.josm.data.osm.RelationMember;
     27import org.openstreetmap.josm.data.osm.Tag;
     28import org.openstreetmap.josm.data.osm.Way;
     29import org.openstreetmap.josm.gui.ExtendedDialog;
     30import org.openstreetmap.josm.gui.MainApplication;
     31import org.openstreetmap.josm.gui.Notification;
     32import org.openstreetmap.josm.gui.dialogs.relation.RelationEditor;
     33import org.openstreetmap.josm.gui.dialogs.relation.sort.RelationSorter;
     34import org.openstreetmap.josm.tools.Pair;
     35import org.openstreetmap.josm.tools.Utils;
     36
     37/**
     38 * A tagging preset dialog.
     39 */
     40public 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

     
    88import org.openstreetmap.josm.data.osm.Tag;
    99
    1010/**
    11  * This interface needs to be implemented in order to display a tagging preset. It allows the preset dialog to query the primitives it should
    12  * 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}.
    1313 */
    1414public interface TaggingPresetHandler {
     15
    1516    /**
    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     *
    1722     * @return A collection of primitives.
    1823     */
    19     Collection<OsmPrimitive> getSelection();
     24    Collection<OsmPrimitive> getPrimitives();
    2025
    2126    /**
    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.
    2433     */
    2534    void updateTags(List<Tag> tags);
    2635}
  • src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetInstance.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import java.awt.ComponentOrientation;
     5import java.util.ArrayList;
     6import java.util.Arrays;
     7import java.util.Collection;
     8import java.util.HashMap;
     9import java.util.List;
     10import java.util.Map;
     11
     12import org.openstreetmap.josm.data.osm.OsmPrimitive;
     13import org.openstreetmap.josm.data.osm.Tag;
     14import org.openstreetmap.josm.data.osm.Tagged;
     15import org.openstreetmap.josm.data.osm.search.SearchCompiler;
     16import org.openstreetmap.josm.gui.widgets.OrientationAction;
     17import org.openstreetmap.josm.tools.ListenerList;
     18import org.openstreetmap.josm.tools.Utils;
     19import 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 */
     30public 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 user
    36  * @since 6068
    37  */
    38 public abstract class TaggingPresetItem {
    39 
    40     // cache the parsing of types using a LRU cache
    41     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 keys
    65      * @return the currently cached items, sorted by priority and alphabet
    66      * @since 18221
    67      */
    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 added
    81      * @param support supporting class for creating the GUI
    82      * @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 needed
    89      */
    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; // NOSONAR
    100     }
    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 icon
    150      * @param iconName the icon name
    151      * @param zipIcons zip file where the image is located
    152      * @param maxSize maximum image size (or null)
    153      * @return the requested image or null if the request failed
    154      */
    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 tags
    166      * @param data the preset items
    167      * @param tags the tags to match
    168      * @return whether the given preset items match the tags
    169      * @since 9932
    170      */
    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 17609
    23  */
    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 enabled
    37      *
    38      * @return true if firing of events is enabled
    39      */
    40     public boolean isEnabled() {
    41         return enabled;
    42     }
    43 
    44     /**
    45      * Enables or disables the firing of events
    46      *
    47      * @param enabled fires if true
    48      * @return the old state of enabled
    49      */
    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 17610
    59      */
    60     public interface ChangeListener {
    61         /**
    62          * Notifies this listener that a preset item input as changed.
    63          * @param source the source of this event
    64          * @param key the tag key
    65          * @param newValue the new tag value
    66          */
    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 primitives
    79      *
    80      * @return the selected primitives
    81      */
    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 as
    90      * opposed to being opened by selection from the menu or toolbar or the search.
    91      *
    92      * @return true if the preset initially matched
    93      */
    94     public boolean isPresetInitiallyMatches() {
    95         return presetInitiallyMatches;
    96     }
    97 
    98     /**
    99      * Creates a new {@code TaggingPresetItemGuiSupport}
    100      *
    101      * @param presetInitiallyMatches whether the preset initially matched
    102      * @param selected the selected primitives
    103      * @param changedTagsSupplier the changed tags
    104      * @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 matched
    115      * @param selected the selected primitives
    116      * @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, then
    126      * overwrite with the current values shown in the dialog.
    127      * Else get only the tags shown in the dialog.
    128      * @return Tags
    129      */
    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 tags
    135         Tagged tagged = Tagged.ofMap(selected.iterator().next().getKeys());
    136         // update changed tags
    137         changedTagsSupplier.get().forEach(tag -> tagged.put(tag));
    138         return tagged;
    139     }
    140 
    141     @Override
    142     public Collection<String> getTemplateKeys() {
    143         return getTagged().keySet();
    144     }
    145 
    146     @Override
    147     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 locale
    154      *
    155      * @return the default component orientation
    156      */
    157     public ComponentOrientation getDefaultComponentOrientation() {
    158         return OrientationAction.getDefaultComponentOrientation();
    159     }
    160 
    161     @Override
    162     public boolean evaluateCondition(SearchCompiler.Match condition) {
    163         return condition.match(getTagged());
    164     }
    165 
    166     /**
    167      * Adds a new change listener
    168      * @param listener the listener to add
    169      */
    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 event
    177      * @param key the tag key
    178      * @param newValue the new tag value
    179      */
    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

     
    1212
    1313/**
    1414 * A hyperlink {@link JLabel}.
    15  * 
     15 *
    1616 * To indicate that the user can click on the text, it displays an appropriate
    1717 * mouse cursor and dotted underline when the mouse is inside the hover area.
    1818 */
  • src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetMenu.java

     
    88import java.awt.Point;
    99import java.awt.PointerInfo;
    1010import java.awt.event.ActionEvent;
    11 import java.io.Serializable;
    12 import java.util.ArrayList;
    13 import java.util.Comparator;
    14 import java.util.List;
     11import java.util.Map;
    1512import java.util.Objects;
    1613
    1714import javax.swing.Action;
    1815import javax.swing.JMenu;
    19 import javax.swing.JMenuItem;
    2016import javax.swing.JPopupMenu;
    21 import javax.swing.JSeparator;
    2217
    2318import org.openstreetmap.josm.gui.MainApplication;
    2419import org.openstreetmap.josm.gui.MainFrame;
    25 import org.openstreetmap.josm.tools.AlphanumComparator;
     20import org.openstreetmap.josm.gui.MenuScroller;
    2621import org.openstreetmap.josm.tools.Logging;
    2722
    2823/**
     
    3025 * <p>
    3126 * Used, to create the nested directory structure in the preset main menu entry.
    3227 */
    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         }
     28public class TaggingPresetMenu extends TaggingPresetBase {
     29    TaggingPresetMenu(Map<String, String> attributes) throws IllegalArgumentException {
     30        super(attributes);
    4731    }
    4832
    4933    /**
    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
    5138     */
    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);
    5841    }
    5942
    6043    @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);
    6348    }
    6449
    6550    @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);
    7455
    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);
    9358        }
     59        if (subMenu.getItemCount() >= TaggingPresets.MIN_ELEMENTS_FOR_SCROLLER.get()) {
     60            MenuScroller.setScrollerFor(subMenu);
     61        }
    9462    }
    9563
    9664    @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();
    11767    }
    11868
    11969    /**
    120      * Sorts the menu items using the translated item text
     70     * {@code TaggingPresetMenu} are considered equivalent if (and only if) their {@link #getRawName()} match.
    12171     */
    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());
    12478    }
    12579
     80    @Override
     81    public int hashCode() {
     82        return Objects.hash(getRawName());
     83    }
     84
    12685    /**
    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.
    12987     */
    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);
    15296        }
    153     }
    15497
    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                }
    164121            }
    165             menu.add(menuItem, oldPos);
    166             pos++;
    167122        }
    168123    }
    169124}
  • src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetNameTemplateList.java

     
    3939            Logging.debug("Building list of presets with name template");
    4040            presetsWithPattern.clear();
    4141            for (TaggingPreset tp : TaggingPresets.getTaggingPresets()) {
    42                 if (tp.nameTemplate != null) {
     42                if (tp.getNameTemplate() != null) {
    4343                    presetsWithPattern.add(tp);
    4444                }
    4545            }
     
    5656            for (TaggingPreset t : presetsWithPattern) {
    5757                Collection<TaggingPresetType> type = EnumSet.of(TaggingPresetType.forPrimitive(primitive));
    5858                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))
    6161                            return t;
    6262                    } else if (t.matches(type, primitive.getKeys(), false)) {
    6363                        return t;
  • src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetReader.java

     
    99import java.io.InputStream;
    1010import java.io.InputStreamReader;
    1111import java.io.Reader;
    12 import java.util.ArrayDeque;
     12import java.net.URI;
     13import java.net.URISyntaxException;
    1314import java.util.ArrayList;
    1415import java.util.Collection;
    15 import java.util.Deque;
    1616import java.util.HashMap;
    17 import java.util.Iterator;
    18 import java.util.LinkedHashSet;
    19 import java.util.LinkedList;
    20 import java.util.List;
    2117import java.util.Map;
    2218import java.util.Set;
     19import java.util.Stack;
    2320
    2421import javax.swing.JOptionPane;
     22import javax.xml.parsers.ParserConfigurationException;
     23import javax.xml.transform.stream.StreamSource;
     24import javax.xml.validation.Schema;
     25import javax.xml.validation.SchemaFactory;
     26import javax.xml.validation.ValidatorHandler;
    2527
    2628import org.openstreetmap.josm.data.preferences.sources.PresetPrefHelper;
    2729import 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;
    4430import org.openstreetmap.josm.io.CachedFile;
    4531import org.openstreetmap.josm.io.NetworkManager;
    4632import org.openstreetmap.josm.io.UTFInputStreamReader;
    4733import org.openstreetmap.josm.spi.preferences.Config;
    4834import org.openstreetmap.josm.tools.I18n;
     35import org.openstreetmap.josm.tools.JosmRuntimeException;
     36import org.openstreetmap.josm.tools.LanguageInfo;
    4937import org.openstreetmap.josm.tools.Logging;
    5038import org.openstreetmap.josm.tools.Stopwatch;
    5139import org.openstreetmap.josm.tools.Utils;
    52 import org.openstreetmap.josm.tools.XmlObjectParser;
     40import org.openstreetmap.josm.tools.XmlParsingException;
     41import org.openstreetmap.josm.tools.XmlUtils;
     42import org.xml.sax.Attributes;
     43import org.xml.sax.ContentHandler;
     44import org.xml.sax.InputSource;
     45import org.xml.sax.Locator;
     46import org.xml.sax.SAXParseException;
    5347import org.xml.sax.SAXException;
     48import org.xml.sax.XMLReader;
     49import org.xml.sax.helpers.DefaultHandler;
     50import org.xml.sax.helpers.XMLFilterImpl;
    5451
    5552/**
    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
    5859 */
    5960public final class TaggingPresetReader {
    6061
     
    7778     */
    7879    public static final String SCHEMA_SOURCE = "resource://data/tagging-preset.xsd";
    7980
    80     private static volatile File zipIcons;
    81     private static volatile boolean loadIcons = true;
     81    private static File zipIcons;
     82    private static boolean loadIcons = true;
    8283
    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         public 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();
    8990
     91        public Root getRoot() {
     92            return root;
     93        }
     94
    9095        @Override
    91         public String toString() {
    92             return "Chunk [id=" + id + ']';
     96        public void setDocumentLocator(Locator locator) {
     97            this.locator = locator;
    9398        }
    94     }
    9599
    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        }
    102111
    103112        @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);
    106135        }
    107     }
    108136
    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        }
    112150
    113151        @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);
    117156        }
    118157
    119158        /**
    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
    122162         */
    123         public E getLast() {
    124             return last;
     163        private void throwException(Exception e) throws XmlParsingException {
     164            throw new XmlParsingException(e).rememberLocation(locator);
    125165        }
    126     }
    127166
    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        }
    135171
    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        }
    159176    }
    160177
    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        }
    170201    }
    171202
    172203    /**
    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.
    179206     */
    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;
    182209
    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        }
    196213
    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);
    211218            } else {
    212                 o = parser.next();
     219                super.startElement(uri, localName, qName, atts);
    213220            }
    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 further
    218                     lastIds.pop();
    219                     ((Chunk) o).id = null;
    220                 } else {
    221                     // if preset item contains an id, store a mapping for later usage
    222                     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 usage
    229                 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/reference
    293                         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 times
    312                             // 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 #8530
    329                         }
    330                         listEntries.clear();
    331                         lastrole = null;
    332                     }
    333                 } else
    334                     throw new SAXException(tr("Preset sub element without parent"));
    335             }
    336221        }
    337         if (!all.isEmpty() && !checks.isEmpty()) {
    338             all.getLast().data.addAll(checks);
    339             checks.clear();
    340         }
    341         return all;
    342222    }
    343223
    344224    /**
    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
    351232     */
    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        }
    354248    }
    355249
    356250    /**
    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
    359254     * @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
    362256     * @throws SAXException if any XML error occurs
    363257     * @throws IOException if any I/O error occurs
    364258     */
    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);
    369261        Stopwatch stopwatch = Stopwatch.createStarted();
     262        Parser parser = new Parser();
     263
    370264        try (
    371             CachedFile cf = new CachedFile(source).setHttpAccept(PRESET_MIME_TYPES);
     265            CachedFile cf = new CachedFile(url);
    372266            // zip may be null, but Java 7 allows it: https://blogs.oracle.com/darcy/entry/project_coin_null_try_with
    373267            InputStream zip = cf.findZipEntryInputStream("xml", "preset")
    374268        ) {
     269            cf.setHttpAccept(PRESET_MIME_TYPES);
    375270            if (zip != null) {
    376271                zipIcons = cf.getFile();
    377272                I18n.addTexts(zipIcons);
    378273            }
    379274            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);
    381280            }
    382281        }
     282
     283        Root patchRoot = readPatchFile(url, validate);
     284        if (patchRoot != null)
     285            parser.getRoot().items.addAll(patchRoot.items);
     286
    383287        Logging.debug(stopwatch.toString("Reading presets"));
    384         return tp;
     288        return parser.getRoot();
    385289    }
    386290
    387291    /**
    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     *
    389337     * @param sources Collection of tagging presets sources.
    390338     * @param validate if {@code true}, presets will be validated against XML schema
    391      * @return Collection of all presets successfully read
     339     * @return the root elements of the XML resources
    392340     */
    393     public static Collection<TaggingPreset> readAll(Collection<String> sources, boolean validate) {
     341    public static Collection<Root> readAll(Collection<String> sources, boolean validate) {
    394342        return readAll(sources, validate, true);
    395343    }
    396344
    397345    /**
    398      * Reads all tagging presets from the given sources.
     346     * Reads all tagging presets from the given XML resources.
     347     *
    399348     * @param sources Collection of tagging presets sources.
    400349     * @param validate if {@code true}, presets will be validated against XML schema
    401350     * @param displayErrMsg if {@code true}, a blocking error message is displayed in case of I/O exception.
    402      * @return Collection of all presets successfully read
     351     * @return the root elements of the XML resources
    403352     */
    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<>();
    406355        for (String source : sources) {
    407356            try {
    408                 readAll(source, validate, allPresets);
     357                result.add(read(source, validate));
    409358            } catch (IOException e) {
    410359                Logging.log(Logging.LEVEL_ERROR, e);
    411360                Logging.error(source);
     
    434383                }
    435384            }
    436385        }
    437         return allPresets;
     386        return result;
    438387    }
    439388
    440389    /**
     
    443392     * @param displayErrMsg if {@code true}, a blocking error message is displayed in case of I/O exception.
    444393     * @return Collection of all presets successfully read
    445394     */
    446     public static Collection<TaggingPreset> readFromPreferences(boolean validate, boolean displayErrMsg) {
     395    public static Collection<Root> readFromPreferences(boolean validate, boolean displayErrMsg) {
    447396        return readAll(getPresetSources(), validate, displayErrMsg);
    448397    }
    449398
    450399    /**
     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    /**
    451408     * Returns the zip file where the icons are located
    452409     * @return the zip file where the icons are located
    453410     */
     
    471428        TaggingPresetReader.loadIcons = loadIcons;
    472429    }
    473430
    474     private TaggingPresetReader() {
    475         // Hide default constructor for utils classes
    476     }
     431    // fix checkstyle HideUtilityClassConstructor
     432    private TaggingPresetReader() {}
    477433}
  • src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetSearchDialog.java

     
    5454        if (buttonIndex == 0) {
    5555            TaggingPreset preset = selector.getSelectedPresetAndUpdateClassification();
    5656            if (preset != null) {
    57                 preset.actionPerformed(null);
     57                preset.getAction().actionPerformed(null);
    5858            }
    5959        }
    6060        selector.savePreferences();
  • src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetSelector.java

     
    2323import javax.swing.Action;
    2424import javax.swing.BoxLayout;
    2525import javax.swing.DefaultListCellRenderer;
    26 import javax.swing.Icon;
    2726import javax.swing.JCheckBox;
    2827import javax.swing.JLabel;
    2928import javax.swing.JList;
     
    3938import org.openstreetmap.josm.data.osm.OsmPrimitive;
    4039import org.openstreetmap.josm.data.preferences.BooleanProperty;
    4140import 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;
    4741import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
    4842import org.openstreetmap.josm.gui.widgets.SearchTextResultListPanel;
    4943import org.openstreetmap.josm.tools.Destroyable;
     
    7771                boolean isSelected, boolean cellHasFocus) {
    7872            JLabel result = (JLabel) def.getListCellRendererComponent(list, tp, index, isSelected, cellHasFocus);
    7973            result.setText(tp.getName());
    80             result.setIcon((Icon) tp.getValue(Action.SMALL_ICON));
     74            result.setIcon(tp.getIcon(Action.SMALL_ICON));
    8175            return result;
    8276        }
    8377    }
     
    8680     * Computes the match ration of a {@link TaggingPreset} wrt. a searchString.
    8781     */
    8882    public static class PresetClassification implements Comparable<PresetClassification> {
     83        /** The preset to classify */
    8984        public final TaggingPreset preset;
     85        /** how well this preset matches */
    9086        public int classification;
     87        /** where to put it in the list */
    9188        public int favoriteIndex;
    9289        private final Collection<String> groups;
    9390        private final Collection<String> names;
     
    9895            Set<String> groupSet = new HashSet<>();
    9996            Set<String> nameSet = new HashSet<>();
    10097            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);
    105101            }
    106             addLocaleNames(nameSet, preset);
    107             for (TaggingPresetItem item: preset.data) {
     102            addLocaleNames(nameSet, preset.getLocaleName());
     103            for (Item item: preset.getAllItems()) {
    108104                if (item instanceof KeyedItem) {
    109                     tagSet.add(((KeyedItem) item).key);
     105                    tagSet.add(((KeyedItem) item).getKey());
     106                    tagSet.addAll(((KeyedItem) item).getValues());
    110107                    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());
    115109                    }
    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());
    123112                }
    124113            }
    125114            this.groups = Utils.toUnmodifiableList(groupSet);
     
    127116            this.tags = Utils.toUnmodifiableList(tagSet);
    128117        }
    129118
    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) {
    132120            if (locName != null) {
    133121                Collections.addAll(collection, locName.toLowerCase(Locale.ENGLISH).split("\\s", -1));
    134122            }
     
    276264
    277265        private final List<PresetClassification> classifications = new ArrayList<>();
    278266
     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         */
    279277        public List<PresetClassification> getMatchingPresets(String searchText, boolean onlyApplicable, boolean inTags,
    280278                Set<TaggingPresetType> presetTypes, final Collection<? extends OsmPrimitive> selectedPrimitives) {
    281279            final String[] groupWords;
     
    292290            return getMatchingPresets(groupWords, nameWords, onlyApplicable, inTags, presetTypes, selectedPrimitives);
    293291        }
    294292
     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         */
    295303        public List<PresetClassification> getMatchingPresets(String[] groupWords, String[] nameWords, boolean onlyApplicable,
    296304                boolean inTags, Set<TaggingPresetType> presetTypes, final Collection<? extends OsmPrimitive> selectedPrimitives) {
    297305
     
    303311                if (onlyApplicable) {
    304312                    boolean suitable = preset.typeMatches(presetTypes);
    305313
    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()));
    310317                        // keep the preset to allow the creation of new relations
    311318                    }
    312319                    if (!suitable) {
     
    360367         */
    361368        public void loadPresets(Collection<TaggingPreset> presets) {
    362369            for (TaggingPreset preset : presets) {
    363                 if (preset instanceof TaggingPresetSeparator || preset instanceof TaggingPresetMenu) {
    364                     continue;
     370                if (preset.getClass() == TaggingPreset.class) {
     371                    classifications.add(new PresetClassification(preset));
    365372                }
    366                 classifications.add(new PresetClassification(preset));
    367373            }
    368374        }
    369375
  • src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetSeparator.java

     
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.gui.tagging.presets;
    33
     4import java.util.Map;
     5
     6import javax.swing.JMenu;
     7import javax.swing.JSeparator;
     8
    49/**
    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.
    611 * @since 895
    712 */
    8 public class TaggingPresetSeparator extends TaggingPreset {
     13final 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
    929    @Override
    10     public void setDisplayName() {
    11         // Do nothing
     30    public void addToMenu(JMenu parentMenu) {
     31        parentMenu.add(new JSeparator());
    1232    }
     33
     34    @Override
     35    public String toString() {
     36        return "TaggingPresetSeparator";
     37    }
    1338}
  • src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetType.java

     
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.gui.tagging.presets;
    33
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
    46import java.util.Arrays;
     7import java.util.EnumSet;
     8import java.util.Map;
    59
    610import org.openstreetmap.josm.data.osm.IPrimitive;
    711import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
     12import org.openstreetmap.josm.gui.util.LruCache;
    813
    914/**
    1015 * Enumeration of OSM primitive types associated with names and icons
     
    2429    private final String iconName;
    2530    private final String name;
    2631
     32    /** LRU cache for the parsing of types */
     33    private static final Map<String, EnumSet<TaggingPresetType>> TYPE_CACHE = new LruCache<>(16);
     34
    2735    TaggingPresetType(String iconName, String name) {
    2836        this.iconName = iconName + ".svg";
    2937        this.name = name;
     
    8391                .filter(t -> t.getName().equals(type))
    8492                .findFirst().orElse(null);
    8593    }
     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    }
    86125}
  • src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetUtils.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5import static org.openstreetmap.josm.tools.I18n.trc;
     6
     7import java.awt.Component;
     8import java.io.File;
     9import java.io.Serializable;
     10import java.util.ArrayList;
     11import java.util.Collection;
     12import java.util.Collections;
     13import java.util.Comparator;
     14import java.util.List;
     15import java.util.Map;
     16import java.util.concurrent.CompletableFuture;
     17import java.util.concurrent.ExecutionException;
     18import java.util.concurrent.TimeUnit;
     19import java.util.concurrent.TimeoutException;
     20
     21import javax.swing.AbstractAction;
     22import javax.swing.ImageIcon;
     23import javax.swing.JMenu;
     24import javax.swing.JMenuItem;
     25import javax.swing.JSeparator;
     26import javax.swing.SwingUtilities;
     27
     28import org.openstreetmap.josm.data.osm.DataSet;
     29import org.openstreetmap.josm.data.osm.OsmDataManager;
     30import org.openstreetmap.josm.data.osm.search.SearchCompiler;
     31import org.openstreetmap.josm.data.osm.search.SearchParseError;
     32import org.openstreetmap.josm.data.osm.search.SearchCompiler.Match;
     33import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem;
     34import org.openstreetmap.josm.gui.MainApplication;
     35import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
     36import org.openstreetmap.josm.tools.AlphanumComparator;
     37import org.openstreetmap.josm.tools.ImageProvider;
     38import org.openstreetmap.josm.tools.ImageResource;
     39import org.openstreetmap.josm.tools.Logging;
     40import org.openstreetmap.josm.tools.template_engine.ParseError;
     41import org.openstreetmap.josm.tools.template_engine.TemplateEntry;
     42import org.openstreetmap.josm.tools.template_engine.TemplateParser;
     43
     44/**
     45 * Utility class for tagging presets.
     46 */
     47public 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" --&gt; ["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

     
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.gui.tagging.presets;
    33
    4 import static java.util.Collections.singleton;
    54import static org.openstreetmap.josm.tools.I18n.tr;
    65
    76import java.util.ArrayList;
    87import java.util.Arrays;
    9 import java.util.Collection;
    108import java.util.List;
    119
    1210import javax.swing.JLabel;
    1311
    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;
    2212import org.openstreetmap.josm.data.preferences.sources.ValidatorPrefHelper;
    2313import org.openstreetmap.josm.data.validation.OsmValidator;
    2414import org.openstreetmap.josm.data.validation.TestError;
     
    2717import org.openstreetmap.josm.gui.MainApplication;
    2818import org.openstreetmap.josm.gui.util.GuiHelper;
    2919import org.openstreetmap.josm.tools.Logging;
    30 import org.openstreetmap.josm.tools.SubclassFilteredCollection;
    3120import org.openstreetmap.josm.tools.Utils;
    3221
    3322/**
    3423 * Validates the preset user input a the given primitive.
    3524 */
    36 interface TaggingPresetValidation {
     25public interface TaggingPresetValidation {
    3726
    3827    /**
    3928     * Asynchronously validates the user input for the given primitive.
    40      * @param original the primitive
     29     * @param handler the handler that holds the primitives to check
    4130     * @param validationLabel the label for validation errors
    42      * @param changedTags the list of tags that are set by this preset
    4331     */
    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));
    4734    }
    4835
    4936    /**
    5037     * Validates the user input for the given primitive.
    51      * @param primitive the primitive
     38     * @param handler the handler that holds the primitives to check
    5239     * @param validationLabel the label for validation errors
    5340     */
    54     static void validate(OsmPrimitive primitive, JLabel validationLabel) {
     41    static void validate(TaggingPresetHandler handler, JLabel validationLabel) {
    5542        try {
    5643            MapCSSTagChecker mapCSSTagChecker = OsmValidator.getTest(MapCSSTagChecker.class);
    5744            OpeningHourTest openingHourTest = OsmValidator.getTest(OpeningHourTest.class);
     
    5845            OsmValidator.initializeTests(Arrays.asList(mapCSSTagChecker, openingHourTest));
    5946
    6047            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()));
    6350
    6451            boolean visible = !errors.isEmpty();
    6552            String toolTipText = "<html>" + Utils.joinAsHtmlUnorderedList(Utils.transform(errors, e ->
     
    6956                validationLabel.setToolTipText(toolTipText);
    7057            });
    7158        } catch (Exception e) {
    72             Logging.warn("Failed to validate {0}", primitive);
     59            Logging.warn("Failed to validate {0}", handler.getPrimitives().iterator().next());
    7360            Logging.warn(e);
    74         } finally {
    75             primitive.getDataSet().clear();
    7661        }
    7762    }
    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     }
    9363}
  • src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresets.java

     
    33
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55
    6 import java.util.ArrayList;
     6import java.io.IOException;
    77import java.util.Collection;
    88import java.util.Collections;
    99import java.util.HashMap;
    1010import java.util.HashSet;
     11import java.util.LinkedHashMap;
     12import java.util.LinkedList;
     13import java.util.List;
    1114import java.util.Map;
    1215import java.util.Set;
     16import java.util.function.Predicate;
    1317
    1418import javax.swing.JMenu;
    15 import javax.swing.JMenuItem;
    16 import javax.swing.JSeparator;
    1719
    1820import org.openstreetmap.josm.actions.PreferencesAction;
    1921import org.openstreetmap.josm.data.osm.IPrimitive;
     
    2224import org.openstreetmap.josm.data.preferences.ListProperty;
    2325import org.openstreetmap.josm.gui.MainApplication;
    2426import org.openstreetmap.josm.gui.MainMenu;
    25 import org.openstreetmap.josm.gui.MenuScroller;
    2627import org.openstreetmap.josm.gui.preferences.ToolbarPreferences;
    2728import 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;
    3329import org.openstreetmap.josm.tools.MultiMap;
    3430import org.openstreetmap.josm.tools.SubclassFilteredCollection;
     31import org.xml.sax.SAXException;
    3532
    3633/**
    3734 * Class holding Tagging Presets and allowing to manage them.
     
    3936 */
    4037public final class TaggingPresets {
    4138
    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<>();
    4441
    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 */
    4645    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<>();
    4948
    5049    /** 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);
    5253    /**
    53      * Sort presets menu alphabetically
     54     * Defines whether the validator should be active in the preset dialog
     55     * @see TaggingPresetValidation
    5456     */
    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);
    6162
    6263    private TaggingPresets() {
    6364        // Hide constructor for utility classes
     
    6465    }
    6566
    6667    /**
    67      * Initializes tagging presets from preferences.
     68     * Standard initialization during app startup. Obeys users prefs.
    6869     */
     70    public static void initialize() {
     71        readFromPreferences();
     72        initializeMenus();
     73    }
     74
     75    /**
     76     * Initializes tagging presets from user preferences.
     77     */
    6978    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);
    7381    }
    7482
    7583    /**
    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
    7789     */
    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() {
    79113        MainMenu mainMenu = MainApplication.getMenu();
    80114        JMenu presetsMenu = mainMenu.presetsMenu;
    81115        if (presetsMenu.getComponentCount() == 0) {
     
    86120            presetsMenu.addSeparator();
    87121        }
    88122
    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()) {
    97130            presetsMenu.setVisible(false);
    98131        } 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);
    119135            }
    120             for (JMenu submenu : submenus.values()) {
    121                 if (submenu.getItemCount() >= MIN_ELEMENTS_FOR_SCROLLER.get()) {
    122                     MenuScroller.setScrollerFor(submenu);
    123                 }
    124             }
    125136        }
    126         if (SORT_MENU.get()) {
    127             TaggingPresetMenu.sortMenu(presetsMenu);
    128         }
    129         listeners.forEach(TaggingPresetListener::taggingPresetsModified);
    130137    }
    131138
    132139    // Cannot implement Destroyable since this is static
     
    137144     * @since 15582
    138145     */
    139146    public static void destroy() {
     147        unInitializeMenus();
     148        cleanUp();
     149    }
     150
     151    static void unInitializeMenus() {
    140152        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();
    148162        PRESET_TAG_CACHE.clear();
    149163        PRESET_ROLE_CACHE.clear();
    150         MainApplication.getMenu().presetsMenu.removeAll();
     164        rootElements.forEach(Item::destroy);
     165        rootElements.clear();
    151166    }
    152167
    153168    /**
    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
    156172     */
    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());
    161179            }
    162         }
     180        });
    163181    }
    164182
    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;
    183192    }
    184193
    185194    /**
    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
    188199     */
     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     */
    189210    public static Collection<TaggingPreset> getTaggingPresets() {
    190         return Collections.unmodifiableCollection(taggingPresets);
     211        return Collections.unmodifiableCollection(PRESET_CACHE.values());
    191212    }
    192213
    193214    /**
    194      * Replies 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
    196217     */
    197     public static Set<String> getPresetRoles() {
    198         return Collections.unmodifiableSet(PRESET_ROLE_CACHE);
     218    public static Collection<Role> getPresetRoles() {
     219        return Collections.unmodifiableCollection(PRESET_ROLE_CACHE);
    199220    }
    200221
    201222    /**
    202      * Replies a set of all keys in the tagging 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
    204225     */
    205226    public static Set<String> getPresetKeys() {
    206227        return Collections.unmodifiableSet(PRESET_TAG_CACHE.keySet());
     
    207228    }
    208229
    209230    /**
    210      * Return set of values for a key in the tagging presets
     231     * Returns all values seen in all presets for this key.
    211232     * @param key the key
    212      * @return set of values for a key in the tagging presets
     233     * @return the set of all values
    213234     */
    214235    public static Set<String> getPresetValues(String key) {
    215236        Set<String> values = PRESET_TAG_CACHE.get(key);
     
    232253     * Replies a new collection of all presets matching the parameters.
    233254     *
    234255     * @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)}
    236257     * @param onlyShowable whether only {@link TaggingPreset#isShowable() showable} presets should be returned
    237258     * @return a new collection of all presets matching the parameters.
    238259     * @see TaggingPreset#matches(Collection, Map, boolean)
     
    256277    }
    257278
    258279    /**
    259      * Adds a list of tagging presets to the current list.
    260      * @param presets The tagging presets to add
    261      */
    262     public static void addTaggingPresets(Collection<TaggingPreset> presets) {
    263         if (presets != null && taggingPresets.addAll(presets)) {
    264             listeners.forEach(TaggingPresetListener::taggingPresetsModified);
    265         }
    266     }
    267 
    268     /**
    269280     * Adds a tagging preset listener.
    270281     * @param listener The listener to add
    271282     */
  • src/org/openstreetmap/josm/gui/tagging/presets/Text.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
     6import java.util.ArrayList;
     7import java.awt.Color;
     8import java.awt.GridBagLayout;
     9import java.awt.Insets;
     10import java.text.NumberFormat;
     11import java.text.ParseException;
     12import java.util.Collection;
     13import java.util.Collections;
     14import java.util.List;
     15import java.util.Map;
     16
     17import javax.swing.AbstractButton;
     18import javax.swing.BorderFactory;
     19import javax.swing.ButtonGroup;
     20import javax.swing.JButton;
     21import javax.swing.JComponent;
     22import javax.swing.JLabel;
     23import javax.swing.JPanel;
     24import javax.swing.JToggleButton;
     25
     26import org.openstreetmap.josm.data.osm.Tag;
     27import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem;
     28import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxEditor;
     29import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel;
     30import org.openstreetmap.josm.gui.tagging.ac.AutoCompTextField;
     31import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
     32import org.openstreetmap.josm.gui.util.DocumentAdapter;
     33import org.openstreetmap.josm.gui.widgets.JosmComboBox;
     34import org.openstreetmap.josm.gui.widgets.OrientationAction;
     35import org.openstreetmap.josm.tools.GBC;
     36import org.openstreetmap.josm.tools.Logging;
     37import org.openstreetmap.josm.tools.Utils;
     38import org.openstreetmap.josm.tools.template_engine.TemplateEntry;
     39
     40/**
     41 * Text field type.
     42 */
     43final 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.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import java.util.Map;
     5
     6import javax.swing.ImageIcon;
     7import javax.swing.JLabel;
     8import javax.swing.SwingConstants;
     9
     10/**
     11 * A tagging preset item displaying a localizable text.
     12 * @since 6190
     13 */
     14abstract 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.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import java.util.Collection;
     5import java.util.NoSuchElementException;
     6import java.util.SortedMap;
     7import java.util.TreeMap;
     8
     9import org.openstreetmap.josm.data.osm.OsmPrimitive;
     10import 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 */
     17public 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; // NOSONAR
    26     /** the value to set when unchecked (default is "no") */
    27     public String value_off = OsmUtils.FALSE_VALUE; // NOSONAR
    28     /** whether the off value is disabled in the dialog, i.e., only unset or yes are provided */
    29     public boolean disable_off; // NOSONAR
    30     /** "on" or "off" or unset (default is unset) */
    31     public String default_; // only used for tagless objects // NOSONAR
    32 
    33     private QuadStateCheckBox check;
    34     private QuadStateCheckBox.State initialState;
    35     private Boolean def;
    36 
    37     @Override
    38     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 untagged
    50                 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.SELECTED
    61                     : value_off.equals(oneValue) || Boolean.FALSE.equals(def)
    62                     ? QuadStateCheckBox.State.NOT_SELECTED
    63                     : QuadStateCheckBox.State.UNSET;
    64 
    65         } else {
    66             def = null;
    67             // the objects have different values, or one or more objects have something
    68             // else than true/false. we display a quad-state check box
    69             // 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 text
    84         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 #15104
    90         } else {
    91             check.applyComponentOrientation(support.getDefaultComponentOrientation());
    92             p.add(check, GBC.eol()); // Do not fill, see #15104
    93         }
    94         check.addChangeListener(l -> support.fireItemValueModified(this, key, getValue()));
    95         return true;
    96     }
    97 
    98     @Override
    99     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     @Override
    114     public MatchType getDefaultMatch() {
    115         return MatchType.NONE;
    116     }
    117 
    118     @Override
    119     public Collection<String> getValues() {
    120         return disable_off ? Arrays.asList(value_on) : Arrays.asList(value_on, value_off);
    121     }
    122 
    123     @Override
    124     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=" + initialState
    132                         + ", " : "") + "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 6114
    20  */
    21 public class CheckGroup extends TaggingPresetItem {
    22 
    23     /**
    24      * Number of columns (positive integer)
    25      */
    26     public short columns = 1; // NOSONAR
    27 
    28     /**
    29      * List of checkboxes
    30      */
    31     public final List<Check> checks = new LinkedList<>();
    32 
    33     @Override
    34     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 #20792
    45             panel.add(new JLabel());
    46         }
    47 
    48         panel.applyComponentOrientation(support.getDefaultComponentOrientation());
    49         p.add(panel, GBC.eol());
    50         return false;
    51     }
    52 
    53     @Override
    54     public void addCommands(List<Tag> changedTags) {
    55         for (Check check : checks) {
    56             check.addCommands(changedTags);
    57         }
    58     }
    59 
    60     @Override
    61     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     @Override
    71     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; // NOSONAR
    47     /** The length of the combo box (number of characters allowed). */
    48     public int length; // NOSONAR
    49 
    50     protected JosmComboBox<PresetListEntry> combobox;
    51     protected AutoCompComboBoxModel<PresetListEntry> dropDownModel;
    52     protected AutoCompComboBoxModel<AutoCompletionItem> autoCompModel;
    53 
    54     class ComponentListener extends ComponentAdapter {
    55         @Override
    56         public void componentResized(ComponentEvent e) {
    57             // Make multi-line JLabels the correct size
    58             // Only needed if there is any short_description
    59             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 fire
    68             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     @Override
    87     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 definition
    92         initListEntries();
    93 
    94         // init the model
    95         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 in
    115         // the dropdown list.  We don't want that to happen because we want to show taller items in
    116         // the list than in the editor.  We can't use
    117         // {@code combobox.setPrototypeDisplayValue(PresetListEntry.ENTRY_EMPTY);} because that would
    118         // 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 the
    122         // value
    123         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)); // NOSONAR
    144             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)); // NOSONAR
    149             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)); // NOSONAR
    154         }
    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 null
    181      */
    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         @Override
    192         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     @Override
    213     protected PresetListEntry getSelectedItem() {
    214         Object sel = combobox.getSelectedItem();
    215         if (sel instanceof PresetListEntry)
    216             // selected from the dropdown
    217             return (PresetListEntry) sel;
    218         if (sel instanceof String) {
    219             // free edit.  If the free edit corresponds to a known entry, use that entry.  This is
    220             // to avoid that we write a display_value to the tag's value, eg. if the user did an
    221             // 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; // NOSONAR
    45     /**
    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; // NOSONAR
    51     /** The context used for translating {@link #values} */
    52     public String values_context; // NOSONAR
    53     /** Disabled internationalisation for value to avoid mistakes, see #11696 */
    54     public boolean values_no_i18n; // NOSONAR
    55     /** Whether to sort the values, defaults to true. */
    56     public boolean values_sort = true; // NOSONAR
    57     /**
    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; // NOSONAR
    63     /** The localized version of {@link #display_values}. */
    64     public String locale_display_values; // NOSONAR
    65     /**
    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; // NOSONAR
    73     /** The localized version of {@link #short_descriptions}. */
    74     public String locale_short_descriptions; // NOSONAR
    75     /** 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_; // NOSONAR
    77     /**
    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 = ';'; // NOSONAR
    83     /** 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; // NOSONAR
    86     /** whether to use values for search via {@link TaggingPresetSelector} */
    87     public boolean values_searchable; // NOSONAR
    88 
    89     /**
    90      * The standard entries in the combobox dropdown or multiselect list. These entries are defined
    91      * 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 text
    102      * 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 to
    116          *
    117          * Note: This is not the width of the list, but the width to which we format any multi-line
    118          * label in the list.  We cannot use the list's width because at the time the combobox
    119          * measures its items, it is not guaranteed that the list is already sized, the combobox may
    120          * not even be layed out yet.  Set this to {@code combobox.getWidth()}
    121          *
    122          * @param width the width
    123          */
    124         public void setWidth(int width) {
    125             if (width <= 0)
    126                 width = 200;
    127             this.width = width - 20;
    128         }
    129 
    130         @Override
    131         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 the
    138                 // editor-ersatz of a readonly combobox. fixes #6157
    139                 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" --&gt; ["A, B, C", "one, two"]
    153      * @param delimiter the delimiter, e.g. a comma. separates the entries and
    154      *      must be escaped within one entry
    155      * @param s the string
    156      * @return splitted items
    157      */
    158     public static List<String> splitEscaped(char delimiter, String s) {
    159         if (s == null)
    160             return null; // NOSONAR
    161 
    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 value
    189      */
    190     protected abstract PresetListEntry getSelectedItem();
    191 
    192     @Override
    193     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 display
    201      */
    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 panel
    209      *
    210      * @param p the panel
    211      * @return the label
    212      */
    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 function
    231         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; // NOSONAR
    251     }
    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 check
    257      * @param b The other list
    258      * @param name The name of the list for error reporting
    259      * @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; // NOSONAR
    267         }
    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} attribute
    282             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 a
    322      * discussion of all the options see the enclosed tickets.
    323      *
    324      * @param usage The key Usage
    325      * @param support The support
    326      * @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 key
    338             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 key
    346             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     @Override
    356     public void addCommands(List<Tag> changedTags) {
    357         String value = getSelectedItem().value;
    358 
    359         // no change if same as before
    360         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) { // NOPMD
    374         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 that
    396      * already have tags.
    397      * <p>
    398      * Note: used for {@code addr:*} tags in {@code defaultpresets.xml}.
    399      *
    400      * @return true if see above
    401      */
    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 add
    409      */
    410     public void addListEntry(PresetListEntry e) {
    411         presetListEntries.add(e);
    412         // we need to fix the entries because the XML Parser
    413         // {@link org.openstreetmap.josm.tools.XmlObjectParser.Parser#startElement} has used the
    414         // default standard constructor for {@link PresetListEntry} if the list entry was defined
    415         // 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 add
    422      */
    423     public void addListEntries(Collection<PresetListEntry> e) {
    424         for (PresetListEntry i : e) {
    425             addListEntry(i);
    426         }
    427     }
    428 
    429     @Override
    430     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 6198
    17  */
    18 public class ItemSeparator extends TaggingPresetItem {
    19 
    20     @Override
    21     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     @Override
    27     public void addCommands(List<Tag> changedTags) {
    28         // Do nothing
    29     }
    30 
    31     @Override
    32     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; // NOSONAR
    20 
    21     @Override
    22     public boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support) {
    23         return false;
    24     }
    25 
    26     @Override
    27     public void addCommands(List<Tag> changedTags) {
    28         changedTags.add(asTag());
    29     }
    30 
    31     /**
    32      * Returns the {@link Tag} set by this item
    33      * @return the tag
    34      */
    35     public Tag asTag() {
    36         return new Tag(key, value);
    37     }
    38 
    39     @Override
    40     public MatchType getDefaultMatch() {
    41         return MatchType.KEY_VALUE_REQUIRED;
    42     }
    43 
    44     @Override
    45     public Collection<String> getValues() {
    46         return Collections.singleton(value);
    47     }
    48 
    49     @Override
    50     public String toString() {
    51         return "Key [key=" + key + ", value=" + value + ", text=" + text
    52                 + ", text_context=" + text_context + ", match=" + match
    53                 + ']';
    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; // NOSONAR
    42     /**
    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(); // NOSONAR
    54 
    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 value
    80          */
    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 value
    88          * @return the {@code MatchType} for the given textual value
    89          */
    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 key
    101      *
    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 primitive
    121          * @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 value
    130          * @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 keys
    138          * @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 of
    156          * {@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 primitives
    176      * @param sel the primitives
    177      * @param key the key
    178      * @return the tag usage
    179      */
    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 required
    212      */
    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 match
    221      */
    222     public abstract MatchType getDefaultMatch();
    223 
    224     /**
    225      * Returns the list of values.
    226      * @return the list of values
    227      */
    228     public abstract Collection<String> getValues();
    229 
    230     protected String getKeyTooltipText() {
    231         return tr("This corresponds to the key ''{0}''", key);
    232     }
    233 
    234     @Override
    235     public Boolean matches(Map<String, String> tags) {
    236         switch (MatchType.ofString(match)) {
    237         case NONE:
    238             return null; // NOSONAR
    239         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     @Override
    264     public String toString() {
    265         return "KeyedItem [key=" + key + ", text=" + text
    266                 + ", text_context=" + text_context + ", match=" + match
    267                 + ']';
    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     @Override
    16     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 8863
    23  */
    24 public class Link extends TextItem {
    25 
    26     /** The OSM wiki page to display. */
    27     public String wiki; // NOSONAR
    28 
    29     /** The link to display. */
    30     public String href; // NOSONAR
    31 
    32     /** The localized version of {@link #href}. */
    33     public String locale_href; // NOSONAR
    34 
    35     @Override
    36     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                 @Override
    51                 public void mouseClicked(MouseEvent e) {
    52                     if (SwingUtilities.isLeftMouseButton(e)) {
    53                         // Open localized page if exists
    54                         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 URL
    75      * @since 15423
    76      */
    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     @Override
    87     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; // NOSONAR
    28 
    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     @Override
    42     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 values
    50         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 makes
    57         // 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 we
    82             // have icons of different sizes. Calculate the size of the first {@code rows} entries
    83             // 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)); // NOSONAR
    96 
    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     @Override
    105     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 8863
    15  */
    16 public class Optional extends TextItem {
    17 
    18     // TODO: Draw a box around optional stuff
    19     @Override
    20     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()); // space
    25         p.add(label, GBC.eol());
    26         p.add(new JLabel(" "), GBC.eol()); // space
    27         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 8863
    26  */
    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         @Override
    39         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 = ""; // NOSONAR
    46 
    47     /**
    48      * Creates a label to be inserted aboive this link
    49      * @return a label
    50      */
    51     public JLabel createLabel() {
    52         initializeLocaleText(tr("Edit also …"));
    53         return new JLabel(locale_text);
    54     }
    55 
    56     @Override
    57     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     @Override
    71     public void addCommands(List<Tag> changedTags) {
    72         // Do nothing
    73     }
    74 
    75     @Override
    76     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} and
    21  * {@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). Except
    31      * when the value is {@code "<different>"}, which is never written, or the value is empty, which
    32      * deletes the tag.  {@code value} is never translated.
    33      */
    34     public String value; // NOSONAR
    35     /** The ComboMultiSelect that displays the list */
    36     public ComboMultiSelect cms; // NOSONAR
    37     /** Text displayed to the user instead of {@link #value}. */
    38     public String display_value; // NOSONAR
    39     /** Text to be displayed below {@link #display_value} in the combobox list. */
    40     public String short_description; // NOSONAR
    41     /** The location of icon file to display */
    42     public String icon; // NOSONAR
    43     /** The size of displayed icon. If not set, default is size from icon file */
    44     public short icon_size; // NOSONAR
    45     /** The localized version of {@link #display_value}. */
    46     public String locale_display_value; // NOSONAR
    47     /** The localized version of {@link #short_description}. */
    48     public String locale_short_description; // NOSONAR
    49 
    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 and
    64      * {@link ComboMultiSelect} context.
    65      *
    66      * @param value value
    67      * @param cms the ComboMultiSelect
    68      */
    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 to
    78      * aid the user.  The whole contents is wrapped to {@code width}.
    79      *
    80      * @param width the width in px
    81      * @return HTML formatted contents
    82      */
    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=4866977
    106         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 display
    126      */
    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 display
    142      */
    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 key
    153      * @return the tooltip
    154      */
    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 Editor
    166     @Override
    167     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     @Override
    174     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     @Override
    182     public int hashCode() {
    183         return Objects.hash(value);
    184     }
    185 
    186     /**
    187      * Returns how many selected primitives had this value set.
    188      * @return see above
    189      */
    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     @Override
    196     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 for
    29  * 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; // NOSONAR
    43         /** Role name used in a relation */
    44         public String key; // NOSONAR
    45         /** Is the role name a regular expression */
    46         public boolean regexp; // NOSONAR
    47         /** The text to display */
    48         public String text; // NOSONAR
    49         /** The context used for translating {@link #text} */
    50         public String text_context; // NOSONAR
    51         /** The localized version of {@link #text}. */
    52         public String locale_text; // NOSONAR
    53         /** An expression (cf. search dialog) for objects of this role */
    54         public SearchCompiler.Match memberExpression; // NOSONAR
    55         /** Is this role required at least once in the relation? */
    56         public boolean required; // NOSONAR
    57         /** 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 types
    63          * @throws SAXException if an unknown type is detected
    64          */
    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 role
    95          * @param memberExpression an expression (cf. search dialog) for objects of this role
    96          * @throws SAXException in case of parsing error
    97          */
    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 appear
    113          */
    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 value
    120          * @param c count
    121          * @return the highest possible value or the lowest allowed value
    122          * @see #required
    123          */
    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             else
    132                 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 check
    138          * @return <code>true</code> if role matches
    139          * @since 11989
    140          */
    141         public boolean isRole(String role) {
    142             if (regexp && role != null) { // pass null through, it will anyway fail
    143                 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 role
    151          * @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         @Override
    181         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     @Override
    192     public boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support) {
    193         p.add(new JLabel(" "), GBC.eol()); // space
    194         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     @Override
    210     public void addCommands(List<Tag> changedTags) {
    211         // Do nothing
    212     }
    213 
    214     @Override
    215     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     @Override
    20     public boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support) {
    21         p.add(new JLabel(" "), GBC.eol()); // space
    22         return false;
    23     }
    24 
    25     @Override
    26     public void addCommands(List<Tag> changedTags) {
    27         // Do nothing
    28     }
    29 
    30     @Override
    31     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; // NOSONAR
    52 
    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_; // NOSONAR
    55     /** The original value */
    56     public String originalValue; // NOSONAR
    57     /** 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"; // NOSONAR
    59     /**
    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; // NOSONAR
    66     /** The length of the text box (number of characters allowed). */
    67     public short length; // NOSONAR
    68     /** A comma separated list of alternative keys to use for autocompletion. */
    69     public String alternative_autocomplete_keys; // NOSONAR
    70 
    71     private JComponent value;
    72     private transient TemplateEntry valueTemplate;
    73 
    74     @Override
    75     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-numeric
    115                     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 enabled
    119                 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 disabled
    126                 textField.setText("");
    127             }
    128             value = textField;
    129             originalValue = null;
    130         } else if (usage.hasUniqueValue()) {
    131             // all objects use the same value
    132             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> this
    138             // cannot be an AutoCompComboBox because the values in the dropdown are different from
    139             // 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 field
    153         // into a panel, appending a number of buttons.
    154         // auto_increment has a format like -2,-1,1,2
    155         // the text box being the first component in the panel is relied
    156         // 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 value
    163             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 group
    182             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 to
    187             // have *no* button selected after the X is clicked, instead
    188             // of the X remaining selected
    189             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     @Override
    236     public void addCommands(List<Tag> changedTags) {
    237 
    238         // return if unchanged
    239         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     @Override
    258     public MatchType getDefaultMatch() {
    259         return MatchType.NONE;
    260     }
    261 
    262     @Override
    263     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 { // NOPMD
    275         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 save
    286         // the same value to all selected primitives, which is probably not
    287         // what you want.
    288         if (valueTemplate == null || support.getSelected().size() > 1) { // only fire on normal fields
    289             textField.getDocument().addDocumentListener(DocumentAdapter.create(ignore ->
    290                     support.fireItemValueModified(this, key, textField.getText())));
    291         } else { // only listen on calculated fields
    292             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 6190
    17  */
    18 public abstract class TextItem extends TaggingPresetItem {
    19 
    20     /** The text to display */
    21     public String text; // NOSONAR
    22 
    23     /** The context used for translating {@link #text} */
    24     public String text_context; // NOSONAR
    25 
    26     /** The localized version of {@link #text} */
    27     public String locale_text; // NOSONAR
    28 
    29     /** The location of icon file to display */
    30     public String icon; // NOSONAR
    31     /** The size of displayed icon. If not set, default is 16px */
    32     public short icon_size = 16; // NOSONAR
    33 
    34     protected final void initializeLocaleText(String defaultText) {
    35         if (locale_text == null) {
    36             locale_text = getLocaleText(text, text_context, defaultText);
    37         }
    38     }
    39 
    40     @Override
    41     public void addCommands(List<Tag> changedTags) {
    42         // Do nothing
    43     }
    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 icon
    53      * @param label the component
    54      * @since 17605
    55      */
    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 17605
    65      */
    66     public ImageIcon getIcon() {
    67         return icon == null ? null : loadImageIcon(icon, TaggingPresetReader.getZipIcons(), (int) icon_size);
    68     }
    69 
    70     @Override
    71     public String toString() {
    72         return getClass().getSimpleName() + " [" + fieldsToString() + ']';
    73     }
    74 }
  • src/org/openstreetmap/josm/gui/widgets/JosmComboBox.java

     
    5959    /** greyed text to display in the editor when the selected value is empty */
    6060    private String hint;
    6161
     62    private boolean fakeWidth;
     63
    6264    /**
    6365     * Creates a {@code JosmComboBox} with a {@link JosmComboBoxModel} data model.
    6466     * The default data model is an empty list of objects.
     
    302304    }
    303305
    304306    /**
     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    /**
    305331     * Get the dropdown list component
    306332     *
    307333     * @return the list or null
     
    392418        int freeAbove = bounds.y - screenBounds.y;
    393419        int freeBelow = (screenBounds.y + screenBounds.height) - (bounds.y + bounds.height);
    394420
    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) {
    400425            // Calculate the free space available on screen
    401426            Insets insets = jList.getInsets();
    402427            // A small fudge factor that accounts for the displacement of the popup relative to the
     
    403428            // combobox and the popup shadow.
    404429            int fudge = 4;
    405430            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;
    408434                Border border = scroller.getViewportBorder();
    409435                if (border != null) {
    410436                    insets = border.getBorderInsets(null);
     
    427453                if (h >= free)
    428454                    break;
    429455            }
    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;
    434457        }
     458        // Logging.debug("free = {0}, h = {1}, i = {2}, bounds = {3}, screenBounds = {4}", free, h, i, bounds, screenBounds);
     459        setMaximumRowCount(maxRowCount);
    435460    }
    436461
    437462    @Override
  • src/org/openstreetmap/josm/gui/widgets/JosmComboBoxModel.java

     
    205205    }
    206206
    207207    /**
     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    /**
    208238     * Adds an element to the top of the list.
    209239     * <p>
    210240     * 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

     
    4242 */
    4343public class JosmTextField extends JTextField implements Destroyable, ComponentListener, FocusListener, PropertyChangeListener {
    4444
    45     private final PopupMenuLauncher launcher;
     45    private PopupMenuLauncher launcher;
    4646    private String hint;
    4747    private Icon icon;
    4848    private Point iconPos;
     
    241241    }
    242242
    243243    /**
     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    /**
    244252     * Empties the internal undo manager.
    245253     * @since 14977
    246254     */
  • src/org/openstreetmap/josm/tools/ImageProvider.java

     
    725725    }
    726726
    727727    /**
     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    /**
    728742     * Load an image with a given file name.
    729743     *
    730744     * @param subdir subdirectory the image lies in
  • src/org/openstreetmap/josm/tools/MultiMap.java

     
    124124    }
    125125
    126126    /**
     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    /**
    127135     * Returns {@code true} if this map contains no key-value mappings.
    128136     * @return {@code true} if this map contains no key-value mappings
    129137     * @see Map#isEmpty()
  • src/org/openstreetmap/josm/tools/OsmPrimitiveImageProvider.java

     
    2222import org.openstreetmap.josm.gui.mappaint.styleelement.MapImage;
    2323import org.openstreetmap.josm.gui.mappaint.styleelement.NodeElement;
    2424import org.openstreetmap.josm.gui.mappaint.styleelement.StyleElement;
    25 import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
    2625import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
    2726
    2827/**
     
    5655        // Check if the presets have icons for nodes/relations.
    5756        if (primitive.isTagged() && (!options.contains(Options.NO_WAY_PRESETS) || OsmPrimitiveType.WAY != primitive.getType())) {
    5857            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()))
    6262                    .filter(Objects::nonNull)
    6363                    .findFirst();
    6464            if (icon.isPresent()) {
  • src/org/openstreetmap/josm/tools/XmlParsingException.java

     
    55
    66import org.xml.sax.Locator;
    77import org.xml.sax.SAXException;
     8import org.xml.sax.helpers.LocatorImpl;
    89
    910/**
    1011 * An exception thrown during XML parsing, with known line and column.
     
    1112 * @since 6906
    1213 */
    1314public class XmlParsingException extends SAXException {
    14     private int columnNumber;
    15     private int lineNumber;
     15    private LocatorImpl locator = new LocatorImpl();
    1616
    1717    /**
    1818     * Constructs a new {@code XmlParsingException}.
     
    4646     */
    4747    public XmlParsingException rememberLocation(Locator locator) {
    4848        if (locator != null) {
    49             this.columnNumber = locator.getColumnNumber();
    50             this.lineNumber = locator.getLineNumber();
     49            this.locator = new LocatorImpl(locator);
    5150        }
    5251        return this;
    5352    }
     
    5554    @Override
    5655    public String getMessage() {
    5756        String msg = super.getMessage();
    58         if (lineNumber == 0 && columnNumber == 0)
     57        if (getLineNumber() == 0 && getColumnNumber() == 0)
    5958            return msg;
    6059        if (msg == null) {
    6160            msg = getClass().getName();
    6261        }
    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());
    6464    }
    6565
    6666    /**
     
    6868     * @return the column number where the exception occurred
    6969     */
    7070    public int getColumnNumber() {
    71         return columnNumber;
     71        return locator.getColumnNumber();
    7272    }
    7373
    7474    /**
     
    7676     * @return the line number where the exception occurred
    7777     */
    7878    public int getLineNumber() {
    79         return lineNumber;
     79        return locator.getLineNumber();
    8080    }
    8181}
  • test/functional/org/openstreetmap/josm/tools/ImageProviderTest.java

     
    66import static org.junit.jupiter.api.Assertions.assertFalse;
    77import static org.junit.jupiter.api.Assertions.assertNotNull;
    88import static org.openstreetmap.josm.gui.mappaint.MapCSSRendererTest.assertImageEquals;
     9import static org.openstreetmap.josm.gui.tagging.presets.ItemFactory.build;
    910
    1011import java.awt.Dimension;
    1112import java.awt.Image;
     
    1415import java.awt.image.BufferedImage;
    1516import java.io.File;
    1617import java.io.IOException;
    17 import java.util.Arrays;
    1818import java.util.Collections;
    1919import java.util.List;
    2020import java.util.function.UnaryOperator;
     
    3434import org.openstreetmap.josm.TestUtils;
    3535import org.openstreetmap.josm.data.coor.LatLon;
    3636import org.openstreetmap.josm.data.osm.Node;
     37import org.openstreetmap.josm.gui.tagging.presets.Key;
     38import org.openstreetmap.josm.gui.tagging.presets.Root;
    3739import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
    3840import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
    39 import org.openstreetmap.josm.gui.tagging.presets.items.Key;
    4041import org.openstreetmap.josm.testutils.JOSMTestRules;
    4142import org.xml.sax.SAXException;
    4243
     
    134135     */
    135136    @Test
    136137    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);
    148150        Node node = new Node(LatLon.ZERO);
    149151        node.put("amenity", "fuel");
    150152        assertDoesNotThrow(() -> OsmPrimitiveImageProvider.getResource(node, Collections.emptyList()));
  • test/unit/org/openstreetmap/josm/data/osm/DefaultNameFormatterTest.java

     
    1616
    1717import org.junit.jupiter.api.Test;
    1818import org.openstreetmap.josm.TestUtils;
    19 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetReader;
    2019import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
    2120import org.openstreetmap.josm.io.IllegalDataException;
    2221import org.openstreetmap.josm.io.OsmReader;
     
    5251    @Test
    5352    @SuppressFBWarnings(value = "ITA_INEFFICIENT_TO_ARRAY")
    5453    void testTicket9632() throws IllegalDataException, IOException, SAXException {
     54
     55        DefaultNameFormatter f = DefaultNameFormatter.getInstance();
     56
    5557        String source = "presets/Presets_BicycleJunction-preset.xml";
    5658        wireMockServer.stubFor(get(urlEqualTo("/" + source))
    5759                .willReturn(aResponse()
     
    5860                    .withStatus(200)
    5961                    .withHeader("Content-Type", "text/xml")
    6062                    .withBodyFile(source)));
    61         TaggingPresets.addTaggingPresets(TaggingPresetReader.readAll(wireMockServer.url(source), true));
    6263
    63         Comparator<IRelation<?>> comparator = DefaultNameFormatter.getInstance().getRelationComparator();
     64        TaggingPresets.testInitialize(wireMockServer.url(source));
     65        Comparator<IRelation<?>> comparator = f.getRelationComparator();
    6466
    6567        try (InputStream is = TestUtils.getRegressionDataStream(9632, "data.osm.zip")) {
    6668            DataSet ds = OsmReader.parseDataSet(is, null);
    6769
     70            // CHECKSTYLE.OFF: SingleSpaceSeparator
     71            // CHECKSTYLE.OFF: ParenPad
     72
    6873            // Test with 3 known primitives causing the problem. Correct order is p1, p3, p2 with this preset
    6974            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);
    7277
    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));
    7981
    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
    8388
    84             assertEquals(comparator.compare(p1, p3), -1); // p1 < p3
    85             assertEquals(comparator.compare(p3, p1),  1); // p3 > p1
    86             assertEquals(comparator.compare(p2, p3),  1); // p2 > p3
    87             assertEquals(comparator.compare(p3, p2), -1); // p3 < p2
    8889            // CHECKSTYLE.ON: SingleSpaceSeparator
     90            // CHECKSTYLE.ON: ParenPad
    8991
    9092            Relation[] relations = new ArrayList<>(ds.getRelations()).toArray(new Relation[0]);
    9193
  • test/unit/org/openstreetmap/josm/data/osm/search/SearchCompilerTest.java

     
    77import static org.junit.jupiter.api.Assertions.assertNotNull;
    88import static org.junit.jupiter.api.Assertions.assertThrows;
    99import static org.junit.jupiter.api.Assertions.assertTrue;
     10import static org.openstreetmap.josm.gui.tagging.presets.ItemFactory.build;
    1011
    1112import java.lang.reflect.Field;
    1213import java.nio.charset.StandardCharsets;
     
    1314import java.nio.file.Files;
    1415import java.nio.file.Paths;
    1516import java.time.Instant;
    16 import java.util.Arrays;
    1717import java.util.Collection;
    18 import java.util.Collections;
    1918import java.util.Set;
    2019
    2120import org.junit.Assert;
     
    3938import org.openstreetmap.josm.data.osm.WayData;
    4039import org.openstreetmap.josm.data.osm.search.SearchCompiler.ExactKeyValue;
    4140import org.openstreetmap.josm.data.osm.search.SearchCompiler.Match;
     41import org.openstreetmap.josm.gui.tagging.presets.Key;
     42import org.openstreetmap.josm.gui.tagging.presets.Root;
    4243import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
    4344import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetMenu;
    44 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType;
    4545import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
    46 import org.openstreetmap.josm.gui.tagging.presets.items.Key;
    4746import org.openstreetmap.josm.testutils.annotations.BasicPreferences;
    4847import org.openstreetmap.josm.tools.Logging;
    4948
     
    602601     */
    603602    @Test
    604603    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);
    609607
    610608        String combinedPresetname = testPreset.getRawName();
    611609        SearchSetting settings = new SearchSetting();
     
    628626     */
    629627    @Test
    630628    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();
    635630
    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\"";
    638641        SearchSetting settings = new SearchSetting();
    639642        settings.text = query;
    640643        settings.mapCSSSearch = false;
    641644
    642         // load presets and add the test preset
    643         TaggingPresets.readFromPreferences();
    644         TaggingPresets.addTaggingPresets(Collections.singletonList(testPreset));
    645 
    646645        Match match = SearchCompiler.compile(settings);
    647646
    648647        // access the private field where all matching presets are stored
     
    667666     */
    668667    @Test
    669668    void testPresetLookupWildcard() throws SearchParseError, NoSuchFieldException, IllegalAccessException {
    670         TaggingPresetMenu group = new TaggingPresetMenu();
    671         group.name = "TestPresetGroup";
     669        Root root = newRoot();
    672670
    673         TaggingPreset testPreset1 = new TaggingPreset();
    674         testPreset1.name = "TestPreset1";
    675         testPreset1.group = group;
     671        TaggingPresetMenu group = newTaggingPresetMenu("TestPresetGroup");
     672        root.addItem(group);
    676673
    677         TaggingPreset testPreset2 = new TaggingPreset();
    678         testPreset2.name = "TestPreset2";
    679         testPreset2.group = group;
     674        TaggingPreset testPreset1 = newTaggingPreset("TestPreset1");
     675        group.addItem(testPreset1);
    680676
    681         TaggingPreset testPreset3 = new TaggingPreset();
    682         testPreset3.name = "TestPreset3";
    683         testPreset3.group = group;
     677        TaggingPreset testPreset2 = newTaggingPreset("TestPreset2");
     678        group.addItem(testPreset2);
    684679
    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/*\"";
    686687        SearchSetting settings = new SearchSetting();
    687688        settings.text = query;
    688689        settings.mapCSSSearch = false;
    689690
    690         TaggingPresets.readFromPreferences();
    691         TaggingPresets.addTaggingPresets(Arrays.asList(testPreset1, testPreset2, testPreset3));
    692 
    693691        Match match = SearchCompiler.compile(settings);
    694692
    695693        // access the private field where all matching presets are stored
     
    717715        final String key = "test_key1";
    718716        final String val = "test_val1";
    719717
    720         Key key1 = new Key();
    721         key1.key = key;
    722         key1.value = val;
     718        Root root = newRoot();
    723719
    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);
    730722
     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
    731729        TaggingPresets.readFromPreferences();
    732         TaggingPresets.addTaggingPresets(Collections.singleton(testPreset));
     730        TaggingPresets.addRoot(root);
    733731
    734732        String query = "preset:" + "\"" + testPreset.getRawName() + "\"";
    735733
     
    763761        }
    764762    }
    765763
     764    private static Root newRoot() {
     765        return (Root) build("presets");
     766    }
     767
    766768    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);
    770770    }
    771771
     772    private static TaggingPresetMenu newTaggingPresetMenu(String name) {
     773        return (TaggingPresetMenu) build("group name={0}", name);
     774    }
     775
    772776    /**
    773777     * Search for {@code nodes:2}.
    774778     * @throws SearchParseError if an error has been encountered while compiling
     
    843847
    844848    /**
    845849     * Non-regression test for JOSM #21463
     850     * @throws SearchParseError in case of parser error
    846851     */
    847852    @Test
    848853    void testNonRegression21463() throws SearchParseError {
  • test/unit/org/openstreetmap/josm/data/validation/tests/OpeningHourTestTest.java

     
    99import static org.junit.jupiter.api.Assertions.assertNotNull;
    1010import static org.junit.jupiter.api.Assertions.assertTrue;
    1111
     12import java.io.IOException;
    1213import java.util.Arrays;
    13 import java.util.Collection;
    1414import java.util.LinkedHashSet;
    1515import java.util.List;
    1616import java.util.Locale;
     
    2424import org.openstreetmap.josm.data.preferences.sources.ValidatorPrefHelper;
    2525import org.openstreetmap.josm.data.validation.Severity;
    2626import org.openstreetmap.josm.data.validation.TestError;
     27import org.openstreetmap.josm.gui.tagging.presets.Item;
     28import org.openstreetmap.josm.gui.tagging.presets.KeyedItem;
    2729import 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;
     30import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
    3131import org.openstreetmap.josm.testutils.annotations.BasicPreferences;
    3232import org.openstreetmap.josm.testutils.annotations.I18n;
    3333import org.openstreetmap.josm.tools.Logging;
    34 
     34import org.xml.sax.SAXException;
    3535import org.junit.jupiter.api.BeforeEach;
    3636import org.junit.jupiter.api.Test;
    3737
     
    216216     * Tests that predefined values in presets are correct.
    217217     */
    218218    @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");
    221222        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()) {
    224225                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())) {
    226227                    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));
    228229                    }
    229230                }
    230231            }
  • test/unit/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditorTest.java

     
    2020import org.openstreetmap.josm.data.osm.Way;
    2121import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    2222import org.openstreetmap.josm.gui.tagging.TagEditorPanel;
    23 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
    2423import org.openstreetmap.josm.testutils.JOSMTestRules;
    2524import org.openstreetmap.josm.testutils.mockers.JOptionPaneSimpleMocker;
    2625
     
    123122        OsmDataLayer layer = new OsmDataLayer(ds, "test", null);
    124123        IRelationEditor re = newRelationEditor(relation, layer);
    125124
    126         AutoCompletingTextField tfRole = GenericRelationEditor.buildRoleTextField(re);
    127         assertNotNull(tfRole);
    128 
    129125        TagEditorPanel tagEditorPanel = new TagEditorPanel(relation, null);
    130126
    131127        JPanel top = GenericRelationEditor.buildTagEditorPanel(tagEditorPanel);
  • test/unit/org/openstreetmap/josm/gui/dialogs/relation/MemberTableModelTest.java

     
    3333            }
    3434
    3535            @Override
    36             public Collection<OsmPrimitive> getSelection() {
     36            public Collection<OsmPrimitive> getPrimitives() {
    3737                return Collections.<OsmPrimitive>singleton(n);
    3838            }
    3939        }).getRelationMemberForPrimitive(n));
  • test/unit/org/openstreetmap/josm/gui/dialogs/relation/actions/AbstractRelationEditorActionTest.java

     
    1212import org.openstreetmap.josm.data.osm.OsmPrimitive;
    1313import org.openstreetmap.josm.data.osm.Relation;
    1414import org.openstreetmap.josm.data.osm.Tag;
     15import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem;
    1516import org.openstreetmap.josm.gui.dialogs.relation.GenericRelationEditorTest;
    1617import org.openstreetmap.josm.gui.dialogs.relation.IRelationEditor;
    1718import org.openstreetmap.josm.gui.dialogs.relation.MemberTable;
     
    2021import org.openstreetmap.josm.gui.dialogs.relation.SelectionTableModel;
    2122import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    2223import org.openstreetmap.josm.gui.tagging.TagEditorModel;
    23 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
     24import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBox;
     25import org.openstreetmap.josm.gui.tagging.ac.AutoCompEvent;
     26import org.openstreetmap.josm.gui.tagging.ac.AutoCompListener;
     27import org.openstreetmap.josm.gui.tagging.ac.AutoCompTextField;
    2428import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetHandler;
    2529import org.openstreetmap.josm.testutils.JOSMTestRules;
    2630
     
    3135 * @author Michael Zangl
    3236 */
    3337@Disabled
    34 public abstract class AbstractRelationEditorActionTest {
     38public abstract class AbstractRelationEditorActionTest implements AutoCompListener {
    3539    /**
    3640     * Platform for tooltips.
    3741     */
     
    4650    private IRelationEditor editor;
    4751    private MemberTable memberTable;
    4852    private MemberTableModel memberTableModel;
    49     private AutoCompletingTextField tfRole;
     53    private AutoCompTextField<AutoCompletionItem> tfRole;
    5054    private TagEditorModel tagModel;
    5155
    5256    protected final IRelationEditorActionAccess relationEditorAccess = new IRelationEditorActionAccess() {
    5357
    5458        @Override
    55         public AutoCompletingTextField getTextFieldRole() {
     59        public AutoCompTextField<AutoCompletionItem> getTextFieldRole() {
    5660            return tfRole;
    5761        }
    5862
     
    102106            }
    103107
    104108            @Override
    105             public Collection<OsmPrimitive> getSelection() {
     109            public Collection<OsmPrimitive> getPrimitives() {
    106110                return Collections.<OsmPrimitive>singleton(orig);
    107111            }
    108112        });
     
    109113        selectionTableModel = new SelectionTableModel(layer);
    110114        selectionTable = new SelectionTable(selectionTableModel, memberTableModel);
    111115        editor = GenericRelationEditorTest.newRelationEditor(orig, layer);
    112         tfRole = new AutoCompletingTextField();
     116        tfRole = new AutoCompTextField<>();
    113117        tagModel = new TagEditorModel();
    114         memberTable = new MemberTable(layer, editor.getRelation(), memberTableModel);
     118        memberTable = new MemberTable(layer, new AutoCompComboBox<String>(), memberTableModel);
    115119    }
     120
     121    @Override
     122    public void autoCompBefore(AutoCompEvent e) {
     123    }
     124
     125    @Override
     126    public void autoCompPerformed(AutoCompEvent e) {
     127    }
    116128}
  • test/unit/org/openstreetmap/josm/gui/preferences/map/TaggingPresetPreferenceTestIT.java

     
    1414import java.util.List;
    1515import java.util.Locale;
    1616import java.util.Set;
     17import java.util.concurrent.ExecutionException;
     18import java.util.concurrent.TimeoutException;
    1719
    1820import org.junit.jupiter.api.BeforeAll;
    1921import org.junit.jupiter.api.extension.RegisterExtension;
     
    2224import org.openstreetmap.josm.TestUtils;
    2325import org.openstreetmap.josm.data.preferences.sources.ExtendedSourceEntry;
    2426import org.openstreetmap.josm.gui.preferences.AbstractExtendedSourceEntryTestCase;
     27import org.openstreetmap.josm.gui.tagging.presets.Link;
    2528import 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;
     29import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetUtils;
     30import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
    2931import org.openstreetmap.josm.spi.preferences.Config;
    3032import org.openstreetmap.josm.testutils.JOSMTestRules;
    3133import org.openstreetmap.josm.tools.HttpClient;
     
    108110
    109111    private void testPresets(Set<String> messages, ExtendedSourceEntry source, List<String> ignoredErrors)
    110112            throws SAXException, IOException {
    111         Collection<TaggingPreset> presets = TaggingPresetReader.readAll(source.url, true);
     113        TaggingPresets.testInitialize(source.url);
     114        Collection<TaggingPreset> presets = TaggingPresets.getTaggingPresets();
    112115        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        }
    114121        // 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 -> {
    116123            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();
    118125                final int code = cr.getResponseCode();
    119126                if (HttpClient.isRedirect(code)) {
    120127                    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);
    122129                } 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);
    124131                }
    125132            } catch (IOException e) {
    126                 Logging.error(e);
     133                addOrIgnoreError(source, messages, e.getMessage(), ignoredErrors);
    127134            }
    128135        });
    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         }
    140136    }
    141137
    142138    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.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import static org.junit.jupiter.api.Assertions.assertEquals;
     5import static org.junit.jupiter.api.Assertions.assertFalse;
     6import static org.junit.jupiter.api.Assertions.assertTrue;
     7
     8import javax.swing.JPanel;
     9
     10import org.junit.jupiter.api.BeforeAll;
     11import org.junit.jupiter.api.Test;
     12import org.openstreetmap.josm.JOSMFixture;
     13
     14/**
     15 * Unit tests of {@link CheckGroup} class.
     16 */
     17class 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.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import static org.junit.jupiter.api.Assertions.assertEquals;
     5import static org.junit.jupiter.api.Assertions.assertTrue;
     6
     7import javax.swing.JPanel;
     8
     9import org.junit.jupiter.api.BeforeAll;
     10import org.junit.jupiter.api.Test;
     11import org.openstreetmap.josm.JOSMFixture;
     12
     13/**
     14 * Unit tests of {@link Check} class.
     15 */
     16class 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.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import static org.junit.jupiter.api.Assertions.assertEquals;
     5import static org.junit.jupiter.api.Assertions.assertTrue;
     6
     7import java.awt.Color;
     8
     9import javax.swing.JPanel;
     10
     11import org.junit.jupiter.api.BeforeAll;
     12import org.junit.jupiter.api.Test;
     13import org.openstreetmap.josm.JOSMFixture;
     14import org.openstreetmap.josm.data.osm.OsmUtils;
     15import org.openstreetmap.josm.tools.I18n;
     16
     17/**
     18 * Unit tests of {@link Combo} class.
     19 */
     20class 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.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import static org.junit.jupiter.api.Assertions.assertEquals;
     5import static org.junit.jupiter.api.Assertions.assertFalse;
     6import static org.junit.jupiter.api.Assertions.assertTrue;
     7
     8import javax.swing.JPanel;
     9
     10import org.junit.jupiter.api.BeforeAll;
     11import org.junit.jupiter.api.Test;
     12import org.openstreetmap.josm.JOSMFixture;
     13
     14/**
     15 * Unit tests of {@link ItemSeparator} class.
     16 */
     17class 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.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import static org.junit.jupiter.api.Assertions.assertEquals;
     5import static org.junit.jupiter.api.Assertions.assertFalse;
     6
     7import javax.swing.JPanel;
     8
     9import org.junit.jupiter.api.BeforeAll;
     10import org.junit.jupiter.api.Test;
     11import org.openstreetmap.josm.JOSMFixture;
     12
     13/**
     14 * Unit tests of {@link Key} class.
     15 */
     16class 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.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import static org.junit.jupiter.api.Assertions.assertEquals;
     5import static org.junit.jupiter.api.Assertions.assertTrue;
     6
     7import javax.swing.JPanel;
     8
     9import org.junit.jupiter.api.BeforeAll;
     10import org.junit.jupiter.api.Test;
     11import org.openstreetmap.josm.JOSMFixture;
     12
     13/**
     14 * Unit tests of {@link Label} class.
     15 */
     16class 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.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import static org.junit.jupiter.api.Assertions.assertEquals;
     5import static org.junit.jupiter.api.Assertions.assertFalse;
     6import static org.junit.jupiter.api.Assertions.assertTrue;
     7import static org.openstreetmap.josm.gui.tagging.presets.ItemFactory.build;
     8
     9import javax.swing.JPanel;
     10
     11import org.junit.jupiter.api.BeforeAll;
     12import org.junit.jupiter.api.Test;
     13import org.openstreetmap.josm.JOSMFixture;
     14import org.openstreetmap.josm.spi.preferences.Config;
     15
     16/**
     17 * Unit tests of {@link Link} class.
     18 */
     19class 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.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import static org.junit.jupiter.api.Assertions.assertEquals;
     5
     6import javax.swing.JPanel;
     7
     8import org.junit.jupiter.api.BeforeAll;
     9import org.junit.jupiter.api.Test;
     10import org.openstreetmap.josm.JOSMFixture;
     11
     12/**
     13 * Unit tests of {@link MultiSelect} class.
     14 */
     15class 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.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import static org.junit.jupiter.api.Assertions.assertEquals;
     5import static org.junit.jupiter.api.Assertions.assertFalse;
     6import static org.junit.jupiter.api.Assertions.assertTrue;
     7
     8import javax.swing.JPanel;
     9
     10import org.junit.jupiter.api.BeforeAll;
     11import org.junit.jupiter.api.Test;
     12import org.openstreetmap.josm.JOSMFixture;
     13
     14/**
     15 * Unit tests of {@link Optional} class.
     16 */
     17class 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

     
    55import static org.junit.jupiter.api.Assertions.assertTrue;
    66
    77import java.io.IOException;
    8 import java.util.Collection;
    98import java.util.Collections;
    109import java.util.EnumSet;
    1110import java.util.List;
     
    2019import org.openstreetmap.josm.data.osm.Way;
    2120import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetSelector.PresetClassification;
    2221import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetSelector.PresetClassifications;
     22import org.openstreetmap.josm.tools.Logging;
    2323import org.xml.sax.SAXException;
    2424
    2525/**
     
    3737    @BeforeAll
    3838    public static void setUp() throws IOException, SAXException {
    3939        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());
    4242    }
    4343
    4444    private List<PresetClassification> getMatchingPresets(String searchText, OsmPrimitive w) {
     
    4747    }
    4848
    4949    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());
    5151    }
    5252
    5353    /**
     
    6060        w.addNode(n1);
    6161        w.addNode(new Node());
    6262        w.addNode(new Node());
     63        Logging.info(getMatchingPresetNames("building", w).toString());
    6364        assertFalse(getMatchingPresetNames("building", w).contains("Building"), "unclosed way should not match building preset");
    6465        w.addNode(n1);
     66        Logging.info(getMatchingPresetNames("building", w).toString());
    6567        assertTrue(getMatchingPresetNames("building", w).contains("Building"), "closed way should match building preset");
    6668    }
    6769
  • test/unit/org/openstreetmap/josm/gui/tagging/presets/PresetLinkTest.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import static org.junit.jupiter.api.Assertions.assertEquals;
     5import static org.junit.jupiter.api.Assertions.assertFalse;
     6import static org.junit.jupiter.api.Assertions.assertTrue;
     7
     8import javax.swing.JPanel;
     9
     10import org.junit.jupiter.api.extension.RegisterExtension;
     11import org.junit.jupiter.api.Test;
     12import org.openstreetmap.josm.testutils.JOSMTestRules;
     13
     14import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
     15
     16/**
     17 * Unit tests of {@link PresetLink} class.
     18 */
     19class 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.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
     5import static org.junit.jupiter.api.Assertions.assertTrue;
     6
     7import javax.swing.JPanel;
     8
     9import org.junit.jupiter.api.BeforeAll;
     10import org.junit.jupiter.api.Test;
     11import org.openstreetmap.josm.JOSMFixture;
     12
     13/**
     14 * Unit tests of {@link PresetListEntry} class.
     15 */
     16class 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.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import static org.junit.jupiter.api.Assertions.assertEquals;
     5import static org.junit.jupiter.api.Assertions.assertFalse;
     6import static org.junit.jupiter.api.Assertions.assertTrue;
     7
     8import javax.swing.JPanel;
     9
     10import org.junit.jupiter.api.BeforeAll;
     11import org.junit.jupiter.api.Test;
     12import org.openstreetmap.josm.JOSMFixture;
     13
     14/**
     15 * Unit tests of {@link Roles} class.
     16 */
     17class 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.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import static org.junit.jupiter.api.Assertions.assertEquals;
     5import static org.junit.jupiter.api.Assertions.assertFalse;
     6import static org.junit.jupiter.api.Assertions.assertTrue;
     7
     8import javax.swing.JPanel;
     9
     10import org.junit.jupiter.api.BeforeAll;
     11import org.junit.jupiter.api.Test;
     12import org.openstreetmap.josm.JOSMFixture;
     13
     14/**
     15 * Unit tests of {@link Space} class.
     16 */
     17class 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.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
     5import org.junit.jupiter.api.Test;
     6import org.junit.jupiter.api.extension.RegisterExtension;
     7import org.openstreetmap.josm.data.osm.OsmUtils;
     8import org.openstreetmap.josm.testutils.JOSMTestRules;
     9import org.openstreetmap.josm.tools.template_engine.TemplateEntry;
     10import org.openstreetmap.josm.tools.template_engine.TemplateParser;
     11
     12import static org.junit.jupiter.api.Assertions.assertEquals;
     13
     14/**
     15 * Unit tests of {@link TaggingPresetInstance}
     16 */
     17class 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 rule
    28      */
    29     @RegisterExtension
    30     @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
    31     public JOSMTestRules test = new JOSMTestRules();
    32 
    33     /**
    34      * Tests {@link TemplateEntry} evaluation
    35      */
    36     @Test
    37     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

     
    44import static org.CustomMatchers.hasSize;
    55import static org.hamcrest.MatcherAssert.assertThat;
    66import static org.junit.jupiter.api.Assertions.assertEquals;
    7 import static org.junit.jupiter.api.Assertions.assertTrue;
    87import static org.junit.jupiter.api.Assertions.fail;
    98
    109import java.io.IOException;
     
    1615import org.junit.jupiter.api.Test;
    1716import org.junit.jupiter.api.extension.RegisterExtension;
    1817import org.openstreetmap.josm.TestUtils;
    19 import org.openstreetmap.josm.gui.tagging.presets.items.Check;
    20 import org.openstreetmap.josm.gui.tagging.presets.items.Key;
    2118import org.openstreetmap.josm.testutils.JOSMTestRules;
    2219import org.xml.sax.SAXException;
    2320
     
    2724 * Unit tests of {@link TaggingPresetReader} class.
    2825 */
    2926class TaggingPresetReaderTest {
    30 
    3127    /**
    32      * Setup rule
     28     * Setup test.
    3329     */
    3430    @RegisterExtension
    3531    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
    36     public JOSMTestRules test = new JOSMTestRules();
     32    public JOSMTestRules rule = new JOSMTestRules();
    3733
    3834    /**
    3935     * #8954 - last checkbox in the preset is not added
     
    4339    @Test
    4440    void testTicket8954() throws SAXException, IOException {
    4541        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();
    4744        Assert.assertEquals("Number of preset items", 1, presets.size());
    4845        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);
    5148        Assert.assertTrue("Entry is not checkbox", item instanceof Check);
     49        TaggingPresets.cleanUp();
    5250    }
    5351
    5452    /**
     
    5856     */
    5957    @Test
    6058    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();
    6261        assertThat(presets, hasSize(1));
    6362        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());
    6665        assertEquals("[A1, A2, A3, B1, B2, B3, C1, C2, C3]", keys.toString());
     66        TaggingPresets.cleanUp();
    6767    }
    6868
    6969    /**
     
    7474    @Test
    7575    void testExternalEntityResolving() throws IOException {
    7676        try {
    77             TaggingPresetReader.readAll(TestUtils.getTestDataRoot() + "preset_external_entity.xml", true);
     77            TaggingPresetReader.read(TestUtils.getTestDataRoot() + "preset_external_entity.xml", true);
    7878            fail("Reading a file with external entities should throw an SAXParseException!");
    7979        } catch (SAXException e) {
    8080            String expected = "DOCTYPE is disallowed when the feature \"http://apache.org/xml/features/disallow-doctype-decl\" set to true.";
    8181            assertEquals(expected, e.getMessage());
    8282        }
     83        TaggingPresets.cleanUp();
    8384    }
    8485
    8586    /**
     
    9192    @Test
    9293    void testReadDefaulPresets() throws SAXException, IOException {
    9394        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();
    9597        Assert.assertTrue("Default presets are empty", presets.size() > 0);
     98        TaggingPresets.cleanUp();
    9699    }
    97100}
  • test/unit/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetSelectorTest.java

     
    2828     */
    2929    @Test
    3030    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
    3337        PresetClassification pc = new PresetClassification(preset);
    3438        assertEquals(0, pc.isMatchingName("foo"));
    3539        assertTrue(pc.isMatchingName("estação") > 0);
  • test/unit/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetTest.java

     
    33
    44import static org.junit.jupiter.api.Assertions.assertFalse;
    55import static org.junit.jupiter.api.Assertions.assertTrue;
     6import static org.openstreetmap.josm.gui.tagging.presets.ItemFactory.build;
    67
    7 import java.util.EnumSet;
    8 
    98import org.junit.jupiter.api.Test;
    109import org.junit.jupiter.api.extension.RegisterExtension;
    1110import org.openstreetmap.josm.data.osm.IPrimitive;
    1211import org.openstreetmap.josm.data.osm.OsmUtils;
    13 import org.openstreetmap.josm.data.osm.search.SearchCompiler;
    1412import org.openstreetmap.josm.data.osm.search.SearchParseError;
    15 import org.openstreetmap.josm.gui.tagging.presets.items.Key;
    1613import org.openstreetmap.josm.testutils.JOSMTestRules;
    1714
    1815import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
     
    3532     */
    3633    @Test
    3734    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");
    4336
     37        TaggingPreset preset = (TaggingPreset) build("item");
     38        preset.addItem(key);
    4439        assertFalse(preset.test(OsmUtils.createPrimitive("node foo=bar")));
    4540        assertTrue(preset.test(OsmUtils.createPrimitive("node railway=tram_stop")));
    4641
    47         preset.types = EnumSet.of(TaggingPresetType.NODE);
     42        preset = (TaggingPreset) build("item type=node");
     43        preset.addItem(key);
    4844        assertTrue(preset.test(OsmUtils.createPrimitive("node railway=tram_stop")));
    4945        assertFalse(preset.test(OsmUtils.createPrimitive("way railway=tram_stop")));
    5046
    51         preset.matchExpression = SearchCompiler.compile("-public_transport");
     47        preset = (TaggingPreset) build("item type=node match_expression=-public_transport");
     48        preset.addItem(key);
    5249        assertTrue(preset.test(OsmUtils.createPrimitive("node railway=tram_stop")));
    5350        assertFalse(preset.test(OsmUtils.createPrimitive("node railway=tram_stop public_transport=stop_position")));
    5451    }
  • test/unit/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetValidationTest.java

     
    44import static org.junit.jupiter.api.Assertions.assertEquals;
    55import static org.junit.jupiter.api.Assertions.assertTrue;
    66
    7 import java.util.Arrays;
     7import java.util.Collection;
     8import java.util.Collections;
    89import java.util.Locale;
    910
    1011import javax.swing.JLabel;
     
    1213import org.junit.jupiter.api.BeforeEach;
    1314import org.junit.jupiter.api.Test;
    1415import org.junit.jupiter.api.extension.RegisterExtension;
    15 import org.openstreetmap.josm.data.osm.DataSet;
    1616import org.openstreetmap.josm.data.osm.OsmPrimitive;
    1717import org.openstreetmap.josm.data.osm.OsmUtils;
    18 import org.openstreetmap.josm.data.osm.Tag;
    1918import org.openstreetmap.josm.data.validation.OsmValidator;
    2019import org.openstreetmap.josm.testutils.JOSMTestRules;
    2120
     
    4645    void testValidate() {
    4746        JLabel label = new JLabel();
    4847        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);
    5049
    51         TaggingPresetValidation.validate(primitive, label);
     50        TaggingPresetValidation.validate(new ReadOnlyTaggingPresetHandler(selection), label);
    5251
    5352        // CHECKSTYLE.OFF: LineLength
    5453        assertTrue(label.isVisible());
     
    6059            "<li>suspicious tag combination (incline on suspicious object)</li></ul>", label.getToolTipText());
    6160        // CHECKSTYLE.ON: LineLength
    6261    }
    63 
    64     /**
    65      * Tests {@link TaggingPresetValidation#applyChangedTags}
    66      */
    67     @Test
    68     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     }
    7562}
  • test/unit/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetsTest.java

     
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.gui.tagging.presets;
    33
    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 
    104import org.junit.jupiter.api.Test;
    115import org.junit.jupiter.api.extension.RegisterExtension;
    126import org.openstreetmap.josm.testutils.JOSMTestRules;
    13 import org.openstreetmap.josm.tools.Logging;
    147
    158import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
    169import net.trajano.commons.testing.UtilityClassTestUtil;
     
    3528    void testUtilityClass() throws ReflectiveOperationException {
    3629        UtilityClassTestUtil.assertUtilityClassWellDefined(TaggingPresets.class);
    3730    }
    38 
    39     /**
    40      * Wait for asynchronous icon loading
    41      * @param presets presets collection
    42      */
    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     }
    5231}
  • test/unit/org/openstreetmap/josm/gui/tagging/presets/TextTest.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.tagging.presets;
     3
     4import static org.junit.jupiter.api.Assertions.assertEquals;
     5import static org.junit.jupiter.api.Assertions.assertTrue;
     6
     7import javax.swing.JPanel;
     8
     9import org.junit.jupiter.api.BeforeAll;
     10import org.junit.jupiter.api.Test;
     11import org.openstreetmap.josm.JOSMFixture;
     12
     13/**
     14 * Unit tests of {@link Text} class.
     15 */
     16class 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     @BeforeAll
    24     public static void setUp() {
    25         JOSMFixture.createUnitTestFixture().init();
    26     }
    27 
    28     /**
    29      * Unit test for {@link CheckGroup#addToPanel}.
    30      */
    31     @Test
    32     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     @RegisterExtension
    25     @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     @Test
    32     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     @RegisterExtension
    29     @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     @Test
    36     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     @Test
    47     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 only
    98         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 too
    110         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     @Test
    126     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     @BeforeAll
    24     public static void setUp() {
    25         JOSMFixture.createUnitTestFixture().init();
    26     }
    27 
    28     /**
    29      * Unit test for {@link ItemSeparator#addToPanel}.
    30      */
    31     @Test
    32     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     @BeforeAll
    23     public static void setUp() {
    24         JOSMFixture.createUnitTestFixture().init();
    25     }
    26 
    27     /**
    28      * Unit test for {@link Key#addToPanel}.
    29      */
    30     @Test
    31     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     @BeforeAll
    23     public static void setUp() {
    24         JOSMFixture.createUnitTestFixture().init();
    25     }
    26 
    27     /**
    28      * Unit test for {@link Label#addToPanel}.
    29      */
    30     @Test
    31     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     @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         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     @BeforeAll
    23     public static void setUp() {
    24         JOSMFixture.createUnitTestFixture().init();
    25     }
    26 
    27     /**
    28      * Unit test for {@link MultiSelect#addToPanel}.
    29      */
    30     @Test
    31     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     @BeforeAll
    24     public static void setUp() {
    25         JOSMFixture.createUnitTestFixture().init();
    26     }
    27 
    28     /**
    29      * Unit test for {@link Optional#addToPanel}.
    30      */
    31     @Test
    32     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     @RegisterExtension
    26     @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     @Test
    33     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     @BeforeAll
    20     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     @Test
    28     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     @Test
    36     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     @BeforeAll
    24     public static void setUp() {
    25         JOSMFixture.createUnitTestFixture().init();
    26     }
    27 
    28     /**
    29      * Unit test for {@link Roles#addToPanel}.
    30      */
    31     @Test
    32     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     @BeforeAll
    24     public static void setUp() {
    25         JOSMFixture.createUnitTestFixture().init();
    26     }
    27 
    28     /**
    29      * Unit test for {@link Space#addToPanel}.
    30      */
    31     @Test
    32     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     @RegisterExtension
    25     @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     @Test
    32     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
     
    77
    88import java.awt.Dimension;
    99import java.util.EnumSet;
     10import java.util.concurrent.ExecutionException;
     11import java.util.concurrent.TimeoutException;
    1012
    1113import javax.swing.ImageIcon;
    1214
     
    1618import org.openstreetmap.josm.JOSMFixture;
    1719import org.openstreetmap.josm.data.osm.Node;
    1820import org.openstreetmap.josm.data.osm.OsmUtils;
     21import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetUtils;
    1922import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
    20 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetsTest;
    2123import org.openstreetmap.josm.testutils.JOSMTestRules;
    2224import org.openstreetmap.josm.tools.OsmPrimitiveImageProvider.Options;
    2325
     
    4547
    4648    /**
    4749     * 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
    4853     */
    4954    @Test
    50     void testGetResource() {
    51         TaggingPresetsTest.waitForIconLoading(TaggingPresets.getTaggingPresets());
     55    void testGetResource() throws InterruptedException, ExecutionException, TimeoutException {
     56        TaggingPresetUtils.waitForIconsLoaded(TaggingPresets.getTaggingPresets(), 30);
    5257
    5358        final EnumSet<Options> noDefault = EnumSet.of(Options.NO_DEFAULT);
    5459        final Dimension iconSize = new Dimension(16, 16);