001package votorola.s.gwt.stage.vote; // Copyright 2012-2013, 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.dom.client.*; 005import com.google.gwt.user.client.ui.*; 006import com.google.web.bindery.event.shared.HandlerRegistration; 007import java.util.*; 008import org.vectomatic.dom.svg.*; 009import org.vectomatic.dom.svg.utils.*; 010import votorola.a.count.gwt.*; 011import votorola.g.lang.*; 012import votorola.g.util.*; 013import votorola.g.web.gwt.*; 014import votorola.g.web.gwt.event.*; 015 016import static votorola.a.count.CountNode.DART_SECTOR_MAX; 017 018 019/** A view of a candidate rendered as an HTML table cell containing an SVG drawing. The 020 * drawing comprises a single {@linkplain BreakableNodeV breakable node view} for 021 * the {@linkplain VoteTrack#candidate() candidate}. It is visible only if the candidate 022 * is non-null and the {@linkplain VoteTrack#downstream() downstream node} (if any) has 023 * been fetched. (The downstream node and its voters are required for the sake of 024 * correctly sizing the view.) 025 * 026 * <p id='ackT'>Acknowledgement: Thomas von der Elbe contributed to the design of the 027 * size visualization. See the thread <a 028 * href='http://mail.zelea.com/list/votorola/2012-June/thread.html' target='_top'>Vote 029 * track</a>.</p> 030 */ 031final class CandidateV extends NodeV.Box 032{ 033 034 035 /** Constructs a CandidateV. 036 * 037 * @param element the outermost HTML element of which the view is composed. 038 */ 039 CandidateV( final VoteTrackV trackV, final TableCellElement element ) 040 { 041 super( votorola.a.count.XCastRelation.CANDIDATE, trackV ); 042 track = trackV.track(); 043 044 // HTML view. 045 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 046 setElement( element ); 047 048 // SVG view. 049 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 050 final OMSVGDocument svgDoc = OMSVGParser.createDocument(); 051 svg = svgDoc.createSVGSVGElement(); 052 element.appendChild( svg.getElement() ); 053 svg.appendChild( new BreakableNodeV( CandidateV.this, /*any dart sector*/0, track )); 054 055 // Controllers. 056 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 057 painter = new Painter( trackV ); 058 new Modeller( trackV ); 059 } 060 061 062 063 // - C o u n t - N o d e . B o x ------------------------------------------------------ 064 065 066 public Iterable<NodeV> nodeViews() { return nodeViews; } 067 068 069 private final Iterable<NodeV> nodeViews = new Iterable<NodeV>() 070 { 071 public Iterator<NodeV> iterator() 072 { 073 return new votorola.g.util.IteratorA<NodeV>() 074 { 075 private NodeV nextV = (NodeV)svg.getFirstChild(); 076 077 public boolean hasNext() { return nextV != null; } 078 079 public NodeV next() 080 { 081 if( nextV == null ) throw new NoSuchElementException(); 082 083 final NodeV nodeV = nextV; 084 nextV = null; // just the one 085 return nodeV; 086 } 087 }; 088 } 089 }; 090 091 092 093 public Object painter() { return painter; } 094 095 096 private final Painter painter; 097 098 099 100 // - W i d g e t ---------------------------------------------------------------------- 101 102 103 protected @Override void onLoad() 104 { 105 if( !spool().isUnwinding() ) painter.init( spool() ); 106 // after load, when size known [but unclear how init depends on size] 107 super.onLoad(); 108 } 109 110 111 112//// P r i v a t e /////////////////////////////////////////////////////////////////////// 113 114 115 CountNodeJS downstream; 116 117 118 119 private final BoardV.Palette palette = new BoardV.Palette(); 120 121 122 123 /** The SVG component of this view. 124 */ 125 private final OMSVGSVGElement svg; 126 127 128 129 private final VoteTrack track; 130 131 132 133 // ==================================================================================== 134 135 136 private final class Modeller extends SuspendedModeller 137 { 138 139 Modeller( final VoteTrackV trackV ) 140 { 141 super( trackV, spool() ); 142 if( trackV == VoteTrackV.iBottomFixed() ) 143 { 144 Scheduler.get().scheduleFinally( new Scheduler.ScheduledCommand() 145 { 146 public void execute() { remodelUnlessMoving(); } // init state 147 // later for bottom-fixed view, or length may init to zero 148 }); 149 } 150 else remodelUnlessMoving(); // init state 151 } 152 153 154 final @Warning("init call") void remodel() 155 { 156 CountNodeJS candidate = track.candidate(); 157 final BreakableNodeV child = (BreakableNodeV)svg.getFirstChild(); 158 boolean wasChanged = child.setCountNode( candidate ); 159 final CountNodeJS downstreamOld = downstream; 160 downstream = track.downstream(); 161 if( candidate == null || candidate.isVoter() && downstream == null ) 162 { 163 setVisible( false ); 164 } 165 else 166 { 167 if( !wasChanged && !ObjectX.nullEquals(downstreamOld,downstream) ) wasChanged = true; 168 palette.occupantCount = 0; 169 palette.outflowTotal = 0; 170 palette.receiveTotal = 0; 171 final JsArray<CountNodeJS> coNodes = downstream == null? 172 candidate.count().baseCandidates(): downstream.voters(); 173 for( int n = 0; n < DART_SECTOR_MAX; ++n ) 174 { 175 final CountNodeJS coNode = coNodes.get( n ); 176 if( coNode == null ) continue; 177 178 ++palette.occupantCount; 179 final SacRegisterJS_v reg = coNode.voteRegister(); 180 palette.outflowTotal += reg.carryVolume() + reg.castVolume(); 181 palette.receiveTotal += reg.receiveVolume(); 182 } 183 setVisible( true ); 184 if( painter != null && wasChanged ) painter.repaint(); 185 } 186 } 187 188 } 189 190 191 192 // ==================================================================================== 193 194 195 private final class Painter extends NodeVPainter 196 { 197 198 Painter( final VoteTrackV trackV ) 199 { 200 super( CandidateV.this ); 201 peerBoardContainer = trackV.peersBoardV().painter().container(); 202 } 203 204 205 private final UIObject peerBoardContainer; // standard for sizing 206 207 208 @Override void repaint( final int width, final float protrusion, final int y, 209 final float halfThickness ) 210 { 211 if( !isVisible() ) return; // await call from remodel 212 213 final BreakableNodeV child = (BreakableNodeV)svg.getFirstChild(); 214 final CountNodeJS candidate = child.getCountNode(); 215 palette.recalibrate( peerBoardContainer.getOffsetWidth(), protrusion, 216 /*isEndBoard*/!candidate.isVoter() ); /* The question is whether it would 217 be an end board when staged, because the point is to determine its size when 218 staged in the peer board. !isVoter is a sufficient test. We already know 219 it's a candidate. So, if it's not voting, then it must be a root candidate 220 (end board). Otherwise it's cyclic and will never stage as an end board. */ 221 final float length = palette.calculateLength( candidate ); 222 final float lengthMax = width - protrusion; 223 // --lengthMax; /* correct overflow that blunts arrow (Firefox 9). Setting 224 // overflow style 'visible' on svg element is no help. */ 225 /// no longer needed, Firefox 16 226 child.repaint( /*x*/0, length, protrusion, y, halfThickness, lengthMax ); 227 GWTX.i().bus().fireEventFromSource( new Change(), Painter.this ); 228 } 229 230 } 231 232 233}