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