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 }