/*******************************************************************************
* Copyright (c) 2012 Google, Inc.
* 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:
* Google, Inc. - initial API and implementation
*******************************************************************************/
package com.windowtester.runtime.swt.internal.abbot;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.CoolItem;
import org.eclipse.swt.widgets.Decorations;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.ScrollBar;
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.ToolItem;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.swt.widgets.Widget;
import abbot.Platform;
/**
* This class adapts SWT to Abbot, e.g. where SWT methods are not public.
* <p/>
* As SWT has evolved, this class has been required to do less and less. There are still
* (at the time of 3.5) a few bits of native magic. To wit:
*
* <ul>
* <li>TableColumns (all) - cocoa (might be able to reuse carbon_getBounds)
* -> note: TableColumn Selection is not currently implemented: com.windowtester.runtime.swt.locator.TableCellLocator.doClick(TableColumn, int, int, Point)</li>
* <li>MenuItems (osx)</li>
* </ul>
*/
public class SWTWorkarounds {
/**
* The MacExtensions instance. It is initialized during plug-in activation
* when running Mac OSX. It will be null otherwise.
* @see com.windowtester.runtime.swt.internal.RuntimePlugin#start(org.osgi.framework.BundleContext)
*/
// public static MacExtensions MacExt;
/*************************** COMMON *****************************/
public static Rectangle getBounds (Object object) {
if (object instanceof Control) {
return ((Control)object).getBounds();
}
Rectangle result = new Rectangle (0, 0, 0, 0);
try {
Method method = object.getClass().getDeclaredMethod ("getBounds", null);
method.setAccessible(true);
result = (Rectangle) method.invoke (object, null);
} catch (Throwable th) {
// TODO - decide what should happen when the method is unavailable
}
return result;
}
public static Rectangle getBounds(MenuItem menuItem) {
if (Platform.isOSX()) {
// Rectangle result = MacExt.getMenuItemBounds(menuItem);
// if (menuItem.isDisposed()) {
// throw new NullPointerException(); // expected to be caught in WidgetLocator.getLocation(Widget, boolean)
// }
// return result;
throw new UnsupportedOperationException("bounds should be provided by OSX-specific plugins");
}
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;
}
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=38436#c143
itemRect.y += menuRect.y;
return itemRect;
}
public static Rectangle getBounds (Menu menu) {
return getBounds ((Object)menu);
}
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;
// TODO - coordinate system may change when the API is added to SWT
return scrollBar.getDisplay().map (scrollBar.getParent (), null, bounds);
}
/*************************** WIN32 *****************************/
static int SendMessage (long hWnd, int Msg, int wParam, int [] lParam)
{
/*
* In the move to 3.5M4 we're seeing this error in the build:
* SendMessage(int,int,int,int[]) in SWTWorkarounds cannot be applied to (long,int,int,int[])
* This cast fixes the compilation. NOTE: this method should not get called in 3.5.
*/
return SendMessage((int)hWnd, Msg, wParam, lParam);
}
static int SendMessage (int hWnd, int Msg, int wParam, int [] lParam)
{
int result = 0;
try {
Class clazz = Class.forName ("org.eclipse.swt.internal.win32.OS");
Class [] params = new Class [] {
Integer.TYPE,
Integer.TYPE,
Integer.TYPE,
lParam.getClass (),
};
Method method = clazz.getMethod ("SendMessage", params);
Object [] args = new Object [] {
new Integer (hWnd),
new Integer (Msg),
new Integer (wParam),
lParam,
};
result = ((Integer) method.invoke (clazz, args)).intValue ();
} catch (Throwable e) {
// TODO - decide what should happen when the method is unavailable
}
return result;
}
static int SendMessage(Control target, int Msg, int wParam, int [] lParam) {
Object handle = getHandle(target);
if (handle instanceof Integer)
return SendMessage(((Integer)handle).intValue(), Msg, wParam, lParam);
if (handle instanceof Long)
return SendMessage(((Long)handle).longValue(), Msg, wParam, lParam);
throw new UnsupportedOperationException("no handle found for: " + target);
}
private static Object getHandle(Control target) {
try {
Field handle = Control.class.getDeclaredField("handle");
handle.setAccessible(true);
return handle.get(target);
} catch (SecurityException e) {
} catch (NoSuchFieldException e) {
} catch (IllegalArgumentException e) {
} catch (IllegalAccessException e) {
}
return null;
}
static Rectangle win32_getBounds(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];
SendMessage (parent, /*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);
}
static Rectangle win32_getBounds(TableColumn tableColumn) {
Table parent = tableColumn.getParent ();
int index = parent.indexOf (tableColumn);
if (index == -1) return new Rectangle (0, 0, 0, 0);
int hwndHeader = SendMessage (parent, /*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);
// TODO - oordinate system may change when the API is added to SWT
return tableColumn.getDisplay().map (parent, null, bounds);
}
/*************************** GTK *****************************/
static void gtk_getBounds (int handle, Rectangle bounds) {
try {
Class clazz = Class.forName ("org.eclipse.swt.internal.gtk.OS");
Class [] params = new Class [] {Integer.TYPE};
Object [] args = new Object [] {new Integer (handle)};
Method method = clazz.getMethod ("GTK_WIDGET_X", params);
bounds.x = ((Integer) method.invoke (clazz, args)).intValue ();
method = clazz.getMethod ("GTK_WIDGET_Y", params);
bounds.y = ((Integer) method.invoke (clazz, args)).intValue ();
method = clazz.getMethod ("GTK_WIDGET_WIDTH", params);
bounds.width = ((Integer) method.invoke (clazz, args)).intValue ();
method = clazz.getMethod ("GTK_WIDGET_HEIGHT", params);
bounds.height = ((Integer) method.invoke (clazz, args)).intValue ();
} catch (Throwable e) {
// TODO - decide what should happen when the method is unavailable
}
}
static Rectangle gtk_getBounds(TableColumn tabColumn) {
Rectangle bounds = new Rectangle (0, 0, 0, 0);
try {
Class c = tabColumn.getClass();
Field f = c.getDeclaredField("buttonHandle");
f.setAccessible(true);
int handle = f.getInt(tabColumn);
gtk_getBounds(handle, bounds);
} catch (Throwable e) {
// TODO - decide what should happen when the method is unavailable
}
return tabColumn.getDisplay().map (tabColumn.getParent (), null, bounds);
}
static Rectangle gtk_getBounds(TabItem tabItem) {
Rectangle bounds = new Rectangle (0, 0, 0, 0);
try {
Class c = Class.forName ("org.eclipse.swt.widgets.Widget");
Field f = c.getDeclaredField("handle");
f.setAccessible(true);
int handle = f.getInt(tabItem);
gtk_getBounds(handle, bounds);
} catch (Throwable e) {
// TODO - decide what should happen when the method is unavailable
}
return tabItem.getDisplay().map (tabItem.getParent (), null, bounds);
}
/*************************** MOTIF *****************************/
static Rectangle motif_getBounds(TabItem tabItem) {
Rectangle bounds = new Rectangle (0, 0, 0, 0);
try {
Class c = tabItem.getClass();
Method m = c.getDeclaredMethod("getBounds", null);
m.setAccessible(true);
bounds = (Rectangle)m.invoke(tabItem, null);
int margin = 2;
bounds.x +=margin;bounds.y+=margin;
bounds.width -= 2*margin; bounds.height-=margin;
} catch (Throwable e) {
// TODO - decide what should happen when the method is unavailable
}
return tabItem.getDisplay().map (tabItem.getParent (), null, bounds);
}
static Rectangle motif_getBounds(TableColumn tableColumn) {
Rectangle bounds = new Rectangle (0, 0, 0, 0);
try {
Class c = tableColumn.getClass();
Method m = c.getDeclaredMethod("getX", null);
m.setAccessible(true);
bounds.x = ((Integer)m.invoke(tableColumn, null)).intValue();
bounds.width = tableColumn.getWidth() - 2;
bounds.height = tableColumn.getParent().getHeaderHeight() - 2;
} catch (Throwable e) {
// TODO - decide what should happen when the method is unavailable
}
return tableColumn.getDisplay().map (tableColumn.getParent (), null, bounds);
}
/*************************** CARBON *****************************/
static Rectangle carbon_getBounds(TabItem tabItem) {
// Rectangle bounds = MacExt.getTabItemBounds(tabItem);
// bounds = tabItem.getDisplay().map(tabItem.getParent(), null, bounds);
// System.out.println(bounds);
// return bounds;
throw new UnsupportedOperationException("bounds should be provided by OSX-specific plugins");
}
static Rectangle carbon_getBounds(TableColumn tableColumn) {
Table table = tableColumn.getParent();
if (!table.getHeaderVisible())
return new Rectangle(0, 0, 0, 0);
TableColumn[] columns = table.getColumns();
int x = 0;
int width = 0;
int height = table.getHeaderHeight();
for (int i = 0; i < columns.length; i++) {
TableColumn col = columns[i];
if (col == tableColumn) {
width = col.getWidth();
break;
} else
x += col.getWidth();
}
if (width == 0)
return new Rectangle(0, 0, 0, 0);
Rectangle bounds = new Rectangle(x, 0, width, height);
bounds = tableColumn.getDisplay().map(table, null, bounds);
return bounds;
}
public static Rectangle getBounds (TabItem tabItem){
if (SWT.getPlatform().equals("win32")) {
return win32_getBounds (tabItem);
}
if (SWT.getPlatform().equals("gtk")) {
return gtk_getBounds (tabItem);
}
if (SWT.getPlatform().equals("motif")) {
return motif_getBounds (tabItem);
}
if (SWT.getPlatform().equals("carbon")) {
return carbon_getBounds (tabItem);
}
if (SWT.getPlatform().equals("cocoa")) {
return cocoa_getBounds (tabItem);
}
return null;
}
private static Rectangle cocoa_getBounds(TabItem tabItem) {
/* $if eclipse.version >= 3.4 $ */
Display display = tabItem.getDisplay();
TabFolder parent = tabItem.getParent();
Rectangle mappedItem = display.map(parent, null, tabItem.getBounds());
Rectangle mappedFolder = display.map(tabItem.getParent(), null, parent.getBounds());
// This appears to be an SWT bug : the y value is set to a puzzling negative value.
// To workaround, we get the y from the parent folder (which we assume to be equivalent).
mappedItem.y = mappedFolder.y;
return mappedItem;
/* $else$
//should never get here
return null;
$endif $ */
}
public static Rectangle getBounds (TableColumn tableColumn) {
if (SWT.getPlatform().equals("win32")) {
return win32_getBounds (tableColumn);
}
if (SWT.getPlatform().equals("gtk")) {
return gtk_getBounds (tableColumn);
}
if (SWT.getPlatform().equals("motif")) {
return motif_getBounds (tableColumn);
}
if (SWT.getPlatform().equals("carbon")) {
return carbon_getBounds (tableColumn);
}
return null;
}
public static Rectangle getBounds (TableItem item) {
return item.getDisplay().map (item.getParent (), null, item.getBounds (0));
}
public static Rectangle getBounds (TreeItem item) {
return item.getDisplay().map (item.getParent (), null, item.getBounds ());
}
public static Rectangle getBounds (CTabItem item) {
return item.getDisplay().map (item.getParent (), null, item.getBounds ());
}
public static Rectangle getBounds (ToolItem item) {
return item.getDisplay().map (item.getParent (), null, item.getBounds ());
}
public static Rectangle getBounds (CoolItem item) {
return item.getDisplay().map (item.getParent (), null, item.getBounds ());
}
//!pq:
public static MenuItem[] getItems(Display d) {
MenuItem[] result = null;
try {
Field field = d.getClass().getDeclaredField("items");
field.setAccessible(true);
result = (MenuItem[]) field.get(d);
} catch (Throwable th) {
th.printStackTrace();
// TODO - decide what should happen when the method is unavailable
}
//prune out null entries
java.util.List pruned = new ArrayList();
if (result != null) {
for (int i = 0; i < result.length; i++) {
if (result[i] != null)
pruned.add(result[i]);
}
}
return (MenuItem[])pruned.toArray(new MenuItem[]{});
}
//!pq:
//TODO[pq]: move this to a fragment
public static Menu[] getMenus(Display d) {
MenuItem[] result = null;
try {
Field field = d.getClass().getDeclaredField("items");
field.setAccessible(true);
result = (MenuItem[]) field.get(d);
} catch (Throwable th) {
th.printStackTrace();
// TODO - decide what should happen when the method is unavailable
}
LinkedHashSet set = new LinkedHashSet();
if (result != null) {
for (int i = 0; i < result.length; i++) {
if (result[i] != null) {
Menu menu = (Menu) ((MenuItem) result[i]).getParent();
// TODO: clean this up!
if (menu != null && !isSubMenu(menu) && !parentIsControl(menu))
set.add(menu);
}
}
}
return (Menu[])set.toArray(new Menu[]{});
}
//TODO: fix me... producing duplicates...
private static boolean isSubMenu(Menu menu) {
MenuItem parent = menu.getParentItem();
return parent != null;
}
private static boolean parentIsControl(Menu menu) {
Decorations parent = menu.getParent();
return (parent != null && parent.getClass().equals(Control.class));
}
//!pq:
public static Menu[] getMenus(Decorations shell) {
Menu[] result = null;
try {
if (Platform.isOSX()) {
Menu bar = shell.getMenuBar();
if (bar == null)
return new Menu[0]; // TODO Mac testing
MenuItem[] items = bar.getItems();
result = new Menu[items.length];
for (int i = 0; i < items.length; i++)
result[i] = items[i].getMenu();
} else {
Field field = Decorations.class.getDeclaredField("menus");
field.setAccessible(true);
result = (Menu[]) field.get(shell);
}
} catch (Throwable th) {
th.printStackTrace();
// TODO - decide what should happen when the method is unavailable
}
return result;
}
/**
* Returns <code>true</code> if there are any listeners
* for the specified event type associated with the receiver,
* and <code>false</code> otherwise.
*
* Added because {@link Widget#isListening(int)} is *not* public in Eclipse 3.0
*
* @param eventType the type of event
* @return true if the event is hooked
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public static boolean isListening(final Widget w, final int eventType) {
return w.isListening(eventType);
}
// /**
// * Return true if the accessibility API must be enabled. This is
// * Mac-specific so return false if not on a Mac.
// *
// * @return true if running on Mac and accessibility needs to be enabled
// */
// public static boolean isMacAccessibilityDisabled() {
// if (Platform.isOSX()) {
// if (!com.windowtester.internal.runtime.Platform.isRunning())
// throw new IllegalStateException("Mac WindowTester Tests must be run as JUnit Plug-in Tests");
// return !MacExt.isAXAPIEnabled();
// }
// return false;
// }
}