1 /* 2 * Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javafx.scene.control.skin; 27 28 29 import java.lang.ref.Reference; 30 import java.lang.ref.WeakReference; 31 import java.util.*; 32 33 import com.sun.javafx.PlatformUtil; 34 import javafx.animation.FadeTransition; 35 import javafx.beans.property.DoubleProperty; 36 import javafx.beans.property.ObjectProperty; 37 import javafx.collections.ListChangeListener; 38 import javafx.collections.ObservableList; 39 import javafx.collections.WeakListChangeListener; 40 import javafx.css.StyleOrigin; 41 import javafx.css.StyleableObjectProperty; 42 import javafx.geometry.Pos; 43 import javafx.scene.Node; 44 import javafx.scene.Parent; 45 import javafx.scene.control.Control; 46 import javafx.scene.control.IndexedCell; 47 import javafx.scene.control.TableColumnBase; 48 import javafx.util.Duration; 49 50 import com.sun.javafx.tk.Toolkit; 51 52 /** 53 * TableRowSkinBase is the base skin class used by controls such as 54 * {@link javafx.scene.control.TableRow} and {@link javafx.scene.control.TreeTableRow} 55 * (the concrete classes are {@link TableRowSkin} and {@link TreeTableRowSkin}, 56 * respectively). 57 * 58 * @param <T> The type of the cell (i.e. the generic type of the {@link IndexedCell} subclass). 59 * @param <C> The cell type (e.g. TableRow or TreeTableRow) 60 * @param <R> The type of cell that is contained within each row (e.g. 61 * {@link javafx.scene.control.TableCell or {@link javafx.scene.control.TreeTableCell}}). 62 * 63 * @since 9 64 * @see javafx.scene.control.TableRow 65 * @see javafx.scene.control.TreeTableRow 66 * @see TableRowSkin 67 * @see TreeTableRowSkin 68 */ 69 public abstract class TableRowSkinBase<T, 70 C extends IndexedCell/*<T>*/, 71 R extends IndexedCell> extends CellSkinBase<C> { 72 73 /*************************************************************************** 74 * * 75 * Static Fields * 76 * * 77 **************************************************************************/ 78 79 // There appears to be a memory leak when using the stub toolkit. Therefore, 80 // to prevent tests from failing we disable the animations below when the 81 // stub toolkit is being used. 82 // Filed as RT-29163. 83 private static boolean IS_STUB_TOOLKIT = Toolkit.getToolkit().toString().contains("StubToolkit"); 84 85 // lets save the CPU and not do animations when on embedded platforms 86 private static boolean DO_ANIMATIONS = ! IS_STUB_TOOLKIT && ! PlatformUtil.isEmbedded(); 87 88 private static final Duration FADE_DURATION = Duration.millis(200); 89 90 /* 91 * This is rather hacky - but it is a quick workaround to resolve the 92 * issue that we don't know maximum width of a disclosure node for a given 93 * TreeView. If we don't know the maximum width, we have no way to ensure 94 * consistent indentation for a given TreeView. 95 * 96 * To work around this, we create a single WeakHashMap to store a max 97 * disclosureNode width per TreeView. We use WeakHashMap to help prevent 98 * any memory leaks. 99 */ 100 static final Map<Control, Double> maxDisclosureWidthMap = new WeakHashMap<Control, Double>(); 101 102 // Specifies the number of times we will call 'recreateCells()' before we blow 103 // out the cellsMap structure and rebuild all cells. This helps to prevent 104 // against memory leaks in certain extreme circumstances. 105 private static final int DEFAULT_FULL_REFRESH_COUNTER = 100; 106 107 108 109 /*************************************************************************** 110 * * 111 * Private Fields * 112 * * 113 **************************************************************************/ 114 115 /* 116 * A map that maps from TableColumn to TableCell (i.e. model to view). 117 * This is recreated whenever the leaf columns change, however to increase 118 * efficiency we create cells for all columns, even if they aren't visible, 119 * and we only create new cells if we don't already have it cached in this 120 * map. 121 * 122 * Note that this means that it is possible for this map to therefore be 123 * a memory leak if an application uses TableView and is creating and removing 124 * a large number of tableColumns. This is mitigated in the recreateCells() 125 * function below - refer to that to learn more. 126 */ 127 WeakHashMap<TableColumnBase, Reference<R>> cellsMap; 128 129 // This observableArrayList contains the currently visible table cells for this row. 130 final List<R> cells = new ArrayList<>(); 131 132 private int fullRefreshCounter = DEFAULT_FULL_REFRESH_COUNTER; 133 134 boolean isDirty = false; 135 boolean updateCells = false; 136 137 private double fixedCellSize; 138 private boolean fixedCellSizeEnabled; 139 140 141 142 /*************************************************************************** 143 * * 144 * Constructors * 145 * * 146 **************************************************************************/ 147 148 /** 149 * Creates a new instance of TableRowSkinBase, although note that this 150 * instance does not handle any behavior / input mappings - this needs to be 151 * handled appropriately by subclasses. 152 * 153 * @param control The control that this skin should be installed onto. 154 */ 155 public TableRowSkinBase(C control) { 156 super(control); 157 getSkinnable().setPickOnBounds(false); 158 159 recreateCells(); 160 updateCells(true); 161 162 // init bindings 163 // watches for any change in the leaf columns observableArrayList - this will indicate 164 // that the column order has changed and that we should update the row 165 // such that the cells are in the new order 166 getVisibleLeafColumns().addListener(weakVisibleLeafColumnsListener); 167 // --- end init bindings 168 169 170 // use invalidation listener here to update even when item equality is true 171 // (e.g. see RT-22463) 172 control.itemProperty().addListener(o -> requestCellUpdate()); 173 registerChangeListener(control.indexProperty(), e -> { 174 // Fix for RT-36661, where empty table cells were showing content, as they 175 // had incorrect table cell indices (but the table row index was correct). 176 // Note that we only do the update on empty cells to avoid the issue 177 // noted below in requestCellUpdate(). 178 if (getSkinnable().isEmpty()) { 179 requestCellUpdate(); 180 } 181 }); 182 183 if (fixedCellSizeProperty() != null) { 184 registerChangeListener(fixedCellSizeProperty(), e -> { 185 fixedCellSize = fixedCellSizeProperty().get(); 186 fixedCellSizeEnabled = fixedCellSize > 0; 187 }); 188 fixedCellSize = fixedCellSizeProperty().get(); 189 fixedCellSizeEnabled = fixedCellSize > 0; 190 } 191 } 192 193 194 195 /*************************************************************************** 196 * * 197 * Listeners * 198 * * 199 **************************************************************************/ 200 201 private ListChangeListener<TableColumnBase> visibleLeafColumnsListener = c -> { 202 isDirty = true; 203 getSkinnable().requestLayout(); 204 }; 205 206 private WeakListChangeListener<TableColumnBase> weakVisibleLeafColumnsListener = 207 new WeakListChangeListener<TableColumnBase>(visibleLeafColumnsListener); 208 209 210 211 /*************************************************************************** 212 * * 213 * Abstract Methods * 214 * * 215 **************************************************************************/ 216 217 /** 218 * Returns the graphic to draw on the inside of the disclosure node. Null 219 * is acceptable when no graphic should be shown. Commonly this is the 220 * graphic associated with a TreeItem (i.e. treeItem.getGraphic()), rather 221 * than a graphic associated with a cell. 222 */ 223 abstract ObjectProperty<Node> graphicProperty(); 224 225 // return TableView / TreeTableView / etc 226 abstract Control getVirtualFlowOwner(); 227 228 abstract ObservableList<? extends TableColumnBase/*<T,?>*/> getVisibleLeafColumns(); 229 230 // cell.updateTableRow(skinnable); (i.e cell.updateTableRow(row)) 231 abstract void updateCell(R cell, C row); 232 233 abstract DoubleProperty fixedCellSizeProperty(); 234 235 abstract boolean isColumnPartiallyOrFullyVisible(TableColumnBase tc); 236 237 abstract R getCell(TableColumnBase tc); 238 239 abstract TableColumnBase<T,?> getTableColumnBase(R cell); 240 241 242 243 /*************************************************************************** 244 * * 245 * Public Methods * 246 * * 247 **************************************************************************/ 248 249 /** {@inheritDoc} */ 250 @Override protected void layoutChildren(double x, final double y, final double w, final double h) { 251 checkState(); 252 if (cellsMap.isEmpty()) return; 253 254 ObservableList<? extends TableColumnBase> visibleLeafColumns = getVisibleLeafColumns(); 255 if (visibleLeafColumns.isEmpty()) { 256 super.layoutChildren(x,y,w,h); 257 return; 258 } 259 260 C control = getSkinnable(); 261 262 /////////////////////////////////////////// 263 // indentation code starts here 264 /////////////////////////////////////////// 265 double leftMargin = 0; 266 double disclosureWidth = 0; 267 double graphicWidth = 0; 268 boolean indentationRequired = isIndentationRequired(); 269 boolean disclosureVisible = isDisclosureNodeVisible(); 270 int indentationColumnIndex = 0; 271 Node disclosureNode = null; 272 if (indentationRequired) { 273 // Determine the column in which we want to put the disclosure node. 274 // By default it is null, which means the 0th column should be 275 // where the indentation occurs. 276 TableColumnBase<?,?> treeColumn = getTreeColumn(); 277 indentationColumnIndex = treeColumn == null ? 0 : visibleLeafColumns.indexOf(treeColumn); 278 indentationColumnIndex = indentationColumnIndex < 0 ? 0 : indentationColumnIndex; 279 280 int indentationLevel = getIndentationLevel(control); 281 if (! isShowRoot()) indentationLevel--; 282 final double indentationPerLevel = getIndentationPerLevel(); 283 leftMargin = indentationLevel * indentationPerLevel; 284 285 // position the disclosure node so that it is at the proper indent 286 Control c = getVirtualFlowOwner(); 287 final double defaultDisclosureWidth = maxDisclosureWidthMap.containsKey(c) ? 288 maxDisclosureWidthMap.get(c) : 0; 289 disclosureWidth = defaultDisclosureWidth; 290 291 disclosureNode = getDisclosureNode(); 292 if (disclosureNode != null) { 293 disclosureNode.setVisible(disclosureVisible); 294 295 if (disclosureVisible) { 296 disclosureWidth = disclosureNode.prefWidth(h); 297 if (disclosureWidth > defaultDisclosureWidth) { 298 maxDisclosureWidthMap.put(c, disclosureWidth); 299 300 // RT-36359: The recorded max width of the disclosure node 301 // has increased. We need to go back and request all 302 // earlier rows to update themselves to take into account 303 // this increased indentation. 304 final VirtualFlow<C> flow = getVirtualFlow(); 305 final int thisIndex = getSkinnable().getIndex(); 306 for (int i = 0; i < flow.cells.size(); i++) { 307 C cell = flow.cells.get(i); 308 if (cell == null || cell.isEmpty()) continue; 309 cell.requestLayout(); 310 cell.layout(); 311 } 312 } 313 } 314 } 315 } 316 /////////////////////////////////////////// 317 // indentation code ends here 318 /////////////////////////////////////////// 319 320 // layout the individual column cells 321 double width; 322 double height; 323 324 final double verticalPadding = snappedTopInset() + snappedBottomInset(); 325 final double horizontalPadding = snappedLeftInset() + snappedRightInset(); 326 final double controlHeight = control.getHeight(); 327 328 /** 329 * RT-26743:TreeTableView: Vertical Line looks unfinished. 330 * We used to not do layout on cells whose row exceeded the number 331 * of items, but now we do so as to ensure we get vertical lines 332 * where expected in cases where the vertical height exceeds the 333 * number of items. 334 */ 335 int index = control.getIndex(); 336 if (index < 0/* || row >= itemsProperty().get().size()*/) return; 337 338 for (int column = 0, max = cells.size(); column < max; column++) { 339 R tableCell = cells.get(column); 340 TableColumnBase<T, ?> tableColumn = getTableColumnBase(tableCell); 341 342 boolean isVisible = true; 343 if (fixedCellSizeEnabled) { 344 // we determine if the cell is visible, and if not we have the 345 // ability to take it out of the scenegraph to help improve 346 // performance. However, we only do this when there is a 347 // fixed cell length specified in the TableView. This is because 348 // when we have a fixed cell length it is possible to know with 349 // certainty the height of each TableCell - it is the fixed value 350 // provided by the developer, and this means that we do not have 351 // to concern ourselves with the possibility that the height 352 // may be variable and / or dynamic. 353 isVisible = isColumnPartiallyOrFullyVisible(tableColumn); 354 355 height = fixedCellSize; 356 } else { 357 height = Math.max(controlHeight, tableCell.prefHeight(-1)); 358 height = snapSize(height) - snapSize(verticalPadding); 359 } 360 361 if (isVisible) { 362 if (fixedCellSizeEnabled && tableCell.getParent() == null) { 363 getChildren().add(tableCell); 364 } 365 366 width = snapSize(tableCell.prefWidth(-1)) - snapSize(horizontalPadding); 367 368 // Added for RT-32700, and then updated for RT-34074. 369 // We change the alignment from CENTER_LEFT to TOP_LEFT if the 370 // height of the row is greater than the default size, and if 371 // the alignment is the default alignment. 372 // What I would rather do is only change the alignment if the 373 // alignment has not been manually changed, but for now this will 374 // do. 375 final boolean centreContent = h <= 24.0; 376 377 // if the style origin is null then the property has not been 378 // set (or it has been reset to its default), which means that 379 // we can set it without overwriting someone elses settings. 380 final StyleOrigin origin = ((StyleableObjectProperty<?>) tableCell.alignmentProperty()).getStyleOrigin(); 381 if (! centreContent && origin == null) { 382 tableCell.setAlignment(Pos.TOP_LEFT); 383 } 384 // --- end of RT-32700 fix 385 386 /////////////////////////////////////////// 387 // further indentation code starts here 388 /////////////////////////////////////////// 389 if (indentationRequired && column == indentationColumnIndex) { 390 if (disclosureVisible) { 391 double ph = disclosureNode.prefHeight(disclosureWidth); 392 393 if (width > 0 && width < (disclosureWidth + leftMargin)) { 394 fadeOut(disclosureNode); 395 } else { 396 fadeIn(disclosureNode); 397 disclosureNode.resize(disclosureWidth, ph); 398 399 disclosureNode.relocate(x + leftMargin, 400 centreContent ? (h / 2.0 - ph / 2.0) : 401 (y + tableCell.getPadding().getTop())); 402 disclosureNode.toFront(); 403 } 404 } 405 406 // determine starting point of the graphic or cell node, and the 407 // remaining width available to them 408 ObjectProperty<Node> graphicProperty = graphicProperty(); 409 Node graphic = graphicProperty == null ? null : graphicProperty.get(); 410 411 if (graphic != null) { 412 graphicWidth = graphic.prefWidth(-1) + 3; 413 double ph = graphic.prefHeight(graphicWidth); 414 415 if (width > 0 && width < disclosureWidth + leftMargin + graphicWidth) { 416 fadeOut(graphic); 417 } else { 418 fadeIn(graphic); 419 420 graphic.relocate(x + leftMargin + disclosureWidth, 421 centreContent ? (h / 2.0 - ph / 2.0) : 422 (y + tableCell.getPadding().getTop())); 423 424 graphic.toFront(); 425 } 426 } 427 } 428 /////////////////////////////////////////// 429 // further indentation code ends here 430 /////////////////////////////////////////// 431 432 tableCell.resize(width, height); 433 tableCell.relocate(x, snappedTopInset()); 434 435 // Request layout is here as (partial) fix for RT-28684. 436 // This does not appear to impact performance... 437 tableCell.requestLayout(); 438 } else { 439 if (fixedCellSizeEnabled) { 440 // we only add/remove to the scenegraph if the fixed cell 441 // length support is enabled - otherwise we keep all 442 // TableCells in the scenegraph 443 getChildren().remove(tableCell); 444 } 445 446 width = snapSize(tableCell.prefWidth(-1)) - snapSize(horizontalPadding); 447 } 448 449 x += width; 450 } 451 } 452 453 int getIndentationLevel(C control) { 454 return 0; 455 } 456 457 double getIndentationPerLevel() { 458 return 0; 459 } 460 461 /** 462 * Used to represent whether the current virtual flow owner is wanting 463 * indentation to be used in this table row. 464 */ 465 boolean isIndentationRequired() { 466 return false; 467 } 468 469 /** 470 * Returns the table column that should show the disclosure nodes and / or 471 * a graphic. By default this is the left-most column. 472 */ 473 TableColumnBase getTreeColumn() { 474 return null; 475 } 476 477 Node getDisclosureNode() { 478 return null; 479 } 480 481 /** 482 * Used to represent whether a disclosure node is visible for _this_ 483 * table row. Not to be confused with isIndentationRequired(), which is the 484 * more general API. 485 */ 486 boolean isDisclosureNodeVisible() { 487 return false; 488 } 489 490 boolean isShowRoot() { 491 return true; 492 } 493 494 TableColumnBase<T,?> getVisibleLeafColumn(int column) { 495 final List<? extends TableColumnBase/*<T,?>*/> visibleLeafColumns = getVisibleLeafColumns(); 496 if (column < 0 || column >= visibleLeafColumns.size()) return null; 497 return visibleLeafColumns.get(column); 498 } 499 500 void updateCells(boolean resetChildren) { 501 // To avoid a potential memory leak (when the TableColumns in the 502 // TableView are created/inserted/removed/deleted, we have a 'refresh 503 // counter' that when we reach 0 will delete all cells in this row 504 // and recreate all of them. 505 if (resetChildren) { 506 if (fullRefreshCounter == 0) { 507 recreateCells(); 508 } 509 fullRefreshCounter--; 510 } 511 512 // if clear isn't called first, we can run into situations where the 513 // cells aren't updated properly. 514 final boolean cellsEmpty = cells.isEmpty(); 515 cells.clear(); 516 517 final C skinnable = getSkinnable(); 518 final int skinnableIndex = skinnable.getIndex(); 519 final List<? extends TableColumnBase/*<T,?>*/> visibleLeafColumns = getVisibleLeafColumns(); 520 521 for (int i = 0, max = visibleLeafColumns.size(); i < max; i++) { 522 TableColumnBase<T,?> col = visibleLeafColumns.get(i); 523 524 R cell = null; 525 if (cellsMap.containsKey(col)) { 526 cell = cellsMap.get(col).get(); 527 528 // the reference has been gc'd, remove key entry from map 529 if (cell == null) { 530 cellsMap.remove(col); 531 } 532 } 533 534 if (cell == null) { 535 // if the cell is null it means we don't have it in cache and 536 // need to create it 537 cell = createCell(col); 538 } 539 540 updateCell(cell, skinnable); 541 cell.updateIndex(skinnableIndex); 542 cells.add(cell); 543 } 544 545 // update children of each row 546 if (!fixedCellSizeEnabled && (resetChildren || cellsEmpty)) { 547 getChildren().setAll(cells); 548 } 549 } 550 551 private VirtualFlow<C> getVirtualFlow() { 552 Parent p = getSkinnable(); 553 while (p != null) { 554 if (p instanceof VirtualFlow) { 555 return (VirtualFlow<C>) p; 556 } 557 p = p.getParent(); 558 } 559 return null; 560 } 561 562 @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { 563 double prefWidth = 0.0; 564 565 final List<? extends TableColumnBase/*<T,?>*/> visibleLeafColumns = getVisibleLeafColumns(); 566 for (int i = 0, max = visibleLeafColumns.size(); i < max; i++) { 567 prefWidth += visibleLeafColumns.get(i).getWidth(); 568 } 569 570 return prefWidth; 571 } 572 573 @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { 574 if (fixedCellSizeEnabled) { 575 return fixedCellSize; 576 } 577 578 // fix for RT-29080 579 checkState(); 580 581 // Support for RT-18467: making it easier to specify a height for 582 // cells via CSS, where the desired height is less than the height 583 // of the TableCells. Essentially, -fx-cell-size is given higher 584 // precedence now 585 if (getCellSize() < DEFAULT_CELL_SIZE) { 586 return getCellSize(); 587 } 588 589 // FIXME according to profiling, this method is slow and should 590 // be optimised 591 double prefHeight = 0.0f; 592 final int count = cells.size(); 593 for (int i=0; i<count; i++) { 594 final R tableCell = cells.get(i); 595 prefHeight = Math.max(prefHeight, tableCell.prefHeight(-1)); 596 } 597 double ph = Math.max(prefHeight, Math.max(getCellSize(), getSkinnable().minHeight(-1))); 598 599 return ph; 600 } 601 602 @Override protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { 603 if (fixedCellSizeEnabled) { 604 return fixedCellSize; 605 } 606 607 // fix for RT-29080 608 checkState(); 609 610 // Support for RT-18467: making it easier to specify a height for 611 // cells via CSS, where the desired height is less than the height 612 // of the TableCells. Essentially, -fx-cell-size is given higher 613 // precedence now 614 if (getCellSize() < DEFAULT_CELL_SIZE) { 615 return getCellSize(); 616 } 617 618 // FIXME according to profiling, this method is slow and should 619 // be optimised 620 double minHeight = 0.0f; 621 final int count = cells.size(); 622 for (int i = 0; i < count; i++) { 623 final R tableCell = cells.get(i); 624 minHeight = Math.max(minHeight, tableCell.minHeight(-1)); 625 } 626 return minHeight; 627 } 628 629 @Override protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { 630 if (fixedCellSizeEnabled) { 631 return fixedCellSize; 632 } 633 return super.computeMaxHeight(width, topInset, rightInset, bottomInset, leftInset); 634 } 635 636 final void checkState() { 637 if (isDirty) { 638 updateCells(true); 639 isDirty = false; 640 } else if (updateCells) { 641 updateCells(false); 642 updateCells = false; 643 } 644 } 645 646 647 648 /*************************************************************************** 649 * * 650 * Private Implementation * 651 * * 652 **************************************************************************/ 653 654 private void requestCellUpdate() { 655 updateCells = true; 656 getSkinnable().requestLayout(); 657 658 // update the index of all children cells (RT-29849). 659 // Note that we do this after the TableRow item has been updated, 660 // rather than when the TableRow index has changed (as this will be 661 // before the row has updated its item). This will result in the 662 // issue highlighted in RT-33602, where the table cell had the correct 663 // item whilst the row had the old item. 664 final int newIndex = getSkinnable().getIndex(); 665 for (int i = 0, max = cells.size(); i < max; i++) { 666 cells.get(i).updateIndex(newIndex); 667 } 668 } 669 670 private void recreateCells() { 671 if (cellsMap != null) { 672 Collection<Reference<R>> cells = cellsMap.values(); 673 Iterator<Reference<R>> cellsIter = cells.iterator(); 674 while (cellsIter.hasNext()) { 675 Reference<R> cellRef = cellsIter.next(); 676 R cell = cellRef.get(); 677 if (cell != null) { 678 cell.updateIndex(-1); 679 cell.getSkin().dispose(); 680 cell.setSkin(null); 681 } 682 } 683 cellsMap.clear(); 684 } 685 686 ObservableList<? extends TableColumnBase/*<T,?>*/> columns = getVisibleLeafColumns(); 687 688 cellsMap = new WeakHashMap<TableColumnBase, Reference<R>>(columns.size()); 689 fullRefreshCounter = DEFAULT_FULL_REFRESH_COUNTER; 690 getChildren().clear(); 691 692 for (TableColumnBase col : columns) { 693 if (cellsMap.containsKey(col)) { 694 continue; 695 } 696 697 // create a TableCell for this column and store it in the cellsMap 698 // for future use 699 createCell(col); 700 } 701 } 702 703 private R createCell(TableColumnBase col) { 704 // we must create a TableCell for this table column 705 R cell = getCell(col); 706 707 // and store this in our HashMap until needed 708 cellsMap.put(col, new WeakReference<R>(cell)); 709 710 return cell; 711 } 712 713 private void fadeOut(final Node node) { 714 if (node.getOpacity() < 1.0) return; 715 716 if (! DO_ANIMATIONS) { 717 node.setOpacity(0); 718 return; 719 } 720 721 final FadeTransition fader = new FadeTransition(FADE_DURATION, node); 722 fader.setToValue(0.0); 723 fader.play(); 724 } 725 726 private void fadeIn(final Node node) { 727 if (node.getOpacity() > 0.0) return; 728 729 if (! DO_ANIMATIONS) { 730 node.setOpacity(1); 731 return; 732 } 733 734 final FadeTransition fader = new FadeTransition(FADE_DURATION, node); 735 fader.setToValue(1.0); 736 fader.play(); 737 } 738 }