001package votorola.s.gwt.scene.axial; // Copyright 2010-2011, 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.HandlerRegistration;
005import com.google.gwt.user.client.*;
006import votorola.s.gwt.scene.*;
007import votorola.g.hold.*;
008import votorola.g.lang.*;
009import votorola.g.web.gwt.*;
010
011
012/** A scoping model based on two orthogonal axes (x,y).  Each axis may be set to its own
013  * independent range of values, such that both ranges together specify a square sub-area.
014  * The scoping state is exposed in the {@linkplain Scenes#sScopingSwitch() 's'
015  * switch}, as follows:
016  *
017  * <table class='definition' style='margin-left:1em'>
018  *     <tr>
019  *         <th class='key'>Switch</th>
020  *         <th>Controlled state</th>
021  *         <th>Default</th>
022  *         </tr>
023  *     <tr><td class='key'>s</td>
024  *
025  *         <td>The scoping state.  4 continguous numbers all padded to the same digit
026  *         length, together representing the ranges of both axes (x,y).  Each number is
027  *         translated to a decimal by adding a leading decimal.  An exception is made for
028  *         numbers beginning with the letter N, which are always translated as 1.0.  For
029  *         example "s=00NN1030" is taken as x in the range 0.00 to 1.00 and y 0.10 to
030  *         0.30.</td>
031  *
032  *         <td>"0N0N" which specifies the full range on both axes.</td>
033  *
034  *         </tr>
035  *     </table>
036  *
037  *     @see Scoping
038  */
039public final class DiaxialScoping implements Scoping
040{
041
042    // cf. TriaxialScoping
043
044
045    /** Constructs a DiaxialScoping.
046      *
047      *     @param spool for release of internal holds.  When unwound, this instance will
048      *         release its internal holds and become disabled.
049      */
050    public DiaxialScoping( final Spool spool )
051    {
052        spool.add( new Hold()
053        {
054            final HandlerRegistration hR = Scenes.i().sScopingSwitch().addHandler(
055              new ValueChangeHandler<String>()
056            {
057                public void onValueChange( final ValueChangeEvent<String> e )
058                {
059                    rescope( e.getValue() );
060                    GWTX.i().bus().fireEventFromSource( new ScopeChangeEvent(), DiaxialScoping.this );
061                }
062            });
063            public void release() { hR.removeHandler(); }
064        });
065        rescope( Scenes.i().sScopingSwitch().get() ); // init state
066    }
067
068
069
070   // ------------------------------------------------------------------------------------
071
072
073    /** The x-axis minimum value, inclusive.
074      *
075      *     @return a number between 0.0 and 1.0 inclusive.
076      */
077    public float xMin() { return xMin; }
078
079
080        private float xMin = 0f;
081
082
083    /** The x-axis maximum value, inclusive.
084      *
085      *     @return a number between 0.0 and 1.0 inclusive.
086      */
087    public float xMax() { return xMax; }
088
089
090        private float xMax = 1f;
091
092
093
094    /** The y-axis minimum value, inclusive.
095      *
096      *     @return a number between 0.0 and 1.0 inclusive.
097      */
098    public float yMin() { return yMin; }
099
100
101        private float yMin = 0f;
102
103
104    /** The y-axis maximum value, inclusive.
105      *
106      *     @return a number between 0.0 and 1.0 inclusive.
107      */
108    public float yMax() { return yMax; }
109
110
111        private float yMax = 1f;
112
113
114
115   // - O b j e c t ----------------------------------------------------------------------
116
117
118    public @Override String toString()
119    {
120        return "diaxial scope = x(" + xMin + ", " + xMax + ") y(" + yMin + ", " + yMax + ")";
121    }
122
123
124
125   // - S c o p i n g --------------------------------------------------------------------
126
127
128    public HandlerRegistration addHandler( final ScopeChangeHandler handler )
129    {
130        return GWTX.i().bus().addHandlerToSource( ScopeChangeEvent.TYPE, /*source*/DiaxialScoping.this,
131          handler );
132    }
133
134
135
136//// P r i v a t e ///////////////////////////////////////////////////////////////////////
137
138
139    static final String assumedNumericPrefix = "0.";
140
141
142
143    static float floatFrom( final String s, int offset, final int digitLength,
144      final StringBuilder buf )
145    {
146        char ch = s.charAt( offset );
147        if( ch == 'N' ) return 1f;
148
149        final int offsetBound = offset + digitLength;
150        buf.setLength( assumedNumericPrefix.length() );
151        for( ;; )
152        {
153            buf.append( ch );
154            if( ++offset >= offsetBound ) break;
155
156            ch = s.charAt( offset );
157        }
158
159        return Float.parseFloat( buf.toString() );
160    }
161
162
163
164    private void rescopeToDefault()
165    {
166        xMin = 0f; xMax = 1f;
167        yMin = 0f; yMax = 1f;
168    }
169
170
171
172    private @Warning("init call") void rescope( final String s )
173    {
174        if( s == null )
175        {
176            rescopeToDefault();
177            return;
178        }
179
180        final int sLength = s.length();
181        if( sLength % 4 > 0 )
182        {
183            Window.alert( "Length of switch s not a multiple of 4: " + s );
184            rescopeToDefault();
185            return;
186        }
187
188        final int digitLength = sLength / 4;
189        final StringBuilder buf = new StringBuilder(
190          /*initial capacity*/assumedNumericPrefix.length() + digitLength );
191        buf.append( assumedNumericPrefix );
192        int digitOffset = 0;
193        try
194        {
195            xMin = floatFrom( s, digitOffset, digitLength, buf ); digitOffset += digitLength;
196            xMax = floatFrom( s, digitOffset, digitLength, buf ); digitOffset += digitLength;
197            yMin = floatFrom( s, digitOffset, digitLength, buf ); digitOffset += digitLength;
198            yMax = floatFrom( s, digitOffset, digitLength, buf );
199        }
200        catch( NumberFormatException x )
201        {
202            Window.alert( "Unable to parse switch s=" + s + ": " + x.toString() );
203            rescopeToDefault();
204        }
205    }
206
207
208}