001package votorola.a.count.gwt; // Copyright 2011, 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.event.dom.client.*; 006import com.google.web.bindery.event.shared.*; 007import com.google.gwt.safehtml.shared.*; 008import com.google.gwt.uibinder.client.*; 009import com.google.gwt.user.cellview.client.*; 010import com.google.gwt.cell.client.*; 011import com.google.gwt.user.client.Event; 012import com.google.gwt.user.client.ui.*; 013import com.google.gwt.view.client.*; 014import java.util.*; 015import votorola.a.web.gwt.*; 016import votorola.g.hold.*; 017import votorola.g.lang.*; 018import votorola.g.web.gwt.*; 019import votorola.g.web.gwt.event.*; 020 021 022/** A view of a superaccount selection. It fires no change events, because its internal 023 * calls to setValue() request none. 024 */ 025 @Warning("permanently disabled on detach from document") 026public final class SacSelectionV extends CellWidget<SacJS> 027{ 028 029 030 /** Constructs a SacSelectionV. 031 */ 032 public SacSelectionV( SacSelection _model ) 033 { 034 super( new SacVCell() ); 035 model = _model; 036 037 addStyleName( "count-SacSelectionV" ); 038 addStyleName( "sacV" ); 039 addStyleName( "hoverOutlined" ); 040 addStyleName( "opaqueEnabled" ); 041 addStyleName( "tool" ); 042 043 // Popup list view. 044 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 045 { 046 final ScrollPanel scroll = new ScrollPanel(); 047 sacListPopup.setWidget( scroll ); 048 049 sacListV = new CellList<SacJS>( sacListVCell ); 050 scroll.setWidget( sacListV ); 051 sacListV.addStyleName( "sacList" ); 052 sacListV.setSelectionModel( model ); 053 sacListV.setKeyboardSelectionPolicy( 054 HasKeyboardSelectionPolicy.KeyboardSelectionPolicy. 055 // BOUND_TO_SELECTION ); 056 //// causes odd coloured (yellow) background to appear on first click 057 DISABLED ); 058 } 059 060 // Controllers. 061 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 062 new Modeller(); 063 new Selector(); 064 065 redraw(); /* because CellWidget does not draw itself initially when value is null, 066 as seen when refreshing for poll with no count */ 067 } 068 069 070 071 // ------------------------------------------------------------------------------------ 072 073 074 /** The model on which this view is based. 075 */ 076 SacSelection model() { return model; } 077 078 079 private final SacSelection model; 080 081 082 083 // - C e l l - W i d g e t ------------------------------------------------------------ 084 085 086 public @Override @Warning("non-API") void setValue( final SacJS value, final boolean fireEvents, 087 final boolean redraw ) 088 { 089 super.setValue( value, fireEvents, redraw ); 090 setEnabled( value != null ); 091 } 092 093 094 095 // - W i d g e t ---------------------------------------------------------------------- 096 097 098 protected @Override void onUnload() { spool.unwind(); } 099 100 101 102//// P r i v a t e /////////////////////////////////////////////////////////////////////// 103 104 105 private boolean isEnabled; 106 107 108 private void setEnabled( final boolean toEnable ) 109 { 110 if( toEnable == isEnabled ) return; 111 112 isEnabled = toEnable; 113 if( toEnable ) 114 { 115 addStyleName( "enabled" ); 116 removeStyleName( "disabled" ); 117 listPopperRegistration = addDomHandler( listPopper, MouseDownEvent.getType() ); 118 } 119 else 120 { 121 addStyleName( "disabled" ); 122 removeStyleName( "enabled" ); 123 listPopperRegistration.removeHandler(); 124 listPopperRegistration = null; 125 } 126 } 127 128 129 130 private final MouseDownHandler listPopper = new MouseDownHandler() 131 { 132 // see note regarding getConsumedEvents() in SacVCell 133 public void onMouseDown( final MouseDownEvent e ) 134 { 135 sacListVCell.setXSArmed( false ); 136 sacListPopup.setPopupPosition( e.getClientX() - 16, e.getClientY() ); 137 sacListPopup.show(); 138 139 final SacJS sac = getValue(); 140 final int s = Collections.binarySearch( sacList, sac, SacJS.NAME_COMPARATOR ); 141 // because CellList does not reveal row of selected item 142 if( s < 0 ) { assert false; return; } 143 144 final Element renderedElement = sacListV.getRowElement( s ); 145 renderedElement.scrollIntoView(); 146 } 147 }; 148 149 150 151 private HandlerRegistration listPopperRegistration; // no need to unregister at end, registry does not outlive the handler 152 153 154 155 private final PopupPanel sacListPopup = new PopupPanel() 156 { 157 { 158 setModal( true ); 159 setAutoHideEnabled( true ); 160 setAutoHideOnHistoryEventsEnabled( true ); 161 // setGlassEnabled( true ); 162 addStyleName( "count-SacSelectionV" ); 163 } 164 public @Override void onPreviewNativeEvent( final Event.NativePreviewEvent eP ) 165 { 166 // instead using addDomHandler(KeyDownHandler) would fail, because only body 167 // and form elements support key handlers 168 final NativeEvent e = eP.getNativeEvent(); 169 if( "keydown".equals( e.getType() ) && e.getKeyCode() == KeyCodes.KEY_ESCAPE ) hide(); 170 } 171 }; 172 173 174 175 private final ArrayList<SacJS> sacList = new ArrayList<SacJS>(); 176 177 178 179 private final CellList<SacJS> sacListV; 180 181 182 183 private final SacVListCell sacListVCell = new SacVListCell(); 184 185 186 187 private final Spool spool = new Spool1(); 188 189 190 191 // ==================================================================================== 192 193 194 private final class Modeller implements PropertyChangeHandler 195 { 196 197 private Modeller() 198 { 199 spool.add( new Hold() 200 { 201 final HandlerRegistration hR = GWTX.i().bus().addHandlerToSource( 202 PropertyChange.TYPE, /*source*/model, Modeller.this ); 203 public void release() { 204 hR.removeHandler(); } 205 }); 206 remodelCount(); // init state 207 } 208 209 210 public void onPropertyChange( final PropertyChange e ) 211 { 212 final String n = e.propertyName(); 213 if( "count".equals( n )) remodelCount(); 214 } 215 216 217 private final @Warning("init call") void remodelCount() 218 { 219 final CountJS count = model().count(); 220 if( count == null ) 221 { 222 sacListV.setRowCount( 0 ); 223 return; 224 } 225 226 sacList.clear(); 227 final JsMap<CountingMethodJS<SacJS>> methodMap = count.superaccounts(); 228 for( final String methodName: methodMap._in() ) 229 { 230 if( !methodMap._hasOwnProperty( methodName )) continue; 231 232 final CountingMethodJS<SacJS> method = methodMap.get( methodName ); 233 for( final String accountName: method._in() ) 234 { 235 if( !method._hasOwnProperty( accountName )) continue; 236 237 final SacJS sac = method.get( accountName ); 238 final int s = Collections.binarySearch( sacList, sac, SacJS.NAME_COMPARATOR ); 239 if( s >= 0 ) throw new IllegalStateException(); 240 241 final int insertionPoint = -s - 1; 242 sacList.add( insertionPoint, sac ); 243 } 244 } 245 sacListV.setRowData( sacList ); 246 } 247 248 249 } 250 251 252 253 // ==================================================================================== 254 255 256 private static final class SacVCell extends AbstractCell<SacJS> 257 { 258 259 private SacVCell() { super( "noevents" ); } 260 // Dummy event as workaround for a GWT bug related to the MouseDownHandler added 261 // to SacV. CellWidget cannot handle default construction (no parameter). It 262 // results in a null value for the set of Cell.getConsumedEvents() and a 263 // NullPointerException when it receives an event. 264 265 // But this raises an exception with GWT 2.5, which rejects "noevents". And the 266 // layout of SacSelectionV in the link track now looks funny, too. FIX these 267 // bugs and remove the comment from votorola.s.gwt.scene.Scenes.setUseRAC. 268 269 270 private SacVCell( String... consumedEvents ) { super( consumedEvents ); } 271 272 273 private static void renderAccountName( final SacJS sac, final SafeHtmlBuilder b ) 274 { 275 if( sac == null ) return; 276 277 b.appendHtmlConstant( "<span class='firstLetter countingMethod-" 278 + sac.countingMethodMnemonic().name() + "'>" ); 279 final String accountName = sac.accountName(); 280 b.append( accountName.charAt( 0 )); // first letter is specially styled 281 b.appendHtmlConstant( "</span>" ); 282 b.appendEscaped( accountName.substring( 1 )); 283 } 284 285 286 // - C e l l ---------------------------------------------------------------------- 287 288 289 public @Override final void render( Context _context, final SacJS sac, 290 final SafeHtmlBuilder b ) 291 { 292 b.appendHtmlConstant( "<span class='dropArrow'> " ); // per count.css 293 b.appendHtmlConstant( "<span class='accountName'>" ); 294 renderAccountName( sac, b ); 295 b.appendHtmlConstant( "<span class='fadeRight5'> </span>" ); // per a.css 296 b.appendHtmlConstant( "</span>" ); 297 b.appendHtmlConstant( "</span>" ); 298 } 299 300 301 } 302 303 304 305 // ==================================================================================== 306 307 308 /** The cell for rendering the items in the superaccount list. It has extended 309 * selection features that select an item whenever the mouse is lifted over that 310 * item, regardless of where the mouse was initially pressed. This allows the user 311 * to make a final correction by dragging the mouse prior, prior to lifting it and 312 * finalizing the choice. It also allows a convenient press, drag and release style 313 * of selection. 314 */ 315 private final class SacVListCell extends AbstractCell<SacJS> 316 { 317 318 SacVListCell() { super( "mousedown", "mouseout", "mouseover", "mouseup" ); } 319 320 321 private void hoverStyle( final Element e ) // because pseudoclass :hover fails, per .css note 322 { 323 if( lastHoverElement != null ) lastHoverElement.removeClassName( "hover" ); 324 325 if( e != null ) e.addClassName( "hover" ); 326 327 lastHoverElement = e; 328 } 329 330 331 private Element lastHoverElement; 332 333 334 /** Arms or disarms the selection extension. Ensure it is disarmed prior to each 335 * separate user session, e.g. when popping a chooser dialog. 336 */ 337 void setXSArmed( final boolean toArm ) 338 { 339 if( toArm ) 340 { 341 if( !xsArmed ) 342 { 343 sacListV.addStyleName( "xsArmed" ); 344 xsArmed = true; 345 } 346 } 347 else if( xsArmed ) 348 { 349 sacListV.removeStyleName( "xsArmed" ); 350 hoverStyle( null ); 351 xsArmed = false; 352 } 353 } 354 355 356 private boolean xsArmed; 357 358 359 // - C e l l ---------------------------------------------------------------------- 360 361 362 public @Override void onBrowserEvent( Cell.Context _context, final Element parent, 363 final SacJS value, final NativeEvent e, ValueUpdater<SacJS> _valueUpdater ) 364 { 365 final String type = e.getType(); 366 if( xsArmed ) 367 { 368 // Only if armed. The user may pop sacListPopup with a full click instead 369 // of a simple press, bringing with it an initial mouse up event. We do 370 // not want to take that as a selection gesture. 371 if( "mouseup".equals( type )) 372 { 373 model.setSac( value ); 374 sacListPopup.hide(); // eagerly, and even if value already selected 375 return; 376 } 377 else if( "mouseover".equals( type )) hoverStyle( parent ); 378 else if( "mouseout".equals( type )) hoverStyle( null ); 379 } 380 else if( "mouseout".equals(type) || "mousedown".equals(type) ) 381 // mouseout rather than mouseover, because mouseover can occur immediately 382 // when list is popped 383 { 384 setXSArmed( true ); 385 hoverStyle( parent ); 386 } 387 } 388 389 390 public @Override final void render( Context _context, final SacJS sac, 391 final SafeHtmlBuilder b ) 392 { 393 if( sac == null ) return; 394 395 b.appendHtmlConstant( "<div class='cell accountName'>" ); 396 SacVCell.renderAccountName( sac, b ); 397 b.appendHtmlConstant( "</div>" ); 398 } 399 400 401 // public @Override boolean resetFocus( Cell.Context _context, Element _parent, SacJS _value ) 402 // { 403 // return false; 404 // } 405 //// thought we might use it to automatically disarmXS(), but it is never called 406 407 } 408 409 410 411 // ==================================================================================== 412 413 414 private final class Selector implements SelectionChangeEvent.Handler 415 { 416 417 Selector() 418 { 419 spool.add( new Hold() 420 { 421 final HandlerRegistration hR = model.addSelectionChangeHandler( 422 Selector.this ); 423 public void release() { hR.removeHandler(); } 424 }); 425 reselect(); // init state 426 } 427 428 429 public void onSelectionChange( SelectionChangeEvent _e ) { reselect(); } 430 431 432 private final @Warning("init call") void reselect() 433 { 434 final SacJS sacOrNull = model.getSac(); 435 setValue( sacOrNull ); 436 sacListPopup.hide(); // if showing 437 } 438 } 439 440 441}