/*******************************************************************************
* 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.drivers;
import java.util.concurrent.Callable;
import com.windowtester.internal.runtime.util.StringUtils;
import com.windowtester.runtime.MultipleWidgetsFoundException;
import com.windowtester.runtime.WidgetNotFoundException;
import com.windowtester.runtime.internal.concurrent.VoidCallable;
import com.windowtester.runtime.swt.internal.selector.PopupMenuSelector2;
import com.windowtester.runtime.swt.internal.widgets.DisplayReference;
import com.windowtester.runtime.swt.internal.widgets.MenuItemReference;
import com.windowtester.runtime.swt.internal.widgets.MenuReference;
import com.windowtester.runtime.util.ScreenCapture;
import com.windowtester.runtime.util.StringComparator;
/**
* Given a path, this class drives menu selection by interacting with menu and menu item
* references. This class collects state and thus should be re-used.
*/
public class MenuDriver
{
private static final int RETRY_LIMIT = 3;
/** Used internally by {@link #getNextMenuItem(MenuReference, String)} */
private MenuItemReference[] items;
/** Used internally by {@link #getNextMenuItem(MenuReference, String)} */
private String[] textForItems;
/**
* Find the menu item(s) specified by the menu item path and select each one in
* succession. Assume that the menu specified in the arguments is already visible but
* nested menus are not.
*
* @param menu the menu containing the first item to be selected.
* @return the last menu item that was selected
*/
public MenuItemReference resolveAndSelect(Callable<MenuReference> showFirstMenu, String menuItemPath)
throws WidgetNotFoundException, MultipleWidgetsFoundException
{
int millisecondPauseBetweenClicks = 0;
int retryCount = 0;
while (true) {
Exception exception;
try {
return resolveAndSelect0(showFirstMenu, menuItemPath, millisecondPauseBetweenClicks);
}
catch (Exception e) {
exception = e;
}
retryCount++;
System.out.println("Failed to select menu item\n menu item path: " + menuItemPath + "\n exception: "
+ exception + "\n attempt " + retryCount + " of " + RETRY_LIMIT);
if (retryCount >= RETRY_LIMIT) {
if (exception instanceof WidgetNotFoundException)
throw (WidgetNotFoundException) exception;
if (exception instanceof MultipleWidgetsFoundException)
throw (MultipleWidgetsFoundException) exception;
if (exception instanceof RuntimeException)
throw (RuntimeException) exception;
// Should not reach here, but just to be safe
throw new RuntimeException(exception);
}
millisecondPauseBetweenClicks += 2000;
}
}
private MenuItemReference resolveAndSelect0(Callable<MenuReference> showFirstMenu, String menuItemPath, int millisecondPauseBetweenClicks)
throws Exception
{
pause(millisecondPauseBetweenClicks);
MenuReference menu = showFirstMenu.call();
if (menu == null)
throw new IllegalArgumentException("menu cannot be null");
PathString path = new PathString(menuItemPath);
try {
MenuItemReference itemRef;
// Click each cascading menu item and wait for the menu to appear
while (true) {
pause(millisecondPauseBetweenClicks);
itemRef = getNextMenuItem(menu, path.next());
if (!path.hasNext())
break;
menu = itemRef.showMenu();
if (menu == null)
throw new IllegalStateException("Failed to show menu for " + itemRef + "\n menu item path: "
+ menuItemPath);
}
// Select the last menu item using a standard click
select(itemRef);
return itemRef;
}
// If any exception occurs, take a screenshot and close any open menus
catch (Exception e) {
ScreenCapture.createScreenCapture();
DisplayReference.getDefault().closeAllMenus();
throw e;
}
}
/**
* Can be customized in subclasses.
*/
protected void select(MenuItemReference itemRef) {
itemRef.click();
/*
* Alternately, select the menu item programmatically, but in testing,
* the SWTBot style selection does not dismiss the menu which causes problems.
* Must dismiss the menus first and then programmatically select menu item
*/
// DisplayReference.getDefault().closeAllMenus();
// System.out.println("menu item not visible, selecting programmatically");
// new SWTMenuItemOperation(itemRef).clickSWTBotStyle().execute();
}
private void pause(int milliseconds) {
if (milliseconds > 0) {
try {
Thread.sleep(milliseconds);
}
catch (InterruptedException e) {
//ignore interrupt
}
}
}
/**
* Search the specified menu for an item with text matching specified text
*
* @param menu the menu containing items to be searched
* @param rawItemText the text to be matched
* @return the menu item with text matching the specified text
*/
private MenuItemReference getNextMenuItem(final MenuReference menu, String rawItemText)
throws WidgetNotFoundException, MultipleWidgetsFoundException
{
String itemText = StringUtils.trimMenuText(rawItemText);
// Get the menu items and their associated text
menu.getDisplayRef().execute(new VoidCallable() {
public void call() throws Exception {
items = menu.getItems();
textForItems = new String[items.length];
for (int i = 0; i < items.length; i++) {
textForItems[i] = items[i].getTextForMatching();
// System.out.println(textForItems[i] + " : " + items[i].getDisplayBounds() + " parent : " + items[i].getParent().getDisplayBounds());
}
}
}, 30000);
// Look for an exact match
MenuItemReference found = null;
for (int i = 0; i < textForItems.length; i++) {
if (itemText.equals(textForItems[i])) {
if (found == null)
found = items[i];
else
throw new MultipleWidgetsFoundException("Multiple menu items found for \'" + itemText + "\' in "
+ getAllItemText(textForItems));
}
}
if (found != null)
return found;
// Look for a pattern match
for (int i = 0; i < textForItems.length; i++) {
if (StringComparator.matches(StringUtils.trimMenuText(textForItems[i]), itemText)) {
if (found == null)
found = items[i];
else
throw new MultipleWidgetsFoundException("Multiple menu items found for \'" + itemText + "\' in "
+ getAllItemText(textForItems));
}
}
if (found != null)
return found;
throw new WidgetNotFoundException("No menu item found for \'" + itemText + "\' in "
+ getAllItemText(textForItems));
}
/**
* Answer all text used in the text comparison as a single string.
*
* @param textForItems an array of text
* @return a string for inclusion in a {@link WidgetNotFoundException} or
* {@link MultipleWidgetsFoundException} message
*/
private StringBuilder getAllItemText(String[] textForItems) {
StringBuilder allItemText = new StringBuilder(128);
for (int i = 0; i < textForItems.length; i++) {
allItemText.append('\n');
allItemText.append(textForItems[i]);
}
return allItemText;
}
}