1 /* 2 * Copyright (c) 2010, 2014, 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 com.sun.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 * </p> 65 * 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 if (table != null) { 345 @SuppressWarnings("unchecked") 346 TreeTablePosition<S,T> editingCell = (TreeTablePosition<S,T>) table.getEditingCell(); 347 348 // Inform the TableView of the edit being ready to be committed. 349 CellEditEvent<S,T> editEvent = new CellEditEvent<S,T>( 350 table, 351 editingCell, 352 TreeTableColumn.<S,T>editCommitEvent(), 353 newValue 354 ); 355 356 Event.fireEvent(getTableColumn(), editEvent); 357 } 358 359 // inform parent classes of the commit, so that they can switch us 360 // out of the editing state. 361 // This MUST come before the updateItem call below, otherwise it will 362 // call cancelEdit(), resulting in both commit and cancel events being 363 // fired (as identified in RT-29650) 364 super.commitEdit(newValue); 365 366 // update the item within this cell, so that it represents the new value 367 updateItem(newValue, false); 368 369 if (table != null) { 370 // reset the editing cell on the TableView 371 table.edit(-1, null); 372 373 // request focus back onto the table, only if the current focus 374 // owner has the table as a parent (otherwise the user might have 375 // clicked out of the table entirely and given focus to something else. 376 // It would be rude of us to request it back again. 377 ControlUtils.requestFocusOnControlOnlyIfCurrentFocusOwnerIsChild(table); 378 } 379 } 380 381 /** {@inheritDoc} */ 382 @Override public void cancelEdit() { 383 if (! isEditing()) return; 384 385 final TreeTableView<S> table = getTreeTableView(); 386 387 super.cancelEdit(); 388 389 // reset the editing index on the TableView 390 if (table != null) { 391 @SuppressWarnings("unchecked") 392 TreeTablePosition<S,T> editingCell = (TreeTablePosition<S,T>) table.getEditingCell(); 393 394 if (updateEditingIndex) table.edit(-1, null); 395 396 // request focus back onto the table, only if the current focus 397 // owner has the table as a parent (otherwise the user might have 398 // clicked out of the table entirely and given focus to something else. 399 // It would be rude of us to request it back again. 400 ControlUtils.requestFocusOnControlOnlyIfCurrentFocusOwnerIsChild(table); 401 402 CellEditEvent<S,T> editEvent = new CellEditEvent<S,T>( 403 table, 404 editingCell, 405 TreeTableColumn.<S,T>editCancelEvent(), 406 null 407 ); 408 409 Event.fireEvent(getTableColumn(), editEvent); 410 } 411 } 412 413 414 415 /* ************************************************************************* 416 * * 417 * Overriding methods * 418 * * 419 **************************************************************************/ 420 421 /** {@inheritDoc} */ 422 @Override public void updateSelected(boolean selected) { 423 // copied from Cell, with the first conditional clause below commented 424 // out, as it is valid for an empty TableCell to be selected, as long 425 // as the parent TableRow is not empty (see RT-15529). 426 /*if (selected && isEmpty()) return;*/ 427 if (getTreeTableRow() == null || getTreeTableRow().isEmpty()) return; 428 setSelected(selected); 429 } 430 431 432 433 /* ************************************************************************* 434 * * 435 * Private Implementation * 436 * * 437 **************************************************************************/ 438 439 /** {@inheritDoc} */ 440 @Override void indexChanged(int oldIndex, int newIndex) { 441 super.indexChanged(oldIndex, newIndex); 442 443 if (isEditing() && newIndex == oldIndex) { 444 // no-op 445 // Fix for RT-31165 - if we (needlessly) update the index whilst the 446 // cell is being edited it will no longer be in an editing state. 447 // This means that in certain (common) circumstances that it will 448 // appear that a cell is uneditable as, despite being clicked, it 449 // will not change to the editing state as a layout of VirtualFlow 450 // is immediately invoked, which forces all cells to be updated. 451 } else { 452 // Ideally we would just use the following two lines of code, rather 453 // than the updateItem() call beneath, but if we do this we end up with 454 // RT-22428 where all the columns are collapsed. 455 // itemDirty = true; 456 // requestLayout(); 457 updateItem(oldIndex); 458 updateSelection(); 459 updateFocus(); 460 updateEditing(); 461 } 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 boolean isFocusedNow = fm != null && 534 fm.isFocused(getIndex(), getTableColumn()); 535 536 setFocused(isFocusedNow); 537 } 538 539 private void updateEditing() { 540 final TreeTableView<S> tv = getTreeTableView(); 541 if (getIndex() == -1 || tv == null) return; 542 543 TreeTablePosition<S,?> editCell = tv.getEditingCell(); 544 boolean match = match(editCell); 545 546 if (match && ! isEditing()) { 547 startEdit(); 548 } else if (! match && isEditing()) { 549 // If my index is not the one being edited then I need to cancel 550 // the edit. The tricky thing here is that as part of this call 551 // I cannot end up calling list.edit(-1) the way that the standard 552 // cancelEdit method would do. Yet, I need to call cancelEdit 553 // so that subclasses which override cancelEdit can execute. So, 554 // I have to use a kind of hacky flag workaround. 555 updateEditingIndex = false; 556 cancelEdit(); 557 updateEditingIndex = true; 558 } 559 } 560 private boolean updateEditingIndex = true; 561 562 private boolean match(TreeTablePosition pos) { 563 return pos != null && pos.getRow() == getIndex() && pos.getTableColumn() == getTableColumn(); 564 } 565 566 private boolean isInCellSelectionMode() { 567 TreeTableView<S> tv = getTreeTableView(); 568 if (tv == null) return false; 569 TreeTableView.TreeTableViewSelectionModel<S> sm = tv.getSelectionModel(); 570 return sm != null && sm.isCellSelectionEnabled(); 571 } 572 573 /* 574 * This was brought in to fix the issue in RT-22077, namely that the 575 * ObservableValue was being GC'd, meaning that changes to the value were 576 * no longer being delivered. By extracting this value out of the method, 577 * it is now referred to from TableCell and will therefore no longer be 578 * GC'd. 579 */ 580 private ObservableValue<T> currentObservableValue = null; 581 582 private boolean isFirstRun = true; 583 584 private WeakReference<S> oldRowItemRef; 585 586 /* 587 * This is called when we think that the data within this TreeTableCell may have 588 * changed. You'll note that this is a private function - it is only called 589 * when one of the triggers above call it. 590 */ 591 private void updateItem(int oldIndex) { 592 if (currentObservableValue != null) { 593 currentObservableValue.removeListener(weaktableRowUpdateObserver); 594 } 595 596 // get the total number of items in the data model 597 final TreeTableView<S> tableView = getTreeTableView(); 598 final TreeTableColumn<S,T> tableColumn = getTableColumn(); 599 final int itemCount = tableView == null ? -1 : getTreeTableView().getExpandedItemCount(); 600 final int index = getIndex(); 601 final boolean isEmpty = isEmpty(); 602 final T oldValue = getItem(); 603 604 final TreeTableRow<S> tableRow = getTreeTableRow(); 605 final S rowItem = tableRow == null ? null : tableRow.getItem(); 606 607 final boolean indexExceedsItemCount = index >= itemCount; 608 609 // there is a whole heap of reasons why we should just punt... 610 outer: if (indexExceedsItemCount || 611 index < 0 || 612 columnIndex < 0 || 613 !isVisible() || 614 tableColumn == null || 615 !tableColumn.isVisible() || 616 tableView.getRoot() == null) { 617 618 // RT-30484 We need to allow a first run to be special-cased to allow 619 // for the updateItem method to be called at least once to allow for 620 // the correct visual state to be set up. In particular, in RT-30484 621 // refer to Ensemble8PopUpTree.png - in this case the arrows are being 622 // shown as the new cells are instantiated with the arrows in the 623 // children list, and are only hidden in updateItem. 624 // RT-32621: There are circumstances where we need to updateItem, 625 // even when the index is greater than the itemCount. For example, 626 // RT-32621 identifies issues where a TreeTableView collapses a 627 // TreeItem but the custom cells remain visible. This is now 628 // resolved with the check for indexExceedsItemCount. 629 if ((!isEmpty && oldValue != null) || isFirstRun || indexExceedsItemCount) { 630 updateItem(null, true); 631 isFirstRun = false; 632 } 633 return; 634 } else { 635 currentObservableValue = tableColumn.getCellObservableValue(index); 636 637 final T newValue = currentObservableValue == null ? null : currentObservableValue.getValue(); 638 639 // RT-35864 - if the index didn't change, then avoid calling updateItem 640 // unless the item has changed. 641 if (oldIndex == index) { 642 if (!isItemChanged(oldValue, newValue)) { 643 // RT-36670: we need to check the row item here to prevent 644 // the issue where the cell value and index doesn't change, 645 // but the backing row object does. 646 S oldRowItem = oldRowItemRef != null ? oldRowItemRef.get() : null; 647 if (oldRowItem != null && oldRowItem.equals(rowItem)) { 648 // RT-37054: we break out of the if/else code here and 649 // proceed with the code following this, so that we may 650 // still update references, listeners, etc as required. 651 break outer; 652 } 653 } 654 } 655 updateItem(newValue, false); 656 } 657 658 oldRowItemRef = new WeakReference<>(rowItem); 659 660 if (currentObservableValue == null) { 661 return; 662 } 663 664 // add property change listeners to this item 665 currentObservableValue.addListener(weaktableRowUpdateObserver); 666 } 667 668 @Override protected void layoutChildren() { 669 if (itemDirty) { 670 updateItem(-1); 671 itemDirty = false; 672 } 673 super.layoutChildren(); 674 } 675 676 677 678 679 /*************************************************************************** 680 * * 681 * Expert API * 682 * * 683 **************************************************************************/ 684 685 /** 686 * Updates the TreeTableView associated with this TreeTableCell. This is typically 687 * only done once when the TreeTableCell is first added to the TreeTableView. 688 * 689 * @expert This function is intended to be used by experts, primarily 690 * by those implementing new Skins. It is not common 691 * for developers or designers to access this function directly. 692 */ 693 public final void updateTreeTableView(TreeTableView<S> tv) { 694 setTreeTableView(tv); 695 } 696 697 /** 698 * Updates the TreeTableRow associated with this TreeTableCell. 699 * 700 * @expert This function is intended to be used by experts, primarily 701 * by those implementing new Skins. It is not common 702 * for developers or designers to access this function directly. 703 */ 704 public final void updateTreeTableRow(TreeTableRow<S> treeTableRow) { 705 this.setTreeTableRow(treeTableRow); 706 } 707 708 /** 709 * Updates the TreeTableColumn associated with this TreeTableCell. 710 * 711 * @expert 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 */ 715 public final void updateTreeTableColumn(TreeTableColumn<S,T> col) { 716 // remove style class of existing tree table column, if it is non-null 717 TreeTableColumn<S,T> oldCol = getTableColumn(); 718 if (oldCol != null) { 719 oldCol.getStyleClass().removeListener(weakColumnStyleClassListener); 720 getStyleClass().removeAll(oldCol.getStyleClass()); 721 722 oldCol.idProperty().removeListener(weakColumnIdListener); 723 oldCol.styleProperty().removeListener(weakColumnStyleListener); 724 725 String id = getId(); 726 String style = getStyle(); 727 if (id != null && id.equals(oldCol.getId())) { 728 setId(null); 729 } 730 if (style != null && style.equals(oldCol.getStyle())) { 731 setStyle(""); 732 } 733 } 734 735 setTableColumn(col); 736 737 if (col != null) { 738 getStyleClass().addAll(col.getStyleClass()); 739 col.getStyleClass().addListener(weakColumnStyleClassListener); 740 741 col.idProperty().addListener(weakColumnIdListener); 742 col.styleProperty().addListener(weakColumnStyleListener); 743 744 possiblySetId(col.getId()); 745 possiblySetStyle(col.getStyle()); 746 } 747 } 748 749 750 751 /*************************************************************************** 752 * * 753 * Stylesheet Handling * 754 * * 755 **************************************************************************/ 756 757 private static final String DEFAULT_STYLE_CLASS = "tree-table-cell"; 758 private static final PseudoClass PSEUDO_CLASS_LAST_VISIBLE = 759 PseudoClass.getPseudoClass("last-visible"); 760 761 /** {@inheritDoc} */ 762 @Override protected Skin<?> createDefaultSkin() { 763 return new TreeTableCellSkin<S,T>(this); 764 } 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 * Accessibility handling * 782 * * 783 **************************************************************************/ 784 785 @Override 786 public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) { 787 switch (attribute) { 788 case ROW_INDEX: return getIndex(); 789 case COLUMN_INDEX: return columnIndex; 790 case SELECTED: return isInCellSelectionMode() ? isSelected() : getTreeTableRow().isSelected(); 791 default: return super.queryAccessibleAttribute(attribute, parameters); 792 } 793 } 794 795 @Override 796 public void executeAccessibleAction(AccessibleAction action, Object... parameters) { 797 switch (action) { 798 case REQUEST_FOCUS: { 799 TreeTableView<S> treeTableView = getTreeTableView(); 800 if (treeTableView != null) { 801 TreeTableViewFocusModel<S> fm = treeTableView.getFocusModel(); 802 if (fm != null) { 803 fm.focus(getIndex(), getTableColumn()); 804 } 805 } 806 break; 807 } 808 default: super.executeAccessibleAction(action, parameters); 809 } 810 } 811 }