modules/controls/src/main/java/javafx/scene/control/skin/TreeTableViewSkin.java

Print this page
rev 9240 : 8076423: JEP 253: Prepare JavaFX UI Controls & CSS APIs for Modularization
   1 /*
   2  * Copyright (c) 2012, 2014, 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 com.sun.javafx.scene.control.skin;
  27 
  28 import com.sun.javafx.collections.NonIterableChange;

  29 import com.sun.javafx.scene.control.ReadOnlyUnbackedObservableList;
  30 


  31 import javafx.event.WeakEventHandler;
  32 import javafx.scene.control.*;
  33 
  34 import com.sun.javafx.scene.control.behavior.TreeTableViewBehavior;
  35 
  36 import java.lang.ref.WeakReference;
  37 import java.util.ArrayList;
  38 import java.util.List;
  39 
  40 import javafx.beans.property.BooleanProperty;
  41 import javafx.beans.property.ObjectProperty;
  42 import javafx.beans.property.SimpleObjectProperty;
  43 import javafx.collections.FXCollections;
  44 import javafx.collections.ObservableList;
  45 import javafx.event.EventHandler;
  46 import javafx.event.EventType;
  47 import javafx.scene.AccessibleAction;
  48 import javafx.scene.AccessibleAttribute;
  49 import javafx.scene.Node;
  50 import javafx.scene.control.TreeItem.TreeModificationEvent;
  51 import javafx.scene.input.MouseEvent;
  52 import javafx.scene.layout.Region;
  53 import javafx.scene.layout.StackPane;
  54 import javafx.util.Callback;
  55 
  56 public class TreeTableViewSkin<S> extends TableViewSkinBase<S, TreeItem<S>, TreeTableView<S>, TreeTableViewBehavior<S>, TreeTableRow<S>, TreeTableColumn<S,?>> {




















































  57     
  58     public TreeTableViewSkin(final TreeTableView<S> treeTableView) {
  59         super(treeTableView, new TreeTableViewBehavior<S>(treeTableView));











  60         
  61         this.treeTableView = treeTableView;
  62         this.tableBackingList = new TreeTableViewBackingList<S>(treeTableView);
  63         this.tableBackingListProperty = new SimpleObjectProperty<ObservableList<TreeItem<S>>>(tableBackingList);






  64         
  65         flow.setFixedCellSize(treeTableView.getFixedCellSize());


  66         
  67         super.init(treeTableView);

  68         
  69         setRoot(getSkinnable().getRoot());
  70 
  71         EventHandler<MouseEvent> ml = event -> {
  72             // RT-15127: cancel editing on scroll. This is a bit extreme
  73             // (we are cancelling editing on touching the scrollbars).
  74             // This can be improved at a later date.
  75             if (treeTableView.getEditingCell() != null) {
  76                 treeTableView.edit(-1, null);
  77             }
  78 
  79             // This ensures that the table maintains the focus, even when the vbar
  80             // and hbar controls inside the flow are clicked. Without this, the
  81             // focus border will not be shown when the user interacts with the
  82             // scrollbars, and more importantly, keyboard navigation won't be
  83             // available to the user.
  84             if (treeTableView.isFocusTraversable()) {
  85                 treeTableView.requestFocus();
  86             }
  87         };
  88         flow.getVbar().addEventFilter(MouseEvent.MOUSE_PRESSED, ml);
  89         flow.getHbar().addEventFilter(MouseEvent.MOUSE_PRESSED, ml);
  90 
  91         // init the behavior 'closures'
  92         TreeTableViewBehavior<S> behavior = getBehavior();
  93         behavior.setOnFocusPreviousRow(() -> { onFocusPreviousCell(); });
  94         behavior.setOnFocusNextRow(() -> { onFocusNextCell(); });
  95         behavior.setOnMoveToFirstCell(() -> { onMoveToFirstCell(); });
  96         behavior.setOnMoveToLastCell(() -> { onMoveToLastCell(); });
  97         behavior.setOnScrollPageDown(isFocusDriven -> onScrollPageDown(isFocusDriven));
  98         behavior.setOnScrollPageUp(isFocusDriven -> onScrollPageUp(isFocusDriven));
  99         behavior.setOnSelectPreviousRow(() -> { onSelectPreviousCell(); });
 100         behavior.setOnSelectNextRow(() -> { onSelectNextCell(); });
 101         behavior.setOnSelectLeftCell(() -> { onSelectLeftCell(); });
 102         behavior.setOnSelectRightCell(() -> { onSelectRightCell(); });
 103         
 104         registerChangeListener(treeTableView.rootProperty(), "ROOT");
 105         registerChangeListener(treeTableView.showRootProperty(), "SHOW_ROOT");
 106         registerChangeListener(treeTableView.rowFactoryProperty(), "ROW_FACTORY");
 107         registerChangeListener(treeTableView.expandedItemCountProperty(), "TREE_ITEM_COUNT");
 108         registerChangeListener(treeTableView.fixedCellSizeProperty(), "FIXED_CELL_SIZE");
 109     }
 110 
 111     @Override protected void handleControlPropertyChanged(String p) {
 112         super.handleControlPropertyChanged(p);
 113         
 114         if ("ROOT".equals(p)) {
 115             // fix for RT-37853
 116             getSkinnable().edit(-1, null);
 117 
 118             setRoot(getSkinnable().getRoot());
 119         } else if ("SHOW_ROOT".equals(p)) {

 120             // if we turn off showing the root, then we must ensure the root
 121             // is expanded - otherwise we end up with no visible items in
 122             // the tree.
 123             if (! getSkinnable().isShowRoot() && getRoot() != null) {
 124                  getRoot().setExpanded(true);
 125             }
 126             // update the item count in the flow and behavior instances
 127             updateRowCount();
 128         } else if ("ROW_FACTORY".equals(p)) {
 129             flow.recreateCells();
 130         } else if ("TREE_ITEM_COUNT".equals(p)) {
 131             rowCountDirty = true;
 132         } else if ("FIXED_CELL_SIZE".equals(p)) {
 133             flow.setFixedCellSize(getSkinnable().getFixedCellSize());
 134         }
 135     }
 136     


 137     /***************************************************************************
 138      *                                                                         *
 139      * Listeners                                                               *
 140      *                                                                         *
 141      **************************************************************************/
 142     





































































 143     
 144     
 145     /***************************************************************************
 146      *                                                                         *
 147      * Internal Fields                                                         *
 148      *                                                                         *
 149      **************************************************************************/
 150 
 151     private TreeTableViewBackingList<S> tableBackingList;
 152     private ObjectProperty<ObservableList<TreeItem<S>>> tableBackingListProperty;
 153     private TreeTableView<S> treeTableView;
 154     private WeakReference<TreeItem<S>> weakRootRef;
 155     
 156     private EventHandler<TreeItem.TreeModificationEvent<S>> rootListener = e -> {
 157         if (e.wasAdded() && e.wasRemoved() && e.getAddedSize() == e.getRemovedSize()) {
 158             // Fix for RT-14842, where the children of a TreeItem were changing,
 159             // but because the overall item count was staying the same, there was
 160             // no event being fired to the skin to be informed that the items
 161             // had changed. So, here we just watch for the case where the number
 162             // of items being added is equal to the number of items being removed.
 163             rowCountDirty = true;
 164             getSkinnable().requestLayout();
 165         } else if (e.getEventType().equals(TreeItem.valueChangedEvent())) {
 166             // Fix for RT-14971 and RT-15338.
 167             needCellsRebuilt = true;
 168             getSkinnable().requestLayout();
 169         } else {
 170             // Fix for RT-20090. We are checking to see if the event coming
 171             // from the TreeItem root is an event where the count has changed.
 172             EventType<?> eventType = e.getEventType();
 173             while (eventType != null) {
 174                 if (eventType.equals(TreeItem.<S>expandedItemCountChangeEvent())) {
 175                     rowCountDirty = true;
 176                     getSkinnable().requestLayout();
 177                     break;
 178                 }
 179                 eventType = eventType.getSuperType();
 180             }
 181         }
 182 
 183         // fix for RT-37853
 184         getSkinnable().edit(-1, null);
 185     };


 186     
 187     private WeakEventHandler<TreeModificationEvent<S>> weakRootListener;


 188             


 189     
 190 //    private WeakReference<TreeItem> weakRoot;
 191     private TreeItem<S> getRoot() {



 192         return weakRootRef == null ? null : weakRootRef.get();
 193     }
 194     private void setRoot(TreeItem<S> newRoot) {
 195         if (getRoot() != null && weakRootListener != null) {
 196             getRoot().removeEventHandler(TreeItem.<S>treeNotificationEvent(), weakRootListener);
 197         }
 198         weakRootRef = new WeakReference<>(newRoot);
 199         if (getRoot() != null) {
 200             weakRootListener = new WeakEventHandler<>(rootListener);
 201             getRoot().addEventHandler(TreeItem.<S>treeNotificationEvent(), weakRootListener);
 202         }
 203         
 204         updateRowCount();
 205     }
 206     
 207     
 208     /***************************************************************************
 209      *                                                                         *
 210      * Public API                                                              *
 211      *                                                                         *
 212      **************************************************************************/  
 213     
 214     /** {@inheritDoc} */
 215     @Override protected ObservableList<TreeTableColumn<S, ?>> getVisibleLeafColumns() {
 216         return treeTableView.getVisibleLeafColumns();
 217     }
 218     
 219     @Override protected int getVisibleLeafIndex(TreeTableColumn<S,?> tc) {
 220         return treeTableView.getVisibleLeafIndex(tc);
 221     }
 222 
 223     @Override protected TreeTableColumn<S,?> getVisibleLeafColumn(int col) {
 224         return treeTableView.getVisibleLeafColumn(col);
 225     }
 226 
 227     /** {@inheritDoc} */
 228     @Override protected TreeTableView.TreeTableViewFocusModel<S> getFocusModel() {
 229         return treeTableView.getFocusModel();
 230     }
 231     
 232     /** {@inheritDoc} */
 233     @Override protected TreeTablePosition<S, ?> getFocusedCell() {
 234         return treeTableView.getFocusModel().getFocusedCell();
 235     }
 236 
 237     /** {@inheritDoc} */
 238         @Override protected TableSelectionModel<TreeItem<S>> getSelectionModel() {
 239         return treeTableView.getSelectionModel();
 240     }
 241 
 242     /** {@inheritDoc} */
 243     @Override protected ObjectProperty<Callback<TreeTableView<S>, TreeTableRow<S>>> rowFactoryProperty() {
 244         return treeTableView.rowFactoryProperty();
 245     }
 246 
 247     /** {@inheritDoc} */
 248     @Override protected ObjectProperty<Node> placeholderProperty() {
 249         return treeTableView.placeholderProperty();
 250     }
 251 
 252     /** {@inheritDoc} */
 253     @Override protected ObjectProperty<ObservableList<TreeItem<S>>> itemsProperty() {




 254         return tableBackingListProperty;
 255     }
 256 
 257     /** {@inheritDoc} */
 258     @Override protected ObservableList<TreeTableColumn<S,?>> getColumns() {
 259         return treeTableView.getColumns();
 260     }
 261     
 262     /** {@inheritDoc} */
 263     @Override protected BooleanProperty tableMenuButtonVisibleProperty() {
 264         return treeTableView.tableMenuButtonVisibleProperty();
 265     }
 266 
 267     /** {@inheritDoc} */
 268     @Override protected ObjectProperty<Callback<ResizeFeaturesBase, Boolean>> columnResizePolicyProperty() {
 269         // TODO Ugly!
 270         return (ObjectProperty<Callback<ResizeFeaturesBase, Boolean>>) (Object) treeTableView.columnResizePolicyProperty();
 271     }
 272 
 273     /** {@inheritDoc} */
 274     @Override protected ObservableList<TreeTableColumn<S,?>> getSortOrder() {
 275         return treeTableView.getSortOrder();
 276     }
 277     
 278     @Override protected boolean resizeColumn(TreeTableColumn<S,?> tc, double delta) {
 279         return treeTableView.resizeColumn(tc, delta);
 280     }
 281 
 282     @Override protected void edit(int index, TreeTableColumn<S, ?> column) {
 283         treeTableView.edit(index, column);
 284     }
 285 
 286     /*
 287      * FIXME: Naive implementation ahead
 288      * Attempts to resize column based on the pref width of all items contained
 289      * in this column. This can be potentially very expensive if the number of
 290      * rows is large.
 291      */
 292     @Override protected void resizeColumnToFitContent(TreeTableColumn<S,?> tc, int maxRows) {
 293         final TreeTableColumn col = tc;
 294         List<?> items = itemsProperty().get();
 295         if (items == null || items.isEmpty()) return;
 296     
 297         Callback cellFactory = col.getCellFactory();
 298         if (cellFactory == null) return;
 299     
 300         TreeTableCell<S,?> cell = (TreeTableCell) cellFactory.call(col);
 301         if (cell == null) return;
 302         
 303         // set this property to tell the TableCell we want to know its actual
 304         // preferred width, not the width of the associated TableColumnBase
 305         cell.getProperties().put(TableCellSkin.DEFER_TO_PARENT_PREF_WIDTH, Boolean.TRUE);
 306         
 307         // determine cell padding
 308         double padding = 10;
 309         Node n = cell.getSkin() == null ? null : cell.getSkin().getNode();
 310         if (n instanceof Region) {
 311             Region r = (Region) n;
 312             padding = r.snappedLeftInset() + r.snappedRightInset();
 313         } 
 314         
 315         TreeTableRow<S> treeTableRow = new TreeTableRow<>();


 316         treeTableRow.updateTreeTableView(treeTableView);
 317         
 318         int rows = maxRows == -1 ? items.size() : Math.min(items.size(), maxRows);
 319         double maxWidth = 0;
 320         for (int row = 0; row < rows; row++) {
 321             treeTableRow.updateIndex(row);
 322             treeTableRow.updateTreeItem(treeTableView.getTreeItem(row));
 323             
 324             cell.updateTreeTableColumn(col);
 325             cell.updateTreeTableView(treeTableView);
 326             cell.updateTreeTableRow(treeTableRow);
 327             cell.updateIndex(row);
 328             
 329             if ((cell.getText() != null && !cell.getText().isEmpty()) || cell.getGraphic() != null) {
 330                 getChildren().add(cell);
 331                 cell.applyCss();
 332                 
 333                 double w = cell.prefWidth(-1);
 334                 
 335                 maxWidth = Math.max(maxWidth, w);


 342 
 343         // RT-36855 - take into account the column header text / graphic widths.
 344         // Magic 10 is to allow for sort arrow to appear without text truncation.
 345         TableColumnHeader header = getTableHeaderRow().getColumnHeaderFor(tc);
 346         double headerTextWidth = Utils.computeTextWidth(header.label.getFont(), tc.getText(), -1);
 347         Node graphic = header.label.getGraphic();
 348         double headerGraphicWidth = graphic == null ? 0 : graphic.prefWidth(-1) + header.label.getGraphicTextGap();
 349         double headerWidth = headerTextWidth + headerGraphicWidth + 10 + header.snappedLeftInset() + header.snappedRightInset();
 350         maxWidth = Math.max(maxWidth, headerWidth);
 351         
 352         // RT-23486
 353         maxWidth += padding;
 354         if(treeTableView.getColumnResizePolicy() == TreeTableView.CONSTRAINED_RESIZE_POLICY) {
 355             maxWidth = Math.max(maxWidth, col.getWidth());
 356         }
 357 
 358         col.impl_setWidth(maxWidth);
 359     }
 360     
 361     /** {@inheritDoc} */
 362     @Override public int getItemCount() {
 363         return treeTableView.getExpandedItemCount();
 364     }
 365     
 366     /** {@inheritDoc} */
 367     @Override public TreeTableRow<S> createCell() {
 368         TreeTableRow<S> cell;
 369 
 370         if (treeTableView.getRowFactory() != null) {
 371             cell = treeTableView.getRowFactory().call(treeTableView);
 372         } else {
 373             cell = new TreeTableRow<S>();
 374         }
 375 
 376         // If there is no disclosure node, then add one of my own
 377         if (cell.getDisclosureNode() == null) {
 378             final StackPane disclosureNode = new StackPane();
 379             disclosureNode.getStyleClass().setAll("tree-disclosure-node");
 380             disclosureNode.setMouseTransparent(true);
 381 
 382             final StackPane disclosureNodeArrow = new StackPane();
 383             disclosureNodeArrow.getStyleClass().setAll("arrow");
 384             disclosureNode.getChildren().add(disclosureNodeArrow);
 385 
 386             cell.setDisclosureNode(disclosureNode);
 387         }
 388 
 389         cell.updateTreeTableView(treeTableView);
 390         return cell;
 391     }
 392 
 393     @Override protected void horizontalScroll() {
 394         super.horizontalScroll();
 395         if (getSkinnable().getFixedCellSize() > 0) {
 396             flow.requestCellLayout();
 397         }
 398     }
 399 
 400     @Override
 401     protected Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
 402         switch (attribute) {
 403             case ROW_AT_INDEX: {
 404                 final int rowIndex = (Integer)parameters[0];
 405                 return rowIndex < 0 ? null : flow.getPrivateCell(rowIndex);
 406             }
 407             case SELECTED_ITEMS: {
 408                 List<Node> selection = new ArrayList<>();
 409                 TreeTableView.TreeTableViewSelectionModel<S> sm = getSkinnable().getSelectionModel();
 410                 for (TreeTablePosition<S,?> pos : sm.getSelectedCells()) {
 411                     TreeTableRow<S> row = flow.getPrivateCell(pos.getRow());
 412                     if (row != null) selection.add(row);
 413                 }
 414                 return FXCollections.observableArrayList(selection);
 415             }
 416             case FOCUS_ITEM: // TableViewSkinBase
 417             case CELL_AT_ROW_COLUMN: // TableViewSkinBase
 418             case COLUMN_AT_INDEX: // TableViewSkinBase
 419             case HEADER: // TableViewSkinBase
 420             case VERTICAL_SCROLLBAR: // TableViewSkinBase
 421             case HORIZONTAL_SCROLLBAR: // TableViewSkinBase
 422             default: return super.queryAccessibleAttribute(attribute, parameters);
 423         }
 424     }
 425 
 426     @Override
 427     protected void executeAccessibleAction(AccessibleAction action, Object... parameters) {
 428         switch (action) {
 429             case SHOW_ITEM: {
 430                 Node item = (Node)parameters[0];
 431                 if (item instanceof TreeTableCell) {
 432                     @SuppressWarnings("unchecked")
 433                     TreeTableCell<S, ?> cell = (TreeTableCell<S, ?>)item;
 434                     flow.show(cell.getIndex());
 435                 }
 436                 break;
 437             }
 438             case SET_SELECTED_ITEMS: {
 439                 @SuppressWarnings("unchecked")
 440                 ObservableList<Node> items = (ObservableList<Node>)parameters[0];
 441                 if (items != null) {
 442                     TreeTableView.TreeTableViewSelectionModel<S> sm = getSkinnable().getSelectionModel();
 443                     if (sm != null) {
 444                         sm.clearSelection();
 445                         for (Node item : items) {
 446                             if (item instanceof TreeTableCell) {
 447                                 @SuppressWarnings("unchecked")
 448                                 TreeTableCell<S, ?> cell = (TreeTableCell<S, ?>)item;
 449                                 sm.select(cell.getIndex(), cell.getTableColumn());
 450                             }
 451                         }
 452                     }
 453                 }
 454                 break;
 455             }
 456             default: super.executeAccessibleAction(action, parameters);
 457         }
 458     }
 459 
 460     /***************************************************************************
 461      *                                                                         *
 462      * Layout                                                                  *
 463      *                                                                         *
 464      **************************************************************************/    
 465     
 466 
 467     
 468     
 469     /***************************************************************************
 470      *                                                                         *
 471      * Private methods                                                         *
 472      *                                                                         *
 473      **************************************************************************/
 474     
 475     @Override protected void updateRowCount() {
 476         updatePlaceholderRegionVisibility();
 477 
 478         tableBackingList.resetSize();
 479         
 480         int oldCount = flow.getCellCount();
 481         int newCount = getItemCount();
 482         
 483         // if this is not called even when the count is the same, we get a 
 484         // memory leak in VirtualFlow.sheet.children. This can probably be 
 485         // optimised in the future when time permits.
 486         flow.setCellCount(newCount);
 487         
 488         if (forceCellRecreate) {
 489             needCellsRecreated = true;
 490             forceCellRecreate = false;
 491         } else if (newCount != oldCount) {
 492             needCellsRebuilt = true;
 493         } else {
 494             needCellsReconfigured = true;
 495         }
 496     }
 497 








 498     /**
 499      * A simple read only list structure that maps into the TreeTableView tree
 500      * structure.
 501      */
 502     private static class TreeTableViewBackingList<S> extends ReadOnlyUnbackedObservableList<TreeItem<S>> {
 503         private final TreeTableView<S> treeTable;
 504         
 505         private int size = -1;
 506         
 507         TreeTableViewBackingList(TreeTableView<S> treeTable) {
 508             this.treeTable = treeTable;
 509         }
 510         
 511         void resetSize() {
 512             int oldSize = size;
 513             size = -1;
 514             
 515             // TODO we can certainly make this better....but it may not really matter
 516             callObservers(new NonIterableChange.GenericAddRemoveChange<TreeItem<S>>(
 517                     0, oldSize, FXCollections.<TreeItem<S>>emptyObservableList(), this));
 518         }
 519         
 520         @Override public TreeItem<S> get(int i) {
 521             return treeTable.getTreeItem(i);
 522         }
 523 
 524         @Override public int size() {
 525             if (size == -1) {
 526                 size = treeTable.getExpandedItemCount();
 527             }
 528             return size;
 529         }
 530     }
 531 }
   1 /*
   2  * Copyright (c) 2012, 2015, 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.skin;
  27 
  28 import com.sun.javafx.collections.NonIterableChange;
  29 import com.sun.javafx.scene.control.Properties;
  30 import com.sun.javafx.scene.control.ReadOnlyUnbackedObservableList;
  31 
  32 import com.sun.javafx.scene.control.behavior.BehaviorBase;
  33 import com.sun.javafx.scene.control.skin.Utils;
  34 import javafx.event.WeakEventHandler;
  35 import javafx.scene.control.*;
  36 
  37 import com.sun.javafx.scene.control.behavior.TreeTableViewBehavior;
  38 
  39 import java.lang.ref.WeakReference;
  40 import java.util.ArrayList;
  41 import java.util.List;
  42 
  43 import javafx.beans.property.BooleanProperty;
  44 import javafx.beans.property.ObjectProperty;
  45 import javafx.beans.property.SimpleObjectProperty;
  46 import javafx.collections.FXCollections;
  47 import javafx.collections.ObservableList;
  48 import javafx.event.EventHandler;
  49 import javafx.event.EventType;
  50 import javafx.scene.AccessibleAction;
  51 import javafx.scene.AccessibleAttribute;
  52 import javafx.scene.Node;
  53 import javafx.scene.control.TreeItem.TreeModificationEvent;
  54 import javafx.scene.input.MouseEvent;
  55 import javafx.scene.layout.Region;
  56 import javafx.scene.layout.StackPane;
  57 import javafx.util.Callback;
  58 
  59 /**
  60  * Default skin implementation for the {@link TreeTableView} control.
  61  *
  62  * @see TreeTableView
  63  * @since 9
  64  */
  65 public class TreeTableViewSkin<T> extends TableViewSkinBase<T, TreeItem<T>, TreeTableView<T>, TreeTableRow<T>, TreeTableColumn<T,?>> {
  66 
  67     /***************************************************************************
  68      *                                                                         *
  69      * Private Fields                                                          *
  70      *                                                                         *
  71      **************************************************************************/
  72 
  73     private TreeTableViewBackingList<T> tableBackingList;
  74     private ObjectProperty<ObservableList<TreeItem<T>>> tableBackingListProperty;
  75     private WeakReference<TreeItem<T>> weakRootRef;
  76     private final TreeTableViewBehavior<T>  behavior;
  77 
  78 
  79 
  80     /***************************************************************************
  81      *                                                                         *
  82      * Listeners                                                               *
  83      *                                                                         *
  84      **************************************************************************/
  85 
  86     private EventHandler<TreeItem.TreeModificationEvent<T>> rootListener = e -> {
  87         if (e.wasAdded() && e.wasRemoved() && e.getAddedSize() == e.getRemovedSize()) {
  88             // Fix for RT-14842, where the children of a TreeItem were changing,
  89             // but because the overall item count was staying the same, there was
  90             // no event being fired to the skin to be informed that the items
  91             // had changed. So, here we just watch for the case where the number
  92             // of items being added is equal to the number of items being removed.
  93             rowCountDirty = true;
  94             getSkinnable().requestLayout();
  95         } else if (e.getEventType().equals(TreeItem.valueChangedEvent())) {
  96             // Fix for RT-14971 and RT-15338.
  97             needCellsRebuilt = true;
  98             getSkinnable().requestLayout();
  99         } else {
 100             // Fix for RT-20090. We are checking to see if the event coming
 101             // from the TreeItem root is an event where the count has changed.
 102             EventType<?> eventType = e.getEventType();
 103             while (eventType != null) {
 104                 if (eventType.equals(TreeItem.<T>expandedItemCountChangeEvent())) {
 105                     rowCountDirty = true;
 106                     getSkinnable().requestLayout();
 107                     break;
 108                 }
 109                 eventType = eventType.getSuperType();
 110             }
 111         }
 112 
 113         // fix for RT-37853
 114         getSkinnable().edit(-1, null);
 115     };
 116 
 117     private WeakEventHandler<TreeModificationEvent<T>> weakRootListener;
 118 
 119 
 120 
 121     /***************************************************************************
 122      *                                                                         *
 123      * Constructors                                                            *
 124      *                                                                         *
 125      **************************************************************************/
 126 
 127     /**
 128      * Creates a new TreeTableViewSkin instance, installing the necessary child
 129      * nodes into the Control {@link Control#getChildren() children} list, as
 130      * well as the necessary input mappings for handling key, mouse, etc events.
 131      *
 132      * @param control The control that this skin should be installed onto.
 133      */
 134     public TreeTableViewSkin(final TreeTableView<T> control) {
 135         super(control);
 136 
 137         // install default input map for the TreeTableView control
 138         behavior = new TreeTableViewBehavior<>(control);
 139 //        control.setInputMap(behavior.getInputMap());
 140         
 141         flow.setFixedCellSize(control.getFixedCellSize());
 142         flow.setCellFactory(flow -> createCell());
 143         
 144         setRoot(getSkinnable().getRoot());
 145 
 146         EventHandler<MouseEvent> ml = event -> {
 147             // RT-15127: cancel editing on scroll. This is a bit extreme
 148             // (we are cancelling editing on touching the scrollbars).
 149             // This can be improved at a later date.
 150             if (control.getEditingCell() != null) {
 151                 control.edit(-1, null);
 152             }
 153 
 154             // This ensures that the table maintains the focus, even when the vbar
 155             // and hbar controls inside the flow are clicked. Without this, the
 156             // focus border will not be shown when the user interacts with the
 157             // scrollbars, and more importantly, keyboard navigation won't be
 158             // available to the user.
 159             if (control.isFocusTraversable()) {
 160                 control.requestFocus();
 161             }
 162         };
 163         flow.getVbar().addEventFilter(MouseEvent.MOUSE_PRESSED, ml);
 164         flow.getHbar().addEventFilter(MouseEvent.MOUSE_PRESSED, ml);
 165 
 166         // init the behavior 'closures'
 167         behavior.setOnFocusPreviousRow(() -> onFocusPreviousCell());
 168         behavior.setOnFocusNextRow(() -> onFocusNextCell());
 169         behavior.setOnMoveToFirstCell(() -> onMoveToFirstCell());
 170         behavior.setOnMoveToLastCell(() -> onMoveToLastCell());

 171         behavior.setOnScrollPageDown(isFocusDriven -> onScrollPageDown(isFocusDriven));
 172         behavior.setOnScrollPageUp(isFocusDriven -> onScrollPageUp(isFocusDriven));
 173         behavior.setOnSelectPreviousRow(() -> onSelectPreviousCell());
 174         behavior.setOnSelectNextRow(() -> onSelectNextCell());
 175         behavior.setOnSelectLeftCell(() -> onSelectLeftCell());
 176         behavior.setOnSelectRightCell(() -> onSelectRightCell());







 177         
 178         registerChangeListener(control.rootProperty(), e -> {



 179             // fix for RT-37853
 180             getSkinnable().edit(-1, null);
 181 
 182             setRoot(getSkinnable().getRoot());
 183         });
 184         registerChangeListener(control.showRootProperty(), e -> {
 185             // if we turn off showing the root, then we must ensure the root
 186             // is expanded - otherwise we end up with no visible items in
 187             // the tree.
 188             if (! getSkinnable().isShowRoot() && getRoot() != null) {
 189                 getRoot().setExpanded(true);
 190             }
 191             // update the item count in the flow and behavior instances
 192             updateRowCount();
 193         });
 194         registerChangeListener(control.rowFactoryProperty(), e -> flow.recreateCells());
 195         registerChangeListener(control.expandedItemCountProperty(), e -> rowCountDirty = true);
 196         registerChangeListener(control.fixedCellSizeProperty(), e -> flow.setFixedCellSize(getSkinnable().getFixedCellSize()));



 197     }
 198 
 199 
 200 
 201     /***************************************************************************
 202      *                                                                         *
 203      * Public API                                                              *
 204      *                                                                         *
 205      **************************************************************************/
 206 
 207     /** {@inheritDoc} */
 208     @Override public void dispose() {
 209         super.dispose();
 210 
 211         if (behavior != null) {
 212             behavior.dispose();
 213         }
 214     }
 215 
 216     /** {@inheritDoc} */
 217     @Override protected Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
 218         switch (attribute) {
 219             case ROW_AT_INDEX: {
 220                 final int rowIndex = (Integer)parameters[0];
 221                 return rowIndex < 0 ? null : flow.getPrivateCell(rowIndex);
 222             }
 223             case SELECTED_ITEMS: {
 224                 List<Node> selection = new ArrayList<>();
 225                 TreeTableView.TreeTableViewSelectionModel<T> sm = getSkinnable().getSelectionModel();
 226                 for (TreeTablePosition<T,?> pos : sm.getSelectedCells()) {
 227                     TreeTableRow<T> row = flow.getPrivateCell(pos.getRow());
 228                     if (row != null) selection.add(row);
 229                 }
 230                 return FXCollections.observableArrayList(selection);
 231             }
 232             case FOCUS_ITEM: // TableViewSkinBase
 233             case CELL_AT_ROW_COLUMN: // TableViewSkinBase
 234             case COLUMN_AT_INDEX: // TableViewSkinBase
 235             case HEADER: // TableViewSkinBase
 236             case VERTICAL_SCROLLBAR: // TableViewSkinBase
 237             case HORIZONTAL_SCROLLBAR: // TableViewSkinBase
 238             default: return super.queryAccessibleAttribute(attribute, parameters);
 239         }
 240     }
 241 
 242     @Override
 243     protected void executeAccessibleAction(AccessibleAction action, Object... parameters) {
 244         switch (action) {
 245             case SHOW_ITEM: {
 246                 Node item = (Node)parameters[0];
 247                 if (item instanceof TreeTableCell) {
 248                     @SuppressWarnings("unchecked")
 249                     TreeTableCell<T, ?> cell = (TreeTableCell<T, ?>)item;
 250                     flow.scrollTo(cell.getIndex());
 251                 }
 252                 break;
 253             }
 254             case SET_SELECTED_ITEMS: {
 255                 @SuppressWarnings("unchecked")
 256                 ObservableList<Node> items = (ObservableList<Node>)parameters[0];
 257                 if (items != null) {
 258                     TreeTableView.TreeTableViewSelectionModel<T> sm = getSkinnable().getSelectionModel();
 259                     if (sm != null) {
 260                         sm.clearSelection();
 261                         for (Node item : items) {
 262                             if (item instanceof TreeTableCell) {
 263                                 @SuppressWarnings("unchecked")
 264                                 TreeTableCell<T, ?> cell = (TreeTableCell<T, ?>)item;
 265                                 sm.select(cell.getIndex(), cell.getTableColumn());
 266                             }
 267                         }
 268                     }
 269                 }
 270                 break;
 271             }
 272             default: super.executeAccessibleAction(action, parameters);
 273         }
 274     }
 275 
 276 
 277     
 278     /***************************************************************************
 279      *                                                                         *
 280      * Private methods                                                         *
 281      *                                                                         *
 282      **************************************************************************/
 283 
 284     /** {@inheritDoc} */
 285     private TreeTableRow<T> createCell() {
 286         TreeTableRow<T> cell;

 287 
 288         TreeTableView<T> treeTableView = getSkinnable();
 289         if (treeTableView.getRowFactory() != null) {
 290             cell = treeTableView.getRowFactory().call(treeTableView);










 291         } else {
 292             cell = new TreeTableRow<T>();










 293         }
 294 
 295         // If there is no disclosure node, then add one of my own
 296         if (cell.getDisclosureNode() == null) {
 297             final StackPane disclosureNode = new StackPane();
 298             disclosureNode.getStyleClass().setAll("tree-disclosure-node");
 299             disclosureNode.setMouseTransparent(true);
 300 
 301             final StackPane disclosureNodeArrow = new StackPane();
 302             disclosureNodeArrow.getStyleClass().setAll("arrow");
 303             disclosureNode.getChildren().add(disclosureNodeArrow);
 304 
 305             cell.setDisclosureNode(disclosureNode);
 306         }
 307 
 308         cell.updateTreeTableView(treeTableView);
 309         return cell;
 310     }
 311 
 312     private TreeItem<T> getRoot() {
 313         return weakRootRef == null ? null : weakRootRef.get();
 314     }
 315     private void setRoot(TreeItem<T> newRoot) {
 316         if (getRoot() != null && weakRootListener != null) {
 317             getRoot().removeEventHandler(TreeItem.<T>treeNotificationEvent(), weakRootListener);
 318         }
 319         weakRootRef = new WeakReference<>(newRoot);
 320         if (getRoot() != null) {
 321             weakRootListener = new WeakEventHandler<>(rootListener);
 322             getRoot().addEventHandler(TreeItem.<T>treeNotificationEvent(), weakRootListener);
 323         }
 324 
 325         updateRowCount();
 326     }
 327 







 328     /** {@inheritDoc} */
 329     @Override ObservableList<TreeTableColumn<T, ?>> getVisibleLeafColumns() {
 330         return getSkinnable().getVisibleLeafColumns();
 331     }
 332 
 333     @Override int getVisibleLeafIndex(TreeTableColumn<T,?> tc) {
 334         return getSkinnable().getVisibleLeafIndex(tc);
 335     }
 336 
 337     @Override TreeTableColumn<T,?> getVisibleLeafColumn(int col) {
 338         return getSkinnable().getVisibleLeafColumn(col);
 339     }
 340 
 341     /** {@inheritDoc} */
 342     @Override TreeTableView.TreeTableViewFocusModel<T> getFocusModel() {
 343         return getSkinnable().getFocusModel();
 344     }
 345 
 346     /** {@inheritDoc} */
 347     @Override TreeTablePosition<T, ?> getFocusedCell() {
 348         return getSkinnable().getFocusModel().getFocusedCell();
 349     }
 350 
 351     /** {@inheritDoc} */
 352     @Override TableSelectionModel<TreeItem<T>> getSelectionModel() {
 353         return getSkinnable().getSelectionModel();
 354     }
 355 
 356     /** {@inheritDoc} */
 357     @Override ObjectProperty<Callback<TreeTableView<T>, TreeTableRow<T>>> rowFactoryProperty() {
 358         return getSkinnable().rowFactoryProperty();
 359     }
 360 
 361     /** {@inheritDoc} */
 362     @Override ObjectProperty<Node> placeholderProperty() {
 363         return getSkinnable().placeholderProperty();
 364     }
 365 
 366     /** {@inheritDoc} */
 367     @Override ObjectProperty<ObservableList<TreeItem<T>>> itemsProperty() {
 368         if (tableBackingListProperty == null) {
 369             this.tableBackingList = new TreeTableViewBackingList<>(getSkinnable());
 370             this.tableBackingListProperty = new SimpleObjectProperty<>(tableBackingList);
 371         }
 372         return tableBackingListProperty;
 373     }
 374 
 375     /** {@inheritDoc} */
 376     @Override ObservableList<TreeTableColumn<T,?>> getColumns() {
 377         return getSkinnable().getColumns();
 378     }
 379 
 380     /** {@inheritDoc} */
 381     @Override BooleanProperty tableMenuButtonVisibleProperty() {
 382         return getSkinnable().tableMenuButtonVisibleProperty();
 383     }
 384 
 385     /** {@inheritDoc} */
 386     @Override ObjectProperty<Callback<ResizeFeaturesBase, Boolean>> columnResizePolicyProperty() {
 387         return (ObjectProperty<Callback<ResizeFeaturesBase, Boolean>>) (Object) getSkinnable().columnResizePolicyProperty();

 388     }
 389 
 390     /** {@inheritDoc} */
 391     @Override ObservableList<TreeTableColumn<T,?>> getSortOrder() {
 392         return getSkinnable().getSortOrder();




 393     }
 394 
 395     @Override boolean resizeColumn(TreeTableColumn<T,?> tc, double delta) {
 396         return getSkinnable().resizeColumn(tc, delta);
 397     }
 398 
 399     /*
 400      * FIXME: Naive implementation ahead
 401      * Attempts to resize column based on the pref width of all items contained
 402      * in this column. This can be potentially very expensive if the number of
 403      * rows is large.
 404      */
 405     @Override void resizeColumnToFitContent(TreeTableColumn<T,?> tc, int maxRows) {
 406         final TreeTableColumn col = tc;
 407         List<?> items = itemsProperty().get();
 408         if (items == null || items.isEmpty()) return;
 409 
 410         Callback cellFactory = col.getCellFactory();
 411         if (cellFactory == null) return;
 412 
 413         TreeTableCell<T,?> cell = (TreeTableCell) cellFactory.call(col);
 414         if (cell == null) return;
 415 
 416         // set this property to tell the TableCell we want to know its actual
 417         // preferred width, not the width of the associated TableColumnBase
 418         cell.getProperties().put(Properties.DEFER_TO_PARENT_PREF_WIDTH, Boolean.TRUE);
 419 
 420         // determine cell padding
 421         double padding = 10;
 422         Node n = cell.getSkin() == null ? null : cell.getSkin().getNode();
 423         if (n instanceof Region) {
 424             Region r = (Region) n;
 425             padding = r.snappedLeftInset() + r.snappedRightInset();
 426         }
 427 
 428         final TreeTableView<T> treeTableView = getSkinnable();
 429 
 430         TreeTableRow<T> treeTableRow = new TreeTableRow<>();
 431         treeTableRow.updateTreeTableView(treeTableView);
 432 
 433         int rows = maxRows == -1 ? items.size() : Math.min(items.size(), maxRows);
 434         double maxWidth = 0;
 435         for (int row = 0; row < rows; row++) {
 436             treeTableRow.updateIndex(row);
 437             treeTableRow.updateTreeItem(treeTableView.getTreeItem(row));
 438 
 439             cell.updateTreeTableColumn(col);
 440             cell.updateTreeTableView(treeTableView);
 441             cell.updateTreeTableRow(treeTableRow);
 442             cell.updateIndex(row);
 443 
 444             if ((cell.getText() != null && !cell.getText().isEmpty()) || cell.getGraphic() != null) {
 445                 getChildren().add(cell);
 446                 cell.applyCss();
 447 
 448                 double w = cell.prefWidth(-1);
 449 
 450                 maxWidth = Math.max(maxWidth, w);


 457 
 458         // RT-36855 - take into account the column header text / graphic widths.
 459         // Magic 10 is to allow for sort arrow to appear without text truncation.
 460         TableColumnHeader header = getTableHeaderRow().getColumnHeaderFor(tc);
 461         double headerTextWidth = Utils.computeTextWidth(header.label.getFont(), tc.getText(), -1);
 462         Node graphic = header.label.getGraphic();
 463         double headerGraphicWidth = graphic == null ? 0 : graphic.prefWidth(-1) + header.label.getGraphicTextGap();
 464         double headerWidth = headerTextWidth + headerGraphicWidth + 10 + header.snappedLeftInset() + header.snappedRightInset();
 465         maxWidth = Math.max(maxWidth, headerWidth);
 466 
 467         // RT-23486
 468         maxWidth += padding;
 469         if(treeTableView.getColumnResizePolicy() == TreeTableView.CONSTRAINED_RESIZE_POLICY) {
 470             maxWidth = Math.max(maxWidth, col.getWidth());
 471         }
 472 
 473         col.impl_setWidth(maxWidth);
 474     }
 475 
 476     /** {@inheritDoc} */
 477     @Override int getItemCount() {
 478         return getSkinnable().getExpandedItemCount();
 479     }
 480 
 481     /** {@inheritDoc} */
 482     @Override void horizontalScroll() {


























 483         super.horizontalScroll();
 484         if (getSkinnable().getFixedCellSize() > 0) {
 485             flow.requestCellLayout();
 486         }
 487     }
 488 
 489     /** {@inheritDoc} */
 490     @Override void updateRowCount() {










































































 491         updatePlaceholderRegionVisibility();
 492 
 493         tableBackingList.resetSize();
 494 
 495         int oldCount = flow.getCellCount();
 496         int newCount = getItemCount();
 497 
 498         // if this is not called even when the count is the same, we get a
 499         // memory leak in VirtualFlow.sheet.children. This can probably be
 500         // optimised in the future when time permits.
 501         flow.setCellCount(newCount);
 502 
 503         if (forceCellRecreate) {
 504             needCellsRecreated = true;
 505             forceCellRecreate = false;
 506         } else if (newCount != oldCount) {
 507             needCellsRebuilt = true;
 508         } else {
 509             needCellsReconfigured = true;
 510         }
 511     }
 512 
 513 
 514 
 515     /***************************************************************************
 516      *                                                                         *
 517      * Support classes                                                         *
 518      *                                                                         *
 519      **************************************************************************/
 520 
 521     /**
 522      * A simple read only list structure that maps into the TreeTableView tree
 523      * structure.
 524      */
 525     private static class TreeTableViewBackingList<T> extends ReadOnlyUnbackedObservableList<TreeItem<T>> {
 526         private final TreeTableView<T> treeTable;
 527         
 528         private int size = -1;
 529         
 530         TreeTableViewBackingList(TreeTableView<T> treeTable) {
 531             this.treeTable = treeTable;
 532         }
 533         
 534         void resetSize() {
 535             int oldSize = size;
 536             size = -1;
 537             
 538             // TODO we can certainly make this better....but it may not really matter
 539             callObservers(new NonIterableChange.GenericAddRemoveChange<TreeItem<T>>(
 540                     0, oldSize, FXCollections.<TreeItem<T>>emptyObservableList(), this));
 541         }
 542         
 543         @Override public TreeItem<T> get(int i) {
 544             return treeTable.getTreeItem(i);
 545         }
 546 
 547         @Override public int size() {
 548             if (size == -1) {
 549                 size = treeTable.getExpandedItemCount();
 550             }
 551             return size;
 552         }
 553     }
 554 }