Spec-Zone .ru
спецификации, руководства, описания, API
|
001/* 002 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. Oracle designates this 008 * particular file as subject to the "Classpath" exception as provided 009 * by Oracle in the LICENSE file that accompanied this code. 010 * 011 * This code is distributed in the hope that it will be useful, but WITHOUT 012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 014 * version 2 for more details (a copy is included in the LICENSE file that 015 * accompanied this code). 016 * 017 * You should have received a copy of the GNU General Public License version 018 * 2 along with this work; if not, write to the Free Software Foundation, 019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 020 * 021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 022 * or visit www.oracle.com if you need additional information or have any 023 * questions. 024 */ 025 026package javafx.scene.control; 027 028import javafx.css.PseudoClass; 029import com.sun.javafx.scene.control.skin.TreeTableCellSkin; 030import javafx.beans.InvalidationListener; 031import javafx.beans.Observable; 032import javafx.beans.WeakInvalidationListener; 033import javafx.beans.value.ObservableValue; 034import javafx.collections.ListChangeListener; 035import javafx.event.Event; 036 037import javafx.collections.WeakListChangeListener; 038import java.lang.ref.WeakReference; 039import javafx.beans.property.ReadOnlyObjectProperty; 040import javafx.beans.property.ReadOnlyObjectWrapper; 041 042import javafx.scene.control.TreeTableColumn.CellEditEvent; 043 044 045/** 046 * Represents a single row/column intersection in a {@link TreeTableView}. To 047 * represent this intersection, a TreeTableCell contains an 048 * {@link #indexProperty() index} property, as well as a 049 * {@link #tableColumnProperty() tableColumn} property. In addition, a TreeTableCell 050 * instance knows what {@link TreeTableRow} it exists in. 051 * 052 * @see TreeTableView 053 * @see TreeTableColumn 054 * @see Cell 055 * @see IndexedCell 056 * @see TreeTableRow 057 * @param <T> The type of the item contained within the Cell. 058 */ 059public class TreeTableCell<S,T> extends IndexedCell<T> { 060 061 /*************************************************************************** 062 * * 063 * Constructors * 064 * * 065 **************************************************************************/ 066 067 /** 068 * Constructs a default TreeTableCell instance with a style class of 069 * 'tree-table-cell'. 070 */ 071 public TreeTableCell() { 072 getStyleClass().addAll(DEFAULT_STYLE_CLASS); 073 074 updateColumnIndex(); 075 } 076 077 078 079 /*************************************************************************** 080 * * 081 * Callbacks and Events * 082 * * 083 **************************************************************************/ 084 085 private boolean itemDirty = false; 086 087 /* 088 * This is the list observer we use to keep an eye on the SelectedCells 089 * ObservableList in the tree table view. Because it is possible that the table can 090 * be mutated, we create this observer here, and add/remove it from the 091 * storeTableView method. 092 */ 093 private ListChangeListener<TreeTablePosition<S,?>> selectedListener = new ListChangeListener<TreeTablePosition<S,?>>() { 094 @Override public void onChanged(Change<? extends TreeTablePosition<S,?>> c) { 095 updateSelection(); 096 } 097 }; 098 099 // same as above, but for focus 100 private final InvalidationListener focusedListener = new InvalidationListener() { 101 @Override public void invalidated(@SuppressWarnings("unused") Observable value) { 102 updateFocus(); 103 } 104 }; 105 106 // same as above, but for for changes to the properties on TableRow 107 private final InvalidationListener tableRowUpdateObserver = new InvalidationListener() { 108 @Override public void invalidated(@SuppressWarnings("unused") Observable value) { 109 itemDirty = true; 110 requestLayout(); 111 } 112 }; 113 114 private final InvalidationListener editingListener = new InvalidationListener() { 115 @Override public void invalidated(@SuppressWarnings("unused") Observable value) { 116 updateEditing(); 117 } 118 }; 119 120 private ListChangeListener<TreeTableColumn<S,?>> visibleLeafColumnsListener = new ListChangeListener<TreeTableColumn<S,?>>() { 121 @Override public void onChanged(Change<? extends TreeTableColumn<S,?>> c) { 122 updateColumnIndex(); 123 } 124 }; 125 126 private ListChangeListener<String> columnStyleClassListener = new ListChangeListener<String>() { 127 @Override public void onChanged(Change<? extends String> c) { 128 while (c.next()) { 129 if (c.wasRemoved()) { 130 getStyleClass().removeAll(c.getRemoved()); 131 } 132 133 if (c.wasAdded()) { 134 getStyleClass().addAll(c.getAddedSubList()); 135 } 136 } 137 } 138 }; 139 140 private final WeakListChangeListener<TreeTablePosition<S,?>> weakSelectedListener = 141 new WeakListChangeListener<TreeTablePosition<S,?>>(selectedListener); 142 private final WeakInvalidationListener weakFocusedListener = 143 new WeakInvalidationListener(focusedListener); 144 private final WeakInvalidationListener weaktableRowUpdateObserver = 145 new WeakInvalidationListener(tableRowUpdateObserver); 146 private final WeakInvalidationListener weakEditingListener = 147 new WeakInvalidationListener(editingListener); 148 private final WeakListChangeListener<TreeTableColumn<S,?>> weakVisibleLeafColumnsListener = 149 new WeakListChangeListener<TreeTableColumn<S,?>>(visibleLeafColumnsListener); 150 private final WeakListChangeListener<String> weakColumnStyleClassListener = 151 new WeakListChangeListener<String>(columnStyleClassListener); 152 153 154 /*************************************************************************** 155 * * 156 * Properties * 157 * * 158 **************************************************************************/ 159 160 // --- TableColumn 161 /** 162 * The TreeTableColumn instance that backs this TreeTableCell. 163 */ 164 private ReadOnlyObjectWrapper<TreeTableColumn<S,T>> treeTableColumn = 165 new ReadOnlyObjectWrapper<TreeTableColumn<S,T>>(this, "treeTableColumn") { 166 @Override protected void invalidated() { 167 updateColumnIndex(); 168 } 169 }; 170 public final ReadOnlyObjectProperty<TreeTableColumn<S,T>> tableColumnProperty() { return treeTableColumn.getReadOnlyProperty(); } 171 private void setTableColumn(TreeTableColumn<S,T> value) { treeTableColumn.set(value); } 172 public final TreeTableColumn<S,T> getTableColumn() { return treeTableColumn.get(); } 173 174 175 // --- TableView 176 /** 177 * The TreeTableView associated with this TreeTableCell. 178 */ 179 private ReadOnlyObjectWrapper<TreeTableView<S>> treeTableView; 180 private void setTreeTableView(TreeTableView<S> value) { 181 treeTableViewPropertyImpl().set(value); 182 } 183 public final TreeTableView<S> getTreeTableView() { 184 return treeTableView == null ? null : treeTableView.get(); 185 } 186 public final ReadOnlyObjectProperty<TreeTableView<S>> treeTableViewProperty() { 187 return treeTableViewPropertyImpl().getReadOnlyProperty(); 188 } 189 190 private ReadOnlyObjectWrapper<TreeTableView<S>> treeTableViewPropertyImpl() { 191 if (treeTableView == null) { 192 treeTableView = new ReadOnlyObjectWrapper<TreeTableView<S>>(this, "treeTableView") { 193 private WeakReference<TreeTableView<S>> weakTableViewRef; 194 @Override protected void invalidated() { 195 TreeTableView.TreeTableViewSelectionModel<S> sm; 196 TreeTableView.TreeTableViewFocusModel<S> fm; 197 198 if (weakTableViewRef != null) { 199 TreeTableView<S> oldTableView = weakTableViewRef.get(); 200 if (oldTableView != null) { 201 sm = oldTableView.getSelectionModel(); 202 if (sm != null) { 203 sm.getSelectedCells().removeListener(weakSelectedListener); 204 } 205 206 fm = oldTableView.getFocusModel(); 207 if (fm != null) { 208 fm.focusedCellProperty().removeListener(weakFocusedListener); 209 } 210 211 oldTableView.editingCellProperty().removeListener(weakEditingListener); 212 oldTableView.getVisibleLeafColumns().removeListener(weakVisibleLeafColumnsListener); 213 } 214 } 215 216 if (get() != null) { 217 sm = get().getSelectionModel(); 218 if (sm != null) { 219 sm.getSelectedCells().addListener(weakSelectedListener); 220 } 221 222 fm = get().getFocusModel(); 223 if (fm != null) { 224 fm.focusedCellProperty().addListener(weakFocusedListener); 225 } 226 227 get().editingCellProperty().addListener(weakEditingListener); 228 get().getVisibleLeafColumns().addListener(weakVisibleLeafColumnsListener); 229 230 weakTableViewRef = new WeakReference<TreeTableView<S>>(get()); 231 } 232 233 updateColumnIndex(); 234 } 235 }; 236 } 237 return treeTableView; 238 } 239 240 241 // --- TableRow 242 /** 243 * The TreeTableRow that this TreeTableCell currently finds itself placed within. 244 */ 245 private ReadOnlyObjectWrapper<TreeTableRow<S>> treeTableRow = 246 new ReadOnlyObjectWrapper<TreeTableRow<S>>(this, "treeTableRow"); 247 private void setTreeTableRow(TreeTableRow<S> value) { treeTableRow.set(value); } 248 public final TreeTableRow<S> getTreeTableRow() { return treeTableRow.get(); } 249 public final ReadOnlyObjectProperty<TreeTableRow<S>> tableRowProperty() { return treeTableRow; } 250 251 252 253 /*************************************************************************** 254 * * 255 * Editing API * 256 * * 257 **************************************************************************/ 258 259 /** {@inheritDoc} */ 260 @Override public void startEdit() { 261 final TreeTableView<S> table = getTreeTableView(); 262 final TreeTableColumn<S,T> column = getTableColumn(); 263 if (! isEditable() || 264 (table != null && ! table.isEditable()) || 265 (column != null && ! getTableColumn().isEditable())) { 266 return; 267 } 268 269 // it makes sense to get the cell into its editing state before firing 270 // the event to listeners below, so that's what we're doing here 271 // by calling super.startEdit(). 272 super.startEdit(); 273 274 @SuppressWarnings("unchecked") 275 TreeTablePosition<S,T> editingCell = (TreeTablePosition<S,T>) table.getEditingCell(); 276 277 if (column != null) { 278 CellEditEvent<S,T> editEvent = new CellEditEvent<S,T>( 279 table, 280 editingCell, 281 TreeTableColumn.<S,T>editStartEvent(), 282 null 283 ); 284 285 Event.fireEvent(column, editEvent); 286 } 287 } 288 289 /** {@inheritDoc} */ 290 @Override public void commitEdit(T newValue) { 291 if (! isEditing()) return; 292 293 final TreeTableView<S> table = getTreeTableView(); 294 if (table != null) { 295 @SuppressWarnings("unchecked") 296 TreeTablePosition<S,T> editingCell = (TreeTablePosition<S,T>) table.getEditingCell(); 297 298 // Inform the TableView of the edit being ready to be committed. 299 CellEditEvent<S,T> editEvent = new CellEditEvent<S,T>( 300 table, 301 editingCell, 302 TreeTableColumn.<S,T>editCommitEvent(), 303 newValue 304 ); 305 306 Event.fireEvent(getTableColumn(), editEvent); 307 } 308 309 // update the item within this cell, so that it represents the new value 310 updateItem(newValue, false); 311 312 // inform parent classes of the commit, so that they can switch us 313 // out of the editing state 314 super.commitEdit(newValue); 315 316 if (table != null) { 317 // reset the editing cell on the TableView 318 table.edit(-1, null); 319 table.requestFocus(); 320 } 321 } 322 323 /** {@inheritDoc} */ 324 @Override public void cancelEdit() { 325 if (! isEditing()) return; 326 327 final TreeTableView<S> table = getTreeTableView(); 328 329 super.cancelEdit(); 330 331 // reset the editing index on the TableView 332 if (table != null) { 333 @SuppressWarnings("unchecked") 334 TreeTablePosition<S,T> editingCell = (TreeTablePosition<S,T>) table.getEditingCell(); 335 336 if (updateEditingIndex) table.edit(-1, null); 337 338 CellEditEvent<S,T> editEvent = new CellEditEvent<S,T>( 339 table, 340 editingCell, 341 TreeTableColumn.<S,T>editCancelEvent(), 342 null 343 ); 344 345 Event.fireEvent(getTableColumn(), editEvent); 346 } 347 } 348 349 350 351 /* ************************************************************************* 352 * * 353 * Overriding methods * 354 * * 355 **************************************************************************/ 356 357 /** {@inheritDoc} */ 358 @Override public void updateSelected(boolean selected) { 359 // copied from Cell, with the first conditional clause below commented 360 // out, as it is valid for an empty TableCell to be selected, as long 361 // as the parent TableRow is not empty (see RT-15529). 362 /*if (selected && isEmpty()) return;*/ 363 if (getTreeTableRow() == null || getTreeTableRow().isEmpty()) return; 364 setSelected(selected); 365 } 366 367 368 369 /* ************************************************************************* 370 * * 371 * Private Implementation * 372 * * 373 **************************************************************************/ 374 375 @Override void indexChanged() { 376 super.indexChanged(); 377 // Ideally we would just use the following two lines of code, rather 378 // than the updateItem() call beneath, but if we do this we end up with 379 // RT-22428 where all the columns are collapsed. 380 // itemDirty = true; 381 // requestLayout(); 382 updateItem(); 383 updateSelection(); 384 updateFocus(); 385 } 386 387 private boolean isLastVisibleColumn = false; 388 private int columnIndex = -1; 389 390 private void updateColumnIndex() { 391 final TreeTableView<S> tv = getTreeTableView(); 392 TreeTableColumn<S,T> tc = getTableColumn(); 393 columnIndex = tv == null || tc == null ? -1 : tv.getVisibleLeafIndex(tc); 394 395 // update the pseudo class state regarding whether this is the last 396 // visible cell (i.e. the right-most). 397 isLastVisibleColumn = getTableColumn() != null && 398 columnIndex != -1 && 399 columnIndex == tv.getVisibleLeafColumns().size() - 1; 400 pseudoClassStateChanged(PSEUDO_CLASS_LAST_VISIBLE, isLastVisibleColumn); 401 } 402 403 private void updateSelection() { 404 /* 405 * This cell should be selected if the selection mode of the table 406 * is cell-based, and if the row and column that this cell represents 407 * is selected. 408 * 409 * If the selection mode is not cell-based, then the listener in the 410 * TableRow class might pick up the need to set an entire row to be 411 * selected. 412 */ 413 if (isEmpty()) return; 414 415 final TreeTableView<S> tv = getTreeTableView(); 416 if (getIndex() == -1 || getTreeTableView() == null) return; 417 if (tv.getSelectionModel() == null) return; 418 419 boolean isSelected = isInCellSelectionMode() && 420 tv.getSelectionModel().isSelected(getIndex(), getTableColumn()); 421 if (isSelected() == isSelected) return; 422 423 updateSelected(isSelected); 424 } 425 426 private void updateFocus() { 427 final TreeTableView<S> tv = getTreeTableView(); 428 if (getIndex() == -1 || tv == null) return; 429 if (tv.getFocusModel() == null) return; 430 431 boolean isFocused = isInCellSelectionMode() && 432 tv.getFocusModel() != null && 433 tv.getFocusModel().isFocused(getIndex(), getTableColumn()); 434 435 setFocused(isFocused); 436 } 437 438 private void updateEditing() { 439 final TreeTableView<S> tv = getTreeTableView(); 440 if (getIndex() == -1 || tv == null) return; 441 442 TreeTablePosition<S,?> editCell = tv.getEditingCell(); 443 boolean match = match(editCell); 444 445 if (match && ! isEditing()) { 446 startEdit(); 447 } else if (! match && isEditing()) { 448 // If my index is not the one being edited then I need to cancel 449 // the edit. The tricky thing here is that as part of this call 450 // I cannot end up calling list.edit(-1) the way that the standard 451 // cancelEdit method would do. Yet, I need to call cancelEdit 452 // so that subclasses which override cancelEdit can execute. So, 453 // I have to use a kind of hacky flag workaround. 454 updateEditingIndex = false; 455 cancelEdit(); 456 updateEditingIndex = true; 457 } 458 } 459 private boolean updateEditingIndex = true; 460 461 private boolean match(TreeTablePosition pos) { 462 return pos != null && pos.getRow() == getIndex() && pos.getTableColumn() == getTableColumn(); 463 } 464 465 private boolean isInCellSelectionMode() { 466 TreeTableView<S> treeTable = getTreeTableView(); 467 return treeTable != null && 468 treeTable.getSelectionModel() != null && 469 treeTable.getSelectionModel().isCellSelectionEnabled(); 470 } 471 472 /* 473 * This was brought in to fix the issue in RT-22077, namely that the 474 * ObservableValue was being GC'd, meaning that changes to the value were 475 * no longer being delivered. By extracting this value out of the method, 476 * it is now referred to from TableCell and will therefore no longer be 477 * GC'd. 478 */ 479 private ObservableValue<T> currentObservableValue = null; 480 481 /* 482 * This is called when we think that the data within this TreeTableCell may have 483 * changed. You'll note that this is a private function - it is only called 484 * when one of the triggers above call it. 485 */ 486 private void updateItem() { 487 if (currentObservableValue != null) { 488 currentObservableValue.removeListener(weaktableRowUpdateObserver); 489 } 490 491 // get the total number of items in the data model 492 final TreeTableView<S> tableView = getTreeTableView(); 493 final TreeTableColumn<S,T> tableColumn = getTableColumn(); 494 final int itemCount = tableView == null ? -1 : getTreeTableView().getExpandedItemCount(); 495 final int index = getIndex(); 496 497 // there is a whole heap of reasons why we should just punt... 498 if (index >= itemCount || 499 index < 0 || 500 columnIndex < 0 || 501 !isVisible() || 502 tableColumn == null || 503 !tableColumn.isVisible() || 504 tableView.getRoot() == null) { 505 506 if (! isEmpty()) { 507 updateItem(null, true); 508 } 509 return; 510 } else { 511 currentObservableValue = tableColumn.getCellObservableValue(index); 512 513 T oldValue = getItem(); 514 T newValue = currentObservableValue == null ? null : currentObservableValue.getValue(); 515 516 // update the 'item' property of this cell. 517 if ((newValue != null && ! newValue.equals(oldValue)) || 518 oldValue != null && ! oldValue.equals(newValue)) { 519 updateItem(newValue, false); 520 } 521 } 522 523 if (currentObservableValue == null) { 524 return; 525 } 526 527 // add property change listeners to this item 528 currentObservableValue.addListener(weaktableRowUpdateObserver); 529 } 530 531 @Override protected void layoutChildren() { 532 if (itemDirty) { 533 updateItem(); 534 itemDirty = false; 535 } 536 super.layoutChildren(); 537 } 538 539 540 541 542 /*************************************************************************** 543 * * 544 * Expert API * 545 * * 546 **************************************************************************/ 547 548 /** 549 * Updates the TreeTableView associated with this TreeTableCell. This is typically 550 * only done once when the TreeTableCell is first added to the TreeTableView. 551 * 552 * @expert This function is intended to be used by experts, primarily 553 * by those implementing new Skins. It is not common 554 * for developers or designers to access this function directly. 555 */ 556 public final void updateTreeTableView(TreeTableView<S> tv) { 557 setTreeTableView(tv); 558 } 559 560 /** 561 * Updates the TreeTableRow associated with this TreeTableCell. 562 * 563 * @expert This function is intended to be used by experts, primarily 564 * by those implementing new Skins. It is not common 565 * for developers or designers to access this function directly. 566 */ 567 public final void updateTreeTableRow(TreeTableRow<S> treeTableRow) { 568 this.setTreeTableRow(treeTableRow); 569 } 570 571 /** 572 * Updates the TreeTableColumn associated with this TreeTableCell. 573 * 574 * @expert This function is intended to be used by experts, primarily 575 * by those implementing new Skins. It is not common 576 * for developers or designers to access this function directly. 577 */ 578 public final void updateTreeTableColumn(TreeTableColumn<S,T> col) { 579 // remove style class of existing tree table column, if it is non-null 580 TreeTableColumn<S,T> oldCol = getTableColumn(); 581 if (oldCol != null) { 582 oldCol.getStyleClass().removeListener(weakColumnStyleClassListener); 583 getStyleClass().removeAll(oldCol.getStyleClass()); 584 } 585 586 setTableColumn(col); 587 588 if (col != null) { 589 getStyleClass().addAll(col.getStyleClass()); 590 col.getStyleClass().addListener(weakColumnStyleClassListener); 591 } 592 } 593 594 595 596 /*************************************************************************** 597 * * 598 * Stylesheet Handling * 599 * * 600 **************************************************************************/ 601 602 private static final String DEFAULT_STYLE_CLASS = "tree-table-cell"; 603 private static final PseudoClass PSEUDO_CLASS_LAST_VISIBLE = 604 PseudoClass.getPseudoClass("last-visible"); 605 606 /** {@inheritDoc} */ 607 @Override protected Skin<?> createDefaultSkin() { 608 return new TreeTableCellSkin<S,T>(this); 609 } 610}