KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > nightlabs > editor2d > j2d > GeneralShape


1 /* *****************************************************************************
2  * NightLabs Editor2D - Graphical editor framework *
3  * Copyright (C) 2004-2005 NightLabs - http://NightLabs.org *
4  * *
5  * This library is free software; you can redistribute it and/or *
6  * modify it under the terms of the GNU Lesser General Public *
7  * License as published by the Free Software Foundation; either *
8  * version 2.1 of the License, or (at your option) any later version. *
9  * *
10  * This library is distributed in the hope that it will be useful, *
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
13  * Lesser General Public License for more details. *
14  * *
15  * You should have received a copy of the GNU Lesser General Public *
16  * License along with this library; if not, write to the *
17  * Free Software Foundation, Inc., *
18  * 51 Franklin St, Fifth Floor, *
19  * Boston, MA 02110-1301 USA *
20  * *
21  * Or get it online : *
22  * http://www.gnu.org/copyleft/lesser.html *
23  * *
24  * *
25  ******************************************************************************/

26
27 package org.nightlabs.editor2d.j2d;
28
29 import java.awt.Shape JavaDoc;
30 import java.awt.geom.AffineTransform JavaDoc;
31 import java.awt.geom.FlatteningPathIterator JavaDoc;
32 import java.awt.geom.IllegalPathStateException JavaDoc;
33 import java.awt.geom.PathIterator JavaDoc;
34 import java.awt.geom.Point2D JavaDoc;
35 import java.awt.geom.Rectangle2D JavaDoc;
36 import java.io.Serializable JavaDoc;
37 import java.util.ArrayList JavaDoc;
38 import java.util.Iterator JavaDoc;
39 import java.util.LinkedList JavaDoc;
40 import java.util.List JavaDoc;
41
42 import sun.awt.geom.Crossings;
43 import sun.awt.geom.Curve;
44
45
46 /**
47  * The <code>GeneralShape</code> class represents a geometric path
48  * constructed from straight lines, and quadratic and cubic
49  * (B&eacute;zier) curves. It can contain multiple subpaths.
50  * <p>
51  * The winding rule specifies how the interior of a path is
52  * determined. There are two types of winding rules:
53  * EVEN_ODD and NON_ZERO.
54  * <p>
55  * An EVEN_ODD winding rule means that enclosed regions
56  * of the path alternate between interior and exterior areas as
57  * traversed from the outside of the path towards a point inside
58  * the region.
59  * <p>
60  * A NON_ZERO winding rule means that if a ray is
61  * drawn in any direction from a given point to infinity
62  * and the places where the path intersects
63  * the ray are examined, the point is inside of the path if and only if
64  * the number of times that the path crosses the ray from
65  * left to right does not equal the number of times that the path crosses
66  * the ray from right to left.
67  */

68 public class GeneralShape
69 implements Shape JavaDoc, Cloneable JavaDoc, Serializable JavaDoc
70 {
71     /**
72      * An even-odd winding rule for determining the interior of
73      * a path.
74      */

75     public static final int WIND_EVEN_ODD = PathIterator.WIND_EVEN_ODD;
76
77     /**
78      * A non-zero winding rule for determining the interior of a
79      * path.
80      */

81     public static final int WIND_NON_ZERO = PathIterator.WIND_NON_ZERO;
82
83     // For code simplicity, copy these constants to our namespace
84
// and cast them to byte constants for easy storage.
85
private static final byte SEG_MOVETO = (byte) PathIterator.SEG_MOVETO;
86     private static final byte SEG_LINETO = (byte) PathIterator.SEG_LINETO;
87     private static final byte SEG_QUADTO = (byte) PathIterator.SEG_QUADTO;
88     private static final byte SEG_CUBICTO = (byte) PathIterator.SEG_CUBICTO;
89     private static final byte SEG_CLOSE = (byte) PathIterator.SEG_CLOSE;
90
91     protected byte[] pointTypes;
92     protected float[] pointCoords;
93     int numTypes;
94     int numCoords;
95     int windingRule;
96     
97 // *************************** Beans Specification BEGIN ****************
98
public byte[] getPointTypes() {
99         return pointTypes;
100     }
101     public void setPointTypes(byte[] _pointTypes) {
102         pointTypes = _pointTypes;
103         bounds = null;
104     }
105     
106     public int getNumCoords() {
107         return numCoords;
108     }
109     public void setNumCoords(int numCoords) {
110         this.numCoords = numCoords;
111         bounds = null;
112     }
113
114     public int getNumTypes() {
115         return numTypes;
116     }
117     public void setNumTypes(int numTypes) {
118         this.numTypes = numTypes;
119         bounds = null;
120     }
121
122     public int getSize() {
123       return numTypes;
124     }
125     
126     public float[] getPointCoords() {
127         return pointCoords;
128     }
129     public void setPointCoords(float[] pointCoords) {
130         this.pointCoords = pointCoords;
131         bounds = null;
132     }
133 // *************************** Beans Specification END ****************
134

135     static final int INIT_SIZE = 20;
136     static final int EXPAND_MAX = 500;
137
138     /**
139      * Constructs a new <code>GeneralShape</code> object.
140      * If an operation performed on this path requires the
141      * interior of the path to be defined then the default NON_ZERO
142      * winding rule is used.
143      * @see #WIND_NON_ZERO
144      */

145     public GeneralShape() {
146         this(WIND_NON_ZERO, INIT_SIZE, INIT_SIZE);
147     }
148
149     /**
150      * Constructs a new <code>GeneralShape</code> object with the specified
151      * winding rule to control operations that require the interior of the
152      * path to be defined.
153      * @param rule the winding rule
154      * @see #WIND_EVEN_ODD
155      * @see #WIND_NON_ZERO
156      */

157     public GeneralShape(int rule) {
158         this(rule, INIT_SIZE, INIT_SIZE);
159     }
160
161     /**
162      * Constructs a new <code>GeneralShape</code> object with the specified
163      * winding rule and the specified initial capacity to store path
164      * coordinates. This number is an initial guess as to how many path
165      * segments are in the path, but the storage is expanded
166      * as needed to store whatever path segments are added to this path.
167      * @param rule the winding rule
168      * @param initialCapacity the estimate for the number of path segments
169      * in the path
170      * @see #WIND_EVEN_ODD
171      * @see #WIND_NON_ZERO
172      */

173     public GeneralShape(int rule, int initialCapacity) {
174         this(rule, initialCapacity, initialCapacity);
175     }
176
177     /**
178      * Constructs a new <code>GeneralShape</code> object with the specified
179      * winding rule and the specified initial capacities to store point types
180      * and coordinates.
181      * These numbers are an initial guess as to how many path segments
182      * and how many points are to be in the path, but the
183      * storage is expanded as needed to store whatever path segments are
184      * added to this path.
185      * @param rule the winding rule
186      * @param initialTypes the estimate for the number of path segments
187      * in the path
188      * @param initialCapacity the estimate for the number of points
189      * @see #WIND_EVEN_ODD
190      * @see #WIND_NON_ZERO
191      */

192     GeneralShape(int rule, int initialTypes, int initialCoords) {
193         setWindingRule(rule);
194         pointTypes = new byte[initialTypes];
195         pointCoords = new float[initialCoords * 2];
196     }
197
198     /**
199      * Constructs a new <code>GeneralShape</code> object from an arbitrary
200      * {@link Shape} object.
201      * All of the initial geometry and the winding rule for this path are
202      * taken from the specified <code>Shape</code> object.
203      * @param s the specified <code>Shape</code> object
204      */

205     public GeneralShape(Shape JavaDoc s) {
206         this(WIND_NON_ZERO, INIT_SIZE, INIT_SIZE);
207         PathIterator JavaDoc pi = s.getPathIterator(null);
208         setWindingRule(pi.getWindingRule());
209         append(pi, false);
210     }
211
212     private void needRoom(int newTypes, int newCoords, boolean needMove) {
213         if (needMove && numTypes == 0) {
214             throw new IllegalPathStateException JavaDoc("missing initial moveto "+
215                                                 "in path definition");
216         }
217         int size = pointCoords.length;
218         if (numCoords + newCoords > size) {
219             int grow = size;
220             if (grow > EXPAND_MAX * 2) {
221                 grow = EXPAND_MAX * 2;
222             }
223             if (grow < newCoords) {
224                 grow = newCoords;
225             }
226             float[] arr = new float[size + grow];
227             System.arraycopy(pointCoords, 0, arr, 0, numCoords);
228             pointCoords = arr;
229         }
230         size = pointTypes.length;
231         if (numTypes + newTypes > size) {
232             int grow = size;
233             if (grow > EXPAND_MAX) {
234                 grow = EXPAND_MAX;
235             }
236             if (grow < newTypes) {
237                 grow = newTypes;
238             }
239             byte[] arr = new byte[size + grow];
240             System.arraycopy(pointTypes, 0, arr, 0, numTypes);
241             pointTypes = arr;
242         }
243     }
244
245     /**
246      * Adds a point to the path by moving to the specified
247      * coordinates.
248      * @param x,&nbsp;y the specified coordinates
249      */

250     public synchronized void moveTo(float x, float y) {
251         if (numTypes > 0 && pointTypes[numTypes - 1] == SEG_MOVETO) {
252             pointCoords[numCoords - 2] = x;
253             pointCoords[numCoords - 1] = y;
254         } else {
255             needRoom(1, 2, false);
256             pointTypes[numTypes++] = SEG_MOVETO;
257             pointCoords[numCoords++] = x;
258             pointCoords[numCoords++] = y;
259         }
260         pathSegments = null;
261     }
262
263     /**
264      * Adds a point to the path by drawing a straight line from the
265      * current coordinates to the new specified coordinates.
266      * @param x,&nbsp;y the specified coordinates
267      */

268     public synchronized void lineTo(float x, float y) {
269         needRoom(1, 2, true);
270         pointTypes[numTypes++] = SEG_LINETO;
271         pointCoords[numCoords++] = x;
272         pointCoords[numCoords++] = y;
273         pathSegments = null;
274         bounds = null;
275     }
276
277     /**
278      * Adds a curved segment, defined by two new points, to the path by
279      * drawing a Quadratic curve that intersects both the current
280      * coordinates and the coordinates (x2,&nbsp;y2), using the
281      * specified point (x1,&nbsp;y1) as a quadratic parametric control
282      * point.
283      * @param x1,&nbsp;y1 the coordinates of the first quadratic control
284      * point
285      * @param x2,&nbsp;y2 the coordinates of the final endpoint
286      */

287     public synchronized void quadTo(float x1, float y1, float x2, float y2) {
288         needRoom(1, 4, true);
289         pointTypes[numTypes++] = SEG_QUADTO;
290         pointCoords[numCoords++] = x1;
291         pointCoords[numCoords++] = y1;
292         pointCoords[numCoords++] = x2;
293         pointCoords[numCoords++] = y2;
294         pathSegments = null;
295         bounds = null;
296     }
297
298     /**
299      * Adds a curved segment, defined by three new points, to the path by
300      * drawing a B&eacute;zier curve that intersects both the current
301      * coordinates and the coordinates (x3,&nbsp;y3), using the
302      * specified points (x1,&nbsp;y1) and (x2,&nbsp;y2) as
303      * B&eacute;zier control points.
304      * @param x1,&nbsp;y1 the coordinates of the first B&eacute;ezier
305      * control point
306      * @param x2,&nbsp;y2 the coordinates of the second B&eacute;zier
307      * control point
308      * @param x3,&nbsp;y3 the coordinates of the final endpoint
309      */

310     public synchronized void curveTo(float x1, float y1,
311                                      float x2, float y2,
312                                      float x3, float y3) {
313         needRoom(1, 6, true);
314         pointTypes[numTypes++] = SEG_CUBICTO;
315         pointCoords[numCoords++] = x1;
316         pointCoords[numCoords++] = y1;
317         pointCoords[numCoords++] = x2;
318         pointCoords[numCoords++] = y2;
319         pointCoords[numCoords++] = x3;
320         pointCoords[numCoords++] = y3;
321         pathSegments = null;
322         bounds = null;
323     }
324
325     /**
326      * Closes the current subpath by drawing a straight line back to
327      * the coordinates of the last <code>moveTo</code>. If the path is already
328      * closed then this method has no effect.
329      */

330     public synchronized void closePath()
331     {
332         if (numTypes == 0 || pointTypes[numTypes - 1] != SEG_CLOSE) {
333             needRoom(1, 0, true);
334             pointTypes[numTypes++] = SEG_CLOSE;
335         }
336         bounds = null;
337     }
338
339     /**
340      * Appends the geometry of the specified <code>Shape</code> object to the
341      * path, possibly connecting the new geometry to the existing path
342      * segments with a line segment.
343      * If the <code>connect</code> parameter is <code>true</code> and the
344      * path is not empty then any initial <code>moveTo</code> in the
345      * geometry of the appended <code>Shape</code>
346      * is turned into a <code>lineTo</code> segment.
347      * If the destination coordinates of such a connecting <code>lineTo</code>
348      * segment match the ending coordinates of a currently open
349      * subpath then the segment is omitted as superfluous.
350      * The winding rule of the specified <code>Shape</code> is ignored
351      * and the appended geometry is governed by the winding
352      * rule specified for this path.
353      * @param s the <code>Shape</code> whose geometry is appended
354      * to this path
355      * @param connect a boolean to control whether or not to turn an
356      * initial <code>moveTo</code> segment into a <code>lineTo</code>
357      * segment to connect the new geometry to the existing path
358      */

359     public void append(Shape JavaDoc s, boolean connect) {
360         PathIterator JavaDoc pi = s.getPathIterator(null);
361         append(pi,connect);
362     }
363
364     /**
365      * Appends the geometry of the specified
366      * {@link PathIterator} object
367      * to the path, possibly connecting the new geometry to the existing
368      * path segments with a line segment.
369      * If the <code>connect</code> parameter is <code>true</code> and the
370      * path is not empty then any initial <code>moveTo</code> in the
371      * geometry of the appended <code>Shape</code> is turned into a
372      * <code>lineTo</code> segment.
373      * If the destination coordinates of such a connecting <code>lineTo</code>
374      * segment match the ending coordinates of a currently open
375      * subpath then the segment is omitted as superfluous.
376      * The winding rule of the specified <code>Shape</code> is ignored
377      * and the appended geometry is governed by the winding
378      * rule specified for this path.
379      * @param pi the <code>PathIterator</code> whose geometry is appended to
380      * this path
381      * @param connect a boolean to control whether or not to turn an
382      * initial <code>moveTo</code> segment into a <code>lineTo</code> segment
383      * to connect the new geometry to the existing path
384      */

385     public void append(PathIterator JavaDoc pi, boolean connect) {
386         float coords[] = new float[6];
387         while (!pi.isDone()) {
388             switch (pi.currentSegment(coords)) {
389             case SEG_MOVETO:
390                 if (!connect || numTypes < 1 || numCoords < 2) {
391                     moveTo(coords[0], coords[1]);
392                     break;
393                 }
394                 if (pointTypes[numTypes - 1] != SEG_CLOSE &&
395                     pointCoords[numCoords - 2] == coords[0] &&
396                     pointCoords[numCoords - 1] == coords[1])
397                 {
398                     // Collapse out initial moveto/lineto
399
break;
400                 }
401                 // NO BREAK;
402
case SEG_LINETO:
403                 lineTo(coords[0], coords[1]);
404                 break;
405             case SEG_QUADTO:
406                 quadTo(coords[0], coords[1],
407                        coords[2], coords[3]);
408                 break;
409             case SEG_CUBICTO:
410                 curveTo(coords[0], coords[1],
411                         coords[2], coords[3],
412                         coords[4], coords[5]);
413                 break;
414             case SEG_CLOSE:
415                 closePath();
416                 break;
417             }
418             pi.next();
419             connect = false;
420         }
421         pathSegments = null;
422         bounds = null;
423     }
424
425     /**
426      * Returns the fill style winding rule.
427      * @return an integer representing the current winding rule.
428      * @see #WIND_EVEN_ODD
429      * @see #WIND_NON_ZERO
430      * @see #setWindingRule
431      */

432     public synchronized int getWindingRule() {
433         return windingRule;
434     }
435
436     /**
437      * Sets the winding rule for this path to the specified value.
438      * @param rule an integer representing the specified
439      * winding rule
440      * @exception <code>IllegalArgumentException</code> if
441      * <code>rule</code> is not either
442      * <code>WIND_EVEN_ODD</code> or
443      * <code>WIND_NON_ZERO</code>
444      * @see #WIND_EVEN_ODD
445      * @see #WIND_NON_ZERO
446      * @see #getWindingRule
447      */

448     public void setWindingRule(int rule) {
449         if (rule != WIND_EVEN_ODD && rule != WIND_NON_ZERO) {
450             throw new IllegalArgumentException JavaDoc("winding rule must be "+
451                                                "WIND_EVEN_ODD or "+
452                                                "WIND_NON_ZERO");
453         }
454         windingRule = rule;
455     }
456
457     /**
458      * Returns the coordinates most recently added to the end of the path
459      * as a {@link Point2D} object.
460      * @return a <code>Point2D</code> object containing the ending
461      * coordinates of the path or <code>null</code> if there are no points
462      * in the path.
463      */

464     public synchronized Point2D JavaDoc getCurrentPoint() {
465         if (numTypes < 1 || numCoords < 2) {
466             return null;
467         }
468         int index = numCoords;
469         if (pointTypes[numTypes - 1] == SEG_CLOSE) {
470         loop:
471             for (int i = numTypes - 2; i > 0; i--) {
472                 switch (pointTypes[i]) {
473                 case SEG_MOVETO:
474                     break loop;
475                 case SEG_LINETO:
476                     index -= 2;
477                     break;
478                 case SEG_QUADTO:
479                     index -= 4;
480                     break;
481                 case SEG_CUBICTO:
482                     index -= 6;
483                     break;
484                 case SEG_CLOSE:
485                     break;
486                 }
487             }
488         }
489         return new Point2D.Float JavaDoc(pointCoords[index - 2],
490                                  pointCoords[index - 1]);
491     }
492
493     /**
494      * Resets the path to empty. The append position is set back to the
495      * beginning of the path and all coordinates and point types are
496      * forgotten.
497      */

498     public synchronized void reset() {
499         numTypes = numCoords = 0;
500         bounds = null;
501     }
502      
503     protected List JavaDoc transformListeners;
504     public void addTransformListener(TransformListener listener)
505     {
506       if (transformListeners == null)
507         transformListeners = new ArrayList JavaDoc();
508     
509       transformListeners.add(listener);
510     }
511     public void removeTransformListener(TransformListener listener)
512     {
513       if (transformListeners == null)
514         transformListeners = new ArrayList JavaDoc();
515       
516       transformListeners.remove(listener);
517     }
518     
519 // protected void fireTransformChanged(AffineTransform _at)
520
// {
521
// if (transformListeners == null)
522
// return;
523
//
524
// for (Iterator it = transformListeners.iterator(); it.hasNext(); )
525
// {
526
// TransformListener listener = (TransformListener) it.next();
527
// listener.transformChanged(_at);
528
// }
529
// }
530
protected void fireTransformChanged(AffineTransform JavaDoc _at)
531     {
532       if (transformListeners == null)
533         return;
534       
535       for (Iterator JavaDoc it = new LinkedList JavaDoc(transformListeners).iterator(); it.hasNext(); )
536       {
537         TransformListener listener = (TransformListener) it.next();
538         listener.transformChanged(_at);
539       }
540     }
541
542     public boolean hasTransformListener()
543     {
544         if (transformListeners != null)
545             return true;
546         else
547             return false;
548     }
549     
550     /**
551      * Transforms the geometry of this path using the specified
552      * {@link AffineTransform}.
553      * The geometry is transformed in place, which permanently changes the
554      * boundary defined by this object.
555      * @param at the <code>AffineTransform</code> used to transform the area
556      */

557     public void transform(AffineTransform JavaDoc at)
558     {
559         at.transform(pointCoords, 0, pointCoords, 0, numCoords / 2);
560         pathSegments = null;
561         bounds = null;
562         fireTransformChanged(at);
563     }
564
565     /**
566      * Returns a new transformed <code>Shape</code>.
567      * @param at the <code>AffineTransform</code> used to transform a
568      * new <code>Shape</code>.
569      * @return a new <code>Shape</code>, transformed with the specified
570      * <code>AffineTransform</code>.
571      */

572     public synchronized Shape JavaDoc createTransformedShape(AffineTransform JavaDoc at) {
573         GeneralShape gp = (GeneralShape) clone();
574         if (at != null) {
575             gp.transform(at);
576         }
577         return gp;
578     }
579
580     /**
581      * Return the bounding box of the path.
582      * @return a {@link java.awt.Rectangle} object that
583      * bounds the current path.
584      */

585     public java.awt.Rectangle JavaDoc getBounds() {
586         return getBounds2D().getBounds();
587     }
588
589     protected Rectangle2D JavaDoc bounds;
590     
591     /**
592      * Returns the bounding box of the path.
593      * @return a {@link Rectangle2D} object that
594      * bounds the current path.
595      */

596     public synchronized Rectangle2D JavaDoc getBounds2D()
597     {
598       if (bounds == null)
599       {
600         float x1, y1, x2, y2;
601         int i = numCoords;
602         if (i > 0) {
603             y1 = y2 = pointCoords[--i];
604             x1 = x2 = pointCoords[--i];
605             while (i > 0) {
606                 float y = pointCoords[--i];
607                 float x = pointCoords[--i];
608                 if (x < x1) x1 = x;
609                 if (y < y1) y1 = y;
610                 if (x > x2) x2 = x;
611                 if (y > y2) y2 = y;
612             }
613         } else {
614             x1 = y1 = x2 = y2 = 0.0f;
615         }
616         return new Rectangle2D.Float JavaDoc(x1, y1, x2 - x1, y2 - y1);
617       }
618       else
619         return bounds;
620     }
621
622     /**
623      * Tests if the specified coordinates are inside the boundary of
624      * this <code>Shape</code>.
625      * @param x,&nbsp;y the specified coordinates
626      * @return <code>true</code> if the specified coordinates are inside this
627      * <code>Shape</code>; <code>false</code> otherwise
628      */

629     public boolean contains(double x, double y) {
630         if (numTypes < 2) {
631             return false;
632         }
633         int cross = Curve.crossingsForPath(getPathIterator(null), x, y);
634         if (windingRule == WIND_NON_ZERO) {
635             return (cross != 0);
636         } else {
637             return ((cross & 1) != 0);
638         }
639     }
640
641     /**
642      * Tests if the specified <code>Point2D</code> is inside the boundary
643      * of this <code>Shape</code>.
644      * @param p the specified <code>Point2D</code>
645      * @return <code>true</code> if this <code>Shape</code> contains the
646      * specified <code>Point2D</code>, <code>false</code> otherwise.
647      */

648     public boolean contains(Point2D JavaDoc p) {
649         return contains(p.getX(), p.getY());
650     }
651
652     /**
653      * Tests if the specified rectangular area is inside the boundary of
654      * this <code>Shape</code>.
655      * @param x,&nbsp;y the specified coordinates
656      * @param w the width of the specified rectangular area
657      * @param h the height of the specified rectangular area
658      * @return <code>true</code> if this <code>Shape</code> contains
659      * the specified rectangluar area; <code>false</code> otherwise.
660      */

661     public boolean contains(double x, double y, double w, double h) {
662         Crossings c = Crossings.findCrossings(getPathIterator(null),
663                                               x, y, x+w, y+h);
664         return (c != null && c.covers(y, y+h));
665     }
666
667     /**
668      * Tests if the specified <code>Rectangle2D</code>
669      * is inside the boundary of this <code>Shape</code>.
670      * @param r a specified <code>Rectangle2D</code>
671      * @return <code>true</code> if this <code>Shape</code> bounds the
672      * specified <code>Rectangle2D</code>; <code>false</code> otherwise.
673      */

674     public boolean contains(Rectangle2D JavaDoc r) {
675         return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight());
676     }
677
678     /**
679      * Tests if the interior of this <code>Shape</code> intersects the
680      * interior of a specified set of rectangular coordinates.
681      * @param x,&nbsp;y the specified coordinates
682      * @param w the width of the specified rectangular coordinates
683      * @param h the height of the specified rectangular coordinates
684      * @return <code>true</code> if this <code>Shape</code> and the
685      * interior of the specified set of rectangular coordinates intersect
686      * each other; <code>false</code> otherwise.
687      */

688     public boolean intersects(double x, double y, double w, double h) {
689         Crossings c = Crossings.findCrossings(getPathIterator(null),
690                                               x, y, x+w, y+h);
691         return (c == null || !c.isEmpty());
692     }
693
694     /**
695      * Tests if the interior of this <code>Shape</code> intersects the
696      * interior of a specified <code>Rectangle2D</code>.
697      * @param r the specified <code>Rectangle2D</code>
698      * @return <code>true</code> if this <code>Shape</code> and the interior
699      * of the specified <code>Rectangle2D</code> intersect each
700      * other; <code>false</code> otherwise.
701      */

702     public boolean intersects(Rectangle2D JavaDoc r) {
703         return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight());
704     }
705
706     /**
707      * Returns a <code>PathIterator</code> object that iterates along the
708      * boundary of this <code>Shape</code> and provides access to the
709      * geometry of the outline of this <code>Shape</code>.
710      * The iterator for this class is not multi-threaded safe,
711      * which means that this <code>GeneralShape</code> class does not
712      * guarantee that modifications to the geometry of this
713      * <code>GeneralShape</code> object do not affect any iterations of
714      * that geometry that are already in process.
715      * @param at an <code>AffineTransform</code>
716      * @return a new <code>PathIterator</code> that iterates along the
717      * boundary of this <code>Shape</code> and provides access to the
718      * geometry of this <code>Shape</code>'s outline
719      */

720     public PathIterator JavaDoc getPathIterator(AffineTransform JavaDoc at) {
721         return new GeneralShapeIterator(this, at);
722     }
723
724     /**
725      * Returns a <code>PathIterator</code> object that iterates along the
726      * boundary of the flattened <code>Shape</code> and provides access to the
727      * geometry of the outline of the <code>Shape</code>.
728      * The iterator for this class is not multi-threaded safe,
729      * which means that this <code>GeneralShape</code> class does not
730      * guarantee that modifications to the geometry of this
731      * <code>GeneralShape</code> object do not affect any iterations of
732      * that geometry that are already in process.
733      * @param at an <code>AffineTransform</code>
734      * @param flatness the maximum distance that the line segments used to
735      * approximate the curved segments are allowed to deviate
736      * from any point on the original curve
737      * @return a new <code>PathIterator</code> that iterates along the flattened
738      * <code>Shape</code> boundary.
739      */

740     public PathIterator JavaDoc getPathIterator(AffineTransform JavaDoc at, double flatness) {
741         return new FlatteningPathIterator JavaDoc(getPathIterator(at), flatness);
742     }
743
744     /**
745      * Creates a new object of the same class as this object.
746      *
747      * @return a clone of this instance.
748      * @exception OutOfMemoryError if there is not enough memory.
749      * @see java.lang.Cloneable
750      * @since 1.2
751      */

752     public Object JavaDoc clone()
753     {
754 // try
755
// {
756
int pointTypesLength = this.pointTypes.length;
757             byte[] copyPointTypes = new byte[pointTypesLength];
758 // for (int i=0; i<pointTypesLength; i++) {
759
// copyPointTypes[i] = this.pointTypes[i];
760
// }
761
System.arraycopy(pointTypes, 0, copyPointTypes, 0, pointTypesLength);
762             int pointCoordsLength = this.pointCoords.length;
763             float[] copyPointCoords = new float[pointCoordsLength];
764 // for (int i=0; i<pointCoordsLength; i++) {
765
// copyPointCoords[i] = this.pointCoords[i];
766
// }
767
System.arraycopy(pointCoords, 0, copyPointCoords, 0, pointCoordsLength);
768 // GeneralShape copy = (GeneralShape) super.clone();
769
GeneralShape copy = new GeneralShape();
770         copy.windingRule = this.windingRule;
771         copy.numTypes = this.numTypes;
772         copy.pointTypes = copyPointTypes;
773         copy.numCoords = this.numCoords;
774         copy.pointCoords = copyPointCoords;
775         return copy;
776 // }
777
// catch (CloneNotSupportedException e) {
778
// // this shouldn't happen, since we are Cloneable
779
// throw new InternalError();
780
// }
781
}
782
783     GeneralShape(int windingRule,
784                 byte[] pointTypes,
785                 int numTypes,
786                 float[] pointCoords,
787                 int numCoords)
788     {
789     // used to construct from native
790
this.windingRule = windingRule;
791         this.pointTypes = pointTypes;
792         this.numTypes = numTypes;
793         this.pointCoords = pointCoords;
794         this.numCoords = numCoords;
795     }
796    
797     public boolean equals(Object JavaDoc o)
798         {
799         if (o instanceof GeneralShape)
800         {
801             GeneralShape gs = (GeneralShape) o;
802
803             // windigRule
804
if (this.windingRule != gs.getWindingRule())
805                 return false;
806             
807             // numCoords
808
if (this.numCoords != gs.getNumCoords())
809                 return false;
810             
811             // numTypes
812
if (this.numTypes != gs.getNumTypes())
813                 return false;
814             
815             // pointCoords
816
if (this.pointCoords.length != gs.getPointCoords().length) {
817                 return false;
818             }
819             else {
820                 float[] gsPointCoords = gs.getPointCoords();
821                 for (int i=0; i<pointCoords.length; i++) {
822                     float f = pointCoords[i];
823                     if (f != gsPointCoords[i])
824                         return false;
825                 }
826             }
827             
828             // pointTypes
829
if (this.pointTypes.length != gs.pointTypes.length) {
830                 return false;
831             }
832             else {
833                 byte[] gsPointTypes = gs.getPointTypes();
834                 for (int i=0; i<pointTypes.length; i++) {
835                     byte b = pointTypes[i];
836                     if (b != gsPointTypes[i])
837                         return false;
838                 }
839             }
840         
841         } // if (o instanceof GeneralShape)
842
else {
843             return false;
844         }
845         return true;
846     }
847
848     /**
849      * Returns the coordinates most recently added to the end of the path
850      * as a {@link Point2D} object.
851      * @return a <code>Point2D</code> object containing the ending
852      * coordinates of the path or <code>null</code> if there are no points
853      * in the path.
854      */

855     public synchronized void setLastPoint(float x, float y)
856 // public void setLastPoint(float x, float y)
857
{
858         if (numTypes < 1 || numCoords < 2) {
859             return;
860         }
861         int index = numCoords;
862         if (pointTypes[numTypes - 1] == PathIterator.SEG_CLOSE) {
863         loop:
864             for (int i = numTypes - 2; i > 0; i--) {
865                 switch (pointTypes[i]) {
866                 case PathIterator.SEG_MOVETO:
867                     break loop;
868                 case PathIterator.SEG_LINETO:
869                     index -= 2;
870                     break;
871                 case PathIterator.SEG_QUADTO:
872                     index -= 4;
873                     break;
874                 case PathIterator.SEG_CUBICTO:
875                     index -= 6;
876                     break;
877                 case PathIterator.SEG_CLOSE:
878                     break;
879                 }
880             }
881         }
882         pointCoords[index - 2] = x;
883         pointCoords[index - 1] = y;
884         bounds = null;
885     }
886     
887     public synchronized void setPathSegment(int type, int index, float[] coords)
888     {
889       // TODO: check why it sometimes works wrong
890
System.out.println("GeneralShape.setPathSegment");
891       switch (type)
892       {
893         case (PathIterator.SEG_MOVETO):
894           pointCoords[index] = coords[0];
895             pointCoords[index+1] = coords[1];
896             break;
897         case (PathIterator.SEG_LINETO):
898           pointCoords[index] = coords[0];
899             pointCoords[index+1] = coords[1];
900             break;
901         case (PathIterator.SEG_QUADTO):
902           pointCoords[index] = coords[0];
903             pointCoords[index+1] = coords[1];
904           pointCoords[index+2] = coords[2];
905             pointCoords[index+3] = coords[3];
906             break;
907         case (PathIterator.SEG_CUBICTO):
908           pointCoords[index] = coords[0];
909             pointCoords[index+1] = coords[1];
910           pointCoords[index+2] = coords[2];
911             pointCoords[index+3] = coords[3];
912           pointCoords[index+4] = coords[4];
913             pointCoords[index+5] = coords[5];
914             break;
915       }
916       bounds = null;
917     }
918     
919 // public static final float DEFAULT_TOLERANCE = 2;
920
//
921
// public int pathSegmentContains(float x1, float y1)
922
// {
923
// return pathSegmentContains(x1, y1, true);
924
// }
925
//
926
// public int pathSegmentContains(float x1, float y1, boolean tolerance)
927
// {
928
// return pathSegmentContains(x1, y1, DEFAULT_TOLERANCE, tolerance);
929
// }
930
//
931
// public int pathSegmentContains(Point2D p, boolean tolerance)
932
// {
933
// return pathSegmentContains((float)p.getX(), (float)p.getY(), DEFAULT_TOLERANCE, tolerance);
934
// }
935

936 // /**
937
// * Checks if any PathSegment contains the given Point + Distance Tolerance
938
// *
939
// * @param x1 x-Coordinate from the Point
940
// * @param y1 y-Coordinate from the Point
941
// * @param tolerance The Distance-Tolerance
942
// * @return The startIndex of the pointCoords[]-Array for the matching PathSegment
943
// */
944
// public int pathSegmentContains(float x1, float y1, float toleranceValue, boolean tolerance)
945
// {
946
// for (int i=0; i<pointCoords.length-1; i = i+2) {
947
// float x2 = pointCoords[i];
948
// float y2 = pointCoords[i+1];
949
// if (tolerance)
950
// {
951
// if (interior (x1, y1, x2, y2, toleranceValue)) {
952
// return i;
953
// }
954
// } else {
955
// if (x1 == x2 && y1 == y2)
956
// return i;
957
// }
958
// }
959
// return -1;
960
// }
961

962 // /**
963
// * Checks if the distance from one Point to another is within the given distance
964
// *
965
// * @param x1 x-Coordinate from Point 1
966
// * @param y1 y-Coordinate from Point 1
967
// * @param x2 x-Coordinate from Point 2
968
// * @param y2 y-Coordinate from Point 2
969
// * @param tolerance The Distance-Tolerance
970
// *
971
// * @return true if the distance is smaller than the tolerance otherwise false
972
// */
973
// public static boolean interior(float x1, float y1, float x2, float y2, float tolerance)
974
// {
975
// // TODO: maybe generating a Rectangle and call contains(x,y) is faster
976
// double dist = Math.sqrt(Math.pow((x1-x2),2) - Math.pow((y1-y2),2));
977
// return dist <= tolerance ? true : false;
978
// }
979

980     protected void initPathSegments()
981     {
982       pathSegments = new ArrayList JavaDoc();
983       int index = 0;
984       float[] coords = new float[6];
985       for (PathIterator JavaDoc pi = getPathIterator(null); !pi.isDone(); pi.next())
986       {
987         int segType = pi.currentSegment(coords);
988         switch (segType)
989         {
990             case (PathIterator.SEG_MOVETO):
991               PathSegment ps = new PathSegment(segType, index, coords, this);
992                 pathSegments.add(ps);
993                 index = index + 2;
994               break;
995             case (PathIterator.SEG_LINETO):
996               PathSegment ps1 = new PathSegment(segType, index, coords, this);
997                 pathSegments.add(ps1);
998                 index = index + 2;
999               break;
1000            case (PathIterator.SEG_QUADTO):
1001              PathSegment ps2 = new PathSegment(segType, index, coords, this);
1002                pathSegments.add(ps2);
1003                index = index + 4;
1004              break;
1005            case (PathIterator.SEG_CUBICTO):
1006              PathSegment ps3 = new PathSegment(segType, index, coords, this);
1007                pathSegments.add(ps3);
1008                index = index + 6;
1009              break;
1010            case (PathIterator.SEG_CLOSE):
1011// PathSegment ps4 = new PathSegment(segType, index, coords, this);
1012
// pathSegments.add(ps4);
1013
// index = index + 2;
1014
break;
1015        }
1016      }
1017    }
1018    
1019    protected List JavaDoc pathSegments;
1020    
1021    public List JavaDoc getPathSegments()
1022    {
1023      if (pathSegments == null)
1024        initPathSegments();
1025        
1026      return pathSegments;
1027    }
1028    
1029    public PathSegment getPathSegment(int index)
1030    {
1031      if (pathSegments == null)
1032        initPathSegments();
1033
1034      return (PathSegment) pathSegments.get(index);
1035    }
1036    
1037    public PathSegment getPathSegmentAt(int x, int y)
1038    {
1039      for (Iterator JavaDoc it = getPathSegments().iterator(); it.hasNext(); ) {
1040        PathSegment ps = (PathSegment) it.next();
1041        int contains = ps.contains(x,y);
1042        if (contains != PathSegment.NO_POINT)
1043          return ps;
1044      }
1045      return null;
1046    }
1047    
1048// public int getPathSegmentIndexAt(int x, int y)
1049
// {
1050
// for (int i = 0; i<getPathSegments().size(); i++)
1051
// {
1052
// PathSegment ps = (PathSegment) getPathSegments().get(i);
1053
// int contains = ps.contains(x,y);
1054
// if (contains != PathSegment.NO_POINT)
1055
// return i;
1056
// }
1057
// return -1;
1058
// }
1059

1060    public int getPointCount()
1061    {
1062      int counter = 0;
1063      for (PathIterator JavaDoc pi = getPathIterator(null); !pi.isDone(); pi.next()) {
1064        counter++;
1065      }
1066      return counter;
1067    }
1068        
1069}
1070
1071
Popular Tags