1 /* 2 * Copyright (c) 2011, 2017, 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.layout; 27 28 import java.util.ArrayList; 29 import java.util.Collections; 30 import java.util.List; 31 import javafx.beans.property.BooleanProperty; 32 import javafx.beans.property.DoubleProperty; 33 import javafx.beans.property.ObjectProperty; 34 import javafx.collections.ListChangeListener.Change; 35 import javafx.collections.ObservableList; 36 import javafx.geometry.HPos; 37 import javafx.geometry.Insets; 38 import javafx.geometry.Orientation; 39 import javafx.geometry.Pos; 40 import javafx.geometry.VPos; 41 import javafx.scene.Group; 42 import javafx.scene.Node; 43 import javafx.scene.paint.Color; 44 import javafx.scene.shape.Line; 45 import com.sun.javafx.collections.TrackableObservableList; 46 import javafx.css.StyleableBooleanProperty; 47 import javafx.css.StyleableDoubleProperty; 48 import javafx.css.StyleableObjectProperty; 49 import javafx.css.CssMetaData; 50 import javafx.css.converter.BooleanConverter; 51 import javafx.css.converter.EnumConverter; 52 import javafx.css.converter.SizeConverter; 53 import java.util.Arrays; 54 import java.util.BitSet; 55 import java.util.Iterator; 56 import java.util.Map.Entry; 57 import java.util.Set; 58 import java.util.SortedMap; 59 import java.util.TreeMap; 60 import java.util.TreeSet; 61 62 import javafx.beans.Observable; 63 import javafx.css.Styleable; 64 import javafx.css.StyleableProperty; 65 import javafx.geometry.BoundingBox; 66 import javafx.geometry.Bounds; 67 import javafx.util.Callback; 68 69 70 71 /** 72 * GridPane lays out its children within a flexible grid of rows and columns. 73 * If a border and/or padding is set, then its content will be layed out within 74 * those insets. 75 * <p> 76 * A child may be placed anywhere within the grid and may span multiple 77 * rows/columns. Children may freely overlap within rows/columns and their 78 * stacking order will be defined by the order of the gridpane's children list 79 * (0th node in back, last node in front). 80 * <p> 81 * GridPane may be styled with backgrounds and borders using CSS. See 82 * {@link javafx.scene.layout.Region Region} superclass for details.</p> 83 * 84 * <h3>Grid Constraints</h3> 85 * <p> 86 * A child's placement within the grid is defined by it's layout constraints: 87 * </p> 88 * 89 * <table border="1"> 90 * <caption>Grid Constraint Table</caption> 91 * <tr><th scope="col">Constraint</th><th scope="col">Type</th><th scope="col">Description</th></tr> 92 * <tr><th scope="row">columnIndex</th><td>integer</td><td>column where child's layout area starts.</td></tr> 93 * <tr><th scope="row">rowIndex</th><td>integer</td><td>row where child's layout area starts.</td></tr> 94 * <tr><th scope="row">columnSpan</th><td>integer</td><td>the number of columns the child's layout area spans horizontally.</td></tr> 95 * <tr><th scope="row">rowSpan</th><td>integer</td><td>the number of rows the child's layout area spans vertically.</td></tr> 96 * </table> 97 * <p> 98 * If the row/column indices are not explicitly set, then the child will be placed 99 * in the first row/column. If row/column spans are not set, they will default to 1. 100 * A child's placement constraints can be changed dynamically and the gridpane 101 * will update accordingly. 102 * <p> 103 * The total number of rows/columns does not need to be specified up front as the 104 * gridpane will automatically expand/contract the grid to accommodate the content. 105 * <p> 106 * To use the GridPane, an application needs to set the layout constraints on 107 * the children and add those children to the gridpane instance. 108 * Constraints are set on the children using static setter methods on the GridPane 109 * class: 110 * <pre><code> GridPane gridpane = new GridPane(); 111 * 112 * // Set one constraint at a time... 113 * // Places the button at the first row and second column 114 * Button button = new Button(); 115 * <b>GridPane.setRowIndex(button, 0); 116 * GridPane.setColumnIndex(button, 1);</b> 117 * 118 * // or convenience methods set more than one constraint at once... 119 * Label label = new Label(); 120 * <b>GridPane.setConstraints(label, 2, 0);</b> // column=2 row=0 121 * 122 * // don't forget to add children to gridpane 123 * <b>gridpane.getChildren().addAll(button, label);</b> 124 * </code></pre> 125 * 126 * Applications may also use convenience methods which combine the steps of 127 * setting the constraints and adding the children: 128 * <pre><code> 129 * GridPane gridpane = new GridPane(); 130 * <b>gridpane.add(new Button(), 1, 0);</b> // column=1 row=0 131 * <b>gridpane.add(new Label(), 2, 0);</b> // column=2 row=0 132 * </code></pre> 133 * 134 * 135 * <h3>Row/Column Sizing</h3> 136 * 137 * By default, rows and columns will be sized to fit their content; 138 * a column will be wide enough to accommodate the widest child, a 139 * row tall enough to fit the tallest child.However, if an application needs 140 * to explicitly control the size of rows or columns, it may do so by adding 141 * RowConstraints and ColumnConstraints objects to specify those metrics. 142 * For example, to create a grid with two fixed-width columns: 143 * <pre><code> 144 * GridPane gridpane = new GridPane(); 145 * <b>gridpane.getColumnConstraints().add(new ColumnConstraints(100));</b> // column 0 is 100 wide 146 * <b>gridpane.getColumnConstraints().add(new ColumnConstraints(200));</b> // column 1 is 200 wide 147 * </code></pre> 148 * By default the gridpane will resize rows/columns to their preferred sizes (either 149 * computed from content or fixed), even if the gridpane is resized larger than 150 * its preferred size. If an application needs a particular row or column to 151 * grow if there is extra space, it may set its grow priority on the RowConstraints 152 * or ColumnConstraints object. For example: 153 * <pre><code> 154 * GridPane gridpane = new GridPane(); 155 * ColumnConstraints column1 = new ColumnConstraints(100,100,Double.MAX_VALUE); 156 * <b>column1.setHgrow(Priority.ALWAYS);</b> 157 * ColumnConstraints column2 = new ColumnConstraints(100); 158 * gridpane.getColumnConstraints().addAll(column1, column2); // first column gets any extra width 159 * </code></pre> 160 * <p> 161 * Note: Nodes spanning multiple rows/columns will be also size to the preferred sizes. 162 * The affected rows/columns are resized by the following priority: grow priorities, last row. 163 * This is with respect to row/column constraints. 164 * 165 * <h3>Percentage Sizing</h3> 166 * 167 * Alternatively, RowConstraints and ColumnConstraints allow the size to be specified 168 * as a percentage of gridpane's available space: 169 * <pre><code> 170 * GridPane gridpane = new GridPane(); 171 * ColumnConstraints column1 = new ColumnConstraints(); 172 * <b>column1.setPercentWidth(50);</b> 173 * ColumnConstraints column2 = new ColumnConstraints(); 174 * <b>column2.setPercentWidth(50);</b> 175 * gridpane.getColumnConstraints().addAll(column1, column2); // each get 50% of width 176 * </code></pre> 177 * If a percentage value is set on a row/column, then that value takes precedent and the 178 * row/column's min, pref, max, and grow constraints will be ignored. 179 * <p> 180 * Note that if the sum of the widthPercent (or heightPercent) values total greater than 100, the values will 181 * be treated as weights. e.g. if 3 columns are each given a widthPercent of 50, 182 * then each will be allocated 1/3 of the gridpane's available width (50/(50+50+50)). 183 * 184 * <h3>Mixing Size Types</h3> 185 * 186 * An application may freely mix the size-types of rows/columns (computed from content, fixed, 187 * or percentage). The percentage rows/columns will always be allocated space first 188 * based on their percentage of the gridpane's available space (size minus insets and gaps). 189 * The remaining space will be allocated to rows/columns given their minimum, preferred, 190 * and maximum sizes and grow priorities. 191 * 192 * <h3>Resizable Range</h3> 193 * <p> 194 * A gridpane's parent will resize the gridpane within the gridpane's resizable range 195 * during layout. By default the gridpane computes this range based on its content 196 * and row/column constraints as outlined in the table below. 197 * </p> 198 * 199 * <table border="1"> 200 * <caption>GridPane Resize Table</caption> 201 * <tr><td></td><th scope="col">width</th><th scope="col">height</th></tr> 202 * <tr><th scope="row">minimum</th> 203 * <td>left/right insets plus the sum of each column's min width.</td> 204 * <td>top/bottom insets plus the sum of each row's min height.</td></tr> 205 * <tr><th scope="row">preferred</th> 206 * <td>left/right insets plus the sum of each column's pref width.</td> 207 * <td>top/bottom insets plus the sum of each row's pref height.</td></tr> 208 * <tr><th scope="row">maximum</th> 209 * <td>Double.MAX_VALUE</td><td>Double.MAX_VALUE</td></tr> 210 * </table> 211 * <p> 212 * A gridpane's unbounded maximum width and height are an indication to the parent that 213 * it may be resized beyond its preferred size to fill whatever space is assigned 214 * to it. 215 * <p> 216 * GridPane provides properties for setting the size range directly. These 217 * properties default to the sentinel value USE_COMPUTED_SIZE, however the 218 * application may set them to other values as needed: 219 * <pre><code> <b>gridpane.setPrefSize(300, 300);</b> 220 * // never size the gridpane larger than its preferred size: 221 * <b>gridpane.setMaxSize(Region.USE_COMPUTED_SIZE, Region.USE_COMPUTED_SIZE);</b> 222 * </code></pre> 223 * Applications may restore the computed values by setting these properties back 224 * to USE_COMPUTED_SIZE. 225 * <p> 226 * GridPane does not clip its content by default, so it is possible that childrens' 227 * bounds may extend outside its own bounds if a child's min size prevents it from 228 * being fit within it space.</p> 229 * 230 * <h3>Optional Layout Constraints</h3> 231 * 232 * <p> 233 * An application may set additional constraints on children to customize how the 234 * child is sized and positioned within the layout area established by it's row/column 235 * indices/spans: 236 * </p> 237 * 238 * <table border="1"> 239 * <caption>GridPane Constraint Table</caption> 240 * <tr><th scope="col">Constraint</th><th scope="col">Type</th><th scope="col">Description</th></tr> 241 * <tr><th scope="row">halignment</th><td>javafx.geometry.HPos</td><td>The horizontal alignment of the child within its layout area.</td></tr> 242 * <tr><th scope="row">valignment</th><td>javafx.geometry.VPos</td><td>The vertical alignment of the child within its layout area.</td></tr> 243 * <tr><th scope="row">hgrow</th><td>javafx.scene.layout.Priority</td><td>The horizontal grow priority of the child.</td></tr> 244 * <tr><th scope="row">vgrow</th><td>javafx.scene.layout.Priority</td><td>The vertical grow priority of the child.</td></tr> 245 * <tr><th scope="row">margin</th><td>javafx.geometry.Insets</td><td>Margin space around the outside of the child.</td></tr> 246 * </table> 247 * <p> 248 * By default the alignment of a child within its layout area is defined by the 249 * alignment set for the row and column. If an individual alignment constraint is 250 * set on a child, that alignment will override the row/column alignment only 251 * for that child. Alignment of other children in the same row or column will 252 * not be affected. 253 * <p> 254 * Grow priorities, on the other hand, can only be applied to entire rows or columns. 255 * Therefore, if a grow priority constraint is set on a single child, it will be 256 * used to compute the default grow priority of the encompassing row/column. If 257 * a grow priority is set directly on a RowConstraint or ColumnConstraint object, 258 * it will override the value computed from content. 259 * 260 * 261 * @since JavaFX 2.0 262 */ 263 public class GridPane extends Pane { 264 265 /** 266 * Sentinel value which may be set on a child's row/column span constraint to 267 * indicate that it should span the remaining rows/columns. 268 */ 269 public static final int REMAINING = Integer.MAX_VALUE; 270 271 /******************************************************************** 272 * BEGIN static methods 273 ********************************************************************/ 274 private static final String MARGIN_CONSTRAINT = "gridpane-margin"; 275 private static final String HALIGNMENT_CONSTRAINT = "gridpane-halignment"; 276 private static final String VALIGNMENT_CONSTRAINT = "gridpane-valignment"; 277 private static final String HGROW_CONSTRAINT = "gridpane-hgrow"; 278 private static final String VGROW_CONSTRAINT = "gridpane-vgrow"; 279 private static final String ROW_INDEX_CONSTRAINT = "gridpane-row"; 280 private static final String COLUMN_INDEX_CONSTRAINT = "gridpane-column"; 281 private static final String ROW_SPAN_CONSTRAINT = "gridpane-row-span"; 282 private static final String COLUMN_SPAN_CONSTRAINT = "gridpane-column-span"; 283 private static final String FILL_WIDTH_CONSTRAINT = "gridpane-fill-width"; 284 private static final String FILL_HEIGHT_CONSTRAINT = "gridpane-fill-height"; 285 286 /** 287 * Sets the row index for the child when contained by a gridpane 288 * so that it will be positioned starting in that row of the gridpane. 289 * If a gridpane child has no row index set, it will be positioned in the 290 * first row. 291 * Setting the value to null will remove the constraint. 292 * @param child the child node of a gridpane 293 * @param value the row index of the child 294 */ 295 public static void setRowIndex(Node child, Integer value) { 296 if (value != null && value < 0) { 297 throw new IllegalArgumentException("rowIndex must be greater or equal to 0, but was "+value); 298 } 299 setConstraint(child, ROW_INDEX_CONSTRAINT, value); 300 } 301 302 /** 303 * Returns the child's row index constraint if set. 304 * @param child the child node of a gridpane 305 * @return the row index for the child or null if no row index was set 306 */ 307 public static Integer getRowIndex(Node child) { 308 return (Integer)getConstraint(child, ROW_INDEX_CONSTRAINT); 309 } 310 311 /** 312 * Sets the column index for the child when contained by a gridpane 313 * so that it will be positioned starting in that column of the gridpane. 314 * If a gridpane child has no column index set, it will be positioned in 315 * the first column. 316 * Setting the value to null will remove the constraint. 317 * @param child the child node of a gridpane 318 * @param value the column index of the child 319 */ 320 public static void setColumnIndex(Node child, Integer value) { 321 if (value != null && value < 0) { 322 throw new IllegalArgumentException("columnIndex must be greater or equal to 0, but was "+value); 323 } 324 setConstraint(child, COLUMN_INDEX_CONSTRAINT, value); 325 } 326 327 /** 328 * Returns the child's column index constraint if set. 329 * @param child the child node of a gridpane 330 * @return the column index for the child or null if no column index was set 331 */ 332 public static Integer getColumnIndex(Node child) { 333 return (Integer)getConstraint(child, COLUMN_INDEX_CONSTRAINT); 334 } 335 336 /** 337 * Sets the row span for the child when contained by a gridpane 338 * so that it will span that number of rows vertically. This may be 339 * set to REMAINING, which will cause the span to extend across all the remaining 340 * rows. 341 * <p> 342 * If a gridpane child has no row span set, it will default to spanning one row. 343 * Setting the value to null will remove the constraint. 344 * @param child the child node of a gridpane 345 * @param value the row span of the child 346 */ 347 public static void setRowSpan(Node child, Integer value) { 348 if (value != null && value < 1) { 349 throw new IllegalArgumentException("rowSpan must be greater or equal to 1, but was "+value); 350 } 351 setConstraint(child, ROW_SPAN_CONSTRAINT, value); 352 } 353 354 /** 355 * Returns the child's row-span constraint if set. 356 * @param child the child node of a gridpane 357 * @return the row span for the child or null if no row span was set 358 */ 359 public static Integer getRowSpan(Node child) { 360 return (Integer)getConstraint(child, ROW_SPAN_CONSTRAINT); 361 } 362 363 /** 364 * Sets the column span for the child when contained by a gridpane 365 * so that it will span that number of columns horizontally. This may be 366 * set to REMAINING, which will cause the span to extend across all the remaining 367 * columns. 368 * <p> 369 * If a gridpane child has no column span set, it will default to spanning one column. 370 * Setting the value to null will remove the constraint. 371 * @param child the child node of a gridpane 372 * @param value the column span of the child 373 */ 374 public static void setColumnSpan(Node child, Integer value) { 375 if (value != null && value < 1) { 376 throw new IllegalArgumentException("columnSpan must be greater or equal to 1, but was "+value); 377 } 378 setConstraint(child, COLUMN_SPAN_CONSTRAINT, value); 379 } 380 381 /** 382 * Returns the child's column-span constraint if set. 383 * @param child the child node of a gridpane 384 * @return the column span for the child or null if no column span was set 385 */ 386 public static Integer getColumnSpan(Node child) { 387 return (Integer)getConstraint(child, COLUMN_SPAN_CONSTRAINT); 388 } 389 390 /** 391 * Sets the margin for the child when contained by a gridpane. 392 * If set, the gridpane will lay it out with the margin space around it. 393 * Setting the value to null will remove the constraint. 394 * @param child the child node of a gridpane 395 * @param value the margin of space around the child 396 */ 397 public static void setMargin(Node child, Insets value) { 398 setConstraint(child, MARGIN_CONSTRAINT, value); 399 } 400 401 /** 402 * Returns the child's margin constraint if set. 403 * @param child the child node of a gridpane 404 * @return the margin for the child or null if no margin was set 405 */ 406 public static Insets getMargin(Node child) { 407 return (Insets)getConstraint(child, MARGIN_CONSTRAINT); 408 } 409 410 private double getBaselineComplementForChild(Node child) { 411 if (isNodePositionedByBaseline(child)) { 412 return rowMinBaselineComplement[getNodeRowIndex(child)]; 413 } 414 return -1; 415 } 416 417 private static final Callback<Node, Insets> marginAccessor = n -> getMargin(n); 418 419 /** 420 * Sets the horizontal alignment for the child when contained by a gridpane. 421 * If set, will override the gridpane's default horizontal alignment. 422 * Setting the value to null will remove the constraint. 423 * @param child the child node of a gridpane 424 * @param value the hozizontal alignment for the child 425 */ 426 public static void setHalignment(Node child, HPos value) { 427 setConstraint(child, HALIGNMENT_CONSTRAINT, value); 428 } 429 430 /** 431 * Returns the child's halignment constraint if set. 432 * @param child the child node of a gridpane 433 * @return the horizontal alignment for the child or null if no alignment was set 434 */ 435 public static HPos getHalignment(Node child) { 436 return (HPos)getConstraint(child, HALIGNMENT_CONSTRAINT); 437 } 438 439 /** 440 * Sets the vertical alignment for the child when contained by a gridpane. 441 * If set, will override the gridpane's default vertical alignment. 442 * Setting the value to null will remove the constraint. 443 * @param child the child node of a gridpane 444 * @param value the vertical alignment for the child 445 */ 446 public static void setValignment(Node child, VPos value) { 447 setConstraint(child, VALIGNMENT_CONSTRAINT, value); 448 } 449 450 /** 451 * Returns the child's valignment constraint if set. 452 * @param child the child node of a gridpane 453 * @return the vertical alignment for the child or null if no alignment was set 454 */ 455 public static VPos getValignment(Node child) { 456 return (VPos)getConstraint(child, VALIGNMENT_CONSTRAINT); 457 } 458 459 /** 460 * Sets the horizontal grow priority for the child when contained by a gridpane. 461 * If set, the gridpane will use the priority to allocate the child additional 462 * horizontal space if the gridpane is resized larger than it's preferred width. 463 * Setting the value to null will remove the constraint. 464 * @param child the child of a gridpane 465 * @param value the horizontal grow priority for the child 466 */ 467 public static void setHgrow(Node child, Priority value) { 468 setConstraint(child, HGROW_CONSTRAINT, value); 469 } 470 471 /** 472 * Returns the child's hgrow constraint if set. 473 * @param child the child node of a gridpane 474 * @return the horizontal grow priority for the child or null if no priority was set 475 */ 476 public static Priority getHgrow(Node child) { 477 return (Priority)getConstraint(child, HGROW_CONSTRAINT); 478 } 479 480 /** 481 * Sets the vertical grow priority for the child when contained by a gridpane. 482 * If set, the gridpane will use the priority to allocate the child additional 483 * vertical space if the gridpane is resized larger than it's preferred height. 484 * Setting the value to null will remove the constraint. 485 * @param child the child of a gridpane 486 * @param value the vertical grow priority for the child 487 */ 488 public static void setVgrow(Node child, Priority value) { 489 setConstraint(child, VGROW_CONSTRAINT, value); 490 } 491 492 /** 493 * Returns the child's vgrow constraint if set. 494 * @param child the child node of a gridpane 495 * @return the vertical grow priority for the child or null if no priority was set 496 */ 497 public static Priority getVgrow(Node child) { 498 return (Priority)getConstraint(child, VGROW_CONSTRAINT); 499 } 500 501 /** 502 * Sets the horizontal fill policy for the child when contained by a gridpane. 503 * If set, the gridpane will use the policy to determine whether node 504 * should be expanded to fill the column or resized to its preferred width. 505 * Setting the value to null will remove the constraint. 506 * If not value is specified for the node nor for the column, the default value is true. 507 * @param child the child node of a gridpane 508 * @param value the horizontal fill policy or null for unset 509 * @since JavaFX 8.0 510 */ 511 public static void setFillWidth(Node child, Boolean value) { 512 setConstraint(child, FILL_WIDTH_CONSTRAINT, value); 513 } 514 515 /** 516 * Returns the child's horizontal fill policy if set 517 * @param child the child node of a gridpane 518 * @return the horizontal fill policy for the child or null if no policy was set 519 * @since JavaFX 8.0 520 */ 521 public static Boolean isFillWidth(Node child) { 522 return (Boolean) getConstraint(child, FILL_WIDTH_CONSTRAINT); 523 } 524 525 /** 526 * Sets the vertical fill policy for the child when contained by a gridpane. 527 * If set, the gridpane will use the policy to determine whether node 528 * should be expanded to fill the row or resized to its preferred height. 529 * Setting the value to null will remove the constraint. 530 * If not value is specified for the node nor for the row, the default value is true. 531 * @param child the child node of a gridpane 532 * @param value the vertical fill policy or null for unset 533 * @since JavaFX 8.0 534 */ 535 public static void setFillHeight(Node child, Boolean value) { 536 setConstraint(child, FILL_HEIGHT_CONSTRAINT, value); 537 } 538 539 /** 540 * Returns the child's vertical fill policy if set 541 * @param child the child node of a gridpane 542 * @return the vertical fill policy for the child or null if no policy was set 543 * @since JavaFX 8.0 544 */ 545 public static Boolean isFillHeight(Node child) { 546 return (Boolean) getConstraint(child, FILL_HEIGHT_CONSTRAINT); 547 } 548 549 /** 550 * Sets the column,row indeces for the child when contained in a gridpane. 551 * @param child the child node of a gridpane 552 * @param columnIndex the column index position for the child 553 * @param rowIndex the row index position for the child 554 */ 555 public static void setConstraints(Node child, int columnIndex, int rowIndex) { 556 setRowIndex(child, rowIndex); 557 setColumnIndex(child, columnIndex); 558 } 559 560 /** 561 * Sets the column, row, column-span, and row-span value for the child when 562 * contained in a gridpane. 563 * @param child the child node of a gridpane 564 * @param columnIndex the column index position for the child 565 * @param rowIndex the row index position for the child 566 * @param columnspan the number of columns the child should span 567 * @param rowspan the number of rows the child should span 568 */ 569 public static void setConstraints(Node child, int columnIndex, int rowIndex, int columnspan, int rowspan) { 570 setRowIndex(child, rowIndex); 571 setColumnIndex(child, columnIndex); 572 setRowSpan(child, rowspan); 573 setColumnSpan(child, columnspan); 574 } 575 576 /** 577 * Sets the grid position, spans, and alignment for the child when contained in a gridpane. 578 * @param child the child node of a gridpane 579 * @param columnIndex the column index position for the child 580 * @param rowIndex the row index position for the child 581 * @param columnspan the number of columns the child should span 582 * @param rowspan the number of rows the child should span 583 * @param halignment the horizontal alignment of the child 584 * @param valignment the vertical alignment of the child 585 */ 586 public static void setConstraints(Node child, int columnIndex, int rowIndex, int columnspan, int rowspan, 587 HPos halignment, VPos valignment) { 588 setRowIndex(child, rowIndex); 589 setColumnIndex(child, columnIndex); 590 setRowSpan(child, rowspan); 591 setColumnSpan(child, columnspan); 592 setHalignment(child, halignment); 593 setValignment(child, valignment); 594 } 595 596 /** 597 * Sets the grid position, spans, and alignment for the child when contained in a gridpane. 598 * @param child the child node of a gridpane 599 * @param columnIndex the column index position for the child 600 * @param rowIndex the row index position for the child 601 * @param columnspan the number of columns the child should span 602 * @param rowspan the number of rows the child should span 603 * @param halignment the horizontal alignment of the child 604 * @param valignment the vertical alignment of the child 605 * @param hgrow the horizontal grow priority of the child 606 * @param vgrow the vertical grow priority of the child 607 */ 608 public static void setConstraints(Node child, int columnIndex, int rowIndex, int columnspan, int rowspan, 609 HPos halignment, VPos valignment, Priority hgrow, Priority vgrow) { 610 setRowIndex(child, rowIndex); 611 setColumnIndex(child, columnIndex); 612 setRowSpan(child, rowspan); 613 setColumnSpan(child, columnspan); 614 setHalignment(child, halignment); 615 setValignment(child, valignment); 616 setHgrow(child, hgrow); 617 setVgrow(child, vgrow); 618 } 619 620 /** 621 * Sets the grid position, spans, alignment, grow priorities, and margin for 622 * the child when contained in a gridpane. 623 * @param child the child node of a gridpane 624 * @param columnIndex the column index position for the child 625 * @param rowIndex the row index position for the child 626 * @param columnspan the number of columns the child should span 627 * @param rowspan the number of rows the child should span 628 * @param halignment the horizontal alignment of the child 629 * @param valignment the vertical alignment of the child 630 * @param hgrow the horizontal grow priority of the child 631 * @param vgrow the vertical grow priority of the child 632 * @param margin the margin of space around the child 633 */ 634 public static void setConstraints(Node child, int columnIndex, int rowIndex, int columnspan, int rowspan, 635 HPos halignment, VPos valignment, Priority hgrow, Priority vgrow, Insets margin) { 636 setRowIndex(child, rowIndex); 637 setColumnIndex(child, columnIndex); 638 setRowSpan(child, rowspan); 639 setColumnSpan(child, columnspan); 640 setHalignment(child, halignment); 641 setValignment(child, valignment); 642 setHgrow(child, hgrow); 643 setVgrow(child, vgrow); 644 setMargin(child, margin); 645 } 646 647 /** 648 * Removes all gridpane constraints from the child node. 649 * @param child the child node 650 */ 651 public static void clearConstraints(Node child) { 652 setRowIndex(child, null); 653 setColumnIndex(child, null); 654 setRowSpan(child, null); 655 setColumnSpan(child, null); 656 setHalignment(child, null); 657 setValignment(child, null); 658 setHgrow(child, null); 659 setVgrow(child, null); 660 setMargin(child, null); 661 } 662 663 664 private static final Color GRID_LINE_COLOR = Color.rgb(30, 30, 30); 665 private static final double GRID_LINE_DASH = 3; 666 667 static void createRow(int rowIndex, int columnIndex, Node... nodes) { 668 for (int i = 0; i < nodes.length; i++) { 669 setConstraints(nodes[i], columnIndex + i, rowIndex); 670 } 671 } 672 673 static void createColumn(int columnIndex, int rowIndex, Node... nodes) { 674 for (int i = 0; i < nodes.length; i++) { 675 setConstraints(nodes[i], columnIndex, rowIndex + i); 676 } 677 } 678 679 static int getNodeRowIndex(Node node) { 680 Integer rowIndex = getRowIndex(node); 681 return rowIndex != null? rowIndex : 0; 682 } 683 684 private static int getNodeRowSpan(Node node) { 685 Integer rowspan = getRowSpan(node); 686 return rowspan != null? rowspan : 1; 687 } 688 689 static int getNodeRowEnd(Node node) { 690 int rowSpan = getNodeRowSpan(node); 691 return rowSpan != REMAINING? getNodeRowIndex(node) + rowSpan - 1 : REMAINING; 692 } 693 694 static int getNodeColumnIndex(Node node) { 695 Integer columnIndex = getColumnIndex(node); 696 return columnIndex != null? columnIndex : 0; 697 } 698 699 private static int getNodeColumnSpan(Node node) { 700 Integer colspan = getColumnSpan(node); 701 return colspan != null? colspan : 1; 702 } 703 704 static int getNodeColumnEnd(Node node) { 705 int columnSpan = getNodeColumnSpan(node); 706 return columnSpan != REMAINING? getNodeColumnIndex(node) + columnSpan - 1 : REMAINING; 707 } 708 709 private static Priority getNodeHgrow(Node node) { 710 Priority hgrow = getHgrow(node); 711 return hgrow != null? hgrow : Priority.NEVER; 712 } 713 714 private static Priority getNodeVgrow(Node node) { 715 Priority vgrow = getVgrow(node); 716 return vgrow != null? vgrow : Priority.NEVER; 717 } 718 719 private static Priority[] createPriorityArray(int length, Priority value) { 720 Priority[] array = new Priority[length]; 721 Arrays.fill(array, value); 722 return array; 723 } 724 725 /******************************************************************** 726 * END static methods 727 ********************************************************************/ 728 729 /** 730 * Creates a GridPane layout with hgap/vgap = 0 and TOP_LEFT alignment. 731 */ 732 public GridPane() { 733 super(); 734 getChildren().addListener((Observable o) -> requestLayout()); 735 } 736 737 /** 738 * The width of the horizontal gaps between columns. 739 * @return the width of the horizontal gaps between columns 740 */ 741 public final DoubleProperty hgapProperty() { 742 if (hgap == null) { 743 hgap = new StyleableDoubleProperty(0) { 744 @Override 745 public void invalidated() { 746 requestLayout(); 747 } 748 749 @Override 750 public CssMetaData<GridPane, Number> getCssMetaData() { 751 return StyleableProperties.HGAP; 752 } 753 754 @Override 755 public Object getBean() { 756 return GridPane.this; 757 } 758 759 @Override 760 public String getName() { 761 return "hgap"; 762 } 763 }; 764 } 765 return hgap; 766 } 767 768 private DoubleProperty hgap; 769 public final void setHgap(double value) { hgapProperty().set(value); } 770 public final double getHgap() { return hgap == null ? 0 : hgap.get(); } 771 772 /** 773 * The height of the vertical gaps between rows. 774 * @return the height of the vertical gaps between rows 775 */ 776 public final DoubleProperty vgapProperty() { 777 if (vgap == null) { 778 vgap = new StyleableDoubleProperty(0) { 779 @Override 780 public void invalidated() { 781 requestLayout(); 782 } 783 784 @Override 785 public CssMetaData<GridPane, Number> getCssMetaData() { 786 return StyleableProperties.VGAP; 787 } 788 789 @Override 790 public Object getBean() { 791 return GridPane.this; 792 } 793 794 @Override 795 public String getName() { 796 return "vgap"; 797 } 798 }; 799 } 800 return vgap; 801 } 802 803 private DoubleProperty vgap; 804 public final void setVgap(double value) { vgapProperty().set(value); } 805 public final double getVgap() { return vgap == null ? 0 : vgap.get(); } 806 807 /** 808 * The alignment of the grid within the gridpane's width and height. 809 * @return the alignment of the grid within the gridpane's width and height 810 */ 811 public final ObjectProperty<Pos> alignmentProperty() { 812 if (alignment == null) { 813 alignment = new StyleableObjectProperty<Pos>(Pos.TOP_LEFT) { 814 @Override 815 public void invalidated() { 816 requestLayout(); 817 } 818 819 @Override 820 public CssMetaData<GridPane, Pos> getCssMetaData() { 821 return StyleableProperties.ALIGNMENT; 822 } 823 824 @Override 825 public Object getBean() { 826 return GridPane.this; 827 } 828 829 @Override 830 public String getName() { 831 return "alignment"; 832 } 833 }; 834 } 835 return alignment; 836 } 837 838 private ObjectProperty<Pos> alignment; 839 public final void setAlignment(Pos value) { 840 alignmentProperty().set(value); 841 } 842 public final Pos getAlignment() { 843 return alignment == null ? Pos.TOP_LEFT : alignment.get(); 844 } 845 private Pos getAlignmentInternal() { 846 Pos localPos = getAlignment(); 847 return localPos == null ? Pos.TOP_LEFT : localPos; 848 } 849 850 /** 851 * For debug purposes only: controls whether lines are displayed to show the gridpane's rows and columns. 852 * Default is <code>false</code>. 853 * @return true if lines are displayed to show the gridpane's rows and columns 854 */ 855 public final BooleanProperty gridLinesVisibleProperty() { 856 if (gridLinesVisible == null) { 857 gridLinesVisible = new StyleableBooleanProperty() { 858 @Override 859 protected void invalidated() { 860 if (get()) { 861 gridLines = new Group(); 862 gridLines.setManaged(false); 863 getChildren().add(gridLines); 864 } else { 865 getChildren().remove(gridLines); 866 gridLines = null; 867 } 868 requestLayout(); 869 } 870 871 @Override 872 public CssMetaData<GridPane, Boolean> getCssMetaData() { 873 return StyleableProperties.GRID_LINES_VISIBLE; 874 } 875 876 @Override 877 public Object getBean() { 878 return GridPane.this; 879 } 880 881 @Override 882 public String getName() { 883 return "gridLinesVisible"; 884 } 885 }; 886 } 887 return gridLinesVisible; 888 } 889 890 private BooleanProperty gridLinesVisible; 891 public final void setGridLinesVisible(boolean value) { gridLinesVisibleProperty().set(value); } 892 public final boolean isGridLinesVisible() { return gridLinesVisible == null ? false : gridLinesVisible.get(); } 893 894 /** 895 * RowConstraints instances can be added to explicitly control individual row 896 * sizing and layout behavior. 897 * If not set, row sizing and layout behavior will be computed based on content. 898 * 899 */ 900 private final ObservableList<RowConstraints> rowConstraints = new TrackableObservableList<RowConstraints>() { 901 @Override 902 protected void onChanged(Change<RowConstraints> c) { 903 while (c.next()) { 904 for (RowConstraints constraints : c.getRemoved()) { 905 if (constraints != null && !rowConstraints.contains(constraints)) { 906 constraints.remove(GridPane.this); 907 } 908 } 909 for (RowConstraints constraints : c.getAddedSubList()) { 910 if (constraints != null) { 911 constraints.add(GridPane.this); 912 } 913 } 914 } 915 requestLayout(); 916 } 917 }; 918 919 /** 920 * Returns list of row constraints. Row constraints can be added to 921 * explicitly control individual row sizing and layout behavior. 922 * If not set, row sizing and layout behavior is computed based on content. 923 * 924 * Index in the ObservableList denotes the row number, so the row constraint for the first row 925 * is at the position of 0. 926 * @return the list of row constraints 927 */ 928 public final ObservableList<RowConstraints> getRowConstraints() { return rowConstraints; } 929 /** 930 * ColumnConstraints instances can be added to explicitly control individual column 931 * sizing and layout behavior. 932 * If not set, column sizing and layout behavior will be computed based on content. 933 */ 934 private final ObservableList<ColumnConstraints> columnConstraints = new TrackableObservableList<ColumnConstraints>() { 935 @Override 936 protected void onChanged(Change<ColumnConstraints> c) { 937 while(c.next()) { 938 for (ColumnConstraints constraints : c.getRemoved()) { 939 if (constraints != null && !columnConstraints.contains(constraints)) { 940 constraints.remove(GridPane.this); 941 } 942 } 943 for (ColumnConstraints constraints : c.getAddedSubList()) { 944 if (constraints != null) { 945 constraints.add(GridPane.this); 946 } 947 } 948 } 949 requestLayout(); 950 } 951 }; 952 953 /** 954 * Returns list of column constraints. Column constraints can be added to 955 * explicitly control individual column sizing and layout behavior. 956 * If not set, column sizing and layout behavior is computed based on content. 957 * 958 * Index in the ObservableList denotes the column number, so the column constraint for the first column 959 * is at the position of 0. 960 * @return the list of column constraints 961 */ 962 public final ObservableList<ColumnConstraints> getColumnConstraints() { return columnConstraints; } 963 964 /** 965 * Adds a child to the gridpane at the specified column,row position. 966 * This convenience method will set the gridpane column and row constraints 967 * on the child. 968 * @param child the node being added to the gridpane 969 * @param columnIndex the column index position for the child within the gridpane, counting from 0 970 * @param rowIndex the row index position for the child within the gridpane, counting from 0 971 */ 972 public void add(Node child, int columnIndex, int rowIndex) { 973 setConstraints(child, columnIndex, rowIndex); 974 getChildren().add(child); 975 } 976 977 /** 978 * Adds a child to the gridpane at the specified column,row position and spans. 979 * This convenience method will set the gridpane column, row, and span constraints 980 * on the child. 981 * @param child the node being added to the gridpane 982 * @param columnIndex the column index position for the child within the gridpane, counting from 0 983 * @param rowIndex the row index position for the child within the gridpane, counting from 0 984 * @param colspan the number of columns the child's layout area should span 985 * @param rowspan the number of rows the child's layout area should span 986 */ 987 public void add(Node child, int columnIndex, int rowIndex, int colspan, int rowspan) { 988 setConstraints(child, columnIndex, rowIndex, colspan, rowspan); 989 getChildren().add(child); 990 } 991 992 /** 993 * Convenience method for placing the specified nodes sequentially in a given 994 * row of the gridpane. If the row already contains nodes the specified nodes 995 * will be appended to the row. For example, the first node will be positioned at [column,row], 996 * the second at [column+1,row], etc. This method will set the appropriate gridpane 997 * row/column constraints on the nodes as well as add the nodes to the gridpane's 998 * children sequence. 999 * 1000 * @param rowIndex the row index position for the children within the gridpane 1001 * @param children the nodes to be added as a row in the gridpane 1002 */ 1003 public void addRow(int rowIndex, Node... children) { 1004 int columnIndex = 0; 1005 final List<Node> managed = getManagedChildren(); 1006 for (int i = 0, size = managed.size(); i < size; i++) { 1007 Node child = managed.get(i); 1008 final int nodeRowIndex = getNodeRowIndex(child); 1009 final int nodeRowEnd = getNodeRowEnd(child); 1010 if (rowIndex >= nodeRowIndex && 1011 (rowIndex <= nodeRowEnd || nodeRowEnd == REMAINING)) { 1012 int index = getNodeColumnIndex(child); 1013 int end = getNodeColumnEnd(child); 1014 columnIndex = Math.max(columnIndex, (end != REMAINING? end : index) + 1); 1015 } 1016 } 1017 createRow(rowIndex, columnIndex, children); 1018 getChildren().addAll(children); 1019 } 1020 1021 /** 1022 * Convenience method for placing the specified nodes sequentially in a given 1023 * column of the gridpane. If the column already contains nodes the specified nodes 1024 * will be appended to the column. For example, the first node will be positioned at [column, row], 1025 * the second at [column, row+1], etc. This method will set the appropriate gridpane 1026 * row/column constraints on the nodes as well as add the nodes to the gridpane's 1027 * children sequence. 1028 * 1029 * @param columnIndex the column index position for the children within the gridpane 1030 * @param children the nodes to be added as a column in the gridpane 1031 */ 1032 public void addColumn(int columnIndex, Node... children) { 1033 int rowIndex = 0; 1034 final List<Node> managed = getManagedChildren(); 1035 for (int i = 0, size = managed.size(); i < size; i++) { 1036 Node child = managed.get(i); 1037 final int nodeColumnIndex = getNodeColumnIndex(child); 1038 final int nodeColumnEnd = getNodeColumnEnd(child); 1039 if (columnIndex >= nodeColumnIndex 1040 && (columnIndex <= nodeColumnEnd || nodeColumnEnd == REMAINING)) { 1041 int index = getNodeRowIndex(child); 1042 int end = getNodeRowEnd(child); 1043 rowIndex = Math.max(rowIndex, (end != REMAINING? end : index) + 1); 1044 } 1045 } 1046 createColumn(columnIndex, rowIndex, children); 1047 getChildren().addAll(children); 1048 } 1049 1050 private Group gridLines; 1051 private Orientation bias; 1052 1053 private double[] rowPercentHeight; 1054 private double rowPercentTotal = 0; 1055 1056 private CompositeSize rowMinHeight; 1057 private CompositeSize rowPrefHeight; 1058 private CompositeSize rowMaxHeight; 1059 private List<Node>[] rowBaseline; 1060 private double[] rowMinBaselineComplement; 1061 private double[] rowPrefBaselineComplement; 1062 private double[] rowMaxBaselineComplement; 1063 private Priority[] rowGrow; 1064 1065 private double[] columnPercentWidth; 1066 private double columnPercentTotal = 0; 1067 1068 private CompositeSize columnMinWidth; 1069 private CompositeSize columnPrefWidth; 1070 private CompositeSize columnMaxWidth; 1071 private Priority[] columnGrow; 1072 1073 private boolean metricsDirty = true; 1074 1075 // This is set to true while in layoutChildren and set false on the conclusion. 1076 // It is used to decide whether to update metricsDirty in requestLayout(). 1077 private boolean performingLayout = false; 1078 1079 private int numRows; 1080 private int numColumns; 1081 1082 private int getNumberOfRows() { 1083 computeGridMetrics(); 1084 return numRows; 1085 } 1086 1087 private int getNumberOfColumns() { 1088 computeGridMetrics(); 1089 return numColumns; 1090 } 1091 1092 private boolean isNodePositionedByBaseline(Node n){ 1093 return (getRowValignment(getNodeRowIndex(n)) == VPos.BASELINE && getValignment(n) == null) 1094 || getValignment(n) == VPos.BASELINE; 1095 } 1096 1097 private void computeGridMetrics() { 1098 if (metricsDirty) { 1099 numRows = rowConstraints.size(); 1100 numColumns = columnConstraints.size(); 1101 final List<Node> managed = getManagedChildren(); 1102 for (int i = 0, size = managed.size(); i < size; i++) { 1103 Node child = managed.get(i); 1104 int rowIndex = getNodeRowIndex(child); 1105 int columnIndex = getNodeColumnIndex(child); 1106 int rowEnd = getNodeRowEnd(child); 1107 int columnEnd = getNodeColumnEnd(child); 1108 numRows = Math.max(numRows, (rowEnd != REMAINING ? rowEnd : rowIndex) + 1); 1109 numColumns = Math.max(numColumns, (columnEnd != REMAINING ? columnEnd : columnIndex) + 1); 1110 } 1111 rowPercentHeight = createDoubleArray(numRows, -1); 1112 rowPercentTotal = 0; 1113 columnPercentWidth = createDoubleArray(numColumns, -1); 1114 columnPercentTotal = 0; 1115 columnGrow = createPriorityArray(numColumns, Priority.NEVER); 1116 rowGrow = createPriorityArray(numRows, Priority.NEVER); 1117 rowMinBaselineComplement = createDoubleArray(numRows, -1); 1118 rowPrefBaselineComplement = createDoubleArray(numRows, -1); 1119 rowMaxBaselineComplement = createDoubleArray(numRows, -1); 1120 rowBaseline = new List[numRows]; 1121 for (int i = 0, sz = numRows; i < sz; ++i) { 1122 if (i < rowConstraints.size()) { 1123 final RowConstraints rc = rowConstraints.get(i); 1124 double percentHeight = rc.getPercentHeight(); 1125 Priority vGrow = rc.getVgrow(); 1126 if (percentHeight >= 0) { 1127 rowPercentHeight[i] = percentHeight; 1128 } 1129 if (vGrow != null) { 1130 rowGrow[i] = vGrow; 1131 } 1132 } 1133 1134 List<Node> baselineNodes = new ArrayList<>(numColumns); 1135 for (int j = 0, size = managed.size(); j < size; j++) { 1136 Node n = managed.get(j); 1137 if (getNodeRowIndex(n) == i && isNodePositionedByBaseline(n)) { 1138 baselineNodes.add(n); 1139 } 1140 } 1141 rowMinBaselineComplement[i] = getMinBaselineComplement(baselineNodes); 1142 rowPrefBaselineComplement[i] = getPrefBaselineComplement(baselineNodes); 1143 rowMaxBaselineComplement[i] = getMaxBaselineComplement(baselineNodes); 1144 rowBaseline[i] = baselineNodes; 1145 1146 } 1147 for (int i = 0, sz = Math.min(numColumns, columnConstraints.size()); i < sz; ++i) { 1148 final ColumnConstraints cc = columnConstraints.get(i); 1149 double percentWidth = cc.getPercentWidth(); 1150 Priority hGrow = cc.getHgrow(); 1151 if (percentWidth >= 0) 1152 columnPercentWidth[i] = percentWidth; 1153 if (hGrow != null) 1154 columnGrow[i] = hGrow; 1155 } 1156 1157 for (int i = 0, size = managed.size(); i < size; i++) { 1158 Node child = managed.get(i); 1159 if (getNodeColumnSpan(child) == 1) { 1160 Priority hg = getNodeHgrow(child); 1161 int idx = getNodeColumnIndex(child); 1162 columnGrow[idx] = Priority.max(columnGrow[idx], hg); 1163 } 1164 if (getNodeRowSpan(child) == 1) { 1165 Priority vg = getNodeVgrow(child); 1166 int idx = getNodeRowIndex(child); 1167 rowGrow[idx] = Priority.max(rowGrow[idx], vg); 1168 } 1169 } 1170 1171 for (int i = 0; i < rowPercentHeight.length; i++) { 1172 if (rowPercentHeight[i] > 0) { 1173 rowPercentTotal += rowPercentHeight[i]; 1174 } 1175 } 1176 if (rowPercentTotal > 100) { 1177 double weight = 100 / rowPercentTotal; 1178 for (int i = 0; i < rowPercentHeight.length; i++) { 1179 if (rowPercentHeight[i] > 0) { 1180 rowPercentHeight[i] *= weight; 1181 } 1182 } 1183 rowPercentTotal = 100; 1184 } 1185 for (int i = 0; i < columnPercentWidth.length; i++) { 1186 if (columnPercentWidth[i] > 0) { 1187 columnPercentTotal += columnPercentWidth[i]; 1188 } 1189 } 1190 if (columnPercentTotal > 100) { 1191 double weight = 100 / columnPercentTotal; 1192 for (int i = 0; i < columnPercentWidth.length; i++) { 1193 if (columnPercentWidth[i] > 0) { 1194 columnPercentWidth[i] *= weight; 1195 } 1196 } 1197 columnPercentTotal = 100; 1198 } 1199 1200 bias = null; 1201 for (int i = 0; i < managed.size(); ++i) { 1202 final Orientation b = managed.get(i).getContentBias(); 1203 if (b != null) { 1204 bias = b; 1205 if (b == Orientation.HORIZONTAL) { 1206 break; 1207 } 1208 } 1209 } 1210 1211 metricsDirty = false; 1212 } 1213 } 1214 1215 @Override protected double computeMinWidth(double height) { 1216 computeGridMetrics(); 1217 performingLayout = true; 1218 try { 1219 final double[] heights = height == -1 ? null : computeHeightsToFit(height).asArray(); 1220 1221 return snapSpaceX(getInsets().getLeft()) + 1222 computeMinWidths(heights).computeTotalWithMultiSize() + 1223 snapSpaceX(getInsets().getRight()); 1224 } finally { 1225 performingLayout = false; 1226 } 1227 1228 } 1229 1230 @Override protected double computeMinHeight(double width) { 1231 computeGridMetrics(); 1232 performingLayout = true; 1233 try { 1234 final double[] widths = width == -1 ? null : computeWidthsToFit(width).asArray(); 1235 1236 return snapSpaceY(getInsets().getTop()) + 1237 computeMinHeights(widths).computeTotalWithMultiSize() + 1238 snapSpaceY(getInsets().getBottom()); 1239 } finally { 1240 performingLayout = false; 1241 } 1242 } 1243 1244 @Override protected double computePrefWidth(double height) { 1245 computeGridMetrics(); 1246 performingLayout = true; 1247 try { 1248 final double[] heights = height == -1 ? null : computeHeightsToFit(height).asArray(); 1249 1250 return snapSpaceX(getInsets().getLeft()) + 1251 computePrefWidths(heights).computeTotalWithMultiSize() + 1252 snapSpaceX(getInsets().getRight()); 1253 } finally { 1254 performingLayout = false; 1255 } 1256 } 1257 1258 @Override protected double computePrefHeight(double width) { 1259 computeGridMetrics(); 1260 performingLayout = true; 1261 try { 1262 final double[] widths = width == -1 ? null : computeWidthsToFit(width).asArray(); 1263 1264 return snapSpaceY(getInsets().getTop()) + 1265 computePrefHeights(widths).computeTotalWithMultiSize() + 1266 snapSpaceY(getInsets().getBottom()); 1267 } finally { 1268 performingLayout = false; 1269 } 1270 } 1271 1272 private VPos getRowValignment(int rowIndex) { 1273 if (rowIndex < getRowConstraints().size()) { 1274 RowConstraints constraints = getRowConstraints().get(rowIndex); 1275 if (constraints.getValignment() != null) { 1276 return constraints.getValignment(); 1277 } 1278 } 1279 return VPos.CENTER; 1280 } 1281 1282 private HPos getColumnHalignment(int columnIndex) { 1283 if (columnIndex < getColumnConstraints().size()) { 1284 ColumnConstraints constraints = getColumnConstraints().get(columnIndex); 1285 if (constraints.getHalignment() != null) { 1286 return constraints.getHalignment(); 1287 } 1288 } 1289 return HPos.LEFT; 1290 } 1291 1292 private double getColumnMinWidth(int columnIndex) { 1293 if (columnIndex < getColumnConstraints().size()) { 1294 ColumnConstraints constraints = getColumnConstraints().get(columnIndex); 1295 return constraints.getMinWidth(); 1296 1297 } 1298 return USE_COMPUTED_SIZE; 1299 } 1300 1301 private double getRowMinHeight(int rowIndex) { 1302 if (rowIndex < getRowConstraints().size()) { 1303 RowConstraints constraints = getRowConstraints().get(rowIndex); 1304 return constraints.getMinHeight(); 1305 } 1306 return USE_COMPUTED_SIZE; 1307 } 1308 1309 private double getColumnMaxWidth(int columnIndex) { 1310 if (columnIndex < getColumnConstraints().size()) { 1311 ColumnConstraints constraints = getColumnConstraints().get(columnIndex); 1312 return constraints.getMaxWidth(); 1313 1314 } 1315 return USE_COMPUTED_SIZE; 1316 } 1317 1318 private double getColumnPrefWidth(int columnIndex) { 1319 if (columnIndex < getColumnConstraints().size()) { 1320 ColumnConstraints constraints = getColumnConstraints().get(columnIndex); 1321 return constraints.getPrefWidth(); 1322 1323 } 1324 return USE_COMPUTED_SIZE; 1325 } 1326 1327 private double getRowPrefHeight(int rowIndex) { 1328 if (rowIndex < getRowConstraints().size()) { 1329 RowConstraints constraints = getRowConstraints().get(rowIndex); 1330 return constraints.getPrefHeight(); 1331 1332 } 1333 return USE_COMPUTED_SIZE; 1334 } 1335 1336 private double getRowMaxHeight(int rowIndex) { 1337 if (rowIndex < getRowConstraints().size()) { 1338 RowConstraints constraints = getRowConstraints().get(rowIndex); 1339 return constraints.getMaxHeight(); 1340 } 1341 return USE_COMPUTED_SIZE; 1342 } 1343 1344 private boolean shouldRowFillHeight(int rowIndex) { 1345 if (rowIndex < getRowConstraints().size()) { 1346 return getRowConstraints().get(rowIndex).isFillHeight(); 1347 } 1348 return true; 1349 } 1350 1351 private boolean shouldColumnFillWidth(int columnIndex) { 1352 if (columnIndex < getColumnConstraints().size()) { 1353 return getColumnConstraints().get(columnIndex).isFillWidth(); 1354 } 1355 return true; 1356 } 1357 1358 private double getTotalWidthOfNodeColumns(Node child, double[] widths) { 1359 if (getNodeColumnSpan(child) == 1) { 1360 return widths[getNodeColumnIndex(child)]; 1361 } else { 1362 double total = 0; 1363 for (int i = getNodeColumnIndex(child), last = getNodeColumnEndConvertRemaining(child); i <= last; ++i) { 1364 total += widths[i]; 1365 } 1366 return total; 1367 } 1368 } 1369 1370 private CompositeSize computeMaxHeights() { 1371 if (rowMaxHeight == null) { 1372 rowMaxHeight = createCompositeRows(Double.MAX_VALUE); // Do not restrict the row (to allow grow). The 1373 // Nodes will be restricted to their computed size 1374 // in Region.layoutInArea call 1375 final ObservableList<RowConstraints> rowConstr = getRowConstraints(); 1376 CompositeSize prefHeights = null; 1377 for (int i = 0; i < rowConstr.size(); ++i) { 1378 final RowConstraints curConstraint = rowConstr.get(i); 1379 final double constrMaxH = curConstraint.getMaxHeight(); 1380 if (constrMaxH == USE_PREF_SIZE) { 1381 if (prefHeights == null) { 1382 prefHeights = computePrefHeights(null); 1383 } 1384 rowMaxHeight.setPresetSize(i, prefHeights.getSize(i)); 1385 } else if (constrMaxH != USE_COMPUTED_SIZE) { 1386 final double maxRowHeight = snapSizeY(constrMaxH); 1387 final double constrMinH = curConstraint.getMinHeight(); 1388 if (constrMinH >= 0 ) { 1389 final double min = snapSizeY(curConstraint.getMinHeight()); 1390 rowMaxHeight.setPresetSize(i, boundedSize(min, maxRowHeight, maxRowHeight)); 1391 } else { 1392 rowMaxHeight.setPresetSize(i, maxRowHeight); 1393 } 1394 } 1395 } 1396 } 1397 return rowMaxHeight; 1398 } 1399 1400 private CompositeSize computePrefHeights(double[] widths) { 1401 CompositeSize result; 1402 if (widths == null) { 1403 if (rowPrefHeight != null) { 1404 return rowPrefHeight; 1405 } 1406 rowPrefHeight = createCompositeRows(0); 1407 result = rowPrefHeight; 1408 } else { 1409 result = createCompositeRows(0); 1410 } 1411 1412 final ObservableList<RowConstraints> rowConstr = getRowConstraints(); 1413 for (int i = 0; i < rowConstr.size(); ++i) { 1414 final RowConstraints curConstraint = rowConstr.get(i); 1415 final double constrMinH = curConstraint.getMinHeight(); 1416 final double constrPrefH = curConstraint.getPrefHeight(); 1417 if (constrPrefH != USE_COMPUTED_SIZE) { 1418 final double prefRowHeight = snapSizeY(constrPrefH); 1419 final double constrMaxH = curConstraint.getMaxHeight(); 1420 if (constrMinH >= 0 || constrMaxH >= 0) { 1421 final double min = (constrMinH < 0 ? 0 : snapSizeY(constrMinH)); 1422 final double max = (constrMaxH < 0 ? Double.POSITIVE_INFINITY : snapSizeY(constrMaxH)); 1423 result.setPresetSize(i, boundedSize(min, prefRowHeight, max)); 1424 } else { 1425 result.setPresetSize(i, prefRowHeight); 1426 } 1427 } else if (constrMinH > 0){ 1428 result.setSize(i, snapSizeY(constrMinH)); 1429 } 1430 } 1431 List<Node> managed = getManagedChildren(); 1432 for (int i = 0, size = managed.size(); i < size; i++) { 1433 Node child = managed.get(i); 1434 int start = getNodeRowIndex(child); 1435 int end = getNodeRowEndConvertRemaining(child); 1436 double childPrefAreaHeight = computeChildPrefAreaHeight(child, isNodePositionedByBaseline(child) ? rowPrefBaselineComplement[start] : -1, getMargin(child), 1437 widths == null ? -1 : getTotalWidthOfNodeColumns(child, widths)); 1438 if (start == end && !result.isPreset(start)) { 1439 double min = getRowMinHeight(start); 1440 double max = getRowMaxHeight(start); 1441 result.setMaxSize(start, boundedSize(min < 0 ? 0 : min, childPrefAreaHeight, max < 0 ? Double.MAX_VALUE : max)); 1442 } else if (start != end){ 1443 result.setMaxMultiSize(start, end + 1, childPrefAreaHeight); 1444 } 1445 } 1446 return result; 1447 } 1448 1449 private CompositeSize computeMinHeights(double[] widths) { 1450 CompositeSize result; 1451 if (widths == null) { 1452 if (rowMinHeight != null) { 1453 return rowMinHeight; 1454 } 1455 rowMinHeight = createCompositeRows(0); 1456 result = rowMinHeight; 1457 } else { 1458 result = createCompositeRows(0); 1459 } 1460 1461 final ObservableList<RowConstraints> rowConstr = getRowConstraints(); 1462 CompositeSize prefHeights = null; 1463 for (int i = 0; i < rowConstr.size(); ++i) { 1464 final double constrMinH = rowConstr.get(i).getMinHeight(); 1465 if (constrMinH == USE_PREF_SIZE) { 1466 if (prefHeights == null) { 1467 prefHeights = computePrefHeights(widths); 1468 } 1469 result.setPresetSize(i, prefHeights.getSize(i)); 1470 } else if (constrMinH != USE_COMPUTED_SIZE) { 1471 result.setPresetSize(i, snapSizeY(constrMinH)); 1472 } 1473 } 1474 List<Node> managed = getManagedChildren(); 1475 for (int i = 0, size = managed.size(); i < size; i++) { 1476 Node child = managed.get(i); 1477 int start = getNodeRowIndex(child); 1478 int end = getNodeRowEndConvertRemaining(child); 1479 double childMinAreaHeight = computeChildMinAreaHeight(child, isNodePositionedByBaseline(child) ? rowMinBaselineComplement[start] : -1, getMargin(child), 1480 widths == null ? -1 : getTotalWidthOfNodeColumns(child, widths)); 1481 if (start == end && !result.isPreset(start)) { 1482 result.setMaxSize(start, childMinAreaHeight); 1483 } else if (start != end){ 1484 result.setMaxMultiSize(start, end + 1, childMinAreaHeight); 1485 } 1486 } 1487 return result; 1488 } 1489 1490 private double getTotalHeightOfNodeRows(Node child, double[] heights) { 1491 if (getNodeRowSpan(child) == 1) { 1492 return heights[getNodeRowIndex(child)]; 1493 } else { 1494 double total = 0; 1495 for (int i = getNodeRowIndex(child), last = getNodeRowEndConvertRemaining(child); i <= last; ++i) { 1496 total += heights[i]; 1497 } 1498 return total; 1499 } 1500 } 1501 1502 private CompositeSize computeMaxWidths() { 1503 if (columnMaxWidth == null) { 1504 columnMaxWidth = createCompositeColumns(Double.MAX_VALUE);// Do not restrict the column (to allow grow). The 1505 // Nodes will be restricted to their computed size 1506 // in Region.layoutInArea call 1507 final ObservableList<ColumnConstraints> columnConstr = getColumnConstraints(); 1508 CompositeSize prefWidths = null; 1509 for (int i = 0; i < columnConstr.size(); ++i) { 1510 final ColumnConstraints curConstraint = columnConstr.get(i); 1511 final double constrMaxW = curConstraint.getMaxWidth(); 1512 if (constrMaxW == USE_PREF_SIZE) { 1513 if (prefWidths == null) { 1514 prefWidths = computePrefWidths(null); 1515 } 1516 columnMaxWidth.setPresetSize(i, prefWidths.getSize(i)); 1517 } else if (constrMaxW != USE_COMPUTED_SIZE) { 1518 double maxColumnWidth = snapSizeX(constrMaxW); 1519 final double constrMinW = curConstraint.getMinWidth(); 1520 if (constrMinW >= 0) { 1521 final double min = snapSizeX(constrMinW); 1522 columnMaxWidth.setPresetSize(i, boundedSize(min, maxColumnWidth, maxColumnWidth)); 1523 } else { 1524 columnMaxWidth.setPresetSize(i, maxColumnWidth); 1525 } 1526 } 1527 } 1528 } 1529 return columnMaxWidth; 1530 } 1531 1532 private CompositeSize computePrefWidths(double[] heights) { 1533 CompositeSize result; 1534 if (heights == null) { 1535 if (columnPrefWidth != null) { 1536 return columnPrefWidth; 1537 } 1538 columnPrefWidth = createCompositeColumns(0); 1539 result = columnPrefWidth; 1540 } else { 1541 result = createCompositeColumns(0); 1542 } 1543 1544 final ObservableList<ColumnConstraints> columnConstr = getColumnConstraints(); 1545 for (int i = 0; i < columnConstr.size(); ++i) { 1546 final ColumnConstraints curConstraint = columnConstr.get(i); 1547 final double constrPrefW = curConstraint.getPrefWidth(); 1548 final double constrMinW = curConstraint.getMinWidth(); 1549 if (constrPrefW != USE_COMPUTED_SIZE) { 1550 final double prefColumnWidth = snapSizeX(constrPrefW); 1551 final double constrMaxW = curConstraint.getMaxWidth(); 1552 if (constrMinW >= 0 || constrMaxW >= 0) { 1553 double min = (constrMinW < 0 ? 0 : snapSizeX(constrMinW)); 1554 final double max = (constrMaxW < 0 ? Double.POSITIVE_INFINITY : snapSizeX(constrMaxW)); 1555 result.setPresetSize(i, boundedSize(min < 0 ? 0 : min, 1556 prefColumnWidth, 1557 max < 0 ? Double.POSITIVE_INFINITY : max)); 1558 } else { 1559 result.setPresetSize(i, prefColumnWidth); 1560 } 1561 } else if (constrMinW > 0){ 1562 result.setSize(i, snapSizeX(constrMinW)); 1563 } 1564 } 1565 List<Node> managed = getManagedChildren(); 1566 for (int i = 0, size = managed.size(); i < size; i++) { 1567 Node child = managed.get(i); 1568 int start = getNodeColumnIndex(child); 1569 int end = getNodeColumnEndConvertRemaining(child); 1570 if (start == end && !result.isPreset(start)) { 1571 double min = getColumnMinWidth(start); 1572 double max = getColumnMaxWidth(start); 1573 result.setMaxSize(start, boundedSize(min < 0 ? 0 : min, computeChildPrefAreaWidth(child, 1574 getBaselineComplementForChild(child), getMargin(child), 1575 heights == null ? -1 : getTotalHeightOfNodeRows(child, heights), false), 1576 max < 0 ? Double.MAX_VALUE : max)); 1577 } else if (start != end) { 1578 result.setMaxMultiSize(start, end + 1, computeChildPrefAreaWidth(child, getBaselineComplementForChild(child), 1579 getMargin(child), 1580 heights == null ? -1 : getTotalHeightOfNodeRows(child, heights), false)); 1581 } 1582 } 1583 return result; 1584 } 1585 1586 private CompositeSize computeMinWidths(double[] heights) { 1587 CompositeSize result; 1588 if (heights == null) { 1589 if (columnMinWidth != null) { 1590 return columnMinWidth; 1591 } 1592 columnMinWidth = createCompositeColumns(0); 1593 result = columnMinWidth; 1594 } else { 1595 result = createCompositeColumns(0); 1596 } 1597 1598 final ObservableList<ColumnConstraints> columnConstr = getColumnConstraints(); 1599 CompositeSize prefWidths = null; 1600 for (int i = 0; i < columnConstr.size(); ++i) { 1601 final double constrMinW = columnConstr.get(i).getMinWidth(); 1602 if (constrMinW == USE_PREF_SIZE) { 1603 if (prefWidths == null) { 1604 prefWidths = computePrefWidths(heights); 1605 } 1606 result.setPresetSize(i, prefWidths.getSize(i)); 1607 } else if (constrMinW != USE_COMPUTED_SIZE) { 1608 result.setPresetSize(i, snapSizeX(constrMinW)); 1609 } 1610 } 1611 List<Node> managed = getManagedChildren(); 1612 for (int i = 0, size = managed.size(); i < size; i++) { 1613 Node child = managed.get(i); 1614 int start = getNodeColumnIndex(child); 1615 int end = getNodeColumnEndConvertRemaining(child); 1616 if (start == end && !result.isPreset(start)) { 1617 result.setMaxSize(start, computeChildMinAreaWidth(child, getBaselineComplementForChild(child), 1618 getMargin(child), 1619 heights == null ? -1 : getTotalHeightOfNodeRows(child, heights),false)); 1620 } else if (start != end){ 1621 result.setMaxMultiSize(start, end + 1, computeChildMinAreaWidth(child, getBaselineComplementForChild(child), 1622 getMargin(child), 1623 heights == null ? -1 : getTotalHeightOfNodeRows(child, heights), false)); 1624 } 1625 } 1626 return result; 1627 } 1628 1629 private CompositeSize computeHeightsToFit(double height) { 1630 assert(height != -1); 1631 final CompositeSize heights; 1632 if (rowPercentTotal == 100) { 1633 // all rows defined by percentage, no need to compute pref heights 1634 heights = createCompositeRows(0); 1635 } else { 1636 heights = (CompositeSize) computePrefHeights(null).clone(); 1637 } 1638 adjustRowHeights(heights, height); 1639 return heights; 1640 } 1641 1642 private CompositeSize computeWidthsToFit(double width) { 1643 assert(width != -1); 1644 final CompositeSize widths; 1645 if (columnPercentTotal == 100) { 1646 // all columns defined by percentage, no need to compute pref widths 1647 widths = createCompositeColumns(0); 1648 } else { 1649 widths = (CompositeSize) computePrefWidths(null).clone(); 1650 } 1651 adjustColumnWidths(widths, width); 1652 return widths; 1653 } 1654 1655 /** 1656 * 1657 * @return null unless one of its children has a content bias. 1658 */ 1659 @Override public Orientation getContentBias() { 1660 computeGridMetrics(); 1661 return bias; 1662 } 1663 1664 @Override public void requestLayout() { 1665 // RT-18878: Do not update metrics dirty if we are performing layout. 1666 // If metricsDirty is set true during a layout pass the next call to computeGridMetrics() 1667 // will clear all the cell bounds resulting in out of date info until the 1668 // next layout pass. 1669 if (performingLayout) { 1670 return; 1671 } else if (metricsDirty) { 1672 super.requestLayout(); 1673 return; 1674 } 1675 metricsDirty = true; 1676 bias = null; 1677 rowGrow = null; 1678 rowMinHeight = rowPrefHeight = rowMaxHeight = null; 1679 columnGrow = null; 1680 columnMinWidth = columnPrefWidth = columnMaxWidth = null; 1681 rowMinBaselineComplement = rowPrefBaselineComplement = rowMaxBaselineComplement = null; 1682 super.requestLayout(); 1683 } 1684 1685 @Override protected void layoutChildren() { 1686 performingLayout = true; 1687 try { 1688 final double snaphgap = snapSpaceX(getHgap()); 1689 final double snapvgap = snapSpaceY(getVgap()); 1690 final double top = snapSpaceY(getInsets().getTop()); 1691 final double bottom = snapSpaceY(getInsets().getBottom()); 1692 final double left = snapSpaceX(getInsets().getLeft()); 1693 final double right = snapSpaceX(getInsets().getRight()); 1694 1695 final double width = getWidth(); 1696 final double height = getHeight(); 1697 final double contentHeight = height - top - bottom; 1698 final double contentWidth = width - left - right; 1699 double columnTotal; 1700 double rowTotal; 1701 computeGridMetrics(); 1702 1703 Orientation contentBias = getContentBias(); 1704 CompositeSize heights; 1705 final CompositeSize widths; 1706 if (contentBias == null) { 1707 heights = (CompositeSize) computePrefHeights(null).clone(); 1708 widths = (CompositeSize) computePrefWidths(null).clone(); 1709 rowTotal = adjustRowHeights(heights, height); 1710 columnTotal = adjustColumnWidths(widths, width); 1711 } else if (contentBias == Orientation.HORIZONTAL) { 1712 widths = (CompositeSize) computePrefWidths(null).clone(); 1713 columnTotal = adjustColumnWidths(widths, width); 1714 heights = computePrefHeights(widths.asArray()); 1715 rowTotal = adjustRowHeights(heights, height); 1716 } else { 1717 heights = (CompositeSize) computePrefHeights(null).clone(); 1718 rowTotal = adjustRowHeights(heights, height); 1719 widths = computePrefWidths(heights.asArray()); 1720 columnTotal = adjustColumnWidths(widths, width); 1721 } 1722 1723 final double x = left + computeXOffset(contentWidth, columnTotal, getAlignmentInternal().getHpos()); 1724 final double y = top + computeYOffset(contentHeight, rowTotal, getAlignmentInternal().getVpos()); 1725 final List<Node> managed = getManagedChildren(); 1726 1727 double[] baselineOffsets = createDoubleArray(numRows, -1); 1728 1729 for (int i = 0, size = managed.size(); i < size; i++) { 1730 final Node child = managed.get(i); 1731 final int rowIndex = getNodeRowIndex(child); 1732 int columnIndex = getNodeColumnIndex(child); 1733 int colspan = getNodeColumnSpan(child); 1734 if (colspan == REMAINING) { 1735 colspan = widths.getLength() - columnIndex; 1736 } 1737 int rowspan = getNodeRowSpan(child); 1738 if (rowspan == REMAINING) { 1739 rowspan = heights.getLength() - rowIndex; 1740 } 1741 double areaX = x; 1742 for (int j = 0; j < columnIndex; j++) { 1743 areaX += widths.getSize(j) + snaphgap; 1744 } 1745 double areaY = y; 1746 for (int j = 0; j < rowIndex; j++) { 1747 areaY += heights.getSize(j) + snapvgap; 1748 } 1749 double areaW = widths.getSize(columnIndex); 1750 for (int j = 2; j <= colspan; j++) { 1751 areaW += widths.getSize(columnIndex + j - 1) + snaphgap; 1752 } 1753 double areaH = heights.getSize(rowIndex); 1754 for (int j = 2; j <= rowspan; j++) { 1755 areaH += heights.getSize(rowIndex + j - 1) + snapvgap; 1756 } 1757 1758 HPos halign = getHalignment(child); 1759 VPos valign = getValignment(child); 1760 Boolean fillWidth = isFillWidth(child); 1761 Boolean fillHeight = isFillHeight(child); 1762 1763 if (halign == null) { 1764 halign = getColumnHalignment(columnIndex); 1765 } 1766 if (valign == null) { 1767 valign = getRowValignment(rowIndex); 1768 } 1769 if (fillWidth == null) { 1770 fillWidth = shouldColumnFillWidth(columnIndex); 1771 } 1772 if (fillHeight == null) { 1773 fillHeight = shouldRowFillHeight(rowIndex); 1774 } 1775 1776 double baselineOffset = 0; 1777 if (valign == VPos.BASELINE) { 1778 if (baselineOffsets[rowIndex] == -1) { 1779 baselineOffsets[rowIndex] = getAreaBaselineOffset(rowBaseline[rowIndex], 1780 marginAccessor, 1781 t -> { 1782 Node n = rowBaseline[rowIndex].get(t); 1783 int c = getNodeColumnIndex(n); 1784 int cs = getNodeColumnSpan(n); 1785 if (cs == REMAINING) { 1786 cs = widths.getLength() - c; 1787 } 1788 double w = widths.getSize(c); 1789 for (int j = 2; j <= cs; j++) { 1790 w += widths.getSize(c + j - 1) + snaphgap; 1791 } 1792 return w; 1793 }, 1794 areaH, 1795 t -> { 1796 Boolean b = isFillHeight(child); 1797 if (b != null) { 1798 return b; 1799 } 1800 return shouldRowFillHeight(getNodeRowIndex(child)); 1801 }, rowMinBaselineComplement[rowIndex] 1802 ); 1803 } 1804 baselineOffset = baselineOffsets[rowIndex]; 1805 } 1806 1807 Insets margin = getMargin(child); 1808 layoutInArea(child, areaX, areaY, areaW, areaH, 1809 baselineOffset, 1810 margin, 1811 fillWidth, fillHeight, 1812 halign, valign); 1813 } 1814 layoutGridLines(widths, heights, x, y, rowTotal, columnTotal); 1815 currentHeights = heights; 1816 currentWidths = widths; 1817 } finally { 1818 performingLayout = false; 1819 } 1820 } 1821 1822 private double adjustRowHeights(final CompositeSize heights, double height) { 1823 assert(height != -1); 1824 final double snapvgap = snapSpaceY(getVgap()); 1825 final double top = snapSpaceY(getInsets().getTop()); 1826 final double bottom = snapSpaceY(getInsets().getBottom()); 1827 final double vgaps = snapvgap * (getNumberOfRows() - 1); 1828 final double contentHeight = height - top - bottom; 1829 1830 // if there are percentage rows, give them their percentages first 1831 if (rowPercentTotal > 0) { 1832 double remainder = 0; 1833 for (int i = 0; i < rowPercentHeight.length; i++) { 1834 if (rowPercentHeight[i] >= 0) { 1835 double size = (contentHeight - vgaps) * (rowPercentHeight[i]/100); 1836 double floor = Math.floor(size); 1837 remainder += size - floor; 1838 1839 // snap size to integer boundary based on the computed remainder as we loop through the rows. 1840 size = floor; 1841 if (remainder >= 0.5) { 1842 size++; 1843 remainder = (-1.0) + remainder; 1844 } 1845 heights.setSize(i, size); 1846 } 1847 } 1848 } 1849 double rowTotal = heights.computeTotal(); 1850 if (rowPercentTotal < 100) { 1851 double heightAvailable = height - top - bottom - rowTotal; 1852 // now that both fixed and percentage rows have been computed, divy up any surplus or deficit 1853 if (heightAvailable != 0) { 1854 // maybe grow or shrink row heights 1855 double remaining = growToMultiSpanPreferredHeights(heights, heightAvailable); 1856 remaining = growOrShrinkRowHeights(heights, Priority.ALWAYS, remaining); 1857 remaining = growOrShrinkRowHeights(heights, Priority.SOMETIMES, remaining); 1858 rowTotal += (heightAvailable - remaining); 1859 } 1860 } 1861 1862 return rowTotal; 1863 } 1864 1865 private double growToMultiSpanPreferredHeights(CompositeSize heights, double extraHeight) { 1866 if (extraHeight <= 0) { 1867 return extraHeight; 1868 } 1869 1870 Set<Integer> rowsAlways = new TreeSet<>(); 1871 Set<Integer> rowsSometimes = new TreeSet<>(); 1872 Set<Integer> lastRows = new TreeSet<>(); 1873 for (Entry<Interval, Double> ms : heights.multiSizes()) { 1874 final Interval interval = ms.getKey(); 1875 for (int i = interval.begin; i < interval.end; ++i) { 1876 if (rowPercentHeight[i] < 0) { 1877 switch (rowGrow[i]) { 1878 case ALWAYS: 1879 rowsAlways.add(i); 1880 break; 1881 case SOMETIMES: 1882 rowsSometimes.add(i); 1883 break; 1884 } 1885 } 1886 } 1887 if (rowPercentHeight[interval.end - 1] < 0) { 1888 lastRows.add(interval.end - 1); 1889 } 1890 } 1891 1892 double remaining = extraHeight; 1893 1894 while (rowsAlways.size() > 0 && remaining > rowsAlways.size()) { 1895 double rowPortion = Math.floor(remaining / rowsAlways.size()); 1896 for (Iterator<Integer> it = rowsAlways.iterator(); it.hasNext();) { 1897 int i = it.next(); 1898 double maxOfRow = getRowMaxHeight(i); 1899 double prefOfRow = getRowPrefHeight(i); 1900 double actualPortion = rowPortion; 1901 1902 for (Entry<Interval, Double> ms : heights.multiSizes()) { 1903 final Interval interval = ms.getKey(); 1904 if (interval.contains(i)) { 1905 int intervalRows = 0; 1906 for (int j = interval.begin; j < interval.end; ++j) { 1907 if (rowsAlways.contains(j)) { 1908 intervalRows++; 1909 } 1910 } 1911 double curLength = heights.computeTotal(interval.begin, interval.end); 1912 actualPortion = Math.min(Math.floor(Math.max(0, (ms.getValue() - curLength) / intervalRows)), 1913 actualPortion); 1914 } 1915 } 1916 1917 final double current = heights.getSize(i); 1918 double bounded = maxOfRow >= 0 ? boundedSize(0, current + actualPortion, maxOfRow) : 1919 maxOfRow == USE_PREF_SIZE && prefOfRow > 0 ? boundedSize(0, current + actualPortion, prefOfRow) : 1920 current + actualPortion; 1921 final double portionUsed = bounded - current; 1922 remaining -= portionUsed; 1923 if (portionUsed != actualPortion || portionUsed == 0) { 1924 it.remove(); 1925 } 1926 heights.setSize(i, bounded); 1927 } 1928 } 1929 1930 while (rowsSometimes.size() > 0 && remaining > rowsSometimes.size()) { 1931 double colPortion = Math.floor(remaining / rowsSometimes.size()); 1932 for (Iterator<Integer> it = rowsSometimes.iterator(); it.hasNext();) { 1933 int i = it.next(); 1934 double maxOfRow = getRowMaxHeight(i); 1935 double prefOfRow = getRowPrefHeight(i); 1936 double actualPortion = colPortion; 1937 1938 for (Entry<Interval, Double> ms : heights.multiSizes()) { 1939 final Interval interval = ms.getKey(); 1940 if (interval.contains(i)) { 1941 int intervalRows = 0; 1942 for (int j = interval.begin; j < interval.end; ++j) { 1943 if (rowsSometimes.contains(j)) { 1944 intervalRows++; 1945 } 1946 } 1947 double curLength = heights.computeTotal(interval.begin, interval.end); 1948 actualPortion = Math.min(Math.floor(Math.max(0, (ms.getValue() - curLength) / intervalRows)), 1949 actualPortion); 1950 } 1951 } 1952 1953 final double current = heights.getSize(i); 1954 double bounded = maxOfRow >= 0 ? boundedSize(0, current + actualPortion, maxOfRow) : 1955 maxOfRow == USE_PREF_SIZE && prefOfRow > 0 ? boundedSize(0, current + actualPortion, prefOfRow) : 1956 current + actualPortion; 1957 final double portionUsed = bounded - current; 1958 remaining -= portionUsed; 1959 if (portionUsed != actualPortion || portionUsed == 0) { 1960 it.remove(); 1961 } 1962 heights.setSize(i, bounded); 1963 } 1964 } 1965 1966 1967 while (lastRows.size() > 0 && remaining > lastRows.size()) { 1968 double colPortion = Math.floor(remaining / lastRows.size()); 1969 for (Iterator<Integer> it = lastRows.iterator(); it.hasNext();) { 1970 int i = it.next(); 1971 double maxOfRow = getRowMaxHeight(i); 1972 double prefOfRow = getRowPrefHeight(i); 1973 double actualPortion = colPortion; 1974 1975 for (Entry<Interval, Double> ms : heights.multiSizes()) { 1976 final Interval interval = ms.getKey(); 1977 if (interval.end - 1 == i) { 1978 double curLength = heights.computeTotal(interval.begin, interval.end); 1979 actualPortion = Math.min(Math.max(0, ms.getValue() - curLength), 1980 actualPortion); 1981 } 1982 } 1983 1984 final double current = heights.getSize(i); 1985 double bounded = maxOfRow >= 0 ? boundedSize(0, current + actualPortion, maxOfRow) : 1986 maxOfRow == USE_PREF_SIZE && prefOfRow > 0 ? boundedSize(0, current + actualPortion, prefOfRow) : 1987 current + actualPortion; 1988 final double portionUsed = bounded - current; 1989 remaining -= portionUsed; 1990 if (portionUsed != actualPortion || portionUsed == 0) { 1991 it.remove(); 1992 } 1993 heights.setSize(i, bounded); 1994 } 1995 } 1996 return remaining; 1997 } 1998 1999 private double growOrShrinkRowHeights(CompositeSize heights, Priority priority, double extraHeight) { 2000 final boolean shrinking = extraHeight < 0; 2001 List<Integer> adjusting = new ArrayList<>(); 2002 2003 for (int i = 0; i < rowGrow.length; i++) { 2004 if (rowPercentHeight[i] < 0 && (shrinking || rowGrow[i] == priority)) { 2005 adjusting.add(i); 2006 } 2007 } 2008 2009 double available = extraHeight; // will be negative in shrinking case 2010 boolean handleRemainder = false; 2011 double portion = 0; 2012 2013 // RT-25684: We have to be careful that when subtracting change 2014 // that we don't jump right past 0 - this leads to an infinite 2015 // loop 2016 final boolean wasPositive = available >= 0.0; 2017 boolean isPositive = wasPositive; 2018 2019 CompositeSize limitSize = shrinking? computeMinHeights(null) : 2020 computeMaxHeights(); 2021 while (available != 0 && wasPositive == isPositive && adjusting.size() > 0) { 2022 if (!handleRemainder) { 2023 portion = available > 0 ? Math.floor(available / adjusting.size()) : 2024 Math.ceil(available / adjusting.size()); // negative in shrinking case 2025 } 2026 if (portion != 0) { 2027 for (Iterator<Integer> i = adjusting.iterator(); i.hasNext();) { 2028 final int index = i.next(); 2029 double limit = snapSpaceY(limitSize.getProportionalMinOrMaxSize(index, shrinking)) 2030 - heights.getSize(index); // negative in shrinking case 2031 if (shrinking && limit > 0 2032 || !shrinking && limit < 0) { // Limit completely if current size 2033 // (originally based on preferred) already passed the computed limit 2034 limit = 0; 2035 } 2036 final double change = Math.abs(limit) <= Math.abs(portion)? limit : portion; 2037 heights.addSize(index, change); 2038 available -= change; 2039 isPositive = available >= 0.0; 2040 if (Math.abs(change) < Math.abs(portion)) { 2041 i.remove(); 2042 } 2043 if (available == 0) { 2044 break; 2045 } 2046 } 2047 } else { 2048 // Handle the remainder 2049 portion = (int)(available) % adjusting.size(); 2050 if (portion == 0) { 2051 break; 2052 } else { 2053 // We have a remainder evenly distribute it. 2054 portion = shrinking ? -1 : 1; 2055 handleRemainder = true; 2056 } 2057 } 2058 } 2059 2060 return available; // might be negative in shrinking case 2061 } 2062 2063 private double adjustColumnWidths(final CompositeSize widths, double width) { 2064 assert(width != -1); 2065 final double snaphgap = snapSpaceX(getHgap()); 2066 final double left = snapSpaceX(getInsets().getLeft()); 2067 final double right = snapSpaceX(getInsets().getRight()); 2068 final double hgaps = snaphgap * (getNumberOfColumns() - 1); 2069 final double contentWidth = width - left - right; 2070 2071 // if there are percentage rows, give them their percentages first 2072 if (columnPercentTotal > 0) { 2073 double remainder = 0; 2074 for (int i = 0; i < columnPercentWidth.length; i++) { 2075 if (columnPercentWidth[i] >= 0) { 2076 double size = (contentWidth - hgaps) * (columnPercentWidth[i]/100); 2077 double floor = Math.floor(size); 2078 remainder += size - floor; 2079 2080 // snap size to integer boundary based on the computed remainder as we loop through the columns. 2081 size = floor; 2082 if (remainder >= 0.5) { 2083 size++; 2084 remainder = (-1.0) + remainder; 2085 } 2086 widths.setSize(i, size); 2087 } 2088 } 2089 } 2090 2091 double columnTotal = widths.computeTotal(); 2092 if (columnPercentTotal < 100) { 2093 double widthAvailable = width - left - right - columnTotal; 2094 // now that both fixed and percentage rows have been computed, divy up any surplus or deficit 2095 if (widthAvailable != 0) { 2096 // maybe grow or shrink row heights 2097 double remaining = growToMultiSpanPreferredWidths(widths, widthAvailable); 2098 remaining = growOrShrinkColumnWidths(widths, Priority.ALWAYS, remaining); 2099 remaining = growOrShrinkColumnWidths(widths, Priority.SOMETIMES, remaining); 2100 columnTotal += (widthAvailable - remaining); 2101 } 2102 } 2103 return columnTotal; 2104 } 2105 2106 private double growToMultiSpanPreferredWidths(CompositeSize widths, double extraWidth) { 2107 if (extraWidth <= 0) { 2108 return extraWidth; 2109 } 2110 2111 Set<Integer> columnsAlways = new TreeSet<>(); 2112 Set<Integer> columnsSometimes = new TreeSet<>(); 2113 Set<Integer> lastColumns = new TreeSet<>(); 2114 for (Entry<Interval, Double> ms : widths.multiSizes()) { 2115 final Interval interval = ms.getKey(); 2116 for (int i = interval.begin; i < interval.end; ++i) { 2117 if (columnPercentWidth[i] < 0) { 2118 switch (columnGrow[i]) { 2119 case ALWAYS: 2120 columnsAlways.add(i); 2121 break; 2122 case SOMETIMES: 2123 columnsSometimes.add(i); 2124 break; 2125 } 2126 } 2127 } 2128 if (columnPercentWidth[interval.end - 1] < 0) { 2129 lastColumns.add(interval.end - 1); 2130 } 2131 } 2132 2133 double remaining = extraWidth; 2134 2135 while (columnsAlways.size() > 0 && remaining > columnsAlways.size()) { 2136 double colPortion = Math.floor(remaining / columnsAlways.size()); 2137 for (Iterator<Integer> it = columnsAlways.iterator(); it.hasNext();) { 2138 int i = it.next(); 2139 double maxOfColumn = getColumnMaxWidth(i); 2140 double prefOfColumn = getColumnPrefWidth(i); 2141 double actualPortion = colPortion; 2142 2143 for (Entry<Interval, Double> ms : widths.multiSizes()) { 2144 final Interval interval = ms.getKey(); 2145 if (interval.contains(i)) { 2146 int intervalColumns = 0; 2147 for (int j = interval.begin; j < interval.end; ++j) { 2148 if (columnsAlways.contains(j)) { 2149 intervalColumns++; 2150 } 2151 } 2152 double curLength = widths.computeTotal(interval.begin, interval.end); 2153 actualPortion = Math.min(Math.floor(Math.max(0, (ms.getValue() - curLength) / intervalColumns)), 2154 actualPortion); 2155 } 2156 } 2157 2158 final double current = widths.getSize(i); 2159 double bounded = maxOfColumn >= 0 ? boundedSize(0, current + actualPortion, maxOfColumn) : 2160 maxOfColumn == USE_PREF_SIZE && prefOfColumn > 0 ? boundedSize(0, current + actualPortion, prefOfColumn) : 2161 current + actualPortion; 2162 final double portionUsed = bounded - current; 2163 remaining -= portionUsed; 2164 if (portionUsed != actualPortion || portionUsed == 0) { 2165 it.remove(); 2166 } 2167 widths.setSize(i, bounded); 2168 } 2169 } 2170 2171 while (columnsSometimes.size() > 0 && remaining > columnsSometimes.size()) { 2172 double colPortion = Math.floor(remaining / columnsSometimes.size()); 2173 for (Iterator<Integer> it = columnsSometimes.iterator(); it.hasNext();) { 2174 int i = it.next(); 2175 double maxOfColumn = getColumnMaxWidth(i); 2176 double prefOfColumn = getColumnPrefWidth(i); 2177 double actualPortion = colPortion; 2178 2179 for (Entry<Interval, Double> ms : widths.multiSizes()) { 2180 final Interval interval = ms.getKey(); 2181 if (interval.contains(i)) { 2182 int intervalColumns = 0; 2183 for (int j = interval.begin; j < interval.end; ++j) { 2184 if (columnsSometimes.contains(j)) { 2185 intervalColumns++; 2186 } 2187 } 2188 double curLength = widths.computeTotal(interval.begin, interval.end); 2189 actualPortion = Math.min(Math.floor(Math.max(0, (ms.getValue() - curLength) / intervalColumns)), 2190 actualPortion); 2191 } 2192 } 2193 2194 final double current = widths.getSize(i); 2195 double bounded = maxOfColumn >= 0 ? boundedSize(0, current + actualPortion, maxOfColumn) : 2196 maxOfColumn == USE_PREF_SIZE && prefOfColumn > 0 ? boundedSize(0, current + actualPortion, prefOfColumn) : 2197 current + actualPortion; 2198 final double portionUsed = bounded - current; 2199 remaining -= portionUsed; 2200 if (portionUsed != actualPortion || portionUsed == 0) { 2201 it.remove(); 2202 } 2203 widths.setSize(i, bounded); 2204 } 2205 } 2206 2207 2208 while (lastColumns.size() > 0 && remaining > lastColumns.size()) { 2209 double colPortion = Math.floor(remaining / lastColumns.size()); 2210 for (Iterator<Integer> it = lastColumns.iterator(); it.hasNext();) { 2211 int i = it.next(); 2212 double maxOfColumn = getColumnMaxWidth(i); 2213 double prefOfColumn = getColumnPrefWidth(i); 2214 double actualPortion = colPortion; 2215 2216 for (Entry<Interval, Double> ms : widths.multiSizes()) { 2217 final Interval interval = ms.getKey(); 2218 if (interval.end - 1 == i) { 2219 double curLength = widths.computeTotal(interval.begin, interval.end); 2220 actualPortion = Math.min(Math.max(0, ms.getValue() - curLength), 2221 actualPortion); 2222 } 2223 } 2224 2225 final double current = widths.getSize(i); 2226 double bounded = maxOfColumn >= 0 ? boundedSize(0, current + actualPortion, maxOfColumn) : 2227 maxOfColumn == USE_PREF_SIZE && prefOfColumn > 0 ? boundedSize(0, current + actualPortion, prefOfColumn) : 2228 current + actualPortion; 2229 final double portionUsed = bounded - current; 2230 remaining -= portionUsed; 2231 if (portionUsed != actualPortion || portionUsed == 0) { 2232 it.remove(); 2233 } 2234 widths.setSize(i, bounded); 2235 } 2236 } 2237 return remaining; 2238 } 2239 2240 private double growOrShrinkColumnWidths(CompositeSize widths, Priority priority, double extraWidth) { 2241 if (extraWidth == 0) { 2242 return 0; 2243 } 2244 final boolean shrinking = extraWidth < 0; 2245 List<Integer> adjusting = new ArrayList<>(); 2246 2247 for (int i = 0; i < columnGrow.length; i++) { 2248 if (columnPercentWidth[i] < 0 && (shrinking || columnGrow[i] == priority)) { 2249 adjusting.add(i); 2250 } 2251 } 2252 2253 double available = extraWidth; // will be negative in shrinking case 2254 boolean handleRemainder = false; 2255 double portion = 0; 2256 2257 // RT-25684: We have to be careful that when subtracting change 2258 // that we don't jump right past 0 - this leads to an infinite 2259 // loop 2260 final boolean wasPositive = available >= 0.0; 2261 boolean isPositive = wasPositive; 2262 2263 CompositeSize limitSize = shrinking? computeMinWidths(null) : 2264 computeMaxWidths(); 2265 while (available != 0 && wasPositive == isPositive && adjusting.size() > 0) { 2266 if (!handleRemainder) { 2267 portion = available > 0 ? Math.floor(available / adjusting.size()) : 2268 Math.ceil(available / adjusting.size()); // negative in shrinking case 2269 } 2270 if (portion != 0) { 2271 for (Iterator<Integer> i = adjusting.iterator(); i.hasNext();) { 2272 final int index = i.next(); 2273 double limit = snapSpaceX(limitSize.getProportionalMinOrMaxSize(index, shrinking)) 2274 - widths.getSize(index); // negative in shrinking case 2275 if (shrinking && limit > 0 2276 || !shrinking && limit < 0) { // Limit completely if current size 2277 // (originally based on preferred) already passed the computed limit 2278 limit = 0; 2279 } 2280 final double change = Math.abs(limit) <= Math.abs(portion)? limit : portion; 2281 widths.addSize(index, change); 2282 available -= change; 2283 isPositive = available >= 0.0; 2284 if (Math.abs(change) < Math.abs(portion)) { 2285 i.remove(); 2286 } 2287 if (available == 0) { 2288 break; 2289 } 2290 } 2291 } else { 2292 // Handle the remainder 2293 portion = (int)(available) % adjusting.size(); 2294 if (portion == 0) { 2295 break; 2296 } else { 2297 // We have a remainder evenly distribute it. 2298 portion = shrinking ? -1 : 1; 2299 handleRemainder = true; 2300 } 2301 } 2302 } 2303 2304 return available; // might be negative in shrinking case 2305 } 2306 2307 private void layoutGridLines(CompositeSize columnWidths, CompositeSize rowHeights, double x, double y, double columnHeight, double rowWidth) { 2308 if (!isGridLinesVisible()) { 2309 return; 2310 } 2311 if (!gridLines.getChildren().isEmpty()) { 2312 gridLines.getChildren().clear(); 2313 } 2314 double hGap = snapSpaceX(getHgap()); 2315 double vGap = snapSpaceY(getVgap()); 2316 2317 // create vertical lines 2318 double linex = x; 2319 double liney = y; 2320 for (int i = 0; i <= columnWidths.getLength(); i++) { 2321 gridLines.getChildren().add(createGridLine(linex, liney, linex, liney + columnHeight)); 2322 if (i > 0 && i < columnWidths.getLength() && hGap != 0) { 2323 linex += hGap; 2324 gridLines.getChildren().add(createGridLine(linex, liney, linex, liney + columnHeight)); 2325 } 2326 if (i < columnWidths.getLength()) { 2327 linex += columnWidths.getSize(i); 2328 } 2329 } 2330 // create horizontal lines 2331 linex = x; 2332 for (int i = 0; i <= rowHeights.getLength(); i++) { 2333 gridLines.getChildren().add(createGridLine(linex, liney, linex + rowWidth, liney)); 2334 if (i > 0 && i < rowHeights.getLength() && vGap != 0) { 2335 liney += vGap; 2336 gridLines.getChildren().add(createGridLine(linex, liney, linex + rowWidth, liney)); 2337 } 2338 if (i < rowHeights.getLength()) { 2339 liney += rowHeights.getSize(i); 2340 } 2341 } 2342 } 2343 2344 private Line createGridLine(double startX, double startY, double endX, double endY) { 2345 Line line = new Line(); 2346 line.setStartX(startX); 2347 line.setStartY(startY); 2348 line.setEndX(endX); 2349 line.setEndY(endY); 2350 line.setStroke(GRID_LINE_COLOR); 2351 line.setStrokeDashOffset(GRID_LINE_DASH); 2352 2353 return line; 2354 } 2355 2356 /** 2357 * Returns a string representation of this {@code GridPane} object. 2358 * @return a string representation of this {@code GridPane} object. 2359 */ 2360 @Override public String toString() { 2361 return "Grid hgap="+getHgap()+", vgap="+getVgap()+", alignment="+getAlignment(); 2362 } 2363 2364 private CompositeSize createCompositeRows(double initSize) { 2365 return new CompositeSize(getNumberOfRows(), rowPercentHeight, rowPercentTotal, 2366 snapSpaceY(getVgap()), initSize); 2367 } 2368 2369 private CompositeSize createCompositeColumns(double initSize) { 2370 return new CompositeSize(getNumberOfColumns(), columnPercentWidth, columnPercentTotal, 2371 snapSpaceX(getHgap()), initSize); 2372 } 2373 2374 private int getNodeRowEndConvertRemaining(Node child) { 2375 int rowSpan = getNodeRowSpan(child); 2376 return rowSpan != REMAINING? getNodeRowIndex(child) + rowSpan - 1 : getNumberOfRows() - 1; 2377 } 2378 2379 private int getNodeColumnEndConvertRemaining(Node child) { 2380 int columnSpan = getNodeColumnSpan(child); 2381 return columnSpan != REMAINING? getNodeColumnIndex(child) + columnSpan - 1 : getNumberOfColumns() - 1; 2382 } 2383 2384 2385 // This methods are inteded to be used by GridPaneDesignInfo 2386 private CompositeSize currentHeights; 2387 private CompositeSize currentWidths; 2388 2389 double[][] getGrid() { 2390 if (currentHeights == null || currentWidths == null) { 2391 return null; 2392 } 2393 return new double[][] {currentWidths.asArray(), currentHeights.asArray()}; 2394 } 2395 2396 /*************************************************************************** 2397 * * 2398 * Stylesheet Handling * 2399 * * 2400 **************************************************************************/ 2401 2402 /* 2403 * Super-lazy instantiation pattern from Bill Pugh. 2404 */ 2405 private static class StyleableProperties { 2406 2407 private static final CssMetaData<GridPane,Boolean> GRID_LINES_VISIBLE = 2408 new CssMetaData<GridPane,Boolean>("-fx-grid-lines-visible", 2409 BooleanConverter.getInstance(), Boolean.FALSE) { 2410 2411 @Override 2412 public boolean isSettable(GridPane node) { 2413 return node.gridLinesVisible == null || 2414 !node.gridLinesVisible.isBound(); 2415 } 2416 2417 @Override 2418 public StyleableProperty<Boolean> getStyleableProperty(GridPane node) { 2419 return (StyleableProperty<Boolean>)node.gridLinesVisibleProperty(); 2420 } 2421 }; 2422 2423 private static final CssMetaData<GridPane,Number> HGAP = 2424 new CssMetaData<GridPane,Number>("-fx-hgap", 2425 SizeConverter.getInstance(), 0.0){ 2426 2427 @Override 2428 public boolean isSettable(GridPane node) { 2429 return node.hgap == null || !node.hgap.isBound(); 2430 } 2431 2432 @Override 2433 public StyleableProperty<Number> getStyleableProperty(GridPane node) { 2434 return (StyleableProperty<Number>)node.hgapProperty(); 2435 } 2436 2437 }; 2438 2439 private static final CssMetaData<GridPane,Pos> ALIGNMENT = 2440 new CssMetaData<GridPane,Pos>("-fx-alignment", 2441 new EnumConverter<Pos>(Pos.class), Pos.TOP_LEFT) { 2442 2443 @Override 2444 public boolean isSettable(GridPane node) { 2445 return node.alignment == null || !node.alignment.isBound(); 2446 } 2447 2448 @Override 2449 public StyleableProperty<Pos> getStyleableProperty(GridPane node) { 2450 return (StyleableProperty<Pos>)node.alignmentProperty(); 2451 } 2452 2453 }; 2454 2455 private static final CssMetaData<GridPane,Number> VGAP = 2456 new CssMetaData<GridPane,Number>("-fx-vgap", 2457 SizeConverter.getInstance(), 0.0){ 2458 2459 @Override 2460 public boolean isSettable(GridPane node) { 2461 return node.vgap == null || !node.vgap.isBound(); 2462 } 2463 2464 @Override 2465 public StyleableProperty<Number> getStyleableProperty(GridPane node) { 2466 return (StyleableProperty<Number>)node.vgapProperty(); 2467 } 2468 2469 }; 2470 2471 private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; 2472 static { 2473 2474 final List<CssMetaData<? extends Styleable, ?>> styleables = 2475 new ArrayList<CssMetaData<? extends Styleable, ?>>(Region.getClassCssMetaData()); 2476 styleables.add(GRID_LINES_VISIBLE); 2477 styleables.add(HGAP); 2478 styleables.add(ALIGNMENT); 2479 styleables.add(VGAP); 2480 2481 STYLEABLES = Collections.unmodifiableList(styleables); 2482 } 2483 } 2484 2485 /** 2486 * @return The CssMetaData associated with this class, which may include the 2487 * CssMetaData of its superclasses. 2488 * @since JavaFX 8.0 2489 */ 2490 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { 2491 return StyleableProperties.STYLEABLES; 2492 } 2493 2494 /** 2495 * {@inheritDoc} 2496 * 2497 * @since JavaFX 8.0 2498 */ 2499 2500 2501 @Override 2502 public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() { 2503 return getClassCssMetaData(); 2504 } 2505 2506 private static final class Interval implements Comparable<Interval> { 2507 2508 public final int begin; 2509 public final int end; 2510 2511 public Interval(int begin, int end) { 2512 this.begin = begin; 2513 this.end = end; 2514 } 2515 2516 @Override 2517 public int compareTo(Interval o) { 2518 return begin != o.begin ? begin - o.begin : end - o.end; 2519 } 2520 2521 private boolean contains(int position) { 2522 return begin <= position && position < end; 2523 } 2524 2525 private int size() { 2526 return end - begin; 2527 } 2528 2529 } 2530 2531 private static final class CompositeSize implements Cloneable { 2532 2533 // These variables will be modified during the computations 2534 double singleSizes[]; 2535 private SortedMap<Interval, Double> multiSizes; 2536 private BitSet preset; 2537 2538 // Preset metrics for this dimension 2539 private final double fixedPercent[]; 2540 private final double totalFixedPercent; 2541 private final double gap; 2542 2543 public CompositeSize(int capacity, double fixedPercent[], double totalFixedPercent, double gap, double initSize) { 2544 singleSizes = new double[capacity]; 2545 Arrays.fill(singleSizes, initSize); 2546 2547 this.fixedPercent = fixedPercent; 2548 this.totalFixedPercent = totalFixedPercent; 2549 this.gap = gap; 2550 } 2551 2552 private void setSize(int position, double size) { 2553 singleSizes[position] = size; 2554 } 2555 2556 private void setPresetSize(int position, double size) { 2557 setSize(position, size); 2558 if (preset == null) { 2559 preset = new BitSet(singleSizes.length); 2560 } 2561 preset.set(position); 2562 } 2563 2564 private boolean isPreset(int position) { 2565 if (preset == null) { 2566 return false; 2567 } 2568 return preset.get(position); 2569 } 2570 2571 private void addSize(int position, double change) { 2572 singleSizes[position] = singleSizes[position] + change; 2573 } 2574 2575 private double getSize(int position) { 2576 return singleSizes[position]; 2577 } 2578 2579 private void setMaxSize(int position, double size) { 2580 singleSizes[position] = Math.max(singleSizes[position], size); 2581 } 2582 2583 private void setMultiSize(int startPosition, int endPosition, double size) { 2584 if (multiSizes == null) { 2585 multiSizes = new TreeMap<>(); 2586 } 2587 Interval i = new Interval(startPosition, endPosition); 2588 multiSizes.put(i, size); 2589 } 2590 2591 private Iterable<Entry<Interval, Double>> multiSizes() { 2592 if (multiSizes == null) { 2593 return Collections.EMPTY_LIST; 2594 } 2595 return multiSizes.entrySet(); 2596 } 2597 2598 private void setMaxMultiSize(int startPosition, int endPosition, double size) { 2599 if (multiSizes == null) { 2600 multiSizes = new TreeMap<>(); 2601 } 2602 Interval i = new Interval(startPosition, endPosition); 2603 Double sz = multiSizes.get(i); 2604 if (sz == null) { 2605 multiSizes.put(i, size); 2606 } else { 2607 multiSizes.put(i, Math.max(size, sz)); 2608 } 2609 } 2610 2611 private double getProportionalMinOrMaxSize(int position, boolean min) { 2612 double result = singleSizes[position]; 2613 if (!isPreset(position) && multiSizes != null) { 2614 for (Interval i : multiSizes.keySet()) { 2615 if (i.contains(position)) { 2616 double segment = multiSizes.get(i) / i.size(); 2617 double propSize = segment; 2618 for (int j = i.begin; j < i.end; ++j) { 2619 if (j != position) { 2620 if (min ? singleSizes[j] > segment : singleSizes[j] < segment) { 2621 propSize += segment - singleSizes[j]; 2622 } 2623 } 2624 } 2625 result = min ? Math.max(result, propSize) : Math.min(result, propSize); 2626 } 2627 } 2628 } 2629 return result; 2630 } 2631 2632 private double computeTotal(final int from, final int to) { 2633 double total = gap * (to - from - 1); 2634 for (int i = from; i < to; ++i) { 2635 total += singleSizes[i]; 2636 } 2637 return total; 2638 } 2639 2640 private double computeTotal() { 2641 return computeTotal(0, singleSizes.length); 2642 } 2643 2644 private boolean allPreset(int begin, int end) { 2645 if (preset == null) { 2646 return false; 2647 } 2648 for (int i = begin; i < end; ++i) { 2649 if (!preset.get(i)) { 2650 return false; 2651 } 2652 } 2653 return true; 2654 } 2655 2656 private double computeTotalWithMultiSize() { 2657 double total = computeTotal(); 2658 if (multiSizes != null) { 2659 for (Entry<Interval, Double> e: multiSizes.entrySet()) { 2660 final Interval i = e.getKey(); 2661 if (!allPreset(i.begin, i.end)) { 2662 double subTotal = computeTotal(i.begin, i.end); 2663 if (e.getValue() > subTotal) { 2664 total += e.getValue() - subTotal; 2665 } 2666 } 2667 } 2668 } 2669 if (totalFixedPercent > 0) { 2670 double totalNotFixed = 0; 2671 // First, remove the sizes that are fixed to be 0 2672 for (int i = 0; i < fixedPercent.length; ++i) { 2673 if (fixedPercent[i] == 0) { 2674 total -= singleSizes[i]; 2675 } 2676 } 2677 for (int i = 0; i < fixedPercent.length; ++i) { 2678 if (fixedPercent[i] > 0) { 2679 // Grow the total so that every size at it's value corresponds at least to it's fixedPercent of the total 2680 // i.e. total * fixedPercent[i] >= singleSizes[i] 2681 total = Math.max(total, singleSizes[i] * (100 / fixedPercent[i])); 2682 } else if (fixedPercent[i] < 0){ 2683 totalNotFixed += singleSizes[i]; 2684 } 2685 } 2686 if (totalFixedPercent < 100) { 2687 total = Math.max(total, totalNotFixed * 100 / (100 - totalFixedPercent)); 2688 } 2689 } 2690 return total; 2691 } 2692 2693 private int getLength() { 2694 return singleSizes.length; 2695 } 2696 2697 @Override 2698 protected Object clone() { 2699 try { 2700 CompositeSize clone = (CompositeSize) super.clone(); 2701 clone.singleSizes = clone.singleSizes.clone(); 2702 if (multiSizes != null) 2703 clone.multiSizes = new TreeMap<>(clone.multiSizes); 2704 return clone; 2705 } catch (CloneNotSupportedException ex) { 2706 throw new RuntimeException(ex); 2707 } 2708 } 2709 2710 private double[] asArray() { 2711 return singleSizes; 2712 } 2713 2714 } 2715 2716 /** 2717 * Returns the number of rows in this GridPane. 2718 * 2719 * @return the row count 2720 * @since 9 2721 */ 2722 public final int getRowCount() { 2723 int nRows = this.getRowConstraints().size(); 2724 for (int i = 0; i < this.getChildren().size(); i++) { 2725 Node child = this.getChildren().get(i); 2726 if (child.isManaged()) { 2727 int rowIndex = GridPane.getNodeRowIndex(child); 2728 int rowEnd = GridPane.getNodeRowEnd(child); 2729 nRows = Math.max(nRows, (rowEnd != GridPane.REMAINING? rowEnd : rowIndex) + 1); 2730 } 2731 } 2732 return nRows; 2733 } 2734 2735 /** 2736 * Returns the number of columns in this GridPane. 2737 * 2738 * @return the column count 2739 * @since 9 2740 */ 2741 public final int getColumnCount() { 2742 int nColumns = this.getColumnConstraints().size(); 2743 for (int i = 0; i < this.getChildren().size(); i++) { 2744 Node child = this.getChildren().get(i); 2745 if (child.isManaged()) { 2746 int columnIndex = GridPane.getNodeColumnIndex(child); 2747 int columnEnd = GridPane.getNodeColumnEnd(child); 2748 nColumns = Math.max(nColumns, (columnEnd != GridPane.REMAINING? columnEnd : columnIndex) + 1); 2749 } 2750 } 2751 return nColumns; 2752 } 2753 2754 /** 2755 * Returns the bounds of the cell at the specified column and row position. 2756 * 2757 * @param columnIndex the column index position for the cell within this 2758 * GridPane, counting from 0 2759 * @param rowIndex the row index position for the cell within this GridPane, 2760 * counting from 0 2761 * @return the bounds of the cell at columnIndex and rowIndex. 2762 * @since 9 2763 */ 2764 public final Bounds getCellBounds(int columnIndex, int rowIndex) { 2765 final double snaphgap = this.snapSpaceX(this.getHgap()); 2766 final double snapvgap = this.snapSpaceY(this.getVgap()); 2767 final double top = this.snapSpaceY(this.getInsets().getTop()); 2768 final double right = this.snapSpaceX(this.getInsets().getRight()); 2769 final double bottom = this.snapSpaceY(this.getInsets().getBottom()); 2770 final double left = this.snapSpaceX(this.getInsets().getLeft()); 2771 final double gridPaneHeight = this.snapSizeY(this.getHeight()) - (top + bottom); 2772 final double gridPaneWidth = this.snapSizeX(this.getWidth()) - (left + right); 2773 2774 // Compute grid. Result contains two double arrays, first for columns, second for rows 2775 double[] columnWidths; 2776 double[] rowHeights; 2777 2778 double[][] grid = this.getGrid(); 2779 if (grid == null) { 2780 rowHeights = new double[] {0}; 2781 rowIndex = 0; 2782 columnWidths = new double[] {0}; 2783 columnIndex = 0; 2784 } else { 2785 columnWidths = grid[0]; 2786 rowHeights = grid[1]; 2787 } 2788 2789 // Compute the total row height 2790 double rowTotal = 0; 2791 for (int i = 0; i < rowHeights.length; i++) { 2792 rowTotal += rowHeights[i]; 2793 } 2794 rowTotal += ((rowHeights.length - 1) * snapvgap); 2795 2796 // Adjust for alignment 2797 double minY = top + Region.computeYOffset(gridPaneHeight, rowTotal, this.getAlignment().getVpos()); 2798 double height = rowHeights[rowIndex]; 2799 for (int j = 0; j < rowIndex; j++) { 2800 minY += rowHeights[j] + snapvgap; 2801 } 2802 2803 // Compute the total column width 2804 double columnTotal = 0; 2805 for (int i = 0; i < columnWidths.length; i++) { 2806 columnTotal += columnWidths[i]; 2807 } 2808 columnTotal += ((columnWidths.length - 1) * snaphgap); 2809 2810 // Adjust for alignment 2811 double minX = left + Region.computeXOffset(gridPaneWidth, columnTotal, this.getAlignment().getHpos()); 2812 double width = columnWidths[columnIndex]; 2813 for (int j = 0; j < columnIndex; j++) { 2814 minX += columnWidths[j] + snaphgap; 2815 } 2816 2817 return new BoundingBox(minX, minY, width, height); 2818 } 2819 2820 }