Ticket #22664: flexibilize-presets_v2.patch

File flexibilize-presets_v2.patch, 39.7 KB (added by anonymous, 15 months ago)
  • resources/data/tagging-preset.xsd

     
    100100    <complexType name="group">
    101101        <annotation>
    102102            <documentation>
    103                 Used to group items in sub menus. name is required, icon is optional attribute.
     103                Used to group items in sub menus. name is required, icon and item_sort are optional attributes.
    104104            </documentation>
    105105        </annotation>
    106106        <complexContent>
    107107            <extension base="tns:group-parent">
    108108                <attributeGroup ref="tns:attributes.name" />
     109                <attribute name="items_sort" type="tns:items_sort" />
    109110            </extension>
    110111        </complexContent>
    111112    </complexType>
     
    267268                ]]></documentation>
    268269            </annotation>
    269270        </attribute>
     271        <attribute name="prefix" type="string">
     272                <annotation>
     273                    <documentation>
     274                        Text that will be put at the start of the resulting value if key is empty, it's not used for appending of existing values.
     275                    </documentation>
     276                </annotation>
     277        </attribute>
     278        <attributeGroup ref="tns:attributes.append" />
    270279        <attribute name="match" type="tns:match" />
    271280    </complexType>
    272281
     
    378387            </annotation>
    379388        </attribute>
    380389        <attributeGroup ref="tns:attributes.icon" />
     390        <attribute name="delimiter" type="tns:values.delimiter" />
    381391        <anyAttribute processContents="skip" />
    382392    </complexType>
    383393
     
    404414        <attributeGroup ref="tns:attributes.icon" />
    405415        <attributeGroup ref="tns:attributes.values" />
    406416        <attribute name="use_last_as_default" type="tns:last_default" />
     417        <attribute name="if_needed_only" type="tns:if_needed_only" />
    407418        <attribute name="editable" type="boolean">
    408419            <annotation>
    409420                <documentation>
     
    425436    <complexType name="multiselect">
    426437        <!-- use either list_entry's or a combination of values/display_values -->
    427438        <sequence>
     439            <element name="selectgroup" type="tns:selectgroup" minOccurs="0" maxOccurs="unbounded" />
    428440            <group ref="tns:list_elements" minOccurs="0" maxOccurs="unbounded" />
    429441        </sequence>
    430442        <attributeGroup ref="tns:attributes.key" />
     
    433445        <attributeGroup ref="tns:attributes.values" />
    434446        <attribute name="use_last_as_default" type="tns:last_default" />
    435447        <attribute name="match" type="tns:match" />
    436 
     448                <attribute name="quick_select" type="boolean">
     449                        <annotation>
     450                    <documentation>
     451                        Allows to enable quick selection of list entries with just clicking on it, without the need to use keyboard. Default is false.
     452                    </documentation>
     453                </annotation>
     454                </attribute>       
     455        <attribute name="prefix" type="string">
     456                <annotation>
     457                    <documentation>
     458                        Text that will be put at the start of the resulting value. If values start with prefix, it will be remove from the value first.
     459                    </documentation>
     460                </annotation>
     461        </attribute>
     462               
    437463        <attribute name="type" use="prohibited" />
    438464        <attribute name="value" use="prohibited" />
    439465        <attribute name="name" use="prohibited" />
     
    441467        <attribute name="display-values" use="prohibited" />
    442468        <anyAttribute processContents="skip" />
    443469    </complexType>
    444 
     470   
    445471    <complexType name="checkgroup">
    446472        <annotation>
    447473            <documentation>
     
    462488        </attribute>
    463489        <anyAttribute processContents="skip" />
    464490    </complexType>
     491   
     492    <complexType name="selectgroup">
     493        <annotation>
     494            <documentation>
     495                To group list_entries for multiselect. From all list_entries in a group only one can be selected at a time.
     496            </documentation>
     497        </annotation>
     498        <sequence>
     499            <choice minOccurs="1" maxOccurs="unbounded">
     500                <element name="list_entry" type="tns:list_entry" />
     501            </choice>
     502        </sequence>
     503        <anyAttribute processContents="skip" />
     504    </complexType>
    465505
    466506    <complexType name="check">
    467507        <attributeGroup ref="tns:attributes.key" />
     
    502542            <enumeration value="off" />
    503543        </restriction>
    504544    </simpleType>
     545   
     546    <simpleType name="append">
     547        <annotation>
     548            <documentation>
     549                Specify if value should be appended to existing value. This defaults implicitly to default if append_value is provided.
     550            </documentation>
     551        </annotation>
     552        <restriction base="string">
     553            <enumeration value="default" />
     554            <enumeration value="always" />
     555        </restriction>
     556    </simpleType>
     557   
     558    <simpleType name="items_sort">
     559        <annotation>
     560            <documentation>
     561                If the items in the menu should be sorted. Defaults to "yes", when "always" is set the menu items will be sorted regardless how the sorting is configured in JOSM.
     562            </documentation>
     563        </annotation>
     564        <restriction base="string">
     565            <enumeration value="yes" />
     566            <enumeration value="no" />
     567            <enumeration value="always" />
     568        </restriction>
     569    </simpleType>
     570       
     571    <simpleType name="values.delimiter">
     572                 <annotation>
     573                <documentation><![CDATA[
     574                    The character that separates values. In case of <combo /> the default is comma. In case of <multiselect /> the default is semicolon and this will also be used to separate selected values in the tag.
     575                ]]></documentation>
     576          </annotation>
     577          <restriction base="string">
     578                <minLength value="1"/>
     579                <maxLength value="1"/>
     580          </restriction>
     581    </simpleType>
     582   
     583    <simpleType name="append.delimiter">
     584                 <annotation>
     585                <documentation><![CDATA[
     586                    The character that separates values for <key />. The default is semicolon and it's possible to provide an empty delimiter.
     587                ]]></documentation>
     588          </annotation>
     589          <restriction base="string">
     590                <minLength value="0"/>
     591                <maxLength value="1"/>
     592          </restriction>
     593    </simpleType>
     594   
     595    <simpleType name="if_needed_only">
     596        <annotation>
     597            <documentation>
     598                Show combo selection only if key is missing otherwise don't show it and retain current value.
     599            </documentation>
     600        </annotation>
     601        <restriction base="string">
     602            <enumeration value="true" />
     603            <enumeration value="false" />
     604        </restriction>
     605    </simpleType>
    505606
    506607    <simpleType name="last_default">
    507608        <annotation>
     
    588689            <enumeration value="optional" />
    589690        </restriction>
    590691    </simpleType>
     692   
     693    <simpleType name="sort_options">
     694        <annotation>
     695            <documentation>
     696                Possible options for values_sort. Default is "true";
     697            </documentation>
     698        </annotation>
     699        <restriction base="string">
     700            <enumeration value="true" />
     701            <enumeration value="false" />
     702            <enumeration value="user" />
     703        </restriction>
     704    </simpleType>
    591705
    592706    <!-- Types and documentation for attributes -->
    593707
     708    <attributeGroup name="attributes.append">
     709        <attribute name="append_value" type="string">
     710            <annotation>
     711                <documentation><![CDATA[
     712                    Specify a value to be appended to the <key> tag. Optional if this value should be used instead of value. The value is only appened if it doesn't already is set for <key> tag or if append=always was specified.
     713                ]]></documentation>
     714            </annotation>
     715        </attribute>
     716        <attribute name="append" type="tns:append" />
     717        <attribute name="delimiter" type="tns:append.delimiter" />
     718    </attributeGroup>
     719
    594720    <attributeGroup name="attributes.name">
    595721        <attribute name="name" type="string" use="required">
    596722            <annotation>
     
    671797                </documentation>
    672798            </annotation>
    673799        </attribute>
    674         <attribute name="values_sort" type="boolean">
     800        <attribute name="values_sort" type="tns:sort_options">
    675801            <annotation>
    676802                <documentation>
    677803                    Values of are sorted alphabetic in every language. With this attribute you can disable the alphabetic sorting if the values should keep the given order, see #5509 and #11926. (In the JOSM internal preset this is used e.g. for the keys tracktype, direction, network, smoothness, visibility or trail_visibility.) Default is "true".<br />
     
    693819                </documentation>
    694820            </annotation>
    695821        </attribute>
    696         <attribute name="delimiter" type="string">
    697             <annotation>
    698                 <documentation><![CDATA[
    699                     The character that separates values. In case of <combo /> the default is comma. In case of <multiselect /> the default is semicolon and this will also be used to separate selected values in the tag.
    700                 ]]></documentation>
    701             </annotation>
    702         </attribute>
     822        <attribute name="delimiter" type="tns:values.delimiter" />
    703823    </attributeGroup>
    704824
    705825    <attributeGroup name="attributes.icon">
  • src/org/openstreetmap/josm/data/osm/Way.java

     
    150150
    151151    /**
    152152     * Replies the ordered {@link List} of chunks of this way. Each chunk is replied as a {@link Pair} of {@link Node nodes}.
    153      * @param sort If true, the nodes of each pair are sorted as defined by {@link Pair#sort}.
     153     * @param sort If true, the nodes of each pair are sorted as defined by {@link Pair#items_sort}.
    154154     *             If false, Pair.a and Pair.b are in the way order
    155155     *             (i.e for a given Pair(n), Pair(n-1).b == Pair(n).a, Pair(n).b == Pair(n+1).a, etc.)
    156156     * @return The ordered list of chunks of this way.
  • src/org/openstreetmap/josm/gui/tagging/presets/TaggingPreset.java

     
    138138    public boolean preset_name_label;
    139139
    140140    /**
     141     * If menu items should be sorted
     142     */
     143    public String items_sort = "yes";
     144
     145    /**
    141146     * The types as preparsed collection.
    142147     */
    143148    public transient Set<TaggingPresetType> types;
     
    255260    }
    256261
    257262    /**
     263     * Called from the XML parser to set the sort option.
     264     * @param sort The sort value for the menu items.
     265     * @since 18619
     266     */
     267    public void setItems_sort(final String sort) {
     268        this.items_sort = sort;
     269    }
     270
     271    /**
    258272     * Called from the XML parser to set the icon.
    259273     * The loading task is performed in the background in order to speedup startup.
    260274     * @param iconName icon name
  • src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetMenu.java

     
    120120     * Sorts the menu items using the translated item text
    121121     */
    122122    public void sortMenu() {
    123         TaggingPresetMenu.sortMenu(this.menu);
     123        TaggingPresetMenu.sortMenu(this.menu, TaggingPresets.SORT_MENU.get());
    124124    }
    125125
    126126    /**
    127127     * Sorts the menu items using the translated item text
    128128     * @param menu menu to sort
     129     * @param itemssort if menu should be sorted.
    129130     */
    130     public static void sortMenu(JMenu menu) {
     131    public static void sortMenu(JMenu menu, boolean itemssort) {
     132        Action a = menu.getAction();
     133
     134        boolean sort = itemssort;
     135
     136        if (a instanceof TaggingPreset) {
     137            sort = (sort && ((TaggingPreset) a).items_sort.equals("yes")) || ((TaggingPreset) a).items_sort.equals("always");
     138        }
     139
    131140        Component[] items = menu.getMenuComponents();
    132141        PresetTextComparator comp = new PresetTextComparator();
    133142        List<JMenuItem> sortarray = new ArrayList<>();
     
    135144        for (int i = 0; i < items.length; i++) {
    136145            Object item = items[i];
    137146            if (item instanceof JMenu) {
    138                 sortMenu((JMenu) item);
     147                sortMenu((JMenu) item, itemssort);
    139148            }
    140             if (item instanceof JMenuItem) {
    141                 sortarray.add((JMenuItem) item);
    142                 if (i == items.length-1) {
     149            if(sort) {
     150                if (item instanceof JMenuItem) {
     151                    sortarray.add((JMenuItem) item);
     152                    if (i == items.length-1) {
     153                        handleMenuItem(menu, comp, sortarray, lastSeparator);
     154                        sortarray = new ArrayList<>();
     155                        lastSeparator = 0;
     156                    }
     157                } else if (item instanceof JSeparator) {
    143158                    handleMenuItem(menu, comp, sortarray, lastSeparator);
    144159                    sortarray = new ArrayList<>();
    145                     lastSeparator = 0;
     160                    lastSeparator = i;
    146161                }
    147             } else if (item instanceof JSeparator) {
    148                 handleMenuItem(menu, comp, sortarray, lastSeparator);
    149                 sortarray = new ArrayList<>();
    150                 lastSeparator = i;
    151162            }
    152163        }
    153164    }
  • src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetReader.java

     
    3939import org.openstreetmap.josm.gui.tagging.presets.items.PresetListEntry;
    4040import org.openstreetmap.josm.gui.tagging.presets.items.Roles;
    4141import org.openstreetmap.josm.gui.tagging.presets.items.Roles.Role;
     42import org.openstreetmap.josm.gui.tagging.presets.items.SelectGroup;
    4243import org.openstreetmap.josm.gui.tagging.presets.items.Space;
    4344import org.openstreetmap.josm.gui.tagging.presets.items.Text;
    4445import org.openstreetmap.josm.io.CachedFile;
     
    145146        parser.mapOnStart("roles", Roles.class);
    146147        parser.map("role", Role.class);
    147148        parser.mapBoth("checkgroup", CheckGroup.class);
     149        parser.mapBoth("selectgroup", SelectGroup.class);
    148150        parser.map("check", Check.class);
    149151        parser.map("combo", Combo.class);
    150152        parser.map("multiselect", MultiSelect.class);
     
    182184
    183185        /** to detect end of {@code <checkgroup>} */
    184186        CheckGroup lastcheckgroup = null;
     187        /** to detect end of {@code <selectgroup>} */
     188        SelectGroup lastselectgroup = null;
     189        int selectgroupcount = PresetListEntry.SELECT_GROUP_NONE;
    185190        /** to detect end of {@code <group>} */
    186191        TaggingPresetMenu lastmenu = null;
    187192        /** to detect end of reused {@code <group>} */
     
    302307                            all.getLast().data.add((TaggingPresetItem) o);
    303308                        }
    304309                    } else if (o instanceof PresetListEntry) {
    305                         listEntries.add((PresetListEntry) o);
     310                        PresetListEntry entry = (PresetListEntry) o;
     311                        entry.select_group = selectgroupcount;
     312                        listEntries.add(entry);
    306313                    } else if (o instanceof CheckGroup) {
    307314                        CheckGroup cg = (CheckGroup) o;
    308315                        if (cg == lastcheckgroup) {
     
    316323                        } else {
    317324                            lastcheckgroup = cg;
    318325                        }
     326                    } else if (o instanceof SelectGroup) {
     327                        SelectGroup sg = (SelectGroup) o;
     328                        if (sg == lastselectgroup) {
     329                            lastselectgroup = null;
     330                        } else {
     331                            lastselectgroup = sg;
     332                            selectgroupcount++;
     333                        }
    319334                    } else {
    320335                        if (!checks.isEmpty()) {
    321336                            all.getLast().data.addAll(checks);
     
    329344                        }
    330345                        listEntries.clear();
    331346                        lastrole = null;
     347                        selectgroupcount = PresetListEntry.SELECT_GROUP_NONE;
    332348                    }
    333349                } else
    334350                    throw new SAXException(tr("Preset sub element without parent"));
  • src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresets.java

     
    123123                }
    124124            }
    125125        }
    126         if (SORT_MENU.get()) {
    127             TaggingPresetMenu.sortMenu(presetsMenu);
    128         }
     126
     127        TaggingPresetMenu.sortMenu(presetsMenu, SORT_MENU.get());
     128
    129129        listeners.forEach(TaggingPresetListener::taggingPresetsModified);
    130130    }
    131131
  • src/org/openstreetmap/josm/gui/tagging/presets/items/Combo.java

     
    168168        combobox.setToolTipText(getKeyTooltipText());
    169169        combobox.applyComponentOrientation(OrientationAction.getValueOrientation(key));
    170170
    171         return true;
     171
     172        return isNeeded(usage);
    172173    }
    173174
     175
     176
    174177    /**
    175178     * Finds the PresetListEntry that matches value.
    176179     * <p>
  • src/org/openstreetmap/josm/gui/tagging/presets/items/ComboMultiSelect.java

     
    3535 * Abstract superclass for combo box and multi-select list types.
    3636 */
    3737public abstract class ComboMultiSelect extends KeyedItem {
    38 
     38    protected static final String VALUE_SORT_USER = "user";
    3939    /**
    4040     * A list of entries.
    4141     * The list has to be separated by commas (for the {@link Combo} box) or by the specified delimiter (for the {@link MultiSelect}).
     
    5353    /** Disabled internationalisation for value to avoid mistakes, see #11696 */
    5454    public boolean values_no_i18n; // NOSONAR
    5555    /** Whether to sort the values, defaults to true. */
    56     public boolean values_sort = true; // NOSONAR
     56    public String values_sort = String.valueOf(true); // NOSONAR
     57    /** Whether to show this combo in dialog only when it's needed.
     58     * It's needed when key doesn't exist or if current value is not in values */
     59    public boolean if_needed_only = false; // NOSONAR
    5760    /**
    5861     * A list of entries that is displayed to the user.
    5962     * Must be the same number and order of entries as {@link #values} and editable must be false or not specified.
     
    138141                // editor-ersatz of a readonly combobox. fixes #6157
    139142                l.setText(value.getListDisplay(width));
    140143            }
     144
    141145            if (value.getCount() > 0) {
    142146                l.setFont(l.getFont().deriveFont(Font.ITALIC + Font.BOLD));
    143147            }
     
    310314            addListEntry(e);
    311315        }
    312316
    313         if (values_sort && TaggingPresets.SORT_MENU.get()) {
     317        if (values_sort.equals(String.valueOf(true)) && TaggingPresets.SORT_MENU.get()) {
    314318            Collections.sort(presetListEntries, (a, b) -> AlphanumComparator.getInstance().compare(a.getDisplayValue(), b.getDisplayValue()));
    315319        }
    316320    }
     
    430434    public MatchType getDefaultMatch() {
    431435        return MatchType.NONE;
    432436    }
     437
     438    protected boolean isNeeded(Usage usage) {
     439        boolean result = !if_needed_only || usage.unused();
     440
     441        if (!result) {
     442            for (String v : usage.map.keySet()) {
     443                for (PresetListEntry p : presetListEntries) {
     444                    if (p.value.equals(v)) {
     445                        return false;
     446                    }
     447                }
     448            }
     449        }
     450
     451        return result;
     452    }
    433453}
  • src/org/openstreetmap/josm/gui/tagging/presets/items/Key.java

     
    77
    88import javax.swing.JPanel;
    99
     10import org.openstreetmap.josm.data.osm.OsmPrimitive;
    1011import org.openstreetmap.josm.data.osm.Tag;
    1112import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;
    1213
     
    1415 * Invisible type allowing to hardcode an OSM key/value from the preset definition.
    1516 */
    1617public class Key extends KeyedItem {
     18    private static final String VALUE_APPEND_ALWAYS = "always";
    1719
    1820    /** The hardcoded value for key */
    1921    public String value; // NOSONAR
    2022
     23    /** The hardcoded value for key appending existing value **/
     24    public String append_value;  // NOSONAR
     25
     26    /** The hardcoded delimiter to use for appending values **/
     27    public String delimiter = ";"; // NOSONAR
     28
     29    /** The hardcoded type of appending */
     30    public String append = null; // NOSONAR
     31
     32    /** The hardcoded prefix to use for setting value to empty key */
     33    public String prefix = null; // NOSONAR
     34
     35    private String useValue;
     36
    2137    @Override
    2238    public boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support) {
     39        if (prefix != null) {
     40            useValue = prefix + value;
     41        }
     42        else {
     43            useValue = value;
     44        }
     45
     46        // If append_value is present append also if append was not set
     47        if (append_value != null || append != null) {
     48            String toUse = append_value != null ? append_value : value;
     49
     50            Collection<OsmPrimitive> col = support.getSelected();
     51
     52            for (OsmPrimitive pr : col) {
     53                String currentValue = pr.get(key);
     54
     55                if (currentValue != null) {
     56                    useValue = currentValue;
     57
     58                    if ((append != null && append.equals(VALUE_APPEND_ALWAYS)) || !useValue.contains(toUse)) {
     59                        useValue += delimiter + toUse;
     60                    }
     61
     62                    break;
     63                }
     64            }
     65        }
     66
    2367        return false;
    2468    }
    2569
     
    3377     * @return the tag
    3478     */
    3579    public Tag asTag() {
    36         return new Tag(key, value);
     80        return new Tag(key, useValue != null ? useValue : value);
    3781    }
    3882
    3983    @Override
     
    4387
    4488    @Override
    4589    public Collection<String> getValues() {
    46         return Collections.singleton(value);
     90        return Collections.singleton(useValue != null ? useValue : value);
    4791    }
    4892
    4993    @Override
    5094    public String toString() {
    51         return "Key [key=" + key + ", value=" + value + ", text=" + text
    52                 + ", text_context=" + text_context + ", match=" + match
    53                 + ']';
     95        return "Key [key=" + key + ", value=" + value + ", value_append=" + append_value
     96                + ", text=" + text + ", text_context=" + text_context + ", match=" + match
     97                + ", delimiter=" + delimiter + ']';
    5498    }
    5599}
  • src/org/openstreetmap/josm/gui/tagging/presets/items/MultiSelect.java

     
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.gui.tagging.presets.items;
    33
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
    46import java.awt.Dimension;
     7import java.awt.GridBagLayout;
    58import java.awt.Insets;
    69import java.awt.Rectangle;
    7 import java.util.stream.Collectors;
     10import java.awt.event.MouseAdapter;
     11import java.awt.event.MouseEvent;
     12import java.util.Collections;
     13import java.util.List;
     14import java.util.Objects;
    815
    916import javax.swing.DefaultListModel;
     17import javax.swing.DefaultListSelectionModel;
     18import javax.swing.JButton;
    1019import javax.swing.JLabel;
    1120import javax.swing.JList;
    1221import javax.swing.JPanel;
    1322import javax.swing.JScrollPane;
     23import javax.swing.ListModel;
     24import javax.swing.ListSelectionModel;
     25import javax.swing.event.ListSelectionEvent;
     26import javax.swing.event.ListSelectionListener;
    1427
    1528import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;
     29import org.openstreetmap.josm.gui.util.ReorderableTableModel;
    1630import org.openstreetmap.josm.gui.widgets.OrientationAction;
    1731import org.openstreetmap.josm.tools.GBC;
     32import org.openstreetmap.josm.tools.ImageProvider;
     33import org.openstreetmap.josm.tools.ImageProvider.ImageSizes;
    1834
    1935/**
    2036 * Multi-select list type.
    2137 */
    2238public class MultiSelect extends ComboMultiSelect {
    23 
    2439    /**
    2540     * Number of rows to display (positive integer, optional).
    2641     */
    2742    public short rows; // NOSONAR
    2843
    2944    /** The model for the JList */
    30     protected final DefaultListModel<PresetListEntry> model = new DefaultListModel<>();
     45    protected final PresetListModel model = new PresetListModel();
    3146    /** The swing component */
    3247    protected final JList<PresetListEntry> list = new JList<>(model);
    3348
     49    /**
     50     * Text that will be put in front of the resulting value.
     51     */
     52    public String prefix; // NOSONAR
     53
     54    /**
     55     * If the entries in the list should be selected with clicking only.
     56     */
     57    public boolean quick_select = false;
     58
     59    /**
     60     * Use selection model that selects/deselects values with single click.
     61     * @since 18619
     62     */
     63    public MultiSelect() {
     64        list.setSelectionModel(new SingleClickSelectionModel(list));
     65    }
     66
    3467    private void addEntry(PresetListEntry entry) {
     68        addEntry(entry, -1);
     69    }
     70
     71    private void addEntry(PresetListEntry entry, int presetCount) {
     72        if (prefix != null && entry.value.startsWith(prefix)) {
     73            entry.value = entry.value.substring(prefix.length());
     74        }
     75
    3576        if (!seenValues.containsKey(entry.value)) {
    36             model.addElement(entry);
    37             seenValues.put(entry.value, entry);
     77            boolean add = true;
     78
     79            for (int i = 0; i < model.size(); i++) {
     80                PresetListEntry test = model.get(i);
     81
     82                if (test.delimiter != null && entry.value.matches(".*\\\\{0}" + test.delimiter+".*")) {
     83                    add = false;
     84
     85                    String[] parts = entry.value.split(test.delimiter,-1);
     86
     87                    for (int j = 0; j < parts.length; j++) {
     88                        if (prefix != null && parts[j].startsWith(prefix)) {
     89                            parts[j] = parts[j].substring(prefix.length());
     90                        }
     91                        addEntry(new PresetListEntry(parts[j], this, j == 0 ? String.valueOf(delimiter) : test.delimiter), presetCount);
     92                    }
     93
     94                    break;
     95                }
     96            }
     97
     98            if (add) {
     99                if (presetCount == -1) {
     100                    model.addElement(entry);
     101                }
     102                else {
     103                    model.insertElementAt(entry, model.getSize() - presetCount);
     104                }
     105                seenValues.put(entry.value, entry);
     106            }
     107        }
     108    }
     109
     110    private void select(PresetListEntry entry) {
     111        for (int i = 0; i < model.getSize(); i++) {
     112            PresetListEntry test = model.get(i);
     113
     114            if (Objects.equals(test.value, entry.value)) {
     115                int[] indices = model.getSelectedIndices();
     116
     117                if (indices != null && indices.length > 0 && indices[indices.length - 1] > i) {
     118                    PresetListEntry removed = model.remove(i);
     119                    model.add(indices[indices.length-1], removed);
     120                    list.addSelectionInterval(indices[indices.length-1], indices[indices.length-1]);
     121                }
     122                else {
     123                    list.addSelectionInterval(i, i);
     124                }
     125
     126                if(usage != null && usage.map != null && !usage.map.containsKey(test.value)) {
     127                    usage.map.put(test.value, 1);
     128                }
     129
     130                break;
     131            }
     132            else if (test.delimiter != null && entry.value.matches(".*\\\\{0}" + test.delimiter+".*")) {
     133                for (String part : entry.value.split(test.delimiter, -1)) {
     134                    select(new PresetListEntry(part, this, test.delimiter));
     135                }
     136            }
    38137        }
    39138    }
    40139
     
    53152        // Add values from the preset.
    54153        presetListEntries.forEach(this::addEntry);
    55154
     155        int count = model.getSize();
     156
    56157        // Add all values used in the selected primitives. This also adds custom values and makes
    57158        // sure we won't lose them.
    58159        usage = usage.splitValues(String.valueOf(delimiter));
    59160        for (String value: usage.map.keySet()) {
    60             addEntry(new PresetListEntry(value, this));
     161            addEntry(new PresetListEntry(value, this), count);
    61162        }
    62163
    63164        // Select the values in the initial value.
    64165        if (!initialValue.isEmpty() && !DIFFERENT.equals(initialValue)) {
     166            if(prefix != null && initialValue.startsWith(prefix)) {
     167                initialValue = initialValue.substring(prefix.length());
     168            }
     169
    65170            for (String value : initialValue.split(String.valueOf(delimiter), -1)) {
    66171                PresetListEntry e = new PresetListEntry(value, this);
    67172                addEntry(e);
    68                 int i = model.indexOf(e);
    69                 list.addSelectionInterval(i, i);
     173                select(e);
    70174            }
    71175        }
     176        else if (default_ != null) {
     177            select(new PresetListEntry(default_, this));
     178        }
    72179
    73180        ComboMultiSelectListCellRenderer renderer = new ComboMultiSelectListCellRenderer(list, list.getCellRenderer(), 200, key);
    74181        list.setCellRenderer(renderer);
     
    92199                sp.setPreferredSize(new Dimension(r.width, r.height));
    93200            }
    94201        }
    95         p.add(sp, GBC.eol().fill(GBC.HORIZONTAL)); // NOSONAR
     202
     203        if(values_sort.equals(VALUE_SORT_USER)) {
     204            final JButton up = new JButton(ImageProvider.get("dialogs", "up", ImageSizes.LARGEICON));
     205            up.setToolTipText(tr("Move the currently selected members up"));
     206            up.addActionListener(e -> {
     207                model.moveUp();
     208            });
     209            final JButton down = new JButton(ImageProvider.get("dialogs", "down", ImageSizes.LARGEICON));
     210            down.setToolTipText(tr("Move the currently selected members down"));
     211            down.addActionListener(e -> {
     212                model.moveDown();
     213            });
     214            up.setEnabled(model.canMoveUp());
     215            down.setEnabled(model.canMoveDown());
     216
     217            list.addListSelectionListener(new ListSelectionListener() {
     218                @Override
     219                public void valueChanged(ListSelectionEvent e) {
     220                    up.setEnabled(model.canMoveUp());
     221                    down.setEnabled(model.canMoveDown());
     222                }
     223            });
     224
     225            JPanel content = new JPanel(new GridBagLayout());
     226            content.add(sp, GBC.std(0,0).span(1, 4).fill());
     227            content.add(GBC.glue(0, 1), GBC.std(1, 0).fill(GBC.VERTICAL));
     228            content.add(up, GBC.std(1, 1));
     229            content.add(down, GBC.std(1, 2));
     230            content.add(GBC.glue(0, 1), GBC.std(1, 3).fill(GBC.VERTICAL));
     231
     232            p.add(content, GBC.eol().fill(GBC.HORIZONTAL)); // NOSONAR
     233        }
     234        else {
     235            p.add(sp, GBC.eol().fill(GBC.HORIZONTAL)); // NOSONAR
     236        }
    96237
    97238        list.addListSelectionListener(l -> support.fireItemValueModified(this, key, getSelectedItem().value));
    98239        list.setToolTipText(getKeyTooltipText());
     
    103244
    104245    @Override
    105246    protected PresetListEntry getSelectedItem() {
    106         return new PresetListEntry(list.getSelectedValuesList()
    107             .stream().map(e -> e.value).distinct().sorted().collect(Collectors.joining(String.valueOf(delimiter))), this);
     247         StringBuilder result = new StringBuilder();
     248         List<PresetListEntry> temp = list.getSelectedValuesList();
     249
     250         if (!temp.isEmpty()) {
     251             if (values_sort.equals(String.valueOf(true))) {
     252                 Collections.sort(temp);
     253             }
     254
     255             if (prefix != null) {
     256                 result.append(prefix);
     257             }
     258
     259             result.append(temp.get(0).value);
     260
     261             for (int i = 1; i < temp.size(); i++) {
     262                 if (result.indexOf(temp.get(i).value) == -1) {
     263                     result.append(temp.get(i).delimiter != null ? temp.get(i).delimiter : delimiter).append(temp.get(i).value);
     264                 }
     265             }
     266         }
     267
     268         return new PresetListEntry(result.toString(), this);
     269    }
     270
     271    private final class SingleClickSelectionModel extends DefaultListSelectionModel {
     272        private boolean mousePressed;
     273        private ListModel<PresetListEntry> model;
     274
     275        private SingleClickSelectionModel(JList<PresetListEntry> list) {
     276            this.model = list.getModel();
     277            list.addMouseListener(new MouseAdapter() {
     278                @Override
     279                public void mousePressed(MouseEvent e) {
     280                    mousePressed = true;
     281                }
     282                @Override
     283                public void mouseReleased(MouseEvent e) {
     284                    mousePressed = false;
     285                }
     286            });
     287        }
     288
     289        @Override
     290        public void addSelectionInterval(int index0, int index1) {
     291            removeSelectionForGroupAtIndex(index0);
     292            super.addSelectionInterval(index0, index1);
     293        }
     294
     295        private void removeSelectionForGroupAtIndex(int index) {
     296            if(!isSelectedIndex(index)) {
     297                int selectGroup = model.getElementAt(index).select_group;
     298
     299                if(selectGroup != PresetListEntry.SELECT_GROUP_NONE) {
     300                    for(int i = 0; i < model.getSize(); i++) {
     301                        if(isSelectedIndex(i) && model.getElementAt(i).select_group == selectGroup) {
     302                            super.removeSelectionInterval(i, i);
     303                        }
     304                    }
     305                }
     306            }
     307        }
     308
     309        @Override
     310        public void setSelectionInterval(int index0, int index1) {
     311            if (quick_select && getSelectionMode() == MULTIPLE_INTERVAL_SELECTION) {
     312                if (!mousePressed) {
     313                    if (isSelectedIndex(index0)) {
     314                        super.removeSelectionInterval(index0, index1);
     315                    }
     316                    else {
     317                        removeSelectionForGroupAtIndex(index0);
     318                        super.addSelectionInterval(index0, index1);
     319                    }
     320                }
     321            }
     322            else {
     323                super.setSelectionInterval(index0, index1);
     324            }
     325        }
     326    }
     327
     328    private final class PresetListModel extends DefaultListModel<PresetListEntry> implements ReorderableTableModel<PresetListEntry> {
     329        @Override
     330        public PresetListEntry getValue(int index) {
     331            return get(index);
     332        }
     333
     334        @Override
     335        public PresetListEntry setValue(int index, PresetListEntry value) {
     336            return set(index, value);
     337        }
     338
     339        @Override
     340        public ListSelectionModel getSelectionModel() {
     341            return list.getSelectionModel();
     342        }
     343
     344        @Override
     345        public int getRowCount() {
     346            return size();
     347        }
    108348    }
    109349}
  • src/org/openstreetmap/josm/gui/tagging/presets/items/PresetListEntry.java

     
    2121 * {@link MultiSelect}.
    2222 */
    2323public class PresetListEntry implements Comparable<PresetListEntry> {
     24    /** Value for entries without a select group*/
     25    public static final int SELECT_GROUP_NONE = -1;
    2426    /** Used to display an entry matching several different values. */
    2527    protected static final PresetListEntry ENTRY_DIFFERENT = new PresetListEntry(KeyedItem.DIFFERENT, null);
    2628    /** Used to display an empty entry used to clear values. */
     
    4648    public String locale_display_value; // NOSONAR
    4749    /** The localized version of {@link #short_description}. */
    4850    public String locale_short_description; // NOSONAR
     51    /** The delimiter for separating this entry from the previous */
     52    public String delimiter; // NOSONAR
     53    /** The select group for multi select */
     54    public int select_group = SELECT_GROUP_NONE;
    4955
    5056    private String cachedDisplayValue;
    5157    private String cachedShortDescription;
    5258    private ImageIcon cachedIcon;
    5359
     60
    5461    /**
    5562     * Constructs a new {@code PresetListEntry}, uninitialized.
    5663     *
     
    6774     * @param cms the ComboMultiSelect
    6875     */
    6976    public PresetListEntry(String value, ComboMultiSelect cms) {
     77        this(value, cms, null);
     78    }
     79
     80    /**
     81     * Constructs a new {@code PresetListEntry}, initialized with a value and
     82     * {@link ComboMultiSelect} context and the delimiter for this entry.
     83     *
     84     * @param value value
     85     * @param cms the ComboMultiSelect
     86     * @param delimiter The character to use to separate this entry from the previous
     87     * @since 18619
     88     */
     89    public PresetListEntry(String value, ComboMultiSelect cms, String delimiter) {
    7090        this.value = value;
    7191        this.cms = cms;
     92        this.delimiter = delimiter;
    7293    }
    7394
    7495    /**
  • src/org/openstreetmap/josm/gui/tagging/presets/items/SelectGroup.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.tagging.presets.items;
     3
     4import java.util.List;
     5
     6import javax.swing.JPanel;
     7
     8import org.openstreetmap.josm.data.osm.Tag;
     9import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem;
     10import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;
     11
     12/**
     13 * A group of {@link PresetListEntry}s.
     14 * @since 18637
     15 */
     16public class SelectGroup extends TaggingPresetItem {
     17
     18    @Override
     19    protected boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support) {
     20        // TODO Auto-generated method stub
     21        return false;
     22    }
     23
     24    @Override
     25    protected void addCommands(List<Tag> changedTags) {
     26        // TODO Auto-generated method stub
     27
     28    }
     29
     30}