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 }