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 }