001package votorola.s.gwt.stage.talk; // Copyright 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 java.util.Date;
004
005import com.google.gwt.core.client.GWT;
006import com.google.gwt.dom.client.*;
007import com.google.gwt.event.dom.client.*;
008import com.google.gwt.uibinder.client.*;
009import com.google.gwt.user.client.ui.*;
010
011import votorola.a.voter.IDPair;
012import votorola.a.web.gwt.App;
013import votorola.g.lang.*;
014import votorola.g.web.gwt.event.Change;
015import votorola.g.web.gwt.event.ChangeHandler;
016import votorola.s.gwt.stage.*;
017import votorola.s.gwt.stage.light.*;
018
019import static com.google.gwt.dom.client.Style.Unit.PX;
020
021
022/** A projection around the mouse cursor showing the personal name (above) and display
023  * title (below) of the cued node.
024  */
025final class HeadsUpDisplay extends Composite implements Actuator<TalkV.Sensor>, Light<TalkV.Sensor>, ChangeHandler
026{
027
028
029    /** Creates a HeadsUpDisplay.  Create at most one for the entire life of the trackV registry,
030      * as currently it does not unregister its listeners there.
031      */
032    HeadsUpDisplay( final TalkTrackV trackV )
033    {
034        Stage.i().lightBank().addLight( HeadsUpDisplay.this );
035        new Shuttler( trackV );
036        redisplay();
037    }
038    
039        
040        
041   // - C h a n g e H a n d l e r --------------------------------------------------------
042        
043        
044       @Override
045       public void onChange(Change e) {
046           redisplay();
047       }
048
049
050
051   // ` e a r l y ````````````````````````````````````````````````````````````````````````
052
053
054    @Warning("non-API") interface UiBinderI extends UiBinder<HTMLPanel,HeadsUpDisplay> {}
055
056        {
057            final UiBinderI uiBinder = GWT.create( UiBinderI.class );
058            initWidget( uiBinder.createAndBindUi( HeadsUpDisplay.this ));
059        }
060
061
062
063   // - A c t u a t o r ------------------------------------------------------------------
064
065
066    public void changed( final TalkV.Sensor sensor )
067    {
068        if( sensor == litSensor ) redisplay();
069    }
070
071
072
073    public Light<TalkV.Sensor> light() { return HeadsUpDisplay.this; }
074
075
076
077    public void out( final TalkV.Sensor sensor )
078    {
079        if( sensor != litSensor ) return; // already out
080
081        litSensor = null;
082        redisplay();
083    }
084
085
086
087    public void over( final TalkV.Sensor sensor )
088    {
089        if( sensor == litSensor ) return; // already over
090
091        litSensor = sensor;
092        redisplay();
093    }
094
095
096
097   // - C o m p o s i t e ----------------------------------------------------------------
098
099
100    protected @Override HTMLPanel getWidget() { return (HTMLPanel)super.getWidget(); }
101
102
103
104   // - L i g h t ------------------------------------------------------------------------
105
106
107    public Actuator<TalkV.Sensor> assignActuator( final TalkV.Sensor sensor )
108    {
109        return HeadsUpDisplay.this;
110    }
111
112
113
114    public final TalkV.Sensor tryCast( final Sensor sensor )
115    {
116        return sensor instanceof TalkV.Sensor? (TalkV.Sensor)sensor: null;
117    }
118
119
120
121//// P r i v a t e ///////////////////////////////////////////////////////////////////////
122
123
124
125    @UiField @Warning("non-API") SpanElement bottomField;
126
127
128
129    @UiField @Warning("non-API") DivElement bottomRail;
130
131
132
133    @UiField @Warning("non-API") SpanElement bottomShuttle;
134    
135   
136    
137    
138    // foot of the bubble
139    @UiField @Warning("non-API") DivElement bottomFootRail;
140
141    
142    
143    @UiField @Warning("non-API") SpanElement bottomFootShuttle;
144    
145    
146   
147    private final String bubbleFootLeftSrc = App.getServletContextLocation() + "/stage/talk/bubbleFootLeft.png";
148    
149    
150    
151    private final String bubbleFootRightSrc = App.getServletContextLocation() + "/stage/talk/bubbleFootRight.png";
152    
153    
154    
155    @UiField @Warning("non-API") ImageElement bottomBubbleFoot;
156    
157        {   bottomBubbleFoot.setSrc(bubbleFootRightSrc);
158            bottomBubbleFoot.setSrc(bubbleFootLeftSrc); }
159
160        
161
162
163    private final Text bottomText = Document.get().createTextNode( "" );
164
165        { bottomField.appendChild( bottomText ); }
166
167
168
169    private TalkV.Sensor litSensor;
170
171
172
173    private void redisplay()
174    {
175        final Style style = getStyleElement().getStyle();
176        if( litSensor == null ) { style.setOpacity( 0 ); } // hide the display
177        else
178        {
179            DiffMessageJS msg = litSensor.view().getMsg();
180            final String senderMne = IDPair.buildUserMnemonic(msg.sender(), new StringBuilder()).toString();
181            final String addresseeMne = IDPair.buildUserMnemonic(msg.addressee(), new StringBuilder()).toString();
182            leftText.setData(relativeAge((long)msg.sentDate()));
183            
184            senderText.setData(senderMne);
185            addresseeText.setData(addresseeMne);
186            bottomText.setData( msg.message().content() );
187            style.clearOpacity(); // to default, opaque and thus visible
188        }
189    }
190    
191    
192    
193    private String relativeAge(long sentDate) 
194    {
195        final long day = 1000 * 60 * 60 * 24; // ms
196        final long week = day * 7; 
197        final long month= day * 30; 
198        final long year= day * 365; 
199        
200        final long difference = new Date().getTime()-sentDate;
201        if (difference>year)
202        {
203            final long years = difference/year;
204            if(years == 1) { return  "1 year"; }
205            else { return years + " years"; }
206            
207        } else if(difference>month) 
208        {
209            final long months = difference/month;
210            if(months == 1) { return  "1 month"; }
211            else { return months + " months"; }
212            
213        } else if(difference>week) 
214        {
215            final long weeks = difference/week;
216            if(weeks == 1) { return "1 week"; } 
217            else { return  weeks + " weeks"; }
218            
219        } else 
220        {
221            final long days = difference/day;
222            if(days == 1) { return  "1 day"; } 
223            else { return days + " days"; }
224        }
225    }
226
227
228
229        @UiField @Warning("non-API") SpanElement leftField;
230        
231        private final Text leftText = Document.get().createTextNode( "" );
232        
233            { leftField.appendChild( leftText ); }
234        
235            
236            
237        @UiField @Warning("non-API") SpanElement rightField;
238        
239        
240        @UiField @Warning("non-API") SpanElement senderField;
241
242        private final Text senderText = Document.get().createTextNode( "" );
243            
244            { senderField.appendChild( senderText ); }
245        
246        
247        @UiField @Warning("non-API") SpanElement addresseeField;
248        
249        private final Text addresseeText = Document.get().createTextNode( "" );
250        
251            { addresseeField.appendChild( addresseeText ); }
252            
253
254
255        @UiField @Warning("non-API") DivElement topRail;
256
257
258
259        @UiField @Warning("non-API") DivElement topShuttle;
260
261        
262
263   // ====================================================================================
264
265
266    /** A controller to position this display.
267      */
268    private final class Shuttler implements MouseMoveHandler, MouseOverHandler
269    {
270
271        Shuttler( final TalkTrackV trackV )
272        {
273            trackV.addDomHandler( Shuttler.this, MouseMoveEvent.getType() ); // no need to unregister, registry does not outlive the handler
274            trackV.addDomHandler( Shuttler.this, MouseOverEvent.getType() ); // "
275        }
276
277        private void reposition( final MouseEvent<?> e )
278        {
279            final int stageWidth = StageV.i().getOffsetWidth();
280            final int left = e.getX() - /*aesthetic adjust*/1;
281            final int widthTop = topShuttle.getClientWidth();
282            final int widthBottom = bottomShuttle.getClientWidth();
283            
284            final int xTop = (left + widthTop/2 > stageWidth) ? stageWidth - widthTop : left - widthTop/2;
285            
286            final boolean rightBordered = left + widthBottom/2 > stageWidth;
287            final int xBottom = rightBordered ? stageWidth - widthBottom : left - widthBottom/2;
288            bottomBubbleFoot.setSrc(rightBordered ? bubbleFootRightSrc : bubbleFootLeftSrc);
289            bottomFootShuttle.getStyle().setLeft(  rightBordered ? left - bottomBubbleFoot.getClientWidth(): left, PX );
290         // if( left < 0 ) left = 0; // keep left field visible
291            topShuttle.getStyle().setLeft( xTop, PX );
292            bottomShuttle.getStyle().setLeft( xBottom, PX );
293            final int y = e.getY();
294              // Adjusting the Y position too, so the fields behave together as a
295              // supplementary cursor.  This helps the user to guide the mouse, which is
296              // otherwise difficult because the cursor in the vote track (unlike poll and
297              // talk tracks) is relatively small compared to the large, moving display
298              // fields, like a dog running alongside a pair of cattle.  Here we position
299              // the top field to abut the visible top of the node view (fill) when the
300              // cursor is at the bottom limit of the view, and vice versa for the bottom
301              // field.  The user need only keep them from touching the edges of the view.
302        //  topRail.getStyle().setTop( y + NodeVPainter.MARGIN_TOP
303        //    + NodeV.STROKE_WIDTH, PX );
304        //    // not sure why stroke needn't be halved here (Firefox 16)
305        //  bottomRail.getStyle().setTop( y - NodeVPainter.MARGIN_BOTTOM
306        //    - NodeV.STROKE_WIDTH / 2, PX );
307        //    // this may be off by one pixel for very large font sizes (Firefox 16)
308        //// but when SVG not absolutely positioned, it happens to be much simpler:
309            topRail.getStyle().setTop( y, PX );
310            bottomFootRail.getStyle().setTop( y, PX );
311            bottomRail.getStyle().setTop( y - 3, PX );
312        }
313
314
315       // - M o u s e - M o v e - H a n d l e r ------------------------------------------
316
317
318        public void onMouseMove( final MouseMoveEvent e ) { reposition( e ); }
319
320
321       // - M o u s e - O v e r - H a n d l e r ------------------------------------------
322
323
324        public void onMouseOver( final MouseOverEvent e ) { reposition( e ); }
325
326    }
327
328
329}