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