package com.windowtester.test.widgets; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashSet; import org.eclipse.swt.custom.CTabFolder; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.CoolBar; 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.Scrollable; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.TabFolder; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.ToolBar; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeItem; import org.eclipse.swt.widgets.Widget; import com.windowtester.runtime.internal.OS; import com.windowtester.runtime.internal.concurrent.VoidCallable; import com.windowtester.runtime.swt.internal.widgets.DisplayReference; /******************************************************************************* * 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 *******************************************************************************/ public class LegacyFinderUtil { private class WidgetGetter extends VoidCallable { //to ensure no duplicates... private LinkedHashSet<Widget> list = new LinkedHashSet<Widget>(); private Widget rootWidget; public Collection<Widget> getList() { return list; } WidgetGetter (Widget w) { this.rootWidget = w; } public void call() { try { if (rootWidget != null && !rootWidget.isDisposed()) list.addAll(getWidgets(rootWidget)); } catch(Exception e) { // LogHandler.log(e); e.printStackTrace(); } } private Collection<Widget> getWidgets(Widget w) { //System.out.println("getWidget: " + w); ArrayList<Widget> localList = new ArrayList<Widget>(); // localList.addAll(Collections.singletonList(w)); if (w instanceof Decorations) { Decorations d = (Decorations)w; //localList.addAll(getAllMenuItems(d.getMenuBar())); addCheck(localList,d.getMenuBar()); //!pq: added to make toolitemt pulldowns surface in hierarchy if (ignoreOrphans) addOrphanedMenus(d, localList); } if (w instanceof Control) { Control c = (Control)w; //localList.addAll(getAllMenuItems(c.getMenu())); addCheck(localList,c.getMenu()); } if(w instanceof Scrollable){ addCheck(localList,((Scrollable)w).getVerticalBar()); addCheck(localList,((Scrollable)w).getHorizontalBar()); } if(w instanceof TreeItem){ Widget[] widgets = ((TreeItem)w).getItems(); addCheck(localList,(Arrays.asList(widgets))); } if(w instanceof Menu){ Widget[] widgets = ((Menu)w).getItems(); addCheck(localList,Arrays.asList(widgets)); } if(w instanceof MenuItem){ Widget childMenu = ((MenuItem)w).getMenu(); addCheck(localList,childMenu); } if (w instanceof Composite) { if (w instanceof ToolBar) { addCheck(localList,Arrays.asList(((ToolBar)w).getItems())); } if (w instanceof Table) { addCheck(localList,Arrays.asList(((Table)w).getItems())); addCheck(localList,Arrays.asList(((Table)w).getColumns())); } if (w instanceof Tree) { addCheck(localList,Arrays.asList(((Tree)w).getItems())); } if (w instanceof CoolBar) { addCheck(localList,Arrays.asList(((CoolBar)w).getItems())); } if(w instanceof TabFolder){ Widget[] widgets = ((TabFolder)w).getItems(); addCheck(localList,Arrays.asList(widgets)); } if(w instanceof CTabFolder){ Widget[] widgets = ((CTabFolder)w).getItems(); addCheck(localList,Arrays.asList(widgets)); } Composite cont = (Composite)w; Control [] children = cont.getChildren(); // for (int i=0;i<children.length;i++) { // if (children[i] instanceof Composite // && ((Composite)children[i]).getChildren().length > 0) // { // addCheck(localList,getWidgets(children[i])); // } // } addCheck(localList,Arrays.asList(children)); } return localList; //outerList = localList; } }; class OrphanFinder { // private SWTHierarchy _hierarchy; // // public OrphanFinder(Display display) { // _hierarchy = new SWTHierarchy(display); // } /** * Is this menu orphaned by this shell? * @param menu - the menu in question * @param shell - the shell in question * @return true if the menu is an orphan */ public boolean isOrphanedBy(Menu menu, Shell shell) { Control[] children = shell.getChildren(); for (int i = 0; i < children.length; i++) { Control child = children[i]; if (isContainedBy(menu, child)) return false; } return !isSubMenu(menu); } private boolean isContainedBy(Menu menu, Control control) { Collection<Widget> widgets = getImmediateChildren(control); return widgets == null ? false : widgets.contains(menu); } public boolean isSubMenu(Menu menu) { return menu.getParentMenu() != null; } } private boolean ignoreOrphans; /* * Turn off orphan menu handling. This legacy behavior needs to be reviewed. */ public LegacyFinderUtil ignoreOrphanedMenus(){ ignoreOrphans = true; return this; } public Collection<Widget> getImmediateChildren(final Widget w) { /** * The lists may be a bit confusing, but what is happening is that the UI * thread looks for widgets that are descendants of the widget passed to * this function. It keeps a list of these that is local to that thread * instance, and this list may include widgets found in recursive calls * to this function. The inner thread can only communicate through * shared class variables, so the class variable outerList is used to * store the results of localList. The function then reads this and returns * its values. */ /* hierarchy issues to resolve: do we want this function to recurse? should * we have a helper function that gets all direct descendants and another * that does the recursion? */ // outerList = new ArrayList(); // ArrayList innerList = new ArrayList(); //Runnable widgetGetter = new Runnable() { WidgetGetter widgetGetter = new WidgetGetter(w); /* * !pq: Using the widget's display here is unsafe since it may be disposed! */ //w.getDisplay().syncExec(widgetGetter); //display.syncExec(widgetGetter); new DisplayReference(w.getDisplay()).execute(widgetGetter); return widgetGetter.getList(); //innerList.addAll(outerList); // System.out.println("List contents:"); // System.out.println("List: " + list); //return innerList; } @SuppressWarnings("unchecked") private static void addCheck(Collection dest, Collection src) { /* add object to collection if non-null */ if (src.size() > 0) { // Iterator iter = src.iterator(); // while (iter.hasNext()) { // dest.addAll(getWidgets((Widget)iter.next())); // } dest.addAll(src); } } @SuppressWarnings("unchecked") private static void addCheck(Collection c, Object o) { /* add object to collection if non-null */ if (o != null) { c.add(o); // c.addAll(getWidgets((Widget)o)); } } // @SuppressWarnings("unchecked") // private Collection getAllMenuItems(Menu m) { // /* get all menus and menu items rooted at this menu */ // ArrayList list = new ArrayList(); // if (m != null) { // MenuItem [] items = m.getItems(); // list.add(m); // for (int i=0;i<items.length;i++) { // list.addAll(getAllMenuItems(items[i].getMenu())); // list.add(items[i]); // } // } // return list; // } /** * Add orphaned menus to the list. * <br> * Explanation: Menus that belong to the shell but not the menubar (e.g., * toolitme pull-downs) are not in the default hierarchy. To remedy this * we need to introspect the Shell's "menus" field which contains these references. * Unfortunately, this array also contains references to menus owned by other controls * in the hierarchy. Our strategy for avoiding duplicates is to look ahead for * each menu and see if it is contained in any of the Shell's children. * * @see OrphanFinder#isOrphanedBy(Menu, Shell) * * @author Phil Quitslund */ private void addOrphanedMenus(Decorations d, ArrayList<Widget> localList) { if (d instanceof Shell) { Menu[] menus = getMenus((Decorations)d); if (menus != null) { OrphanFinder finder = new OrphanFinder(); for (int i = 0; i < menus.length; i++) { Menu menu = menus[i]; if (menu != null && finder.isOrphanedBy(menu, (Shell) d)) { addCheck(localList, menu); } } } } } public static Menu[] getMenus(Decorations shell) { Menu[] result = null; try { if (OS.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; } // // @SuppressWarnings("unchecked") // public Collection getRootDecorations(Display display) { // //!pq: added for insertion order invariant // Set set = new LinkedHashSet(); // // TODO: In this version we don't do any tracking, but we provide this // // function for similarity with the Abbot API // //// synchronized(contexts) { //// Iterator iter = contexts.keySet().iterator(); //// while (iter.hasNext()) { //// EventQueue queue = (EventQueue)iter.next(); //// Toolkit toolkit = Toolkit.getDefaultToolkit(); //// Map map = (Map)contexts.get(queue); //// set.addAll(map.values()); //// } //// } //// Shell activeShell = null; //// if (display != null) { //// activeShell = display.getActiveShell(); //// } else { //// System.out.println("Couldn't find display"); //// } //// if (activeShell==null) { //// System.out.println ("Tracker couldn't find any shells"); //// return set; //// } //// Shell[] shells = activeShell.getShells(); // Shell [] shells = getShells(display); // for (int i=0;i < shells.length;i++) { // set.add(shells[i]); // } // //Log.debug(String.valueOf(list.size()) + " total Frames"); // return set; // } // // // public static /* synchronized */ Shell[] getShells(final Display d){ // // [author=Dan] Do NOT synchronize this entire method because // // it causes the test thread and the UI thread to occasionally deadlock // // Instead, rework method so that it does not use a field // // and thus does not need to be synchronized. // if(d == null || d.isDisposed()) // return new Shell[] {}; // if(d.getThread() == Thread.currentThread()) // return d.getShells(); // try{ // Thread.sleep(0,10); // } // catch(Exception e) { // } //// final Object[] result = new Object[1]; //// Robot.syncExec(d,null,new Runnable(){ //// public void run(){ //// if (d.isDisposed()) //// result[0] = new Shell[] {}; //// else //// result[0] = d.getShells(); //// } //// }); //// return (Shell[]) result[0]; // return new DisplayReference(d).execute(new Callable<Shell[]>(){ // public Shell[] call() throws Exception { // if (d.isDisposed()) // return new Shell[] {}; // else // return d.getShells(); // } // // }); // // } // public void dbPrintWidgets() { // /* debugging function that prints all widgets in the hierarchy */ // /* rewritten to use getWidgets */ // Collection allRoots = getRoots(); // Iterator iter = allRoots.iterator(); // System.out.println("Printing widgets:"); // //System.out.println("Current display:" + display.toString()); // System.out.println("Roots:"); // while (iter.hasNext()) { // final Shell shell = (Shell)iter.next(); // display.syncExec( new Runnable() { // public void run() { // dbPrintWidgets(shell,1); // //String s = shell.toString(); // //System.out.println("Root Shell: " + s); // //dbPrintMenu(shell.getMenuBar(), 1); // //dbPrintChildren(shell, 1); // } // } ); // } // } public Collection<Widget> getAllWidgets(Widget w) { LinkedHashSet<Widget> widgets = new LinkedHashSet<Widget>(); return getAllWidgets(widgets, w); } private Collection<Widget> getAllWidgets(Collection<Widget> widgets, Widget w) { widgets.add(w); for (Widget child : getImmediateChildren(w)) getAllWidgets(widgets, child); return widgets; } 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<Menu> set = new LinkedHashSet<Menu>(); 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)); } }