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 }