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}