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}