Ticket #21851: 21851.patch

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