Spec-Zone .ru
спецификации, руководства, описания, API
|
001/* 002 * Copyright (c) 2011, 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.property.ObjectProperty; 033import javafx.beans.property.SimpleObjectProperty; 034import javafx.collections.ListChangeListener; 035import javafx.scene.Node; 036import com.sun.javafx.scene.control.skin.TreeCellSkin; 037import javafx.collections.WeakListChangeListener; 038import java.lang.ref.WeakReference; 039import javafx.beans.property.BooleanProperty; 040import javafx.beans.property.ReadOnlyObjectProperty; 041import javafx.beans.property.ReadOnlyObjectWrapper; 042import javafx.beans.value.ChangeListener; 043import javafx.beans.value.ObservableValue; 044import javafx.beans.value.WeakChangeListener; 045 046/** 047 * The {@link Cell} type used with the {@link TreeView} control. In addition to 048 * the API defined on {@link IndexedCell}, the TreeCell 049 * exposes additional states and pseudo classes for use by CSS. 050 * <p> 051 * A TreeCell watches the selection model of the TreeView for which it is 052 * associated, ensuring that it visually indicates to the user whether it is 053 * selected. When a TreeCell is selected, this is exposed both via the 054 * {@link #selectedProperty() selected} property, as well as via the 'selected' 055 * CSS pseudo class state. 056 * <p> 057 * Due to the fact that TreeCell extends from {@link IndexedCell}, each TreeCell 058 * also provides an {@link #indexProperty() index} property. The index will be 059 * updated as cells are expanded and collapsed, and therefore should be 060 * considered a view index rather than a model index. 061 * <p> 062 * Finally, each TreeCell also has a reference back to the TreeView that it is 063 * being used with. Each TreeCell belongs to one and only one TreeView. 064 * 065 * @see TreeView 066 * @see TreeItem 067 * @param <T> The type of the value contained within the 068 * {@link #treeItemProperty() TreeItem} property. 069 */ 070public class TreeCell<T> extends IndexedCell<T> { 071 072 /*************************************************************************** 073 * * 074 * Constructors * 075 * * 076 **************************************************************************/ 077 078 /** 079 * Creates a default TreeCell instance. 080 */ 081 public TreeCell() { 082 getStyleClass().addAll(DEFAULT_STYLE_CLASS); 083 } 084 085 086 087 /*************************************************************************** 088 * * 089 * Callbacks and events * 090 * * 091 **************************************************************************/ 092 093 private final ListChangeListener<Integer> selectedListener = new ListChangeListener<Integer>() { 094 @Override public void onChanged(ListChangeListener.Change<? extends Integer> c) { 095 updateSelection(); 096 } 097 }; 098 099 /** 100 * Listens to the selectionModel property on the TreeView. Whenever the entire model is changed, 101 * we have to unhook the weakSelectedListener and update the selection. 102 */ 103 private final ChangeListener<MultipleSelectionModel<TreeItem<T>>> selectionModelPropertyListener = new ChangeListener<MultipleSelectionModel<TreeItem<T>>>() { 104 @Override public void changed(ObservableValue<? extends MultipleSelectionModel<TreeItem<T>>> observable, 105 MultipleSelectionModel<TreeItem<T>> oldValue, 106 MultipleSelectionModel<TreeItem<T>> newValue) { 107 if (oldValue != null) { 108 oldValue.getSelectedIndices().removeListener(weakSelectedListener); 109 } 110 if (newValue != null) { 111 newValue.getSelectedIndices().addListener(weakSelectedListener); 112 } 113 updateSelection(); 114 } 115 }; 116 117 private final InvalidationListener focusedListener = new InvalidationListener() { 118 @Override public void invalidated(Observable valueModel) { 119 updateFocus(); 120 } 121 }; 122 123 /** 124 * Listens to the focusModel property on the TreeView. Whenever the entire model is changed, 125 * we have to unhook the weakFocusedListener and update the focus. 126 */ 127 private final ChangeListener<FocusModel<TreeItem<T>>> focusModelPropertyListener = new ChangeListener<FocusModel<TreeItem<T>>>() { 128 @Override public void changed(ObservableValue<? extends FocusModel<TreeItem<T>>> observable, 129 FocusModel<TreeItem<T>> oldValue, 130 FocusModel<TreeItem<T>> newValue) { 131 if (oldValue != null) { 132 oldValue.focusedIndexProperty().removeListener(weakFocusedListener); 133 } 134 if (newValue != null) { 135 newValue.focusedIndexProperty().addListener(weakFocusedListener); 136 } 137 updateFocus(); 138 } 139 }; 140 141 private final InvalidationListener editingListener = new InvalidationListener() { 142 @Override public void invalidated(Observable valueModel) { 143 updateEditing(); 144 } 145 }; 146 147 private final InvalidationListener leafListener = new InvalidationListener() { 148 @Override public void invalidated(Observable valueModel) { 149 // necessary to update the disclosure node in the skin when the 150 // leaf property changes 151 TreeItem<T> treeItem = getTreeItem(); 152 if (treeItem != null) { 153 requestLayout(); 154 } 155 } 156 }; 157 158 /* proxy pseudo-class state change from treeItem's expandedProperty */ 159 private final InvalidationListener treeItemExpandedInvalidationListener = new InvalidationListener() { 160 @Override public void invalidated(Observable o) { 161 boolean isExpanded = ((BooleanProperty)o).get(); 162 pseudoClassStateChanged(EXPANDED_PSEUDOCLASS_STATE, isExpanded); 163 pseudoClassStateChanged(COLLAPSED_PSEUDOCLASS_STATE, !isExpanded); 164 } 165 }; 166 167 private final WeakListChangeListener<Integer> weakSelectedListener = new WeakListChangeListener<Integer>(selectedListener); 168 private final WeakChangeListener<MultipleSelectionModel<TreeItem<T>>> weakSelectionModelPropertyListener = new WeakChangeListener<MultipleSelectionModel<TreeItem<T>>>(selectionModelPropertyListener); 169 private final WeakInvalidationListener weakFocusedListener = new WeakInvalidationListener(focusedListener); 170 private final WeakChangeListener<FocusModel<TreeItem<T>>> weakFocusModelPropertyListener = new WeakChangeListener<FocusModel<TreeItem<T>>>(focusModelPropertyListener); 171 private final WeakInvalidationListener weakEditingListener = new WeakInvalidationListener(editingListener); 172 private final WeakInvalidationListener weakLeafListener = new WeakInvalidationListener(leafListener); 173 private final WeakInvalidationListener weakTreeItemExpandedInvalidationListener = 174 new WeakInvalidationListener(treeItemExpandedInvalidationListener); 175 176 177 178 /*************************************************************************** 179 * * 180 * Properties * 181 * * 182 **************************************************************************/ 183 184 // --- TreeItem 185 private ReadOnlyObjectWrapper<TreeItem<T>> treeItem = 186 new ReadOnlyObjectWrapper<TreeItem<T>>(this, "treeItem") { 187 188 TreeItem<T> oldValue = null; 189 190 @Override protected void invalidated() { 191 if (oldValue != null) { 192 oldValue.expandedProperty().removeListener(weakTreeItemExpandedInvalidationListener); 193 } 194 195 oldValue = get(); 196 197 if (oldValue != null) { 198 oldValue.expandedProperty().addListener(weakTreeItemExpandedInvalidationListener); 199 // fake an invalidation to ensure updated pseudo-class state 200 weakTreeItemExpandedInvalidationListener.invalidated(oldValue.expandedProperty()); 201 } 202 203 } 204 }; 205 private void setTreeItem(TreeItem<T> value) { 206 treeItem.set(value); 207 } 208 209 /** 210 * Returns the TreeItem currently set in this TreeCell. 211 */ 212 public final TreeItem<T> getTreeItem() { return treeItem.get(); } 213 214 /** 215 * Each TreeCell represents at most a single {@link TreeItem}, which is 216 * represented by this property. 217 */ 218 public final ReadOnlyObjectProperty<TreeItem<T>> treeItemProperty() { return treeItem.getReadOnlyProperty(); } 219 220 221 222 // --- Disclosure Node 223 private ObjectProperty<Node> disclosureNode = new SimpleObjectProperty<Node>(this, "disclosureNode"); 224 225 /** 226 * The node to use as the "disclosure" triangle, or toggle, used for 227 * expanding and collapsing items. This is only used in the case of 228 * an item in the tree which contains child items. If not specified, the 229 * TreeCell's Skin implementation is responsible for providing a default 230 * disclosure node. 231 */ 232 public final void setDisclosureNode(Node value) { disclosureNodeProperty().set(value); } 233 234 /** 235 * Returns the current disclosure node set in this TreeCell. 236 */ 237 public final Node getDisclosureNode() { return disclosureNode.get(); } 238 239 /** 240 * The disclosure node is commonly seen represented as a triangle that rotates 241 * on screen to indicate whether or not the TreeItem that it is placed 242 * beside is expanded or collapsed. 243 */ 244 public final ObjectProperty<Node> disclosureNodeProperty() { return disclosureNode; } 245 246 247 // --- TreeView 248 private ReadOnlyObjectWrapper<TreeView<T>> treeView = new ReadOnlyObjectWrapper<TreeView<T>>() { 249 private WeakReference<TreeView<T>> weakTreeViewRef; 250 @Override protected void invalidated() { 251 MultipleSelectionModel<TreeItem<T>> sm; 252 FocusModel<TreeItem<T>> fm; 253 254 if (weakTreeViewRef != null) { 255 TreeView<T> oldTreeView = weakTreeViewRef.get(); 256 if (oldTreeView != null) { 257 // remove old listeners 258 sm = oldTreeView.getSelectionModel(); 259 if (sm != null) { 260 sm.getSelectedIndices().removeListener(weakSelectedListener); 261 } 262 263 fm = oldTreeView.getFocusModel(); 264 if (fm != null) { 265 fm.focusedIndexProperty().removeListener(weakFocusedListener); 266 } 267 268 oldTreeView.editingItemProperty().removeListener(weakEditingListener); 269 oldTreeView.focusModelProperty().removeListener(weakFocusModelPropertyListener); 270 oldTreeView.selectionModelProperty().removeListener(weakSelectionModelPropertyListener); 271 } 272 273 weakTreeViewRef = null; 274 } 275 276 TreeView<T> treeView = get(); 277 if (treeView != null) { 278 sm = treeView.getSelectionModel(); 279 if (sm != null) { 280 // listening for changes to treeView.selectedIndex and IndexedCell.index, 281 // to determine if this cell is selected 282 sm.getSelectedIndices().addListener(weakSelectedListener); 283 } 284 285 fm = treeView.getFocusModel(); 286 if (fm != null) { 287 // similar to above, but this time for focus 288 fm.focusedIndexProperty().addListener(weakFocusedListener); 289 } 290 291 treeView.editingItemProperty().addListener(weakEditingListener); 292 treeView.focusModelProperty().addListener(weakFocusModelPropertyListener); 293 treeView.selectionModelProperty().addListener(weakSelectionModelPropertyListener); 294 295 weakTreeViewRef = new WeakReference<TreeView<T>>(treeView); 296 } 297 298 updateItem(); 299 requestLayout(); 300 } 301 302 @Override 303 public Object getBean() { 304 return TreeCell.this; 305 } 306 307 @Override 308 public String getName() { 309 return "treeView"; 310 } 311 }; 312 313 private void setTreeView(TreeView<T> value) { treeView.set(value); } 314 315 /** 316 * Returns the TreeView associated with this TreeCell. 317 */ 318 public final TreeView<T> getTreeView() { return treeView.get(); } 319 320 /** 321 * A TreeCell is explicitly linked to a single {@link TreeView} instance, 322 * which is represented by this property. 323 */ 324 public final ReadOnlyObjectProperty<TreeView<T>> treeViewProperty() { return treeView.getReadOnlyProperty(); } 325 326 327 328 /*************************************************************************** 329 * * 330 * Public API * 331 * * 332 **************************************************************************/ 333 334 /** {@inheritDoc} */ 335 @Override public void startEdit() { 336 final TreeView<T> tree = getTreeView(); 337 if (! isEditable() || (tree != null && ! tree.isEditable())) { 338// if (Logging.getControlsLogger().isLoggable(PlatformLogger.SEVERE)) { 339// Logging.getControlsLogger().severe( 340// "Can not call TreeCell.startEdit() on this TreeCell, as it " 341// + "is not allowed to enter its editing state (TreeCell: " 342// + this + ", TreeView: " + tree + ")."); 343// } 344 return; 345 } 346 347 // it makes sense to get the cell into its editing state before firing 348 // the event to the TreeView below, so that's what we're doing here 349 // by calling super.startEdit(). 350 super.startEdit(); 351 352 // Inform the TreeView of the edit starting. 353 if (tree != null) { 354 tree.fireEvent(new TreeView.EditEvent<T>(tree, 355 TreeView.<T>editStartEvent(), 356 getTreeItem(), 357 getItem(), 358 null)); 359 360 tree.requestFocus(); 361 } 362 } 363 364 /** {@inheritDoc} */ 365 @Override public void commitEdit(T newValue) { 366 if (! isEditing()) return; 367 final TreeItem<T> treeItem = getTreeItem(); 368 final TreeView<T> tree = getTreeView(); 369 if (tree != null) { 370 // Inform the TreeView of the edit being ready to be committed. 371 tree.fireEvent(new TreeView.EditEvent<T>(tree, 372 TreeView.<T>editCommitEvent(), 373 treeItem, 374 getItem(), 375 newValue)); 376 } 377 378 // update the item within this cell, so that it represents the new value 379 if (treeItem != null) { 380 treeItem.setValue(newValue); 381 updateTreeItem(treeItem); 382 updateItem(newValue, false); 383 } 384 385 // inform parent classes of the commit, so that they can switch us 386 // out of the editing state 387 super.commitEdit(newValue); 388 389 if (tree != null) { 390 // reset the editing item in the TreetView 391 tree.edit(null); 392 tree.requestFocus(); 393 } 394 } 395 396 /** {@inheritDoc} */ 397 @Override public void cancelEdit() { 398 if (! isEditing()) return; 399 400 TreeView<T> tree = getTreeView(); 401 402 super.cancelEdit(); 403 404 if (tree != null) { 405 // reset the editing index on the TreeView 406 if (updateEditingIndex) tree.edit(null); 407 tree.requestFocus(); 408 409 tree.fireEvent(new TreeView.EditEvent<T>(tree, 410 TreeView.<T>editCancelEvent(), 411 getTreeItem(), 412 getItem(), 413 null)); 414 } 415 } 416 417 /** {@inheritDoc} */ 418 @Override protected Skin<?> createDefaultSkin() { 419 return new TreeCellSkin(this); 420 } 421 422 /*************************************************************************** 423 * * 424 * Private Implementation * 425 * * 426 **************************************************************************/ 427 428 private int index = -1; 429 430 @Override void indexChanged() { 431 int oldIndex = index; 432 index = getIndex(); 433 434 // when the cell index changes, this may result in the cell 435 // changing state to be selected and/or focused. 436 updateItem(); 437 updateSelection(); 438 updateFocus(); 439 } 440 441 private void updateItem() { 442 TreeView<T> tv = getTreeView(); 443 if (tv == null) return; 444 445 // Compute whether the index for this cell is for a real item 446 boolean valid = index >=0 && index < tv.getExpandedItemCount(); 447 448 // Cause the cell to update itself 449 if (valid) { 450 TreeItem<T> oldTreeItem = getTreeItem(); 451 T oldValue = getItem(); 452 453 // update the TreeCell state. 454 // get the new treeItem that is about to go in to the TreeCell 455 TreeItem<T> newTreeItem = tv.getTreeItem(index); 456 T newValue = newTreeItem == null ? null : newTreeItem.getValue(); 457 458 // For the sake of RT-14279, it is important that the order of these 459 // method calls is as shown below. If the order is switched, it is 460 // likely that events will be fired where the item is null, even 461 // though calling cell.getTreeItem().getValue() returns the value 462 // as expected 463 if ((newTreeItem != null && ! newTreeItem.equals(oldTreeItem)) || 464 oldTreeItem != null && ! oldTreeItem.equals(newTreeItem)) { 465 updateTreeItem(newTreeItem); 466 } 467 468 if ((newValue != null && ! newValue.equals(oldValue)) || 469 oldValue != null && ! oldValue.equals(newValue)) { 470 updateItem(newValue, false); 471 } 472 } else { 473 if (! isEmpty()) { 474 updateTreeItem(null); 475 updateItem(null, true); 476 } 477 } 478 } 479 480 private void updateSelection() { 481 if (isEmpty()) return; 482 if (index == -1 || getTreeView() == null) return; 483 if (getTreeView().getSelectionModel() == null) return; 484 485 boolean isSelected = getTreeView().getSelectionModel().isSelected(index); 486 if (isSelected() == isSelected) return; 487 488 updateSelected(isSelected); 489 } 490 491 private void updateFocus() { 492 if (index == -1 || getTreeView() == null) return; 493 if (getTreeView().getFocusModel() == null) return; 494 495 setFocused(getTreeView().getFocusModel().isFocused(index)); 496 } 497 498 private boolean updateEditingIndex = true; 499 private void updateEditing() { 500 final int index = getIndex(); 501 final TreeView<T> tree = getTreeView(); 502 final TreeItem<T> treeItem = getTreeItem(); 503 final TreeItem<T> editItem = tree == null ? null : tree.getEditingItem(); 504 final boolean editing = isEditing(); 505 506 if (index == -1 || tree == null || treeItem == null) return; 507 508 final boolean match = treeItem.equals(editItem); 509 510 // If my tree item is the item being edited and I'm not currently in 511 // the edit mode, then I need to enter the edit mode 512 if (match && !editing) { 513 startEdit(); 514 } else if (match && editing) { 515 // If my tree item is not the one being edited then I need to cancel 516 // the edit. The tricky thing here is that as part of this call 517 // I cannot end up calling tree.edit(null) the way that the standard 518 // cancelEdit method would do. Yet, I need to call cancelEdit 519 // so that subclasses which override cancelEdit can execute. So, 520 // I have to use a kind of hacky flag workaround. 521 updateEditingIndex = false; 522 cancelEdit(); 523 updateEditingIndex = true; 524 } 525 } 526 527 528 529 /*************************************************************************** 530 * * 531 * Expert API * 532 * * 533 **************************************************************************/ 534 535 536 537 /** 538 * Updates the TreeView associated with this TreeCell. 539 * 540 * @param tree The new TreeView that should be associated with this TreeCell. 541 * @expert This function is intended to be used by experts, primarily 542 * by those implementing new Skins. It is not common 543 * for developers or designers to access this function directly. 544 */ 545 public final void updateTreeView(TreeView<T> tree) { 546 setTreeView(tree); 547 } 548 549 /** 550 * Updates the TreeItem associated with this TreeCell. 551 * 552 * @param treeItem The new TreeItem that should be associated with this 553 * TreeCell. 554 * @expert This function is intended to be used by experts, primarily 555 * by those implementing new Skins. It is not common 556 * for developers or designers to access this function directly. 557 */ 558 public final void updateTreeItem(TreeItem<T> treeItem) { 559 TreeItem<T> _treeItem = getTreeItem(); 560 if (_treeItem != null) { 561 _treeItem.leafProperty().removeListener(weakLeafListener); 562 } 563 setTreeItem(treeItem); 564 if (treeItem != null) { 565 treeItem.leafProperty().addListener(weakLeafListener); 566 } 567 } 568 569 570 571 /*************************************************************************** 572 * * 573 * Stylesheet Handling * 574 * * 575 **************************************************************************/ 576 577 private static final String DEFAULT_STYLE_CLASS = "tree-cell"; 578 579 private static final PseudoClass EXPANDED_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("expanded"); 580 private static final PseudoClass COLLAPSED_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("collapsed"); 581 582}