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 }