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 */ 210 protected abstract R createCell(TableColumnBase<T,?> tc); 211 212 /** 213 * A method to allow the given cell to be told that it is a member of the given row. 214 * How this is implemented is dependent on the actual cell implementation. 215 * @param cell The cell for which we want to inform it of its owner row. 216 * @param row The row which will be set on the given cell. 217 */ 218 protected abstract void updateCell(R cell, C row); 219 220 /** 221 * Returns the {@link TableColumnBase} instance for the given cell instance. 222 * @param cell The cell for which a TableColumn is desired. 223 */ 224 protected abstract TableColumnBase<T,?> getTableColumn(R cell); 225 226 /** 227 * Returns an unmodifiable list containing the currently visible leaf columns. 228 */ 229 protected abstract ObservableList<? extends TableColumnBase/*<T,?>*/> getVisibleLeafColumns(); 230 231 232 233 /*************************************************************************** 234 * * 235 * Public Methods * 236 * * 237 **************************************************************************/ 238 239 /** 240 * Returns the graphic to draw on the inside of the disclosure node. Null 241 * is acceptable when no graphic should be shown. Commonly this is the 242 * graphic associated with a TreeItem (i.e. treeItem.getGraphic()), rather 243 * than a graphic associated with a cell. 244 */ 245 protected ObjectProperty<Node> graphicProperty() { 246 return null; 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 final double defaultDisclosureWidth = maxDisclosureWidthMap.containsKey(treeColumn) ? 287 maxDisclosureWidthMap.get(treeColumn) : 0; 288 disclosureWidth = defaultDisclosureWidth; 289 290 disclosureNode = getDisclosureNode(); 291 if (disclosureNode != null) { 292 disclosureNode.setVisible(disclosureVisible); 293 294 if (disclosureVisible) { 295 disclosureWidth = disclosureNode.prefWidth(h); 296 if (disclosureWidth > defaultDisclosureWidth) { 297 maxDisclosureWidthMap.put(treeColumn, disclosureWidth); 298 299 // RT-36359: The recorded max width of the disclosure node 300 // has increased. We need to go back and request all 301 // earlier rows to update themselves to take into account 302 // this increased indentation. 303 final VirtualFlow<C> flow = getVirtualFlow(); 304 final int thisIndex = getSkinnable().getIndex(); 305 for (int i = 0; i < flow.cells.size(); i++) { 306 C cell = flow.cells.get(i); 307 if (cell == null || cell.isEmpty()) continue; 308 cell.requestLayout(); 309 cell.layout(); 310 } 311 } 312 } 313 } 314 } 315 /////////////////////////////////////////// 316 // indentation code ends here 317 /////////////////////////////////////////// 318 319 // layout the individual column cells 320 double width; 321 double height; 322 323 final double verticalPadding = snappedTopInset() + snappedBottomInset(); 324 final double horizontalPadding = snappedLeftInset() + snappedRightInset(); 325 final double controlHeight = control.getHeight(); 326 327 /** 328 * RT-26743:TreeTableView: Vertical Line looks unfinished. 329 * We used to not do layout on cells whose row exceeded the number 330 * of items, but now we do so as to ensure we get vertical lines 331 * where expected in cases where the vertical height exceeds the 332 * number of items. 333 */ 334 int index = control.getIndex(); 335 if (index < 0/* || row >= itemsProperty().get().size()*/) return; 336 337 for (int column = 0, max = cells.size(); column < max; column++) { 338 R tableCell = cells.get(column); 339 TableColumnBase<T, ?> tableColumn = getTableColumn(tableCell); 340 341 boolean isVisible = true; 342 if (fixedCellSizeEnabled) { 343 // we determine if the cell is visible, and if not we have the 344 // ability to take it out of the scenegraph to help improve 345 // performance. However, we only do this when there is a 346 // fixed cell length specified in the TableView. This is because 347 // when we have a fixed cell length it is possible to know with 348 // certainty the height of each TableCell - it is the fixed value 349 // provided by the developer, and this means that we do not have 350 // to concern ourselves with the possibility that the height 351 // may be variable and / or dynamic. 352 isVisible = isColumnPartiallyOrFullyVisible(tableColumn); 353 354 height = fixedCellSize; 355 } else { 356 height = Math.max(controlHeight, tableCell.prefHeight(-1)); 357 height = snapSize(height) - snapSize(verticalPadding); 358 } 359 360 if (isVisible) { 361 if (fixedCellSizeEnabled && tableCell.getParent() == null) { 362 getChildren().add(tableCell); 363 } 364 365 width = tableCell.prefWidth(height) - snapSize(horizontalPadding); 366 367 // Added for RT-32700, and then updated for RT-34074. 368 // We change the alignment from CENTER_LEFT to TOP_LEFT if the 369 // height of the row is greater than the default size, and if 370 // the alignment is the default alignment. 371 // What I would rather do is only change the alignment if the 372 // alignment has not been manually changed, but for now this will 373 // do. 374 final boolean centreContent = h <= 24.0; 375 376 // if the style origin is null then the property has not been 377 // set (or it has been reset to its default), which means that 378 // we can set it without overwriting someone elses settings. 379 final StyleOrigin origin = ((StyleableObjectProperty<?>) tableCell.alignmentProperty()).getStyleOrigin(); 380 if (! centreContent && origin == null) { 381 tableCell.setAlignment(Pos.TOP_LEFT); 382 } 383 // --- end of RT-32700 fix 384 385 /////////////////////////////////////////// 386 // further indentation code starts here 387 /////////////////////////////////////////// 388 if (indentationRequired && column == indentationColumnIndex) { 389 if (disclosureVisible) { 390 double ph = disclosureNode.prefHeight(disclosureWidth); 391 392 if (width > 0 && width < (disclosureWidth + leftMargin)) { 393 fadeOut(disclosureNode); 394 } else { 395 fadeIn(disclosureNode); 396 disclosureNode.resize(disclosureWidth, ph); 397 398 disclosureNode.relocate(x + leftMargin, 399 centreContent ? (h / 2.0 - ph / 2.0) : 400 (y + tableCell.getPadding().getTop())); 401 disclosureNode.toFront(); 402 } 403 } 404 405 // determine starting point of the graphic or cell node, and the 406 // remaining width available to them 407 ObjectProperty<Node> graphicProperty = graphicProperty(); 408 Node graphic = graphicProperty == null ? null : graphicProperty.get(); 409 410 if (graphic != null) { 411 graphicWidth = graphic.prefWidth(-1) + 3; 412 double ph = graphic.prefHeight(graphicWidth); 413 414 if (width > 0 && width < disclosureWidth + leftMargin + graphicWidth) { 415 fadeOut(graphic); 416 } else { 417 fadeIn(graphic); 418 419 graphic.relocate(x + leftMargin + disclosureWidth, 420 centreContent ? (h / 2.0 - ph / 2.0) : 421 (y + tableCell.getPadding().getTop())); 422 423 graphic.toFront(); 424 } 425 } 426 } 427 /////////////////////////////////////////// 428 // further indentation code ends here 429 /////////////////////////////////////////// 430 431 tableCell.resize(width, height); 432 tableCell.relocate(x, snappedTopInset()); 433 434 // Request layout is here as (partial) fix for RT-28684. 435 // This does not appear to impact performance... 436 tableCell.requestLayout(); 437 } else { 438 width = snapSize(tableCell.prefWidth(-1)) - snapSize(horizontalPadding); 439 440 if (fixedCellSizeEnabled) { 441 // we only add/remove to the scenegraph if the fixed cell 442 // length support is enabled - otherwise we keep all 443 // TableCells in the scenegraph 444 getChildren().remove(tableCell); 445 } 446 } 447 448 x += width; 449 } 450 } 451 452 int getIndentationLevel(C control) { 453 return 0; 454 } 455 456 double getIndentationPerLevel() { 457 return 0; 458 } 459 460 /** 461 * Used to represent whether the current virtual flow owner is wanting 462 * indentation to be used in this table row. 463 */ 464 boolean isIndentationRequired() { 465 return false; 466 } 467 468 /** 469 * Returns the table column that should show the disclosure nodes and / or 470 * a graphic. By default this is the left-most column. 471 */ 472 TableColumnBase getTreeColumn() { 473 return null; 474 } 475 476 Node getDisclosureNode() { 477 return null; 478 } 479 480 /** 481 * Used to represent whether a disclosure node is visible for _this_ 482 * table row. Not to be confused with isIndentationRequired(), which is the 483 * more general API. 484 */ 485 boolean isDisclosureNodeVisible() { 486 return false; 487 } 488 489 boolean isShowRoot() { 490 return true; 491 } 492 493 void updateCells(boolean resetChildren) { 494 // To avoid a potential memory leak (when the TableColumns in the 495 // TableView are created/inserted/removed/deleted, we have a 'refresh 496 // counter' that when we reach 0 will delete all cells in this row 497 // and recreate all of them. 498 if (resetChildren) { 499 if (fullRefreshCounter == 0) { 500 recreateCells(); 501 } 502 fullRefreshCounter--; 503 } 504 505 // if clear isn't called first, we can run into situations where the 506 // cells aren't updated properly. 507 final boolean cellsEmpty = cells.isEmpty(); 508 cells.clear(); 509 510 final C skinnable = getSkinnable(); 511 final int skinnableIndex = skinnable.getIndex(); 512 final List<? extends TableColumnBase/*<T,?>*/> visibleLeafColumns = getVisibleLeafColumns(); 513 514 for (int i = 0, max = visibleLeafColumns.size(); i < max; i++) { 515 TableColumnBase<T,?> col = visibleLeafColumns.get(i); 516 517 R cell = null; 518 if (cellsMap.containsKey(col)) { 519 cell = cellsMap.get(col).get(); 520 521 // the reference has been gc'd, remove key entry from map 522 if (cell == null) { 523 cellsMap.remove(col); 524 } 525 } 526 527 if (cell == null) { 528 // if the cell is null it means we don't have it in cache and 529 // need to create it 530 cell = createCellAndCache(col); 531 } 532 533 updateCell(cell, skinnable); 534 cell.updateIndex(skinnableIndex); 535 cells.add(cell); 536 } 537 538 // update children of each row 539 if (fixedCellSizeEnabled) { 540 // we leave the adding / removing up to the layoutChildren method mostly, 541 // but here we remove any children cells that refer to columns that are 542 // not visible 543 List<Node> toRemove = new ArrayList<>(); 544 for (Node cell : getChildren()) { 545 if (! (cell instanceof IndexedCell)) continue; 546 if (!getTableColumn((R)cell).isVisible()) { 547 toRemove.add(cell); 548 } 549 } 550 getChildren().removeAll(toRemove); 551 } else if (!fixedCellSizeEnabled && (resetChildren || cellsEmpty)) { 552 getChildren().setAll(cells); 553 } 554 } 555 556 VirtualFlow<C> getVirtualFlow() { 557 Parent p = getSkinnable(); 558 while (p != null) { 559 if (p instanceof VirtualFlow) { 560 return (VirtualFlow<C>) p; 561 } 562 p = p.getParent(); 563 } 564 return null; 565 } 566 567 /** {@inheritDoc} */ 568 @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { 569 double prefWidth = 0.0; 570 571 final List<? extends TableColumnBase/*<T,?>*/> visibleLeafColumns = getVisibleLeafColumns(); 572 for (int i = 0, max = visibleLeafColumns.size(); i < max; i++) { 573 prefWidth += visibleLeafColumns.get(i).getWidth(); 574 } 575 576 return prefWidth; 577 } 578 579 /** {@inheritDoc} */ 580 @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { 581 if (fixedCellSizeEnabled) { 582 return fixedCellSize; 583 } 584 585 // fix for RT-29080 586 checkState(); 587 588 // Support for RT-18467: making it easier to specify a height for 589 // cells via CSS, where the desired height is less than the height 590 // of the TableCells. Essentially, -fx-cell-size is given higher 591 // precedence now 592 if (getCellSize() < DEFAULT_CELL_SIZE) { 593 return getCellSize(); 594 } 595 596 // FIXME according to profiling, this method is slow and should 597 // be optimised 598 double prefHeight = 0.0f; 599 final int count = cells.size(); 600 for (int i=0; i<count; i++) { 601 final R tableCell = cells.get(i); 602 prefHeight = Math.max(prefHeight, tableCell.prefHeight(-1)); 603 } 604 double ph = Math.max(prefHeight, Math.max(getCellSize(), getSkinnable().minHeight(-1))); 605 606 return ph; 607 } 608 609 /** {@inheritDoc} */ 610 @Override protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { 611 if (fixedCellSizeEnabled) { 612 return fixedCellSize; 613 } 614 615 // fix for RT-29080 616 checkState(); 617 618 // Support for RT-18467: making it easier to specify a height for 619 // cells via CSS, where the desired height is less than the height 620 // of the TableCells. Essentially, -fx-cell-size is given higher 621 // precedence now 622 if (getCellSize() < DEFAULT_CELL_SIZE) { 623 return getCellSize(); 624 } 625 626 // FIXME according to profiling, this method is slow and should 627 // be optimised 628 double minHeight = 0.0f; 629 final int count = cells.size(); 630 for (int i = 0; i < count; i++) { 631 final R tableCell = cells.get(i); 632 minHeight = Math.max(minHeight, tableCell.minHeight(-1)); 633 } 634 return minHeight; 635 } 636 637 /** {@inheritDoc} */ 638 @Override protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { 639 if (fixedCellSizeEnabled) { 640 return fixedCellSize; 641 } 642 return super.computeMaxHeight(width, topInset, rightInset, bottomInset, leftInset); 643 } 644 645 final void checkState() { 646 if (isDirty) { 647 updateCells(true); 648 isDirty = false; 649 updateCells = false; 650 } else if (updateCells) { 651 updateCells(false); 652 updateCells = false; 653 } 654 } 655 656 657 658 /*************************************************************************** 659 * * 660 * Private Implementation * 661 * * 662 **************************************************************************/ 663 664 private boolean isColumnPartiallyOrFullyVisible(TableColumnBase col) { 665 if (col == null || !col.isVisible()) return false; 666 667 final VirtualFlow<?> virtualFlow = getVirtualFlow(); 668 double scrollX = virtualFlow == null ? 0.0 : virtualFlow.getHbar().getValue(); 669 670 // work out where this column header is, and it's width (start -> end) 671 double start = 0; 672 final ObservableList<? extends TableColumnBase> visibleLeafColumns = getVisibleLeafColumns(); 673 for (int i = 0, max = visibleLeafColumns.size(); i < max; i++) { 674 TableColumnBase<?,?> c = visibleLeafColumns.get(i); 675 if (c.equals(col)) break; 676 start += c.getWidth(); 677 } 678 double end = start + col.getWidth(); 679 680 // determine the width of the table 681 final Insets padding = getSkinnable().getPadding(); 682 double headerWidth = getSkinnable().getWidth() - padding.getLeft() + padding.getRight(); 683 684 return (start >= scrollX || end > scrollX) && (start < (headerWidth + scrollX) || end <= (headerWidth + scrollX)); 685 } 686 687 private void requestCellUpdate() { 688 updateCells = true; 689 getSkinnable().requestLayout(); 690 691 // update the index of all children cells (RT-29849). 692 // Note that we do this after the TableRow item has been updated, 693 // rather than when the TableRow index has changed (as this will be 694 // before the row has updated its item). This will result in the 695 // issue highlighted in RT-33602, where the table cell had the correct 696 // item whilst the row had the old item. 697 final int newIndex = getSkinnable().getIndex(); 698 for (int i = 0, max = cells.size(); i < max; i++) { 699 cells.get(i).updateIndex(newIndex); 700 } 701 } 702 703 private void recreateCells() { 704 if (cellsMap != null) { 705 Collection<Reference<R>> cells = cellsMap.values(); 706 Iterator<Reference<R>> cellsIter = cells.iterator(); 707 while (cellsIter.hasNext()) { 708 Reference<R> cellRef = cellsIter.next(); 709 R cell = cellRef.get(); 710 if (cell != null) { 711 cell.updateIndex(-1); 712 cell.getSkin().dispose(); 713 cell.setSkin(null); 714 } 715 } 716 cellsMap.clear(); 717 } 718 719 ObservableList<? extends TableColumnBase/*<T,?>*/> columns = getVisibleLeafColumns(); 720 721 cellsMap = new WeakHashMap<>(columns.size()); 722 fullRefreshCounter = DEFAULT_FULL_REFRESH_COUNTER; 723 getChildren().clear(); 724 725 for (TableColumnBase col : columns) { 726 if (cellsMap.containsKey(col)) { 727 continue; 728 } 729 730 // create a TableCell for this column and store it in the cellsMap 731 // for future use 732 createCellAndCache(col); 733 } 734 } 735 736 private R createCellAndCache(TableColumnBase<T,?> col) { 737 // we must create a TableCell for this table column 738 R cell = createCell(col); 739 740 // and store this in our HashMap until needed 741 cellsMap.put(col, new WeakReference<>(cell)); 742 743 return cell; 744 } 745 746 private void fadeOut(final Node node) { 747 if (node.getOpacity() < 1.0) return; 748 749 if (! DO_ANIMATIONS) { 750 node.setOpacity(0); 751 return; 752 } 753 754 final FadeTransition fader = new FadeTransition(FADE_DURATION, node); 755 fader.setToValue(0.0); 756 fader.play(); 757 } 758 759 private void fadeIn(final Node node) { 760 if (node.getOpacity() > 0.0) return; 761 762 if (! DO_ANIMATIONS) { 763 node.setOpacity(1); 764 return; 765 } 766 767 final FadeTransition fader = new FadeTransition(FADE_DURATION, node); 768 fader.setToValue(1.0); 769 fader.play(); 770 } 771 }