1 /*
   2  * Copyright (c) 2010, 2017, 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;
  27 
  28 import javafx.css.PseudoClass;
  29 import javafx.beans.InvalidationListener;
  30 import javafx.beans.WeakInvalidationListener;
  31 import javafx.beans.value.ObservableValue;
  32 import javafx.collections.ListChangeListener;
  33 import javafx.event.Event;
  34 import javafx.scene.AccessibleAction;
  35 import javafx.scene.AccessibleAttribute;
  36 import javafx.scene.AccessibleRole;
  37 import javafx.scene.control.TableView.TableViewFocusModel;
  38 
  39 import javafx.scene.control.skin.TableCellSkin;
  40 import javafx.collections.WeakListChangeListener;
  41 import java.lang.ref.WeakReference;
  42 import java.util.List;
  43 import javafx.beans.property.ReadOnlyObjectProperty;
  44 import javafx.beans.property.ReadOnlyObjectWrapper;
  45 import javafx.collections.FXCollections;
  46 
  47 import javafx.scene.control.TableColumn.CellEditEvent;
  48 
  49 
  50 /**
  51  * Represents a single row/column intersection in a {@link TableView}. To
  52  * represent this intersection, a TableCell contains an
  53  * {@link #indexProperty() index} property, as well as a
  54  * {@link #tableColumnProperty() tableColumn} property. In addition, a TableCell
  55  * instance knows what {@link TableRow} it exists in.
  56  *
  57  * <p><strong>A note about selection:</strong> A TableCell visually shows it is
  58  * selected when two conditions are met:
  59  * <ol>
  60  *   <li>The {@link TableSelectionModel#isSelected(int, TableColumnBase)} method
  61  *   returns true for the row / column that this cell represents, and</li>
  62  *   <li>The {@link javafx.scene.control.TableSelectionModel#cellSelectionEnabledProperty() cell selection mode}
  63  *   property is set to true (to represent that it is allowable to select
  64  *   individual cells (and not just rows of cells)).</li>
  65  * </ol>
  66  *
  67  * @see TableView
  68  * @see TableColumn
  69  * @see Cell
  70  * @see IndexedCell
  71  * @see TableRow
  72  * @param <S> The type of the TableView generic type (i.e. S == TableView&lt;S&gt;).
  73  *           This should also match with the first generic type in TableColumn.
  74  * @param <T> The type of the item contained within the Cell.
  75  * @since JavaFX 2.0
  76  */
  77 public class TableCell<S,T> extends IndexedCell<T> {
  78 
  79     /***************************************************************************
  80      *                                                                         *
  81      * Constructors                                                            *
  82      *                                                                         *
  83      **************************************************************************/
  84 
  85     /**
  86      * Constructs a default TableCell instance with a style class of 'table-cell'
  87      */
  88     public TableCell() {
  89         getStyleClass().addAll(DEFAULT_STYLE_CLASS);
  90         setAccessibleRole(AccessibleRole.TABLE_CELL);
  91 
  92         updateColumnIndex();
  93     }
  94 
  95 
  96 
  97     /***************************************************************************
  98      *                                                                         *
  99      * Private fields                                                          *
 100      *                                                                         *
 101      **************************************************************************/
 102 
 103     // package for testing
 104     boolean lockItemOnEdit = false;
 105 
 106 
 107     /***************************************************************************
 108      *                                                                         *
 109      * Callbacks and Events                                                    *
 110      *                                                                         *
 111      **************************************************************************/
 112 
 113     private boolean itemDirty = false;
 114 
 115     /*
 116      * This is the list observer we use to keep an eye on the SelectedCells
 117      * ObservableList in the table view. Because it is possible that the table can
 118      * be mutated, we create this observer here, and add/remove it from the
 119      * storeTableView method.
 120      */
 121     private ListChangeListener<TablePosition> selectedListener = c -> {
 122         while (c.next()) {
 123             if (c.wasAdded() || c.wasRemoved()) {
 124                 updateSelection();
 125             }
 126         }
 127     };
 128 
 129     // same as above, but for focus
 130     private final InvalidationListener focusedListener = value -> {
 131         updateFocus();
 132     };
 133 
 134     // same as above, but for for changes to the properties on TableRow
 135     private final InvalidationListener tableRowUpdateObserver = value -> {
 136         itemDirty = true;
 137         requestLayout();
 138     };
 139 
 140     private final InvalidationListener editingListener = value -> {
 141         updateEditing();
 142     };
 143 
 144     private ListChangeListener<TableColumn<S,?>> visibleLeafColumnsListener = c -> {
 145         updateColumnIndex();
 146     };
 147 
 148     private ListChangeListener<String> columnStyleClassListener = c -> {
 149         while (c.next()) {
 150             if (c.wasRemoved()) {
 151                 getStyleClass().removeAll(c.getRemoved());
 152             }
 153 
 154             if (c.wasAdded()) {
 155                 getStyleClass().addAll(c.getAddedSubList());
 156             }
 157         }
 158     };
 159 
 160     private final InvalidationListener columnStyleListener = value -> {
 161         if (getTableColumn() != null) {
 162             possiblySetStyle(getTableColumn().getStyle());
 163         }
 164     };
 165 
 166     private final InvalidationListener columnIdListener = value -> {
 167         if (getTableColumn() != null) {
 168             possiblySetId(getTableColumn().getId());
 169         }
 170     };
 171 
 172     private final WeakListChangeListener<TablePosition> weakSelectedListener =
 173             new WeakListChangeListener<>(selectedListener);
 174     private final WeakInvalidationListener weakFocusedListener =
 175             new WeakInvalidationListener(focusedListener);
 176     private final WeakInvalidationListener weaktableRowUpdateObserver =
 177             new WeakInvalidationListener(tableRowUpdateObserver);
 178     private final WeakInvalidationListener weakEditingListener =
 179             new WeakInvalidationListener(editingListener);
 180     private final WeakInvalidationListener weakColumnStyleListener =
 181             new WeakInvalidationListener(columnStyleListener);
 182     private final WeakInvalidationListener weakColumnIdListener =
 183             new WeakInvalidationListener(columnIdListener);
 184     private final WeakListChangeListener<TableColumn<S,?>> weakVisibleLeafColumnsListener =
 185             new WeakListChangeListener<>(visibleLeafColumnsListener);
 186     private final WeakListChangeListener<String> weakColumnStyleClassListener =
 187             new WeakListChangeListener<String>(columnStyleClassListener);
 188 
 189 
 190     /***************************************************************************
 191      *                                                                         *
 192      * Properties                                                              *
 193      *                                                                         *
 194      **************************************************************************/
 195 
 196     // --- TableColumn
 197     private ReadOnlyObjectWrapper<TableColumn<S,T>> tableColumn = new ReadOnlyObjectWrapper<TableColumn<S,T>>() {
 198         @Override protected void invalidated() {
 199             updateColumnIndex();
 200         }
 201 
 202         @Override public Object getBean() {
 203             return TableCell.this;
 204         }
 205 
 206         @Override public String getName() {
 207             return "tableColumn";
 208         }
 209     };
 210     /**
 211      * The TableColumn instance that backs this TableCell.
 212      * @return the TableColumn instance that backs this TableCell
 213      */
 214     public final ReadOnlyObjectProperty<TableColumn<S,T>> tableColumnProperty() { return tableColumn.getReadOnlyProperty(); }
 215     private void setTableColumn(TableColumn<S,T> value) { tableColumn.set(value); }
 216     public final TableColumn<S,T> getTableColumn() { return tableColumn.get(); }
 217 
 218 
 219     // --- TableView
 220     private ReadOnlyObjectWrapper<TableView<S>> tableView;
 221     private void setTableView(TableView<S> value) {
 222         tableViewPropertyImpl().set(value);
 223     }
 224     public final TableView<S> getTableView() {
 225         return tableView == null ? null : tableView.get();
 226     }
 227 
 228     /**
 229      * The TableView associated with this TableCell.
 230      * @return the TableView associated with this TableCell
 231      */
 232     public final ReadOnlyObjectProperty<TableView<S>> tableViewProperty() {
 233         return tableViewPropertyImpl().getReadOnlyProperty();
 234     }
 235 
 236     private ReadOnlyObjectWrapper<TableView<S>> tableViewPropertyImpl() {
 237         if (tableView == null) {
 238             tableView = new ReadOnlyObjectWrapper<TableView<S>>() {
 239                 private WeakReference<TableView<S>> weakTableViewRef;
 240                 @Override protected void invalidated() {
 241                     TableView.TableViewSelectionModel<S> sm;
 242                     TableViewFocusModel<S> fm;
 243 
 244                     if (weakTableViewRef != null) {
 245                         cleanUpTableViewListeners(weakTableViewRef.get());
 246                     }
 247 
 248                     if (get() != null) {
 249                         sm = get().getSelectionModel();
 250                         if (sm != null) {
 251                             sm.getSelectedCells().addListener(weakSelectedListener);
 252                         }
 253 
 254                         fm = get().getFocusModel();
 255                         if (fm != null) {
 256                             fm.focusedCellProperty().addListener(weakFocusedListener);
 257                         }
 258 
 259                         get().editingCellProperty().addListener(weakEditingListener);
 260                         get().getVisibleLeafColumns().addListener(weakVisibleLeafColumnsListener);
 261 
 262                         weakTableViewRef = new WeakReference<TableView<S>>(get());
 263                     }
 264 
 265                     updateColumnIndex();
 266                 }
 267 
 268                 @Override public Object getBean() {
 269                     return TableCell.this;
 270                 }
 271 
 272                 @Override public String getName() {
 273                     return "tableView";
 274                 }
 275             };
 276         }
 277         return tableView;
 278     }
 279 
 280 
 281 
 282     // --- TableRow
 283     /**
 284      * The TableRow that this TableCell currently finds itself placed within.
 285      * The TableRow may be null early in the TableCell lifecycle, in the period
 286      * between the TableCell being instantiated and being set into an owner
 287      * TableRow.
 288      */
 289     private ReadOnlyObjectWrapper<TableRow<S>> tableRow = new ReadOnlyObjectWrapper<>(this, "tableRow");
 290     private void setTableRow(TableRow<S> value) { tableRow.set(value); }
 291     public final TableRow<S> getTableRow() { return tableRow.get(); }
 292     public final ReadOnlyObjectProperty<TableRow<S>> tableRowProperty() { return tableRow;  }
 293 
 294 
 295 
 296     /***************************************************************************
 297      *                                                                         *
 298      * Editing API                                                             *
 299      *                                                                         *
 300      **************************************************************************/
 301 
 302     /** {@inheritDoc} */
 303     @Override public void startEdit() {
 304         final TableView<S> table = getTableView();
 305         final TableColumn<S,T> column = getTableColumn();
 306         if (! isEditable() ||
 307                 (table != null && ! table.isEditable()) ||
 308                 (column != null && ! getTableColumn().isEditable())) {
 309             return;
 310         }
 311 
 312         // We check the boolean lockItemOnEdit field here, as whilst we want to
 313         // updateItem normally, when it comes to unit tests we can't have the
 314         // item change in all circumstances.
 315         if (! lockItemOnEdit) {
 316             updateItem(-1);
 317         }
 318 
 319         // it makes sense to get the cell into its editing state before firing
 320         // the event to listeners below, so that's what we're doing here
 321         // by calling super.startEdit().
 322         super.startEdit();
 323 
 324         if (column != null) {
 325             CellEditEvent<S,?> editEvent = new CellEditEvent<>(
 326                 table,
 327                 table.getEditingCell(),
 328                 TableColumn.editStartEvent(),
 329                 null
 330             );
 331 
 332             Event.fireEvent(column, editEvent);
 333         }
 334     }
 335 
 336     /** {@inheritDoc} */
 337     @Override public void commitEdit(T newValue) {
 338         if (! isEditing()) return;
 339 
 340         final TableView<S> table = getTableView();
 341         if (table != null) {
 342             // Inform the TableView of the edit being ready to be committed.
 343             CellEditEvent editEvent = new CellEditEvent(
 344                 table,
 345                 table.getEditingCell(),
 346                 TableColumn.editCommitEvent(),
 347                 newValue
 348             );
 349 
 350             Event.fireEvent(getTableColumn(), editEvent);
 351         }
 352 
 353         // inform parent classes of the commit, so that they can switch us
 354         // out of the editing state.
 355         // This MUST come before the updateItem call below, otherwise it will
 356         // call cancelEdit(), resulting in both commit and cancel events being
 357         // fired (as identified in RT-29650)
 358         super.commitEdit(newValue);
 359 
 360         // update the item within this cell, so that it represents the new value
 361         updateItem(newValue, false);
 362 
 363         if (table != null) {
 364             // reset the editing cell on the TableView
 365             table.edit(-1, null);
 366 
 367             // request focus back onto the table, only if the current focus
 368             // owner has the table as a parent (otherwise the user might have
 369             // clicked out of the table entirely and given focus to something else.
 370             // It would be rude of us to request it back again.
 371             ControlUtils.requestFocusOnControlOnlyIfCurrentFocusOwnerIsChild(table);
 372         }
 373     }
 374 
 375     /** {@inheritDoc} */
 376     @Override public void cancelEdit() {
 377         if (! isEditing()) return;
 378 
 379         final TableView<S> table = getTableView();
 380 
 381         super.cancelEdit();
 382 
 383         // reset the editing index on the TableView
 384         if (table != null) {
 385             TablePosition<S,?> editingCell = table.getEditingCell();
 386             if (updateEditingIndex) table.edit(-1, null);
 387 
 388             // request focus back onto the table, only if the current focus
 389             // owner has the table as a parent (otherwise the user might have
 390             // clicked out of the table entirely and given focus to something else.
 391             // It would be rude of us to request it back again.
 392             ControlUtils.requestFocusOnControlOnlyIfCurrentFocusOwnerIsChild(table);
 393 
 394             CellEditEvent<S,?> editEvent = new CellEditEvent<>(
 395                 table,
 396                 editingCell,
 397                 TableColumn.editCancelEvent(),
 398                 null
 399             );
 400 
 401             Event.fireEvent(getTableColumn(), editEvent);
 402         }
 403     }
 404 
 405 
 406 
 407     /* *************************************************************************
 408      *                                                                         *
 409      * Overriding methods                                                      *
 410      *                                                                         *
 411      **************************************************************************/
 412 
 413     /** {@inheritDoc} */
 414     @Override public void updateSelected(boolean selected) {
 415         // copied from Cell, with the first conditional clause below commented
 416         // out, as it is valid for an empty TableCell to be selected, as long
 417         // as the parent TableRow is not empty (see RT-15529).
 418         /*if (selected && isEmpty()) return;*/
 419         if (getTableRow() == null || getTableRow().isEmpty()) return;
 420         setSelected(selected);
 421     }
 422 
 423     /** {@inheritDoc} */
 424     @Override protected Skin<?> createDefaultSkin() {
 425         return new TableCellSkin<S,T>(this);
 426     }
 427 
 428 //    @Override public void dispose() {
 429 //        cleanUpTableViewListeners(getTableView());
 430 //
 431 //        if (currentObservableValue != null) {
 432 //            currentObservableValue.removeListener(weaktableRowUpdateObserver);
 433 //        }
 434 //
 435 //        super.dispose();
 436 //    }
 437 
 438     /* *************************************************************************
 439     *                                                                         *
 440     * Private Implementation                                                  *
 441     *                                                                         *
 442     **************************************************************************/
 443 
 444     private void cleanUpTableViewListeners(TableView<S> tableView) {
 445         if (tableView != null) {
 446             TableView.TableViewSelectionModel<S> sm = tableView.getSelectionModel();
 447             if (sm != null) {
 448                 sm.getSelectedCells().removeListener(weakSelectedListener);
 449             }
 450 
 451             TableViewFocusModel<S> fm = tableView.getFocusModel();
 452             if (fm != null) {
 453                 fm.focusedCellProperty().removeListener(weakFocusedListener);
 454             }
 455 
 456             tableView.editingCellProperty().removeListener(weakEditingListener);
 457             tableView.getVisibleLeafColumns().removeListener(weakVisibleLeafColumnsListener);
 458         }
 459     }
 460 
 461     @Override void indexChanged(int oldIndex, int newIndex) {
 462         super.indexChanged(oldIndex, newIndex);
 463 
 464         // Ideally we would just use the following two lines of code, rather
 465         // than the updateItem() call beneath, but if we do this we end up with
 466         // RT-22428 where all the columns are collapsed.
 467         // itemDirty = true;
 468         // requestLayout();
 469         updateItem(oldIndex);
 470         updateSelection();
 471         updateFocus();
 472 
 473         // Fix for JDK-8150525
 474         updateEditing();
 475     }
 476 
 477     private boolean isLastVisibleColumn = false;
 478     private int columnIndex = -1;
 479 
 480     private void updateColumnIndex() {
 481         TableView<S> tv = getTableView();
 482         TableColumn<S,T> tc = getTableColumn();
 483         columnIndex = tv == null || tc == null ? -1 : tv.getVisibleLeafIndex(tc);
 484 
 485         // update the pseudo class state regarding whether this is the last
 486         // visible cell (i.e. the right-most).
 487         isLastVisibleColumn = getTableColumn() != null &&
 488                 columnIndex != -1 &&
 489                 columnIndex == getTableView().getVisibleLeafColumns().size() - 1;
 490         pseudoClassStateChanged(PSEUDO_CLASS_LAST_VISIBLE, isLastVisibleColumn);
 491     }
 492 
 493     private void updateSelection() {
 494         /*
 495          * This cell should be selected if the selection mode of the table
 496          * is cell-based, and if the row and column that this cell represents
 497          * is selected.
 498          *
 499          * If the selection mode is not cell-based, then the listener in the
 500          * TableRow class might pick up the need to set an entire row to be
 501          * selected.
 502          */
 503         if (isEmpty()) return;
 504 
 505         final boolean isSelected = isSelected();
 506         if (! isInCellSelectionMode()) {
 507             if (isSelected) {
 508                 updateSelected(false);
 509             }
 510             return;
 511         }
 512 
 513         final TableView<S> tableView = getTableView();
 514         if (getIndex() == -1 || tableView == null) return;
 515 
 516         TableSelectionModel<S> sm = tableView.getSelectionModel();
 517         if (sm == null) {
 518             updateSelected(false);
 519             return;
 520         }
 521 
 522         boolean isSelectedNow = sm.isSelected(getIndex(), getTableColumn());
 523         if (isSelected == isSelectedNow) return;
 524 
 525         updateSelected(isSelectedNow);
 526     }
 527 
 528     private void updateFocus() {
 529         final boolean isFocused = isFocused();
 530         if (! isInCellSelectionMode()) {
 531             if (isFocused) {
 532                 setFocused(false);
 533             }
 534             return;
 535         }
 536 
 537         final TableView<S> tableView = getTableView();
 538         final TableRow<S> tableRow = getTableRow();
 539         final int index = getIndex();
 540         if (index == -1 || tableView == null || tableRow == null) return;
 541 
 542         final TableViewFocusModel<S> fm = tableView.getFocusModel();
 543         if (fm == null) {
 544             setFocused(false);
 545             return;
 546         }
 547 
 548         setFocused(fm.isFocused(index, getTableColumn()));
 549     }
 550 
 551     private void updateEditing() {
 552         if (getIndex() == -1 || getTableView() == null) return;
 553 
 554         TablePosition<S,?> editCell = getTableView().getEditingCell();
 555         boolean match = match(editCell);
 556 
 557         if (match && ! isEditing()) {
 558             startEdit();
 559         } else if (! match && isEditing()) {
 560             // If my index is not the one being edited then I need to cancel
 561             // the edit. The tricky thing here is that as part of this call
 562             // I cannot end up calling list.edit(-1) the way that the standard
 563             // cancelEdit method would do. Yet, I need to call cancelEdit
 564             // so that subclasses which override cancelEdit can execute. So,
 565             // I have to use a kind of hacky flag workaround.
 566             updateEditingIndex = false;
 567             cancelEdit();
 568             updateEditingIndex = true;
 569         }
 570     }
 571     private boolean updateEditingIndex = true;
 572 
 573     private boolean match(TablePosition<S,?> pos) {
 574         return pos != null && pos.getRow() == getIndex() && pos.getTableColumn() == getTableColumn();
 575     }
 576 
 577     private boolean isInCellSelectionMode() {
 578         TableView<S> tableView = getTableView();
 579         if (tableView == null) return false;
 580         TableSelectionModel<S> sm = tableView.getSelectionModel();
 581         return sm != null && sm.isCellSelectionEnabled();
 582     }
 583 
 584     /*
 585      * This was brought in to fix the issue in RT-22077, namely that the
 586      * ObservableValue was being GC'd, meaning that changes to the value were
 587      * no longer being delivered. By extracting this value out of the method,
 588      * it is now referred to from TableCell and will therefore no longer be
 589      * GC'd.
 590      */
 591     private ObservableValue<T> currentObservableValue = null;
 592 
 593     private boolean isFirstRun = true;
 594 
 595     private WeakReference<S> oldRowItemRef;
 596 
 597     /*
 598      * This is called when we think that the data within this TableCell may have
 599      * changed. You'll note that this is a private function - it is only called
 600      * when one of the triggers above call it.
 601      */
 602     private void updateItem(int oldIndex) {
 603         if (currentObservableValue != null) {
 604             currentObservableValue.removeListener(weaktableRowUpdateObserver);
 605         }
 606 
 607         // get the total number of items in the data model
 608         final TableView<S> tableView = getTableView();
 609         final List<S> items = tableView == null ? FXCollections.<S>emptyObservableList() : tableView.getItems();
 610         final TableColumn<S,T> tableColumn = getTableColumn();
 611         final int itemCount = items == null ? -1 : items.size();
 612         final int index = getIndex();
 613         final boolean isEmpty = isEmpty();
 614         final T oldValue = getItem();
 615 
 616         final TableRow<S> tableRow = getTableRow();
 617         final S rowItem = tableRow == null ? null : tableRow.getItem();
 618 
 619         final boolean indexExceedsItemCount = index >= itemCount;
 620 
 621         // there is a whole heap of reasons why we should just punt...
 622         outer: if (indexExceedsItemCount ||
 623                 index < 0 ||
 624                 columnIndex < 0 ||
 625                 !isVisible() ||
 626                 tableColumn == null ||
 627                 !tableColumn.isVisible()) {
 628 
 629             // RT-30484 We need to allow a first run to be special-cased to allow
 630             // for the updateItem method to be called at least once to allow for
 631             // the correct visual state to be set up. In particular, in RT-30484
 632             // refer to Ensemble8PopUpTree.png - in this case the arrows are being
 633             // shown as the new cells are instantiated with the arrows in the
 634             // children list, and are only hidden in updateItem.
 635             // RT-32621: There are circumstances where we need to updateItem,
 636             // even when the index is greater than the itemCount. For example,
 637             // RT-32621 identifies issues where a TreeTableView collapses a
 638             // TreeItem but the custom cells remain visible. This is now
 639             // resolved with the check for indexExceedsItemCount.
 640             if ((!isEmpty && oldValue != null) || isFirstRun || indexExceedsItemCount) {
 641                 updateItem(null, true);
 642                 isFirstRun = false;
 643             }
 644             return;
 645         } else {
 646             currentObservableValue = tableColumn.getCellObservableValue(index);
 647             final T newValue = currentObservableValue == null ? null : currentObservableValue.getValue();
 648 
 649             // RT-35864 - if the index didn't change, then avoid calling updateItem
 650             // unless the item has changed.
 651             if (oldIndex == index) {
 652                 if (!isItemChanged(oldValue, newValue)) {
 653                     // RT-36670: we need to check the row item here to prevent
 654                     // the issue where the cell value and index doesn't change,
 655                     // but the backing row object does.
 656                     S oldRowItem = oldRowItemRef != null ? oldRowItemRef.get() : null;
 657                     if (oldRowItem != null && oldRowItem.equals(rowItem)) {
 658                         // RT-37054:  we break out of the if/else code here and
 659                         // proceed with the code following this, so that we may
 660                         // still update references, listeners, etc as required//.
 661                         break outer;
 662                     }
 663                 }
 664             }
 665             updateItem(newValue, false);
 666         }
 667 
 668         oldRowItemRef = new WeakReference<>(rowItem);
 669 
 670         if (currentObservableValue == null) {
 671             return;
 672         }
 673 
 674         // add property change listeners to this item
 675         currentObservableValue.addListener(weaktableRowUpdateObserver);
 676     }
 677 
 678     @Override protected void layoutChildren() {
 679         if (itemDirty) {
 680             updateItem(-1);
 681             itemDirty = false;
 682         }
 683         super.layoutChildren();
 684     }
 685 
 686 
 687 
 688 
 689     /***************************************************************************
 690      *                                                                         *
 691      *                              Expert API                                 *
 692      *                                                                         *
 693      **************************************************************************/
 694 
 695     /**
 696      * Updates the TableView associated with this TableCell. This is typically
 697      * only done once when the TableCell is first added to the TableView.
 698      *
 699      * Note: This function is intended to be used by experts, primarily
 700      *       by those implementing new Skins. It is not common
 701      *       for developers or designers to access this function directly.
 702      * @param tv the TableView associated with this TableCell
 703      */
 704     public final void updateTableView(TableView tv) {
 705         setTableView(tv);
 706     }
 707 
 708     /**
 709      * Updates the TableRow associated with this TableCell.
 710      *
 711      * Note: This function is intended to be used by experts, primarily
 712      *       by those implementing new Skins. It is not common
 713      *       for developers or designers to access this function directly.
 714      * @param tableRow the TableRow associated with this TableCell
 715      */
 716     public final void updateTableRow(TableRow tableRow) {
 717         this.setTableRow(tableRow);
 718     }
 719 
 720     /**
 721      * Updates the TableColumn associated with this TableCell.
 722      *
 723      * Note: This function is intended to be used by experts, primarily
 724      *       by those implementing new Skins. It is not common
 725      *       for developers or designers to access this function directly.
 726      * @param col the TableColumn associated with this TableCell
 727      */
 728     public final void updateTableColumn(TableColumn col) {
 729         // remove style class of existing table column, if it is non-null
 730         TableColumn<S,T> oldCol = getTableColumn();
 731         if (oldCol != null) {
 732             oldCol.getStyleClass().removeListener(weakColumnStyleClassListener);
 733             getStyleClass().removeAll(oldCol.getStyleClass());
 734 
 735             oldCol.idProperty().removeListener(weakColumnIdListener);
 736             oldCol.styleProperty().removeListener(weakColumnStyleListener);
 737 
 738             String id = getId();
 739             String style = getStyle();
 740             if (id != null && id.equals(oldCol.getId())) {
 741                 setId(null);
 742             }
 743             if (style != null && style.equals(oldCol.getStyle())) {
 744                 setStyle("");
 745             }
 746         }
 747 
 748         setTableColumn(col);
 749 
 750         if (col != null) {
 751             getStyleClass().addAll(col.getStyleClass());
 752             col.getStyleClass().addListener(weakColumnStyleClassListener);
 753 
 754             col.idProperty().addListener(weakColumnIdListener);
 755             col.styleProperty().addListener(weakColumnStyleListener);
 756 
 757             possiblySetId(col.getId());
 758             possiblySetStyle(col.getStyle());
 759         }
 760     }
 761 
 762 
 763 
 764     /***************************************************************************
 765      *                                                                         *
 766      * Stylesheet Handling                                                     *
 767      *                                                                         *
 768      **************************************************************************/
 769 
 770     private static final String DEFAULT_STYLE_CLASS = "table-cell";
 771     private static final PseudoClass PSEUDO_CLASS_LAST_VISIBLE =
 772             PseudoClass.getPseudoClass("last-visible");
 773 
 774     private void possiblySetId(String idCandidate) {
 775         if (getId() == null || getId().isEmpty()) {
 776             setId(idCandidate);
 777         }
 778     }
 779 
 780     private void possiblySetStyle(String styleCandidate) {
 781         if (getStyle() == null || getStyle().isEmpty()) {
 782             setStyle(styleCandidate);
 783         }
 784     }
 785 
 786 
 787 
 788     /***************************************************************************
 789      *                                                                         *
 790      * Accessibility handling                                                  *
 791      *                                                                         *
 792      **************************************************************************/
 793 
 794     @Override
 795     public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
 796         switch (attribute) {
 797             case ROW_INDEX: return getIndex();
 798             case COLUMN_INDEX: return columnIndex;
 799             case SELECTED: return isInCellSelectionMode() ? isSelected() : getTableRow().isSelected();
 800             default: return super.queryAccessibleAttribute(attribute, parameters);
 801         }
 802     }
 803 
 804     @Override
 805     public void executeAccessibleAction(AccessibleAction action, Object... parameters) {
 806         switch (action) {
 807             case REQUEST_FOCUS: {
 808                 TableView<S> tableView = getTableView();
 809                 if (tableView != null) {
 810                     TableViewFocusModel<S> fm = tableView.getFocusModel();
 811                     if (fm != null) {
 812                         fm.focus(getIndex(), getTableColumn());
 813                     }
 814                 }
 815                 break;
 816             }
 817             default: super.executeAccessibleAction(action, parameters);
 818         }
 819     }
 820 }