1 /*
   2  * Copyright (c) 2016, 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 package javafx.scene.control.skin;
  26 
  27 import com.sun.javafx.scene.control.Properties;
  28 import com.sun.javafx.scene.control.TreeTableViewBackingList;
  29 import com.sun.javafx.scene.control.skin.Utils;
  30 import javafx.beans.property.BooleanProperty;
  31 import javafx.beans.property.ObjectProperty;
  32 import javafx.beans.property.SimpleObjectProperty;
  33 import javafx.collections.FXCollections;
  34 import javafx.collections.ObservableList;
  35 import javafx.scene.Node;
  36 import javafx.scene.control.Control;
  37 import javafx.scene.control.IndexedCell;
  38 import javafx.scene.control.ResizeFeaturesBase;
  39 import javafx.scene.control.TableCell;
  40 import javafx.scene.control.TableColumn;
  41 import javafx.scene.control.TableColumnBase;
  42 import javafx.scene.control.TableFocusModel;
  43 import javafx.scene.control.TablePositionBase;
  44 import javafx.scene.control.TableSelectionModel;
  45 import javafx.scene.control.TableView;
  46 import javafx.scene.control.TreeTableCell;
  47 import javafx.scene.control.TreeTableColumn;
  48 import javafx.scene.control.TreeTableRow;
  49 import javafx.scene.control.TreeTableView;
  50 import javafx.scene.layout.Region;
  51 import javafx.util.Callback;
  52 
  53 import java.util.List;
  54 import java.util.Optional;
  55 
  56 // NOT PUBLIC API
  57 class TableSkinUtils {
  58 
  59     private TableSkinUtils() { }
  60 
  61     public static boolean resizeColumn(TableViewSkinBase<?,?,?,?,?> tableSkin, TableColumnBase<?,?> tc, double delta) {
  62         if (!tc.isResizable()) return false;
  63 
  64         Object control = tableSkin.getSkinnable();
  65         if (control instanceof TableView) {
  66             return ((TableView)control).resizeColumn((TableColumn)tc, delta);
  67         } else if (control instanceof TreeTableView) {
  68             return ((TreeTableView)control).resizeColumn((TreeTableColumn)tc, delta);
  69         }
  70         return false;
  71     }
  72 
  73     /*
  74      * FIXME: Naive implementation ahead
  75      * Attempts to resize column based on the pref width of all items contained
  76      * in this column. This can be potentially very expensive if the number of
  77      * rows is large.
  78      */
  79     /** {@inheritDoc} */
  80     public static void resizeColumnToFitContent(TableViewSkinBase<?,?,?,?,?> tableSkin, TableColumnBase<?,?> tc, int maxRows) {
  81         if (!tc.isResizable()) return;
  82 
  83         Object control = tableSkin.getSkinnable();
  84         if (control instanceof TableView) {
  85             resizeColumnToFitContent((TableView)control, (TableColumn)tc, tableSkin, maxRows);
  86         } else if (control instanceof TreeTableView) {
  87             resizeColumnToFitContent((TreeTableView)control, (TreeTableColumn)tc, tableSkin, maxRows);
  88         }
  89     }
  90 
  91     private static <T,S> void resizeColumnToFitContent(TableView<T> tv, TableColumn<T, S> tc, TableViewSkinBase tableSkin, int maxRows) {
  92         List<?> items = tv.getItems();
  93         if (items == null || items.isEmpty()) return;
  94 
  95         Callback/*<TableColumn<T, ?>, TableCell<T,?>>*/ cellFactory = tc.getCellFactory();
  96         if (cellFactory == null) return;
  97 
  98         TableCell<T,?> cell = (TableCell<T, ?>) cellFactory.call(tc);
  99         if (cell == null) return;
 100 
 101         // set this property to tell the TableCell we want to know its actual
 102         // preferred width, not the width of the associated TableColumnBase
 103         cell.getProperties().put(Properties.DEFER_TO_PARENT_PREF_WIDTH, Boolean.TRUE);
 104 
 105         // determine cell padding
 106         double padding = 10;
 107         Node n = cell.getSkin() == null ? null : cell.getSkin().getNode();
 108         if (n instanceof Region) {
 109             Region r = (Region) n;
 110             padding = r.snappedLeftInset() + r.snappedRightInset();
 111         }
 112 
 113         int rows = maxRows == -1 ? items.size() : Math.min(items.size(), maxRows);
 114         double maxWidth = 0;
 115         for (int row = 0; row < rows; row++) {
 116             cell.updateTableColumn(tc);
 117             cell.updateTableView(tv);
 118             cell.updateIndex(row);
 119 
 120             if ((cell.getText() != null && !cell.getText().isEmpty()) || cell.getGraphic() != null) {
 121                 tableSkin.getChildren().add(cell);
 122                 cell.applyCss();
 123                 maxWidth = Math.max(maxWidth, cell.prefWidth(-1));
 124                 tableSkin.getChildren().remove(cell);
 125             }
 126         }
 127 
 128         // dispose of the cell to prevent it retaining listeners (see RT-31015)
 129         cell.updateIndex(-1);
 130 
 131         // RT-36855 - take into account the column header text / graphic widths.
 132         // Magic 10 is to allow for sort arrow to appear without text truncation.
 133         TableColumnHeader header = tableSkin.getTableHeaderRow().getColumnHeaderFor(tc);
 134         double headerTextWidth = Utils.computeTextWidth(header.label.getFont(), tc.getText(), -1);
 135         Node graphic = header.label.getGraphic();
 136         double headerGraphicWidth = graphic == null ? 0 : graphic.prefWidth(-1) + header.label.getGraphicTextGap();
 137         double headerWidth = headerTextWidth + headerGraphicWidth + 10 + header.snappedLeftInset() + header.snappedRightInset();
 138         maxWidth = Math.max(maxWidth, headerWidth);
 139 
 140         // RT-23486
 141         maxWidth += padding;
 142         if(tv.getColumnResizePolicy() == TableView.CONSTRAINED_RESIZE_POLICY) {
 143             maxWidth = Math.max(maxWidth, tc.getWidth());
 144         }
 145 
 146         tc.impl_setWidth(maxWidth);
 147     }
 148 
 149 
 150     /*
 151      * FIXME: Naive implementation ahead
 152      * Attempts to resize column based on the pref width of all items contained
 153      * in this column. This can be potentially very expensive if the number of
 154      * rows is large.
 155      */
 156     private static <T,S> void resizeColumnToFitContent(TreeTableView<T> ttv, TreeTableColumn<T,S> tc, TableViewSkinBase tableSkin, int maxRows) {
 157         List<?> items = new TreeTableViewBackingList(ttv);
 158         if (items == null || items.isEmpty()) return;
 159 
 160         Callback cellFactory = tc.getCellFactory();
 161         if (cellFactory == null) return;
 162 
 163         TreeTableCell<T,S> cell = (TreeTableCell) cellFactory.call(tc);
 164         if (cell == null) return;
 165 
 166         // set this property to tell the TableCell we want to know its actual
 167         // preferred width, not the width of the associated TableColumnBase
 168         cell.getProperties().put(Properties.DEFER_TO_PARENT_PREF_WIDTH, Boolean.TRUE);
 169 
 170         // determine cell padding
 171         double padding = 10;
 172         Node n = cell.getSkin() == null ? null : cell.getSkin().getNode();
 173         if (n instanceof Region) {
 174             Region r = (Region) n;
 175             padding = r.snappedLeftInset() + r.snappedRightInset();
 176         }
 177 
 178         TreeTableRow<T> treeTableRow = new TreeTableRow<>();
 179         treeTableRow.updateTreeTableView(ttv);
 180 
 181         int rows = maxRows == -1 ? items.size() : Math.min(items.size(), maxRows);
 182         double maxWidth = 0;
 183         for (int row = 0; row < rows; row++) {
 184             treeTableRow.updateIndex(row);
 185             treeTableRow.updateTreeItem(ttv.getTreeItem(row));
 186 
 187             cell.updateTreeTableColumn(tc);
 188             cell.updateTreeTableView(ttv);
 189             cell.updateTreeTableRow(treeTableRow);
 190             cell.updateIndex(row);
 191 
 192             if ((cell.getText() != null && !cell.getText().isEmpty()) || cell.getGraphic() != null) {
 193                 tableSkin.getChildren().add(cell);
 194                 cell.applyCss();
 195 
 196                 double w = cell.prefWidth(-1);
 197 
 198                 maxWidth = Math.max(maxWidth, w);
 199                 tableSkin.getChildren().remove(cell);
 200             }
 201         }
 202 
 203         // dispose of the cell to prevent it retaining listeners (see RT-31015)
 204         cell.updateIndex(-1);
 205 
 206         // RT-36855 - take into account the column header text / graphic widths.
 207         // Magic 10 is to allow for sort arrow to appear without text truncation.
 208         TableColumnHeader header = tableSkin.getTableHeaderRow().getColumnHeaderFor(tc);
 209         double headerTextWidth = Utils.computeTextWidth(header.label.getFont(), tc.getText(), -1);
 210         Node graphic = header.label.getGraphic();
 211         double headerGraphicWidth = graphic == null ? 0 : graphic.prefWidth(-1) + header.label.getGraphicTextGap();
 212         double headerWidth = headerTextWidth + headerGraphicWidth + 10 + header.snappedLeftInset() + header.snappedRightInset();
 213         maxWidth = Math.max(maxWidth, headerWidth);
 214 
 215         // RT-23486
 216         maxWidth += padding;
 217         if(ttv.getColumnResizePolicy() == TreeTableView.CONSTRAINED_RESIZE_POLICY) {
 218             maxWidth = Math.max(maxWidth, tc.getWidth());
 219         }
 220 
 221         tc.impl_setWidth(maxWidth);
 222     }
 223 
 224     public static ObjectProperty<Callback<ResizeFeaturesBase,Boolean>> columnResizePolicyProperty(TableViewSkinBase<?,?,?,?,?> tableSkin) {
 225         Object control = tableSkin.getSkinnable();
 226         if (control instanceof TableView) {
 227             return ((TableView)control).columnResizePolicyProperty();
 228         } else if (control instanceof TreeTableView) {
 229             return ((TreeTableView)control).columnResizePolicyProperty();
 230         }
 231         return null;
 232     }
 233 
 234     public static BooleanProperty tableMenuButtonVisibleProperty(TableViewSkinBase<?,?,?,?,?> tableSkin) {
 235         Object control = tableSkin.getSkinnable();
 236         if (control instanceof TableView) {
 237             return ((TableView)control).tableMenuButtonVisibleProperty();
 238         } else if (control instanceof TreeTableView) {
 239             return ((TreeTableView)control).tableMenuButtonVisibleProperty();
 240         }
 241         return null;
 242     }
 243 
 244     public static ObjectProperty<Node> placeholderProperty(TableViewSkinBase<?,?,?,?,?> tableSkin) {
 245         Object control = tableSkin.getSkinnable();
 246         if (control instanceof TableView) {
 247             return ((TableView)control).placeholderProperty();
 248         } else if (control instanceof TreeTableView) {
 249             return ((TreeTableView)control).placeholderProperty();
 250         }
 251         return null;
 252     }
 253 
 254     public static <C extends Control,I extends IndexedCell<?>> ObjectProperty<Callback<C,I>> rowFactoryProperty(TableViewSkinBase<?,?,C,I,?> tableSkin) {
 255         Object control = tableSkin.getSkinnable();
 256         if (control instanceof TableView) {
 257             return ((TableView)control).rowFactoryProperty();
 258         } else if (control instanceof TreeTableView) {
 259             return ((TreeTableView)control).rowFactoryProperty();
 260         }
 261         return null;
 262     }
 263 
 264     public static ObservableList<TableColumnBase<?,?>> getSortOrder(TableViewSkinBase<?,?,?,?,?> tableSkin) {
 265         Object control = tableSkin.getSkinnable();
 266         if (control instanceof TableView) {
 267             return ((TableView)control).getSortOrder();
 268         } else if (control instanceof TreeTableView) {
 269             return ((TreeTableView)control).getSortOrder();
 270         }
 271         return FXCollections.emptyObservableList();
 272     }
 273 
 274     public static ObservableList<TableColumnBase<?,?>> getColumns(TableViewSkinBase<?,?,?,?,?> tableSkin) {
 275         Object control = tableSkin.getSkinnable();
 276         if (control instanceof TableView) {
 277             return ((TableView)control).getColumns();
 278         } else if (control instanceof TreeTableView) {
 279             return ((TreeTableView)control).getColumns();
 280         }
 281         return FXCollections.emptyObservableList();
 282     }
 283 
 284     public static <T> TableSelectionModel<T> getSelectionModel(TableViewSkinBase<?,?,?,?,?> tableSkin) {
 285         Object control = tableSkin.getSkinnable();
 286         if (control instanceof TableView) {
 287             return ((TableView)control).getSelectionModel();
 288         } else if (control instanceof TreeTableView) {
 289             return ((TreeTableView)control).getSelectionModel();
 290         }
 291         return null;
 292     }
 293 
 294     public static <T> TableFocusModel<T,?> getFocusModel(TableViewSkinBase<T,?,?,?,?> tableSkin) {
 295         Object control = tableSkin.getSkinnable();
 296         if (control instanceof TableView) {
 297             return ((TableView<T>)control).getFocusModel();
 298         } else if (control instanceof TreeTableView) {
 299             return ((TreeTableView)control).getFocusModel();
 300         }
 301         return null;
 302     }
 303 
 304     public static <T, TC extends TableColumnBase<T,?>> TablePositionBase<? extends TC> getFocusedCell(TableViewSkinBase<?,T,?,?,TC> tableSkin) {
 305         Object control = tableSkin.getSkinnable();
 306         if (control instanceof TableView) {
 307             return ((TableView<T>)control).getFocusModel().getFocusedCell();
 308         } else if (control instanceof TreeTableView) {
 309             return ((TreeTableView)control).getFocusModel().getFocusedCell();
 310         }
 311         return null;
 312     }
 313 
 314     public static <TC extends TableColumnBase<?,?>> ObservableList<TC> getVisibleLeafColumns(TableViewSkinBase<?,?,?,?,TC> tableSkin) {
 315         Object control = tableSkin.getSkinnable();
 316         if (control instanceof TableView) {
 317             return ((TableView)control).getVisibleLeafColumns();
 318         } else if (control instanceof TreeTableView) {
 319             return ((TreeTableView)control).getVisibleLeafColumns();
 320         }
 321         return FXCollections.emptyObservableList();
 322     }
 323 
 324     // returns the index of a column in the visible leaf columns
 325     public static int getVisibleLeafIndex(TableViewSkinBase<?,?,?,?,?> tableSkin, TableColumnBase tc) {
 326         Object control = tableSkin.getSkinnable();
 327         if (control instanceof TableView) {
 328             return ((TableView)control).getVisibleLeafIndex((TableColumn)tc);
 329         } else if (control instanceof TreeTableView) {
 330             return ((TreeTableView)control).getVisibleLeafIndex((TreeTableColumn)tc);
 331         }
 332         return -1;
 333     }
 334 
 335     // returns the leaf column at the given index
 336     public static <T, TC extends TableColumnBase<T,?>> TC getVisibleLeafColumn(TableViewSkinBase<?,T,?,?,TC> tableSkin, int col) {
 337         Object control = tableSkin.getSkinnable();
 338         if (control instanceof TableView) {
 339             return (TC) ((TableView)control).getVisibleLeafColumn(col);
 340         } else if (control instanceof TreeTableView) {
 341             return (TC) ((TreeTableView)control).getVisibleLeafColumn(col);
 342         }
 343         return null;
 344     }
 345 
 346     // returns a property representing the list of items in the control
 347     public static <T> ObjectProperty<ObservableList<T>> itemsProperty(TableViewSkinBase<?,?,?,?,?> tableSkin) {
 348         Object control = tableSkin.getSkinnable();
 349         if (control instanceof TableView) {
 350             return ((TableView)control).itemsProperty();
 351         } else if (control instanceof TreeTableView && tableSkin instanceof TreeTableViewSkin) {
 352             TreeTableViewSkin treeTableViewSkin = (TreeTableViewSkin)tableSkin;
 353             if (treeTableViewSkin.tableBackingListProperty == null) {
 354                 treeTableViewSkin.tableBackingList = new TreeTableViewBackingList<>((TreeTableView)control);
 355                 treeTableViewSkin.tableBackingListProperty = new SimpleObjectProperty<>(treeTableViewSkin.tableBackingList);
 356             }
 357             return treeTableViewSkin.tableBackingListProperty;
 358         }
 359         return null;
 360     }
 361 }