/*******************************************************************************
* 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.recorder.event;
import java.awt.Component;
import java.awt.Point;
import java.awt.Window;
import java.awt.event.MouseEvent;
import javax.swing.JComboBox;
import javax.swing.JList;
import javax.swing.JMenuItem;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.JTree;
import javax.swing.KeyStroke;
import javax.swing.tree.TreePath;
import abbot.script.ArgumentParser;
import abbot.tester.ComponentLocation;
import abbot.tester.ComponentTester;
import abbot.tester.JTreeLocation;
import abbot.tester.KeyStrokeMap;
import abbot.util.AWT;
import com.windowtester.internal.runtime.IWidgetIdentifier;
import com.windowtester.internal.swing.WidgetLocatorService;
import com.windowtester.internal.swing.util.ComponentAccessor;
import com.windowtester.internal.swing.util.TextUtils;
import com.windowtester.internal.tester.swing.JTableTester;
import com.windowtester.internal.tester.swing.JTreeTester;
import com.windowtester.recorder.event.user.SemanticComboSelectionEvent;
import com.windowtester.recorder.event.user.SemanticKeyDownEvent;
import com.windowtester.recorder.event.user.SemanticListSelectionEvent;
import com.windowtester.recorder.event.user.SemanticMenuSelectionEvent;
import com.windowtester.recorder.event.user.SemanticShellClosingEvent;
import com.windowtester.recorder.event.user.SemanticShellDisposedEvent;
import com.windowtester.recorder.event.user.SemanticShellShowingEvent;
import com.windowtester.recorder.event.user.SemanticTabbedPaneSelectionEvent;
import com.windowtester.recorder.event.user.SemanticTableSelectionEvent;
import com.windowtester.recorder.event.user.SemanticTreeItemSelectionEvent;
import com.windowtester.recorder.event.user.SemanticWidgetSelectionEvent;
import com.windowtester.recorder.event.user.TreeEventType;
import com.windowtester.recorder.event.user.UISemanticEvent;
import com.windowtester.recorder.event.user.UISemanticEvent.EventInfo;
import com.windowtester.runtime.WidgetLocator;
import com.windowtester.runtime.swing.locator.AbstractPathLocator;
import com.windowtester.runtime.swing.locator.JComboBoxLocator;
import com.windowtester.runtime.swing.locator.JListLocator;
import com.windowtester.runtime.swing.locator.JTabbedPaneLocator;
import com.windowtester.runtime.swing.locator.JTableItemLocator;
import com.windowtester.runtime.swing.locator.JTextComponentLocator;
import com.windowtester.runtime.swing.locator.JTreeItemLocator;
import com.windowtester.runtime.swing.locator.NamedWidgetLocator;
/***
*
* Factory for creating semantic events from AWT events using the new widgetLocator
* scheme.
*/
public class UISemanticEventFactory {
////////////////////////////////////////////////////////////////////////////
//
// Cached references
//
////////////////////////////////////////////////////////////////////////////
//the last seen widget --- whereby seen we mean has had info extracted...
private static Component _lastWidget;
//the last calculated widget locator
private static WidgetLocator _lastWidgetLocator;
// private static Hierarchy _hierarchy = AWTHierarchy.getDefault();
private static ComponentTester _menuItemTester = ComponentTester.getTester(JMenuItem.class);
private static ComponentTester _treeTester = ComponentTester.getTester(JTree.class);
////////////////////////////////////////////////////////////////////////////
//
// Public factory methods
//
////////////////////////////////////////////////////////////////////////////
/**
* Construct a SemanticKeyDownEvent
* @return a semantic event
*/
public static SemanticKeyDownEvent createKeyDownEvent(Component comp, char keychar,int mods) {
EventInfo info = extractInfo(comp,0,0,1);
SemanticKeyDownEvent keyDown = new SemanticKeyDownEvent(info);
keyDown.setKey(keychar);
KeyStroke ks = KeyStrokeMap.getKeyStroke(keychar);
keyDown.setKeyCode(ks.getKeyCode());
return keyDown;
}
public static SemanticKeyDownEvent createKeyDownEvent(Component comp, int code,char keychar){
EventInfo info = extractInfo(comp,0,0,0);
SemanticKeyDownEvent keyDown = new SemanticKeyDownEvent(info);
keyDown.setKey(keychar);
keyDown.setKeyCode(code);
return keyDown;
}
/**
* Construct a SemanticMenuSelectionEvent.
*
* @return a semantic event
*/
public static SemanticMenuSelectionEvent createMenuSelectionEvent(Component menuItem,int x,int y) {
JMenuItem item = (JMenuItem)menuItem;
String label = ComponentAccessor.extractMenuItemLabel(item);
String pathString =ComponentAccessor.extractMenuPath(item) + "/" + label;
//JMenu root = getRootMenu(item);
EventInfo info = new EventInfo();
//NOTE: we use the root to find
// 12/27: kp: why should we use root? - we need locator for menu item, so use the menuitem
AbstractPathLocator locator = (AbstractPathLocator)inferIdentifyingInfo(item);
//update locator path string
// locator.setPath(pathString);
info.cls = item.getClass().getName();
info.hierarchyInfo = locator;
info.button = 1;
info.x = x;
info.y = y;
SemanticMenuSelectionEvent menuSelect = new SemanticMenuSelectionEvent(info);
menuSelect.setItemLabel(label);
menuSelect.setPath(pathString);
return menuSelect;
}
/**
* Create a context menu selection event
* @param invoker
* @param x
* @param y
* @param item - menu item
* @return SemanticMenuSelectionEvent
*/
public static UISemanticEvent createContextMenuSelectionEvent(Component invoker,int x,int y,JMenuItem item) {
String pathString = _menuItemTester.deriveTag(item);
pathString = TextUtils.fixTabs(pathString);
EventInfo info = new EventInfo();
// info.toString = getTrimmedDescription(event);
info.cls = invoker.getClass().getName();
info.hierarchyInfo = inferIdentifyingInfo(invoker);
info.button = 3;
info.x = x;
info.y = y;
SemanticMenuSelectionEvent menuSelect = new SemanticMenuSelectionEvent(info);
String label = ComponentAccessor.extractMenuItemLabel(item);
menuSelect.setItemLabel(label);
menuSelect.setPath(pathString);
return menuSelect;
}
/**
* Create a tree selection semantic event
* @param invoker
* @param x
* @param y
* @param mask
* @param clkCount
* @param button
* @return
*/
public static UISemanticEvent createTreeItemSelectionEvent(
JTree invoker,int x,int y,String mask,int clkCount,int button){
/*
* Calculate path string
*/
ComponentLocation location = _treeTester.getLocation(invoker,new Point(x,y));
TreePath path = ((JTreeLocation)location).getPath(invoker);
TreePath pathString = JTreeTester.pathToStringPath(invoker,path);
String[] nodeNames = ComponentAccessor.parseTreePath(pathString.toString());
String nodePath = ComponentAccessor.assemblePath(nodeNames);
EventInfo info = extractInfo(invoker,x,y,button);
/*
* Extract guarantees uniqueness.
* Now we can set up a JTreeItemLocator based on the infered tree locator.
*/
IWidgetIdentifier locator = info.hierarchyInfo;
com.windowtester.runtime.swing.SwingWidgetLocator parent = getParentInfo(locator);
int index = getIndex(info.hierarchyInfo);
// check if tree is named
String name = invoker.getName();
if (name != null){
parent = new NamedWidgetLocator(name);
index = WidgetLocator.UNASSIGNED;
}
JTreeItemLocator itemLocator = new JTreeItemLocator(nodePath, index, parent);
//swap in our item locator
info.hierarchyInfo = itemLocator;
SemanticTreeItemSelectionEvent treeItemSelect;
if (clkCount == 1)
treeItemSelect =
new SemanticTreeItemSelectionEvent(info,TreeEventType.SINGLE_CLICK);
else
treeItemSelect =
new SemanticTreeItemSelectionEvent(info,TreeEventType.DOUBLE_CLICK);
treeItemSelect.setClicks(clkCount);
if (mask != null)
treeItemSelect.setMask(mask);
treeItemSelect.setItemLabel(nodeNames[nodeNames.length-1]);
treeItemSelect.setItemPath(nodePath);
return treeItemSelect;
}
private static com.windowtester.runtime.swing.SwingWidgetLocator getParentInfo(IWidgetIdentifier locator) {
if (locator instanceof com.windowtester.runtime.swing.SwingWidgetLocator) { //ugh
return (com.windowtester.runtime.swing.SwingWidgetLocator)((com.windowtester.runtime.swing.SwingWidgetLocator)locator).getParentInfo();
}
return null;
}
private static int getIndex(IWidgetIdentifier locator) {
if (locator instanceof WidgetLocator) { //ugh
return ((WidgetLocator)locator).getIndex();
}
return WidgetLocator.UNASSIGNED;
}
/**
* Create a context menu selection on a tree item
* @param invoker
* @param x
* @param y
* @param item
* @return
*/
public static UISemanticEvent createTreeItemContextMenuSelectionEvent(JTree invoker,int x,int y,JMenuItem item){
String menuPath = _menuItemTester.deriveTag(item);
menuPath = TextUtils.fixTabs(menuPath);
SemanticTreeItemSelectionEvent treeContextMenuSelect =
(SemanticTreeItemSelectionEvent)createTreeItemSelectionEvent(invoker,x,y,null,1,3);
treeContextMenuSelect.setContextMenuSelectionPath(menuPath);
return treeContextMenuSelect;
}
/**
* Create a context menu selection on a tree item
* @param invoker
* @param row
* @param item
* @return
*/
public static UISemanticEvent createTreeItemContextMenuSelectionEvent(JTree invoker,int row,JMenuItem item){
EventInfo info = extractInfo(invoker,-1,-1,3);
SemanticTreeItemSelectionEvent treeItemSelect =
new SemanticTreeItemSelectionEvent(info,TreeEventType.SINGLE_CLICK);
treeItemSelect.setClicks(1);
JTreeLocation location = new JTreeLocation(row);
TreePath path = location.getPath(invoker);
TreePath pathString = JTreeTester.pathToStringPath(invoker,path);
String[] nodeNames = ComponentAccessor.parseTreePath(pathString.toString());
String nodePath = ComponentAccessor.assemblePath(nodeNames);
treeItemSelect.setItemLabel(nodeNames[nodeNames.length-1]);
treeItemSelect.setItemPath(nodePath);
String menuPath = _menuItemTester.deriveTag(item);
menuPath = TextUtils.fixTabs(menuPath);
treeItemSelect.setContextMenuSelectionPath(menuPath);
return treeItemSelect;
}
public static UISemanticEvent createTabbedPaneSelectionEvent(JTabbedPane invoker,int x,int y,int index){
EventInfo info = extractInfo(invoker,x,y,1);
String tabLabel = invoker.getTitleAt(index);
// swap in custom tabbed pane locator
com.windowtester.runtime.swing.SwingWidgetLocator parentInfo = getParentInfo(info.hierarchyInfo);
int indx = getIndex(info.hierarchyInfo);
// check whether component is named
String name = invoker.getName();
if (name != null){
parentInfo = new NamedWidgetLocator(name);
indx = WidgetLocator.UNASSIGNED;
}
info.hierarchyInfo = new JTabbedPaneLocator(tabLabel,indx,parentInfo);
SemanticTabbedPaneSelectionEvent tabbedPaneEvent =
new SemanticTabbedPaneSelectionEvent(info);
tabbedPaneEvent.setIndex(index);
tabbedPaneEvent.setTabLabel(tabLabel);
return tabbedPaneEvent;
}
/**
* Create a table selection semantic event
* @param invoker
* @param x
* @param y
* @param mods
* @param clkCount
* @param button
* @return
*/
public static UISemanticEvent createTableSelectionEvent(JTable invoker,int x,int y,String mask,int clkCount,int button){
EventInfo info = extractInfo(invoker,x,y,button);
Point where = new Point(x, y);
int row = invoker.rowAtPoint(where);
int col = invoker.columnAtPoint(where);
String label = JTableTester.valueToString(invoker,row,col);
//build and hook up table item locator
com.windowtester.runtime.swing.SwingWidgetLocator parentInfo = getParentInfo(info.hierarchyInfo);
int index = getIndex(info.hierarchyInfo);
// check if table is named, if yes create named widget locator
String name = invoker.getName();
if (name != null){
parentInfo = new NamedWidgetLocator(name);
index = WidgetLocator.UNASSIGNED;
}
info.hierarchyInfo = new JTableItemLocator(new Point(row, col), index, parentInfo);
SemanticTableSelectionEvent tableSelect = new SemanticTableSelectionEvent(info);
tableSelect.setClicks(clkCount);
tableSelect.setItemLabel(label);
tableSelect.setTableItemRow(row);
tableSelect.setTableItemCol(col);
if (mask != null)
tableSelect.setMask(mask);
return tableSelect;
}
/**
* create a context menu selection for a table item.
* @param table
* @param x
* @param y
* @param item
* @return
*/
public static UISemanticEvent createTableContextMenuSelectionEvent(JTable table,int x,int y,JMenuItem item){
//String menuPath = ComponentAccessor.extractPopupMenuPath(item);;
String menuPath = _menuItemTester.deriveTag(item);
menuPath = TextUtils.fixTabs(menuPath);
SemanticTableSelectionEvent tableContextMenuSelect =
(SemanticTableSelectionEvent)createTableSelectionEvent(table,x,y,null,1,3);
tableContextMenuSelect.setContextMenuSelectionPath(menuPath);
return tableContextMenuSelect;
}
/**
* Create a context menu selection for a table item.
* @param table
* @param item
* @param row
* @param col
* @return
*/
public static UISemanticEvent createTableContextMenuSelectionEvent(JTable table,JMenuItem item,int row,int col){
// get menu label - not path
//String menuPath = ComponentAccessor.extractPopupMenuPath(item);
String menuPath = _menuItemTester.deriveTag(item);
menuPath = TextUtils.fixTabs(menuPath);
EventInfo info = extractInfo(table,-1,-1,3);
SemanticTableSelectionEvent tableContextMenuSelect =
new SemanticTableSelectionEvent(info);
tableContextMenuSelect.setClicks(1);
String label = JTableTester.valueToString(table,row,col);
tableContextMenuSelect.setItemLabel(label);
tableContextMenuSelect.setTableItemRow(row);
tableContextMenuSelect.setContextMenuSelectionPath(menuPath);
return tableContextMenuSelect;
}
/**
* Create a combo box selection semantic event.
* @param combo
* @param label
* @return
*/
public static SemanticComboSelectionEvent createComboSelectionEvent(JComboBox combo,String label){
EventInfo info = extractInfo(combo,1,1,1);
//swap in our combo locator (post identification)
com.windowtester.runtime.swing.SwingWidgetLocator parentInfo = getParentInfo(info.hierarchyInfo);
int index = getIndex(info.hierarchyInfo);
// check if combo is named
String name = combo.getName();
if (name != null){
parentInfo = new NamedWidgetLocator(name);
index = WidgetLocator.UNASSIGNED;
}
info.hierarchyInfo = new JComboBoxLocator(label, index,parentInfo);
SemanticComboSelectionEvent comboEvent =
new SemanticComboSelectionEvent(info);
comboEvent.setSelection(label);
return comboEvent;
}
/**
* create a list selection semantic event
* @param list
* @param x
* @param y
* @param mods
* @param count
* @param button
* @return
*/
public static UISemanticEvent createListSelectionEvent(JList list, int x, int y,
int mods, int count,int button) {
EventInfo info = new EventInfo();
info.cls = list.getClass().getName();
info.hierarchyInfo = inferIdentifyingInfo(list);
info.button = button;
info.x = x;
info.y = y;
int index = list.locationToIndex(new Point(x,y));
Object value = list.getModel().getElementAt(index);
Component cr = list.getCellRenderer().getListCellRendererComponent(list, value, index, false, false);
String string = null;
String item;
if (cr instanceof javax.swing.JLabel) {
string = ((javax.swing.JLabel)cr).getText();
if (string != null){
string = string.trim();
}
}
if (!"".equals(string)
&& !ArgumentParser.isDefaultToString(string)) {
item = string;
}
else {
item = list.getModel().getElementAt(index).toString();
}
//swap in custom list locator
com.windowtester.runtime.swing.SwingWidgetLocator parentInfo = getParentInfo(info.hierarchyInfo);
int indx = getIndex(info.hierarchyInfo);
// check if name is set
String name = list.getName();
if (name != null){ // create a named widget locator
parentInfo = new NamedWidgetLocator(name);
indx = WidgetLocator.UNASSIGNED;
}
info.hierarchyInfo = new JListLocator(item, indx, parentInfo);
SemanticListSelectionEvent listSelect = new SemanticListSelectionEvent(info);
listSelect.setClicks(count);
if (mods != MouseEvent.BUTTON1_MASK)
listSelect.setMask(AWT.getMouseModifiers(mods));
listSelect.setItem(item);
return listSelect;
}
/**
* create a widget selection semantic event
* @param widget
* @param x
* @param y
* @param count
* @param button
* @return
*/
public static UISemanticEvent createWidgetSelectionEvent(Component widget,
int x, int y, int count,int button) {
EventInfo info = extractInfo(widget,x,y,button);
SemanticWidgetSelectionEvent widgetSelect = new SemanticWidgetSelectionEvent(info);
widgetSelect.setClicks(count);
widgetSelect.setItemLabel(ComponentAccessor.extractWidgetLabel(widget));
return widgetSelect;
}
public static UISemanticEvent createTextComponentSelectionEvent(Component widget,
int x,int y,int count,int button,int caret) {
EventInfo info = new EventInfo();
info.cls = widget.getClass().getName();
info.hierarchyInfo = inferIdentifyingInfo(widget);
info.button = button;
info.x = x;
info.y = y;
// swap in custom list locator , if component is JTextPane
//com.windowtester.runtime.swing.SwingWidgetLocator parentInfo = getParentInfo(info.hierarchyInfo);
// set caret if necessary, a click not in the start of the field
if (widget instanceof JTextField)
if (((JTextField)widget).getText().length() == caret)
caret = 0;
// if it is a NamedWidgetLocator, there is no set caret
if (info.hierarchyInfo instanceof JTextComponentLocator) {
if (caret != 0)
((JTextComponentLocator)info.hierarchyInfo).setCaretPosition(caret);
else
((JTextComponentLocator)info.hierarchyInfo).setCaretPosition(WidgetLocator.UNASSIGNED);
}
SemanticWidgetSelectionEvent widgetSelect = new SemanticWidgetSelectionEvent(info);
widgetSelect.setClicks(count);
return widgetSelect;
}
/**
* Create a shell closing semantic event for window close AWT events
* @param window
* @return
*/
public static UISemanticEvent createShellClosingEvent(Window window) {
EventInfo info = extractInfo(window,0,0,1);
String text = null;
text = ComponentAccessor.extractTitle(window);
return new SemanticShellClosingEvent(info, text);
}
/**
* Create a shell disposed semantic event for window close AWT events
* @param window
* @return
*/
public static UISemanticEvent createShellDisposedEvent(Window window) {
EventInfo info = extractInfo(window,0,0,1);
String text = null;
text = ComponentAccessor.extractTitle(window);
return new SemanticShellDisposedEvent(info, text);
}
/**
* Create a shell showing semantic event.
* @param window
* @return
*/
public static UISemanticEvent createShellShowingEvent(Window window) {
EventInfo info = extractInfo(window,0,0,1);
String text = null;
text = ComponentAccessor.extractTitle(window);
return new SemanticShellShowingEvent(info, text);
}
/**
* Extract relevant info .
* @param event
* @return
*/
private static EventInfo extractInfo(Component widget, int x, int y, int button) {
EventInfo info = new EventInfo();
info.cls = widget.getClass().getName();
info.button = button;
info.x = x;
info.y = y;
/*
* here we do a little caching to speed things up
*/
if (widget == _lastWidget && _lastWidgetLocator != null) {
info.hierarchyInfo = _lastWidgetLocator;
} else { //do it the hard way:
info.hierarchyInfo = inferIdentifyingInfo(widget);
//and update the cache
_lastWidgetLocator = (WidgetLocator)info.hierarchyInfo;
_lastWidget = widget;
}
return info;
}
private static WidgetLocator inferIdentifyingInfo(Component widget) {
WidgetLocator locator = new WidgetLocatorService().inferIdentifyingInfo(widget);
return locator;
}
////////////////////////////////////////////////////////////////////////////
//
// Construction helpers
//
////////////////////////////////////////////////////////////////////////////
}