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 com.sun.javafx.scene.traversal.ParentTraversalEngine; 29 30 import javafx.beans.property.BooleanProperty; 31 import javafx.beans.property.BooleanPropertyBase; 32 import javafx.beans.property.ObjectProperty; 33 import javafx.beans.property.ObjectPropertyBase; 34 import javafx.beans.value.ChangeListener; 35 import javafx.beans.value.WritableValue; 36 import javafx.event.ActionEvent; 37 import javafx.geometry.Pos; 38 import javafx.scene.AccessibleAttribute; 39 import javafx.scene.AccessibleRole; 40 import javafx.scene.Node; 41 import javafx.css.PseudoClass; 42 43 import javafx.scene.control.skin.ToggleButtonSkin; 44 45 import javafx.css.StyleableProperty; 46 47 /** 48 * A {@code ToggleButton} is a specialized control which has the ability to be 49 * selected. Typically a {@code ToggleButton} is rendered similarly to a Button. 50 * However, they are two different types of Controls. A Button is a "command" 51 * button which invokes a function when clicked. A {@code ToggleButton} on the 52 * other hand is simply a control with a Boolean indicating whether it has been 53 * selected. 54 * <p> 55 * {@code ToggleButton} can also be placed in groups. By default, a 56 * {@code ToggleButton} is not in a group. When in groups, only one 57 * {@code ToggleButton} at a time within that group can be selected. To put two 58 * {@code ToggleButtons} in the same group, simply assign them both the same 59 * value for {@link ToggleGroup}. 60 * </p> 61 * <p> 62 * Unlike {@link RadioButton RadioButtons}, {@code ToggleButtons} in a 63 * {@code ToggleGroup} do not attempt to force at least one selected 64 * {@code ToggleButton} in the group. That is, if a {@code ToggleButton} is 65 * selected, clicking on it will cause it to become unselected. With 66 * {@code RadioButton}, clicking on the selected button in the group will have 67 * no effect. 68 * </p> 69 * 70 * <p>Example:</p> 71 * <pre><code> 72 * ToggleButton tb1 = new ToggleButton("toggle button 1"); 73 * ToggleButton tb2 = new ToggleButton("toggle button 2"); 74 * ToggleButton tb3 = new ToggleButton("toggle button 3"); 75 * ToggleGroup group = new ToggleGroup(); 76 * tb1.setToggleGroup(group); 77 * tb2.setToggleGroup(group); 78 * tb3.setToggleGroup(group); 79 * </code></pre> 80 * 81 * <p> 82 * MnemonicParsing is enabled by default for ToggleButton. 83 * </p> 84 * @since JavaFX 2.0 85 */ 86 87 // TODO Mention the semantics when binding "selected" on multiple toggle buttons 88 // which are all on the same toggle group, and how the selected state on the 89 // toggle group is affected or not in such a case. 90 91 public class ToggleButton extends ButtonBase implements Toggle { 92 93 /*************************************************************************** 94 * * 95 * Constructors * 96 * * 97 **************************************************************************/ 98 99 /** 100 * Creates a toggle button with an empty string for its label. 101 */ 102 public ToggleButton() { 103 initialize(); 104 } 105 106 /** 107 * Creates a toggle button with the specified text as its label. 108 * 109 * @param text A text string for its label. 110 */ 111 public ToggleButton(String text) { 112 setText(text); 113 initialize(); 114 } 115 116 /** 117 * Creates a toggle button with the specified text and icon for its label. 118 * 119 * @param text A text string for its label. 120 * @param graphic the icon for its label. 121 */ 122 public ToggleButton(String text, Node graphic) { 123 setText(text); 124 setGraphic(graphic); 125 initialize(); 126 } 127 128 private void initialize() { 129 getStyleClass().setAll(DEFAULT_STYLE_CLASS); 130 setAccessibleRole(AccessibleRole.TOGGLE_BUTTON); 131 // alignment is styleable through css. Calling setAlignment 132 // makes it look to css like the user set the value and css will not 133 // override. Initializing alignment by calling set on the 134 // CssMetaData ensures that css will be able to override the value. 135 ((StyleableProperty<Pos>)(WritableValue<Pos>)alignmentProperty()).applyStyle(null, Pos.CENTER); 136 setMnemonicParsing(true); // enable mnemonic auto-parsing by default 137 } 138 /*************************************************************************** 139 * * 140 * Properties * 141 * * 142 **************************************************************************/ 143 /** 144 * Indicates whether this toggle button is selected. This can be manipulated 145 * programmatically. 146 */ 147 private BooleanProperty selected; 148 public final void setSelected(boolean value) { 149 selectedProperty().set(value); 150 } 151 152 public final boolean isSelected() { 153 return selected == null ? false : selected.get(); 154 } 155 156 public final BooleanProperty selectedProperty() { 157 if (selected == null) { 158 selected = new BooleanPropertyBase() { 159 @Override protected void invalidated() { 160 final boolean selected = get(); 161 final ToggleGroup tg = getToggleGroup(); 162 // Note: these changes need to be done before selectToggle/clearSelectedToggle since 163 // those operations change properties and can execute user code, possibly modifying selected property again 164 pseudoClassStateChanged(PSEUDO_CLASS_SELECTED, selected); 165 notifyAccessibleAttributeChanged(AccessibleAttribute.SELECTED); 166 if (tg != null) { 167 if (selected) { 168 tg.selectToggle(ToggleButton.this); 169 } else if (tg.getSelectedToggle() == ToggleButton.this) { 170 tg.clearSelectedToggle(); 171 } 172 } 173 } 174 175 @Override 176 public Object getBean() { 177 return ToggleButton.this; 178 } 179 180 @Override 181 public String getName() { 182 return "selected"; 183 } 184 }; 185 } 186 return selected; 187 } 188 /** 189 * The {@link ToggleGroup} to which this {@code ToggleButton} belongs. A 190 * {@code ToggleButton} can only be in one group at any one time. If the 191 * group is changed, then the button is removed from the old group prior to 192 * being added to the new group. 193 */ 194 private ObjectProperty<ToggleGroup> toggleGroup; 195 public final void setToggleGroup(ToggleGroup value) { 196 toggleGroupProperty().set(value); 197 } 198 199 public final ToggleGroup getToggleGroup() { 200 return toggleGroup == null ? null : toggleGroup.get(); 201 } 202 203 public final ObjectProperty<ToggleGroup> toggleGroupProperty() { 204 if (toggleGroup == null) { 205 toggleGroup = new ObjectPropertyBase<ToggleGroup>() { 206 private ToggleGroup old; 207 private ChangeListener<Toggle> listener = (o, oV, nV) -> 208 getImpl_traversalEngine().setOverriddenFocusTraversability(nV != null ? isSelected() : null); 209 210 @Override protected void invalidated() { 211 final ToggleGroup tg = get(); 212 if (tg != null && !tg.getToggles().contains(ToggleButton.this)) { 213 if (old != null) { 214 old.getToggles().remove(ToggleButton.this); 215 } 216 tg.getToggles().add(ToggleButton.this); 217 final ParentTraversalEngine parentTraversalEngine = new ParentTraversalEngine(ToggleButton.this); 218 setImpl_traversalEngine(parentTraversalEngine); 219 // If there's no toggle selected, do not override 220 parentTraversalEngine.setOverriddenFocusTraversability(tg.getSelectedToggle() != null ? isSelected() : null); 221 tg.selectedToggleProperty().addListener(listener); 222 } else if (tg == null) { 223 old.selectedToggleProperty().removeListener(listener); 224 old.getToggles().remove(ToggleButton.this); 225 setImpl_traversalEngine(null); 226 } 227 228 old = tg; 229 } 230 231 @Override 232 public Object getBean() { 233 return ToggleButton.this; 234 } 235 236 @Override 237 public String getName() { 238 return "toggleGroup"; 239 } 240 }; 241 } 242 return toggleGroup; 243 } 244 245 /*************************************************************************** 246 * * 247 * Methods * 248 * * 249 **************************************************************************/ 250 251 /** {@inheritDoc} */ 252 @Override public void fire() { 253 // TODO (aruiz): if (!isReadOnly(isSelected()) { 254 if (!isDisabled()) { 255 setSelected(!isSelected()); 256 fireEvent(new ActionEvent()); 257 } 258 } 259 260 /** {@inheritDoc} */ 261 @Override protected Skin<?> createDefaultSkin() { 262 return new ToggleButtonSkin(this); 263 } 264 265 266 /*************************************************************************** 267 * * 268 * Stylesheet Handling * 269 * * 270 **************************************************************************/ 271 272 private static final String DEFAULT_STYLE_CLASS = "toggle-button"; 273 private static final PseudoClass PSEUDO_CLASS_SELECTED = 274 PseudoClass.getPseudoClass("selected"); 275 276 /** 277 * Not everything uses the default value of false for alignment. 278 * This method provides a way to have them return the correct initial value. 279 * @treatAsPrivate implementation detail 280 */ 281 @Deprecated @Override 282 protected Pos impl_cssGetAlignmentInitialValue() { 283 return Pos.CENTER; 284 } 285 286 287 /*************************************************************************** 288 * * 289 * Accessibility handling * 290 * * 291 **************************************************************************/ 292 293 @Override 294 public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) { 295 switch (attribute) { 296 case SELECTED: return isSelected(); 297 default: return super.queryAccessibleAttribute(attribute, parameters); 298 } 299 } 300 }