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