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<S>). 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 }