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.ObjectProperty;
029import javafx.beans.property.SimpleObjectProperty;
030import javafx.collections.ListChangeListener;
031import javafx.collections.ObservableList;
032
033import com.sun.javafx.collections.TrackableObservableList;
034import javafx.css.CssMetaData;
035import com.sun.javafx.scene.control.skin.AccordionSkin;
036import javafx.css.StyleableProperty;
037
038/**
039 * <p>An accordion is a group of {@link TitledPane TitlePanes}.  Only one TitledPane can be opened at
040 * a time.</p>
041 *
042 * <p>The {@link TitledPane} content in an accordion can be any {@link javafx.scene.Node} such as UI controls or groups
043 * of nodes added to a layout container.</p>
044 *
045 * <p>It is not recommended to set the MinHeight, PrefHeight, or MaxHeight
046 * for this control.  Unexpected behavior will occur because the
047 * Accordion's height changes when a TitledPane is opened or closed.</p>
048 *
049 * <p>
050 * Accordion sets focusTraversable to false.
051 * </p>
052 *
053 * <p>Example:
054 * <pre><code>
055 * TitledPane t1 = new TitledPane("T1", new Button("B1"));
056 * TitledPane t2 = new TitledPane("T2", new Button("B2"));
057 * TitledPane t3 = new TitledPane("T3", new Button("B3"));
058 * Accordion accordion = new Accordion();
059 * accordion.getPanes().addAll(t1, t2, t3);</code></pre>
060 */
061public class Accordion extends Control {
062
063    /***************************************************************************
064     *                                                                         *
065     * Constructors                                                            *
066     *                                                                         *
067     **************************************************************************/
068
069    /**
070     * Creates a new Accordion with no TitledPanes.
071     */
072    public Accordion() {
073        getStyleClass().setAll(DEFAULT_STYLE_CLASS);
074        // focusTraversable is styleable through css. Calling setFocusTraversable
075        // makes it look to css like the user set the value and css will not 
076        // override. Initializing focusTraversable by calling applyStyle with null
077        // StyleOrigin ensures that css will be able to override the value.
078        ((StyleableProperty)focusTraversableProperty()).applyStyle(null, Boolean.FALSE);
079    }
080
081    /***************************************************************************
082     *                                                                         *
083     * Instance Variables                                                      *
084     *                                                                         *
085     **************************************************************************/
086
087    // The ObservableList of TitlePanes to use in this Accordion.
088    private final ObservableList<TitledPane> panes = new TrackableObservableList<TitledPane>() {
089        @Override protected void onChanged(ListChangeListener.Change<TitledPane> c) {
090            // If one of the removed panes was the expandedPane, then clear
091            // the expandedPane property. This can only be done if expandedPane
092            // is not bound (if it is bound, we just have to accept the
093            // potential error condition and allow the skin to handle it).
094            while (c.next()) {
095                if (c.wasRemoved() && !expandedPane.isBound()) {
096                    for (TitledPane pane : c.getRemoved()) {
097                        if (!c.getAddedSubList().contains(pane) && getExpandedPane() == pane) {
098                            setExpandedPane(null);
099                            break; // There can be only one, so no point continuing iteration
100                        }
101                    }
102                }
103            }
104        }
105    };
106
107    /***************************************************************************
108     *                                                                         *
109     * Properties                                                              *
110     *                                                                         *
111     **************************************************************************/
112
113    // --- Expanded Pane
114    private ObjectProperty<TitledPane> expandedPane = new SimpleObjectProperty<TitledPane>(this, "expandedPane") {
115        @Override public void set(final TitledPane newSelectedToggle) {
116            if (isBound()) {
117                throw new java.lang.RuntimeException("A bound value cannot be set.");
118            }            
119            if (newSelectedToggle != null) {
120                newSelectedToggle.setExpanded(true);
121            } else {
122                TitledPane old = get();
123                if (old != null) {
124                    old.setExpanded(false);
125                }
126            }
127            super.set(newSelectedToggle);
128        }
129    };
130
131    /**
132     * <p>The expanded {@link TitledPane} that is currently visible. While it is technically
133     * possible to set the expanded pane to a value that is not in {@link #getPanes},
134     * doing so will be treated by the skin as if expandedPane is null. If a pane
135     * is set as the expanded pane, and is subsequently removed from {@link #getPanes},
136     * then expanded pane will be set to null, if possible. (This will not be possible
137     * if you have manually bound the expanded pane to some value, for example).
138     * </p>
139     */
140    public final void setExpandedPane(TitledPane value) { expandedPaneProperty().set(value); }
141
142    /**
143     * Gets the expanded TitledPane in the Accordion.  If the expanded pane has been
144     * removed or there is no expanded TitledPane {@code null} is returned.
145     *
146     * @return The expanded TitledPane in the Accordion.
147     */
148    public final TitledPane getExpandedPane() { return expandedPane.get(); }
149
150    /**
151     * The expanded TitledPane in the Accordion.
152     *
153     * @return The expanded TitledPane in the Accordion.
154     */
155    public final ObjectProperty<TitledPane> expandedPaneProperty() { return expandedPane; }
156
157    /***************************************************************************
158     *                                                                         *
159     * Public API                                                              *
160     *                                                                         *
161     **************************************************************************/
162
163    /**
164     * Gets the list of {@link TitledPane} in this Accordion.  Changing this ObservableList
165     * will immediately result in the Accordion updating to display
166     * the new contents of this ObservableList.
167     *
168     * @return The list of TitledPane in this Accordion.
169     */
170    public final ObservableList<TitledPane> getPanes() { return panes; }
171
172    /** {@inheritDoc} */
173    @Override protected Skin<?> createDefaultSkin() {
174        return new AccordionSkin(this);
175    }
176
177    /***************************************************************************
178     *                                                                         *
179     * Stylesheet Handling                                                     *
180     *                                                                         *
181     **************************************************************************/
182
183    private static final String DEFAULT_STYLE_CLASS = "accordion";
184
185    /**
186      * Most Controls return true for focusTraversable, so Control overrides
187      * this method to return true, but Accordion returns false for
188      * focusTraversable's initial value; hence the override of the override. 
189      * This method is called from CSS code to get the correct initial value.
190      * @treatAsPrivate implementation detail
191      */
192    @Deprecated @Override
193    protected /*do not make final*/ Boolean impl_cssGetFocusTraversableInitialValue() {
194        return Boolean.FALSE;
195    }
196
197}