1 /*
   2  * Copyright (c) 2011, 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;
  27 
  28 import javafx.beans.InvalidationListener;
  29 import javafx.beans.WeakInvalidationListener;
  30 import javafx.collections.ListChangeListener;
  31 import javafx.scene.AccessibleAttribute;
  32 import javafx.scene.AccessibleRole;
  33 import javafx.scene.control.TableView.TableViewFocusModel;
  34 
  35 import javafx.collections.WeakListChangeListener;
  36 import java.lang.ref.WeakReference;
  37 import java.util.List;
  38 import javafx.beans.property.ReadOnlyObjectProperty;
  39 import javafx.beans.property.ReadOnlyObjectWrapper;
  40 import javafx.scene.control.skin.TableRowSkin;
  41 
  42 /**
  43  * <p>TableRow is an {@link javafx.scene.control.IndexedCell IndexedCell}, but
  44  * rarely needs to be used by developers creating TableView instances. The only
  45  * time TableRow is likely to be encountered at all by a developer is if they
  46  * wish to create a custom {@link TableView#rowFactoryProperty() rowFactory} 
  47  * that replaces an entire row of a TableView.</p>
  48  *
  49  * <p>More often than not, it is actually easier for a developer to customize
  50  * individual cells in a row, rather than the whole row itself. To do this,
  51  * you can specify a custom {@link TableColumn#cellFactoryProperty() cellFactory} 
  52  * on each TableColumn instance.</p>
  53  *
  54  * @see TableView
  55  * @see TableColumn
  56  * @see TableCell
  57  * @see IndexedCell
  58  * @see Cell
  59  * @param <T> The type of the item contained within the Cell.
  60  * @since JavaFX 2.0
  61  */
  62 public class TableRow<T> extends IndexedCell<T> {
  63 
  64     /***************************************************************************
  65      *                                                                         *
  66      * Constructors                                                            *
  67      *                                                                         *
  68      **************************************************************************/
  69 
  70     /**
  71      * Constructs a default TableRow instance with a style class of 'table-row-cell'
  72      */
  73     public TableRow() {
  74         getStyleClass().addAll(DEFAULT_STYLE_CLASS);
  75         setAccessibleRole(AccessibleRole.TABLE_ROW);
  76     }
  77 
  78 
  79 
  80     /***************************************************************************
  81      *                                                                         *
  82      * Instance Variables                                                      *
  83      *                                                                         *
  84      **************************************************************************/
  85 
  86     
  87 
  88     /***************************************************************************
  89      *                                                                         *
  90      * Callbacks and Events                                                    *
  91      *                                                                         *
  92      **************************************************************************/
  93 
  94     /*
  95      * This is the list observer we use to keep an eye on the SelectedCells
  96      * list in the table view. Because it is possible that the table can
  97      * be mutated, we create this observer here, and add/remove it from the
  98      * storeTableView method.
  99      */
 100     private ListChangeListener<TablePosition> selectedListener = c -> {
 101         updateSelection();
 102     };
 103 
 104     // Same as selectedListener, but this time for focus events
 105     private final InvalidationListener focusedListener = valueModel -> {
 106         updateFocus();
 107     };
 108 
 109     // same as above, but for editing events
 110     private final InvalidationListener editingListener = valueModel -> {
 111         updateEditing();
 112     };
 113 
 114     private final WeakListChangeListener<TablePosition> weakSelectedListener = new WeakListChangeListener<>(selectedListener);
 115     private final WeakInvalidationListener weakFocusedListener = new WeakInvalidationListener(focusedListener);
 116     private final WeakInvalidationListener weakEditingListener = new WeakInvalidationListener(editingListener);
 117 
 118     
 119     
 120     /***************************************************************************
 121      *                                                                         *
 122      * Properties                                                              *
 123      *                                                                         *
 124      **************************************************************************/
 125     
 126     // --- TableView
 127     private ReadOnlyObjectWrapper<TableView<T>> tableView;
 128     private void setTableView(TableView<T> value) {
 129         tableViewPropertyImpl().set(value);
 130     }
 131 
 132     public final TableView<T> getTableView() {
 133         return tableView == null ? null : tableView.get();
 134     }
 135 
 136     /**
 137      * The TableView associated with this Cell.
 138      */
 139     public final ReadOnlyObjectProperty<TableView<T>> tableViewProperty() {
 140         return tableViewPropertyImpl().getReadOnlyProperty();
 141     }
 142 
 143     private ReadOnlyObjectWrapper<TableView<T>> tableViewPropertyImpl() {
 144         if (tableView == null) {
 145             tableView = new ReadOnlyObjectWrapper<TableView<T>>() {
 146                 private WeakReference<TableView<T>> weakTableViewRef;
 147                 @Override protected void invalidated() {
 148                     TableView.TableViewSelectionModel<T> sm;
 149                     TableViewFocusModel<T> fm;
 150 
 151                     if (weakTableViewRef != null) {
 152                         TableView<T> oldTableView = weakTableViewRef.get();
 153                         if (oldTableView != null) {
 154                             sm = oldTableView.getSelectionModel();
 155                             if (sm != null) {
 156                                 sm.getSelectedCells().removeListener(weakSelectedListener);
 157                             }
 158 
 159                             fm = oldTableView.getFocusModel();
 160                             if (fm != null) {
 161                                 fm.focusedCellProperty().removeListener(weakFocusedListener);
 162                             }
 163 
 164                             oldTableView.editingCellProperty().removeListener(weakEditingListener);
 165                         }
 166                         
 167                         weakTableViewRef = null;
 168                     }
 169 
 170                     TableView<T> tableView = getTableView();
 171                     if (tableView != null) {
 172                         sm = tableView.getSelectionModel();
 173                         if (sm != null) {
 174                             sm.getSelectedCells().addListener(weakSelectedListener);
 175                         }
 176 
 177                         fm = tableView.getFocusModel();
 178                         if (fm != null) {
 179                             fm.focusedCellProperty().addListener(weakFocusedListener);
 180                         }
 181 
 182                         tableView.editingCellProperty().addListener(weakEditingListener);
 183                         
 184                         weakTableViewRef = new WeakReference<TableView<T>>(get());
 185                     }
 186                 }
 187 
 188                 @Override
 189                 public Object getBean() {
 190                     return TableRow.this;
 191                 }
 192 
 193                 @Override
 194                 public String getName() {
 195                     return "tableView";
 196                 }
 197             };
 198         }
 199         return tableView;
 200     }
 201 
 202 
 203 
 204     /***************************************************************************
 205      *                                                                         *
 206      * Public API                                                              *
 207      *                                                                         *
 208      **************************************************************************/
 209 
 210     /** {@inheritDoc} */
 211     @Override protected Skin<?> createDefaultSkin() {
 212         return new TableRowSkin<>(this);
 213     }
 214 
 215     /***************************************************************************
 216      *                                                                         *
 217      * Private implementation                                                  *
 218      *                                                                         *
 219      **************************************************************************/
 220 
 221     /** {@inheritDoc} */
 222     @Override void indexChanged(int oldIndex, int newIndex) {
 223         super.indexChanged(oldIndex, newIndex);
 224 
 225         updateItem(oldIndex);
 226         updateSelection();
 227         updateFocus();
 228     }
 229 
 230     private boolean isFirstRun = true;
 231     private void updateItem(int oldIndex) {
 232         TableView<T> tv = getTableView();
 233         if (tv == null || tv.getItems() == null) return;
 234         
 235         final List<T> items = tv.getItems();
 236         final int itemCount = items == null ? -1 : items.size();
 237 
 238         // Compute whether the index for this cell is for a real item
 239         final int newIndex = getIndex();
 240         boolean valid = newIndex >= 0 && newIndex < itemCount;
 241 
 242         final T oldValue = getItem();
 243         final boolean isEmpty = isEmpty();
 244 
 245         // Cause the cell to update itself
 246         outer: if (valid) {
 247             final T newValue = items.get(newIndex);
 248 
 249             // RT-35864 - if the index didn't change, then avoid calling updateItem
 250             // unless the item has changed.
 251             if (oldIndex == newIndex) {
 252                 if (!isItemChanged(oldValue, newValue)) {
 253                     // RT-37054:  we break out of the if/else code here and
 254                     // proceed with the code following this, so that we may
 255                     // still update references, listeners, etc as required.
 256                     break outer;
 257                 }
 258             }
 259             updateItem(newValue, false);
 260         } else {
 261             // RT-30484 We need to allow a first run to be special-cased to allow
 262             // for the updateItem method to be called at least once to allow for
 263             // the correct visual state to be set up. In particular, in RT-30484
 264             // refer to Ensemble8PopUpTree.png - in this case the arrows are being
 265             // shown as the new cells are instantiated with the arrows in the
 266             // children list, and are only hidden in updateItem.
 267             if ((!isEmpty && oldValue != null) || isFirstRun) {
 268                 updateItem(null, true);
 269                 isFirstRun = false;
 270             }
 271         }
 272     }
 273     
 274     private void updateSelection() {
 275         /*
 276          * This cell should be selected if the selection mode of the table
 277          * is row-based, and if the row that this cell represents is selected.
 278          *
 279          * If the selection mode is not row-based, then the listener in the
 280          * TableCell class might pick up the need to set a single cell to be
 281          * selected.
 282          */
 283         if (getIndex() == -1) return;
 284         
 285         TableView<T> table = getTableView();
 286         boolean isSelected = table != null &&
 287                 table.getSelectionModel() != null &&
 288                 ! table.getSelectionModel().isCellSelectionEnabled() &&
 289                 table.getSelectionModel().isSelected(getIndex());
 290 
 291         updateSelected(isSelected);
 292     }
 293 
 294     private void updateFocus() {
 295         if (getIndex() == -1) return;
 296         
 297         TableView<T> table = getTableView();
 298         if (table == null) return;
 299         
 300         TableView.TableViewSelectionModel<T> sm = table.getSelectionModel();
 301         TableView.TableViewFocusModel<T> fm = table.getFocusModel();
 302         if (sm == null || fm == null) return;
 303         
 304         boolean isFocused = ! sm.isCellSelectionEnabled() && fm.isFocused(getIndex());
 305         setFocused(isFocused);
 306     }
 307 
 308     private void updateEditing() {
 309         if (getIndex() == -1) return;
 310         
 311         TableView<T> table = getTableView();
 312         if (table == null) return;
 313 
 314         TableView.TableViewSelectionModel<T> sm = table.getSelectionModel();
 315         if (sm == null || sm.isCellSelectionEnabled()) return;
 316 
 317         TablePosition<T,?> editCell = table.getEditingCell();
 318         if (editCell != null && editCell.getTableColumn() != null) {
 319             return;
 320         }
 321 
 322         boolean rowMatch = editCell == null ? false : editCell.getRow() == getIndex();
 323 
 324         if (! isEditing() && rowMatch) {
 325             startEdit();
 326         } else if (isEditing() && ! rowMatch) {
 327             cancelEdit();
 328         }
 329     }
 330 
 331 
 332 
 333     /***************************************************************************
 334      *                                                                         *
 335      * Expert API                                                              *
 336      *                                                                         *
 337      **************************************************************************/
 338 
 339     /**
 340      * Updates the TableView associated with this TableCell. This is typically
 341      * only done once when the TableCell is first added to the TableView.
 342      *
 343      * @expert This function is intended to be used by experts, primarily
 344      *         by those implementing new Skins. It is not common
 345      *         for developers or designers to access this function directly.
 346      */
 347     public final void updateTableView(TableView<T> tv) {
 348         setTableView(tv);
 349     }
 350 
 351 
 352     /***************************************************************************
 353      *                                                                         *
 354      * Stylesheet Handling                                                     *
 355      *                                                                         *
 356      **************************************************************************/
 357 
 358     private static final String DEFAULT_STYLE_CLASS = "table-row-cell";
 359 
 360 
 361     /***************************************************************************
 362      *                                                                         *
 363      * Accessibility handling                                                  *
 364      *                                                                         *
 365      **************************************************************************/
 366 
 367     @Override
 368     public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
 369         switch (attribute) {
 370             case INDEX: return getIndex();
 371             default: return super.queryAccessibleAttribute(attribute, parameters);
 372         }
 373     }
 374 }