1 /* 2 * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javafx.scene.control; 27 28 import java.util.HashMap; 29 import java.util.List; 30 31 import javafx.beans.property.ReadOnlyObjectProperty; 32 import javafx.beans.property.ReadOnlyObjectWrapper; 33 import javafx.collections.ListChangeListener.Change; 34 import javafx.collections.FXCollections; 35 import javafx.collections.ObservableList; 36 import javafx.collections.ObservableMap; 37 38 import com.sun.javafx.collections.VetoableListDecorator; 39 import com.sun.javafx.collections.TrackableObservableList; 40 41 /** 42 * A class which contains a reference to all {@code Toggles} whose 43 * {@code selected} variables should be managed such that only a single 44 * <code>{@link Toggle}</code> within the {@code ToggleGroup} may be selected at 45 * any one time. 46 * <p> 47 * Generally {@code ToggleGroups} are managed automatically simply by specifying 48 * the name of a {@code ToggleGroup} on the <code>{@link Toggle}</code>, but in 49 * some situations it is desirable to explicitly manage which 50 * {@code ToggleGroup} is used by <code>{@link Toggle Toggles}</code>. 51 * </p> 52 * @since JavaFX 2.0 53 */ 54 public class ToggleGroup { 55 56 /** 57 * Creates a default ToggleGroup instance. 58 */ 59 public ToggleGroup() { 60 61 } 62 63 /** 64 * The list of toggles within the ToggleGroup. 65 * @return the list of toggles within the ToggleGroup 66 */ 67 public final ObservableList<Toggle> getToggles() { 68 return toggles; 69 } 70 71 private final ObservableList<Toggle> toggles = new VetoableListDecorator<Toggle>(new TrackableObservableList<Toggle>() { 72 @Override protected void onChanged(Change<Toggle> c) { 73 while (c.next()) { 74 // Look through the removed toggles, and if any of them was the 75 // one and only selected toggle, then we will clear the selected 76 // toggle property. 77 for (Toggle t : c.getRemoved()) { 78 if (t.isSelected()) { 79 selectToggle(null); 80 } 81 } 82 83 // A Toggle can only be in one group at any one time. If the 84 // group is changed, then the toggle is removed from the old group prior to 85 // being added to the new group. 86 for (Toggle t: c.getAddedSubList()) { 87 if (!ToggleGroup.this.equals(t.getToggleGroup())) { 88 if (t.getToggleGroup() != null) { 89 t.getToggleGroup().getToggles().remove(t); 90 } 91 t.setToggleGroup(ToggleGroup.this); 92 } 93 } 94 95 // Look through all the added toggles and the very first selected 96 // toggle we encounter will become the one we make the selected 97 // toggle for this group. 98 for (Toggle t : c.getAddedSubList()) { 99 if (t.isSelected()) { 100 selectToggle(t); 101 break; 102 } 103 } 104 } 105 } 106 }) { 107 @Override protected void onProposedChange(List<Toggle> toBeAdded, int... indexes) { 108 for (Toggle t: toBeAdded) { 109 if (indexes[0] == 0 && indexes[1] == size()) { 110 // we don't need to check for duplicates because this is a setAll. 111 break; 112 } 113 if (toggles.contains(t)) { 114 throw new IllegalArgumentException("Duplicate toggles are not allow in a ToggleGroup."); 115 } 116 } 117 } 118 }; 119 120 private final ReadOnlyObjectWrapper<Toggle> selectedToggle = new ReadOnlyObjectWrapper<Toggle>() { 121 // Note: "set" is really what I want here. If the selectedToggle property 122 // is bound, then this whole chunk of code is bypassed, which is exactly 123 // what I want to do. 124 @Override public void set(final Toggle newSelectedToggle) { 125 if (isBound()) { 126 throw new java.lang.RuntimeException("A bound value cannot be set."); 127 } 128 final Toggle old = get(); 129 if (old == newSelectedToggle) { 130 return; 131 } 132 if (setSelected(newSelectedToggle, true) || 133 (newSelectedToggle != null && newSelectedToggle.getToggleGroup() == ToggleGroup.this) || 134 (newSelectedToggle == null)) { 135 if (old == null || old.getToggleGroup() == ToggleGroup.this || !old.isSelected()) { 136 setSelected(old, false); 137 } 138 super.set(newSelectedToggle); 139 } 140 } 141 }; 142 143 /** 144 * Selects the toggle. 145 * 146 * @param value The {@code Toggle} that is to be selected. 147 */ 148 // Note that since selectedToggle is a read-only property, the selectToggle method is some 149 // other method than setSelectedToggle, even though it is in essence doing the same thing 150 public final void selectToggle(Toggle value) { selectedToggle.set(value); } 151 152 /** 153 * Gets the selected {@code Toggle}. 154 * @return Toggle The selected toggle. 155 */ 156 public final Toggle getSelectedToggle() { return selectedToggle.get(); } 157 158 /** 159 * The selected toggle. 160 * @return the selected toggle 161 */ 162 public final ReadOnlyObjectProperty<Toggle> selectedToggleProperty() { return selectedToggle.getReadOnlyProperty(); } 163 164 private boolean setSelected(Toggle toggle, boolean selected) { 165 if (toggle != null && 166 toggle.getToggleGroup() == this && 167 !toggle.selectedProperty().isBound()) { 168 toggle.setSelected(selected); 169 return true; 170 } 171 return false; 172 } 173 174 // Clear the selected toggle only if there are no other toggles selected. 175 final void clearSelectedToggle() { 176 if (!selectedToggle.getValue().isSelected()) { 177 for (Toggle toggle: getToggles()) { 178 if (toggle.isSelected()) { 179 return; 180 } 181 } 182 } 183 selectedToggle.set(null); 184 } 185 186 /************************************************************************* 187 * * 188 * * 189 * * 190 *************************************************************************/ 191 192 private static final Object USER_DATA_KEY = new Object(); 193 // A map containing a set of properties for this scene 194 private ObservableMap<Object, Object> properties; 195 196 /** 197 * Returns an observable map of properties on this node for use primarily 198 * by application developers. 199 * 200 * @return an observable map of properties on this node for use primarily 201 * by application developers 202 * 203 * @since JavaFX 8u40 204 */ 205 public final ObservableMap<Object, Object> getProperties() { 206 if (properties == null) { 207 properties = FXCollections.observableMap(new HashMap<Object, Object>()); 208 } 209 return properties; 210 } 211 212 /** 213 * Tests if ToggleGroup has properties. 214 * @return true if node has properties. 215 * 216 * @since JavaFX 8u40 217 */ 218 public boolean hasProperties() { 219 return properties != null && !properties.isEmpty(); 220 } 221 222 /** 223 * Convenience method for setting a single Object property that can be 224 * retrieved at a later date. This is functionally equivalent to calling 225 * the getProperties().put(Object key, Object value) method. This can later 226 * be retrieved by calling {@link ToggleGroup#getUserData()}. 227 * 228 * @param value The value to be stored - this can later be retrieved by calling 229 * {@link ToggleGroup#getUserData()}. 230 * 231 * @since JavaFX 8u40 232 */ 233 public void setUserData(Object value) { 234 getProperties().put(USER_DATA_KEY, value); 235 } 236 237 /** 238 * Returns a previously set Object property, or null if no such property 239 * has been set using the {@link ToggleGroup#setUserData(java.lang.Object)} method. 240 * 241 * @return The Object that was previously set, or null if no property 242 * has been set or if null was set. 243 * 244 * @since JavaFX 8u40 245 */ 246 public Object getUserData() { 247 return getProperties().get(USER_DATA_KEY); 248 } 249 }