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