source: trunk/src/org/expeditee/items/Line.java@ 286

Last change on this file since 286 was 278, checked in by ra33, 16 years ago

Fixed some bugs...
AbstractCharts can not be copied, resized, etc

File size: 29.3 KB
Line 
1package org.expeditee.items;
2
3import java.awt.BasicStroke;
4import java.awt.Color;
5import java.awt.Graphics2D;
6import java.awt.Point;
7import java.awt.Polygon;
8import java.awt.Rectangle;
9import java.awt.Shape;
10import java.awt.Stroke;
11import java.awt.geom.AffineTransform;
12import java.util.ArrayList;
13import java.util.Collection;
14import java.util.HashSet;
15import java.util.Iterator;
16import java.util.LinkedList;
17import java.util.List;
18
19import org.expeditee.gui.DisplayIO;
20import org.expeditee.gui.Frame;
21
22/**
23 * Implements a line that is drawn on the screen. A line is represented as two
24 * Items that share a common Line ID.
25 *
26 * @author jdm18
27 *
28 */
29public class Line extends Item {
30
31 private static final int AUTO_ARROWHEAD_LENGTH = -1;
32
33 private static final int ARROW_HEAD_TO_LENGTH_RATIO = 8;
34
35 private static final int MINIMUM_ARROW_HEAD_LENGTH = 8;
36
37 private static final int MAXIMUM_ARROW_HEAD_LENGTH = 60;
38
39 private Item _start;
40
41 private Item _end;
42
43 // old point locations, used for clearing
44 private Point _startOffset = new Point(0, 0);
45
46 private Point _endOffset = new Point(0, 0);
47
48 // brush strokes used for painting this line and highlighting
49 private Stroke _lineStroke = new BasicStroke(DEFAULT_THICKNESS, CAP, JOIN,
50 4.0F);
51
52 Stroke getStroke() {
53 return _lineStroke;
54 }
55
56 /**
57 * Constructs a new Line with the given start and end point and Item ID.
58 *
59 * @param start
60 * The starting Point of this Line.
61 * @param end
62 * The end Point of this Line.
63 * @param id
64 * The Item ID of this Line.
65 */
66 public Line(Item start, Item end, int id) {
67 super();
68 setID(id);
69 _start = start;
70 _end = end;
71
72 // update the two end points to add this line
73 _start.addLine(this);
74 _end.addLine(this);
75
76 // initialise the line size
77 float thick = (_start.getThickness() + _end.getThickness()) / 2.0f;
78
79 if (thick < 0)
80 setThickness(DEFAULT_THICKNESS);
81 else{
82 refreshStroke(thick);
83 }
84
85 }
86
87 private void refreshStroke(float thick) {
88 int[] pattern = _start.getLinePattern();
89 if (pattern == null)
90 pattern = _end.getLinePattern();
91
92 if (pattern != null) {
93 float[] dash = new float[pattern.length];
94 for (int i = 0; i < pattern.length; i++)
95 dash[i] = (float) pattern[i];
96 _lineStroke = new BasicStroke(Math.max(thick,
97 MINIMUM_PAINT_THICKNESS), CAP, JOIN, 10f, dash, 0.0f);
98 } else {
99 _lineStroke = new BasicStroke(Math.max(thick,
100 MINIMUM_PAINT_THICKNESS), CAP, JOIN);
101 }
102
103 updatePolygon();
104 }
105
106 /**
107 * Returns the Dot at the start of this Line.
108 *
109 * @return The Dot that is the start of this line.
110 */
111 public Item getStartItem() {
112 return _start;
113 }
114
115 /**
116 * Returns the Dot that is the end of this Line.
117 *
118 * @return The Dot that is the end of this line.
119 */
120 public Item getEndItem() {
121 return _end;
122 }
123
124 /**
125 * Returns the Dot at the opposite end to the Dot given. If the given Dot is
126 * not part of this line, null is returned.
127 *
128 * @param fromThis
129 * The Dot at the opposite end than this Dot is returned
130 * @return The Dot at the opposite end of this Line to the given Dot, or
131 * Null if the given Dot is not on this Line.
132 */
133 public Item getOppositeEnd(Item fromThis) {
134 if (_start == fromThis)
135 return _end;
136
137 if (_end == fromThis)
138 return _start;
139
140 return null;
141 }
142
143 /**
144 * Replaces either the start or end point of this Line with the given
145 * LineEnd. This is used when merging LineEnds. It will also add and remove
146 * the line from the LineEnds as well as adjust constraints.
147 *
148 * @param replace
149 * Either the start or end Dot of this Line indicating which is
150 * to be replaced.
151 * @param with
152 * The Dot to replace the start or end Dot with.
153 */
154 public void replaceLineEnd(Item replace, Item with) {
155 Item otherEnd = null;
156 if (_start == replace) {
157 setStartItem(with);
158 otherEnd = _end;
159 } else if (_end == replace) {
160 setEndItem(with);
161 otherEnd = _start;
162 }
163 // if no end was replaced
164 if (otherEnd == null)
165 return;
166 // Copy the constraints list so the endPoints list can be modified
167 List<Constraint> constraints = new ArrayList<Constraint>(replace
168 .getConstraints());
169 // Now change all the constraints for this line only
170 for (int i = 0; i < constraints.size(); i++) {
171 Constraint c = constraints.get(i);
172 if (c.contains(otherEnd)) {
173 c.replaceEnd(replace, with);
174 }
175 }
176 }
177
178 public void setStartItem(Item start) {
179 if (start != null) {
180 _start.removeLine(this);
181
182 /*
183 * if (!(start instanceof Dot)) { _startOffset.x = _start.getX() -
184 * start.getX(); _startOffset.y = _start.getY() - start.getY(); }
185 * else
186 */{
187 _startOffset.x = 0;
188 _startOffset.y = 0;
189 }
190
191 _start = start;
192 _start.addLine(this);
193 updatePolygon();
194 }
195 }
196
197 public void setEndItem(Item end) {
198 if (end != null) {
199 _end.removeLine(this);
200
201 /*
202 * if (!(end instanceof Dot)) { _endOffset.x = _end.getX() -
203 * end.getX(); _endOffset.y = _end.getY() - end.getY(); } else
204 */{
205 _endOffset.x = 0;
206 _endOffset.y = 0;
207 }
208
209 _end = end;
210 _end.addLine(this);
211 updatePolygon();
212 }
213 }
214
215 /**
216 * Returns the Y value of the start Dot of this Line. To determine the end
217 * Dot Y value use getEndDot().getY()
218 *
219 * @return The Y value of the starting Dot of this Line.
220 * @see #getStartDot()
221 * @see #getEndDot()
222 */
223 public int getY() {
224 return _start.getY();
225 }
226
227 /**
228 * Returns the X value of the start Dot of this Line. To determine the end
229 * Dot X value use getEndDot().getX()
230 *
231 * @return The X value of the starting Dot of this Line.
232 * @see #getStartDot()
233 * @see #getEndDot()
234 */
235 public int getX() {
236 // return 0;
237 return _start.getX();
238 }
239
240 @Override
241 public void setX(float x) {
242 }
243
244 @Override
245 public void setY(float y) {
246 }
247
248 @Override
249 public void setPosition(float x, float y) {
250 }
251
252 @Override
253 public String getLink() {
254 return null;
255 }
256
257 @Override
258 public void setLinePattern(int[] pattern) {
259 super.setLinePattern(pattern);
260
261 float thick = getThickness();
262
263 if (thick < 0)
264 thick = DEFAULT_THICKNESS;
265
266 if (pattern != null) {
267 float[] dash = new float[pattern.length];
268 for (int i = 0; i < pattern.length; i++)
269 dash[i] = (float) pattern[i];
270 _lineStroke = new BasicStroke(Math.max(thick, MINIMUM_THICKNESS),
271 CAP, JOIN, 10f, dash, 0.0f);
272 } else
273 _lineStroke = new BasicStroke(Math.max(thick, MINIMUM_THICKNESS),
274 CAP, JOIN);
275
276 if (_start.getLinePattern() != pattern)
277 _start.setLinePattern(pattern);
278
279 if (_end.getLinePattern() != pattern)
280 _end.setLinePattern(pattern);
281
282 invalidateAll();
283 }
284
285 @Override
286 public void paint(Graphics2D g) {
287 if (!isVisible())
288 return;
289
290 // Dont paint lines with thickness 0 if they are the border on a filled
291 // shape
292 if (dontPaint())
293 return;
294
295 g.setColor(getPaintColor());
296
297 // if (this._mode == Item.SelectedMode.Disconnect)
298 // System.out.println("Disconnect mode!");
299 g.setStroke(_lineStroke);
300 // Get a path of points
301 int[][][] paths = getPaths();
302 for (int i = 0; i < paths.length; i++) {
303 int[][] path = paths[i];
304 int last = path[0].length - 1;
305 if (path[0][0] == path[0][last] && path[1][0] == path[1][last]) {
306 g.drawPolygon(path[0], path[1], last);
307 } else {
308 g.drawPolyline(path[0], path[1], last + 1);
309 }
310 }
311 // paint the arrowhead (if necessary)
312 paintArrows(g);
313
314 if (showLineHighlight() && isHighlighted()) {
315 g.setColor(getHighlightColor());
316 g.setStroke(HIGHLIGHT_STROKE);
317 ((Graphics2D) g).draw(this.getArea());
318 }
319 }
320
321 protected boolean dontPaint() {
322 return getThickness() <= 0 && _start.isEnclosed()
323 && _start.getFillColor() != null;
324 }
325
326 protected int[][][] getPaths() {
327 List<List<Point>> pointPaths = new LinkedList<List<Point>>();
328 Collection<Line> visited = new HashSet<Line>();
329 LinkedList<Line> toExplore = new LinkedList<Line>();
330 toExplore.add(this);
331 while (toExplore.size() > 0) {
332 Line nextLine = toExplore.remove(0);
333 // Start at the item we have already explored... unless both have
334 // been explored
335 if (!visited.contains(nextLine)) {
336 pointPaths.add(nextLine.getPath(visited, toExplore));
337 }
338 }
339 // Put the paths into int arrays
340 int[][][] paths = new int[pointPaths.size()][][];
341 Iterator<List<Point>> iter = pointPaths.iterator();
342
343 for (int i = 0; i < paths.length; i++) {
344 List<Point> pointPath = iter.next();
345 int[][] path = new int[2][pointPath.size()];
346 paths[i] = path;
347 // Add all the x and y's to the array
348 for (int j = 0; j < path[0].length; j++) {
349 path[0][j] = pointPath.get(j).x;
350 path[1][j] = pointPath.get(j).y;
351 }
352 }
353 return paths;
354 }
355
356 protected List<Point> getPath(Collection<Line> visited,
357 LinkedList<Line> toExplore) {
358 LinkedList<Point> points = new LinkedList<Point>();
359 // put the start item points into our list
360 Item start = getStartItem();
361 Item end = getEndItem();
362 visited.add(this);
363
364 start.appendPath(visited, points, true, toExplore);
365 end.appendPath(visited, points, false, toExplore);
366 return points;
367 }
368
369 public void paintArrows(Graphics2D g) {
370 if (dontPaint())
371 return;
372
373 g.setColor(getPaintColor());
374 g
375 .setStroke(new BasicStroke(getPaintThickness(), CAP,
376 BasicStroke.JOIN_MITER));
377 paintArrow(g, _start, _startOffset, _endOffset);
378 paintArrow(g, _end, _endOffset, _startOffset);
379
380 if (_virtualSpot != null) {
381 _virtualSpot.paint(g);
382 invalidateVirtualSpot();
383 _virtualSpot = null;
384 }
385 }
386
387 private float getPaintThickness() {
388 float thickness = getThickness();
389 if(thickness > 0){
390 return thickness;
391 }
392 return MINIMUM_PAINT_THICKNESS;
393 }
394
395 private void invalidateVirtualSpot() {
396 assert (_virtualSpot != null);
397
398 invalidate(_virtualSpot.getDrawingArea());
399
400 }
401
402 /**
403 * Based on code from DeSL (Arrow2D) http://sourceforge.net/projects/desl/
404 */
405 private void paintArrow(Graphics2D g, Item withArrow, Point startOffset,
406 Point endOffset) {
407 boolean disconnectMode = withArrow._mode == Item.HighlightMode.Disconnect;
408 // only draw an arrowhead if necessary
409 if (!(this._mode == Item.HighlightMode.Disconnect && disconnectMode)
410 && (!withArrow.hasVisibleArrow() || withArrow.getLines().size() > 1))
411 return;
412
413 int x0, x1, y0, y1;
414
415 x1 = withArrow.getX() + startOffset.x;
416 y1 = withArrow.getY() + startOffset.y;
417 x0 = getOppositeEnd(withArrow).getX() + endOffset.x;
418 y0 = getOppositeEnd(withArrow).getY() + endOffset.y;
419
420 float arrowLength = withArrow.getArrowheadLength();
421 double arrowRatio = withArrow.getArrowheadRatio();
422
423 // set the size of the disconnect indicator arrowhead
424 if (this._mode == Item.HighlightMode.Disconnect) {
425 arrowLength = 15;
426 arrowRatio = 0.3;
427 }
428
429 // if the arrowhead is 'auto', then one and only one end must be
430 // floating
431 if (arrowLength != AUTO_ARROWHEAD_LENGTH && arrowRatio < 0)
432 return;
433
434 int deltaX = x1 - x0;
435 int deltaY = y1 - y0;
436
437 int length = (int) Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
438
439 // The length of the line must at least be as long as the arrow or we
440 // wont show the arrow
441 if (length <= MINIMUM_ARROW_HEAD_LENGTH)
442 return;
443 if (arrowLength == AUTO_ARROWHEAD_LENGTH) {
444 arrowLength = getAutoArrowheadLength(length);
445 withArrow.setArrowhead(null);
446 if (arrowRatio < 0)
447 arrowRatio = DEFAULT_ARROWHEAD_RATIO;
448 }
449
450 // only calculate the arrowhead polygon if necessary
451 Polygon arrowhead = withArrow.getArrowhead();
452 if (arrowhead == null || disconnectMode) {
453 arrowhead = new Polygon();
454 arrowhead.addPoint((int) x0, (int) y0);
455 arrowhead.addPoint((int) Math.round(x0 - arrowLength), (int) Math
456 .round((y0 - (arrowLength * arrowRatio))));
457 arrowhead.addPoint((int) x0, (int) y0);
458 arrowhead.addPoint((int) Math.round(x0 - arrowLength), (int) Math
459 .round((y0 + (arrowLength * arrowRatio))));
460 if (!disconnectMode)
461 withArrow.setArrowhead(arrowhead);
462 }
463 double rad = calcAngle((float) x0, (float) y0, (float) x1, (float) y1);
464 arrowhead.translate((x0 + length) - arrowhead.xpoints[0], y0
465 - arrowhead.ypoints[0]);
466 AffineTransform tx = AffineTransform.getRotateInstance(rad, x0, y0);
467 g.draw(tx.createTransformedShape((Shape) arrowhead));
468 }
469
470 /**
471 *
472 * @param length
473 * The length of the line
474 * @return
475 */
476 private float getAutoArrowheadLength(float length) {
477 float arrowLength;
478 arrowLength = length / ARROW_HEAD_TO_LENGTH_RATIO;
479 arrowLength = Math.max(MINIMUM_ARROW_HEAD_LENGTH, arrowLength);
480 arrowLength = Math.min(MAXIMUM_ARROW_HEAD_LENGTH, arrowLength);
481 return arrowLength;
482 }
483
484 /**
485 * Calcuates the angle of rotation (in radians) of the arrowhead Based on
486 * code from DeSL (Arrow2D) http://sourceforge.net/projects/desl/
487 */
488 private double calcAngle(float x1, float y1, float x2, float y2) {
489 double rad = 0.0d;
490 float dx = x2 - x1;
491 float dy = y2 - y1;
492
493 if (dx == 0.0) {
494 if (dy == 0.0) {
495 rad = 0.0;
496 } else if (dy > 0.0) {
497 rad = Math.PI / 2.0;
498 } else {
499 rad = Math.PI * 3.0 / 2.0;
500 }
501 } else if (dy == 0.0) {
502 if (dx > 0.0) {
503 rad = 0.0;
504 } else {
505 rad = Math.PI;
506 }
507 } else {
508 if (dx < 0.0) {
509 rad = Math.atan(dy / dx) + Math.PI;
510 } else if (dy < 0.0) {
511 rad = Math.atan(dy / dx) + (2 * Math.PI);
512 } else {
513 rad = Math.atan(dy / dx);
514 }
515 }
516
517 return rad;
518 }
519
520 @Override
521 public Line copy() {
522 Item s = _start.copy();
523 Item e = _end.copy();
524
525 Line temp = new Line(s, e, getID());
526
527 Item.DuplicateItem(this, temp);
528
529 temp.setLinePattern(getLinePattern());
530 temp.setThickness(getThickness());
531
532 temp.setArrow(getArrowheadLength(), getArrowheadRatio());
533
534 return temp;
535 }
536
537 public Item getEndPointToDisconnect(int x, int y) {
538 if (!hasPermission(Permission.full))
539 return null;
540
541 Item start = getStartItem();
542 Item end = getEndItem();
543 int deltaX = start.getX() - end.getX();
544 int deltaY = start.getY() - end.getY();
545 int lineLength = (int) Math.sqrt(deltaX * deltaX + deltaY * deltaY);
546
547 int startX = start.getX() - x;
548 int endX = end.getX() - x;
549 int startY = start.getY() - y;
550 int endY = end.getY() - y;
551
552 int distStart = (int) Math.sqrt(startX * startX + startY * startY);
553 int distEnd = (int) Math.sqrt(endX * endX + endY * endY);
554
555 final double DISCONNECT_THRESHHOLD = Math.min(0.2 * lineLength, 25.0);
556 final double NORMAL_THRESHHOLD = Math.min(0.1 * lineLength, 13.0);
557
558 if (distStart < NORMAL_THRESHHOLD) {
559 start._mode = Item.HighlightMode.Normal;
560 return start;
561 } else if (distEnd < NORMAL_THRESHHOLD) {
562 end._mode = Item.HighlightMode.Normal;
563 return end;
564 } else if (distStart < DISCONNECT_THRESHHOLD) {
565 if (start.getLines().size() > 1
566 || start.getConstraints().size() > 0)
567 start._mode = Item.HighlightMode.Disconnect;
568 else
569 start._mode = Item.HighlightMode.Normal;
570 return start;
571 } else if (distEnd < DISCONNECT_THRESHHOLD) {
572 if (end.getLines().size() > 1 || end.getConstraints().size() > 0)
573 end._mode = Item.HighlightMode.Disconnect;
574 else
575 end._mode = Item.HighlightMode.Normal;
576 return end;
577 }
578
579 return null;
580 }
581
582 @Override
583 public int setHighlightColor() {
584 super.setHighlightColor();
585
586 return Item.UNCHANGED_CURSOR;
587 }
588
589 /**
590 * Sets the Color of this Line and the two Dots at the ends.
591 *
592 * @param c
593 * The Color to paint this Line in.
594 */
595 @Override
596 public void setColor(Color c) {
597 super.setColor(c);
598
599 _start.lineColorChanged(c);
600 _end.lineColorChanged(c);
601 }
602
603 @Override
604 public Color getFillColor() {
605 return _start.getFillColor();
606 }
607
608 @Override
609 public void setThickness(float thick, boolean setConnectedThickness) {
610
611 float oldThickness = this.getThickness();
612 if (thick == oldThickness)
613 return;
614 boolean bigger = thick > oldThickness;
615
616 if (!bigger)
617 invalidateCommonTrait(ItemAppearence.Thickness);
618
619 if (thick < 0)
620 thick = DEFAULT_THICKNESS;
621
622 if (setConnectedThickness) {
623 if (_start.getThickness() != thick)
624 _start.setThickness(thick);
625
626 if (_end.getThickness() != thick)
627 _end.setThickness(thick);
628 }
629
630 refreshStroke(thick);
631
632 if (bigger)
633 invalidateCommonTrait(ItemAppearence.Thickness);
634 }
635
636 @Override
637 public final float getThickness() {
638 return (_start.getThickness() + _end.getThickness()) / 2;
639 }
640
641 @Override
642 public void setAnnotation(boolean val) {
643 if (_end.isAnnotation() != val)
644 _end.setAnnotation(val);
645 else if (_start instanceof Text && _start.isAnnotation() != val)
646 _start.setAnnotation(val);
647 }
648
649 @Override
650 public boolean isAnnotation() {
651 return _start.isAnnotation() || _end.isAnnotation();
652 }
653
654 @Override
655 public boolean isConnectedToAnnotation() {
656 return _start.isConnectedToAnnotation()
657 || _end.isConnectedToAnnotation();
658 }
659
660 /**
661 * Fixes the length of the arrowhead on the end of the line.
662 */
663 public void fixArrowheadLength() {
664 if (_end.getArrowheadLength() == AUTO_ARROWHEAD_LENGTH) {
665 fixArrowheadLength(_end);
666 }
667 }
668
669 public void autoArrowheadLength() {
670 _end.setArrowheadLength(AUTO_ARROWHEAD_LENGTH);
671 }
672
673 public void fixArrowheadLength(Item arrow) {
674 // if the arrow isnt there turn it on!
675 int x0, x1, y0, y1;
676
677 x1 = arrow.getX();
678 y1 = arrow.getY();
679 x0 = getOppositeEnd(arrow).getX();
680 y0 = getOppositeEnd(arrow).getY();
681
682 int deltaX = x1 - x0;
683 int deltaY = y1 - y0;
684
685 int length = (int) Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
686 length /= ARROW_HEAD_TO_LENGTH_RATIO;
687 arrow.setArrowheadLength(Math.min(MAXIMUM_ARROW_HEAD_LENGTH, Math.max(
688 MINIMUM_ARROW_HEAD_LENGTH, length)));
689 }
690
691 /**
692 * This method toggles the arrow on the floating end point of a line. If the
693 * line does not have one floating end and one fixed end this method does
694 * nothing.
695 *
696 */
697 public void toggleArrow() {
698 Item arrow;
699 if (_start.isFloating() && !_end.isFloating())
700 arrow = _start;
701 else if (_end.isFloating() && !_start.isFloating())
702 arrow = _end;
703 else
704 return;
705
706 invalidateAll();
707 // if there already an arrow, turn it off
708 if (arrow.getArrowheadLength() != 0 && arrow.getArrowheadRatio() > 0) {
709 arrow.setArrowheadLength(0);
710 return;
711 }
712
713 fixArrowheadLength(arrow);
714 invalidateAll();
715 }
716
717 private static double[] ArrowHeadRatios = new double[] { 0.25, 0.5, 1.0 };
718
719 /**
720 * This will toggle the arrow head between the various types of UML arrow
721 * heads.
722 *
723 * @param amount
724 */
725 public void toggleArrowHeadRatio(int amount) {
726 Item arrow = null;
727 if (_start.isFloating() && !_end.isFloating())
728 arrow = _start;
729 else if (_end.isFloating() && !_start.isFloating())
730 arrow = _end;
731 else
732 return;
733
734 // find the index of the current line pattern
735 double currentRatio = arrow.getArrowheadRatio();
736
737 // Find the current pattern and move to the next pattern in the wheel
738 for (int i = 0; i < ArrowHeadRatios.length; i++) {
739 if (currentRatio - ArrowHeadRatios[i] < 0.001) {
740 i += ArrowHeadRatios.length + amount;
741 i %= ArrowHeadRatios.length;
742 arrow.setArrowheadRatio(ArrowHeadRatios[i]);
743 return;
744 }
745 }
746 }
747
748 private static int[] ArrowHeadLength = new int[] { /* AUTO_ARROWHEAD_LENGTH, */
749 0, 20, 40, MAXIMUM_ARROW_HEAD_LENGTH };
750
751 /**
752 * Changes the length of the arrow head. In the future this may toggle the
753 * arrow head between the various types of UML arrow heads.
754 *
755 * @param amount
756 */
757 public void toggleArrowHeadLength(int amount) {
758 Item arrow = null;
759 if (_start.isFloating() && !_end.isFloating())
760 arrow = _start;
761 else if (_end.isFloating() && !_start.isFloating())
762 arrow = _end;
763 else
764 return;
765
766 // find the index of the current line pattern
767 float currentLength = arrow.getArrowheadLength();
768
769 // Find the current pattern and move to the next pattern in the wheel
770 for (int i = 0; i < ArrowHeadLength.length; i++) {
771 if (currentLength <= ArrowHeadLength[i]) {
772 i += ArrowHeadLength.length + amount;
773 i %= ArrowHeadLength.length;
774 arrow.setArrowheadLength(ArrowHeadLength[i]);
775 return;
776 }
777 }
778 }
779
780 private Item _virtualSpot = null;
781
782 public void showVirtualSpot(Item orig, int mouseX, int mouseY) {
783 if (orig.getLines().size() != 1)
784 return;
785
786 // lines that are in 'connected' mode, also cannot be attached
787 if (orig.getLines().get(0).getOppositeEnd(orig).isFloating())
788 return;
789
790 Item spot = new Dot(-1, orig.getX(), orig.getY());
791 Item.DuplicateItem(orig, spot);
792 spot.setThickness(Math.max(this.getThickness(), 5));
793 if (this.getColor() != Color.RED)
794 spot.setColor(Color.RED);
795 else
796 spot.setColor(Color.BLUE);
797
798 // unhighlight all the dots
799 for (Item conn : getAllConnected()) {
800 conn.setHighlightMode(Item.HighlightMode.None);
801 }
802
803 // calculate nearest point on line from spot
804 double slope1;
805 double slope2;
806 double x, y;
807
808 slope1 = (_start.getY() - _end.getY() * 1.0)
809 / (_start.getX() - _end.getX());
810 slope2 = -1 / slope1;
811
812 // if the line is horizontal
813 if (slope1 == 0) {
814 x = spot.getX();
815 y = _start.getY();
816 // if the line is vertical
817 } else if (slope2 == 0) {
818 x = _start.getX();
819 y = spot.getY();
820 // otherwise, the line is sloped
821 } else {
822 x = (-1 * slope2 * spot.getX() + spot.getY() - _start.getY() + slope1
823 * _start.getX())
824 / (slope1 - slope2);
825 y = slope1 * (x - _start.getX()) + _start.getY();
826 }
827
828 // position spot on the line
829 spot.setPosition((int) x, (int) y);
830 _virtualSpot = spot;
831 invalidateVirtualSpot();
832 }
833
834 public void showVirtualSpot(int mouseX, int mouseY) {
835 Item spot = new Dot(mouseX, mouseY, -1);
836 spot.setThickness(Math.max(this.getThickness(), 5));
837 if (DEFAULT_HIGHLIGHT.equals(this.getColor()))
838 spot.setColor(ALTERNATE_HIGHLIGHT);
839 else
840 spot.setColor(DEFAULT_HIGHLIGHT);
841
842 // calculate nearest point on line from spot
843 double slope1;
844 double slope2;
845 double x, y;
846
847 slope1 = (_start.getY() - _end.getY() * 1.0)
848 / (_start.getX() - _end.getX());
849 slope2 = -1 / slope1;
850
851 // if the line is horizontal
852 if (slope1 == 0) {
853 x = spot.getX();
854 y = _start.getY();
855 // if the line is vertical
856 } else if (slope2 == 0) {
857 x = _start.getX();
858 y = spot.getY();
859 // otherwise, the line is sloped
860 } else {
861 x = (-1 * slope2 * spot.getX() + spot.getY() - _start.getY() + slope1
862 * _start.getX())
863 / (slope1 - slope2);
864 y = slope1 * (x - _start.getX()) + _start.getY();
865 }
866
867 // position spot on the line
868 spot.setPosition((int) x, (int) y);
869 _virtualSpot = spot;
870 invalidateVirtualSpot();
871 }
872
873 public Item forceMerge(Item spot, int mouseX, int mouseY) {
874
875 // calculate nearest point on line from spot
876 double slope1;
877 double slope2;
878 double x, y;
879
880 slope1 = (_start.getY() - _end.getY() * 1.0)
881 / (_start.getX() - _end.getX());
882 slope2 = -1 / slope1;
883
884 // if the line is horizontal
885 if (slope1 == 0) {
886 x = spot.getX();
887 y = _start.getY();
888 // if the line is vertical
889 } else if (slope2 == 0) {
890 x = _start.getX();
891 y = spot.getY();
892 // otherwise, the line is sloped
893 } else {
894 x = (-1 * slope2 * spot.getX() + spot.getY() - _start.getY() + slope1
895 * _start.getX())
896 / (slope1 - slope2);
897 y = slope1 * (x - _start.getX()) + _start.getY();
898 }
899
900 // position spot on the line
901 spot.setPosition((int) x, (int) y);
902
903 // Keep constraints
904 // If its a constrained line dont merge
905 for (Constraint c : _end.getConstraints()) {
906 if (c.getOppositeEnd(_end).equals(_start)) {
907 // c.replaceEnd(_start, spot);
908 new Constraint(spot, _start, getParentOrCurrentFrame()
909 .getNextItemID(), c.getType());
910 new Constraint(spot, _end, getParentOrCurrentFrame()
911 .getNextItemID(), c.getType());
912 return null;
913 }
914 }
915
916 Line temp = copy();
917
918 Frame currentFrame = DisplayIO.getCurrentFrame();
919 temp.setID(currentFrame.getNextItemID());
920 temp.setEndItem(_end);
921 temp.setStartItem(spot);
922 currentFrame.addItem(temp);
923
924 setEndItem(spot);
925
926 // if(_arrowEnd == spot)
927 // setArrow(-1,-1);
928
929 return spot;
930 }
931
932 @Override
933 public Item merge(Item merger, int mouseX, int mouseY) {
934
935 if (!(merger.isLineEnd()))
936 return merger;
937
938 Item spot = merger;
939
940 // dots with multiple lines cannot be attached, nor can dots with no
941 // lines
942 if (spot.getLines().size() != 1)
943 return merger;
944
945 // lines that are in 'connected' mode, also cannot be attached
946 if (spot.isFloating()
947 && spot.getLines().get(0).getOppositeEnd(spot).isFloating())
948 return merger;
949
950 return forceMerge(merger, mouseX, mouseY);
951 }
952
953 @Override
954 public Collection<Item> getConnected() {
955 return getAllConnected();
956 }
957
958 @Override
959 public void addAllConnected(Collection<Item> connected) {
960 if (!connected.contains(this))
961 connected.add(this);
962
963 if (!connected.contains(_end))
964 _end.addAllConnected(connected);
965
966 if (!connected.contains(_start))
967 _start.addAllConnected(connected);
968 }
969
970 @Override
971 public boolean equals(Object o) {
972 if (!(o instanceof Line))
973 return false;
974
975 return ((Item) o).getID() == getID();
976 }
977
978 @Override
979 public void setActions(List<String> actions) {
980 }
981
982 @Override
983 public List<String> getAction() {
984 return null;
985 }
986
987 public String getLineEnds() {
988 return _start.getID() + " " + _end.getID();
989 }
990
991 public int getLineType() {
992 return 1;
993 }
994
995 @Override
996 public boolean isFloating() {
997 return _start.isFloating() && _end.isFloating();
998 }
999
1000 @Override
1001 public void updatePolygon() {
1002 _poly = new Polygon();
1003
1004 Rectangle one;
1005 Rectangle two;
1006
1007 /**
1008 * TODO: Fake dots seems inefficient, Fix it.
1009 */
1010
1011 // line goes from left->right, one->two
1012 if (_start.getX() <= _end.getX()) {
1013 /*
1014 * one = _start.getPolygon().getBounds(); two =
1015 * _end.getPolygon().getBounds();
1016 */
1017
1018 Item s = new Dot(_start.getX(), _start.getY(), -1);
1019 Item e = new Dot(_end.getX(), _end.getY(), -1);
1020
1021 one = s.getPolygon().getBounds();
1022 two = e.getPolygon().getBounds();
1023 } else {
1024 Item s = new Dot(_start.getX(), _start.getY(), -1);
1025 Item e = new Dot(_end.getX(), _end.getY(), -1);
1026
1027 one = e.getPolygon().getBounds();
1028 two = s.getPolygon().getBounds();
1029 /*
1030 * one = _end.getPolygon().getBounds(); two =
1031 * _start.getPolygon().getBounds();
1032 */
1033 }
1034
1035 // if one is above two
1036 if (one.getY() < two.getY()) {
1037 _poly.addPoint((int) one.getMaxX(), (int) one.getMinY());
1038 _poly.addPoint((int) two.getMaxX(), (int) two.getMinY());
1039 _poly.addPoint((int) two.getMinX(), (int) two.getMaxY());
1040 _poly.addPoint((int) one.getMinX(), (int) one.getMaxY());
1041 // if one is below two
1042 } else {
1043 _poly.addPoint((int) one.getMinX(), (int) one.getMinY());
1044 _poly.addPoint((int) two.getMinX(), (int) two.getMinY());
1045 _poly.addPoint((int) two.getMaxX(), (int) two.getMaxY());
1046 _poly.addPoint((int) one.getMaxX(), (int) one.getMaxY());
1047 }
1048
1049 }
1050
1051 @Override
1052 public void delete() {
1053 super.delete();
1054
1055 _start.removeLine(this);
1056 _end.removeLine(this);
1057
1058 if (_start.getLines().size() == 0)
1059 _start.delete();
1060 if (_end.getLines().size() == 0)
1061 _end.delete();
1062 }
1063
1064 @Override
1065 public void anchor() {
1066 Frame current = getParentOrCurrentFrame();
1067 // Check if either end is off the current frame
1068 Frame parentEnd = getEndItem().getParent();
1069 if (parentEnd != null && parentEnd != current) {
1070 this.setParent(parentEnd);
1071 } else {
1072 Frame parentStart = getStartItem().getParent();
1073 if (parentStart != null && parentStart != current)
1074 this.setParent(parentStart);
1075 }
1076
1077 super.anchor();
1078 fixArrowheadLength();
1079 }
1080
1081 /**
1082 * Removes constraints from the end points of this line.
1083 */
1084 @Override
1085 public void removeAllConstraints() {
1086 // Find all constraints that include both the start and end.
1087 for (Constraint c : _start.getConstraints()) {
1088 if (c.contains(_end)) {
1089 _start.removeConstraint(c);
1090 _end.removeConstraint(c);
1091 }
1092 }
1093 }
1094
1095 public double getLength() {
1096 return getLength(_start.getPosition(), _end.getPosition());
1097 }
1098
1099 public Integer getPossibleConstraint() {
1100 if (_start.getY() == _end.getY() && _start.getX() != _end.getX())
1101 return Constraint.HORIZONTAL;
1102 else if (_start.getX() == _end.getX() && _start.getY() != _end.getY())
1103 return Constraint.VERTICAL;
1104 return null;
1105 }
1106
1107 public static double getLength(Point p1, Point p2) {
1108 return Point.distance(p1.x, p1.y, p2.x, p2.y);
1109 }
1110
1111 @Override
1112 public Rectangle[] getDrawingArea() { // TODO: CACHE - LIKE UPDATE POLYGON
1113
1114 float currentThickness = this.getThickness() + 4;
1115
1116 // Establish bounds
1117 int x = Math.min(_start.getX(), _end.getX());
1118 int y = Math.min(_start.getY(), _end.getY());
1119 int w = Math.max(_start.getX(), _end.getX()) - x + 1;
1120 int h = Math.max(_start.getY(), _end.getY()) - y + 1;
1121
1122 int thickness = (int) Math.ceil(currentThickness);
1123 if (thickness < 4)
1124 thickness = 4;
1125 int halfThickness = (int) Math.ceil(currentThickness / 2);
1126
1127 // Consider line thickness
1128 w += thickness;
1129 h += thickness;
1130 x -= halfThickness;
1131 y -= halfThickness;
1132
1133 Rectangle bounds = new Rectangle(x, y, w, h);
1134
1135 // TODO: Cap bounds
1136
1137 if (_end.hasVisibleArrow()) {
1138
1139 double arrowLength = _end.getArrowheadLength();
1140
1141 if (arrowLength == AUTO_ARROWHEAD_LENGTH) {
1142 arrowLength = Math
1143 .ceil(getAutoArrowheadLength((float) getLength()));
1144 } else {
1145 arrowLength = Math.ceil(arrowLength);
1146 }
1147
1148 arrowLength += ((thickness * 2) + 1);
1149 int nArrowLength = (int) arrowLength;
1150
1151 // NAIVE version - but probably more efficient in the long run...
1152 // overall
1153 // as opposed to advanced/expensive calculations for getting exact
1154 // bounding box
1155 Rectangle arrowBounds = new Rectangle(_end.getX() - nArrowLength,
1156 _end.getY() - nArrowLength, 2 * nArrowLength,
1157 2 * nArrowLength);
1158
1159 if (currentThickness > 0.0f) {
1160
1161 arrowBounds = new Rectangle(arrowBounds.x - halfThickness,
1162 arrowBounds.y - halfThickness, arrowBounds.width
1163 + thickness, arrowBounds.height + thickness);
1164 }
1165
1166 return new Rectangle[] { bounds, arrowBounds };
1167 } else {
1168 return new Rectangle[] { bounds };
1169 }
1170
1171 }
1172
1173 @Override
1174 public boolean hasPermission(Permission p) {
1175 return _start.hasPermission(p);
1176 }
1177
1178 @Override
1179 public void setPermission(Permission p) {
1180 _start.setPermission(p);
1181 _end.setPermission(p);
1182 }
1183
1184 @Override
1185 public boolean dontSave() {
1186 return true;
1187 }
1188}
Note: See TracBrowser for help on using the repository browser.