001// License: GPL. For details, see LICENSE file.
002
003package org.openstreetmap.josm.gui.tagging.presets.items;
004
005import static org.openstreetmap.josm.tools.I18n.tr;
006import static org.openstreetmap.josm.tools.I18n.trc;
007
008import java.util.Objects;
009
010import javax.swing.ImageIcon;
011
012import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem;
013import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetReader;
014import org.openstreetmap.josm.tools.AlphanumComparator;
015import org.openstreetmap.josm.tools.Utils;
016
017/**
018 * Preset list entry.
019 * <p>
020 * Used for controls that offer a list of items to choose from like {@link Combo} and
021 * {@link MultiSelect}.
022 */
023public class PresetListEntry implements Comparable<PresetListEntry> {
024    /** Used to display an entry matching several different values. */
025    protected static final PresetListEntry ENTRY_DIFFERENT = new PresetListEntry(KeyedItem.DIFFERENT, null);
026    /** Used to display an empty entry used to clear values. */
027    protected static final PresetListEntry ENTRY_EMPTY = new PresetListEntry("", null);
028
029    /**
030     * This is the value that is going to be written to the tag on the selected primitive(s). Except
031     * when the value is {@code "<different>"}, which is never written, or the value is empty, which
032     * deletes the tag.  {@code value} is never translated.
033     */
034    public String value; // NOSONAR
035    /** The ComboMultiSelect that displays the list */
036    public ComboMultiSelect cms; // NOSONAR
037    /** Text displayed to the user instead of {@link #value}. */
038    public String display_value; // NOSONAR
039    /** Text to be displayed below {@link #display_value} in the combobox list. */
040    public String short_description; // NOSONAR
041    /** The location of icon file to display */
042    public String icon; // NOSONAR
043    /** The size of displayed icon. If not set, default is size from icon file */
044    public short icon_size; // NOSONAR
045    /** The localized version of {@link #display_value}. */
046    public String locale_display_value; // NOSONAR
047    /** The localized version of {@link #short_description}. */
048    public String locale_short_description; // NOSONAR
049
050    private String cachedDisplayValue;
051    private String cachedShortDescription;
052    private ImageIcon cachedIcon;
053
054    /**
055     * Constructs a new {@code PresetListEntry}, uninitialized.
056     *
057     * Public default constructor is needed by {@link org.openstreetmap.josm.tools.XmlObjectParser.Parser#startElement}
058     */
059    public PresetListEntry() {
060    }
061
062    /**
063     * Constructs a new {@code PresetListEntry}, initialized with a value and
064     * {@link ComboMultiSelect} context.
065     *
066     * @param value value
067     * @param cms the ComboMultiSelect
068     */
069    public PresetListEntry(String value, ComboMultiSelect cms) {
070        this.value = value;
071        this.cms = cms;
072    }
073
074    /**
075     * Returns the contents displayed in the dropdown list.
076     *
077     * This is the contents that would be displayed in the current view plus a short description to
078     * aid the user.  The whole contents is wrapped to {@code width}.
079     *
080     * @param width the width in px
081     * @return HTML formatted contents
082     */
083    public String getListDisplay(int width) {
084        String displayValue = getDisplayValue();
085        Integer count = getCount();
086
087        if (count > 0 && cms.usage.getSelectedCount() > 1) {
088            displayValue = tr("{0} ({1})", displayValue, count);
089        }
090
091        if (this.equals(ENTRY_DIFFERENT)) {
092            return "<html><b>" + Utils.escapeReservedCharactersHTML(displayValue) + "</b></html>";
093        }
094
095        String shortDescription = getShortDescription();
096
097        if (shortDescription.isEmpty()) {
098            // avoids a collapsed list entry if value == ""
099            if (displayValue.isEmpty()) {
100                return " ";
101            }
102            return displayValue;
103        }
104
105        // RTL not supported in HTML. See: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4866977
106        return String.format("<html><div style=\"width: %d\"><b>%s</b><p style=\"padding-left: 10\">%s</p></div></html>",
107                width,
108                displayValue,
109                Utils.escapeReservedCharactersHTML(shortDescription));
110    }
111
112    /**
113     * Returns the entry icon, if any.
114     * @return the entry icon, or {@code null}
115     */
116    public ImageIcon getIcon() {
117        if (icon != null && cachedIcon == null) {
118            cachedIcon = TaggingPresetItem.loadImageIcon(icon, TaggingPresetReader.getZipIcons(), (int) icon_size);
119        }
120        return cachedIcon;
121    }
122
123    /**
124     * Returns the contents displayed in the current item view.
125     * @return the value to display
126     */
127    public String getDisplayValue() {
128        if (cachedDisplayValue == null) {
129            if (cms != null && cms.values_no_i18n) {
130                cachedDisplayValue = Utils.firstNonNull(value, " ");
131            } else {
132                cachedDisplayValue = Utils.firstNonNull(
133                    locale_display_value, tr(display_value), trc(cms == null ? null : cms.values_context, value), " ");
134            }
135        }
136        return cachedDisplayValue;
137    }
138
139    /**
140     * Returns the short description to display.
141     * @return the short description to display
142     */
143    public String getShortDescription() {
144        if (cachedShortDescription == null) {
145            cachedShortDescription = Utils.firstNonNull(locale_short_description, tr(short_description), "");
146        }
147        return cachedShortDescription;
148    }
149
150    /**
151     * Returns the tooltip for this entry.
152     * @param key the tag key
153     * @return the tooltip
154     */
155    public String getToolTipText(String key) {
156        if (this.equals(ENTRY_DIFFERENT)) {
157            return tr("Keeps the original values of the selected objects unchanged.");
158        }
159        if (value != null && !value.isEmpty()) {
160            return tr("Sets the key ''{0}'' to the value ''{1}''.", key, value);
161        }
162        return tr("Clears the key ''{0}''.", key);
163    }
164
165    // toString is mainly used to initialize the Editor
166    @Override
167    public String toString() {
168        if (this.equals(ENTRY_DIFFERENT))
169            return getDisplayValue();
170        return getDisplayValue().replaceAll("\\s*<.*>\\s*", " "); // remove additional markup, e.g. <br>
171    }
172
173    @Override
174    public boolean equals(Object o) {
175        if (this == o) return true;
176        if (o == null || getClass() != o.getClass()) return false;
177        PresetListEntry that = (PresetListEntry) o;
178        return Objects.equals(value, that.value);
179    }
180
181    @Override
182    public int hashCode() {
183        return Objects.hash(value);
184    }
185
186    /**
187     * Returns how many selected primitives had this value set.
188     * @return see above
189     */
190    public int getCount() {
191        Integer count = cms == null ? null : cms.usage.map.get(value);
192        return count == null ? 0 : count;
193    }
194
195    @Override
196    public int compareTo(PresetListEntry o) {
197        return AlphanumComparator.getInstance().compare(this.value, o.value);
198    }
199}