package votorola.s.gwt.stage.vote; // Copyright 2012, Michael Allan. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Votorola Software"), to deal in the Votorola Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicence, and/or sell copies of the Votorola Software, and to permit persons to whom the Votorola 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 Votorola Software. THE VOTOROLA 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 VOTOROLA SOFTWARE OR THE USE OR OTHER DEALINGS IN THE VOTOROLA SOFTWARE. import com.google.gwt.core.client.Scheduler; import com.google.gwt.dom.client.*; import votorola.a.count.*; import votorola.a.count.gwt.*; import votorola.a.diff.*; import votorola.a.web.gwt.*; import votorola.g.lang.*; import votorola.g.web.gwt.*; import votorola.g.web.gwt.event.*; import votorola.s.gwt.stage.*; import votorola.s.gwt.stage.light.*; import votorola.s.gwt.stage.link.*; import static votorola.s.gwt.stage.vote.LightableDifference.ACTOR_LINK_VARIANT_BASE; import static votorola.s.gwt.stage.link.NominalDifferenceTargeter.NOMINAL_DIFF; /** A stage light to indicate {@linkplain votorola.a.diff.DiffKey position differences}. * The light either actuates according to the {@linkplain PositionSensor position sensor} * pointed by the user, or it falls back to the sensor of the {@linkplain #baseActuator() * base actuator}. Whatever the sensor, if it has an associated {@linkplain * #differenceFor(String,String) lightable difference}, then that difference is lit. */ public abstract class DifferenceLight implements Light { /** Does nothing itself but the call forces static initialization of this class. */ public static void forceInitClass() {} /** Partially creates a DifferenceLight for {@linkplain #init(Stage) init} to finish. * Construct at most one for the entire life of the document, as currently it does * not unregister its listeners or otherwise clean up after itself. * * @see #voteTrack() */ protected DifferenceLight( VoteTrack _voteTrack ) { voteTrack = _voteTrack; if( voteTrack == null ) throw new NullPointerException(); // eagerly actuator = baseActuator = sceneName == null? new BaseActuator1(): new BaseActuatorSD(); } private static DifferenceLight instance; { if( instance != null ) throw new IllegalStateException(); instance = DifferenceLight.this; } /** Completes the creation of this DifferenceLight. Call once only. */ protected void init( final Stage stage ) { addStageRelClasses(); stage.lightBank().addLight( DifferenceLight.this ); // no need to remove, co-extant stage.addInitializer( new TheatreInitializerC() // auto-removed { // LinkTrackV accessible and stage settled when: public void initComplete( final Stage s, boolean _rPending ) { new DifferenceStager( DifferenceLight.this, s ); baseActuator.init( new LinkLighter(baseActuator,LinkTrackV.i(StageV.i()),s), s ); } }); } // ------------------------------------------------------------------------------------ /** Adds an {@linkplain LightableDifference#stageRelClass() existential style class} * for {@linkplain #run(LightableDifference.Runner) each} lightable difference to the * #StageV-top element. This method is called during initialization. */ protected final void addStageRelClasses() { run( new LightableDifference.Runner() { final Element eStageV = Document.get().getElementById( "StageV-top" ); public void run( final D diff ) { eStageV.addClassName( diff.stageRelClass() ); } }); } /** Removes the {@linkplain LightableDifference#stageRelClass() existential style * class} for {@linkplain #run(LightableDifference.Runner) each} lightable * difference from the #StageV-top element. */ public final void removeStageRelClasses() { run( new LightableDifference.Runner() { final Element eStageV = Document.get().getElementById( "StageV-top" ); public void run( final D diff ) { eStageV.removeClassName( diff.stageRelClass() ); } }); } /** The actuator for the sensor defined by the current {@linkplain Stage#getActorName() * actor} and {@linkplain Stage#getPollName() poll}, which engages whenever the user * is not pointing to a particular {@linkplain PositionSensor position sensor}. */ protected final Actuator_DL baseActuator() { return baseActuator; } private final BaseActuator baseActuator; /** The style class {@value} assigned to the document body when a {@linkplain * #setScene(String,String) scene difference} is set and is currently lighted from * the vantage of the anchoring author. */ public static final String BODY_ANCHOR = "voLiDi-anchor"; private String ancClassLit; /** The style class {@value} assigned to the document body when a {@linkplain * #setScene(String,String) scene difference} is set and is currently lighted. The * scene may assign the initial value (adding it to the body or leaving it off) * depending on how it wishes to be styled before this light initializes and assigns * the correct value. */ public static final String BODY_SCENE = "voLiDi-scene"; // Changing? Grep value and change where hardcoded, e.g. in s/wic/diff/WP_D.html private String scnClassLit = ElementX.hasClassName( /*allNames*/Document.get().getBody().getClassName(), BODY_SCENE )? BODY_SCENE: null; /** Returns the lightable difference for the specified person and poll, or null if * there is none. */ protected abstract D differenceFor( String personName, String pollName ); /** Answers whether the lightable differences of this light are also {@linkplain * DiffLook difference looks} and thus able to be directly staged. */ protected abstract boolean hasDiffLooks(); /** The maximum number of differences ({@value}) that can exist in the vote track. * The number includes up to twenty dart-sectored authors and one mosquito for each * of two boards ({@linkplain VoteTrack#voters() voters} and {@linkplain * VoteTrack#peers() peers}), plus the {@linkplain VoteTrack#candidate() candidate} * and less the {@linkplain VoteTrack#anchor() anchoring author}, versus whose draft * the others are compared. */ public static final int MAX_DIFFS = (20 + 1) * 2 + 1 - 1; /** Redirects the light according to the current light actuator. * * @return the difference associated with the actuator's sensor, or null if there * is none. */ protected LightableDifference redirect() { final LightableDifference diff = actuator.diff(); final String ancClass; final String scnClass; final String ordClass; final String relClass; final String secClass; if( diff == null ) { ancClass = null; scnClass = null; ordClass = null; relClass = null; secClass = null; } else { ancClass = actuator.bodyAncClass(); scnClass = actuator.bodyScnClass(); ordClass = diff.bodyOrdClass(); relClass = diff.bodyRelClass(); secClass = diff.bodySecClass(); } final Element body = Document.get().getBody(); ElementX.replaceNullClassName( ancClassLit, ancClass, body ); ElementX.replaceNullClassName( scnClassLit, scnClass, body ); ElementX.replaceNullClassName( ordClassLit, ordClass, body ); ElementX.replaceNullClassName( relClassLit, relClass, body ); ElementX.replaceNullClassName( secClassLit, secClass, body ); ancClassLit = ancClass; scnClassLit = scnClass; ordClassLit = ordClass; relClassLit = relClass; secClassLit = secClass; return diff; } /** Passes all lightable differences through the specified runner. */ protected abstract void run( LightableDifference.Runner runner ); /** The name of the other author of the scene difference as opposed to the anchor; or * null if no scene difference is set. * * @see #setScene(String,String) */ public static String sceneName() { return sceneName; } private static String sceneName; /** The {@linkplain LightableDifference style symbol} for the cast relation of the * other author of the scene difference as regarded by the anchor, e.g. {@linkplain * LightableDifference#REL_VOTER REL_VOTER}; or '\u0000' if no scene difference is * set. * * @see #setScene(String,String) */ public static char sceneRel() { return sceneRel; } private static char sceneRel; /** Specifies a difference that is displayed in the scene. Call this method from the * global configuration function {@linkplain StageMod voGWTConfig.s_gwt_stage} in * this fashion:
   s_gwt_stage_vote_DifferenceLight_setScene( 'Frank-FlippityNet' );
      *     // default is null, which lights no default difference
* * A {@linkplain Stage#getDefaultActorName() default actor} and {@linkplain * Stage#getDefaultDifference() difference} should also be set on stage. If those * are set, then the scene difference will be lighted and style class {@value * BODY_SCENE} assigned to the document body whenever either of the two authors is on * stage, while at other times the class will be removed. * * @see #sceneName() * @see #sceneRel() */ public static @GWTConfigCallback void setScene( String _sceneName, String _sceneRel ) { sceneName = _sceneName; if( _sceneRel.length() != 1 ) throw new IllegalArgumentException(); sceneRel = _sceneRel.charAt( 0 ); } private static native void exposeScene() /*-{ $wnd.s_gwt_stage_vote_DifferenceLight_setScene = $entry( @votorola.s.gwt.stage.vote.DifferenceLight::setScene(Ljava/lang/String;Ljava/lang/String;) ); }-*/; static { assert StageMod.isForcedInit(): "forced init " + DifferenceLight.class.getName(); exposeScene(); } /** The staged instance of the vote track. */ protected final VoteTrack voteTrack() { return voteTrack; } private final VoteTrack voteTrack; // - L i g h t ------------------------------------------------------------------------ public Actuator_DL assignActuator( final PositionSensor sensor ) { final Actuator1 actuator = sceneName == null? new Actuator1( sensor ): new ActuatorSD( sensor ); actuator.init(); return actuator; } public final PositionSensor tryCast( final Sensor sensor ) { return sensor instanceof PositionSensor? (PositionSensor)sensor: null; } // ==================================================================================== /** An actuator for the difference light. */ public static interface Actuator_DL extends Actuator { /** Completes the creation of this Actuator_DL. Call once only. */ void init(); // -------------------------------------------------------------------------------- /** The style class {@value votorola.s.gwt.stage.vote.DifferenceLight#BODY_ANCHOR} * to assign to the document body, or null to assign none. */ String bodyAncClass(); /** The style class {@value votorola.s.gwt.stage.vote.DifferenceLight#BODY_ANCHOR} * to assign to the document body, or null to assign none. */ String bodyScnClass(); /** Responds to a change in the assigned sensor's properties. */ public void changed(); /** The difference currently associated with this actuator's sensor, or null if * there is none. * * @see #differenceFor(String,String) */ LightableDifference diff(); } //// P r i v a t e /////////////////////////////////////////////////////////////////////// private Actuator_DL actuator; // never null after init, instead set to baseActuator private String ordClassLit; private String relClassLit; private String secClassLit; private LightableDifference syntheticSceneDiff; // ==================================================================================== private final class ActorSensor implements PositionSensor, PropertyChangeHandler { /** Completes the creation of this ActorSensor. Call once only. */ void init( final Stage stage ) { GWTX.i().bus().addHandlerToSource( PropertyChange.TYPE, /*source*/stage, ActorSensor.this ); // no need to unregister, registry does not outlive this listener } // - P o s i t i o n - S e n s o r ------------------------------------------------ public String personName() { return Stage.i().getActorName(); } public String pollName() { return Stage.i().getPollName(); } // - P r o p e r t y - C h a n g e - H a n d l e r -------------------------------- public void onPropertyChange( final PropertyChange e ) { final String name = e.propertyName(); if( name.equals("actorName") || name.equals("pollName") ) changed(); } // - S e n s o r ------------------------------------------------------------------ public void changed() { baseActuator.changed(); } public void out() {} // base sensor, not pointer activated public void over() {} // " } // ==================================================================================== private class Actuator1 implements Actuator_DL { /** Partially creates an Actuator1 for {@linkplain #init() init} to finish. */ Actuator1( PositionSensor _sensor ) { sensor = _sensor; } public final void init() { changed(); } // -------------------------------------------------------------------------------- void changedHook() {} final PositionSensor sensor; // - A c t u a t o r -------------------------------------------------------------- public final void changed( PositionSensor _sensor ) { changed(); } public final Light light() { return DifferenceLight.this; } public final void out( PositionSensor _sensor ) { if( actuator != Actuator1.this ) return; // already out actuator = baseActuator; // to default redirect(); } public void over( PositionSensor _sensor ) { if( diff == null || /*already over*/actuator == Actuator1.this ) return; actuator = Actuator1.this; redirect(); } // - A c t u a t o r - D L ------------------------------------------------------- public String bodyAncClass() { return null; } public String bodyScnClass() { return null; } public void changed() { diff = differenceFor( sensor.personName(), sensor.pollName() ); changedHook(); if( actuator == Actuator1.this ) redirect(); } public final LightableDifference diff() { return diff; } LightableDifference diff; } // ==================================================================================== private class ActuatorSD extends Actuator1 // for use when scene difference set { /** Partially creates an ActuatorSD for {@linkplain #init() init} to finish. */ ActuatorSD( PositionSensor _sensor ) { super( _sensor ); } boolean isAnchor; private String personName; // - A c t u a t o r -------------------------------------------------------------- public final @Override void over( PositionSensor _sensor ) { // let redirect set correct bodyScnClass even for non-drafters (null diff) if( personName == null || /*already over*/actuator == ActuatorSD.this ) return; actuator = ActuatorSD.this; redirect(); } // - A c t u a t o r _ D L ------------------------------------------------------- public final @Override String bodyAncClass() { return bodyAncClass; } private String bodyAncClass; public final @Override String bodyScnClass() { return bodyScnClass; } private String bodyScnClass; public @Override void changed() { final Stage stage = Stage.i(); // isAnchor, bodyScnClass // ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` isAnchor = false; bodyScnClass = null; // unless proven otherwise personName = sensor.personName(); if( personName != null ) { final String pollName = sensor.pollName(); if( pollName != null && pollName.equals( stage.getDefaultPollName() )) { if( personName.equals( stage.getDefaultActorName() )) { isAnchor = true; bodyScnClass = BODY_SCENE; } else if( personName.equals( sceneName )) bodyScnClass = BODY_SCENE; } } // bodyAncClass // ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` bodyAncClass = isAnchor? BODY_ANCHOR: null; // ` ` ` super.changed(); // calls changedHook before redirecting } // - A c t u a t o r - 1 ---------------------------------------------------------- final @Override void changedHook() { // diff // ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` if( diff == null && isAnchor ) { // anchor to light and scene diff set (this being ActuatorSD), but no formal diff if( syntheticSceneDiff == null ) { final DiffLook defaultDiff = Stage.i().getDefaultDifference(); if( defaultDiff == null ) assert false: "default diff set with scene diff"; else syntheticSceneDiff = new LightableDifference1( sceneRel, /*sec, unknown*/-2, defaultDiff.selectand(), sensor.personName(), sensor.pollName() ); } diff = syntheticSceneDiff; } } } // ==================================================================================== private static interface BaseActuator extends Actuator_DL { /** Completes the creation of this BaseActuator. Call once only. */ void init( Object linkLighter, Stage stage ); /* Object because "non-static class LinkLighter cannot be referenced from a static context" */ String actorLinkVariant(); } // ==================================================================================== private final class BaseActuator1 extends Actuator1 implements BaseActuator { /** Partially creates a BaseActuator1 for {@linkplain #init(LinkTrackV) init} to * finish. */ BaseActuator1() { super( new ActorSensor() ); } @SuppressWarnings("unchecked") public final @Override void init( Object _linkLighter, final Stage stage ) { linkLighter = (LinkLighter)_linkLighter; ((ActorSensor)sensor).init( stage ); init(); } private LinkLighter linkLighter; // final after init // - A c t u a t o r _ D L ------------------------------------------------------- public @Override void changed() { super.changed(); linkLighter.schedule(); } // - B a s e - A c t u a t o r ---------------------------------------------------- public String actorLinkVariant() { return diff == null? ACTOR_LINK_VARIANT_BASE: diff.actorLinkVariant(); } } // ==================================================================================== private final class BaseActuatorSD extends ActuatorSD implements BaseActuator { /** Partially creates a BaseActuatorSD for {@linkplain #init(LinkTrackV) init} to * finish. */ BaseActuatorSD() { super( new ActorSensor() ); } @SuppressWarnings("unchecked") public final @Override void init( Object _linkLighter, final Stage stage ) { linkLighter = (LinkLighter)_linkLighter; ((ActorSensor)sensor).init( stage ); init(); } private LinkLighter linkLighter; // final after init // - A c t u a t o r _ D L ------------------------------------------------------- public @Override void changed() { super.changed(); linkLighter.schedule(); } // - B a s e - A c t u a t o r ---------------------------------------------------- public String actorLinkVariant() { final String v; if( diff == null ) v = ACTOR_LINK_VARIANT_BASE; else if( isAnchor ) { // Actor is anchor. Scene diff is set (this being BaseActuatorSD), so // variant will depend on cast relation (it's not the usual orange-brown // anchor styling). But need to reverse relation because it's expressed // in terms of other person. final String other = diff.actorLinkVariant(); if( other.endsWith( "-voter" )) v = ACTOR_LINK_VARIANT_BASE + "-candidate"; else if( other.endsWith( "-candidate" )) v = ACTOR_LINK_VARIANT_BASE + "-voter"; else if( other.endsWith( "-peer-a" )) v = ACTOR_LINK_VARIANT_BASE + "-peer-b"; else if( other.endsWith( "-peer-b" )) v = ACTOR_LINK_VARIANT_BASE + "-peer-a"; else if( other.endsWith( "-unknown-a" )) v = ACTOR_LINK_VARIANT_BASE + "-unknown-b"; else if( other.endsWith( "-unknown-b" )) v = ACTOR_LINK_VARIANT_BASE + "-unknown-a"; else throw new IllegalStateException(); } else v = diff.actorLinkVariant(); return v; } } //// ==================================================================================== // // // private final class FastDeselector implements PropertyChangeHandler // { // // FastDeselector( final Stage stage ) // { // // GWTX.i().bus().addHandlerToSource( PropertyChange.TYPE, /*source*/stage, // // FastDeselector.this ); // no need to unregister, registry does not outlive this listener // } // // // // - P r o p e r t y - C h a n g e - H a n d l e r -------------------------------- // // // public void onPropertyChange( final PropertyChange e ) // { // final CountNodeJS anchor = voteTrack.anchor(); // if( anchor == null ) return; // // final String name = e.propertyName(); // if( name.equals("actorName") || name.equals("pollName") ) // staged position changed ... // { // final Stage s = Stage.i(); // if( anchor.name().equals(s.getActorName()) // ... to anchor // && voteTrack.count().pollName().equals(s.getPollName()) ) // { // if( actuator != baseActuator ) // user is over another sensor // { // // Other sensor may be position control (like NodeV) and user // // may have clicked it just now to deselect and revert to anchor. // // In any case, don't wait for user to move from sensor, but instead // // force an immediate redirect to anchor for sake of feedback: // actuator = baseActuator; // whose ActorSensor is (or will be) at anchor // redirect(); // } // } // } // } // // } //// no longer needed, and redirect no longer without effect on node under mouse // ==================================================================================== private final class LinkLighter extends CoalescingSchedulerS implements PropertyChangeHandler, Scheduler.ScheduledCommand { LinkLighter( BaseActuator _baseActuator, LinkTrackV _linkTrackV, final Stage stage ) { super( Scheduler.get(), CoalescingSchedulerS.DEFERRED ); baseActuator = _baseActuator; linkTrackV = _linkTrackV; init( /*command*/LinkLighter.this ); GWTX.i().bus().addHandlerToSource( PropertyChange.TYPE, /*source*/stage, LinkLighter.this ); // no need to unregister, registry does not outlive this listener schedule(); } private final BaseActuator baseActuator; private final LinkTrackV linkTrackV; void relight() { final String v = baseActuator.actorLinkVariant(); linkTrackV.setActorLinkVariant( v ); // if( v == null ) return; // no diff link variant, no need to correct it //// v never null in practice, rather defaults to ACTOR_LINK_VARIANT_BASE final Stage stage = Stage.i(); final DiffLook dS = stage.getDifference(); // staged diff if( dS == null || dS == NOMINAL_DIFF || LightableDifference.TYPIFIER.isInstance(dS) ) return; // dS cannot possibly mismatch dL, no need to correct link variant final DiffLook dSDefault = stage.getDefaultDifference(); if( dSDefault != null && dSDefault.key().equals(dS.key()) ) { if( sceneName != null ) // dS is scene diff { if( dS.selectand().equals( dSDefault.selectand() )) return; // probably non-drafter; link self targets and will be disabled, no problem final @SuppressWarnings("unchecked") BaseActuatorSD baseActuatorSD = (BaseActuatorSD)baseActuator; if( !baseActuatorSD.isAnchor && baseActuator.bodyScnClass() != null ) return; // dL is scene diff too, okay } } final LightableDifference dL = baseActuator.diff(); // lit diff if( dL instanceof DiffLook && DiffLookJS.EQUATOR.equals(dS,(DiffLook)dL) ) return; // dS and dL are same, all's well linkTrackV.diffLink().resetVariant(); // Forcefully clear to indicate the link now points to an unlit (older or // newer) difference of the same pair. At least the pair is *expected* to // be the same; DifferenceStager primes the stage according to the rules for // stage.setDifference, which subsequent setters are also expected to obey. } // - S c h e d u l e r . S c h e d u l e d - C o m m a n d ------------------------ public final void execute() { relight(); } // - P r o p e r t y - C h a n g e - H a n d l e r -------------------------------- public void onPropertyChange( final PropertyChange e ) { if( e.propertyName().equals( "difference" )) schedule(); } } }