KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > thoughtriver > open > vectorvisuals > meta > connector > VisualConnectorObject


1 /*
2  * VisualConnectorObject.java
3  *
4  * Created on 10 June 2003, 14:34
5  */

6
7 package com.thoughtriver.open.vectorvisuals.meta.connector;
8
9 import java.awt.*;
10 import java.awt.font.*;
11 import java.awt.geom.*;
12 import java.util.*;
13
14 import com.thoughtriver.open.vectorvisuals.*;
15
16 /**
17  * Instances of this class are used to create visual connections between two
18  * <CODE>VisualObject</CODE>s. <CODE>VisualConnectorObject</CODE>s are
19  * defined through the use of <CODE>EndpointConfiguration</CODE> instances.
20  * The pair of endpoints is connected with a straight line, and can bear a text
21  * label if so desired.
22  *
23  * @author Brandon Franklin
24  * @version $Date: 2006/11/25 08:58:56 $
25  */

26 public class VisualConnectorObject extends VisualObject {
27
28     /**
29      * The <CODE>EndpointConfiguration</CODE> of the first end of this
30      * connector
31      */

32     private EndpointConfiguration endConfig1 = null;
33
34     /**
35      * The <CODE>EndpointConfiguration</CODE> of the second end of this
36      * connector
37      */

38     private EndpointConfiguration endConfig2 = null;
39
40     /** The text of the label, if any */
41     private String JavaDoc labelText = null;
42
43     /** The <CODE>Font</CODE> of the label, if any */
44     private Font labelFont = null;
45
46     /** The <CODE>Brush</CODE> of the label, if any */
47     private Brush labelBrush = null;
48
49     /** Whether or not the label will be locked in a horizontal orientation */
50     private boolean fixedHorizontalLabel = false;
51
52     /**
53      * Creates a new instance of <CODE>VisualConnectorObject</CODE> with the
54      * specified <CODE>Brush</CODE> and endpoints.
55      *
56      * @param lineBrush the <CODE>Brush</CODE> to use to draw the connector
57      * @param endpoint1 the <CODE>EndpointConfiguration</CODE> for the first
58      * endpoint to be connected
59      * @param endpoint2 the <CODE>EndpointConfiguration</CODE> for the second
60      * endpoint to be connected
61      */

62     public VisualConnectorObject(final Brush lineBrush, final EndpointConfiguration endpoint1, final EndpointConfiguration endpoint2) {
63         super(new Area(), lineBrush, null);
64         endConfig1 = endpoint1;
65         endConfig2 = endpoint2;
66     }
67
68     /**
69      * Sets the text label that will be displayed at the center of the connector
70      * line.
71      *
72      * @param label the text of the label
73      * @param font the <CODE>Font</CODE> to render the label with
74      * @param brush the <CODE>Brush</CODE> to render the label with
75      */

76     public void setLabel(final String JavaDoc label, final Font font, final Brush brush) {
77         labelText = label;
78         labelFont = font;
79         labelBrush = brush;
80     }
81
82     /**
83      * Returns the <CODE>String</CODE> that will be displayed as a label on
84      * this connector.
85      *
86      * @return the <CODE>String</CODE> that will be displayed as a label on
87      * this connector
88      */

89     public String JavaDoc getLabelText() {
90         return labelText;
91     }
92
93     /**
94      * Causes this connector to calculate its <CODE>Shape</CODE> in
95      * preparation for an upcoming <CODE>render</CODE> call.
96      */

97     @Override JavaDoc
98     public void prepare() {
99         Point2D firstPoint = null;
100         Point2D secondPoint = null;
101
102         // Find the transforms of the objects relative to our shared parent
103
AffineTransform trans1 = endConfig1.getObject().getTransformRelativeTo(getParent());
104         AffineTransform trans2 = endConfig2.getObject().getTransformRelativeTo(getParent());
105
106         // Create transformed versions of the shapes
107
Shape startShape = endConfig1.getObject().getShape();
108         startShape = trans1.createTransformedShape(startShape);
109         Shape endShape = endConfig2.getObject().getShape();
110         endShape = trans2.createTransformedShape(endShape);
111
112         // For the moment, assume a simplistic edge for the start of the
113
// connector
114
RectangularShape bounds = startShape.getBounds2D();
115         firstPoint = new Point2D.Double(bounds.getCenterX(), bounds.getCenterY());
116
117         if (endConfig2.isEdge()) {
118             // Figure out the closest point on the second object
119
secondPoint = getClosestPointOnShape(firstPoint, endShape);
120
121         }
122         else {
123             bounds = endShape.getBounds2D();
124             secondPoint = new Point2D.Double(bounds.getCenterX(), bounds.getCenterY());
125         }
126
127         // If the connector is supposed to start from an edge, re-examine it
128
if (endConfig1.isEdge()) {
129             firstPoint = getClosestPointOnShape(secondPoint, startShape);
130         }
131
132         // Transform the points back based on our parent's inverse transform
133
try {
134             AffineTransform inverseTransform = getTransformRelativeTo(getParent()).createInverse();
135             firstPoint = inverseTransform.transform(firstPoint, firstPoint);
136             secondPoint = inverseTransform.transform(secondPoint, secondPoint);
137         }
138         catch (NoninvertibleTransformException e) {
139             e.printStackTrace();
140         }
141
142         Line2D line = new Line2D.Double(firstPoint, secondPoint);
143         setShape(line);
144     }
145
146     /**
147      * Renders this <CODE>VisualConnectorObject</CODE> using a straight line
148      * to connect the endpoints.
149      *
150      * @param g the <CODE>Graphics2D</CODE> instance to paint with
151      */

152     @Override JavaDoc
153     public void renderOutline(final Graphics2D g) {
154
155         Line2D line = (Line2D) getShape();
156         if (getLineBrush() != null) {
157             getLineBrush().useOn(g);
158         }
159         g.draw(line);
160
161         // Draw endpoint shapes, if present
162
renderEndpointShape(g, endConfig1.getShape(), line, false);
163         renderEndpointShape(g, endConfig2.getShape(), line, true);
164
165         if (getLabelText() != null) {
166             renderLabel(g, getLabelText(), line);
167         }
168
169     }
170
171     /**
172      * Overridden to disable.
173      *
174      * @param g the <CODE>Graphics2D</CODE> to paint with
175      */

176     @Override JavaDoc
177     public void renderObject(@SuppressWarnings JavaDoc("unused")
178     final Graphics2D g) {
179         // does nothing
180
}
181
182     /**
183      * Renders a <CODE>Shape</CODE> at the end of a <CODE>Line2D</CODE>,
184      * with a rotation based on the slope of the <CODE>Line2D</CODE>.
185      *
186      * @param g the <CODE>Graphics2D</CODE> to paint on
187      * @param shape the <CODE>Shape</CODE> to render
188      * @param line the <CODE>Line2D</CODE> that the endpoint is being rendered
189      * based on
190      * @param end true if the second end of the <CODE>Line2D</CODE> is to be
191      * used for rendering, false if the first end should
192      */

193     protected void renderEndpointShape(final Graphics2D g, final Shape shape, final Line2D line, final boolean end) {
194
195         // If no shape, return immediately
196
if (shape == null) {
197             return;
198         }
199
200         // Create an appropriate translate transform
201
AffineTransform trans = null;
202         if (end) {
203             trans = AffineTransform.getTranslateInstance(line.getX2(), line.getY2());
204         }
205         else {
206             trans = AffineTransform.getTranslateInstance(line.getX1(), line.getY1());
207         }
208
209         // Calculate the rotation of the shape
210
double xChange = line.getX2() - line.getX1();
211         double yChange = line.getY2() - line.getY1();
212         double theta = Math.atan2(yChange, xChange);
213         if (end) {
214             trans.rotate(theta - (Math.PI / 2.0));
215         }
216         else {
217             trans.rotate((Math.PI / 2.0) + theta);
218         }
219
220         // Transform the shape
221
Shape transformedShape = trans.createTransformedShape(shape);
222
223         // Paint it
224
g.draw(transformedShape);
225     }
226
227     /**
228      * Renders a label on this connector based on the line segment provided.
229      * Typically, the label will be positioned at the point that is exactly
230      * halfway along the line or curve of this segment of the connector.
231      *
232      * @param g the <CODE>Graphics2D</CODE> to render the label on
233      * @param labelText the text to render
234      * @param line the line on which to render the label
235      */

236     protected void renderLabel(final Graphics2D g, final String JavaDoc labelText, final Line2D line) {
237         Graphics2D newGraphics = (Graphics2D) g.create();
238
239         labelBrush.useOn(newGraphics);
240
241         // Build the GlyphVector for the label
242
FontRenderContext frc = g.getFontRenderContext();
243         GlyphVector glyphVector = labelFont.createGlyphVector(frc, labelText);
244
245         // Calculate the center point of the line
246
Point2D firstPoint = line.getP1();
247         Point2D secondPoint = line.getP2();
248         RectangularShape rect = new Rectangle2D.Double(firstPoint.getX(), firstPoint.getY(), secondPoint.getX()
249                 - firstPoint.getX(), secondPoint.getY() - firstPoint.getY());
250         Point2D halfwayPoint = new Point2D.Double(rect.getCenterX(), rect.getCenterY());
251
252         // Calculate the offset that nicely centers the label rectangle
253
Rectangle2D labelRect = glyphVector.getVisualBounds();
254         double xOffset = -(labelRect.getWidth() / 2);
255         newGraphics.translate(halfwayPoint.getX(), halfwayPoint.getY());
256
257         if (isFixedHorizontalLabel()) {
258
259             // Y Offset is only applied for horizontal labels
260
double yOffset = (labelRect.getHeight() / 2);
261             newGraphics.translate(0, yOffset);
262
263         }
264         else {
265
266             // If labels are not fixed horizontal, rotate to align with the line
267
double xChange = line.getX2() - line.getX1();
268             double yChange = line.getY2() - line.getY1();
269             double theta = Math.atan2(yChange, xChange);
270             newGraphics.rotate(theta);
271
272             // Apply a tiny bit of space between the text and the line
273
newGraphics.translate(0, -3);
274         }
275
276         newGraphics.drawGlyphVector(glyphVector, (float) xOffset, 0);
277         newGraphics.dispose();
278     }
279
280     /**
281      * Returns whether or not the label is locked in a horizontal position. If
282      * the label is not locked, it will be rotated to align with the connection
283      * line itself.
284      *
285      * @return true if the label is to be locked in a horizontal position, false
286      * otherwise
287      */

288     public boolean isFixedHorizontalLabel() {
289         return fixedHorizontalLabel;
290     }
291
292     /**
293      * Sets whether or not the label will be locked in a horizontal position. If
294      * the label is not locked, it will be rotated to align with the connection
295      * line itself.
296      *
297      * @param horizontal true if the label is to be locked in a horizontal
298      * position, false otherwise
299      */

300     public void setFixedHorizontalLabel(final boolean horizontal) {
301         fixedHorizontalLabel = horizontal;
302     }
303
304     /**
305      * Given a <CODE>Point2D</CODE> and a <CODE>Shape</CODE>, returns the
306      * <CODE>Point2D</CODE> that lies along the <CODE>Shape</CODE> that is
307      * approximately closest to the <CODE>Point2D</CODE>. This will always be
308      * the midpoint of one of the segments that makes up the <CODE>Shape</CODE>.
309      *
310      * @param point the <CODE>Point2D</CODE> to find the closest point to
311      * @param shape the <CODE>Shape</CODE> along with the point must lie
312      * @return the approximately closest <CODE>Point2D</CODE> to the supplied
313      * <CODE>Point2D</CODE> that lies along the <CODE>Shape</CODE>
314      */

315     private Point2D getClosestPointOnShape(final Point2D point, final Shape shape) {
316
317         double[] lastCoords = null;
318         double[] lastMoveToCoords = null;
319         PathIterator pIter = shape.getPathIterator(null);
320         ArrayList<SegmentInfo> segments = new ArrayList<SegmentInfo>();
321
322         // Convert all of the segments into SegmentInfo objects
323
while (!pIter.isDone()) {
324
325             SegmentInfo segInfo = new SegmentInfo();
326             segInfo.type = pIter.currentSegment(segInfo.coords);
327
328             // Might need to record a MOVETO
329
if (segInfo.type == PathIterator.SEG_MOVETO) {
330                 lastMoveToCoords = segInfo.coords;
331
332                 // In the case of a CLOSE, we have to fill in the info
333
}
334             else if (segInfo.type == PathIterator.SEG_CLOSE) {
335                 segInfo.coords = lastMoveToCoords;
336             }
337
338             segInfo.prevCoords = lastCoords;
339             lastCoords = segInfo.coords;
340             pIter.next();
341
342             segments.add(segInfo);
343         }
344
345         // Now find the closest center point
346
double shortestDist = Double.MAX_VALUE;
347         int shortestIndex = 0;
348         for (int i = 0; i < segments.size(); i++) {
349             SegmentInfo segInfo = segments.get(i);
350
351             double dist = point.distance(segInfo.getCenterPoint());
352             if (dist < shortestDist) {
353                 shortestDist = dist;
354                 shortestIndex = i;
355             }
356         }
357
358         SegmentInfo closestSegment = segments.get(shortestIndex);
359         return closestSegment.getCenterPoint();
360     }
361
362     /**
363      * Instances of this class are used to store segment information found using
364      * a <CODE>PathIterator</CODE> in a <CODE>List</CODE>.
365      */

366     static private class SegmentInfo {
367
368         /** The type of the segment, as defined in <CODE>PathIterator</CODE> */
369         public int type;
370
371         /** The coordinates associated with the segment */
372         public double[] coords = null;
373
374         /** The coordinates prior to this segment's in the path */
375         public double[] prevCoords = null;
376
377         /**
378          * Creates a new instance of <CODE>SegmentInfo</CODE>, ready for use.
379          */

380         public SegmentInfo() {
381             coords = new double[6];
382         }
383
384         /**
385          * Calculates and returns the coordinates of the center of this segment.
386          *
387          * @return the <CODE>Point2D</CODE> representing the center of this
388          * segment
389          */

390         public Point2D getCenterPoint() {
391
392             double[] centerCoords = new double[2];
393
394             if (prevCoords == null) {
395                 centerCoords[0] = coords[0];
396                 centerCoords[1] = coords[1];
397             }
398             else {
399
400                 RectangularShape rect = new Rectangle2D.Double(prevCoords[0], prevCoords[1], coords[0]
401                         - prevCoords[0], coords[1] - prevCoords[1]);
402
403                 centerCoords[0] = rect.getCenterX();
404                 centerCoords[1] = rect.getCenterY();
405             }
406
407             return new Point2D.Double(centerCoords[0], centerCoords[1]);
408         }
409
410     }
411
412 }
413
Popular Tags