source: trunk/src/org/expeditee/items/Item.java@ 963

Last change on this file since 963 was 954, checked in by bln4, 9 years ago
File size: 81.0 KB
Line 
1/**
2 * Item.java
3 * Copyright (C) 2010 New Zealand Digital Library, http://expeditee.org
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19package org.expeditee.items;
20
21import java.awt.BasicStroke;
22import java.awt.Color;
23import java.awt.Cursor;
24import java.awt.GradientPaint;
25import java.awt.Graphics2D;
26import java.awt.Point;
27import java.awt.Polygon;
28import java.awt.Rectangle;
29import java.awt.Shape;
30import java.awt.Stroke;
31import java.awt.geom.AffineTransform;
32import java.awt.geom.Area;
33import java.awt.geom.Point2D;
34import java.awt.geom.Rectangle2D;
35import java.util.ArrayList;
36import java.util.Collection;
37import java.util.ConcurrentModificationException;
38import java.util.HashSet;
39import java.util.LinkedHashSet;
40import java.util.LinkedList;
41import java.util.List;
42
43import org.expeditee.actions.Actions;
44import org.expeditee.actions.IncorrectUseOfStatementException;
45import org.expeditee.actions.Javascript;
46import org.expeditee.actions.Misc;
47import org.expeditee.actions.Simple;
48import org.expeditee.gui.AttributeValuePair;
49import org.expeditee.gui.DisplayIO;
50import org.expeditee.gui.Frame;
51import org.expeditee.gui.FrameGraphics;
52import org.expeditee.gui.FrameIO;
53import org.expeditee.gui.FrameKeyboardActions;
54import org.expeditee.gui.FrameMouseActions;
55import org.expeditee.gui.FrameUtils;
56import org.expeditee.gui.FreeItems;
57import org.expeditee.gui.MessageBay;
58import org.expeditee.gui.Overlay;
59import org.expeditee.gui.Vector;
60import org.expeditee.io.Conversion;
61import org.expeditee.settings.UserSettings;
62import org.expeditee.simple.Context;
63import org.expeditee.stats.AgentStats;
64import org.expeditee.stats.Formatter;
65import org.mozilla.javascript.Scriptable;
66import org.mozilla.javascript.ScriptableObject;
67
68/**
69 * Represents everything that can be drawn on the screen (text, lines, dots,
70 * images). Each specific type is a subclass of Item.
71 *
72 * @author jdm18
73 *
74 */
75public abstract class Item implements Comparable<Item>, Runnable {
76
77 public static final Float DEFAULT_THICKNESS = 2f;
78
79 public static final Float MINIMUM_THICKNESS = 0f;
80
81 public static final Float MINIMUM_PAINT_THICKNESS = 1f;
82
83 protected final int JOIN = BasicStroke.JOIN_ROUND;
84
85 protected final int CAP = BasicStroke.CAP_BUTT;
86
87 protected final Stroke DOT_STROKE = new BasicStroke(DEFAULT_THICKNESS,
88 CAP, JOIN, 4.0F);
89
90 protected final Stroke HIGHLIGHT_STROKE = new BasicStroke(
91 MINIMUM_THICKNESS, CAP, JOIN, 4.0F);
92
93 // contains all dots (including this one) that form an enclosure
94 // if this dot is part of an enclosing shape
95 private Collection<Item> _enclosure = null;
96
97 public static final int LEFT_MARGIN = 13;
98
99 // indicates which end the arrowhead should be drawn at
100 protected Polygon _poly = null;
101
102 protected boolean _connectedToAnnotation = false;
103
104 protected boolean _save = true;
105
106 private int _gradientAngle = 0;
107
108 public static final int NEAR_DISTANCE = 15;
109
110 /**
111 * The default Color to draw highlighting in
112 */
113 public static final int DEFAULT_HIGHLIGHT_THICKNESS = 2;
114
115 public static final Color DEFAULT_HIGHLIGHT = Color.RED;
116
117 public static final Color DEPRESSED_HIGHLIGHT = Color.GREEN;
118
119 public static final Color ALTERNATE_HIGHLIGHT = Color.BLUE;
120
121 public static final Color LINK_COLOR = Color.BLACK;
122
123 public static final Color ACTION_COLOR = Color.BLACK;
124
125 public static final Color LINK_ACTION_COLOR = Color.RED;
126
127 public static final Color DEFAULT_FOREGROUND = Color.BLACK;
128
129 public static final Color DEFAULT_BACKGROUND = Color.white;
130
131 public static final Color TRANSPARENT = new Color(0, 0, 0, 0);
132
133 /**
134 * The number of pixels highlighting should extend around Items.
135 */
136 public static final int XGRAVITY = 3;
137
138 public static final int MARGIN_RIGHT = 2;
139
140 public static final int MARGIN_LEFT = 15;
141
142 protected static final double DEFAULT_ARROWHEAD_RATIO = 0.3; // used to be 0.5
143
144 public static final double DEFAULT_ARROWHEAD_NIB_PERC = 0.75;
145
146 public static final Color GREEN = Color.GREEN.darker();
147
148 public static final int UNCHANGED_CURSOR = -100;
149
150 public static final int DEFAULT_CURSOR = Cursor.DEFAULT_CURSOR;
151
152 public static final int HIDDEN_CURSOR = Cursor.CUSTOM_CURSOR;
153
154 public static final int TEXT_CURSOR = Cursor.TEXT_CURSOR;
155
156 public static final int CROP_CURSOR = Cursor.CROSSHAIR_CURSOR;
157
158 // The default value for integer attributes
159 public static final int DEFAULT_INTEGER = -1;
160
161 protected DotType _type = DotType.square;
162
163 protected boolean _filled = true;
164
165 private Tooltip _tooltip = new Tooltip();
166
167 public enum AnchorEdgeType {
168 None, Left, Right, Top, Bottom
169 }
170
171 public static void DuplicateItem(Item source, Item dest) {
172 dest.setX(source.getX());
173 dest.setY(source.getY());
174
175 dest.setActions(source.getAction());
176 dest.setActionCursorEnter(source.getActionCursorEnter());
177 dest.setActionCursorLeave(source.getActionCursorLeave());
178 dest.setActionEnterFrame(source.getActionEnterFrame());
179 dest.setActionLeaveFrame(source.getActionLeaveFrame());
180 dest.setActionMark(source.getActionMark());
181
182 dest.setBackgroundColor(source.getBackgroundColor());
183 dest.setBottomShadowColor(source.getBottomShadowColor());
184 dest.setColor(source.getColor());
185 dest.setBorderColor(source.getBorderColor());
186
187 dest.setTooltips(source.getTooltip());
188 dest.setData(source.getData());
189 dest.setTag(source.getTag());
190 dest.setFillColor(source.getFillColor());
191 dest.setGradientColor(source.getGradientColor());
192 dest.setGradientAngle(source.getGradientAngle());
193 dest.setFillPattern(source.getFillPattern());
194
195 dest.setHighlight(source.getHighlight());
196 dest.setLink(source.getLink());
197 dest.setLinkFrameset(source.getLinkFrameset());
198 dest.setLinkMark(source.getLinkMark());
199 dest.setLinkTemplate(source.getLinkTemplate());
200
201 // dest.setMaxWidth(source.getMaxWidth());
202
203 dest.setOffset(source.getOffset());
204 // dest.setOwner(source.getOwner());
205 dest.setThickness(source.getThickness());
206 dest.setSize(source.getSize());
207 dest.setTopShadowColor(source.getTopShadowColor());
208 dest.setLinePattern(source.getLinePattern());
209
210 dest.setFloating(source.isFloating());
211 dest.setArrow(source.getArrowheadLength(), source.getArrowheadRatio(), source.getArrowheadNibPerc());
212
213 dest.setDotType(source.getDotType());
214 dest.setFilled(source.getFilled());
215 /*
216 * Calling the methods will move the item... This messes things up when
217 * the user uses backspace to delete a text line end
218 */
219 // dest._anchorLeft = source._anchorLeft;
220 // dest._anchorRight = source._anchorRight;
221 // dest._anchorTop = source._anchorTop;
222 // dest._anchorBottom = source._anchorBottom;
223 dest.setFormula(source.getFormula());
224 dest._overlay = source._overlay;
225 dest._mode = source._mode;// SelectedMode.None;
226 // dest._highlightColor = source._highlightColor;
227 // dest.setHighlight(source.getHighlight());
228
229 dest._visible = source._visible;
230
231 Frame parent = DisplayIO.getCurrentFrame();
232 if (parent == null)
233 parent = source.getParentOrCurrentFrame();
234 dest.setParent(parent);
235
236 /*
237 * TODO MIKE says maybe we could tighten up and only give items ID's if
238 * their current ID is negative?
239 */
240 if (parent != null) {
241 dest.setID(parent.getNextItemID());
242 }
243
244 if (parent != null && !UserSettings.UserName.equals(parent.getOwner())) {
245 dest.setOwner(UserSettings.UserName.get());
246 }
247 }
248
249 public void setGradientAngle(int gradientAngle) {
250 _gradientAngle = gradientAngle;
251
252 for (Line line : _lines) {
253 Item other = line.getOppositeEnd(this);
254 if (other.getGradientAngle() != gradientAngle)
255 other.setGradientAngle(gradientAngle);
256 }
257
258 invalidateCommonTrait(ItemAppearence.GradientColor);
259 invalidateFill();
260 }
261
262 public int getGradientAngle() {
263 return _gradientAngle;
264 }
265
266 public int getGravity() {
267 if (isVectorItem()) {
268 return 2;
269 }
270 return UserSettings.Gravity.get();
271 }
272
273 public static boolean showLineHighlight() {
274 return UserSettings.LineHighlight.get();
275 }
276
277 public enum HighlightMode {
278 None, Enclosed, Connected, Disconnect, Normal
279 }
280
281 public void setHighlightMode(HighlightMode mode) {
282 setHighlightMode(mode, DEFAULT_HIGHLIGHT);
283 }
284
285 protected Float _anchorLeft = null;
286 protected Float _anchorRight = null;
287
288 protected Float _anchorTop = null;
289 protected Float _anchorBottom = null;
290
291 protected HighlightMode _mode = HighlightMode.None;
292
293 private Point _offset = new Point(0, 0);
294
295 protected float _x;
296 protected float _y;
297
298 private int _id;
299
300 private Item _editTarget = this;
301
302 private String _creationDate = null;
303
304 private boolean _linkMark = true;
305
306 private boolean _actionMark = true;
307
308 private boolean _highlight = true;
309
310 // private int _maxWidth = -1;
311
312 private String _owner = null;
313
314 private String _link = null;
315
316 private boolean _linkHistory = true;
317
318 private StringBuffer _tag = new StringBuffer();
319
320 private List<String> _actionCursorEnter = null;
321
322 private List<String> _actionCursorLeave = null;
323
324 private List<String> _actionEnterFrame = null;
325
326 private List<String> _actionLeaveFrame = null;
327
328 private PermissionPair _permissionPair = null;
329
330 private UserAppliedPermission _overlayPermission = null;
331
332 public void setOverlayPermission(UserAppliedPermission overlayPermission) {
333 _overlayPermission = overlayPermission;
334 }
335
336 public void setPermission(PermissionPair permissionPair) {
337 _permissionPair = permissionPair;
338 }
339
340 public PermissionPair getPermission() {
341 return _permissionPair;
342 }
343
344 public UserAppliedPermission getUserAppliedPermission() {
345 String owner = _owner != null ? _owner : _parent != null ? _parent.getOwner() : null;
346 if(_permissionPair != null) return _permissionPair.getPermission(owner);
347 if(_overlayPermission != null) return _overlayPermission;
348 if(_parent != null) return _parent.getUserAppliedPermission();
349 return UserAppliedPermission.full;
350 }
351
352 public boolean hasPermission(UserAppliedPermission permission) {
353 return getUserAppliedPermission().ordinal() >= permission.ordinal();
354 }
355
356 // A fill color of null represents transparent
357 private Color _colorFill = null;
358
359 // A gradient color of null represents NO gradient
360 private Color _colorGradient = null;
361
362 // A fore color of null represents the default color
363 private Color _color = null;
364
365 protected Color _highlightColor = DEFAULT_HIGHLIGHT;
366
367 private Color _colorBackground = null;
368
369 private Color _colorBorder = null;
370
371 private Color _colorTopShadow = null;
372
373 private Color _colorBottomShadow = null;
374
375 // the link\action circle
376 private Polygon _circle = null;
377
378 // the invalid link cross
379 private Polygon _circleCross = null;
380
381 private Frame _parent = null;
382 private Frame _oldParent = null;
383
384 protected int _highlightThickness = 2;
385
386 protected int _vectorHighlightThickness = 1;
387
388 // arrowhead parameters
389 private float _arrowheadLength = 0;
390 private double _arrowheadRatio = DEFAULT_ARROWHEAD_RATIO;
391 private double _arrowheadNibPerc = DEFAULT_ARROWHEAD_NIB_PERC;
392
393 private Polygon _arrowhead = null;
394
395 // the list of lines that this point is part of.
396 private List<Line> _lines = new ArrayList<Line>();
397
398 private int[] _linePattern = null;
399
400 private boolean _floating = false;
401
402 // list of points constrained with this point
403 private List<Constraint> _constraints = new ArrayList<Constraint>();
404
405 private List<String> _actions = null;
406
407 private List<String> _data = null;
408
409 private String _formula = null;
410
411 private String _link_frameset = null;
412
413 private String _link_template = null;
414
415 private String _fillPattern = null;
416
417 private boolean _visible = true;
418
419 private float _thickness = -1.0F;
420
421 protected Item() {
422 _creationDate = Formatter.getLongDateTime();
423 }
424
425 /**
426 * Adds an action to this Item.
427 *
428 * @param action
429 * The action to add to this Item
430 */
431 public void addAction(String action) {
432 if (action == null || action.equals("")) {
433 return;
434 }
435
436 if (_actions == null) {
437 _actions = new LinkedList<String>();
438 }
439 _actions.add(action);
440 if (_actions.size() == 1) {
441 _poly = null;
442 invalidateCommonTrait(ItemAppearence.LinkChanged);
443 }
444 }
445
446 public void addAllConnected(Collection<Item> connected) {
447 if (!connected.contains(this))
448 connected.add(this);
449
450 for (Item item : getConnected()) {
451 if (!connected.contains(item))
452 item.addAllConnected(connected);
453 }
454 }
455
456 /**
457 * Adds the given Constraint to this Dot
458 *
459 * @param c
460 * The Constraint to set this Dot as a member of.
461 */
462 public void addConstraint(Constraint c) {
463 // do not add duplicate constraint
464 if (_constraints.contains(c))
465 return;
466
467 _constraints.add(c);
468 }
469
470 /**
471 * Adds a given line to the list of lines that this Point is an end for.
472 *
473 * @param line
474 * The Line that this Point is an end of.
475 */
476 public void addLine(Line line) {
477 if (_lines.contains(line)) {
478 return;
479 }
480
481 _lines.add(line);
482 }
483
484 /**
485 * Items are sorted by their Y coordinate on the screen.
486 *
487 * @param i
488 * The Item to compare this Item to
489 * @return a negative integer, zero, or a positive integer as this object is
490 * less than, equal to, or greater than the specified object.
491 */
492 public int compareTo(Item i) {
493 return getY() - i.getY();
494 }
495
496 /**
497 * Every Item has an area around it defined by a Shape (typically a
498 * rectangle), this method returns true if the given x,y pair lies within
499 * the area and false otherwise.
500 *
501 * @param x
502 * The x coordinate to check
503 * @param y
504 * The y coordinate to check
505 * @return True if the Shape around this Item contains the given x,y pair,
506 * false otherwise.
507 */
508 public boolean contains(int x, int y) {
509 return getPolygon().contains(x, y);
510 }
511
512 /**
513 * Returns a deep copy of this Item, note: it is up to the receiver to
514 * change the Item ID etc as necessary.
515 *
516 * @return A deep copy of this Item.
517 */
518 public abstract Item copy();
519
520 public void delete() {
521 _deleted = true;
522 }
523
524 @Override
525 public boolean equals(Object o) {
526 if (o == null)
527 return false;
528 if (getClass().equals(o.getClass())) {
529 Item i = (Item) o;
530 return i.getID() == getID()
531 && ((i.getParent() == _parent) || (i.getParent() != null && i
532 .getParent().equals(_parent)));
533 } else
534 return false;
535 }
536
537 /**
538 * Returns a list of any action code that is currently associated with this
539 * Item
540 *
541 * @return A List of action code associated with this Item, or null if none
542 * has been assigned.
543 */
544 public List<String> getAction() {
545 return _actions;
546 }
547
548 public List<String> getData() {
549 return _data;
550 }
551
552 public List<String> getActionCursorEnter() {
553 return _actionCursorEnter;
554 }
555
556 public List<String> getActionCursorLeave() {
557 return _actionCursorLeave;
558 }
559
560 public List<String> getActionEnterFrame() {
561 return _actionEnterFrame;
562 }
563
564 public List<String> getActionLeaveFrame() {
565 return _actionLeaveFrame;
566 };
567
568 public boolean getActionMark() {
569 return _actionMark;
570 }
571
572 /**
573 * Gets all the items connected to this item. Uses a recursive approach to
574 * search connected points.
575 *
576 * @return
577 */
578 public Collection<Item> getAllConnected() {
579 Collection<Item> list = new LinkedHashSet<Item>();
580 addAllConnected(list);
581 return list;
582 }
583
584 public Area getArea() {
585 return new Area(getPolygon());
586 }
587
588 public String getArrow() {
589 if (!hasVisibleArrow())
590 return null;
591
592 String ratio = "" + getArrowheadRatio();
593 if (ratio.length() - ratio.indexOf(".") > 2)
594 ratio = ratio.substring(0, ratio.indexOf(".") + 3);
595
596 return getArrowheadLength() + " " + ratio + " " + getArrowheadNibPerc();
597 }
598
599 public Polygon getArrowhead() {
600 return _arrowhead;
601 }
602
603 public float getArrowheadLength() {
604 return _arrowheadLength;
605 }
606
607 public double getArrowheadRatio() {
608 return _arrowheadRatio;
609 }
610
611 public double getArrowheadNibPerc() {
612 return _arrowheadNibPerc;
613 }
614
615 public Color getBackgroundColor() {
616 return _colorBackground;
617 }
618
619 public Color getBorderColor() {
620 return _colorBorder;
621 }
622
623 /**
624 * Returns the Color being used to shade the bottom half of this Item's
625 * border. This can be NULL if no Color is being used
626 *
627 * @return The Color displayed on the bottom\right half of this Item's
628 * border.
629 */
630 public Color getBottomShadowColor() {
631 return _colorBottomShadow;
632 }
633
634 /**
635 * Returns the height (in pixels) of this Item's surrounding area.
636 *
637 * @return The height (in pixels) of this Item's surrounding area as
638 * returned by getArea().
639 */
640 public int getBoundsHeight() {
641 return getPolygon().getBounds().height;
642 }
643
644 /**
645 * Returns the width (in pixels) of this Item's surrounding area.
646 *
647 * @return The width (in pixels) of this Item's surrounding area as returned
648 * by getArea().
649 */
650 public int getBoundsWidth() {
651 return getPolygon().getBounds().width;
652 }
653
654 // TODO draw the link with a circle rather than a polygon!!
655 public Polygon getLinkPoly() {
656 if (_circle == null) {
657 int points = 16;
658
659 double radians = 0.0;
660 int xPoints[] = new int[points];
661 int yPoints[] = new int[xPoints.length];
662
663 for (int i = 0; i < xPoints.length; i++) {
664 // circle looks best if these values are not related to gravity
665 xPoints[i] = (int) (3.5 * Math.cos(radians)) + 6;// (2 *
666 // GRAVITY);
667 yPoints[i] = (int) (3.5 * Math.sin(radians)) + 3;// GRAVITY;
668 radians += (2.0 * Math.PI) / xPoints.length;
669 }
670
671 _circle = new Polygon(xPoints, yPoints, xPoints.length);
672 }
673
674 return _circle;
675 }
676
677 protected Polygon getCircleCross() {
678
679 if (_circleCross == null) {
680 _circleCross = new Polygon();
681
682 Rectangle bounds = getLinkPoly().getBounds();
683 int x1 = (int) bounds.getMinX();
684 int x2 = (int) bounds.getMaxX();
685 int y1 = (int) bounds.getMinY();
686 int y2 = (int) bounds.getMaxY();
687 int midX = ((x2 - x1) / 2) + x1;
688 int midY = ((y2 - y1) / 2) + y1;
689
690 _circleCross.addPoint(x1, y1);
691 _circleCross.addPoint(x2, y2);
692 _circleCross.addPoint(midX, midY);
693 _circleCross.addPoint(x1, y2);
694 _circleCross.addPoint(x2, y1);
695 _circleCross.addPoint(midX, midY);
696 }
697
698 return _circleCross;
699 }
700
701 public Color getColor() {
702 return _color;
703 }
704
705 public Collection<Item> getConnected() {
706 List<Item> conn = new LinkedList<Item>();
707 conn.add(this);
708 conn.addAll(getEnclosures());
709 conn.addAll(getLines());
710 return conn;
711 }
712
713 public String getConstraintIDs() {
714 if (_constraints == null || _constraints.size() == 0)
715 return null;
716
717 String cons = "";
718
719 for (Constraint c : _constraints)
720 cons += c.getID() + " ";
721
722 return cons.trim();
723 }
724
725 /*
726 * public void setLinkValid(boolean val) { _isValidLink = val; }
727 */
728
729 /**
730 * Returns a List of any Constraints that this Dot is a memeber of.
731 *
732 * @return a List of Constraints that this Dot is a member of.
733 */
734 public List<Constraint> getConstraints() {
735 return _constraints;
736 }
737
738 public String getTag() {
739 if (_tag != null && _tag.length() > 0)
740 return _tag.toString();
741 return null;
742 }
743
744 public String getDateCreated() {
745 return _creationDate;
746 }
747
748 public Color getFillColor() {
749 return _colorFill;
750 }
751
752 public String getFillPattern() {
753 return _fillPattern;
754 }
755
756 public String getFirstAction() {
757 if (_actions == null || _actions.size() == 0)
758 return null;
759 return _actions.get(0);
760 }
761
762 public boolean getHighlight() {
763 return _highlight;
764 }
765
766 public Color getHighlightColor() {
767 if (_highlightColor.equals(getPaintColor()))
768 return getAlternateHighlightColor();
769 return getDefaultHighlightColor();
770 }
771
772 /**
773 * Returns the ID of this Item, which must be unique for the Frame.
774 *
775 * @return The ID of this Item.
776 */
777 public int getID() {
778 return _id;
779 }
780
781 /**
782 * Returns the list of IDs of the Lines that this Dot is an end of.
783 *
784 * @return The list of Line IDs that this point is part of.
785 */
786 public String getLineIDs() {
787 String lineID = null;
788
789 if (_lines.size() > 0) {
790 lineID = "" + _lines.get(0).getID();
791
792 for (int i = 1; i < _lines.size(); i++)
793 lineID += " " + _lines.get(i).getID();
794 }
795
796 return lineID;
797 }
798
799 public int[] getLinePattern() {
800 return _linePattern;
801 }
802
803 /**
804 * Returns a list of Lines where this Dot is an end.
805 *
806 * @return A list of the Lines that this Dot is an end for or null if no
807 * Lines have been added.
808 */
809 public List<Line> getLines() {
810 return _lines;
811 }
812
813 /**
814 * Returns the name of a Frame that this Item links to, or null if this Item
815 * has no link.
816 *
817 * @return The name of a Frame that this Item links to (if any) or null if
818 * this Item does not link to anything.
819 */
820 public String getLink() {
821 return _link;
822 }
823
824 public String getFormula() {
825 return _formula;
826 }
827
828 public boolean hasFormula() {
829 return _formula != null;
830 }
831
832 public boolean hasAttributeValuePair() {
833 return _attributeValuePair != null && _attributeValuePair.hasPair();
834 }
835
836 public void setFormula(String formula) {
837 _formula = formula;
838 }
839
840 public boolean calculate(String formula) {
841 setFormula(formula);
842 return true;
843 }
844
845 public String getLinkFrameset() {
846 return _link_frameset;
847 }
848
849 public boolean getLinkMark() {
850 return _linkMark;
851 }
852
853 public String getLinkTemplate() {
854 return _link_template;
855 }
856
857 // public int getMaxWidth() {
858 // return _maxWidth;
859 // }
860
861 public Point getOffset() {
862 return _offset;
863 }
864
865 public String getOwner() {
866 return _owner;
867 }
868
869 public Color getPaintBackgroundColor() {
870 Color colorBackground = getBackgroundColor();
871 if (colorBackground == null) {
872 if (getParent() != null && getParent().getBackgroundColor() != null)
873 return getParent().getBackgroundColor();
874
875 return DEFAULT_BACKGROUND;
876 }
877
878 return colorBackground;
879 }
880
881 /**
882 * Returns the foreground Color of this Item.
883 *
884 * @return The Color of this item (foreground)
885 */
886 public final Color getPaintColor() {
887 // If color is null then get the paint foregroundColor for the frame the
888 // item is on which is a color adjusted to suit the background
889 Color color = getColor();
890
891 if (color == null) {
892 if (getParent() != null)
893 return getParent().getPaintForegroundColor();
894
895 Frame current = DisplayIO.getCurrentFrame();
896 if (current == null) {
897 return DEFAULT_FOREGROUND;
898 }
899 return current.getPaintForegroundColor();
900 }
901
902 return color;
903 }
904
905 public final Color getPaintBorderColor() {
906 // If color is null then get the paint foregroundColor for the frame the
907 // item is on which is a color adjusted to suit the background
908 Color color = getBorderColor();
909
910 if (color == null) {
911 if (getParent() != null)
912 return getParent().getPaintForegroundColor();
913
914 Frame current = DisplayIO.getCurrentFrame();
915 if (current == null) {
916 return DEFAULT_FOREGROUND;
917 }
918 return current.getPaintForegroundColor();
919 }
920
921 return color;
922 }
923
924 protected Color getPaintHighlightColor() {
925 Color highlightColor = getDefaultHighlightColor();
926 if (hasVisibleBorder()) {
927 if (getPaintBorderColor().equals(highlightColor)) {
928 highlightColor = getDefaultHighlightColor();
929 }
930 } else if (getPaintBackgroundColor().equals(highlightColor)) {
931 highlightColor = getDefaultHighlightColor();
932 }
933 if (getParent() != null
934 && getParent().getPaintBackgroundColor().equals(highlightColor))
935 highlightColor = getParent().getPaintForegroundColor();
936
937 if (hasVisibleBorder()) {
938 if (highlightColor.equals(getBorderColor())
939 && getThickness() == getHighlightThickness()) {
940 highlightColor = new Color(highlightColor.getRed(),
941 highlightColor.getGreen(), highlightColor.getBlue(),
942 150);
943 }
944 }
945
946 return highlightColor;
947 }
948
949 static final int BRIGHTNESS = 185;
950
951 protected Color getDefaultHighlightColor() {
952 if (isVectorItem()
953 && !this.contains(FrameMouseActions.getX(), FrameMouseActions
954 .getY())) {
955 return new Color(255, BRIGHTNESS, BRIGHTNESS);
956 }
957 return _highlightColor;
958 }
959
960 protected Color getAlternateHighlightColor() {
961 if (isVectorItem()
962 && !this.contains(FrameMouseActions.getX(), FrameMouseActions
963 .getY())) {
964 return new Color(BRIGHTNESS, BRIGHTNESS, 255);
965 }
966 return ALTERNATE_HIGHLIGHT;
967 }
968
969 protected int getHighlightThickness() {
970 if (isVectorItem())
971 return _vectorHighlightThickness;
972 return _highlightThickness;
973 }
974
975 public final Frame getParent() {
976 return _parent;
977 }
978
979 public final Point getPosition() {
980 return new Point(getX(), getY());
981 }
982
983 /**
984 * Returns the size of this Item. For Text this is the Font size, for Lines
985 * and Dots this is the thickness.
986 *
987 * @return The size of this Item.
988 */
989 public float getSize() {
990 return -1.0F;
991 }
992
993 /**
994 * Returns the Color being used to shade the top half of this Item's border.
995 * This can be NULL if no Color is being used
996 *
997 * @return The Color displayed on the top\left half of this Item's border.
998 */
999 public Color getTopShadowColor() {
1000 return _colorTopShadow;
1001 }
1002
1003 public String getTypeAndID() {
1004 return "T " + getID();
1005 }
1006
1007 public Integer getWidthToSave() {
1008 return getWidth();
1009 }
1010
1011 public Integer getWidth() {
1012 return null;
1013 }
1014
1015 public int getHeight() {
1016 return 0;
1017 }
1018
1019 /**
1020 * Returns the X coordinate of this Item on the screen
1021 *
1022 * @return The X coordinate of this Item on the screen
1023 */
1024 public int getX() {
1025 return Math.round(_x);
1026 }
1027
1028 /**
1029 * Returns the Y coordinate of this Item on the screen
1030 *
1031 * @return The Y coordinate of this Item on the screen
1032 */
1033 public int getY() {
1034 return Math.round(_y);
1035 }
1036
1037 public boolean hasVisibleArrow() {
1038 return isLineEnd() && getArrowheadRatio() != 0 && getArrowheadLength() != 0;
1039 }
1040
1041 /**
1042 * Checks if the given Shape intersects with the Shape around this Item.
1043 *
1044 * @param s
1045 * The Shape to check.
1046 * @return True if the two Shapes overlap, False otherwise.
1047 */
1048 public boolean intersects(Polygon p) {
1049 if (p == null)
1050 return false;
1051
1052 Area a = new Area(p);
1053 Area thisArea = this.getArea();
1054 // Need to do this check for circles
1055 if (a.equals(thisArea))
1056 return true;
1057
1058 a.intersect(thisArea);
1059
1060 // Need to check the second equality so that we dont pick up circles
1061 // inside other circles
1062 return !a.isEmpty() && !a.equals(new Area(p));
1063 }
1064
1065 /**
1066 * Note: Pictures always return False, as they should be drawn even when no
1067 * other annotation Items are.
1068 *
1069 * @return True if this Item is an annotation, False otherwise.
1070 */
1071 public boolean isAnnotation() {
1072 return false;
1073 }
1074
1075 public boolean isFloating() {
1076 return _floating;
1077 }
1078
1079 public boolean isFrameName() {
1080 if (this.getParent() == null || this.getParent().getNameItem() != this)
1081 return false;
1082 return true;
1083 }
1084
1085 public boolean isFrameTitle() {
1086 if (this.getParent() == null || this.getParent().getTitleItem() != this)
1087 return false;
1088 return true;
1089 }
1090
1091 /**
1092 * Returns True if this Item is currently highlighted.
1093 *
1094 * @return True if this Item is currently highlighted on the screen, False
1095 * otherwise.
1096 */
1097 public boolean isHighlighted() {
1098 if (isFloating())
1099 return false;
1100 return _mode != HighlightMode.None;
1101 }
1102
1103 /**
1104 * Tests if the item link is a valid framename, that is, the String must
1105 * begin with a character, end with a number with 0 or more letters and
1106 * numbers in between. If there is a dot in the framename all the chars
1107 * after it must be digits.
1108 *
1109 * @return True if the given framename is proper, false otherwise.
1110 */
1111 public boolean isLinkValid() {
1112 if (FrameIO.isPositiveInteger(getLink()))
1113 return true;
1114
1115 if (FrameIO.isValidFrameName(getLink()))
1116 return true;
1117 return false;
1118 }
1119
1120 public boolean isNear(int x, int y) {
1121
1122 int xLeft = getPolygon().getBounds().x;
1123 int yTop = getPolygon().getBounds().y;
1124
1125 return (x > xLeft - NEAR_DISTANCE && y > yTop - NEAR_DISTANCE
1126 && x < xLeft + getBoundsWidth() + NEAR_DISTANCE && y < yTop
1127 + getBoundsHeight() + NEAR_DISTANCE);
1128 }
1129
1130 public boolean isOldTag() {
1131 if (this instanceof Text)
1132 if (((Text) this).getTextList().get(0).toLowerCase().equals("@old"))
1133 return true;
1134 return false;
1135 }
1136
1137 /**
1138 * Merges this Item with the given Item. The merger Item should be left
1139 * unchanged after this method. The merger may or may not be the same class
1140 * as this Item, exact behaviour depends on the subclass, No-op is allowed.
1141 *
1142 * @param merger
1143 * The Item to merge with
1144 * @return any Item that should remain on the cursor
1145 */
1146 public abstract Item merge(Item merger, int mouseX, int mouseY);
1147
1148 /**
1149 * Displays this item directly on the screen. Note: All Items are
1150 * responsible for their own drawing, buffering, etc.
1151 *
1152 * @param g
1153 * The Graphics to draw this Item on.
1154 */
1155 public abstract void paint(Graphics2D g);
1156
1157 public void setTooltips(final List<String> tooltips) {
1158 if(tooltips == null || tooltips.size() == 0) _tooltip = new Tooltip();
1159 else for(final String content: tooltips) _tooltip.addTooltip(content);
1160 }
1161
1162 public void setTooltip(final String tooltip) {
1163 if(tooltip != null && tooltip.trim().length() > 0) {
1164 _tooltip.addTooltip(tooltip);
1165 }
1166 }
1167
1168 public List<String> getTooltip() {
1169 return _tooltip.asStringList();
1170 }
1171
1172 public List<Text> getTooltipItems() {
1173 return _tooltip.getTooltips();
1174 }
1175
1176 public void clearTooltips() {
1177 final Frame frame = this.getParent();
1178 if(_tooltip != null)
1179 for(final Text tooltip: _tooltip.getTooltips()) frame.removeItem(tooltip);
1180 }
1181
1182 public void paintTooltip(final Graphics2D g) {
1183 final Rectangle bounds = this.getPolygon().getBounds();
1184 int x = bounds.x + bounds.width;
1185 if(x + _tooltip.getWidth() > FrameGraphics.getMaxFrameSize().width) {
1186 x -= x + _tooltip.getWidth() - FrameGraphics.getMaxFrameSize().width;
1187 }
1188 int y = bounds.y + bounds.height;
1189 if(y + _tooltip.getCollectiveHeight() > FrameGraphics.getMaxFrameSize().height) {
1190 y = bounds.y + bounds.height / 2 - _tooltip.getCollectiveHeight();
1191 }
1192 for(final Text tooltip : _tooltip.getTooltips()) {
1193 this.getParent().addItem(tooltip);
1194 tooltip.setPosition(x, y);
1195 tooltip.paint(g);
1196 y += tooltip.getHeight();
1197 }
1198 }
1199
1200 public void paintFill(Graphics2D g) {
1201 Color fillColor = getFillColor();
1202 if (fillColor != null && getEnclosingDots() != null) {
1203 setFillPaint(g);
1204 g.fillPolygon(getEnclosedShape());
1205 }
1206 }
1207
1208 protected void setFillPaint(Graphics2D g) {
1209 Color fillColor = getFillColor();
1210 if (isFloating()) {
1211 // TODO experiment with adding alpha when picking up filled
1212 // items... Slows things down quite alot!!
1213 fillColor = new Color(fillColor.getRed(), fillColor.getGreen(),
1214 fillColor.getBlue(), fillColor.getAlpha());
1215 }
1216 g.setColor(fillColor);
1217 Color gradientColor = getGradientColor();
1218 if (gradientColor != null) {
1219 /*
1220 * It is slow when painting gradients... modify so this is only done
1221 * once unless it is resized...
1222 */
1223 Shape s = getEnclosedShape();
1224 if (s != null) {
1225 Rectangle b = s.getBounds();
1226 double rads = getGradientAngle() * Math.PI / 180;
1227 double cos = Math.cos(rads);
1228 double sin = Math.sin(rads);
1229
1230 GradientPaint gp = new GradientPaint((int) (b.x + b.width
1231 * (0.2 * cos + 0.5)), (int) (b.y + b.height
1232 * (0.2 * sin + 0.5)), fillColor, (int) (b.x + b.width
1233 * (-0.8 * cos + 0.5)), (int) (b.y + b.height
1234 * (-0.8 * sin + 0.5)), gradientColor);
1235 g.setPaint(gp);
1236 }
1237 }
1238 }
1239
1240 /**
1241 * This method performs all the actions in an items list. If it contains a
1242 * link as well the link is used as the source frame for all acitons.
1243 */
1244 public void performActions() {
1245 Frame sourceFrame = null;
1246 Item sourceItem = FreeItems.getItemAttachedToCursor();
1247
1248 if (sourceItem == null) {
1249 sourceItem = this;
1250 } else {
1251 for (Item i : sourceItem.getAllConnected()) {
1252 if (i instanceof Text) {
1253 sourceItem = i;
1254 break;
1255 }
1256 }
1257 }
1258
1259 // TODO decide whether to have items or
1260 // if a link exists make it the source frame for this action
1261 if (getLink() != null) {
1262 sourceFrame = FrameUtils.getFrame(getAbsoluteLink());
1263 }
1264 // if no link exists or the link is bad then use the
1265 // currently displayed frame as the source frame for the
1266 // action
1267 if (sourceFrame == null) {
1268 // For actions like format they rely on this being set to the
1269 // current frame incase the item being activated is on an overlay
1270 sourceFrame = DisplayIO.getCurrentFrame();
1271 }
1272
1273 for (String s : getAction()) {
1274 Object returnValue = Actions.PerformActionCatchErrors(sourceFrame,
1275 sourceItem, s);
1276 if (returnValue != null) {
1277 FreeItems.getInstance().clear();
1278 if (returnValue instanceof Item) {
1279 Misc.attachToCursor(((Item) returnValue).getAllConnected());
1280 } else if (returnValue instanceof Collection) {
1281 try {
1282 Misc.attachToCursor((Collection) returnValue);
1283 } catch (Exception e) {
1284 e.printStackTrace();
1285 }
1286 } else {
1287 Misc.attachStatsToCursor(returnValue.toString());
1288 }
1289 }
1290 }
1291 }
1292
1293 /**
1294 * Removes all constraints that this item has.
1295 *
1296 */
1297 public void removeAllConstraints() {
1298 while (_constraints.size() > 0) {
1299 Constraint c = _constraints.get(0);
1300 c.getEnd().removeConstraint(c);
1301 c.getStart().removeConstraint(c);
1302 }
1303 }
1304
1305 /**
1306 * Clears the list of Lines that this Dot is an end of. Note: This only
1307 * clears this Dot's list and does not have any affect on the Lines or other
1308 * Dots.
1309 */
1310 public void removeAllLines() {
1311 for (Line l : _lines) {
1312 l.invalidateAll();
1313 }
1314 _lines.clear();
1315 }
1316
1317 /**
1318 * Removes the given Constraint from the list of constraints that this Dot
1319 * is a part of.
1320 *
1321 * @param c
1322 * The Constraint that this Dot is no longer a part of.
1323 */
1324 public void removeConstraint(Constraint c) {
1325 _constraints.remove(c);
1326 }
1327
1328 /**
1329 * Removes the given Line from the list of lines that this Dot is an end
1330 * for.
1331 *
1332 * @param line
1333 * The Line that this Dot is no longer an end of.
1334 */
1335 public void removeLine(Line line) {
1336 if (_lines.remove(line))
1337 line.invalidateAll();
1338 }
1339
1340 public void run() {
1341 try {
1342
1343 List<String> action = this.getAction();
1344 if (action != null) {
1345 String action_name = action.get(0);
1346 if (action_name.equalsIgnoreCase("RunJavascriptFrame")){
1347 // Associate a new Context with this thread
1348 org.mozilla.javascript.Context javascript_context = org.mozilla.javascript.Context.enter();
1349 try {
1350 Scriptable javascript_scope = javascript_context.initStandardObjects();
1351 Context simple_context = new Context();
1352
1353
1354 //Object jsDisplayIO = org.mozilla.javascript.Context.javaToJS(org.expeditee.gui.DisplayIO, javascript_scope);
1355 //ScriptableObject.putProperty(javascript_scope, "displayIO", jsDisplayIO);
1356
1357
1358 Object jsSimpleContext = org.mozilla.javascript.Context.javaToJS(simple_context, javascript_scope);
1359 ScriptableObject.putProperty(javascript_scope, "simpleContext", jsSimpleContext);
1360
1361 Object jsErr = org.mozilla.javascript.Context.javaToJS(System.err, javascript_scope);
1362 ScriptableObject.putProperty(javascript_scope, "err", jsErr);
1363
1364 Object jsOut = org.mozilla.javascript.Context.javaToJS(System.out, javascript_scope);
1365 ScriptableObject.putProperty(javascript_scope, "out", jsOut);
1366
1367 Javascript.ProgramStarted();
1368 Javascript.RunFrameAndReportError(this, javascript_context,javascript_scope);
1369 MessageBay.displayMessage(AgentStats.getStats(), GREEN);
1370 }
1371 finally {
1372 org.mozilla.javascript.Context.exit();
1373 }
1374 }
1375 }
1376 else {
1377
1378 // assume it is a simple program that is to be run
1379 Simple.ProgramStarted();
1380 Context simple_context = new Context();
1381 Simple.RunFrameAndReportError(this, simple_context);
1382 MessageBay.displayMessage(AgentStats.getStats(), GREEN);
1383 }
1384 } catch (ConcurrentModificationException ce) {
1385 ce.printStackTrace();
1386 } catch (IncorrectUseOfStatementException ise) {
1387 MessageBay.linkedErrorMessage(ise.getMessage());
1388 MessageBay.displayMessage("See SIMPLE doc for ["
1389 + ise.getStatement() + "] statement", ise.getStatement()
1390 + "1", Color.CYAN.darker(), true, null);
1391 } catch (Exception e) {
1392 MessageBay.linkedErrorMessage(e.getMessage());
1393 }
1394 Simple.ProgramFinished();
1395 // Need to repaint any highlights etc
1396 FrameGraphics.requestRefresh(true);
1397 }
1398
1399 /**
1400 * Check if it has a relative link if so make it absolute.
1401 *
1402 */
1403 public void setAbsoluteLink() {
1404 String link = getLink();
1405 if (link == null)
1406 return;
1407 // Check if all the characters are digits and hence it is a relative
1408 // link
1409 if (!FrameIO.isPositiveInteger(link))
1410 return;
1411
1412 // Make it an absolute link
1413 String framesetName;
1414
1415 if (_parent == null)
1416 framesetName = DisplayIO.getCurrentFrame().getFramesetName();
1417 else
1418 framesetName = _parent.getFramesetName();
1419
1420 setLink(framesetName + link);
1421 }
1422
1423 /**
1424 * Sets any action code that should be associated with this Item Each entry
1425 * in the list is one line of code
1426 *
1427 * @param actions
1428 * The lines of code to associate with this Item
1429 */
1430 public void setActions(List<String> actions) {
1431 if (actions == null || actions.size() == 0) {
1432 invalidateCommonTrait(ItemAppearence.LinkChanged);
1433 _actions = null;
1434 } else
1435 _actions = new LinkedList<String>(actions);
1436
1437 // Want to resize the highlight box for text items if actions have been
1438 // added
1439 _poly = null;
1440 invalidateCommonTrait(ItemAppearence.LinkChanged);
1441 }
1442
1443 public void setData(List<String> data) {
1444 if (data == null || data.size() == 0)
1445 _data = null;
1446 else
1447 _data = new LinkedList<String>(data);
1448 }
1449
1450 public void setData(String data) {
1451 if (data == null || data.length() == 0)
1452 _data = null;
1453 else {
1454 _data = new LinkedList<String>();
1455 _data.add(data);
1456 }
1457 }
1458
1459 public void addToData(String dataItem) {
1460 if (dataItem != null) {
1461 if (_data == null)
1462 _data = new LinkedList<String>();
1463 _data.add(dataItem);
1464 }
1465 }
1466
1467 public void setActionCursorEnter(List<String> enter) {
1468 _actionCursorEnter = enter;
1469 }
1470
1471 public void setActionCursorLeave(List<String> leave) {
1472 _actionCursorLeave = leave;
1473 }
1474
1475 public void setActionEnterFrame(List<String> enter) {
1476 _actionEnterFrame = enter;
1477 }
1478
1479 public void setActionLeaveFrame(List<String> leave) {
1480 _actionLeaveFrame = leave;
1481 }
1482
1483 public void setActionMark(boolean val) {
1484 if (!val)
1485 invalidateCommonTrait(ItemAppearence.LinkChanged);
1486 _poly = null;
1487 _actionMark = val;
1488 if (val)
1489 invalidateCommonTrait(ItemAppearence.LinkChanged);
1490 }
1491
1492 /**
1493 * Sets whether this Item is an Annotation.
1494 *
1495 * @param val
1496 * True if this Item is an Annotation, False otherwise.
1497 */
1498 public abstract void setAnnotation(boolean val);
1499
1500 /**
1501 * Used to set this Line as an Arrow. If length and ratio are 0, no arrow is
1502 * shown.
1503 *
1504 * @param length
1505 * The how far down the shaft of the line the arrowhead should
1506 * come.
1507 * @param ratio
1508 * The ratio of the arrow's length to its width.
1509 */
1510 public void setArrow(float length, double ratio, double nib_perc) {
1511 _arrowheadLength = length;
1512 _arrowheadRatio = ratio;
1513 _arrowheadNibPerc = nib_perc;
1514 updateArrowPolygon();
1515 }
1516
1517 public void setArrow(float length, double ratio) {
1518 setArrow(length,ratio,DEFAULT_ARROWHEAD_NIB_PERC);
1519 }
1520
1521 public void setArrowhead(Polygon arrow) {
1522 _arrowhead = arrow;
1523 }
1524
1525 public void setArrowheadLength(float length) {
1526 _arrowheadLength = length;
1527 updateArrowPolygon();
1528 }
1529
1530 public void setArrowheadRatio(double ratio) {
1531 _arrowheadRatio = ratio;
1532 updateArrowPolygon();
1533 }
1534
1535 public void setArrowheadNibPerc(double perc) {
1536 _arrowheadNibPerc = perc;
1537 updateArrowPolygon();
1538 }
1539
1540 public void setBackgroundColor(Color c) {
1541 if (c != _colorBackground) {
1542 _colorBackground = c;
1543 invalidateCommonTrait(ItemAppearence.BackgroundColorChanged);
1544 }
1545 }
1546
1547 public void setBorderColor(Color c) {
1548 if (c != _colorBorder) {
1549 _colorBorder = c;
1550 invalidateCommonTrait(ItemAppearence.BorderColorChanged);
1551 }
1552 }
1553
1554 /**
1555 * Sets the Color to use on the bottom and right sections of this Item's
1556 * border. If top is NULL, then the Item's background Color will be used.
1557 *
1558 * @param top
1559 * The Color to display in the bottom and right sections of this
1560 * Item's border.
1561 */
1562 public void setBottomShadowColor(Color bottom) {
1563 _colorBottomShadow = bottom;
1564 }
1565
1566 /**
1567 * Sets the foreground Color of this Item to the given Color.
1568 *
1569 * @param c
1570 */
1571 public void setColor(Color c) {
1572 if (c != _color) {
1573 _color = c;
1574 invalidateCommonTrait(ItemAppearence.ForegroundColorChanged);
1575 if (hasVector()) {
1576 // TODO make this more efficient so it only repaints the items
1577 // for this vector
1578 FrameKeyboardActions.Refresh();
1579 }
1580 }
1581 }
1582
1583 public void setConstraintIDs(String IDs) {
1584 }
1585
1586 public void setConstraints(List<Constraint> constraints) {
1587 _constraints = constraints;
1588 }
1589
1590 public void setTag(String newData) {
1591 if (newData != null)
1592 _tag = new StringBuffer(newData);
1593 else
1594 _tag = null;
1595 }
1596
1597 /**
1598 * Sets the created date of this Frame to the given String.
1599 *
1600 * @param date
1601 * The date to use for this Frame.
1602 */
1603 public void setDateCreated(String date) {
1604 _creationDate = date;
1605 }
1606
1607 public void setFillColor(Color c) {
1608
1609 _colorFill = c;
1610
1611 for (Line line : _lines) {
1612 Item other = line.getOppositeEnd(this);
1613 if (other.getFillColor() != c) {
1614 other.setFillColor(c);
1615 }
1616 }
1617
1618 invalidateCommonTrait(ItemAppearence.FillColor);
1619 invalidateFill();
1620 }
1621
1622 public void setGradientColor(Color c) {
1623 _colorGradient = c;
1624
1625 for (Line line : _lines) {
1626 Item other = line.getOppositeEnd(this);
1627 if (other.getGradientColor() != c)
1628 other.setGradientColor(c);
1629 }
1630
1631 invalidateCommonTrait(ItemAppearence.GradientColor);
1632 invalidateFill();
1633 }
1634
1635 public Color getGradientColor() {
1636 return _colorGradient;
1637 }
1638
1639 public void setFillPattern(String patternLink) {
1640 _fillPattern = patternLink;
1641 invalidateCommonTrait(ItemAppearence.FillPattern);
1642 invalidateFill();
1643 }
1644
1645 public void setFloating(boolean val) {
1646 _floating = val;
1647 }
1648
1649 public void setHighlight(boolean val) {
1650 _highlight = val;
1651 }
1652
1653 /**
1654 * Sets the ID of this Item to the given Integer. Note: Items with ID's < 0
1655 * are not saved
1656 *
1657 * @param newID
1658 * The new ID to assign this Item.
1659 */
1660 public void setID(int newID) {
1661 _id = newID;
1662 }
1663
1664 /**
1665 * Sets the list of lines that this point is part of (may be set to null).
1666 *
1667 * @param lineID
1668 * A String of line ID numbers separated by spaces.
1669 */
1670 public void setLineIDs(String lineID) {
1671 }
1672
1673 public void setLinePattern(int[] pattern) {
1674 _linePattern = pattern;
1675
1676 for (Line line : getLines())
1677 line.setLinePattern(pattern);
1678 }
1679
1680 public void setLines(List<Line> lines) {
1681 _lines = lines;
1682
1683 for (Line line : lines)
1684 line.setLinePattern(getLinePattern());
1685
1686 }
1687
1688 /**
1689 * Links this item to the given Frame, this may be set to null to remove a
1690 * link.
1691 *
1692 * @param frameName
1693 * The name of the Frame to link this item to.
1694 */
1695 public void setLink(String frameName) {
1696 if (frameName == null) {
1697 invalidateCommonTrait(ItemAppearence.LinkChanged);
1698 }
1699
1700 // If a link is being removed or set then need to reset poly so the
1701 // highlighting is drawn with the correct width
1702 if (frameName == null || getLink() == null)
1703 _poly = null;
1704
1705 if (FrameIO.isValidLink(frameName))
1706 _link = frameName;
1707 else
1708 MessageBay.errorMessage("[" + frameName
1709 + "] is not a valid frame name");
1710 // TODO make this throw exceptions etc...
1711
1712 invalidateCommonTrait(ItemAppearence.LinkChanged);
1713 }
1714
1715 public void setLinkHistory(boolean value) {
1716 _linkHistory = value;
1717 }
1718
1719 public boolean getLinkHistory() {
1720 return _linkHistory;
1721 }
1722
1723 public void setLinkFrameset(String frameset) {
1724 if (frameset == null || FrameIO.isValidFramesetName(frameset))
1725 _link_frameset = frameset;
1726 else
1727 MessageBay.errorMessage("[" + frameset
1728 + "] is not a valid frameset name");
1729 // TODO make this throw exceptions etc...
1730 }
1731
1732 public void setLinkMark(boolean val) {
1733 if (!val)
1734 invalidateCommonTrait(ItemAppearence.LinkChanged);
1735 _poly = null;
1736 _linkMark = val;
1737 if (val)
1738 invalidateCommonTrait(ItemAppearence.LinkChanged);
1739 }
1740
1741 public void setLinkTemplate(String template) {
1742 if (FrameIO.isValidLink(template))
1743 _link_template = template;
1744 else
1745 MessageBay.errorMessage("[" + template
1746 + "] is not a valid frame name");
1747 // TODO make this throw exceptions etc...
1748 }
1749
1750 // /**
1751 // * Sets the maximum coordinates on the screen that this item may occupy.
1752 // * This is used by Text items to compute word-wrapping lengths.
1753 // *
1754 // * @param d
1755 // * The Maximum size of the Frame containing this Item.
1756 // */
1757 // public void setMaxWidth(int width) {
1758 // if (width > 0) {
1759 // _maxWidth = width;
1760 // updatePolygon();
1761 // }
1762 // }
1763
1764 public void setOffset(int x, int y) {
1765 _offset.setLocation(x, y);
1766 }
1767
1768 public void setOffset(Point p) {
1769 _offset.setLocation(p);
1770 }
1771
1772 public void setOwner(String own) {
1773 _owner = own;
1774 }
1775
1776 public void setParent(Frame frame) {
1777 _oldParent = _parent;
1778 _parent = frame;
1779
1780 if (_parent != null && UserSettings.UserName != null
1781 && !UserSettings.UserName.equals(_parent.getOwner())) {
1782 setOwner(UserSettings.UserName.get());
1783 }
1784 }
1785
1786 /**
1787 * Invalidates this, connected lines and fill
1788 *
1789 * @param trait
1790 */
1791 private void invalidateCommonTraitForAll(ItemAppearence trait) {
1792 invalidateCommonTrait(trait);
1793 if (isLineEnd()) {
1794 boolean hasLinePattern = getLines().get(0).getLinePattern() != null;
1795 if (hasLinePattern) {
1796 for (Item i : getAllConnected()) {
1797 if (i instanceof Line) {
1798 ((Line) i).invalidateCommonTrait(trait);
1799 }
1800 }
1801 } else {
1802 for (Line line : getLines()) {
1803 line.invalidateCommonTrait(trait);
1804 }
1805 }
1806 }
1807 if (_colorFill != null) {
1808 invalidateFill(); // only invalidates if has fill
1809 }
1810 for (XRayable x : getEnclosures()) {
1811 x.invalidateCommonTrait(trait);
1812 }
1813
1814 }
1815
1816
1817
1818
1819
1820 protected void anchorConstraints()
1821 {
1822 // update the position of any dots that are constrained by this one
1823 for (Constraint c : _constraints) {
1824 Item other = c.getOppositeEnd(this);
1825
1826 // only set position if the other dot is still fixed to the
1827 // frame
1828 if (/* this.isFloating() && */!other.isFloating()) {
1829 if (c.getType() == Constraint.HORIZONTAL) {
1830 if (isAnchoredY()) {
1831 // Make the 'other' item have the same anchor top/bottom values as this
1832 other._anchorTop = _anchorTop;
1833 other._anchorBottom = _anchorBottom;
1834 }
1835 } else if (c.getType() == Constraint.VERTICAL) {
1836 if (isAnchoredX()) {
1837 // Make the 'other' item have the same anchor left/right values as this
1838 other._anchorLeft = _anchorLeft;
1839 other._anchorRight = _anchorRight;
1840 }
1841 } else if (c.isDiagonal()) {
1842
1843 System.err.println("Warning: anchorConstraints() not implement for Diagonal setting");
1844 }
1845 }
1846 }
1847 }
1848
1849 /**
1850 * Sets the position of this item on the screen
1851 *
1852 * @param x
1853 * The new X coordinate
1854 * @param y
1855 * The new Y coordinate
1856 */
1857 public void setPosition(float x, float y) {
1858 float deltaX = x - _x;
1859 float deltaY = y - _y;
1860
1861 if (deltaX == 0 && deltaY == 0)
1862 return;
1863
1864 invalidateCommonTraitForAll(ItemAppearence.PreMoved);
1865
1866 _x = x;
1867 _y = y;
1868
1869 for (Item i : getEnclosures()) {
1870 i.updatePolygon();
1871 }
1872 updatePolygon();
1873
1874 // update the position of any dots that are constrained by this one
1875 for (Constraint c : _constraints) {
1876 Item other = c.getOppositeEnd(this);
1877
1878 // only set position if the other dot is still fixed to the
1879 // frame
1880 if (/* this.isFloating() && */!other.isFloating()) {
1881 if (c.getType() == Constraint.HORIZONTAL) {
1882 if (other._y != y) {
1883 other.setY(y);
1884 }
1885 } else if (c.getType() == Constraint.VERTICAL) {
1886 if (other._x != x) {
1887 other.setX(x);
1888 }
1889 } else if (c.isDiagonal()) {
1890 if (Math.abs(other._x - x) != Math.abs(other._y - y)) {
1891
1892 float m1 = c.getGradient();
1893 float c1 = y - m1 * x;
1894 // Now work out the equation for the second line
1895 // Get the first line the other end is attached to that
1896 // is not the diagonal line
1897 List<Line> lines = other.getLines();
1898 // If there is only one line...
1899 if (lines.size() == 1) {
1900 if (m1 != 0) {
1901 if (Math.abs(deltaX) > Math.abs(deltaY)) {
1902 other.setX((other._y - c1) / m1);
1903 } else {
1904 other.setY(m1 * other._x + c1);
1905 }
1906 }
1907 } else if (lines.size() > 1) {
1908 Line otherLine = lines.get(0);
1909 Item end = otherLine.getOppositeEnd(other);
1910 if (end.equals(this)) {
1911 otherLine = lines.get(1);
1912 end = otherLine.getOppositeEnd(other);
1913 assert (!end.equals(this));
1914 }
1915
1916 float xDiff = end._x - other._x;
1917 float yDiff = end._y - other._y;
1918 if (xDiff == 0) {
1919 other.setY(m1 * other._x + c1);
1920 } else if (Math.abs(xDiff) == Math.abs(yDiff)
1921 && !this.isFloating() && deltaX == 0
1922 && deltaY == 0) {
1923 if (deltaX == 0) {
1924 _x = (_y - other._y) * m1 + other._x;
1925 } else {
1926 _y = (_x - other._x) * m1 + other._y;
1927 }
1928 } else {
1929 float m2 = yDiff / xDiff;
1930 float c2 = end._y - m2 * end._x;
1931 float mDiff = m1 - m2;
1932 if (Math.abs(mDiff) < 0.000001) {
1933 assert (false);
1934 // TODO how do I handle this case!!
1935 } else {
1936 float newX = (c2 - c1) / mDiff;
1937 float newY = m1 * newX + c1;
1938 if (other._x != newX
1939 /* && other._y != newY */) {
1940 other.setPosition(newX, newY);
1941 }
1942 }
1943 }
1944 }
1945 // Do simultaneous equations to get the new postion for
1946 // the other end of the diagonal line
1947 }
1948 }
1949 }
1950 }
1951
1952 for (Line line : getLines()) {
1953 line.updatePolygon();
1954 }
1955
1956 // for (Item item : getAllConnected()) {
1957 // item.updatePolygon();
1958 // }
1959
1960 invalidateCommonTraitForAll(ItemAppearence.PostMoved);
1961
1962 }
1963
1964 public void setPosition(Point position) {
1965 setPosition(position.x, position.y);
1966 }
1967
1968 public void setRelativeLink() {
1969 String link = getLink();
1970 if (link == null)
1971 return;
1972 assert (_parent != null);
1973
1974 if (FrameIO.isPositiveInteger(link))
1975 return;
1976
1977 // Check if the link is for the current frameset
1978 if (_parent.getFramesetName().equalsIgnoreCase(
1979 Conversion.getFramesetName(link))) {
1980 setLink("" + Conversion.getFrameNumber(link));
1981 }
1982 }
1983
1984 /**
1985 * Sets the size of this Item. For Text this is the Font size. For Lines and
1986 * Dots this is the thickness.
1987 */
1988 public void setSize(float size) {
1989 }
1990
1991 /**
1992 * Sets the thickness of the item.
1993 *
1994 * @param thick
1995 */
1996 public final void setThickness(float thick) {
1997 setThickness(thick, true);
1998 }
1999
2000 /**
2001 * Sets the thickness of this item.
2002 *
2003 * @param thick
2004 * the new thickness for the item
2005 * @param setConnectedThickness
2006 * true if all items connected to this item should also have
2007 * their thickness set
2008 */
2009 public void setThickness(float thick, boolean setConnectedThickness) {
2010 if (thick == _thickness)
2011 return;
2012 boolean bigger = thick > _thickness;
2013
2014 if (!bigger) {
2015 if (setConnectedThickness) {
2016 // TODO is there a more efficient way of doing this?
2017 for (Item i : getConnected())
2018 i.invalidateCommonTrait(ItemAppearence.Thickness);
2019 } else {
2020 invalidateCommonTrait(ItemAppearence.Thickness);
2021 }
2022 }
2023
2024 _thickness = thick;
2025 // update the size of any lines
2026 /*
2027 * TODO: Revise the way line thickness is set to make it more efficient
2028 * etc...
2029 */
2030 for (Line line : getLines())
2031 line.setThickness(thick, setConnectedThickness);
2032
2033 if (setConnectedThickness)
2034 updatePolygon();
2035
2036 if (bigger) {
2037 if (setConnectedThickness) {
2038 for (Item i : getConnected())
2039 i.invalidateCommonTrait(ItemAppearence.Thickness);
2040 } else {
2041 invalidateCommonTrait(ItemAppearence.Thickness);
2042 }
2043 }
2044 }
2045
2046 /**
2047 * Returns the thickness (in pixels) of this Dot.
2048 *
2049 * @return The 'thickness' of this Dot. (returns -1 if the thickness is not
2050 * set).
2051 */
2052 public float getThickness() {
2053 return _thickness;
2054 }
2055
2056 /**
2057 * Sets the Color to use on the top and left sections of this Item's border.
2058 * If top is NULL, then the Item's background Color will be used.
2059 *
2060 * @param top
2061 * The Color to display in the top and left sections of this
2062 * Item's border.
2063 */
2064 public void setTopShadowColor(Color top) {
2065 _colorTopShadow = top;
2066 }
2067
2068 public void setWidth(Integer width) throws UnsupportedOperationException {
2069 throw new UnsupportedOperationException(
2070 "Item type does not support width attribute!");
2071 }
2072
2073 public void setRightMargin(int i, boolean fixWidth) {
2074 int newWidth = i - getX() - Item.MARGIN_LEFT;
2075 if (!fixWidth) {
2076 newWidth *= -1;
2077 }
2078
2079 setWidth(newWidth);
2080 }
2081
2082 /**
2083 * Sets the position of this Item on the X axis
2084 *
2085 * @param newX
2086 * The position on the X axis to assign to this Item
2087 */
2088 public void setX(float newX) {
2089 setPosition(newX, getY());
2090 }
2091
2092 /**
2093 * Sets the position of this Item on the Y axis
2094 *
2095 * @param newY
2096 * The position on the Y axis to assign to this Item
2097 */
2098 public void setY(float newY) {
2099 setPosition(getX(), newY);
2100 }
2101
2102 /**
2103 * Paints any highlighting of this Item. This may include changing the
2104 * thickness (lines) or painting a box around the item (Text, Images). If
2105 * val is True then the Graphics Color is changed to the highlight Color, if
2106 * False then the Graphics Color is left unchanged (for clearing of
2107 * highlighting).
2108 *
2109 * @param val
2110 * True if this Item should be highlighted, false if the
2111 * highlighting is being cleared.
2112 * @return The desired mouse cursor when this Item is highlighted (negative
2113 * means no change)
2114 */
2115 public int setHighlightColor() {
2116 return setHighlightColor(DEFAULT_HIGHLIGHT);
2117 }
2118
2119 public int setHighlightColor(Color c) {
2120 if (!this.isVisible() && this.hasVector()) {
2121 for (Item i : this.getParentOrCurrentFrame().getVectorItems()) {
2122 if (i.getEditTarget() == this) {
2123 i.setHighlightColor(c);
2124 }
2125 }
2126 }
2127
2128 _highlightThickness = DEFAULT_HIGHLIGHT_THICKNESS;
2129
2130 Color selColor = (c != null) ? c : DEFAULT_HIGHLIGHT;
2131 if (_highlightColor != c) {
2132 _highlightColor = selColor;
2133 this.invalidateCommonTrait(ItemAppearence.HighlightColorChanged);
2134 }
2135
2136 return Item.UNCHANGED_CURSOR;
2137
2138 }
2139
2140 private void updateArrowPolygon() {
2141 if (getArrowheadLength() < 0 || getArrowheadRatio() < 0 || getArrowheadNibPerc() < 0)
2142 _arrowhead = null;
2143 else {
2144 _arrowhead = Line.createArrowheadPolygon(getX(),getY(),getArrowheadLength(),getArrowheadRatio(),getArrowheadNibPerc());
2145 }
2146 }
2147
2148 public abstract void updatePolygon();
2149
2150 public void setHidden(boolean state) {
2151 this._visible = !state;
2152 }
2153
2154 public void setVisible(boolean state) {
2155 this._visible = state;
2156 }
2157
2158 public boolean isVisible() {
2159 return _visible && !_deleted;
2160 }
2161
2162 /**
2163 * Raised whenever the item is removed, added, no longer in view (That is,
2164 * when it is not on any of the current frames, of overlays of the current
2165 * frames) or has become visible. That is, when it is either on a current
2166 * frame, or an overlay of a current frame.
2167 *
2168 * @param e
2169 * The event
2170 */
2171 public void onParentStateChanged(ItemParentStateChangedEvent e) {
2172 }
2173
2174 public void setHighlightMode(HighlightMode mode, Color color) {
2175 setHighlightColor(color);
2176 if (hasPermission(UserAppliedPermission.followLinks)
2177 || getEditTarget().hasPermission(UserAppliedPermission.followLinks)) {
2178 if (_mode != mode) {
2179 _mode = mode;
2180 this.invalidateCommonTrait(ItemAppearence.HighlightModeChanged);
2181 }
2182 }
2183 }
2184
2185 public HighlightMode getHighlightMode() {
2186 return _mode;
2187 }
2188
2189 public void anchor() {
2190 Frame current = getParentOrCurrentFrame();
2191 // only set the id if we've moved to a different frame, or if the frame already has an item with that id
2192 if(!current.equals(_oldParent) || current.getItemWithID(getID()) != null) {
2193 int id = _id;
2194 setID(current.getNextItemID());
2195 // System.out.println(this + " - Set ID to " + _id + " (was " + id + ")");
2196 } else {
2197 // System.out.println(this + " - Kept old ID of " + _id);
2198 }
2199 setOffset(0, 0);
2200 setParent(current);
2201
2202 current.addItem(this, false);
2203 current.setResort(true);
2204 setRelativeLink();
2205 setFloating(false);
2206
2207 // // If its an unconstrained line end check if we should add a
2208 // constraint
2209 // if (isLineEnd() && getLines().size() <= 2
2210 // && getConstraints().size() <= 1) {
2211 // Constraint existingConstraint = null;
2212 // List<Constraint> constraints = getConstraints();
2213 // // Get the existing constraint
2214 // if (constraints.size() > 0) {
2215 // existingConstraint = constraints.get(0);
2216 // }
2217 // for (Line line : getLines()) {
2218 // Integer constraintType = line.getPossibleConstraint();
2219 // if (constraintType != null) {
2220 // Item oppositeEnd = line.getOppositeEnd(this);
2221 // if (existingConstraint == null
2222 // || !existingConstraint.contains(oppositeEnd)) {
2223 // new Constraint(this, oppositeEnd,
2224 // getParentOrCurrentFrame().getNextItemID(),
2225 // constraintType);
2226 // }
2227 // }
2228 // }
2229 // }
2230 }
2231
2232 /**
2233 * Gets the parent frame if it is set or the current frame if this item does
2234 * not have a parent set.
2235 *
2236 * @return
2237 */
2238 public Frame getParentOrCurrentFrame() {
2239 // if the item is from an overlay the parent will NOT be null
2240 if (getParent() == null) {
2241 return DisplayIO.getCurrentFrame();
2242 }
2243 return getParent();
2244 }
2245
2246 /**
2247 * Sets the list of Dots (including this one) that form a closed shape.
2248 * Passing null sets this dot back to its normal (non-enclosed) state.
2249 *
2250 * @param enclosed
2251 * The List of Dots including this one that form a closed shape,
2252 * or null.
2253 */
2254 public void setEnclosedList(Collection<Item> enclosed) {
2255
2256 boolean changed = (_enclosure == null && enclosed != null);
2257
2258 if (_enclosure != null && enclosed == null) {
2259 invalidateFill();
2260 }
2261
2262 _enclosure = enclosed;
2263
2264 if (changed) {
2265 invalidateFill();
2266 ;
2267 }
2268 }
2269
2270 /**
2271 * Returns the polygon that represents the shape created by all the Dots in
2272 * this Dot's enclosed list. If the list is null, then null is returned.
2273 *
2274 * @return A Polygon the same shape and position as created by the Dots in
2275 * the enclosed list.
2276 */
2277 public Polygon getEnclosedShape() {
2278 if (_enclosure == null)
2279 return null;
2280
2281 Polygon poly = new Polygon();
2282 for (Item d : _enclosure) {
2283 poly.addPoint(d.getX(), d.getY());
2284 }
2285
2286 return poly;
2287 }
2288
2289 /**
2290 * Returns the list of Dots that, along with this Dot, form an enclosed
2291 * polygon. If this Dot is not part of an enclosure null may be returned.
2292 *
2293 * @return The List of Dots that form an enclosed shape with this Dot, or
2294 * null if this Dot is not part of an enclosure.
2295 */
2296 public Collection<Item> getEnclosingDots() {
2297 return _enclosure;
2298 }
2299
2300 /**
2301 * Returns whether this Dot has an assigned enclosure list of other Dots.
2302 * The result is the same as getEnclosedShape() != null.
2303 *
2304 * @return True if this Dot has an enclosure list of other Dots, false
2305 * otherwise.
2306 */
2307 public boolean isEnclosed() {
2308 return _enclosure != null;
2309 }
2310
2311 /**
2312 * True if this item is the end of a line.
2313 *
2314 * @return
2315 */
2316 public boolean isLineEnd() {
2317 // TODO this will need to be redone when enclosure class is added...
2318 // At the moment enclosures are only circles...we don't want circle
2319 // centres to be lineEnds
2320 return _lines.size() > 0;
2321 }
2322
2323 public boolean hasEnclosures() {
2324 return _enclosures.size() > 0;
2325 }
2326
2327 /**
2328 * Method that is called to notify an item that is on the end of a line that
2329 * its line has changed color.
2330 *
2331 * @param c
2332 * the new color for the line
2333 */
2334 protected void lineColorChanged(Color c) {
2335 for (Line l : getLines()) {
2336 if (l.getColor() != c)
2337 l.setColor(c);
2338 }
2339 }
2340
2341 /**
2342 * Checks if this item is off the left or top of the screen
2343 *
2344 * @return
2345 */
2346 public boolean offScreenTopOrLeft() {
2347 Rectangle itemRect = getArea().getBounds();
2348 // Check that the bottom right corner of this item is on the screen
2349 if (itemRect.x + itemRect.width >= 0
2350 && itemRect.y + itemRect.height >= 0)
2351 return false;
2352 // Check if all the items it is connected to are offscreen
2353 for (Item i : getAllConnected()) {
2354 Rectangle iRect = i.getArea().getBounds();
2355 // Check that the bottom right corner of this item is on the screen
2356 if (iRect.x + iRect.width >= 0 && iRect.y + iRect.height >= 0) {
2357 return false;
2358 }
2359 }
2360 return true;
2361 }
2362
2363 public void setConnectedToAnnotation(boolean val) {
2364 _connectedToAnnotation = val;
2365 }
2366
2367 public boolean isConnectedToAnnotation() {
2368 return _connectedToAnnotation;
2369 }
2370
2371 public boolean hasAction() {
2372 List<String> actions = getAction();
2373 return actions != null && actions.size() > 0;
2374 }
2375
2376 public void setAction(String action) {
2377 // Want to resize the highlight box for text items if actions are been
2378 // added
2379 if (action == null || action.length() == 0) {
2380 invalidateCommonTrait(ItemAppearence.LinkChanged);
2381 }
2382 if (_actions == null || _actions.size() == 0) {
2383 _poly = null;
2384 _actions = new LinkedList<String>();
2385 } else {
2386 _actions.clear();
2387 }
2388 if (action != null && action.length() > 0)
2389 _actions.add(action);
2390 invalidateCommonTrait(ItemAppearence.LinkChanged);
2391 }
2392
2393 protected int getLinkYOffset() {
2394 return 0;
2395 }
2396
2397 protected Rectangle getLinkDrawArea() {
2398 return getLinkDrawArea(getX() - LEFT_MARGIN, getY() + getLinkYOffset());
2399 }
2400
2401 /**
2402 * TODO: Revise - it would be good to have a member that defines the link
2403 * dimensions.
2404 *
2405 * @param x
2406 * Left of graphic (i.e not centered)
2407 * @param y
2408 * Above graphic (i.e not centered)
2409 *
2410 * @return The drawing area of the link at the given coordinates.
2411 */
2412 public Rectangle getLinkDrawArea(int x, int y) {
2413 return new Rectangle(x + 2, y - 1, 8, 8);
2414 }
2415
2416 /**
2417 * Paint the link symbol for the item if it is a
2418 *
2419 * @param g
2420 */
2421 protected void paintLink(Graphics2D g) {
2422 paintLinkGraphic(g, getX() - LEFT_MARGIN, getY() + getLinkYOffset());
2423 }
2424
2425 /**
2426 * Paint the link symbol for the item at a given position.
2427 *
2428 * @see #paintLink
2429 *
2430 * @param g
2431 * The graphics to paint with
2432 *
2433 * @param x
2434 * The x position of the link. Left of graphic (i.e not centered)
2435 *
2436 * @param y
2437 * The y position of the link. Above of graphic (i.e not
2438 * centered)
2439 */
2440 public void paintLinkGraphic(Graphics2D g, int x, int y) {
2441
2442 boolean hasLink = getLink() != null;
2443 boolean hasAction = hasAction();
2444
2445 if (hasLink || hasAction) {
2446 g.setStroke(HIGHLIGHT_STROKE);
2447 if (hasLink && hasAction) {
2448 g.setColor(LINK_ACTION_COLOR);
2449 } else if (hasLink) {
2450 g.setColor(LINK_COLOR);
2451 } else if (hasAction) {
2452 g.setColor(ACTION_COLOR);
2453 }
2454
2455 AffineTransform at = new AffineTransform();
2456 AffineTransform orig = g.getTransform();
2457 at.translate(x, y);
2458 g.setTransform(at);
2459
2460 if (getLinkMark() && getLink() != null) {
2461 g.drawPolygon(getLinkPoly());
2462
2463 // if the link is not valid, cross out the circle
2464 if (!isLinkValid())
2465 g.drawPolygon(getCircleCross());
2466 }
2467
2468 if (getActionMark() && hasAction()) {
2469 g.drawPolygon(getLinkPoly());
2470 g.fillPolygon(getLinkPoly());
2471
2472 // if the link is not valid, cross out the circle
2473 if (!isLinkValid() && getLink() != null) {
2474 g.setColor(getParent().getPaintBackgroundColor());
2475 g.drawPolygon(getCircleCross());
2476 }
2477 }
2478
2479 // reset the graphics tranformation
2480 g.setTransform(orig);
2481 }
2482 }
2483
2484 /**
2485 * Gets the distance between the start of the text and the left border of
2486 * the item. This distance changes depending on whether or not the item is
2487 * linked or has an associated action.
2488 *
2489 * @return the gap size in pixels
2490 */
2491 protected int getLeftMargin() {
2492 return ((getLinkMark() && getLink() != null)
2493 || (getActionMark() && getAction() != null) ? MARGIN_LEFT
2494 - MARGIN_RIGHT : MARGIN_RIGHT);
2495 }
2496
2497 public String getName() {
2498 return getText();
2499 }
2500
2501 final public String getAbsoluteLinkTemplate() {
2502 return getAbsoluteLink(getLinkTemplate());
2503 }
2504
2505 final public String getAbsoluteLinkFrameset() {
2506 return getAbsoluteLink(getLinkFrameset());
2507 }
2508
2509 final public String getAbsoluteLink() {
2510 return getAbsoluteLink(getLink());
2511 }
2512
2513 /**
2514 * @param link
2515 * @return
2516 */
2517 private String getAbsoluteLink(String link) {
2518 if (link == null)
2519 return null;
2520 // assert (_parent!= null);
2521 Frame parent = getParentOrCurrentFrame();
2522 if (_parent == null) {
2523 // if parent is null it is an item on the message box
2524 // so it must already be absolute
2525 // assert (!FrameIO.isPositiveInteger(link));
2526 // return link;
2527
2528 }
2529
2530 // if its a relative link then return absolute
2531 if (FrameIO.isPositiveInteger(link)) {
2532 return parent.getFramesetName() + link;
2533 }
2534 return link;
2535 }
2536
2537 public static String convertToAbsoluteLink(String link) {
2538 if (link == null)
2539 return null;
2540 // assert (_parent!= null);
2541 Frame parent = DisplayIO.getCurrentFrame();
2542 assert (parent != null);
2543
2544 // if its a relative link then return absolute
2545 if (FrameIO.isPositiveInteger(link)) {
2546 return parent.getFramesetName() + link;
2547 }
2548 return link;
2549 }
2550
2551 /**
2552 * Sets the x and y values of this item ignoring constraints.
2553 *
2554 * @param x
2555 * new x position
2556 * @param y
2557 * new y position
2558 */
2559 public void setXY(float x, float y) {
2560 _x = x;
2561 _y = y;
2562 }
2563
2564 /**
2565 * Recursive function for getting the path around a shape. This is used to
2566 * get the path that is painted on the screen.
2567 *
2568 * @param visited
2569 * @param points
2570 * @param addToEnd
2571 * @param toExplore
2572 */
2573 public void appendPath(Collection<Line> visited, LinkedList<Point> points,
2574 boolean addToEnd, Collection<Line> toExplore) {
2575
2576 if (addToEnd) {
2577 // put the start item points into our list
2578 points.addLast(getPosition());
2579 } else {
2580 points.addFirst(getPosition());
2581 }
2582
2583 // Find the line that has not been added yet
2584 LinkedList<Line> lines = new LinkedList<Line>();
2585 lines.addAll(getLines());
2586
2587 while (!lines.isEmpty()) {
2588 Line l = lines.remove();
2589 // if we havnt visited the line yet visit it
2590 if (!visited.contains(l)) {
2591 visited.add(l);
2592 Item otherEnd = l.getOppositeEnd(this);
2593 // Add all the enexplored lines to our list
2594 while (!lines.isEmpty()) {
2595 l = lines.remove();
2596 // Get the paths for the rest of the lines to be explored
2597 // later
2598 if (!toExplore.contains(l) && !visited.contains(l)) {
2599 toExplore.add(l);
2600 }
2601 }
2602 otherEnd.appendPath(visited, points, addToEnd, toExplore);
2603 }
2604 }
2605 }
2606
2607 /**
2608 * Gets the size of the enclosure that this item is part of. Used to
2609 * determine the paint order of items, with smaller items being painted
2610 * first.
2611 *
2612 * @return the area of the box surrounding the enclosed shape that this item
2613 * is part of
2614 */
2615 public double getEnclosedArea() {
2616 if (_enclosure == null)
2617 return 0.0;
2618 Rectangle2D box = getEnclosedShape().getBounds2D();
2619 return box.getWidth() * box.getHeight();
2620 }
2621
2622 public Rectangle getEnclosedRectangle() {
2623 if (_enclosure == null)
2624 return null;
2625 return getEnclosedShape().getBounds();
2626 }
2627
2628 public int getEnclosureID() {
2629 return _enclosure == null ? 0 : _enclosure.hashCode();
2630 }
2631
2632 /**
2633 * Returns the Shape that surrounds this Item representing this Item's
2634 * 'gravity'.
2635 *
2636 * @return The Shape (rectangle) surrounding this Item, which represents
2637 * this Items 'gravity'.
2638 */
2639 public final Polygon getPolygon() {
2640 if (_poly == null)
2641 updatePolygon();
2642
2643 return new Polygon(_poly.xpoints, _poly.ypoints, _poly.npoints);
2644 }
2645
2646 /**
2647 * Shifts the position of the item along the line between this items
2648 * location and a specified point.
2649 *
2650 * @param origin
2651 * @param ratio
2652 */
2653 public void translate(Point2D origin, double ratio) {
2654
2655 invalidateCommonTraitForAll(ItemAppearence.PreMoved);
2656
2657 _x = (float) (origin.getX() + ratio * (_x - origin.getX()));
2658 _y = (float) (origin.getY() + ratio * (_y - origin.getY()));
2659 updatePolygon();
2660 for (Line line : getLines())
2661 line.updatePolygon();
2662
2663 invalidateCommonTraitForAll(ItemAppearence.PostMoved);
2664
2665 }
2666
2667 private static int[] LinePatterns = new int[] { 0, 10, 20 };
2668
2669 /**
2670 * The rotates through a wheel of dashed lines.
2671 *
2672 * @param amount
2673 * number of rotations around the wheel to toggle by.
2674 */
2675 public void toggleDashed(int amount) {
2676 // find the index of the current line pattern
2677 int[] currentPattern = getLinePattern();
2678
2679 // Find the current pattern and move to the next pattern in the wheel
2680 for (int i = 0; i < LinePatterns.length; i++) {
2681 if (currentPattern == null || currentPattern[0] == LinePatterns[i]) {
2682 i += LinePatterns.length + amount;
2683 i %= LinePatterns.length;
2684
2685 // if we are at the start of the wheel make it 'null' (solid
2686 // line)
2687 if (i == 0) {
2688 setLinePattern(null);
2689 } else {
2690 setLinePattern(new int[] { LinePatterns[i], LinePatterns[i] });
2691 }
2692
2693 invalidateCommonTrait(ItemAppearence.ToggleDashed);
2694 return;
2695 }
2696 }
2697
2698 }
2699
2700 Collection<XRayable> _enclosures = new HashSet<XRayable>();
2701
2702 private boolean _deleted = false;
2703
2704 private Overlay _overlay = null;
2705
2706 protected AttributeValuePair _attributeValuePair = null;
2707
2708 private Float _autoStamp = null;
2709
2710 /**
2711 * For now there can only be one enclosure per item
2712 *
2713 * @param enclosure
2714 */
2715 public void addEnclosure(XRayable enclosure) {
2716 _enclosures.clear();
2717 _enclosures.add(enclosure);
2718 }
2719
2720 /**
2721 * Gets any XRayable items that have this item as a source.
2722 *
2723 * @return the collection of items that are linked to this item as source.
2724 * Guaranteed not to be null.
2725 */
2726 public Collection<? extends XRayable> getEnclosures() {
2727 return _enclosures;
2728 }
2729
2730 public void removeEnclosure(Item i) {
2731 _enclosures.remove(i);
2732
2733 }
2734
2735 public boolean isDeleted() {
2736 return _deleted;
2737 }
2738
2739 /**
2740 * @return The full canvas that this item draws to. Must include
2741 * highlighting bounds
2742 */
2743 public Rectangle[] getDrawingArea() {
2744
2745 return new Rectangle[] { ItemUtils.expandRectangle(getPolygon()
2746 .getBounds(), (int) Math.ceil(Math.max(_highlightThickness,
2747 getThickness()))) };
2748
2749 }
2750
2751 /**
2752 *
2753 * @param area
2754 * @return True if area intersects with this items drawing area.
2755 */
2756 public final boolean isInDrawingArea(Area area) {
2757 for (Rectangle r : getDrawingArea()) {
2758 if (area.intersects(r))
2759 return true;
2760 }
2761 return false;
2762 }
2763
2764 /**
2765 * Completetly invalidates the item - so that it should be redrawed. Note:
2766 * This is handled internally, it should be reare to invoke this externally
2767 */
2768 public final void invalidateAll() {
2769 invalidate(getDrawingArea());
2770 }
2771
2772 /**
2773 * Invalidates areas on the parent frame. Purpose: to be called on specific
2774 * areas of the item that needs redrawing.
2775 *
2776 * @param damagedAreas
2777 */
2778 protected final void invalidate(Rectangle[] damagedAreas) {
2779 for (Rectangle r : damagedAreas)
2780 invalidate(r);
2781 }
2782
2783 /**
2784 * Invalidates areas on the parent frame. Purpose: to be called on specific
2785 * areas of the item that needs redrawing.
2786 *
2787 * @param damagedAreas
2788 */
2789 protected final void invalidate(Rectangle damagedArea) {
2790 FrameGraphics.invalidateItem(this, damagedArea);
2791 }
2792
2793 /**
2794 * Used to invalidate visual traits commonly shared by all items.
2795 *
2796 * @param trait
2797 */
2798 public final void invalidateCommonTrait(ItemAppearence trait) {
2799 invalidate(getDamagedArea(trait));
2800
2801 if (_colorFill != null
2802 && (trait == ItemAppearence.Added || trait == ItemAppearence.Removed)) {
2803 invalidateFill();
2804 }
2805 }
2806
2807 /**
2808 * Invalidates fill if has one, even if no color is set.
2809 */
2810 public void invalidateFill() {
2811 if (isLineEnd() && _enclosure != null) {
2812 invalidate(getEnclosedShape().getBounds());
2813 }
2814 }
2815
2816 /**
2817 * Default implementation always uses drawing area except for links, where
2818 * the link drawing area is used. Override to make item drawing more
2819 * efficient - defining only parts of the item that needs redrawing.
2820 *
2821 * @see Item.getDrawingArea
2822 *
2823 * @param trait
2824 * The visual trait that has changed.
2825 *
2826 * @return The damaged area according to the visual trait that has changed.
2827 */
2828 protected Rectangle[] getDamagedArea(ItemAppearence trait) {
2829
2830 if (trait == ItemAppearence.LinkChanged)
2831 return new Rectangle[] { getLinkDrawArea() }; // Invalidate area
2832 // where link is
2833 // drawn
2834
2835 return getDrawingArea();
2836
2837 }
2838
2839 public boolean hasVector() {
2840 return _overlay instanceof Vector;
2841 }
2842
2843 public boolean hasOverlay() {
2844 return _overlay != null;
2845 }
2846
2847 public Vector getVector() {
2848 if (_overlay instanceof Vector)
2849 return (Vector) _overlay;
2850 return null;
2851 }
2852
2853 public void setOverlay(Overlay overlay) {
2854 _overlay = overlay;
2855 }
2856
2857 public boolean dontSave() {
2858 /*
2859 * TODO Mike says: checkout if the ID check is still needed- When will
2860 * ID still be -1 when saving a frame? assert (i != null);
2861 */
2862 // make it save stuff that's off the screen so stuff isn't deleted by panning - jts21
2863 return !_save || !isVisible() || getID() < 0; // || offScreenTopOrLeft();
2864 }
2865
2866 public void setAnchorLeft(Float anchor) {
2867 this._anchorLeft = anchor;
2868 this._anchorRight = null;
2869 if (anchor != null) {
2870 anchorConstraints();
2871 setX(anchor);
2872 }
2873 }
2874
2875 public void setAnchorRight(Float anchor) {
2876 this._anchorRight = anchor;
2877 this._anchorLeft = null;
2878 if (anchor != null) {
2879 anchorConstraints();
2880 setX(FrameGraphics.getMaxFrameSize().width - anchor
2881 - getBoundsWidth());
2882 }
2883 }
2884
2885 public void setAnchorTop(Float anchor) {
2886 this._anchorTop = anchor;
2887 this._anchorBottom = null;
2888 if (anchor != null) {
2889 anchorConstraints();
2890 setY(anchor);
2891 }
2892 }
2893
2894
2895 public void setAnchorBottom(Float anchor) {
2896 this._anchorBottom = anchor;
2897 this._anchorTop = null;
2898 if (anchor != null) {
2899 anchorConstraints();
2900 setY(FrameGraphics.getMaxFrameSize().height - anchor);
2901 }
2902 }
2903
2904
2905 public boolean isAnchored() {
2906 return ((_anchorLeft != null) || (_anchorRight != null)
2907 || (_anchorTop != null) || (_anchorBottom != null));
2908 }
2909
2910 public boolean isAnchoredX() {
2911 return ((_anchorLeft != null) || (_anchorRight != null));
2912 }
2913
2914 public boolean isAnchoredY() {
2915 return ((_anchorTop != null) || (_anchorBottom != null));
2916 }
2917
2918 public Float getAnchorLeft() {
2919 return _anchorLeft;
2920 }
2921
2922 public Float getAnchorRight() {
2923 return _anchorRight;
2924 }
2925
2926 public Float getAnchorTop() {
2927 return _anchorTop;
2928 }
2929
2930 public Float getAnchorBottom() {
2931 return _anchorBottom;
2932 }
2933
2934 public String getText() {
2935 return "@" + getClass().getSimpleName() + ":" + getID();
2936 }
2937
2938 public void setText(String text) {
2939 }
2940
2941 public boolean recalculateWhenChanged() {
2942 return false;
2943 }
2944
2945 public boolean update() {
2946 return calculate(getText());
2947 }
2948
2949 public Collection<Item> getEnclosedItems() {
2950 return FrameUtils.getItemsEnclosedBy(this.getParentOrCurrentFrame(),
2951 this.getEnclosedShape());
2952 }
2953
2954 public Collection<Text> getEnclosedNonAnnotationText() {
2955 Collection<Text> items = new LinkedHashSet<Text>();
2956 for (Item t : getEnclosedItems()) {
2957 if (t instanceof Text && !t.isAnnotation())
2958 items.add((Text) t);
2959 }
2960
2961 return items;
2962 }
2963
2964 public void dispose() {
2965 setParent(null);
2966 }
2967
2968 /**
2969 * @return
2970 */
2971 protected boolean hasVisibleBorder() {
2972 return getThickness() > 0 && !isLineEnd() && getBorderColor() != null;
2973 }
2974
2975 public Frame getChild() {
2976 String childName = getAbsoluteLink();
2977 if (childName != null) {
2978 return FrameIO.LoadFrame(childName);
2979 }
2980 return null;
2981 }
2982
2983 public boolean hasLink() {
2984 return _link != null;
2985 }
2986
2987 protected void anchorConnectedOLD(AnchorEdgeType anchorEdgeType, Float delta) {
2988 // Check for a more efficient way to do this!!
2989 // Invalidate all the items
2990 for (Item i : this.getAllConnected()) {
2991 i.invalidateAll();
2992 }
2993 // Move the items
2994 for (Item i : this.getAllConnected()) {
2995 if (i.isLineEnd()) {
2996 if (delta != null) {
2997 if ((anchorEdgeType == AnchorEdgeType.Left) || (anchorEdgeType == AnchorEdgeType.Right)) {
2998 // 'delta' encodes a horizontal (x) move
2999 if (anchorEdgeType == AnchorEdgeType.Left) {
3000 i.setAnchorLeft(null);
3001 }
3002 else {
3003 // must be Right
3004 i.setAnchorRight(null);
3005 }
3006
3007 i.setXY(i.getX() + delta, i.getY());
3008 }
3009 if ((anchorEdgeType == AnchorEdgeType.Top) || (anchorEdgeType == AnchorEdgeType.Bottom)) {
3010 // 'delta; encodes a vertical (y) move
3011 if (anchorEdgeType == AnchorEdgeType.Top) {
3012 i.setAnchorTop(null);
3013 }
3014 else {
3015 // must be Bottom
3016 i.setAnchorBottom(null);
3017 }
3018 i.setXY(i.getX(), i.getY() + delta);
3019 }
3020
3021 }
3022 }
3023 }
3024 // Invalidate them again!!
3025 for (Item i : this.getAllConnected()) {
3026 i.updatePolygon();
3027 i.invalidateAll();
3028 }
3029 }
3030
3031
3032 protected void anchorConnected(AnchorEdgeType anchorEdgeType, Float delta) {
3033
3034 // Check for a more efficient way to do this!!
3035 // Invalidate all the items
3036 for (Item i : this.getAllConnected()) {
3037 i.invalidateAll();
3038 }
3039
3040 // Move the items
3041 for (Item i : this.getAllConnected()) {
3042 if (i.isLineEnd()) {
3043 if (delta != null) {
3044 if ((anchorEdgeType == AnchorEdgeType.Left) || (anchorEdgeType == AnchorEdgeType.Right)) {
3045 // 'delta' encodes a horizontal (x) move
3046 if (anchorEdgeType == AnchorEdgeType.Left) {
3047 // Processing a Left anchor
3048 // => Anything connected that is *not* anchored to the right should be moved by 'delta'
3049 if (i.getAnchorRight()==null) {
3050 i.setXY(i.getX() + delta, i.getY());
3051 }
3052 }
3053 else {
3054 // Processing a Right anchor
3055 // => Anything connected that is *not* anchored to the left should be moved by 'delta'
3056 if (i.getAnchorLeft()==null) {
3057 i.setXY(i.getX() + delta, i.getY());
3058 }
3059 }
3060
3061 }
3062 if ((anchorEdgeType == AnchorEdgeType.Top) || (anchorEdgeType == AnchorEdgeType.Bottom)) {
3063 // 'delta; encodes a vertical (y) move
3064 if (anchorEdgeType == AnchorEdgeType.Top) {
3065 // Processing a Top anchor
3066 // => Anything connected that is *not* anchored to the bottom should be moved by 'delta'
3067 if (i.getAnchorBottom()==null) {
3068 i.setXY(i.getX(), i.getY() + delta);
3069 }
3070 }
3071 else {
3072 // Processing a Bottom anchor
3073 // => Anything connected that is *not* anchored to the top should be moved by 'delta'
3074 if (i.getAnchorTop()==null) {
3075 // must be Bottom
3076 //i.setAnchorBottom(null);
3077 i.setXY(i.getX(), i.getY() + delta);
3078 }
3079 }
3080 }
3081 }
3082 }
3083 }
3084
3085 anchorConstraints();
3086
3087 // Invalidate them again!!
3088 for (Item i : this.getAllConnected()) {
3089 i.updatePolygon();
3090 i.invalidateAll();
3091 }
3092 }
3093 /**
3094 * Sets the item to pickup when the user attempts to pick this item up.
3095 * EditTarget has a value of 'this' by default but may be set to other
3096 * values if this item is on a vector.
3097 *
3098 * @param target
3099 * the item to be copied or picked up when the user attempts to
3100 * edit this item.
3101 */
3102 public void setEditTarget(Item target) {
3103 _editTarget = target;
3104 }
3105
3106 /**
3107 * Gets the item to pickup when the user attempts to pick this item up.
3108 * EditTarget has a value of 'this' by default but may be set to other
3109 * values if this item is on a vector.
3110 */
3111 public Item getEditTarget() {
3112 return _editTarget;
3113 }
3114
3115 public void scale(Float scale, int originX, int originY) {
3116 setXY((getX() - originX) * scale + originX, (getY() - originY) * scale + originY);
3117 setArrowheadLength(getArrowheadLength() * scale);
3118
3119 float thickness = getThickness();
3120 if (thickness > 0)
3121 setThickness(thickness * scale, false);
3122
3123 // DONT PUT SIZE IN HERE CAUSE IT STUFFS UP CIRCLES
3124
3125 updatePolygon();
3126 }
3127
3128 protected boolean isVectorItem() {
3129 return _editTarget != this;
3130 }
3131
3132 public AttributeValuePair getAttributeValuePair() {
3133 if (_attributeValuePair == null) {
3134 _attributeValuePair = new AttributeValuePair(getText());
3135 }
3136 return _attributeValuePair;
3137 }
3138
3139 /*
3140 * private static Set<Object> _locks = new HashSet<Object>();
3141 *
3142 * public static void lock(Object itemToLock) { _locks.add(itemToLock); }
3143 *
3144 * public static void unlock(Object itemToUnlock) {
3145 * _locks.remove(itemToUnlock); }
3146 *
3147 * public static boolean isLocked(Object item) { return
3148 * _locks.contains(item); }
3149 */
3150
3151 public void setSave(boolean state) {
3152 _save = state;
3153 }
3154
3155 public boolean getSave() {
3156 return _save;
3157 }
3158
3159 public void setAutoStamp(Float rate) {
3160 _autoStamp = rate;
3161 }
3162
3163 public Float getAutoStamp() {
3164 return _autoStamp;
3165 }
3166
3167 public boolean isAutoStamp() {
3168 return _autoStamp != null && _autoStamp >= 0.0;
3169 }
3170
3171 public void setDotType(DotType type) {
3172 invalidateAll();
3173 _type = type;
3174 invalidateAll();
3175 }
3176
3177 public DotType getDotType() {
3178 return _type;
3179 }
3180
3181 public void setFilled(boolean filled) {
3182 invalidateAll();
3183 _filled = filled;
3184 invalidateAll();
3185 }
3186
3187 public boolean getFilled() {
3188 return _filled;
3189 }
3190
3191 private Item _magnetizedItemLeft = null;
3192 private Item _magnetizedItemRight = null;
3193 private Item _magnetizedItemTop = null;
3194 private Item _magnetizedItemBottom = null;
3195
3196 public int getMagnetizedItemLeft() {
3197 if(_magnetizedItemLeft != null) return _magnetizedItemLeft.getID();
3198 else return -1;
3199 }
3200
3201 public void setMagnetizedItemLeft(final Item item) {
3202 _magnetizedItemLeft = item;
3203 }
3204
3205 public void setMagnetizedItemLeft(final int id) {
3206 setMagnetizedItemLeft(this.getParent().getItemWithID(id));
3207 }
3208
3209 public int getMagnetizedItemRight() {
3210 if(_magnetizedItemRight != null) return _magnetizedItemRight.getID();
3211 else return -1;
3212 }
3213
3214 public void setMagnetizedItemRight(final Item item) {
3215 _magnetizedItemRight = item;
3216 }
3217
3218 public void setMagnetizedItemRight(final int id) {
3219 setMagnetizedItemRight(this.getParent().getItemWithID(id));
3220 }
3221
3222 public int getMagnetizedItemTop() {
3223 if(_magnetizedItemTop != null) return _magnetizedItemTop.getID();
3224 else return -1;
3225 }
3226
3227 public void setMagnetizedItemTop(final Item item) {
3228 _magnetizedItemTop = item;
3229 }
3230
3231 public void setMagnetizedItemTop(final int id) {
3232 setMagnetizedItemTop(this.getParent().getItemWithID(id));
3233 }
3234
3235 public int getMagnetizedItemBottom() {
3236 if(_magnetizedItemBottom != null) return _magnetizedItemBottom.getID();
3237 else return -1;
3238 }
3239
3240 public void setMagnetizedItemBottom(final Item item) {
3241 _magnetizedItemBottom = item;
3242 }
3243
3244 public void setMagnetizedItemBottom(final int id) {
3245 setMagnetizedItemBottom(this.getParent().getItemWithID(id));
3246 }
3247}
Note: See TracBrowser for help on using the repository browser.