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