source: trunk/src/org/expeditee/gui/Frame.java@ 579

Last change on this file since 579 was 579, checked in by jts21, 11 years ago

Fix occasional NullPointerException when creating a rectangle

File size: 53.9 KB
Line 
1package org.expeditee.gui;
2
3import java.awt.Color;
4import java.awt.Image;
5import java.awt.Polygon;
6import java.awt.image.ImageObserver;
7import java.awt.image.VolatileImage;
8import java.sql.Time;
9import java.util.ArrayList;
10import java.util.Collection;
11import java.util.Collections;
12import java.util.HashMap;
13import java.util.HashSet;
14import java.util.LinkedHashSet;
15import java.util.LinkedList;
16import java.util.List;
17import java.util.Map;
18import java.util.Stack;
19
20import org.expeditee.actions.Simple;
21import org.expeditee.io.Conversion;
22import org.expeditee.items.Dot;
23import org.expeditee.items.Item;
24import org.expeditee.items.ItemAppearence;
25import org.expeditee.items.ItemParentStateChangedEvent;
26import org.expeditee.items.ItemUtils;
27import org.expeditee.items.Line;
28import org.expeditee.items.PermissionPair;
29import org.expeditee.items.UserAppliedPermission;
30import org.expeditee.items.Text;
31import org.expeditee.items.XRayable;
32import org.expeditee.items.Item.HighlightMode;
33import org.expeditee.items.widgets.InteractiveWidget;
34import org.expeditee.items.widgets.WidgetCorner;
35import org.expeditee.settings.UserSettings;
36import org.expeditee.simple.UnitTestFailedException;
37import org.expeditee.stats.Formatter;
38import org.expeditee.stats.SessionStats;
39
40/**
41 * Represents a Expeditee Frame that is displayed on the screen. Also is a
42 * registered MouseListener on the Browser, and processes any MouseEvents
43 * directly.
44 *
45 * @author jdm18
46 *
47 */
48public class Frame implements ImageObserver {
49
50 private boolean _protectionChanged = false;
51
52 public boolean isReadOnly() {
53 return !_frameName.hasPermission(UserAppliedPermission.full)
54 && !_protectionChanged;
55 }
56
57 public static Color[] COLOR_WHEEL = { new Color(235, 235, 235),
58 new Color(225, 225, 255), new Color(195, 255, 255),
59 new Color(225, 255, 225), new Color(255, 255, 195),
60 new Color(255, 225, 225), new Color(255, 195, 255), Color.WHITE,
61 Color.GRAY, Color.DARK_GRAY, Color.BLACK, null };
62
63 // The various attributes of this Frame
64 private String _frameset = null;
65
66 private int _number = -1;
67
68 private int _version = 0;
69
70 private PermissionPair _permissionPair = null;
71
72 private String _owner = null;
73
74 private String _creationDate = null;
75
76 private String _modifiedUser = null;
77
78 private String _modifiedDate = null;
79
80 private String _frozenDate = null;
81
82 // Background color is clear
83 private Color _background = null;
84
85 // Foreground color is automatic by default
86 private Color _foreground = null;
87
88 private String path;
89
90 private boolean _isLocal = true;
91
92 private boolean _sorted = true;
93
94 // The items contained in this Frame
95 // records whether a change has been made to this Frame (for saving
96 // purposes).
97 private boolean _change = false;
98
99 private boolean _saved = false;
100
101 // list of deleted items that can be restored
102 private Stack<Item> _undo = new Stack<Item>();
103
104 // basically just a list of smaller objects?
105 // maybe a hashtable (id -> item?)
106 // Note: Needs to be able to be iterated through (for painting)
107 private List<Item> _body = new ArrayList<Item>();
108
109 // for drawing purposes
110 private List<InteractiveWidget> _iWidgets = new ArrayList<InteractiveWidget>();
111
112 private int _lineCount = 0;
113
114 private int _itemCount = 1;
115
116 // The frameName to display on the screen
117 private Text _frameName = null;
118
119 private Map<Overlay, Frame> _overlays = new HashMap<Overlay, Frame>();
120
121 private List<Vector> _vectors = new ArrayList<Vector>();
122
123 private Image _buffer = null;
124
125 private boolean _validBuffer = true;
126
127 private Time _activeTime = new Time(0);
128
129 private Time _darkTime = new Time(0);
130
131 private Collection<Item> _interactableItems = new LinkedHashSet<Item>();
132
133 private Collection<Item> _overlayItems = new LinkedHashSet<Item>();
134
135 private Collection<Item> _vectorItems = new LinkedHashSet<Item>();
136
137 private Text _dotTemplate = UserSettings.DotTemplate.copy();
138
139 /**
140 * Default constructor, nothing is set.
141 */
142 public Frame() {
143 }
144
145 public void reset() {
146 refreshItemPermissions(UserAppliedPermission.full);
147 //System.out.println("Reset");
148 resetDot();
149 SessionStats.NewFrameSession();
150 }
151
152 private void resetDot() {
153 _dotTemplate.setColor(Item.COLOR_WHEEL[1]);
154 _dotTemplate.setFillColor(Item.FILL_COLOR_WHEEL[0]);
155 }
156
157 public void nextDot() {
158 _dotTemplate.setFillColor(ColorUtils.getNextColor(_dotTemplate
159 .getFillColor(), Item.FILL_COLOR_WHEEL, null));
160 _dotTemplate.setColor(ColorUtils.getNextColor(_dotTemplate.getColor(),
161 Item.COLOR_WHEEL, null));
162
163 if (_dotTemplate.getColor() == null || _dotTemplate.getColor().equals(Color.white)) {
164 resetDot();
165 }
166 }
167
168 public Image getBuffer() {
169 return _buffer;
170 }
171
172 public void setBuffer(Image newBuffer) {
173 _buffer = newBuffer;
174 }
175
176 public boolean isBufferValid() {
177 if (_buffer == null
178 || (_buffer instanceof VolatileImage && ((VolatileImage) _buffer)
179 .contentsLost()))
180 return false;
181
182 return _validBuffer;
183 }
184
185 private void setBufferValid(boolean newValue) {
186 _validBuffer = newValue;
187 }
188
189 public int getNextItemID() {
190 return ++_itemCount;
191 }
192
193 public void updateIDs(List<Item> items) {
194 for (Item i : items)
195 if (!(i instanceof Line))
196 i.setID(getNextItemID());
197 else
198 i.setID(++_lineCount);
199 }
200
201 /**
202 *
203 * @return The interactive widgets that are currently anchored in this frame.
204 * Hence it excludes free-widgets. Returns a copy
205 */
206 public List<InteractiveWidget> getInteractiveWidgets() {
207 LinkedList<InteractiveWidget> clone = new LinkedList<InteractiveWidget>();
208 clone.addAll(this._iWidgets);
209 return clone;
210 }
211
212 /**
213 * Returns whether this Frame has been changed and required saving to disk.
214 *
215 * @return True if this Frame has been altered, false otherwise.
216 */
217 public boolean hasChanged() {
218 // virtual frames are never saved
219 if (_number == -1)
220 return false;
221
222 return _change;
223 }
224
225 /**
226 * Sets whether this Frame should be saved to disk.
227 *
228 * @param value
229 * False if this Frame should be saved to disk, False otherwise.
230 */
231 public void setChanged(boolean value) {
232 // System.out.println(getName() + " " + value);
233 boolean oldValue = _change;
234
235 // if (value) {
236 // notifyObservers();
237 // }
238
239 if (oldValue == value)
240 return;
241
242 _change = value;
243
244 if (_change) {
245 setBufferValid(false);
246 _saved = false;
247 }
248 }
249
250 // private static int updateCount = 0;
251
252 /**
253 * Notify items observing the data on this frame that the frame content has
254 * changed.
255 *
256 * @param recalculate
257 * true if the frame should be recalculated first.
258 */
259 public void notifyObservers(boolean bRecalculate) {
260 if (bRecalculate) {
261 recalculate();
262 }
263 // Notify the frame listeners that the frame has changed
264 /*
265 * Avoid ConcurrentMod Exceptions when user anchors an item onto this
266 * frame which is observing this frame, by NOT using foreach loop.
267 * Calling update on a dataFrameWidget resets its subjects hence
268 * changing this frames observer list.
269 */
270 Collection<FrameObserver> observersCopy = new LinkedList<FrameObserver>(
271 _observers);
272 // System.out.println(++updateCount + " update");
273
274 for (FrameObserver fl : observersCopy) {
275 if (/* !Item.isLocked(fl) && */fl.isVisible())
276 fl.update();
277 }
278 }
279
280 // indicates the frame has changed
281 public void change() {
282 setChanged(true);
283 _interactableItems.clear();
284 }
285
286 /**
287 * Returns an ArrayList of all Items currently on the Frame (excludes Items
288 * attached to the cursor).
289 *
290 * @return The list of Item objects that are on this Frame.
291 */
292 public List<Item> getItems(boolean visible) {
293
294 if (!_sorted) {
295 Collections.sort(_body);
296 _sorted = true;
297 }
298
299 List<Item> items = new ArrayList<Item>();
300
301 for (Item i : _body) {
302 if (i == null)
303 continue;
304 if (i.isVisible() || (!visible && !i.isDeleted())) {
305 items.add(i);
306 }
307 }
308
309 return items;
310 }
311
312 public List<Item> getItems() {
313 return getItems(false);
314 }
315
316 /**
317 * @param i
318 * Item to check if contained in this frame
319 * @return True if this frame contains i.
320 */
321 public boolean containsItem(Item i) {
322 if (i == null)
323 throw new NullPointerException("i");
324 return _body.contains(i);
325 }
326
327 /**
328 * Returns a list of all the non annotation text items on the frame which
329 * are not the title or frame name or special annotation items.
330 *
331 * @param includeAnnotations
332 * true if annotation items without special meaning should be
333 * included
334 * @param includeLineEnds
335 * true if text on the end of lines should be included in the
336 * list
337 * @return the list of body text items.
338 */
339 public List<Text> getBodyTextItems(boolean includeAnnotations) {
340 List<Text> bodyTextItems = new ArrayList<Text>();
341 for (Item i : getItems(true)) {
342 // only add up normal body text items
343 if ((i instanceof Text)
344 && ((includeAnnotations && !((Text) i)
345 .isSpecialAnnotation()) || !i.isAnnotation())
346 && !i.isLineEnd()) {
347 bodyTextItems.add((Text) i);
348 }
349 }
350 bodyTextItems.remove(getTitleItem());
351
352 return bodyTextItems;
353 }
354
355 public Collection<Item> getNonAnnotationItems(boolean removeTitle) {
356 Collection<Item> items = new ArrayList<Item>();
357 for (Item i : getItems(true)) {
358 // only add up normal body text items
359 if (!i.isAnnotation()) {
360 items.add(i);
361 }
362 }
363 if (removeTitle) {
364 items.remove(getTitleItem());
365 }
366 return items;
367 }
368
369 /**
370 * Gets the last item on the frame that is a non annotation item but is also
371 * text.
372 *
373 * @return the last non annotation text item.
374 */
375 public Item getLastNonAnnotationTextItem() {
376 List<Item> items = getItems();
377
378 // find the last non-annotation text item
379 for (int i = (items.size() - 1); i >= 0; i--) {
380 Item it = items.get(i);
381
382 if (it instanceof Text && !it.isAnnotation()) {
383 return (Item) it;
384 }
385 }
386 return null;
387 }
388
389 /**
390 * Iterates through the list of items on the frame, and returns one with the
391 * given id if one exists, otherwise returns null.
392 *
393 * @param id
394 * The id to search for in the list of items
395 * @return The item on this frame with the given ID, or null if one is not
396 * found.
397 */
398 public Item getItemWithID(int id) {
399 for (Item i : _body)
400 if (i.getID() == id)
401 return i;
402
403 return null;
404 }
405
406 /**
407 * Sets this Frame's Title which is displayed in the top left corner.
408 *
409 * @param title
410 * The title to assign to this Frame
411 */
412 public void setTitle(String title) {
413 if (title == null || title.equals(""))
414 return;
415
416 boolean oldchange = _change;
417
418 // remove any numbering this title has
419 title = title.replaceAll("^\\d*[.] *", "");
420 Text frameTitle = getTitleItem();
421
422 if (frameTitle == null) {
423 if (UserSettings.TitleTemplate == null) {
424 frameTitle = new Text(getNextItemID(), title);
425 /*
426 * Need to set the parent otherwise an exception is thrown when
427 * new profile is created
428 */
429 frameTitle.setParent(this);
430 frameTitle.resetTitlePosition();
431 } else {
432 frameTitle = UserSettings.TitleTemplate.copy();
433 frameTitle.setID(this.getNextItemID());
434 frameTitle.setText(title);
435 }
436 addItem(frameTitle);
437 } else {
438 // If it begins with a tag remove it
439
440 // Remove the @ symbol if it is there
441 // title = ItemUtils.StripTagSymbol(title);
442 frameTitle.setText(title);
443 // If the @ symbol is followed by numbering or a bullet remove that
444 // too
445 String autoBulletText = FrameKeyboardActions.getAutoBullet(title);
446 if (autoBulletText.length() > 0)
447 frameTitle.stripFirstWord();
448 }
449 // TODO Widgets... check this out
450 // Brook: Cannot figure what is going on above... widget annot titles
451 // should be stripped always
452 if (ItemUtils.startsWithTag(frameTitle, ItemUtils
453 .GetTag(ItemUtils.TAG_IWIDGET))) {
454 frameTitle.stripFirstWord();
455 }
456
457 FrameUtils.Parse(this);
458
459 // do not save if this is the only change
460 setChanged(oldchange);
461 }
462
463 public Text getTitleItem() {
464 List<Item> items = getVisibleItems();
465 for (Item i : items) {
466 if (i instanceof Text && i.getX() < UserSettings.TitlePosition
467 && i.getY() < UserSettings.TitlePosition)
468 return (Text) i;
469 }
470
471 return null;
472 }
473
474 public String getTitle() {
475 Text title = getTitleItem();
476 if (title == null)
477 return getName();
478
479 return title.getFirstLine();
480 }
481
482 public Item getNameItem() {
483 return _frameName;
484 }
485
486 public Text getItemTemplate() {
487 return getTemplate(UserSettings.ItemTemplate,
488 ItemUtils.TAG_ITEM_TEMPLATE);
489 }
490
491 public Text getAnnotationTemplate() {
492 Text t = getTemplate(UserSettings.AnnotationTemplate,
493 ItemUtils.TAG_ANNOTATION_TEMPLATE);
494
495 if (t == null) {
496 t = getItemTemplate();
497 }
498
499 return t;
500 }
501
502 public Text getStatTemplate() {
503 SessionStats.CreatedText();
504 Text t = getTemplate(UserSettings.StatTemplate,
505 ItemUtils.TAG_STAT_TEMPLATE);
506
507 if (t == null) {
508 t = getItemTemplate();
509 }
510
511 return t;
512 }
513
514 public Item getStatsTextItem(String itemText) {
515 return getTextItem(itemText, getStatTemplate());
516 }
517
518 public Item getTextItem(String itemText) {
519 return getTextItem(itemText, getItemTemplate());
520 }
521
522 private Item getTextItem(String itemText, Text template) {
523 Text t = template;
524 // We dont want the stats to wrap at all
525 // t.setMaxWidth(Integer.MAX_VALUE);
526 t.setPosition(DisplayIO.getMouseX(), FrameMouseActions.getY());
527 // The next line is needed to make sure the item is removed from the
528 // frame when picked up
529 t.setParent(this);
530 t.setText(itemText);
531 return t;
532 }
533
534 public Text getCodeCommentTemplate() {
535 Text t = getTemplate(UserSettings.CodeCommentTemplate,
536 ItemUtils.TAG_CODE_COMMENT_TEMPLATE);
537
538 if (t == null) {
539 t = getItemTemplate();
540 }
541
542 return t;
543 }
544
545
546 /**
547 * Returns any items on this frame that are within the given Shape. Also
548 * returns any Items on overlay frames that are within the Shape.
549 *
550 * @param shape
551 * The Shape to search for Items in
552 * @return All Items on this Frame or overlayed Frames for which
553 * Item.intersects(shape) return true.
554 */
555 public Collection<Item> getItemsWithin(Polygon poly) {
556 Collection<Item> results = new LinkedHashSet<Item>();
557 for (Item i : getVisibleItems()) {
558 if (i.intersects(poly)) {
559 if (i instanceof XRayable) {
560 results.addAll(i.getConnected());
561 // Dont add circle centers
562 // TODO change this to be isCircle center
563 } else if (!i.hasEnclosures()) {
564 results.add(i);
565 }
566 }
567 }
568
569 for (Overlay o : _overlays.keySet())
570 results.addAll(o.Frame.getItemsWithin(poly));
571
572 for (Item i : getVectorItems()) {
573 if (i.intersects(poly)) {
574 // This assumes a results is a set
575 results.add(i.getEditTarget());
576 }
577 }
578
579 return results;
580 }
581
582 /**
583 * Sets the name of this Frame to the given String, to be displayed in the
584 * upper right corner.
585 *
586 * @param name
587 * The name to use for this Frame.
588 */
589 public void setFrameset(String name) {
590 _frameset = name;
591 }
592
593 public void setName(String framename) {
594 int num = Conversion.getFrameNumber(framename);
595 String frameset = Conversion.getFramesetName(framename, false);
596
597 setName(frameset, num);
598 }
599
600 /**
601 * Sets the frame number of this Frame to the given integer
602 *
603 * @param number
604 * The number to set as the frame number
605 */
606 public void setFrameNumber(int number) {
607 assert (number >= 0);
608
609 if (_number == number)
610 return;
611
612 _number = number;
613 boolean oldchange = _change;
614
615 int id;
616
617 if (_frameName != null) {
618 id = _frameName.getID();
619 } else {
620 id = -1 * getNextItemID();
621 }
622 _frameName = new Text(id);
623 _frameName.setParent(this);
624 _frameName.setText(getFramesetName() + _number);
625 _frameName.resetFrameNamePosition();
626 setChanged(oldchange);
627 }
628
629 /**
630 * Returns the number of this Frame.
631 *
632 * @return The Frame number of this Frame or -1 if it is not set.
633 */
634 public int getNumber() {
635 return _number;
636 }
637
638 /**
639 * Increments the version of this Frame to the given String.
640 *
641 * @param version
642 * The version to use for this Frame.
643 */
644 public void setVersion(int version) {
645 _version = version;
646 }
647
648 /**
649 * Sets the protection of this Frame to the given String.
650 *
651 * @param protection
652 * The protection to use for this Frame.
653 */
654 public void setPermission(PermissionPair permission) {
655 if (_permissionPair != null && _permissionPair.getPermission(this._owner).equals(permission))
656 _protectionChanged = true;
657
658 _permissionPair = new PermissionPair(permission);
659
660 if (_body.size() > 0)
661 refreshItemPermissions(permission.getPermission(_owner));
662 }
663
664 /**
665 * Sets the owner of this Frame to the given String.
666 *
667 * @param owner
668 * The owner to use for this Frame.
669 */
670 public void setOwner(String owner) {
671 _owner = owner;
672 }
673
674 /**
675 * Sets the created date of this Frame to the given String.
676 *
677 * @param date
678 * The date to use for this Frame.
679 */
680 public void setDateCreated(String date) {
681 _creationDate = date;
682 _modifiedDate = date;
683 for (Item i : _body) {
684 i.setDateCreated(date);
685 }
686 }
687
688 /**
689 * Resets the dates and version numbers for newly created frames.
690 *
691 */
692 public void resetDateCreated() {
693 setDateCreated(Formatter.getDateTime());
694 resetTimes();
695 setVersion(0);
696 }
697
698 private void resetTimes() {
699 setActiveTime(new Time(0));
700 setDarkTime(new Time(0));
701 }
702
703 /**
704 * Sets the last modifying user of this Frame to the given String.
705 *
706 * @param user
707 * The user to set as the last modifying user.
708 */
709 public void setLastModifyUser(String user) {
710 _modifiedUser = user;
711 }
712
713 /**
714 * Sets the last modified date of this Frame to the given String.
715 *
716 * @param date
717 * The date to set as the last modified date.
718 */
719 public void setLastModifyDate(String date) {
720 _modifiedDate = date;
721 }
722
723 /**
724 * Sets the last frozen date of this Frame to the given String.
725 *
726 * @param date
727 * The date to set as the last frozen date.
728 */
729 public void setFrozenDate(String date) {
730 _frozenDate = date;
731 }
732
733 public void setResort(boolean value) {
734 _sorted = !value;
735 }
736
737 /**
738 * Adds the given Item to the body of this Frame.
739 *
740 * @param item
741 * The Item to add to this Frame.
742 */
743 public void addItem(Item item) {
744 addItem(item, true);
745 }
746
747 public void addItem(Item item, boolean recalculate) {
748 if (item == null || item.equals(_frameName) || _body.contains(item))
749 return;
750
751 // When an annotation item is anchored the annotation list must be
752 // refreshed
753 if (item.isAnnotation()) {
754 clearAnnotations();
755 }
756
757 if (item instanceof Line)
758 _lineCount++;
759
760 _itemCount = Math.max(_itemCount, item.getID());
761
762 _body.add(item);
763 item.setParent(this);
764 item.setFloating(false); // esnure that it is anchored
765
766 item.invalidateCommonTrait(ItemAppearence.Added);
767
768 // If the item is a line end and has constraints with items already
769 // on the frame then make sure the constraints hold
770 if (item.isLineEnd()) {
771 item.setPosition(item.getPosition());
772 }
773
774 _sorted = false;
775
776 // item.setMaxWidth(FrameGraphics.getMaxFrameSize().width);
777 // add widget items to the list of widgets
778 if (item instanceof WidgetCorner) {
779 InteractiveWidget iw = ((WidgetCorner) item).getWidgetSource();
780 if (!this._iWidgets.contains(iw)) { // A set would have been
781 if (FrameMouseActions.isControlDown())
782 _iWidgets.add(iw);
783 else
784 _iWidgets.add(0, iw);
785 }
786 }
787
788 item.onParentStateChanged(new ItemParentStateChangedEvent(this,
789 ItemParentStateChangedEvent.EVENT_TYPE_ADDED));
790
791 // if (recalculate && item.recalculateWhenChanged())
792 // recalculate();
793
794 change();
795 }
796
797 public void refreshSize() {
798 // assert (size != null);
799 boolean bReparse = false;
800 for (Item i : getItems()) {
801 Float anchorBottom = i.getAnchorBottom();
802 Float anchorRight = i.getAnchorRight();
803 if (anchorRight != null) {
804 i.setAnchorRight(anchorRight);
805 if (i.hasVector()) {
806 bReparse = true;
807 }
808 }
809 if (anchorBottom != null) {
810 i.setAnchorBottom(anchorBottom);
811 if (i.hasVector()) {
812 bReparse = true;
813 }
814 }
815 }
816
817 // Do the anchors on the overlays
818 for (Overlay o : getOverlays()) {
819 o.Frame.refreshSize();
820 }
821
822 if (bReparse) {
823 FrameUtils.Parse(this, false);
824 }
825
826 _frameName.resetFrameNamePosition();
827 }
828
829 public void addAllItems(Collection<Item> toAdd) {
830 for (Item i : toAdd) {
831 // If an annotation is being deleted clear the annotation list
832 if (i.isAnnotation())
833 i.getParentOrCurrentFrame().clearAnnotations();
834 // TODO Improve efficiency when addAll is called
835 addItem(i);
836 }
837 }
838
839 public void removeAllItems(Collection<Item> toRemove) {
840 for (Item i : toRemove) {
841 // If an annotation is being deleted clear the annotation list
842 if (i.isAnnotation())
843 i.getParentOrCurrentFrame().clearAnnotations();
844 removeItem(i);
845 }
846 }
847
848 public void removeItem(Item item) {
849 removeItem(item, true);
850 }
851
852 public void removeItem(Item item, boolean recalculate) {
853 // If an annotation is being deleted clear the annotation list
854 if (item.isAnnotation())
855 item.getParentOrCurrentFrame().clearAnnotations();
856
857 if (_body.remove(item)) {
858 change();
859 // Remove widgets from the widget list
860 if (item != null) {
861 item.onParentStateChanged(new ItemParentStateChangedEvent(this,
862 ItemParentStateChangedEvent.EVENT_TYPE_REMOVED));
863 if (item instanceof WidgetCorner) {
864 _iWidgets.remove(((WidgetCorner) item).getWidgetSource());
865 }
866 item.invalidateCommonTrait(ItemAppearence.Removed);
867 }
868 // TODO Improve efficiency when removeAll is called
869 // if (recalculate && item.recalculateWhenChanged())
870 // recalculate();
871 }
872 }
873
874 /**
875 * Adds the given list of Items to the undo stack. This is the same as
876 * calling addToUndo() for each Item in the list.
877 *
878 * @param items
879 * The List of Items to add to the undo stack.
880 */
881 public void addAllToUndo(Collection<Item> items) {
882 if (items.size() < 1)
883 return;
884
885 String id = "" + _undo.size();
886
887 for (Item i : items) {
888 i.setTag(id);
889 _undo.push(i);
890 }
891 }
892
893 public void addToUndo(Item item) {
894 if (item == null)
895 return;
896
897 item.setTag("" + _undo.size());
898 _undo.push(item);
899 }
900
901 public void undo() {
902 Item undo = null;
903 boolean bReparse = false;
904 boolean bRecalculate = false;
905
906 if (_undo.size() <= 0)
907 return;
908
909 undo = _undo.pop();
910
911 // if the change was to characteristics
912 if (undo.isVisible() && _body.contains(undo)) {
913 Item old = _body.get(_body.indexOf(undo));
914 _body.set(_body.indexOf(old), undo);
915 // the item was deleted
916 } else {
917 List<Item> toRestore = new LinkedList<Item>();
918 toRestore.add(undo);
919 if (undo.hasOverlay())
920 bReparse = true;
921
922 // remove any connected items at the top of the stack
923 while (_undo.size() > 0) {
924 Item next = _undo.peek();
925 // if this item was connected to one already picked up, remove
926 // it from the stack
927 if (toRestore.contains(next))
928 _undo.pop();
929 // else, if this item should be restored (deleted in an enclosed
930 // set)
931 else if (next.getTag().equals(undo.getTag())) {
932 _undo.pop();
933 toRestore.add(next);
934 if (next.hasOverlay())
935 bReparse = true;
936
937 // otherwise, we are done
938 } else
939 break;
940 }
941
942 // if these items were deleted from a frame, add them
943 addAllItems(toRestore);
944
945 for (Item i : toRestore) {
946 bReparse |= i.hasOverlay();
947 bRecalculate |= i.recalculateWhenChanged();
948 if (i instanceof Line) {
949 Line line = (Line) i;
950 line.getStartItem().addLine(line);
951 line.getEndItem().addLine(line);
952 } else {
953 i.setOffset(0, 0);
954 }
955 }
956 }
957
958 change();
959 FrameMouseActions.getInstance().refreshHighlights();
960 if (bReparse) {
961 FrameUtils.Parse(this, false, false);
962 FrameGraphics.requestRefresh(false);
963 } else {
964 notifyObservers(bRecalculate);
965 }
966 FrameGraphics.Repaint();
967 ItemUtils.EnclosedCheck(_body);
968 }
969
970 /**
971 * Returns the frameset of this Frame
972 *
973 * @return The name of this Frame's frameset.
974 */
975 public String getFramesetName() {
976 return _frameset;
977 }
978
979 public String getName() {
980 return getFramesetName() + _number;
981 }
982
983 /**
984 * Returns the format version of this Frame
985 *
986 * @return The version of this Frame.
987 */
988 public int getVersion() {
989 return _version;
990 }
991
992 public PermissionPair getPermission() {
993 return _permissionPair;
994 }
995
996 public UserAppliedPermission getUserAppliedPermission() {
997 return getUserAppliedPermission(UserAppliedPermission.full);
998 }
999
1000 public UserAppliedPermission getUserAppliedPermission(UserAppliedPermission defaultPermission) {
1001 if (_permissionPair == null)
1002 return defaultPermission;
1003
1004 return _permissionPair.getPermission(_owner);
1005 }
1006
1007
1008 public String getOwner() {
1009 return _owner;
1010 }
1011
1012 public String getDateCreated() {
1013 return _creationDate;
1014 }
1015
1016 public String getLastModifyUser() {
1017 return _modifiedUser;
1018 }
1019
1020 public String getLastModifyDate() {
1021 return _modifiedDate;
1022 }
1023
1024 public String getFrozenDate() {
1025 return _frozenDate;
1026 }
1027
1028 public void setBackgroundColor(Color back) {
1029 _background = back;
1030 change();
1031
1032 if (this == DisplayIO.getCurrentFrame()) {
1033 FrameGraphics.refresh(false);
1034 }
1035 }
1036
1037 public Color getBackgroundColor() {
1038 return _background;
1039 }
1040
1041 public Color getPaintBackgroundColor() {
1042 // If null... return white
1043 if (_background == null) {
1044 return Item.DEFAULT_BACKGROUND;
1045 }
1046
1047 return _background;
1048 }
1049
1050 public void setForegroundColor(Color front) {
1051 _foreground = front;
1052 change();
1053 // FrameGraphics.Repaint();
1054 }
1055
1056 public Color getForegroundColor() {
1057 return _foreground;
1058 }
1059
1060 public Color getPaintForegroundColor() {
1061 final int GRAY = Color.gray.getBlue();
1062 final int THRESHOLD = 10;
1063
1064 if (_foreground == null) {
1065 Color back = getPaintBackgroundColor();
1066 if (Math.abs(back.getRed() - GRAY) < THRESHOLD
1067 && Math.abs(back.getBlue() - GRAY) < THRESHOLD
1068 && Math.abs(back.getGreen() - GRAY) < THRESHOLD)
1069 return Color.WHITE;
1070
1071 Color fore = new Color(
1072 Math.abs(Conversion.RGB_MAX - back.getRed()), Math
1073 .abs(Conversion.RGB_MAX - back.getGreen()), Math
1074 .abs(Conversion.RGB_MAX - back.getBlue()));
1075 return fore;
1076 }
1077
1078 return _foreground;
1079 }
1080
1081 public String toString() {
1082 StringBuilder s = new StringBuilder();
1083 s.append(String.format("Name: %s%d%n", _frameset, _number));
1084 s.append(String.format("Version: %d%n", _version));
1085 // s.append(String.format("Permission: %s%n", _permission.toString()));
1086 // s.append(String.format("Owner: %s%n", _owner));
1087 // s.append(String.format("Date Created: %s%n", _creationDate));
1088 // s.append(String.format("Last Mod. User: %s%n", _modifiedUser));
1089 // s.append(String.format("Last Mod. Date: %s%n", _modifiedDate));
1090 s.append(String.format("Items: %d%n", _body.size()));
1091 return s.toString();
1092 }
1093
1094 public Text getTextAbove(Text current) {
1095 Collection<Text> currentTextItems = FrameUtils.getCurrentTextItems();
1096 List<Text> toCheck = new ArrayList<Text>();
1097 if (currentTextItems.contains(current)) {
1098 toCheck.addAll(currentTextItems);
1099 } else {
1100 toCheck.addAll(getTextItems());
1101 }
1102 // Make sure the items are sorted
1103 Collections.sort(toCheck);
1104
1105 int ind = toCheck.indexOf(current);
1106 if (ind == -1)
1107 return null;
1108
1109 // loop through all items above this one, return the first match
1110 for (int i = ind - 1; i >= 0; i--) {
1111 Text check = toCheck.get(i);
1112 if (FrameUtils.inSameColumn(check, current))
1113 return check;
1114 }
1115
1116 return null;
1117 }
1118
1119 /**
1120 * Updates any Images that require it from their ImageObserver (Principally
1121 * Animated GIFs)
1122 */
1123 public boolean imageUpdate(Image img, int infoflags, int x, int y,
1124 int width, int height) {
1125 FrameGraphics.ForceRepaint();
1126
1127 if (DisplayIO.getCurrentFrame() == this)
1128 return true;
1129
1130 return false;
1131 }
1132
1133 /**
1134 * Gets the text items that are in the same column and below a specified
1135 * item. Frame title and name are excluded from the column list.
1136 *
1137 * @param from
1138 * The Item to get the column for.
1139 */
1140 public List<Text> getColumn(Item from) {
1141 // Check that this item is on the current frame
1142 if (!_body.contains(from))
1143 return null;
1144
1145 if (from == null) {
1146 from = getLastNonAnnotationTextItem();
1147 }
1148
1149 if (from == null)
1150 return null;
1151
1152 // Get the enclosedItems
1153 Collection<Text> enclosed = FrameUtils.getCurrentTextItems();
1154 List<Text> toCheck = null;
1155 if (enclosed.contains(from)) {
1156 toCheck = new ArrayList<Text>();
1157 toCheck.addAll(enclosed);
1158 } else {
1159 toCheck = getBodyTextItems(true);
1160 }
1161
1162 List<Text> column = new ArrayList<Text>();
1163 if (toCheck.size() > 0) {
1164
1165 // Make sure the items are sorted
1166 Collections.sort(toCheck);
1167
1168 // Create a list of items consisting of the item 'from' and all the
1169 // items below it which are also in the same column as it
1170 int index = toCheck.indexOf(from);
1171
1172 // If its the title index will be 0
1173 if (index < 0)
1174 index = 0;
1175
1176 for (int i = index; i < toCheck.size(); i++) {
1177 Text item = toCheck.get(i);
1178 if (FrameUtils.inSameColumn(from, item))
1179 column.add(item);
1180 }
1181 }
1182
1183 return column;
1184 }
1185
1186 /**
1187 * Adds the given Vector to the list of vector Frames being drawn with this
1188 * Frame.
1189 *
1190 * @param vector
1191 * The Vector to add
1192 *
1193 * @throws NullPointerException
1194 * If overlay is null.
1195 */
1196 protected boolean addVector(Vector toAdd) {
1197 // make sure we dont add this frame as an overlay of itself
1198 if (toAdd.Frame == this)
1199 return false;
1200 _vectors.add(toAdd);
1201 // Items must be notified that they have been added or removed from this
1202 // frame via the vector...
1203 int maxX = 0;
1204 int maxY = 0;
1205 HighlightMode mode = toAdd.Source.getHighlightMode();
1206 if (mode != HighlightMode.None)
1207 mode = HighlightMode.Connected;
1208 Color highlightColor = toAdd.Source.getHighlightColor();
1209 for (Item i : ItemUtils.CopyItems(toAdd.Frame.getVectorItems(), toAdd)) {
1210 i.onParentStateChanged(new ItemParentStateChangedEvent(this,
1211 ItemParentStateChangedEvent.EVENT_TYPE_ADDED_VIA_OVERLAY,
1212 toAdd.permission));
1213 i.setEditTarget(toAdd.Source);
1214 i.setHighlightMode(mode, highlightColor);
1215 _vectorItems.add(i);
1216 i.invalidateAll();
1217 i.invalidateFill();
1218 // Get the right most x and bottom most y pos
1219 int itemRight = i.getX() + i.getBoundsWidth();
1220 if (itemRight > maxX)
1221 maxX = itemRight;
1222 int itemBottom = i.getY() + i.getBoundsHeight();
1223 if (itemBottom > maxY)
1224 maxY = itemBottom;
1225 }
1226 toAdd.setSize(maxX, maxY);
1227 return true;
1228 }
1229
1230 public Collection<Vector> getVectors() {
1231 Collection<Vector> l = new LinkedList<Vector>();
1232 l.addAll(_vectors);
1233 return l;
1234 }
1235
1236 public Collection<Overlay> getOverlays() {
1237 return new LinkedList<Overlay>(_overlays.keySet());
1238 }
1239
1240 /**
1241 * @return All vectosr seen by this frame (including its vector's vectors).
1242 */
1243 public List<Vector> getVectorsDeep() {
1244 List<Vector> l = new LinkedList<Vector>();
1245 getVectorsDeep(l, this, new LinkedList<Frame>());
1246 return l;
1247 }
1248
1249 private boolean getVectorsDeep(List<Vector> vectors, Frame vector,
1250 List<Frame> seenVectors) {
1251
1252 if (seenVectors.contains(vector))
1253 return false;
1254
1255 seenVectors.add(vector);
1256
1257 for (Vector o : vector.getVectors()) {
1258 if (getVectorsDeep(vectors, o.Frame, seenVectors)) {
1259 vectors.add(o);
1260 }
1261 }
1262
1263 return true;
1264 }
1265
1266 // private boolean getOverlaysDeep(List<Overlay> overlays, Frame overlay,
1267 // List<Frame> seenOverlays) {
1268 //
1269 // if (seenOverlays.contains(overlay))
1270 // return false;
1271 //
1272 // seenOverlays.add(overlay);
1273 //
1274 // for (Overlay o : overlay.getOverlays()) {
1275 // if (getOverlaysDeep(overlays, o.Frame, seenOverlays)) {
1276 // overlays.add(o);
1277 // }
1278 // }
1279 //
1280 // return true;
1281 // }
1282
1283 /**
1284 * Gets the overlay on this frame which owns the given item.
1285 *
1286 * @param item
1287 * The item - must not be null.
1288 * @return The overlay that contains the itm. Null if no overlay owns the
1289 * item.
1290 */
1291 public Overlay getOverlayOwner(Item item) {
1292 if (item == null)
1293 throw new NullPointerException("item");
1294
1295 for (Overlay l : getOverlays()) {
1296 if (item.getParent() == l.Frame)
1297 return l;
1298 }
1299
1300 // TODO return the correct vector... not just the first vector matching
1301 // the vectorFrame
1302 for (Vector v : getVectors()) {
1303 if (item.getParent() == v.Frame)
1304 return v;
1305 }
1306
1307 return null;
1308 }
1309
1310 public void clearVectors() {
1311 _vectors.clear();
1312
1313 for (Item i : _vectorItems) { // TODO: Rethink where this should live
1314 i.invalidateAll();
1315 i.invalidateFill();
1316 }
1317 _vectorItems.clear();
1318
1319 }
1320
1321 protected boolean removeVector(Vector toRemove) {
1322 if (!_vectors.remove(toRemove))
1323 return false;
1324 for (Item i : toRemove.Frame.getVectorItems()) {
1325 i.invalidateAll();
1326 i.invalidateFill();
1327 _vectorItems.remove(i);
1328 i.onParentStateChanged(new ItemParentStateChangedEvent(this,
1329 ItemParentStateChangedEvent.EVENT_TYPE_REMOVED_VIA_OVERLAY,
1330 toRemove.permission));
1331
1332 }
1333 return true;
1334 }
1335
1336 public void clearOverlays() {
1337 for (Overlay o : _overlays.keySet()) {
1338 for (Item i : o.Frame.getItems()) {
1339 i
1340 .onParentStateChanged(new ItemParentStateChangedEvent(
1341 this,
1342 ItemParentStateChangedEvent.EVENT_TYPE_REMOVED_VIA_OVERLAY,
1343 o.permission));
1344 }
1345 }
1346 _overlayItems.clear();
1347 _overlays.clear();
1348 assert (_overlays.isEmpty());
1349 }
1350
1351 protected boolean removeOverlay(Frame f) {
1352 for (Overlay o : _overlays.keySet()) {
1353 if (o.Frame == f) {
1354 _overlays.remove(o);
1355 for (Item i : f.getItems()) {
1356 _overlayItems.remove(i);
1357 i
1358 .onParentStateChanged(new ItemParentStateChangedEvent(
1359 this,
1360 ItemParentStateChangedEvent.EVENT_TYPE_REMOVED_VIA_OVERLAY,
1361 o.permission));
1362 }
1363 return true;
1364 }
1365 }
1366 return false;
1367 }
1368
1369 public void addAllVectors(List<Vector> vectors) {
1370 for (Vector v : vectors) {
1371 addVector(v);
1372 }
1373 }
1374
1375 public void addAllOverlays(Collection<Overlay> overlays) {
1376 for (Overlay o : overlays) {
1377 addOverlay(o);
1378 }
1379 }
1380
1381 protected boolean addOverlay(Overlay toAdd) {
1382 // make sure we dont add this frame as an overlay of itself
1383 if (toAdd.Frame == this)
1384 return false;
1385 // Dont add the overlay if there is already one for this frame
1386 if (_overlays.values().contains(toAdd.Frame))
1387 return false;
1388 // Add the overlay to the map of overlays on this frame
1389 _overlays.put(toAdd, toAdd.Frame);
1390 // Add all the overlays from the overlay frame to this frame
1391 for (Overlay o : toAdd.Frame.getOverlays())
1392 addOverlay(o);
1393
1394 // Add all the vectors from the overlay frame to this frame
1395 for (Vector v : toAdd.Frame.getVectors())
1396 addVector(v);
1397
1398 // Now add the items for this overlay
1399 UserAppliedPermission permission = UserAppliedPermission.min(toAdd.Frame.getUserAppliedPermission(),toAdd.permission);
1400
1401 // Items must be notified that they have been added or removed from this
1402 // frame via the overlay...
1403 for (Item i : toAdd.Frame.getVisibleItems()) {
1404 i.onParentStateChanged(new ItemParentStateChangedEvent(this,
1405 ItemParentStateChangedEvent.EVENT_TYPE_ADDED_VIA_OVERLAY,
1406 permission));
1407 // i.setPermission(permission);
1408 _overlayItems.add(i);
1409 }
1410
1411 return true;
1412 }
1413
1414 @Override
1415 public boolean equals(Object o) {
1416 if (o instanceof String) {
1417 return (String.CASE_INSENSITIVE_ORDER
1418 .compare((String) o, getName()) == 0);
1419 }
1420
1421 if (o instanceof Frame) {
1422 return getName().equals(((Frame) o).getName());
1423 }
1424
1425 return super.equals(o);
1426 }
1427
1428 /**
1429 * Merge one frames contents into another.
1430 *
1431 * @param toMergeWith
1432 */
1433 private void merge(Frame toMergeWith) {
1434 if (toMergeWith == null)
1435 return;
1436
1437 List<Item> copies = ItemUtils.CopyItems(toMergeWith.getItems());
1438 copies.remove(toMergeWith.getNameItem());
1439
1440 for (Item i : copies) {
1441 if (i.getID() >= 0) {
1442 i.setID(this.getNextItemID());
1443 addItem(i);
1444 }
1445 }
1446 }
1447
1448 /**
1449 * This method is for merging frames or setting frame attributes via
1450 * injecting a text item into the frameName item.
1451 *
1452 * @param toMerge
1453 * @return the items that cant be merged
1454 */
1455 public List<Item> merge(List<Item> toMerge) {
1456 ArrayList<Item> remain = new ArrayList<Item>(0);
1457
1458 for (Item i : toMerge) {
1459 if (!(i instanceof Text))
1460 remain.add(i);
1461 else {
1462 if (!AttributeUtils.setAttribute(this, (Text) i)) {
1463 if (i.getLink() != null)
1464 merge(FrameIO.LoadFrame(i.getAbsoluteLink()));
1465 else if (FrameIO
1466 .isValidFrameName(((Text) i).getFirstLine())) {
1467 // If we get hear we are merging frames
1468 merge(FrameIO.LoadFrame(((Text) i).getFirstLine()));
1469 }
1470 }
1471 }
1472 }
1473
1474 return remain;
1475 }
1476
1477 /**
1478 * Removes all non-title non-annotation items from this Frame. All removed
1479 * items are added to the backup-stack.
1480 */
1481 public void clear(boolean keepAnnotations) {
1482 List<Item> newBody = new ArrayList<Item>(0);
1483 Item title = getTitleItem();
1484 if (title != null) {
1485 newBody.add(title);
1486 _body.remove(title);
1487 }
1488 if (keepAnnotations) {
1489 for (Item i : _body) {
1490 if (i.isAnnotation())
1491 newBody.add(i);
1492 }
1493 }
1494 _body.removeAll(newBody);
1495 addAllToUndo(_body);
1496 _body = newBody;
1497 change();
1498
1499 if (!keepAnnotations && _annotations != null)
1500 _annotations.clear();
1501 }
1502
1503 /**
1504 * Creates a new text item with the given text.
1505 *
1506 * @param text
1507 * @return
1508 */
1509 public Text createNewText(String text) {
1510 Text t = createBlankText(text);
1511 t.setText(text);
1512 return t;
1513 }
1514
1515 /**
1516 * Creates a new Text Item with no text. The newly created Item is a copy
1517 * the ItemTemplate if one is present, and inherits all the attributes of
1518 * the Template
1519 *
1520 * @return The newly created Text Item
1521 */
1522 public Text createBlankText(String templateType) {
1523 SessionStats.CreatedText();
1524 Text t;
1525 if (templateType.length() == 0)
1526 t = getItemTemplate().copy();
1527 else
1528 t = getItemTemplate(templateType.charAt(0));
1529
1530 // reset attributes
1531 t.setID(getNextItemID());
1532 t.setPosition(DisplayIO.getMouseX(), FrameMouseActions.getY());
1533 t.setText("");
1534 t.setParent(this);
1535
1536 // Set the width if the template doesnt have a width
1537 // Make it the width of the page
1538 // t.setMaxWidth(FrameGraphics.getMaxFrameSize().width);
1539 // if (t.getWidth() <= 0) {
1540 // String maxWidthString = getAnnotationValue("maxwidth");
1541 // int width = FrameGraphics.getMaxFrameSize().width;
1542 // if (maxWidthString != null) {
1543 // try {
1544 // width = Math.min(width, Integer.parseInt(maxWidthString));
1545 // } catch (NumberFormatException nfe) {
1546 // }
1547 // }
1548 //
1549 // t.setRightMargin(width);
1550 // }
1551 addItem(t);
1552 return t;
1553 }
1554
1555 public Item createDot() {
1556 Item dot = new Dot(DisplayIO.getMouseX(), FrameMouseActions.getY(),
1557 getNextItemID());
1558
1559 Item template = getTemplate(_dotTemplate, ItemUtils.TAG_DOT_TEMPLATE);
1560 float thickness = template.getThickness();
1561 if (thickness > 0)
1562 dot.setThickness(template.getThickness());
1563 if (template.getLinePattern() != null)
1564 dot.setLinePattern(template.getLinePattern());
1565 dot.setColor(template.getColor());
1566 dot.setFillColor(template.getFillColor());
1567 // reset attributes
1568 dot.setParent(this);
1569 return dot;
1570 }
1571
1572 private Text getTemplate(Text defaultTemplate, int templateTag) {
1573 Text t = null;
1574
1575 // check for an updated template...
1576 for (Item i : this.getItems()) {
1577 if (ItemUtils.startsWithTag(i, templateTag)) {
1578 t = (Text) i;
1579 break;
1580 }
1581 }
1582
1583 if (t == null) {
1584 if (defaultTemplate == null) {
1585 return null;
1586 }
1587 t = defaultTemplate;
1588 }
1589
1590 // If the item is linked apply any attribute pairs on the child frame
1591 String link = t.getAbsoluteLink();
1592 // need to get link first because copy doesnt copy the link
1593 t = t.copy();
1594 if (link != null) {
1595 t.setLink(null);
1596 Frame childFrame = FrameIO.LoadFrame(link);
1597 if (childFrame != null) {
1598 // read in attribute value pairs
1599 for (Text attribute : childFrame.getBodyTextItems(false)) {
1600 AttributeUtils.setAttribute(t, attribute);
1601 }
1602 }
1603 }
1604 return t;
1605 }
1606
1607 private Text getItemTemplate(char firstChar) {
1608 switch (firstChar) {
1609 case '@':
1610 return getAnnotationTemplate();
1611 case '/':
1612 case '#':
1613 return getCodeCommentTemplate();
1614 default:
1615 return getItemTemplate();
1616 }
1617 }
1618
1619 public Text createNewText() {
1620 return createNewText("");
1621 }
1622
1623 public Text addText(int x, int y, String text, String action) {
1624 Text t = createNewText(text);
1625 t.setPosition(x, y);
1626 t.addAction(action);
1627 return t;
1628 }
1629
1630 public Item addText(int x, int y, String text, String action, String link) {
1631 Item t = addText(x, y, text, action);
1632 t.setLink(link);
1633 return t;
1634 }
1635
1636 public Item addDot(int x, int y) {
1637 Item d = new Dot(x, y, getNextItemID());
1638 addItem(d);
1639 return d;
1640 }
1641
1642 public boolean isSaved() {
1643 return _saved;
1644 }
1645
1646 public void setSaved() {
1647 // System.out.println(getName() + " saved");
1648 _saved = true;
1649 _change = false;
1650 }
1651
1652 public static boolean rubberbandingLine() {
1653 return FreeItems.getInstance().size() == 2
1654 && (FreeItems.getInstance().get(0) instanceof Line || FreeItems
1655 .getInstance().get(1) instanceof Line);
1656 }
1657
1658 /**
1659 * Tests if an item is a non title, non frame name, non special annotation
1660 * text item.
1661 *
1662 * @param it
1663 * the item to be tested
1664 * @return true if the item is a normal text item
1665 */
1666 public boolean isNormalTextItem(Item it) {
1667 if (it instanceof Text && it != getTitleItem() && it != _frameName
1668 && !((Text) it).isSpecialAnnotation()) {
1669 return true;
1670 }
1671
1672 return false;
1673 }
1674
1675 /**
1676 * Moves the mouse to the end of the text item with a specified index.
1677 *
1678 * @param index
1679 */
1680 public boolean moveMouseToTextItem(int index) {
1681 List<Item> items = getItems();
1682 int itemsFound = 0;
1683 for (int i = 0; i < items.size(); i++) {
1684 Item it = items.get(i);
1685 if (isNormalTextItem(it))
1686 itemsFound++;
1687 if (itemsFound > index) {
1688 DisplayIO.setCursorPosition(((Text) it)
1689 .getParagraphEndPosition().x, it.getY());
1690 DisplayIO.resetCursorOffset();
1691 FrameGraphics.Repaint();
1692 return true;
1693 }
1694 }
1695
1696 return false;
1697 }
1698
1699 /*
1700 * public boolean moveMouseToNextTextItem(int index) { List<Item> items =
1701 * getItems(); int itemsFound = 0; for (int i = 0; i < items.size(); i++) {
1702 * Item it = items.get(i); if ( isNormalTextItem(it)) itemsFound++; if
1703 * (itemsFound > index) {
1704 * DisplayIO.setCursorPosition(((Text)it).getEndParagraphPosition().x,
1705 * it.getY()); DisplayIO.resetCursorOffset(); FrameGraphics.Repaint();
1706 * return true; } }
1707 *
1708 * return false; }
1709 */
1710
1711 /**
1712 * Searches for an annotation item called start to be used as the default
1713 * cursor location when TDFC occurs.
1714 */
1715 public boolean moveMouseToDefaultLocation() {
1716 List<Item> items = getItems();
1717
1718 for (Item it : items) {
1719 if (it instanceof Text) {
1720 Text t = (Text) it;
1721 if (t.getText().toLowerCase().startsWith("@start")
1722 || t.getText().toLowerCase().equals("@start:")) {
1723 // Used to allow users the option of putting an initial
1724 // bullet after the @start
1725 // This was replaced by width
1726 // t.stripFirstWord();
1727 t.setText("");
1728
1729 if (t.getText().equals(""))
1730 DisplayIO.getCurrentFrame().removeItem(t);
1731 if (!FreeItems.itemsAttachedToCursor()) {
1732 DisplayIO.setCursorPosition(((Text) it)
1733 .getParagraphEndPosition());
1734 DisplayIO.resetCursorOffset();
1735 }
1736 FrameGraphics.Repaint();
1737 return true;
1738 }
1739 }
1740 }
1741
1742 return false;
1743 }
1744
1745 /**
1746 * Gets the file name that actions should use to export files created by
1747 * running actions from this frame.
1748 *
1749 * @return the fileName if the frame contains an '@file' tag. Returns the
1750 * name of the frame if the tag isnt on the frame.
1751 */
1752 public String getExportFileName() {
1753 String fileName = getExportFileTagValue();
1754
1755 if (fileName == null) {
1756 fileName = getTitle();
1757
1758 if (fileName == null) {
1759 fileName = getName();
1760 }
1761 }
1762
1763 return fileName;
1764 }
1765
1766 public void toggleBackgroundColor() {
1767 setBackgroundColor(ColorUtils.getNextColor(_background,
1768 Frame.COLOR_WHEEL, null));
1769 }
1770
1771 public void setName(String frameset, int i) {
1772 setFrameset(frameset);
1773 setFrameNumber(i);
1774 }
1775
1776 /**
1777 * Sets the item permissions to match the protection for the frame.
1778 *
1779 */
1780 public void refreshItemPermissions(UserAppliedPermission maxPermission) {
1781 if(_frameName == null)
1782 return;
1783
1784 UserAppliedPermission permission = UserAppliedPermission.min(maxPermission, getUserAppliedPermission());
1785 _frameName.setPermission(permission);
1786
1787 switch (permission) {
1788 case none:
1789 _frameName.setBackgroundColor(new Color(255, 220, 220));
1790 break;
1791 case followLinks:
1792 _frameName.setBackgroundColor(new Color(255, 230, 135));
1793 break;
1794 case copy:
1795 _frameName.setBackgroundColor(new Color(255, 255, 155));
1796 break;
1797 case createFrames:
1798 _frameName.setBackgroundColor(new Color(220, 255, 220));
1799 break;
1800 case full:
1801 _frameName.setBackgroundColor(null);
1802 break;
1803 default:
1804 assert (false);
1805 break;
1806 }
1807
1808 for (Overlay o : getOverlays())
1809 o.Frame.refreshItemPermissions(o.permission);
1810
1811 // Only update the permissions if we have to
1812 if (_body.size() > 0 && permission.equals(_body.get(0).getPermission()))
1813 return;
1814
1815 for (Item i : _body) {
1816 i.setPermission(permission);
1817 }
1818
1819 }
1820
1821 public boolean isTestFrame() {
1822 Text title = getTitleItem();
1823 if (title == null)
1824 return false;
1825 String action = title.getFirstAction();
1826 if (action == null)
1827 return false;
1828 action = action.toLowerCase();
1829 return action.startsWith(Simple.RUN_FRAME_ACTION)
1830 || action.startsWith(Simple.DEBUG_FRAME_ACTION);
1831 }
1832
1833 public void setActiveTime(String activeTime) {
1834 try {
1835 _activeTime = new Time(Time.valueOf(activeTime).getTime() + 12 * 60
1836 * 60 * 1000);
1837 } catch (Exception e) {
1838 _activeTime = new Time(0);
1839 }
1840 }
1841
1842 public void setActiveTime(Time activeTime) {
1843 _activeTime = activeTime;
1844 }
1845
1846 public void setDarkTime(Time darkTime) {
1847 _darkTime = darkTime;
1848 }
1849
1850 public void setDarkTime(String darkTime) {
1851 try {
1852 _darkTime = new Time(Time.valueOf(darkTime).getTime() + 12 * 60
1853 * 60 * 1000);
1854 } catch (Exception e) {
1855 _darkTime = new Time(0);
1856 }
1857 }
1858
1859 /**
1860 * Returns null if their is no backup frame or if it is invalid.
1861 *
1862 * @return the backup frame for this frame
1863 */
1864 public Frame getBackupFrame() {
1865 Text backupTag = _annotations.get("old");
1866 if (backupTag == null)
1867 return null;
1868 // TODO want another way to deal with updating of annotations items
1869 // without F12 refresh
1870 // Reparse the frame if annotation item has been modified
1871 String[] processedText = backupTag.getProcessedText();
1872 if (processedText == null) {
1873 // Reparse the frame if this item has not yet been parsed
1874 FrameUtils.Parse(this);
1875 return getBackupFrame();
1876 }
1877 // Now return the name of the backed up frame
1878 String link = backupTag.getAbsoluteLink();
1879 if (link == null || link.equalsIgnoreCase(getName()))
1880 return null;
1881
1882 Frame backup = FrameIO.LoadFrame(link);
1883 return backup;
1884 }
1885
1886 public Time getDarkTime() {
1887 return _darkTime;
1888 }
1889
1890 public Time getActiveTime() {
1891 return _activeTime;
1892 }
1893
1894 /**
1895 * Gets the number of backed up versions of this frame are saved plus 1 for
1896 * this frame.
1897 *
1898 * @return the number of frames in the backed up comet
1899 */
1900 public int getCometLength() {
1901 Frame backup = getBackupFrame();
1902 return 1 + (backup == null ? 0 : backup.getCometLength());
1903 }
1904
1905 public void addAnnotation(Text item) {
1906 if (_annotations == null) {
1907 _annotations = new HashMap<String, Text>();
1908 }
1909 // Check if this item has already been processed
1910 String[] tokens = item.getProcessedText();
1911 if (tokens != null) {
1912 if (tokens.length > 0) {
1913 _annotations.put(tokens[0], item);
1914 }
1915 return;
1916 }
1917
1918 String text = item.getText().trim();
1919 assert (text.charAt(0) == '@');
1920 // Ignore annotations with spaces after the tag symbol
1921 if (text.length() < 2 || !Character.isLetter(text.charAt(1))) {
1922 item.setProcessedText(new String[0]);
1923 return;
1924 }
1925 // The separator char must come before the first non letter otherwise we
1926 // ignore the annotation item
1927 for (int i = 2; i < text.length(); i++) {
1928 char ch = text.charAt(i);
1929 if (!Character.isLetterOrDigit(ch)) {
1930 // Must have an attribute value pair
1931 if (ch == AttributeValuePair.SEPARATOR_CHAR) {
1932 // Get the attribute
1933 String attribute = text.substring(1, i).toLowerCase();
1934 String value = "";
1935 if (text.length() > 1 + i) {
1936 value = text.substring(i + 1).trim();
1937 }
1938 item.setProcessedText(new String[] { attribute, value });
1939 _annotations.put(attribute, item);
1940 return;
1941 } else {
1942 item.setProcessedText(new String[0]);
1943 return;
1944 }
1945 }
1946 }
1947 // If it was nothing but letters and digits save the tag
1948 String lowerCaseText = text.substring(1).toLowerCase();
1949 item.setProcessedText(new String[] { lowerCaseText });
1950 _annotations.put(lowerCaseText, item);
1951 }
1952
1953 public boolean hasAnnotation(String annotation) {
1954 if (_annotations == null)
1955 refreshAnnotationList();
1956 return _annotations.containsKey(annotation.toLowerCase());
1957 }
1958
1959 /**
1960 * Returns the annotation value in full case.
1961 *
1962 * @param annotation
1963 * the annotation to retrieve the value of.
1964 * @return the annotation item value in full case or null if the annotation
1965 * is not on the frame or has no value.
1966 */
1967 public String getAnnotationValue(String annotation) {
1968 if (_annotations == null)
1969 refreshAnnotationList();
1970
1971 Text text = _annotations.get(annotation.toLowerCase());
1972 if (text == null)
1973 return null;
1974
1975 String[] tokens = text.getProcessedText();
1976 if (tokens != null && tokens.length > 1)
1977 return tokens[1];
1978 return null;
1979 }
1980
1981 Map<String, Text> _annotations = null;
1982
1983 private Collection<FrameObserver> _observers = new HashSet<FrameObserver>();
1984
1985 public void clearAnnotations() {
1986 _annotations = null;
1987 }
1988
1989 public List<Item> getVisibleItems() {
1990 return getItems(true);
1991 }
1992
1993 private void refreshAnnotationList() {
1994 if (_annotations == null)
1995 _annotations = new HashMap<String, Text>();
1996 else
1997 _annotations.clear();
1998 for (Text text : getTextItems()) {
1999 if (text.isAnnotation()) {
2000 addAnnotation(text);
2001 }
2002 }
2003 }
2004
2005 public Collection<Text> getAnnotationItems() {
2006 if (_annotations == null) {
2007 refreshAnnotationList();
2008 }
2009 return _annotations.values();
2010 }
2011
2012 /**
2013 * Gets a list of items to be saved to file by text file writers.
2014 *
2015 * @return the list of items to be saved to a text file
2016 */
2017 public List<Item> getItemsToSave() {
2018
2019 if (!_sorted) {
2020 Collections.sort(_body);
2021 _sorted = true;
2022 }
2023
2024 // iWidgets are handled specially since 8 items are written as one
2025 Collection<InteractiveWidget> seenWidgets = new LinkedHashSet<InteractiveWidget>();
2026
2027 List<Item> toSave = new ArrayList<Item>();
2028
2029 for (Item i : _body) {
2030 if (i == null || i.dontSave()) {
2031 continue;
2032 }
2033
2034 // Ensure only one of the WidgetCorners represent a single widget
2035 if (i instanceof WidgetCorner) {
2036 InteractiveWidget iw = ((WidgetCorner) i).getWidgetSource();
2037 if (seenWidgets.contains(iw))
2038 continue;
2039 seenWidgets.add(iw);
2040 toSave.add(iw.getSource());
2041 } else if (i instanceof XRayable) {
2042 XRayable x = (XRayable) i;
2043 toSave.addAll(x.getItemsToSave());
2044 }// Circle centers are items with attached enclosures
2045 else if (i.hasEnclosures()) {
2046 continue;
2047 } else {
2048 toSave.add(i);
2049 }
2050 }
2051
2052 for (Vector v : getVectors()) {
2053 toSave.add(v.Source);
2054 }
2055
2056 return toSave;
2057 }
2058
2059 public Collection<Item> getOverlayItems() {
2060 return _overlayItems;
2061 }
2062
2063 /**
2064 * Returns true if this frame has and overlays for the specified frame.
2065 *
2066 * @param frame
2067 * @return
2068 */
2069 public boolean hasOverlay(Frame frame) {
2070 return _overlays.containsValue(frame);
2071 }
2072
2073 public Collection<Item> getAllItems() {
2074 Collection<Item> allItems = new LinkedHashSet<Item>(_body);
2075 allItems.addAll(_overlayItems);
2076 allItems.addAll(_vectorItems);
2077 return allItems;
2078 }
2079
2080 public Collection<Item> getVectorItems() {
2081 Collection<Item> vectorItems = new LinkedHashSet<Item>(_vectorItems);
2082 vectorItems.addAll(getNonAnnotationItems(false));
2083 return vectorItems;
2084 }
2085
2086 /**
2087 * Gets a list of all the text items on the frame.
2088 *
2089 * @return
2090 */
2091 public Collection<Text> getTextItems() {
2092 Collection<Text> textItems = new ArrayList<Text>();
2093 for (Item i : getItems(true)) {
2094 // only add up normal body text items
2095 if ((i instanceof Text)) {
2096 textItems.add((Text) i);
2097 }
2098 }
2099 return textItems;
2100 }
2101
2102 public Text getAnnotation(String annotation) {
2103 if (_annotations == null)
2104 refreshAnnotationList();
2105
2106 return _annotations.get(annotation.toLowerCase());
2107 }
2108
2109 public void recalculate() {
2110
2111 for (Item i : getItems()) {
2112 if (i.hasFormula() && !i.isAnnotation()) {
2113 i.calculate(i.getFormula());
2114 }
2115 }
2116 }
2117
2118 public void removeObserver(FrameObserver observer) {
2119 _observers.remove(observer);
2120 }
2121
2122 public void addObserver(FrameObserver observer) {
2123 _observers.add(observer);
2124 }
2125
2126 public void clearObservers() {
2127 for (FrameObserver fl : _observers) {
2128 fl.removeSubject(this);
2129 }
2130 // The frame listener will call the frames removeListener method
2131 assert (_observers.size() == 0);
2132 }
2133
2134 public Collection<Text> getNonAnnotationText(boolean removeTitle) {
2135 Collection<Text> items = new LinkedHashSet<Text>();
2136 for (Item i : getItems(true)) {
2137 // only add up normal body text items
2138 if (i instanceof Text && !i.isAnnotation()) {
2139 items.add((Text) i);
2140 }
2141 }
2142 if (removeTitle) {
2143 items.remove(getTitleItem());
2144 }
2145 return items;
2146 }
2147
2148 public void dispose() {
2149 clearObservers();
2150 for (Item i : _body) {
2151 i.dispose();
2152 }
2153 _frameName.dispose();
2154 _body = null;
2155 _frameName = null;
2156 }
2157
2158 public void parse() {
2159 for (Overlay o : getOverlays()) {
2160 o.Frame.parse();
2161 }
2162 // Must parse the frame AFTER the overlays
2163 FrameUtils.Parse(this);
2164 }
2165
2166 public void setPath(String path) {
2167 this.path = path;
2168 }
2169
2170 public String getPath() {
2171 return path;
2172 }
2173
2174 public void setLocal(boolean isLocal) {
2175 this._isLocal = isLocal;
2176 }
2177
2178 public boolean isLocal() {
2179 return _isLocal;
2180 }
2181
2182 public String getExportFileTagValue() {
2183 return getAnnotationValue("file");
2184 }
2185
2186 public void assertEquals(Frame frame2) {
2187 // Check that all the items on the frame are the same
2188 List<Item> items1 = getVisibleItems();
2189 List<Item> items2 = frame2.getVisibleItems();
2190 if (items1.size() != items2.size()) {
2191 throw new UnitTestFailedException(items1.size() + " items", items2
2192 .size()
2193 + " items");
2194 } else {
2195 for (int i = 0; i < items1.size(); i++) {
2196 Item i1 = items1.get(i);
2197 Item i2 = items2.get(i);
2198 String s1 = i1.getText();
2199 String s2 = i2.getText();
2200 if (!s1.equals(s2)) {
2201 throw new UnitTestFailedException(s1, s2);
2202 }
2203 }
2204 }
2205 }
2206
2207 public boolean hasObservers() {
2208 return _observers != null && _observers.size() > 0;
2209 }
2210
2211 public Collection<? extends Item> getInteractableItems() {
2212 /*
2213 * TODO: Cache the interactableItems list so we dont have to recreate it
2214 * every time this method is called
2215 */
2216 if (_interactableItems.size() > 0)
2217 return _interactableItems;
2218
2219 for (Item i : _body) {
2220 if (i == null) {
2221 continue;
2222 }
2223 if (i.isVisible()) {
2224 _interactableItems.add(i);
2225 }
2226 }
2227
2228 for (Item i : _overlayItems) {
2229 if (i.hasPermission(UserAppliedPermission.followLinks)) {
2230 _interactableItems.add(i);
2231 }
2232 }
2233
2234 for (Item i : _vectorItems) {
2235 if (i.hasPermission(UserAppliedPermission.none)) {
2236 _interactableItems.add(i);
2237 }
2238 }
2239
2240 return _interactableItems;
2241 }
2242}
Note: See TracBrowser for help on using the repository browser.