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

Last change on this file since 1326 was 1326, checked in by bln4, 5 years ago

*facepalm* actually fixed bug with frames storing data.

ExpWriter had to be updated to deal with the fact that the header could now have a list in it.

File size: 65.1 KB
Line 
1/**
2 * Frame.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.gui;
20
21import java.io.File;
22import java.sql.Time;
23import java.util.ArrayList;
24import java.util.Collection;
25import java.util.Collections;
26import java.util.HashMap;
27import java.util.HashSet;
28import java.util.LinkedHashSet;
29import java.util.LinkedList;
30import java.util.List;
31import java.util.Map;
32import java.util.Stack;
33
34import org.expeditee.actions.Simple;
35import org.expeditee.core.Colour;
36import org.expeditee.core.Image;
37import org.expeditee.core.bounds.PolygonBounds;
38import org.expeditee.gio.EcosystemManager;
39import org.expeditee.gio.gesture.Gesture;
40import org.expeditee.gio.gesture.Gesture.GestureType;
41import org.expeditee.gio.gesture.StandardGestureActions;
42import org.expeditee.gio.gesture.StandardGestureActions.StandardGestureType;
43import org.expeditee.gio.gesture.data.RefreshGestureData;
44import org.expeditee.gio.input.KBMInputEvent.Key;
45import org.expeditee.gio.input.StandardInputEventListeners;
46import org.expeditee.io.Conversion;
47import org.expeditee.io.ExpReader;
48import org.expeditee.items.Constraint;
49import org.expeditee.items.Dot;
50import org.expeditee.items.Item;
51import org.expeditee.items.Item.HighlightMode;
52import org.expeditee.items.ItemAppearence;
53import org.expeditee.items.ItemParentStateChangedEvent;
54import org.expeditee.items.ItemUtils;
55import org.expeditee.items.Line;
56import org.expeditee.items.PermissionPair;
57import org.expeditee.items.Text;
58import org.expeditee.items.UserAppliedPermission;
59import org.expeditee.items.XRayable;
60import org.expeditee.items.widgets.Widget;
61import org.expeditee.items.widgets.WidgetCorner;
62import org.expeditee.settings.UserSettings;
63import org.expeditee.settings.templates.TemplateSettings;
64import org.expeditee.simple.UnitTestFailedException;
65import org.expeditee.stats.Formatter;
66import org.expeditee.stats.SessionStats;
67
68/**
69 * Represents a Expeditee Frame that is displayed on the screen. Also is a
70 * registered MouseListener on the Browser, and processes any MouseEvents
71 * directly.
72 *
73 * @author jdm18
74 *
75 */
76public class Frame {
77
78 /** The frame number to indicate this is a virtual frame. */
79 public static final int VIRTUAL_FRAME_NUMBER = -1;
80
81 /** The background colour the frame name should take if the frame has user permission level 'none'. */
82 public static final Colour FRAME_NAME_BACKGROUND_COLOUR_FOR_PERMISSION_NONE = Colour.FromRGB255(255, 220, 220);
83 /** The background colour the frame name should take if the frame has user permission level 'followLinks'. */
84 public static final Colour FRAME_NAME_BACKGROUND_COLOUR_FOR_PERMISSION_FOLLOW_LINKS = Colour.FromRGB255(255, 230, 135);
85 /** The background colour the frame name should take if the frame has user permission level 'copy'. */
86 public static final Colour FRAME_NAME_BACKGROUND_COLOUR_FOR_PERMISSION_COPY = Colour.FromRGB255(255, 255, 155);
87 /** The background colour the frame name should take if the frame has user permission level 'createFrames'. */
88 public static final Colour FRAME_NAME_BACKGROUND_COLOUR_FOR_PERMISSION_CREATE_FRAMES = Colour.FromRGB255(220, 255, 220);
89 /** The background colour the frame name should take if the frame has user permission level 'full'. */
90 public static final Colour FRAME_NAME_BACKGROUND_COLOUR_FOR_PERMISSION_FULL = null;
91
92 private boolean _protectionChanged = false;
93
94 // The various attributes of this Frame
95 private String _frameset = null;
96
97 private int _number = -1;
98
99 private int _version = 0;
100
101 private PermissionPair _permissionPair = null;
102
103 private String _owner = null;
104
105 private String _creationDate = null;
106
107 private String _modifiedUser = null;
108
109 private String _modifiedDate = null;
110 private long _modifiedDatePrecise;
111
112 private String _frozenDate = null;
113
114 // Background color is clear
115 private Colour _background = null;
116
117 // Foreground color is automatic by default
118 private Colour _foreground = null;
119
120 private String path;
121
122 private boolean _isLocal = true;
123
124 private boolean _sorted = true;
125
126 /** Whether the frame has changed and therefore needs saving. */
127 private boolean _change = false;
128
129 /** Whether the frame has been saved. */
130 private boolean _saved = false;
131
132 // list of deleted items that can be restored
133 private Stack<History> _undo = new Stack<History>();
134 private Stack<History> _redo = new Stack<History>();
135
136 private List<Item> _body = new ArrayList<Item>();
137 private List<Item> _bodyHiddenDueToPermissions = new ArrayList<Item>();
138
139 // for drawing purposes
140 private List<Widget> _iWidgets = new ArrayList<Widget>();
141
142 // frame data
143 private List<String> _frameData = null;
144
145 private int _lineCount = 0;
146
147 private int _itemCount = 1;
148
149 // The frameName to display on the screen
150 private Text _frameName = null;
151
152 private Map<Overlay, Frame> _overlays = new HashMap<Overlay, Frame>();
153
154 private List<Vector> _vectors = new ArrayList<Vector>();
155
156 private Image _buffer = null;
157
158 private boolean _validBuffer = true;
159
160 private Time _activeTime = new Time(0);
161
162 private Time _darkTime = new Time(0);
163
164 private Collection<Item> _interactableItems = new LinkedHashSet<Item>();
165
166 private Collection<Item> _overlayItems = new LinkedHashSet<Item>();
167
168 private Collection<Item> _vectorItems = new LinkedHashSet<Item>();
169
170 private Text _dotTemplate = TemplateSettings.DotTemplate.get().copy();
171
172 private Map<String, Text> _annotations = null;
173
174 private Collection<FrameObserver> _observers = new HashSet<FrameObserver>();
175
176 private String _encryptionLabel;
177
178 /** Default constructor, nothing is set. */
179 public Frame() {
180 }
181
182 public boolean isReadOnly()
183 {
184 return !_frameName.hasPermission(UserAppliedPermission.full) && !_protectionChanged;
185 }
186
187 public void reset()
188 {
189 refreshItemPermissions(UserAppliedPermission.full);
190 resetDot();
191 SessionStats.NewFrameSession();
192 }
193
194 private void resetDot()
195 {
196 _dotTemplate.setColor(TemplateSettings.ColorWheel.getSafe(1));
197 _dotTemplate.setFillColor(TemplateSettings.FillColorWheel.getSafe(0));
198 }
199
200 public void nextDot()
201 {
202 _dotTemplate.setFillColor(ColorUtils.getNextColor(_dotTemplate.getFillColor(), TemplateSettings.FillColorWheel.get(), null));
203 _dotTemplate.setColor(ColorUtils.getNextColor(_dotTemplate.getColor(), TemplateSettings.ColorWheel.get(), null));
204 if (_dotTemplate.getColor() == null || _dotTemplate.getColor().equals(Colour.WHITE)) {
205 resetDot();
206 }
207 }
208
209 public Image getBuffer()
210 {
211 return _buffer;
212 }
213
214 public void setBuffer(Image newBuffer)
215 {
216 _buffer = newBuffer;
217 }
218
219 public boolean isBufferValid()
220 {
221 if (_buffer == null) {
222 return false;
223 }
224
225 return _validBuffer;
226 }
227
228 private void setBufferValid(boolean newValue)
229 {
230 _validBuffer = newValue;
231 }
232
233 public int getNextItemID()
234 {
235 return ++_itemCount;
236 }
237
238 public void updateIDs(List<Item> items)
239 {
240 for (Item i : items) {
241 if (!(i instanceof Line)) {
242 i.setID(getNextItemID());
243 } else {
244 i.setID(++_lineCount);
245 }
246 }
247 }
248
249 /**
250 *
251 * @return The interactive widgets that are currently anchored in this frame.
252 * Hence it excludes free-widgets. Returns a copy
253 */
254 public List<Widget> getInteractiveWidgets()
255 {
256 LinkedList<Widget> clone = new LinkedList<Widget>();
257 clone.addAll(this._iWidgets);
258 return clone;
259 }
260
261 /**
262 * Returns whether this Frame has been changed and required saving to disk.
263 *
264 * @return True if this Frame has been altered, false otherwise.
265 */
266 public boolean hasChanged()
267 {
268 // virtual frames are never saved
269 if (_number == VIRTUAL_FRAME_NUMBER) {
270 return false;
271 }
272
273 return _change;
274 }
275
276 /**
277 * Sets whether this Frame should be saved to disk.
278 *
279 * @param value
280 * True if this Frame should be saved to disk, False otherwise.
281 */
282 public void setChanged(boolean value)
283 {
284 if (_change == value) {
285 return;
286 }
287
288 _change = value;
289
290 if (_change) {
291 setBufferValid(false);
292 _saved = false;
293 }
294 }
295
296 /**
297 * Notify items observing the data on this frame that the frame content has
298 * changed.
299 *
300 * @param recalculate
301 * true if the frame should be recalculated first.
302 */
303 public void notifyObservers(boolean bRecalculate) {
304 if (bRecalculate) {
305 recalculate();
306 }
307 // Notify the frame listeners that the frame has changed
308 /*
309 * Avoid ConcurrentMod Exceptions when user anchors an item onto this
310 * frame which is observing this frame, by NOT using foreach loop.
311 * Calling update on a dataFrameWidget resets its subjects hence
312 * changing this frames observer list.
313 */
314 Collection<FrameObserver> observersCopy = new LinkedList<FrameObserver>(_observers);
315 // System.out.println(++updateCount + " update");
316
317 for (FrameObserver fl : observersCopy) {
318 if (fl.isVisible()) {
319 fl.update();
320 }
321 }
322 }
323
324 // indicates the frame has changed
325 public void change()
326 {
327 setChanged(true);
328 _interactableItems.clear();
329 }
330
331 /**
332 * Returns an ArrayList of all Items currently on the Frame (excludes Items
333 * attached to the cursor).
334 *
335 * @return The list of Item objects that are on this Frame.
336 */
337 public List<Item> getItems(boolean visible)
338 {
339 if (!_sorted) {
340 for(int i = 0; i < _body.size();) {
341 if(_body.get(i) == null) {
342 _body.remove(i);
343 } else {
344 i++;
345 }
346 }
347 Collections.sort(_body);
348 _sorted = true;
349 }
350
351 List<Item> items = new ArrayList<Item>();
352
353 for (Item i : _body) {
354 if (i == null) {
355 continue;
356 }
357
358 if (i.isVisible() || (!visible && !i.isDeleted())) {
359 items.add(i);
360 }
361 }
362
363 return items;
364 }
365
366 /** TODO: Comment. cts16 */
367 public List<Item> getItems()
368 {
369 return getItems(false);
370 }
371
372 /**
373 * @param i
374 * Item to check if contained in this frame
375 * @return True if this frame contains i.
376 */
377 public boolean containsItem(Item i)
378 {
379 if (i == null) {
380 throw new NullPointerException("i");
381 }
382
383 return _body.contains(i);
384 }
385
386 /**
387 * Returns a list of all the non annotation text items on the frame which
388 * are not the title or frame name or special annotation items.
389 *
390 * @param includeAnnotations
391 * true if annotation items without special meaning should be
392 * included
393 * @param includeLineEnds
394 * true if text on the end of lines should be included in the
395 * list
396 * @return the list of body text items.
397 */
398 public List<Text> getBodyTextItems(boolean includeAnnotations)
399 {
400 List<Text> bodyTextItems = new ArrayList<Text>();
401
402 for (Item i : getItems(true)) {
403 // only add up normal body text items
404 if ((i instanceof Text) && ((includeAnnotations && !((Text) i).isSpecialAnnotation()) || !i.isAnnotation()) && !i.isLineEnd()) {
405 bodyTextItems.add((Text) i);
406 }
407 }
408
409 bodyTextItems.remove(getTitleItem());
410
411 return bodyTextItems;
412 }
413
414 public Collection<Item> getNonAnnotationItems(boolean removeTitle)
415 {
416 Collection<Item> items = new ArrayList<Item>();
417 for (Item i : getItems(true)) {
418 // only add up normal body text items
419 if (!i.isAnnotation()) {
420 items.add(i);
421 }
422 }
423
424 if (removeTitle) {
425 items.remove(getTitleItem());
426 }
427
428 return items;
429 }
430
431 /**
432 * Gets the last item on the frame that is a non annotation item but is also
433 * text.
434 *
435 * @return the last non annotation text item.
436 */
437 public Item getLastNonAnnotationTextItem()
438 {
439 List<Item> items = getItems();
440
441 // find the last non-annotation text item
442 for (int i = (items.size() - 1); i >= 0; i--) {
443 Item it = items.get(i);
444
445 if (it instanceof Text && !it.isAnnotation()) {
446 return it;
447 }
448 }
449 return null;
450 }
451
452 /**
453 * Iterates through the list of items on the frame, and returns one with the
454 * given id if one exists, otherwise returns null.
455 *
456 * @param id
457 * The id to search for in the list of items
458 * @return The item on this frame with the given ID, or null if one is not
459 * found.
460 */
461 public Item getItemWithID(int id)
462 {
463 for (Item i : _body) {
464 if (i.getID() == id) {
465 return i;
466 }
467 }
468 return null;
469 }
470
471 /**
472 * Sets this Frame's Title which is displayed in the top left corner.
473 *
474 * @param title
475 * The title to assign to this Frame
476 */
477 public void setTitle(String title)
478 {
479 if (title == null || title.equals("")) {
480 return;
481 }
482
483 boolean oldchange = _change;
484
485 // remove any numbering this title has
486 title = title.replaceAll("^\\d*[.] *", "");
487 Text frameTitle = getTitleItem();
488
489 if (frameTitle == null) {
490 if (TemplateSettings.TitleTemplate.get() == null) {
491 frameTitle = new Text(getNextItemID(), title);
492 } else {
493 frameTitle = TemplateSettings.TitleTemplate.get().copy();
494 frameTitle.setID(this.getNextItemID());
495 frameTitle.setText(title);
496 }
497 /*
498 * Need to set the parent otherwise an exception is thrown when
499 * new profile is created
500 */
501 frameTitle.setParent(this);
502 frameTitle.resetTitlePosition();
503 addItem(frameTitle);
504 } else {
505 // If it begins with a tag remove it
506
507 // Remove the @ symbol if it is there
508 // title = ItemUtils.StripTagSymbol(title);
509 frameTitle.setText(title);
510 // If the @ symbol is followed by numbering or a bullet remove that too
511 String autoBulletText = StandardGestureActions.getAutoBullet(title);
512 if (autoBulletText.length() > 0) {
513 frameTitle.stripFirstWord();
514 }
515 }
516 // TODO Widgets... check this out
517 // Brook: Cannot figure what is going on above... widget annot titles
518 // should be stripped always
519 if (ItemUtils.startsWithTag(frameTitle, ItemUtils.GetTag(ItemUtils.TAG_IWIDGET))) {
520 frameTitle.stripFirstWord();
521 }
522
523 FrameUtils.Parse(this);
524
525 // do not save if this is the only change
526 setChanged(oldchange);
527 }
528
529 public Text getTitleItem()
530 {
531 List<Item> items = getVisibleItems();
532
533 for (Item i : items) {
534 if (i instanceof Text && i.getX() < UserSettings.TitlePosition.get() && i.getY() < UserSettings.TitlePosition.get()) {
535 return (Text) i;
536 }
537 }
538
539 return null;
540 }
541
542 public String getTitle()
543 {
544 Text title = getTitleItem();
545 if (title == null) {
546 return getName();
547 }
548
549 return title.getFirstLine();
550 }
551
552 public Item getNameItem() {
553 //Text ret = _frameName;
554 if (this.getEncryptionLabel() != null && this.getEncryptionLabel().length() > 0) {
555 _frameName.setText("\uD83D\uDD12" + getFramesetName() + _number);
556 _frameName.resetFrameNamePosition();
557 }
558 return _frameName;
559 }
560
561 public Text getItemTemplate()
562 {
563 return getTemplate(TemplateSettings.ItemTemplate.get(), ItemUtils.TAG_ITEM_TEMPLATE);
564 }
565
566 public Text getAnnotationTemplate()
567 {
568 Text t = getTemplate(TemplateSettings.AnnotationTemplate.get(), ItemUtils.TAG_ANNOTATION_TEMPLATE);
569
570 if (t == null) {
571 t = getItemTemplate();
572 }
573
574 return t;
575 }
576
577 public Text getStatTemplate()
578 {
579 SessionStats.CreatedText();
580 Text t = getTemplate(TemplateSettings.StatTemplate.get(), ItemUtils.TAG_STAT_TEMPLATE);
581
582 if (t == null) {
583 t = getItemTemplate();
584 }
585
586 return t;
587 }
588
589 public Item getTooltipTextItem(String tooltipText)
590 {
591 return getTextItem(tooltipText, TemplateSettings.TooltipTemplate.get().copy());
592 }
593
594 public Item getStatsTextItem(String itemText)
595 {
596 return getTextItem(itemText, getStatTemplate());
597 }
598
599 public Item getTextItem(String itemText)
600 {
601 return getTextItem(itemText, getItemTemplate());
602 }
603
604 private Item getTextItem(String itemText, Text template)
605 {
606 Text t = template;
607 // We dont want the stats to wrap at all
608 // t.setMaxWidth(Integer.MAX_VALUE);
609 t.setPosition(DisplayController.getMousePosition());
610 // The next line is needed to make sure the item is removed from the
611 // frame when picked up
612 t.setParent(this);
613 t.setText(itemText);
614 return t;
615 }
616
617 public Text getCodeCommentTemplate()
618 {
619 Text t = getTemplate(TemplateSettings.CommentTemplate.get(), ItemUtils.TAG_CODE_COMMENT_TEMPLATE);
620
621 if (t == null) {
622 t = getItemTemplate();
623 }
624
625 return t;
626 }
627
628
629 /**
630 * Returns any items on this frame that are within the given Shape. Also
631 * returns any Items on overlay frames that are within the Shape.
632 *
633 * @param shape
634 * The Shape to search for Items in
635 * @return All Items on this Frame or overlayed Frames for which
636 * Item.intersects(shape) return true.
637 */
638 public Collection<Item> getItemsWithin(PolygonBounds poly)
639 {
640 Collection<Item> results = new LinkedHashSet<Item>();
641 for (Item i : getVisibleItems()) {
642 if (i.intersects(poly)) {
643 if (i instanceof XRayable) {
644 results.addAll(i.getConnected());
645 // Dont add circle centers
646 // TODO change this to be isCircle center
647 } else if (!i.hasEnclosures()) {
648 results.add(i);
649 }
650 }
651 }
652
653 for (Overlay o : _overlays.keySet()) {
654 results.addAll(o.Frame.getItemsWithin(poly));
655 }
656
657 for (Item i : getVectorItems()) {
658 if (i.intersects(poly)) {
659 // This assumes a results is a set
660 results.add(i.getEditTarget());
661 }
662 }
663
664 return results;
665 }
666
667 /**
668 * Sets the name of this Frame to the given String, to be displayed in the
669 * upper right corner.
670 *
671 * @param name
672 * The name to use for this Frame.
673 */
674 public void setFrameset(String name)
675 {
676 _frameset = name;
677 }
678
679 public void setName(String framename)
680 {
681 int num = Conversion.getFrameNumber(framename);
682 String frameset = Conversion.getFramesetName(framename, false);
683
684 setName(frameset, num);
685 }
686
687 /**
688 * Sets the frame number of this Frame to the given integer
689 *
690 * @param number
691 * The number to set as the frame number
692 */
693 public void setFrameNumber(int number)
694 {
695 assert (number >= 0);
696
697 if (_number == number) {
698 return;
699 }
700
701 _number = number;
702 boolean oldchange = _change;
703
704 int id;
705
706 if (_frameName != null) {
707 id = _frameName.getID();
708 } else {
709 id = -1 * getNextItemID();
710 }
711
712 _frameName = new Text(id);
713 _frameName.setParent(this);
714 _frameName.setText(getFramesetName() + _number);
715 _frameName.resetFrameNamePosition();
716 setChanged(oldchange);
717 }
718
719 /**
720 * Returns the number of this Frame.
721 *
722 * @return The Frame number of this Frame or -1 if it is not set.
723 */
724 public int getNumber()
725 {
726 return _number;
727 }
728
729 /**
730 * Increments the version of this Frame to the given String.
731 *
732 * @param version
733 * The version to use for this Frame.
734 */
735 public void setVersion(int version)
736 {
737 _version = version;
738 }
739
740 /**
741 * Sets the protection of this Frame to the given String.
742 *
743 * @param protection
744 * The protection to use for this Frame.
745 */
746 public void setPermission(PermissionPair permission)
747 {
748 if (_permissionPair != null && !_permissionPair.getPermission(this._owner).equals(permission.getPermission(this._owner))) {
749 _protectionChanged = true;
750 }
751
752 _permissionPair = new PermissionPair(permission);
753
754 if (_body.size() > 0) {
755 refreshItemPermissions(permission.getPermission(_owner));
756 }
757 }
758
759 /**
760 * Sets the owner of this Frame to the given String.
761 *
762 * @param owner
763 * The owner to use for this Frame.
764 */
765 public void setOwner(String owner)
766 {
767 _owner = owner;
768 }
769
770 /**
771 * Sets the created date of this Frame to the given String.
772 *
773 * @param date
774 * The date to use for this Frame.
775 */
776 public void setDateCreated(String date)
777 {
778 _creationDate = date;
779 _modifiedDate = date;
780 for (Item i : _body) {
781 i.setDateCreated(date);
782 }
783 }
784
785 /**
786 * Resets the dates and version numbers for newly created frames.
787 *
788 */
789 public void resetDateCreated()
790 {
791 setDateCreated(Formatter.getDateTime());
792 resetTimes();
793 setVersion(0);
794 }
795
796 private void resetTimes()
797 {
798 setActiveTime(new Time(0));
799 setDarkTime(new Time(0));
800 }
801
802 /**
803 * Sets the last modifying user of this Frame to the given String.
804 *
805 * @param user
806 * The user to set as the last modifying user.
807 */
808 public void setLastModifyUser(String user)
809 {
810 _modifiedUser = user;
811 }
812
813 /**
814 * Sets the last modified date of this Frame to the given String.
815 *
816 * @param date
817 * The date to set as the last modified date.
818 * @param precise The millisecond precision last modified date.
819 */
820 public void setLastModifyDate(String date, long precise) {
821 _modifiedDate = date;
822 _modifiedDatePrecise = precise;
823 }
824
825 /**
826 * Sets the last modified date of this Frame to the given String.
827 * Used during startup. If able to be more precise then use the overloaded function.
828 *
829 * @param date
830 * The date to set as the last modified date.
831 */
832 public void setLastModifyDate(String date) {
833 _modifiedDate = date;
834 _modifiedDatePrecise = -1l;
835 }
836
837 /**
838 * Sets the last frozen date of this Frame to the given String.
839 *
840 * @param date
841 * The date to set as the last frozen date.
842 */
843 public void setFrozenDate(String date) {
844 _frozenDate = date;
845 }
846
847 public void setResort(boolean value)
848 {
849 _sorted = !value;
850 }
851
852 /**
853 * Adds the given Item to the body of this Frame.
854 *
855 * @param item
856 * The Item to add to this Frame.
857 */
858 public void addItem(Item item)
859 {
860 addItem(item, true);
861 }
862
863 public void addItem(Item item, boolean recalculate)
864 {
865 if (item == null || item.equals(_frameName) || _body.contains(item)) {
866 return;
867 }
868
869 // When an annotation item is anchored the annotation list must be
870 // refreshed
871 if (item.isAnnotation()) {
872 clearAnnotations();
873 }
874
875 if (item instanceof Line) {
876 _lineCount++;
877 }
878
879 _itemCount = Math.max(_itemCount, item.getID());
880
881 _body.add(item);
882 item.setParent(this);
883 item.setFloating(false); // esnure that it is anchored
884
885 item.invalidateCommonTrait(ItemAppearence.Added);
886
887 // If the item is a line end and has constraints with items already
888 // on the frame then make sure the constraints hold
889 if (item.isLineEnd()) {
890 item.setPosition(item.getPosition());
891 }
892
893 _sorted = false;
894
895 // item.setMaxWidth(FrameGraphics.getMaxFrameSize().width);
896 // add widget items to the list of widgets
897 if (item instanceof WidgetCorner) {
898 Widget iw = ((WidgetCorner) item).getWidgetSource();
899 if (!this._iWidgets.contains(iw)) { // A set would have been
900 if (StandardInputEventListeners.kbmStateListener.isKeyDown(Key.CTRL)) {
901 _iWidgets.add(iw);
902 } else {
903 _iWidgets.add(0, iw);
904 }
905 }
906 }
907
908 item.onParentStateChanged(new ItemParentStateChangedEvent(this, ItemParentStateChangedEvent.EVENT_TYPE_ADDED));
909
910 // if (recalculate && item.recalculateWhenChanged())
911 // recalculate();
912
913 change();
914 }
915
916 public void refreshSize()
917 {
918 boolean bReparse = false;
919
920 for (Item i : getItems()) {
921 Integer anchorLeft = i.getAnchorLeft();
922 Integer anchorRight = i.getAnchorRight();
923 Integer anchorTop = i.getAnchorTop();
924 Integer anchorBottom = i.getAnchorBottom();
925
926
927 if (anchorLeft != null) {
928 i.setAnchorLeft(anchorLeft);
929 if (i.hasVector()) {
930 bReparse = true;
931 }
932 }
933
934 if (anchorRight != null) {
935 i.setAnchorRight(anchorRight);
936 if (i.hasVector()) {
937 bReparse = true;
938 }
939 }
940
941 if (anchorTop != null) {
942 i.setAnchorTop(anchorTop);
943 if (i.hasVector()) {
944 bReparse = true;
945 }
946 }
947
948 if (anchorBottom != null) {
949 i.setAnchorBottom(anchorBottom);
950 if (i.hasVector()) {
951 bReparse = true;
952 }
953 }
954 }
955
956 // Do the anchors on the overlays
957 for (Overlay o : getOverlays()) {
958 o.Frame.refreshSize();
959 }
960
961 if (bReparse) {
962 FrameUtils.Parse(this, false);
963 }
964
965 _frameName.resetFrameNamePosition();
966 }
967
968 public void addAllItems(Collection<Item> toAdd)
969 {
970 for (Item i : toAdd) {
971 // If an annotation is being deleted clear the annotation list
972 if (i.isAnnotation()) {
973 i.getParentOrCurrentFrame().clearAnnotations();
974 }
975 // TODO Improve efficiency when addAll is called
976 addItem(i);
977 }
978 }
979
980 public void removeAllItems(Collection<Item> toRemove)
981 {
982 for (Item i : toRemove) {
983 // If an annotation is being deleted clear the annotation list
984 if (i.isAnnotation()) {
985 i.getParentOrCurrentFrame().clearAnnotations();
986 }
987 removeItem(i);
988 }
989 }
990
991 public void removeItem(Item item) {
992 removeItem(item, true);
993 }
994
995 public void removeItem(Item item, boolean recalculate)
996 {
997 // If an annotation is being deleted clear the annotation list
998 if (item.isAnnotation()) {
999 item.getParentOrCurrentFrame().clearAnnotations();
1000 }
1001
1002 if (_body.remove(item)) {
1003 change();
1004 // Remove widgets from the widget list
1005 if (item != null) {
1006 item.onParentStateChanged(new ItemParentStateChangedEvent(this, ItemParentStateChangedEvent.EVENT_TYPE_REMOVED));
1007
1008 if (item instanceof WidgetCorner) {
1009 _iWidgets.remove(((WidgetCorner) item).getWidgetSource());
1010 }
1011
1012 item.invalidateCommonTrait(ItemAppearence.Removed);
1013 }
1014 // TODO Improve efficiency when removeAll is called
1015 // if (recalculate && item.recalculateWhenChanged())
1016 // recalculate();
1017 }
1018 }
1019
1020 /**
1021 * Adds the given History event to the stack.
1022 *
1023 * @param stack The stack to add to
1024 * @param items The items to put in the event
1025 * @param type The type of event that occurred
1026 */
1027 private void addToUndo(Collection<Item> items, History.Type type)
1028 {
1029 if (items.size() < 1) {
1030 return;
1031 }
1032
1033 _undo.push(new History(items, type));
1034 }
1035
1036 public void addToUndoDelete(Collection<Item> items)
1037 {
1038 addToUndo(items, History.Type.deletion);
1039 }
1040
1041 public void addToUndoMove(Collection<Item> items)
1042 {
1043 addToUndo(items, History.Type.movement);
1044 }
1045
1046 public void undo()
1047 {
1048 boolean bReparse = false;
1049 boolean bRecalculate = false;
1050
1051 if (_undo.size() <= 0) {
1052 return;
1053 }
1054
1055 History undo = _undo.pop();
1056
1057 // System.out.println("Undoing: " + undo);
1058
1059 switch(undo.type) {
1060 case deletion:
1061 _redo.push(undo);
1062 for(Item i : undo.items) {
1063 _body.add(i);
1064 bReparse |= i.hasOverlay();
1065 bRecalculate |= i.recalculateWhenChanged();
1066 if (i instanceof Line) {
1067 Line line = (Line) i;
1068 line.getStartItem().addLine(line);
1069 line.getEndItem().addLine(line);
1070 } else {
1071 i.setOffset(0, 0);
1072 }
1073 }
1074 break;
1075 case movement:
1076 List<Item> changed = new LinkedList<Item>(_body);
1077 changed.retainAll(undo.items);
1078 _redo.push(new History(changed, History.Type.movement));
1079 for(Item i : undo.items) {
1080 int index;
1081 if(i.isVisible() && (index = _body.indexOf(i)) != -1) {
1082 _body.set(index, i);
1083 }
1084 }
1085 break;
1086 }
1087
1088 change();
1089
1090 StandardGestureActions.refreshHighlights();
1091
1092 if (bReparse) {
1093 FrameUtils.Parse(this, false, false);
1094 } else {
1095 notifyObservers(bRecalculate);
1096 }
1097
1098 // always request a refresh otherwise filled shapes
1099 // that were broken by a deletion and then reconnected by the undo
1100 // don't get filled until the user otherwise causes them to redraw
1101 DisplayController.requestRefresh(false);
1102 // ItemUtils.EnclosedCheck(_body);
1103 ItemUtils.Justify(this);
1104 }
1105
1106 public void redo()
1107 {
1108 boolean bReparse = false;
1109 boolean bRecalculate = false;
1110
1111 if (_redo.size() <= 0) {
1112 return;
1113 }
1114
1115 History redo = _redo.pop();
1116
1117 // System.out.println("Redoing: " + redo);
1118
1119 switch(redo.type) {
1120 case deletion:
1121 _undo.push(redo);
1122 for(Item i : redo.items) {
1123 _body.remove(i);
1124 bReparse |= i.hasOverlay();
1125 bRecalculate |= i.recalculateWhenChanged();
1126 if (i instanceof Line) {
1127 Line line = (Line) i;
1128 line.getStartItem().removeLine(line);
1129 line.getEndItem().removeLine(line);
1130 } else {
1131 i.setOffset(0, 0);
1132 }
1133 }
1134 break;
1135 case movement:
1136 List<Item> changed = new LinkedList<Item>(_body);
1137 changed.retainAll(redo.items);
1138 _undo.push(new History(changed, History.Type.movement));
1139 for(Item i : redo.items) {
1140 int index;
1141 if(i.isVisible() && (index = _body.indexOf(i)) != -1) {
1142 _body.set(index, i);
1143 }
1144 }
1145 break;
1146 }
1147
1148 change();
1149
1150 StandardGestureActions.refreshHighlights();
1151
1152 if (bReparse) {
1153 FrameUtils.Parse(this, false, false);
1154 } else {
1155 notifyObservers(bRecalculate);
1156 }
1157
1158 // always request a refresh otherwise filled shapes
1159 // that were broken by a deletion and then reconnected by the undo
1160 // don't get filled until the user otherwise causes them to redraw
1161 DisplayController.requestRefresh(false);
1162 // ItemUtils.EnclosedCheck(_body);
1163 ItemUtils.Justify(this);
1164 }
1165
1166 /**
1167 * Returns the frameset of this Frame
1168 *
1169 * @return The name of this Frame's frameset.
1170 */
1171 public String getFramesetName()
1172 {
1173 return _frameset;
1174 }
1175
1176 public String getName()
1177 {
1178 return getFramesetName() + _number;
1179 }
1180
1181 /**
1182 * Returns the format version of this Frame
1183 *
1184 * @return The version of this Frame.
1185 */
1186 public int getVersion()
1187 {
1188 return _version;
1189 }
1190
1191 public PermissionPair getPermission()
1192 {
1193 return _permissionPair;
1194 }
1195
1196 public UserAppliedPermission getUserAppliedPermission()
1197 {
1198 return getUserAppliedPermission(UserAppliedPermission.full);
1199 }
1200
1201 public UserAppliedPermission getUserAppliedPermission(UserAppliedPermission defaultPermission)
1202 {
1203 if (_permissionPair == null) {
1204 return defaultPermission;
1205 }
1206
1207 return _permissionPair.getPermission(_owner);
1208 }
1209
1210 public String getOwner()
1211 {
1212 return _owner;
1213 }
1214
1215 public String getDateCreated()
1216 {
1217 return _creationDate;
1218 }
1219
1220 public String getLastModifyUser()
1221 {
1222 return _modifiedUser;
1223 }
1224
1225 public String getLastModifyDate() {
1226 return _modifiedDate;
1227 }
1228
1229 public long getLastModifyPrecise() {
1230 return _modifiedDatePrecise;
1231 }
1232
1233 public String getFrozenDate()
1234 {
1235 return _frozenDate;
1236 }
1237
1238 public void setBackgroundColor(Colour back)
1239 {
1240 _background = back;
1241
1242 change();
1243
1244 if (this == DisplayController.getCurrentFrame()) {
1245 DisplayController.requestRefresh(false);
1246 }
1247 }
1248
1249 public Colour getBackgroundColor()
1250 {
1251 return _background;
1252 }
1253
1254 public Colour getPaintBackgroundColor()
1255 {
1256 // If null... return white
1257 if (_background == null) {
1258 return Item.DEFAULT_BACKGROUND;
1259 }
1260
1261 return _background;
1262 }
1263
1264 public void setForegroundColor(Colour front)
1265 {
1266 _foreground = front;
1267
1268 change();
1269 }
1270
1271 public Colour getForegroundColor()
1272 {
1273 return _foreground;
1274 }
1275
1276 public Colour getPaintForegroundColor()
1277 {
1278 final int GRAY = Colour.GREY.getBlue();
1279 final int THRESHOLD = Colour.FromComponent255(10);
1280
1281 if (_foreground == null) {
1282 Colour back = getPaintBackgroundColor();
1283 if (Math.abs(back.getRed() - GRAY) < THRESHOLD
1284 && Math.abs(back.getBlue() - GRAY) < THRESHOLD
1285 && Math.abs(back.getGreen() - GRAY) < THRESHOLD)
1286 {
1287 return Colour.WHITE;
1288 }
1289
1290 Colour fore = back.inverse();
1291
1292 return fore;
1293 }
1294
1295 return _foreground;
1296 }
1297
1298 @Override
1299 public String toString()
1300 {
1301 StringBuilder s = new StringBuilder();
1302 s.append(String.format("Name: %s%d%n", _frameset, _number));
1303 s.append(String.format("Version: %d%n", _version));
1304 // s.append(String.format("Permission: %s%n", _permission.toString()));
1305 // s.append(String.format("Owner: %s%n", _owner));
1306 // s.append(String.format("Date Created: %s%n", _creationDate));
1307 // s.append(String.format("Last Mod. User: %s%n", _modifiedUser));
1308 // s.append(String.format("Last Mod. Date: %s%n", _modifiedDate));
1309 s.append(String.format("Items: %d%n", _body.size()));
1310 return s.toString();
1311 }
1312
1313 public Text getTextAbove(Text current)
1314 {
1315 Collection<Text> currentTextItems = FrameUtils.getCurrentTextItems();
1316 List<Text> toCheck = new ArrayList<Text>();
1317
1318 if (currentTextItems.contains(current)) {
1319 toCheck.addAll(currentTextItems);
1320 } else {
1321 toCheck.addAll(getTextItems());
1322 }
1323
1324 // Make sure the items are sorted
1325 Collections.sort(toCheck);
1326
1327 int ind = toCheck.indexOf(current);
1328 if (ind == -1) {
1329 return null;
1330 }
1331
1332 // loop through all items above this one, return the first match
1333 for (int i = ind - 1; i >= 0; i--) {
1334 Text check = toCheck.get(i);
1335 if (FrameUtils.inSameColumn(check, current)) {
1336 return check;
1337 }
1338 }
1339
1340 return null;
1341 }
1342
1343 /**
1344 * Gets the text items that are in the same column and below a specified
1345 * item. Frame title and name are excluded from the column list.
1346 *
1347 * @param from
1348 * The Item to get the column for.
1349 */
1350 public List<Text> getColumn(Item from)
1351 {
1352 // Check that this item is on the current frame
1353 if (!_body.contains(from)) {
1354 return null;
1355 }
1356
1357 if (from == null) {
1358 from = getLastNonAnnotationTextItem();
1359 }
1360
1361 if (from == null) {
1362 return null;
1363 }
1364
1365 // Get the enclosedItems
1366 Collection<Text> enclosed = FrameUtils.getCurrentTextItems();
1367 List<Text> toCheck = null;
1368
1369 if (enclosed.contains(from)) {
1370 toCheck = new ArrayList<Text>();
1371 toCheck.addAll(enclosed);
1372 } else {
1373 toCheck = getBodyTextItems(true);
1374 }
1375
1376 List<Text> column = new ArrayList<Text>();
1377
1378 if (toCheck.size() > 0) {
1379 // Make sure the items are sorted
1380 Collections.sort(toCheck);
1381
1382 // Create a list of items consisting of the item 'from' and all the
1383 // items below it which are also in the same column as it
1384 int index = toCheck.indexOf(from);
1385
1386 // If its the title index will be 0
1387 if (index < 0) {
1388 index = 0;
1389 }
1390
1391 for (int i = index; i < toCheck.size(); i++) {
1392 Text item = toCheck.get(i);
1393 if (FrameUtils.inSameColumn(from, item)) {
1394 column.add(item);
1395 }
1396 }
1397 }
1398
1399 return column;
1400 }
1401
1402 /**
1403 * Adds the given Vector to the list of vector Frames being drawn with this
1404 * Frame.
1405 *
1406 * @param vector
1407 * The Vector to add
1408 *
1409 * @throws NullPointerException
1410 * If overlay is null.
1411 */
1412 protected boolean addVector(Vector toAdd)
1413 {
1414 // make sure we dont add this frame as an overlay of itself
1415 if (toAdd.Frame == this) {
1416 return false;
1417 }
1418
1419 _vectors.add(toAdd);
1420
1421 // Items must be notified that they have been added or removed from this
1422 // frame via the vector...
1423 int maxX = 0;
1424 int maxY = 0;
1425
1426 HighlightMode mode = toAdd.Source.getHighlightMode();
1427 if (mode != HighlightMode.None) {
1428 mode = HighlightMode.Connected;
1429 }
1430
1431 Colour highlightColor = toAdd.Source.getHighlightColor();
1432
1433 for (Item i : ItemUtils.CopyItems(toAdd.Frame.getVectorItems(), toAdd)) {
1434 i.onParentStateChanged(new ItemParentStateChangedEvent(this, ItemParentStateChangedEvent.EVENT_TYPE_ADDED_VIA_OVERLAY, toAdd.permission));
1435 i.setEditTarget(toAdd.Source);
1436 i.setHighlightModeAndColour(mode, highlightColor);
1437 _vectorItems.add(i);
1438 i.invalidateAll();
1439 i.invalidateFill();
1440
1441 // Get the right most x and bottom most y pos
1442 int itemRight = i.getX() + i.getBoundsWidth();
1443 if (itemRight > maxX) {
1444 maxX = itemRight;
1445 }
1446
1447 int itemBottom = i.getY() + i.getBoundsHeight();
1448 if (itemBottom > maxY) {
1449 maxY = itemBottom;
1450 }
1451 }
1452
1453 toAdd.setSize(maxX, maxY);
1454
1455 return true;
1456 }
1457
1458 public Collection<Vector> getVectors()
1459 {
1460 Collection<Vector> l = new LinkedList<Vector>();
1461 l.addAll(_vectors);
1462 return l;
1463 }
1464
1465 public Collection<Overlay> getOverlays()
1466 {
1467 return new LinkedList<Overlay>(_overlays.keySet());
1468 }
1469
1470 /**
1471 * @return All vectors seen by this frame (including its vector's vectors).
1472 */
1473 public List<Vector> getVectorsDeep()
1474 {
1475 List<Vector> l = new LinkedList<Vector>();
1476 getVectorsDeep(l, this, new LinkedList<Frame>());
1477 return l;
1478 }
1479
1480 private boolean getVectorsDeep(List<Vector> vectors, Frame vector, List<Frame> seenVectors)
1481 {
1482 if (seenVectors.contains(vector)) {
1483 return false;
1484 }
1485
1486 seenVectors.add(vector);
1487
1488 for (Vector v : vector.getVectors()) {
1489 if (getVectorsDeep(vectors, v.Frame, seenVectors)) {
1490 vectors.add(v);
1491 }
1492 }
1493
1494 return true;
1495 }
1496
1497 public List<Overlay> getOverlaysDeep()
1498 {
1499 List<Overlay> ret = new LinkedList<Overlay>();
1500
1501 getOverlaysDeep(ret, new LinkedList<Frame>());
1502
1503 return ret;
1504 }
1505
1506 private boolean getOverlaysDeep(List<Overlay> overlays, List<Frame> seenOverlays)
1507 {
1508 if (seenOverlays.contains(this)) {
1509 return false;
1510 }
1511
1512 seenOverlays.add(this);
1513
1514 for (Overlay o : this.getOverlays()) {
1515 if (o.Frame.getOverlaysDeep(overlays, seenOverlays)) {
1516 overlays.add(o);
1517 }
1518 }
1519 return true;
1520 }
1521
1522 /**
1523 * Recursive function similar to AddAllOverlayItems.
1524 *
1525 * @param widgets
1526 * The collection the widgets will be added to
1527 * @param overlay
1528 * An "overlay" frame - this initially will be the parent frame
1529 * @param seenOverlays
1530 * Used for state in the recursion stack. Pass as an empty
1531 * (non-null) list.
1532 */
1533 public List<Widget> getAllOverlayWidgets()
1534 {
1535 List<Widget> widgets = new LinkedList<Widget>();
1536
1537 for (Overlay o : getOverlaysDeep()) {
1538 widgets.addAll(o.Frame.getInteractiveWidgets());
1539 }
1540
1541 return widgets;
1542 }
1543
1544 /**
1545 * Gets the overlay on this frame which owns the given item.
1546 *
1547 * @param item
1548 * The item - must not be null.
1549 * @return The overlay that contains the item. Null if no overlay owns the
1550 * item.
1551 */
1552 public Overlay getOverlayOwner(Item item)
1553 {
1554 if (item == null) {
1555 throw new NullPointerException("item");
1556 }
1557
1558 for (Overlay l : getOverlays()) {
1559 if (item.getParent() == l.Frame) {
1560 return l;
1561 }
1562 }
1563
1564 // TODO return the correct vector... not just the first vector matching
1565 // the vector frame
1566 for (Vector v : getVectors()) {
1567 if (item.getParent() == v.Frame) {
1568 return v;
1569 }
1570 }
1571
1572 return null;
1573 }
1574
1575 public void clearVectors()
1576 {
1577 _vectors.clear();
1578
1579 for (Item i : _vectorItems) { // TODO: Rethink where this should live
1580 i.invalidateAll();
1581 i.invalidateFill();
1582 }
1583 _vectorItems.clear();
1584
1585 }
1586
1587 protected boolean removeVector(Vector toRemove)
1588 {
1589 if (!_vectors.remove(toRemove)) {
1590 return false;
1591 }
1592
1593 for (Item i : toRemove.Frame.getVectorItems()) {
1594 i.invalidateAll();
1595 i.invalidateFill();
1596 _vectorItems.remove(i);
1597 i.onParentStateChanged(new ItemParentStateChangedEvent(this,
1598 ItemParentStateChangedEvent.EVENT_TYPE_REMOVED_VIA_OVERLAY,
1599 toRemove.permission));
1600
1601 }
1602
1603 return true;
1604 }
1605
1606 public void clearOverlays()
1607 {
1608 for (Overlay o : _overlays.keySet()) {
1609 for (Item i : o.Frame.getItems()) {
1610 i.onParentStateChanged(new ItemParentStateChangedEvent(
1611 this,
1612 ItemParentStateChangedEvent.EVENT_TYPE_REMOVED_VIA_OVERLAY,
1613 o.permission));
1614 }
1615 }
1616 _overlayItems.clear();
1617 _overlays.clear();
1618 assert (_overlays.isEmpty());
1619 }
1620
1621 protected boolean removeOverlay(Frame f)
1622 {
1623 for (Overlay o : _overlays.keySet()) {
1624 if (o.Frame == f) {
1625 _overlays.remove(o);
1626
1627 for (Item i : f.getItems()) {
1628 _overlayItems.remove(i);
1629 i.onParentStateChanged(new ItemParentStateChangedEvent(
1630 this,
1631 ItemParentStateChangedEvent.EVENT_TYPE_REMOVED_VIA_OVERLAY,
1632 o.permission));
1633 }
1634
1635 return true;
1636 }
1637 }
1638
1639 return false;
1640 }
1641
1642 public void addAllVectors(List<Vector> vectors)
1643 {
1644 for (Vector v : vectors) {
1645 addVector(v);
1646 }
1647 }
1648
1649 public void addAllOverlays(Collection<Overlay> overlays)
1650 {
1651 for (Overlay o : overlays) {
1652 addOverlay(o);
1653 }
1654 }
1655
1656 protected boolean addOverlay(Overlay toAdd)
1657 {
1658 // make sure we dont add this frame as an overlay of itself
1659 if (toAdd.Frame == this) {
1660 return false;
1661 }
1662
1663 // Dont add the overlay if there is already one for this frame
1664 if (_overlays.values().contains(toAdd.Frame)) {
1665 return false;
1666 }
1667
1668 // Add the overlay to the map of overlays on this frame
1669 _overlays.put(toAdd, toAdd.Frame);
1670
1671 // Add all the overlays from the overlay frame to this frame
1672 // TODO: Can this cause a recursion loop? If A and B are overlays of each other? cts16
1673 for (Overlay o : toAdd.Frame.getOverlays()) {
1674 addOverlay(o);
1675 }
1676
1677 // Add all the vectors from the overlay frame to this frame
1678 for (Vector v : toAdd.Frame.getVectors()) {
1679 addVector(v);
1680 }
1681
1682 // Now add the items for this overlay
1683 UserAppliedPermission permission = UserAppliedPermission.min(toAdd.Frame.getUserAppliedPermission(), toAdd.permission);
1684
1685 // Items must be notified that they have been added or removed from this
1686 // frame via the overlay...
1687 for (Item i : toAdd.Frame.getVisibleItems()) {
1688 i.onParentStateChanged(new ItemParentStateChangedEvent(this, ItemParentStateChangedEvent.EVENT_TYPE_ADDED_VIA_OVERLAY, permission));
1689 _overlayItems.add(i);
1690 }
1691
1692 return true;
1693 }
1694
1695 @Override
1696 public boolean equals(Object o)
1697 {
1698 if (o instanceof String) {
1699 return (String.CASE_INSENSITIVE_ORDER.compare((String) o, getName()) == 0);
1700 }
1701
1702 if (o instanceof Frame) {
1703 return getName().equals(((Frame) o).getName());
1704 }
1705
1706 return super.equals(o);
1707 }
1708
1709 /**
1710 * Merge one frames contents into another.
1711 *
1712 * @param toMergeWith
1713 */
1714 private void merge(Frame toMergeWith)
1715 {
1716 if (toMergeWith == null) {
1717 return;
1718 }
1719
1720 List<Item> copies = ItemUtils.CopyItems(toMergeWith.getItems());
1721 copies.remove(toMergeWith.getNameItem());
1722
1723 for (Item i : copies) {
1724 if (i.getID() >= 0) {
1725 i.setID(this.getNextItemID());
1726 addItem(i);
1727 }
1728 }
1729 }
1730
1731 /**
1732 * This method is for merging frames or setting frame attributes via
1733 * injecting a text item into the frameName item.
1734 *
1735 * @param toMerge
1736 * @return the items that cant be merged
1737 */
1738 public List<Item> merge(List<Item> toMerge)
1739 {
1740 ArrayList<Item> remain = new ArrayList<Item>(0);
1741
1742 for (Item i : toMerge) {
1743 if (!(i instanceof Text)) {
1744 remain.add(i);
1745 } else {
1746 if (!AttributeUtils.setAttribute(this, (Text) i)) {
1747 if (i.getLink() != null) {
1748 merge(FrameIO.LoadFrame(i.getAbsoluteLink()));
1749 } else if (FrameIO.isValidFrameName(((Text) i).getFirstLine())) {
1750 // If we get hear we are merging frames
1751 merge(FrameIO.LoadFrame(((Text) i).getFirstLine()));
1752 }
1753 }
1754 }
1755 }
1756
1757 return remain;
1758 }
1759
1760 /**
1761 * Removes all non-title non-annotation items from this Frame. All removed
1762 * items are added to the backup-stack.
1763 */
1764 public void clear(boolean keepAnnotations)
1765 {
1766 List<Item> newBody = new ArrayList<Item>(0);
1767
1768 Item title = getTitleItem();
1769
1770 if (title != null) {
1771 newBody.add(title);
1772 _body.remove(title);
1773 }
1774
1775 if (keepAnnotations) {
1776 for (Item i : _body) {
1777 if (i.isAnnotation()) {
1778 newBody.add(i);
1779 }
1780 }
1781 }
1782
1783 _body.removeAll(newBody);
1784 addToUndoDelete(_body);
1785 _body = newBody;
1786 change();
1787
1788 if (!keepAnnotations && _annotations != null) {
1789 _annotations.clear();
1790 }
1791 }
1792
1793 /**
1794 * Creates a new text item with the given text.
1795 *
1796 * @param text
1797 * @return
1798 */
1799 public Text createNewText(String text)
1800 {
1801 Text t = createBlankText(text);
1802 t.setText(text);
1803 return t;
1804 }
1805
1806 /**
1807 * Creates a new Text Item with no text. The newly created Item is a copy
1808 * the ItemTemplate if one is present, and inherits all the attributes of
1809 * the Template
1810 *
1811 * @return The newly created Text Item
1812 */
1813 public Text createBlankText(String templateType) {
1814 File file = new File(getFramePathReal());
1815 long fileLastModified = file.lastModified();
1816 long frameLastModified = this.getLastModifyPrecise();
1817 //if (ExpReader.getVersion(getFramePathReal()) > this._version) {
1818 if (fileLastModified > frameLastModified) {
1819 GestureType refreshGestureType = StandardGestureActions.getInstance().gestureType(StandardGestureType.REFRESH);
1820 RefreshGestureData refreshGestureData = new RefreshGestureData(true, false);
1821 try {
1822 StandardGestureActions.getInstance().onGesture(new Gesture(refreshGestureType, refreshGestureData));
1823 EcosystemManager.getMiscManager().beep();
1824 } catch (NullPointerException e) {
1825 //Detected more recent data on file system than on Frame in memory. Unfortunately not in a position to cause a refresh.
1826 }
1827 }
1828
1829 SessionStats.CreatedText();
1830 Text t;
1831
1832 if (templateType.length() == 0) {
1833 t = getItemTemplate().copy();
1834 } else {
1835 t = getItemTemplate(templateType.charAt(0));
1836 }
1837
1838 // reset attributes
1839 t.setID(getNextItemID());
1840 t.setPosition(DisplayController.getMousePosition());
1841 t.setText("");
1842 t.setParent(this);
1843
1844 // Set the width if the template doesnt have a width
1845 // Make it the width of the page
1846 // t.setMaxWidth(FrameGraphics.getMaxFrameSize().width);
1847 // if (t.getWidth() <= 0) {
1848 // String maxWidthString = getAnnotationValue("maxwidth");
1849 // int width = FrameGraphics.getMaxFrameSize().width;
1850 // if (maxWidthString != null) {
1851 // try {
1852 // width = Math.min(width, Integer.parseInt(maxWidthString));
1853 // } catch (NumberFormatException nfe) {
1854 // }
1855 // }
1856 //
1857 // t.setRightMargin(width);
1858 // }
1859 addItem(t);
1860 return t;
1861 }
1862
1863 /**
1864 * Returns the data associated with the frame.
1865 * @return
1866 */
1867 public List<String> getData() {
1868 return _frameData;
1869 }
1870
1871 /**
1872 * Adds a piece of data to be associated with the frame.
1873 * @param dataItem
1874 */
1875 public void addToData(String dataItem) {
1876 if (dataItem != null) {
1877 if (_frameData == null)
1878 _frameData = new LinkedList<String>();
1879 _frameData.add(dataItem);
1880 }
1881 }
1882
1883 /**
1884 * Returns the path (String) to the .exp file that this Frame represents.
1885 * This follows redirects, meaning that it provides the actual file from which
1886 * the frames data is drawn from.
1887 * @return The path to the .exp file that this Frame represents
1888 * @see getFramePathLogical
1889 * @see getFramesetPath
1890 */
1891 public String getFramePathReal() {
1892 String framesetPath = getFramesetPath();
1893 String redirect = ExpReader.redirectTo(getFramePathLogical());
1894
1895 if (redirect == null) {
1896 return getFramePathLogical();
1897 }
1898
1899 while (redirect != null) {
1900 framesetPath = getFramesetPath() + redirect;
1901 redirect = ExpReader.redirectTo(redirect);
1902 }
1903 return framesetPath;
1904 }
1905
1906 /**
1907 * Returns the path (String) to the .exp file that this Frame represents.
1908 * Does not follow redirects, opting to instead provide the logical path to this file.
1909 * @return The path to the .exp file that this Frame represents
1910 * @see getFramePathReal
1911 * @see getFramesetPath
1912 */
1913 public String getFramePathLogical() {
1914 return getFramesetPath() + this.getNumber() + ExpReader.EXTENTION;
1915 }
1916
1917 /**
1918 * Returns the path (String) to the frameset directory that the file that this Frame represents is contained within.
1919 * @return The path to this Frames frameset directory
1920 * @see getFramesetPathLogical
1921 * @see getFramesetPathReal
1922 */
1923 public String getFramesetPath() {
1924 return this.getPath() + File.separator + this.getFramesetName() + File.separator;
1925 }
1926
1927 public Item createDot() {
1928 Item dot = new Dot(DisplayController.getMouseX(), DisplayController.getMouseY(), getNextItemID());
1929
1930 Item template = getTemplate(_dotTemplate, ItemUtils.TAG_DOT_TEMPLATE);
1931 float thickness = template.getThickness();
1932 if (thickness > 0) {
1933 dot.setThickness(template.getThickness());
1934 }
1935 if (template.getLinePattern() != null) {
1936 dot.setLinePattern(template.getLinePattern());
1937 }
1938 dot.setColor(template.getColor());
1939 dot.setFillColor(template.getFillColor());
1940 // reset attributes
1941 dot.setParent(this);
1942 return dot;
1943 }
1944
1945 private Text getTemplate(Text defaultTemplate, int templateTag)
1946 {
1947 Text t = null;
1948
1949 // check for an updated template...
1950 for (Item i : this.getItems()) {
1951 if (ItemUtils.startsWithTag(i, templateTag)) {
1952 t = (Text) i;
1953 break;
1954 }
1955 }
1956
1957 if (t == null) {
1958 if (defaultTemplate == null) {
1959 return null;
1960 }
1961
1962 t = defaultTemplate;
1963 }
1964
1965 // If the item is linked apply any attribute pairs on the child frame
1966 String link = t.getAbsoluteLink();
1967
1968 // need to get link first because copy doesnt copy the link
1969 t = t.copy();
1970 t.setTooltip(null);
1971 if (link != null) {
1972 t.setLink(null);
1973 Frame childFrame = FrameIO.LoadFrame(link);
1974 if (childFrame != null) {
1975 // read in attribute value pairs
1976 for (Text attribute : childFrame.getBodyTextItems(false)) {
1977 AttributeUtils.setAttribute(t, attribute);
1978 }
1979 }
1980 }
1981 return t;
1982 }
1983
1984 /**
1985 * TODO: Comment. cts16
1986 * TODO: Remove magic constants. cts16
1987 */
1988 public Text getItemTemplate(char firstChar)
1989 {
1990 switch (firstChar) {
1991 case '@':
1992 return getAnnotationTemplate();
1993 case '/':
1994 case '#':
1995 return getCodeCommentTemplate();
1996 default:
1997 return getItemTemplate();
1998 }
1999 }
2000
2001 public Text createNewText()
2002 {
2003 return createNewText("");
2004 }
2005
2006 public Text addText(int x, int y, String text, String action)
2007 {
2008 Text t = createNewText(text);
2009 t.setPosition(x, y);
2010 t.addAction(action);
2011 return t;
2012 }
2013
2014 public Text addText(int x, int y, String text, String action, String link)
2015 {
2016 Text t = addText(x, y, text, action);
2017 t.setLink(link);
2018 return t;
2019 }
2020
2021 public Dot addDot(int x, int y)
2022 {
2023 Dot d = new Dot(x, y, getNextItemID());
2024 addItem(d);
2025 return d;
2026 }
2027
2028 /**
2029 * Adds a rectangle to the frame
2030 *
2031 * @param x
2032 * X coordinate of the top-left corner of the rectangle
2033 * @param y
2034 * Y coordinate of the top-left corner of the rectangle
2035 * @param width
2036 * Width of the rectangle
2037 * @param height
2038 * Height of the rectangle
2039 * @param borderThickness
2040 * Thickness, in pixels, of the rectangle's border/outline
2041 * @param borderColor
2042 * Color of the rectangle's border/outline
2043 * @param fillColor
2044 * Color to fill the rectangle with
2045 */
2046 public List<Item> addRectangle(int x, int y, int width, int height, float borderThickness, Colour borderColor, Colour fillColor)
2047 {
2048 List<Item> rectComponents = new ArrayList<Item>();
2049 Item[] corners = new Item[4];
2050
2051 // Top Left
2052 corners[0] = this.createDot();
2053 corners[0].setPosition(x, y);
2054
2055 // Top Right
2056 corners[1] = this.createDot();
2057 corners[1].setPosition(x + width, y);
2058
2059 // Bottom Right
2060 corners[2] = this.createDot();
2061 corners[2].setPosition(x + width, y + height);
2062
2063 // Bottom Left
2064 corners[3] = this.createDot();
2065 corners[3].setPosition(x, y + height);
2066
2067 // Add corners to the collection and setting their attributes
2068 for (int i = 0; i < corners.length; i++) {
2069 corners[i].setThickness(borderThickness);
2070 corners[i].setColor(borderColor);
2071 corners[i].setFillColor(fillColor);
2072 rectComponents.add(corners[i]);
2073 }
2074
2075 // create lines between the corners
2076 rectComponents.add(new Line(corners[0], corners[1], this.getNextItemID()));
2077 rectComponents.add(new Line(corners[1], corners[2], this.getNextItemID()));
2078 rectComponents.add(new Line(corners[2], corners[3], this.getNextItemID()));
2079 rectComponents.add(new Line(corners[3], corners[0], this.getNextItemID()));
2080
2081 // Add constraints between each corner
2082 new Constraint(corners[0], corners[1], this.getNextItemID(), Constraint.HORIZONTAL);
2083 new Constraint(corners[2], corners[3], this.getNextItemID(), Constraint.HORIZONTAL);
2084 new Constraint(corners[1], corners[2], this.getNextItemID(), Constraint.VERTICAL);
2085 new Constraint(corners[3], corners[0], this.getNextItemID(), Constraint.VERTICAL);
2086
2087 List<Item> rect = new ArrayList<Item>(rectComponents);
2088 this.addAllItems(rectComponents);
2089 StandardGestureActions.anchor(rectComponents);
2090 return rect;
2091 }
2092
2093 public boolean isSaved()
2094 {
2095 return _saved;
2096 }
2097
2098 public void setSaved()
2099 {
2100 _saved = true;
2101 _change = false;
2102 }
2103
2104 public static boolean rubberbandingLine()
2105 {
2106 return FreeItems.getInstance().size() == 2 &&
2107 (FreeItems.getInstance().get(0) instanceof Line || FreeItems.getInstance().get(1) instanceof Line);
2108 }
2109
2110 /**
2111 * Tests if an item is a non title, non frame name, non special annotation
2112 * text item.
2113 *
2114 * @param it
2115 * the item to be tested
2116 * @return true if the item is a normal text item
2117 */
2118 public boolean isNormalTextItem(Item it)
2119 {
2120 if (it instanceof Text && it != getTitleItem() && it != _frameName && !((Text) it).isSpecialAnnotation()) {
2121 return true;
2122 }
2123
2124 return false;
2125 }
2126
2127 /**
2128 * Moves the mouse to the end of the text item with a specified index.
2129 *
2130 * @param index
2131 */
2132 public boolean moveMouseToTextItem(int index)
2133 {
2134 List<Item> items = getItems();
2135 int itemsFound = 0;
2136 for (int i = 0; i < items.size(); i++) {
2137 Item it = items.get(i);
2138 if (isNormalTextItem(it)) {
2139 itemsFound++;
2140 }
2141 if (itemsFound > index) {
2142 DisplayController.setCursorPosition(((Text) it).getParagraphEndPosition().getX(), it.getY());
2143 DisplayController.resetCursorOffset();
2144 DisplayController.requestRefresh(true);
2145 return true;
2146 }
2147 }
2148
2149 return false;
2150 }
2151
2152 /**
2153 * Searches for an annotation item called start to be used as the default
2154 * cursor location when TDFC occurs.
2155 *
2156 * TODO: Remove magic constants. cts16
2157 */
2158 public boolean moveMouseToDefaultLocation()
2159 {
2160 List<Item> items = getItems();
2161
2162 for (Item it : items) {
2163 if (it instanceof Text) {
2164 Text t = (Text) it;
2165 if (t.getText().toLowerCase().startsWith("@start") || t.getText().toLowerCase().equals("@start:")) {
2166 // Used to allow users the option of putting an initial
2167 // bullet after the @start
2168 // This was replaced by width
2169 // t.stripFirstWord();
2170 t.setText("");
2171
2172 if (t.getText().equals("")) {
2173 DisplayController.getCurrentFrame().removeItem(t);
2174 }
2175
2176 if (!FreeItems.hasItemsAttachedToCursor()) {
2177 DisplayController.setCursorPosition(((Text) it).getParagraphEndPosition());
2178 DisplayController.resetCursorOffset();
2179 }
2180
2181 DisplayController.requestRefresh(true);
2182
2183 return true;
2184 }
2185 }
2186 }
2187
2188 return false;
2189 }
2190
2191 /**
2192 * Gets the file name that actions should use to export files created by
2193 * running actions from this frame.
2194 *
2195 * @return the fileName if the frame contains an '@file' tag. Returns the
2196 * name of the frame if the tag isnt on the frame.
2197 */
2198 public String getExportFileName()
2199 {
2200 String fileName = getExportFileTagValue();
2201
2202 if (fileName == null) {
2203 fileName = getTitle();
2204
2205 if (fileName == null) {
2206 fileName = getName();
2207 }
2208 }
2209
2210 return fileName;
2211 }
2212
2213 public void toggleBackgroundColor()
2214 {
2215 setBackgroundColor(ColorUtils.getNextColor(_background, TemplateSettings.BackgroundColorWheel.get(), null));
2216 }
2217
2218 public void setName(String frameset, int i)
2219 {
2220 setFrameset(frameset);
2221 setFrameNumber(i);
2222 }
2223
2224 /**
2225 * Sets the item permissions to match the protection for the frame.
2226 * No longer sets item permissions, since items can have their own permissions now (but still default to frame permissions)
2227 *
2228 */
2229 public void refreshItemPermissions(UserAppliedPermission maxPermission)
2230 {
2231 if(_frameName == null) {
2232 return;
2233 }
2234
2235 UserAppliedPermission permission = UserAppliedPermission.min(maxPermission, getUserAppliedPermission());
2236
2237 switch (permission) {
2238 case none:
2239 _frameName.setBackgroundColor(FRAME_NAME_BACKGROUND_COLOUR_FOR_PERMISSION_NONE);
2240 break;
2241 case followLinks:
2242 _frameName.setBackgroundColor(FRAME_NAME_BACKGROUND_COLOUR_FOR_PERMISSION_FOLLOW_LINKS);
2243 break;
2244 case copy:
2245 _frameName.setBackgroundColor(FRAME_NAME_BACKGROUND_COLOUR_FOR_PERMISSION_COPY);
2246 break;
2247 case createFrames:
2248 _frameName.setBackgroundColor(FRAME_NAME_BACKGROUND_COLOUR_FOR_PERMISSION_CREATE_FRAMES);
2249 break;
2250 case full:
2251 _frameName.setBackgroundColor(FRAME_NAME_BACKGROUND_COLOUR_FOR_PERMISSION_FULL);
2252 break;
2253 default:
2254 assert (false);
2255 break;
2256 }
2257
2258 for (Overlay o : getOverlays()) {
2259 for(Item i : o.Frame._body) {
2260 i.setOverlayPermission(o.permission);
2261 }
2262 o.Frame.refreshItemPermissions(o.permission);
2263 }
2264 }
2265
2266 public boolean isTestFrame()
2267 {
2268 Text title = getTitleItem();
2269 if (title == null) {
2270 return false;
2271 }
2272 String action = title.getFirstAction();
2273 if (action == null) {
2274 return false;
2275 }
2276 action = action.toLowerCase();
2277 return action.startsWith(Simple.RUN_FRAME_ACTION) || action.startsWith(Simple.DEBUG_FRAME_ACTION);
2278 }
2279
2280 public void setActiveTime(String activeTime)
2281 {
2282 try {
2283 _activeTime = new Time(Time.valueOf(activeTime).getTime() + 12 * 60 * 60 * 1000);
2284 } catch (Exception e) {
2285 _activeTime = new Time(0);
2286 }
2287 }
2288
2289 public void setActiveTime(Time activeTime)
2290 {
2291 _activeTime = activeTime;
2292 }
2293
2294 public void setDarkTime(Time darkTime)
2295 {
2296 _darkTime = darkTime;
2297 }
2298
2299 public void setDarkTime(String darkTime)
2300 {
2301 try {
2302 _darkTime = new Time(Time.valueOf(darkTime).getTime() + 12 * 60 * 60 * 1000);
2303 } catch (Exception e) {
2304 _darkTime = new Time(0);
2305 }
2306 }
2307
2308 /**
2309 * Returns null if their is no backup frame or if it is invalid.
2310 *
2311 * @return the backup frame for this frame
2312 */
2313 public Frame getBackupFrame()
2314 {
2315 Text backupTag = _annotations.get("old");
2316 if (backupTag == null) {
2317 return null;
2318 }
2319
2320 // TODO want another way to deal with updating of annotations items
2321 // without F12 refresh
2322 // Reparse the frame if annotation item has been modified
2323 String[] processedText = backupTag.getProcessedText();
2324 if (processedText == null) {
2325 // Reparse the frame if this item has not yet been parsed
2326 FrameUtils.Parse(this);
2327 return getBackupFrame();
2328 }
2329
2330 // Now return the name of the backed up frame
2331 String link = backupTag.getAbsoluteLink();
2332 if (link == null || link.equalsIgnoreCase(getName())) {
2333 return null;
2334 }
2335
2336 Frame backup = FrameIO.LoadFrame(link);
2337 return backup;
2338 }
2339
2340 public Time getDarkTime()
2341 {
2342 return _darkTime;
2343 }
2344
2345 public Time getActiveTime()
2346 {
2347 return _activeTime;
2348 }
2349
2350 /**
2351 * Gets the number of backed up versions of this frame are saved plus 1 for
2352 * this frame.
2353 *
2354 * @return the number of frames in the backed up comet
2355 */
2356 public int getCometLength()
2357 {
2358 Frame backup = getBackupFrame();
2359 return 1 + (backup == null ? 0 : backup.getCometLength());
2360 }
2361
2362 public void addAnnotation(Text item)
2363 {
2364 if (_annotations == null) {
2365 _annotations = new HashMap<String, Text>();
2366 }
2367
2368 // Check if this item has already been processed
2369 String[] tokens = item.getProcessedText();
2370 if (tokens != null) {
2371 if (tokens.length > 0) {
2372 _annotations.put(tokens[0], item);
2373 }
2374 return;
2375 }
2376
2377 String text = item.getText().trim();
2378 assert (text.charAt(0) == '@');
2379
2380 // Ignore annotations with spaces after the tag symbol
2381 if (text.length() < 2 || !Character.isLetter(text.charAt(1))) {
2382 item.setProcessedText(new String[0]);
2383 return;
2384 }
2385
2386 // The separator char must come before the first non letter otherwise we
2387 // ignore the annotation item
2388 for (int i = 2; i < text.length(); i++) {
2389 char ch = text.charAt(i);
2390 if (!Character.isLetterOrDigit(ch)) {
2391 // Must have an attribute value pair
2392 if (ch == AttributeValuePair.SEPARATOR_CHAR) {
2393 // Get the attribute
2394 String attribute = text.substring(1, i).toLowerCase();
2395 String value = "";
2396 if (text.length() > 1 + i) {
2397 value = text.substring(i + 1).trim();
2398 }
2399 item.setProcessedText(new String[] { attribute, value });
2400 _annotations.put(attribute, item);
2401 return;
2402 } else {
2403 item.setProcessedText(new String[0]);
2404 return;
2405 }
2406 }
2407 }
2408
2409 // If it was nothing but letters and digits save the tag
2410 String lowerCaseText = text.substring(1).toLowerCase();
2411 item.setProcessedText(new String[] { lowerCaseText });
2412 _annotations.put(lowerCaseText, item);
2413 }
2414
2415 public boolean hasAnnotation(String annotation)
2416 {
2417 if (_annotations == null) {
2418 refreshAnnotationList();
2419 }
2420
2421 return _annotations.containsKey(annotation.toLowerCase());
2422 }
2423
2424 /**
2425 * Returns the annotation value in full case.
2426 *
2427 * @param annotation
2428 * the annotation to retrieve the value of.
2429 * @return the annotation item value in full case or null if the annotation
2430 * is not on the frame or has no value.
2431 */
2432 public String getAnnotationValue(String annotation)
2433 {
2434 if (_annotations == null) {
2435 refreshAnnotationList();
2436 }
2437
2438 Text text = _annotations.get(annotation.toLowerCase());
2439 if (text == null) {
2440 return null;
2441 }
2442
2443 String[] tokens = text.getProcessedText();
2444
2445 if (tokens != null && tokens.length > 1) {
2446 return tokens[1];
2447 }
2448
2449 return null;
2450 }
2451
2452 public void clearAnnotations()
2453 {
2454 _annotations = null;
2455 }
2456
2457 public List<Item> getVisibleItems()
2458 {
2459 return getItems(true);
2460 }
2461
2462 private void refreshAnnotationList()
2463 {
2464 if (_annotations == null) {
2465 _annotations = new HashMap<String, Text>();
2466 } else {
2467 _annotations.clear();
2468 }
2469
2470 for (Text text : getTextItems()) {
2471 if (text.isAnnotation()) {
2472 addAnnotation(text);
2473 }
2474 }
2475 }
2476
2477 public Collection<Text> getAnnotationItems()
2478 {
2479 if (_annotations == null) {
2480 refreshAnnotationList();
2481 }
2482
2483 return _annotations.values();
2484 }
2485
2486 /**
2487 * Gets a list of items to be saved to file by text file writers.
2488 *
2489 * @return the list of items to be saved to a text file
2490 */
2491 public List<Item> getItemsToSave()
2492 {
2493 if (!_sorted) {
2494 Collections.sort(_body);
2495 _sorted = true;
2496 }
2497
2498 // iWidgets are handled specially since 8 items are written as one
2499 Collection<Widget> seenWidgets = new LinkedHashSet<Widget>();
2500
2501 List<Item> toSave = new ArrayList<Item>();
2502
2503 for (Item i : _body) {
2504 if (i == null || i.dontSave()) {
2505 continue;
2506 }
2507
2508 // Ensure only one of the WidgetCorners represent a single widget
2509 if (i instanceof WidgetCorner) {
2510 Widget iw = ((WidgetCorner) i).getWidgetSource();
2511 if (seenWidgets.contains(iw)) {
2512 continue;
2513 }
2514 seenWidgets.add(iw);
2515 toSave.add(iw.getSource());
2516 } else if (i instanceof XRayable) {
2517 XRayable x = (XRayable) i;
2518 toSave.addAll(x.getItemsToSave());
2519 // Circle centers are items with attached enclosures
2520 } else if (i.hasEnclosures()) {
2521 continue;
2522 } else {
2523 toSave.add(i);
2524 }
2525 }
2526
2527 for (Vector v : getVectors()) {
2528 toSave.add(v.Source);
2529 }
2530
2531 return toSave;
2532 }
2533
2534 public Collection<Item> getOverlayItems()
2535 {
2536 return _overlayItems;
2537 }
2538
2539 /**
2540 * Returns true if this frame has and overlays for the specified frame.
2541 *
2542 * @param frame
2543 * @return
2544 */
2545 public boolean hasOverlay(Frame frame)
2546 {
2547 return _overlays.containsValue(frame);
2548 }
2549
2550 public Collection<Item> getAllItems()
2551 {
2552 Collection<Item> allItems = new LinkedHashSet<Item>(_body);
2553
2554 allItems.addAll(_overlayItems);
2555 allItems.addAll(_vectorItems);
2556 return allItems;
2557 }
2558
2559 public Collection<Item> getVectorItems()
2560 {
2561 Collection<Item> vectorItems = new LinkedHashSet<Item>(_vectorItems);
2562 vectorItems.addAll(getNonAnnotationItems(false));
2563 return vectorItems;
2564 }
2565
2566 /**
2567 * Gets a list of all the text items on the frame.
2568 *
2569 * @return
2570 */
2571 public Collection<Text> getTextItems()
2572 {
2573 Collection<Text> textItems = new ArrayList<Text>();
2574
2575 for (Item i : getItems(true)) {
2576 // only add up normal body text items
2577 if ((i instanceof Text)) {
2578 textItems.add((Text) i);
2579 }
2580 }
2581
2582 return textItems;
2583 }
2584
2585 public Text getAnnotation(String annotation)
2586 {
2587 if (_annotations == null) {
2588 refreshAnnotationList();
2589 }
2590
2591 return _annotations.get(annotation.toLowerCase());
2592 }
2593
2594 public void recalculate()
2595 {
2596 for (Item i : getItems()) {
2597 if (i.hasFormula() && !i.isAnnotation()) {
2598 i.calculate(i.getFormula());
2599 }
2600 }
2601 }
2602
2603 public void removeObserver(FrameObserver observer)
2604 {
2605 _observers.remove(observer);
2606 }
2607
2608 public void addObserver(FrameObserver observer)
2609 {
2610 _observers.add(observer);
2611 }
2612
2613 public void clearObservers()
2614 {
2615 for (FrameObserver fl : _observers) {
2616 fl.removeSubject(this);
2617 }
2618
2619 // The frame listener will call the frames removeListener method
2620 assert (_observers.size() == 0);
2621 }
2622
2623 public Collection<Text> getNonAnnotationText(boolean removeTitle)
2624 {
2625 Collection<Text> items = new LinkedHashSet<Text>();
2626
2627 for (Item i : getItems(true)) {
2628 // only add up normal body text items
2629 if (i instanceof Text && !i.isAnnotation()) {
2630 items.add((Text) i);
2631 }
2632 }
2633
2634 if (removeTitle) {
2635 items.remove(getTitleItem());
2636 }
2637
2638 return items;
2639 }
2640
2641 public void dispose()
2642 {
2643 clearObservers();
2644
2645 for (Item i : _body) {
2646 i.dispose();
2647 }
2648
2649 _frameName.dispose();
2650 _body = null;
2651 _frameName = null;
2652 }
2653
2654 public void parse()
2655 {
2656 for (Overlay o : getOverlays()) {
2657 o.Frame.parse();
2658 }
2659
2660 // Must parse the frame AFTER the overlays
2661 FrameUtils.Parse(this);
2662 }
2663
2664 public void setPath(String path)
2665 {
2666 this.path = path;
2667 }
2668
2669 public String getPath()
2670 {
2671 return path;
2672 }
2673
2674 public void setLocal(boolean isLocal)
2675 {
2676 this._isLocal = isLocal;
2677 }
2678
2679 public boolean isLocal()
2680 {
2681 return _isLocal;
2682 }
2683
2684 public String getExportFileTagValue()
2685 {
2686 return getAnnotationValue("file");
2687 }
2688
2689 public void assertEquals(Frame frame2)
2690 {
2691 // Check that all the items on the frame are the same
2692 List<Item> items1 = getVisibleItems();
2693 List<Item> items2 = frame2.getVisibleItems();
2694
2695 if (items1.size() != items2.size()) {
2696 throw new UnitTestFailedException(items1.size() + " items", items2.size() + " items");
2697 } else {
2698 for (int i = 0; i < items1.size(); i++) {
2699 Item i1 = items1.get(i);
2700 Item i2 = items2.get(i);
2701 String s1 = i1.getText();
2702 String s2 = i2.getText();
2703 if (!s1.equals(s2)) {
2704 throw new UnitTestFailedException(s1, s2);
2705 }
2706 }
2707 }
2708 }
2709
2710 public boolean hasObservers()
2711 {
2712 return _observers != null && _observers.size() > 0;
2713 }
2714
2715 public List<Item> getBodyItemsWithInsufficientPermissions() {
2716 return _bodyHiddenDueToPermissions;
2717 }
2718
2719 public void moveItemToBodyHiddenDueToPermission(final Item i) {
2720 _body.remove(i);
2721 _bodyHiddenDueToPermissions.add(i);
2722 }
2723
2724 public void moveItemFromBodyHiddenDueToPermission(Item i, PermissionPair newPermission) {
2725 if (_bodyHiddenDueToPermissions.contains(i)) {
2726 _bodyHiddenDueToPermissions.remove(i);
2727 i.setPermission(newPermission);
2728 _body.add(i);
2729 }
2730 }
2731
2732 public Collection<? extends Item> getInteractableItems() {
2733 /*
2734 * TODO: Cache the interactableItems list so we dont have to recreate it
2735 * every time this method is called
2736 */
2737 if (_interactableItems.size() > 0) {
2738 return _interactableItems;
2739 }
2740
2741 for (Item i : _body) {
2742 if (i == null) {
2743 continue;
2744 }
2745 if (i.isVisible()) {
2746 _interactableItems.add(i);
2747 }
2748 }
2749
2750 for (Item i : _overlayItems) {
2751 if (i.hasPermission(UserAppliedPermission.followLinks)) {
2752 _interactableItems.add(i);
2753 }
2754 }
2755
2756 for (Item i : _vectorItems) {
2757 if (i.hasPermission(UserAppliedPermission.none)) {
2758 _interactableItems.add(i);
2759 }
2760 }
2761
2762 return _interactableItems;
2763 }
2764
2765 public String getEncryptionLabel() {
2766 return _encryptionLabel;
2767 }
2768
2769 public void setEncryptionLabel(String label) {
2770 _encryptionLabel = label;
2771 }
2772
2773 private static final class History {
2774
2775 public enum Type {
2776 deletion,
2777 movement
2778 }
2779
2780 public final List<Item> items;
2781
2782 public final Type type;
2783
2784 public History(Collection<Item> items, Type type)
2785 {
2786 this.items = new LinkedList<Item>(items);
2787 this.type = type;
2788 }
2789
2790 @Override
2791 public String toString()
2792 {
2793 return this.type.toString() + ":\n" + this.items.toString();
2794 }
2795 }
2796}
Note: See TracBrowser for help on using the repository browser.