1 /*
   2  * Copyright (c) 2013, 2015, 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 com.sun.javafx.scene.control.infrastructure;
  26 
  27 import static org.junit.Assert.assertNotNull;
  28 
  29 import javafx.geometry.Orientation;
  30 import javafx.scene.Node;
  31 import javafx.scene.control.*;
  32 import javafx.scene.control.skin.ComboBoxListViewSkin;
  33 import com.sun.javafx.scene.control.LabeledText;
  34 import javafx.scene.control.skin.NestedTableColumnHeader;
  35 import javafx.scene.control.skin.TableColumnHeader;
  36 import javafx.scene.control.skin.TableHeaderRow;
  37 import javafx.scene.control.skin.TableViewSkinBase;
  38 import javafx.scene.control.skin.VirtualFlow;
  39 import com.sun.javafx.scene.control.VirtualScrollBar;
  40 import javafx.util.Callback;
  41 import java.util.List;
  42 
  43 import static org.junit.Assert.*;
  44 
  45 public class VirtualFlowTestUtils {
  46 
  47     public static void assertListContainsItemsInOrder(final List items, final Object... expected) {
  48         assertEquals(expected.length, items.size());
  49         for (int i = 0; i < expected.length; i++) {
  50             Object item = items.get(i);
  51             assertEquals(expected[i], item);
  52         }
  53     }
  54 
  55     public static void clickOnRow(final Control control, int row, KeyModifier... modifiers) {
  56         clickOnRow(control, row, 1, false, modifiers);
  57     }
  58 
  59     public static void clickOnRow(final Control control, int row, int clickCount, KeyModifier... modifiers) {
  60         clickOnRow(control, row, clickCount, false, modifiers);
  61     }
  62 
  63     public static void clickOnRow(final Control control, int row, boolean ignoreChildren, KeyModifier... modifiers) {
  64         clickOnRow(control, row, 1, ignoreChildren, modifiers);
  65     }
  66 
  67     // ignore children allows clicking on the row, rather than a child in that row.
  68     // This is good for testing, for example, TableRowBehavior
  69     public static void clickOnRow(final Control control, int row, int clickCount, boolean ignoreChildren, KeyModifier... modifiers) {
  70         IndexedCell cell = VirtualFlowTestUtils.getCell(control, row);
  71 
  72         if (! ignoreChildren && ((cell instanceof TableRow) || (cell instanceof TreeTableRow))) {
  73             for (Node n : cell.getChildrenUnmodifiable()) {
  74                 if (! (n instanceof IndexedCell)) {
  75                     continue;
  76                 }
  77                 IndexedCell<?> childCell = (IndexedCell<?>)n;
  78                 MouseEventFirer mouse = new MouseEventFirer(childCell);
  79                 mouse.fireMousePressAndRelease(clickCount, modifiers);
  80                 mouse.dispose();
  81                 break;
  82             }
  83         } else {
  84             if (ignoreChildren) {
  85                 // special case when we want to click on the row rather than its
  86                 // children (e.g. TableRow rather than its TableCell)
  87                 MouseEventFirer mouse = new MouseEventFirer(cell);
  88                 mouse.fireMousePressed(cell.getWidth(), cell.getHeight() / 2.0, modifiers);
  89                 mouse.fireMouseReleased(cell.getWidth(), cell.getHeight() / 2.0, modifiers);
  90                 mouse.dispose();
  91             } else {
  92                 MouseEventFirer mouse = new MouseEventFirer(cell);
  93                 mouse.fireMousePressAndRelease(clickCount, modifiers);
  94                 mouse.dispose();
  95             }
  96         }
  97     }
  98 
  99     public static void assertRowsEmpty(final Control control, final int startRow, final int endRow) {
 100         assertRows(control, startRow, endRow, true);
 101     }
 102 
 103     public static void assertRowsNotEmpty(final Control control, final int startRow, final int endRow) {
 104         assertRows(control, startRow, endRow, false);
 105     }
 106 
 107     public static void assertCellEmpty(IndexedCell cell) {
 108         if (cell instanceof TableRow || cell instanceof TreeTableRow) {
 109             for (Node n : cell.getChildrenUnmodifiable()) {
 110                 if (! (n instanceof IndexedCell)) {
 111                     continue;
 112                 }
 113                 IndexedCell<?> childCell = (IndexedCell<?>)n;
 114                 assertCellEmpty(childCell);
 115             }
 116         } else {
 117             final String text = cell.getText();
 118             assertTrue("Expected null, found '" + text + "'", text == null || text.isEmpty());
 119 
 120             final Node graphic = cell.getGraphic();
 121             assertTrue("Expected null graphic, found " + graphic, graphic == null);
 122         }
 123     }
 124 
 125     public static void assertCellNotEmpty(IndexedCell cell) {
 126         if (cell instanceof TableRow || cell instanceof TreeTableRow) {
 127             for (Node n : cell.getChildrenUnmodifiable()) {
 128                 if (! (n instanceof IndexedCell)) {
 129                     continue;
 130                 }
 131                 IndexedCell<?> childCell = (IndexedCell<?>)n;
 132                 assertCellNotEmpty(childCell);
 133             }
 134         } else {
 135             final String text = cell.getText();
 136             final Node graphic = cell.getGraphic();
 137             assertTrue("Expected a non-null text or graphic property",
 138                        (text != null && ! text.isEmpty()) || graphic != null);
 139         }
 140     }
 141 
 142     private static void assertRows(final Control control, final int startRow, final int endRow, final boolean expectEmpty) {
 143         Callback<IndexedCell<?>, Void> callback = indexedCell -> {
 144             boolean hasChildrenCell = false;
 145             for (Node n : indexedCell.getChildrenUnmodifiable()) {
 146                 if (! (n instanceof IndexedCell)) {
 147                     continue;
 148                 }
 149                 hasChildrenCell = true;
 150                 IndexedCell<?> childCell = (IndexedCell<?>)n;
 151 
 152                 if (expectEmpty) {
 153                     assertCellEmpty(childCell);
 154                 } else {
 155                     assertCellNotEmpty(childCell);
 156                 }
 157             }
 158 
 159             if (! hasChildrenCell) {
 160                 if (expectEmpty) {
 161                     assertCellEmpty(indexedCell);
 162                 } else {
 163                     assertCellNotEmpty(indexedCell);
 164                 }
 165             }
 166             return null;
 167         };
 168 
 169         assertCallback(control, startRow, endRow, callback);
 170     }
 171 
 172     public static void assertCellTextEquals(final Control control, final int index, final String... expected) {
 173         if (expected == null || expected.length == 0) return;
 174 
 175         Callback<IndexedCell<?>, Void> callback = indexedCell -> {
 176             if (indexedCell.getIndex() != index) return null;
 177 
 178             if (expected.length == 1) {
 179                 assertEquals(expected[0], indexedCell.getText());
 180             } else {
 181                 int jump = 0;
 182                 for (int i = 0; i < expected.length; i++) {
 183                     Node childNode = indexedCell.getChildrenUnmodifiable().get(i + jump);
 184                     String text = null;
 185                     if (! (childNode instanceof IndexedCell)) {
 186                         jump++;
 187                         continue;
 188                     }
 189 
 190                     text = ((IndexedCell) childNode).getText();
 191                     assertEquals(expected[i], text);
 192                 }
 193             }
 194             return null;
 195         };
 196 
 197         assertCallback(control, index, index + 1, callback);
 198     }
 199 
 200     public static void assertTableCellTextEquals(final Control control, final int row, final int column, final String expected) {
 201         Callback<IndexedCell<?>, Void> callback = indexedCell -> {
 202             if (indexedCell.getIndex() != row) return null;
 203 
 204             int _column = column;
 205 
 206             // we need to account for TreeTableView having LabeledText node in the TreeTableRow
 207             if (indexedCell instanceof TreeTableRow) {
 208                 _column++;
 209             }
 210 
 211             IndexedCell cell = (IndexedCell) indexedCell.getChildrenUnmodifiable().get(_column);
 212             assertEquals(expected, cell.getText());
 213             return null;
 214         };
 215 
 216         assertCallback(control, row, row + 1, callback);
 217     }
 218 
 219     // used by TreeView / TreeTableView to ensure the correct indentation
 220     // (although note that it has only been developed so far for TreeView)
 221     public static void assertLayoutX(final Control control, final int startRow, final int endRow, final double expectedLayoutX) {
 222         Callback<IndexedCell<?>, Void> callback = indexedCell -> {
 223             List<Node> childrenOfCell = indexedCell.getChildrenUnmodifiable();
 224             LabeledText labeledText = null;
 225             for (int j = 0; j < childrenOfCell.size(); j++) {
 226                 Node child = childrenOfCell.get(j);
 227                 if (child instanceof LabeledText) {
 228                     labeledText = (LabeledText) child;
 229                 }
 230             }
 231 
 232             String error = "Element in row " + indexedCell.getIndex() + " has incorrect indentation. "
 233                     + "Expected " + expectedLayoutX + ", but found " + labeledText.getLayoutX();
 234             assertEquals(error, expectedLayoutX, labeledText.getLayoutX(), 0.0);
 235             return null;
 236         };
 237 
 238         assertCallback(control, startRow, endRow, callback);
 239     }
 240 
 241     public static int getCellCount(final Control control) {
 242         return getVirtualFlow(control).getCellCount();
 243     }
 244 
 245     public static IndexedCell getCell(final Control control, final int index) {
 246         return getVirtualFlow(control).getCell(index);
 247     }
 248 
 249     public static IndexedCell getCell(final Control control, final int row, final int column) {
 250         IndexedCell rowCell = getVirtualFlow(control).getCell(row);
 251         if (column == -1) {
 252             return rowCell;
 253         }
 254 
 255         int count = -1;
 256         for (Node n : rowCell.getChildrenUnmodifiable()) {
 257             if (! (n instanceof IndexedCell)) {
 258                 continue;
 259             }
 260             count++;
 261             if (count == column) {
 262                 return (IndexedCell) n;
 263             }
 264         }
 265         return null;
 266     }
 267 
 268     public static void assertCallback(final Control control, final int startRow, final int endRow, final Callback<IndexedCell<?>, Void> callback) {
 269         final VirtualFlow<?> flow = getVirtualFlow(control);
 270         final int sheetSize = flow.getCellCount();
 271 
 272         // NOTE: we used to only go to the end of the sheet, but now we go past that if
 273         // endRow desires. This is to allow for us to test cells that are visually
 274         // shown, but empty.
 275         final int end = endRow == -1 ? sheetSize : endRow; //Math.min(endRow, sheetSize);
 276 
 277         for (int row = startRow; row < end; row++) {
 278             // old approach:
 279             // callback.call((IndexedCell<?>)sheet.getChildren().get(row));
 280 
 281             // new approach:
 282             callback.call(flow.getCell(row));
 283         }
 284     }
 285 
 286     public static void assertGraphicIsVisible(final Control control, int row) {
 287         assertGraphicIsVisible(control, row, -1);
 288     }
 289 
 290     public static void assertGraphicIsVisible(final Control control, int row, int column) {
 291         Cell cell = getCell(control, row, column);
 292         Node graphic = cell.getGraphic();
 293         assertNotNull(graphic);
 294         assertTrue(graphic.isVisible() && graphic.getOpacity() == 1.0);
 295     }
 296 
 297     public static void assertGraphicIsNotVisible(final Control control, int row) {
 298         assertGraphicIsNotVisible(control, row, -1);
 299     }
 300 
 301     public static void assertGraphicIsNotVisible(final Control control, int row, int column) {
 302         Cell cell = getCell(control, row, column);
 303         Node graphic = cell.getGraphic();
 304         if (graphic == null) {
 305             return;
 306         }
 307 
 308         assertNotNull(graphic);
 309         assertTrue(!graphic.isVisible() || graphic.getOpacity() == 0.0);
 310     }
 311 
 312     public static void assertCellCount(final Control control, final int expected) {
 313         assertEquals(getVirtualFlow(control).getCellCount(), expected);
 314     }
 315 
 316     public static void assertTableHeaderColumnExists(final Control control, final TableColumnBase column, boolean expected) {
 317         TableHeaderRow headerRow = getTableHeaderRow(control);
 318 
 319         NestedTableColumnHeader rootHeader = getNestedTableColumnHeader(headerRow);
 320         boolean match = false;
 321         for (TableColumnHeader header : rootHeader.getColumnHeaders()) {
 322             match = column.equals(header.getTableColumn());
 323             if (match) break;
 324         }
 325 
 326         if (expected) {
 327             assertTrue(match);
 328         } else {
 329             assertFalse(match);
 330         }
 331     }
 332     
 333     public static boolean BLOCK_STAGE_LOADER_DISPOSE = false;
 334 
 335     public static VirtualFlow<?> getVirtualFlow(Control control) {
 336         StageLoader sl = null;
 337         boolean stageLoaderCreated = false;
 338         if (control.getScene() == null) {
 339             sl = new StageLoader(control);
 340             stageLoaderCreated = true;
 341         }
 342 
 343         VirtualFlow<?> flow;
 344         if (control instanceof ComboBox) {
 345             final ComboBox cb = (ComboBox) control;
 346             final ComboBoxListViewSkin skin = (ComboBoxListViewSkin) cb.getSkin();
 347             control = (ListView) skin.getPopupContent();
 348         }
 349 
 350         flow = (VirtualFlow<?>)control.lookup("#virtual-flow");
 351         
 352         if (stageLoaderCreated && sl != null && ! BLOCK_STAGE_LOADER_DISPOSE) {
 353             sl.dispose();
 354         }
 355 
 356         return flow;
 357     }
 358 
 359     public static VirtualScrollBar getVirtualFlowVerticalScrollbar(final Control control) {
 360         return getVirtualFlowScrollbar(control, Orientation.VERTICAL);
 361     }
 362 
 363     public static VirtualScrollBar getVirtualFlowHorizontalScrollbar(final Control control) {
 364         return getVirtualFlowScrollbar(control, Orientation.HORIZONTAL);
 365     }
 366 
 367     // this method must be called with the control having been shown in a
 368     // stage (e.g. via StageLoader). Be sure to dispose too!
 369     public static TableHeaderRow getTableHeaderRow(final Control control) {
 370         if (control.getSkin() == null) {
 371             throw new IllegalStateException("getTableHeaderRow requires the control to be visible in a stage");
 372         }
 373 
 374         TableViewSkinBase<?,?,?,?,?> skin = (TableViewSkinBase) control.getSkin();
 375         TableHeaderRow headerRow = null;
 376         for (Node n : skin.getChildren()) {
 377             if (n instanceof TableHeaderRow) {
 378                 headerRow = (TableHeaderRow) n;
 379                 break;
 380             }
 381         }
 382 
 383         return headerRow;
 384     }
 385 
 386     private static VirtualScrollBar getVirtualFlowScrollbar(final Control control, Orientation orientation) {
 387         VirtualFlow<?> flow = getVirtualFlow(control);
 388         VirtualScrollBar scrollBar = null;
 389         for (Node child : flow.getChildrenUnmodifiable()) {
 390             if (child instanceof VirtualScrollBar) {
 391                 if (((VirtualScrollBar)child).getOrientation() == orientation) {
 392                     scrollBar = (VirtualScrollBar) child;
 393                 }
 394             }
 395         }
 396 
 397         return scrollBar;
 398     }
 399 
 400     public static TableColumnHeader getTableColumnHeader(Control table, TableColumnBase<?,?> column) {
 401         TableHeaderRow headerRow = VirtualFlowTestUtils.getTableHeaderRow(table);
 402         return findColumnHeader(getNestedTableColumnHeader(headerRow), column);
 403     }
 404 
 405     private static TableColumnHeader findColumnHeader(NestedTableColumnHeader nestedHeader, TableColumnBase<?,?> column) {
 406         for (TableColumnHeader header : nestedHeader.getColumnHeaders()) {
 407             if (header instanceof NestedTableColumnHeader) {
 408                 TableColumnHeader result = findColumnHeader((NestedTableColumnHeader)header, column);
 409                 if (result != null) {
 410                     return result;
 411                 }
 412             } else {
 413                 if (column.equals(header.getTableColumn())) {
 414                     return header;
 415                 }
 416             }
 417         }
 418         return null;
 419     }
 420 
 421     public static NestedTableColumnHeader getNestedTableColumnHeader(TableHeaderRow headerRow) {
 422         NestedTableColumnHeader rootHeader = null;
 423 
 424         for (Node n : headerRow.getChildren()) {
 425             if (n instanceof NestedTableColumnHeader) {
 426                 rootHeader = (NestedTableColumnHeader) n;
 427             }
 428         }
 429         return rootHeader;
 430     }
 431 }