001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.actions;
003
004import java.awt.event.ActionEvent;
005import java.util.HashSet;
006import java.util.Set;
007
008import javax.swing.ButtonModel;
009import javax.swing.JCheckBox;
010import javax.swing.JCheckBoxMenuItem;
011import javax.swing.JRadioButton;
012import javax.swing.JRadioButtonMenuItem;
013import javax.swing.JToggleButton;
014
015import org.openstreetmap.josm.tools.ImageProvider;
016import org.openstreetmap.josm.tools.Logging;
017import org.openstreetmap.josm.tools.Shortcut;
018
019/**
020 * Abtract class for Toggle Actions.
021 * @since 6220
022 */
023public abstract class ToggleAction extends JosmAction {
024
025    private final transient Set<ButtonModel> buttonModels = new HashSet<>();
026
027    /**
028     * Constructs a {@code ToggleAction}.
029     *
030     * @param name the action's text as displayed on the menu (if it is added to a menu)
031     * @param icon the icon to use
032     * @param tooltip  a longer description of the action that will be displayed in the tooltip. Please note
033     *           that html is not supported for menu actions on some platforms.
034     * @param shortcut a ready-created shortcut object or null if you don't want a shortcut. But you always
035     *            do want a shortcut, remember you can always register it with group=none, so you
036     *            won't be assigned a shortcut unless the user configures one. If you pass null here,
037     *            the user CANNOT configure a shortcut for your action.
038     * @param registerInToolbar register this action for the toolbar preferences?
039     * @param toolbarId identifier for the toolbar preferences. The iconName is used, if this parameter is null
040     * @param installAdapters false, if you don't want to install layer changed and selection changed adapters
041     */
042    public ToggleAction(String name, ImageProvider icon, String tooltip, Shortcut shortcut, boolean registerInToolbar,
043            String toolbarId, boolean installAdapters) {
044        super(name, icon, tooltip, shortcut, registerInToolbar, toolbarId, installAdapters);
045        // It is required to set the SELECTED_KEY to a non-null value in order to let Swing components update it
046        setSelected(false);
047    }
048
049    /**
050     * Constructs a {@code ToggleAction}.
051     *
052     * @param name the action's text as displayed on the menu (if it is added to a menu)
053     * @param iconName the name of icon to use
054     * @param tooltip  a longer description of the action that will be displayed in the tooltip. Please note
055     *           that html is not supported for menu actions on some platforms.
056     * @param shortcut a ready-created shortcut object or null if you don't want a shortcut. But you always
057     *            do want a shortcut, remember you can always register it with group=none, so you
058     *            won't be assigned a shortcut unless the user configures one. If you pass null here,
059     *            the user CANNOT configure a shortcut for your action.
060     * @param registerInToolbar register this action for the toolbar preferences?
061     */
062    public ToggleAction(String name, String iconName, String tooltip, Shortcut shortcut, boolean registerInToolbar) {
063        super(name, iconName, tooltip, shortcut, registerInToolbar);
064        // It is required to set the SELECTED_KEY to a non-null value in order to let Swing components update it
065        setSelected(false);
066    }
067
068    protected final void setSelected(boolean selected) {
069        putValue(SELECTED_KEY, selected);
070    }
071
072    /**
073     * Determines if this action is currently being selected.
074     * @return {@code true} if this action is currently being selected, {@code false} otherwise
075     */
076    public final boolean isSelected() {
077        Object selected = getValue(SELECTED_KEY);
078        if (selected instanceof Boolean) {
079            return (Boolean) selected;
080        } else {
081            Logging.warn(getClass().getName() + " does not define a boolean for SELECTED_KEY but " + selected +
082                    ". You should report it to JOSM developers.");
083            return false;
084        }
085    }
086
087    /**
088     * Adds a button model
089     * @param model The button model to add
090     */
091    public final void addButtonModel(ButtonModel model) {
092        if (model != null && !buttonModels.contains(model)) {
093            buttonModels.add(model);
094            model.setSelected(isSelected());
095        }
096    }
097
098    /**
099     * Removes a button model
100     * @param model The button model to remove
101     */
102    public final void removeButtonModel(ButtonModel model) {
103        if (model != null && buttonModels.contains(model)) {
104            buttonModels.remove(model);
105        }
106    }
107
108    protected void notifySelectedState() {
109        boolean selected = isSelected();
110        for (ButtonModel model: buttonModels) {
111            if (model.isSelected() != selected) {
112                model.setSelected(selected);
113            }
114        }
115    }
116
117    /**
118     * Toggles the selcted action state, if needed according to the ActionEvent that trigerred the action.
119     * This method will do nothing if the action event comes from a Swing component supporting the SELECTED_KEY property because
120     * the component already set the selected state.
121     * This method needs to be called especially if the action is associated with a keyboard shortcut to ensure correct selected state.
122     * @param e ActionEvent that trigerred the action
123     * @see <a href="https://weblogs.java.net/blog/zixle/archive/2005/11/changes_to_acti.html">Changes to Actions in 1.6</a>
124     * @see <a href="http://docs.oracle.com/javase/6/docs/api/javax/swing/Action.html">Interface Action</a>
125     */
126    protected final void toggleSelectedState(ActionEvent e) {
127        if (e == null || !(e.getSource() instanceof JToggleButton ||
128                           e.getSource() instanceof JCheckBox ||
129                           e.getSource() instanceof JRadioButton ||
130                           e.getSource() instanceof JCheckBoxMenuItem ||
131                           e.getSource() instanceof JRadioButtonMenuItem
132                           )) {
133            setSelected(!isSelected());
134        }
135    }
136}