1 /* 2 * Copyright (c) 2010, 2018, 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 java.util.ArrayList; 29 import java.util.List; 30 31 import javafx.beans.property.BooleanProperty; 32 import javafx.beans.property.ObjectProperty; 33 import javafx.collections.FXCollections; 34 import javafx.collections.ObservableList; 35 import javafx.event.EventHandler; 36 import javafx.scene.AccessibleAction; 37 import javafx.scene.AccessibleAttribute; 38 import javafx.scene.Node; 39 import javafx.scene.control.ResizeFeaturesBase; 40 import javafx.scene.control.TableCell; 41 import javafx.scene.control.TableColumn; 42 import javafx.scene.control.TablePosition; 43 import javafx.scene.control.TableRow; 44 import javafx.scene.control.TableSelectionModel; 45 import javafx.scene.control.TableView; 46 import javafx.scene.control.TableView.TableViewFocusModel; 47 import javafx.scene.control.TableView.TableViewSelectionModel; 48 import javafx.scene.input.MouseEvent; 49 import javafx.scene.layout.Region; 50 import javafx.util.Callback; 51 52 import com.sun.javafx.scene.control.behavior.TableViewBehavior; 53 54 public class TableViewSkin<T> extends TableViewSkinBase<T, T, TableView<T>, TableViewBehavior<T>, TableRow<T>, TableColumn<T, ?>> { 55 56 private final TableView<T> tableView; 57 58 public TableViewSkin(final TableView<T> tableView) { 59 super(tableView, new TableViewBehavior<T>(tableView)); 60 61 this.tableView = tableView; 62 flow.setFixedCellSize(tableView.getFixedCellSize()); 63 64 super.init(tableView); 65 66 EventHandler<MouseEvent> ml = event -> { 67 // RT-15127: cancel editing on scroll. This is a bit extreme 68 // (we are cancelling editing on touching the scrollbars). 69 // This can be improved at a later date. 70 if (tableView.getEditingCell() != null) { 71 tableView.edit(-1, null); 72 } 73 74 // This ensures that the table maintains the focus, even when the vbar 75 // and hbar controls inside the flow are clicked. Without this, the 76 // focus border will not be shown when the user interacts with the 77 // scrollbars, and more importantly, keyboard navigation won't be 78 // available to the user. 79 if (tableView.isFocusTraversable()) { 80 tableView.requestFocus(); 81 } 82 }; 83 flow.getVbar().addEventFilter(MouseEvent.MOUSE_PRESSED, ml); 84 flow.getHbar().addEventFilter(MouseEvent.MOUSE_PRESSED, ml); 85 86 // init the behavior 'closures' 87 TableViewBehavior<T> behavior = getBehavior(); 88 behavior.setOnFocusPreviousRow(() -> { onFocusPreviousCell(); }); 89 behavior.setOnFocusNextRow(() -> { onFocusNextCell(); }); 90 behavior.setOnMoveToFirstCell(() -> { onMoveToFirstCell(); }); 91 behavior.setOnMoveToLastCell(() -> { onMoveToLastCell(); }); 92 behavior.setOnScrollPageDown(isFocusDriven -> onScrollPageDown(isFocusDriven)); 93 behavior.setOnScrollPageUp(isFocusDriven -> onScrollPageUp(isFocusDriven)); 94 behavior.setOnSelectPreviousRow(() -> { onSelectPreviousCell(); }); 95 behavior.setOnSelectNextRow(() -> { onSelectNextCell(); }); 96 behavior.setOnSelectLeftCell(() -> { onSelectLeftCell(); }); 97 behavior.setOnSelectRightCell(() -> { onSelectRightCell(); }); 98 99 registerChangeListener(tableView.fixedCellSizeProperty(), "FIXED_CELL_SIZE"); 100 101 } 102 103 @Override protected void handleControlPropertyChanged(String p) { 104 super.handleControlPropertyChanged(p); 105 106 if ("FIXED_CELL_SIZE".equals(p)) { 107 flow.setFixedCellSize(getSkinnable().getFixedCellSize()); 108 } 109 } 110 111 /*************************************************************************** 112 * * 113 * Listeners * 114 * * 115 **************************************************************************/ 116 117 118 119 /*************************************************************************** 120 * * 121 * Internal Fields * 122 * * 123 **************************************************************************/ 124 125 126 127 /*************************************************************************** 128 * * 129 * Public API * 130 * * 131 **************************************************************************/ 132 133 /** {@inheritDoc} */ 134 @Override protected ObservableList<TableColumn<T, ?>> getVisibleLeafColumns() { 135 return tableView.getVisibleLeafColumns(); 136 } 137 138 @Override protected int getVisibleLeafIndex(TableColumn<T, ?> tc) { 139 return tableView.getVisibleLeafIndex(tc); 140 } 141 142 @Override protected TableColumn<T, ?> getVisibleLeafColumn(int col) { 143 return tableView.getVisibleLeafColumn(col); 144 } 145 146 /** {@inheritDoc} */ 147 @Override protected TableViewFocusModel<T> getFocusModel() { 148 return tableView.getFocusModel(); 149 } 150 151 /** {@inheritDoc} */ 152 @Override protected TablePosition<T, ?> getFocusedCell() { 153 return tableView.getFocusModel().getFocusedCell(); 154 } 155 156 /** {@inheritDoc} */ 157 @Override protected TableSelectionModel<T> getSelectionModel() { 158 return tableView.getSelectionModel(); 159 } 160 161 /** {@inheritDoc} */ 162 @Override protected ObjectProperty<Callback<TableView<T>, TableRow<T>>> rowFactoryProperty() { 163 return tableView.rowFactoryProperty(); 164 } 165 166 /** {@inheritDoc} */ 167 @Override protected ObjectProperty<Node> placeholderProperty() { 168 return tableView.placeholderProperty(); 169 } 170 171 /** {@inheritDoc} */ 172 @Override protected ObjectProperty<ObservableList<T>> itemsProperty() { 173 return tableView.itemsProperty(); 174 } 175 176 /** {@inheritDoc} */ 177 @Override protected ObservableList<TableColumn<T, ?>> getColumns() { 178 return tableView.getColumns(); 179 } 180 181 /** {@inheritDoc} */ 182 @Override protected BooleanProperty tableMenuButtonVisibleProperty() { 183 return tableView.tableMenuButtonVisibleProperty(); 184 } 185 186 /** {@inheritDoc} */ 187 @Override protected ObjectProperty<Callback<ResizeFeaturesBase, Boolean>> columnResizePolicyProperty() { 188 // TODO Ugly! 189 return (ObjectProperty<Callback<ResizeFeaturesBase, Boolean>>) (Object) tableView.columnResizePolicyProperty(); 190 } 191 192 /** {@inheritDoc} */ 193 @Override protected ObservableList<TableColumn<T,?>> getSortOrder() { 194 return tableView.getSortOrder(); 195 } 196 197 @Override protected boolean resizeColumn(TableColumn<T, ?> tc, double delta) { 198 return tableView.resizeColumn(tc, delta); 199 } 200 201 @Override protected void edit(int index, TableColumn<T, ?> column) { 202 tableView.edit(index, (TableColumn<T,?>)column); 203 } 204 205 /* 206 * FIXME: Naive implementation ahead 207 * Attempts to resize column based on the pref width of all items contained 208 * in this column. This can be potentially very expensive if the number of 209 * rows is large. 210 */ 211 @Override protected void resizeColumnToFitContent(TableColumn<T, ?> tc, int maxRows) { 212 if (!tc.isResizable()) return; 213 214 // final TableColumn<T, ?> col = tc; 215 List<?> items = itemsProperty().get(); 216 if (items == null || items.isEmpty()) return; 217 218 Callback/*<TableColumn<T, ?>, TableCell<T,?>>*/ cellFactory = tc.getCellFactory(); 219 if (cellFactory == null) return; 220 221 TableCell<T,?> cell = (TableCell<T, ?>) cellFactory.call(tc); 222 if (cell == null) return; 223 224 // set this property to tell the TableCell we want to know its actual 225 // preferred width, not the width of the associated TableColumnBase 226 cell.getProperties().put(TableCellSkin.DEFER_TO_PARENT_PREF_WIDTH, Boolean.TRUE); 227 228 // determine cell padding 229 double padding = 10; 230 Node n = cell.getSkin() == null ? null : cell.getSkin().getNode(); 231 if (n instanceof Region) { 232 Region r = (Region) n; 233 padding = r.snappedLeftInset() + r.snappedRightInset(); 234 } 235 236 int rows = maxRows == -1 ? items.size() : Math.min(items.size(), maxRows); 237 double maxWidth = 0; 238 for (int row = 0; row < rows; row++) { 239 cell.updateTableColumn(tc); 240 cell.updateTableView(tableView); 241 cell.updateIndex(row); 242 243 if ((cell.getText() != null && !cell.getText().isEmpty()) || cell.getGraphic() != null) { 244 getChildren().add(cell); 245 cell.applyCss(); 246 maxWidth = Math.max(maxWidth, cell.prefWidth(-1)); 247 getChildren().remove(cell); 248 } 249 } 250 251 // dispose of the cell to prevent it retaining listeners (see RT-31015) 252 cell.updateIndex(-1); 253 254 // RT-36855 - take into account the column header text / graphic widths. 255 // Magic 10 is to allow for sort arrow to appear without text truncation. 256 TableColumnHeader header = getTableHeaderRow().getColumnHeaderFor(tc); 257 double headerTextWidth = Utils.computeTextWidth(header.label.getFont(), tc.getText(), -1); 258 Node graphic = header.label.getGraphic(); 259 double headerGraphicWidth = graphic == null ? 0 : graphic.prefWidth(-1) + header.label.getGraphicTextGap(); 260 double headerWidth = headerTextWidth + headerGraphicWidth + 10 + header.snappedLeftInset() + header.snappedRightInset(); 261 maxWidth = Math.max(maxWidth, headerWidth); 262 263 // RT-23486 264 maxWidth += padding; 265 if (tableView.getColumnResizePolicy() == TableView.CONSTRAINED_RESIZE_POLICY && tableView.getWidth() > 0) { 266 267 if (maxWidth > tc.getMaxWidth()) { 268 maxWidth = tc.getMaxWidth(); 269 } 270 271 int size = tc.getColumns().size(); 272 if (size > 0) { 273 resizeColumnToFitContent(tc.getColumns().get(size - 1), maxRows); 274 return; 275 } 276 277 resizeColumn(tc, Math.round(maxWidth - tc.getWidth())); 278 } else { 279 tc.impl_setWidth(maxWidth); 280 } 281 } 282 283 /** {@inheritDoc} */ 284 @Override public int getItemCount() { 285 return tableView.getItems() == null ? 0 : tableView.getItems().size(); 286 } 287 288 /** {@inheritDoc} */ 289 @Override public TableRow<T> createCell() { 290 TableRow<T> cell; 291 292 if (tableView.getRowFactory() != null) { 293 cell = tableView.getRowFactory().call(tableView); 294 } else { 295 cell = new TableRow<T>(); 296 } 297 298 cell.updateTableView(tableView); 299 return cell; 300 } 301 302 @Override protected void horizontalScroll() { 303 super.horizontalScroll(); 304 if (getSkinnable().getFixedCellSize() > 0) { 305 flow.requestCellLayout(); 306 } 307 } 308 309 @Override 310 public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) { 311 switch (attribute) { 312 case SELECTED_ITEMS: { 313 List<Node> selection = new ArrayList<>(); 314 TableViewSelectionModel<T> sm = getSkinnable().getSelectionModel(); 315 for (TablePosition<T,?> pos : sm.getSelectedCells()) { 316 TableRow<T> row = flow.getPrivateCell(pos.getRow()); 317 if (row != null) selection.add(row); 318 } 319 return FXCollections.observableArrayList(selection); 320 } 321 default: return super.queryAccessibleAttribute(attribute, parameters); 322 } 323 } 324 325 @Override 326 protected void executeAccessibleAction(AccessibleAction action, Object... parameters) { 327 switch (action) { 328 case SHOW_ITEM: { 329 Node item = (Node)parameters[0]; 330 if (item instanceof TableCell) { 331 @SuppressWarnings("unchecked") 332 TableCell<T, ?> cell = (TableCell<T, ?>)item; 333 flow.show(cell.getIndex()); 334 } 335 break; 336 } 337 case SET_SELECTED_ITEMS: { 338 @SuppressWarnings("unchecked") 339 ObservableList<Node> items = (ObservableList<Node>)parameters[0]; 340 if (items != null) { 341 TableSelectionModel<T> sm = getSkinnable().getSelectionModel(); 342 if (sm != null) { 343 sm.clearSelection(); 344 for (Node item : items) { 345 if (item instanceof TableCell) { 346 @SuppressWarnings("unchecked") 347 TableCell<T, ?> cell = (TableCell<T, ?>)item; 348 sm.select(cell.getIndex(), cell.getTableColumn()); 349 } 350 } 351 } 352 } 353 break; 354 } 355 default: super.executeAccessibleAction(action, parameters); 356 } 357 } 358 359 /*************************************************************************** 360 * * 361 * Layout * 362 * * 363 **************************************************************************/ 364 365 366 367 /*************************************************************************** 368 * * 369 * Private methods * 370 * * 371 **************************************************************************/ 372 373 }