Spec-Zone .ru
спецификации, руководства, описания, API
|
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 java.util.ArrayList; 029import java.util.Collections; 030import java.util.List; 031import java.util.WeakHashMap; 032 033import javafx.beans.DefaultProperty; 034import javafx.beans.property.DoubleProperty; 035import javafx.beans.property.ObjectProperty; 036import javafx.beans.property.SimpleDoubleProperty; 037import javafx.collections.FXCollections; 038import javafx.collections.ListChangeListener; 039import javafx.collections.ObservableList; 040import javafx.geometry.Orientation; 041import javafx.scene.Node; 042 043import com.sun.javafx.collections.annotations.ReturnsUnmodifiableCollection; 044import javafx.css.StyleableObjectProperty; 045import javafx.css.CssMetaData; 046import javafx.css.PseudoClass; 047import com.sun.javafx.css.converters.EnumConverter; 048import com.sun.javafx.scene.control.skin.SplitPaneSkin; 049import javafx.css.Styleable; 050import javafx.css.StyleableProperty; 051 052/** 053 * <p>A control that has two or more sides, each separated by a divider, which can be 054 * dragged by the user to give more space to one of the sides, resulting in 055 * the other side shrinking by an equal amount.</p> 056 * 057 * <p>{@link Node Nodes} can be positioned horizontally next to each other, or stacked 058 * vertically. This can be controlled by setting the {@link #orientationProperty()}.</p> 059 * 060 * <p> The dividers in a SplitPane have the following behavior 061 * <ul> 062 * <li>Dividers cannot overlap another divider</li> 063 * <li>Dividers cannot overlap a node.</li> 064 * <li>Dividers moving to the left/top will stop when the node's min size is reached.</li> 065 * <li>Dividers moving to the right/bottom will stop when the node's max size is reached.</li> 066 * </ul> 067 * 068 * <p>Nodes needs to be placed inside a layout container before they are added 069 * into the SplitPane. If the node is not inside a layout container 070 * the maximum and minimum position of the divider will be the 071 * maximum and minimum size of the content. 072 * </p> 073 * 074 * <p>A divider's position ranges from 0 to 1.0(inclusive). A position of 0 will place the 075 * divider at the left/top most edge of the SplitPane plus the minimum size of the node. A 076 * position of 1.0 will place the divider at the right/bottom most edge of the SplitPane minus the 077 * minimum size of the node. A divider position of 0.5 will place the 078 * the divider in the middle of the SplitPane. Setting the divider position greater 079 * than the node's maximum size position will result in the divider being set at the 080 * node's maximum size position. Setting the divider position less than the node's minimum size position 081 * will result in the divider being set at the node's minimum size position. Therefore the value set in 082 * {@link #setDividerPosition} and {@link #setDividerPositions} may not be the same as the value returned by 083 * {@link #getDividerPositions}. 084 * </p> 085 * 086 * <p>If there are more than two nodes in the SplitPane and the divider positions are set in such a 087 * way that the dividers cannot fit the nodes the dividers will be automatically adjusted by the SplitPane. 088 * <p>For example we have three nodes whose sizes and divider positions are 089 * </p> 090 * <pre> 091 * Node 1: min 25 max 100 092 * Node 2: min 100 max 200 093 * Node 3: min 25 max 50 094 * divider 1: 0.40 095 * divider 2: 0.45 096 * </pre> 097 * 098 * <p>The result will be Node 1 size will be its pref size and divider 1 will be positioned 0.40, 099 * Node 2 size will be its min size and divider 2 position will be the min size of Node 2 plus 100 * divider 1 position, and the remaining space will be given to Node 3. 101 * </p> 102 * 103 * <p> 104 * SplitPane sets focusTraversable to false. 105 * </p> 106 * 107 * <p>Example:</p> 108 * <pre><code> 109 * SplitPane sp = new SplitPane(); 110 * final StackPane sp1 = new StackPane(); 111 * sp1.getChildren().add(new Button("Button One")); 112 * final StackPane sp2 = new StackPane(); 113 * sp2.getChildren().add(new Button("Button Two")); 114 * final StackPane sp3 = new StackPane(); 115 * sp3.getChildren().add(new Button("Button Three")); 116 * sp.getItems().addAll(sp1, sp2, sp3); 117 * sp.setDividerPositions(0.3f, 0.6f, 0.9f); 118 * </code></pre> 119 * 120 */ 121@DefaultProperty("items") 122public class SplitPane extends Control { 123 124 /******************************************************************** 125 * static methods 126 ********************************************************************/ 127 private static final String RESIZABLE_WITH_PARENT = "resizable-with-parent"; 128 129 /** 130 * Sets a node in the SplitPane to be resizable or not when the SplitPane is 131 * resized. By default all node are resizable. Setting value to false will 132 * prevent the node from being resized. 133 * @param node A node in the SplitPane. 134 * @param value true if the node is resizable or false if not resizable. 135 */ 136 public static void setResizableWithParent(Node node, Boolean value) { 137 if (value == null) { 138 node.getProperties().remove(RESIZABLE_WITH_PARENT); 139 } else { 140 node.getProperties().put(RESIZABLE_WITH_PARENT, value); 141 } 142 } 143 144 /** 145 * Return true if the node is resizable when the parent container is resized false otherwise. 146 * @param node A node in the SplitPane. 147 * @defaultValue true 148 * @return true if the node is resizable false otherwise. 149 */ 150 public static Boolean isResizableWithParent(Node node) { 151 if (node.hasProperties()) { 152 Object value = node.getProperties().get(RESIZABLE_WITH_PARENT); 153 if (value != null) { 154 return (Boolean)value; 155 } 156 } 157 return true; 158 } 159 160 /*************************************************************************** 161 * * 162 * Constructors * 163 * * 164 **************************************************************************/ 165 166 /** 167 * Creates a new SplitPane with no content. 168 */ 169 public SplitPane() { 170 getStyleClass().setAll(DEFAULT_STYLE_CLASS); 171 // focusTraversable is styleable through css. Calling setFocusTraversable 172 // makes it look to css like the user set the value and css will not 173 // override. Initializing focusTraversable by calling applyStyle with a 174 // null StyleOrigin ensures that css will be able to override the value. 175 ((StyleableProperty<Boolean>)focusTraversableProperty()).applyStyle(null, Boolean.FALSE); 176 177 items.addListener(new ListChangeListener<Node>() { 178 @Override public void onChanged(Change<? extends Node> c) { 179 while (c.next()) { 180 int from = c.getFrom(); 181 int index = from; 182 for (int i = 0; i < c.getRemovedSize(); i++) { 183 if (index < dividers.size()) { 184 dividerCache.put(index, Double.MAX_VALUE); 185 } else if (index == dividers.size()) { 186 if (!dividers.isEmpty()) { 187 if (c.wasReplaced()) { 188 dividerCache.put(index - 1, dividers.get(index - 1).getPosition()); 189 } else { 190 dividerCache.put(index - 1, Double.MAX_VALUE); 191 } 192 } 193 } 194 index++; 195 } 196 for (int i = 0; i < dividers.size(); i++) { 197 if (dividerCache.get(i) == null) { 198 dividerCache.put(i, dividers.get(i).getPosition()); 199 } 200 } 201 } 202 dividers.clear(); 203 for (int i = 0; i < items.size() - 1; i++) { 204 if (dividerCache.containsKey(i) && dividerCache.get(i) != Double.MAX_VALUE) { 205 Divider d = new Divider(); 206 d.setPosition(dividerCache.get(i)); 207 dividers.add(d); 208 } else { 209 dividers.add(new Divider()); 210 } 211 dividerCache.remove(i); 212 } 213 } 214 }); 215 216 // initialize pseudo-class state 217 pseudoClassStateChanged(HORIZONTAL_PSEUDOCLASS_STATE, true); 218 } 219 220 /*************************************************************************** 221 * * 222 * Properties * 223 * * 224 **************************************************************************/ 225 226 // --- Vertical 227 private ObjectProperty<Orientation> orientation; 228 229 /** 230 * <p>This property controls how the SplitPane should be displayed to the 231 * user: if set to {@code true}, the SplitPane will be 'horizontal', resulting in 232 * the two nodes being placed next to each other, whilst being 233 * set to {@code false} will result in the nodes being stacked vertically.</p> 234 * 235 */ 236 public final void setOrientation(Orientation value) { 237 orientationProperty().set(value); 238 }; 239 240 /** 241 * The orientation for the SplitPane. 242 * @return The orientation for the SplitPane. 243 */ 244 public final Orientation getOrientation() { 245 return orientation == null ? Orientation.HORIZONTAL : orientation.get(); 246 } 247 248 /** 249 * The orientation for the SplitPane. 250 */ 251 public final ObjectProperty<Orientation> orientationProperty() { 252 if (orientation == null) { 253 orientation = new StyleableObjectProperty<Orientation>(Orientation.HORIZONTAL) { 254 @Override public void invalidated() { 255 final boolean isVertical = (get() == Orientation.VERTICAL); 256 pseudoClassStateChanged(VERTICAL_PSEUDOCLASS_STATE, isVertical); 257 pseudoClassStateChanged(HORIZONTAL_PSEUDOCLASS_STATE, !isVertical); 258 } 259 260 @Override public CssMetaData<SplitPane,Orientation> getCssMetaData() { 261 return StyleableProperties.ORIENTATION; 262 } 263 264 @Override 265 public Object getBean() { 266 return SplitPane.this; 267 } 268 269 @Override 270 public String getName() { 271 return "orientation"; 272 } 273 }; 274 } 275 return orientation; 276 } 277 278 279 280 /*************************************************************************** 281 * * 282 * Instance Variables * 283 * * 284 **************************************************************************/ 285 286 private final ObservableList<Node> items = FXCollections.observableArrayList(); 287 288 private final ObservableList<Divider> dividers = FXCollections.observableArrayList(); 289 private final ObservableList<Divider> unmodifiableDividers = FXCollections.unmodifiableObservableList(dividers); 290 291 // Cache the divider positions if the items have not been created. 292 private final WeakHashMap<Integer, Double> dividerCache = new WeakHashMap<Integer, Double>(); 293 294 /*************************************************************************** 295 * * 296 * Public API * 297 * * 298 **************************************************************************/ 299 300 /** 301 * Returns an ObservableList which can be use to modify the contents of the SplitPane. 302 * The order the nodes are placed into this list will be the same order in the SplitPane. 303 * 304 * @return the list of items in this SplitPane. 305 */ 306 public ObservableList<Node> getItems() { 307 return items; 308 } 309 310 /** 311 * Returns an unmodifiable list of all the dividers in this SplitPane. 312 * 313 * @return the list of dividers. 314 */ 315 @ReturnsUnmodifiableCollection public ObservableList<Divider> getDividers() { 316 return unmodifiableDividers; 317 } 318 319 /** 320 * Sets the position of the divider at the specified divider index. 321 * 322 * @param dividerIndex the index of the divider. 323 * @param position the divider position, between 0.0 and 1.0 (inclusive). 324 */ 325 public void setDividerPosition(int dividerIndex, double position) { 326 if (getDividers().size() <= dividerIndex) { 327 dividerCache.put(dividerIndex, position); 328 return; 329 } 330 if (dividerIndex >= 0) { 331 getDividers().get(dividerIndex).setPosition(position); 332 } 333 } 334 335 /** 336 * Sets the position of the divider 337 * 338 * @param positions the divider position, between 0.0 and 1.0 (inclusive). 339 */ 340 public void setDividerPositions(double... positions) { 341 if (dividers.isEmpty()) { 342 for (int i = 0; i < positions.length; i++) { 343 dividerCache.put(i, positions[i]); 344 } 345 return; 346 } 347 for (int i = 0; i < positions.length && i < dividers.size(); i++) { 348 dividers.get(i).setPosition(positions[i]); 349 } 350 } 351 352 /** 353 * Returns an array of double containing the position of each divider. 354 * 355 * @return an array of double containing the position of each divider. 356 */ 357 public double[] getDividerPositions() { 358 double[] positions = new double[dividers.size()]; 359 for (int i = 0; i < dividers.size(); i++) { 360 positions[i] = dividers.get(i).getPosition(); 361 } 362 return positions; 363 } 364 365 /** {@inheritDoc} */ 366 @Override protected Skin<?> createDefaultSkin() { 367 return new SplitPaneSkin(this); 368 } 369 370 /*************************************************************************** 371 * * 372 * Stylesheet Handling * 373 * * 374 **************************************************************************/ 375 376 private static final String DEFAULT_STYLE_CLASS = "split-pane"; 377 378 /** @treatAsPrivate */ 379 private static class StyleableProperties { 380 private static final CssMetaData<SplitPane,Orientation> ORIENTATION = 381 new CssMetaData<SplitPane,Orientation>("-fx-orientation", 382 new EnumConverter<Orientation>(Orientation.class), 383 Orientation.HORIZONTAL) { 384 385 @Override 386 public Orientation getInitialValue(SplitPane node) { 387 // A vertical SplitPane should remain vertical 388 return node.getOrientation(); 389 } 390 391 @Override 392 public boolean isSettable(SplitPane n) { 393 return n.orientation == null || !n.orientation.isBound(); 394 } 395 396 @Override 397 public StyleableProperty<Orientation> getStyleableProperty(SplitPane n) { 398 return (StyleableProperty<Orientation>)n.orientationProperty(); 399 } 400 }; 401 402 private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; 403 static { 404 final List<CssMetaData<? extends Styleable, ?>> styleables = 405 new ArrayList<CssMetaData<? extends Styleable, ?>>(Control.getClassCssMetaData()); 406 styleables.add(ORIENTATION); 407 STYLEABLES = Collections.unmodifiableList(styleables); 408 } 409 } 410 411 /** 412 * @return The CssMetaData associated with this class, which may include the 413 * CssMetaData of its super classes. 414 */ 415 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { 416 return StyleableProperties.STYLEABLES; 417 } 418 419 /** 420 * {@inheritDoc} 421 */ 422 @Override 423 public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() { 424 return getClassCssMetaData(); 425 } 426 427 private static final PseudoClass VERTICAL_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("vertical"); 428 private static final PseudoClass HORIZONTAL_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("horizontal"); 429 430 /** 431 * Most Controls return true for focusTraversable, so Control overrides 432 * this method to return true, but SplitPane returns false for 433 * focusTraversable's initial value; hence the override of the override. 434 * This method is called from CSS code to get the correct initial value. 435 * @treatAsPrivate implementation detail 436 */ 437 @Deprecated @Override 438 protected /*do not make final*/ Boolean impl_cssGetFocusTraversableInitialValue() { 439 return Boolean.FALSE; 440 } 441 442 443 /*************************************************************************** 444 * * 445 * Support Classes * 446 * * 447 **************************************************************************/ 448 449 /** 450 * Represents a single divider in the SplitPane. 451 */ 452 public static class Divider { 453 /** 454 * <p>Represents the location where the divider should ideally be 455 * positioned, between 0.0 and 1.0 (inclusive). 0.0 represents the 456 * left- or top-most point, and 1.0 represents the right- or bottom-most 457 * point (depending on the horizontal property). The SplitPane will attempt 458 * to get the divider to the point requested, but it must take into account 459 * the minimum width/height of the nodes contained within it.</p> 460 * 461 * <p>As the user drags the SplitPane divider around this property will 462 * be updated to always represent its current location.</p> 463 * 464 * @defaultValue 0.5 465 */ 466 private DoubleProperty position; 467 public final void setPosition(double value) { 468 positionProperty().set(value); 469 } 470 471 public final double getPosition() { 472 return position == null ? 0.5F : position.get(); 473 } 474 475 public final DoubleProperty positionProperty() { 476 if (position == null) { 477 position = new SimpleDoubleProperty(this, "position", 0.5F);// { 478// @Override protected void invalidated() { 479// if (get() < 0) { 480// this.value = value; 481// } else if (get() > 1) { 482// this.value = value; 483// } 484// } 485// }; 486 } 487 return position; 488 } 489 } 490}