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