001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.tagging.presets.items;
003
004import java.awt.GridBagLayout;
005import java.awt.event.MouseAdapter;
006import java.awt.event.MouseEvent;
007import java.util.ArrayList;
008import java.util.Arrays;
009import java.util.Collection;
010import java.util.List;
011
012import javax.swing.ImageIcon;
013import javax.swing.JLabel;
014import javax.swing.JPanel;
015import javax.swing.SwingConstants;
016
017import org.openstreetmap.josm.data.osm.OsmPrimitive;
018import org.openstreetmap.josm.data.osm.OsmUtils;
019import org.openstreetmap.josm.data.osm.Tag;
020import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetReader;
021import org.openstreetmap.josm.gui.widgets.QuadStateCheckBox;
022import org.openstreetmap.josm.tools.GBC;
023
024/**
025 * Checkbox type.
026 */
027public class Check extends KeyedItem {
028
029    /** The localized version of {@link #text}. */
030    public String locale_text; // NOSONAR
031    /** the value to set when checked (default is "yes") */
032    public String value_on = OsmUtils.TRUE_VALUE; // NOSONAR
033    /** the value to set when unchecked (default is "no") */
034    public String value_off = OsmUtils.FALSE_VALUE; // NOSONAR
035    /** whether the off value is disabled in the dialog, i.e., only unset or yes are provided */
036    public boolean disable_off; // NOSONAR
037    /** "on" or "off" or unset (default is unset) */
038    public String default_; // only used for tagless objects // NOSONAR
039    /** The location of icon file to display */
040    public String icon; // NOSONAR
041    /** The size of displayed icon. If not set, default is 16px */
042    public String icon_size; // NOSONAR
043
044    private QuadStateCheckBox check;
045    private QuadStateCheckBox.State initialState;
046    private Boolean def;
047
048    @Override
049    public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) {
050
051        // find out if our key is already used in the selection.
052        final Usage usage = determineBooleanUsage(sel, key);
053        final String oneValue = usage.values.isEmpty() ? null : usage.values.last();
054        def = "on".equals(default_) ? Boolean.TRUE : "off".equals(default_) ? Boolean.FALSE : null;
055
056        if (locale_text == null) {
057            locale_text = getLocaleText(text, text_context, null);
058        }
059
060        if (usage.values.size() < 2 && (oneValue == null || value_on.equals(oneValue) || value_off.equals(oneValue))) {
061            if (def != null && !PROP_FILL_DEFAULT.get()) {
062                // default is set and filling default values feature is disabled - check if all primitives are untagged
063                for (OsmPrimitive s : sel) {
064                    if (s.hasKeys()) {
065                        def = null;
066                    }
067                }
068            }
069
070            // all selected objects share the same value which is either true or false or unset,
071            // we can display a standard check box.
072            initialState = value_on.equals(oneValue) || Boolean.TRUE.equals(def)
073                    ? QuadStateCheckBox.State.SELECTED
074                    : value_off.equals(oneValue) || Boolean.FALSE.equals(def)
075                    ? QuadStateCheckBox.State.NOT_SELECTED
076                    : QuadStateCheckBox.State.UNSET;
077
078        } else {
079            def = null;
080            // the objects have different values, or one or more objects have something
081            // else than true/false. we display a quad-state check box
082            // in "partial" state.
083            initialState = QuadStateCheckBox.State.PARTIAL;
084        }
085
086        final List<QuadStateCheckBox.State> allowedStates = new ArrayList<>(4);
087        if (QuadStateCheckBox.State.PARTIAL == initialState)
088            allowedStates.add(QuadStateCheckBox.State.PARTIAL);
089        allowedStates.add(QuadStateCheckBox.State.SELECTED);
090        if (!disable_off || value_off.equals(oneValue))
091            allowedStates.add(QuadStateCheckBox.State.NOT_SELECTED);
092        allowedStates.add(QuadStateCheckBox.State.UNSET);
093        check = new QuadStateCheckBox(icon == null ? locale_text : null, initialState,
094                allowedStates.toArray(new QuadStateCheckBox.State[0]));
095        check.setPropertyText(key);
096        check.setState(check.getState()); // to update the tooltip text
097
098        if (icon != null) {
099            JPanel checkPanel = new JPanel(new GridBagLayout());
100            checkPanel.add(check, GBC.std());
101            JLabel label = new JLabel(locale_text, getIcon(), SwingConstants.LEFT);
102            label.addMouseListener(new MouseAdapter() {
103                @Override
104                public void mousePressed(MouseEvent e) {
105                    check.getMouseAdapter().mousePressed(e);
106                }
107            });
108            checkPanel.add(label);
109            checkPanel.add(new JLabel(), GBC.eol().fill());
110            p.add(checkPanel, GBC.eol()); // Do not fill, see #15104
111        } else {
112            p.add(check, GBC.eol()); // Do not fill, see #15104
113        }
114        return true;
115    }
116
117    @Override
118    public void addCommands(List<Tag> changedTags) {
119        // if the user hasn't changed anything, don't create a command.
120        if (def == null && check.getState() == initialState) return;
121
122        // otherwise change things according to the selected value.
123        changedTags.add(new Tag(key,
124                check.getState() == QuadStateCheckBox.State.SELECTED ? value_on :
125                    check.getState() == QuadStateCheckBox.State.NOT_SELECTED ? value_off :
126                        null));
127    }
128
129    @Override
130    public MatchType getDefaultMatch() {
131        return MatchType.NONE;
132    }
133
134    /**
135     * Returns the entry icon, if any.
136     * @return the entry icon, or {@code null}
137     * @since 15437
138     */
139    public ImageIcon getIcon() {
140        Integer size = parseInteger(icon_size);
141        return icon == null ? null : loadImageIcon(icon, TaggingPresetReader.getZipIcons(), size != null ? size : 16);
142    }
143
144    @Override
145    public Collection<String> getValues() {
146        return disable_off ? Arrays.asList(value_on) : Arrays.asList(value_on, value_off);
147    }
148
149    @Override
150    public String toString() {
151        return "Check [key=" + key + ", text=" + text + ", "
152                + (locale_text != null ? "locale_text=" + locale_text + ", " : "")
153                + (value_on != null ? "value_on=" + value_on + ", " : "")
154                + (value_off != null ? "value_off=" + value_off + ", " : "")
155                + "default_=" + default_ + ", "
156                + (check != null ? "check=" + check + ", " : "")
157                + (initialState != null ? "initialState=" + initialState
158                        + ", " : "") + "def=" + def + ']';
159    }
160}