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 // 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 }