package net.xoetrope.swt;
import java.awt.AWTEvent;
import java.lang.reflect.Method;
import java.util.EventObject;
import java.util.Hashtable;
import net.xoetrope.xui.build.BuildProperties;
import net.xoetrope.debug.DebugLogger;
import net.xoetrope.xui.PageSupport;
import net.xoetrope.xui.WidgetAdapter;
import net.xoetrope.xui.XEventHandler;
import net.xoetrope.xui.XHashCode;
import net.xoetrope.xui.XProject;
import net.xoetrope.xui.events.XuiEventHandler;
import net.xoetrope.xui.validation.XValidationHandler;
import net.xoetrope.xui.validation.XValidator;
import net.xoetrope.xui.XMethodReference;
import net.xoetrope.xui.XPageHelper;
import net.xoetrope.xui.evaluator.XAttributeEvaluator;
import net.xoetrope.xui.evaluator.XDefaultAttributeEvaluator;
import net.xoetrope.xui.events.XEventAdapter;
import net.xoetrope.xui.events.XListenerHelper;
import net.xoetrope.xui.helper.XuiUtilities;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.MouseTrackListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
/**
* <p>
* Implements an event handler for XUI. Most of the common events are handled.
* The class is intended as a mixin for a panel class such as XPage and should
* not be used directly
* </p>
* <p>
* Copyright (c) Xoetrope Ltd., 2002-2003
* </p>
* <p>
* License: see license.txt
* </p>
* $Revision: 1.8 $
*/
public class XSwtEventHandler implements XuiEventHandler, SelectionListener, FocusListener, KeyListener, MouseListener, MouseMoveListener,
MouseTrackListener
{
protected static EventObject currentEvt;
protected Object container;
protected XProject currentProject;
protected Hashtable handlers;
protected Hashtable classInstances;
protected static Object mouseDownComponent = null;
protected static boolean mouseEventInvoked = false;
protected static int suppressFocusEvents = 0;
protected XValidationHandler xValidationHandler;
/**
* Create a new XSwtEventHandler
*
* @param project
* project concerned
* @param c
* container object
* @param vh
* validation handler
*/
public XSwtEventHandler( XProject project, Object c, XValidationHandler vh )
{
handlers = new Hashtable( 5 );
currentProject = project;
container = c;
xValidationHandler = vh;
vh.setupEventHandler( this, "org.eclipse.swt.events.FocusListener", AWTEvent.FOCUS_EVENT_MASK );
}
/**
* Remove all the event handlers for a particular object
*
* @param comp
* the object whose events are being removed
*/
public void removeHandlers( Object comp )
{
long hashCode = comp.hashCode();
if ( comp instanceof XHashCode )
hashCode = ( (XHashCode)comp ).getComponentHashCode();
long[] handledEvents = {
AWTEvent.ACTION_EVENT_MASK, AWTEvent.FOCUS_EVENT_MASK, AWTEvent.TEXT_EVENT_MASK, AWTEvent.ITEM_EVENT_MASK, AWTEvent.KEY_EVENT_MASK,
AWTEvent.MOUSE_EVENT_MASK, AWTEvent.MOUSE_MOTION_EVENT_MASK
};
// String[] listenerMethods = {
// "removeActionListener", "removeFocusListener", "removeTextListener", "removeItemListener", "removeKeyListener", "removeMouseListener",
// "removeMouseMotionListener"
// };
String[] listener = {
"ActionListener", "FocusListener", "TextListener", "ItemListener", "KeyListener", "MouseListener", "MouseMotionListener"
};
for ( int i = 0; i < handledEvents.length; i++ ) {
Object key = new Long( handledEvents[ i ] * hashCode );
Object handler = handlers.get( key );
if ( handler != null ) {
handlers.remove( key );
addListener( comp, "remove" + listener[ i ], "org.eclipse.swt.events." + listener[ i ], this );
}
}
}
/**
* Get the validation handler reference. Note that you should not hold a
* reference to this value as it will probably cause a memory leak.
*
* @return the current validation handler
*/
public XValidationHandler getValidationHandler()
{
return xValidationHandler;
}
/**
* Invokes an event. Called in response to an event. If a handler has been
* added for the event it will be invoked.
*
* @param eventType
* type of event
* @param evt
* the event object
*/
public void invoke( long eventType, EventObject evt )
{
// If necessary first check for presence of a validation event handler
// the method name will be "validationHandler" and stop event dispatch if an
// error has occurred.
try {
currentEvt = evt;
if ( xValidationHandler.validationHandler( this ) > XValidator.LEVEL_WARNING )
return;
// Then call any other event handler
XMethodReference reference = (XMethodReference)handlers.get( new Long( eventType * evt.getSource().hashCode() ) );
Method m = reference.method;
try {
if ( ( m != null ) && ( m.getName().compareTo( "validationHandler" ) != 0 ) )
m.invoke( container, (Object[])null );
}
catch ( Exception e ) {
DebugLogger.logWarning( "error invoking '" + m.getName() + "' in XEventHandler" );
e.getCause().printStackTrace();
}
currentEvt = null;
}
catch ( Exception ex ) {
System.out.println( "Error invoking" );
}
}
/**
* Lookup an event for a component.
*
* @param src
* the event source object
*/
public Method findEvent( Object src, long eventType )
{
XMethodReference reference = (XMethodReference)handlers.get( new Long( eventType * src.hashCode() ) );
return reference.method;
}
/**
* Check the focus change status
*
* @return true if the focus change events are being suppressed.
*/
public boolean isFocusChangeSuppressed()
{
return suppressFocusEvents > 0;
}
/**
* Get the current event
*
* @return the AWTEvent that was last triggered
*/
public EventObject getCurrentEvent()
{
return currentEvt;
}
/**
* Adds an event handler. A specific handler such as the addActionHandler
* should be used instead of calling this method
*
* @param comp
* the component that fires the event
* @param methodStr
* the method to be invoked in response to the object
*/
public XMethodReference addHandler( Object comp, long eventType, String methodStr ) throws ClassNotFoundException, NoSuchMethodException
{
XMethodReference reference;
String className, methodName;
int pos = methodStr.lastIndexOf( "." );
if ( pos < 0 ) {
methodName = methodStr;
className = null;
}
else {
className = methodStr.substring( 0, pos );
methodName = methodStr.substring( pos + 1 );
}
reference = getMethodReference( container, className, methodName );
long hashCode = comp.hashCode();
if ( comp instanceof XHashCode )
hashCode = ( (XHashCode)comp ).getComponentHashCode();
handlers.put( new Long( eventType * hashCode ), reference );
return reference;
}
/**
* Adds a handler for action events
*
* @param srcObj
* the menu item that fires the events
* @param methodName
* the method to be invoked in response to the action event
* @param adderMethod
* the adder method name e.g. addActionListener
* @param listenerInterface
* the listener interface e.g. org.eclipse.swt.events.ActionListener
* @param eventMask
* the event mask e.g. AWTEvent.ACTION_EVENT_MASK
* @param listener
* the listener implementation, usually the page's this pointer
* @see org.eclipse.swt.events.ActionListener
* @see org.eclipse.swt.events.ActionEvent
*/
public XMethodReference addHandler( Object srcObj, String methodName, String adderMethod, String listenerInterface, long eventMask, Object listener )
{
XMethodReference reference = null;
addListener( srcObj, adderMethod, listenerInterface, listener );
try {
reference = addHandler( srcObj, eventMask, methodName );
}
catch ( Error error ) {
if ( BuildProperties.DEBUG )
DebugLogger.logError( "Unable to add the handler: " + methodName );
error.printStackTrace();
}
catch ( Exception ex ) {
ex.printStackTrace();
}
return reference;
}
/**
* Adds a listener for an event type. This method should not normally be
* called by an application
*
* @param comp
* the component that fires events
* @param listenerName
* the name of the listener interface
* @param argType
* the listener arguments
* @param listener
* the listener implementation
*/
public void addListener( Object comp, String listenerName, String argType, Object listener )
{
try {
Class params[] = new Class[1];
params[ 0 ] = Class.forName( argType.trim());
Method m = comp.getClass().getMethod( listenerName, params );
Object args[] = new Object[1];
args[ 0 ] = listener;
m.invoke( comp, args );
}
catch ( Exception e ) {
e.printStackTrace();
if ( e.getCause() != null )
e.getCause().printStackTrace();
}
}
/**
* Adds a handler for action events
*
* @param menuItem
* the menu item that fires the events
* @param methodName
* the method to be invoked in response to the action event
* @see org.eclipse.swt.events.ActionListener
* @see org.eclipse.swt.events.ActionEvent
*/
public void addMenuHandler( Object menuItem, String methodName )
{
addHandler( menuItem, methodName, "addSelectionListener", "org.eclipse.swt.events.SelectionListener", AWTEvent.ITEM_EVENT_MASK, this );
}
/**
* Adds a handler for action events
*
* @param comp
* the component that fires the events
* @param methodName
* the method to be invoked in response to the action event
* @see org.eclipse.swt.events.ActionListener
* @see org.eclipse.swt.events.ActionEvent
*/
public void addActionHandler( Object comp, String methodName )
{
addHandler( comp, methodName, "addSelectionListener", "org.eclipse.swt.events.SelectionListener", AWTEvent.ACTION_EVENT_MASK, this );
}
/**
* Adds a handler for focus events
*
* @param comp
* the component that fires the events
* @param methodName
* the method to be invoked in response to the focus event
* @see org.eclipse.swt.events.FocusListener
* @see org.eclipse.swt.events.FocusEvent
*/
public void addFocusHandler( Object comp, String methodName )
{
addHandler( comp, methodName, "addFocusListener", "org.eclipse.swt.events.FocusListener", AWTEvent.FOCUS_EVENT_MASK, this );
}
/**
* Adds a handler for text events
*
* @param comp
* the component that fires the events
* @param methodName
* the method to be invoked in response to the text event
* @see org.eclipse.swt.events.TextListener
* @see org.eclipse.swt.events.TextEvent
*/
public void addTextHandler( Object comp, String methodName )
{
addHandler( comp, methodName, "addTextListener", "org.eclipse.swt.events.TextListener", AWTEvent.TEXT_EVENT_MASK, this );
}
/**
* Adds a handler for item events
*
* @param comp
* the component that fires the events
* @param methodName
* the method to be invoked in response to the item event
* @see org.eclipse.swt.events.ItemListener
* @see org.eclipse.swt.events.ItemEvent
*/
public void addItemHandler( Object comp, String methodName )
{
addHandler( comp, methodName, "addSelectionListener", "org.eclipse.swt.events.SelectionListener", AWTEvent.ITEM_EVENT_MASK, this );
}
/**
* Adds a handler for key events
*
* @param comp
* the component that fires the events
* @param methodName
* the method to be invoked in response to the key event
* @see org.eclipse.swt.events.KeyListener
* @see org.eclipse.swt.events.KeyEvent
*/
public void addKeyHandler( Object comp, String methodName )
{
addHandler( comp, methodName, "addKeyListener", "org.eclipse.swt.events.KeyListener", AWTEvent.KEY_EVENT_MASK, this );
}
/**
* Adds a handler for mouse events
*
* @param comp
* the component that fires the events
* @param methodName
* the method to be invoked in response to the mouse event
* @see org.eclipse.swt.events.MouseMotionListener
* @see org.eclipse.swt.events.MouseEvent
*/
public void addMouseHandler( Object comp, String methodName )
{
addHandler( comp, methodName, "addMouseListener", "org.eclipse.swt.events.MouseListener", AWTEvent.MOUSE_EVENT_MASK, this );
}
/**
* Adds a handler for mouse motion events
*
* @param comp
* the component that fires the events
* @param methodName
* the method to be invoked in response to the mouse event
* @see org.eclipse.swt.events.MouseMotionListener
* @see org.eclipse.swt.events.MouseEvent
*/
public void addMouseMotionHandler( Object comp, String methodName )
{
addHandler( comp, methodName, "addMouseMotionListener", "org.eclipse.swt.events.MouseTrackListener", AWTEvent.MOUSE_MOTION_EVENT_MASK, this );
}
/**
* A utility method used to determine if the last event corrseponds to a mouse
* click. The notion of a click is extended by assuming the a mouse press and
* release within a single component constitutes a click even if not at the
* same coordinate. A MouseEvent.MOUSE_CLICKED is only triggered when the
* press and release are at the same location and this is often inadequate for
* end-user interaction.
*
* @return true if the mouse was clicked
*/
public boolean wasMouseClicked()
{
if ( currentEvt != null ) {
if ( ( currentEvt instanceof MouseEvent )
&& ( ( ( ( (MouseEvent)currentEvt ).button & SWT.BUTTON1 ) > 0 ) && ( currentEvt.getSource() == mouseDownComponent ) ) ) {
mouseDownComponent = null;
return true;
}
}
mouseEventInvoked = true;
return false;
}
/**
* A utility method used to determine if the last event corrseponds to a mouse
* double click. The notion of a click is extended by assuming the a mouse
* press and release within a single component constitutes a click even if not
* at the same coordinate. A MouseEvent.MOUSE_CLICKED is only triggered when
* the press and release are at the same location and this is often inadequate
* for end-user interaction.
*
* @return true if the mouse was double clicked
*/
public boolean wasMouseDoubleClicked()
{
if ( currentEvt != null ) {
if ( ( currentEvt instanceof MouseEvent )
&& ( ( ( ( (MouseEvent)currentEvt ).button & SWT.BUTTON1 ) > 0 ) && ( currentEvt.getSource() == mouseDownComponent ) ) ) {
mouseDownComponent = null;
return true;
}
}
mouseEventInvoked = true;
return false;
}
/**
* A utility method used to determine if the last event corrseponds to a mouse
* right click. The notion of a click is extended by assuming the a mouse
* press and release within a single component constitutes a click even if not
* at the same coordinate. A MouseEvent.MOUSE_CLICKED is only triggered when
* the press and release are at the same location and this is often inadequate
* for end-user interaction.
*
* @return true if the mouse was right clicked
*/
public boolean wasMouseRightClicked()
{
if ( currentEvt != null ) {
if ( ( currentEvt instanceof MouseEvent )
&& ( ( ( ( (MouseEvent)currentEvt ).button & SWT.BUTTON3 ) > 0 ) && ( currentEvt.getSource() == mouseDownComponent ) ) ) {
mouseDownComponent = null;
return true;
}
}
mouseEventInvoked = true;
return false;
}
public void widgetSelected( SelectionEvent e )
{
invoke( AWTEvent.ITEM_EVENT_MASK, e );
}
public void widgetDefaultSelected( SelectionEvent e )
{
invoke( AWTEvent.ITEM_EVENT_MASK, e );
}
public void focusGained( FocusEvent e )
{
// The suppressFocusEvents flag is used as poping up a message dialog causes
// input fields to loose focus. This in turn could cause a continuous loop
// of validation errors - messages - loss of focus - validations -
// validation errors
if ( suppressFocusEvents < 2 ) {
invoke( AWTEvent.FOCUS_EVENT_MASK, e );
if ( suppressFocusEvents == 1 ) {
suppressFocusEvents++;
}
}
}
public void focusLost( FocusEvent e )
{
if ( suppressFocusEvents == 0 ) {
invoke( AWTEvent.FOCUS_EVENT_MASK, e );
}
}
public void keyPressed( KeyEvent e )
{
invoke( AWTEvent.KEY_EVENT_MASK, e );
}
public void keyReleased( KeyEvent e )
{
invoke( AWTEvent.KEY_EVENT_MASK, e );
}
public void keyTyped( KeyEvent e )
{
invoke( AWTEvent.KEY_EVENT_MASK, e );
}
public void mouseClick( MouseEvent e )
{
if ( !mouseEventInvoked )
invoke( AWTEvent.MOUSE_EVENT_MASK, e );
}
public void mouseDoubleClick( MouseEvent e )
{
if ( !mouseEventInvoked )
invoke( AWTEvent.MOUSE_EVENT_MASK, e );
}
public void mouseEnter( MouseEvent e )
{
invoke( AWTEvent.MOUSE_EVENT_MASK, e );
}
public void mouseExit( MouseEvent e )
{
invoke( AWTEvent.MOUSE_EVENT_MASK, e );
}
public void mouseDown( MouseEvent e )
{
mouseDownComponent = e.getSource();
mouseEventInvoked = false;
invoke( AWTEvent.MOUSE_EVENT_MASK, e );
}
public void mouseUp( MouseEvent e )
{
invoke( AWTEvent.MOUSE_EVENT_MASK, e );
}
public void mouseMove( MouseEvent e )
{
invoke( AWTEvent.MOUSE_MOTION_EVENT_MASK, e );
}
public void mouseHover( MouseEvent e )
{
invoke( AWTEvent.MOUSE_MOTION_EVENT_MASK, e );
}
public void mouseDragged( MouseEvent e )
{
invoke( AWTEvent.MOUSE_MOTION_EVENT_MASK, e );
}
/**
* Used by messageboxes and other dialogs to prevent the display of the dialog
* causing extra focus events from being fired.
*
* @param suppress
* true to suppress focus events
*/
public void suppressFocusEvents( boolean suppress )
{
if ( suppress )
suppressFocusEvents++;
else
suppressFocusEvents = Math.max( --suppressFocusEvents, 0 );
}
/**
* Get a reference to a method and a class instance.
*
* @param container
* the page or object whose event is being handled
* @param className
* the name of the class containing the handler
* @param methodName
* the name of the method.
* @throws ClassNotFoundException
* the className class could not be loaded
* @throws NoSuchMethodException
* the methodName method could not be found in the className class
*/
private XMethodReference getMethodReference( Object container, String className, String methodName ) throws ClassNotFoundException,
NoSuchMethodException
{
Class clazz = null;
if ( className == null ) {
clazz = container.getClass();
className = clazz.getName();
}
if ( className.startsWith( "${" ) ) {
XDefaultAttributeEvaluator attEval = (XDefaultAttributeEvaluator)currentProject.getObject( "DefaultAttributeEvaluator" );
if ( attEval == null ) {
attEval = new XDefaultAttributeEvaluator( currentProject );
attEval.setCurrentProject( currentProject );
currentProject.setObject( "DefaultAttributeEvaluator", attEval );
}
XMethodReference methodRef = attEval.getMethodReference( (PageSupport)container, className + "." + methodName );
return methodRef;
}
else if ( className.startsWith( "Script" ) ) {
XMethodReference methodRef = null;
try {
XAttributeEvaluator attEval = (XAttributeEvaluator)currentProject.getObject( "ScriptAttributeEvaluator" );
if ( attEval == null ) {
attEval = (XAttributeEvaluator)XEventHandler.class.forName( "incubator.net.xoetrope.scripts.ScriptAttributeEvaluator" ).newInstance();
attEval.setCurrentProject( currentProject );
currentProject.setObject( "ScriptAttributeEvaluator", attEval );
}
methodRef = attEval.getMethodReference( methodName );
}
catch ( Throwable t ) {
}
return methodRef;
}
else {
Method method = null;
if ( clazz == null )
clazz = Class.forName( className.trim());
if ( methodName.endsWith( "}" ) )
methodName = methodName.substring( 0, methodName.length() - 1 );
if ( methodName.endsWith( "()" ) )
methodName = methodName.substring( 0, methodName.length() - 2 );
int pos;
if ( ( pos = methodName.indexOf( "(" ) ) > 0 ) {
String argValues = methodName.substring( pos + 1, methodName.indexOf( ')' ) );
int numArgs = 1 + XuiUtilities.count( argValues, ',' );
Object[] args = new Object[numArgs];
Class[] params = new Class[numArgs];
XuiUtilities.getArguments( argValues, params, args, ',' );
methodName = methodName.substring( 0, pos );
method = clazz.getMethod( methodName, params );
return new XMethodReference( clazz, container, method, args );
}
else
method = clazz.getMethod( methodName, (Class[])null );
return new XMethodReference( clazz, container, method, null );
}
}
//----------------------------------------------------------------------------
/**
* Adds an event handler.
* @param xpage The page that contains the response methods
* @param targetComp the component to which the event handler is added
* @param typeStr the type of handler
* @param methodName the name of the response method
*/
public void addHandler( PageSupport xpage, Object targetComp, String typeStr, String methodName )
{
try {
// String ifaceName;
// long mask;
String types[] = { "MouseHandler", "MouseMotionHandler",
"ActionHandler", "FocusHandler",
"ItemHandler", "KeyHandler",
"TextHandler", "MenuHandler" };
String iface[] = { "org.eclipse.swt.events.MouseListener", "org.eclipse.swt.events.MouseMotionListener",
"org.eclipse.swt.events.SelectionListener", "org.eclipse.swt.events.FocusListener",
"org.eclipse.swt.events.SelectionListener", "org.eclipse.swt.events.KeyListener",
"org.eclipse.swt.events.TextListener", "org.eclipse.swt.events.SelectionListener" };
long masks[] = { AWTEvent.MOUSE_EVENT_MASK, AWTEvent.MOUSE_MOTION_EVENT_MASK, AWTEvent.ITEM_EVENT_MASK, AWTEvent.FOCUS_EVENT_MASK,
AWTEvent.ITEM_EVENT_MASK, AWTEvent.KEY_EVENT_MASK, AWTEvent.TEXT_EVENT_MASK, AWTEvent.ITEM_EVENT_MASK };
for ( int i = 0; i < types.length; i++ ) {
if ( typeStr.equals( types[ i ] )) {
String adder = "add" + iface[ i ].substring( iface[ i ].lastIndexOf( '.' ) + 1 );
addHandler( targetComp, methodName, adder, iface[ i ], masks[ i ], this );
return;
}
}
/**
* @todo handle this in a more generic way
*/
if ( targetComp.getClass().getName().indexOf( "Button" ) > -1 )
WidgetAdapter.getInstance().setCursor( targetComp, XPageHelper.hand );
if ( typeStr.indexOf( '.' ) > 0 ) {
try
{
XEventAdapter xea = (XEventAdapter)Class.forName( typeStr.trim()).newInstance();
xea.setEventHandler( this );
addHandler( targetComp, methodName, xea.getAddMethodName(), xea.getListenerInterfaceName(), xea.getEventMask(), xea );
}
catch ( Exception ex ) {
ex.printStackTrace();
}
}
else
((XListenerHelper)targetComp).addHandler( xpage, typeStr, methodName );
}
catch ( NoSuchMethodException ex ) {
if ( BuildProperties.DEBUG )
DebugLogger.logError( "BUILDER", "Unable to add the event handler method: " + methodName );
}
}
}