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