You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
504 lines
17 KiB
504 lines
17 KiB
/*
|
|
* Copyright (c) 1998, 2008, Oracle and/or its affiliates. All rights reserved.
|
|
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
package javax.swing.text.html;
|
|
|
|
import java.awt.Polygon;
|
|
import java.io.Serializable;
|
|
import java.util.StringTokenizer;
|
|
import java.util.Vector;
|
|
import javax.swing.text.AttributeSet;
|
|
|
|
/**
|
|
* Map is used to represent a map element that is part of an HTML document.
|
|
* Once a Map has been created, and any number of areas have been added,
|
|
* you can test if a point falls inside the map via the contains method.
|
|
*
|
|
* @author Scott Violet
|
|
*/
|
|
class Map implements Serializable {
|
|
/** Name of the Map. */
|
|
private String name;
|
|
/** An array of AttributeSets. */
|
|
private Vector<AttributeSet> areaAttributes;
|
|
/** An array of RegionContainments, will slowly grow to match the
|
|
* length of areaAttributes as needed. */
|
|
private Vector<RegionContainment> areas;
|
|
|
|
public Map() {
|
|
}
|
|
|
|
public Map(String name) {
|
|
this.name = name;
|
|
}
|
|
|
|
/**
|
|
* Returns the name of the Map.
|
|
*/
|
|
public String getName() {
|
|
return name;
|
|
}
|
|
|
|
/**
|
|
* Defines a region of the Map, based on the passed in AttributeSet.
|
|
*/
|
|
public void addArea(AttributeSet as) {
|
|
if (as == null) {
|
|
return;
|
|
}
|
|
if (areaAttributes == null) {
|
|
areaAttributes = new Vector<AttributeSet>(2);
|
|
}
|
|
areaAttributes.addElement(as.copyAttributes());
|
|
}
|
|
|
|
/**
|
|
* Removes the previously created area.
|
|
*/
|
|
public void removeArea(AttributeSet as) {
|
|
if (as != null && areaAttributes != null) {
|
|
int numAreas = (areas != null) ? areas.size() : 0;
|
|
for (int counter = areaAttributes.size() - 1; counter >= 0;
|
|
counter--) {
|
|
if (areaAttributes.elementAt(counter).isEqual(as)){
|
|
areaAttributes.removeElementAt(counter);
|
|
if (counter < numAreas) {
|
|
areas.removeElementAt(counter);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the AttributeSets representing the differet areas of the Map.
|
|
*/
|
|
public AttributeSet[] getAreas() {
|
|
int numAttributes = (areaAttributes != null) ? areaAttributes.size() :
|
|
0;
|
|
if (numAttributes != 0) {
|
|
AttributeSet[] retValue = new AttributeSet[numAttributes];
|
|
|
|
areaAttributes.copyInto(retValue);
|
|
return retValue;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Returns the AttributeSet that contains the passed in location,
|
|
* <code>x</code>, <code>y</code>. <code>width</code>, <code>height</code>
|
|
* gives the size of the region the map is defined over. If a matching
|
|
* area is found, the AttribueSet for it is returned.
|
|
*/
|
|
public AttributeSet getArea(int x, int y, int width, int height) {
|
|
int numAttributes = (areaAttributes != null) ?
|
|
areaAttributes.size() : 0;
|
|
|
|
if (numAttributes > 0) {
|
|
int numAreas = (areas != null) ? areas.size() : 0;
|
|
|
|
if (areas == null) {
|
|
areas = new Vector<RegionContainment>(numAttributes);
|
|
}
|
|
for (int counter = 0; counter < numAttributes; counter++) {
|
|
if (counter >= numAreas) {
|
|
areas.addElement(createRegionContainment
|
|
(areaAttributes.elementAt(counter)));
|
|
}
|
|
RegionContainment rc = areas.elementAt(counter);
|
|
if (rc != null && rc.contains(x, y, width, height)) {
|
|
return areaAttributes.elementAt(counter);
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Creates and returns an instance of RegionContainment that can be
|
|
* used to test if a particular point lies inside a region.
|
|
*/
|
|
protected RegionContainment createRegionContainment
|
|
(AttributeSet attributes) {
|
|
Object shape = attributes.getAttribute(HTML.Attribute.SHAPE);
|
|
|
|
if (shape == null) {
|
|
shape = "rect";
|
|
}
|
|
if (shape instanceof String) {
|
|
String shapeString = ((String)shape).toLowerCase();
|
|
RegionContainment rc = null;
|
|
|
|
try {
|
|
if (shapeString.equals("rect")) {
|
|
rc = new RectangleRegionContainment(attributes);
|
|
}
|
|
else if (shapeString.equals("circle")) {
|
|
rc = new CircleRegionContainment(attributes);
|
|
}
|
|
else if (shapeString.equals("poly")) {
|
|
rc = new PolygonRegionContainment(attributes);
|
|
}
|
|
else if (shapeString.equals("default")) {
|
|
rc = DefaultRegionContainment.sharedInstance();
|
|
}
|
|
} catch (RuntimeException re) {
|
|
// Something wrong with attributes.
|
|
rc = null;
|
|
}
|
|
return rc;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Creates and returns an array of integers from the String
|
|
* <code>stringCoords</code>. If one of the values represents a
|
|
* % the returned value with be negative. If a parse error results
|
|
* from trying to parse one of the numbers null is returned.
|
|
*/
|
|
static protected int[] extractCoords(Object stringCoords) {
|
|
if (stringCoords == null || !(stringCoords instanceof String)) {
|
|
return null;
|
|
}
|
|
|
|
StringTokenizer st = new StringTokenizer((String)stringCoords,
|
|
", \t\n\r");
|
|
int[] retValue = null;
|
|
int numCoords = 0;
|
|
|
|
while(st.hasMoreElements()) {
|
|
String token = st.nextToken();
|
|
int scale;
|
|
|
|
if (token.endsWith("%")) {
|
|
scale = -1;
|
|
token = token.substring(0, token.length() - 1);
|
|
}
|
|
else {
|
|
scale = 1;
|
|
}
|
|
try {
|
|
int intValue = Integer.parseInt(token);
|
|
|
|
if (retValue == null) {
|
|
retValue = new int[4];
|
|
}
|
|
else if(numCoords == retValue.length) {
|
|
int[] temp = new int[retValue.length * 2];
|
|
|
|
System.arraycopy(retValue, 0, temp, 0, retValue.length);
|
|
retValue = temp;
|
|
}
|
|
retValue[numCoords++] = intValue * scale;
|
|
} catch (NumberFormatException nfe) {
|
|
return null;
|
|
}
|
|
}
|
|
if (numCoords > 0 && numCoords != retValue.length) {
|
|
int[] temp = new int[numCoords];
|
|
|
|
System.arraycopy(retValue, 0, temp, 0, numCoords);
|
|
retValue = temp;
|
|
}
|
|
return retValue;
|
|
}
|
|
|
|
|
|
/**
|
|
* Defines the interface used for to check if a point is inside a
|
|
* region.
|
|
*/
|
|
interface RegionContainment {
|
|
/**
|
|
* Returns true if the location <code>x</code>, <code>y</code>
|
|
* falls inside the region defined in the receiver.
|
|
* <code>width</code>, <code>height</code> is the size of
|
|
* the enclosing region.
|
|
*/
|
|
public boolean contains(int x, int y, int width, int height);
|
|
}
|
|
|
|
|
|
/**
|
|
* Used to test for containment in a rectangular region.
|
|
*/
|
|
static class RectangleRegionContainment implements RegionContainment {
|
|
/** Will be non-null if one of the values is a percent, and any value
|
|
* that is non null indicates it is a percent
|
|
* (order is x, y, width, height). */
|
|
float[] percents;
|
|
/** Last value of width passed in. */
|
|
int lastWidth;
|
|
/** Last value of height passed in. */
|
|
int lastHeight;
|
|
/** Top left. */
|
|
int x0;
|
|
int y0;
|
|
/** Bottom right. */
|
|
int x1;
|
|
int y1;
|
|
|
|
public RectangleRegionContainment(AttributeSet as) {
|
|
int[] coords = Map.extractCoords(as.getAttribute(HTML.
|
|
Attribute.COORDS));
|
|
|
|
percents = null;
|
|
if (coords == null || coords.length != 4) {
|
|
throw new RuntimeException("Unable to parse rectangular area");
|
|
}
|
|
else {
|
|
x0 = coords[0];
|
|
y0 = coords[1];
|
|
x1 = coords[2];
|
|
y1 = coords[3];
|
|
if (x0 < 0 || y0 < 0 || x1 < 0 || y1 < 0) {
|
|
percents = new float[4];
|
|
lastWidth = lastHeight = -1;
|
|
for (int counter = 0; counter < 4; counter++) {
|
|
if (coords[counter] < 0) {
|
|
percents[counter] = Math.abs
|
|
(coords[counter]) / 100.0f;
|
|
}
|
|
else {
|
|
percents[counter] = -1.0f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public boolean contains(int x, int y, int width, int height) {
|
|
if (percents == null) {
|
|
return contains(x, y);
|
|
}
|
|
if (lastWidth != width || lastHeight != height) {
|
|
lastWidth = width;
|
|
lastHeight = height;
|
|
if (percents[0] != -1.0f) {
|
|
x0 = (int)(percents[0] * width);
|
|
}
|
|
if (percents[1] != -1.0f) {
|
|
y0 = (int)(percents[1] * height);
|
|
}
|
|
if (percents[2] != -1.0f) {
|
|
x1 = (int)(percents[2] * width);
|
|
}
|
|
if (percents[3] != -1.0f) {
|
|
y1 = (int)(percents[3] * height);
|
|
}
|
|
}
|
|
return contains(x, y);
|
|
}
|
|
|
|
public boolean contains(int x, int y) {
|
|
return ((x >= x0 && x <= x1) &&
|
|
(y >= y0 && y <= y1));
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Used to test for containment in a polygon region.
|
|
*/
|
|
static class PolygonRegionContainment extends Polygon implements
|
|
RegionContainment {
|
|
/** If any value is a percent there will be an entry here for the
|
|
* percent value. Use percentIndex to find out the index for it. */
|
|
float[] percentValues;
|
|
int[] percentIndexs;
|
|
/** Last value of width passed in. */
|
|
int lastWidth;
|
|
/** Last value of height passed in. */
|
|
int lastHeight;
|
|
|
|
public PolygonRegionContainment(AttributeSet as) {
|
|
int[] coords = Map.extractCoords(as.getAttribute(HTML.Attribute.
|
|
COORDS));
|
|
|
|
if (coords == null || coords.length == 0 ||
|
|
coords.length % 2 != 0) {
|
|
throw new RuntimeException("Unable to parse polygon area");
|
|
}
|
|
else {
|
|
int numPercents = 0;
|
|
|
|
lastWidth = lastHeight = -1;
|
|
for (int counter = coords.length - 1; counter >= 0;
|
|
counter--) {
|
|
if (coords[counter] < 0) {
|
|
numPercents++;
|
|
}
|
|
}
|
|
|
|
if (numPercents > 0) {
|
|
percentIndexs = new int[numPercents];
|
|
percentValues = new float[numPercents];
|
|
for (int counter = coords.length - 1, pCounter = 0;
|
|
counter >= 0; counter--) {
|
|
if (coords[counter] < 0) {
|
|
percentValues[pCounter] = coords[counter] /
|
|
-100.0f;
|
|
percentIndexs[pCounter] = counter;
|
|
pCounter++;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
percentIndexs = null;
|
|
percentValues = null;
|
|
}
|
|
npoints = coords.length / 2;
|
|
xpoints = new int[npoints];
|
|
ypoints = new int[npoints];
|
|
|
|
for (int counter = 0; counter < npoints; counter++) {
|
|
xpoints[counter] = coords[counter + counter];
|
|
ypoints[counter] = coords[counter + counter + 1];
|
|
}
|
|
}
|
|
}
|
|
|
|
public boolean contains(int x, int y, int width, int height) {
|
|
if (percentValues == null || (lastWidth == width &&
|
|
lastHeight == height)) {
|
|
return contains(x, y);
|
|
}
|
|
// Force the bounding box to be recalced.
|
|
bounds = null;
|
|
lastWidth = width;
|
|
lastHeight = height;
|
|
float fWidth = (float)width;
|
|
float fHeight = (float)height;
|
|
for (int counter = percentValues.length - 1; counter >= 0;
|
|
counter--) {
|
|
if (percentIndexs[counter] % 2 == 0) {
|
|
// x
|
|
xpoints[percentIndexs[counter] / 2] =
|
|
(int)(percentValues[counter] * fWidth);
|
|
}
|
|
else {
|
|
// y
|
|
ypoints[percentIndexs[counter] / 2] =
|
|
(int)(percentValues[counter] * fHeight);
|
|
}
|
|
}
|
|
return contains(x, y);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Used to test for containment in a circular region.
|
|
*/
|
|
static class CircleRegionContainment implements RegionContainment {
|
|
/** X origin of the circle. */
|
|
int x;
|
|
/** Y origin of the circle. */
|
|
int y;
|
|
/** Radius of the circle. */
|
|
int radiusSquared;
|
|
/** Non-null indicates one of the values represents a percent. */
|
|
float[] percentValues;
|
|
/** Last value of width passed in. */
|
|
int lastWidth;
|
|
/** Last value of height passed in. */
|
|
int lastHeight;
|
|
|
|
public CircleRegionContainment(AttributeSet as) {
|
|
int[] coords = Map.extractCoords(as.getAttribute(HTML.Attribute.
|
|
COORDS));
|
|
|
|
if (coords == null || coords.length != 3) {
|
|
throw new RuntimeException("Unable to parse circular area");
|
|
}
|
|
x = coords[0];
|
|
y = coords[1];
|
|
radiusSquared = coords[2] * coords[2];
|
|
if (coords[0] < 0 || coords[1] < 0 || coords[2] < 0) {
|
|
lastWidth = lastHeight = -1;
|
|
percentValues = new float[3];
|
|
for (int counter = 0; counter < 3; counter++) {
|
|
if (coords[counter] < 0) {
|
|
percentValues[counter] = coords[counter] /
|
|
-100.0f;
|
|
}
|
|
else {
|
|
percentValues[counter] = -1.0f;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
percentValues = null;
|
|
}
|
|
}
|
|
|
|
public boolean contains(int x, int y, int width, int height) {
|
|
if (percentValues != null && (lastWidth != width ||
|
|
lastHeight != height)) {
|
|
int newRad = Math.min(width, height) / 2;
|
|
|
|
lastWidth = width;
|
|
lastHeight = height;
|
|
if (percentValues[0] != -1.0f) {
|
|
this.x = (int)(percentValues[0] * width);
|
|
}
|
|
if (percentValues[1] != -1.0f) {
|
|
this.y = (int)(percentValues[1] * height);
|
|
}
|
|
if (percentValues[2] != -1.0f) {
|
|
radiusSquared = (int)(percentValues[2] *
|
|
Math.min(width, height));
|
|
radiusSquared *= radiusSquared;
|
|
}
|
|
}
|
|
return (((x - this.x) * (x - this.x) +
|
|
(y - this.y) * (y - this.y)) <= radiusSquared);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* An implementation that will return true if the x, y location is
|
|
* inside a rectangle defined by origin 0, 0, and width equal to
|
|
* width passed in, and height equal to height passed in.
|
|
*/
|
|
static class DefaultRegionContainment implements RegionContainment {
|
|
/** A global shared instance. */
|
|
static DefaultRegionContainment si = null;
|
|
|
|
public static DefaultRegionContainment sharedInstance() {
|
|
if (si == null) {
|
|
si = new DefaultRegionContainment();
|
|
}
|
|
return si;
|
|
}
|
|
|
|
public boolean contains(int x, int y, int width, int height) {
|
|
return (x <= width && x >= 0 && y >= 0 && y <= width);
|
|
}
|
|
}
|
|
}
|