001: /* ====================================================================
002: * The JRefactory License, Version 1.0
003: *
004: * Copyright (c) 2001 JRefactory. All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions
008: * are met:
009: *
010: * 1. Redistributions of source code must retain the above copyright
011: * notice, this list of conditions and the following disclaimer.
012: *
013: * 2. Redistributions in binary form must reproduce the above copyright
014: * notice, this list of conditions and the following disclaimer in
015: * the documentation and/or other materials provided with the
016: * distribution.
017: *
018: * 3. The end-user documentation included with the redistribution,
019: * if any, must include the following acknowledgment:
020: * "This product includes software developed by the
021: * JRefactory (http://www.sourceforge.org/projects/jrefactory)."
022: * Alternately, this acknowledgment may appear in the software itself,
023: * if and wherever such third-party acknowledgments normally appear.
024: *
025: * 4. The names "JRefactory" must not be used to endorse or promote
026: * products derived from this software without prior written
027: * permission. For written permission, please contact seguin@acm.org.
028: *
029: * 5. Products derived from this software may not be called "JRefactory",
030: * nor may "JRefactory" appear in their name, without prior written
031: * permission of Chris Seguin.
032: *
033: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
034: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
035: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
036: * DISCLAIMED. IN NO EVENT SHALL THE CHRIS SEGUIN OR
037: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
038: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
039: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
040: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
041: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
042: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
043: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
044: * SUCH DAMAGE.
045: * ====================================================================
046: *
047: * This software consists of voluntary contributions made by many
048: * individuals on behalf of JRefactory. For more information on
049: * JRefactory, please see
050: * <http://www.sourceforge.org/projects/jrefactory>.
051: */
052: package org.acm.seguin.uml.line;
053:
054: import java.awt.Color;
055: import java.awt.Component;
056: import java.awt.Point;
057: import java.awt.Rectangle;
058: import java.awt.Graphics;
059: import java.awt.Graphics2D;
060: import java.awt.Stroke;
061: import java.awt.BasicStroke;
062: import java.awt.event.ComponentListener;
063: import java.awt.event.ComponentEvent;
064: import java.io.PrintWriter;
065: import java.util.StringTokenizer;
066: import org.acm.seguin.uml.UMLType;
067:
068: /**
069: * SegmentedLine. <P>
070: *
071: * For future builds, it may be worth considering caching the points above and
072: * below the line for an efficiency increase when there are lots of lines to
073: * paint.
074: *
075: *@author Chris Seguin
076: *@author Mike Atkinson
077: *@created July 28, 1999
078: */
079: public class SegmentedLine implements ComponentListener {
080: /**
081: * The set of vertices on the line
082: */
083: protected Vertex[] vertices;
084:
085: /**
086: * This array is used only during the paint method for drawing polylines
087: */
088: protected int[] Xs;
089: /**
090: * This array is used only during the paint method for drawing polylines
091: */
092: protected int[] Ys;
093: private EndPointPanel startPanel;
094: private EndPointPanel endPanel;
095: private int activeVertex;
096: private Segment workingSegment;
097: /**
098: * Description of the Field
099: */
100: protected double scalingFactor;
101:
102: private static final double SHORT_BACK = 17.32050807568877;
103: private static final Stroke FULL_STROKE = new BasicStroke(1,
104: BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 20);
105:
106: // 8.660254037844386;
107:
108: /**
109: * Constructor for the SegmentedLine object
110: *
111: *@param start Panel that the segmented line starts at
112: *@param end End point of the panel
113: */
114: public SegmentedLine(EndPointPanel start, EndPointPanel end) {
115: // Remember the start and end panels
116: startPanel = start;
117: endPanel = end;
118:
119: scalingFactor = 1.0;
120:
121: // Create and initialize vertices
122: vertices = new Vertex[2];
123: workingSegment = new Segment();
124: initEndPoints();
125:
126: // No vertices are active
127: activeVertex = -1;
128:
129: // Create buffers for drawing polylines
130: Xs = new int[5];
131: Ys = new int[5];
132:
133: // Add this as a listener
134: startPanel.addComponentListener(this );
135: endPanel.addComponentListener(this );
136:
137: if (startPanel == endPanel) {
138: Rectangle rect = startPanel.getBounds();
139:
140: int x = rect.x + rect.width / 2;
141: int y = rect.y + rect.height + 10;
142: insertAt(1, new Vertex(new Point(x, y)));
143:
144: x = rect.x - 10;
145: y = rect.y + rect.height + 10;
146: insertAt(2, new Vertex(new Point(x, y)));
147:
148: x = rect.x - 10;
149: y = rect.y + rect.height / 2;
150: insertAt(3, new Vertex(new Point(x, y)));
151:
152: updateEnd();
153: }
154: }
155:
156: /**
157: * Determines if both the start and end points are selected
158: *
159: *@return true when both are selected
160: */
161: public boolean isBothEndsSelected() {
162: return startPanel.isSelected() && endPanel.isSelected();
163: }
164:
165: public EndPointPanel getStartPanel() {
166: return startPanel;
167: }
168:
169: public EndPointPanel getEndPanel() {
170: return endPanel;
171: }
172:
173: /**
174: * Draws the segmented line
175: *
176: *@param g Description of Parameter
177: */
178: public void paint(Graphics g) {
179: Graphics2D g2d = (Graphics2D) g;
180: for (int ndx = 0; ndx < vertices.length; ndx++) {
181: Xs[ndx] = vertices[ndx].getX();
182: Ys[ndx] = vertices[ndx].getY();
183: vertices[ndx].paint(g);
184: }
185: Point shortPoint = getShortPoint();
186: Xs[vertices.length - 1] = shortPoint.x;
187: Ys[vertices.length - 1] = shortPoint.y;
188:
189: // Use a dashed stroke
190: Stroke stroke = g2d.getStroke();
191: g2d.setStroke(getStroke());
192:
193: // Draw lines
194: g2d.setColor(getColor());
195: g2d.drawPolyline(Xs, Ys, vertices.length);
196:
197: // Reset the stroke
198: g2d.setStroke(stroke);
199: drawArrow(g2d);
200: }
201:
202: protected Stroke getStroke() {
203: return FULL_STROKE;
204: }
205:
206: /**
207: * Description of the Method
208: *
209: *@param way Description of Parameter
210: */
211: public void select(boolean way) {
212: for (int ndx = 0; ndx < vertices.length; ndx++) {
213: vertices[ndx].select(way);
214: vertices[ndx].active(false);
215: }
216: }
217:
218: /**
219: * Description of the Method
220: *
221: *@param attempt Description of Parameter
222: *@return Description of the Returned Value
223: */
224: public boolean hit(Point attempt) {
225: activeVertex = hitVertex(attempt);
226: if (activeVertex == -1) {
227: int insertAt = hitSegment(attempt);
228: if (insertAt == -1) {
229: select(false);
230: return false;
231: }
232:
233: Vertex newOne = new Vertex(attempt);
234: insertAt(insertAt, newOne);
235: select(true);
236: newOne.active(true);
237: activeVertex = insertAt;
238: } else {
239: select(true);
240: vertices[activeVertex].active(true);
241: }
242:
243: return (activeVertex >= 0);
244: }
245:
246: /**
247: * Drag the current vertex
248: *
249: *@param point New location of the current vertex
250: */
251: public void drag(Point point) {
252: if ((activeVertex > 0) && (activeVertex < vertices.length - 1)) {
253: vertices[activeVertex].move(point);
254: if (activeVertex == 1) {
255: updateStart();
256: }
257: if (activeVertex == vertices.length - 2) {
258: updateEnd();
259: }
260: }
261: }
262:
263: /**
264: * The point was dropped
265: */
266: public void drop() {
267: if ((activeVertex > 0) && (activeVertex < vertices.length - 1)) {
268: vertices[activeVertex].active(false);
269:
270: if (shouldDelete(activeVertex)) {
271: deleteVertex(activeVertex);
272: }
273:
274: activeVertex = -1;
275: }
276: }
277:
278: /**
279: * Description of the Method
280: *
281: *@param cevt Description of Parameter
282: */
283: public void componentHidden(ComponentEvent cevt) {
284: }
285:
286: /**
287: * Description of the Method
288: *
289: *@param cevt Description of Parameter
290: */
291: public void componentMoved(ComponentEvent cevt) {
292: Component moved = cevt.getComponent();
293: if (vertices.length == 2) {
294: updateStart();
295: updateEnd();
296: } else if (moved.equals(startPanel)) {
297: updateStart();
298: } else if (moved.equals(endPanel)) {
299: updateEnd();
300: }
301: }
302:
303: /**
304: * Description of the Method
305: *
306: *@param cevt Description of Parameter
307: */
308: public void componentResized(ComponentEvent cevt) {
309: Component moved = cevt.getComponent();
310: if (moved.equals(startPanel)) {
311: updateStart();
312: } else if (moved.equals(endPanel)) {
313: updateEnd();
314: }
315: }
316:
317: /**
318: * Description of the Method
319: *
320: *@param cevt Description of Parameter
321: */
322: public void componentShown(ComponentEvent cevt) {
323: }
324:
325: /**
326: * Saves a segmented to the output stream
327: *
328: *@param output the output stream
329: */
330: public void save(PrintWriter output) {
331: output.print("S[");
332: saveStartPanel(output);
333: output.print(",");
334: saveEndPanel(output);
335: output.print("]");
336: saveVertices(output);
337: output.println("");
338: }
339:
340: /**
341: * Determines whether this panel matches the two desired end points
342: *
343: *@param start the starting panel to be matched
344: *@param end the ending panel to be matched
345: *@return true if it matches
346: */
347: public boolean match(EndPointPanel start, EndPointPanel end) {
348: return (start.equals(startPanel) && end.equals(endPanel));
349: }
350:
351: /**
352: * Loads a segmented line from a buffer
353: *
354: *@param buffer the buffer containing the vertices
355: */
356: public void load(String buffer) {
357: StringTokenizer tok = new StringTokenizer(buffer, ":");
358: String countString = tok.nextToken();
359: int count;
360: try {
361: count = Integer.parseInt(countString);
362: } catch (NumberFormatException nfe) {
363: return;
364: }
365: vertices = new Vertex[count];
366:
367: String rest = tok.nextToken();
368: tok = new StringTokenizer(rest, "(),");
369:
370: for (int ndx = 0; ndx < count; ndx++) {
371: String strX = tok.nextToken();
372: String strY = tok.nextToken();
373:
374: try {
375: int x = Integer.parseInt(strX);
376: int y = Integer.parseInt(strY);
377:
378: vertices[ndx] = new Vertex(new Point(x, y));
379: } catch (NumberFormatException nfe) {
380: vertices[ndx] = new Vertex(new Point(0, 0));
381: }
382: }
383:
384: int modFiveSize = count / 5 + 1;
385: Xs = new int[modFiveSize * 5];
386: Ys = new int[modFiveSize * 5];
387: }
388:
389: /**
390: * Shifts the entire line
391: *
392: *@param x the amount to shift in the x coordinate
393: *@param y the amount to shift in the y coordinate
394: */
395: public void shift(int x, int y) {
396: for (int ndx = 0; ndx < vertices.length; ndx++) {
397: vertices[ndx].shift(x, y);
398: }
399: }
400:
401: /**
402: * Scales the entire line
403: *
404: *@param value the amount to scale
405: */
406: public void scale(double value) {
407: for (int ndx = 0; ndx < vertices.length; ndx++) {
408: vertices[ndx].scale(value);
409: }
410: scalingFactor = value;
411: }
412:
413: /**
414: * Determine the point at which the last segment should stop
415: *
416: *@return the point
417: */
418: protected Point getShortPoint() {
419: int last = vertices.length;
420: workingSegment.reset(vertices[last - 2].getPoint(),
421: vertices[last - 1].getPoint());
422:
423: double t = workingSegment.findFromEnd(SHORT_BACK
424: * scalingFactor);
425:
426: return workingSegment.getPoint(t);
427: }
428:
429: /**
430: * Determine the point at which the last segment should stop
431: *
432: *@return the point
433: */
434: protected Point getArrowPointAbove() {
435: int last = vertices.length;
436: workingSegment.reset(vertices[last - 2].getPoint(),
437: vertices[last - 1].getPoint());
438:
439: double t = workingSegment.findFromEnd(SHORT_BACK
440: * scalingFactor);
441:
442: return workingSegment.aboveLine(t, 10 * scalingFactor);
443: }
444:
445: /**
446: * Determine the point at which the last segment should stop
447: *
448: *@return the point
449: */
450: protected Point getArrowPointBelow() {
451: int last = vertices.length;
452: workingSegment.reset(vertices[last - 2].getPoint(),
453: vertices[last - 1].getPoint());
454:
455: double t = workingSegment.findFromEnd(SHORT_BACK
456: * scalingFactor);
457:
458: return workingSegment.belowLine(t, 10 * scalingFactor);
459: }
460:
461: /**
462: * Updates the location of the end vertex
463: */
464: protected void updateEnd() {
465: int last = vertices.length;
466: Rectangle right = endPanel.getBounds();
467: workingSegment.reset(vertices[last - 2].getPoint(), right);
468:
469: double t1 = workingSegment.intersect(right);
470:
471: vertices[last - 1].move(workingSegment.getPoint(t1));
472: }
473:
474: protected Color getColor() {
475: return Color.BLACK;
476: }
477:
478: /**
479: * Draws the arrow and the last segment
480: *
481: *@param g the graphics object
482: */
483: protected void drawArrow(Graphics2D g) {
484: Point shortPt = getShortPoint();
485:
486: int last = vertices.length;
487:
488: Point end = vertices[last - 1].getPoint();
489: Point above = getArrowPointAbove();
490: Point below = getArrowPointBelow();
491:
492: Xs[0] = (int) end.getX();
493: Xs[1] = (int) above.getX();
494: Xs[2] = (int) below.getX();
495: Xs[3] = (int) end.getX();
496:
497: Ys[0] = (int) end.getY();
498: Ys[1] = (int) above.getY();
499: Ys[2] = (int) below.getY();
500: Ys[3] = (int) end.getY();
501:
502: g.drawPolyline(Xs, Ys, 4);
503: }
504:
505: /**
506: * Saves the start panel
507: *
508: *@param output the output stream
509: */
510: protected void saveStartPanel(PrintWriter output) {
511: savePanel(output, startPanel);
512: }
513:
514: /**
515: * Saves the end panel
516: *
517: *@param output the output stream
518: */
519: protected void saveEndPanel(PrintWriter output) {
520: savePanel(output, endPanel);
521: }
522:
523: /**
524: * Saves a panel
525: *
526: *@param output the output stream
527: *@param panel the panel to be saved
528: */
529: protected void savePanel(PrintWriter output, EndPointPanel panel) {
530: if (panel instanceof UMLType) {
531: output.print(((UMLType) panel).getID());
532: } else {
533: output.print("???");
534: }
535: }
536:
537: /**
538: * Saves the vertices
539: *
540: *@param output the output stream
541: */
542: protected void saveVertices(PrintWriter output) {
543: output.print("{");
544: output.print(vertices.length);
545: output.print(":");
546: vertices[0].save(output);
547: for (int ndx = 1; ndx < vertices.length; ndx++) {
548: output.print(",");
549: vertices[ndx].save(output);
550: }
551: output.print("}");
552: }
553:
554: /**
555: * Initialize the end points of the segmented line.
556: */
557: private void initEndPoints() {
558: Rectangle left = startPanel.getBounds();
559: Rectangle right = endPanel.getBounds();
560: workingSegment.reset(left, right);
561:
562: double t0 = workingSegment.intersect(left);
563: double t1 = workingSegment.intersect(right);
564:
565: vertices[0] = new Vertex(workingSegment.getPoint(t0));
566: vertices[vertices.length - 1] = new Vertex(workingSegment
567: .getPoint(t1));
568: }
569:
570: /**
571: * Updates the location the start vertex
572: */
573: private void updateStart() {
574: Rectangle left = startPanel.getBounds();
575: workingSegment.reset(left, vertices[1].getPoint());
576:
577: double t0 = workingSegment.intersect(left);
578:
579: vertices[0].move(workingSegment.getPoint(t0));
580: }
581:
582: /**
583: * Description of the Method
584: *
585: *@param ndx Description of Parameter
586: *@param newOne Description of Parameter
587: */
588: private void insertAt(int ndx, Vertex newOne) {
589: Vertex[] newArray = new Vertex[vertices.length + 1];
590: System.arraycopy(vertices, 0, newArray, 0, ndx);
591: System.arraycopy(vertices, ndx, newArray, ndx + 1,
592: vertices.length - ndx);
593: newArray[ndx] = newOne;
594: vertices = newArray;
595:
596: if (vertices.length > Xs.length) {
597: Xs = new int[Xs.length + 5];
598: Ys = new int[Ys.length + 5];
599: }
600: }
601:
602: /**
603: * Have we hit a particular vertex
604: *
605: *@param point the mouse input
606: *@return the index of the vertex that we have hit
607: */
608: private int hitVertex(Point point) {
609: for (int ndx = 1; ndx < vertices.length - 1; ndx++) {
610: if (vertices[ndx].hit(point)) {
611: return ndx;
612: }
613: }
614:
615: return -1;
616: }
617:
618: /**
619: * Description of the Method
620: *
621: *@param attempt Description of Parameter
622: *@return Description of the Returned Value
623: */
624: private int hitSegment(Point attempt) {
625: for (int ndx = 0; ndx < vertices.length - 1; ndx++) {
626: Point start = vertices[ndx].getPoint();
627: Point end = vertices[ndx + 1].getPoint();
628: workingSegment.reset(start, end);
629:
630: double distance = workingSegment.distanceToPoint(attempt);
631: if ((distance > 0) && (distance < 3)) {
632: return ndx + 1;
633: }
634: }
635:
636: return -1;
637: }
638:
639: /**
640: * Should delete the vertices
641: *
642: *@param active Description of Parameter
643: *@return Description of the Returned Value
644: */
645: private boolean shouldDelete(int active) {
646: Point pt = vertices[active].getPoint();
647:
648: // Check if they are in the end rectangle
649: if (startPanel.getBounds().contains(pt)
650: || endPanel.getBounds().contains(pt)) {
651: return true;
652: }
653:
654: // Check the other vertices
655: for (int ndx = 0; ndx < vertices.length; ndx++) {
656: if (ndx != active) {
657: if (vertices[ndx].hit(pt)) {
658: return true;
659: }
660: }
661: }
662:
663: // Don't delete it
664: return false;
665: }
666:
667: /**
668: * Delete a vertex
669: *
670: *@param dead Description of Parameter
671: */
672: private void deleteVertex(int dead) {
673: Vertex[] newArray = new Vertex[vertices.length - 1];
674: System.arraycopy(vertices, 0, newArray, 0, dead);
675: System.arraycopy(vertices, dead + 1, newArray, dead,
676: vertices.length - dead - 1);
677: vertices = newArray;
678: }
679: }
|