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 * Abstract 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 protected 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 protected 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) { 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 selected action state, if needed according to the ActionEvent that triggered the action. 119 * This method will do nothing if the action event comes from a Swing component 120 * supporting the SELECTED_KEY property because the component already set the selected state. 121 * This method needs to be called especially if the action is associated with a keyboard 122 * shortcut to ensure correct selected state. 123 * @param e ActionEvent that triggered the action 124 * @see <a href="https://docs.oracle.com/javase/8/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}