001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.tagging.presets.items;
003
004import java.awt.Dimension;
005import java.awt.Insets;
006import java.awt.Rectangle;
007import java.util.stream.Collectors;
008
009import javax.swing.DefaultListModel;
010import javax.swing.JLabel;
011import javax.swing.JList;
012import javax.swing.JPanel;
013import javax.swing.JScrollPane;
014
015import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;
016import org.openstreetmap.josm.gui.widgets.OrientationAction;
017import org.openstreetmap.josm.tools.GBC;
018
019/**
020 * Multi-select list type.
021 */
022public class MultiSelect extends ComboMultiSelect {
023
024    /**
025     * Number of rows to display (positive integer, optional).
026     */
027    public short rows; // NOSONAR
028
029    /** The model for the JList */
030    protected final DefaultListModel<PresetListEntry> model = new DefaultListModel<>();
031    /** The swing component */
032    protected final JList<PresetListEntry> list = new JList<>(model);
033
034    private void addEntry(PresetListEntry entry) {
035        if (!seenValues.containsKey(entry.value)) {
036            model.addElement(entry);
037            seenValues.put(entry.value, entry);
038        }
039    }
040
041    @Override
042    protected boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support) {
043        initializeLocaleText(null);
044        usage = determineTextUsage(support.getSelected(), key);
045        seenValues.clear();
046        initListEntries();
047
048        model.clear();
049        // disable if the selected primitives have different values
050        list.setEnabled(usage.hasUniqueValue() || usage.unused());
051        String initialValue = getInitialValue(usage, support);
052
053        // Add values from the preset.
054        presetListEntries.forEach(this::addEntry);
055
056        // Add all values used in the selected primitives. This also adds custom values and makes
057        // sure we won't lose them.
058        usage = usage.splitValues(String.valueOf(delimiter));
059        for (String value: usage.map.keySet()) {
060            addEntry(new PresetListEntry(value, this));
061        }
062
063        // Select the values in the initial value.
064        if (!initialValue.isEmpty() && !DIFFERENT.equals(initialValue)) {
065            for (String value : initialValue.split(String.valueOf(delimiter), -1)) {
066                PresetListEntry e = new PresetListEntry(value, this);
067                addEntry(e);
068                int i = model.indexOf(e);
069                list.addSelectionInterval(i, i);
070            }
071        }
072
073        ComboMultiSelectListCellRenderer renderer = new ComboMultiSelectListCellRenderer(list, list.getCellRenderer(), 200, key);
074        list.setCellRenderer(renderer);
075        JLabel label = addLabel(p);
076        label.setLabelFor(list);
077        JScrollPane sp = new JScrollPane(list);
078
079        if (rows > 0) {
080            list.setVisibleRowCount(rows);
081            // setVisibleRowCount() only works when all cells have the same height, but sometimes we
082            // have icons of different sizes. Calculate the size of the first {@code rows} entries
083            // and size the scrollpane accordingly.
084            Rectangle r = list.getCellBounds(0, Math.min(rows, model.size() - 1));
085            if (r != null) {
086                Insets insets = list.getInsets();
087                r.width += insets.left + insets.right;
088                r.height += insets.top + insets.bottom;
089                insets = sp.getInsets();
090                r.width += insets.left + insets.right;
091                r.height += insets.top + insets.bottom;
092                sp.setPreferredSize(new Dimension(r.width, r.height));
093            }
094        }
095        p.add(sp, GBC.eol().fill(GBC.HORIZONTAL)); // NOSONAR
096
097        list.addListSelectionListener(l -> support.fireItemValueModified(this, key, getSelectedItem().value));
098        list.setToolTipText(getKeyTooltipText());
099        list.applyComponentOrientation(OrientationAction.getValueOrientation(key));
100
101        return true;
102    }
103
104    @Override
105    protected PresetListEntry getSelectedItem() {
106        return new PresetListEntry(list.getSelectedValuesList()
107            .stream().map(e -> e.value).distinct().sorted().collect(Collectors.joining(String.valueOf(delimiter))), this);
108    }
109}