/******************************************************************************* * 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.selector; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.MenuItem; import org.eclipse.swt.widgets.Widget; import abbot.finder.swt.BasicFinder; import abbot.tester.swt.MenuItemTester; import com.windowtester.internal.debug.IRuntimePluginTraceOptions; import com.windowtester.internal.debug.TraceHandler; import com.windowtester.runtime.MultipleWidgetsFoundException; import com.windowtester.runtime.WidgetNotFoundException; import com.windowtester.runtime.swt.condition.SWTIdleCondition; import com.windowtester.runtime.swt.internal.abbot.matcher.HierarchyMatcher; import com.windowtester.runtime.swt.internal.abbot.matcher.InstanceMatcher; import com.windowtester.swt.util.PathStringTokenizerUtil; /** * A Selector for Menu Items. * * @author Phil Quitslund */ public class MenuItemSelector extends BasicWidgetSelector { private static final long IDLE_TIMEOUT = 3000; /* just a guess */ private final class SWTIdleConditionWithTimeout extends SWTIdleCondition { private final Display display; private long timeout = IDLE_TIMEOUT; private SWTIdleConditionWithTimeout(Display display) { super(display); this.display = display; } public void waitForIdle() { final long now = System.currentTimeMillis(); while (!timedOut(now) && !test()) { if (display.getThread() != Thread.currentThread()) { try { Thread.sleep(100); } catch (InterruptedException e) { // ignored } } } } private boolean timedOut(long now) { boolean timedOut = System.currentTimeMillis() - now > timeout; if (timedOut) TraceHandler.trace(IRuntimePluginTraceOptions.WIDGET_SELECTION, "wait for idle in menu selection timeout exceeded (" + timeout + ") proceeding..."); return timedOut; } } private int clickCount; /** * @throws MultipleWidgetsFoundException * @throws WidgetNotFoundException * @see com.windowtester.event.swt.ISWTWidgetSelectorDelegate#click(org.eclipse.swt.widgets.Widget, java.lang.String) */ public Widget click(Widget w, String path) throws WidgetNotFoundException, MultipleWidgetsFoundException { if (w instanceof MenuItem) return click((MenuItem)w, path); if (w instanceof Menu) return click((Menu)w, path); throw new UnsupportedOperationException("Widgets of type " + w.getClass() + " not supported."); } /** * Click the menu item rooted by this item and described by this path. * @param item - the root item * @param path - the path to the item to click * @return the clicked menu item * @throws MultipleWidgetsFoundException * @throws WidgetNotFoundException */ public Widget click(MenuItem item, String path) throws WidgetNotFoundException, MultipleWidgetsFoundException { MenuItemTester tester = new MenuItemTester(); // get the menu and its parent before clicking in case the menu is disposed early Menu menu = tester.getMenu(item); Menu parent = null; if (menu == null) parent = tester.getParent(item); click(item); // if there is no submenu default to clicking just the top level item if (menu == null){ // [Dan] Do we have a test for this? Can this special case code be removed? if(SWT.getPlatform().equals("gtk")&&((UIProxy.getStyle(parent) & SWT.BAR)!=0)){ waitForIdle(item.getDisplay()); pauseDisplayThread(item.getDisplay(), 500); click(item); waitForIdle(item.getDisplay()); } return item; } return click(menu, path); } /** * @see com.windowtester.event.selector.swt.BasicWidgetSelector#openToolItemMenu(org.eclipse.swt.widgets.Widget) */ public synchronized Widget click(Widget w) { /* * Click and increment our counter. */ Widget clicked = super.click(w); ++clickCount; return clicked; } /** * Click the menu item contained by this menu and described by this path. * @param menu - the containing menu * @param path - the path to the item to click * @return the clicked menu item * @throws MultipleWidgetsFoundException * @throws WidgetNotFoundException */ public Widget click(Menu menu, String path) throws WidgetNotFoundException, MultipleWidgetsFoundException { trace("click: " + path); //fixing to handle escaped '\'s //String[] items = path.split(DELIM); String[] items = PathStringTokenizerUtil.tokenize(path); Widget clicked = menu; for (int i = 0; i < items.length; i++) { String item = items[i]; clicked = resolveAndClick(item, clicked); } //reset count clickCount = 0; return clicked; } /** * Find the menu item with this parent and click it. * @param item - the label of the item to click. * @param parent - the parent Menu or MenuItem * @return the clicked item * @throws WidgetNotFoundException * @throws MultipleWidgetsFoundException */ protected Widget resolveAndClick(String item, Widget parent) throws WidgetNotFoundException, MultipleWidgetsFoundException { if (parent instanceof MenuItem) parent = new MenuItemTester().getMenu((MenuItem)parent); //other case is a menu... Widget widget; try { widget = BasicFinder.getDefault().find(new HierarchyMatcher(MenuItem.class, item, new InstanceMatcher(parent))); click(widget); return widget; } catch (abbot.finder.swt.WidgetNotFoundException wnfe) { //close menu in case of failure handleMenuClose(); //replace/rethrow with our own exception throw new WidgetNotFoundException(wnfe.getMessage()); } catch (abbot.finder.swt.MultipleWidgetsFoundException mwfe) { //close menu in case of failure handleMenuClose(); //replace/rethrow with our own exception throw new MultipleWidgetsFoundException(mwfe.getMessage()); } } /** * Close open menus (called on error cases). * */ protected void handleMenuClose() { //takeScreenShot(); //BEFORE closing menu //TODO: this may be OS-specific... for (int i= 0; i <= clickCount; ++i) { //System.err.println("ESC"); keyClick(SWT.ESC); //close menu by hitting ESCAPE } } //Override to allow for timeouts to protect native dialog case protected /*synchronized*/ void waitForIdle(final Display display){ /* * Fix for spawned native dialogs which block the idle condition. * TODO: this is really just a best guess strat -- and could be revisited */ //provisional fix for Dialogs Opened During Window Tester Widget Selector Actions Cause Hangs //new SWTIdleCondition(display).waitForIdle(); //remedy issues with native dialogs opening and blocking idle new SWTIdleConditionWithTimeout(display).waitForIdle(); } }