001package 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.
002
003import com.google.gwt.core.client.Scheduler;
004import com.google.gwt.dom.client.*;
005import votorola.a.count.*;
006import votorola.a.count.gwt.*;
007import votorola.a.diff.*;
008import votorola.a.web.gwt.*;
009import votorola.g.lang.*;
010import votorola.g.web.gwt.*;
011import votorola.g.web.gwt.event.*;
012import votorola.s.gwt.stage.*;
013import votorola.s.gwt.stage.light.*;
014import votorola.s.gwt.stage.link.*;
015
016import static votorola.s.gwt.stage.vote.LightableDifference.ACTOR_LINK_VARIANT_BASE;
017import static votorola.s.gwt.stage.link.NominalDifferenceTargeter.NOMINAL_DIFF;
018
019
020/** A stage light to indicate {@linkplain votorola.a.diff.DiffKey position differences}.
021  * The light either actuates according to the {@linkplain PositionSensor position sensor}
022  * pointed by the user, or it falls back to the sensor of the {@linkplain #baseActuator()
023  * base actuator}.  Whatever the sensor, if it has an associated {@linkplain
024  * #differenceFor(String,String) lightable difference}, then that difference is lit.
025  */
026public abstract class DifferenceLight<D extends LightableDifference> implements Light<PositionSensor>
027{
028
029
030    /** Does nothing itself but the call forces static initialization of this class.
031      */
032    public static void forceInitClass() {}
033
034
035
036    /** Partially creates a DifferenceLight for {@linkplain #init(Stage) init} to finish.
037      * Construct at most one for the entire life of the document, as currently it does
038      * not unregister its listeners or otherwise clean up after itself.
039      *
040      *     @see #voteTrack()
041      */
042    protected DifferenceLight( VoteTrack _voteTrack )
043    {
044        voteTrack = _voteTrack;
045        if( voteTrack == null ) throw new NullPointerException(); // eagerly
046
047        actuator = baseActuator = sceneName == null? new BaseActuator1():
048          new BaseActuatorSD();
049    }
050
051
052
053    private static DifferenceLight<?> instance;
054
055        {
056            if( instance != null ) throw new IllegalStateException();
057
058            instance = DifferenceLight.this;
059        }
060
061
062
063    /** Completes the creation of this DifferenceLight.  Call once only.
064      */
065    protected void init( final Stage stage )
066    {
067        addStageRelClasses();
068        stage.lightBank().addLight( DifferenceLight.this ); // no need to remove, co-extant
069        stage.addInitializer( new TheatreInitializerC() // auto-removed
070        {
071            // LinkTrackV accessible and stage settled when:
072            public void initComplete( final Stage s, boolean _rPending )
073            {
074                new DifferenceStager( DifferenceLight.this, s );
075                baseActuator.init( new LinkLighter(baseActuator,LinkTrackV.i(StageV.i()),s), s );
076            }
077        });
078    }
079
080
081
082   // ------------------------------------------------------------------------------------
083
084
085    /** Adds an {@linkplain LightableDifference#stageRelClass() existential style class}
086      * for {@linkplain #run(LightableDifference.Runner) each} lightable difference to the
087      * <code>#StageV-top</code> element.  This method is called during initialization.
088      */
089    protected final void addStageRelClasses()
090    {
091        run( new LightableDifference.Runner<D>()
092        {
093            final Element eStageV = Document.get().getElementById( "StageV-top" );
094            public void run( final D diff ) { eStageV.addClassName( diff.stageRelClass() ); }
095        });
096    }
097
098
099        /** Removes the {@linkplain LightableDifference#stageRelClass() existential style
100          * class} for {@linkplain #run(LightableDifference.Runner) each} lightable
101          * difference from the <code>#StageV-top</code> element.
102          */
103        public final void removeStageRelClasses()
104        {
105            run( new LightableDifference.Runner<D>()
106            {
107                final Element eStageV = Document.get().getElementById( "StageV-top" );
108                public void run( final D diff ) { eStageV.removeClassName( diff.stageRelClass() ); }
109            });
110        }
111
112
113
114    /** The actuator for the sensor defined by the current {@linkplain Stage#getActorName()
115      * actor} and {@linkplain Stage#getPollName() poll}, which engages whenever the user
116      * is not pointing to a particular {@linkplain PositionSensor position sensor}.
117      */
118    protected final Actuator_DL baseActuator() { return baseActuator; }
119
120
121        private final BaseActuator baseActuator;
122
123
124
125    /** The style class {@value} assigned to the document body when a {@linkplain
126      * #setScene(String,String) scene difference} is set and is currently lighted from
127      * the vantage of the anchoring author.
128      */
129    public static final String BODY_ANCHOR = "voLiDi-anchor";
130
131
132        private String ancClassLit;
133
134
135
136    /** The style class {@value} assigned to the document body when a {@linkplain
137      * #setScene(String,String) scene difference} is set and is currently lighted.  The
138      * scene may assign the initial value (adding it to the body or leaving it off)
139      * depending on how it wishes to be styled before this light initializes and assigns
140      * the correct value.
141      */
142    public static final String BODY_SCENE = "voLiDi-scene";
143
144        // Changing?  Grep value and change where hardcoded, e.g. in s/wic/diff/WP_D.html
145
146        private String scnClassLit = ElementX.hasClassName(
147          /*allNames*/Document.get().getBody().getClassName(), BODY_SCENE )? BODY_SCENE: null;
148
149
150
151    /** Returns the lightable difference for the specified person and poll, or null if
152      * there is none.
153      */
154    protected abstract D differenceFor( String personName, String pollName );
155
156
157
158    /** Answers whether the lightable differences of this light are also {@linkplain
159      * DiffLook difference looks} and thus able to be directly staged.
160      */
161    protected abstract boolean hasDiffLooks();
162
163
164
165    /** The maximum number of differences ({@value}) that can exist in the vote track.
166      * The number includes up to twenty dart-sectored authors and one mosquito for each
167      * of two boards ({@linkplain VoteTrack#voters() voters} and {@linkplain
168      * VoteTrack#peers() peers}), plus the {@linkplain VoteTrack#candidate() candidate}
169      * and less the {@linkplain VoteTrack#anchor() anchoring author}, versus whose draft
170      * the others are compared.
171      */
172    public static final int MAX_DIFFS = (20 + 1) * 2 + 1 - 1;
173
174
175
176    /** Redirects the light according to the current light actuator.
177      *
178      *     @return the difference associated with the actuator's sensor, or null if there
179      *       is none.
180      */
181    protected LightableDifference redirect()
182    {
183        final LightableDifference diff = actuator.diff();
184        final String ancClass;
185        final String scnClass;
186        final String ordClass;
187        final String relClass;
188        final String secClass;
189        if( diff == null )
190        {
191            ancClass = null;
192            scnClass = null;
193            ordClass = null;
194            relClass = null;
195            secClass = null;
196        }
197        else
198        {
199            ancClass = actuator.bodyAncClass();
200            scnClass = actuator.bodyScnClass();
201            ordClass = diff.bodyOrdClass();
202            relClass = diff.bodyRelClass();
203            secClass = diff.bodySecClass();
204        }
205        final Element body = Document.get().getBody();
206        ElementX.replaceNullClassName( ancClassLit, ancClass, body );
207        ElementX.replaceNullClassName( scnClassLit, scnClass, body );
208        ElementX.replaceNullClassName( ordClassLit, ordClass, body );
209        ElementX.replaceNullClassName( relClassLit, relClass, body );
210        ElementX.replaceNullClassName( secClassLit, secClass, body );
211        ancClassLit = ancClass;
212        scnClassLit = scnClass;
213        ordClassLit = ordClass;
214        relClassLit = relClass;
215        secClassLit = secClass;
216        return diff;
217    }
218
219
220
221    /** Passes all lightable differences through the specified runner.
222      */
223    protected abstract void run( LightableDifference.Runner<D> runner );
224
225
226
227    /** The name of the other author of the scene difference as opposed to the anchor; or
228      * null if no scene difference is set.
229      *
230      *     @see #setScene(String,String)
231      */
232    public static String sceneName() { return sceneName; }
233
234
235        private static String sceneName;
236
237
238
239    /** The {@linkplain LightableDifference style symbol} for the cast relation of the
240      * other author of the scene difference as regarded by the anchor, e.g. {@linkplain
241      * LightableDifference#REL_VOTER REL_VOTER}; or '\u0000' if no scene difference is
242      * set.
243      *
244      *     @see #setScene(String,String)
245      */
246    public static char sceneRel() { return sceneRel; }
247
248
249        private static char sceneRel;
250
251
252
253    /** Specifies a difference that is displayed in the scene.  Call this method from the
254      * global configuration function {@linkplain StageMod voGWTConfig.s_gwt_stage} in
255      * this fashion:<pre
256      *
257     *>   s_gwt_stage_vote_DifferenceLight_setScene( 'Frank-FlippityNet' );
258      *     // default is null, which lights no default difference</pre>
259      *
260      * A {@linkplain Stage#getDefaultActorName() default actor} and {@linkplain
261      * Stage#getDefaultDifference() difference} should also be set on stage.  If those
262      * are set, then the scene difference will be lighted and style class {@value
263      * BODY_SCENE} assigned to the document body whenever either of the two authors is on
264      * stage, while at other times the class will be removed.
265      *
266      *     @see #sceneName()
267      *     @see #sceneRel()
268      */
269    public static @GWTConfigCallback void setScene( String _sceneName,
270      String _sceneRel )
271    {
272        sceneName = _sceneName;
273        if( _sceneRel.length() != 1 ) throw new IllegalArgumentException();
274
275        sceneRel = _sceneRel.charAt( 0 );
276    }
277
278
279        private static native void exposeScene()
280        /*-{
281            $wnd.s_gwt_stage_vote_DifferenceLight_setScene = $entry(
282              @votorola.s.gwt.stage.vote.DifferenceLight::setScene(Ljava/lang/String;Ljava/lang/String;) );
283        }-*/;
284
285
286        static
287        {
288            assert StageMod.isForcedInit(): "forced init " + DifferenceLight.class.getName();
289            exposeScene();
290        }
291
292
293
294    /** The staged instance of the vote track.
295      */
296    protected final VoteTrack voteTrack() { return voteTrack; }
297
298
299        private final VoteTrack voteTrack;
300
301
302
303   // - L i g h t ------------------------------------------------------------------------
304
305
306    public Actuator_DL assignActuator( final PositionSensor sensor )
307    {
308        final Actuator1 actuator = sceneName == null? new Actuator1( sensor ):
309          new ActuatorSD( sensor );
310        actuator.init();
311        return actuator;
312    }
313
314
315
316    public final PositionSensor tryCast( final Sensor sensor )
317    {
318        return sensor instanceof PositionSensor? (PositionSensor)sensor: null;
319    }
320
321
322
323   // ====================================================================================
324
325
326    /** An actuator for the difference light.
327      */
328    public static interface Actuator_DL extends Actuator<PositionSensor>
329    {
330
331        /** Completes the creation of this Actuator_DL.  Call once only.
332          */
333        void init();
334
335
336       // --------------------------------------------------------------------------------
337
338
339        /** The style class {@value votorola.s.gwt.stage.vote.DifferenceLight#BODY_ANCHOR}
340          * to assign to the document body, or null to assign none.
341          */
342        String bodyAncClass();
343
344
345        /** The style class {@value votorola.s.gwt.stage.vote.DifferenceLight#BODY_ANCHOR}
346          * to assign to the document body, or null to assign none.
347          */
348        String bodyScnClass();
349
350
351        /** Responds to a change in the assigned sensor's properties.
352          */
353        public void changed();
354
355
356        /** The difference currently associated with this actuator's sensor, or null if
357          * there is none.
358          *
359          *     @see #differenceFor(String,String)
360          */
361        LightableDifference diff();
362
363
364    }
365
366
367
368//// P r i v a t e ///////////////////////////////////////////////////////////////////////
369
370
371    private Actuator_DL actuator; // never null after init, instead set to baseActuator
372
373
374
375    private String ordClassLit;
376
377
378
379    private String relClassLit;
380
381
382
383    private String secClassLit;
384
385
386
387    private LightableDifference syntheticSceneDiff;
388
389
390
391   // ====================================================================================
392
393
394    private final class ActorSensor implements PositionSensor, PropertyChangeHandler
395    {
396
397        /** Completes the creation of this ActorSensor.  Call once only.
398          */
399        void init( final Stage stage )
400        {
401            GWTX.i().bus().addHandlerToSource( PropertyChange.TYPE, /*source*/stage,
402              ActorSensor.this ); // no need to unregister, registry does not outlive this listener
403        }
404
405
406       // - P o s i t i o n - S e n s o r ------------------------------------------------
407
408
409        public String personName() { return Stage.i().getActorName(); }
410
411
412        public String pollName() { return Stage.i().getPollName(); }
413
414
415       // - P r o p e r t y - C h a n g e - H a n d l e r --------------------------------
416
417
418        public void onPropertyChange( final PropertyChange e )
419        {
420            final String name = e.propertyName();
421            if( name.equals("actorName") || name.equals("pollName") ) changed();
422        }
423
424
425       // - S e n s o r ------------------------------------------------------------------
426
427
428        public void changed() { baseActuator.changed(); }
429
430
431        public void out() {} // base sensor, not pointer activated
432
433
434        public void over() {} // "
435
436    }
437
438
439
440   // ====================================================================================
441
442
443    private class Actuator1 implements Actuator_DL
444    {
445
446        /** Partially creates an Actuator1 for {@linkplain #init() init} to finish.
447          */
448        Actuator1( PositionSensor _sensor ) { sensor = _sensor; }
449
450
451        public final void init() { changed(); }
452
453
454       // --------------------------------------------------------------------------------
455
456
457        void changedHook() {}
458
459
460        final PositionSensor sensor;
461
462
463       // - A c t u a t o r --------------------------------------------------------------
464
465
466        public final void changed( PositionSensor _sensor ) { changed(); }
467
468
469        public final Light<PositionSensor> light() { return DifferenceLight.this; }
470
471
472        public final void out( PositionSensor _sensor )
473        {
474            if( actuator != Actuator1.this ) return; // already out
475
476            actuator = baseActuator; // to default
477            redirect();
478        }
479
480
481        public void over( PositionSensor _sensor )
482        {
483            if( diff == null || /*already over*/actuator == Actuator1.this ) return;
484
485            actuator = Actuator1.this;
486            redirect();
487        }
488
489
490       // - A c t u a t o r - D  L -------------------------------------------------------
491
492
493        public String bodyAncClass() { return null; }
494
495
496        public String bodyScnClass() { return null; }
497
498
499        public void changed()
500        {
501            diff = differenceFor( sensor.personName(), sensor.pollName() );
502            changedHook();
503            if( actuator == Actuator1.this ) redirect();
504        }
505
506
507        public final LightableDifference diff() { return diff; }
508
509
510            LightableDifference diff;
511
512    }
513
514
515
516   // ====================================================================================
517
518
519    private class ActuatorSD extends Actuator1 // for use when scene difference set
520    {
521
522        /** Partially creates an ActuatorSD for {@linkplain #init() init} to finish.
523          */
524        ActuatorSD( PositionSensor _sensor ) { super( _sensor ); }
525
526
527        boolean isAnchor;
528
529
530        private String personName;
531
532
533       // - A c t u a t o r --------------------------------------------------------------
534
535
536        public final @Override void over( PositionSensor _sensor )
537        {
538            // let redirect set correct bodyScnClass even for non-drafters (null diff)
539            if( personName == null || /*already over*/actuator == ActuatorSD.this ) return;
540
541            actuator = ActuatorSD.this;
542            redirect();
543        }
544
545
546       // - A c t u a t o r _ D  L -------------------------------------------------------
547
548
549        public final @Override String bodyAncClass() { return bodyAncClass; }
550
551
552            private String bodyAncClass;
553
554
555        public final @Override String bodyScnClass() { return bodyScnClass; }
556
557
558            private String bodyScnClass;
559
560
561        public @Override void changed()
562        {
563            final Stage stage = Stage.i();
564
565          // isAnchor, bodyScnClass
566          // ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `
567            isAnchor = false;
568            bodyScnClass = null; // unless proven otherwise
569            personName = sensor.personName();
570            if( personName != null )
571            {
572                final String pollName = sensor.pollName();
573                if( pollName != null && pollName.equals( stage.getDefaultPollName() ))
574                {
575                    if( personName.equals( stage.getDefaultActorName() ))
576                    {
577                        isAnchor = true;
578                        bodyScnClass = BODY_SCENE;
579                    }
580                    else if( personName.equals( sceneName )) bodyScnClass = BODY_SCENE;
581                }
582            }
583
584          // bodyAncClass
585          // ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `
586            bodyAncClass = isAnchor? BODY_ANCHOR: null;
587
588          // ` ` `
589            super.changed(); // calls changedHook before redirecting
590        }
591
592
593       // - A c t u a t o r - 1 ----------------------------------------------------------
594
595
596        final @Override void changedHook()
597        {
598          // diff
599          // ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `
600            if( diff == null && isAnchor )
601            {
602                // anchor to light and scene diff set (this being ActuatorSD), but no formal diff
603                if( syntheticSceneDiff == null )
604                {
605                    final DiffLook defaultDiff = Stage.i().getDefaultDifference();
606                    if( defaultDiff == null ) assert false: "default diff set with scene diff";
607                    else syntheticSceneDiff = new LightableDifference1( sceneRel,
608                      /*sec, unknown*/-2, defaultDiff.selectand(), sensor.personName(),
609                      sensor.pollName() );
610                }
611                diff = syntheticSceneDiff;
612            }
613        }
614
615    }
616
617
618
619   // ====================================================================================
620
621
622    private static interface BaseActuator extends Actuator_DL
623    {
624
625        /** Completes the creation of this BaseActuator.  Call once only.
626          */
627        void init( Object linkLighter, Stage stage ); /* Object because "non-static class
628          LinkLighter cannot be referenced from a static context" */
629
630
631        String actorLinkVariant();
632
633    }
634
635
636
637   // ====================================================================================
638
639
640    private final class BaseActuator1 extends Actuator1 implements BaseActuator
641    {
642
643        /** Partially creates a BaseActuator1 for {@linkplain #init(LinkTrackV) init} to
644          * finish.
645          */
646        BaseActuator1() { super( new ActorSensor() ); }
647
648
649          @SuppressWarnings("unchecked")
650        public final @Override void init( Object _linkLighter, final Stage stage )
651        {
652            linkLighter = (LinkLighter)_linkLighter;
653            ((ActorSensor)sensor).init( stage );
654            init();
655        }
656
657
658        private LinkLighter linkLighter; // final after init
659
660
661       // - A c t u a t o r _ D  L -------------------------------------------------------
662
663
664        public @Override void changed()
665        {
666            super.changed();
667            linkLighter.schedule();
668        }
669
670
671       // - B a s e - A c t u a t o r ----------------------------------------------------
672
673
674        public String actorLinkVariant()
675        {
676            return diff == null? ACTOR_LINK_VARIANT_BASE: diff.actorLinkVariant();
677        }
678
679    }
680
681
682
683   // ====================================================================================
684
685
686    private final class BaseActuatorSD extends ActuatorSD implements BaseActuator
687    {
688
689        /** Partially creates a BaseActuatorSD for {@linkplain #init(LinkTrackV) init} to
690          * finish.
691          */
692        BaseActuatorSD() { super( new ActorSensor() ); }
693
694
695          @SuppressWarnings("unchecked")
696        public final @Override void init( Object _linkLighter, final Stage stage )
697        {
698            linkLighter = (LinkLighter)_linkLighter;
699            ((ActorSensor)sensor).init( stage );
700            init();
701        }
702
703
704        private LinkLighter linkLighter; // final after init
705
706
707       // - A c t u a t o r _ D  L -------------------------------------------------------
708
709
710        public @Override void changed()
711        {
712            super.changed();
713            linkLighter.schedule();
714        }
715
716
717       // - B a s e - A c t u a t o r ----------------------------------------------------
718
719
720        public String actorLinkVariant()
721        {
722            final String v;
723            if( diff == null ) v = ACTOR_LINK_VARIANT_BASE;
724            else if( isAnchor )
725            {
726                // Actor is anchor.  Scene diff is set (this being BaseActuatorSD), so
727                // variant will depend on cast relation (it's not the usual orange-brown
728                // anchor styling).  But need to reverse relation because it's expressed
729                // in terms of other person.
730                final String other = diff.actorLinkVariant();
731                if( other.endsWith( "-voter" )) v = ACTOR_LINK_VARIANT_BASE + "-candidate";
732                else if( other.endsWith( "-candidate" )) v = ACTOR_LINK_VARIANT_BASE + "-voter";
733                else if( other.endsWith( "-peer-a" )) v = ACTOR_LINK_VARIANT_BASE + "-peer-b";
734                else if( other.endsWith( "-peer-b" )) v = ACTOR_LINK_VARIANT_BASE + "-peer-a";
735                else if( other.endsWith( "-unknown-a" )) v = ACTOR_LINK_VARIANT_BASE + "-unknown-b";
736                else if( other.endsWith( "-unknown-b" )) v = ACTOR_LINK_VARIANT_BASE + "-unknown-a";
737                else throw new IllegalStateException();
738            }
739            else v = diff.actorLinkVariant();
740            return v;
741        }
742
743    }
744
745
746
747 //// ====================================================================================
748 //
749 //
750 // private final class FastDeselector implements PropertyChangeHandler
751 // {
752 //
753 //     FastDeselector( final Stage stage )
754 //     {
755 //      // GWTX.i().bus().addHandlerToSource( PropertyChange.TYPE, /*source*/stage,
756 //      //   FastDeselector.this ); // no need to unregister, registry does not outlive this listener
757 //     }
758 //
759 //
760 //    // - P r o p e r t y - C h a n g e - H a n d l e r --------------------------------
761 //
762 //
763 //     public void onPropertyChange( final PropertyChange e )
764 //     {
765 //         final CountNodeJS anchor = voteTrack.anchor();
766 //         if( anchor == null ) return;
767 //
768 //         final String name = e.propertyName();
769 //         if( name.equals("actorName") || name.equals("pollName") ) // staged position changed ...
770 //         {
771 //             final Stage s = Stage.i();
772 //             if( anchor.name().equals(s.getActorName()) // ... to anchor
773 //              && voteTrack.count().pollName().equals(s.getPollName()) )
774 //             {
775 //                 if( actuator != baseActuator ) // user is over another sensor
776 //                 {
777 //                     // Other sensor may be position control (like NodeV) and user
778 //                     // may have clicked it just now to deselect and revert to anchor.
779 //                     // In any case, don't wait for user to move from sensor, but instead
780 //                     // force an immediate redirect to anchor for sake of feedback:
781 //                     actuator = baseActuator; // whose ActorSensor is (or will be) at anchor
782 //                     redirect();
783 //                 }
784 //             }
785 //         }
786 //     }
787 //
788 // }
789 //// no longer needed, and redirect no longer without effect on node under mouse
790
791
792
793   // ====================================================================================
794
795
796    private final class LinkLighter extends CoalescingSchedulerS
797      implements PropertyChangeHandler, Scheduler.ScheduledCommand
798    {
799
800        LinkLighter( BaseActuator _baseActuator, LinkTrackV _linkTrackV, final Stage stage )
801        {
802            super( Scheduler.get(), CoalescingSchedulerS.DEFERRED );
803            baseActuator = _baseActuator;
804            linkTrackV = _linkTrackV;
805            init( /*command*/LinkLighter.this );
806
807            GWTX.i().bus().addHandlerToSource( PropertyChange.TYPE, /*source*/stage,
808              LinkLighter.this ); // no need to unregister, registry does not outlive this listener
809            schedule();
810        }
811
812
813        private final BaseActuator baseActuator;
814
815
816        private final LinkTrackV linkTrackV;
817
818
819        void relight()
820        {
821            final String v = baseActuator.actorLinkVariant();
822            linkTrackV.setActorLinkVariant( v );
823         // if( v == null ) return; // no diff link variant, no need to correct it
824         //// v never null in practice, rather defaults to ACTOR_LINK_VARIANT_BASE
825
826            final Stage stage = Stage.i();
827            final DiffLook dS = stage.getDifference(); // staged diff
828            if( dS == null || dS == NOMINAL_DIFF
829             || LightableDifference.TYPIFIER.isInstance(dS) ) return;
830              // dS cannot possibly mismatch dL, no need to correct link variant
831
832            final DiffLook dSDefault = stage.getDefaultDifference();
833            if( dSDefault != null && dSDefault.key().equals(dS.key()) )
834            {
835                if( sceneName != null ) // dS is scene diff
836                {
837                    if( dS.selectand().equals( dSDefault.selectand() )) return;
838                      // probably non-drafter; link self targets and will be disabled, no problem
839
840                    final @SuppressWarnings("unchecked") BaseActuatorSD baseActuatorSD =
841                      (BaseActuatorSD)baseActuator;
842                    if( !baseActuatorSD.isAnchor && baseActuator.bodyScnClass() != null ) return;
843                      // dL is scene diff too, okay
844                }
845            }
846            final LightableDifference dL = baseActuator.diff(); // lit diff
847            if( dL instanceof DiffLook && DiffLookJS.EQUATOR.equals(dS,(DiffLook)dL) ) return;
848              // dS and dL are same, all's well
849
850            linkTrackV.diffLink().resetVariant();
851              // Forcefully clear to indicate the link now points to an unlit (older or
852              // newer) difference of the same pair.  At least the pair is *expected* to
853              // be the same; DifferenceStager primes the stage according to the rules for
854              // stage.setDifference, which subsequent setters are also expected to obey.
855        }
856
857
858       // - 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 ------------------------
859
860
861        public final void execute() { relight(); }
862
863
864       // - P r o p e r t y - C h a n g e - H a n d l e r --------------------------------
865
866
867        public void onPropertyChange( final PropertyChange e )
868        {
869            if( e.propertyName().equals( "difference" )) schedule();
870        }
871
872    }
873
874
875}