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