blob: 0196d98774bcedb66ab1f9c6f5d9681ec70ee0af [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2008 IBM Corporation and Others
* 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:
* Takashi ITOH - initial API and implementation
*******************************************************************************/
package org.eclipse.actf.visualization.gui.ui.views;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.actf.accservice.swtbridge.AccessibleObject;
import org.eclipse.actf.accservice.swtbridge.AccessibleObjectFactory;
import org.eclipse.actf.accservice.swtbridge.IA2;
import org.eclipse.actf.accservice.swtbridge.MSAA;
import org.eclipse.actf.accservice.swtbridge.event.IAccessibleEventListener;
import org.eclipse.actf.accservice.swtbridge.event.IAccessibleEventMonitor;
import org.eclipse.actf.accservice.swtbridge.ia2.Accessible2;
import org.eclipse.actf.accservice.swtbridge.ia2.AccessibleText;
import org.eclipse.actf.accservice.swtbridge.ia2.TextSegment;
import org.eclipse.actf.ai.voice.VoicePlugin;
import org.eclipse.actf.util.win32.WindowUtil;
import org.eclipse.actf.visualization.gui.GuiPlugin;
import org.eclipse.actf.visualization.gui.Messages;
import org.eclipse.actf.visualization.gui.preferences.GuiPreferenceConstants;
import org.eclipse.actf.visualization.gui.util.ScreenReaderRenderer;
import org.eclipse.actf.visualization.gui.util.TTSMonitor;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferenceConverter;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.dialogs.ListSelectionDialog;
import org.eclipse.ui.dialogs.PreferencesUtil;
import org.eclipse.ui.part.ViewPart;
public class MSAAEventView extends ViewPart implements IMSAAEventView, IAccessibleEventListener {
public static final String ID = MSAAEventView.class.getName();
private StyledText text;
private Action clearAction;
private Action speakEventAction;
private Action openPreferencesAction;
private Action eventFilterAction;
private Action showTTSEventAction;
private boolean showRawEvent = false;
private IPreferenceStore preferenceStore = GuiPlugin.getDefault().getPreferenceStore();
private static final int[] SUPPORTED_EVENTS = new int[] {
0x001, 0x002, 0x003, 0x004, 0x005, 0x006, 0x007, 0x008, 0x009, 0x00A, 0x00B, 0x00C, 0x00D, 0x00E, 0x00F,
0x010, 0x011, 0x012, 0x013, 0x014, 0x015, 0x016, 0x017,
0x8000, 0x8001, 0x8002, 0x8003, 0x8004, 0x8005, 0x8006, 0x8007, 0x8008, 0x8009, 0x800A, 0x800B, 0x800C, 0x800D, 0x800E, 0x800F,
0x8010, 0x8011, 0x8012,
0x101, 0x102, 0x103, 0x104, 0x105, 0x106, 0x107, 0x108, 0x109, 0x10A, 0x10B, 0x10C, 0x10D, 0x10E, 0x10F,
0x110, 0x111, 0x112, 0x113, 0x114, 0x115, 0x116, 0x117, 0x118, 0x119, 0x11A, 0x11B, 0x11C, 0x11D, 0x11E, 0x11F,
0x120, 0x121, 0x122
};
private static final int[] DEFAULT_EVENTS = new int[] {
MSAA.EVENT_OBJECT_FOCUS,
};
private IAccessibleEventMonitor monitor = null;
/**
* The constructor.
*/
public MSAAEventView() {
}
/**
* This is a callback that will allow us
* to create the viewer and initialize it.
*/
public void createPartControl(Composite parent) {
text = new StyledText(parent,SWT.WRAP | SWT.V_SCROLL | SWT.H_SCROLL);
text.setEditable(false);
text.setLayout(new GridLayout());
text.setLayoutData(new GridData());
resetFont();
preferenceStore.addPropertyChangeListener(new IPropertyChangeListener() {
public void propertyChange(PropertyChangeEvent event) {
if( GuiPreferenceConstants.MSAAEventView_Font.equals(event.getProperty())) {
resetFont();
}
}
});
makeActions();
hookContextMenu();
contributeToActionBars();
reset();
TTSMonitor.startThread(this);
}
private void hookContextMenu() {
MenuManager menuMgr = new MenuManager("#PopupMenu"); //$NON-NLS-1$
menuMgr.setRemoveAllWhenShown(true);
menuMgr.addMenuListener(new IMenuListener() {
public void menuAboutToShow(IMenuManager manager) {
MSAAEventView.this.fillContextMenu(manager);
}
});
Menu menu = menuMgr.createContextMenu(text);
text.setMenu(menu);
}
private void contributeToActionBars() {
IActionBars bars = getViewSite().getActionBars();
fillLocalPullDown(bars.getMenuManager());
fillLocalToolBar(bars.getToolBarManager());
}
private void fillLocalPullDown(IMenuManager manager) {
manager.add(eventFilterAction);
// manager.add(showTTSEventAction); // TODO: Tempolary disabled for alphaWorks. To be restored for ACTF
manager.add(new Separator());
manager.add(speakEventAction);
manager.add(new Separator());
manager.add(openPreferencesAction);
}
private void fillContextMenu(IMenuManager manager) {
manager.add(clearAction);
// Other plug-ins can contribute there actions here
manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
}
private void fillLocalToolBar(IToolBarManager manager) {
manager.add(clearAction);
}
private void makeActions() {
clearAction = new Action() {
public void run() {
text.setText(""); //$NON-NLS-1$
}
};
clearAction.setText(Messages.getString("msaa.clear")); //$NON-NLS-1$
clearAction.setToolTipText(Messages.getString("msaa.clear_tip")); //$NON-NLS-1$
clearAction.setImageDescriptor(GuiPlugin.IMAGE_CLEAR);
speakEventAction = new Action(Messages.getString("msaa.speak_event"),Action.AS_CHECK_BOX) { //$NON-NLS-1$
public void run() {
}
};
final Shell shell = this.getViewSite().getShell();
openPreferencesAction = new Action(Messages.getString("msaa.preferences")) { //$NON-NLS-1$
public void run() {
PreferencesUtil
.createPreferenceDialogOn(shell, "org.eclipse.actf.visualization.gui.preferences.GuiPreferencePage", null, null) //$NON-NLS-1$
.open();
}
};
showTTSEventAction = new Action(Messages.getString("msaa.showTTSEvents"),Action.AS_CHECK_BOX) { //$NON-NLS-1$
public void run() {
}
};
showTTSEventAction.setChecked(true);
eventFilterAction = new Action(Messages.getString("msaa.filters_menu")) { //$NON-NLS-1$
public void run() {
EventFiltersDialog dialog = new EventFiltersDialog(shell,intArrayToObjectArray(SUPPORTED_EVENTS),showRawEvent);
if( Window.OK == dialog.open() ) {
filters.clear();
Object[] result= dialog.getResult();
for( int i=0; i<result.length; i++ ) {
filters.put(result[i],Boolean.TRUE);
}
showRawEvent = dialog.getShowDetails();
if( null != monitor ) {
monitor.installEventHook(MSAAEventView.this, getFilter());
}
}
}
};
}
class EventFiltersDialog extends ListSelectionDialog {
private boolean showDetails;
private Button showDetailsButton;
public EventFiltersDialog(Shell parentShell, Integer[] input, boolean showDetails) {
super(parentShell, input, new EventFiltersContentProvider(), new EventFiltersLabelProvider(), Messages.getString("msaa.filters_message")); //$NON-NLS-1$
setTitle(Messages.getString("msaa.filters_title")); //$NON-NLS-1$
List<Integer> initialSelection = new ArrayList<Integer>();
for( int i=0; i<input.length; i++ ) {
if( getFilterEnabled(input[i].intValue()) ) {
initialSelection.add(input[i]);
}
}
setInitialElementSelections(initialSelection);
this.showDetails = showDetails;
}
protected Control createDialogArea(Composite parent) {
Composite composite = (Composite)super.createDialogArea(parent);
Control[] children = composite.getChildren();
Composite buttonComposite = (Composite)children[children.length-1];
Button defaultButton = new Button(buttonComposite,SWT.PUSH);
defaultButton.setText(Messages.getString("msaa.filters_default")); //$NON-NLS-1$
defaultButton.addSelectionListener(new SelectionAdapter(){
public void widgetSelected(SelectionEvent e) {
getViewer().setCheckedElements(intArrayToObjectArray(DEFAULT_EVENTS));
showDetailsButton.setSelection(false);
}
});
showDetailsButton = new Button(composite,SWT.CHECK);
showDetailsButton.setText(Messages.getString("msaa.showRawEvents")); //$NON-NLS-1$
showDetailsButton.setSelection(showDetails);
showDetailsButton.setFont(parent.getFont());
// Adjust button layout
showDetailsButton.moveAbove(buttonComposite);
((GridLayout)buttonComposite.getLayout()).numColumns++;
defaultButton.moveAbove(null);
return composite;
}
protected void okPressed() {
showDetails = showDetailsButton.getSelection();
super.okPressed();
}
public boolean getShowDetails() {
return showDetails;
}
}
class EventFiltersContentProvider implements IStructuredContentProvider {
public Object[] getElements(Object input) {
if( input instanceof Integer[] ) {
return (Object[])input;
}
return new Object[0];
}
public void inputChanged(Viewer viewer, Object a, Object b) {}
public void dispose() {}
};
class EventFiltersLabelProvider extends LabelProvider {
public String getText(Object element) {
if( element instanceof Integer ) {
return MSAA.getEventTypeText(((Integer)element).intValue());
}
return null;
}
};
private Integer[] intArrayToObjectArray(int[] intArray) {
Integer[] objArray = new Integer[intArray.length];
for( int i=0; i<intArray.length; i++ ) {
objArray[i] = new Integer(intArray[i]);
}
return objArray;
}
private void resetFont() {
FontData fontData = PreferenceConverter.getFontData(preferenceStore,GuiPreferenceConstants.MSAAEventView_Font);
text.setFont(new Font(text.getDisplay(), fontData));
}
/**
* Passing the focus request to the viewer's control.
*/
public void setFocus() {
text.setFocus();
}
public void dispose() {
super.dispose();
TTSMonitor.stopThread();
if( null != monitor ) {
monitor.removeEventHook();
monitor = null;
}
}
private void showEventInfo(int event, int hwnd, int idObject, int idChild, AccessibleObject accObject) {
if( text.isDisposed() || text.isFocusControl() ) {
return;
}
boolean release = false;
if( null == accObject) {
accObject = AccessibleObjectFactory.getAccessibleObjectFromEvent(hwnd,idObject,idChild);
release = true;
}
if( null!=accObject ) {
int accRole = accObject.getAccRole();
int accState = accObject.getAccState();
boolean ignore = 0!=(accState & MSAA.STATE_INVISIBLE);
if( !ignore ) {
switch( event ) {
case MSAA.EVENT_OBJECT_FOCUS:
Accessible2 accExt = accObject.getAccessible2();
if( null != accExt ) {
IMSAAOutlineView outlineView = (IMSAAOutlineView)MSAAViewRegistory.findView(MSAAViewRegistory.MSAAOutlineView_ID);
if( null != outlineView ) {
AccessibleObject accTarget = getCachedChildFromUID(MSAAViewRegistory.rootObject,accExt.getUniqueID());
outlineView.setSelection(accTarget);
if( null == accTarget ) {
release = !MSAAViewRegistory.showProperties(accObject);
}
}
}
break;
case MSAA.EVENT_OBJECT_NAMECHANGE:
break;
case MSAA.EVENT_OBJECT_STATECHANGE:
case MSAA.EVENT_OBJECT_VALUECHANGE:
ignore = 0==(MSAA.STATE_FOCUSED&accState);
break;
case MSAA.EVENT_OBJECT_CREATE:
// For refresh IE
if( MSAA.ROLE_SYSTEM_PANE==accRole ) {
if( validate(MSAAViewRegistory.rootObject,5,10) ) {
break; // Skip banner window
}
if( delayedRefresh(2000) ) {
break;
}
}
ignore = true;
break;
}
}
if( !ignore && getFilterEnabled(event) ) {
checkInterval();
int buttonIndex = -1;
if( MSAA.ROLE_SYSTEM_PUSHBUTTON == accRole ) {
buttonIndex = getChildIndex(accObject);
}
if( showRawEvent/*Action.isChecked()*/ ) {
text.append("event="+MSAA.getEventTypeText(event) +", "+ //$NON-NLS-1$ //$NON-NLS-2$
"accName="+accObject.getAccName() +", "+ //$NON-NLS-1$ //$NON-NLS-2$
"accRole="+accObject.getRoleText() +", "+ //$NON-NLS-1$ //$NON-NLS-2$
"accState="+MSAA.getStateText(accState) +"(0x"+Integer.toHexString(accState)+")\n"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
String str = null;
boolean doRender = true;
switch( event ) {
case IA2.IA2_EVENT_TEXT_CARET_MOVED:
AccessibleText accText = accObject.getAccessibleText();
if( null != accText ) {
Point selection = accText.getSelection(0);
if( null != selection ) {
str = accText.getTextRange(selection.x,selection.y);
}
else {
TextSegment ts = accText.getTextAtIndex(accText.getCaretPosition(),IA2.IA2_TEXT_BOUNDARY_CHAR);
if( null != ts ) {
str = ts.text;
}
}
if( null != str ) {
text.append(" "); //$NON-NLS-1$
StyleRange range = new StyleRange();
range.background = Display.getCurrent().getSystemColor(SWT.COLOR_YELLOW);
range.start = text.getCharCount();
range.length = str.length();
text.append(str);
text.setStyleRange(range);
}
}
doRender = false;
break;
case IA2.IA2_EVENT_TEXT_SELECTION_CHANGED:
doRender = false;
break;
}
if( doRender ) {
ScreenReaderRenderer renderer = new ScreenReaderRenderer(text,null);
str = renderer.renderEvent(event);
if( 0==str.length() ) {
str = renderer.renderItem(accObject,false,buttonIndex);
}
}
text.setSelection(text.getCharCount());
if( speakEventAction.isChecked() ) {
if( null != str && str.length() > 0 ) {
VoicePlugin.getVoice().speak(str,true);
}
}
}
if( release ) {
AccessibleObject parent = accObject.getCachedParent();
try {
if( null != parent ) {
parent.dispose();
}
else {
accObject.dispose();
}
}
catch( Exception e ) {
e.printStackTrace();
}
}
}
}
private boolean refreshing = false;
private boolean delayedRefresh(int delayMS) {
if( refreshing ) {
return false;
}
refreshing = true;
text.getDisplay().timerExec(delayMS,new Runnable(){
public void run() {
try {
if( !text.isDisposed() ) {
MSAAViewRegistory.refreshRootObject();
}
}
finally {
refreshing = false;
}
}
});
return true;
}
private Map filters = getDefaultFilters();
private Map getDefaultFilters() {
Map defaultFilters = new HashMap();
for( int i=0; i<DEFAULT_EVENTS.length; i++ ) {
defaultFilters.put(new Integer(DEFAULT_EVENTS[i]), Boolean.TRUE);
}
return defaultFilters;
}
public boolean getFilterEnabled(int event) {
Integer key = new Integer(event);
Boolean enabled = (Boolean)filters.get(key);
if( null == enabled ) {
enabled = Boolean.FALSE;
filters.put(key, enabled);
}
return enabled.booleanValue();
}
public void setTTSText(final String speakText) {
if( null != speakText && speakText.length() > 0 ) {
text.getDisplay().asyncExec(new Runnable(){
public void run() {
if( text.isFocusControl() || !showTTSEventAction.isChecked() ) {
return;
}
checkInterval();
StyleRange range = new StyleRange();
range.foreground = Display.getCurrent().getSystemColor(SWT.COLOR_DARK_GREEN);
range.start = text.getCharCount();
range.length = speakText.length();
text.append(speakText);
text.setStyleRange(range);
text.setSelection(text.getCharCount());
if( speakEventAction.isChecked() ) {
VoicePlugin.getVoice().speak(speakText,true);
}
}
});
}
}
private long lastTime = 0;
private void checkInterval() {
long currentTime = System.currentTimeMillis();
if( 0 != lastTime ) {
try {
long passed = Math.abs(currentTime - lastTime);
if( passed > 1000 ) {
String separatorString = MessageFormat.format(Messages.getString("msaa.ns_passed"),new Object[]{new Float(((float)passed)/1000)}); //$NON-NLS-1$
StyleRange range = new StyleRange();
range.foreground = Display.getCurrent().getSystemColor(SWT.COLOR_GRAY);
range.start = text.getCharCount();
int lastPos = range.start-1;
if( lastPos > 0 && !"\n".equals(text.getText(lastPos,lastPos)) ) { //$NON-NLS-1$
text.append("\n"); //$NON-NLS-1$
}
text.append(separatorString);
text.append("\n"); //$NON-NLS-1$
range.length = text.getCharCount()-range.start;
text.setStyleRange(range);
}
}
catch( Exception e ) {
e.printStackTrace();
}
}
lastTime = currentTime;
}
public void handleEvent(final int event, final int hwnd, final int idObject, final int idChild, final AccessibleObject accObject) {
if( checkShowInfo(event, hwnd) ) {
text.getDisplay().asyncExec(new Runnable(){
public void run() {
showEventInfo(event,hwnd,idObject,idChild,accObject);
}
});
}
}
private Set getFilter() {
Set enabledSet = new HashSet();
for( Iterator it = filters.keySet().iterator(); it.hasNext(); ) {
Object key = it.next();
Boolean enabled = (Boolean)filters.get(key);
if( null != enabled && enabled.booleanValue() ) {
enabledSet.add(key);
}
}
enabledSet.add(new Integer(MSAA.EVENT_OBJECT_CREATE)); // For refresh IE
return enabledSet;
}
private boolean checkShowInfo(int event, int hwnd) {
boolean showInfo = false;
if( 0 == MSAAViewRegistory.getUpdateRef() && null != MSAAViewRegistory.rootObject ) {
if( null != MSAA.getEventTypeText(event) ) {
int hwndRoot = MSAAViewRegistory.rootObject.getWindow();
if( 0 !=hwndRoot ) {
showInfo = MSAA.EVENT_OBJECT_FOCUS==event && WindowUtil.isPopupMenu(hwnd);
for( int hwndParent = hwnd; !(showInfo || 0==hwndParent); hwndParent = WindowUtil.GetParentWindow(hwndParent) ) {
showInfo = (hwndRoot == hwndParent);
}
}
}
}
return showInfo;
}
private static AccessibleObject getCachedChildFromUID(AccessibleObject root,int uid) {
Accessible2 accessible2 = root.getAccessible2();
if( null != accessible2 && uid == accessible2.getUniqueID() ) {
return root;
}
AccessibleObject cachedChildren[] = root.getCachedChildren();
for( int i=0; i<cachedChildren.length; i++ ) {
AccessibleObject accObj = getCachedChildFromUID(cachedChildren[i],uid);
if( null != accObj ) {
return accObj;
}
}
return null;
}
/**
* Get index number (for JAWS simulation)
* @return
*/
private static int getChildIndex(AccessibleObject accObject) {
AccessibleObject disposeLater = null;
try {
AccessibleObject parent = accObject.getCachedParent();
if( null==parent ) {
parent = disposeLater = accObject.getAccParent();
if( null==parent ) return 0;
}
AccessibleObject[] children = parent.getChildren();
Rectangle myRect = accObject.getAccLocation();
if( null != myRect ) {
for( int i=0; i<children.length; i++ ) {
if( accObject.equals(children[i]) ||
myRect.equals(children[i].getAccLocation()) ) {
return i;
}
}
}
}
finally {
if( null != disposeLater ) {
try {
disposeLater.dispose();
}
catch( Exception e) {
e.printStackTrace();
}
}
}
return 0;
}
private static boolean validate(AccessibleObject accValidate, int depth, int bound) {
if (null == accValidate.getAccName() &&
null == accValidate.getAccLocation() &&
null == accValidate.getAccKeyboardShortcut()) {
return false;
}
AccessibleObject parent = accValidate.getCachedParent();
AccessibleObject[] children = accValidate.getCachedChildren();
if( null==parent && children.length==0 ) {
return false;
}
if (depth-- > 0) {
int max = children.length;
int len = Math.min(max,bound);
for (int i = 0; i < len; i++) {
AccessibleObject ao = children[i];
if (null!=ao && !validate(ao,depth,bound)) {
return false;
}
}
len = max - len;
if( len > 0 ) {
for (int i = 0; i < len; i++) {
AccessibleObject ao = children[max-1-i];
if (null!=ao && !validate(ao,depth,bound)) {
return false;
}
}
}
}
return true;
}
public void reset() {
if( null != monitor ) {
monitor.removeEventHook();
monitor = null;
}
monitor = AccessibleObjectFactory.getAccessibleEventMonitor();
if( null != monitor ) {
monitor.installEventHook(this, getFilter());
}
}
}