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