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}