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}