Spec-Zone .ru
спецификации, руководства, описания, API
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 java.util.List;
029import javafx.beans.property.ReadOnlyObjectProperty;
030import javafx.beans.property.ReadOnlyObjectWrapper;
031import javafx.collections.ListChangeListener.Change;
032import javafx.collections.ObservableList;
033import com.sun.javafx.collections.VetoableListDecorator;
034import com.sun.javafx.collections.TrackableObservableList;
035
036/**
037 * A class which contains a reference to all {@code Toggles} whose
038 * {@code selected} variables should be managed such that only a single
039 * <code>{@link Toggle}</code> within the {@code ToggleGroup} may be selected at
040 * any one time.
041 * <p>
042 * Generally {@code ToggleGroups} are managed automatically simply by specifying
043 * the name of a {@code ToggleGroup} on the <code>{@link Toggle}</code>, but in
044 * some situations it is desirable to explicitly manage which
045 * {@code ToggleGroup} is used by <code>{@link Toggle Toggles}</code>.
046 * </p>
047 */
048public class ToggleGroup {
049    /**
050     * The list of toggles within the ToggleGroup.
051     */
052    public final ObservableList<Toggle> getToggles() {
053        return toggles;
054    }
055
056    private final ObservableList<Toggle> toggles = new VetoableListDecorator<Toggle>(new TrackableObservableList<Toggle>() {
057        @Override protected void onChanged(Change<Toggle> c) {            
058            while (c.next()) {
059                // Look through the removed toggles, and if any of them was the
060                // one and only selected toggle, then we will clear the selected
061                // toggle property.
062                for (Toggle t : c.getRemoved()) {
063                    if (t.isSelected()) {
064                        selectToggle(null);
065                    }
066                }
067                
068                // A Toggle can only be in one group at any one time. If the
069                // group is changed, then the toggle is removed from the old group prior to
070                // being added to the new group.
071                for (Toggle t: c.getAddedSubList()) {
072                    if (!ToggleGroup.this.equals(t.getToggleGroup())) {
073                        if (t.getToggleGroup() != null) {
074                            t.getToggleGroup().getToggles().remove(t);
075                        }
076                        t.setToggleGroup(ToggleGroup.this);
077                    }
078                }
079                
080                // Look through all the added toggles and the very first selected
081                // toggle we encounter will become the one we make the selected
082                // toggle for this group.                
083                for (Toggle t : c.getAddedSubList()) {                    
084                    if (t.isSelected()) {
085                        selectToggle(t);
086                        break;
087                    }
088                }
089            }
090        }
091    }) {
092        @Override protected void onProposedChange(List<Toggle> toBeAdded, int... indexes) {
093            for (Toggle t: toBeAdded) {
094                if (indexes[0] == 0 && indexes[1] == size()) {
095                    // we don't need to check for duplicates because this is a setAll.
096                    break;
097                }
098                if (toggles.contains(t)) {
099                    throw new IllegalArgumentException("Duplicate toggles are not allow in a ToggleGroup.");
100                }
101            }
102        }
103    };
104
105    private final ReadOnlyObjectWrapper<Toggle> selectedToggle = new ReadOnlyObjectWrapper<Toggle>() {
106        // Note: "set" is really what I want here. If the selectedToggle property
107        // is bound, then this whole chunk of code is bypassed, which is exactly
108        // what I want to do.
109        @Override public void set(final Toggle newSelectedToggle) {
110            if (isBound()) {
111                throw new java.lang.RuntimeException("A bound value cannot be set.");
112            }
113            final Toggle old = get();
114            if (setSelected(newSelectedToggle, true) ||
115                    (newSelectedToggle != null && newSelectedToggle.getToggleGroup() == ToggleGroup.this) ||
116                    (newSelectedToggle == null)) {
117                if (old == null || old.getToggleGroup() == ToggleGroup.this || !old.isSelected()) {
118                    setSelected(old, false);
119                }
120                super.set(newSelectedToggle);
121            }
122        }
123    };
124    
125    /**
126     * Selects the toggle.
127     *
128     * @param value The {@code Toggle} that is to be selected.
129     */
130    // Note that since selectedToggle is a read-only property, the selectToggle method is some
131    // other method than setSelectedToggle, even though it is in essence doing the same thing
132    public final void selectToggle(Toggle value) { selectedToggle.set(value); }
133
134    /**
135     * Gets the selected {@code Toggle}.
136     * @return Toggle The selected toggle.
137     */
138    public final Toggle getSelectedToggle() { return selectedToggle.get(); }
139
140    /**
141     * The selected toggle.
142     */
143    public final ReadOnlyObjectProperty<Toggle> selectedToggleProperty() { return selectedToggle.getReadOnlyProperty(); }
144
145    private boolean setSelected(Toggle toggle, boolean selected) {
146        if (toggle != null &&
147                toggle.getToggleGroup() == this &&
148                !toggle.selectedProperty().isBound()) {
149            toggle.setSelected(selected);
150            return true;
151        }
152        return false;
153    }
154
155    // Clear the selected toggle only if there are no other toggles selected.
156    final void clearSelectedToggle() {
157        if (!selectedToggle.getValue().isSelected()) {
158             for (Toggle toggle: getToggles()) {
159                 if (toggle.isSelected()) {
160                     return;
161                 }
162             }
163        }
164        selectedToggle.set(null);
165    }
166}