Spec-Zone .ru
спецификации, руководства, описания, API
|
001/* 002 * Copyright (c) 2012, 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.TreeTableRowSkin; 030import java.lang.ref.WeakReference; 031import javafx.beans.InvalidationListener; 032import javafx.beans.Observable; 033import javafx.beans.WeakInvalidationListener; 034import javafx.beans.property.BooleanProperty; 035import javafx.beans.property.ObjectProperty; 036import javafx.beans.property.ReadOnlyObjectProperty; 037import javafx.beans.property.ReadOnlyObjectWrapper; 038import javafx.beans.property.SimpleObjectProperty; 039import javafx.collections.ListChangeListener; 040import javafx.collections.WeakListChangeListener; 041import javafx.scene.Node; 042import javafx.scene.control.TreeTableView.TreeTableViewFocusModel; 043import javafx.scene.control.TreeTableView.TreeTableViewSelectionModel; 044 045/** 046 * <p>TreeTableRow is an {@link javafx.scene.control.IndexedCell IndexedCell}, but 047 * rarely needs to be used by developers creating TreeTableView instances. The only 048 * time TreeTableRow is likely to be encountered at all by a developer is if they 049 * wish to create a custom {@link TreeTableView#rowFactoryProperty() rowFactory} 050 * that replaces an entire row of a TreeTableView.</p> 051 * 052 * <p>More often than not, it is actually easier for a developer to customize 053 * individual cells in a row, rather than the whole row itself. To do this, 054 * you can specify a custom {@link TreeTableColumn#cellFactoryProperty() cellFactory} 055 * on each TreeTableColumn instance.</p> 056 * 057 * @see TreeTableView 058 * @see TreeTableColumn 059 * @see TreeTableCell 060 * @see IndexedCell 061 * @see Cell 062 * @param <T> The type of the item contained within the Cell. 063 */ 064public class TreeTableRow<T> extends IndexedCell<T> { 065 066 067 /*************************************************************************** 068 * * 069 * Constructors * 070 * * 071 **************************************************************************/ 072 073 /** 074 * Creates a default TreeTableRow instance. 075 */ 076 public TreeTableRow() { 077 getStyleClass().addAll(DEFAULT_STYLE_CLASS); 078 } 079 080 081 082 /*************************************************************************** 083 * * 084 * Callbacks and events * 085 * * 086 **************************************************************************/ 087 088 private final ListChangeListener<Integer> selectedListener = new ListChangeListener<Integer>() { 089 @Override public void onChanged(ListChangeListener.Change<? extends Integer> c) { 090 updateSelection(); 091 } 092 }; 093 094 private final InvalidationListener focusedListener = new InvalidationListener() { 095 @Override public void invalidated(Observable valueModel) { 096 updateFocus(); 097 } 098 }; 099 100 private final InvalidationListener editingListener = new InvalidationListener() { 101 @Override public void invalidated(Observable valueModel) { 102 updateEditing(); 103 } 104 }; 105 106 private final InvalidationListener leafListener = new InvalidationListener() { 107 @Override public void invalidated(Observable valueModel) { 108 // necessary to update the disclosure node in the skin when the 109 // leaf property changes 110 TreeItem<T> treeItem = getTreeItem(); 111 if (treeItem != null) { 112 requestLayout(); 113 } 114 } 115 }; 116 117 private final InvalidationListener treeItemExpandedInvalidationListener = new InvalidationListener() { 118 @Override public void invalidated(Observable o) { 119 final boolean expanded = ((BooleanProperty)o).get(); 120 pseudoClassStateChanged(EXPANDED_PSEUDOCLASS_STATE, expanded); 121 pseudoClassStateChanged(COLLAPSED_PSEUDOCLASS_STATE, !expanded); 122 } 123 }; 124 125 private final WeakListChangeListener<Integer> weakSelectedListener = 126 new WeakListChangeListener<Integer>(selectedListener); 127 private final WeakInvalidationListener weakFocusedListener = 128 new WeakInvalidationListener(focusedListener); 129 private final WeakInvalidationListener weakEditingListener = 130 new WeakInvalidationListener(editingListener); 131 private final WeakInvalidationListener weakLeafListener = 132 new WeakInvalidationListener(leafListener); 133 private final WeakInvalidationListener weakTreeItemExpandedInvalidationListener = 134 new WeakInvalidationListener(treeItemExpandedInvalidationListener); 135 136 137 138 /*************************************************************************** 139 * * 140 * Properties * 141 * * 142 **************************************************************************/ 143 144 // --- TreeItem 145 private ReadOnlyObjectWrapper<TreeItem<T>> treeItem = 146 new ReadOnlyObjectWrapper<TreeItem<T>>(this, "treeItem") { 147 148 TreeItem<T> oldValue = null; 149 150 @Override protected void invalidated() { 151 if (oldValue != null) { 152 oldValue.expandedProperty().removeListener(weakTreeItemExpandedInvalidationListener); 153 } 154 155 oldValue = get(); 156 157 if (oldValue != null) { 158 oldValue.expandedProperty().addListener(weakTreeItemExpandedInvalidationListener); 159 // fake an invalidation to ensure updated pseudo-class state 160 weakTreeItemExpandedInvalidationListener.invalidated(oldValue.expandedProperty()); 161 } 162 } 163 }; 164 private void setTreeItem(TreeItem<T> value) { 165 treeItem.set(value); 166 } 167 168 /** 169 * Returns the TreeItem currently set in this TreeCell. 170 */ 171 public final TreeItem<T> getTreeItem() { return treeItem.get(); } 172 173 /** 174 * Each TreeTableCell represents at most a single {@link TreeItem}, which is 175 * represented by this property. 176 */ 177 public final ReadOnlyObjectProperty<TreeItem<T>> treeItemProperty() { return treeItem.getReadOnlyProperty(); } 178 179 180 181 // --- Disclosure Node 182 private ObjectProperty<Node> disclosureNode = new SimpleObjectProperty<Node>(this, "disclosureNode"); 183 184 /** 185 * The node to use as the "disclosure" triangle, or toggle, used for 186 * expanding and collapsing items. This is only used in the case of 187 * an item in the tree which contains child items. If not specified, the 188 * TreeTableCell's Skin implementation is responsible for providing a default 189 * disclosure node. 190 */ 191 public final void setDisclosureNode(Node value) { disclosureNodeProperty().set(value); } 192 193 /** 194 * Returns the current disclosure node set in this TreeTableCell. 195 */ 196 public final Node getDisclosureNode() { return disclosureNode.get(); } 197 198 /** 199 * The disclosure node is commonly seen represented as a triangle that rotates 200 * on screen to indicate whether or not the TreeItem that it is placed 201 * beside is expanded or collapsed. 202 */ 203 public final ObjectProperty<Node> disclosureNodeProperty() { return disclosureNode; } 204 205 206 // --- TreeView 207 private ReadOnlyObjectWrapper<TreeTableView<T>> treeTableView = new ReadOnlyObjectWrapper<TreeTableView<T>>(this, "treeTableView") { 208 private WeakReference<TreeTableView<T>> weakTreeTableViewRef; 209 @Override protected void invalidated() { 210 TreeTableViewSelectionModel<T> sm; 211 TreeTableViewFocusModel<T> fm; 212 213 if (weakTreeTableViewRef != null) { 214 TreeTableView<T> oldTreeTableView = weakTreeTableViewRef.get(); 215 if (oldTreeTableView != null) { 216 // remove old listeners 217 sm = oldTreeTableView.getSelectionModel(); 218 if (sm != null) { 219 sm.getSelectedIndices().removeListener(weakSelectedListener); 220 } 221 222 fm = oldTreeTableView.getFocusModel(); 223 if (fm != null) { 224 fm.focusedIndexProperty().removeListener(weakFocusedListener); 225 } 226 227 oldTreeTableView.editingItemProperty().removeListener(weakEditingListener); 228 } 229 230 weakTreeTableViewRef = null; 231 } 232 233 if (get() != null) { 234 sm = get().getSelectionModel(); 235 if (sm != null) { 236 // listening for changes to treeView.selectedIndex and IndexedCell.index, 237 // to determine if this cell is selected 238 sm.getSelectedIndices().addListener(weakSelectedListener); 239 } 240 241 fm = get().getFocusModel(); 242 if (fm != null) { 243 // similar to above, but this time for focus 244 fm.focusedIndexProperty().addListener(weakFocusedListener); 245 } 246 247 get().editingItemProperty().addListener(weakEditingListener); 248 249 weakTreeTableViewRef = new WeakReference<TreeTableView<T>>(get()); 250 } 251 252 updateItem(); 253 requestLayout(); 254 } 255 }; 256 257 private void setTreeTableView(TreeTableView<T> value) { treeTableView.set(value); } 258 259 /** 260 * Returns the TreeTableView associated with this TreeTableCell. 261 */ 262 public final TreeTableView<T> getTreeTableView() { return treeTableView.get(); } 263 264 /** 265 * A TreeTableCell is explicitly linked to a single {@link TreeTableView} instance, 266 * which is represented by this property. 267 */ 268 public final ReadOnlyObjectProperty<TreeTableView<T>> treeTableViewProperty() { return treeTableView.getReadOnlyProperty(); } 269 270 271 272 273 /*************************************************************************** 274 * * 275 * Public API * 276 * * 277 **************************************************************************/ 278 279 280 @Override void indexChanged() { 281 index = getIndex(); 282 283 // when the cell index changes, this may result in the cell 284 // changing state to be selected and/or focused. 285 updateItem(); 286 updateSelection(); 287 updateFocus(); 288// oldIndex = index; 289 } 290 291 292 /** {@inheritDoc} */ 293 @Override public void startEdit() { 294 final TreeTableView<T> treeTable = getTreeTableView(); 295 if (! isEditable() || (treeTable != null && ! treeTable.isEditable())) { 296 return; 297 } 298 299 // it makes sense to get the cell into its editing state before firing 300 // the event to the TreeView below, so that's what we're doing here 301 // by calling super.startEdit(). 302 super.startEdit(); 303 304 // Inform the TreeView of the edit starting. 305 if (treeTable != null) { 306 treeTable.fireEvent(new TreeTableView.EditEvent<T>(treeTable, 307 TreeTableView.<T>editStartEvent(), 308 getTreeItem(), 309 getItem(), 310 null)); 311 312 treeTable.requestFocus(); 313 } 314 } 315 316 /** {@inheritDoc} */ 317 @Override public void commitEdit(T newValue) { 318 if (! isEditing()) return; 319 final TreeItem<T> treeItem = getTreeItem(); 320 final TreeTableView<T> treeTable = getTreeTableView(); 321 if (treeTable != null) { 322 // Inform the TreeView of the edit being ready to be committed. 323 treeTable.fireEvent(new TreeTableView.EditEvent<T>(treeTable, 324 TreeTableView.<T>editCommitEvent(), 325 treeItem, 326 getItem(), 327 newValue)); 328 } 329 330 // update the item within this cell, so that it represents the new value 331 if (treeItem != null) { 332 treeItem.setValue(newValue); 333 updateTreeItem(treeItem); 334 updateItem(newValue, false); 335 } 336 337 // inform parent classes of the commit, so that they can switch us 338 // out of the editing state 339 super.commitEdit(newValue); 340 341 if (treeTable != null) { 342 // reset the editing item in the TreetView 343 treeTable.edit(null); 344 treeTable.requestFocus(); 345 } 346 } 347 348 /** {@inheritDoc} */ 349 @Override public void cancelEdit() { 350 if (! isEditing()) return; 351 352 TreeTableView<T> treeTable = getTreeTableView(); 353 if (treeTable != null) { 354 treeTable.fireEvent(new TreeTableView.EditEvent<T>(treeTable, 355 TreeTableView.<T>editCancelEvent(), 356 getTreeItem(), 357 getItem(), 358 null)); 359 } 360 361 super.cancelEdit(); 362 363 if (treeTable != null) { 364 // reset the editing index on the TreeView 365 treeTable.edit(null); 366 treeTable.requestFocus(); 367 } 368 } 369 370 371 372 /*************************************************************************** 373 * * 374 * Private Implementation * 375 * * 376 **************************************************************************/ 377 378 private int index = -1; 379 380 private void updateItem() { 381 TreeTableView<T> tv = getTreeTableView(); 382 if (tv == null) return; 383 384 // Compute whether the index for this cell is for a real item 385 boolean valid = index >=0 && index < tv.getExpandedItemCount(); 386 387 // Cause the cell to update itself 388 if (valid) { 389 // update the TreeCell state. 390 // get the new treeItem that is about to go in to the TreeCell 391 TreeItem<T> treeItem = tv.getTreeItem(index); 392 393 // For the sake of RT-14279, it is important that the order of these 394 // method calls is as shown below. If the order is switched, it is 395 // likely that events will be fired where the item is null, even 396 // though calling cell.getTreeItem().getValue() returns the value 397 // as expected 398 updateTreeItem(treeItem); 399 updateItem(treeItem == null ? null : treeItem.getValue(), false); 400 } else { 401 updateTreeItem(null); 402 updateItem(null, true); 403 } 404 } 405 406 private void updateSelection() { 407 if (isEmpty()) return; 408 if (index == -1 || getTreeTableView() == null) return; 409 if (getTreeTableView().getSelectionModel() == null) return; 410 411 boolean isSelected = getTreeTableView().getSelectionModel().isSelected(index); 412 if (isSelected() == isSelected) return; 413 414 updateSelected(isSelected); 415 } 416 417 private void updateFocus() { 418 if (getIndex() == -1 || getTreeTableView() == null) return; 419 if (getTreeTableView().getFocusModel() == null) return; 420 421 setFocused(getTreeTableView().getFocusModel().isFocused(getIndex())); 422 } 423 424 private void updateEditing() { 425 if (getIndex() == -1 || getTreeTableView() == null || getTreeItem() == null) return; 426 427 TreeItem<T> editItem = getTreeTableView().getEditingItem(); 428 if (! isEditing() && getTreeItem().equals(editItem)) { 429 startEdit(); 430 } else if (isEditing() && ! getTreeItem().equals(editItem)) { 431 cancelEdit(); 432 } 433 } 434 435 436 437 /*************************************************************************** 438 * * 439 * Expert API * 440 * * 441 **************************************************************************/ 442 443 /** 444 * Updates the TreeTableView associated with this TreeTableCell. 445 * 446 * @param tree The new TreeTableView that should be associated with this 447 * TreeTableCell. 448 * @expert This function is intended to be used by experts, primarily 449 * by those implementing new Skins. It is not common 450 * for developers or designers to access this function directly. 451 */ 452 public final void updateTreeTableView(TreeTableView<T> treeTable) { 453 setTreeTableView(treeTable); 454 } 455 456 /** 457 * Updates the TreeItem associated with this TreeTableCell. 458 * 459 * @param treeItem The new TreeItem that should be associated with this 460 * TreeTableCell. 461 * @expert This function is intended to be used by experts, primarily 462 * by those implementing new Skins. It is not common 463 * for developers or designers to access this function directly. 464 */ 465 public final void updateTreeItem(TreeItem<T> treeItem) { 466 TreeItem<T> _treeItem = getTreeItem(); 467 if (_treeItem != null) { 468 _treeItem.leafProperty().removeListener(weakLeafListener); 469 } 470 setTreeItem(treeItem); 471 if (treeItem != null) { 472 treeItem.leafProperty().addListener(weakLeafListener); 473 } 474 } 475 476 477 478 /*************************************************************************** 479 * * 480 * Stylesheet Handling * 481 * * 482 **************************************************************************/ 483 484 private static final String DEFAULT_STYLE_CLASS = "tree-table-row-cell"; 485 486 private static final PseudoClass EXPANDED_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("expanded"); 487 private static final PseudoClass COLLAPSED_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("collapsed"); 488 489 /** {@inheritDoc} */ 490 @Override protected Skin<?> createDefaultSkin() { 491 return new TreeTableRowSkin<T>(this); 492 } 493}