1 /*
   2  * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation. Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package javafx.scene.control.test.tableview;
  26 
  27 import java.util.Collection;
  28 import java.util.HashSet;
  29 import javafx.collections.ObservableList;
  30 import javafx.geometry.Orientation;
  31 import javafx.scene.Node;
  32 import javafx.scene.control.Control;
  33 import javafx.scene.control.IndexedCell;
  34 import javafx.scene.control.ScrollBar;
  35 import javafx.scene.control.TableCell;
  36 import javafx.scene.control.TablePosition;
  37 import javafx.scene.control.TableRow;
  38 import javafx.scene.control.TableSelectionModel;
  39 import javafx.scene.control.TableView;
  40 import javafx.scene.control.TableView.TableViewSelectionModel;
  41 import javafx.scene.control.TreeTableCell;
  42 import javafx.scene.control.TreeTablePosition;
  43 import javafx.scene.control.TreeTableRow;
  44 import javafx.scene.control.TreeTableView;
  45 import javafx.scene.control.TreeTableView.TreeTableViewSelectionModel;
  46 import javafx.scene.control.test.util.MultipleSelectionHelper;
  47 import javafx.scene.control.test.util.MultipleSelectionHelper.Range;
  48 import javafx.scene.control.test.util.UtilTestFunctions;
  49 import javafx.scene.text.Text;
  50 import junit.framework.Assert;
  51 import org.jemmy.Point;
  52 import org.jemmy.Rectangle;
  53 import org.jemmy.action.GetAction;
  54 import org.jemmy.control.Wrap;
  55 import org.jemmy.fx.ByStyleClass;
  56 import org.jemmy.fx.NodeWrap;
  57 import org.jemmy.fx.Root;
  58 import org.jemmy.fx.control.TableViewWrap;
  59 import org.jemmy.fx.control.TreeTableViewWrap;
  60 import org.jemmy.input.AbstractScroll;
  61 import org.jemmy.interfaces.Caret;
  62 import org.jemmy.interfaces.Parent;
  63 import org.jemmy.lookup.Lookup;
  64 import org.jemmy.lookup.LookupCriteria;
  65 import org.jemmy.timing.State;
  66 
  67 /**
  68  * @author Alexander Kirov
  69  */
  70 public class TestBaseCommon extends UtilTestFunctions {
  71 
  72     public static Wrap<? extends TableCell> getCellWrap(Wrap<? extends Control> testedControl, final int column, final int row) {
  73         scrollTo(testedControl, column, row);
  74         Lookup lookup = testedControl.as(Parent.class, Node.class).lookup(IndexedCell.class, new TableViewTest.ByPosition(column, row));
  75         if (lookup.size() == 0) { // TODO: what's that?!!
  76             scrollTo(testedControl, column, row);
  77             lookup.size();
  78         }
  79         return lookup.wrap();
  80     }
  81 
  82     public static void scrollTo(final Wrap<? extends Control> testedControl, final int column, final int row) {
  83         AbstractScroll vScroll = getScroll(testedControl, true);
  84         AbstractScroll hScroll = getScroll(testedControl, false);
  85 
  86         if (vScroll != null) {
  87             vScroll.caret().to(new Caret.Direction() {
  88                 public int to() {
  89                     int[] shown = shown(testedControl);
  90                     if (shown[1] > row) {
  91                             return -1;
  92                         }
  93                     if (shown[3] < row) {
  94                             return 1;
  95                         }
  96                     return 0;
  97                 }
  98             });
  99         }
 100         if (hScroll != null) {
 101             hScroll.caret().to(new Caret.Direction() {
 102                 public int to() {
 103                     int[] shown = shown(testedControl);
 104                     if (shown[0] > column) {
 105                             return -1;
 106                         }
 107                     if (shown[2] < column) {
 108                             return 1;
 109                         }
 110                     return 0;
 111                 }
 112             });
 113         }
 114     }
 115 
 116     private static AbstractScroll getScroll(final Wrap<? extends Control> testedControl, final boolean vertical) {
 117         Lookup<ScrollBar> lookup = testedControl.as(Parent.class, Node.class).lookup(ScrollBar.class,
 118                 new LookupCriteria<ScrollBar>() {
 119                     @Override
 120                     public boolean check(ScrollBar control) {
 121                         return control.isVisible() && (control.getOrientation() == Orientation.VERTICAL) == vertical;
 122                     }
 123                 });
 124         int count = lookup.size();
 125         if (count == 0) {
 126             return null;
 127         } else if (count == 1) {
 128             return lookup.as(AbstractScroll.class);
 129         } else {
 130             return null;
 131         }
 132     }
 133 
 134     public static int[] shown(final Wrap<? extends Control> testedControl) {
 135         final Rectangle viewArea = getContainerWrap(testedControl).getScreenBounds();
 136         final Rectangle clippedArea = getClippedContainerWrap(testedControl).getScreenBounds();
 137 
 138         final Rectangle actuallyVisibleArea = new Rectangle(viewArea.x, viewArea.y, clippedArea.width, clippedArea.height);
 139 
 140         final boolean isTable = testedControl.getControl() instanceof TableView;
 141         int[] res = new GetAction<int[]>() {
 142             @Override
 143             @SuppressWarnings("unchecked")
 144             public void run(Object... parameters) {
 145                 final int[] res = new int[]{Integer.MAX_VALUE, Integer.MAX_VALUE, -1, -1};
 146                 testedControl.as(Parent.class, Node.class).lookup(new LookupCriteria<Node>() {
 147                     @Override
 148                     public boolean check(Node control) {
 149                         if (isTable) {
 150                             if (!TableCell.class.isAssignableFrom(control.getClass())) {
 151                                 return false;
 152                             }
 153                         } else {
 154                             if (!TreeTableCell.class.isAssignableFrom(control.getClass())) {
 155                                 return false;
 156                             }
 157                         }
 158 
 159                         if (control.isVisible() && control.getOpacity() == 1.0) {
 160                             Rectangle bounds = NodeWrap.getScreenBounds(testedControl.getEnvironment(), control);
 161                             int column;
 162                             if (isTable) {
 163                                 column = getColumnIndex((TableCell) control);
 164                             } else {
 165                                 column = getColumnIndex((TreeTableCell) control);
 166                             }
 167                             int row;
 168                             if (isTable) {
 169                                 row = getRowIndex((TableCell) control);
 170                             } else {
 171                                 row = getRowIndex((TreeTableCell) control);
 172                             }
 173 
 174                             //For cases, when we don't see cell fully, we will require only click point area.
 175                             final int xEpsilon = 2;
 176                             final int yEpsilon = 2;
 177                             final Point center = new Point(bounds.getX() + bounds.getWidth() / 2, bounds.getY() + bounds.getHeight() / 2);
 178                             Rectangle neededRec = new Rectangle(center.x - xEpsilon, center.y - yEpsilon, 2 * xEpsilon, 2 * yEpsilon);
 179                             if (actuallyVisibleArea.contains(neededRec) && row >= 0) {
 180                                 res[0] = Math.min(res[0], column);
 181                                 res[1] = Math.min(res[1], row);
 182                                 res[2] = Math.max(res[2], column);
 183                                 res[3] = Math.max(res[3], row);
 184                             }
 185                         }
 186                         return false;
 187                     }
 188                 }).size();
 189 
 190                 setResult(res);
 191             }
 192         }.dispatch(testedControl.getEnvironment());
 193 
 194         return res;
 195     }
 196 
 197     /**
 198      * @return wrap of parent container that contains Cells
 199      */
 200     static Wrap<? extends javafx.scene.Parent> getContainerWrap(Wrap<? extends Control> parent) {
 201         return getParentWrap(parent.as(Parent.class, Node.class), VIRTIAL_FLOW_CLASS_NAME);
 202     }
 203 
 204     static Wrap<? extends javafx.scene.Parent> getClippedContainerWrap(Wrap<? extends Control> parent) {
 205         return getParentWrap(parent.as(Parent.class, Node.class), CLIPPED_CONTAINER_CLASS_NAME);
 206     }
 207 
 208     static private Wrap<? extends javafx.scene.Parent> getParentWrap(Parent<Node> parent, final String className) {
 209         return parent.lookup(javafx.scene.Parent.class, new LookupCriteria<javafx.scene.Parent>() {
 210             @Override
 211             public boolean check(javafx.scene.Parent control) {
 212                 return control.getClass().getName().endsWith(className);
 213             }
 214         }).wrap();
 215     }
 216 
 217     public static Wrap<Text> getCellWrap(Wrap<? extends TableView> testedControl, final String item) {
 218         return testedControl.as(Parent.class, String.class).lookup(
 219                 new LookupCriteria<String>() {
 220                     @Override
 221                     public boolean check(String cell_item) {
 222                         return cell_item.equals(item);
 223                     }
 224                 }).wrap();
 225     }
 226 
 227     public static int getRowIndex(final TableCell tableCell) {
 228         return new GetAction<Integer>() {
 229             @Override
 230             public void run(Object... os) throws Exception {
 231                 setResult(tableCell.getTableRow().getIndex());
 232             }
 233         }.dispatch(Root.ROOT.getEnvironment());
 234     }
 235 
 236     public static int getRowIndex(final TreeTableCell tableCell) {
 237         return new GetAction<Integer>() {
 238             @Override
 239             public void run(Object... os) throws Exception {
 240                 setResult(tableCell.getTreeTableRow().getIndex());
 241             }
 242         }.dispatch(Root.ROOT.getEnvironment());
 243     }
 244 
 245     public static int getColumnIndex(final TableCell tableCell) {
 246         return new GetAction<Integer>() {
 247             @Override
 248             public void run(Object... os) throws Exception {
 249                 setResult(tableCell.getTableView().getVisibleLeafIndex(tableCell.getTableColumn()));
 250             }
 251         }.dispatch(Root.ROOT.getEnvironment());
 252     }
 253 
 254     public static int getColumnIndex(final TreeTableCell tableCell) {
 255         return new GetAction<Integer>() {
 256             @Override
 257             public void run(Object... os) throws Exception {
 258                 setResult(tableCell.getTreeTableView().getVisibleLeafIndex(tableCell.getTableColumn()));
 259             }
 260         }.dispatch(Root.ROOT.getEnvironment());
 261     }
 262 
 263     protected static Range getVisibleRange(final Wrap<? extends Control> testedControl) {
 264         int[] visibleIndices;
 265 
 266         if (testedControl.getControl() instanceof TableView) {
 267             visibleIndices = org.jemmy.fx.control.TableUtils.shown(
 268                     testedControl.getEnvironment(),
 269                     testedControl,
 270                     new org.jemmy.fx.control.TableUtils.TableViewIndexInfoProvider((TableViewWrap) testedControl), TableCell.class);
 271         } else {
 272             visibleIndices = org.jemmy.fx.control.TableUtils.shown(
 273                     testedControl.getEnvironment(),
 274                     testedControl,
 275                     new org.jemmy.fx.control.TableUtils.TreeTableViewIndexInfoProvider((TreeTableViewWrap) testedControl), TreeTableCell.class);
 276         }
 277 
 278         return new Range(visibleIndices[1], visibleIndices[3]);
 279     }
 280 
 281     protected static void checkSelection(final Wrap<? extends Control> testedControl, final MultipleSelectionHelper selectionHelper) {
 282         testedControl.waitState(new State() {
 283             public Object reached() {
 284                 Collection<Point> helperSelected = selectionHelper.getSelected();
 285                 Collection<Point> selected = getSelected(testedControl);
 286                 Point helperFocus = selectionHelper.focus;
 287                 Point focus = getSelectedItem(testedControl);
 288 
 289                 System.out.println("Helper selection : " + helperSelected);
 290                 System.out.println("Selection : " + selected);
 291                 System.out.println("Helper focus : " + helperFocus);
 292                 System.out.println("Focus : " + focus);
 293                 System.out.println("Anchor : " + selectionHelper.anchor + "\n\n");
 294 
 295                 if (helperSelected.size() == selected.size()
 296                         && helperSelected.containsAll(selected)
 297                         && (focus.equals(helperFocus) || selectionHelper.ctrlA)) {
 298                     return true;
 299                 } else {
 300                     return null;
 301                 }
 302             }
 303         });
 304     }
 305 
 306     protected static HashSet<Point> getSelected(final Wrap<? extends Control> testedControl) {
 307         return new GetAction<HashSet<Point>>() {
 308             @Override
 309             public void run(Object... parameters) throws Exception {
 310                 HashSet<Point> selected = new HashSet<Point>();
 311                 if (testedControl.getControl() instanceof TableView) {
 312                     TableViewSelectionModel model = ((TableView) testedControl.getControl()).getSelectionModel();
 313                     for (Object obj : model.getSelectedCells()) {
 314                         TablePosition tablePos = (TablePosition) obj;
 315                         if (model.isCellSelectionEnabled()) {
 316                             selected.add(new Point(tablePos.getColumn(), tablePos.getRow()));
 317                         } else {
 318                             selected.add(new Point(-1, tablePos.getRow()));
 319                         }
 320                     }
 321                     setResult(selected);
 322                 } else {
 323                     TreeTableViewSelectionModel model = ((TreeTableView) testedControl.getControl()).getSelectionModel();
 324                     for (Object obj : model.getSelectedCells()) {
 325                         TreeTablePosition treeTablePos = (TreeTablePosition) obj;
 326                         if (model.isCellSelectionEnabled()) {
 327                             selected.add(new Point(treeTablePos.getColumn(), treeTablePos.getRow()));
 328                         } else {
 329                             selected.add(new Point(-1, treeTablePos.getRow()));
 330                         }
 331                     }
 332                     setResult(selected);
 333                 }
 334             }
 335         }.dispatch(Root.ROOT.getEnvironment());
 336     }
 337 
 338     protected static Point getSelectedItem(final Wrap<? extends Control> testedControl) {
 339         return new GetAction<Point>() {
 340             @Override
 341             public void run(Object... parameters) throws Exception {
 342                 TableSelectionModel model;
 343                 if (testedControl.getControl() instanceof TableView) {
 344                     model = ((TableView) testedControl.getControl()).getSelectionModel();
 345                 } else {
 346                     model = ((TreeTableView) testedControl.getControl()).getSelectionModel();
 347                 }
 348                 if (model.isCellSelectionEnabled()) {
 349                     Lookup lookup = testedControl.as(Parent.class, Node.class).lookup(new LookupCriteria<Node>() {
 350                         public boolean check(Node row) {
 351 //                            if (IndexedCell.class.isAssignableFrom(row.getClass()) ) {
 352 //                                System.out.println("row.getClass() = " + row.getClass());
 353 //                                System.out.println("IndexedCell.class.isAssignableFrom(row.getClass()) = " + IndexedCell.class.isAssignableFrom(row.getClass()));
 354 //                                System.out.println("((IndexedCell) row).getText() = " + ((IndexedCell) row).getText());
 355 //                                System.out.println("((IndexedCell) row).isFocused() = " + ((IndexedCell) row).isFocused());
 356 //                            }
 357                             return IndexedCell.class.isAssignableFrom(row.getClass()) && ((IndexedCell) row).isFocused();
 358                         }
 359                     });
 360                     if (lookup.size() > 0) {
 361                         if (testedControl.getControl() instanceof TableView) {
 362                             TableCell cell = (TableCell) lookup.get();
 363                             setResult(new Point(getColumnIndex(cell), cell.getTableRow().getIndex()));
 364                         } else {
 365                             TreeTableCell cell = (TreeTableCell) lookup.lookup(TreeTableCell.class).get();
 366                             setResult(new Point(getColumnIndex(cell), cell.getTreeTableRow().getIndex()));
 367                         }
 368                         return;
 369                     }
 370                 } else {
 371                     Lookup lookup = testedControl.as(Parent.class, Node.class).lookup(new LookupCriteria<Node>() {
 372                         public boolean check(Node row) {
 373                             if (testedControl.getControl() instanceof TableView) {
 374                                 return TableRow.class.isAssignableFrom(row.getClass())
 375                                         && ((TableRow) row).isVisible()
 376                                         && ((TableRow) row).isFocused();
 377                             } else {
 378                                 return TreeTableRow.class.isAssignableFrom(row.getClass())
 379                                         && ((TreeTableRow) row).isVisible()
 380                                         && ((TreeTableRow) row).isFocused();
 381                             }
 382                         }
 383                     });
 384                     if (lookup.size() > 0) {
 385                         if (lookup.size() > 1) {
 386                             throw new IllegalStateException("Too many focused rows.");
 387                         }
 388                         if (testedControl.getControl() instanceof TableView) {
 389                             setResult(new Point(-1, ((TableRow) lookup.get()).getIndex()));
 390                         } else {
 391                             setResult(new Point(-1, ((TreeTableRow) lookup.get()).getIndex()));
 392                         }
 393                         return;
 394                     }
 395                 }
 396                 setResult(new Point(-1, -1));
 397             }
 398         }.dispatch(Root.ROOT.getEnvironment());
 399     }
 400 
 401     protected static Wrap<? extends TableCell> getCellWrapByContent(final Wrap<? extends Control> testedControl, final int column, final int row) {
 402         return testedControl.as(Parent.class, TableCell.class).lookup(new LookupCriteria<TableCell<TableViewApp.Data, String>>() {
 403             public boolean check(TableCell<TableViewApp.Data, String> control) {
 404                 String item = control.getItem();
 405                 return item != null && item.startsWith(String.format("item %02d field %d", row, column));
 406             }
 407         }).wrap();
 408     }
 409 
 410 //    protected static Integer getColumn(TableCell cell) throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchFieldException {
 411 //        Field columnIndex = TableCell.class.getDeclaredField("columnIndex");
 412 //        columnIndex.setAccessible(true);
 413 //        return (Integer) columnIndex.get(cell);
 414 //    }
 415     protected static void clickOnFirstCell(final Wrap<? extends Control> tableViewWrap) {
 416         getCellWrap(tableViewWrap, 0, 0).mouse().click();
 417     }
 418 
 419     protected static Wrap getHeaderWrap(Wrap<? extends Control> tableViewWrap) {
 420         return tableViewWrap.as(Parent.class, Node.class).lookup(Node.class, new ByStyleClass("column-header-background")).wrap();
 421     }
 422 
 423     public static boolean isSelectedCellVisible(final Wrap<? extends Control> wrap) {
 424         Lookup focusedCells;
 425 
 426         if (!(wrap.getControl() instanceof TableView || wrap.getControl() instanceof TreeTableView)) {
 427             return false;
 428         }
 429 
 430         focusedCells = wrap.as(Parent.class, Node.class).lookup(IndexedCell.class,
 431                 new LookupCriteria<IndexedCell>() {
 432                     public boolean check(IndexedCell cell) {
 433                         return (TreeTableCell.class.isAssignableFrom(cell.getClass()) || TableCell.class.isAssignableFrom(cell.getClass())) && cell.isFocused();
 434                     }
 435                 });
 436 
 437         Assert.assertEquals("Must be only one focused cell", 1, focusedCells.size());
 438 
 439         final Wrap cell = focusedCells.wrap();
 440 
 441         Rectangle cellBounds = cell.getScreenBounds();
 442         Rectangle controlBounds = wrap.getScreenBounds();
 443 
 444         boolean isCellVisuallyVisible = controlBounds.contains(cellBounds);
 445         boolean isCellProgrammlyVisible = new GetAction<Boolean>() {
 446             @Override
 447             public void run(Object... parameters) throws Exception {
 448                 setResult(((IndexedCell) cell.getControl()).isVisible());
 449             }
 450         }.dispatch(wrap.getEnvironment()).booleanValue();
 451 
 452         if (!isCellVisuallyVisible) {
 453             System.out.println("Focused cell is outside of control bounds.");
 454             System.out.println("cell bounds = " + cellBounds);
 455             System.out.println("control bounds = " + controlBounds);
 456 
 457             System.out.println("wrap.getScreenBounds() = " + wrap.getScreenBounds());
 458             System.out.println("cell.getScreenBounds() = " + cell.getScreenBounds());
 459         }
 460 
 461         if (!isCellProgrammlyVisible) {
 462             System.out.println("Focused cell is invisible");
 463         }
 464 
 465         return isCellVisuallyVisible && isCellProgrammlyVisible;
 466     }
 467     private static final String VIRTIAL_FLOW_CLASS_NAME = "VirtualFlow";
 468     private static final String CLIPPED_CONTAINER_CLASS_NAME = "VirtualFlow$ClippedContainer";
 469 }