package votorola.s.gwt.scene.vote; // Copyright 2011-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.*; import com.google.gwt.event.shared.GwtEvent; // avoid * duplicating com.google.web.bindery.event.shared import com.google.gwt.view.client.*; import com.google.web.bindery.event.shared.HandlerRegistration; import votorola.a.count.*; import votorola.a.count.gwt.*; import votorola.a.web.gwt.*; import votorola.g.hold.*; import votorola.g.lang.*; import votorola.g.web.gwt.*; import votorola.g.web.gwt.event.*; import votorola.s.gwt.scene.*; import votorola.s.gwt.scene.feed.*; import votorola.s.gwt.stage.*; /** A social scene structured on lines of formal assent in regard to a * particular issue. It relies on dart scoping to provide a degree of {@linkplain * votorola.a.count.CountNode#dartSector() structural stability} in dependent views. * *

State changes are effected as late as the "finally" phase. Associated change * events are subsequently dispatched in the "deferred" phase where they appear atomic * regardless of the number of state variables involved.

* * @see VotespaceV */ public final class Votespace implements Hold, Scene, SacSelection { /** Constructs a Votespace. Call {@linkplain #release release}() when done with it. */ public Votespace() { new Scoper(); // first to set count early, if it was cached sacSetter = new SacSetter( Votespace.this, /*state*/CoalescingSchedulerS.FINALLY, // after any count change /*events*/CoalescingSchedulerS.DEFERRED ); } // ` e a r l y ```````````````````````````````````````````````````````````````````````` private final Spool spool = new Spool1(); // ------------------------------------------------------------------------------------ /** Decodes the dart sector for the specified offset along the votepath. * * @param v the character offset along the votepath. * @return a dart sector of 1 to {@value * votorola.a.count.CountNode#DART_SECTOR_MAX}. * * @see #votepath() * @see votorola.a.count.CountNode#dartSector() */ byte dartSector( final int v ) { return scoping.dartSector( v ); } /** The atomizer for request/response exchanges related to scoping, such as for counts * and count nodes. */ JSONPAtomizer exchangeAtomizerS() { return exchangeAtomizerS; } private final JSONPAtomizer exchangeAtomizerS = new JSONPAtomizer(); /** The name of the poll that is shown, or null if no poll is shown. The value is * bound via the {@linkplain GWTX#bus() event bus} to property name pollName. * When no poll is shown the count is always null and the votepath empty. When a new * poll is shown the count may intially be null until it is fetched from the server. * * @see #count() * @see #votepath() * @see Category:Poll */ final String pollName() { return pollName; } private String pollName; /** Prompts the scoping model to set the stage actor if that is wanted. * * @see DartScoping#syncActorToStage(CountNodeJS,String) */ void syncActorToStage( CountNodeJS _nodeOnVotepath, String _votepathFromNode ) { scoping.syncActorToStage( _nodeOnVotepath, _votepathFromNode ); } /** The votepath that is shown. The value is bound via the {@linkplain GWTX#bus() * event bus} to property name votepath, except that no event is fired when * the poll name or count changes. * * @see #count() * @see #pollName() * @see DartScoping#votepath() */ String votepath() { return scoping.votepath(); } // - H a s - H a n d l e r s ---------------------------------------------------------- public void fireEvent( final GwtEvent e ) { GWTX.i().bus().fireEventFromSource( e, Votespace.this ); } // - H o l d -------------------------------------------------------------------------- public void release() { spool.unwind(); } // - S c e n e ------------------------------------------------------------------------ /** {@inheritDoc} The scope is controlled by a {@linkplain DartScoping dart scoping * model} that bounds and filters the scene. Scoping is deep: each particular tree or * branch includes all uptree branches. If no scope is specified by the scoping * model, then the whole forest is included by default; if no poll is specified, then * the scope is unlimited. */ public boolean inScope( final BiteJS bite ) { if( pollName == null ) return true; // no specific poll, accept all bites final Poll pollB = bite.poll(); if( pollB == null || !pollName.equals( pollB.name() )) return false; final int vSN = votepath().length(); if( vSN == 0 ) return true; // no specific path, accept all bites for poll final JsArray persons = bite.persons(); bitePersons: for( int p = 0, pN = persons.length(); p < pN; ++p ) { final PersonJS personB = persons.get( p ); JsArray voteTraceB = personB.voteTraceProjected(); if( voteTraceB == null ) voteTraceB = personB.voteTrace(); if( voteTraceB == null ) break; // unknown trace, probably no count reported final int vBN = voteTraceB.length(); if( vBN < vSN ) continue; // person in bite is lower in tree than scope for( int vB = vBN - 1, vS = vSN - 1; vS >= 0; --vB, --vS ) // from bottom up { final PersonJS traceeB = voteTraceB.get( vB ); final byte sectorB = traceeB.dartSector(); if( sectorB < 1 ) continue bitePersons; // tracee of person in bite has no dart sector final byte sectorS = dartSector( vS ); if( sectorB != sectorS ) continue bitePersons; // person in bite is out of scope } return true; // all sectors in scope are matched by person in bite } return false; // no person in scope } // - P r o v i d e s - K e y ---------------------------------------------------------- public Object getKey( final SacJS item ) { return item; } // each instance is unique // - S a c - S e l e c t i o n -------------------------------------------------------- /** {@inheritDoc} If no value is specified in the URL, then it is taken to * designate the {@linkplain CountJS#voteSuperaccount() vote superaccount}. */ public Switch aSacSwitch() { return aSacSwitch; } private final Switch aSacSwitch = new Switch( "a", Scenes.i().history() ); { spool.add( new Hold() { public void release() { aSacSwitch.clear(); }}); } /** @see #pollName() */ public CountJS count() { return count; } private CountJS count; public SacJS getSac() { return sacSetter.sac(); } public void setSac( final SacJS newSac ) // indirectly through the 'a' switch { if( newSac != null && "Votes".equals(newSac.accountName()) && CountingMethodJS.SwitchMnemonic.v == newSac.countingMethodMnemonic() ) { aSacSwitch.clear(); // it's the default } else aSacSwitch.set( SacJS.appendSwitch(newSac,GWTX.stringBuilderClear()).toString() ); } // - S e l e c t i o n - M o d e l ---------------------------------------------------- public com.google.gwt.event.shared.HandlerRegistration addSelectionChangeHandler( final SelectionChangeEvent.Handler handler ) { return new HandlerRegistrationCW( GWTX.i().bus().addHandlerToSource( SelectionChangeEvent.getType(), /*source*/Votespace.this, handler )); } public boolean isSelected( final SacJS s ) { return ObjectX.nullEquals( s, getSac() ); } public void setSelected( final SacJS s, final boolean toSelect ) { if( toSelect ) setSac( s ); else if( s != null && s.equals( getSac() )) setSac( null ); } //// P r i v a t e /////////////////////////////////////////////////////////////////////// private final DelayedEventGun gun = new DelayedEventGun( /*source*/Votespace.this, CoalescingSchedulerS.DEFERRED ); private final SacSetter sacSetter; private final DartScoping scoping = new DartScoping( spool ); // ==================================================================================== private final class Scoper implements ScopeChangeHandler { // cf. s.gwt.stage.vote.VoteTrack.Tracker private Scoper() { scoping.addHandler( Scoper.this ); // no need to unregister, registry does not outlive this listener rescope( false ); // init state } /** @param r whether this is a regular or init call. */ private final @Warning("init call") void rescope( final boolean r ) { final String pollNameNew = scoping.pollName(); if( pollNameNew == null ) { if( pollName != null ) { count = null; pollName = null; if( r ) { sacSetter.schedule(); gun.schedule( new PropertyChange( "count" )); gun.schedule( new PropertyChange( "pollName" )); } } } else if( pollNameNew.equals( pollName )) // only the votepath has changed { if( r ) gun.schedule( new PropertyChange( "votepath" )); } else // new poll { pollName = pollNameNew; final CountCache countCache = CountCache.i(); final CountCache.RequestRecord record = countCache.requestRecord( pollName ); final CountJS countNew; if( record == null ) // not previously requested { final String loc = CountCache.requestCount_loc( pollName, GWTX.stringBuilderClear() ).toString(); final AtomicAsyncCallback callbackA = AtomicAsyncCallback.wrap( exchangeAtomizerS, new CountCallback( loc, Stage.i() ) { public void onSuccess( final CountJS countNew ) { count = countNew; sacSetter.schedule(); gun.schedule( new PropertyChange( "count" )); } // no need to null on failure, already nulled (below) on request }); callbackA.init( countCache.requestCount( pollName, loc, SpooledAsyncCallback.wrap(spool,callbackA) )); countNew = null; } else countNew = record.count(); if( countNew == null ) { if( count != null ) { count = null; if( r ) { sacSetter.schedule(); gun.schedule( new PropertyChange( "count" )); } } } else { count = countNew; if( r ) { sacSetter.schedule(); gun.schedule( new PropertyChange( "count" )); } } if( r ) gun.schedule( new PropertyChange( "pollName" )); } } // - S c o p e - C h a n g e - H a n d l e r -------------------------------------- public void onScopeChange( ScopeChangeEvent _e ) { rescope( true ); } } }