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