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.ObjectProperty;
  29 import javafx.collections.ListChangeListener;
  30 import javafx.collections.ObservableList;
  31 
  32 import com.sun.javafx.collections.TrackableObservableList;
  33 import com.sun.javafx.scene.control.skin.AccordionSkin;
  34 
  35 import javafx.beans.property.ObjectPropertyBase;
  36 import javafx.beans.value.WritableValue;
  37 import javafx.css.StyleableProperty;
  38 
  39 /**
  40  * <p>An accordion is a group of {@link TitledPane TitlePanes}.  Only one TitledPane can be opened at
  41  * a time.</p>
  42  *
  43  * <p>The {@link TitledPane} content in an accordion can be any {@link javafx.scene.Node} such as UI controls or groups
  44  * of nodes added to a layout container.</p>
  45  *
  46  * <p>It is not recommended to set the MinHeight, PrefHeight, or MaxHeight
  47  * for this control.  Unexpected behavior will occur because the
  48  * Accordion's height changes when a TitledPane is opened or closed.</p>
  49  *
  50  * <p>
  51  * Accordion sets focusTraversable to false.
  52  * </p>
  53  *
  54  * <p>Example:
  55  * <pre><code>
  56  * TitledPane t1 = new TitledPane("T1", new Button("B1"));
  57  * TitledPane t2 = new TitledPane("T2", new Button("B2"));
  58  * TitledPane t3 = new TitledPane("T3", new Button("B3"));
  59  * Accordion accordion = new Accordion();
  60  * accordion.getPanes().addAll(t1, t2, t3);</code></pre>
  61  * @since JavaFX 2.0
  62  */
  63 public class Accordion extends Control {
  64 
  65     /***************************************************************************
  66      *                                                                         *
  67      * Constructors                                                            *
  68      *                                                                         *
  69      **************************************************************************/
  70 
  71     /**
  72      * Creates a new Accordion with no TitledPanes.
  73      */
  74     public Accordion() {
  75         this((TitledPane[])null);
  76     }
  77 
  78     /**
  79      * Creates a new Accordion with the given TitledPanes showing within it.
  80      *
  81      * @param titledPanes The TitledPanes to show inside the Accordion.
  82      * @since JavaFX 8u40
  83      */
  84     public Accordion(TitledPane... titledPanes) {
  85         getStyleClass().setAll(DEFAULT_STYLE_CLASS);
  86 
  87         if (titledPanes != null) {
  88             getPanes().addAll(titledPanes);
  89         }
  90 
  91         // focusTraversable is styleable through css. Calling setFocusTraversable
  92         // makes it look to css like the user set the value and css will not 
  93         // override. Initializing focusTraversable by calling applyStyle with null
  94         // StyleOrigin ensures that css will be able to override the value.
  95         ((StyleableProperty<Boolean>)(WritableValue<Boolean>)focusTraversableProperty()).applyStyle(null, Boolean.FALSE);
  96     }
  97 
  98     /***************************************************************************
  99      *                                                                         *
 100      * Instance Variables                                                      *
 101      *                                                                         *
 102      **************************************************************************/
 103 
 104     // The ObservableList of TitlePanes to use in this Accordion.
 105     private final ObservableList<TitledPane> panes = new TrackableObservableList<TitledPane>() {
 106         @Override protected void onChanged(ListChangeListener.Change<TitledPane> c) {
 107             // If one of the removed panes was the expandedPane, then clear
 108             // the expandedPane property. This can only be done if expandedPane
 109             // is not bound (if it is bound, we just have to accept the
 110             // potential error condition and allow the skin to handle it).
 111             while (c.next()) {
 112                 if (c.wasRemoved() && !expandedPane.isBound()) {
 113                     for (TitledPane pane : c.getRemoved()) {
 114                         if (!c.getAddedSubList().contains(pane) && getExpandedPane() == pane) {
 115                             setExpandedPane(null);
 116                             break; // There can be only one, so no point continuing iteration
 117                         }
 118                     }
 119                 }
 120             }
 121         }
 122     };
 123 
 124     /***************************************************************************
 125      *                                                                         *
 126      * Properties                                                              *
 127      *                                                                         *
 128      **************************************************************************/
 129 
 130     // --- Expanded Pane
 131     private ObjectProperty<TitledPane> expandedPane = new ObjectPropertyBase<TitledPane>() {
 132 
 133         private TitledPane oldValue;
 134         
 135         @Override
 136         protected void invalidated() {
 137             final TitledPane value = get();
 138             if (value != null) {
 139                 value.setExpanded(true);
 140             } else {
 141                 if (oldValue != null) {
 142                     oldValue.setExpanded(false);
 143                 }
 144             }
 145             oldValue = value;
 146         }
 147 
 148         @Override
 149         public String getName() {
 150             return "expandedPane";
 151         }
 152 
 153         @Override
 154         public Object getBean() {
 155             return Accordion.this;
 156         }
 157         
 158     };
 159 
 160     /**
 161      * <p>The expanded {@link TitledPane} that is currently visible. While it is technically
 162      * possible to set the expanded pane to a value that is not in {@link #getPanes},
 163      * doing so will be treated by the skin as if expandedPane is null. If a pane
 164      * is set as the expanded pane, and is subsequently removed from {@link #getPanes},
 165      * then expanded pane will be set to null, if possible. (This will not be possible
 166      * if you have manually bound the expanded pane to some value, for example).
 167      * </p>
 168      */
 169     public final void setExpandedPane(TitledPane value) { expandedPaneProperty().set(value); }
 170 
 171     /**
 172      * Gets the expanded TitledPane in the Accordion.  If the expanded pane has been
 173      * removed or there is no expanded TitledPane {@code null} is returned.
 174      *
 175      * @return The expanded TitledPane in the Accordion.
 176      */
 177     public final TitledPane getExpandedPane() { return expandedPane.get(); }
 178 
 179     /**
 180      * The expanded TitledPane in the Accordion.
 181      *
 182      * @return The expanded TitledPane in the Accordion.
 183      */
 184     public final ObjectProperty<TitledPane> expandedPaneProperty() { return expandedPane; }
 185 
 186     /***************************************************************************
 187      *                                                                         *
 188      * Public API                                                              *
 189      *                                                                         *
 190      **************************************************************************/
 191 
 192     /**
 193      * Gets the list of {@link TitledPane} in this Accordion.  Changing this ObservableList
 194      * will immediately result in the Accordion updating to display
 195      * the new contents of this ObservableList.
 196      *
 197      * @return The list of TitledPane in this Accordion.
 198      */
 199     public final ObservableList<TitledPane> getPanes() { return panes; }
 200 
 201     /** {@inheritDoc} */
 202     @Override protected Skin<?> createDefaultSkin() {
 203         return new AccordionSkin(this);
 204     }
 205 
 206     /***************************************************************************
 207      *                                                                         *
 208      * Stylesheet Handling                                                     *
 209      *                                                                         *
 210      **************************************************************************/
 211 
 212     private static final String DEFAULT_STYLE_CLASS = "accordion";
 213 
 214     /**
 215       * Most Controls return true for focusTraversable, so Control overrides
 216       * this method to return true, but Accordion returns false for
 217       * focusTraversable's initial value; hence the override of the override. 
 218       * This method is called from CSS code to get the correct initial value.
 219       * @treatAsPrivate implementation detail
 220       */
 221     @Deprecated @Override
 222     protected /*do not make final*/ Boolean impl_cssGetFocusTraversableInitialValue() {
 223         return Boolean.FALSE;
 224     }
 225 
 226 }