package net.xoetrope.html;
import java.awt.AWTEvent;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
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.XMethodReference;
import net.xoetrope.xui.XPageHelper;
import net.xoetrope.xui.XProject;
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.events.XuiEventHandler;
import net.xoetrope.xui.exception.XExceptionHandler;
import net.xoetrope.xui.validation.XValidationHandler;
import net.xoetrope.xui.validation.XValidator;
import net.xoetrope.xui.helper.XuiUtilities;
/**
* <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.2 $
*/
public class XHtmlEventHandler implements XuiEventHandler, FocusListener, // SelectionListener,
KeyListener, MouseListener, MouseMotionListener
{
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;
public XHtmlEventHandler( XProject project, Object c, XValidationHandler vh )
{
handlers = new Hashtable( 5 );
currentProject = project;
container = c;
xValidationHandler = vh;
xValidationHandler.setupEventHandler( 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
* the event type
* @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() ) );
if ( reference == null )
return;
Method m = reference.method;
try {
if ( ( m != null ) && ( m.getName().compareTo( "validationHandler" ) != 0 ) )
m.invoke( reference.instance, (Object[])reference.args );
}
catch ( Throwable error ) {
DebugLogger.logWarning( "error invoking '" + m.getName() + "' in XEventHandler" );
boolean continueHandling = false;
if ( container instanceof XExceptionHandler )
continueHandling = ( (XExceptionHandler)container ).handleEventHandlerException( currentProject, container, error );
if ( continueHandling && ( xValidationHandler != container ) )
continueHandling = xValidationHandler.handleEventHandlerException( currentProject, container, error );
if ( continueHandling ) {
XExceptionHandler exceptionHandler = currentProject.getExceptionHandler();
if ( exceptionHandler != null )
continueHandling = exceptionHandler.handleEventHandlerException( currentProject, container, error );
}
if ( continueHandling ) {
if ( BuildProperties.DEBUG )
DebugLogger.logError( "Error while invoking the method: " + reference.method.getName() + ", in class: " + reference.clazz.getName() );
error.getCause().printStackTrace();
}
}
currentEvt = null;
}
catch ( Exception ex ) {
System.out.println( "Error invoking" );
}
}
/**
* Lookup an event for a component.
*
* @param evt
* the event 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;
}
/**
* <p>
* Adds an event handler. A specific handler such as the addActionHandler
* should be used instead of calling this method
* </p>
* <p>
* The handler can also be defined in classes other than the current page or
* classes derived from XPage. The syntax for such expressions is as follows:
* </p>
* <ul>
* <li><code>mypackage.MyClass[referenceName].myMethod</code> for a named
* object instance</li>
* <li><code>mypackage.MyClass[].myMethod</code> to create a new instance
* of the class on each evaluation</li>
* <li><code>mypackage.MyClass.myMethod</code> to invoke a static method</li>
* <li><code>myMethod[referenceName]</code> for a method contained with the
* invoking page</li>
* </ul>
* <p>
* where mypackage is the name of the Java package containing the class
* MyClass. The value of referenceName is a user defined value that identifies
* the instance of the class. The application instantiates an instance of the
* class when the expression is first encountered and thereafter maintains the
* instance with each subsequent call retrieving the same instance of the
* class.
* </p>
* <p>
* The page may also reference scripts with the expression
* <code>${script.XXXXX()}</code> where <code>XXXXX</code> is the name of
* the script method to be invoked.
* </p>
*
* @param eventType
* the event type
* @param methodStr
* the method to be invoked in response to the object
* @param comp
* the component that fires the event
* @throws java.lang.ClassNotFoundException
* The class cannot be found
* @throws java.lang.NoSuchMethodException
* The specified method does not exist in the class
*/
public XMethodReference addHandler( Object comp, long eventType, String methodStr ) throws ClassNotFoundException, NoSuchMethodException
{
XMethodReference reference;
String className, methodName;
if ( methodStr == null )
return null;
if ( methodStr.startsWith( "${script." ) ) {
className = "Script";
methodName = methodStr;
}
else {
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. java.awt.event.ActionListener
* @param eventMask
* the event mask e.g. AWTEvent.ACTION_EVENT_MASK
* @param listener
* the listener implementation, usually the page's this pointer
* @see java.awt.event.ActionListener
* @see java.awt.event.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;
}
/**
* 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 );
// The addListener method just invokes the named method, so it can
// remove the listener
// just as easily.
addListener( comp, "remove" + listener[ i ], "java.awt.event." + listener[ i ], this );
}
}
}
/**
* 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, usually the page's this pointer
*/
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 ] = this;
// m.invoke( comp, args );
// }
// catch ( Exception e )
// {
// e.printStackTrace();
// }
}
/**
* 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.ACTION_EVENT_MASK, e );
// }
//
// public void widgetDefaultSelected( SelectionEvent e )
// {
// invoke( AWTEvent.ACTION_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 mouseClicked( MouseEvent e )
{
if ( !mouseEventInvoked )
invoke( AWTEvent.MOUSE_EVENT_MASK, e );
}
public void mouseDoubleClicked( MouseEvent e )
{
if ( !mouseEventInvoked )
invoke( AWTEvent.MOUSE_EVENT_MASK, e );
}
public void mouseEntered( MouseEvent e )
{
invoke( AWTEvent.MOUSE_EVENT_MASK, e );
}
public void mouseExited( MouseEvent e )
{
invoke( AWTEvent.MOUSE_EVENT_MASK, e );
}
public void mousePressed( MouseEvent e )
{
mouseDownComponent = e.getSource();
mouseEventInvoked = false;
invoke( AWTEvent.MOUSE_EVENT_MASK, e );
}
public void mouseReleased( MouseEvent e )
{
invoke( AWTEvent.MOUSE_EVENT_MASK, e );
}
public void mouseMoved( 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( "${" ) ) {
XAttributeEvaluator attEval = (XAttributeEvaluator)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[] = {
"java.awt.event.MouseListener", "java.awt.event.MouseMotionListener", "java.awt.event.ActionListener", "java.awt.event.FocusListener",
"java.awt.event.ItemListener", "java.awt.event.KeyListener", "java.awt.event.TextListener", "java.awt.event.ActionListener"
};
long masks[] = {
AWTEvent.MOUSE_EVENT_MASK, AWTEvent.MOUSE_MOTION_EVENT_MASK, AWTEvent.ACTION_EVENT_MASK, AWTEvent.FOCUS_EVENT_MASK,
AWTEvent.ITEM_EVENT_MASK, AWTEvent.KEY_EVENT_MASK, AWTEvent.TEXT_EVENT_MASK, AWTEvent.ACTION_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 );
}
}
}