/******************************************************************************* * Copyright (c) 2013 BREDEX GmbH. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * BREDEX GmbH - initial API and implementation *******************************************************************************/ package org.eclipse.jubula.rc.javafx.tester.adapter; import java.awt.Rectangle; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.Callable; import org.eclipse.jubula.rc.common.adaptable.AdapterFactoryRegistry; import org.eclipse.jubula.rc.common.exception.RobotException; import org.eclipse.jubula.rc.common.exception.StepExecutionException; import org.eclipse.jubula.rc.common.implclasses.table.Cell; import org.eclipse.jubula.rc.common.tester.adapter.interfaces.IComponent; import org.eclipse.jubula.rc.common.tester.adapter.interfaces.ITableComponent; import org.eclipse.jubula.rc.common.tester.adapter.interfaces.ITextComponent; import org.eclipse.jubula.rc.common.util.IndexConverter; import org.eclipse.jubula.rc.common.util.MatchUtil; import org.eclipse.jubula.rc.javafx.driver.EventThreadQueuerJavaFXImpl; import org.eclipse.jubula.rc.javafx.tester.util.AbstractTraverser; import org.eclipse.jubula.rc.javafx.tester.util.GenericTraverseHelper; import org.eclipse.jubula.rc.javafx.tester.util.NodeBounds; import org.eclipse.jubula.rc.javafx.tester.util.NodeTraverseHelper; import org.eclipse.jubula.rc.javafx.tester.util.Rounding; import org.eclipse.jubula.tools.internal.constants.TestDataConstants; import org.eclipse.jubula.tools.internal.objects.event.EventFactory; import org.eclipse.jubula.tools.internal.objects.event.TestErrorEvent; import org.eclipse.jubula.tools.internal.utils.StringParsing; import com.sun.javafx.scene.control.skin.TableColumnHeader; import com.sun.javafx.scene.control.skin.TableHeaderRow; import javafx.collections.ObservableList; import javafx.scene.control.TableCell; import javafx.scene.control.TableColumn; import javafx.scene.control.TablePosition; import javafx.scene.control.TableView; import javafx.scene.layout.Pane; /** * Adapter for a TableView(Table) * * @author BREDEX GmbH * @created 7.11.2013 */ public class TableAdapter extends JavaFXComponentAdapter<TableView<?>> implements ITableComponent<TableCell<?, ?>> { /** * Workaround to support nested Columns without modifying classes which would * affect other toolkits **/ private List<TableColumn> m_columns = new ArrayList<TableColumn>(); /** * Creates an adapter for a TableView. * * @param objectToAdapt * the object which needed to be adapted */ public TableAdapter(TableView<?> objectToAdapt) { super(objectToAdapt); } @Override public String getText() { String result = EventThreadQueuerJavaFXImpl.invokeAndWait("getText", //$NON-NLS-1$ new Callable<String>() { @Override public String call() throws Exception { ObservableList<?> sCells = getRealComponent() .getSelectionModel().getSelectedCells(); if (!sCells.isEmpty()) { TablePosition<?, ?> pos = (TablePosition<?, ?>) sCells.get(0); return getCellText(pos.getRow(), pos.getColumn()); } throw new StepExecutionException("No selection found", //$NON-NLS-1$ EventFactory .createActionError(TestErrorEvent. NO_SELECTION)); } }); return result; } @Override public int getColumnCount() { int result = EventThreadQueuerJavaFXImpl.invokeAndWait( "getColumnCount", new Callable<Integer>() { //$NON-NLS-1$ @Override public Integer call() throws Exception { int counter = 0; for (TableColumn<?, ?> column : getRealComponent() .getColumns()) { counter += new GenericTraverseHelper <TableColumn, TableColumn>() .getInstancesOf( new AbstractTraverser <TableColumn, TableColumn>( column) { @Override public Iterable<TableColumn> getTraversableData() { return this.getObject() .getColumns(); } }, TableColumn.class).size(); } return counter + getRealComponent().getColumns().size(); } }); return result; } @Override public int getRowCount() { int result = EventThreadQueuerJavaFXImpl.invokeAndWait("getRowCount", //$NON-NLS-1$ new Callable<Integer>() { @Override public Integer call() throws Exception { return getRealComponent().getItems().size(); } }); return result; } @Override public String getCellText(final int row, final int column) { String result = EventThreadQueuerJavaFXImpl.invokeAndWait("getCellText", //$NON-NLS-1$ new Callable<String>() { @Override public String call() throws Exception { TableView table = getRealComponent(); TableColumn<?, ?> col = null; if (m_columns.size() == 0) { col = table.getVisibleLeafColumn(column); } else { col = m_columns.get(column); } table.scrollTo(row); table.scrollToColumn(col); table.layout(); List<? extends TableCell> tCells = NodeTraverseHelper .getInstancesOf(table, TableCell.class); for (TableCell<?, ?> cell : tCells) { if (cell.getIndex() == row && cell.getTableColumn() == col && cell.getTableView() == table && NodeTraverseHelper.isVisible(cell)) { IComponent adapter = (IComponent) AdapterFactoryRegistry.getInstance() .getAdapter(IComponent.class, cell); if (adapter != null && adapter instanceof ITextComponent) { return ((ITextComponent) adapter).getText(); } } } return null; } }); return result; } @Override public String getColumnHeaderText(final int column) { String result = EventThreadQueuerJavaFXImpl.invokeAndWait( "getColumnHeaderText", //$NON-NLS-1$ new Callable<String>() { @Override public String call() throws Exception { if (m_columns.size() > 0) { TableColumn<?, ?> tCol = m_columns.get(column); return tCol.getText(); } TableColumn<?, ?> tCol = getRealComponent(). getVisibleLeafColumn(column); return tCol.getText(); } }); return result; } @Override public int getColumnFromString(final String colPath, final String op) { Integer result = EventThreadQueuerJavaFXImpl.invokeAndWait( "getColumnFromString", new Callable<Integer>() { //$NON-NLS-1$ @Override public Integer call() throws Exception { TableView table = getRealComponent(); List<String> path = StringParsing.splitToList(colPath, TestDataConstants.PATH_CHAR_DEFAULT, TestDataConstants.ESCAPE_CHAR_DEFAULT, false); TableColumn<?, ?> column = determineColumn(colPath, op, table, path); if (column == null) { return -2; } if (table.getVisibleLeafColumns().contains(column)) { return table.getVisibleLeafColumns(). indexOf(column); } m_columns.add(column); return m_columns.indexOf(column); } }); return result.intValue(); } /** * get column for the given path * @param colPath the path * @param op the operation * @param table the table * @param path the path as list * @return the column or null if no column was found */ private TableColumn<?, ?> determineColumn(final String colPath, final String op, TableView table, List<String> path) { ObservableList<TableColumn> columns; if (colPath.contains("" + TestDataConstants.PATH_CHAR_DEFAULT)) { //$NON-NLS-1$ columns = table.getColumns(); } else { columns = table.getVisibleLeafColumns(); } Iterator<String> pathIterator = path.iterator(); String currCol = null; TableColumn<?, ?> column = null; pathIteration: while (pathIterator.hasNext()) { try { currCol = pathIterator.next(); int usrIdxCol = Integer.parseInt(currCol); if (usrIdxCol == 0) { usrIdxCol = usrIdxCol + 1; } int i = IndexConverter.toImplementationIndex(usrIdxCol); if (MatchUtil.NOT_EQUALS == op) { for (int j = 0; j < columns.size(); j++) { if (j != i) { if (pathIterator.hasNext()) { columns = columns.get(j).getColumns(); } else { column = columns.get(j); } } } } else { try { if (pathIterator.hasNext()) { columns = columns.get(i).getColumns(); } else { column = columns.get(i); } } catch (IndexOutOfBoundsException e) { return null; } } } catch (NumberFormatException nfe) { try { if (path.size() <= 1) { columns = table.getColumns(); } if (columns.size() <= 0) { throw new StepExecutionException( "No Columns", EventFactory.createActionError(//$NON-NLS-1$ TestErrorEvent.NO_HEADER)); } for (TableColumn c : columns) { String h = c.getText(); if (MatchUtil.getInstance().match(h, currCol, op)) { column = c; if (pathIterator.hasNext()) { columns = c.getColumns(); } continue pathIteration; } } return null; } catch (IllegalArgumentException iae) { // do nothing here } } } return column; } @Override public String getRowText(int row) { // TableView does not act like lists return null; } @Override public int getRowFromString(final String row, final String operator) { Integer result = EventThreadQueuerJavaFXImpl.invokeAndWait( "getRowFromString", new Callable<Integer>() { //$NON-NLS-1$ @Override public Integer call() throws Exception { Integer rowInt = null; TableView<?> table = getRealComponent(); try { rowInt = IndexConverter .toImplementationIndex(Integer .parseInt(row)); if (rowInt == -1) { if (table.getColumns().size() <= 0) { throw new StepExecutionException( "No Header", //$NON-NLS-1$ EventFactory .createActionError( TestErrorEvent. NO_HEADER)); } } } catch (NumberFormatException nfe) { for (int i = 0; i < table.getItems().size(); i++) { String cellTxt = getCellText(i, 0); if (MatchUtil.getInstance().match(cellTxt, row, operator)) { return new Integer(i); } } } if (rowInt == null) { throw new StepExecutionException( "Row not found", //$NON-NLS-1$ EventFactory .createActionError(TestErrorEvent. NOT_FOUND)); } return rowInt; } }); return result.intValue(); } @Override public Rectangle getHeaderBounds(final int column) { Rectangle result = EventThreadQueuerJavaFXImpl.invokeAndWait( "getHeaderBounds", new Callable<Rectangle>() { //$NON-NLS-1$ @Override public Rectangle call() throws Exception { TableView table = getRealComponent(); TableColumn<?, ?> col; if (m_columns.size() > 0) { col = m_columns.get(column); } else { col = getRealComponent(). getVisibleLeafColumn(column); } table.scrollToColumn(col); // Update the layout coordinates otherwise // we would get old position values table.layout(); // DEPENDENCY TO INTERNAL API // This should only be one node, but who knows what // people do List<? extends TableHeaderRow> headerRow = NodeTraverseHelper.getInstancesOf( table, TableHeaderRow.class); TableColumnHeader colH = null; for (TableHeaderRow tableHeaderRow : headerRow) { colH = tableHeaderRow.getColumnHeaderFor(col); if (colH != null) { return NodeBounds.getRelativeBounds(colH, tableHeaderRow); } } return null; } }); return result; } @Override public Cell getSelectedCell() throws StepExecutionException { Cell result = EventThreadQueuerJavaFXImpl.invokeAndWait( "getSelectedCell", new Callable<Cell>() { //$NON-NLS-1$ @Override public Cell call() throws StepExecutionException { TableView<?> table = getRealComponent(); ObservableList<?> list = table .getSelectionModel().getSelectedCells(); if (list.size() > 0) { TablePosition<?, ?> pos = null; for (Object object : list) { TablePosition<?, ?> curr = (TablePosition<?, ?>) object; if (curr.getRow() == table.getSelectionModel() .getSelectedIndex()) { pos = curr; break; } } if (pos != null) { return new Cell(pos.getRow(), pos.getColumn()); } } throw new StepExecutionException("No selection found", //$NON-NLS-1$ EventFactory .createActionError(TestErrorEvent. NO_SELECTION)); } }); return result; } @Override public boolean isHeaderVisible() { boolean result = EventThreadQueuerJavaFXImpl.invokeAndWait( "isHeaderVisible", new Callable<Boolean>() { //$NON-NLS-1$ @Override public Boolean call() throws Exception { Pane header = (Pane) getRealComponent().lookup( "TableHeaderRow"); //$NON-NLS-1$ if (header != null) { return header.isVisible(); } return false; } }); return result; } @Override public boolean isCellEditable(final int row, final int column) { boolean result = EventThreadQueuerJavaFXImpl.invokeAndWait( "isCellEditable", new Callable<Boolean>() { //$NON-NLS-1$ @Override public Boolean call() throws Exception { TableView table = getRealComponent(); if (table.isEditable()) { TableColumn<?, ?> col = null; if (m_columns.size() == 0) { col = table.getVisibleLeafColumn(column); } else { col = m_columns.get(column); } if (col.isEditable()) { table.scrollTo(row); table.scrollToColumn(col); table.layout(); List<? extends TableCell> tCells = NodeTraverseHelper .getInstancesOf(table, TableCell.class); for (TableCell<?, ?> cell : tCells) { if (cell.getIndex() == row && cell.getTableColumn() == col && cell.getTableView() == table && NodeTraverseHelper .isVisible(cell)) { return cell.isEditable(); } } } } return false; } }); return result; } @Override public boolean hasCellSelection() { boolean result = EventThreadQueuerJavaFXImpl.invokeAndWait( "hasCellSelection", new Callable<Boolean>() { //$NON-NLS-1$ @Override public Boolean call() throws Exception { TableView<?> table = getRealComponent(); return table.getSelectionModel().getSelectedCells() .size() > 0; } }); return result; } @Override public Rectangle scrollCellToVisible(final int row, final int column) throws StepExecutionException { Rectangle result = EventThreadQueuerJavaFXImpl.invokeAndWait( "scrollCellToVisible", new Callable<Rectangle>() { //$NON-NLS-1$ @Override public Rectangle call() throws Exception { TableView table = getRealComponent(); TableColumn<?, ?> col = null; if (m_columns.size() == 0) { col = table.getVisibleLeafColumn(column); } else { col = m_columns.get(column); } table.scrollTo(row); table.scrollToColumn(col); // Update the layout coordinates otherwise // we would get old position values table.layout(); List<? extends TableCell> tCells = NodeTraverseHelper. getInstancesOf(table, TableCell.class); for (TableCell<?, ?> cell : tCells) { if (cell.getIndex() == row && cell.getTableColumn() == col && cell.getTableView() == table) { Rectangle b = NodeBounds .getAbsoluteBounds(cell); Rectangle tableB = NodeBounds .getAbsoluteBounds(table); return new Rectangle( Math.abs(tableB.x - b.x), Math.abs(tableB.y - b.y), Rounding.round(b.getWidth()), Rounding.round(b.getHeight())); } } return null; } }); return result; } @Override public Object getTableHeader() { Object result = EventThreadQueuerJavaFXImpl.invokeAndWait( "getTableHeader", new Callable<Object>() { //$NON-NLS-1$ @Override public Object call() throws Exception { return getRealComponent().lookup("TableHeaderRow"); //$NON-NLS-1$ } }); return result; } /** * {@inheritDoc} */ public String getPropertyValueOfCell(String name, TableCell<?, ?> cell) { Object prop = EventThreadQueuerJavaFXImpl.invokeAndWait("getProperty", //$NON-NLS-1$ new Callable<String>() { @Override public String call() throws Exception { try { IComponent adapter = (IComponent) AdapterFactoryRegistry.getInstance() .getAdapter(IComponent.class, cell); if (adapter != null && adapter instanceof ITextComponent) { return ((ITextComponent) adapter) .getPropteryValue(name); } return null; } catch (RobotException e) { throw new StepExecutionException(e.getMessage(), EventFactory.createActionError( TestErrorEvent .PROPERTY_NOT_ACCESSABLE)); } } }); return String.valueOf(prop); } }