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 java.util.HashMap;
  29 import java.util.List;
  30 
  31 import javafx.beans.property.ReadOnlyObjectProperty;
  32 import javafx.beans.property.ReadOnlyObjectWrapper;
  33 import javafx.collections.ListChangeListener.Change;
  34 import javafx.collections.FXCollections;
  35 import javafx.collections.ObservableList;
  36 import javafx.collections.ObservableMap;
  37 
  38 import com.sun.javafx.collections.VetoableListDecorator;
  39 import com.sun.javafx.collections.TrackableObservableList;
  40 
  41 /**
  42  * A class which contains a reference to all {@code Toggles} whose
  43  * {@code selected} variables should be managed such that only a single
  44  * <code>{@link Toggle}</code> within the {@code ToggleGroup} may be selected at
  45  * any one time.
  46  * <p>
  47  * Generally {@code ToggleGroups} are managed automatically simply by specifying
  48  * the name of a {@code ToggleGroup} on the <code>{@link Toggle}</code>, but in
  49  * some situations it is desirable to explicitly manage which
  50  * {@code ToggleGroup} is used by <code>{@link Toggle Toggles}</code>.
  51  * </p>
  52  * @since JavaFX 2.0
  53  */
  54 public class ToggleGroup {
  55 
  56     /**
  57      * Creates a default ToggleGroup instance.
  58      */
  59     public ToggleGroup() {
  60 
  61     }
  62 
  63     /**
  64      * The list of toggles within the ToggleGroup.
  65      * @return the list of toggles within the ToggleGroup
  66      */
  67     public final ObservableList<Toggle> getToggles() {
  68         return toggles;
  69     }
  70 
  71     private final ObservableList<Toggle> toggles = new VetoableListDecorator<Toggle>(new TrackableObservableList<Toggle>() {
  72         @Override protected void onChanged(Change<Toggle> c) {
  73             while (c.next()) {
  74                 // Look through the removed toggles, and if any of them was the
  75                 // one and only selected toggle, then we will clear the selected
  76                 // toggle property.
  77                 for (Toggle t : c.getRemoved()) {
  78                     if (t.isSelected()) {
  79                         selectToggle(null);
  80                     }
  81                 }
  82 
  83                 // A Toggle can only be in one group at any one time. If the
  84                 // group is changed, then the toggle is removed from the old group prior to
  85                 // being added to the new group.
  86                 for (Toggle t: c.getAddedSubList()) {
  87                     if (!ToggleGroup.this.equals(t.getToggleGroup())) {
  88                         if (t.getToggleGroup() != null) {
  89                             t.getToggleGroup().getToggles().remove(t);
  90                         }
  91                         t.setToggleGroup(ToggleGroup.this);
  92                     }
  93                 }
  94 
  95                 // Look through all the added toggles and the very first selected
  96                 // toggle we encounter will become the one we make the selected
  97                 // toggle for this group.
  98                 for (Toggle t : c.getAddedSubList()) {
  99                     if (t.isSelected()) {
 100                         selectToggle(t);
 101                         break;
 102                     }
 103                 }
 104             }
 105         }
 106     }) {
 107         @Override protected void onProposedChange(List<Toggle> toBeAdded, int... indexes) {
 108             for (Toggle t: toBeAdded) {
 109                 if (indexes[0] == 0 && indexes[1] == size()) {
 110                     // we don't need to check for duplicates because this is a setAll.
 111                     break;
 112                 }
 113                 if (toggles.contains(t)) {
 114                     throw new IllegalArgumentException("Duplicate toggles are not allow in a ToggleGroup.");
 115                 }
 116             }
 117         }
 118     };
 119 
 120     private final ReadOnlyObjectWrapper<Toggle> selectedToggle = new ReadOnlyObjectWrapper<Toggle>() {
 121         // Note: "set" is really what I want here. If the selectedToggle property
 122         // is bound, then this whole chunk of code is bypassed, which is exactly
 123         // what I want to do.
 124         @Override public void set(final Toggle newSelectedToggle) {
 125             if (isBound()) {
 126                 throw new java.lang.RuntimeException("A bound value cannot be set.");
 127             }
 128             final Toggle old = get();
 129             if (old == newSelectedToggle) {
 130                 return;
 131             }
 132             if (setSelected(newSelectedToggle, true) ||
 133                     (newSelectedToggle != null && newSelectedToggle.getToggleGroup() == ToggleGroup.this) ||
 134                     (newSelectedToggle == null)) {
 135                 if (old == null || old.getToggleGroup() == ToggleGroup.this || !old.isSelected()) {
 136                     setSelected(old, false);
 137                 }
 138                 super.set(newSelectedToggle);
 139             }
 140         }
 141     };
 142 
 143     /**
 144      * Selects the toggle.
 145      *
 146      * @param value The {@code Toggle} that is to be selected.
 147      */
 148     // Note that since selectedToggle is a read-only property, the selectToggle method is some
 149     // other method than setSelectedToggle, even though it is in essence doing the same thing
 150     public final void selectToggle(Toggle value) { selectedToggle.set(value); }
 151 
 152     /**
 153      * Gets the selected {@code Toggle}.
 154      * @return Toggle The selected toggle.
 155      */
 156     public final Toggle getSelectedToggle() { return selectedToggle.get(); }
 157 
 158     /**
 159      * The selected toggle.
 160      * @return the selected toggle
 161      */
 162     public final ReadOnlyObjectProperty<Toggle> selectedToggleProperty() { return selectedToggle.getReadOnlyProperty(); }
 163 
 164     private boolean setSelected(Toggle toggle, boolean selected) {
 165         if (toggle != null &&
 166                 toggle.getToggleGroup() == this &&
 167                 !toggle.selectedProperty().isBound()) {
 168             toggle.setSelected(selected);
 169             return true;
 170         }
 171         return false;
 172     }
 173 
 174     // Clear the selected toggle only if there are no other toggles selected.
 175     final void clearSelectedToggle() {
 176         if (!selectedToggle.getValue().isSelected()) {
 177              for (Toggle toggle: getToggles()) {
 178                  if (toggle.isSelected()) {
 179                      return;
 180                  }
 181              }
 182         }
 183         selectedToggle.set(null);
 184     }
 185 
 186     /*************************************************************************
 187     *                                                                        *
 188     *                                                                        *
 189     *                                                                        *
 190     *************************************************************************/
 191 
 192     private static final Object USER_DATA_KEY = new Object();
 193     // A map containing a set of properties for this scene
 194     private ObservableMap<Object, Object> properties;
 195 
 196     /**
 197       * Returns an observable map of properties on this node for use primarily
 198       * by application developers.
 199       *
 200       * @return an observable map of properties on this node for use primarily
 201       * by application developers
 202       *
 203       * @since JavaFX 8u40
 204       */
 205      public final ObservableMap<Object, Object> getProperties() {
 206         if (properties == null) {
 207             properties = FXCollections.observableMap(new HashMap<Object, Object>());
 208         }
 209         return properties;
 210     }
 211 
 212     /**
 213      * Tests if ToggleGroup has properties.
 214      * @return true if node has properties.
 215      *
 216      * @since JavaFX 8u40
 217      */
 218      public boolean hasProperties() {
 219         return properties != null && !properties.isEmpty();
 220     }
 221 
 222     /**
 223      * Convenience method for setting a single Object property that can be
 224      * retrieved at a later date. This is functionally equivalent to calling
 225      * the getProperties().put(Object key, Object value) method. This can later
 226      * be retrieved by calling {@link ToggleGroup#getUserData()}.
 227      *
 228      * @param value The value to be stored - this can later be retrieved by calling
 229      *          {@link ToggleGroup#getUserData()}.
 230      *
 231      * @since JavaFX 8u40
 232      */
 233     public void setUserData(Object value) {
 234         getProperties().put(USER_DATA_KEY, value);
 235     }
 236 
 237     /**
 238      * Returns a previously set Object property, or null if no such property
 239      * has been set using the {@link ToggleGroup#setUserData(java.lang.Object)} method.
 240      *
 241      * @return The Object that was previously set, or null if no property
 242      *          has been set or if null was set.
 243      *
 244      * @since JavaFX 8u40
 245      */
 246     public Object getUserData() {
 247         return getProperties().get(USER_DATA_KEY);
 248     }
 249 }