1 /* 2 * Copyright (c) 1998, 2008, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package javax.swing.text.html; 26 27 import java.awt.Polygon; 28 import java.io.Serializable; 29 import java.util.StringTokenizer; 30 import java.util.Vector; 31 import javax.swing.text.AttributeSet; 32 33 /** 34 * Map is used to represent a map element that is part of an HTML document. 35 * Once a Map has been created, and any number of areas have been added, 36 * you can test if a point falls inside the map via the contains method. 37 * 38 * @author Scott Violet 39 */ 40 class Map implements Serializable { 41 /** Name of the Map. */ 42 private String name; 43 /** An array of AttributeSets. */ 44 private Vector<AttributeSet> areaAttributes; 45 /** An array of RegionContainments, will slowly grow to match the 46 * length of areaAttributes as needed. */ 47 private Vector<RegionContainment> areas; 48 49 public Map() { 50 } 51 52 public Map(String name) { 53 this.name = name; 54 } 55 56 /** 57 * Returns the name of the Map. 58 */ 59 public String getName() { 60 return name; 61 } 62 63 /** 64 * Defines a region of the Map, based on the passed in AttributeSet. 65 */ 66 public void addArea(AttributeSet as) { 67 if (as == null) { 68 return; 69 } 70 if (areaAttributes == null) { 71 areaAttributes = new Vector<AttributeSet>(2); 72 } 73 areaAttributes.addElement(as.copyAttributes()); 74 } 75 76 /** 77 * Removes the previously created area. 78 */ 79 public void removeArea(AttributeSet as) { 80 if (as != null && areaAttributes != null) { 81 int numAreas = (areas != null) ? areas.size() : 0; 82 for (int counter = areaAttributes.size() - 1; counter >= 0; 83 counter--) { 84 if (areaAttributes.elementAt(counter).isEqual(as)){ 85 areaAttributes.removeElementAt(counter); 86 if (counter < numAreas) { 87 areas.removeElementAt(counter); 88 } 89 } 90 } 91 } 92 } 93 94 /** 95 * Returns the AttributeSets representing the differet areas of the Map. 96 */ 97 public AttributeSet[] getAreas() { 98 int numAttributes = (areaAttributes != null) ? areaAttributes.size() : 99 0; 100 if (numAttributes != 0) { 101 AttributeSet[] retValue = new AttributeSet[numAttributes]; 102 103 areaAttributes.copyInto(retValue); 104 return retValue; 105 } 106 return null; 107 } 108 109 /** 110 * Returns the AttributeSet that contains the passed in location, 111 * <code>x</code>, <code>y</code>. <code>width</code>, <code>height</code> 112 * gives the size of the region the map is defined over. If a matching 113 * area is found, the AttribueSet for it is returned. 114 */ 115 public AttributeSet getArea(int x, int y, int width, int height) { 116 int numAttributes = (areaAttributes != null) ? 117 areaAttributes.size() : 0; 118 119 if (numAttributes > 0) { 120 int numAreas = (areas != null) ? areas.size() : 0; 121 122 if (areas == null) { 123 areas = new Vector<RegionContainment>(numAttributes); 124 } 125 for (int counter = 0; counter < numAttributes; counter++) { 126 if (counter >= numAreas) { 127 areas.addElement(createRegionContainment 128 (areaAttributes.elementAt(counter))); 129 } 130 RegionContainment rc = areas.elementAt(counter); 131 if (rc != null && rc.contains(x, y, width, height)) { 132 return areaAttributes.elementAt(counter); 133 } 134 } 135 } 136 return null; 137 } 138 139 /** 140 * Creates and returns an instance of RegionContainment that can be 141 * used to test if a particular point lies inside a region. 142 */ 143 protected RegionContainment createRegionContainment 144 (AttributeSet attributes) { 145 Object shape = attributes.getAttribute(HTML.Attribute.SHAPE); 146 147 if (shape == null) { 148 shape = "rect"; 149 } 150 if (shape instanceof String) { 151 String shapeString = ((String)shape).toLowerCase(); 152 RegionContainment rc = null; 153 154 try { 155 if (shapeString.equals("rect")) { 156 rc = new RectangleRegionContainment(attributes); 157 } 158 else if (shapeString.equals("circle")) { 159 rc = new CircleRegionContainment(attributes); 160 } 161 else if (shapeString.equals("poly")) { 162 rc = new PolygonRegionContainment(attributes); 163 } 164 else if (shapeString.equals("default")) { 165 rc = DefaultRegionContainment.sharedInstance(); 166 } 167 } catch (RuntimeException re) { 168 // Something wrong with attributes. 169 rc = null; 170 } 171 return rc; 172 } 173 return null; 174 } 175 176 /** 177 * Creates and returns an array of integers from the String 178 * <code>stringCoords</code>. If one of the values represents a 179 * % the returned value with be negative. If a parse error results 180 * from trying to parse one of the numbers null is returned. 181 */ 182 static protected int[] extractCoords(Object stringCoords) { 183 if (stringCoords == null || !(stringCoords instanceof String)) { 184 return null; 185 } 186 187 StringTokenizer st = new StringTokenizer((String)stringCoords, 188 ", \t\n\r"); 189 int[] retValue = null; 190 int numCoords = 0; 191 192 while(st.hasMoreElements()) { 193 String token = st.nextToken(); 194 int scale; 195 196 if (token.endsWith("%")) { 197 scale = -1; 198 token = token.substring(0, token.length() - 1); 199 } 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 } 209 else if(numCoords == retValue.length) { 210 int[] temp = new int[retValue.length * 2]; 211 212 System.arraycopy(retValue, 0, temp, 0, 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 /** 231 * Defines the interface used for to check if a point is inside a 232 * region. 233 */ 234 interface RegionContainment { 235 /** 236 * Returns true if the location <code>x</code>, <code>y</code> 237 * falls inside the region defined in the receiver. 238 * <code>width</code>, <code>height</code> is the size of 239 * the enclosing region. 240 */ 241 public boolean contains(int x, int y, int width, int height); 242 } 243 244 245 /** 246 * Used to test for containment in a rectangular region. 247 */ 248 static class RectangleRegionContainment implements RegionContainment { 249 /** Will be non-null if one of the values is a percent, and any value 250 * that is non null indicates it is a percent 251 * (order is x, y, width, height). */ 252 float[] percents; 253 /** Last value of width passed in. */ 254 int lastWidth; 255 /** Last value of height passed in. */ 256 int lastHeight; 257 /** Top left. */ 258 int x0; 259 int y0; 260 /** Bottom right. */ 261 int x1; 262 int y1; 263 264 public RectangleRegionContainment(AttributeSet as) { 265 int[] coords = Map.extractCoords(as.getAttribute(HTML. 266 Attribute.COORDS)); 267 268 percents = null; 269 if (coords == null || coords.length != 4) { 270 throw new RuntimeException("Unable to parse rectangular area"); 271 } 272 else { 273 x0 = coords[0]; 274 y0 = coords[1]; 275 x1 = coords[2]; 276 y1 = coords[3]; 277 if (x0 < 0 || y0 < 0 || x1 < 0 || y1 < 0) { 278 percents = new float[4]; 279 lastWidth = lastHeight = -1; 280 for (int counter = 0; counter < 4; counter++) { 281 if (coords[counter] < 0) { 282 percents[counter] = Math.abs 283 (coords[counter]) / 100.0f; 284 } 285 else { 286 percents[counter] = -1.0f; 287 } 288 } 289 } 290 } 291 } 292 293 public boolean contains(int x, int y, int width, int height) { 294 if (percents == null) { 295 return contains(x, y); 296 } 297 if (lastWidth != width || lastHeight != height) { 298 lastWidth = width; 299 lastHeight = height; 300 if (percents[0] != -1.0f) { 301 x0 = (int)(percents[0] * width); 302 } 303 if (percents[1] != -1.0f) { 304 y0 = (int)(percents[1] * height); 305 } 306 if (percents[2] != -1.0f) { 307 x1 = (int)(percents[2] * width); 308 } 309 if (percents[3] != -1.0f) { 310 y1 = (int)(percents[3] * height); 311 } 312 } 313 return contains(x, y); 314 } 315 316 public boolean contains(int x, int y) { 317 return ((x >= x0 && x <= x1) && 318 (y >= y0 && y <= y1)); 319 } 320 } 321 322 323 /** 324 * Used to test for containment in a polygon region. 325 */ 326 static class PolygonRegionContainment extends Polygon implements 327 RegionContainment { 328 /** If any value is a percent there will be an entry here for the 329 * percent value. Use percentIndex to find out the index for it. */ 330 float[] percentValues; 331 int[] percentIndexs; 332 /** Last value of width passed in. */ 333 int lastWidth; 334 /** Last value of height passed in. */ 335 int lastHeight; 336 337 public PolygonRegionContainment(AttributeSet as) { 338 int[] coords = Map.extractCoords(as.getAttribute(HTML.Attribute. 339 COORDS)); 340 341 if (coords == null || coords.length == 0 || 342 coords.length % 2 != 0) { 343 throw new RuntimeException("Unable to parse polygon area"); 344 } 345 else { 346 int numPercents = 0; 347 348 lastWidth = lastHeight = -1; 349 for (int counter = coords.length - 1; counter >= 0; 350 counter--) { 351 if (coords[counter] < 0) { 352 numPercents++; 353 } 354 } 355 356 if (numPercents > 0) { 357 percentIndexs = new int[numPercents]; 358 percentValues = new float[numPercents]; 359 for (int counter = coords.length - 1, pCounter = 0; 360 counter >= 0; counter--) { 361 if (coords[counter] < 0) { 362 percentValues[pCounter] = coords[counter] / 363 -100.0f; 364 percentIndexs[pCounter] = counter; 365 pCounter++; 366 } 367 } 368 } 369 else { 370 percentIndexs = null; 371 percentValues = null; 372 } 373 npoints = coords.length / 2; 374 xpoints = new int[npoints]; 375 ypoints = new int[npoints]; 376 377 for (int counter = 0; counter < npoints; counter++) { 378 xpoints[counter] = coords[counter + counter]; 379 ypoints[counter] = coords[counter + counter + 1]; 380 } 381 } 382 } 383 384 public boolean contains(int x, int y, int width, int height) { 385 if (percentValues == null || (lastWidth == width && 386 lastHeight == height)) { 387 return contains(x, y); 388 } 389 // Force the bounding box to be recalced. 390 bounds = null; 391 lastWidth = width; 392 lastHeight = height; 393 float fWidth = (float)width; 394 float fHeight = (float)height; 395 for (int counter = percentValues.length - 1; counter >= 0; 396 counter--) { 397 if (percentIndexs[counter] % 2 == 0) { 398 // x 399 xpoints[percentIndexs[counter] / 2] = 400 (int)(percentValues[counter] * fWidth); 401 } 402 else { 403 // y 404 ypoints[percentIndexs[counter] / 2] = 405 (int)(percentValues[counter] * fHeight); 406 } 407 } 408 return contains(x, y); 409 } 410 } 411 412 413 /** 414 * Used to test for containment in a circular region. 415 */ 416 static class CircleRegionContainment implements RegionContainment { 417 /** X origin of the circle. */ 418 int x; 419 /** Y origin of the circle. */ 420 int y; 421 /** Radius of the circle. */ 422 int radiusSquared; 423 /** Non-null indicates one of the values represents a percent. */ 424 float[] percentValues; 425 /** Last value of width passed in. */ 426 int lastWidth; 427 /** Last value of height passed in. */ 428 int lastHeight; 429 430 public CircleRegionContainment(AttributeSet as) { 431 int[] coords = Map.extractCoords(as.getAttribute(HTML.Attribute. 432 COORDS)); 433 434 if (coords == null || coords.length != 3) { 435 throw new RuntimeException("Unable to parse circular area"); 436 } 437 x = coords[0]; 438 y = coords[1]; 439 radiusSquared = coords[2] * coords[2]; 440 if (coords[0] < 0 || coords[1] < 0 || coords[2] < 0) { 441 lastWidth = lastHeight = -1; 442 percentValues = new float[3]; 443 for (int counter = 0; counter < 3; counter++) { 444 if (coords[counter] < 0) { 445 percentValues[counter] = coords[counter] / 446 -100.0f; 447 } 448 else { 449 percentValues[counter] = -1.0f; 450 } 451 } 452 } 453 else { 454 percentValues = null; 455 } 456 } 457 458 public boolean contains(int x, int y, int width, int height) { 459 if (percentValues != null && (lastWidth != width || 460 lastHeight != height)) { 461 int newRad = Math.min(width, height) / 2; 462 463 lastWidth = width; 464 lastHeight = height; 465 if (percentValues[0] != -1.0f) { 466 this.x = (int)(percentValues[0] * width); 467 } 468 if (percentValues[1] != -1.0f) { 469 this.y = (int)(percentValues[1] * height); 470 } 471 if (percentValues[2] != -1.0f) { 472 radiusSquared = (int)(percentValues[2] * 473 Math.min(width, height)); 474 radiusSquared *= radiusSquared; 475 } 476 } 477 return (((x - this.x) * (x - this.x) + 478 (y - this.y) * (y - this.y)) <= radiusSquared); 479 } 480 } 481 482 483 /** 484 * An implementation that will return true if the x, y location is 485 * inside a rectangle defined by origin 0, 0, and width equal to 486 * width passed in, and height equal to height passed in. 487 */ 488 static class DefaultRegionContainment implements RegionContainment { 489 /** A global shared instance. */ 490 static DefaultRegionContainment si = null; 491 492 public static DefaultRegionContainment sharedInstance() { 493 if (si == null) { 494 si = new DefaultRegionContainment(); 495 } 496 return si; 497 } 498 499 public boolean contains(int x, int y, int width, int height) { 500 return (x <= width && x >= 0 && y >= 0 && y <= width); 501 } 502 } 503 }