001 /*
002 * Copyright 1998-2006 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025 package javax.swing.text.html;
026
027 import java.awt.Polygon;
028 import java.io.Serializable;
029 import java.util.StringTokenizer;
030 import java.util.Vector;
031 import javax.swing.text.AttributeSet;
032
033 /**
034 * Map is used to represent a map element that is part of an HTML document.
035 * Once a Map has been created, and any number of areas have been added,
036 * you can test if a point falls inside the map via the contains method.
037 *
038 * @author Scott Violet
039 * @version 1.16 05/05/07
040 */
041 class Map implements Serializable {
042 /** Name of the Map. */
043 private String name;
044 /** An array of AttributeSets. */
045 private Vector areaAttributes;
046 /** An array of RegionContainments, will slowly grow to match the
047 * length of areaAttributes as needed. */
048 private Vector areas;
049
050 public Map() {
051 }
052
053 public Map(String name) {
054 this .name = name;
055 }
056
057 /**
058 * Returns the name of the Map.
059 */
060 public String getName() {
061 return name;
062 }
063
064 /**
065 * Defines a region of the Map, based on the passed in AttributeSet.
066 */
067 public void addArea(AttributeSet as) {
068 if (as == null) {
069 return;
070 }
071 if (areaAttributes == null) {
072 areaAttributes = new Vector(2);
073 }
074 areaAttributes.addElement(as.copyAttributes());
075 }
076
077 /**
078 * Removes the previously created area.
079 */
080 public void removeArea(AttributeSet as) {
081 if (as != null && areaAttributes != null) {
082 int numAreas = (areas != null) ? areas.size() : 0;
083 for (int counter = areaAttributes.size() - 1; counter >= 0; counter--) {
084 if (((AttributeSet) areaAttributes.elementAt(counter))
085 .isEqual(as)) {
086 areaAttributes.removeElementAt(counter);
087 if (counter < numAreas) {
088 areas.removeElementAt(counter);
089 }
090 }
091 }
092 }
093 }
094
095 /**
096 * Returns the AttributeSets representing the differet areas of the Map.
097 */
098 public AttributeSet[] getAreas() {
099 int numAttributes = (areaAttributes != null) ? areaAttributes
100 .size() : 0;
101 if (numAttributes != 0) {
102 AttributeSet[] retValue = new AttributeSet[numAttributes];
103
104 areaAttributes.copyInto(retValue);
105 return retValue;
106 }
107 return null;
108 }
109
110 /**
111 * Returns the AttributeSet that contains the passed in location,
112 * <code>x</code>, <code>y</code>. <code>width</code>, <code>height</code>
113 * gives the size of the region the map is defined over. If a matching
114 * area is found, the AttribueSet for it is returned.
115 */
116 public AttributeSet getArea(int x, int y, int width, int height) {
117 int numAttributes = (areaAttributes != null) ? areaAttributes
118 .size() : 0;
119
120 if (numAttributes > 0) {
121 int numAreas = (areas != null) ? areas.size() : 0;
122
123 if (areas == null) {
124 areas = new Vector(numAttributes);
125 }
126 for (int counter = 0; counter < numAttributes; counter++) {
127 if (counter >= numAreas) {
128 areas
129 .addElement(createRegionContainment((AttributeSet) areaAttributes
130 .elementAt(counter)));
131 }
132 RegionContainment rc = (RegionContainment) areas
133 .elementAt(counter);
134 if (rc != null && rc.contains(x, y, width, height)) {
135 return (AttributeSet) areaAttributes
136 .elementAt(counter);
137 }
138 }
139 }
140 return null;
141 }
142
143 /**
144 * Creates and returns an instance of RegionContainment that can be
145 * used to test if a particular point lies inside a region.
146 */
147 protected RegionContainment createRegionContainment(
148 AttributeSet attributes) {
149 Object shape = attributes.getAttribute(HTML.Attribute.SHAPE);
150
151 if (shape == null) {
152 shape = "rect";
153 }
154 if (shape instanceof String) {
155 String shapeString = ((String) shape).toLowerCase();
156 RegionContainment rc = null;
157
158 try {
159 if (shapeString.equals("rect")) {
160 rc = new RectangleRegionContainment(attributes);
161 } else if (shapeString.equals("circle")) {
162 rc = new CircleRegionContainment(attributes);
163 } else if (shapeString.equals("poly")) {
164 rc = new PolygonRegionContainment(attributes);
165 } else if (shapeString.equals("default")) {
166 rc = DefaultRegionContainment.sharedInstance();
167 }
168 } catch (RuntimeException re) {
169 // Something wrong with attributes.
170 rc = null;
171 }
172 return rc;
173 }
174 return null;
175 }
176
177 /**
178 * Creates and returns an array of integers from the String
179 * <code>stringCoords</code>. If one of the values represents a
180 * % the returned value with be negative. If a parse error results
181 * from trying to parse one of the numbers null is returned.
182 */
183 static protected int[] extractCoords(Object stringCoords) {
184 if (stringCoords == null || !(stringCoords instanceof String)) {
185 return null;
186 }
187
188 StringTokenizer st = new StringTokenizer((String) stringCoords,
189 ", \t\n\r");
190 int[] retValue = null;
191 int numCoords = 0;
192
193 while (st.hasMoreElements()) {
194 String token = st.nextToken();
195 int scale;
196
197 if (token.endsWith("%")) {
198 scale = -1;
199 token = token.substring(0, token.length() - 1);
200 } else {
201 scale = 1;
202 }
203 try {
204 int intValue = Integer.parseInt(token);
205
206 if (retValue == null) {
207 retValue = new int[4];
208 } else if (numCoords == retValue.length) {
209 int[] temp = new int[retValue.length * 2];
210
211 System.arraycopy(retValue, 0, temp, 0,
212 retValue.length);
213 retValue = temp;
214 }
215 retValue[numCoords++] = intValue * scale;
216 } catch (NumberFormatException nfe) {
217 return null;
218 }
219 }
220 if (numCoords > 0 && numCoords != retValue.length) {
221 int[] temp = new int[numCoords];
222
223 System.arraycopy(retValue, 0, temp, 0, numCoords);
224 retValue = temp;
225 }
226 return retValue;
227 }
228
229 /**
230 * Defines the interface used for to check if a point is inside a
231 * region.
232 */
233 interface RegionContainment {
234 /**
235 * Returns true if the location <code>x</code>, <code>y</code>
236 * falls inside the region defined in the receiver.
237 * <code>width</code>, <code>height</code> is the size of
238 * the enclosing region.
239 */
240 public boolean contains(int x, int y, int width, int height);
241 }
242
243 /**
244 * Used to test for containment in a rectangular region.
245 */
246 static class RectangleRegionContainment implements
247 RegionContainment {
248 /** Will be non-null if one of the values is a percent, and any value
249 * that is non null indicates it is a percent
250 * (order is x, y, width, height). */
251 float[] percents;
252 /** Last value of width passed in. */
253 int lastWidth;
254 /** Last value of height passed in. */
255 int lastHeight;
256 /** Top left. */
257 int x0;
258 int y0;
259 /** Bottom right. */
260 int x1;
261 int y1;
262
263 public RectangleRegionContainment(AttributeSet as) {
264 int[] coords = Map.extractCoords(as
265 .getAttribute(HTML.Attribute.COORDS));
266
267 percents = null;
268 if (coords == null || coords.length != 4) {
269 throw new RuntimeException(
270 "Unable to parse rectangular area");
271 } else {
272 x0 = coords[0];
273 y0 = coords[1];
274 x1 = coords[2];
275 y1 = coords[3];
276 if (x0 < 0 || y0 < 0 || x1 < 0 || y1 < 0) {
277 percents = new float[4];
278 lastWidth = lastHeight = -1;
279 for (int counter = 0; counter < 4; counter++) {
280 if (coords[counter] < 0) {
281 percents[counter] = Math
282 .abs(coords[counter]) / 100.0f;
283 } else {
284 percents[counter] = -1.0f;
285 }
286 }
287 }
288 }
289 }
290
291 public boolean contains(int x, int y, int width, int height) {
292 if (percents == null) {
293 return contains(x, y);
294 }
295 if (lastWidth != width || lastHeight != height) {
296 lastWidth = width;
297 lastHeight = height;
298 if (percents[0] != -1.0f) {
299 x0 = (int) (percents[0] * width);
300 }
301 if (percents[1] != -1.0f) {
302 y0 = (int) (percents[1] * height);
303 }
304 if (percents[2] != -1.0f) {
305 x1 = (int) (percents[2] * width);
306 }
307 if (percents[3] != -1.0f) {
308 y1 = (int) (percents[3] * height);
309 }
310 }
311 return contains(x, y);
312 }
313
314 public boolean contains(int x, int y) {
315 return ((x >= x0 && x <= x1) && (y >= y0 && y <= y1));
316 }
317 }
318
319 /**
320 * Used to test for containment in a polygon region.
321 */
322 static class PolygonRegionContainment extends Polygon implements
323 RegionContainment {
324 /** If any value is a percent there will be an entry here for the
325 * percent value. Use percentIndex to find out the index for it. */
326 float[] percentValues;
327 int[] percentIndexs;
328 /** Last value of width passed in. */
329 int lastWidth;
330 /** Last value of height passed in. */
331 int lastHeight;
332
333 public PolygonRegionContainment(AttributeSet as) {
334 int[] coords = Map.extractCoords(as
335 .getAttribute(HTML.Attribute.COORDS));
336
337 if (coords == null || coords.length == 0
338 || coords.length % 2 != 0) {
339 throw new RuntimeException(
340 "Unable to parse polygon area");
341 } else {
342 int numPercents = 0;
343
344 lastWidth = lastHeight = -1;
345 for (int counter = coords.length - 1; counter >= 0; counter--) {
346 if (coords[counter] < 0) {
347 numPercents++;
348 }
349 }
350
351 if (numPercents > 0) {
352 percentIndexs = new int[numPercents];
353 percentValues = new float[numPercents];
354 for (int counter = coords.length - 1, pCounter = 0; counter >= 0; counter--) {
355 if (coords[counter] < 0) {
356 percentValues[pCounter] = coords[counter]
357 / -100.0f;
358 percentIndexs[pCounter] = counter;
359 pCounter++;
360 }
361 }
362 } else {
363 percentIndexs = null;
364 percentValues = null;
365 }
366 npoints = coords.length / 2;
367 xpoints = new int[npoints];
368 ypoints = new int[npoints];
369
370 for (int counter = 0; counter < npoints; counter++) {
371 xpoints[counter] = coords[counter + counter];
372 ypoints[counter] = coords[counter + counter + 1];
373 }
374 }
375 }
376
377 public boolean contains(int x, int y, int width, int height) {
378 if (percentValues == null
379 || (lastWidth == width && lastHeight == height)) {
380 return contains(x, y);
381 }
382 // Force the bounding box to be recalced.
383 bounds = null;
384 lastWidth = width;
385 lastHeight = height;
386 float fWidth = (float) width;
387 float fHeight = (float) height;
388 for (int counter = percentValues.length - 1; counter >= 0; counter--) {
389 if (percentIndexs[counter] % 2 == 0) {
390 // x
391 xpoints[percentIndexs[counter] / 2] = (int) (percentValues[counter] * fWidth);
392 } else {
393 // y
394 ypoints[percentIndexs[counter] / 2] = (int) (percentValues[counter] * fHeight);
395 }
396 }
397 return contains(x, y);
398 }
399 }
400
401 /**
402 * Used to test for containment in a circular region.
403 */
404 static class CircleRegionContainment implements RegionContainment {
405 /** X origin of the circle. */
406 int x;
407 /** Y origin of the circle. */
408 int y;
409 /** Radius of the circle. */
410 int radiusSquared;
411 /** Non-null indicates one of the values represents a percent. */
412 float[] percentValues;
413 /** Last value of width passed in. */
414 int lastWidth;
415 /** Last value of height passed in. */
416 int lastHeight;
417
418 public CircleRegionContainment(AttributeSet as) {
419 int[] coords = Map.extractCoords(as
420 .getAttribute(HTML.Attribute.COORDS));
421
422 if (coords == null || coords.length != 3) {
423 throw new RuntimeException(
424 "Unable to parse circular area");
425 }
426 x = coords[0];
427 y = coords[1];
428 radiusSquared = coords[2] * coords[2];
429 if (coords[0] < 0 || coords[1] < 0 || coords[2] < 0) {
430 lastWidth = lastHeight = -1;
431 percentValues = new float[3];
432 for (int counter = 0; counter < 3; counter++) {
433 if (coords[counter] < 0) {
434 percentValues[counter] = coords[counter]
435 / -100.0f;
436 } else {
437 percentValues[counter] = -1.0f;
438 }
439 }
440 } else {
441 percentValues = null;
442 }
443 }
444
445 public boolean contains(int x, int y, int width, int height) {
446 if (percentValues != null
447 && (lastWidth != width || lastHeight != height)) {
448 int newRad = Math.min(width, height) / 2;
449
450 lastWidth = width;
451 lastHeight = height;
452 if (percentValues[0] != -1.0f) {
453 this .x = (int) (percentValues[0] * width);
454 }
455 if (percentValues[1] != -1.0f) {
456 this .y = (int) (percentValues[1] * height);
457 }
458 if (percentValues[2] != -1.0f) {
459 radiusSquared = (int) (percentValues[2] * Math.min(
460 width, height));
461 radiusSquared *= radiusSquared;
462 }
463 }
464 return (((x - this .x) * (x - this .x) + (y - this .y)
465 * (y - this .y)) <= radiusSquared);
466 }
467 }
468
469 /**
470 * An implementation that will return true if the x, y location is
471 * inside a rectangle defined by origin 0, 0, and width equal to
472 * width passed in, and height equal to height passed in.
473 */
474 static class DefaultRegionContainment implements RegionContainment {
475 /** A global shared instance. */
476 static DefaultRegionContainment si = null;
477
478 public static DefaultRegionContainment sharedInstance() {
479 if (si == null) {
480 si = new DefaultRegionContainment();
481 }
482 return si;
483 }
484
485 public boolean contains(int x, int y, int width, int height) {
486 return (x <= width && x >= 0 && y >= 0 && y <= width);
487 }
488 }
489 }
|