001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui;
003
004import java.beans.PropertyChangeEvent;
005import java.beans.PropertyChangeListener;
006import java.util.Optional;
007
008import javax.swing.Action;
009import javax.swing.Icon;
010import javax.swing.JToggleButton;
011
012import org.openstreetmap.josm.actions.ExpertToggleAction;
013import org.openstreetmap.josm.actions.ExpertToggleAction.ExpertModeChangeListener;
014import org.openstreetmap.josm.spi.preferences.Config;
015import org.openstreetmap.josm.tools.CheckParameterUtil;
016import org.openstreetmap.josm.tools.Destroyable;
017
018/**
019 * Just a toggle button, with smaller border and icon only to display in
020 * MapFrame toolbars.
021 * Also provides methods for storing hidden state in preferences
022 * @author imi, akks
023 */
024public class IconToggleButton extends JToggleButton implements HideableButton, PropertyChangeListener, Destroyable, ExpertModeChangeListener {
025
026    private transient ShowHideButtonListener listener;
027    private boolean hideIfDisabled;
028    private final boolean isExpert;
029
030    /**
031     * Construct the toggle button with the given action.
032     * @param action associated action
033     */
034    public IconToggleButton(Action action) {
035        this(action, false);
036    }
037
038    /**
039     * Construct the toggle button with the given action.
040     * @param action associated action
041     * @param isExpert {@code true} if it's reserved to expert mode
042     */
043    public IconToggleButton(Action action, boolean isExpert) {
044        super(action);
045        CheckParameterUtil.ensureParameterNotNull(action, "action");
046        this.isExpert = isExpert;
047        setText(null);
048
049        Object o = action.getValue(Action.SHORT_DESCRIPTION);
050        if (o != null) {
051            setToolTipText(o.toString());
052        }
053
054        action.addPropertyChangeListener(this);
055
056        ExpertToggleAction.addExpertModeChangeListener(this);
057    }
058
059    @Override
060    public void propertyChange(PropertyChangeEvent evt) {
061        if ("active".equals(evt.getPropertyName())) {
062            setSelected((Boolean) evt.getNewValue());
063            requestFocusInWindow();
064        } else if ("selected".equals(evt.getPropertyName())) {
065            setSelected((Boolean) evt.getNewValue());
066        }
067    }
068
069    @Override
070    public void destroy() {
071        Action action = getAction();
072        if (action instanceof Destroyable) {
073            ((Destroyable) action).destroy();
074        }
075        if (action != null) {
076            action.removePropertyChangeListener(this);
077        }
078    }
079
080    String getPreferenceKey() {
081        String s = (String) getSafeActionValue("toolbar");
082        if (s == null && getAction() != null) {
083            s = getAction().getClass().getName();
084        }
085        return "sidetoolbar.hidden."+s;
086    }
087
088    @Override
089    public void expertChanged(boolean isExpert) {
090        applyButtonHiddenPreferences();
091    }
092
093    @Override
094    public void applyButtonHiddenPreferences() {
095        boolean alwaysHideDisabled = Config.getPref().getBoolean("sidetoolbar.hideDisabledButtons", false);
096        if (!isEnabled() && (hideIfDisabled || alwaysHideDisabled)) {
097            setVisible(false);  // hide because of disabled button
098        } else {
099            boolean hiddenFlag = false;
100            String hiddenFlagStr = Config.getPref().get(getPreferenceKey(), null);
101            if (hiddenFlagStr == null) {
102                if (isExpert && !ExpertToggleAction.isExpert()) {
103                    hiddenFlag = true;
104                }
105            } else {
106                hiddenFlag = Boolean.parseBoolean(hiddenFlagStr);
107            }
108            setVisible(!hiddenFlag); // show or hide, do what preferences say
109        }
110    }
111
112    @Override
113    public void setButtonHidden(boolean b) {
114        setVisible(!b);
115        if (listener != null) { // if someone wants to know about changes of visibility
116            if (!b) listener.buttonShown(); else listener.buttonHidden();
117        }
118        if ((b && isExpert && !ExpertToggleAction.isExpert()) ||
119            (!b && isExpert && ExpertToggleAction.isExpert())) {
120            Config.getPref().put(getPreferenceKey(), null);
121        } else {
122            Config.getPref().putBoolean(getPreferenceKey(), b);
123        }
124    }
125
126    /**
127     * This function should be called for plugins that want to enable auto-hiding
128     * custom buttons when they are disabled (because of incorrect layer, for example)
129     * @param b hide if disabled
130     */
131    public void setAutoHideDisabledButton(boolean b) {
132        hideIfDisabled = b;
133        if (b && !isEnabled()) {
134            setVisible(false);
135        }
136    }
137
138    @Override
139    public void showButton() {
140        setButtonHidden(false);
141    }
142
143    @Override
144    public void hideButton() {
145        setButtonHidden(true);
146    }
147
148    @Override
149    public String getActionName() {
150        return (String) getSafeActionValue(Action.NAME);
151    }
152
153    @Override
154    public Icon getIcon() {
155        return (Icon) Optional.ofNullable(getSafeActionValue(Action.LARGE_ICON_KEY)).orElseGet(() -> getSafeActionValue(Action.SMALL_ICON));
156    }
157
158    @Override
159    public boolean isButtonVisible() {
160        return isVisible();
161    }
162
163    @Override
164    public boolean isExpert() {
165        return isExpert;
166    }
167
168    @Override
169    public void setShowHideButtonListener(ShowHideButtonListener l) {
170        listener = l;
171    }
172
173    protected final Object getSafeActionValue(String key) {
174        // Mac OS X Aqua L&F can call accessors from constructor, so getAction() can be null in those cases
175        return getAction() != null ? getAction().getValue(key) : null;
176    }
177}