org.eclipse.jubula.rc.swt.utils.SwtUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.jubula.rc.swt.utils.SwtUtils.java

Source

/*******************************************************************************
 * 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.CompSystemConstants;
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.tools.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 = (Widget) evThreadQueuer.invokeAndWait("getWidgetAtCursorLocation", //$NON-NLS-1$
                new IRunnable() {
                    public Object run() throws StepExecutionException {
                        return getWidgetAtCursorLocation();
                    }
                });
        return widget;
    }

    /**
     * @return the Control under the cursor
     */
    public static Control getCursorControl() {
        IEventThreadQueuer evThreadQueuer = new EventThreadQueuerSwtImpl();
        return (Control) evThreadQueuer.invokeAndWait("getCursorControl", //$NON-NLS-1$
                new IRunnable() {
                    public Object 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) {
            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 (Control) evThreadQueuer.invokeAndWait("checkControlParent", //$NON-NLS-1$
                new IRunnable() {

                    public Object 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 = (Widget) evThreadQueuer.invokeAndWait("getWidgetParent", //$NON-NLS-1$
                new IRunnable() {
                    public Object 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) {
            // FIXME Clemens
            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) {
            // FIXME Clemens:
            System.out.println();
            // Log.debug("requested the parent of a Tracker- UNFINDABLE");
        }
        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 = (Widget[]) evThreadQueuer.invokeAndWait("getWidgetChildren", //$NON-NLS-1$
                new IRunnable() {
                    public Object 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 objT = new LinkedList();
        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) {
            if (((Scrollable) widget).getVerticalBar() != null) {
                objT.add(((Scrollable) widget).getVerticalBar());
            }
            if (((Scrollable) widget).getHorizontalBar() != null) {
                objT.add(((Scrollable) widget).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) {
            Widget[] widgets = ((Table) widget).getItems();
            if (widgets.length != 0) {
                objT.addAll(Arrays.asList(widgets));
            }
            widgets = ((Table) widget).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 extendedFamily = new LinkedList();
            for (int i = 0; i < objT.size(); i++) {
                Widget w = (Widget) objT.get(i);
                extendedFamily.addAll(Arrays.asList(getWidgetChildrenImpl(w, recurse)));
            }
            objT.addAll(extendedFamily);
        }
        return (Widget[]) (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 objT) {
        if (widget instanceof TreeItem) {
            Widget[] widgets = ((TreeItem) widget).getItems();
            if (widgets.length != 0) {
                ((LinkedList) objT).addAll(Arrays.asList(widgets));
            }
        }
        if (widget instanceof Menu) {
            Widget[] widgets = ((Menu) widget).getItems();
            if (widgets.length != 0) {
                ((LinkedList) objT).addAll(Arrays.asList(widgets));
            }
        }
        if (widget instanceof MenuItem) {
            Widget childMenu = ((MenuItem) widget).getMenu();
            if (childMenu != null) {
                ((LinkedList) 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 https://bugs.eclipse.org/bugs/show_bug.cgi?id=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(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(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 = 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 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 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 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", //$NON-NLS-1$
                    e);
        }

        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 https://bugs.eclipse.org/bugs/show_bug.cgi?id=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();
        String widgetToStr = (String) evThreadQueuer.invokeAndWait("toString", //$NON-NLS-1$
                new IRunnable() {
                    public Object run() throws StepExecutionException {
                        return String.valueOf(widget);
                    }
                });
        return widgetToStr;
    }

    /**
     * 
     * @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 curser is inside the bounds, false otherwise.
     */
    public static boolean isMouseCursorInWidget(final Widget widget) {
        IEventThreadQueuer evThreadQueuer = new EventThreadQueuerSwtImpl();
        boolean isInComponent = ((Boolean) evThreadQueuer.invokeAndWait("isInComponent", //$NON-NLS-1$
                new IRunnable() {
                    public Object run() throws StepExecutionException {
                        return SwtUtils.getWidgetBounds(widget).contains(widget.getDisplay().getCursorLocation())
                                ? Boolean.TRUE
                                : Boolean.FALSE;
                    }

                })).booleanValue();
        return isInComponent;
    }

    /**
     * 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 ((Boolean) evThreadQueuer.invokeAndWait("getWidgetChildren", //$NON-NLS-1$
                new IRunnable() {

                    public Object 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 ? Boolean.TRUE : Boolean.FALSE;
                    }
                })).booleanValue();
    }

    /**
     * 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(CompSystemConstants.MODIFIER_CMD);
        }
        return KeyCodeConverter.getKeyCode(CompSystemConstants.MODIFIER_CONTROL);
    }

    /**
     * @return the second system modifier
     */
    public static int getSystemModifier2() {
        return KeyCodeConverter.getKeyCode(CompSystemConstants.MODIFIER_SHIFT);
    }

    /**
     * @return the third system modifier
     */
    public static int getSystemModifier3() {
        return KeyCodeConverter.getKeyCode(CompSystemConstants.MODIFIER_ALT);
    }

    /**
     * @return the fourth system modifier; only available on Mac OS X
     */
    public static int getSystemModifier4() {
        return KeyCodeConverter.getKeyCode(CompSystemConstants.MODIFIER_CONTROL);
    }

    /**
     * 
     * @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;
    }
}