package 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. import com.google.gwt.core.client.*; import com.google.gwt.dom.client.*; import com.google.gwt.event.dom.client.*; import com.google.web.bindery.event.shared.*; import com.google.gwt.safehtml.shared.*; import com.google.gwt.uibinder.client.*; import com.google.gwt.user.cellview.client.*; import com.google.gwt.cell.client.*; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.ui.*; import com.google.gwt.view.client.*; import java.util.*; import votorola.a.web.gwt.*; import votorola.g.hold.*; import votorola.g.lang.*; import votorola.g.web.gwt.*; import votorola.g.web.gwt.event.*; /** A view of a superaccount selection. It fires no change events, because its internal * calls to setValue() request none. */ @Warning("permanently disabled on detach from document") public final class SacSelectionV extends CellWidget { /** Constructs a SacSelectionV. */ public SacSelectionV( SacSelection _model ) { super( new SacVCell() ); model = _model; addStyleName( "count-SacSelectionV" ); addStyleName( "sacV" ); addStyleName( "hoverOutlined" ); addStyleName( "opaqueEnabled" ); addStyleName( "tool" ); // Popup list view. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - { final ScrollPanel scroll = new ScrollPanel(); sacListPopup.setWidget( scroll ); sacListV = new CellList( sacListVCell ); scroll.setWidget( sacListV ); sacListV.addStyleName( "sacList" ); sacListV.setSelectionModel( model ); sacListV.setKeyboardSelectionPolicy( HasKeyboardSelectionPolicy.KeyboardSelectionPolicy. // BOUND_TO_SELECTION ); //// causes odd coloured (yellow) background to appear on first click DISABLED ); } // Controllers. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - new Modeller(); new Selector(); redraw(); /* because CellWidget does not draw itself initially when value is null, as seen when refreshing for poll with no count */ } // ------------------------------------------------------------------------------------ /** The model on which this view is based. */ SacSelection model() { return model; } private final SacSelection model; // - C e l l - W i d g e t ------------------------------------------------------------ public @Override @Warning("non-API") void setValue( final SacJS value, final boolean fireEvents, final boolean redraw ) { super.setValue( value, fireEvents, redraw ); setEnabled( value != null ); } // - W i d g e t ---------------------------------------------------------------------- protected @Override void onUnload() { spool.unwind(); } //// P r i v a t e /////////////////////////////////////////////////////////////////////// private boolean isEnabled; private void setEnabled( final boolean toEnable ) { if( toEnable == isEnabled ) return; isEnabled = toEnable; if( toEnable ) { addStyleName( "enabled" ); removeStyleName( "disabled" ); listPopperRegistration = addDomHandler( listPopper, MouseDownEvent.getType() ); } else { addStyleName( "disabled" ); removeStyleName( "enabled" ); listPopperRegistration.removeHandler(); listPopperRegistration = null; } } private final MouseDownHandler listPopper = new MouseDownHandler() { // see note regarding getConsumedEvents() in SacVCell public void onMouseDown( final MouseDownEvent e ) { sacListVCell.setXSArmed( false ); sacListPopup.setPopupPosition( e.getClientX() - 16, e.getClientY() ); sacListPopup.show(); final SacJS sac = getValue(); final int s = Collections.binarySearch( sacList, sac, SacJS.NAME_COMPARATOR ); // because CellList does not reveal row of selected item if( s < 0 ) { assert false; return; } final Element renderedElement = sacListV.getRowElement( s ); renderedElement.scrollIntoView(); } }; private HandlerRegistration listPopperRegistration; // no need to unregister at end, registry does not outlive the handler private final PopupPanel sacListPopup = new PopupPanel() { { setModal( true ); setAutoHideEnabled( true ); setAutoHideOnHistoryEventsEnabled( true ); // setGlassEnabled( true ); addStyleName( "count-SacSelectionV" ); } public @Override void onPreviewNativeEvent( final Event.NativePreviewEvent eP ) { // instead using addDomHandler(KeyDownHandler) would fail, because only body // and form elements support key handlers final NativeEvent e = eP.getNativeEvent(); if( "keydown".equals( e.getType() ) && e.getKeyCode() == KeyCodes.KEY_ESCAPE ) hide(); } }; private final ArrayList sacList = new ArrayList(); private final CellList sacListV; private final SacVListCell sacListVCell = new SacVListCell(); private final Spool spool = new Spool1(); // ==================================================================================== private final class Modeller implements PropertyChangeHandler { private Modeller() { spool.add( new Hold() { final HandlerRegistration hR = GWTX.i().bus().addHandlerToSource( PropertyChange.TYPE, /*source*/model, Modeller.this ); public void release() { hR.removeHandler(); } }); remodelCount(); // init state } public void onPropertyChange( final PropertyChange e ) { final String n = e.propertyName(); if( "count".equals( n )) remodelCount(); } private final @Warning("init call") void remodelCount() { final CountJS count = model().count(); if( count == null ) { sacListV.setRowCount( 0 ); return; } sacList.clear(); final JsMap> methodMap = count.superaccounts(); for( final String methodName: methodMap._in() ) { if( !methodMap._hasOwnProperty( methodName )) continue; final CountingMethodJS method = methodMap.get( methodName ); for( final String accountName: method._in() ) { if( !method._hasOwnProperty( accountName )) continue; final SacJS sac = method.get( accountName ); final int s = Collections.binarySearch( sacList, sac, SacJS.NAME_COMPARATOR ); if( s >= 0 ) throw new IllegalStateException(); final int insertionPoint = -s - 1; sacList.add( insertionPoint, sac ); } } sacListV.setRowData( sacList ); } } // ==================================================================================== private static final class SacVCell extends AbstractCell { private SacVCell() { super( "noevents" ); } // Dummy event as workaround for a GWT bug related to the MouseDownHandler added // to SacV. CellWidget cannot handle default construction (no parameter). It // results in a null value for the set of Cell.getConsumedEvents() and a // NullPointerException when it receives an event. // But this raises an exception with GWT 2.5, which rejects "noevents". And the // layout of SacSelectionV in the link track now looks funny, too. FIX these // bugs and remove the comment from votorola.s.gwt.scene.Scenes.setUseRAC. private SacVCell( String... consumedEvents ) { super( consumedEvents ); } private static void renderAccountName( final SacJS sac, final SafeHtmlBuilder b ) { if( sac == null ) return; b.appendHtmlConstant( "" ); final String accountName = sac.accountName(); b.append( accountName.charAt( 0 )); // first letter is specially styled b.appendHtmlConstant( "" ); b.appendEscaped( accountName.substring( 1 )); } // - C e l l ---------------------------------------------------------------------- public @Override final void render( Context _context, final SacJS sac, final SafeHtmlBuilder b ) { b.appendHtmlConstant( " " ); //   per count.css b.appendHtmlConstant( "" ); renderAccountName( sac, b ); b.appendHtmlConstant( " " ); // per a.css b.appendHtmlConstant( "" ); b.appendHtmlConstant( "" ); } } // ==================================================================================== /** The cell for rendering the items in the superaccount list. It has extended * selection features that select an item whenever the mouse is lifted over that * item, regardless of where the mouse was initially pressed. This allows the user * to make a final correction by dragging the mouse prior, prior to lifting it and * finalizing the choice. It also allows a convenient press, drag and release style * of selection. */ private final class SacVListCell extends AbstractCell { SacVListCell() { super( "mousedown", "mouseout", "mouseover", "mouseup" ); } private void hoverStyle( final Element e ) // because pseudoclass :hover fails, per .css note { if( lastHoverElement != null ) lastHoverElement.removeClassName( "hover" ); if( e != null ) e.addClassName( "hover" ); lastHoverElement = e; } private Element lastHoverElement; /** Arms or disarms the selection extension. Ensure it is disarmed prior to each * separate user session, e.g. when popping a chooser dialog. */ void setXSArmed( final boolean toArm ) { if( toArm ) { if( !xsArmed ) { sacListV.addStyleName( "xsArmed" ); xsArmed = true; } } else if( xsArmed ) { sacListV.removeStyleName( "xsArmed" ); hoverStyle( null ); xsArmed = false; } } private boolean xsArmed; // - C e l l ---------------------------------------------------------------------- public @Override void onBrowserEvent( Cell.Context _context, final Element parent, final SacJS value, final NativeEvent e, ValueUpdater _valueUpdater ) { final String type = e.getType(); if( xsArmed ) { // Only if armed. The user may pop sacListPopup with a full click instead // of a simple press, bringing with it an initial mouse up event. We do // not want to take that as a selection gesture. if( "mouseup".equals( type )) { model.setSac( value ); sacListPopup.hide(); // eagerly, and even if value already selected return; } else if( "mouseover".equals( type )) hoverStyle( parent ); else if( "mouseout".equals( type )) hoverStyle( null ); } else if( "mouseout".equals(type) || "mousedown".equals(type) ) // mouseout rather than mouseover, because mouseover can occur immediately // when list is popped { setXSArmed( true ); hoverStyle( parent ); } } public @Override final void render( Context _context, final SacJS sac, final SafeHtmlBuilder b ) { if( sac == null ) return; b.appendHtmlConstant( "
" ); SacVCell.renderAccountName( sac, b ); b.appendHtmlConstant( "
" ); } // public @Override boolean resetFocus( Cell.Context _context, Element _parent, SacJS _value ) // { // return false; // } //// thought we might use it to automatically disarmXS(), but it is never called } // ==================================================================================== private final class Selector implements SelectionChangeEvent.Handler { Selector() { spool.add( new Hold() { final HandlerRegistration hR = model.addSelectionChangeHandler( Selector.this ); public void release() { hR.removeHandler(); } }); reselect(); // init state } public void onSelectionChange( SelectionChangeEvent _e ) { reselect(); } private final @Warning("init call") void reselect() { final SacJS sacOrNull = model.getSac(); setValue( sacOrNull ); sacListPopup.hide(); // if showing } } }