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