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}