package abbot.finder.swt; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import org.eclipse.swt.custom.CTabFolder; import org.eclipse.swt.dnd.DragSource; import org.eclipse.swt.dnd.DropTarget; 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.TreeItem; import org.eclipse.swt.widgets.Widget; import abbot.ExitException; import abbot.Log; import abbot.tester.swt.DecorationsTracker; import abbot.tester.swt.Robot; import com.windowtester.runtime.swt.internal.abbot.SWTWorkarounds; import com.windowtester.runtime.swt.internal.debug.LogHandler; import com.windowtester.runtime.swt.internal.finder.SWTHierarchyHelper; /** Provides access to the current SWT hierarchy. * * Methods beginning with db are for development debugging and should not * be considered part of the final API. * * */ public class SWTHierarchy implements Hierarchy { protected static Display display; // = new Display(); protected static DecorationsTracker tracker; //= DecorationsTracker.getTracker(display); // private ArrayList outerList;// = new ArrayList(); // private Decorations rootDecorations; // static vars used for inner classes (ie, runnables). // Only use these in synchronized methods // private boolean boolT; private Widget widgetT; // private String stringT; // private int intT; // private Object objT; public SWTHierarchy(Display d) { display = d; tracker = DecorationsTracker.getTracker(display); } /* Default hierarchy removed: no longer necessary */ /** Returns whether the given widget is reachable from any of the root * windows. The default is to consider all widgets contained in the * hierarchy, whether they are reachable or not (NOTE: isReachable is a * distinctly different operation). */ public boolean contains(Widget c) { /* not implemented in abbot either */ return true; } public void dispose(final Decorations w) { // Decorations[] owned = w.getOwnedDecorationss(); // for (int i=0;i < owned.length;i++) { // // Decorations.dispose is recursive; make Hierarchy.dispose recursive // // as well. // dispose(owned[i]); // } // // if (SWT.isSharedInvisibleFrame(w)) { // // Don't dispose, or any child windows will be disposed // // automatically. // return; // } // Ensure the dispose is done on the UI thread so we can catch any // exceptions. Runnable action = new Runnable() { public void run() { try { // Distinguish between the abbot framework disposing a // window and anyone else doing so. System.setProperty("abbot.finder.disposal", "true"); w.dispose(); System.setProperty("abbot.finder.disposal", "false"); } catch(NullPointerException npe) { // Catch bug in SWT 1.3.1 when generating hierarchy // events Log.log(npe); } catch(ExitException e) { // Some apps might call System.exit on WINDOW_CLOSED Log.log("Ignoring SUT exit: " + e); } catch(Throwable e) { // Don't allow other exceptions to interfere with // disposal. Log.warn(e); Log.warn("An exception was thrown when disposing " + " the window " + Robot.toString(w) + ". The exception is ignored"); } } }; Display disposeDisplay = w.getDisplay(); disposeDisplay.syncExec(action); } /** Return all root widgets in the current SWT hierarchy. */ // TODO: implement in SWT DecorationsTracker public Collection getRoots() { return tracker.getRootDecorations(); } public Display getDisplay() { return display; } private void addCheck(Collection c, Object o) { /* add object to collection if non-null */ if (o!=null) { c.add(o); //c.addAll(getWidgets((Widget)o)); } } private 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); } } protected static final Collection EMPTY = new ArrayList(); /** Return all descendents of interest of the given Widget. This includes owned windows for Decorations, children for Composites. */ private class WidgetGetter implements Runnable { //to ensure no duplicates... private LinkedHashSet list = new LinkedHashSet(); private Widget rootWidget; public Collection getList() { return list; } WidgetGetter (Widget w) { this.rootWidget = w; } public void run() { try { if (rootWidget != null && !rootWidget.isDisposed()) list.addAll(getWidgets(rootWidget)); } catch(Exception e) { LogHandler.log(e); } // Iterator iter = list.iterator(); // while (iter.hasNext()) { // list.addAll(getDirectWidgets((Widget)iter.next())); // } } // private Collection getWidgets(Widget w) { // ArrayList localList = new ArrayList(); // LinkedList searchQ = new LinkedList(Collections.singletonList(rootWidget)); // while (searchQ.size() > 0) { // Widget current = (Widget)searchQ.removeFirst(); // localList.add(current); // searchQ.addAll(getDirectDescendantWidgets(current)); // } // return localList; // } // //!pq: // private Collection addUnattachedMenus() { // System.out.println("adding..."); // // TODO Auto-generated method stub // return null; // } private Collection getWidgets(Widget w) { //System.out.println("getWidget: " + w); ArrayList localList = new ArrayList(); // 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 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; } }; // public Collection getWidgets(final Widget w) { // /* read up on inner anonymous classes */ // // } public Collection getWidgets(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); return widgetGetter.getList(); //innerList.addAll(outerList); // System.out.println("List contents:"); // System.out.println("List: " + list); //return innerList; } public synchronized Widget getParent(final Widget c) { // if (c instanceof Control) { // return ((Control)c).getParent(); // } // if (c instanceof Menu) { // return ((Menu)c).getParent(); // } // if (c instanceof Item) { // // TODO: return parents for each item type if necessary // } widgetT = null; Robot.syncExec(c.getDisplay(),this,new Runnable(){ public void run(){ if(c instanceof Control) widgetT = ((Control)c).getParent(); if(c instanceof Caret) widgetT = ((Caret)c).getParent(); if(c instanceof Menu) widgetT = ((Menu)c).getParent(); if(c instanceof ScrollBar) widgetT = ((ScrollBar)c).getParent(); if(c instanceof CoolItem) widgetT = ((CoolItem)c).getParent(); if(c instanceof MenuItem) widgetT = ((MenuItem)c).getParent(); if(c instanceof TabItem) widgetT = ((TabItem)c).getParent(); if(c instanceof TableColumn) widgetT = ((TableColumn)c).getParent(); if(c instanceof TableItem) widgetT = ((TableItem)c).getParent(); if(c instanceof ToolItem) widgetT = ((ToolItem)c).getParent(); if(c instanceof TreeItem) widgetT = ((TreeItem)c).getParent(); if(c instanceof DragSource) widgetT = ((DragSource)c).getControl().getParent(); if(c instanceof DropTarget) widgetT = ((DropTarget)c).getControl().getParent(); if(c instanceof Tracker) Log.debug("requested the parent of a Tracker- UNFINDABLE"); } }); return widgetT; } private String indent(int level) { String indentation = ""; for (int i=0;i<level;i++) indentation = indentation + " "; return indentation; } private void dbPrintItems (Item [] items, int level) { for (int i=0;i<items.length;i++) { System.out.println(indent(level) + items[i].toString()); if (items[i] instanceof MenuItem) { Menu m = ((MenuItem)items[i]).getMenu(); dbPrintMenu(m, level+1); } } } private void dbPrintMenu(Menu m, int level) { if (m != null) { System.out.println (indent(level) + m.toString()); dbPrintItems(m.getItems(), level + 1); } } // 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; // } // private void dbPrintItems(Composite c, int level) { // /* Prints items in composites that have them */ // if (c instanceof ToolBar) { // dbPrintItems(((ToolBar)c).getItems(), level); // } // if (c instanceof CoolBar) { // dbPrintItems(((CoolBar)c).getItems(), level); // } // if (c instanceof Tree) { // dbPrintItems(((Tree)c).getItems(), level); // } // if (c instanceof Table) { // dbPrintItems(((Table)c).getColumns(), level); // dbPrintItems(((Table)c).getItems(), level); // } // if (c instanceof TabFolder) { // dbPrintItems(((TabFolder)c).getItems(), level); // } // } // private void dbPrintChildren(Composite c, int level) { // /* recursively prints children of composite */ // if (c instanceof Shell) { // Shell[] shells = ((Shell)c).getShells(); // System.out.println(indent(level) + "child shells: " + shells.length); // for (int j=0;j<shells.length;j++) { // dbPrintMenu(shells[j].getMenu(), level+1); // dbPrintChildren(shells[j], level+1); // } // } // Control[] children = c.getChildren(); // for (int i=0;i<children.length;i++) { // System.out.println(indent(level) + children[i].toString()); // if (children[i] instanceof Control) { // dbPrintMenu(children[i].getMenu(), level+1); // } // if (children[i] instanceof Composite) { // Composite child = (Composite)children[i]; // dbPrintItems (child, level+1); // dbPrintChildren(child, level+1); // } // } // } // public void dbPrintWidgets() { // /* debugging function that prints all widgets in the hierarchy */ // 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() { // String s = shell.toString(); // System.out.println("Root Shell: " + s); // dbPrintMenu(shell.getMenuBar(), 1); // dbPrintChildren(shell, 1); // } // } ); // } // } 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); } } ); } } private void dbPrintWidgets (Widget w, int level) { System.out.print(indent(level)); boolean visible = SWTHierarchyHelper.isVisible(w); if (!visible) System.out.print("["); System.out.print(w+"<HC|"+w.hashCode()+">"); if (!visible) System.out.print("]->(invisible)"); System.out.println(); //temporary fix: //System.out.println(w.getData("unique.id")); Collection childWidgets = getWidgets(w); Iterator iter = childWidgets.iterator(); while (iter.hasNext()) { //Widget j = (Widget)iter.next(); //System.out.println(indent(level+1)+j+"<HC|"+j.hashCode()+">"); dbPrintWidgets((Widget)iter.next(), level+1); } } //!pq public Widget[] getWidgets() { List widgets = new ArrayList(); Collection allRoots = getRoots(); Iterator iter = allRoots.iterator(); while (iter.hasNext()) { final Shell shell = (Shell)iter.next(); widgets.add(shell); addChildren(shell, widgets); } return (Widget[]) widgets.toArray(new Widget[]{}); } private void addChildren(final Widget w, final List widgets) { display.syncExec( new Runnable() { public void run() { Collection childWidgets = getWidgets(w); Iterator iter = childWidgets.iterator(); while (iter.hasNext()) { Widget next = (Widget)iter.next(); widgets.add(next); addChildren(next, widgets); } } } ); } /** * 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) */ private void addOrphanedMenus(Decorations d, ArrayList localList) { if (d instanceof Shell) { Menu[] menus = SWTWorkarounds.getMenus((Decorations)d); if (menus != null) { OrphanFinder finder = new OrphanFinder(d.getDisplay()); for (int i = 0; i < menus.length; i++) { Menu menu = menus[i]; if (menu != null && finder.isOrphanedBy(menu, (Shell) d)) { addCheck(localList, menu); } } } } } }