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

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

Added more import and mail stuff... including text importer

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