/******************************************************************************* * Copyright (c) 2008, 2016 Ketan Padegaonkar and others. * 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: * Ketan Padegaonkar - initial API and implementation * Cédric Chabanois - http://swtbot.org/bugzilla/show_bug.cgi?id=10 * Ketan Patel - https://bugs.eclipse.org/bugs/show_bug.cgi?id=259720 * Kristine Jetzke - Bug 379185 * Aparna Argade(Cadence Design Systems, Inc.) - Bug 363916 * Stephane Bouchet (Intel Corporation) - Bug 451547 * Patrick Tasse - Improve SWTBot menu API and implementation (Bug 479091) *******************************************************************************/ package org.eclipse.swtbot.swt.finder.widgets; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeItem; import org.eclipse.swtbot.swt.finder.SWTBot; import org.eclipse.swtbot.swt.finder.exceptions.WidgetNotFoundException; import org.eclipse.swtbot.swt.finder.finders.UIThreadRunnable; import org.eclipse.swtbot.swt.finder.results.ArrayResult; import org.eclipse.swtbot.swt.finder.results.BoolResult; import org.eclipse.swtbot.swt.finder.results.IntResult; import org.eclipse.swtbot.swt.finder.results.ListResult; import org.eclipse.swtbot.swt.finder.results.Result; import org.eclipse.swtbot.swt.finder.results.StringResult; import org.eclipse.swtbot.swt.finder.results.VoidResult; import org.eclipse.swtbot.swt.finder.results.WidgetResult; import org.eclipse.swtbot.swt.finder.utils.MessageFormat; import org.eclipse.swtbot.swt.finder.utils.SWTUtils; import org.eclipse.swtbot.swt.finder.utils.TableRow; import org.eclipse.swtbot.swt.finder.utils.TextDescription; import org.eclipse.swtbot.swt.finder.utils.internal.Assert; import org.eclipse.swtbot.swt.finder.waits.DefaultCondition; import org.hamcrest.SelfDescribing; /** * @author Ketan Padegaonkar <KetanPadegaonkar [at] gmail [dot] com> * @author Ketan Patel * @author Joshua Gosse <jlgosse [at] ca [dot] ibm [dot] com> * @version $Id$ */ public class SWTBotTreeItem extends AbstractSWTBot<TreeItem> { // private static final int expandKey = SWT.getPlatform().equals("gtk") ? SWT.KEYPAD_ADD : SWT.ARROW_RIGHT; // private static final int collapseKey = SWT.getPlatform().equals("gtk") ? SWT.KEYPAD_SUBTRACT : SWT.ARROW_LEFT; private Tree tree; /** * @param treeItem the widget. * @throws WidgetNotFoundException if the widget is <code>null</code> or widget has been disposed. */ public SWTBotTreeItem(final TreeItem treeItem) throws WidgetNotFoundException { this(treeItem, null); } /** * @param treeItem the widget. * @param description the description of the widget, this will be reported by {@link #toString()} * @throws WidgetNotFoundException if the widget is <code>null</code> or widget has been disposed. */ public SWTBotTreeItem(final TreeItem treeItem, SelfDescribing description) throws WidgetNotFoundException { super(treeItem, description); this.tree = syncExec(new WidgetResult<Tree>() { public Tree run() { return treeItem.getParent(); } }); } /** * Returns the text stored at the given column index in the receiver, or empty string if the text has not been set. * Throws an exception if the column is greater than the number of columns in the tree. * * @param column the column index. * @return the cell at the location specified by the column */ public String cell(final int column) { if (column == 0) { return getText(); } int columnCount = new SWTBotTree(tree).columnCount(); Assert.isLegal(column < columnCount, java.text.MessageFormat.format( "The column index ({0}) is more than the number of column({1}) in the tree.", column, columnCount)); //$NON-NLS-1$ return syncExec(new StringResult() { public String run() { return widget.getText(column); } }); } /** * Returns the table row representation of cell values * * @return the cell values for this item */ public TableRow row() { return syncExec(new Result<TableRow>() { public TableRow run() { int columnCount = tree.getColumnCount(); TableRow tableRow = new TableRow(); if (columnCount == 0) tableRow.add(widget.getText()); else for (int j = 0; j < columnCount; j++) tableRow.add(widget.getText(j)); return tableRow; } }); } /** * Returns the number of items contained in the receiver that are direct item children of the receiver. * * @return the number of items */ public int rowCount() { return syncExec(new IntResult() { public Integer run() { return widget.getItemCount(); } }); } /** * Gets the nodes at the given, zero-relative index in the receiver. Throws an exception if the index is out of * range. * * @param row the index of the item to return * @return the item at the given index */ public SWTBotTreeItem getNode(final int row) { int rowCount = rowCount(); Assert.isLegal(row < rowCount, java.text.MessageFormat.format("The row number ({0}) is more than the number of rows({1}) in the tree.", row, rowCount)); //$NON-NLS-1$ return syncExec(new Result<SWTBotTreeItem>() { public SWTBotTreeItem run() { return new SWTBotTreeItem(widget.getItem(row)); } }); } /** * Gets the cell data for the given row/column index. * * @param row the row index. * @param column the column index. * @return the cell at the location specified by the row and column * @see #getNode(int) * @see #cell(int) */ public String cell(final int row, final int column) { return getNode(row).cell(column); } /** * Expands the tree item to simulate click the plus sign. * * @return the tree item, after expanding it. */ public SWTBotTreeItem expand() { assertEnabled(); if (isExpanded()) { log.warn(MessageFormat.format("Tree item {0} is already expanded. Won''t expand it again.", this)); return this; } preExpandNotify(); asyncExec(new VoidResult() { public void run() { widget.setExpanded(true); } }); postExpandNotify(); return this; } /** * Collapses the tree item to simulate click the plus sign. * * @return the tree item, after collapsing it. */ public SWTBotTreeItem collapse() { assertEnabled(); if (!isExpanded()) { log.warn(MessageFormat.format("Tree item {0} is already collapsed. Won''t collapse it again.", this)); return this; } preCollapseNotify(); asyncExec(new VoidResult() { public void run() { widget.setExpanded(false); } }); postCollapseNotify(); return this; } private void preExpandNotify() { notifyTree(SWT.Expand, createEvent()); } private void postExpandNotify() { notifyTree(SWT.MouseMove); notifyTree(SWT.Activate); notifyTree(SWT.FocusIn); notifyTree(SWT.MouseDown); notifyTree(SWT.MeasureItem); notifyTree(SWT.Deactivate); notifyTree(SWT.FocusOut); } private void preCollapseNotify() { notifyTree(SWT.Collapse, createEvent()); } private void postCollapseNotify() { notifyTree(SWT.MouseMove); notifyTree(SWT.Activate); notifyTree(SWT.FocusIn); notifyTree(SWT.MouseDown); notifyTree(SWT.MeasureItem); notifyTree(SWT.Deactivate); notifyTree(SWT.FocusOut); } private void notifyTree(int eventType) { notify(eventType, createEvent(), tree); } private void notifyTree(int eventType, Event event) { notify(eventType, event, tree); } protected Event createEvent() { Event event = super.createEvent(); event.widget = tree; event.item = widget; return event; } /** * Gets the nodes of the tree item. * * @return the list of nodes in the treeitem. */ public List<String> getNodes() { return syncExec(new ListResult<String>() { public List<String> run() { TreeItem[] items = widget.getItems(); List<String> result = new ArrayList<String>(items.length); for (TreeItem item : items) result.add(item.getText()); return result; } }); } /** * Expands the node matching the given node texts. * * @param nodes the text on the node. * @return the last tree node that was expanded or <code>null</code> if none exists. * @throws WidgetNotFoundException if any of the nodes on the path do not exist */ public SWTBotTreeItem expandNode(final String... nodes) { Assert.isNotEmpty((Object[]) nodes); assertEnabled(); SWTBotTreeItem item = this; for (String node : nodes) item = item.getNode(node).expand(); return item; } /** * Collapses the node matching the given node text. * * @param nodeText the text on the node. * @return the node that was collapsed or <code>null</code> if not match exists. */ public SWTBotTreeItem collapseNode(final String nodeText) { assertEnabled(); return getNode(nodeText).collapse(); } /** * Gets the node matching the given node text and index. * * @param nodeText the text on the node. * @param index the n'th node with the nodeText. * @return the node with the specified text or <code>WidgetNotFoundException</code> if not match exists. * @since 2.0 */ public SWTBotTreeItem getNode(final String nodeText, final int index) { List<SWTBotTreeItem> nodes = getNodes(nodeText); Assert.isTrue(index < nodes.size(), MessageFormat.format("The index ({0}) was more than the number of nodes ({1}) in the tree.", index, nodes.size())); return nodes.get(index); } /** * Gets all nodes matching the given node text. * * @param nodeText the text on the node. * @return the nodes with the specified text or <code>WidgetNotFoundException</code> if not match exists. * @since 2.0 */ public List<SWTBotTreeItem> getNodes(final String nodeText) { List<SWTBotTreeItem> foundItems = syncExec(new ListResult<SWTBotTreeItem>() { public List<SWTBotTreeItem> run() { TreeItem[] items = widget.getItems(); List<SWTBotTreeItem> results = new ArrayList<SWTBotTreeItem>(); for (TreeItem treeItem : items) { if (treeItem.getText().equals(nodeText)) results.add(new SWTBotTreeItem(treeItem, new TextDescription("Tree node with text: " + nodeText))); } return results; } }); if (foundItems.isEmpty()) throw new WidgetNotFoundException("Could not find node with text: " + nodeText); //$NON-NLS-1$ return foundItems; } /** * Gets the first node found matching the given node text. * * @param nodeText the text on the node. * @return the first node with the specified text or <code>WidgetNotFoundException</code> if not match exists. * @since 1.2 */ public SWTBotTreeItem getNode(final String nodeText) { return getNode(nodeText, 0); } /** * Selects the current tree item. * * @return the current node. * @since 1.0 */ public SWTBotTreeItem select() { assertEnabled(); syncExec(new VoidResult() { public void run() { tree.setFocus(); tree.setSelection(widget); } }); notifySelect(); return this; } /** * Click on the tree at given coordinates * * @param x the x co-ordinate of the click * @param y the y co-ordinate of the click * @since 1.2 */ @Override protected void clickXY(int x, int y) { log.debug(MessageFormat.format("Clicking on {0}", this)); //$NON-NLS-1$ notifyTree(SWT.MouseEnter, createMouseEvent(x, y, 0, SWT.NONE, 0)); notifyTree(SWT.Activate, super.createEvent()); syncExec(new VoidResult() { public void run() { if (tree.getSelectionCount() != 1 || !tree.getSelection()[0].equals(widget)) { tree.setSelection(widget); } if (!tree.isFocusControl()) { tree.setFocus(); } } }); notifyTree(SWT.FocusIn, super.createEvent()); notifyTree(SWT.MouseDown, createMouseEvent(x, y, 1, SWT.NONE, 1)); notifyTree(SWT.Selection); notifyTree(SWT.MouseUp, createMouseEvent(x, y, 1, SWT.BUTTON1, 1)); notifyTree(SWT.MouseExit, createMouseEvent(x, y, 0, SWT.NONE, 0)); notifyTree(SWT.Deactivate, super.createEvent()); notifyTree(SWT.FocusOut, super.createEvent()); log.debug(MessageFormat.format("Clicked on {0}", this)); //$NON-NLS-1$ } /** * Clicks on this node. * * @return the current node. * @since 1.2 */ public SWTBotTreeItem click() { assertEnabled(); Point center = getCenter(getCellBounds()); clickXY(center.x, center.y); return this; } /** * Clicks on this node at the given column index. * * @return the current node. * @since 2.0 */ public SWTBotTreeItem click(final int column) { assertEnabled(); Point center = getCenter(getCellBounds(column)); clickXY(center.x, center.y); return this; } /** * Double clicks on this node. * * @return the current node. * @since 1.2 */ public SWTBotTreeItem doubleClick() { assertEnabled(); final Point center = getCenter(getCellBounds()); log.debug(MessageFormat.format("Double-clicking on {0}", this)); //$NON-NLS-1$ notifyTree(SWT.MouseEnter, createMouseEvent(center.x, center.y, 0, SWT.NONE, 0)); notifyTree(SWT.Activate, super.createEvent()); syncExec(new VoidResult() { public void run() { if (tree.getSelectionCount() != 1 || !tree.getSelection()[0].equals(widget)) { tree.setSelection(widget); } if (!tree.isFocusControl()) { tree.setFocus(); } } }); notifyTree(SWT.FocusIn, super.createEvent()); notifyTree(SWT.MouseDown, createMouseEvent(center.x, center.y, 1, SWT.NONE, 1)); notifyTree(SWT.Selection); notifyTree(SWT.MouseUp, createMouseEvent(center.x, center.y, 1, SWT.BUTTON1, 1)); notifyTree(SWT.MouseDown, createMouseEvent(center.x, center.y, 1, SWT.NONE, 2)); notifyTree(SWT.Selection); notifyTree(SWT.MouseDoubleClick, createMouseEvent(center.x, center.y, 1, SWT.NONE, 2)); notifyTree(SWT.DefaultSelection); notifyTree(SWT.MouseUp, createMouseEvent(center.x, center.y, 1, SWT.BUTTON1, 2)); notifyTree(SWT.MouseExit, createMouseEvent(center.x, center.y, 0, SWT.NONE, 0)); notifyTree(SWT.Deactivate, super.createEvent()); notifyTree(SWT.FocusOut, super.createEvent()); log.debug(MessageFormat.format("Double-clicked on {0}", this)); //$NON-NLS-1$ return this; } /** * Get the cell bounds. widget should be enabled before calling this method. * * @param column the tree column index * @return the cell bounds */ private Rectangle getCellBounds(final int column) { return syncExec(new Result<Rectangle>() { public Rectangle run() { return widget.getBounds(column); } }); } /** * Get the cell bounds. widget should be enabled before calling this method. * * @return the cell bounds */ private Rectangle getCellBounds() { return syncExec(new Result<Rectangle>() { public Rectangle run() { return widget.getBounds(); } }); } /** * Get the center of the given rectangle. * * @param bounds the rectangle * @return the center. */ private Point getCenter(Rectangle bounds) { return new Point(bounds.x + (bounds.width / 2), bounds.y + (bounds.height / 2)); } /** * Selects the items matching the array provided. * * @param items the items to select. * @return the current node. * @since 1.0 */ public SWTBotTreeItem select(final String... items) { assertEnabled(); final List<String> nodes = Arrays.asList(items); Assert.isTrue(getNodes().containsAll(nodes)); syncExec(new VoidResult() { public void run() { ArrayList<TreeItem> selection = new ArrayList<TreeItem>(); for (String item : items) { SWTBotTreeItem si = getTreeItem(item); selection.add(si.widget); } tree.setFocus(); tree.setSelection(selection.toArray(new TreeItem[selection.size()])); } }); notifySelect(); return this; } /** * Selects the item matching the given name. * * @param item the items to select. * @return the current node. * @since 1.0 */ public SWTBotTreeItem select(final String item) { return select(new String[] { item }); } /** * notifies the tree widget about selection changes. * * @since 1.0 */ private void notifySelect() { notifyTree(SWT.MouseEnter); notifyTree(SWT.MouseMove); notifyTree(SWT.Activate); notifyTree(SWT.FocusIn); notifyTree(SWT.MouseDown); notifyTree(SWT.Selection); notifyTree(SWT.MouseUp); notifyTree(SWT.MouseHover); notifyTree(SWT.MouseMove); notifyTree(SWT.MouseExit); notifyTree(SWT.Deactivate); } @Override public String getText() { return SWTUtils.getText(widget); } @Override public SWTBotRootMenu contextMenu() { new SWTBotTree(tree).waitForEnabled(); select(); return contextMenu(tree); } /** * Toggle the tree item. * * @since 1.3 */ public void toggleCheck() { setChecked(!isChecked()); } /** * Check the tree item. * * @since 1.3 */ public void check() { setChecked(true); } /** * Uncheck the tree item. * * @since 1.3 */ public void uncheck() { setChecked(false); } /** * Gets if the checkbox button is checked. * * @return <code>true</code> if the checkbox is checked. Otherwise <code>false</code>. * @since 1.3 */ public boolean isChecked() { assertIsCheck(); return syncExec(new BoolResult() { public Boolean run() { return widget.getChecked(); } }); } /** * Creates an event for CheckboxTreeItem case. * * @return an event that encapsulates {@link #widget} and {@link #display}. */ private Event createCheckEvent() { Event event = createEvent(); event.time = (int) System.currentTimeMillis(); event.item = widget; event.widget = tree; event.detail = SWT.CHECK; return event; } private void setChecked(final boolean checked) { assertEnabled(); assertIsCheck(); syncExec(new VoidResult() { public void run() { log.debug(MessageFormat.format("Setting state to {0} on: {1}", (checked ? "checked" : "unchecked"), widget.getText())); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ widget.setChecked(checked); } }); notifyCheck(); } private void assertIsCheck() { Assert.isLegal(hasStyle(tree, SWT.CHECK), "The tree does not have the style SWT.CHECK"); //$NON-NLS-1$ } /** * notify listeners about checkbox state change. * * @since 1.3 */ private void notifyCheck() { syncExec(new VoidResult() { public void run() { tree.notifyListeners(SWT.Selection, createCheckEvent()); } }); } protected void assertEnabled() { new SWTBotTree(tree).assertEnabled(); } /** * @return <code>true</code> if the item is selected, <code>false</code> otherwise. * @since 2.0 */ public boolean isSelected() { return UIThreadRunnable.syncExec(new BoolResult() { public Boolean run() { return Arrays.asList(tree.getSelection()).contains(widget); } }); } /** * Gets if the item is expanded. * * @return <code>true</code> if the item is expanded, <code>false</code> otherwise. * @since 2.0 */ public boolean isExpanded() { assertEnabled(); return UIThreadRunnable.syncExec(new BoolResult() { public Boolean run() { return widget.getExpanded(); } }); } /** * Gets all the items in this tree node. * * @return all the items in this tree node. */ public SWTBotTreeItem[] getItems() { return syncExec(new ArrayResult<SWTBotTreeItem>() { public SWTBotTreeItem[] run() { TreeItem[] items = widget.getItems(); List<SWTBotTreeItem> children = new ArrayList<SWTBotTreeItem>(); for (int i = 0; i < items.length; i++) { if (!items[i].isDisposed()) { children.add(new SWTBotTreeItem(items[i])); } } return children.toArray(new SWTBotTreeItem[children.size()]); } }); } @Override public Color foregroundColor() { return syncExec(new Result<Color>() { public Color run() { return widget.getForeground(); } }); } @Override public Color backgroundColor() { return syncExec(new Result<Color>() { public Color run() { return widget.getBackground(); } }); } /** * Gets the tree item matching the given name. * * @param nodeText the text on the node. * @return the tree item with the specified text. * @throws WidgetNotFoundException if the node was not found. */ private SWTBotTreeItem getTreeItem(final String nodeText) throws WidgetNotFoundException { try { new SWTBot().waitUntil(new DefaultCondition() { public String getFailureMessage() { return "Could not find node with text " + nodeText; //$NON-NLS-1$ } public boolean test() throws Exception { return getItem(nodeText) != null; } }); } catch (TimeoutException e) { throw new WidgetNotFoundException("Timed out waiting for tree item " + nodeText, e); //$NON-NLS-1$ } return new SWTBotTreeItem(getItem(nodeText)); } /** * Gets the item matching the given name. * * @param nodeText the text on the node. * @return the tree item with the specified text. */ private TreeItem getItem(final String nodeText) { return syncExec(new WidgetResult<TreeItem>() { public TreeItem run() { TreeItem[] items = widget.getItems(); for (TreeItem item : items) { if (item.getText().equals(nodeText)) return item; } return null; } }); } public boolean isEnabled() { return syncExec(new BoolResult() { public Boolean run() { return tree.isEnabled(); } }); } public boolean isGrayed() { assertIsCheck(); return syncExec(new BoolResult() { public Boolean run() { return widget.getGrayed(); } }); } @Override protected Rectangle absoluteLocation() { return UIThreadRunnable.syncExec(new Result<Rectangle>() { public Rectangle run() { return display.map(widget.getParent(), null, widget.getBounds()); } }); } }