001package votorola.s.gwt.stage.vote; // Copyright 2013, Michael Allan, Christian Weilbach. 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.GWT; 004import com.google.gwt.dom.client.*; 005import com.google.gwt.event.dom.client.*; 006import com.google.gwt.uibinder.client.*; 007import com.google.gwt.user.client.ui.*; 008import votorola.a.count.gwt.*; 009import votorola.g.hold.*; 010import votorola.g.lang.*; 011import votorola.s.gwt.stage.*; 012import votorola.s.gwt.stage.light.*; 013 014import static com.google.gwt.dom.client.Style.Unit.PX; 015 016/** A projection above or below the mouse cursor showing the display title or personal 017 * name of the cued node. 018 */ 019final class HeadsUpDisplay extends Composite implements Actuator<NodalSensor>, Light<NodalSensor> 020{ 021 022 023 /** Creates a HeadsUpDisplay. Create at most one for the entire life of the trackV 024 * registry, as currently it does not unregister its listeners there. 025 * 026 * @param spool the spool for the release of associated holds. When unwound it 027 * releases the holds of this display and thereby disables it. 028 */ 029 HeadsUpDisplay( VoteTrackV _trackV, final Spool spool ) 030 { 031 trackV = _trackV; 032 if( trackV == VoteTrackV.iBottomFixed() ) 033 { 034 rail = topRail; 035 shuttle = topShuttle; 036 text = topText; 037 } 038 else 039 { 040 rail = bottomRail; 041 shuttle = bottomShuttle; 042 text = bottomText; 043 } 044 final Element div = rail.getParentElement(); // layout div.display.top or bottom 045 div.addClassName( "inside" ); 046 div.getStyle().setDisplay( Style.Display.BLOCK ); 047 Stage.i().lightBank().addLight( HeadsUpDisplay.this ); 048 spool.add( new Hold() 049 { 050 public void release() { Stage.i().lightBank().removeLight( HeadsUpDisplay.this ); } 051 }); 052 new Shuttler(); 053 redisplay(); 054 } 055 056 057 058 // ` e a r l y ```````````````````````````````````````````````````````````````````````` 059 060 061 @Warning("non-API") interface UiBinderI extends UiBinder<HTMLPanel,HeadsUpDisplay> {} 062 063 { 064 final UiBinderI uiBinder = GWT.create( UiBinderI.class ); 065 initWidget( uiBinder.createAndBindUi( HeadsUpDisplay.this )); 066 } 067 068 069 070 // - A c t u a t o r ------------------------------------------------------------------ 071 072 073 public void changed( final NodalSensor sensor ) 074 { 075 if( sensor == litSensor ) redisplay(); 076 } 077 078 079 080 public Light<NodalSensor> light() { return HeadsUpDisplay.this; } 081 082 083 084 public void out( final NodalSensor sensor ) 085 { 086 if( sensor != litSensor ) return; // already out 087 088 litSensor = null; 089 redisplay(); 090 } 091 092 093 094 public void over( final NodalSensor sensor ) 095 { 096 if( sensor == litSensor ) return; // already over 097 098 litSensor = sensor; 099 redisplay(); 100 } 101 102 103 104 // - C o m p o s i t e ---------------------------------------------------------------- 105 106 107 protected @Override HTMLPanel getWidget() { return (HTMLPanel)super.getWidget(); } 108 109 110 111 // - L i g h t ------------------------------------------------------------------------ 112 113 114 public Actuator<NodalSensor> assignActuator( final NodalSensor sensor ) 115 { 116 return sensor.box().trackV() == trackV? HeadsUpDisplay.this: null; 117 // actuate only for sensors in the same track view 118 } 119 120 121 122 public final NodalSensor tryCast( final Sensor sensor ) 123 { 124 return sensor instanceof NodalSensor? (NodalSensor)sensor: null; 125 } 126 127 128 129//// P r i v a t e /////////////////////////////////////////////////////////////////////// 130 131 132 private static final String BLANK_TEXT = "\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0"; 133 // non-breaking spaces 134 135 136 137 @UiField @Warning("non-API") SpanElement bottomField; 138 139 140 141 @UiField @Warning("non-API") DivElement bottomRail; 142 143 144 145 @UiField @Warning("non-API") SpanElement bottomShuttle; 146 147 148 149 private final Text bottomText = Document.get().createTextNode( "" ); 150 151 { bottomField.appendChild( bottomText ); } 152 153 154 155 private NodalSensor litSensor; 156 157 158 159 private final DivElement rail; // top or bottom, whichever is displayed 160 161 162 163 private @Warning("init call") void redisplay() 164 { 165 final Style style = getStyleElement().getStyle(); 166 if( litSensor == null ) style.setOpacity( 0 ); // hide the display 167 else 168 { 169 String s = litSensor.displayTitle(); 170 if( s == null ) 171 { 172 s = litSensor.personName(); 173 if( s == null ) s = BLANK_TEXT; 174 } 175 text.setData( s ); 176 style.clearOpacity(); // to default, opaque and thus visible 177 } 178 } 179 180 181 182 private final SpanElement shuttle; // top or bottom, whichever is displayed 183 184 185 186 private final Text text; // top or bottom, whichever is displayed 187 188 189 190 @UiField @Warning("non-API") SpanElement topField; 191 192 193 194 @UiField @Warning("non-API") DivElement topRail; 195 196 197 198 @UiField @Warning("non-API") SpanElement topShuttle; 199 200 201 202 private final Text topText = Document.get().createTextNode( "" ); 203 204 { topField.appendChild( topText ); } 205 206 207 208 private final VoteTrackV trackV; 209 210 211 212 // ==================================================================================== 213 214 215 /** A controller to position this display. 216 */ 217 private final class Shuttler implements MouseMoveHandler, MouseOverHandler 218 { 219 220 Shuttler() 221 { 222 trackV.addDomHandler( Shuttler.this, MouseMoveEvent.getType() ); // no need to unregister, registry does not outlive the handler 223 trackV.addDomHandler( Shuttler.this, MouseOverEvent.getType() ); // " 224 } 225 226 227 private void reposition( final MouseEvent<?> e ) 228 { 229 final int left = e.getX() - /*aesthetic adjust*/1; 230 // if( left < 0 ) left = 0; // keep left field visible 231 shuttle.getStyle().setLeft( left , PX ); 232 final int y = e.getY(); 233 // Adjusting the Y position too, so the fields behave together as a 234 // supplementary cursor. This helps the user to guide the mouse, which is 235 // otherwise difficult because the cursor in the vote track (unlike poll and 236 // talk tracks) is relatively small compared to the large, moving display 237 // fields, like a dog running alongside a pair of cattle. Here we position 238 // the top field to abut the visible top of the node view (fill) when the 239 // cursor is at the bottom limit of the view, and vice versa for the bottom 240 // field. The user need only keep them from touching the edges of the view. 241 // topRail.getStyle().setTop( y + NodeVPainter.MARGIN_TOP 242 // + NodeV.STROKE_WIDTH, PX ); 243 // // not sure why stroke needn't be halved here (Firefox 16) 244 // bottomRail.getStyle().setTop( y - NodeVPainter.MARGIN_BOTTOM 245 // - NodeV.STROKE_WIDTH / 2, PX ); 246 // // this may be off by one pixel for very large font sizes (Firefox 16) 247 //// but when SVG not absolutely positioned, it happens to be much simpler: 248 rail.getStyle().setTop( y, PX ); 249 } 250 251 252 // - M o u s e - M o v e - H a n d l e r ------------------------------------------ 253 254 255 public void onMouseMove( final MouseMoveEvent e ) { reposition( e ); } 256 257 258 // - M o u s e - O v e r - H a n d l e r ------------------------------------------ 259 260 261 public void onMouseOver( final MouseOverEvent e ) { reposition( e ); } 262 263 } 264 265 266}