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

Last change on this file since 309 was 298, checked in by ra33, 16 years ago

Adding networking stuff for peer to peer sharing of frames

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