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 }