1 |
/* Copyright (C) 2000, 2002 Free Software Foundation |
/* RectangularShape.java -- a rectangular frame for several generic shapes |
2 |
|
Copyright (C) 2000, 2002 Free Software Foundation |
3 |
|
|
4 |
This file is part of GNU Classpath. |
This file is part of GNU Classpath. |
5 |
|
|
35 |
obligated to do so. If you do not wish to do so, delete this |
obligated to do so. If you do not wish to do so, delete this |
36 |
exception statement from your version. */ |
exception statement from your version. */ |
37 |
|
|
38 |
|
|
39 |
package java.awt.geom; |
package java.awt.geom; |
40 |
import java.awt.*; |
|
41 |
import java.awt.geom.Rectangle2D; |
import java.awt.Rectangle; |
42 |
|
import java.awt.Shape; |
43 |
|
|
44 |
/** |
/** |
45 |
|
* This class provides a generic framework, and several helper methods, for |
46 |
|
* subclasses which represent geometric objects inside a rectangular frame. |
47 |
|
* This does not specify any geometry except for the bounding box. |
48 |
|
* |
49 |
* @author Tom Tromey <tromey@cygnus.com> |
* @author Tom Tromey <tromey@cygnus.com> |
50 |
* @date April 16, 2000 |
* @author Eric Blake <ebb9@email.byu.edu> |
51 |
|
* @since 1.2 |
52 |
|
* @see Arc2D |
53 |
|
* @see Ellipse2D |
54 |
|
* @see Rectangle2D |
55 |
|
* @see RoundRectangle2D |
56 |
|
* @status updated to 1.4 |
57 |
*/ |
*/ |
|
|
|
58 |
public abstract class RectangularShape implements Shape, Cloneable |
public abstract class RectangularShape implements Shape, Cloneable |
59 |
{ |
{ |
60 |
protected RectangularShape () |
/** |
61 |
{ |
* Default constructor. |
62 |
} |
*/ |
63 |
|
protected RectangularShape() |
64 |
public abstract double getX (); |
{ |
65 |
public abstract double getY (); |
} |
66 |
public abstract double getWidth (); |
|
67 |
public abstract double getHeight (); |
/** |
68 |
|
* Get the x coordinate of the upper-left corner of the framing rectangle. |
69 |
public double getMinX () |
* |
70 |
{ |
* @return the x coordinate |
71 |
return Math.min (getX (), getX () + getWidth ()); |
*/ |
72 |
} |
public abstract double getX(); |
73 |
|
|
74 |
public double getMinY () |
/** |
75 |
{ |
* Get the y coordinate of the upper-left corner of the framing rectangle. |
76 |
return Math.min (getY (), getY () + getHeight ()); |
* |
77 |
} |
* @return the y coordinate |
78 |
|
*/ |
79 |
public double getMaxX () |
public abstract double getY(); |
80 |
{ |
|
81 |
return Math.max (getX (), getX () + getWidth ()); |
/** |
82 |
} |
* Get the width of the framing rectangle. |
83 |
|
* |
84 |
public double getMaxY () |
* @return the width |
85 |
{ |
*/ |
86 |
return Math.max (getY (), getY () + getHeight ()); |
public abstract double getWidth(); |
87 |
} |
|
88 |
|
/** |
89 |
public double getCenterX () |
* Get the height of the framing rectangle. |
90 |
{ |
* |
91 |
return getX () + getWidth () / 2; |
* @return the height |
92 |
} |
*/ |
93 |
|
public abstract double getHeight(); |
94 |
public double getCenterY () |
|
95 |
{ |
/** |
96 |
return getY () + getHeight () / 2; |
* Get the minimum x coordinate in the frame. This is misnamed, or else |
97 |
} |
* Sun has a bug, because the implementation returns getX() even when |
98 |
|
* getWidth() is negative. |
99 |
public Rectangle2D getFrame () |
* |
100 |
{ |
* @return the minimum x coordinate |
101 |
return new Rectangle2D.Double (getX (), getY (), |
*/ |
102 |
getWidth (), getHeight ()); |
public double getMinX() |
103 |
} |
{ |
104 |
|
return getX(); |
105 |
public abstract boolean isEmpty (); |
} |
106 |
public abstract void setFrame (double x, double y, double w, double h); |
|
107 |
|
/** |
108 |
public void setFrame (Point2D loc, Dimension2D size) |
* Get the minimum y coordinate in the frame. This is misnamed, or else |
109 |
{ |
* Sun has a bug, because the implementation returns getY() even when |
110 |
setFrame (loc.getX (), loc.getY (), size.getWidth (), size.getHeight ()); |
* getHeight() is negative. |
111 |
} |
* |
112 |
|
* @return the minimum y coordinate |
113 |
public void setFrame (Rectangle2D r) |
*/ |
114 |
{ |
public double getMinY() |
115 |
setFrame (r.getX (), r.getY (), r.getWidth (), r.getHeight ()); |
{ |
116 |
} |
return getY(); |
117 |
|
} |
118 |
public void setFrameFromDiagonal (double x1, double y1, |
|
119 |
double x2, double y2) |
/** |
120 |
|
* Get the maximum x coordinate in the frame. This is misnamed, or else |
121 |
|
* Sun has a bug, because the implementation returns getX()+getWidth() even |
122 |
|
* when getWidth() is negative. |
123 |
|
* |
124 |
|
* @return the maximum x coordinate |
125 |
|
*/ |
126 |
|
public double getMaxX() |
127 |
|
{ |
128 |
|
return getX() + getWidth(); |
129 |
|
} |
130 |
|
|
131 |
|
/** |
132 |
|
* Get the maximum y coordinate in the frame. This is misnamed, or else |
133 |
|
* Sun has a bug, because the implementation returns getY()+getHeight() even |
134 |
|
* when getHeight() is negative. |
135 |
|
* |
136 |
|
* @return the maximum y coordinate |
137 |
|
*/ |
138 |
|
public double getMaxY() |
139 |
|
{ |
140 |
|
return getY() + getHeight(); |
141 |
|
} |
142 |
|
|
143 |
|
/** |
144 |
|
* Return the x coordinate of the center point of the framing rectangle. |
145 |
|
* |
146 |
|
* @return the central x coordinate |
147 |
|
*/ |
148 |
|
public double getCenterX() |
149 |
|
{ |
150 |
|
return getX() + getWidth() / 2; |
151 |
|
} |
152 |
|
|
153 |
|
/** |
154 |
|
* Return the y coordinate of the center point of the framing rectangle. |
155 |
|
* |
156 |
|
* @return the central y coordinate |
157 |
|
*/ |
158 |
|
public double getCenterY() |
159 |
|
{ |
160 |
|
return getY() + getHeight() / 2; |
161 |
|
} |
162 |
|
|
163 |
|
/** |
164 |
|
* Return the frame around this object. Note that this may be a looser |
165 |
|
* bounding box than getBounds2D. |
166 |
|
* |
167 |
|
* @return the frame, in double precision |
168 |
|
* @see #setFrame(double, double, double, double) |
169 |
|
*/ |
170 |
|
public Rectangle2D getFrame() |
171 |
|
{ |
172 |
|
return new Rectangle2D.Double(getX(), getY(), getWidth(), getHeight()); |
173 |
|
} |
174 |
|
|
175 |
|
/** |
176 |
|
* Test if the shape is empty, meaning that no points are inside it. |
177 |
|
* |
178 |
|
* @return true if the shape is empty |
179 |
|
*/ |
180 |
|
public abstract boolean isEmpty(); |
181 |
|
|
182 |
|
/** |
183 |
|
* Set the framing rectangle of this shape to the given coordinate and size. |
184 |
|
* |
185 |
|
* @param x the new x coordinate |
186 |
|
* @param y the new y coordinate |
187 |
|
* @param w the new width |
188 |
|
* @param h the new height |
189 |
|
* @see #getFrame() |
190 |
|
*/ |
191 |
|
public abstract void setFrame(double x, double y, double w, double h); |
192 |
|
|
193 |
|
/** |
194 |
|
* Set the framing rectangle of this shape to the given coordinate and size. |
195 |
|
* |
196 |
|
* @param p the new point |
197 |
|
* @param d the new dimension |
198 |
|
* @throws NullPointerException if p or d is null |
199 |
|
* @see #getFrame() |
200 |
|
*/ |
201 |
|
public void setFrame(Point2D p, Dimension2D d) |
202 |
|
{ |
203 |
|
setFrame(p.getX(), p.getY(), d.getWidth(), d.getHeight()); |
204 |
|
} |
205 |
|
|
206 |
|
/** |
207 |
|
* Set the framing rectangle of this shape to the given rectangle. |
208 |
|
* |
209 |
|
* @param r the new framing rectangle |
210 |
|
* @throws NullPointerException if r is null |
211 |
|
* @see #getFrame() |
212 |
|
*/ |
213 |
|
public void setFrame(Rectangle2D r) |
214 |
|
{ |
215 |
|
setFrame(r.getX(), r.getY(), r.getWidth(), r.getHeight()); |
216 |
|
} |
217 |
|
|
218 |
|
/** |
219 |
|
* Set the framing rectangle of this shape using two points on a diagonal. |
220 |
|
* The area will be positive. |
221 |
|
* |
222 |
|
* @param x1 the first x coordinate |
223 |
|
* @param y1 the first y coordinate |
224 |
|
* @param x2 the second x coordinate |
225 |
|
* @param y2 the second y coordinate |
226 |
|
*/ |
227 |
|
public void setFrameFromDiagonal(double x1, double y1, double x2, double y2) |
228 |
{ |
{ |
229 |
if (x1 > x2) |
if (x1 > x2) |
230 |
{ |
{ |
238 |
y2 = y1; |
y2 = y1; |
239 |
y1 = t; |
y1 = t; |
240 |
} |
} |
241 |
setFrame (x1, y1, x2 - x1, y2 - y1); |
setFrame(x1, y1, x2 - x1, y2 - y1); |
|
} |
|
|
|
|
|
public void setFrameFromDiagonal (Point2D p1, Point2D p2) |
|
|
{ |
|
|
setFrameFromDiagonal (p1.getX (), p1.getY (), |
|
|
p2.getX (), p2.getY ()); |
|
|
} |
|
|
|
|
|
public void setFrameFromCenter (double centerX, double centerY, |
|
|
double cornerX, double cornerY) |
|
|
{ |
|
|
double halfw = Math.abs (cornerX - centerX); |
|
|
double halfh = Math.abs (cornerY - centerY); |
|
|
setFrame (centerX - halfw, centerY - halfh, |
|
|
2 * halfw, 2 * halfh); |
|
|
} |
|
|
|
|
|
public void setFrameFromCenter (Point2D center, Point2D corner) |
|
|
{ |
|
|
setFrameFromCenter (center.getX (), center.getY (), |
|
|
corner.getX (), corner.getY ()); |
|
|
} |
|
|
|
|
|
public boolean contains (Point2D p) |
|
|
{ |
|
|
double x = p.getX (); |
|
|
double y = p.getY (); |
|
|
double rx = getX (); |
|
|
double ry = getY (); |
|
|
double w = getWidth (); |
|
|
double h = getHeight (); |
|
|
return x >= rx && x < rx + w && y >= ry && y < ry + h; |
|
|
} |
|
|
|
|
|
public boolean intersects (Rectangle2D r) |
|
|
{ |
|
|
double x = getX (); |
|
|
double w = getWidth (); |
|
|
double mx = r.getX (); |
|
|
double mw = r.getWidth (); |
|
|
if (x < mx || x >= mx + mw || x + w < mx || x + w >= mx + mw) |
|
|
return false; |
|
|
double y = getY (); |
|
|
double h = getHeight (); |
|
|
double my = r.getY (); |
|
|
double mh = r.getHeight (); |
|
|
return y >= my && y < my + mh && y + h >= my && y + h < my + mh; |
|
242 |
} |
} |
243 |
|
|
244 |
private boolean containsPoint (double x, double y) |
/** |
245 |
{ |
* Set the framing rectangle of this shape using two points on a diagonal. |
246 |
double mx = getX (); |
* The area will be positive. |
247 |
double mw = getWidth (); |
* |
248 |
if (x < mx || x >= mx + mw) |
* @param p1 the first point |
249 |
return false; |
* @param p2 the second point |
250 |
double my = getY (); |
* @throws NullPointerException if either point is null |
251 |
double mh = getHeight (); |
*/ |
252 |
return y >= my && y < my + mh; |
public void setFrameFromDiagonal(Point2D p1, Point2D p2) |
253 |
} |
{ |
254 |
|
setFrameFromDiagonal(p1.getX(), p1.getY(), p2.getX(), p2.getY()); |
255 |
public boolean contains (Rectangle2D r) |
} |
256 |
{ |
|
257 |
return (containsPoint (r.getMinX (), r.getMinY ()) |
/** |
258 |
&& containsPoint (r.getMaxX (), r.getMaxY ())); |
* Set the framing rectangle of this shape using the center of the frame, |
259 |
} |
* and one of the four corners. The area will be positive. |
260 |
|
* |
261 |
public Rectangle getBounds () |
* @param centerX the x coordinate at the center |
262 |
{ |
* @param centerY the y coordinate at the center |
263 |
return new Rectangle ((int) getX (), (int) getY (), |
* @param cornerX the x coordinate at a corner |
264 |
(int) getWidth (), (int) getHeight ()); |
* @param cornerY the y coordinate at a corner |
265 |
} |
*/ |
266 |
|
public void setFrameFromCenter(double centerX, double centerY, |
267 |
public PathIterator getPathIterator (AffineTransform at, double flatness) |
double cornerX, double cornerY) |
268 |
{ |
{ |
269 |
return at.new Iterator (new Iterator ()); |
double halfw = Math.abs(cornerX - centerX); |
270 |
} |
double halfh = Math.abs(cornerY - centerY); |
271 |
|
setFrame(centerX - halfw, centerY - halfh, halfw + halfw, halfh + halfh); |
272 |
public Object clone () |
} |
273 |
|
|
274 |
|
/** |
275 |
|
* Set the framing rectangle of this shape using the center of the frame, |
276 |
|
* and one of the four corners. The area will be positive. |
277 |
|
* |
278 |
|
* @param center the center point |
279 |
|
* @param corner a corner point |
280 |
|
* @throws NullPointerException if either point is null |
281 |
|
*/ |
282 |
|
public void setFrameFromCenter(Point2D center, Point2D corner) |
283 |
|
{ |
284 |
|
setFrameFromCenter(center.getX(), center.getY(), |
285 |
|
corner.getX(), corner.getY()); |
286 |
|
} |
287 |
|
|
288 |
|
/** |
289 |
|
* Tests if a point is inside the boundary of the shape. |
290 |
|
* |
291 |
|
* @param p the point to test |
292 |
|
* @return true if the point is inside the shape |
293 |
|
* @throws NullPointerException if p is null |
294 |
|
* @see #contains(double, double) |
295 |
|
*/ |
296 |
|
public boolean contains(Point2D p) |
297 |
|
{ |
298 |
|
return contains(p.getX(), p.getY()); |
299 |
|
} |
300 |
|
|
301 |
|
/** |
302 |
|
* Tests if a rectangle and this shape share common internal points. |
303 |
|
* |
304 |
|
* @param r the rectangle to test |
305 |
|
* @return true if the rectangle intersects this shpae |
306 |
|
* @throws NullPointerException if r is null |
307 |
|
* @see #intersects(double, double, double, double) |
308 |
|
*/ |
309 |
|
public boolean intersects(Rectangle2D r) |
310 |
|
{ |
311 |
|
return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight()); |
312 |
|
} |
313 |
|
|
314 |
|
/** |
315 |
|
* Tests if the shape completely contains the given rectangle. |
316 |
|
* |
317 |
|
* @param r the rectangle to test |
318 |
|
* @return true if r is contained in this shape |
319 |
|
* @throws NullPointerException if r is null |
320 |
|
* @see #contains(double, double, double, double) |
321 |
|
*/ |
322 |
|
public boolean contains(Rectangle2D r) |
323 |
|
{ |
324 |
|
return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight()); |
325 |
|
} |
326 |
|
|
327 |
|
/** |
328 |
|
* Returns a bounding box for this shape, in integer format. Notice that you |
329 |
|
* may get a tighter bound with getBounds2D. If the frame is empty, the |
330 |
|
* box is the default empty box at the origin. |
331 |
|
* |
332 |
|
* @return a bounding box |
333 |
|
*/ |
334 |
|
public Rectangle getBounds() |
335 |
|
{ |
336 |
|
if (isEmpty()) |
337 |
|
return new Rectangle(); |
338 |
|
double x = getX(); |
339 |
|
double y = getY(); |
340 |
|
double maxx = Math.ceil(x + getWidth()); |
341 |
|
double maxy = Math.ceil(y + getHeight()); |
342 |
|
x = Math.floor(x); |
343 |
|
y = Math.floor(y); |
344 |
|
return new Rectangle((int) x, (int) y, (int) (maxx - x), (int) (maxy - y)); |
345 |
|
} |
346 |
|
|
347 |
|
/** |
348 |
|
* Return an iterator along the shape boundary. If the optional transform |
349 |
|
* is provided, the iterator is transformed accordingly. The path is |
350 |
|
* flattened until all segments differ from the curve by at most the value |
351 |
|
* of the flatness parameter, within the limits of the default interpolation |
352 |
|
* recursion limit of 1024 segments between actual points. Each call |
353 |
|
* returns a new object, independent from others in use. The result is |
354 |
|
* threadsafe if and only if the iterator returned by |
355 |
|
* {@link #getPathIterator(AffineTransform)} is as well. |
356 |
|
* |
357 |
|
* @param transform an optional transform to apply to the iterator |
358 |
|
* @param flatness the desired flatness |
359 |
|
* @return a new iterator over the boundary |
360 |
|
* @throws IllegalArgumentException if flatness is invalid |
361 |
|
* @since 1.2 |
362 |
|
*/ |
363 |
|
public PathIterator getPathIterator(AffineTransform at, double flatness) |
364 |
|
{ |
365 |
|
return new FlatteningPathIterator(getPathIterator(at), flatness); |
366 |
|
} |
367 |
|
|
368 |
|
/** |
369 |
|
* Create a new shape of the same run-time type with the same contents as |
370 |
|
* this one. |
371 |
|
* |
372 |
|
* @return the clone |
373 |
|
*/ |
374 |
|
public Object clone() |
375 |
{ |
{ |
376 |
try |
try |
377 |
{ |
{ |
378 |
return super.clone (); |
return super.clone(); |
379 |
} |
} |
380 |
catch (CloneNotSupportedException _) {return null;} |
catch (CloneNotSupportedException e) |
381 |
} |
{ |
382 |
|
throw (Error) new InternalError().initCause(e); // Impossible |
383 |
// This implements the PathIterator for all RectangularShape objects |
} |
|
// that don't override getPathIterator. |
|
|
private class Iterator implements PathIterator |
|
|
{ |
|
|
// Our current coordinate. |
|
|
private int coord; |
|
|
|
|
|
private static final int START = 0; |
|
|
private static final int END_PLUS_ONE = 5; |
|
|
|
|
|
public Iterator () |
|
|
{ |
|
|
coord = START; |
|
|
} |
|
|
|
|
|
public int currentSegment (double[] coords) |
|
|
{ |
|
|
int r; |
|
|
switch (coord) |
|
|
{ |
|
|
case 0: |
|
|
coords[0] = getX (); |
|
|
coords[1] = getY (); |
|
|
r = SEG_MOVETO; |
|
|
break; |
|
|
|
|
|
case 1: |
|
|
coords[0] = getX () + getWidth (); |
|
|
coords[1] = getY (); |
|
|
r = SEG_LINETO; |
|
|
break; |
|
|
|
|
|
case 2: |
|
|
coords[0] = getX () + getWidth (); |
|
|
coords[1] = getY () + getHeight (); |
|
|
r = SEG_LINETO; |
|
|
break; |
|
|
|
|
|
case 3: |
|
|
coords[0] = getX (); |
|
|
coords[1] = getY () + getHeight (); |
|
|
r = SEG_LINETO; |
|
|
break; |
|
|
|
|
|
case 4: |
|
|
r = SEG_CLOSE; |
|
|
break; |
|
|
|
|
|
default: |
|
|
// It isn't clear what to do if the caller calls us after |
|
|
// isDone returns true. |
|
|
r = SEG_CLOSE; |
|
|
break; |
|
|
} |
|
|
|
|
|
return r; |
|
|
} |
|
|
|
|
|
public int currentSegment (float[] coords) |
|
|
{ |
|
|
int r; |
|
|
switch (coord) |
|
|
{ |
|
|
case 0: |
|
|
coords[0] = (float) getX (); |
|
|
coords[1] = (float) getY (); |
|
|
r = SEG_MOVETO; |
|
|
break; |
|
|
|
|
|
case 1: |
|
|
coords[0] = (float) (getX () + getWidth ()); |
|
|
coords[1] = (float) getY (); |
|
|
r = SEG_LINETO; |
|
|
break; |
|
|
|
|
|
case 2: |
|
|
coords[0] = (float) (getX () + getWidth ()); |
|
|
coords[1] = (float) (getY () + getHeight ()); |
|
|
r = SEG_LINETO; |
|
|
break; |
|
|
|
|
|
case 3: |
|
|
coords[0] = (float) getX (); |
|
|
coords[1] = (float) (getY () + getHeight ()); |
|
|
r = SEG_LINETO; |
|
|
break; |
|
|
|
|
|
case 4: |
|
|
default: |
|
|
// It isn't clear what to do if the caller calls us after |
|
|
// isDone returns true. We elect to keep returning |
|
|
// SEG_CLOSE. |
|
|
r = SEG_CLOSE; |
|
|
break; |
|
|
} |
|
|
|
|
|
return r; |
|
|
} |
|
|
|
|
|
public int getWindingRule () |
|
|
{ |
|
|
return WIND_NON_ZERO; |
|
|
} |
|
|
|
|
|
public boolean isDone () |
|
|
{ |
|
|
return coord == END_PLUS_ONE; |
|
|
} |
|
|
|
|
|
public void next () |
|
|
{ |
|
|
if (coord < END_PLUS_ONE) |
|
|
++coord; |
|
|
} |
|
384 |
} |
} |
385 |
} |
} // class RectangularShape |