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 }