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'>&nbsp;" ); // &nbsp; per count.css
293            b.appendHtmlConstant(    "<span class='accountName'>" );
294            renderAccountName( sac, b );
295            b.appendHtmlConstant(       "<span class='fadeRight5'>&nbsp;</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}