1 /******************************************************************************* 2 * Copyright (c) 2005, 2015 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 *******************************************************************************/ 14 15 package org.eclipse.jface.commands; 16 17 import java.util.HashMap; 18 import java.util.HashSet; 19 import java.util.Map; 20 import java.util.Set; 21 22 import org.eclipse.core.commands.IStateListener; 23 import org.eclipse.core.commands.State; 24 import org.eclipse.jface.menus.IMenuStateIds; 25 26 /** 27 * <p> 28 * A piece of boolean state grouped with other boolean states. Of these states, 29 * only one may have a value of {@link Boolean#TRUE} at any given point in time. 30 * The values of all other states must be {@link Boolean#FALSE}. 31 * </p> 32 * <p> 33 * If this state is registered using {@link IMenuStateIds#STYLE}, then it will 34 * control the presentation of the command if displayed in the menus, tool bars 35 * or status line. 36 * </p> 37 * <p> 38 * Clients may instantiate or extend this interface. 39 * </p> 40 * 41 * @since 3.2 42 */ 43 public class RadioState extends ToggleState { 44 45 /** 46 * The manager of radio groups within the application. This ensures that 47 * only one member of a radio group is active at any one time, and tracks 48 * group memberships. 49 */ 50 private static final class RadioStateManager { 51 52 /** 53 * A group of radio states with the same identifier. 54 */ 55 private static final class RadioGroup implements IStateListener { 56 57 /** 58 * The active state. If there is no active state, then this value is 59 * <code>null</code>. 60 */ 61 private RadioState active = null; 62 63 /** 64 * The current members in this group. If there are no members, then 65 * this value is <code>nlistenerull</code>. 66 */ 67 private Set<RadioState> members = null; 68 69 /** 70 * Activates a member. This checks to see if there are any other 71 * active members. If there are, they are deactivated. 72 * 73 * @param state 74 * The state that should become active; must not be 75 * <code>null</code>. 76 */ activateMember(final RadioState state)77 private final void activateMember(final RadioState state) { 78 if (active != null && active != state) { 79 active.setValue(Boolean.FALSE); 80 } 81 active = state; 82 } 83 84 /** 85 * Adds a member to this radio group. If the state being added is 86 * active, then it replaces the currently active group member as 87 * the active state. 88 * 89 * @param state 90 * The state to add; must not be <code>null</code>. 91 */ addMember(final RadioState state)92 private final void addMember(final RadioState state) { 93 if (members == null) { 94 members = new HashSet<>(5); 95 } 96 97 members.add(state); 98 state.addListener(this); 99 100 final Object value = state.getValue(); 101 if (value instanceof Boolean) { 102 if (((Boolean) value).booleanValue()) { 103 activateMember(state); 104 } 105 } 106 } 107 108 @Override handleStateChange(final State state, final Object oldValue)109 public final void handleStateChange(final State state, 110 final Object oldValue) { 111 final Object newValue = state.getValue(); 112 if (newValue instanceof Boolean) { 113 if (((Boolean) newValue).booleanValue()) { 114 activateMember((RadioState) state); 115 } 116 } 117 } 118 119 /** 120 * Removes a member from this radio group. If the state was the 121 * active state, then there will be no active state. 122 * 123 * @param state 124 * The state to remove; must not be <code>null</code>. 125 */ removeMember(final RadioState state)126 private final void removeMember(final RadioState state) { 127 state.removeListener(this); 128 if (active == state) { 129 active = null; 130 } 131 132 if (members == null) { 133 return; 134 } 135 members.remove(state); 136 } 137 } 138 139 /** 140 * The map of radio states indexed by identifier (<code>String</code>). 141 * The radio states is either a single <code>RadioState</code> instance 142 * or a <code>Collection</code> of <code>RadioState</code> instances. 143 */ 144 private static Map<String, RadioGroup> radioStatesById = null; 145 146 /** 147 * Activates a particular state within a given group. 148 * 149 * @param identifier 150 * The identifier of the group to which the state belongs; 151 * must not be <code>null</code>. 152 * @param state 153 * The state to activate; must not be <code>null</code>. 154 */ activateGroup(final String identifier, final RadioState state)155 private static final void activateGroup(final String identifier, 156 final RadioState state) { 157 if (radioStatesById == null) { 158 return; 159 } 160 161 final RadioGroup radioGroup = radioStatesById.get(identifier); 162 if (radioGroup != null) { 163 radioGroup.activateMember(state); 164 } 165 } 166 167 /** 168 * Registers a piece of state with the radio manager. 169 * 170 * @param identifier 171 * The identifier of the radio group; must not be 172 * <code>null</code>. 173 * @param state 174 * The state to register; must not be <code>null</code>. 175 */ registerState(final String identifier, final RadioState state)176 private static final void registerState(final String identifier, 177 final RadioState state) { 178 if (radioStatesById == null) { 179 radioStatesById = new HashMap<>(); 180 } 181 182 RadioGroup radioGroup = radioStatesById.get(identifier); 183 if (radioGroup == null) { 184 radioGroup = new RadioGroup(); 185 radioStatesById.put(identifier, radioGroup); 186 } 187 radioGroup.addMember(state); 188 } 189 190 /** 191 * Unregisters a piece of state from the radio manager. 192 * 193 * @param identifier 194 * The identifier of the radio group; must not be 195 * <code>null</code>. 196 * @param state 197 * The state to unregister; must not be <code>null</code>. 198 */ unregisterState(final String identifier, final RadioState state)199 private static final void unregisterState(final String identifier, 200 final RadioState state) { 201 if (radioStatesById == null) { 202 return; 203 } 204 205 final RadioGroup radioGroup = radioStatesById.get(identifier); 206 if (radioGroup != null) { 207 radioGroup.removeMember(state); 208 } 209 } 210 } 211 212 /** 213 * The identifier of the radio group to which this state belongs. This value 214 * may be <code>null</code> if this state doesn't really belong to a group 215 * (yet). 216 */ 217 private String radioGroupIdentifier = null; 218 219 /** 220 * Unregisters this state from the manager, which detaches the listeners. 221 */ 222 @Override dispose()223 public void dispose() { 224 setRadioGroupIdentifier(null); 225 } 226 227 /** 228 * Sets the identifier of the radio group for this piece of state. If the 229 * identifier is cleared, then the state is unregistered. 230 * 231 * @param identifier 232 * The identifier of the radio group for this state; may be 233 * <code>null</code> if the identifier is being cleared. 234 * 235 */ setRadioGroupIdentifier(final String identifier)236 public final void setRadioGroupIdentifier(final String identifier) { 237 if (identifier == null) { 238 RadioStateManager.unregisterState(radioGroupIdentifier, this); 239 radioGroupIdentifier = null; 240 } else { 241 radioGroupIdentifier = identifier; 242 RadioStateManager.registerState(identifier, this); 243 } 244 } 245 246 /** 247 * Sets the value for this object. This notifies the radio state manager of 248 * the change. 249 * 250 * @param value 251 * The new value; should be a <code>Boolean</code>. 252 */ 253 @Override setValue(final Object value)254 public void setValue(final Object value) { 255 if (!(value instanceof Boolean)) { 256 throw new IllegalArgumentException( 257 "RadioState takes a Boolean as a value"); //$NON-NLS-1$ 258 } 259 260 if (((Boolean) value).booleanValue() && (radioGroupIdentifier != null)) { 261 RadioStateManager.activateGroup(radioGroupIdentifier, this); 262 } 263 264 super.setValue(value); 265 } 266 } 267