001/*
002 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.  Oracle designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Oracle in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
022 * or visit www.oracle.com if you need additional information or have any
023 * questions.
024 */
025
026package javafx.scene.control;
027
028import javafx.beans.property.BooleanProperty;
029import javafx.beans.property.BooleanPropertyBase;
030import javafx.beans.property.ObjectProperty;
031import javafx.beans.property.ObjectPropertyBase;
032import javafx.event.ActionEvent;
033import javafx.geometry.Pos;
034import javafx.scene.Node;
035import javafx.css.CssMetaData;
036import javafx.css.PseudoClass;
037import com.sun.javafx.scene.control.skin.ToggleButtonSkin;
038import javafx.css.StyleableProperty;
039
040/**
041 * A {@code ToggleButton} is a specialized control which has the ability to be
042 * selected. Typically a {@code ToggleButton} is rendered similarly to a Button.
043 * However, they are two different types of Controls. A Button is a "command"
044 * button which invokes a function when clicked. A {@code ToggleButton} on the
045 * other hand is simply a control with a Boolean indicating whether it has been
046 * selected.
047 * <p>
048 * {@code ToggleButton} can also be placed in groups. By default, a
049 * {@code ToggleButton} is not in a group. When in groups, only one
050 * {@code ToggleButton} at a time within that group can be selected. To put two
051 * {@code ToggleButtons} in the same group, simply assign them both the same
052 * value for {@link ToggleGroup}.
053 * </p>
054 * <p>
055 * Unlike {@link RadioButton RadioButtons}, {@code ToggleButtons} in a
056 * {@code ToggleGroup} do not attempt to force at least one selected
057 * {@code ToggleButton} in the group. That is, if a {@code ToggleButton} is
058 * selected, clicking on it will cause it to become unselected. With
059 * {@code RadioButton}, clicking on the selected button in the group will have
060 * no effect.
061 * </p>
062 *
063 * <p>Example:</p>
064 * <pre><code>
065 * ToggleButton tb1 = new ToggleButton("toggle button 1");
066 * ToggleButton tb2 = new ToggleButton("toggle button 2");
067 * ToggleButton tb3 = new ToggleButton("toggle button 3");
068 * ToggleGroup group = new ToggleGroup();
069 * tb1.setToggleGroup(group);
070 * tb2.setToggleGroup(group);
071 * tb3.setToggleGroup(group);
072 * </code></pre>
073 *
074 * <p>
075 * MnemonicParsing is enabled by default for ToggleButton.
076 * </p>
077 */
078
079// TODO Mention the semantics when binding "selected" on multiple toggle buttons
080// which are all on the same toggle group, and how the selected state on the
081// toggle group is affected or not in such a case.
082
083 public class ToggleButton extends ButtonBase implements Toggle {
084
085    /***************************************************************************
086     *                                                                         *
087     * Constructors                                                            *
088     *                                                                         *
089     **************************************************************************/
090
091    /**
092     * Creates a toggle button with an empty string for its label.
093     */
094    public ToggleButton() {
095        initialize();
096    }
097
098    /**
099     * Creates a toggle button with the specified text as its label.
100     *
101     * @param text A text string for its label.
102     */
103    public ToggleButton(String text) {
104        setText(text);
105        initialize();
106    }
107
108    /**
109     * Creates a toggle button with the specified text and icon for its label.
110     *
111     * @param text A text string for its label.
112     * @param graphic the icon for its label.
113     */
114    public ToggleButton(String text, Node graphic) {
115        setText(text);
116        setGraphic(graphic);
117        initialize();
118    }
119
120    private void initialize() {
121        getStyleClass().setAll(DEFAULT_STYLE_CLASS);
122        // alignment is styleable through css. Calling setAlignment
123        // makes it look to css like the user set the value and css will not 
124        // override. Initializing alignment by calling set on the 
125        // CssMetaData ensures that css will be able to override the value.
126        ((StyleableProperty)alignmentProperty()).applyStyle(null, Pos.CENTER);
127        setMnemonicParsing(true);     // enable mnemonic auto-parsing by default
128    }
129    /***************************************************************************
130     *                                                                         *
131     * Properties                                                              *
132     *                                                                         *
133     **************************************************************************/
134    /**
135     * Indicates whether this toggle button is selected. This can be manipulated
136     * programmatically.
137     */
138    private BooleanProperty selected;
139    public final void setSelected(boolean value) {
140        selectedProperty().set(value);
141    }
142
143    public final boolean isSelected() {
144        return selected == null ? false : selected.get();
145    }
146
147    public final BooleanProperty selectedProperty() {
148        if (selected == null) {
149            selected = new BooleanPropertyBase() {
150                @Override protected void invalidated() {
151                    if (getToggleGroup() != null) {
152                        if (get()) {
153                            getToggleGroup().selectToggle(ToggleButton.this);
154                        } else if (getToggleGroup().getSelectedToggle() == ToggleButton.this) {
155                            getToggleGroup().clearSelectedToggle();
156                        }
157                    }
158                    pseudoClassStateChanged(PSEUDO_CLASS_SELECTED, get());
159                }
160
161                @Override
162                public Object getBean() {
163                    return ToggleButton.this;
164                }
165
166                @Override
167                public String getName() {
168                    return "selected";
169                }
170            };
171        }
172        return selected;
173    }
174    /**
175     * The {@link ToggleGroup} to which this {@code ToggleButton} belongs. A
176     * {@code ToggleButton} can only be in one group at any one time. If the
177     * group is changed, then the button is removed from the old group prior to
178     * being added to the new group.
179     */
180    private ObjectProperty<ToggleGroup> toggleGroup;
181    public final void setToggleGroup(ToggleGroup value) {
182        toggleGroupProperty().set(value);
183    }
184
185    public final ToggleGroup getToggleGroup() {
186        return toggleGroup == null ? null : toggleGroup.get();
187    }
188
189    public final ObjectProperty<ToggleGroup> toggleGroupProperty() {
190        if (toggleGroup == null) {
191            toggleGroup = new ObjectPropertyBase<ToggleGroup>() {                
192                private ToggleGroup old;
193                @Override protected void invalidated() {
194                    final ToggleGroup tg = get();
195                    if (tg != null && !tg.getToggles().contains(ToggleButton.this)) {
196                        if (old != null) {
197                            old.getToggles().remove(ToggleButton.this);
198                        }
199                        tg.getToggles().add(ToggleButton.this);
200                    } else if (tg == null) {
201                        old.getToggles().remove(ToggleButton.this);
202                    }
203                    old = tg;
204                }
205
206                @Override
207                public Object getBean() {
208                    return ToggleButton.this;
209                }
210
211                @Override
212                public String getName() {
213                    return "toggleGroup";
214                }
215            };
216        }
217        return toggleGroup;
218    }
219
220    /***************************************************************************
221     *                                                                         *
222     * Methods                                                                 *
223     *                                                                         *
224     **************************************************************************/
225
226    /** {@inheritDoc} */
227    @Override public void fire() {
228        // TODO (aruiz): if (!isReadOnly(isSelected()) {
229        setSelected(!isSelected());
230        fireEvent(new ActionEvent());
231    }
232
233    /** {@inheritDoc} */
234    @Override protected Skin<?> createDefaultSkin() {
235        return new ToggleButtonSkin(this);
236    }
237
238
239    /***************************************************************************
240     *                                                                         *
241     * Stylesheet Handling                                                     *
242     *                                                                         *
243     **************************************************************************/
244
245    private static final String DEFAULT_STYLE_CLASS = "toggle-button";
246    private static final PseudoClass PSEUDO_CLASS_SELECTED =
247            PseudoClass.getPseudoClass("selected");
248
249     /**
250      * Not everything uses the default value of false for alignment. 
251      * This method provides a way to have them return the correct initial value.
252      * @treatAsPrivate implementation detail
253      */
254    @Deprecated @Override
255    protected Pos impl_cssGetAlignmentInitialValue() {
256        return Pos.CENTER;
257    }
258        
259}