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 javafx.beans.property.BooleanProperty;
  29 import javafx.beans.property.BooleanPropertyBase;
  30 import javafx.beans.property.SimpleBooleanProperty;
  31 
  32 import javafx.event.ActionEvent;
  33 import javafx.geometry.Pos;
  34 import javafx.css.PseudoClass;
  35 import javafx.scene.AccessibleAttribute;
  36 import javafx.scene.AccessibleRole;
  37 import com.sun.javafx.scene.control.skin.CheckBoxSkin;
  38 
  39 /**
  40  * A tri-state selection Control typically skinned as a box with a checkmark or
  41  * tick mark when checked. A CheckBox control can be in one of three states:
  42  * <ul>
  43  *  <li><em>checked</em>: indeterminate == false, checked == true</li>
  44  *  <li><em>unchecked</em>: indeterminate == false, checked == false</li>
  45  *  <li><em>undefined</em>: indeterminate == true</li>
  46  * </ul>
  47  * If a CheckBox is checked, then it is also by definition defined. When
  48  * checked the CheckBox is typically rendered with a "check" or "tick" mark.
  49  * A CheckBox is in this state if selected is true and indeterminate is false.
  50  * <p>
  51  * A CheckBox is unchecked if selected is false and indeterminate is false.
  52  * <p>
  53  * A CheckBox is undefined if indeterminate is true, regardless of the state
  54  * of selected. A typical rendering would be with a minus or dash, to
  55  * indicate an undefined or indeterminate state of the CheckBox.
  56  * This is convenient for constructing tri-state checkbox
  57  * based trees, for example, where undefined check boxes typically mean "inherit
  58  * settings from the parent".
  59  * <p>
  60  * The allowIndeterminate variable, if true, allows the user
  61  * to cycle through the undefined state. If false, the CheckBox is
  62  * not in the indeterminate state, and the user is allowed to change only the checked
  63  * state.
  64  *
  65  * <p>Example:
  66  * <pre><code>CheckBox cb = new CheckBox("a checkbox");
  67  * cb.setIndeterminate(false);</code></pre>
  68  *
  69  * <p>
  70  * MnemonicParsing is enabled by default for CheckBox.
  71  * </p>
  72  *
  73  * @since JavaFX 2.0
  74  */
  75 
  76 public class CheckBox extends ButtonBase {
  77 
  78     /***************************************************************************
  79      *                                                                         *
  80      * Constructors                                                            *
  81      *                                                                         *
  82      **************************************************************************/
  83 
  84     /**
  85      * Creates a check box with an empty string for its label.
  86      */
  87     public CheckBox() {
  88         initialize();
  89     }
  90 
  91     /**
  92      * Creates a check box with the specified text as its label.
  93      *
  94      * @param text A text string for its label.
  95      */
  96     public CheckBox(String text) {
  97         setText(text);
  98         initialize();
  99     }
 100 
 101     private void initialize() {
 102         getStyleClass().setAll(DEFAULT_STYLE_CLASS);
 103         setAccessibleRole(AccessibleRole.CHECK_BOX);
 104         setAlignment(Pos.CENTER_LEFT);
 105         setMnemonicParsing(true);     // enable mnemonic auto-parsing by default
 106         
 107         // initialize pseudo-class state
 108         pseudoClassStateChanged(PSEUDO_CLASS_DETERMINATE, true);
 109     }
 110     
 111     /***************************************************************************
 112      *                                                                         *
 113      * Properties                                                              *
 114      *                                                                         *
 115      **************************************************************************/
 116     /**
 117      * Determines whether the CheckBox is in the indeterminate state.
 118      */
 119     private BooleanProperty indeterminate;
 120     public final void setIndeterminate(boolean value) {
 121         indeterminateProperty().set(value);
 122     }
 123 
 124     public final boolean isIndeterminate() {
 125         return indeterminate == null ? false : indeterminate.get();
 126     }
 127 
 128     public final BooleanProperty indeterminateProperty() {
 129         if (indeterminate == null) {
 130             indeterminate = new BooleanPropertyBase(false) {
 131                 @Override protected void invalidated() {
 132                     final boolean active = get();
 133                     pseudoClassStateChanged(PSEUDO_CLASS_DETERMINATE,  !active);
 134                     pseudoClassStateChanged(PSEUDO_CLASS_INDETERMINATE, active);
 135                     notifyAccessibleAttributeChanged(AccessibleAttribute.INDETERMINATE);
 136                 }
 137 
 138                 @Override
 139                 public Object getBean() {
 140                     return CheckBox.this;
 141                 }
 142 
 143                 @Override
 144                 public String getName() {
 145                     return "indeterminate";
 146                 }
 147             };
 148         }
 149         return indeterminate;
 150     }
 151     /**
 152      * Indicates whether this CheckBox is checked.
 153      */
 154     private BooleanProperty selected;
 155     public final void setSelected(boolean value) {
 156         selectedProperty().set(value);
 157     }
 158 
 159     public final boolean isSelected() {
 160         return selected == null ? false : selected.get();
 161     }
 162 
 163     public final BooleanProperty selectedProperty() {
 164         if (selected == null) {
 165             selected = new BooleanPropertyBase() {
 166                 @Override protected void invalidated() {
 167                     final Boolean v = get();
 168                     pseudoClassStateChanged(PSEUDO_CLASS_SELECTED, v);
 169                     notifyAccessibleAttributeChanged(AccessibleAttribute.SELECTED);
 170                 }
 171 
 172                 @Override
 173                 public Object getBean() {
 174                     return CheckBox.this;
 175                 }
 176 
 177                 @Override
 178                 public String getName() {
 179                     return "selected";
 180                 }
 181             };
 182         }
 183         return selected;
 184     }
 185     /**
 186      * Determines whether the user toggling the CheckBox should cycle through
 187      * all three states: <em>checked</em>, <em>unchecked</em>, and
 188      * <em>undefined</em>. If {@code true} then all three states will be
 189      * cycled through; if {@code false} then only <em>checked</em> and
 190      * <em>unchecked</em> will be cycled.
 191      */
 192     private BooleanProperty allowIndeterminate;
 193 
 194     public final void setAllowIndeterminate(boolean value) {
 195         allowIndeterminateProperty().set(value);
 196     }
 197 
 198     public final boolean isAllowIndeterminate() {
 199         return allowIndeterminate == null ? false : allowIndeterminate.get();
 200     }
 201 
 202     public final BooleanProperty allowIndeterminateProperty() {
 203         if (allowIndeterminate == null) {
 204             allowIndeterminate =
 205                     new SimpleBooleanProperty(this, "allowIndeterminate");
 206         }
 207         return allowIndeterminate;
 208     }
 209 
 210     /***************************************************************************
 211      *                                                                         *
 212      * Methods                                                                 *
 213      *                                                                         *
 214      **************************************************************************/
 215 
 216     /**
 217      * Toggles the state of the {@code CheckBox}. If allowIndeterminate is
 218      * true, then each invocation of this function will advance the CheckBox
 219      * through the states checked, unchecked, and undefined. If
 220      * allowIndeterminate is false, then the CheckBox will only cycle through
 221      * the checked and unchecked states, and forcing indeterminate to equal to
 222      * false.
 223      */
 224     @Override public void fire() {
 225         if (!isDisabled()) {
 226             if (isAllowIndeterminate()) {
 227                 if (!isSelected() && !isIndeterminate()) {
 228                     setIndeterminate(true);
 229                 } else if (isSelected() && !isIndeterminate()) {
 230                     setSelected(false);
 231                 } else if (isIndeterminate()) {
 232                     setSelected(true);
 233                     setIndeterminate(false);
 234                 }
 235             } else {
 236                 setSelected(!isSelected());
 237                 setIndeterminate(false);
 238             }
 239             fireEvent(new ActionEvent());
 240         }
 241     }
 242 
 243     /** {@inheritDoc} */
 244     @Override protected Skin<?> createDefaultSkin() {
 245         return new CheckBoxSkin(this);
 246     }
 247 
 248 
 249     /***************************************************************************
 250      *                                                                         *
 251      * Stylesheet Handling                                                     *
 252      *                                                                         *
 253      **************************************************************************/
 254 
 255     private static final String DEFAULT_STYLE_CLASS = "check-box";
 256     private static final PseudoClass PSEUDO_CLASS_DETERMINATE = 
 257             PseudoClass.getPseudoClass("determinate");
 258     private static final PseudoClass PSEUDO_CLASS_INDETERMINATE = 
 259             PseudoClass.getPseudoClass("indeterminate");
 260     private static final PseudoClass PSEUDO_CLASS_SELECTED = 
 261             PseudoClass.getPseudoClass("selected");
 262 
 263 
 264     /***************************************************************************
 265      *                                                                         *
 266      * Accessibility handling                                                  *
 267      *                                                                         *
 268      **************************************************************************/
 269 
 270     @Override
 271     public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
 272         switch (attribute) {
 273             case SELECTED: return isSelected();
 274             case INDETERMINATE: return isIndeterminate();
 275             default: return super.queryAccessibleAttribute(attribute, parameters);
 276         }
 277     }
 278 }