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