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 }