001package 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. 002 003import com.google.gwt.core.client.*; 004import com.google.gwt.event.shared.GwtEvent; // avoid * duplicating com.google.web.bindery.event.shared 005import com.google.gwt.view.client.*; 006import com.google.web.bindery.event.shared.HandlerRegistration; 007import votorola.a.count.*; 008import votorola.a.count.gwt.*; 009import votorola.a.web.gwt.*; 010import votorola.g.hold.*; 011import votorola.g.lang.*; 012import votorola.g.web.gwt.*; 013import votorola.g.web.gwt.event.*; 014import votorola.s.gwt.scene.*; 015import votorola.s.gwt.scene.feed.*; 016import votorola.s.gwt.stage.*; 017 018 019/** A social scene structured on lines of <a 020 * href='../../../../../../../d/theory.xht#medium'>formal assent</a> in regard to a 021 * particular issue. It relies on dart scoping to provide a degree of {@linkplain 022 * votorola.a.count.CountNode#dartSector() structural stability} in dependent views. 023 * 024 * <p>State changes are effected as late as the "finally" phase. Associated change 025 * events are subsequently dispatched in the "deferred" phase where they appear atomic 026 * regardless of the number of state variables involved.</p> 027 * 028 * @see VotespaceV 029 */ 030public final class Votespace implements Hold, Scene, SacSelection 031{ 032 033 034 /** Constructs a Votespace. Call {@linkplain #release release}() when done with it. 035 */ 036 public Votespace() 037 { 038 new Scoper(); // first to set count early, if it was cached 039 sacSetter = new SacSetter( Votespace.this, 040 /*state*/CoalescingSchedulerS.FINALLY, // after any count change 041 /*events*/CoalescingSchedulerS.DEFERRED ); 042 } 043 044 045 046 // ` e a r l y ```````````````````````````````````````````````````````````````````````` 047 048 049 private final Spool spool = new Spool1(); 050 051 052 053 // ------------------------------------------------------------------------------------ 054 055 056 /** Decodes the dart sector for the specified offset along the votepath. 057 * 058 * @param v the character offset along the votepath. 059 * @return a dart sector of 1 to {@value 060 * votorola.a.count.CountNode#DART_SECTOR_MAX}. 061 * 062 * @see #votepath() 063 * @see votorola.a.count.CountNode#dartSector() 064 */ 065 byte dartSector( final int v ) { return scoping.dartSector( v ); } 066 067 068 069 /** The atomizer for request/response exchanges related to scoping, such as for counts 070 * and count nodes. 071 */ 072 JSONPAtomizer<CountCache.WAPResponse> exchangeAtomizerS() { return exchangeAtomizerS; } 073 074 075 private final JSONPAtomizer<CountCache.WAPResponse> exchangeAtomizerS = 076 new JSONPAtomizer<CountCache.WAPResponse>(); 077 078 079 080 /** The name of the poll that is shown, or null if no poll is shown. The value is 081 * bound via the {@linkplain GWTX#bus() event bus} to property name <tt>pollName</tt>. 082 * When no poll is shown the count is always null and the votepath empty. When a new 083 * poll is shown the count may intially be null until it is fetched from the server. 084 * 085 * @see #count() 086 * @see #votepath() 087 * @see <a href='http://reluk.ca/w/Category:Poll' 088 * target='_top'>Category:Poll</a> 089 */ 090 final String pollName() { return pollName; } 091 092 093 private String pollName; 094 095 096 097 /** Prompts the scoping model to set the stage actor if that is wanted. 098 * 099 * @see DartScoping#syncActorToStage(CountNodeJS,String) 100 */ 101 void syncActorToStage( CountNodeJS _nodeOnVotepath, String _votepathFromNode ) 102 { 103 scoping.syncActorToStage( _nodeOnVotepath, _votepathFromNode ); 104 } 105 106 107 108 /** The votepath that is shown. The value is bound via the {@linkplain GWTX#bus() 109 * event bus} to property name <tt>votepath</tt>, except that no event is fired when 110 * the poll name or count changes. 111 * 112 * @see #count() 113 * @see #pollName() 114 * @see DartScoping#votepath() 115 */ 116 String votepath() { return scoping.votepath(); } 117 118 119 120 // - H a s - H a n d l e r s ---------------------------------------------------------- 121 122 123 public void fireEvent( final GwtEvent<?> e ) 124 { 125 GWTX.i().bus().fireEventFromSource( e, Votespace.this ); 126 } 127 128 129 130 // - H o l d -------------------------------------------------------------------------- 131 132 133 public void release() { spool.unwind(); } 134 135 136 137 // - S c e n e ------------------------------------------------------------------------ 138 139 140 /** {@inheritDoc} The scope is controlled by a {@linkplain DartScoping dart scoping 141 * model} that bounds and filters the scene. Scoping is deep: each particular tree or 142 * branch includes all uptree branches. If no scope is specified by the scoping 143 * model, then the whole forest is included by default; if no poll is specified, then 144 * the scope is unlimited. 145 */ 146 public boolean inScope( final BiteJS bite ) 147 { 148 if( pollName == null ) return true; // no specific poll, accept all bites 149 150 final Poll pollB = bite.poll(); 151 if( pollB == null || !pollName.equals( pollB.name() )) return false; 152 153 final int vSN = votepath().length(); 154 if( vSN == 0 ) return true; // no specific path, accept all bites for poll 155 156 final JsArray<PersonJS> persons = bite.persons(); 157 bitePersons: for( int p = 0, pN = persons.length(); p < pN; ++p ) 158 { 159 final PersonJS personB = persons.get( p ); 160 JsArray<PersonJS> voteTraceB = personB.voteTraceProjected(); 161 if( voteTraceB == null ) voteTraceB = personB.voteTrace(); 162 if( voteTraceB == null ) break; // unknown trace, probably no count reported 163 164 final int vBN = voteTraceB.length(); 165 if( vBN < vSN ) continue; // person in bite is lower in tree than scope 166 167 for( int vB = vBN - 1, vS = vSN - 1; vS >= 0; --vB, --vS ) // from bottom up 168 { 169 final PersonJS traceeB = voteTraceB.get( vB ); 170 final byte sectorB = traceeB.dartSector(); 171 if( sectorB < 1 ) continue bitePersons; // tracee of person in bite has no dart sector 172 173 final byte sectorS = dartSector( vS ); 174 if( sectorB != sectorS ) continue bitePersons; // person in bite is out of scope 175 } 176 return true; // all sectors in scope are matched by person in bite 177 } 178 return false; // no person in scope 179 } 180 181 182 183 // - P r o v i d e s - K e y ---------------------------------------------------------- 184 185 186 public Object getKey( final SacJS item ) { return item; } // each instance is unique 187 188 189 190 // - S a c - S e l e c t i o n -------------------------------------------------------- 191 192 193 /** {@inheritDoc} If no value is specified in the URL, then it is taken to 194 * designate the {@linkplain CountJS#voteSuperaccount() vote superaccount}. 195 */ 196 public Switch aSacSwitch() { return aSacSwitch; } 197 198 199 private final Switch aSacSwitch = new Switch( "a", Scenes.i().history() ); 200 201 { 202 spool.add( new Hold() { public void release() { aSacSwitch.clear(); }}); 203 } 204 205 206 207 /** @see #pollName() 208 */ 209 public CountJS count() { return count; } 210 211 212 private CountJS count; 213 214 215 216 public SacJS getSac() { return sacSetter.sac(); } 217 218 219 public void setSac( final SacJS newSac ) // indirectly through the 'a' switch 220 { 221 if( newSac != null && "Votes".equals(newSac.accountName()) 222 && CountingMethodJS.SwitchMnemonic.v == newSac.countingMethodMnemonic() ) 223 { 224 aSacSwitch.clear(); // it's the default 225 } 226 else aSacSwitch.set( SacJS.appendSwitch(newSac,GWTX.stringBuilderClear()).toString() ); 227 } 228 229 230 231 // - S e l e c t i o n - M o d e l ---------------------------------------------------- 232 233 234 public com.google.gwt.event.shared.HandlerRegistration addSelectionChangeHandler( 235 final SelectionChangeEvent.Handler handler ) 236 { 237 return new HandlerRegistrationCW( GWTX.i().bus().addHandlerToSource( 238 SelectionChangeEvent.getType(), /*source*/Votespace.this, handler )); 239 } 240 241 242 public boolean isSelected( final SacJS s ) { return ObjectX.nullEquals( s, getSac() ); } 243 244 245 246 public void setSelected( final SacJS s, final boolean toSelect ) 247 { 248 if( toSelect ) setSac( s ); 249 else if( s != null && s.equals( getSac() )) setSac( null ); 250 } 251 252 253 254//// P r i v a t e /////////////////////////////////////////////////////////////////////// 255 256 257 private final DelayedEventGun gun = new DelayedEventGun( /*source*/Votespace.this, 258 CoalescingSchedulerS.DEFERRED ); 259 260 261 262 private final SacSetter sacSetter; 263 264 265 266 private final DartScoping scoping = new DartScoping( spool ); 267 268 269 270 // ==================================================================================== 271 272 273 private final class Scoper implements ScopeChangeHandler 274 { 275 // cf. s.gwt.stage.vote.VoteTrack.Tracker 276 277 private Scoper() 278 { 279 scoping.addHandler( Scoper.this ); // no need to unregister, registry does not outlive this listener 280 rescope( false ); // init state 281 } 282 283 284 /** @param r whether this is a regular or init call. 285 */ 286 private final @Warning("init call") void rescope( final boolean r ) 287 { 288 final String pollNameNew = scoping.pollName(); 289 if( pollNameNew == null ) 290 { 291 if( pollName != null ) 292 { 293 count = null; 294 pollName = null; 295 if( r ) 296 { 297 sacSetter.schedule(); 298 gun.schedule( new PropertyChange( "count" )); 299 gun.schedule( new PropertyChange( "pollName" )); 300 } 301 } 302 } 303 else if( pollNameNew.equals( pollName )) // only the votepath has changed 304 { 305 if( r ) gun.schedule( new PropertyChange( "votepath" )); 306 } 307 else // new poll 308 { 309 pollName = pollNameNew; 310 final CountCache countCache = CountCache.i(); 311 final CountCache.RequestRecord record = countCache.requestRecord( pollName ); 312 final CountJS countNew; 313 if( record == null ) // not previously requested 314 { 315 final String loc = CountCache.requestCount_loc( pollName, 316 GWTX.stringBuilderClear() ).toString(); 317 final AtomicAsyncCallback<CountJS, CountCache.WAPResponse> callbackA = 318 AtomicAsyncCallback.wrap( exchangeAtomizerS, new CountCallback<CountJS>( 319 loc, Stage.i() ) 320 { 321 public void onSuccess( final CountJS countNew ) 322 { 323 count = countNew; 324 sacSetter.schedule(); 325 gun.schedule( new PropertyChange( "count" )); 326 } 327 // no need to null on failure, already nulled (below) on request 328 }); 329 callbackA.init( countCache.requestCount( pollName, loc, 330 SpooledAsyncCallback.wrap(spool,callbackA) )); 331 countNew = null; 332 } 333 else countNew = record.count(); 334 if( countNew == null ) 335 { 336 if( count != null ) 337 { 338 count = null; 339 if( r ) 340 { 341 sacSetter.schedule(); 342 gun.schedule( new PropertyChange( "count" )); 343 } 344 } 345 } 346 else 347 { 348 count = countNew; 349 if( r ) 350 { 351 sacSetter.schedule(); 352 gun.schedule( new PropertyChange( "count" )); 353 } 354 } 355 if( r ) gun.schedule( new PropertyChange( "pollName" )); 356 } 357 } 358 359 360 // - S c o p e - C h a n g e - H a n d l e r -------------------------------------- 361 362 363 public void onScopeChange( ScopeChangeEvent _e ) { rescope( true ); } 364 365 366 } 367 368 369 370}