Spec-Zone .ru
спецификации, руководства, описания, API
|
001/* 002 * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. Oracle designates this 008 * particular file as subject to the "Classpath" exception as provided 009 * by Oracle in the LICENSE file that accompanied this code. 010 * 011 * This code is distributed in the hope that it will be useful, but WITHOUT 012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 014 * version 2 for more details (a copy is included in the LICENSE file that 015 * accompanied this code). 016 * 017 * You should have received a copy of the GNU General Public License version 018 * 2 along with this work; if not, write to the Free Software Foundation, 019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 020 * 021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 022 * or visit www.oracle.com if you need additional information or have any 023 * questions. 024 */ 025 026package javafx.scene.control; 027 028import java.lang.ref.WeakReference; 029import java.util.ArrayList; 030import java.util.BitSet; 031import java.util.Collections; 032import java.util.Comparator; 033import java.util.HashMap; 034import java.util.LinkedHashSet; 035import java.util.List; 036import java.util.Set; 037 038import javafx.beans.DefaultProperty; 039import javafx.beans.InvalidationListener; 040import javafx.beans.Observable; 041import javafx.beans.WeakInvalidationListener; 042import javafx.beans.property.BooleanProperty; 043import javafx.beans.property.DoubleProperty; 044import javafx.beans.property.ObjectProperty; 045import javafx.beans.property.ObjectPropertyBase; 046import javafx.beans.property.ReadOnlyObjectProperty; 047import javafx.beans.property.ReadOnlyObjectWrapper; 048import javafx.beans.property.SimpleBooleanProperty; 049import javafx.beans.property.SimpleDoubleProperty; 050import javafx.beans.property.SimpleObjectProperty; 051import javafx.beans.value.ChangeListener; 052import javafx.beans.value.ObservableValue; 053import javafx.beans.value.WeakChangeListener; 054import javafx.collections.FXCollections; 055import javafx.collections.ListChangeListener; 056import javafx.collections.MapChangeListener; 057import javafx.collections.ObservableList; 058import javafx.collections.WeakListChangeListener; 059import javafx.css.CssMetaData; 060import javafx.css.PseudoClass; 061import javafx.css.Styleable; 062import javafx.css.StyleableDoubleProperty; 063import javafx.css.StyleableProperty; 064import javafx.event.EventHandler; 065import javafx.event.EventType; 066import javafx.geometry.Orientation; 067import javafx.scene.Node; 068import javafx.scene.layout.Region; 069import javafx.util.Callback; 070 071import com.sun.javafx.collections.MappingChange; 072import com.sun.javafx.collections.NonIterableChange; 073import com.sun.javafx.collections.annotations.ReturnsUnmodifiableCollection; 074import com.sun.javafx.css.converters.EnumConverter; 075import com.sun.javafx.css.converters.SizeConverter; 076import com.sun.javafx.scene.control.ReadOnlyUnbackedObservableList; 077import com.sun.javafx.scene.control.TableColumnComparatorBase.TableColumnComparator; 078import com.sun.javafx.scene.control.skin.TableViewSkin; 079import com.sun.javafx.scene.control.skin.TableViewSkinBase; 080 081/** 082 * The TableView control is designed to visualize an unlimited number of rows 083 * of data, broken out into columns. A TableView is therefore very similar to the 084 * {@link ListView} control, with the addition of support for columns. For an 085 * example on how to create a TableView, refer to the 'Creating a TableView' 086 * control section below. 087 * 088 * <p>The TableView control has a number of features, including: 089 * <ul> 090 * <li>Powerful {@link TableColumn} API: 091 * <ul> 092 * <li>Support for {@link TableColumn#cellFactoryProperty() cell factories} to 093 * easily customize {@link Cell cell} contents in both rendering and editing 094 * states. 095 * <li>Specification of {@link #minWidthProperty() minWidth}/ 096 * {@link #prefWidthProperty() prefWidth}/{@link #maxWidthProperty() maxWidth}, 097 * and also {@link TableColumn#resizableProperty() fixed width columns}. 098 * <li>Width resizing by the user at runtime. 099 * <li>Column reordering by the user at runtime. 100 * <li>Built-in support for {@link TableColumn#getColumns() column nesting} 101 * </ul> 102 * <li>Different {@link #columnResizePolicyProperty() resizing policies} to 103 * dictate what happens when the user resizes columns. 104 * <li>Support for {@link #getSortOrder() multiple column sorting} by clicking 105 * the column header (hold down Shift keyboard key whilst clicking on a 106 * header to sort by multiple columns). 107 * </ul> 108 * </p> 109 * 110 * <p>Note that TableView is intended to be used to visualize data - it is not 111 * intended to be used for laying out your user interface. If you want to lay 112 * your user interface out in a grid-like fashion, consider the 113 * {@link GridPane} layout.</p> 114 * 115 * <h2>Creating a TableView</h2> 116 * 117 * <p>Creating a TableView is a multi-step process, and also depends on the 118 * underlying data model needing to be represented. For this example we'll use 119 * an ObservableList<Person>, as it is the simplest way of showing data in a 120 * TableView. The <code>Person</code> class will consist of a first 121 * name and last name properties. That is: 122 * 123 * <pre> 124 * {@code 125 * public class Person { 126 * private StringProperty firstName; 127 * public void setFirstName(String value) { firstNameProperty().set(value); } 128 * public String getFirstName() { return firstNameProperty().get(); } 129 * public StringProperty firstNameProperty() { 130 * if (firstName == null) firstName = new SimpleStringProperty(this, "firstName"); 131 * return firstName; 132 * } 133 * 134 * private StringProperty lastName; 135 * public void setLastName(String value) { lastNameProperty().set(value); } 136 * public String getLastName() { return lastNameProperty().get(); } 137 * public StringProperty lastNameProperty() { 138 * if (lastName == null) lastName = new SimpleStringProperty(this, "lastName"); 139 * return lastName; 140 * } 141 * }}</pre> 142 * 143 * <p>Firstly, a TableView instance needs to be defined, as such: 144 * 145 * <pre> 146 * {@code 147 * TableView<Person> table = new TableView<Person>();}</pre> 148 * 149 * <p>With the basic table defined, we next focus on the data model. As mentioned, 150 * for this example, we'll be using a ObservableList<Person>. We can immediately 151 * set such a list directly in to the TableView, as such: 152 * 153 * <pre> 154 * {@code 155 * ObservableList<Person> teamMembers = getTeamMembers(); 156 * table.setItems(teamMembers);}</pre> 157 * 158 * <p>With the items set as such, TableView will automatically update whenever 159 * the <code>teamMembers</code> list changes. If the items list is available 160 * before the TableView is instantiated, it is possible to pass it directly into 161 * the constructor. 162 * 163 * <p>At this point we now have a TableView hooked up to observe the 164 * <code>teamMembers</code> observableList. The missing ingredient 165 * now is the means of splitting out the data contained within the model and 166 * representing it in one or more {@link TableColumn TableColumn} instances. To 167 * create a two-column TableView to show the firstName and lastName properties, 168 * we extend the last code sample as follows: 169 * 170 * <pre> 171 * {@code 172 * ObservableList<Person> teamMembers = ...; 173 * table.setItems(teamMembers); 174 * 175 * TableColumn<Person,String> firstNameCol = new TableColumn<Person,String>("First Name"); 176 * firstNameCol.setCellValueFactory(new PropertyValueFactory("firstName")); 177 * TableColumn<Person,String> lastNameCol = new TableColumn<Person,String>("Last Name"); 178 * lastNameCol.setCellValueFactory(new PropertyValueFactory("lastName")); 179 * 180 * table.getColumns().setAll(firstNameCol, lastNameCol);}</pre> 181 * 182 * <p>With the code shown above we have fully defined the minimum properties 183 * required to create a TableView instance. Running this code (assuming the 184 * people ObservableList is appropriately created) will result in a TableView being 185 * shown with two columns for firstName and lastName. Any other properties of the 186 * Person class will not be shown, as no TableColumns are defined. 187 * 188 * <h3>TableView support for classes that don't contain properties</h3> 189 * 190 * <p>The code shown above is the shortest possible code for creating a TableView 191 * when the domain objects are designed with JavaFX properties in mind 192 * (additionally, {@link javafx.scene.control.cell.PropertyValueFactory} supports 193 * normal JavaBean properties too, although there is a caveat to this, so refer 194 * to the class documentation for more information). When this is not the case, 195 * it is necessary to provide a custom cell value factory. More information 196 * about cell value factories can be found in the {@link TableColumn} API 197 * documentation, but briefly, here is how a TableColumn could be specified: 198 * 199 * <pre> 200 * {@code 201 * firstNameCol.setCellValueFactory(new Callback<CellDataFeatures<Person, String>, ObservableValue<String>>() { 202 * public ObservableValue<String> call(CellDataFeatures<Person, String> p) { 203 * // p.getValue() returns the Person instance for a particular TableView row 204 * return p.getValue().firstNameProperty(); 205 * } 206 * }); 207 * }}</pre> 208 * 209 * <h3>TableView Selection / Focus APIs</h3> 210 * <p>To track selection and focus, it is necessary to become familiar with the 211 * {@link SelectionModel} and {@link FocusModel} classes. A TableView has at most 212 * one instance of each of these classes, available from 213 * {@link #selectionModelProperty() selectionModel} and 214 * {@link #focusModelProperty() focusModel} properties respectively. 215 * Whilst it is possible to use this API to set a new selection model, in 216 * most circumstances this is not necessary - the default selection and focus 217 * models should work in most circumstances. 218 * 219 * <p>The default {@link SelectionModel} used when instantiating a TableView is 220 * an implementation of the {@link MultipleSelectionModel} abstract class. 221 * However, as noted in the API documentation for 222 * the {@link MultipleSelectionModel#selectionModeProperty() selectionMode} 223 * property, the default value is {@link SelectionMode#SINGLE}. To enable 224 * multiple selection in a default TableView instance, it is therefore necessary 225 * to do the following: 226 * 227 * <pre> 228 * {@code 229 * tableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);}</pre> 230 * 231 * <h3>Customizing TableView Visuals</h3> 232 * <p>The visuals of the TableView can be entirely customized by replacing the 233 * default {@link #rowFactoryProperty() row factory}. A row factory is used to 234 * generate {@link TableRow} instances, which are used to represent an entire 235 * row in the TableView. 236 * 237 * <p>In many cases, this is not what is desired however, as it is more commonly 238 * the case that cells be customized on a per-column basis, not a per-row basis. 239 * It is therefore important to note that a {@link TableRow} is not a 240 * {@link TableCell}. A {@link TableRow} is simply a container for zero or more 241 * {@link TableCell}, and in most circumstances it is more likely that you'll 242 * want to create custom TableCells, rather than TableRows. The primary use case 243 * for creating custom TableRow instances would most probably be to introduce 244 * some form of column spanning support. 245 * 246 * <p>You can create custom {@link TableCell} instances per column by assigning 247 * the appropriate function to the TableColumn 248 * {@link TableColumn#cellFactoryProperty() cell factory} property. 249 * 250 * <p>See the {@link Cell} class documentation for a more complete 251 * description of how to write custom Cells. 252 * 253 * @see TableColumn 254 * @see TablePosition 255 * @param <S> The type of the objects contained within the TableView items list. 256 */ 257@DefaultProperty("items") 258public class TableView<S> extends Control { 259 260 /*************************************************************************** 261 * * 262 * Static properties and methods * 263 * * 264 **************************************************************************/ 265 266 // strings used to communicate via the TableView properties map between 267 // the control and the skin. Because they are private here, the strings 268 // are also duplicated in the TableViewSkin class - so any changes to these 269 // strings must also be duplicated there 270 static final String SET_CONTENT_WIDTH = "TableView.contentWidth"; 271 272 /** 273 * <p>Very simple resize policy that just resizes the specified column by the 274 * provided delta and shifts all other columns (to the right of the given column) 275 * further to the right (when the delta is positive) or to the left (when the 276 * delta is negative). 277 * 278 * <p>It also handles the case where we have nested columns by sharing the new space, 279 * or subtracting the removed space, evenly between all immediate children columns. 280 * Of course, the immediate children may themselves be nested, and they would 281 * then use this policy on their children. 282 */ 283 public static final Callback<ResizeFeatures, Boolean> UNCONSTRAINED_RESIZE_POLICY = new Callback<ResizeFeatures, Boolean>() { 284 @Override public String toString() { 285 return "unconstrained-resize"; 286 } 287 288 @Override public Boolean call(ResizeFeatures prop) { 289 double result = TableUtil.resize(prop.getColumn(), prop.getDelta()); 290 return Double.compare(result, 0.0) == 0; 291 } 292 }; 293 294 /** 295 * <p>Simple policy that ensures the width of all visible leaf columns in 296 * this table sum up to equal the width of the table itself. 297 * 298 * <p>When the user resizes a column width with this policy, the table automatically 299 * adjusts the width of the right hand side columns. When the user increases a 300 * column width, the table decreases the width of the rightmost column until it 301 * reaches its minimum width. Then it decreases the width of the second 302 * rightmost column until it reaches minimum width and so on. When all right 303 * hand side columns reach minimum size, the user cannot increase the size of 304 * resized column any more. 305 */ 306 public static final Callback<ResizeFeatures, Boolean> CONSTRAINED_RESIZE_POLICY = new Callback<ResizeFeatures, Boolean>() { 307 308 private boolean isFirstRun = true; 309 310 @Override public String toString() { 311 return "constrained-resize"; 312 } 313 314 @Override public Boolean call(ResizeFeatures prop) { 315 TableView<?> table = prop.getTable(); 316 List<? extends TableColumnBase<?,?>> visibleLeafColumns = table.getVisibleLeafColumns(); 317 Boolean result = TableUtil.constrainedResize(prop, 318 isFirstRun, 319 table.contentWidth, 320 visibleLeafColumns); 321 isFirstRun = false; 322 return result; 323 } 324 }; 325 326 /** 327 * The default {@link #sortPolicyProperty() sort policy} that this TableView 328 * will use if no other policy is specified. The sort policy is a simple 329 * {@link Callback} that accepts a TableView as the sole argument and expects 330 * a Boolean response representing whether the sort succeeded or not. A Boolean 331 * response of true represents success, and a response of false (or null) will 332 * be considered to represent failure. 333 */ 334 public static final Callback<TableView, Boolean> DEFAULT_SORT_POLICY = new Callback<TableView, Boolean>() { 335 @Override public Boolean call(TableView table) { 336 try { 337 FXCollections.sort(table.getItems(), table.getComparator()); 338 return true; 339 } catch (UnsupportedOperationException e) { 340 // TODO might need to support other exception types including: 341 // ClassCastException - if the class of the specified element prevents it from being added to this list 342 // NullPointerException - if the specified element is null and this list does not permit null elements 343 // IllegalArgumentException - if some property of this element prevents it from being added to this list 344 345 // If we are here the list does not support sorting, so we gracefully 346 // fail the sort request and ensure the UI is put back to its previous 347 // state. This is handled in the code that calls the sort policy. 348 349 return false; 350 } 351 } 352 }; 353 354 355 356 /*************************************************************************** 357 * * 358 * Constructors * 359 * * 360 **************************************************************************/ 361 362 /** 363 * Creates a default TableView control with no content. 364 * 365 * <p>Refer to the {@link TableView} class documentation for details on the 366 * default state of other properties. 367 */ 368 public TableView() { 369 this(FXCollections.<S>observableArrayList()); 370 } 371 372 /** 373 * Creates a TableView with the content provided in the items ObservableList. 374 * This also sets up an observer such that any changes to the items list 375 * will be immediately reflected in the TableView itself. 376 * 377 * <p>Refer to the {@link TableView} class documentation for details on the 378 * default state of other properties. 379 * 380 * @param items The items to insert into the TableView, and the list to watch 381 * for changes (to automatically show in the TableView). 382 */ 383 public TableView(ObservableList<S> items) { 384 getStyleClass().setAll(DEFAULT_STYLE_CLASS); 385 386 // we quite happily accept items to be null here 387 setItems(items); 388 389 // install default selection and focus models 390 // it's unlikely this will be changed by many users. 391 setSelectionModel(new TableViewArrayListSelectionModel<S>(this)); 392 setFocusModel(new TableViewFocusModel<S>(this)); 393 394 // we watch the columns list, such that when it changes we can update 395 // the leaf columns and visible leaf columns lists (which are read-only). 396 getColumns().addListener(weakColumnsObserver); 397 398 // watch for changes to the sort order list - and when it changes run 399 // the sort method. 400 getSortOrder().addListener(new ListChangeListener<TableColumn<S,?>>() { 401 @Override public void onChanged(Change<? extends TableColumn<S,?>> c) { 402 doSort(TableUtil.SortEventType.SORT_ORDER_CHANGE, c); 403 } 404 }); 405 406 // We're watching for changes to the content width such 407 // that the resize policy can be run if necessary. This comes from 408 // TreeViewSkin. 409 getProperties().addListener(new MapChangeListener<Object, Object>() { 410 @Override 411 public void onChanged(Change<? extends Object, ? extends Object> c) { 412 if (c.wasAdded() && SET_CONTENT_WIDTH.equals(c.getKey())) { 413 if (c.getValueAdded() instanceof Number) { 414 setContentWidth((Double) c.getValueAdded()); 415 } 416 getProperties().remove(SET_CONTENT_WIDTH); 417 } 418 } 419 }); 420 421 isInited = true; 422 } 423 424 425 426 /*************************************************************************** 427 * * 428 * Instance Variables * 429 * * 430 **************************************************************************/ 431 432 // this is the only publicly writable list for columns. This represents the 433 // columns as they are given initially by the developer. 434 private final ObservableList<TableColumn<S,?>> columns = FXCollections.observableArrayList(); 435 436 // Finally, as convenience, we also have an observable list that contains 437 // only the leaf columns that are currently visible. 438 private final ObservableList<TableColumn<S,?>> visibleLeafColumns = FXCollections.observableArrayList(); 439 private final ObservableList<TableColumn<S,?>> unmodifiableVisibleLeafColumns = FXCollections.unmodifiableObservableList(visibleLeafColumns); 440 441 442 // Allows for multiple column sorting based on the order of the TableColumns 443 // in this observableArrayList. Each TableColumn is responsible for whether it is 444 // sorted using ascending or descending order. 445 private ObservableList<TableColumn<S,?>> sortOrder = FXCollections.observableArrayList(); 446 447 // width of VirtualFlow minus the vbar width 448 private double contentWidth; 449 450 // Used to minimise the amount of work performed prior to the table being 451 // completely initialised. In particular it reduces the amount of column 452 // resize operations that occur, which slightly improves startup time. 453 private boolean isInited = false; 454 455 456 457 /*************************************************************************** 458 * * 459 * Callbacks and Events * 460 * * 461 **************************************************************************/ 462 463 private final ListChangeListener<TableColumn<S,?>> columnsObserver = new ListChangeListener<TableColumn<S,?>>() { 464 @Override public void onChanged(Change<? extends TableColumn<S,?>> c) { 465 // We don't maintain a bind for leafColumns, we simply call this update 466 // function behind the scenes in the appropriate places. 467 updateVisibleLeafColumns(); 468 469 // Fix for RT-15194: Need to remove removed columns from the 470 // sortOrder list. 471 List<TableColumn<S,?>> toRemove = new ArrayList<TableColumn<S,?>>(); 472 while (c.next()) { 473 final List<? extends TableColumn<S, ?>> removed = c.getRemoved(); 474 final List<? extends TableColumn<S, ?>> added = c.getAddedSubList(); 475 476 if (c.wasRemoved()) { 477 toRemove.addAll(removed); 478 for (TableColumn<S,?> tc : removed) { 479 tc.setTableView(null); 480 } 481 } 482 483 if (c.wasAdded()) { 484 toRemove.removeAll(added); 485 for (TableColumn<S,?> tc : added) { 486 tc.setTableView(TableView.this); 487 } 488 } 489 490 // set up listeners 491 TableUtil.removeColumnsListener(removed, weakColumnsObserver); 492 TableUtil.addColumnsListener(added, weakColumnsObserver); 493 494 TableUtil.removeTableColumnListener(c.getRemoved(), 495 weakColumnVisibleObserver, 496 weakColumnSortableObserver, 497 weakColumnSortTypeObserver, 498 weakColumnComparatorObserver); 499 TableUtil.addTableColumnListener(c.getAddedSubList(), 500 weakColumnVisibleObserver, 501 weakColumnSortableObserver, 502 weakColumnSortTypeObserver, 503 weakColumnComparatorObserver); 504 } 505 506 sortOrder.removeAll(toRemove); 507 } 508 }; 509 510 private final InvalidationListener columnVisibleObserver = new InvalidationListener() { 511 @Override public void invalidated(Observable valueModel) { 512 updateVisibleLeafColumns(); 513 } 514 }; 515 516 private final InvalidationListener columnSortableObserver = new InvalidationListener() { 517 @Override public void invalidated(Observable valueModel) { 518 TableColumn col = (TableColumn) ((BooleanProperty)valueModel).getBean(); 519 if (! getSortOrder().contains(col)) return; 520 doSort(TableUtil.SortEventType.COLUMN_SORTABLE_CHANGE, col); 521 } 522 }; 523 524 private final InvalidationListener columnSortTypeObserver = new InvalidationListener() { 525 @Override public void invalidated(Observable valueModel) { 526 TableColumn col = (TableColumn) ((ObjectProperty)valueModel).getBean(); 527 if (! getSortOrder().contains(col)) return; 528 doSort(TableUtil.SortEventType.COLUMN_SORT_TYPE_CHANGE, col); 529 } 530 }; 531 532 private final InvalidationListener columnComparatorObserver = new InvalidationListener() { 533 @Override public void invalidated(Observable valueModel) { 534 TableColumn col = (TableColumn) ((SimpleObjectProperty)valueModel).getBean(); 535 if (! getSortOrder().contains(col)) return; 536 doSort(TableUtil.SortEventType.COLUMN_COMPARATOR_CHANGE, col); 537 } 538 }; 539 540 /* proxy pseudo-class state change from selectionModel's cellSelectionEnabledProperty */ 541 private final InvalidationListener cellSelectionModelInvalidationListener = new InvalidationListener() { 542 @Override public void invalidated(Observable o) { 543 final boolean isCellSelection = ((BooleanProperty)o).get(); 544 pseudoClassStateChanged(PSEUDO_CLASS_CELL_SELECTION, isCellSelection); 545 pseudoClassStateChanged(PSEUDO_CLASS_ROW_SELECTION, !isCellSelection); 546 } 547 }; 548 549 550 private final WeakInvalidationListener weakColumnVisibleObserver = 551 new WeakInvalidationListener(columnVisibleObserver); 552 553 private final WeakInvalidationListener weakColumnSortableObserver = 554 new WeakInvalidationListener(columnSortableObserver); 555 556 private final WeakInvalidationListener weakColumnSortTypeObserver = 557 new WeakInvalidationListener(columnSortTypeObserver); 558 559 private final WeakInvalidationListener weakColumnComparatorObserver = 560 new WeakInvalidationListener(columnComparatorObserver); 561 562 private final WeakListChangeListener<TableColumn<S,?>> weakColumnsObserver = 563 new WeakListChangeListener<TableColumn<S,?>>(columnsObserver); 564 565 private final WeakInvalidationListener weakCellSelectionModelInvalidationListener = 566 new WeakInvalidationListener(cellSelectionModelInvalidationListener); 567 568 /*************************************************************************** 569 * * 570 * Properties * 571 * * 572 **************************************************************************/ 573 574 575 // --- Items 576 /** 577 * The underlying data model for the TableView. Note that it has a generic 578 * type that must match the type of the TableView itself. 579 */ 580 public final ObjectProperty<ObservableList<S>> itemsProperty() { return items; } 581 private ObjectProperty<ObservableList<S>> items = 582 new SimpleObjectProperty<ObservableList<S>>(this, "items") { 583 WeakReference<ObservableList<S>> oldItemsRef; 584 585 @Override protected void invalidated() { 586 ObservableList<S> oldItems = oldItemsRef == null ? null : oldItemsRef.get(); 587 588 // FIXME temporary fix for RT-15793. This will need to be 589 // properly fixed when time permits 590 if (getSelectionModel() instanceof TableViewArrayListSelectionModel) { 591 ((TableViewArrayListSelectionModel)getSelectionModel()).updateItemsObserver(oldItems, getItems()); 592 } 593 if (getFocusModel() != null) { 594 ((TableViewFocusModel)getFocusModel()).updateItemsObserver(oldItems, getItems()); 595 } 596 if (getSkin() instanceof TableViewSkin) { 597 TableViewSkin skin = (TableViewSkin) getSkin(); 598 skin.updateTableItems(oldItems, getItems()); 599 } 600 601 oldItemsRef = new WeakReference<ObservableList<S>>(getItems()); 602 } 603 }; 604 public final void setItems(ObservableList<S> value) { itemsProperty().set(value); } 605 public final ObservableList<S> getItems() {return items.get(); } 606 607 608 // --- Table menu button visible 609 private BooleanProperty tableMenuButtonVisible; 610 /** 611 * This controls whether a menu button is available when the user clicks 612 * in a designated space within the TableView, within which is a radio menu 613 * item for each TableColumn in this table. This menu allows for the user to 614 * show and hide all TableColumns easily. 615 */ 616 public final BooleanProperty tableMenuButtonVisibleProperty() { 617 if (tableMenuButtonVisible == null) { 618 tableMenuButtonVisible = new SimpleBooleanProperty(this, "tableMenuButtonVisible"); 619 } 620 return tableMenuButtonVisible; 621 } 622 public final void setTableMenuButtonVisible (boolean value) { 623 tableMenuButtonVisibleProperty().set(value); 624 } 625 public final boolean isTableMenuButtonVisible() { 626 return tableMenuButtonVisible == null ? false : tableMenuButtonVisible.get(); 627 } 628 629 630 // --- Column Resize Policy 631 private ObjectProperty<Callback<ResizeFeatures, Boolean>> columnResizePolicy; 632 public final void setColumnResizePolicy(Callback<ResizeFeatures, Boolean> callback) { 633 columnResizePolicyProperty().set(callback); 634 } 635 public final Callback<ResizeFeatures, Boolean> getColumnResizePolicy() { 636 return columnResizePolicy == null ? UNCONSTRAINED_RESIZE_POLICY : columnResizePolicy.get(); 637 } 638 639 /** 640 * This is the function called when the user completes a column-resize 641 * operation. The two most common policies are available as static functions 642 * in the TableView class: {@link #UNCONSTRAINED_RESIZE_POLICY} and 643 * {@link #CONSTRAINED_RESIZE_POLICY}. 644 */ 645 public final ObjectProperty<Callback<ResizeFeatures, Boolean>> columnResizePolicyProperty() { 646 if (columnResizePolicy == null) { 647 columnResizePolicy = new SimpleObjectProperty<Callback<ResizeFeatures, Boolean>>(this, "columnResizePolicy", UNCONSTRAINED_RESIZE_POLICY) { 648 private Callback<ResizeFeatures, Boolean> oldPolicy; 649 650 @Override protected void invalidated() { 651 if (isInited) { 652 get().call(new ResizeFeatures(TableView.this, null, 0.0)); 653 refresh(); 654 655 if (oldPolicy != null) { 656 PseudoClass state = PseudoClass.getPseudoClass(oldPolicy.toString()); 657 pseudoClassStateChanged(state, false); 658 } 659 if (get() != null) { 660 PseudoClass state = PseudoClass.getPseudoClass(get().toString()); 661 pseudoClassStateChanged(state, true); 662 } 663 oldPolicy = get(); 664 } 665 } 666 }; 667 } 668 return columnResizePolicy; 669 } 670 671 672 // --- Row Factory 673 private ObjectProperty<Callback<TableView<S>, TableRow<S>>> rowFactory; 674 675 /** 676 * A function which produces a TableRow. The system is responsible for 677 * reusing TableRows. Return from this function a TableRow which 678 * might be usable for representing a single row in a TableView. 679 * <p> 680 * Note that a TableRow is <b>not</b> a TableCell. A TableRow is 681 * simply a container for a TableCell, and in most circumstances it is more 682 * likely that you'll want to create custom TableCells, rather than 683 * TableRows. The primary use case for creating custom TableRow 684 * instances would most probably be to introduce some form of column 685 * spanning support. 686 * <p> 687 * You can create custom TableCell instances per column by assigning the 688 * appropriate function to the cellFactory property in the TableColumn class. 689 */ 690 public final ObjectProperty<Callback<TableView<S>, TableRow<S>>> rowFactoryProperty() { 691 if (rowFactory == null) { 692 rowFactory = new SimpleObjectProperty<Callback<TableView<S>, TableRow<S>>>(this, "rowFactory"); 693 } 694 return rowFactory; 695 } 696 public final void setRowFactory(Callback<TableView<S>, TableRow<S>> value) { 697 rowFactoryProperty().set(value); 698 } 699 public final Callback<TableView<S>, TableRow<S>> getRowFactory() { 700 return rowFactory == null ? null : rowFactory.get(); 701 } 702 703 704 // --- Placeholder Node 705 private ObjectProperty<Node> placeholder; 706 /** 707 * This Node is shown to the user when the table has no content to show. 708 * This may be the case because the table model has no data in the first 709 * place, that a filter has been applied to the table model, resulting 710 * in there being nothing to show the user, or that there are no currently 711 * visible columns. 712 */ 713 public final ObjectProperty<Node> placeholderProperty() { 714 if (placeholder == null) { 715 placeholder = new SimpleObjectProperty<Node>(this, "placeholder"); 716 } 717 return placeholder; 718 } 719 public final void setPlaceholder(Node value) { 720 placeholderProperty().set(value); 721 } 722 public final Node getPlaceholder() { 723 return placeholder == null ? null : placeholder.get(); 724 } 725 726 727 // --- Selection Model 728 private ObjectProperty<TableViewSelectionModel<S>> selectionModel 729 = new SimpleObjectProperty<TableViewSelectionModel<S>>(this, "selectionModel") { 730 731 TableViewSelectionModel<S> oldValue = null; 732 733 @Override protected void invalidated() { 734 735 if (oldValue != null) { 736 oldValue.cellSelectionEnabledProperty().removeListener(weakCellSelectionModelInvalidationListener); 737 } 738 739 oldValue = get(); 740 741 if (oldValue != null) { 742 oldValue.cellSelectionEnabledProperty().addListener(weakCellSelectionModelInvalidationListener); 743 // fake an invalidation to ensure updated pseudo-class state 744 weakCellSelectionModelInvalidationListener.invalidated(oldValue.cellSelectionEnabledProperty()); 745 } 746 } 747 }; 748 749 /** 750 * The SelectionModel provides the API through which it is possible 751 * to select single or multiple items within a TableView, as well as inspect 752 * which items have been selected by the user. Note that it has a generic 753 * type that must match the type of the TableView itself. 754 */ 755 public final ObjectProperty<TableViewSelectionModel<S>> selectionModelProperty() { 756 return selectionModel; 757 } 758 public final void setSelectionModel(TableViewSelectionModel<S> value) { 759 selectionModelProperty().set(value); 760 } 761 762 public final TableViewSelectionModel<S> getSelectionModel() { 763 return selectionModel.get(); 764 } 765 766 767 // --- Focus Model 768 private ObjectProperty<TableViewFocusModel<S>> focusModel; 769 public final void setFocusModel(TableViewFocusModel<S> value) { 770 focusModelProperty().set(value); 771 } 772 public final TableViewFocusModel<S> getFocusModel() { 773 return focusModel == null ? null : focusModel.get(); 774 } 775 /** 776 * Represents the currently-installed {@link TableViewFocusModel} for this 777 * TableView. Under almost all circumstances leaving this as the default 778 * focus model will suffice. 779 */ 780 public final ObjectProperty<TableViewFocusModel<S>> focusModelProperty() { 781 if (focusModel == null) { 782 focusModel = new SimpleObjectProperty<TableViewFocusModel<S>>(this, "focusModel"); 783 } 784 return focusModel; 785 } 786 787 788// // --- Span Model 789// private ObjectProperty<SpanModel<S>> spanModel 790// = new SimpleObjectProperty<SpanModel<S>>(this, "spanModel") { 791// 792// @Override protected void invalidated() { 793// ObservableList<String> styleClass = getStyleClass(); 794// if (getSpanModel() == null) { 795// styleClass.remove(CELL_SPAN_TABLE_VIEW_STYLE_CLASS); 796// } else if (! styleClass.contains(CELL_SPAN_TABLE_VIEW_STYLE_CLASS)) { 797// styleClass.add(CELL_SPAN_TABLE_VIEW_STYLE_CLASS); 798// } 799// } 800// }; 801// 802// public final ObjectProperty<SpanModel<S>> spanModelProperty() { 803// return spanModel; 804// } 805// public final void setSpanModel(SpanModel<S> value) { 806// spanModelProperty().set(value); 807// } 808// 809// public final SpanModel<S> getSpanModel() { 810// return spanModel.get(); 811// } 812 813 // --- Editable 814 private BooleanProperty editable; 815 public final void setEditable(boolean value) { 816 editableProperty().set(value); 817 } 818 public final boolean isEditable() { 819 return editable == null ? false : editable.get(); 820 } 821 /** 822 * Specifies whether this TableView is editable - only if the TableView, the 823 * TableColumn (if applicable) and the TableCells within it are both 824 * editable will a TableCell be able to go into their editing state. 825 */ 826 public final BooleanProperty editableProperty() { 827 if (editable == null) { 828 editable = new SimpleBooleanProperty(this, "editable", false); 829 } 830 return editable; 831 } 832 833 834 // --- Fixed cell size 835 private DoubleProperty fixedCellSize; 836 837 /** 838 * Sets the new fixed cell size for this control. Any value greater than 839 * zero will enable fixed cell size mode, whereas a zero or negative value 840 * (or Region.USE_COMPUTED_SIZE) will be used to disabled fixed cell size 841 * mode. 842 * 843 * @param value The new fixed cell size value, or -1 (or Region.USE_COMPUTED_SIZE) 844 * to disable. 845 */ 846 public final void setFixedCellSize(double value) { 847 fixedCellSizeProperty().set(value); 848 } 849 850 /** 851 * Returns the fixed cell size value, which may be -1 to represent fixed cell 852 * size mode is disabled, or a value greater than zero to represent the size 853 * of all cells in this control. 854 * 855 * @return A double representing the fixed cell size of this control, or -1 856 * if fixed cell size mode is disabled. 857 */ 858 public final double getFixedCellSize() { 859 return fixedCellSize == null ? Region.USE_COMPUTED_SIZE : fixedCellSize.get(); 860 } 861 /** 862 * Specifies whether this control has cells that are a fixed height (of the 863 * specified value). If this value is -1 (i.e. {@link Region#USE_COMPUTED_SIZE}), 864 * then all cells are individually sized and positioned. This is a slow 865 * operation. Therefore, when performance matters and developers are not 866 * dependent on variable cell sizes it is a good idea to set the fixed cell 867 * size value. Generally cells are around 24px, so setting a fixed cell size 868 * of 24 is likely to result in very little difference in visuals, but a 869 * improvement to performance. 870 * 871 * <p>To set this property via CSS, use the -fx-fixed-cell-size property. 872 * This should not be confused with the -fx-cell-size property. The difference 873 * between these two CSS properties is that -fx-cell-size will size all 874 * cells to the specified size, but it will not enforce that this is the 875 * only size (thus allowing for variable cell sizes, and preventing the 876 * performance gains from being possible). Therefore, when performance matters 877 * use -fx-fixed-cell-size, instead of -fx-cell-size. If both properties are 878 * specified in CSS, -fx-fixed-cell-size takes precedence.</p> 879 */ 880 public final DoubleProperty fixedCellSizeProperty() { 881 if (fixedCellSize == null) { 882 fixedCellSize = new StyleableDoubleProperty(Region.USE_COMPUTED_SIZE) { 883 @Override public CssMetaData<TableView<?>,Number> getCssMetaData() { 884 return StyleableProperties.FIXED_CELL_SIZE; 885 } 886 887 @Override public Object getBean() { 888 return TableView.this; 889 } 890 891 @Override public String getName() { 892 return "fixedCellSize"; 893 } 894 }; 895 } 896 return fixedCellSize; 897 } 898 899 900 // --- Editing Cell 901 private ReadOnlyObjectWrapper<TablePosition<S,?>> editingCell; 902 private void setEditingCell(TablePosition<S,?> value) { 903 editingCellPropertyImpl().set(value); 904 } 905 public final TablePosition<S,?> getEditingCell() { 906 return editingCell == null ? null : editingCell.get(); 907 } 908 909 /** 910 * Represents the current cell being edited, or null if 911 * there is no cell being edited. 912 */ 913 public final ReadOnlyObjectProperty<TablePosition<S,?>> editingCellProperty() { 914 return editingCellPropertyImpl().getReadOnlyProperty(); 915 } 916 917 private ReadOnlyObjectWrapper<TablePosition<S,?>> editingCellPropertyImpl() { 918 if (editingCell == null) { 919 editingCell = new ReadOnlyObjectWrapper<TablePosition<S,?>>(this, "editingCell"); 920 } 921 return editingCell; 922 } 923 924 925 // --- Comparator (built via sortOrder list, so read-only) 926 /** 927 * The comparator property is a read-only property that is representative of the 928 * current state of the {@link #getSortOrder() sort order} list. The sort 929 * order list contains the columns that have been added to it either programmatically 930 * or via a user clicking on the headers themselves. 931 */ 932 private ReadOnlyObjectWrapper<Comparator<S>> comparator; 933 private void setComparator(Comparator<S> value) { 934 comparatorPropertyImpl().set(value); 935 } 936 public final Comparator<S> getComparator() { 937 return comparator == null ? null : comparator.get(); 938 } 939 public final ReadOnlyObjectProperty<Comparator<S>> comparatorProperty() { 940 return comparatorPropertyImpl().getReadOnlyProperty(); 941 } 942 private ReadOnlyObjectWrapper<Comparator<S>> comparatorPropertyImpl() { 943 if (comparator == null) { 944 comparator = new ReadOnlyObjectWrapper<Comparator<S>>(this, "comparator"); 945 } 946 return comparator; 947 } 948 949 950 // --- sortPolicy 951 /** 952 * The sort policy specifies how sorting in this TableView should be performed. 953 * For example, a basic sort policy may just call 954 * {@code FXCollections.sort(tableView.getItems())}, whereas a more advanced 955 * sort policy may call to a database to perform the necessary sorting on the 956 * server-side. 957 * 958 * <p>TableView ships with a {@link TableView#DEFAULT_SORT_POLICY default 959 * sort policy} that does precisely as mentioned above: it simply attempts 960 * to sort the items list in-place. 961 * 962 * <p>It is recommended that rather than override the {@link TableView#sort() sort} 963 * method that a different sort policy be provided instead. 964 */ 965 private ObjectProperty<Callback<TableView<S>, Boolean>> sortPolicy; 966 public final void setSortPolicy(Callback<TableView<S>, Boolean> callback) { 967 sortPolicyProperty().set(callback); 968 } 969 @SuppressWarnings("unchecked") 970 public final Callback<TableView<S>, Boolean> getSortPolicy() { 971 return sortPolicy == null ? 972 (Callback<TableView<S>, Boolean>)(Object) DEFAULT_SORT_POLICY : 973 sortPolicy.get(); 974 } 975 @SuppressWarnings("unchecked") 976 public final ObjectProperty<Callback<TableView<S>, Boolean>> sortPolicyProperty() { 977 if (sortPolicy == null) { 978 sortPolicy = new SimpleObjectProperty<Callback<TableView<S>, Boolean>>( 979 this, "sortPolicy", (Callback<TableView<S>, Boolean>)(Object) DEFAULT_SORT_POLICY) { 980 @Override protected void invalidated() { 981 sort(); 982 } 983 }; 984 } 985 return sortPolicy; 986 } 987 988 989 // onSort 990 /** 991 * Called when there's a request to sort the control. 992 */ 993 private ObjectProperty<EventHandler<SortEvent<TableView<S>>>> onSort; 994 995 public void setOnSort(EventHandler<SortEvent<TableView<S>>> value) { 996 onSortProperty().set(value); 997 } 998 999 public EventHandler<SortEvent<TableView<S>>> getOnSort() { 1000 if( onSort != null ) { 1001 return onSort.get(); 1002 } 1003 return null; 1004 } 1005 1006 public ObjectProperty<EventHandler<SortEvent<TableView<S>>>> onSortProperty() { 1007 if( onSort == null ) { 1008 onSort = new ObjectPropertyBase<EventHandler<SortEvent<TableView<S>>>>() { 1009 @Override protected void invalidated() { 1010 EventType<SortEvent<TableView<S>>> eventType = SortEvent.sortEvent(); 1011 EventHandler<SortEvent<TableView<S>>> eventHandler = get(); 1012 setEventHandler(eventType, eventHandler); 1013 } 1014 1015 @Override public Object getBean() { 1016 return TableView.this; 1017 } 1018 1019 @Override public String getName() { 1020 return "onSort"; 1021 } 1022 }; 1023 } 1024 return onSort; 1025 } 1026 1027 1028 /*************************************************************************** 1029 * * 1030 * Public API * 1031 * * 1032 **************************************************************************/ 1033 /** 1034 * The TableColumns that are part of this TableView. As the user reorders 1035 * the TableView columns, this list will be updated to reflect the current 1036 * visual ordering. 1037 * 1038 * <p>Note: to display any data in a TableView, there must be at least one 1039 * TableColumn in this ObservableList.</p> 1040 */ 1041 public final ObservableList<TableColumn<S,?>> getColumns() { 1042 return columns; 1043 } 1044 1045 /** 1046 * The sortOrder list defines the order in which {@link TableColumn} instances 1047 * are sorted. An empty sortOrder list means that no sorting is being applied 1048 * on the TableView. If the sortOrder list has one TableColumn within it, 1049 * the TableView will be sorted using the 1050 * {@link TableColumn#sortTypeProperty() sortType} and 1051 * {@link TableColumn#comparatorProperty() comparator} properties of this 1052 * TableColumn (assuming 1053 * {@link TableColumn#sortableProperty() TableColumn.sortable} is true). 1054 * If the sortOrder list contains multiple TableColumn instances, then 1055 * the TableView is firstly sorted based on the properties of the first 1056 * TableColumn. If two elements are considered equal, then the second 1057 * TableColumn in the list is used to determine ordering. This repeats until 1058 * the results from all TableColumn comparators are considered, if necessary. 1059 * 1060 * @return An ObservableList containing zero or more TableColumn instances. 1061 */ 1062 public final ObservableList<TableColumn<S,?>> getSortOrder() { 1063 return sortOrder; 1064 } 1065 1066 /** 1067 * Scrolls the TableView so that the given index is visible within the viewport. 1068 * @param index The index of an item that should be visible to the user. 1069 */ 1070 public void scrollTo(int index) { 1071 ControlUtils.scrollToIndex(this, index); 1072 } 1073 1074 /** 1075 * Scrolls the TableView so that the given object is visible within the viewport. 1076 * @param object The object that should be visible to the user. 1077 */ 1078 public void scrollTo(S object) { 1079 if( getItems() != null ) { 1080 int idx = getItems().indexOf(object); 1081 if( idx >= 0 ) { 1082 ControlUtils.scrollToIndex(this, idx); 1083 } 1084 } 1085 } 1086 1087 /** 1088 * Called when there's a request to scroll an index into view using {@link #scrollTo(int)} 1089 * or {@link #scrollTo(Object)} 1090 */ 1091 private ObjectProperty<EventHandler<ScrollToEvent<Integer>>> onScrollTo; 1092 1093 public void setOnScrollTo(EventHandler<ScrollToEvent<Integer>> value) { 1094 onScrollToProperty().set(value); 1095 } 1096 1097 public EventHandler<ScrollToEvent<Integer>> getOnScrollTo() { 1098 if( onScrollTo != null ) { 1099 return onScrollTo.get(); 1100 } 1101 return null; 1102 } 1103 1104 public ObjectProperty<EventHandler<ScrollToEvent<Integer>>> onScrollToProperty() { 1105 if( onScrollTo == null ) { 1106 onScrollTo = new ObjectPropertyBase<EventHandler<ScrollToEvent<Integer>>>() { 1107 @Override 1108 protected void invalidated() { 1109 setEventHandler(ScrollToEvent.scrollToTopIndex(), get()); 1110 } 1111 @Override 1112 public Object getBean() { 1113 return TableView.this; 1114 } 1115 1116 @Override 1117 public String getName() { 1118 return "onScrollTo"; 1119 } 1120 }; 1121 } 1122 return onScrollTo; 1123 } 1124 1125 /** 1126 * Scrolls the TableView so that the given column is visible within the viewport. 1127 * @param column The column that should be visible to the user. 1128 */ 1129 public void scrollToColumn(TableColumn<S, ?> column) { 1130 ControlUtils.scrollToColumn(this, column); 1131 } 1132 1133 /** 1134 * Scrolls the TableView so that the given index is visible within the viewport. 1135 * @param columnIndex The index of a column that should be visible to the user. 1136 */ 1137 public void scrollToColumnIndex(int columnIndex) { 1138 if( getColumns() != null ) { 1139 ControlUtils.scrollToColumn(this, getColumns().get(columnIndex)); 1140 } 1141 } 1142 1143 /** 1144 * Called when there's a request to scroll a column into view using {@link #scrollToColumn(TableColumn)} 1145 * or {@link #scrollToColumnIndex(int)} 1146 */ 1147 private ObjectProperty<EventHandler<ScrollToEvent<TableColumn<S, ?>>>> onScrollToColumn; 1148 1149 public void setOnScrollToColumn(EventHandler<ScrollToEvent<TableColumn<S, ?>>> value) { 1150 onScrollToColumnProperty().set(value); 1151 } 1152 1153 public EventHandler<ScrollToEvent<TableColumn<S, ?>>> getOnScrollToColumn() { 1154 if( onScrollToColumn != null ) { 1155 return onScrollToColumn.get(); 1156 } 1157 return null; 1158 } 1159 1160 public ObjectProperty<EventHandler<ScrollToEvent<TableColumn<S, ?>>>> onScrollToColumnProperty() { 1161 if( onScrollToColumn == null ) { 1162 onScrollToColumn = new ObjectPropertyBase<EventHandler<ScrollToEvent<TableColumn<S, ?>>>>() { 1163 @Override protected void invalidated() { 1164 EventType<ScrollToEvent<TableColumn<S, ?>>> type = ScrollToEvent.scrollToColumn(); 1165 setEventHandler(type, get()); 1166 } 1167 1168 @Override public Object getBean() { 1169 return TableView.this; 1170 } 1171 1172 @Override public String getName() { 1173 return "onScrollToColumn"; 1174 } 1175 }; 1176 } 1177 return onScrollToColumn; 1178 } 1179 1180 /** 1181 * Applies the currently installed resize policy against the given column, 1182 * resizing it based on the delta value provided. 1183 */ 1184 public boolean resizeColumn(TableColumn<S,?> column, double delta) { 1185 if (column == null || Double.compare(delta, 0.0) == 0) return false; 1186 1187 boolean allowed = getColumnResizePolicy().call(new ResizeFeatures<S>(TableView.this, column, delta)); 1188 if (!allowed) return false; 1189 1190 // This fixes the issue where if the column width is reduced and the 1191 // table width is also reduced, horizontal scrollbars will begin to 1192 // appear at the old width. This forces the VirtualFlow.maxPrefBreadth 1193 // value to be reset to -1 and subsequently recalculated. Of course 1194 // ideally we'd just refreshView, but for the time-being no such function 1195 // exists. 1196 refresh(); 1197 return true; 1198 } 1199 1200 /** 1201 * Causes the cell at the given row/column view indexes to switch into 1202 * its editing state, if it is not already in it, and assuming that the 1203 * TableView and column are also editable. 1204 */ 1205 public void edit(int row, TableColumn<S,?> column) { 1206 if (!isEditable() || (column != null && ! column.isEditable())) return; 1207 setEditingCell(new TablePosition(this, row, column)); 1208 } 1209 1210 /** 1211 * Returns an unmodifiable list containing the currently visible leaf columns. 1212 */ 1213 @ReturnsUnmodifiableCollection 1214 public ObservableList<TableColumn<S,?>> getVisibleLeafColumns() { 1215 return unmodifiableVisibleLeafColumns; 1216 } 1217 1218 /** 1219 * Returns the position of the given column, relative to all other 1220 * visible leaf columns. 1221 */ 1222 public int getVisibleLeafIndex(TableColumn<S,?> column) { 1223 return visibleLeafColumns.indexOf(column); 1224 } 1225 1226 /** 1227 * Returns the TableColumn in the given column index, relative to all other 1228 * visible leaf columns. 1229 */ 1230 public TableColumn<S,?> getVisibleLeafColumn(int column) { 1231 if (column < 0 || column >= visibleLeafColumns.size()) return null; 1232 return visibleLeafColumns.get(column); 1233 } 1234 1235 /** {@inheritDoc} */ 1236 @Override protected Skin<?> createDefaultSkin() { 1237 return new TableViewSkin(this); 1238 } 1239 1240 /** 1241 * The sort method forces the TableView to re-run its sorting algorithm. More 1242 * often than not it is not necessary to call this method directly, as it is 1243 * automatically called when the {@link #getSortOrder() sort order}, 1244 * {@link #sortPolicyProperty() sort policy}, or the state of the 1245 * TableColumn {@link TableColumn#sortTypeProperty() sort type} properties 1246 * change. In other words, this method should only be called directly when 1247 * something external changes and a sort is required. 1248 */ 1249 public void sort() { 1250 final ObservableList<? extends TableColumnBase> sortOrder = getSortOrder(); 1251 1252 // update the Comparator property 1253 final Comparator<S> oldComparator = getComparator(); 1254 Comparator<S> newComparator = new TableColumnComparator(sortOrder); 1255 setComparator(newComparator); 1256 1257// if (sortOrder.isEmpty()) { 1258// // TODO this should eventually handle returning a SortedList back 1259// // to its unsorted state 1260// setComparator(null); 1261// } 1262 1263 // fire the onSort event and check if it is consumed, if 1264 // so, don't run the sort 1265 SortEvent<TableView<S>> sortEvent = new SortEvent<TableView<S>>(TableView.this, TableView.this); 1266 fireEvent(sortEvent); 1267 if (sortEvent.isConsumed()) { 1268 // if the sort is consumed we could back out the last action (the code 1269 // is commented out right below), but we don't as we take it as a 1270 // sign that the developer has decided to handle the event themselves. 1271 1272 // sortLock = true; 1273 // TableUtil.handleSortFailure(sortOrder, lastSortEventType, lastSortEventSupportInfo); 1274 // sortLock = false; 1275 return; 1276 } 1277 1278 // get the sort policy and run it 1279 Callback<TableView<S>, Boolean> sortPolicy = getSortPolicy(); 1280 if (sortPolicy == null) return; 1281 Boolean success = sortPolicy.call(this); 1282 1283 if (success == null || ! success) { 1284 // the sort was a failure. Need to backout if possible 1285 sortLock = true; 1286 TableUtil.handleSortFailure(sortOrder, lastSortEventType, lastSortEventSupportInfo); 1287 setComparator(oldComparator); 1288 sortLock = false; 1289 } 1290 } 1291 1292 1293 1294 /*************************************************************************** 1295 * * 1296 * Private Implementation * 1297 * * 1298 **************************************************************************/ 1299 1300 private boolean sortLock = false; 1301 private TableUtil.SortEventType lastSortEventType = null; 1302 private Object[] lastSortEventSupportInfo = null; 1303 1304 private void doSort(final TableUtil.SortEventType sortEventType, final Object... supportInfo) { 1305 if (sortLock) { 1306 return; 1307 } 1308 1309 this.lastSortEventType = sortEventType; 1310 this.lastSortEventSupportInfo = supportInfo; 1311 sort(); 1312 this.lastSortEventType = null; 1313 this.lastSortEventSupportInfo = null; 1314 } 1315 1316 /** 1317 * Call this function to force the TableView to re-evaluate itself. This is 1318 * useful when the underlying data model is provided by a TableModel, and 1319 * you know that the data model has changed. This will force the TableView 1320 * to go back to the dataProvider and get the row count, as well as update 1321 * the view to ensure all sorting is still correct based on any changes to 1322 * the data model. 1323 */ 1324 private void refresh() { 1325 getProperties().put(TableViewSkinBase.REFRESH, Boolean.TRUE); 1326 } 1327 1328 1329 // --- Content width 1330 private void setContentWidth(double contentWidth) { 1331 this.contentWidth = contentWidth; 1332 if (isInited) { 1333 // sometimes the current column resize policy will have to modify the 1334 // column width of all columns in the table if the table width changes, 1335 // so we short-circuit the resize function and just go straight there 1336 // with a null TableColumn, which indicates to the resize policy function 1337 // that it shouldn't actually do anything specific to one column. 1338 getColumnResizePolicy().call(new ResizeFeatures<S>(TableView.this, null, 0.0)); 1339 refresh(); 1340 } 1341 } 1342 1343 /** 1344 * Recomputes the currently visible leaf columns in this TableView. 1345 */ 1346 private void updateVisibleLeafColumns() { 1347 // update visible leaf columns list 1348 List<TableColumn<S,?>> cols = new ArrayList<TableColumn<S,?>>(); 1349 buildVisibleLeafColumns(getColumns(), cols); 1350 visibleLeafColumns.setAll(cols); 1351 1352 // sometimes the current column resize policy will have to modify the 1353 // column width of all columns in the table if the table width changes, 1354 // so we short-circuit the resize function and just go straight there 1355 // with a null TableColumn, which indicates to the resize policy function 1356 // that it shouldn't actually do anything specific to one column. 1357 getColumnResizePolicy().call(new ResizeFeatures<S>(TableView.this, null, 0.0)); 1358 refresh(); 1359 } 1360 1361 private void buildVisibleLeafColumns(List<TableColumn<S,?>> cols, List<TableColumn<S,?>> vlc) { 1362 for (TableColumn<S,?> c : cols) { 1363 if (c == null) continue; 1364 1365 boolean hasChildren = ! c.getColumns().isEmpty(); 1366 1367 if (hasChildren) { 1368 buildVisibleLeafColumns(c.getColumns(), vlc); 1369 } else if (c.isVisible()) { 1370 vlc.add(c); 1371 } 1372 } 1373 } 1374 1375 1376 1377 /*************************************************************************** 1378 * * 1379 * Stylesheet Handling * 1380 * * 1381 **************************************************************************/ 1382 1383 private static final String DEFAULT_STYLE_CLASS = "table-view"; 1384 1385 private static final PseudoClass PSEUDO_CLASS_CELL_SELECTION = 1386 PseudoClass.getPseudoClass("cell-selection"); 1387 private static final PseudoClass PSEUDO_CLASS_ROW_SELECTION = 1388 PseudoClass.getPseudoClass("row-selection"); 1389 1390 /** @treatAsPrivate */ 1391 private static class StyleableProperties { 1392 private static final CssMetaData<TableView<?>,Number> FIXED_CELL_SIZE = 1393 new CssMetaData<TableView<?>,Number>("-fx-fixed-cell-size", 1394 SizeConverter.getInstance(), 1395 Region.USE_COMPUTED_SIZE) { 1396 1397 @Override public Double getInitialValue(TableView node) { 1398 return node.getFixedCellSize(); 1399 } 1400 1401 @Override public boolean isSettable(TableView n) { 1402 return n.fixedCellSize == null || !n.fixedCellSize.isBound(); 1403 } 1404 1405 @Override public StyleableProperty<Number> getStyleableProperty(TableView n) { 1406 return (StyleableProperty<Number>) n.fixedCellSizeProperty(); 1407 } 1408 }; 1409 1410 private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; 1411 static { 1412 final List<CssMetaData<? extends Styleable, ?>> styleables = 1413 new ArrayList<CssMetaData<? extends Styleable, ?>>(Control.getClassCssMetaData()); 1414 styleables.add(FIXED_CELL_SIZE); 1415 STYLEABLES = Collections.unmodifiableList(styleables); 1416 } 1417 } 1418 1419 /** 1420 * @return The CssMetaData associated with this class, which may include the 1421 * CssMetaData of its super classes. 1422 */ 1423 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { 1424 return StyleableProperties.STYLEABLES; 1425 } 1426 1427 /** 1428 * {@inheritDoc} 1429 */ 1430 @Override 1431 public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() { 1432 return getClassCssMetaData(); 1433 } 1434 1435 1436 1437 /*************************************************************************** 1438 * * 1439 * Support Interfaces * 1440 * * 1441 **************************************************************************/ 1442 1443 /** 1444 * An immutable wrapper class for use in the TableView 1445 * {@link TableView#columnResizePolicyProperty() column resize} functionality. 1446 */ 1447 public static class ResizeFeatures<S> extends ResizeFeaturesBase<S> { 1448 private TableView<S> table; 1449 1450 /** 1451 * Creates an instance of this class, with the provided TableView, 1452 * TableColumn and delta values being set and stored in this immutable 1453 * instance. 1454 * 1455 * @param table The TableView upon which the resize operation is occurring. 1456 * @param column The column upon which the resize is occurring, or null 1457 * if this ResizeFeatures instance is being created as a result of a 1458 * TableView resize operation. 1459 * @param delta The amount of horizontal space added or removed in the 1460 * resize operation. 1461 */ 1462 public ResizeFeatures(TableView<S> table, TableColumn<S,?> column, Double delta) { 1463 super(column, delta); 1464 this.table = table; 1465 } 1466 1467 /** 1468 * Returns the column upon which the resize is occurring, or null 1469 * if this ResizeFeatures instance was created as a result of a 1470 * TableView resize operation. 1471 */ 1472 @Override public TableColumn<S,?> getColumn() { 1473 return (TableColumn) super.getColumn(); 1474 } 1475 1476 /** 1477 * Returns the TableView upon which the resize operation is occurring. 1478 */ 1479 public TableView<S> getTable() { 1480 return table; 1481 } 1482 } 1483 1484 1485 1486 /*************************************************************************** 1487 * * 1488 * Support Classes * 1489 * * 1490 **************************************************************************/ 1491 1492 1493 /** 1494 * A simple extension of the {@link SelectionModel} abstract class to 1495 * allow for special support for TableView controls. 1496 */ 1497 public static abstract class TableViewSelectionModel<S> extends TableSelectionModel<S, TableColumn<S,?>> { 1498 1499 /*********************************************************************** 1500 * * 1501 * Private fields * 1502 * * 1503 **********************************************************************/ 1504 1505 private final TableView<S> tableView; 1506 1507 1508 1509 /*********************************************************************** 1510 * * 1511 * Constructors * 1512 * * 1513 **********************************************************************/ 1514 1515 /** 1516 * Builds a default TableViewSelectionModel instance with the provided 1517 * TableView. 1518 * @param tableView The TableView upon which this selection model should 1519 * operate. 1520 * @throws NullPointerException TableView can not be null. 1521 */ 1522 public TableViewSelectionModel(final TableView<S> tableView) { 1523 if (tableView == null) { 1524 throw new NullPointerException("TableView can not be null"); 1525 } 1526 1527 this.tableView = tableView; 1528 } 1529 1530 1531 1532 /*********************************************************************** 1533 * * 1534 * Abstract API * 1535 * * 1536 **********************************************************************/ 1537 1538 /** 1539 * A read-only ObservableList representing the currently selected cells 1540 * in this TableView. Rather than directly modify this list, please 1541 * use the other methods provided in the TableViewSelectionModel. 1542 */ 1543 public abstract ObservableList<TablePosition> getSelectedCells(); 1544 1545 1546 1547 /*********************************************************************** 1548 * * 1549 * Public API * 1550 * * 1551 **********************************************************************/ 1552 1553 /** 1554 * Returns the TableView instance that this selection model is installed in. 1555 */ 1556 public TableView<S> getTableView() { 1557 return tableView; 1558 } 1559 1560 /** 1561 * Convenience method that returns getTableView().getItems(). 1562 * @return The items list of the current TableView. 1563 */ 1564 protected ObservableList<S> getTableModel() { 1565 return tableView.getItems(); 1566 } 1567 1568 /** {@inheritDoc} */ 1569 @Override protected S getModelItem(int index) { 1570 if (index < 0 || index > getItemCount()) return null; 1571 return tableView.getItems().get(index); 1572 } 1573 1574 /** {@inheritDoc} */ 1575 @Override protected int getItemCount() { 1576 return getTableModel().size(); 1577 } 1578 1579 /** {@inheritDoc} */ 1580 @Override public void focus(int row) { 1581 focus(row, null); 1582 } 1583 1584 /** {@inheritDoc} */ 1585 @Override public int getFocusedIndex() { 1586 return getFocusedCell().getRow(); 1587 } 1588 1589 1590 1591 /*********************************************************************** 1592 * * 1593 * Private implementation * 1594 * * 1595 **********************************************************************/ 1596 1597 void focus(int row, TableColumn<S,?> column) { 1598 focus(new TablePosition(getTableView(), row, column)); 1599 } 1600 1601 void focus(TablePosition pos) { 1602 if (getTableView().getFocusModel() == null) return; 1603 1604 getTableView().getFocusModel().focus(pos.getRow(), pos.getTableColumn()); 1605 } 1606 1607 TablePosition getFocusedCell() { 1608 if (getTableView().getFocusModel() == null) { 1609 return new TablePosition(getTableView(), -1, null); 1610 } 1611 return getTableView().getFocusModel().getFocusedCell(); 1612 } 1613 } 1614 1615 1616 1617 /** 1618 * A primitive selection model implementation, using a List<Integer> to store all 1619 * selected indices. 1620 */ 1621 // package for testing 1622 static class TableViewArrayListSelectionModel<S> extends TableViewSelectionModel<S> { 1623 1624 private int itemCount = 0; 1625 1626 /*********************************************************************** 1627 * * 1628 * Constructors * 1629 * * 1630 **********************************************************************/ 1631 1632 public TableViewArrayListSelectionModel(final TableView<S> tableView) { 1633 super(tableView); 1634 this.tableView = tableView; 1635// this.selectedIndicesBitSet = new BitSet(); 1636 1637 updateItemCount(); 1638 1639 cellSelectionEnabledProperty().addListener(new InvalidationListener() { 1640 @Override public void invalidated(Observable o) { 1641 isCellSelectionEnabled(); 1642 clearSelection(); 1643 } 1644 }); 1645 1646 final MappingChange.Map<TablePosition<S,?>,S> cellToItemsMap = new MappingChange.Map<TablePosition<S,?>, S>() { 1647 @Override public S map(TablePosition<S,?> f) { 1648 return getModelItem(f.getRow()); 1649 } 1650 }; 1651 1652 final MappingChange.Map<TablePosition<S,?>,Integer> cellToIndicesMap = new MappingChange.Map<TablePosition<S,?>, Integer>() { 1653 @Override public Integer map(TablePosition f) { 1654 return f.getRow(); 1655 } 1656 }; 1657 1658 selectedCells = FXCollections.<TablePosition<S,?>>observableArrayList(); 1659 selectedCells.addListener(new ListChangeListener<TablePosition<S,?>>() { 1660 @Override public void onChanged(final Change<? extends TablePosition<S,?>> c) { 1661 // RT-29313: because selectedIndices and selectedItems represent 1662 // row-based selection, we need to update the 1663 // selectedIndicesBitSet when the selectedCells changes to 1664 // ensure that selectedIndices and selectedItems return only 1665 // the correct values (and only once). The issue identified 1666 // by RT-29313 is that the size and contents of selectedIndices 1667 // and selectedItems can not simply defer to the 1668 // selectedCells as selectedCells may be representing 1669 // multiple cells from one row (e.g. selectedCells of 1670 // [(0,1), (1,1), (1,2), (1,3)] should result in 1671 // selectedIndices of [0,1], not [0,1,1,1]). 1672 // An inefficient solution would rebuild the selectedIndicesBitSet 1673 // every time the change happens, but we can do better than 1674 // that. Inefficient solution: 1675 // 1676 // selectedIndicesBitSet.clear(); 1677 // for (int i = 0; i < selectedCells.size(); i++) { 1678 // final TablePosition<S,?> tp = selectedCells.get(i); 1679 // final int row = tp.getRow(); 1680 // selectedIndicesBitSet.set(row); 1681 // } 1682 // 1683 // A more efficient solution: 1684 final List<Integer> newlySelectedRows = new ArrayList<Integer>(); 1685 final List<Integer> newlyUnselectedRows = new ArrayList<Integer>(); 1686 1687 while (c.next()) { 1688 if (c.wasRemoved()) { 1689 List<? extends TablePosition<S,?>> removed = c.getRemoved(); 1690 for (int i = 0; i < removed.size(); i++) { 1691 final TablePosition<S,?> tp = removed.get(i); 1692 final int row = tp.getRow(); 1693 1694 if (selectedIndices.get(row)) { 1695 selectedIndices.clear(row); 1696 newlySelectedRows.add(row); 1697 } 1698 } 1699 } 1700 if (c.wasAdded()) { 1701 List<? extends TablePosition<S,?>> added = c.getAddedSubList(); 1702 for (int i = 0; i < added.size(); i++) { 1703 final TablePosition<S,?> tp = added.get(i); 1704 final int row = tp.getRow(); 1705 1706 if (! selectedIndices.get(row)) { 1707 selectedIndices.set(row); 1708 newlySelectedRows.add(row); 1709 } 1710 } 1711 } 1712 } 1713 c.reset(); 1714 1715 // when the selectedCells observableArrayList changes, we manually call 1716 // the observers of the selectedItems, selectedIndices and 1717 // selectedCells lists. 1718 1719 // create an on-demand list of the removed objects contained in the 1720 // given rows 1721 selectedItems.callObservers(new MappingChange<TablePosition<S,?>, S>(c, cellToItemsMap, selectedItems)); 1722 c.reset(); 1723 1724 final ReadOnlyUnbackedObservableList<Integer> selectedIndicesSeq = 1725 (ReadOnlyUnbackedObservableList<Integer>)getSelectedIndices(); 1726 1727 if (! newlySelectedRows.isEmpty() && newlyUnselectedRows.isEmpty()) { 1728 // need to come up with ranges based on the actualSelectedRows, and 1729 // then fire the appropriate number of changes. We also need to 1730 // translate from a desired row to select to where that row is 1731 // represented in the selectedIndices list. For example, 1732 // we may have requested to select row 5, and the selectedIndices 1733 // list may therefore have the following: [1,4,5], meaning row 5 1734 // is in position 2 of the selectedIndices list 1735 Change<Integer> change = createRangeChange(selectedIndicesSeq, newlySelectedRows); 1736 selectedIndicesSeq.callObservers(change); 1737 } else { 1738 selectedIndicesSeq.callObservers(new MappingChange<TablePosition<S,?>, Integer>(c, cellToIndicesMap, selectedIndicesSeq)); 1739 c.reset(); 1740 } 1741 1742 selectedCellsSeq.callObservers(new MappingChange<TablePosition<S,?>, TablePosition<S,?>>(c, MappingChange.NOOP_MAP, selectedCellsSeq)); 1743 c.reset(); 1744 } 1745 }); 1746 1747 selectedItems = new ReadOnlyUnbackedObservableList<S>() { 1748 @Override public S get(int i) { 1749 return getModelItem(getSelectedIndices().get(i)); 1750 } 1751 1752 @Override public int size() { 1753 return getSelectedIndices().size(); 1754 } 1755 }; 1756 1757 selectedCellsSeq = new ReadOnlyUnbackedObservableList<TablePosition<S,?>>() { 1758 @Override public TablePosition<S,?> get(int i) { 1759 return selectedCells.get(i); 1760 } 1761 1762 @Override public int size() { 1763 return selectedCells.size(); 1764 } 1765 }; 1766 1767 1768 /* 1769 * The following two listeners are used in conjunction with 1770 * SelectionModel.select(T obj) to allow for a developer to select 1771 * an item that is not actually in the data model. When this occurs, 1772 * we actively try to find an index that matches this object, going 1773 * so far as to actually watch for all changes to the items list, 1774 * rechecking each time. 1775 */ 1776 1777 // watching for changes to the items list 1778 tableView.itemsProperty().addListener(weakItemsPropertyListener); 1779 1780 // watching for changes to the items list content 1781 ObservableList<S> items = getTableModel(); 1782 if (items != null) { 1783 items.addListener(weakItemsContentListener); 1784 } 1785 } 1786 1787 private final TableView<S> tableView; 1788 1789 private ChangeListener<ObservableList<S>> itemsPropertyListener = new ChangeListener<ObservableList<S>>() { 1790 @Override 1791 public void changed(ObservableValue<? extends ObservableList<S>> observable, 1792 ObservableList<S> oldList, ObservableList<S> newList) { 1793 updateItemsObserver(oldList, newList); 1794 } 1795 }; 1796 1797 private WeakChangeListener<ObservableList<S>> weakItemsPropertyListener = 1798 new WeakChangeListener<ObservableList<S>>(itemsPropertyListener); 1799 1800 final ListChangeListener<S> itemsContentListener = new ListChangeListener<S>() { 1801 @Override public void onChanged(Change<? extends S> c) { 1802 updateItemCount(); 1803 1804 List<S> items = getTableModel(); 1805 1806 while (c.next()) { 1807 final S selectedItem = getSelectedItem(); 1808 final int selectedIndex = getSelectedIndex(); 1809 1810 if (items == null || items.isEmpty()) { 1811 clearSelection(); 1812 } else if (getSelectedIndex() == -1 && getSelectedItem() != null) { 1813 int newIndex = items.indexOf(getSelectedItem()); 1814 if (newIndex != -1) { 1815 setSelectedIndex(newIndex); 1816 } 1817 } else if (c.wasRemoved() && 1818 c.getRemovedSize() == 1 && 1819 ! c.wasAdded() && 1820 selectedItem != null && 1821 selectedItem.equals(c.getRemoved().get(0))) { 1822 // Bug fix for RT-28637 1823 if (getSelectedIndex() < getItemCount()) { 1824 S newSelectedItem = getModelItem(selectedIndex); 1825 if (! selectedItem.equals(newSelectedItem)) { 1826 setSelectedItem(newSelectedItem); 1827 } 1828 } 1829 } 1830 } 1831 1832 updateSelection(c); 1833 } 1834 }; 1835 1836 final WeakListChangeListener weakItemsContentListener 1837 = new WeakListChangeListener(itemsContentListener); 1838 1839 private void updateItemsObserver(ObservableList<S> oldList, ObservableList<S> newList) { 1840 // the listview items list has changed, we need to observe 1841 // the new list, and remove any observer we had from the old list 1842 if (oldList != null) { 1843 oldList.removeListener(weakItemsContentListener); 1844 } 1845 if (newList != null) { 1846 newList.addListener(weakItemsContentListener); 1847 } 1848 1849 updateItemCount(); 1850 1851 // when the items list totally changes, we should clear out 1852 // the selection 1853 setSelectedIndex(-1); 1854 } 1855 1856 1857 /*********************************************************************** 1858 * * 1859 * Observable properties (and getters/setters) * 1860 * * 1861 **********************************************************************/ 1862 1863 // the only 'proper' internal observableArrayList, selectedItems and selectedIndices 1864 // are both 'read-only and unbacked'. 1865 private final ObservableList<TablePosition<S,?>> selectedCells; 1866 1867 // used to represent the _row_ backing data for the selectedCells 1868 private final ReadOnlyUnbackedObservableList<S> selectedItems; 1869 @Override public ObservableList<S> getSelectedItems() { 1870 return selectedItems; 1871 } 1872 1873 private final ReadOnlyUnbackedObservableList<TablePosition<S,?>> selectedCellsSeq; 1874 @Override public ObservableList<TablePosition> getSelectedCells() { 1875 return (ObservableList<TablePosition>)(Object)selectedCellsSeq; 1876 } 1877 1878 1879 /*********************************************************************** 1880 * * 1881 * Internal properties * 1882 * * 1883 **********************************************************************/ 1884 1885 private int previousModelSize = 0; 1886 1887 // Listen to changes in the tableview items list, such that when it 1888 // changes we can update the selected indices list to refer to the 1889 // new indices. 1890 private void updateSelection(ListChangeListener.Change<? extends S> c) { 1891 c.reset(); 1892 while (c.next()) { 1893 if (c.wasReplaced()) { 1894 if (c.getList().isEmpty()) { 1895 // the entire items list was emptied - clear selection 1896 clearSelection(); 1897 } else { 1898 int index = getSelectedIndex(); 1899 1900 if (previousModelSize == c.getRemovedSize()) { 1901 // all items were removed from the model 1902 clearSelection(); 1903 } else if (index < getItemCount() && index >= 0) { 1904 // Fix for RT-18969: the list had setAll called on it 1905 // Use of makeAtomic is a fix for RT-20945 1906 makeAtomic = true; 1907 clearSelection(index); 1908 makeAtomic = false; 1909 select(index); 1910 } else { 1911 // Fix for RT-22079 1912 clearSelection(); 1913 } 1914 } 1915 } else if (c.wasAdded() || c.wasRemoved()) { 1916 int position = c.getFrom(); 1917 int shift = c.wasAdded() ? c.getAddedSize() : -c.getRemovedSize(); 1918 1919 if (position < 0) return; 1920 if (shift == 0) return; 1921 1922 List<TablePosition<S,?>> newIndices = new ArrayList<TablePosition<S,?>>(selectedCells.size()); 1923 1924 for (int i = 0; i < selectedCells.size(); i++) { 1925 final TablePosition<S,?> old = selectedCells.get(i); 1926 final int oldRow = old.getRow(); 1927 final int newRow = oldRow < position ? oldRow : oldRow + shift; 1928 1929 // Special case for RT-28637 (See unit test in TableViewTest). 1930 // Essentially the selectedItem was correct, but selectedItems 1931 // was empty. 1932 if (oldRow == 0 && shift == -1) { 1933 newIndices.add(new TablePosition(getTableView(), 0, old.getTableColumn())); 1934 continue; 1935 } 1936 1937 if (newRow < 0) continue; 1938 newIndices.add(new TablePosition(getTableView(), newRow, old.getTableColumn())); 1939 } 1940 1941 quietClearSelection(); 1942 1943 // Fix for RT-22079 1944 for (int i = 0; i < newIndices.size(); i++) { 1945 TablePosition<S,?> tp = newIndices.get(i); 1946 select(tp.getRow(), tp.getTableColumn()); 1947 } 1948 } else if (c.wasPermutated()) { 1949 // General approach: 1950 // -- detected a sort has happened 1951 // -- Create a permutation lookup map (1) 1952 // -- dump all the selected indices into a list (2) 1953 // -- clear the selected items / indexes (3) 1954 // -- create a list containing the new indices (4) 1955 // -- for each previously-selected index (5) 1956 // -- if index is in the permutation lookup map 1957 // -- add the new index to the new indices list 1958 // -- Perform batch selection (6) 1959 1960 // (1) 1961 int length = c.getTo() - c.getFrom(); 1962 HashMap<Integer, Integer> pMap = new HashMap<Integer, Integer> (length); 1963 for (int i = c.getFrom(); i < c.getTo(); i++) { 1964 pMap.put(i, c.getPermutation(i)); 1965 } 1966 1967 // (2) 1968 List<TablePosition<S,?>> selectedIndices = 1969 new ArrayList<TablePosition<S,?>>((ObservableList<TablePosition<S,?>>)(Object)getSelectedCells()); 1970 1971 1972 // (3) 1973 clearSelection(); 1974 1975 // (4) 1976 List<TablePosition<S,?>> newIndices = new ArrayList<TablePosition<S,?>>(getSelectedIndices().size()); 1977 1978 // (5) 1979 for (int i = 0; i < selectedIndices.size(); i++) { 1980 TablePosition<S,?> oldIndex = selectedIndices.get(i); 1981 1982 if (pMap.containsKey(oldIndex.getRow())) { 1983 Integer newIndex = pMap.get(oldIndex.getRow()); 1984 newIndices.add(new TablePosition(oldIndex.getTableView(), newIndex, oldIndex.getTableColumn())); 1985 } 1986 } 1987 1988 // (6) 1989 quietClearSelection(); 1990 selectedCells.setAll(newIndices); 1991 selectedCellsSeq.callObservers(new NonIterableChange.SimpleAddChange<TablePosition<S,?>>(0, newIndices.size(), selectedCellsSeq)); 1992 } 1993 } 1994 1995 previousModelSize = getItemCount(); 1996 } 1997 1998 /*********************************************************************** 1999 * * 2000 * Public selection API * 2001 * * 2002 **********************************************************************/ 2003 2004 @Override public void clearAndSelect(int row) { 2005 clearAndSelect(row, null); 2006 } 2007 2008 @Override public void clearAndSelect(int row, TableColumn<S,?> column) { 2009 quietClearSelection(); 2010 select(row, column); 2011 } 2012 2013 @Override public void select(int row) { 2014 select(row, null); 2015 } 2016 2017 @Override 2018 public void select(int row, TableColumn<S,?> column) { 2019 if (row < 0 || row >= getItemCount()) return; 2020 2021 // if I'm in cell selection mode but the column is null, I don't want 2022 // to select the whole row instead... 2023 if (isCellSelectionEnabled() && column == null) return; 2024// 2025// // If I am not in cell selection mode (so I want to select rows only), 2026// // if a column is given, I return 2027// if (! isCellSelectionEnabled() && column != null) return; 2028 2029 TablePosition pos = new TablePosition(getTableView(), row, column); 2030 2031 if (getSelectionMode() == SelectionMode.SINGLE) { 2032 quietClearSelection(); 2033 } 2034 2035 if (! selectedCells.contains(pos)) { 2036 selectedCells.add(pos); 2037 } 2038 2039 updateSelectedIndex(row); 2040 focus(row, column); 2041 } 2042 2043 @Override public void select(S obj) { 2044 if (obj == null && getSelectionMode() == SelectionMode.SINGLE) { 2045 clearSelection(); 2046 return; 2047 } 2048 2049 // We have no option but to iterate through the model and select the 2050 // first occurrence of the given object. Once we find the first one, we 2051 // don't proceed to select any others. 2052 S rowObj = null; 2053 for (int i = 0; i < getItemCount(); i++) { 2054 rowObj = getModelItem(i); 2055 if (rowObj == null) continue; 2056 2057 if (rowObj.equals(obj)) { 2058 if (isSelected(i)) { 2059 return; 2060 } 2061 2062 if (getSelectionMode() == SelectionMode.SINGLE) { 2063 quietClearSelection(); 2064 } 2065 2066 select(i); 2067 return; 2068 } 2069 } 2070 2071 // if we are here, we did not find the item in the entire data model. 2072 // Even still, we allow for this item to be set to the give object. 2073 // We expect that in concrete subclasses of this class we observe the 2074 // data model such that we check to see if the given item exists in it, 2075 // whilst SelectedIndex == -1 && SelectedItem != null. 2076 setSelectedItem(obj); 2077 } 2078 2079 @Override public void selectIndices(int row, int... rows) { 2080 if (rows == null) { 2081 select(row); 2082 return; 2083 } 2084 2085 /* 2086 * Performance optimisation - if multiple selection is disabled, only 2087 * process the end-most row index. 2088 */ 2089 int rowCount = getItemCount(); 2090 2091 if (getSelectionMode() == SelectionMode.SINGLE) { 2092 quietClearSelection(); 2093 2094 for (int i = rows.length - 1; i >= 0; i--) { 2095 int index = rows[i]; 2096 if (index >= 0 && index < rowCount) { 2097 select(index); 2098 break; 2099 } 2100 } 2101 2102 if (selectedCells.isEmpty()) { 2103 if (row > 0 && row < rowCount) { 2104 select(row); 2105 } 2106 } 2107 } else { 2108 int lastIndex = -1; 2109 Set<TablePosition<S,?>> positions = new LinkedHashSet<TablePosition<S,?>>(); 2110 2111 if (row >= 0 && row < rowCount) { 2112 TablePosition<S,Object> tp = new TablePosition<S,Object>(getTableView(), row, null); 2113 2114 // refer to the multi-line comment below for the justification for the following 2115 // code. 2116 boolean match = false; 2117 for (int j = 0; j < selectedCells.size(); j++) { 2118 TablePosition<S,?> selectedCell = selectedCells.get(j); 2119 if (selectedCell.getRow() == row) { 2120 match = true; 2121 break; 2122 } 2123 } 2124 if (! match) { 2125 positions.add(tp); 2126 lastIndex = row; 2127 } 2128 } 2129 2130 outer: for (int i = 0; i < rows.length; i++) { 2131 int index = rows[i]; 2132 if (index < 0 || index >= rowCount) continue; 2133 lastIndex = index; 2134 2135 // we need to manually check all selected cells to see whether this index is already 2136 // selected. This is because selectIndices is inherently row-based, but there may 2137 // be a selected cell where the column is non-null. If we were to simply do a 2138 // selectedCells.contains(pos), then we would not find the match and duplicate the 2139 // row selection. This leads to bugs such as RT-29930. 2140 for (int j = 0; j < selectedCells.size(); j++) { 2141 TablePosition<S,?> selectedCell = selectedCells.get(j); 2142 if (selectedCell.getRow() == index) continue outer; 2143 } 2144 2145 // if we are here then we have successfully gotten through the for-loop above 2146 TablePosition<S,Object> pos = new TablePosition<S,Object>(getTableView(), index, null); 2147 positions.add(pos); 2148 } 2149 2150 selectedCells.addAll(positions); 2151 2152 if (lastIndex != -1) { 2153 select(lastIndex); 2154 } 2155 } 2156 } 2157 2158 @Override public void selectAll() { 2159 if (getSelectionMode() == SelectionMode.SINGLE) return; 2160 2161 quietClearSelection(); 2162 2163 if (isCellSelectionEnabled()) { 2164 List<TablePosition<S,?>> indices = new ArrayList<TablePosition<S,?>>(); 2165 TableColumn column; 2166 TablePosition<S,?> tp = null; 2167 for (int col = 0; col < getTableView().getVisibleLeafColumns().size(); col++) { 2168 column = getTableView().getVisibleLeafColumns().get(col); 2169 for (int row = 0; row < getItemCount(); row++) { 2170 tp = new TablePosition(getTableView(), row, column); 2171 indices.add(tp); 2172 } 2173 } 2174 selectedCells.setAll(indices); 2175 2176 if (tp != null) { 2177 select(tp.getRow(), tp.getTableColumn()); 2178 focus(tp.getRow(), tp.getTableColumn()); 2179 } 2180 } else { 2181 List<TablePosition<S,?>> indices = new ArrayList<TablePosition<S,?>>(); 2182 for (int i = 0; i < getItemCount(); i++) { 2183 indices.add(new TablePosition(getTableView(), i, null)); 2184 } 2185 selectedCells.setAll(indices); 2186 2187 int focusedIndex = getFocusedIndex(); 2188 if (focusedIndex == -1) { 2189 select(getItemCount() - 1); 2190 focus(indices.get(indices.size() - 1)); 2191 } else { 2192 select(focusedIndex); 2193 focus(focusedIndex); 2194 } 2195 } 2196 } 2197 2198 @Override public void clearSelection(int index) { 2199 clearSelection(index, null); 2200 } 2201 2202 @Override 2203 public void clearSelection(int row, TableColumn<S,?> column) { 2204 TablePosition tp = new TablePosition(getTableView(), row, column); 2205 2206 boolean csMode = isCellSelectionEnabled(); 2207 2208 for (TablePosition pos : getSelectedCells()) { 2209 if ((! csMode && pos.getRow() == row) || (csMode && pos.equals(tp))) { 2210 selectedCells.remove(pos); 2211 2212 // give focus to this cell index 2213 focus(row); 2214 2215 return; 2216 } 2217 } 2218 } 2219 2220 @Override public void clearSelection() { 2221 updateSelectedIndex(-1); 2222 focus(-1); 2223 quietClearSelection(); 2224 } 2225 2226 private void quietClearSelection() { 2227 selectedCells.clear(); 2228 } 2229 2230 @Override public boolean isSelected(int index) { 2231 return isSelected(index, null); 2232 } 2233 2234 @Override 2235 public boolean isSelected(int row, TableColumn<S,?> column) { 2236 // When in cell selection mode, we currently do NOT support selecting 2237 // entire rows, so a isSelected(row, null) 2238 // should always return false. 2239 if (isCellSelectionEnabled() && (column == null)) return false; 2240 2241 for (TablePosition tp : getSelectedCells()) { 2242 boolean columnMatch = ! isCellSelectionEnabled() || 2243 (column == null && tp.getTableColumn() == null) || 2244 (column != null && column.equals(tp.getTableColumn())); 2245 2246 if (tp.getRow() == row && columnMatch) { 2247 return true; 2248 } 2249 } 2250 return false; 2251 } 2252 2253 @Override public boolean isEmpty() { 2254 return selectedCells.isEmpty(); 2255 } 2256 2257 @Override public void selectPrevious() { 2258 if (isCellSelectionEnabled()) { 2259 // in cell selection mode, we have to wrap around, going from 2260 // right-to-left, and then wrapping to the end of the previous line 2261 TablePosition<S,?> pos = getFocusedCell(); 2262 if (pos.getColumn() - 1 >= 0) { 2263 // go to previous row 2264 select(pos.getRow(), getTableColumn(pos.getTableColumn(), -1)); 2265 } else if (pos.getRow() < getItemCount() - 1) { 2266 // wrap to end of previous row 2267 select(pos.getRow() - 1, getTableColumn(getTableView().getVisibleLeafColumns().size() - 1)); 2268 } 2269 } else { 2270 int focusIndex = getFocusedIndex(); 2271 if (focusIndex == -1) { 2272 select(getItemCount() - 1); 2273 } else if (focusIndex > 0) { 2274 select(focusIndex - 1); 2275 } 2276 } 2277 } 2278 2279 @Override public void selectNext() { 2280 if (isCellSelectionEnabled()) { 2281 // in cell selection mode, we have to wrap around, going from 2282 // left-to-right, and then wrapping to the start of the next line 2283 TablePosition<S,?> pos = getFocusedCell(); 2284 if (pos.getColumn() + 1 < getTableView().getVisibleLeafColumns().size()) { 2285 // go to next column 2286 select(pos.getRow(), getTableColumn(pos.getTableColumn(), 1)); 2287 } else if (pos.getRow() < getItemCount() - 1) { 2288 // wrap to start of next row 2289 select(pos.getRow() + 1, getTableColumn(0)); 2290 } 2291 } else { 2292 int focusIndex = getFocusedIndex(); 2293 if (focusIndex == -1) { 2294 select(0); 2295 } else if (focusIndex < getItemCount() -1) { 2296 select(focusIndex + 1); 2297 } 2298 } 2299 } 2300 2301 @Override public void selectAboveCell() { 2302 TablePosition pos = getFocusedCell(); 2303 if (pos.getRow() == -1) { 2304 select(getItemCount() - 1); 2305 } else if (pos.getRow() > 0) { 2306 select(pos.getRow() - 1, pos.getTableColumn()); 2307 } 2308 } 2309 2310 @Override public void selectBelowCell() { 2311 TablePosition pos = getFocusedCell(); 2312 2313 if (pos.getRow() == -1) { 2314 select(0); 2315 } else if (pos.getRow() < getItemCount() -1) { 2316 select(pos.getRow() + 1, pos.getTableColumn()); 2317 } 2318 } 2319 2320 @Override public void selectFirst() { 2321 TablePosition focusedCell = getFocusedCell(); 2322 2323 if (getSelectionMode() == SelectionMode.SINGLE) { 2324 quietClearSelection(); 2325 } 2326 2327 if (getItemCount() > 0) { 2328 if (isCellSelectionEnabled()) { 2329 select(0, focusedCell.getTableColumn()); 2330 } else { 2331 select(0); 2332 } 2333 } 2334 } 2335 2336 @Override public void selectLast() { 2337 TablePosition focusedCell = getFocusedCell(); 2338 2339 if (getSelectionMode() == SelectionMode.SINGLE) { 2340 quietClearSelection(); 2341 } 2342 2343 int numItems = getItemCount(); 2344 if (numItems > 0 && getSelectedIndex() < numItems - 1) { 2345 if (isCellSelectionEnabled()) { 2346 select(numItems - 1, focusedCell.getTableColumn()); 2347 } else { 2348 select(numItems - 1); 2349 } 2350 } 2351 } 2352 2353 @Override 2354 public void selectLeftCell() { 2355 if (! isCellSelectionEnabled()) return; 2356 2357 TablePosition pos = getFocusedCell(); 2358 if (pos.getColumn() - 1 >= 0) { 2359 select(pos.getRow(), getTableColumn(pos.getTableColumn(), -1)); 2360 } 2361 } 2362 2363 @Override 2364 public void selectRightCell() { 2365 if (! isCellSelectionEnabled()) return; 2366 2367 TablePosition pos = getFocusedCell(); 2368 if (pos.getColumn() + 1 < getTableView().getVisibleLeafColumns().size()) { 2369 select(pos.getRow(), getTableColumn(pos.getTableColumn(), 1)); 2370 } 2371 } 2372 2373 2374 2375 /*********************************************************************** 2376 * * 2377 * Support code * 2378 * * 2379 **********************************************************************/ 2380 2381 private TableColumn<S,?> getTableColumn(int pos) { 2382 return getTableView().getVisibleLeafColumn(pos); 2383 } 2384 2385// private TableColumn<S,?> getTableColumn(TableColumn<S,?> column) { 2386// return getTableColumn(column, 0); 2387// } 2388 2389 // Gets a table column to the left or right of the current one, given an offset 2390 private TableColumn<S,?> getTableColumn(TableColumn<S,?> column, int offset) { 2391 int columnIndex = getTableView().getVisibleLeafIndex(column); 2392 int newColumnIndex = columnIndex + offset; 2393 return getTableView().getVisibleLeafColumn(newColumnIndex); 2394 } 2395 2396 private void updateSelectedIndex(int row) { 2397 setSelectedIndex(row); 2398 setSelectedItem(getModelItem(row)); 2399 } 2400 2401 /** {@inheritDoc} */ 2402 @Override protected int getItemCount() { 2403 return itemCount; 2404 } 2405 2406 private void updateItemCount() { 2407 if (tableView == null) { 2408 itemCount = -1; 2409 } else { 2410 List<S> items = getTableModel(); 2411 itemCount = items == null ? -1 : items.size(); 2412 } 2413 } 2414 } 2415 2416 2417 2418 2419 /** 2420 * A {@link FocusModel} with additional functionality to support the requirements 2421 * of a TableView control. 2422 * 2423 * @see TableView 2424 */ 2425 public static class TableViewFocusModel<S> extends TableFocusModel<S, TableColumn<S, ?>> { 2426 2427 private final TableView<S> tableView; 2428 2429 private final TablePosition EMPTY_CELL; 2430 2431 /** 2432 * Creates a default TableViewFocusModel instance that will be used to 2433 * manage focus of the provided TableView control. 2434 * 2435 * @param tableView The tableView upon which this focus model operates. 2436 * @throws NullPointerException The TableView argument can not be null. 2437 */ 2438 public TableViewFocusModel(final TableView<S> tableView) { 2439 if (tableView == null) { 2440 throw new NullPointerException("TableView can not be null"); 2441 } 2442 2443 this.tableView = tableView; 2444 2445 this.tableView.itemsProperty().addListener(weakItemsPropertyListener); 2446 if (tableView.getItems() != null) { 2447 this.tableView.getItems().addListener(weakItemsContentListener); 2448 } 2449 2450 TablePosition pos = new TablePosition(tableView, -1, null); 2451 setFocusedCell(pos); 2452 EMPTY_CELL = pos; 2453 } 2454 2455 private ChangeListener<ObservableList<S>> itemsPropertyListener = new ChangeListener<ObservableList<S>>() { 2456 @Override 2457 public void changed(ObservableValue<? extends ObservableList<S>> observable, 2458 ObservableList<S> oldList, ObservableList<S> newList) { 2459 updateItemsObserver(oldList, newList); 2460 } 2461 }; 2462 2463 private WeakChangeListener<ObservableList<S>> weakItemsPropertyListener = 2464 new WeakChangeListener<ObservableList<S>>(itemsPropertyListener); 2465 2466 // Listen to changes in the tableview items list, such that when it 2467 // changes we can update the focused index to refer to the new indices. 2468 private final ListChangeListener<S> itemsContentListener = new ListChangeListener<S>() { 2469 @Override public void onChanged(Change<? extends S> c) { 2470 c.next(); 2471 if (c.getFrom() > getFocusedIndex()) return; 2472 c.reset(); 2473 boolean added = false; 2474 boolean removed = false; 2475 int addedSize = 0; 2476 int removedSize = 0; 2477 while (c.next()) { 2478 added |= c.wasAdded(); 2479 removed |= c.wasRemoved(); 2480 addedSize += c.getAddedSize(); 2481 removedSize += c.getRemovedSize(); 2482 } 2483 if (added && ! removed) { 2484 focus(getFocusedIndex() + addedSize); 2485 } else if (!added && removed) { 2486 focus(getFocusedIndex() - removedSize); 2487 } 2488 } 2489 }; 2490 2491 private WeakListChangeListener<S> weakItemsContentListener 2492 = new WeakListChangeListener<S>(itemsContentListener); 2493 2494 private void updateItemsObserver(ObservableList<S> oldList, ObservableList<S> newList) { 2495 // the tableview items list has changed, we need to observe 2496 // the new list, and remove any observer we had from the old list 2497 if (oldList != null) oldList.removeListener(weakItemsContentListener); 2498 if (newList != null) newList.addListener(weakItemsContentListener); 2499 } 2500 2501 /** {@inheritDoc} */ 2502 @Override protected int getItemCount() { 2503 if (tableView.getItems() == null) return -1; 2504 return tableView.getItems().size(); 2505 } 2506 2507 /** {@inheritDoc} */ 2508 @Override protected S getModelItem(int index) { 2509 if (tableView.getItems() == null) return null; 2510 2511 if (index < 0 || index >= getItemCount()) return null; 2512 2513 return tableView.getItems().get(index); 2514 } 2515 2516 /** 2517 * The position of the current item in the TableView which has the focus. 2518 */ 2519 private ReadOnlyObjectWrapper<TablePosition> focusedCell; 2520 public final ReadOnlyObjectProperty<TablePosition> focusedCellProperty() { 2521 return focusedCellPropertyImpl().getReadOnlyProperty(); 2522 } 2523 private void setFocusedCell(TablePosition value) { focusedCellPropertyImpl().set(value); } 2524 public final TablePosition getFocusedCell() { return focusedCell == null ? EMPTY_CELL : focusedCell.get(); } 2525 2526 private ReadOnlyObjectWrapper<TablePosition> focusedCellPropertyImpl() { 2527 if (focusedCell == null) { 2528 focusedCell = new ReadOnlyObjectWrapper<TablePosition>(EMPTY_CELL) { 2529 private TablePosition old; 2530 @Override protected void invalidated() { 2531 if (get() == null) return; 2532 2533 if (old == null || !old.equals(get())) { 2534 setFocusedIndex(get().getRow()); 2535 setFocusedItem(getModelItem(getValue().getRow())); 2536 2537 old = get(); 2538 } 2539 } 2540 2541 @Override 2542 public Object getBean() { 2543 return TableViewFocusModel.this; 2544 } 2545 2546 @Override 2547 public String getName() { 2548 return "focusedCell"; 2549 } 2550 }; 2551 } 2552 return focusedCell; 2553 } 2554 2555 2556 /** 2557 * Causes the item at the given index to receive the focus. 2558 * 2559 * @param row The row index of the item to give focus to. 2560 * @param column The column of the item to give focus to. Can be null. 2561 */ 2562 @Override public void focus(int row, TableColumn<S,?> column) { 2563 if (row < 0 || row >= getItemCount()) { 2564 setFocusedCell(EMPTY_CELL); 2565 } else { 2566 setFocusedCell(new TablePosition(tableView, row, column)); 2567 } 2568 } 2569 2570 /** 2571 * Convenience method for setting focus on a particular row or cell 2572 * using a {@link TablePosition}. 2573 * 2574 * @param pos The table position where focus should be set. 2575 */ 2576 public void focus(TablePosition pos) { 2577 if (pos == null) return; 2578 focus(pos.getRow(), pos.getTableColumn()); 2579 } 2580 2581 2582 /*********************************************************************** 2583 * * 2584 * Public API * 2585 * * 2586 **********************************************************************/ 2587 2588 /** 2589 * Tests whether the row / cell at the given location currently has the 2590 * focus within the TableView. 2591 */ 2592 @Override public boolean isFocused(int row, TableColumn<S,?> column) { 2593 if (row < 0 || row >= getItemCount()) return false; 2594 2595 TablePosition cell = getFocusedCell(); 2596 boolean columnMatch = column == null || column.equals(cell.getTableColumn()); 2597 2598 return cell.getRow() == row && columnMatch; 2599 } 2600 2601 /** 2602 * Causes the item at the given index to receive the focus. This does not 2603 * cause the current selection to change. Updates the focusedItem and 2604 * focusedIndex properties such that <code>focusedIndex = -1</code> unless 2605 * <pre><code>0 <= index < model size</code></pre>. 2606 * 2607 * @param index The index of the item to get focus. 2608 */ 2609 @Override public void focus(int index) { 2610 if (index < 0 || index >= getItemCount()) { 2611 setFocusedCell(EMPTY_CELL); 2612 } else { 2613 setFocusedCell(new TablePosition(tableView, index, null)); 2614 } 2615 } 2616 2617 /** 2618 * Attempts to move focus to the cell above the currently focused cell. 2619 */ 2620 @Override public void focusAboveCell() { 2621 TablePosition cell = getFocusedCell(); 2622 2623 if (getFocusedIndex() == -1) { 2624 focus(getItemCount() - 1, cell.getTableColumn()); 2625 } else if (getFocusedIndex() > 0) { 2626 focus(getFocusedIndex() - 1, cell.getTableColumn()); 2627 } 2628 } 2629 2630 /** 2631 * Attempts to move focus to the cell below the currently focused cell. 2632 */ 2633 @Override public void focusBelowCell() { 2634 TablePosition cell = getFocusedCell(); 2635 if (getFocusedIndex() == -1) { 2636 focus(0, cell.getTableColumn()); 2637 } else if (getFocusedIndex() != getItemCount() -1) { 2638 focus(getFocusedIndex() + 1, cell.getTableColumn()); 2639 } 2640 } 2641 2642 /** 2643 * Attempts to move focus to the cell to the left of the currently focused cell. 2644 */ 2645 @Override public void focusLeftCell() { 2646 TablePosition cell = getFocusedCell(); 2647 if (cell.getColumn() <= 0) return; 2648 focus(cell.getRow(), getTableColumn(cell.getTableColumn(), -1)); 2649 } 2650 2651 /** 2652 * Attempts to move focus to the cell to the right of the the currently focused cell. 2653 */ 2654 @Override public void focusRightCell() { 2655 TablePosition cell = getFocusedCell(); 2656 if (cell.getColumn() == getColumnCount() - 1) return; 2657 focus(cell.getRow(), getTableColumn(cell.getTableColumn(), 1)); 2658 } 2659 2660 2661 2662 /*********************************************************************** 2663 * * 2664 * Private Implementation * 2665 * * 2666 **********************************************************************/ 2667 2668 private int getColumnCount() { 2669 return tableView.getVisibleLeafColumns().size(); 2670 } 2671 2672 // Gets a table column to the left or right of the current one, given an offset 2673 private TableColumn<S,?> getTableColumn(TableColumn<S,?> column, int offset) { 2674 int columnIndex = tableView.getVisibleLeafIndex(column); 2675 int newColumnIndex = columnIndex + offset; 2676 return tableView.getVisibleLeafColumn(newColumnIndex); 2677 } 2678 } 2679 2680}