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

Last change on this file since 737 was 737, checked in by jts21, 10 years ago

Enable per-item permission values, and tidy up DefaultFrameWriter a little

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