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}