/*******************************************************************************
* Copyright (c) 2004, 2010 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 and/or initial documentation
*******************************************************************************/
package org.eclipse.jubula.rc.swt.utils;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jubula.rc.common.AUTServer;
import org.eclipse.jubula.rc.common.driver.IEventThreadQueuer;
import org.eclipse.jubula.rc.common.driver.IRunnable;
import org.eclipse.jubula.rc.common.exception.StepExecutionException;
import org.eclipse.jubula.rc.swt.SwtAUTServer;
import org.eclipse.jubula.rc.swt.driver.EventThreadQueuerSwtImpl;
import org.eclipse.jubula.rc.swt.driver.KeyCodeConverter;
import org.eclipse.jubula.toolkit.enums.ValueSets;
import org.eclipse.jubula.tools.internal.utils.EnvironmentUtils;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CCombo;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.dnd.DragSource;
import org.eclipse.swt.dnd.DropTarget;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Caret;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.CoolBar;
import org.eclipse.swt.widgets.CoolItem;
import org.eclipse.swt.widgets.Decorations;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Item;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.swt.widgets.Scrollable;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.TabFolder;
import org.eclipse.swt.widgets.TabItem;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.swt.widgets.Tracker;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author BREDEX GmbH
* @created 19.07.2006
*/
public class SwtUtils {
/** Constant for identification of win32 graphics toolkit */
private static final String WIN32 = "win32"; //$NON-NLS-1$
/** Constant for identification of GTK graphics toolkit */
private static final String GTK = "gtk"; //$NON-NLS-1$
/** name of method to use for retrieving the bounds of a component */
private static final String METHOD_NAME_GET_BOUNDS = "getBounds"; //$NON-NLS-1$
/** the logger */
private static Logger log = LoggerFactory.getLogger(SwtUtils.class);
/**
* Utility constructor
*/
private SwtUtils() {
// do nothing
}
/**
* Gets the Widget under the current mouse position or null.
* @return the Widget under the current mouse position or null.
*/
public static Widget getWidgetAtCursorLocation() {
final Display display = ((SwtAUTServer)AUTServer.getInstance())
.getAutDisplay();
if (display == null || display.isDisposed()) {
return null;
}
Control control = display.getCursorControl();
if (control == null) {
return null;
}
control = checkControlParent(control);
Widget widget = checkControlChildrenAtCursorLocation(control);
return widget;
}
/**
* Calls <code>getWidgetAtCursorLocation()</code> in the GUI thread.
*
* @return the result of the called method.
*/
public static Widget invokeGetWidgetAtCursorLocation() {
IEventThreadQueuer evThreadQueuer = new EventThreadQueuerSwtImpl();
Widget widget = evThreadQueuer.invokeAndWait("getWidgetAtCursorLocation", //$NON-NLS-1$
new IRunnable<Widget>() {
public Widget run() throws StepExecutionException {
return getWidgetAtCursorLocation();
}
});
return widget;
}
/**
* @return the Control under the cursor
*/
public static Control getCursorControl() {
IEventThreadQueuer evThreadQueuer = new EventThreadQueuerSwtImpl();
return evThreadQueuer.invokeAndWait("getCursorControl", //$NON-NLS-1$
new IRunnable<Control>() {
public Control run() {
Display disp = Display.getCurrent();
if (disp == null || disp.isDisposed()) {
return null;
}
return disp.getCursorControl();
}
});
}
/**
* Returns when there are no more events for the display to process.
*
* @param display The display to wait on.
*/
public static void waitForDisplayIdle(final Display display) {
display.syncExec(new Runnable() {
public void run() {
while (!display.isDisposed() && display.readAndDispatch()) {
display.readAndDispatch();
}
display.update();
}
});
}
/**
* @param widget a Widget
* @return the items of the given Widget or an empty Array
* if the given Widget does not contain any Items
*/
public static Item[] getMappableItems(Widget widget) {
try {
if (widget instanceof ToolBar) {
return ((ToolBar)widget).getItems();
}
if (widget instanceof CoolBar) {
return ((CoolBar)widget).getItems();
}
} catch (NullPointerException e) {
// Do nothing. This occurs when the widget (ToolBar) is currently
// being disposed, as the "items" field (ToolBar.items) is null.
// See Ticket #2160.
}
return new Item[0];
}
/**
* @param widget a Widget
* @return the Shell of the given Widget or null if no Shell was found
* @see Control#getShell();
*/
public static Shell getShell(Widget widget) {
Widget wdgt = widget;
while (!(wdgt instanceof Control) && wdgt != null) {
wdgt = getWidgetParent(wdgt);
}
if (wdgt != null && !wdgt.isDisposed()) {
return ((Control)wdgt).getShell();
}
return null;
}
/**
* Checks the given Control if there is a child at the current
* Mouseposition which cannot be found
* via {@link Display#getCursorControl()}, e.g. Tool-/CoolbarItem,
* disabled Button, etc. and returns this Widget.<br>
* If the given Control does not contain any other controls, the given
* Control will be returned.
* @param control the Control to check.
* @return a Widget.
*/
private static Widget checkControlChildrenAtCursorLocation(
final Control control) {
Item[] items = getMappableItems(control);
if (items == null) {
return control;
}
final Point absMousePos = control.getDisplay().getCursorLocation();
for (int i = 0; i < items.length; i++) {
Item item = items[i];
Rectangle itemBounds = getWidgetBounds(item);
if (itemBounds.contains(absMousePos)) {
return item;
}
}
if (control instanceof Composite) {
Composite composite = (Composite)control;
Control[] children = composite.getChildren();
for (int i = 0; i < children.length; i++) {
Control ctrl = children[i];
if (getWidgetBounds(ctrl).contains(absMousePos)) {
return checkControlChildrenAtCursorLocation(ctrl);
}
}
}
return control;
}
/**
* Checks the given Control if its parent is a composite that should
* be treated as the actual component. For example, if <code>control</code>
* is a Button which is part of a <code>CCombo</code>, we treat the
* <code>CCombo</code> as the component.
* If <code>control</code> is not part of such a composite,
* <code>control</code> will be returned.
* @param control the Control to check.
* @return the Control to be treated as the actual component.
*/
public static Control checkControlParent(final Control control) {
IEventThreadQueuer evThreadQueuer = new EventThreadQueuerSwtImpl();
return evThreadQueuer.invokeAndWait("checkControlParent", //$NON-NLS-1$
new IRunnable<Control>() {
public Control run() throws StepExecutionException {
if (control != null && !control.isDisposed()) {
Control parent = control.getParent();
if (parent instanceof CCombo
|| parent instanceof Table) { // children could be a
// TableEditor?
return parent;
}
}
return control;
}
});
}
/**
* This method runs in the GUI-Thread.
*
* @param widget the widget to get the parent for
* @return the parent widget, or <code>null</code> if the given widget has
* already been disposed.
*/
public static Widget getWidgetParent(final Widget widget) {
IEventThreadQueuer evThreadQueuer = new EventThreadQueuerSwtImpl();
Widget parent = evThreadQueuer.invokeAndWait("getWidgetParent", //$NON-NLS-1$
new IRunnable<Widget>() {
public Widget run() throws StepExecutionException {
if (widget == null || widget.isDisposed()) {
return null;
}
return getWidgetParentImpl(widget);
}
});
return parent;
}
/**
* Look up the apparent parent of a component. A popup menu's parent is the
* menu or component that spawned it.
*
* @param widget the widget to get the parent for
* @return the parent widget
*/
private static Widget getWidgetParentImpl(Widget widget) {
Widget parent = null;
if (widget == null) {
log.error("Cannot get parent for null widget."); //$NON-NLS-1$
}
if (widget instanceof Control) {
parent = ((Control)widget).getParent();
} else if (widget instanceof Caret) {
parent = ((Caret)widget).getParent();
} else if (widget instanceof Menu) {
parent = ((Menu)widget).getParent();
} else if (widget instanceof ScrollBar) {
parent = ((ScrollBar)widget).getParent();
} else if (widget instanceof CoolItem) {
parent = ((CoolItem)widget).getParent();
} else if (widget instanceof MenuItem) {
parent = ((MenuItem)widget).getParent();
} else if (widget instanceof TabItem) {
parent = ((TabItem)widget).getParent();
} else if (widget instanceof TableColumn) {
parent = ((TableColumn)widget).getParent();
} else if (widget instanceof TableItem) {
parent = ((TableItem)widget).getParent();
} else if (widget instanceof ToolItem) {
parent = ((ToolItem)widget).getParent();
} else if (widget instanceof TreeItem) {
parent = ((TreeItem)widget).getParent();
} else if (widget instanceof DragSource) {
parent = ((DragSource)widget).getControl().getParent();
} else if (widget instanceof DropTarget) {
parent = ((DropTarget)widget).getControl().getParent();
} else if (widget instanceof Tracker) {
log.error("requested the parent of a Tracker- UNFINDABLE"); //$NON-NLS-1$
}
return parent;
}
/**
* Returns a point describing the receiver's location in
* absolute/display/screen coordinates.
*
* @param widget a widget
* @return the widget's location
*/
public static Point getWidgetLocation(Widget widget) {
Rectangle bounds = getWidgetBounds(widget);
return new Point(bounds.x, bounds.y);
}
/**
* Look up the bounds of <code>widget</code>. The location/origin of these bounds
* is relative to the location/origin of <code>relativeTo</code>.
* @param widget the widget to get the bounds for. If this value is
* <code>null</code>, <code>null</code> will be returned.
* @param relativeTo the Control to which the returned bounds are relative.
* May be <code>null</code>. If this value is
* <code>null</code>, the returned bounds are "relative"
* to the Display (which means they are essentially
* absolute).
* @return the bounds of <code>widget</code> relative to
* <code>relativeTo</code>, or <code>null</code> if the bounds
* cannot be determined (for example, if <code>widget</code> is
* <code>null</code>).
*/
public static Rectangle getRelativeWidgetBounds(final Widget widget,
final Control relativeTo) {
Rectangle absoluteBounds = getWidgetBounds(widget);
if (absoluteBounds == null || relativeTo == null) {
return absoluteBounds;
}
return relativeTo.getDisplay().map(null, relativeTo, absoluteBounds);
}
/**
* Look up the bounds of a component. These bounds are in
* absolute/screen/display coordinates, <b>NOT</b> relative/local/component
* coordinates.
* @param widget the widget to get the bounds for. If this value is
* <code>null</code>, <code>null</code> will be returned.
* @return the bounds of the widget in display coordinates, or
* <code>null</code> if the bounds cannot be determined
* (for example, if the given widget is null).
*/
public static Rectangle getWidgetBounds(final Widget widget) {
Rectangle bounds = null;
if (widget == null) {
log.debug("Trying to find bounds for null Widget. Null bounds returned"); //$NON-NLS-1$
return null;
}
if (widget instanceof Shell) {
bounds = getBounds((Shell)widget);
} else if (widget instanceof Control) {
bounds = getBounds((Control)widget);
} else if (widget instanceof Caret) {
bounds = getBounds((Caret)widget);
} else if (widget instanceof Menu) {
bounds = getBounds((Menu)widget);
} else if (widget instanceof ScrollBar) {
bounds = getBounds((ScrollBar)widget);
} else if (widget instanceof CoolItem) {
bounds = getBounds((CoolItem)widget);
} else if (widget instanceof MenuItem) {
bounds = getBounds((MenuItem)widget);
} else if (widget instanceof TabItem) {
bounds = getBounds((TabItem)widget);
} else if (widget instanceof CTabItem) {
bounds = getBounds((CTabItem)widget);
} else if (widget instanceof TableColumn) {
bounds = getBounds((TableColumn)widget);
} else if (widget instanceof TableItem) {
bounds = getBounds((TableItem)widget);
} else if (widget instanceof ToolItem) {
bounds = getBounds((ToolItem)widget);
} else if (widget instanceof TreeItem) {
bounds = getBounds((TreeItem)widget);
} else {
if (log.isDebugEnabled()) {
log.debug("Tried to find bounds for unknown component type: " //$NON-NLS-1$
+ widget.getClass().getName()
+ ". Returning null bounds."); //$NON-NLS-1$
}
}
return bounds;
}
/**
* This method runs in the GUI-Thread.
* @param widget the widget to get the children for
* @param recurse true or false
* @return a linked list with all children of the given widget
*/
public static Widget[] getWidgetChildren(final Widget widget,
final boolean recurse) {
IEventThreadQueuer evThreadQueuer = new EventThreadQueuerSwtImpl();
Widget[] widgets = evThreadQueuer.invokeAndWait("getWidgetChildren", //$NON-NLS-1$
new IRunnable<Widget[]>() {
public Widget[] run() throws StepExecutionException {
if (widget == null || widget.isDisposed()) {
return new Widget[] {};
}
return getWidgetChildrenImpl(widget, recurse);
}
});
return widgets;
}
/**
* @param widget the widget to get the children for
* @param recurse true or false
* @return a linked list with all children of the given widget
*/
private static Widget[] getWidgetChildrenImpl(final Widget widget,
final boolean recurse) {
List<Widget> objT = new LinkedList<Widget>();
if (widget == null || widget.isDisposed()) {
return new Widget[0];
}
if (widget instanceof Control && ((Control)widget).getMenu() != null) {
objT.add(((Control)widget).getMenu());
}
if (widget instanceof Scrollable) {
Scrollable scrollable = (Scrollable) widget;
if (scrollable.getVerticalBar() != null) {
objT.add(scrollable.getVerticalBar());
}
if (scrollable.getHorizontalBar() != null) {
objT.add(scrollable.getHorizontalBar());
}
}
if (widget instanceof Decorations
&& ((Decorations)widget).getMenuBar() != null) {
objT.add(((Decorations)widget).getMenuBar());
}
if (widget instanceof Composite) {
Widget[] widgets = ((Composite)widget).getChildren();
if (widgets.length != 0) {
objT.addAll(Arrays.asList(widgets));
}
}
if (widget instanceof CoolBar) {
Widget[] widgets = ((CoolBar)widget).getItems();
if (widgets.length != 0) {
objT.addAll(Arrays.asList(widgets));
}
}
if (widget instanceof TabFolder) {
Widget[] widgets = ((TabFolder)widget).getItems();
if (widgets.length != 0) {
objT.addAll(Arrays.asList(widgets));
}
}
if (widget instanceof Table) {
Table table = (Table) widget;
Widget[] widgets = table.getItems();
if (widgets.length != 0) {
objT.addAll(Arrays.asList(widgets));
}
widgets = table.getColumns();
if (widgets.length != 0) {
objT.addAll(Arrays.asList(widgets));
}
}
if (widget instanceof ToolBar) {
Widget[] widgets = ((ToolBar)widget).getItems();
if (widgets.length != 0) {
objT.addAll(Arrays.asList(widgets));
}
}
if (widget instanceof Tree) {
Widget[] widgets = ((Tree)widget).getItems();
if (widgets.length != 0) {
objT.addAll(Arrays.asList(widgets));
}
}
addNonControlChildren(widget, objT);
if (recurse && objT.size() > 0) {
List<Widget> extendedFamily = new LinkedList<Widget>();
for (int i = 0; i < objT.size(); i++) {
Widget w = objT.get(i);
extendedFamily.addAll(Arrays.asList(getWidgetChildrenImpl(
w, recurse)));
}
objT.addAll(extendedFamily);
}
return (objT.toArray(new Widget[objT.size()]));
}
/**
* @param widget the non-Control-widget to get the children for
* @param objT linkedList to put the children in
*/
private static void addNonControlChildren(final Widget widget,
List<Widget> objT) {
if (widget instanceof TreeItem) {
Widget[] widgets = ((TreeItem)widget).getItems();
if (widgets.length != 0) {
objT.addAll(Arrays.asList(widgets));
}
}
if (widget instanceof Menu) {
Widget[] widgets = ((Menu)widget).getItems();
if (widgets.length != 0) {
objT.addAll(Arrays.asList(widgets));
}
}
if (widget instanceof MenuItem) {
Widget childMenu = ((MenuItem)widget).getMenu();
if (childMenu != null) {
objT.add(childMenu);
}
}
}
/**
* @param widget the widget to get the shell for
* @return the component's owning shell. There will <b>always</b> one of these.
*/
public static Shell getWidgetShell(Widget widget) {
Widget parent = widget;
while (!(parent instanceof Shell) && getWidgetParent(parent) != null) {
parent = getWidgetParent(parent);
}
return (Shell)parent;
}
/**
* @param control the control to get bounds for
* @return the bounds for the given control
*/
public static Rectangle getBounds(Control control) {
Rectangle bounds = control.getDisplay().map(
control.getParent(), null, control.getBounds());
bounds = getActiveArea(control, bounds);
return bounds;
}
/**
* Gets the active area of the given Control with the given bounds.
* @param control the Control
* @param bounds the bounds of the given Control.
* @return a Rectangle, the border of the active area.
*/
private static Rectangle getActiveArea(Control control, Rectangle bounds) {
final int borderWidth = control.getBorderWidth();
if (borderWidth > 0) {
bounds.x += borderWidth;
bounds.y += borderWidth;
final int dimAdjustment = (2 * borderWidth);
bounds.height -= dimAdjustment;
bounds.width -= dimAdjustment;
}
return bounds;
}
/**
* @param caret the caret to get bounds for
* @return the bounds for the given caret
*/
public static Rectangle getBounds(Caret caret) {
return caret.getDisplay().map(
caret.getParent(), null, caret.getBounds());
}
/**
*
* @param shell the shell to get bounds for
* @return the bounds for the given shell
*/
public static Rectangle getBounds(Shell shell) {
final Rectangle clientArea = shell.getClientArea();
final Rectangle bounds = shell.getBounds();
final int titleBarHeight = bounds.height - clientArea.height;
final int widthDiff = bounds.width - clientArea.width;
// set upper left edge:
clientArea.x = bounds.x + (widthDiff / 2);
final int borderWidth = shell.getBorderWidth();
clientArea.y = bounds.y + titleBarHeight - borderWidth * 2;
// adjust height:
clientArea.height -= borderWidth;
return clientArea;
}
/*
* BEGIN CODE ADAPTED FROM http://eclip.se/38436#c153
*/
/*************************** COMMON *****************************/
/**
* Tries to use reflection to invoke getBounds() on the given Object and
* returns the result.
* @param object The Object for which to find the bounds.
* @return the bounds, or (0, 0, 0, 0) if the bounds cannot be retrieved.
*/
private static Rectangle getBounds(Object object) {
Rectangle result = new Rectangle(0, 0, 0, 0);
String methodName = "getBounds"; //$NON-NLS-1$
try {
Method method = object.getClass().getDeclaredMethod(methodName,
null);
method.setAccessible(true);
result = (Rectangle) method.invoke(object, null);
} catch (Exception e) {
return handleBoundsError(e);
}
return result;
}
/**
* Workaround for package scope of MenuItem.getBounds(). Returns a rectangle
* describing the receiver's size and location relative to the display.
* @param menuItem the menu item to get bounds for
* @return the receiver's bounding rectangle
*
* @since 3.1
*/
public static Rectangle getBounds(MenuItem menuItem) {
Rectangle itemRect = getBounds((Object)menuItem);
Rectangle menuRect = getBounds(menuItem.getParent());
if ((menuItem.getParent().getStyle() & SWT.RIGHT_TO_LEFT) != 0) {
itemRect.x = menuRect.x + menuRect.width
- itemRect.width - itemRect.x;
} else {
itemRect.x += menuRect.x;
}
itemRect.y += menuRect.y;
return itemRect;
}
/**
* Workaround for lack of Menu.getBounds().
*
* Returns a rectangle describing the receiver's size and location relative
* to the display.
* <p>
* Note that the bounds of a menu or menu item are undefined when the menu
* is not visible. This is because most platforms compute the bounds of a
* menu dynamically just before it is displayed.
* </p>
* @param menu the menu to get bounds for
* @return the receiver's bounding rectangle
*/
public static Rectangle getBounds(Menu menu) {
return getBounds((Object)menu);
}
/**
* Workaround for lack of ScrollBar.getBounds().
* @param scrollBar the scrollbar to get bounds for.
* @return the rectagle bounds of the scrollbar, in display coordinates
*/
public static Rectangle getBounds(ScrollBar scrollBar) {
Point size = scrollBar.getSize();
Rectangle bounds = scrollBar.getParent().getBounds();
if ((scrollBar.getParent().getStyle() & SWT.RIGHT_TO_LEFT) != 0) {
bounds.x = 0;
bounds.width = size.x;
} else {
bounds.x = bounds.width - size.x;
}
bounds.y = bounds.height - size.y;
// FIXME zeb: coordinate system may change when the API is added to SWT
return scrollBar.getDisplay().map(
scrollBar.getParent(), null, bounds);
}
/*************************** WIN32 *****************************/
/**
* Sends a message to the win32 window manager. This method is only for
* win32.
*
* @param hWnd The handle of the component to send the message to.
* @param msg The message to send.
* @param wParam wParam
* @param lParam lParam
*
* @return the response to the message.
*/
static int sendMessage(int hWnd, int msg, int wParam, int [] lParam)
throws Exception {
int result = 0;
String methodName = "SendMessage"; //$NON-NLS-1$
String className = "org.eclipse.swt.internal.win32.OS"; //$NON-NLS-1$
Class clazz = Class.forName(className);
Class [] params = new Class [] {
Integer.TYPE,
Integer.TYPE,
Integer.TYPE,
lParam.getClass(),
};
Method method = clazz.getMethod(methodName, params);
Object [] args = new Object [] {
new Integer(hWnd),
new Integer(msg),
new Integer(wParam),
lParam,
};
result = ((Integer) method.invoke(clazz, args)).intValue();
return result;
}
/**
* Win32-specific workaround for getting the bounds of a TabItem.
*
* @param tabItem The TabItem to find the bounds for.
* @return the bounding rectangle
*/
static Rectangle win32getBounds(TabItem tabItem) {
TabFolder parent = tabItem.getParent();
int index = parent.indexOf(tabItem);
if (index == -1) {
return new Rectangle(0, 0, 0, 0);
}
int [] rect = new int [4];
try {
sendMessage((int) parent.handle, /*TCM_GETITEMRECT*/ 0x130a,
index, rect);
int width = rect [2] - rect[0];
int height = rect [3] - rect [1];
Rectangle bounds = new Rectangle(rect [0], rect [1], width, height);
return tabItem.getDisplay().map(tabItem.getParent(), null, bounds);
} catch (Exception e) {
return handleBoundsError(e);
}
}
/**
* Win32-specific workaround for getting the bounds of a TableColumn.
*
* @param tableColumn The TableColumn to find the bounds for.
* @return the bounding rectangle
*/
static Rectangle win32getBounds(TableColumn tableColumn) {
Table parent = tableColumn.getParent();
int index = parent.indexOf(tableColumn);
if (index == -1) {
return new Rectangle(0, 0, 0, 0);
}
try {
int hwndHeader = sendMessage((int)parent.handle,
/*LVM_GETHEADER*/ 0x101f,
0, new int [0]);
int [] rect = new int [4];
sendMessage(hwndHeader,
/*HDM_GETITEMRECT*/ 0x1200 + 7, index, rect);
int width = rect [2] - rect[0];
int height = rect [3] - rect [1];
Rectangle bounds = new Rectangle(rect [0], rect [1], width, height);
// FIXME zeb: coordinate system may change when the API is added to SWT
int hwndTable = (int)parent.handle;
try {
parent.handle = hwndHeader;
return tableColumn.getDisplay().map(parent, null, bounds);
} finally {
parent.handle = hwndTable;
}
} catch (Exception e) {
return handleBoundsError(e);
}
}
/**
* Win32-specific workaround for getting the bounds of a TreeColumn.
*
* @param treeColumn The TreeColumn to find the bounds for.
* @return the bounding rectangle
*/
static Rectangle win32getBounds(TreeColumn treeColumn) {
Tree parent = treeColumn.getParent();
int index = parent.indexOf(treeColumn);
if (index == -1) {
return new Rectangle(0, 0, 0, 0);
}
int hwndHeader = 0;
try {
Class<? extends Tree> clazz = parent.getClass();
Field f = clazz.getDeclaredField("hwndHeader"); //$NON-NLS-1$
f.setAccessible(true);
hwndHeader = f.getInt(parent);
int [] rect = new int [4];
sendMessage(hwndHeader,
/*HDM_GETITEMRECT*/ 0x1200 + 7, index, rect);
int width = rect [2] - rect[0];
int height = rect [3] - rect [1];
Rectangle bounds = new Rectangle(rect [0], rect [1], width, height);
bounds.y -= treeColumn.getParent().getHeaderHeight();
return treeColumn.getDisplay().map(parent, null, bounds);
} catch (Exception e) {
return handleBoundsError(e);
}
}
/*************************** GTK *****************************/
/**
* GTK-specific method for getting the bounds of a component. This method
* is only for GTK.
* @param handle The handle of the component to get the bounds for.
* @param bounds The bounds will be written to this rectangle since there
* is no return value.
*/
static void gtkgetBounds(int handle, Rectangle bounds)
throws Exception {
Class clazz = Class.forName("org.eclipse.swt.internal.gtk.OS"); //$NON-NLS-1$
Class [] params = new Class [] {Integer.TYPE};
Object [] args = new Object [] {new Integer(handle)};
try {
Method method = clazz.getMethod("gtkWIDGET_X", params); //$NON-NLS-1$
bounds.x = ((Integer) method.invoke(clazz, args)).intValue();
method = clazz.getMethod("gtkWIDGET_Y", params); //$NON-NLS-1$
bounds.y = ((Integer) method.invoke(clazz, args)).intValue();
method = clazz.getMethod("gtkWIDGET_WIDTH", params); //$NON-NLS-1$
bounds.width = ((Integer) method.invoke(clazz, args)).intValue();
method = clazz.getMethod("gtkWIDGET_HEIGHT", params); //$NON-NLS-1$
bounds.height = ((Integer) method.invoke(clazz, args)).intValue();
} catch (NoSuchMethodException nsme) {
Method method = clazz.getMethod("GTK_WIDGET_X", params); //$NON-NLS-1$
bounds.x = ((Integer) method.invoke(clazz, args)).intValue();
method = clazz.getMethod("GTK_WIDGET_Y", params); //$NON-NLS-1$
bounds.y = ((Integer) method.invoke(clazz, args)).intValue();
method = clazz.getMethod("GTK_WIDGET_WIDTH", params); //$NON-NLS-1$
bounds.width = ((Integer) method.invoke(clazz, args)).intValue();
method = clazz.getMethod("GTK_WIDGET_HEIGHT", params); //$NON-NLS-1$
bounds.height = ((Integer) method.invoke(clazz, args)).intValue();
}
}
/**
* GTK-specific workaround for getting the bounds of a TableColumn.
*
* @param tableColumn The TableColumn to find the bounds for.
* @return the bounding rectangle
*/
static Rectangle gtkgetBounds(TableColumn tableColumn) {
Rectangle bounds = new Rectangle(0, 0, 0, 0);
try {
Class<? extends TableColumn> clazz = tableColumn.getClass();
Field f = clazz.getDeclaredField("buttonHandle"); //$NON-NLS-1$
f.setAccessible(true);
int handle = f.getInt(tableColumn);
gtkgetBounds(handle, bounds);
bounds.y -= tableColumn.getParent().getHeaderHeight();
return tableColumn.getDisplay().map(
tableColumn.getParent(), null, bounds);
} catch (Exception e) {
return handleBoundsError(e);
}
}
/**
* GTK-specific workaround for getting the bounds of a TreeColumn.
*
* @param treeColumn The TreeColumn to find the bounds for.
* @return the bounding rectangle
*/
static Rectangle gtkgetBounds(TreeColumn treeColumn) {
Rectangle bounds = new Rectangle(0, 0, 0, 0);
try {
Class<? extends TreeColumn> clazz = treeColumn.getClass();
Field f = clazz.getDeclaredField("buttonHandle"); //$NON-NLS-1$
f.setAccessible(true);
int handle = f.getInt(treeColumn);
gtkgetBounds(handle, bounds);
bounds.y -= treeColumn.getParent().getHeaderHeight();
return treeColumn.getDisplay().map(
treeColumn.getParent(), null, bounds);
} catch (Exception e) {
return handleBoundsError(e);
}
}
/**
* GTK-specific workaround for getting the bounds of a TabItem.
*
* @param tabItem The TabItem to find the bounds for.
* @return the bounding rectangle
*/
static Rectangle gtkgetBounds(TabItem tabItem) {
Rectangle bounds = new Rectangle(0, 0, 0, 0);
try {
Class clazz = Class.forName("org.eclipse.swt.widgets.Widget"); //$NON-NLS-1$
Field f = clazz.getDeclaredField("handle"); //$NON-NLS-1$
f.setAccessible(true);
int handle = f.getInt(tabItem);
gtkgetBounds(handle, bounds);
return tabItem.getDisplay().map(tabItem.getParent(), null, bounds);
} catch (Exception e) {
return handleBoundsError(e);
}
}
/**
* Workaround for lack of TabItem.getBounds().
*
* Returns a rectangle describing the TabItem's size and location in display
* coordinates.
* @param tabItem the tab item to get bounds for
* @return the bounding rectangle
*
*/
public static Rectangle getBounds(TabItem tabItem) {
// first try to use getBounds() directly. this should work in most cases, as it is implemented in SWT since 3.4.
try {
Method getBoundsMethod = tabItem.getClass().getMethod(
METHOD_NAME_GET_BOUNDS, null);
Object result = getBoundsMethod.invoke(tabItem, null);
if (result instanceof Rectangle) {
Rectangle bounds = (Rectangle)result;
TabFolder parent = tabItem.getParent();
// The Cocoa implementation of SWT seems to deliver bounds with
// a blatantly incorrect y-value. The fix is to adjust the
// y-value based on the style of the parent TabFolder.
bounds.y = (parent.getStyle() & SWT.BOTTOM) == SWT.BOTTOM
? parent.getBounds().y - bounds.height : 0;
return tabItem.getDisplay().map(
tabItem.getParent(), null, bounds);
}
String className = result != null
? result.getClass().getName() : "null"; //$NON-NLS-1$
log.error("Expected getBounds() to return an object of type 'Rectangle', but received object of type '" //$NON-NLS-1$
+ className + "'."); //$NON-NLS-1$
// fall through
} catch (Exception e) {
log.warn("Could not invoke getBounds() on TabItem. This is expected when using an SWT version lower than 3.4", e); //$NON-NLS-1$
}
if (SWT.getPlatform().equals(WIN32)) {
return win32getBounds(tabItem);
}
if (SWT.getPlatform().equals(GTK)) {
return gtkgetBounds(tabItem);
}
return null;
}
/**
* Returns a rectangle describing the TableColumn's size and location in
* display coordinates.
* @param tableColumn the TableColumn to get bounds for
* @return the bounding rectangle
*
*/
public static Rectangle getBounds(TableColumn tableColumn) {
if (SWT.getPlatform().equals(WIN32)) {
return win32getBounds(tableColumn);
}
if (SWT.getPlatform().equals(GTK)) {
return gtkgetBounds(tableColumn);
}
return null;
}
/**
* Returns a rectangle describing the TreeColumn's size and location in
* display coordinates.
* @param treeColumn the TreeColumn to get bounds for
* @return the bounding rectangle
*
*/
public static Rectangle getBounds(TreeColumn treeColumn) {
if (SWT.getPlatform().equals(WIN32)) {
return win32getBounds(treeColumn);
}
if (SWT.getPlatform().equals(GTK)) {
return gtkgetBounds(treeColumn);
}
return null;
}
/**
* Returns a rectangle describing the TableItem's size and location in
* display coordinates.
* @param item the TableItem to get bounds for
* @return the bounding rectangle
*
*/
public static Rectangle getBounds(TableItem item) {
// FIXME zeb the method TableItem.getBounds() was introduced in
// SWT 3.2. We currently compile the AUT Server with
// SWT 3.1. When we abandon SWT 3.1, we should probably
// use getBounds() instead of getBounds(int), as it is
// more accurate.
Rectangle bounds = item.getDisplay().map(
item.getParent(), null, item.getBounds(0));
return bounds;
}
/**
* Returns a rectangle describing the TreeItem's size and location in
* display coordinates.
* @param item the TreeItem to get bounds for
* @return the bounding rectangle
*
*/
public static Rectangle getBounds(TreeItem item) {
Rectangle bounds = item.getBounds();
// Works around an issue where
// the width of the item is computed as "1" by SWT when the
// columns are actually the only elements that have width.
if (item.getParent().getColumnCount() > 0) {
bounds.width = item.getParent().getBounds().width;
}
return item.getDisplay().map(
item.getParent(), null, bounds);
}
/**
* Returns a rectangle describing the TreeItem's size and location in
* display coordinates for the given column.
* @param item the TreeItem to get bounds for
* @param column the column for the TreeItem
* @return the bounding rectangle
*
*/
public static Rectangle getBounds(TreeItem item, int column) {
Rectangle bounds = item.getBounds(column);
return item.getDisplay().map(
item.getParent(), null, bounds);
}
/**
* Returns a rectangle describing the TreeItem's size and location,
* relative to its parent, for the given column.
* @param item the TreeItem to get bounds for
* @param column the column for the TreeItem
* @return the bounding rectangle
*
*/
public static Rectangle getRelativeBounds(TreeItem item, int column) {
Rectangle absoluteBounds = getBounds(item, column);
return item.getDisplay().map(
null, item.getParent(), absoluteBounds);
}
/**
* Returns a rectangle describing the CTabItem's size and location in
* display coordinates.
* @param item the CTabItem to get bounds for
* @return the bounding rectangle
*
*/
public static Rectangle getBounds(CTabItem item) {
return item.getDisplay().map(
item.getParent(), null, item.getBounds());
}
/**
* Returns a rectangle describing the ToolItem's size and location in
* display coordinates.
* @param item the ToolItem to get bounds for
* @return the bounding rectangle
*
*/
public static Rectangle getBounds(ToolItem item) {
return item.getDisplay().map(
item.getParent(), null, item.getBounds());
}
/**
* Returns a rectangle describing the CoolItem size and location in
* display coordinates.
* @param item the CoolItem to get bounds for
* @return the bounding rectangle
*
*/
public static Rectangle getBounds(CoolItem item) {
return item.getDisplay().map(
item.getParent(), null, item.getBounds());
}
/*
* END CODE ADAPTED FROM http://eclip.se/38436#c153
*/
/**
* Handles exceptions that occur while attempting to find the bounds of a
* component. Logs various information about the attempt.
*
* @param e The exception thrown during the getBounds() attempt.
* @return the default bounds to use (0, 0, 0, 0).
*/
private static Rectangle handleBoundsError(Exception e) {
Rectangle defaultResult = new Rectangle(0, 0, 0, 0);
String message = "getBounds() failed. Returning default bounds result: " //$NON-NLS-1$
+ defaultResult
+ "."; //$NON-NLS-1$
if (EnvironmentUtils.isMacOS()) {
log.debug(message, e);
} else {
log.error(message, e);
}
return defaultResult;
}
/**
* Calls the toString() method on the given Widget in the GUI-Thread.
* @param widget a Widget.
* @return the toString-representation of the given Widget.
*/
public static String toString(final Widget widget) {
IEventThreadQueuer evThreadQueuer = new EventThreadQueuerSwtImpl();
return evThreadQueuer.invokeAndWait("toString", //$NON-NLS-1$
new IRunnable<String>() {
public String run() throws StepExecutionException {
return String.valueOf(widget);
}
});
}
/**
*
* @param awtPoint The {@link java.awt.Point} to convert.
* @return a new {@link org.eclipse.swt.graphics.Point} with the same
* coordinates as <code>awtPoint</code>.
*/
public static Point convertToSwtPoint(java.awt.Point awtPoint) {
return new Point(awtPoint.x, awtPoint.y);
}
/**
* Determines whether the mouse cursor is currently inside the bounds of the
* given widget
*
* @param widget the widget to check the mouse position for
* @return true if mouse cursor is inside the bounds, false otherwise.
*/
public static boolean isMouseCursorInWidget(final Widget widget) {
IEventThreadQueuer evThreadQueuer = new EventThreadQueuerSwtImpl();
return evThreadQueuer.invokeAndWait("isInComponent", //$NON-NLS-1$
new IRunnable<Boolean>() {
public Boolean run() throws StepExecutionException {
return SwtUtils.getWidgetBounds(widget).contains(
widget.getDisplay().getCursorLocation());
}
});
}
/**
* Determines whether one widget is completely contained within another.
*
* @param boundsWidget The "owning" widget.
* @param eventWidget The "child" widget.
* @return <code>true</code> if <code>eventWidget</code> is completely
* contained within <code>boundsWidget</code>. This method always
* returns <code>false</code> if either or both of the given widgets
* have been disposed.
*/
public static boolean isInBounds(final Widget boundsWidget,
final Widget eventWidget) {
IEventThreadQueuer evThreadQueuer = new EventThreadQueuerSwtImpl();
return evThreadQueuer.invokeAndWait("getWidgetChildren", //$NON-NLS-1$
new IRunnable<Boolean>() {
public Boolean run() throws StepExecutionException {
if (boundsWidget == null || boundsWidget.isDisposed()
|| eventWidget == null || eventWidget.isDisposed()) {
return Boolean.FALSE;
}
Rectangle widgetBounds = getWidgetBounds(boundsWidget);
boolean isInBounds = widgetBounds.equals(
widgetBounds.union(getWidgetBounds(eventWidget)));
return isInBounds;
}
});
}
/**
* Checks whether the given rectangle contains the given point. This
* differs from {@link Rectangle#contains(Point)} in that a point that is
* exactly on the border of the rectangle counts as being contained within
* the rectangle.
*
* @param rect The rectangle against which to check.
* @param point The point to check.
* @return <code>true</code> if <code>point</code> does not lie outside of
* <code>rect</code>. Otherwise <code>false</code>.
*/
public static boolean containsInclusive(Rectangle rect, Point point) {
Point treeLocUpperLeft = new Point(rect.x, rect.y);
Point treeLocLowerRight = new Point(
rect.width + treeLocUpperLeft.x,
rect.height + treeLocUpperLeft.y);
final boolean x1 = point.x >= treeLocUpperLeft.x;
final boolean x2 = point.x < treeLocLowerRight.x;
final boolean y1 = point.y >= treeLocUpperLeft.y;
final boolean y2 = point.y < treeLocLowerRight.y;
return x1 && x2 && y1 && y2;
}
/**
* method for removing mnemonics from string
* @param text String
* @return String without mnemonics
*/
public static final String removeMnemonics(final String text) {
if (text == null) {
return null;
}
int index = text.indexOf('&');
if (index == -1) {
return text;
}
int len = text.length();
StringBuffer sb = new StringBuffer(len);
int lastIndex = 0;
while (index != -1) {
// ignore & at the end
if (index == len - 1) {
break;
}
// handle the && case
if (text.charAt(index + 1) == '&') {
++index;
}
// DBCS languages use "(&X)" format
if (index > 0 && text.charAt(index - 1) == '('
&& text.length() >= index + 3
&& text.charAt(index + 2) == ')') {
sb.append(text.substring(lastIndex, index - 1));
index += 3;
} else {
sb.append(text.substring(lastIndex, index));
// skip the &
++index;
}
lastIndex = index;
index = text.indexOf('&', index);
}
if (lastIndex < len) {
sb.append(text.substring(lastIndex, len));
}
return sb.toString();
}
/**
* gives default modifier of the current OS.
*
* @return meta (command) for OSX, control for Windows/Linux etc
*/
public static int getSystemDefaultModifier() {
if (EnvironmentUtils.isMacOS()) {
return KeyCodeConverter
.getKeyCode(ValueSets.Modifier.cmd.rcValue());
}
return KeyCodeConverter
.getKeyCode(ValueSets.Modifier.control.rcValue());
}
/**
* @return the second system modifier
*/
public static int getSystemModifier2() {
return KeyCodeConverter.getKeyCode(ValueSets.Modifier.shift.rcValue());
}
/**
* @return the third system modifier
*/
public static int getSystemModifier3() {
return KeyCodeConverter.getKeyCode(ValueSets.Modifier.alt.rcValue());
}
/**
* @return the fourth system modifier; only available on Mac OS X
*/
public static int getSystemModifier4() {
return KeyCodeConverter
.getKeyCode(ValueSets.Modifier.control.rcValue());
}
/**
*
* @param shell The shell to check. May be <code>null</code>.
* @return <code>true</code> if the given shell appears to be a dropdown
* list (ex. from a CCombo). Returns <code>false</code> if the
* given shell is <code>null</code> or disposed, or if the shell
* does not meet the necessary criteria to be considered a dropdown
* list.
*/
public static boolean isDropdownListShell(Shell shell) {
return shell != null
&& !shell.isDisposed()
&& StringUtils.isBlank(shell.getText())
&& (shell.getStyle() & SWT.ON_TOP) != 0
&& shell.getChildren() != null
&& shell.getChildren().length == 1
&& shell.getChildren()[0] instanceof org.eclipse.swt.widgets.List;
}
}