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

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

Added Spell Checker
Added word count stats
Fixed some mail stuff

File size: 29.6 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 public 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.setStroke(new BasicStroke(getPaintThickness(), CAP,
377 BasicStroke.JOIN_MITER));
378 paintArrow(g, _start, _startOffset, _endOffset);
379 paintArrow(g, _end, _endOffset, _startOffset);
380
381 if (_virtualSpot != null) {
382 _virtualSpot.paint(g);
383 invalidateVirtualSpot();
384 _virtualSpot = null;
385 }
386 }
387
388 private float getPaintThickness() {
389 float thickness = getThickness();
390 if (thickness > 0) {
391 return thickness;
392 }
393 return MINIMUM_PAINT_THICKNESS;
394 }
395
396 private void invalidateVirtualSpot() {
397 assert (_virtualSpot != null);
398
399 invalidate(_virtualSpot.getDrawingArea());
400
401 }
402
403 /**
404 * Based on code from DeSL (Arrow2D) http://sourceforge.net/projects/desl/
405 */
406 private void paintArrow(Graphics2D g, Item withArrow, Point startOffset,
407 Point endOffset) {
408 boolean disconnectMode = withArrow._mode == Item.HighlightMode.Disconnect;
409 // only draw an arrowhead if necessary
410 if (!(this._mode == Item.HighlightMode.Disconnect && disconnectMode)
411 && (!withArrow.hasVisibleArrow() || withArrow.getLines().size() > 1))
412 return;
413
414 int x0, x1, y0, y1;
415
416 x1 = withArrow.getX() + startOffset.x;
417 y1 = withArrow.getY() + startOffset.y;
418 x0 = getOppositeEnd(withArrow).getX() + endOffset.x;
419 y0 = getOppositeEnd(withArrow).getY() + endOffset.y;
420
421 float arrowLength = withArrow.getArrowheadLength();
422 double arrowRatio = withArrow.getArrowheadRatio();
423
424 // set the size of the disconnect indicator arrowhead
425 if (this._mode == Item.HighlightMode.Disconnect) {
426 arrowLength = 15;
427 arrowRatio = 0.3;
428 }
429
430 // if the arrowhead is 'auto', then one and only one end must be
431 // floating
432 if (arrowLength != AUTO_ARROWHEAD_LENGTH && arrowRatio < 0)
433 return;
434
435 int deltaX = x1 - x0;
436 int deltaY = y1 - y0;
437
438 int length = (int) Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
439
440 // The length of the line must at least be as long as the arrow or we
441 // wont show the arrow
442 if (length <= MINIMUM_ARROW_HEAD_LENGTH)
443 return;
444 if (arrowLength == AUTO_ARROWHEAD_LENGTH) {
445 arrowLength = getAutoArrowheadLength(length);
446 withArrow.setArrowhead(null);
447 if (arrowRatio < 0)
448 arrowRatio = DEFAULT_ARROWHEAD_RATIO;
449 }
450
451 // only calculate the arrowhead polygon if necessary
452 Polygon arrowhead = withArrow.getArrowhead();
453 if (arrowhead == null || disconnectMode) {
454 arrowhead = new Polygon();
455 arrowhead.addPoint((int) x0, (int) y0);
456 arrowhead.addPoint((int) Math.round(x0 - arrowLength), (int) Math
457 .round((y0 - (arrowLength * arrowRatio))));
458 arrowhead.addPoint((int) x0, (int) y0);
459 arrowhead.addPoint((int) Math.round(x0 - arrowLength), (int) Math
460 .round((y0 + (arrowLength * arrowRatio))));
461 if (!disconnectMode)
462 withArrow.setArrowhead(arrowhead);
463 }
464 double rad = calcAngle((float) x0, (float) y0, (float) x1, (float) y1);
465 arrowhead.translate((x0 + length) - arrowhead.xpoints[0], y0
466 - arrowhead.ypoints[0]);
467 AffineTransform tx = AffineTransform.getRotateInstance(rad, x0, y0);
468 g.draw(tx.createTransformedShape((Shape) arrowhead));
469 }
470
471 /**
472 *
473 * @param length
474 * The length of the line
475 * @return
476 */
477 private float getAutoArrowheadLength(float length) {
478 float arrowLength;
479 arrowLength = length / ARROW_HEAD_TO_LENGTH_RATIO;
480 arrowLength = Math.max(MINIMUM_ARROW_HEAD_LENGTH, arrowLength);
481 arrowLength = Math.min(MAXIMUM_ARROW_HEAD_LENGTH, arrowLength);
482 return arrowLength;
483 }
484
485 /**
486 * Calcuates the angle of rotation (in radians) of the arrowhead Based on
487 * code from DeSL (Arrow2D) http://sourceforge.net/projects/desl/
488 */
489 private double calcAngle(float x1, float y1, float x2, float y2) {
490 double rad = 0.0d;
491 float dx = x2 - x1;
492 float dy = y2 - y1;
493
494 if (dx == 0.0) {
495 if (dy == 0.0) {
496 rad = 0.0;
497 } else if (dy > 0.0) {
498 rad = Math.PI / 2.0;
499 } else {
500 rad = Math.PI * 3.0 / 2.0;
501 }
502 } else if (dy == 0.0) {
503 if (dx > 0.0) {
504 rad = 0.0;
505 } else {
506 rad = Math.PI;
507 }
508 } else {
509 if (dx < 0.0) {
510 rad = Math.atan(dy / dx) + Math.PI;
511 } else if (dy < 0.0) {
512 rad = Math.atan(dy / dx) + (2 * Math.PI);
513 } else {
514 rad = Math.atan(dy / dx);
515 }
516 }
517
518 return rad;
519 }
520
521 @Override
522 public Line copy() {
523 Item s = _start.copy();
524 Item e = _end.copy();
525
526 Line temp = new Line(s, e, getID());
527
528 Item.DuplicateItem(this, temp);
529
530 temp.setLinePattern(getLinePattern());
531 temp.setThickness(getThickness());
532
533 temp.setArrow(getArrowheadLength(), getArrowheadRatio());
534
535 return temp;
536 }
537
538 public Item getEndPointToDisconnect(int x, int y) {
539 if (!hasPermission(Permission.full))
540 return null;
541
542 Item start = getStartItem();
543 Item end = getEndItem();
544 int deltaX = start.getX() - end.getX();
545 int deltaY = start.getY() - end.getY();
546 int lineLength = (int) Math.sqrt(deltaX * deltaX + deltaY * deltaY);
547
548 int startX = start.getX() - x;
549 int endX = end.getX() - x;
550 int startY = start.getY() - y;
551 int endY = end.getY() - y;
552
553 int distStart = (int) Math.sqrt(startX * startX + startY * startY);
554 int distEnd = (int) Math.sqrt(endX * endX + endY * endY);
555
556 final double DISCONNECT_THRESHHOLD = Math.min(0.2 * lineLength, 25.0);
557 final double NORMAL_THRESHHOLD = Math.min(0.1 * lineLength, 13.0);
558
559 if (distStart < NORMAL_THRESHHOLD) {
560 start._mode = Item.HighlightMode.Normal;
561 return start;
562 } else if (distEnd < NORMAL_THRESHHOLD) {
563 end._mode = Item.HighlightMode.Normal;
564 return end;
565 } else if (distStart < DISCONNECT_THRESHHOLD) {
566 if (start.getLines().size() > 1
567 || start.getConstraints().size() > 0)
568 start._mode = Item.HighlightMode.Disconnect;
569 else
570 start._mode = Item.HighlightMode.Normal;
571 return start;
572 } else if (distEnd < DISCONNECT_THRESHHOLD) {
573 if (end.getLines().size() > 1 || end.getConstraints().size() > 0)
574 end._mode = Item.HighlightMode.Disconnect;
575 else
576 end._mode = Item.HighlightMode.Normal;
577 return end;
578 }
579
580 return null;
581 }
582
583 @Override
584 public int setHighlightColor() {
585 super.setHighlightColor();
586
587 return Item.UNCHANGED_CURSOR;
588 }
589
590 /**
591 * Sets the Color of this Line and the two Dots at the ends.
592 *
593 * @param c
594 * The Color to paint this Line in.
595 */
596 @Override
597 public void setColor(Color c) {
598 super.setColor(c);
599
600 _start.lineColorChanged(c);
601 _end.lineColorChanged(c);
602 }
603
604 @Override
605 public Color getFillColor() {
606 return _start.getFillColor();
607 }
608
609 @Override
610 public void setThickness(float thick, boolean setConnectedThickness) {
611
612 float oldThickness = this.getThickness();
613 if (thick == oldThickness)
614 return;
615 boolean bigger = thick > oldThickness;
616
617 if (!bigger)
618 invalidateCommonTrait(ItemAppearence.Thickness);
619
620 if (thick < 0)
621 thick = DEFAULT_THICKNESS;
622
623 if (setConnectedThickness) {
624 if (_start.getThickness() != thick)
625 _start.setThickness(thick);
626
627 if (_end.getThickness() != thick)
628 _end.setThickness(thick);
629 }
630
631 refreshStroke(thick);
632
633 if (bigger)
634 invalidateCommonTrait(ItemAppearence.Thickness);
635 }
636
637 @Override
638 public final float getThickness() {
639 /*
640 * Don't change this method of getting the thickness otherwise setting
641 * thickness for connected lines breaks. The method for setting
642 * thickness of connected lines is inefficient and should be changed at
643 * some stage!
644 */
645 return (_start.getThickness() + _end.getThickness()) / 2;
646 }
647
648 @Override
649 public void setAnnotation(boolean val) {
650 if (_end.isAnnotation() != val)
651 _end.setAnnotation(val);
652 else if (_start instanceof Text && _start.isAnnotation() != val)
653 _start.setAnnotation(val);
654 }
655
656 @Override
657 public boolean isAnnotation() {
658 return _start.isAnnotation() || _end.isAnnotation();
659 }
660
661 @Override
662 public boolean isConnectedToAnnotation() {
663 return _start.isConnectedToAnnotation()
664 || _end.isConnectedToAnnotation();
665 }
666
667 /**
668 * Fixes the length of the arrowhead on the end of the line.
669 */
670 public void fixArrowheadLength() {
671 if (_end.getArrowheadLength() == AUTO_ARROWHEAD_LENGTH) {
672 fixArrowheadLength(_end);
673 }
674 }
675
676 public void autoArrowheadLength() {
677 _end.setArrowheadLength(AUTO_ARROWHEAD_LENGTH);
678 }
679
680 public void fixArrowheadLength(Item arrow) {
681 // if the arrow isnt there turn it on!
682 int x0, x1, y0, y1;
683
684 x1 = arrow.getX();
685 y1 = arrow.getY();
686 x0 = getOppositeEnd(arrow).getX();
687 y0 = getOppositeEnd(arrow).getY();
688
689 int deltaX = x1 - x0;
690 int deltaY = y1 - y0;
691
692 int length = (int) Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
693 length /= ARROW_HEAD_TO_LENGTH_RATIO;
694 arrow.setArrowheadLength(Math.min(MAXIMUM_ARROW_HEAD_LENGTH, Math.max(
695 MINIMUM_ARROW_HEAD_LENGTH, length)));
696 }
697
698 /**
699 * This method toggles the arrow on the floating end point of a line. If the
700 * line does not have one floating end and one fixed end this method does
701 * nothing.
702 *
703 */
704 public void toggleArrow() {
705 Item arrow;
706 if (_start.isFloating() && !_end.isFloating())
707 arrow = _start;
708 else if (_end.isFloating() && !_start.isFloating())
709 arrow = _end;
710 else
711 return;
712
713 invalidateAll();
714 // if there already an arrow, turn it off
715 if (arrow.getArrowheadLength() != 0 && arrow.getArrowheadRatio() > 0) {
716 arrow.setArrowheadLength(0);
717 return;
718 }
719
720 fixArrowheadLength(arrow);
721 invalidateAll();
722 }
723
724 private static double[] ArrowHeadRatios = new double[] { 0.25, 0.5, 1.0 };
725
726 /**
727 * This will toggle the arrow head between the various types of UML arrow
728 * heads.
729 *
730 * @param amount
731 */
732 public void toggleArrowHeadRatio(int amount) {
733 Item arrow = null;
734 if (_start.isFloating() && !_end.isFloating())
735 arrow = _start;
736 else if (_end.isFloating() && !_start.isFloating())
737 arrow = _end;
738 else
739 return;
740
741 // find the index of the current line pattern
742 double currentRatio = arrow.getArrowheadRatio();
743
744 // Find the current pattern and move to the next pattern in the wheel
745 for (int i = 0; i < ArrowHeadRatios.length; i++) {
746 if (currentRatio - ArrowHeadRatios[i] < 0.001) {
747 i += ArrowHeadRatios.length + amount;
748 i %= ArrowHeadRatios.length;
749 arrow.setArrowheadRatio(ArrowHeadRatios[i]);
750 return;
751 }
752 }
753 }
754
755 private static int[] ArrowHeadLength = new int[] { /* AUTO_ARROWHEAD_LENGTH, */
756 0, 20, 40, MAXIMUM_ARROW_HEAD_LENGTH };
757
758 /**
759 * Changes the length of the arrow head. In the future this may toggle the
760 * arrow head between the various types of UML arrow heads.
761 *
762 * @param amount
763 */
764 public void toggleArrowHeadLength(int amount) {
765 Item arrow = null;
766 if (_start.isFloating() && !_end.isFloating())
767 arrow = _start;
768 else if (_end.isFloating() && !_start.isFloating())
769 arrow = _end;
770 else
771 return;
772
773 // find the index of the current line pattern
774 float currentLength = arrow.getArrowheadLength();
775
776 // Find the current pattern and move to the next pattern in the wheel
777 for (int i = 0; i < ArrowHeadLength.length; i++) {
778 if (currentLength <= ArrowHeadLength[i]) {
779 i += ArrowHeadLength.length + amount;
780 i %= ArrowHeadLength.length;
781 arrow.setArrowheadLength(ArrowHeadLength[i]);
782 return;
783 }
784 }
785 }
786
787 private Item _virtualSpot = null;
788
789 public void showVirtualSpot(Item orig, int mouseX, int mouseY) {
790 if (orig.getLines().size() != 1)
791 return;
792
793 // lines that are in 'connected' mode, also cannot be attached
794 if (orig.getLines().get(0).getOppositeEnd(orig).isFloating())
795 return;
796
797 Item spot = new Dot(-1, orig.getX(), orig.getY());
798 Item.DuplicateItem(orig, spot);
799 spot.setThickness(Math.max(this.getThickness(), 5));
800 if (this.getColor() != Color.RED)
801 spot.setColor(Color.RED);
802 else
803 spot.setColor(Color.BLUE);
804
805 // unhighlight all the dots
806 for (Item conn : getAllConnected()) {
807 conn.setHighlightMode(Item.HighlightMode.None);
808 }
809
810 // calculate nearest point on line from spot
811 double slope1;
812 double slope2;
813 double x, y;
814
815 slope1 = (_start.getY() - _end.getY() * 1.0)
816 / (_start.getX() - _end.getX());
817 slope2 = -1 / slope1;
818
819 // if the line is horizontal
820 if (slope1 == 0) {
821 x = spot.getX();
822 y = _start.getY();
823 // if the line is vertical
824 } else if (slope2 == 0) {
825 x = _start.getX();
826 y = spot.getY();
827 // otherwise, the line is sloped
828 } else {
829 x = (-1 * slope2 * spot.getX() + spot.getY() - _start.getY() + slope1
830 * _start.getX())
831 / (slope1 - slope2);
832 y = slope1 * (x - _start.getX()) + _start.getY();
833 }
834
835 // position spot on the line
836 spot.setPosition((int) x, (int) y);
837 _virtualSpot = spot;
838 invalidateVirtualSpot();
839 }
840
841 public void showVirtualSpot(int mouseX, int mouseY) {
842 Item spot = new Dot(mouseX, mouseY, -1);
843 spot.setThickness(Math.max(this.getThickness(), 5));
844 if (DEFAULT_HIGHLIGHT.equals(this.getColor()))
845 spot.setColor(ALTERNATE_HIGHLIGHT);
846 else
847 spot.setColor(DEFAULT_HIGHLIGHT);
848
849 // calculate nearest point on line from spot
850 double slope1;
851 double slope2;
852 double x, y;
853
854 slope1 = (_start.getY() - _end.getY() * 1.0)
855 / (_start.getX() - _end.getX());
856 slope2 = -1 / slope1;
857
858 // if the line is horizontal
859 if (slope1 == 0) {
860 x = spot.getX();
861 y = _start.getY();
862 // if the line is vertical
863 } else if (slope2 == 0) {
864 x = _start.getX();
865 y = spot.getY();
866 // otherwise, the line is sloped
867 } else {
868 x = (-1 * slope2 * spot.getX() + spot.getY() - _start.getY() + slope1
869 * _start.getX())
870 / (slope1 - slope2);
871 y = slope1 * (x - _start.getX()) + _start.getY();
872 }
873
874 // position spot on the line
875 spot.setPosition((int) x, (int) y);
876 _virtualSpot = spot;
877 invalidateVirtualSpot();
878 }
879
880 public Item forceMerge(Item spot, int mouseX, int mouseY) {
881
882 // calculate nearest point on line from spot
883 double slope1;
884 double slope2;
885 double x, y;
886
887 slope1 = (_start.getY() - _end.getY() * 1.0)
888 / (_start.getX() - _end.getX());
889 slope2 = -1 / slope1;
890
891 // if the line is horizontal
892 if (slope1 == 0) {
893 x = spot.getX();
894 y = _start.getY();
895 // if the line is vertical
896 } else if (slope2 == 0) {
897 x = _start.getX();
898 y = spot.getY();
899 // otherwise, the line is sloped
900 } else {
901 x = (-1 * slope2 * spot.getX() + spot.getY() - _start.getY() + slope1
902 * _start.getX())
903 / (slope1 - slope2);
904 y = slope1 * (x - _start.getX()) + _start.getY();
905 }
906
907 // position spot on the line
908 spot.setPosition((int) x, (int) y);
909
910 // Keep constraints
911 // If its a constrained line dont merge
912 for (Constraint c : _end.getConstraints()) {
913 if (c.getOppositeEnd(_end).equals(_start)) {
914 // c.replaceEnd(_start, spot);
915 new Constraint(spot, _start, getParentOrCurrentFrame()
916 .getNextItemID(), c.getType());
917 new Constraint(spot, _end, getParentOrCurrentFrame()
918 .getNextItemID(), c.getType());
919 return null;
920 }
921 }
922
923 Line temp = copy();
924
925 Frame currentFrame = DisplayIO.getCurrentFrame();
926 temp.setID(currentFrame.getNextItemID());
927 temp.setEndItem(_end);
928 temp.setStartItem(spot);
929 currentFrame.addItem(temp);
930
931 setEndItem(spot);
932
933 // if(_arrowEnd == spot)
934 // setArrow(-1,-1);
935
936 return spot;
937 }
938
939 @Override
940 public Item merge(Item merger, int mouseX, int mouseY) {
941
942 if (!(merger.isLineEnd()))
943 return merger;
944
945 Item spot = merger;
946
947 // dots with multiple lines cannot be attached, nor can dots with no
948 // lines
949 if (spot.getLines().size() != 1)
950 return merger;
951
952 // lines that are in 'connected' mode, also cannot be attached
953 if (spot.isFloating()
954 && spot.getLines().get(0).getOppositeEnd(spot).isFloating())
955 return merger;
956
957 return forceMerge(merger, mouseX, mouseY);
958 }
959
960 @Override
961 public Collection<Item> getConnected() {
962 return getAllConnected();
963 }
964
965 @Override
966 public void addAllConnected(Collection<Item> connected) {
967 if (!connected.contains(this))
968 connected.add(this);
969
970 if (!connected.contains(_end))
971 _end.addAllConnected(connected);
972
973 if (!connected.contains(_start))
974 _start.addAllConnected(connected);
975 }
976
977 @Override
978 public boolean equals(Object o) {
979 if (!(o instanceof Line))
980 return false;
981
982 return ((Item) o).getID() == getID();
983 }
984
985 @Override
986 public void setActions(List<String> actions) {
987 }
988
989 @Override
990 public List<String> getAction() {
991 return null;
992 }
993
994 public String getLineEnds() {
995 return _start.getID() + " " + _end.getID();
996 }
997
998 public int getLineType() {
999 return 1;
1000 }
1001
1002 @Override
1003 public boolean isFloating() {
1004 return _start.isFloating() && _end.isFloating();
1005 }
1006
1007 @Override
1008 public void updatePolygon() {
1009 _poly = new Polygon();
1010
1011 Rectangle one;
1012 Rectangle two;
1013
1014 /**
1015 * TODO: Fake dots seems inefficient, Fix it.
1016 */
1017
1018 // line goes from left->right, one->two
1019 if (_start.getX() <= _end.getX()) {
1020 /*
1021 * one = _start.getPolygon().getBounds(); two =
1022 * _end.getPolygon().getBounds();
1023 */
1024
1025 Item s = new Dot(_start.getX(), _start.getY(), -1);
1026 Item e = new Dot(_end.getX(), _end.getY(), -1);
1027
1028 one = s.getPolygon().getBounds();
1029 two = e.getPolygon().getBounds();
1030 } else {
1031 Item s = new Dot(_start.getX(), _start.getY(), -1);
1032 Item e = new Dot(_end.getX(), _end.getY(), -1);
1033
1034 one = e.getPolygon().getBounds();
1035 two = s.getPolygon().getBounds();
1036 /*
1037 * one = _end.getPolygon().getBounds(); two =
1038 * _start.getPolygon().getBounds();
1039 */
1040 }
1041
1042 // if one is above two
1043 if (one.getY() < two.getY()) {
1044 _poly.addPoint((int) one.getMaxX(), (int) one.getMinY());
1045 _poly.addPoint((int) two.getMaxX(), (int) two.getMinY());
1046 _poly.addPoint((int) two.getMinX(), (int) two.getMaxY());
1047 _poly.addPoint((int) one.getMinX(), (int) one.getMaxY());
1048 // if one is below two
1049 } else {
1050 _poly.addPoint((int) one.getMinX(), (int) one.getMinY());
1051 _poly.addPoint((int) two.getMinX(), (int) two.getMinY());
1052 _poly.addPoint((int) two.getMaxX(), (int) two.getMaxY());
1053 _poly.addPoint((int) one.getMaxX(), (int) one.getMaxY());
1054 }
1055
1056 }
1057
1058 @Override
1059 public void delete() {
1060 super.delete();
1061
1062 _start.removeLine(this);
1063 _end.removeLine(this);
1064
1065 if (_start.getLines().size() == 0)
1066 _start.delete();
1067 if (_end.getLines().size() == 0)
1068 _end.delete();
1069 }
1070
1071 @Override
1072 public void anchor() {
1073 Frame current = getParentOrCurrentFrame();
1074 // Check if either end is off the current frame
1075 Frame parentEnd = getEndItem().getParent();
1076 if (parentEnd != null && parentEnd != current) {
1077 this.setParent(parentEnd);
1078 } else {
1079 Frame parentStart = getStartItem().getParent();
1080 if (parentStart != null && parentStart != current)
1081 this.setParent(parentStart);
1082 }
1083
1084 super.anchor();
1085 fixArrowheadLength();
1086 }
1087
1088 /**
1089 * Removes constraints from the end points of this line.
1090 */
1091 @Override
1092 public void removeAllConstraints() {
1093 // Find all constraints that include both the start and end.
1094 for (Constraint c : _start.getConstraints()) {
1095 if (c.contains(_end)) {
1096 _start.removeConstraint(c);
1097 _end.removeConstraint(c);
1098 }
1099 }
1100 }
1101
1102 public double getLength() {
1103 return getLength(_start.getPosition(), _end.getPosition());
1104 }
1105
1106 public Integer getPossibleConstraint() {
1107 if (_start.getY() == _end.getY() && _start.getX() != _end.getX())
1108 return Constraint.HORIZONTAL;
1109 else if (_start.getX() == _end.getX() && _start.getY() != _end.getY())
1110 return Constraint.VERTICAL;
1111 return null;
1112 }
1113
1114 public static double getLength(Point p1, Point p2) {
1115 return Point.distance(p1.x, p1.y, p2.x, p2.y);
1116 }
1117
1118 @Override
1119 public Rectangle[] getDrawingArea() { // TODO: CACHE - LIKE UPDATE POLYGON
1120
1121 float currentThickness = this.getThickness() + 4;
1122
1123 // Establish bounds
1124 int x = Math.min(_start.getX(), _end.getX());
1125 int y = Math.min(_start.getY(), _end.getY());
1126 int w = Math.max(_start.getX(), _end.getX()) - x + 1;
1127 int h = Math.max(_start.getY(), _end.getY()) - y + 1;
1128
1129 int thickness = (int) Math.ceil(currentThickness);
1130 if (thickness < 4)
1131 thickness = 4;
1132 int halfThickness = (int) Math.ceil(currentThickness / 2);
1133
1134 // Consider line thickness
1135 w += thickness;
1136 h += thickness;
1137 x -= halfThickness;
1138 y -= halfThickness;
1139
1140 Rectangle bounds = new Rectangle(x, y, w, h);
1141
1142 // TODO: Cap bounds
1143
1144 if (_end.hasVisibleArrow()) {
1145
1146 double arrowLength = _end.getArrowheadLength();
1147
1148 if (arrowLength == AUTO_ARROWHEAD_LENGTH) {
1149 arrowLength = Math
1150 .ceil(getAutoArrowheadLength((float) getLength()));
1151 } else {
1152 arrowLength = Math.ceil(arrowLength);
1153 }
1154
1155 arrowLength += ((thickness * 2) + 1);
1156 int nArrowLength = (int) arrowLength;
1157
1158 // NAIVE version - but probably more efficient in the long run...
1159 // overall
1160 // as opposed to advanced/expensive calculations for getting exact
1161 // bounding box
1162 Rectangle arrowBounds = new Rectangle(_end.getX() - nArrowLength,
1163 _end.getY() - nArrowLength, 2 * nArrowLength,
1164 2 * nArrowLength);
1165
1166 if (currentThickness > 0.0f) {
1167
1168 arrowBounds = new Rectangle(arrowBounds.x - halfThickness,
1169 arrowBounds.y - halfThickness, arrowBounds.width
1170 + thickness, arrowBounds.height + thickness);
1171 }
1172
1173 return new Rectangle[] { bounds, arrowBounds };
1174 } else {
1175 return new Rectangle[] { bounds };
1176 }
1177
1178 }
1179
1180 @Override
1181 public boolean hasPermission(Permission p) {
1182 return _start.hasPermission(p);
1183 }
1184
1185 @Override
1186 public void setPermission(Permission p) {
1187 _start.setPermission(p);
1188 _end.setPermission(p);
1189 }
1190
1191 @Override
1192 public boolean dontSave() {
1193 return true;
1194 }
1195
1196 @Override
1197 public void scale(Float scale, int originX, int originY) {
1198 }
1199}
Note: See TracBrowser for help on using the repository browser.