1. /*
  2. * @(#)Map.java 1.3 01/11/29
  3. *
  4. * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing.text.html;
  8. import java.awt.Polygon;
  9. import java.awt.Rectangle;
  10. import java.util.StringTokenizer;
  11. import java.util.Vector;
  12. import javax.swing.text.AttributeSet;
  13. /**
  14. * Map is used to represent a map element that is part of an HTML document.
  15. * Once a Map has been created, and any number of areas have been added,
  16. * you can test if a point falls inside the map via the contains method.
  17. *
  18. * @author Scott Violet
  19. * @version 1.3 11/29/01
  20. */
  21. class Map {
  22. /** Name of the Map. */
  23. private String name;
  24. /** An array of AttributeSets. */
  25. private Vector areaAttributes;
  26. /** An array of RegionContainments, will slowly grow to match the
  27. * length of areaAttributes as needed. */
  28. private Vector areas;
  29. public Map() {
  30. }
  31. public Map(String name) {
  32. this.name = name;
  33. }
  34. /**
  35. * Returns the name of the Map.
  36. */
  37. public String getName() {
  38. return name;
  39. }
  40. /**
  41. * Defines a region of the Map, based on the passed in AttributeSet.
  42. */
  43. public void addArea(AttributeSet as) {
  44. if (as == null) {
  45. return;
  46. }
  47. if (areaAttributes == null) {
  48. areaAttributes = new Vector(2);
  49. }
  50. areaAttributes.addElement(as.copyAttributes());
  51. }
  52. /**
  53. * Removes the previously created area.
  54. */
  55. public void removeArea(AttributeSet as) {
  56. if (as != null && areaAttributes != null) {
  57. int numAreas = (areas != null) ? areas.size() : 0;
  58. for (int counter = areaAttributes.size() - 1; counter >= 0;
  59. counter--) {
  60. if (((AttributeSet)areaAttributes.elementAt(counter)).
  61. isEqual(as)){
  62. areaAttributes.removeElementAt(counter);
  63. if (counter < numAreas) {
  64. areas.removeElementAt(counter);
  65. }
  66. }
  67. }
  68. }
  69. }
  70. /**
  71. * Returns the AttributeSets representing the differet areas of the Map.
  72. */
  73. public AttributeSet[] getAreas() {
  74. int numAttributes = (areaAttributes != null) ? areaAttributes.size() :
  75. 0;
  76. if (numAttributes != 0) {
  77. AttributeSet[] retValue = new AttributeSet[numAttributes];
  78. areaAttributes.copyInto(retValue);
  79. return retValue;
  80. }
  81. return null;
  82. }
  83. /**
  84. * Returns the AttributeSet that contains the passed in location,
  85. * <code>x</code>, <code>y</code>. <code>width</code>, <code>height</code>
  86. * gives the size of the region the map is defined over. If a matching
  87. * area is found, the AttribueSet for it is returned.
  88. */
  89. public AttributeSet getArea(int x, int y, int width, int height) {
  90. int numAttributes = (areaAttributes != null) ?
  91. areaAttributes.size() : 0;
  92. if (numAttributes > 0) {
  93. int numAreas = (areas != null) ? areas.size() : 0;
  94. if (areas == null) {
  95. areas = new Vector(numAttributes);
  96. }
  97. for (int counter = 0; counter < numAttributes; counter++) {
  98. if (counter >= numAreas) {
  99. areas.addElement(createRegionContainment
  100. ((AttributeSet)areaAttributes.elementAt(counter)));
  101. }
  102. RegionContainment rc = (RegionContainment)areas.
  103. elementAt(counter);
  104. if (rc != null && rc.contains(x, y, width, height)) {
  105. return (AttributeSet)areaAttributes.elementAt(counter);
  106. }
  107. }
  108. }
  109. return null;
  110. }
  111. /**
  112. * Creates and returns an instance of RegionContainment that can be
  113. * used to test if a particular point lies inside a region.
  114. */
  115. protected RegionContainment createRegionContainment
  116. (AttributeSet attributes) {
  117. Object shape = attributes.getAttribute(HTML.Attribute.SHAPE);
  118. if (shape == null) {
  119. shape = "rect";
  120. }
  121. if (shape instanceof String) {
  122. String shapeString = ((String)shape).toLowerCase();
  123. RegionContainment rc = null;
  124. try {
  125. if (shapeString.equals("rect")) {
  126. rc = new RectangleRegionContainment(attributes);
  127. }
  128. else if (shapeString.equals("circle")) {
  129. rc = new CircleRegionContainment(attributes);
  130. }
  131. else if (shapeString.equals("poly")) {
  132. rc = new PolygonRegionContainment(attributes);
  133. }
  134. else if (shapeString.equals("default")) {
  135. rc = DefaultRegionContainment.sharedInstance();
  136. }
  137. } catch (RuntimeException re) {
  138. // Something wrong with attributes.
  139. rc = null;
  140. }
  141. return rc;
  142. }
  143. return null;
  144. }
  145. /**
  146. * Creates and returns an array of integers from the String
  147. * <code>stringCoords</code>. If one of the values represents a
  148. * % the returned value with be negative. If a parse error results
  149. * from trying to parse one of the numbers null is returned.
  150. */
  151. static protected int[] extractCoords(Object stringCoords) {
  152. if (stringCoords == null || !(stringCoords instanceof String)) {
  153. return null;
  154. }
  155. StringTokenizer st = new StringTokenizer((String)stringCoords,
  156. ", \t\n\r");
  157. int[] retValue = null;
  158. int numCoords = 0;
  159. while(st.hasMoreElements()) {
  160. String token = st.nextToken();
  161. int scale;
  162. if (token.endsWith("%")) {
  163. scale = -1;
  164. token = token.substring(0, token.length() - 1);
  165. }
  166. else {
  167. scale = 1;
  168. }
  169. try {
  170. int intValue = Integer.parseInt(token);
  171. if (retValue == null) {
  172. retValue = new int[4];
  173. }
  174. else if(numCoords == retValue.length) {
  175. int[] temp = new int[retValue.length * 2];
  176. System.arraycopy(retValue, 0, temp, 0, retValue.length);
  177. retValue = temp;
  178. }
  179. retValue[numCoords++] = intValue * scale;
  180. } catch (NumberFormatException nfe) {
  181. return null;
  182. }
  183. }
  184. if (numCoords > 0 && numCoords != retValue.length) {
  185. int[] temp = new int[numCoords];
  186. System.arraycopy(retValue, 0, temp, 0, numCoords);
  187. retValue = temp;
  188. }
  189. return retValue;
  190. }
  191. /**
  192. * Defines the interface used for to check if a point is inside a
  193. * region.
  194. */
  195. interface RegionContainment {
  196. /**
  197. * Returns true if the location <code>x</code>, <code>y</code>
  198. * falls inside the region defined in the receiver.
  199. * <code>width</code>, <code>height</code> is the size of
  200. * the enclosing region.
  201. */
  202. public boolean contains(int x, int y, int width, int height);
  203. }
  204. /**
  205. * Used to test for containment in a rectangular region.
  206. */
  207. static class RectangleRegionContainment extends Rectangle implements
  208. RegionContainment {
  209. /** Will be non-null if one of the values is a percent, and any value
  210. * that is non null indicates it is a percent
  211. * (order is x, y, width, height). */
  212. float[] percents;
  213. /** Last value of width passed in. */
  214. int lastWidth;
  215. /** Last value of height passed in. */
  216. int lastHeight;
  217. public RectangleRegionContainment(AttributeSet as) {
  218. int[] coords = Map.extractCoords(as.getAttribute(HTML.
  219. Attribute.COORDS));
  220. percents = null;
  221. if (coords == null || coords.length != 4) {
  222. throw new RuntimeException("Unable to parse rectangular area");
  223. }
  224. else {
  225. x = coords[0];
  226. y = coords[1];
  227. width = coords[2];
  228. height = coords[3];
  229. if (x < 0 || y < 0 || width < 0 || height < 0) {
  230. percents = new float[4];
  231. lastWidth = lastHeight = -1;
  232. for (int counter = 0; counter < 4; counter++) {
  233. if (coords[counter] < 0) {
  234. percents[counter] = Math.abs
  235. (coords[counter]) / 100.0f;
  236. }
  237. else {
  238. percents[counter] = -1.0f;
  239. }
  240. }
  241. }
  242. }
  243. }
  244. public boolean contains(int x, int y, int width, int height) {
  245. if (percents == null) {
  246. return contains(x, y);
  247. }
  248. if (lastWidth != width || lastHeight != height) {
  249. lastWidth = width;
  250. lastHeight = height;
  251. if (percents[0] != -1.0f) {
  252. this.x = (int)(percents[0] * width);
  253. }
  254. if (percents[1] != -1.0f) {
  255. this.y = (int)(percents[1] * height);
  256. }
  257. if (percents[2] != -1.0f) {
  258. this.width = (int)(percents[2] * width);
  259. }
  260. if (percents[3] != -1.0f) {
  261. this.height = (int)(percents[3] * height);
  262. }
  263. }
  264. return contains(x, y);
  265. }
  266. }
  267. /**
  268. * Used to test for containment in a polygon region.
  269. */
  270. static class PolygonRegionContainment extends Polygon implements
  271. RegionContainment {
  272. /** If any value is a percent there will be an entry here for the
  273. * percent value. Use percentIndex to find out the index for it. */
  274. float[] percentValues;
  275. int[] percentIndexs;
  276. /** Last value of width passed in. */
  277. int lastWidth;
  278. /** Last value of height passed in. */
  279. int lastHeight;
  280. public PolygonRegionContainment(AttributeSet as) {
  281. int[] coords = Map.extractCoords(as.getAttribute(HTML.Attribute.
  282. COORDS));
  283. if (coords == null || coords.length == 0 ||
  284. coords.length % 2 != 0) {
  285. throw new RuntimeException("Unable to parse polygon area");
  286. }
  287. else {
  288. int numPercents = 0;
  289. lastWidth = lastHeight = -1;
  290. for (int counter = coords.length - 1; counter >= 0;
  291. counter--) {
  292. if (coords[counter] < 0) {
  293. numPercents++;
  294. }
  295. }
  296. if (numPercents > 0) {
  297. percentIndexs = new int[numPercents];
  298. percentValues = new float[numPercents];
  299. for (int counter = coords.length - 1, pCounter = 0;
  300. counter >= 0; counter--) {
  301. if (coords[counter] < 0) {
  302. percentValues[pCounter] = coords[counter] /
  303. -100.0f;
  304. percentIndexs[pCounter] = counter;
  305. pCounter++;
  306. }
  307. }
  308. }
  309. else {
  310. percentIndexs = null;
  311. percentValues = null;
  312. }
  313. npoints = coords.length / 2;
  314. xpoints = new int[npoints];
  315. ypoints = new int[npoints];
  316. for (int counter = 0; counter < npoints; counter++) {
  317. xpoints[counter] = coords[counter + counter];
  318. ypoints[counter] = coords[counter + counter + 1];
  319. }
  320. }
  321. }
  322. public boolean contains(int x, int y, int width, int height) {
  323. if (percentValues == null || (lastWidth == width &&
  324. lastHeight == height)) {
  325. return contains(x, y);
  326. }
  327. // Force the bounding box to be recalced.
  328. bounds = null;
  329. lastWidth = width;
  330. lastHeight = height;
  331. float fWidth = (float)width;
  332. float fHeight = (float)height;
  333. for (int counter = percentValues.length - 1; counter >= 0;
  334. counter--) {
  335. if (percentIndexs[counter] % 2 == 0) {
  336. // x
  337. xpoints[percentIndexs[counter] / 2] =
  338. (int)(percentValues[counter] * fWidth);
  339. }
  340. else {
  341. // y
  342. ypoints[percentIndexs[counter] / 2] =
  343. (int)(percentValues[counter] * fHeight);
  344. }
  345. }
  346. return contains(x, y);
  347. }
  348. }
  349. /**
  350. * Used to test for containment in a circular region.
  351. */
  352. static class CircleRegionContainment implements RegionContainment {
  353. /** X origin of the circle. */
  354. int x;
  355. /** Y origin of the circle. */
  356. int y;
  357. /** Radius of the circle. */
  358. int radiusSquared;
  359. /** Non-null indicates one of the values represents a percent. */
  360. float[] percentValues;
  361. /** Last value of width passed in. */
  362. int lastWidth;
  363. /** Last value of height passed in. */
  364. int lastHeight;
  365. public CircleRegionContainment(AttributeSet as) {
  366. int[] coords = Map.extractCoords(as.getAttribute(HTML.Attribute.
  367. COORDS));
  368. if (coords == null || coords.length != 3) {
  369. throw new RuntimeException("Unable to parse circular area");
  370. }
  371. x = coords[0];
  372. y = coords[1];
  373. radiusSquared = coords[2] * coords[2];
  374. if (coords[0] < 0 || coords[1] < 0 || coords[2] < 0) {
  375. lastWidth = lastHeight = -1;
  376. percentValues = new float[3];
  377. for (int counter = 0; counter < 3; counter++) {
  378. if (coords[counter] < 0) {
  379. percentValues[counter] = coords[counter] /
  380. -100.0f;
  381. }
  382. else {
  383. percentValues[counter] = -1.0f;
  384. }
  385. }
  386. }
  387. else {
  388. percentValues = null;
  389. }
  390. }
  391. public boolean contains(int x, int y, int width, int height) {
  392. if (percentValues != null && (lastWidth != width ||
  393. lastHeight != height)) {
  394. int newRad = Math.min(width, height) / 2;
  395. lastWidth = width;
  396. lastHeight = height;
  397. if (percentValues[0] != -1.0f) {
  398. this.x = (int)(percentValues[0] * width);
  399. }
  400. if (percentValues[1] != -1.0f) {
  401. this.y = (int)(percentValues[1] * height);
  402. }
  403. if (percentValues[2] != -1.0f) {
  404. radiusSquared = (int)(percentValues[2] *
  405. Math.min(width, height));
  406. radiusSquared *= radiusSquared;
  407. }
  408. }
  409. return (((x - this.x) * (x - this.x) +
  410. (y - this.y) * (y - this.y)) <= radiusSquared);
  411. }
  412. }
  413. /**
  414. * An implementation that will return true if the x, y location is
  415. * inside a rectangle defined by origin 0, 0, and width equal to
  416. * width passed in, and height equal to height passed in.
  417. */
  418. static class DefaultRegionContainment implements RegionContainment {
  419. /** A global shared instance. */
  420. static DefaultRegionContainment si = null;
  421. public static DefaultRegionContainment sharedInstance() {
  422. if (si == null) {
  423. si = new DefaultRegionContainment();
  424. }
  425. return si;
  426. }
  427. public boolean contains(int x, int y, int width, int height) {
  428. return (x <= width && x >= 0 && y >= 0 && y <= width);
  429. }
  430. }
  431. }