/* * Copyright (c) 2010, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package javafx.scene.control; import javafx.beans.property.ObjectProperty; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import com.sun.javafx.collections.TrackableObservableList; import javafx.geometry.Orientation; import javafx.scene.Node; import javafx.scene.control.skin.AccordionSkin; import javafx.beans.property.ObjectPropertyBase; import javafx.beans.value.WritableValue; import javafx.css.StyleableProperty; import java.util.List; /** *

An accordion is a group of {@link TitledPane TitlePanes}. Only one TitledPane can be opened at * a time.

* *

The {@link TitledPane} content in an accordion can be any {@link javafx.scene.Node} such as UI controls or groups * of nodes added to a layout container.

* *

It is not recommended to set the MinHeight, PrefHeight, or MaxHeight * for this control. Unexpected behavior will occur because the * Accordion's height changes when a TitledPane is opened or closed.

* *

* Accordion sets focusTraversable to false. *

* *

Example: *


 * TitledPane t1 = new TitledPane("T1", new Button("B1"));
 * TitledPane t2 = new TitledPane("T2", new Button("B2"));
 * TitledPane t3 = new TitledPane("T3", new Button("B3"));
 * Accordion accordion = new Accordion();
 * accordion.getPanes().addAll(t1, t2, t3);
* @since JavaFX 2.0 */ public class Accordion extends Control { private boolean biasDirty = true; private Orientation bias; /*************************************************************************** * * * Constructors * * * **************************************************************************/ /** * Creates a new Accordion with no TitledPanes. */ public Accordion() { this((TitledPane[])null); } /** * Creates a new Accordion with the given TitledPanes showing within it. * * @param titledPanes The TitledPanes to show inside the Accordion. * @since JavaFX 8u40 */ public Accordion(TitledPane... titledPanes) { getStyleClass().setAll(DEFAULT_STYLE_CLASS); if (titledPanes != null) { getPanes().addAll(titledPanes); } // focusTraversable is styleable through css. Calling setFocusTraversable // makes it look to css like the user set the value and css will not // override. Initializing focusTraversable by calling applyStyle with null // StyleOrigin ensures that css will be able to override the value. ((StyleableProperty)(WritableValue)focusTraversableProperty()).applyStyle(null, Boolean.FALSE); } /*************************************************************************** * * * Instance Variables * * * **************************************************************************/ // The ObservableList of TitlePanes to use in this Accordion. private final ObservableList panes = new TrackableObservableList() { @Override protected void onChanged(ListChangeListener.Change c) { // If one of the removed panes was the expandedPane, then clear // the expandedPane property. This can only be done if expandedPane // is not bound (if it is bound, we just have to accept the // potential error condition and allow the skin to handle it). while (c.next()) { if (c.wasRemoved() && !expandedPane.isBound()) { for (TitledPane pane : c.getRemoved()) { if (!c.getAddedSubList().contains(pane) && getExpandedPane() == pane) { setExpandedPane(null); break; // There can be only one, so no point continuing iteration } } } } } }; /*************************************************************************** * * * Properties * * * **************************************************************************/ // --- Expanded Pane private ObjectProperty expandedPane = new ObjectPropertyBase() { private TitledPane oldValue; @Override protected void invalidated() { final TitledPane value = get(); if (value != null) { value.setExpanded(true); } else { if (oldValue != null) { oldValue.setExpanded(false); } } oldValue = value; } @Override public String getName() { return "expandedPane"; } @Override public Object getBean() { return Accordion.this; } }; /** *

The expanded {@link TitledPane} that is currently visible. While it is technically * possible to set the expanded pane to a value that is not in {@link #getPanes}, * doing so will be treated by the skin as if expandedPane is null. If a pane * is set as the expanded pane, and is subsequently removed from {@link #getPanes}, * then expanded pane will be set to null, if possible. (This will not be possible * if you have manually bound the expanded pane to some value, for example). *

* @param value the expanded {@link TitledPane} */ public final void setExpandedPane(TitledPane value) { expandedPaneProperty().set(value); } /** * Gets the expanded TitledPane in the Accordion. If the expanded pane has been * removed or there is no expanded TitledPane {@code null} is returned. * * @return The expanded TitledPane in the Accordion. */ public final TitledPane getExpandedPane() { return expandedPane.get(); } /** * The expanded TitledPane in the Accordion. * * @return The expanded TitledPane in the Accordion. */ public final ObjectProperty expandedPaneProperty() { return expandedPane; } /*************************************************************************** * * * Public API * * * **************************************************************************/ /** * Gets the list of {@link TitledPane} in this Accordion. Changing this ObservableList * will immediately result in the Accordion updating to display * the new contents of this ObservableList. * * @return The list of TitledPane in this Accordion. */ public final ObservableList getPanes() { return panes; } /** {@inheritDoc} */ @Override protected Skin createDefaultSkin() { return new AccordionSkin(this); } /** {@inheritDoc} */ @Override public void requestLayout() { biasDirty = true; bias = null; super.requestLayout(); } /** {@inheritDoc} */ @Override public Orientation getContentBias() { if (biasDirty) { bias = null; final List children = getManagedChildren(); for (Node child : children) { Orientation contentBias = child.getContentBias(); if (contentBias != null) { bias = contentBias; if (contentBias == Orientation.HORIZONTAL) { break; } } } biasDirty = false; } return bias; } /*************************************************************************** * * * Stylesheet Handling * * * **************************************************************************/ private static final String DEFAULT_STYLE_CLASS = "accordion"; /** * Returns the initial focus traversable state of this control, for use * by the JavaFX CSS engine to correctly set its initial value. This method * is overridden as by default UI controls have focus traversable set to true, * but that is not appropriate for this control. * * @since 9 */ @Override protected Boolean getInitialFocusTraversable() { return Boolean.FALSE; } }