/******************************************************************************* * Copyright (c) 2012, 2015 Lorenzo Bettini 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: * Stefan Seelmann (initial) * Stefan Schaefer (extension) * Lorenzo Bettini (extracted method to get the MenuItem) * Patrick Tasse - Improve SWTBot menu API and implementation (Bug 479091) *******************************************************************************/ package org.eclipse.swtbot.swt.finder.finders; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.MenuItem; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableColumn; import org.eclipse.swt.widgets.TableItem; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeColumn; import org.eclipse.swt.widgets.TreeItem; import org.eclipse.swt.widgets.Widget; import org.eclipse.swtbot.swt.finder.results.Result; import org.eclipse.swtbot.swt.finder.results.VoidResult; import org.eclipse.swtbot.swt.finder.widgets.AbstractSWTBot; /** * This helper is a workaround for a bug in SWTBot, where the bot can't find a * dynamically created context menu, see also * http://www.eclipse.org/forums/index.php/t/11863/ * * @author Stefan Seelmann (initial) * @author Stefan Schaefer (extension) * @author Lorenzo Bettini (extracted method to get the MenuItem) */ public class ContextMenuHelper { /** * Gets the menu item widget matching the given text path in the given * control's pop up menu. It will attempt to recursively find the menu items * in sequence in the matching sub-menus that are found. * <p> * This is equivalent to bot.contextMenu().menu(texts).widget. * * @param bot * the control to get the context menu from. * @param texts * the texts on the menu items that are to be found. * @return the menu item widget that has the given text. */ public static MenuItem contextMenu(final AbstractSWTBot<? extends Control> bot, final String... texts) { return bot.contextMenu().menu(texts).widget; } /** * Gets the menu item widget matching the given text path in the given * control's pop up menu. It will attempt to recursively find the menu items * in sequence in the matching sub-menus that are found. * <p> * This is equivalent to bot.contextMenu().menu(texts).widget. * * @param bot * the control to get the context menu from. * @param widget * ignored. * @param texts * the texts on the menu items that are to be found. * @since 2.4 * @return the menu item widget that has the given text. */ public static MenuItem contextMenu(final AbstractSWTBot<? extends Control> bot, final Widget widget, final String... texts) { return bot.contextMenu().menu(texts).widget; } /** * Notify the control of SWT.MenuDetect when a context menu occurs on a * widget. The event coordinates are set to the center of the widget. * * @param control * the control that should be notified * @param widget * the widget on which the context menu was triggered on * @since 2.4 */ public static boolean notifyMenuDetect(final Control control, final Widget widget) { Rectangle bounds = getBounds(widget); if (bounds == null) { return false; } final Event event = new Event(); event.time = (int) System.currentTimeMillis(); event.display = control.getDisplay(); event.widget = control; event.x = bounds.x + bounds.width / 2; event.y = bounds.y + bounds.height / 2; UIThreadRunnable.syncExec(new VoidResult() { public void run() { control.notifyListeners(SWT.MenuDetect, event); } }); return event.doit; } /** * Get the bounds of the widget in display-relative coordinates. * * @return the widget bounds or null */ private static Rectangle getBounds(final Widget widget) { return UIThreadRunnable.syncExec(widget.getDisplay(), new Result<Rectangle>() { public Rectangle run() { Control parent; Rectangle widgetBounds; // Control, TableItem, TreeItem, etc don't have a common interface for this if (widget instanceof Control) { parent = ((Control) widget).getParent(); widgetBounds = ((Control) widget).getBounds(); } else if (widget instanceof TableItem) { TableItem tableItem = (TableItem) widget; parent = tableItem.getParent(); widgetBounds = getTableItemBounds(tableItem); } else if (widget instanceof TreeItem) { TreeItem treeItem = (TreeItem) widget; parent = treeItem.getParent(); widgetBounds = getTreeItemBounds(treeItem); } else if (widget instanceof TableColumn) { TableColumn tableColumn = (TableColumn) widget; parent = tableColumn.getParent(); widgetBounds = getTableColumnBounds(tableColumn); // We use the Table's parent coordinate system as it is a // sure way of getting the very top left corner of the Table // widget. The (0, 0) location in Table coordinates is not // consistent across windowing system; sometimes it includes // the header, sometimes not. Rectangle grandParentWidgetBounds = toGrandParentBounds(parent, widgetBounds); return toDisplayBounds(parent.getParent(), grandParentWidgetBounds); } else if (widget instanceof TreeColumn) { TreeColumn treeColumn = (TreeColumn) widget; parent = treeColumn.getParent(); widgetBounds = getTreeColumnBounds(treeColumn); Rectangle grandParentWidgetBounds = toGrandParentBounds(parent, widgetBounds); return toDisplayBounds(parent.getParent(), grandParentWidgetBounds); } else { return null; } return toDisplayBounds(parent, widgetBounds); } /** * Convert the bounds (parent-relative) to the grand-parent-relative * bounds by using the parent location. */ private Rectangle toGrandParentBounds(Control parent, Rectangle bounds) { Point parentLocation = parent.getLocation(); return new Rectangle(parentLocation.x + bounds.x, parentLocation.y + bounds.y, bounds.width, bounds.height); } /** * Convert the bounds (parent-relative) to display-relative bounds. */ private Rectangle toDisplayBounds(Control parent, Rectangle bounds) { Point location = new Point(bounds.x, bounds.y); if (parent != null) { location = parent.toDisplay(location); } return new Rectangle(location.x, location.y, bounds.width, bounds.height); } /** * For both table and tree items, we consider the bounds to be from * the start of the table to the end of the last column. On some * platforms, there can be empty space between the last column and * the end of the table. */ private Rectangle getTableItemBounds(TableItem tableItem) { Table table = tableItem.getParent(); int[] columnOrder = table.getColumnOrder(); Rectangle itemBounds; if (columnOrder.length > 0) { // Use the bounds of the last column to know where the item really ends int lastColumnIndex = columnOrder[columnOrder.length - 1]; itemBounds = tableItem.getBounds(lastColumnIndex); itemBounds.width = itemBounds.x + itemBounds.width; } else { Rectangle tableBounds = table.getBounds(); itemBounds = tableItem.getBounds(); itemBounds.width = tableBounds.width; } itemBounds.x = 0; return itemBounds; } /** * See {@link #getTableItemBounds} */ private Rectangle getTreeItemBounds(TreeItem treeItem) { Tree tree = treeItem.getParent(); int[] columnOrder = tree.getColumnOrder(); Rectangle itemBounds; if (columnOrder.length > 0) { // Use the bounds of the last column to know where the item really ends int lastColumnIndex = columnOrder[columnOrder.length - 1]; itemBounds = treeItem.getBounds(lastColumnIndex); itemBounds.width = itemBounds.x + itemBounds.width; } else { Rectangle treeBounds = tree.getBounds(); itemBounds = treeItem.getBounds(); itemBounds.width = treeBounds.width; } itemBounds.x = 0; return itemBounds; } private Rectangle getTableColumnBounds(TableColumn tablecolumn) { Table parent = tablecolumn.getParent(); Rectangle bounds = new Rectangle(0, 0, tablecolumn.getWidth(), parent.getHeaderHeight()); for (int i : parent.getColumnOrder()) { TableColumn column = parent.getColumn(i); if (column.equals(widget)) { break; } else { bounds.x += column.getWidth(); } } return bounds; } private Rectangle getTreeColumnBounds(TreeColumn treecolumn) { Tree parent = treecolumn.getParent(); Rectangle bounds = new Rectangle(0, 0, treecolumn.getWidth(), parent.getHeaderHeight()); for (int i : parent.getColumnOrder()) { TreeColumn column = parent.getColumn(i); if (column.equals(widget)) { break; } else { bounds.x += column.getWidth(); } } return bounds; } }); } }