1 /* 2 * Copyright (c) 2012-2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package org.jemmy.fx.control; 26 27 import javafx.scene.control.skin.VirtualFlow; 28 import java.util.ArrayList; 29 import java.util.List; 30 import javafx.collections.FXCollections; 31 import javafx.collections.ObservableList; 32 import javafx.scene.control.IndexedCell; 33 import javafx.scene.control.ScrollBar; 34 import javafx.scene.control.TableColumnBase; 35 import javafx.scene.control.TreeItem; 36 import javafx.scene.control.TreeTableCell; 37 import javafx.scene.control.TreeTableColumn; 38 import javafx.scene.control.TreeTablePosition; 39 import javafx.scene.control.TreeTableRow; 40 import javafx.scene.control.TreeTableView; 41 import org.jemmy.Point; 42 import org.jemmy.action.GetAction; 43 import org.jemmy.control.As; 44 import org.jemmy.control.ControlInterfaces; 45 import org.jemmy.control.ControlType; 46 import org.jemmy.control.Property; 47 import org.jemmy.dock.Shortcut; 48 import org.jemmy.env.Environment; 49 import org.jemmy.fx.control.Scrollable2DImpl.ScrollsLookupCriteria; 50 import org.jemmy.interfaces.Caret; 51 import org.jemmy.interfaces.EditableCellOwner; 52 import org.jemmy.interfaces.Focusable; 53 import org.jemmy.interfaces.Parent; 54 import org.jemmy.interfaces.Scroll; 55 import org.jemmy.interfaces.Scrollable2D; 56 import org.jemmy.interfaces.Scroller; 57 import org.jemmy.interfaces.Selectable; 58 import org.jemmy.interfaces.Selector; 59 import org.jemmy.interfaces.Table; 60 import org.jemmy.interfaces.Tree; 61 62 /** 63 * TreeTable support in JemmyFX is provided through a few different control 64 * interfaces. Namely, these are 65 * <code>Table</code>, 66 * <code>Tree</code>, 67 * <code>Parent</code> and 68 * <code>Selectable</code>. A tree could be considered a parent/selectable for 69 * two types of objects: 70 * <code>javafx.scene.control.TreeItem</code> in which case TreeItems are 71 * themselves the elements of the hierarchy/list or the underlying data held 72 * within the tree items. 73 * 74 * @see #asTreeTableItemParent() 75 * @see #asTreeTableItemSelectable() 76 * @see #asSelectable(java.lang.Class) 77 * @see #asItemParent(java.lang.Class) 78 * @author Alexander Kirov 79 * @param <CONTROL> 80 */ 81 @ControlType({TreeTableView.class}) 82 @ControlInterfaces(value = {Selectable.class, EditableCellOwner.class, EditableCellOwner.class, Tree.class, Table.class, Scroll.class, Scrollable2D.class}, 83 encapsulates = {TreeItem.class, Object.class, Object.class, Object.class, Object.class}, 84 name = {"asTreeItemSelectable", "asTableItemParent", "asTreeItemParent"}) 85 public class TreeTableViewWrap<CONTROL extends TreeTableView> extends ControlWrap<CONTROL> implements Scroll, Selectable<TreeItem>, Focusable { 86 87 public static final String SELECTED_INDEX_PROP_NAME = "tree.selected.index"; 88 public static final String SELECTED_ITEM_PROP_NAME = "tree.selected.item"; 89 public static final String SHOW_ROOT_PROP_NAME = "show.root"; 90 public static final String ROOT_PROP_NAME = "root.item"; 91 public static final String SELECTED_ITEMS_PROP_NAME = "selected.items"; 92 public final static String SELECTED_CELLS_PROP_NAME = "selected.cells"; 93 public static final String SELECTED_INDICES_PROP_NAME = "selected.indices"; 94 public static final String ITEMS_COUNT_PROP_NAME = "item.count"; 95 public static final String ITEMS_PROP_NAME = "items"; 96 public static final String DATA_COLUMNS_PROP_NAME = "data.columns"; 97 public static final String COLUMNS_PROP_NAME = "columns"; 98 private Tree tree; 99 private Table table; 100 private TreeTableCellParent cellParent; 101 private TreeTableItemParent treeTableItemParent; 102 private TreeTableNodeParent nodeParent; 103 private Selectable selectable; 104 private Selectable<TreeItem> itemSelectable; 105 private TableTreeScroll scroll; 106 private Scrollable2D scrollable2D; 107 108 public TreeTableViewWrap(Environment env, CONTROL nd) { 109 super(env, nd); 110 } 111 112 /** 113 * Jemmy table control interface introduces multiple selections mechanism. 114 * 115 * @param <T> 116 * @param type 117 * @return 118 */ 119 @As(Object.class) 120 public <T> Table<T> asTable(Class<T> type) { 121 if (table == null || !table.getType().equals(type)) { 122 table = asTreeTableCellItemParent(type); 123 } 124 return table; 125 } 126 127 /** 128 * Allows selections of paths. Notice that in the tree implementation, tree 129 * root is not looked up. That is, fist lookup criterion of the criteria 130 * passed for tree selection will be used for finding a child of the root 131 * node whether or not the root node is shown. 132 * 133 * @param <T> 134 * @param type 135 * @return 136 */ 137 @As(Object.class) 138 public <T> Tree<T> asTree(Class<T> type) { 139 if (tree == null || !tree.getType().equals(type)) { 140 asTreeItemParent(TreeItem.class); 141 tree = new TreeTableImpl<T>(type, this, treeTableItemParent); 142 } 143 return tree; 144 } 145 146 /** 147 * You could find items within treetable and operate with them just like with 148 * any other UI elements. 149 * 150 * @param <T> 151 * @param type 152 * @return 153 * @see TableCellItemWrap 154 */ 155 @As(Object.class) 156 public <T> EditableCellOwner<T> asTableItemParent(Class<T> type) { 157 return asTreeTableCellItemParent(type); 158 } 159 160 /** 161 * Allows to perform lookup for data objects - the objects which are 162 * accessible through 163 * <code>javafx.scene.control.TreeItem.getValue()</code>. All objects within 164 * the treeTable are elements of this hierarchy: whether visible or not. Notice 165 * though that the implementation does not expand nodes, so if a tree is 166 * loaded dynamically on node expand, those dynamically added nodes will not 167 * be a part of hierarchy. 168 * 169 * @param <T> type of data supported by the tree. If should be consistent 170 * with the data present in the tree, because the tree data may get casted 171 * to this type parameter during lookup operations. That, this must be a 172 * super type for all types present in the tree. 173 * @param type 174 * @return 175 * @see #asTableItemParent() 176 */ 177 @As(Object.class) 178 public <T> EditableCellOwner<T> asTreeItemParent(Class<T> type) { 179 if (treeTableItemParent == null || !treeTableItemParent.getType().equals(type)) { 180 treeTableItemParent = new TreeTableItemParent<T>(this, getRoot(), type); 181 } 182 return treeTableItemParent; 183 } 184 185 /** 186 * Allows to perform lookup for 187 * <code>javafx.scene.control.TreeItem</code>s. All objects within the tree 188 * are elements of this hierarchy: whether visible or not. Notice though 189 * that the implementation does not expand nodes, so if a tree is loaded 190 * dynamically on node expand, those dynamically added nodes will not be a 191 * part of hierarchy. 192 * 193 * @param <T> 194 * @param type 195 * @return 196 * @see #asTreeItemParent() 197 */ 198 @As(TreeItem.class) 199 public <T extends TreeItem> EditableCellOwner<T> asItemParent(Class<T> type) { 200 if (nodeParent == null) { 201 nodeParent = new TreeTableNodeParent<T>(this, type); 202 } 203 return nodeParent; 204 } 205 206 /** 207 * Allows to work with treeTable as with a list on selectable data objects - the 208 * objects which are accessible through 209 * <code>javafx.scene.control.TreeItem.getValue()</code>. Notice that only 210 * expanded tree items get into the list. A set of items in the hierarchy is 211 * also limited by the type parameter - objects of other types do not make 212 * it to the list. 213 * 214 * @param <T> 215 * @param type 216 * @return 217 * @see #asTreeItemSelectable() 218 */ 219 @As(Object.class) 220 public <T> Selectable<T> asSelectable(Class<T> type) { 221 if (selectable == null || !selectable.getType().equals(type)) { 222 selectable = new TreeTableViewSelectable<T>(type); 223 } 224 return selectable; 225 } 226 227 /** 228 * Allows to work with treeTable as with a list on selectable items. Notice 229 * that only expanded tree items get into the list. 230 * 231 * @return 232 * @see #asSelectable(java.lang.Class) 233 */ 234 @As(TreeItem.class) 235 public Selectable<TreeItem> asTreeItemSelectable() { 236 if (itemSelectable == null) { 237 itemSelectable = new TreeTableViewSelectable(); 238 } 239 return itemSelectable; 240 } 241 242 /** 243 * Returns implementation of Scrollable2D interface. 244 */ 245 @As 246 public Scrollable2D asScrollable2D() { 247 if (scrollable2D == null) { 248 scrollable2D = new Scrollable2DImpl(this, new ScrollsLookupCriteria() { 249 @Override 250 public boolean checkFor(ScrollBar scrollBar) { 251 return ((scrollBar.getParent() instanceof VirtualFlow) 252 && (scrollBar.getParent().getParent() instanceof TreeTableView)); 253 } 254 }); 255 } 256 return scrollable2D; 257 } 258 259 /** 260 * Returns implementation of vertical scroll. 261 */ 262 @As 263 public Scroll asScroll() { 264 checkScroll(); 265 return scroll.asScroll(); 266 } 267 268 @Property(ROOT_PROP_NAME) 269 public TreeItem getRoot() { 270 return new GetAction<TreeItem>() { 271 @Override 272 public void run(Object... os) throws Exception { 273 setResult(getControl().getRoot()); 274 } 275 }.dispatch(getEnvironment()); 276 } 277 278 /** 279 * Returns selected row index. 280 * 281 * @return 282 */ 283 @Property(SELECTED_INDEX_PROP_NAME) 284 public int getSelectedIndex() { 285 return new GetAction<Integer>() { 286 @Override 287 public void run(Object... parameters) { 288 setResult(Integer.valueOf(getControl().getSelectionModel().getSelectedIndex())); 289 } 290 }.dispatch(getEnvironment()); 291 } 292 293 @Property(SELECTED_ITEM_PROP_NAME) 294 public TreeItem getSelectedItem() { 295 return new GetAction<TreeItem>() { 296 @Override 297 public void run(Object... parameters) { 298 setResult((TreeItem) getControl().getSelectionModel().getSelectedItem()); 299 } 300 }.dispatch(getEnvironment()); 301 } 302 303 @Property(SHOW_ROOT_PROP_NAME) 304 public boolean isShowRoot() { 305 return new GetAction<Boolean>() { 306 @Override 307 public void run(Object... parameters) throws Exception { 308 setResult(getControl().isShowRoot()); 309 } 310 }.dispatch(getEnvironment()); 311 } 312 313 @Override 314 public TreeItem getState() { 315 return asTreeItemSelectable().getState(); 316 } 317 318 @Override 319 public List<TreeItem> getStates() { 320 return asTreeItemSelectable().getStates(); 321 } 322 323 @Override 324 public Class<TreeItem> getType() { 325 return TreeItem.class; 326 } 327 328 @Override 329 public Selector<TreeItem> selector() { 330 return asTreeItemSelectable().selector(); 331 } 332 333 @Property(VALUE_PROP_NAME) 334 @Override 335 public double position() { 336 checkScroll(); 337 return scroll.position(); 338 } 339 340 @Property(MINIMUM_PROP_NAME) 341 @Override 342 public double minimum() { 343 checkScroll(); 344 return scroll.minimum(); 345 } 346 347 @Property(MAXIMUM_PROP_NAME) 348 @Override 349 public double maximum() { 350 checkScroll(); 351 return scroll.maximum(); 352 } 353 354 @Override 355 @Deprecated 356 public double value() { 357 checkScroll(); 358 return position(); 359 } 360 361 @Deprecated 362 @Override 363 public Scroller scroller() { 364 checkScroll(); 365 return scroll.scroller(); 366 } 367 368 @Override 369 public Caret caret() { 370 checkScroll(); 371 return scroll.caret(); 372 } 373 374 @Override 375 public void to(double position) { 376 checkScroll(); 377 scroll.to(position); 378 } 379 380 private void checkScroll() { 381 if (scroll == null) { 382 scroll = new TableTreeScroll(this); 383 } 384 } 385 386 @Property(COLUMNS_PROP_NAME) 387 public List<TreeTableColumn> getColumns() { 388 return new GetAction<java.util.List<TreeTableColumn>>() { 389 @Override 390 public void run(Object... parameters) throws Exception { 391 setResult(getControl().getColumns()); 392 } 393 }.dispatch(getEnvironment()); 394 } 395 396 /** 397 * @return List of columns, which are on the last level (leaf columns) of 398 * tree table header. In the case of nested columns, some columns are 399 * parents, and some are children. This method returns the list of columns, 400 * which have no children. Those columns correspond to the realy shown 401 * columns of data in TreeTableView. 402 */ 403 @Property(DATA_COLUMNS_PROP_NAME) 404 public List<TreeTableColumn> getDataColumns() { 405 return new GetAction<java.util.List<TreeTableColumn>>() { 406 @Override 407 public void run(Object... parameters) throws Exception { 408 ArrayList fillList = new ArrayList<TableColumnBase>(); 409 TableViewWrap.getLastLevelColumns((java.util.List<? extends TableColumnBase>) getControl().getColumns(), fillList); 410 setResult((java.util.List) fillList); 411 } 412 }.dispatch(getEnvironment()); 413 } 414 415 /** 416 * Gives a size of a list of objects (rows) displayed in the tree table. 417 * 418 * @return 419 */ 420 @Property(ITEMS_PROP_NAME) 421 public ObservableList getItems() { 422 return new GetAction<ObservableList>() { 423 @Override 424 public void run(Object... parameters) throws Exception { 425 ObservableList listForItems = FXCollections.observableArrayList(); 426 if (getControl().isShowRoot()) { 427 if (getControl().getRoot() != null) { 428 listForItems.add(getControl().getRoot()); 429 } 430 } 431 getItemsRec(getControl().getRoot(), listForItems); 432 setResult(listForItems); 433 } 434 435 private void getItemsRec(TreeItem rootItem, ObservableList listForItems) { 436 if (rootItem.isExpanded()) { 437 for (Object item : rootItem.getChildren()) { 438 listForItems.add(item); 439 getItemsRec((TreeItem) item, listForItems); 440 } 441 } 442 } 443 }.dispatch(getEnvironment()); 444 } 445 446 public TreeTableItemWrap getRootWrap() { 447 TreeItem root = getRoot(); 448 if (root == null) { 449 return null; 450 } else { 451 return new TreeTableItemWrap(Object.class, root, this, null); 452 } 453 } 454 455 /** 456 * Gives a list of objects (rows) displayed in the tree table. 457 * 458 * @return 459 */ 460 @Property(ITEMS_COUNT_PROP_NAME) 461 public int getSize() { 462 return new GetAction<Integer>() { 463 @Override 464 public void run(Object... parameters) throws Exception { 465 setResult(getControl().getExpandedItemCount()); 466 } 467 }.dispatch(getEnvironment()); 468 } 469 470 /** 471 * @return List<Integer>, containing list of selected indices. 472 */ 473 @Property(SELECTED_INDICES_PROP_NAME) 474 public java.util.List<Integer> getSelectedIndices() { 475 return new GetAction<java.util.List<Integer>>() { 476 @Override 477 public void run(Object... parameters) throws Exception { 478 setResult((java.util.List<Integer>) getControl().getSelectionModel().getSelectedIndices()); 479 } 480 }.dispatch(getEnvironment()); 481 } 482 483 @Property(SELECTED_ITEMS_PROP_NAME) 484 public List getSelectedItems() { 485 return new GetAction<List>() { 486 @Override 487 public void run(Object... parameters) throws Exception { 488 setResult(getControl().getSelectionModel().getSelectedItems()); 489 } 490 }.dispatch(getEnvironment()); 491 } 492 493 @Property(SELECTED_CELLS_PROP_NAME) 494 public List<Point> getSelectedCells() { 495 return new GetAction<java.util.List<Point>>() { 496 @Override 497 public void run(Object... parameters) throws Exception { 498 java.util.List<Point> res = new ArrayList<Point>(); 499 for (TreeTablePosition tp : (java.util.List<TreeTablePosition>) getControl().getSelectionModel().getSelectedCells()) { 500 res.add(new Point(tp.getColumn(), tp.getRow())); 501 } 502 setResult(res); 503 } 504 }.dispatch(getEnvironment()); 505 } 506 507 @Shortcut 508 public void scrollTo(int row, int column) { 509 if (scroll == null) { 510 scroll = new TableTreeScroll(this); 511 } 512 scroll.checkScrolls(); 513 TableUtils.<TreeTableCell>scrollTo(getEnvironment(), this, 514 scroll.hScroll, scroll.vScroll, 515 row, column, 516 new TableUtils.TreeTableViewIndexInfoProvider(this), TreeTableCell.class); 517 } 518 519 Object getRow(final int index) { 520 return getItems().get(index); 521 } 522 523 TreeTableColumn getColumn(final int index) { 524 return (TreeTableColumn) getDataColumns().get(index); 525 } 526 527 protected int getRowIndex(IndexedCell tableCell) { 528 return ((TreeTableCell) tableCell).getTreeTableRow().getIndex(); 529 } 530 531 protected int getColumnIndex(IndexedCell tableCell) { 532 return ((TreeTableCell) tableCell).getTreeTableView().getVisibleLeafIndex(((TreeTableCell) tableCell).getTableColumn()); 533 } 534 535 public int getRow(TreeItem item) { 536 return getItems().indexOf(item); 537 } 538 539 public IndexedCell getTreeCell(TreeItem item) { 540 return (TreeTableRow) new TreeTableNodeWrap<>(item, new TreeTableItemWrap<>(Object.class, item, this, null), this, null).getNode().getControl(); 541 } 542 543 protected class TreeTableViewSelectable<T> extends TreeSelectable<T> { 544 545 TreeTableViewSelectable(Class<T> type) { 546 super(type); 547 } 548 549 TreeTableViewSelectable() { 550 super(); 551 } 552 553 @Override 554 protected TreeItem getRoot() { 555 return TreeTableViewWrap.this.getRoot(); 556 } 557 558 @Override 559 protected boolean isShowRoot() { 560 return TreeTableViewWrap.this.isShowRoot(); 561 } 562 563 @Override 564 protected TreeItem getSelectedItem() { 565 return TreeTableViewWrap.this.getSelectedItem(); 566 } 567 568 @Override 569 protected Parent asTreeItemParent() { 570 return TreeTableViewWrap.this.asItemParent(TreeItem.class); 571 } 572 573 @Override 574 protected Environment getEnvironment() { 575 return TreeTableViewWrap.this.getEnvironment(); 576 } 577 } 578 579 private <T> TreeTableCellParent<T> asTreeTableCellItemParent(Class<T> type) { 580 if (cellParent == null || !cellParent.getType().equals(type)) { 581 cellParent = new TreeTableCellParent<T>(this, type); 582 } 583 return cellParent; 584 } 585 }