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