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         // inform parent classes of the commit, so that they can switch us
 341         // out of the editing state.
 342         // This MUST come before the updateItem call below, otherwise it will
 343         // call cancelEdit(), resulting in both commit and cancel events being
 344         // fired (as identified in RT-29650)
 345         super.commitEdit(newValue);
 346 
 347         final TableView<S> table = getTableView();
 348         if (table != null) {
 349             // Inform the TableView of the edit being ready to be committed.
 350             CellEditEvent editEvent = new CellEditEvent(
 351                 table,
 352                 new TablePosition<>(getTableView(), getIndex(), getTableColumn()),
 353                 TableColumn.editCommitEvent(),
 354                 newValue
 355             );
 356 
 357             Event.fireEvent(getTableColumn(), editEvent);
 358         }
 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             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 
 556         boolean match = match(editCell);
 557 
 558         if (match && ! isEditing()) {
 559             startEdit();
 560         } else if (! match && isEditing()) {
 561             attemptEditCommit();
 562         }
 563     }
 564 
 565     private boolean match(TablePosition<S,?> pos) {
 566         return pos != null && pos.getRow() == getIndex() && pos.getTableColumn() == getTableColumn();
 567     }
 568 
 569     private boolean isInCellSelectionMode() {
 570         TableView<S> tableView = getTableView();
 571         if (tableView == null) return false;
 572         TableSelectionModel<S> sm = tableView.getSelectionModel();
 573         return sm != null && sm.isCellSelectionEnabled();
 574     }
 575 
 576     /*
 577      * This was brought in to fix the issue in RT-22077, namely that the
 578      * ObservableValue was being GC'd, meaning that changes to the value were
 579      * no longer being delivered. By extracting this value out of the method,
 580      * it is now referred to from TableCell and will therefore no longer be
 581      * GC'd.
 582      */
 583     private ObservableValue<T> currentObservableValue = null;
 584 
 585     private boolean isFirstRun = true;
 586 
 587     private WeakReference<S> oldRowItemRef;
 588 
 589     /*
 590      * This is called when we think that the data within this TableCell may have
 591      * changed. You'll note that this is a private function - it is only called
 592      * when one of the triggers above call it.
 593      */
 594     private void updateItem(int oldIndex) {
 595         if (currentObservableValue != null) {
 596             currentObservableValue.removeListener(weaktableRowUpdateObserver);
 597         }
 598 
 599         // get the total number of items in the data model
 600         final TableView<S> tableView = getTableView();
 601         final List<S> items = tableView == null ? FXCollections.<S>emptyObservableList() : tableView.getItems();
 602         final TableColumn<S,T> tableColumn = getTableColumn();
 603         final int itemCount = items == null ? -1 : items.size();
 604         final int index = getIndex();
 605         final boolean isEmpty = isEmpty();
 606         final T oldValue = getItem();
 607 
 608         final TableRow<S> tableRow = getTableRow();
 609         final S rowItem = tableRow == null ? null : tableRow.getItem();
 610 
 611         final boolean indexExceedsItemCount = index >= itemCount;
 612 
 613         // there is a whole heap of reasons why we should just punt...
 614         outer: if (indexExceedsItemCount ||
 615                 index < 0 ||
 616                 columnIndex < 0 ||
 617                 !isVisible() ||
 618                 tableColumn == null ||
 619                 !tableColumn.isVisible()) {
 620 
 621             // RT-30484 We need to allow a first run to be special-cased to allow
 622             // for the updateItem method to be called at least once to allow for
 623             // the correct visual state to be set up. In particular, in RT-30484
 624             // refer to Ensemble8PopUpTree.png - in this case the arrows are being
 625             // shown as the new cells are instantiated with the arrows in the
 626             // children list, and are only hidden in updateItem.
 627             // RT-32621: There are circumstances where we need to updateItem,
 628             // even when the index is greater than the itemCount. For example,
 629             // RT-32621 identifies issues where a TreeTableView collapses a
 630             // TreeItem but the custom cells remain visible. This is now
 631             // resolved with the check for indexExceedsItemCount.
 632             if ((!isEmpty && oldValue != null) || isFirstRun || indexExceedsItemCount) {
 633                 updateItem(null, true);
 634                 isFirstRun = false;
 635             }
 636             return;
 637         } else {
 638             currentObservableValue = tableColumn.getCellObservableValue(index);
 639             final T newValue = currentObservableValue == null ? null : currentObservableValue.getValue();
 640 
 641             // RT-35864 - if the index didn't change, then avoid calling updateItem
 642             // unless the item has changed.
 643             if (oldIndex == index) {
 644                 if (!isItemChanged(oldValue, newValue)) {
 645                     // RT-36670: we need to check the row item here to prevent
 646                     // the issue where the cell value and index doesn't change,
 647                     // but the backing row object does.
 648                     S oldRowItem = oldRowItemRef != null ? oldRowItemRef.get() : null;
 649                     if (oldRowItem != null && oldRowItem.equals(rowItem)) {
 650                         // RT-37054:  we break out of the if/else code here and
 651                         // proceed with the code following this, so that we may
 652                         // still update references, listeners, etc as required//.
 653                         break outer;
 654                     }
 655                 }
 656             }
 657             updateItem(newValue, false);
 658         }
 659 
 660         oldRowItemRef = new WeakReference<>(rowItem);
 661 
 662         if (currentObservableValue == null) {
 663             return;
 664         }
 665 
 666         // add property change listeners to this item
 667         currentObservableValue.addListener(weaktableRowUpdateObserver);
 668     }
 669 
 670     @Override protected void layoutChildren() {
 671         if (itemDirty) {
 672             updateItem(-1);
 673             itemDirty = false;
 674         }
 675         super.layoutChildren();
 676     }
 677 
 678 
 679 
 680 
 681     /***************************************************************************
 682      *                                                                         *
 683      *                              Expert API                                 *
 684      *                                                                         *
 685      **************************************************************************/
 686 
 687     /**
 688      * Updates the TableView associated with this TableCell. This is typically
 689      * only done once when the TableCell is first added to the TableView.
 690      *
 691      * Note: This function is intended to be used by experts, primarily
 692      *       by those implementing new Skins. It is not common
 693      *       for developers or designers to access this function directly.
 694      * @param tv the TableView associated with this TableCell
 695      */
 696     public final void updateTableView(TableView tv) {
 697         setTableView(tv);
 698     }
 699 
 700     /**
 701      * Updates the TableRow associated with this TableCell.
 702      *
 703      * Note: This function is intended to be used by experts, primarily
 704      *       by those implementing new Skins. It is not common
 705      *       for developers or designers to access this function directly.
 706      * @param tableRow the TableRow associated with this TableCell
 707      */
 708     public final void updateTableRow(TableRow tableRow) {
 709         this.setTableRow(tableRow);
 710     }
 711 
 712     /**
 713      * Updates the TableColumn associated with this TableCell.
 714      *
 715      * Note: This function is intended to be used by experts, primarily
 716      *       by those implementing new Skins. It is not common
 717      *       for developers or designers to access this function directly.
 718      * @param col the TableColumn associated with this TableCell
 719      */
 720     public final void updateTableColumn(TableColumn col) {
 721         // remove style class of existing table column, if it is non-null
 722         TableColumn<S,T> oldCol = getTableColumn();
 723         if (oldCol != null) {
 724             oldCol.getStyleClass().removeListener(weakColumnStyleClassListener);
 725             getStyleClass().removeAll(oldCol.getStyleClass());
 726 
 727             oldCol.idProperty().removeListener(weakColumnIdListener);
 728             oldCol.styleProperty().removeListener(weakColumnStyleListener);
 729 
 730             String id = getId();
 731             String style = getStyle();
 732             if (id != null && id.equals(oldCol.getId())) {
 733                 setId(null);
 734             }
 735             if (style != null && style.equals(oldCol.getStyle())) {
 736                 setStyle("");
 737             }
 738         }
 739 
 740         setTableColumn(col);
 741 
 742         if (col != null) {
 743             getStyleClass().addAll(col.getStyleClass());
 744             col.getStyleClass().addListener(weakColumnStyleClassListener);
 745 
 746             col.idProperty().addListener(weakColumnIdListener);
 747             col.styleProperty().addListener(weakColumnStyleListener);
 748 
 749             possiblySetId(col.getId());
 750             possiblySetStyle(col.getStyle());
 751         }
 752     }
 753 
 754 
 755 
 756     /***************************************************************************
 757      *                                                                         *
 758      * Stylesheet Handling                                                     *
 759      *                                                                         *
 760      **************************************************************************/
 761 
 762     private static final String DEFAULT_STYLE_CLASS = "table-cell";
 763     private static final PseudoClass PSEUDO_CLASS_LAST_VISIBLE =
 764             PseudoClass.getPseudoClass("last-visible");
 765 
 766     private void possiblySetId(String idCandidate) {
 767         if (getId() == null || getId().isEmpty()) {
 768             setId(idCandidate);
 769         }
 770     }
 771 
 772     private void possiblySetStyle(String styleCandidate) {
 773         if (getStyle() == null || getStyle().isEmpty()) {
 774             setStyle(styleCandidate);
 775         }
 776     }
 777 
 778 
 779 
 780     /***************************************************************************
 781      *                                                                         *
 782      * Accessibility handling                                                  *
 783      *                                                                         *
 784      **************************************************************************/
 785 
 786     @Override
 787     public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
 788         switch (attribute) {
 789             case ROW_INDEX: return getIndex();
 790             case COLUMN_INDEX: return columnIndex;
 791             case SELECTED: return isInCellSelectionMode() ? isSelected() : getTableRow().isSelected();
 792             default: return super.queryAccessibleAttribute(attribute, parameters);
 793         }
 794     }
 795 
 796     @Override
 797     public void executeAccessibleAction(AccessibleAction action, Object... parameters) {
 798         switch (action) {
 799             case REQUEST_FOCUS: {
 800                 TableView<S> tableView = getTableView();
 801                 if (tableView != null) {
 802                     TableViewFocusModel<S> fm = tableView.getFocusModel();
 803                     if (fm != null) {
 804                         fm.focus(getIndex(), getTableColumn());
 805                     }
 806                 }
 807                 break;
 808             }
 809             default: super.executeAccessibleAction(action, parameters);
 810         }
 811     }
 812 }