001package votorola.g.web.gwt; // Copyright 2010-2012, 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.event.logical.shared.*;
004import com.google.web.bindery.event.shared.*;
005import com.google.gwt.regexp.shared.*;
006import votorola.g.hold.*;
007import votorola.g.lang.*;
008import votorola.g.web.gwt.event.*;
009
010
011/** A URL fragment variable for controlling the navigable state of the application.  The
012  * {@linkplain #SWITCH_SPEC_PATTERN format} is similar to that of a query parameter.
013  * Switch names are documented and reserved on a per application basis.  See {@linkplain
014  * votorola.s.gwt.scene.Scenes Scenes} for an example.
015  */
016public class Switch implements Hold, ValueChangeHandler<String>
017{
018
019
020    /** Constructs a Switch and initializes it.  Call {@linkplain #release release}() if
021      * you are finished with it prior to releasing the provided history stack.
022      *
023      *     @see #name()
024      *     @see #history()
025      */
026    public Switch( String _name, HistoryX _history ) { this( _name, _history, /*toInit*/true ); }
027
028
029
030    /** Constructs a Switch.  Call {@linkplain #release release}() if you are finished
031      * with it prior to releasing the provided history stack.
032      *
033      *     @see #name()
034      *     @see #history()
035      *     @param toInit whether to initialize the newly constructed instance.
036      *       Subclasses may use this to defer initialization so they can use their
037      *       overridden versions of the init methods.
038      */
039    protected Switch( String _name, HistoryX _history, final boolean toInit )
040    {
041        name = _name;
042        history = _history;
043
044        spool.add( new Hold()
045        {
046            final HandlerRegistration hR = history.addHandler( Switch.this );
047            public void release() { hR.removeHandler(); }
048        });
049        if( toInit ) syncFromHistory(); // init state
050    }
051
052
053
054   // ------------------------------------------------------------------------------------
055
056
057    /** Registers a handler to receive change events fired by this switch.  These are
058      * similar to the change events of the history stack, only more specific.  Both types
059      * of event are fired only after all switches have been updated with their new
060      * values.
061      *
062      *     @see HistoryX#addHandler(ValueChangeHandler)
063      */
064    public final HandlerRegistration addHandler( final ValueChangeHandler<String> handler )
065    {
066        return history.bus().addHandlerToSource( ValueChangeEvent.getType(), /*source*/Switch.this,
067          handler );
068    }
069
070
071
072    /** Removes this switch from the URL and returns its value.
073      *
074      *     @see HistoryX#clearSwitchValue(String)
075      */
076    public final String clear() { return history.clearSwitchValue( name ); }
077
078      // We'll eventually need a reference mechanism for holding onto the switch and
079      // automatically clearing it when all references are dropped.  That way multiple,
080      // independent components may share the switch as a common state variable.  They
081      // may depend on it to retain its value in the URL for as long as one component
082      // needs it, and no longer.
083
084
085
086    /** Returns the value of this switch, or null if it has no value.
087      *
088      *     @see HistoryX#getSwitchValue(String)
089      */
090    public final String get() { return history.getSwitchValue( name ); }
091
092
093
094    /** The history stack.
095      */
096    public final HistoryX history() { return history; }
097
098
099        private HistoryX history;
100          // would be final except an apparent GWT compiler bug results in the error
101          // "blank final field history may not have been initialized"
102
103
104
105    /** The unique name of this switch.
106      */
107    public final String name() { return name; }
108
109
110        private final String name;
111
112
113
114    /** Sets the value of this switch and returns the old value.  Setting an empty value
115      * has the same effect as clearing the switch.
116      *
117      *     @see HistoryX#setSwitchValue(String,String)
118      */
119    public final String set( final String value ) { return history.setSwitchValue( name, value ); }
120
121
122
123    /** The pattern of a switch specification.  The form is nearly identical to that of
124      * an HTTP query parameter specification: <var>NAME</var>=<var>VALUE</var>.  A match
125      * sets the name and value to groups 1 and 2.
126      *
127      * <p>Like in the value of a query parameter, actual spaces ' ' in the switch value
128      * are encoded as plus signs '+' in the formal URL.  Unlike in a query parameter,
129      * actual <em>plus signs</em> are encoded as escaped spaces (%20), not plus signs
130      * (%2B).  This is the only difference between the two.</p>
131      */
132    public static final RegExp SWITCH_SPEC_PATTERN = RegExp.compile(  "^(.+)=(.+)$" );
133
134
135
136    /** The pattern of a switch specification separator.  As in HTTP query parameters,
137      * multiple specifications are separated by ampersand characters (&amp;).
138      */
139    public static final RegExp SWITCH_SPEC_SEPARATOR_PATTERN = RegExp.compile( "\\&" );
140
141
142
143   // - H o l d --------------------------------------------------------------------------
144
145
146    public final void release() { spool.unwind(); }
147
148
149
150   // - V a l u e - C h a n g e - H a n d l e r ------------------------------------------
151
152
153    /** Handles changes to the overall fragment.
154      */
155    public final void onValueChange( final ValueChangeEvent<String> e )
156    {
157        if( !syncFromHistory() ) return; // no actual change
158
159        history.bus().fireEventFromSource( new ValueChange<String>(lastValue), Switch.this );
160          // lastValue is actually the new value, as changed
161    }
162
163
164
165//// P r i v a t e ///////////////////////////////////////////////////////////////////////
166
167
168    protected String lastValue;
169
170
171
172    protected Spool spool = new Spool1();
173
174
175
176    /** Ensures that lastValue is correctly set and returns true if it was changed.
177      * {@linkplain #onValueChange(ValueChangeEvent) onValueChange}(e) calls this method
178      * in order to determine whether to fire a switch change event.
179      */
180    private @Warning("init call") final boolean syncFromHistory()
181    {
182        final String value = history.getSwitchValue( name );
183        if( ObjectX.nullEquals( lastValue, value )) return false; // no actual change
184
185        lastValue = value;
186        return true;
187    }
188
189
190
191}