package textbender.a.u.locusPoint; // Copyright 2006, Michael Allan. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Textbender Software"), to deal in the Textbender Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicence, and/or sell copies of the Textbender Software, and to permit persons to whom the Textbender Software is furnished to do so, subject to the following conditions: The preceding copyright notice and this permission notice shall be included in all copies or substantial portions of the Textbender Software. THE TEXTBENDER SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE TEXTBENDER SOFTWARE OR THE USE OR OTHER DEALINGS IN THE TEXTBENDER SOFTWARE.
import java.beans.PropertyChangeEvent;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.security.*;
import org.w3c.dom.*;
import org.w3c.dom.events.*;
import textbender.a.r.page.*;
import textbender.d.gene.Gene;
import textbender.g.beans.PropertyChangeListenerR;
import textbender.g.hold.Hold;
import textbender.g.lang.*;
import textbender.g.util.logging.LoggerX;
import textbender.o.rhinohide.events.RelaySIP;
/** Point detector/publisher for a Web document, via an in-page user-applet.
* Whenever it detects a gene in the document being overpassed by the mouse,
* it posts its locus as the locus-point for the desktop.
*/
public final class InPagePointer extends UnicastRemoteObject implements PropertyChangeListenerR
{
/** Constructs an InPagePointer.
*
* @param vP page visit, context
*
* @throws RemoteException if the locus-point service is unreachable
* @throws UnsupportedOperationException
* if the browser does not support DOM level 2 events;
* or the page {@linkplain PageVisit#isPageSilent() is silent}
*/
public InPagePointer( PageVisit vP ) throws RemoteException
{
pageVisit = vP;
if( !pageVisit.document().getImplementation().hasFeature( "Events", "2.0" )
|| pageVisit.isPageSilent() ) throw new UnsupportedOperationException( "browser does not support DOM level 2 events" );
locusPoint = PageDaemons.i().connections().hostServiceRegistry().getService( LocusPoint.class );
locusPoint.addPropertyChangeListener( "enabled", InPagePointer.this );
pageVisit.spool().add( new Hold()
{
public @ThreadSafe void release() { try{ locusPoint.disabledPropertyChangeListener( "enabled", InPagePointer.this ); } catch( RemoteException x ) { LoggerX.i(getClass()).warning( x.toString() ); }}
});
if( locusPoint.isEnabled() ) setEnabled( true ); // fire up
}
// - P r o p e r t y - C h a n g e - L i s t e n e r - R ------------------------------
public @ThreadSafe void propertyChange( PropertyChangeEvent e )
{
setEnabled( (Boolean)e.getNewValue() );
}
//// P r i v a t e ///////////////////////////////////////////////////////////////////////
// private @ThreadRestricted("Rhinohide event dispatch") boolean hasLoggedException;
private @ThreadRestricted("holds this") boolean hasLoggedException;
private final PageVisit pageVisit;
// private @ThreadRestricted("Rhinohide event dispatch") String pointLocusLast;
private @ThreadRestricted("holds this") String pointLocusLast;
private final LocusPoint locusPoint;
private synchronized void setEnabled( boolean newEnabled )
{
if( newEnabled == enabled ) return;
if( newEnabled )
{
EventTarget registry = (EventTarget)pageVisit.document();
registry.addEventListener( "mouseover", pageListener, /*use capture*/false );
registry.addEventListener( "mouseout", pageListener, /*use capture*/false );
// pageVisit.spool().add( enabledHold );
////// no need to unregister at exit, registry will not outlive listeners
}
else // disable
{
// pageVisit.spool().remove( enabledHold );
enabledHold.release();
}
enabled = newEnabled;
}
private final Hold enabledHold = new Hold()
{
public @ThreadSafe void release()
{
EventTarget registry = (EventTarget)pageVisit.document();
registry.removeEventListener( "mouseover", pageListener, /*use capture*/false );
registry.removeEventListener( "mouseout", pageListener, /*use capture*/false );
}
};
private @ThreadRestricted("holds this") boolean enabled;
// ====================================================================================
private final EventListener pageListener = new RelaySIP()
{
// public @ThreadRestricted("Rhinohide event dispatch") void handleEvent( Event _e )
public @ThreadSafe void handleEvent( Event _e )
{
// assert pageVisit.rhinohide().isEventDispatchThread();
if( pageVisit.spool().isUnwinding() ) return;
final MouseEvent e = (MouseEvent)_e;
// System.out.println( "IPP event: " + e );
String pointLocus = null; // till proven otherwise
final String eventType = e.getType();
if( "mouseover".equals( eventType ))
{
Node pointNode;
{
EventTarget t = e.getTarget();
if( t instanceof Node ) pointNode = (Node)t;
else pointNode = null;
}
// System.out.println( "IPP over node: " + pointNode );
for( ;; pointNode = pointNode.getParentNode() ) // cf. Gene.selfOrAncestorAsGene()
{
if( pointNode == null || pointNode instanceof Document ) break;
if( !(pointNode instanceof Element )) continue; // ignore non-elements, are not genes
Element pointElement = (Element)pointNode;
final int gIndex = Gene.gIndexOf( pointElement );
if( gIndex < 0 ) continue; // not a gene
// String locus = Gene.locusOf( pointElement );
// if( locus == null ) continue; // not a gene
if( !Gene.hasChildGene( pointElement )) // point to leaf only, not parent genes
{
// pointLocus = pageVisit.g(gIndex).getAttributeNS( null, "locus" );
pointLocus = pageVisit.gList().get(gIndex).getAttributeNS( null, "locus" );
}
break;
}
}
else
{
assert "mouseout".equals( eventType ) : "unexpected event type: " + eventType;
if( e.getRelatedTarget() != null ) return; // ignore, act on related mouseover instead
// else set locus null...
}
synchronized( this ) // atomic test/set, and per below (2)
{
if( ObjectX.nullEquals( pointLocus, pointLocusLast )) return; // optimization, to avoid an RMI call that's probably unecessary
pointLocusLast = pointLocus;
}
AccessController.doPrivileged( new PrivilegedAction()
{
// System.out.println( "IPP over locus: " + pointLocus );
public Void run() // in this protection domain
{
try
{
locusPoint.setLocus( pointLocusLast );
}
catch( RemoteException x )
{
if( !hasLoggedException ) // 2. thread var cache refreshed by above sync. No atomic test/set, only non-critical logging done here.
{
pageVisit.user().logAndShow( x );
hasLoggedException = true;
}
}
return null; // to caller of doPrivileged()
}
});
}
};
}