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