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

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

When a loaded frame has an encyption label in its data, a padlock is prepended to the frame name item. Fancy.

File size: 65.0 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 Item frameData = new Text("Frame Data");
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.getData();
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 frameData.addToData(dataItem);
1877 }
1878
1879 /**
1880 * Returns the path (String) to the .exp file that this Frame represents.
1881 * This follows redirects, meaning that it provides the actual file from which
1882 * the frames data is drawn from.
1883 * @return The path to the .exp file that this Frame represents
1884 * @see getFramePathLogical
1885 * @see getFramesetPath
1886 */
1887 public String getFramePathReal() {
1888 String framesetPath = getFramesetPath();
1889 String redirect = ExpReader.redirectTo(getFramePathLogical());
1890
1891 if (redirect == null) {
1892 return getFramePathLogical();
1893 }
1894
1895 while (redirect != null) {
1896 framesetPath = getFramesetPath() + redirect;
1897 redirect = ExpReader.redirectTo(redirect);
1898 }
1899 return framesetPath;
1900 }
1901
1902 /**
1903 * Returns the path (String) to the .exp file that this Frame represents.
1904 * Does not follow redirects, opting to instead provide the logical path to this file.
1905 * @return The path to the .exp file that this Frame represents
1906 * @see getFramePathReal
1907 * @see getFramesetPath
1908 */
1909 public String getFramePathLogical() {
1910 return getFramesetPath() + this.getNumber() + ExpReader.EXTENTION;
1911 }
1912
1913 /**
1914 * Returns the path (String) to the frameset directory that the file that this Frame represents is contained within.
1915 * @return The path to this Frames frameset directory
1916 * @see getFramesetPathLogical
1917 * @see getFramesetPathReal
1918 */
1919 public String getFramesetPath() {
1920 return this.getPath() + File.separator + this.getFramesetName() + File.separator;
1921 }
1922
1923 public Item createDot() {
1924 Item dot = new Dot(DisplayController.getMouseX(), DisplayController.getMouseY(), getNextItemID());
1925
1926 Item template = getTemplate(_dotTemplate, ItemUtils.TAG_DOT_TEMPLATE);
1927 float thickness = template.getThickness();
1928 if (thickness > 0) {
1929 dot.setThickness(template.getThickness());
1930 }
1931 if (template.getLinePattern() != null) {
1932 dot.setLinePattern(template.getLinePattern());
1933 }
1934 dot.setColor(template.getColor());
1935 dot.setFillColor(template.getFillColor());
1936 // reset attributes
1937 dot.setParent(this);
1938 return dot;
1939 }
1940
1941 private Text getTemplate(Text defaultTemplate, int templateTag)
1942 {
1943 Text t = null;
1944
1945 // check for an updated template...
1946 for (Item i : this.getItems()) {
1947 if (ItemUtils.startsWithTag(i, templateTag)) {
1948 t = (Text) i;
1949 break;
1950 }
1951 }
1952
1953 if (t == null) {
1954 if (defaultTemplate == null) {
1955 return null;
1956 }
1957
1958 t = defaultTemplate;
1959 }
1960
1961 // If the item is linked apply any attribute pairs on the child frame
1962 String link = t.getAbsoluteLink();
1963
1964 // need to get link first because copy doesnt copy the link
1965 t = t.copy();
1966 t.setTooltip(null);
1967 if (link != null) {
1968 t.setLink(null);
1969 Frame childFrame = FrameIO.LoadFrame(link);
1970 if (childFrame != null) {
1971 // read in attribute value pairs
1972 for (Text attribute : childFrame.getBodyTextItems(false)) {
1973 AttributeUtils.setAttribute(t, attribute);
1974 }
1975 }
1976 }
1977 return t;
1978 }
1979
1980 /**
1981 * TODO: Comment. cts16
1982 * TODO: Remove magic constants. cts16
1983 */
1984 public Text getItemTemplate(char firstChar)
1985 {
1986 switch (firstChar) {
1987 case '@':
1988 return getAnnotationTemplate();
1989 case '/':
1990 case '#':
1991 return getCodeCommentTemplate();
1992 default:
1993 return getItemTemplate();
1994 }
1995 }
1996
1997 public Text createNewText()
1998 {
1999 return createNewText("");
2000 }
2001
2002 public Text addText(int x, int y, String text, String action)
2003 {
2004 Text t = createNewText(text);
2005 t.setPosition(x, y);
2006 t.addAction(action);
2007 return t;
2008 }
2009
2010 public Text addText(int x, int y, String text, String action, String link)
2011 {
2012 Text t = addText(x, y, text, action);
2013 t.setLink(link);
2014 return t;
2015 }
2016
2017 public Dot addDot(int x, int y)
2018 {
2019 Dot d = new Dot(x, y, getNextItemID());
2020 addItem(d);
2021 return d;
2022 }
2023
2024 /**
2025 * Adds a rectangle to the frame
2026 *
2027 * @param x
2028 * X coordinate of the top-left corner of the rectangle
2029 * @param y
2030 * Y coordinate of the top-left corner of the rectangle
2031 * @param width
2032 * Width of the rectangle
2033 * @param height
2034 * Height of the rectangle
2035 * @param borderThickness
2036 * Thickness, in pixels, of the rectangle's border/outline
2037 * @param borderColor
2038 * Color of the rectangle's border/outline
2039 * @param fillColor
2040 * Color to fill the rectangle with
2041 */
2042 public List<Item> addRectangle(int x, int y, int width, int height, float borderThickness, Colour borderColor, Colour fillColor)
2043 {
2044 List<Item> rectComponents = new ArrayList<Item>();
2045 Item[] corners = new Item[4];
2046
2047 // Top Left
2048 corners[0] = this.createDot();
2049 corners[0].setPosition(x, y);
2050
2051 // Top Right
2052 corners[1] = this.createDot();
2053 corners[1].setPosition(x + width, y);
2054
2055 // Bottom Right
2056 corners[2] = this.createDot();
2057 corners[2].setPosition(x + width, y + height);
2058
2059 // Bottom Left
2060 corners[3] = this.createDot();
2061 corners[3].setPosition(x, y + height);
2062
2063 // Add corners to the collection and setting their attributes
2064 for (int i = 0; i < corners.length; i++) {
2065 corners[i].setThickness(borderThickness);
2066 corners[i].setColor(borderColor);
2067 corners[i].setFillColor(fillColor);
2068 rectComponents.add(corners[i]);
2069 }
2070
2071 // create lines between the corners
2072 rectComponents.add(new Line(corners[0], corners[1], this.getNextItemID()));
2073 rectComponents.add(new Line(corners[1], corners[2], this.getNextItemID()));
2074 rectComponents.add(new Line(corners[2], corners[3], this.getNextItemID()));
2075 rectComponents.add(new Line(corners[3], corners[0], this.getNextItemID()));
2076
2077 // Add constraints between each corner
2078 new Constraint(corners[0], corners[1], this.getNextItemID(), Constraint.HORIZONTAL);
2079 new Constraint(corners[2], corners[3], this.getNextItemID(), Constraint.HORIZONTAL);
2080 new Constraint(corners[1], corners[2], this.getNextItemID(), Constraint.VERTICAL);
2081 new Constraint(corners[3], corners[0], this.getNextItemID(), Constraint.VERTICAL);
2082
2083 List<Item> rect = new ArrayList<Item>(rectComponents);
2084 this.addAllItems(rectComponents);
2085 StandardGestureActions.anchor(rectComponents);
2086 return rect;
2087 }
2088
2089 public boolean isSaved()
2090 {
2091 return _saved;
2092 }
2093
2094 public void setSaved()
2095 {
2096 _saved = true;
2097 _change = false;
2098 }
2099
2100 public static boolean rubberbandingLine()
2101 {
2102 return FreeItems.getInstance().size() == 2 &&
2103 (FreeItems.getInstance().get(0) instanceof Line || FreeItems.getInstance().get(1) instanceof Line);
2104 }
2105
2106 /**
2107 * Tests if an item is a non title, non frame name, non special annotation
2108 * text item.
2109 *
2110 * @param it
2111 * the item to be tested
2112 * @return true if the item is a normal text item
2113 */
2114 public boolean isNormalTextItem(Item it)
2115 {
2116 if (it instanceof Text && it != getTitleItem() && it != _frameName && !((Text) it).isSpecialAnnotation()) {
2117 return true;
2118 }
2119
2120 return false;
2121 }
2122
2123 /**
2124 * Moves the mouse to the end of the text item with a specified index.
2125 *
2126 * @param index
2127 */
2128 public boolean moveMouseToTextItem(int index)
2129 {
2130 List<Item> items = getItems();
2131 int itemsFound = 0;
2132 for (int i = 0; i < items.size(); i++) {
2133 Item it = items.get(i);
2134 if (isNormalTextItem(it)) {
2135 itemsFound++;
2136 }
2137 if (itemsFound > index) {
2138 DisplayController.setCursorPosition(((Text) it).getParagraphEndPosition().getX(), it.getY());
2139 DisplayController.resetCursorOffset();
2140 DisplayController.requestRefresh(true);
2141 return true;
2142 }
2143 }
2144
2145 return false;
2146 }
2147
2148 /**
2149 * Searches for an annotation item called start to be used as the default
2150 * cursor location when TDFC occurs.
2151 *
2152 * TODO: Remove magic constants. cts16
2153 */
2154 public boolean moveMouseToDefaultLocation()
2155 {
2156 List<Item> items = getItems();
2157
2158 for (Item it : items) {
2159 if (it instanceof Text) {
2160 Text t = (Text) it;
2161 if (t.getText().toLowerCase().startsWith("@start") || t.getText().toLowerCase().equals("@start:")) {
2162 // Used to allow users the option of putting an initial
2163 // bullet after the @start
2164 // This was replaced by width
2165 // t.stripFirstWord();
2166 t.setText("");
2167
2168 if (t.getText().equals("")) {
2169 DisplayController.getCurrentFrame().removeItem(t);
2170 }
2171
2172 if (!FreeItems.hasItemsAttachedToCursor()) {
2173 DisplayController.setCursorPosition(((Text) it).getParagraphEndPosition());
2174 DisplayController.resetCursorOffset();
2175 }
2176
2177 DisplayController.requestRefresh(true);
2178
2179 return true;
2180 }
2181 }
2182 }
2183
2184 return false;
2185 }
2186
2187 /**
2188 * Gets the file name that actions should use to export files created by
2189 * running actions from this frame.
2190 *
2191 * @return the fileName if the frame contains an '@file' tag. Returns the
2192 * name of the frame if the tag isnt on the frame.
2193 */
2194 public String getExportFileName()
2195 {
2196 String fileName = getExportFileTagValue();
2197
2198 if (fileName == null) {
2199 fileName = getTitle();
2200
2201 if (fileName == null) {
2202 fileName = getName();
2203 }
2204 }
2205
2206 return fileName;
2207 }
2208
2209 public void toggleBackgroundColor()
2210 {
2211 setBackgroundColor(ColorUtils.getNextColor(_background, TemplateSettings.BackgroundColorWheel.get(), null));
2212 }
2213
2214 public void setName(String frameset, int i)
2215 {
2216 setFrameset(frameset);
2217 setFrameNumber(i);
2218 }
2219
2220 /**
2221 * Sets the item permissions to match the protection for the frame.
2222 * No longer sets item permissions, since items can have their own permissions now (but still default to frame permissions)
2223 *
2224 */
2225 public void refreshItemPermissions(UserAppliedPermission maxPermission)
2226 {
2227 if(_frameName == null) {
2228 return;
2229 }
2230
2231 UserAppliedPermission permission = UserAppliedPermission.min(maxPermission, getUserAppliedPermission());
2232
2233 switch (permission) {
2234 case none:
2235 _frameName.setBackgroundColor(FRAME_NAME_BACKGROUND_COLOUR_FOR_PERMISSION_NONE);
2236 break;
2237 case followLinks:
2238 _frameName.setBackgroundColor(FRAME_NAME_BACKGROUND_COLOUR_FOR_PERMISSION_FOLLOW_LINKS);
2239 break;
2240 case copy:
2241 _frameName.setBackgroundColor(FRAME_NAME_BACKGROUND_COLOUR_FOR_PERMISSION_COPY);
2242 break;
2243 case createFrames:
2244 _frameName.setBackgroundColor(FRAME_NAME_BACKGROUND_COLOUR_FOR_PERMISSION_CREATE_FRAMES);
2245 break;
2246 case full:
2247 _frameName.setBackgroundColor(FRAME_NAME_BACKGROUND_COLOUR_FOR_PERMISSION_FULL);
2248 break;
2249 default:
2250 assert (false);
2251 break;
2252 }
2253
2254 for (Overlay o : getOverlays()) {
2255 for(Item i : o.Frame._body) {
2256 i.setOverlayPermission(o.permission);
2257 }
2258 o.Frame.refreshItemPermissions(o.permission);
2259 }
2260 }
2261
2262 public boolean isTestFrame()
2263 {
2264 Text title = getTitleItem();
2265 if (title == null) {
2266 return false;
2267 }
2268 String action = title.getFirstAction();
2269 if (action == null) {
2270 return false;
2271 }
2272 action = action.toLowerCase();
2273 return action.startsWith(Simple.RUN_FRAME_ACTION) || action.startsWith(Simple.DEBUG_FRAME_ACTION);
2274 }
2275
2276 public void setActiveTime(String activeTime)
2277 {
2278 try {
2279 _activeTime = new Time(Time.valueOf(activeTime).getTime() + 12 * 60 * 60 * 1000);
2280 } catch (Exception e) {
2281 _activeTime = new Time(0);
2282 }
2283 }
2284
2285 public void setActiveTime(Time activeTime)
2286 {
2287 _activeTime = activeTime;
2288 }
2289
2290 public void setDarkTime(Time darkTime)
2291 {
2292 _darkTime = darkTime;
2293 }
2294
2295 public void setDarkTime(String darkTime)
2296 {
2297 try {
2298 _darkTime = new Time(Time.valueOf(darkTime).getTime() + 12 * 60 * 60 * 1000);
2299 } catch (Exception e) {
2300 _darkTime = new Time(0);
2301 }
2302 }
2303
2304 /**
2305 * Returns null if their is no backup frame or if it is invalid.
2306 *
2307 * @return the backup frame for this frame
2308 */
2309 public Frame getBackupFrame()
2310 {
2311 Text backupTag = _annotations.get("old");
2312 if (backupTag == null) {
2313 return null;
2314 }
2315
2316 // TODO want another way to deal with updating of annotations items
2317 // without F12 refresh
2318 // Reparse the frame if annotation item has been modified
2319 String[] processedText = backupTag.getProcessedText();
2320 if (processedText == null) {
2321 // Reparse the frame if this item has not yet been parsed
2322 FrameUtils.Parse(this);
2323 return getBackupFrame();
2324 }
2325
2326 // Now return the name of the backed up frame
2327 String link = backupTag.getAbsoluteLink();
2328 if (link == null || link.equalsIgnoreCase(getName())) {
2329 return null;
2330 }
2331
2332 Frame backup = FrameIO.LoadFrame(link);
2333 return backup;
2334 }
2335
2336 public Time getDarkTime()
2337 {
2338 return _darkTime;
2339 }
2340
2341 public Time getActiveTime()
2342 {
2343 return _activeTime;
2344 }
2345
2346 /**
2347 * Gets the number of backed up versions of this frame are saved plus 1 for
2348 * this frame.
2349 *
2350 * @return the number of frames in the backed up comet
2351 */
2352 public int getCometLength()
2353 {
2354 Frame backup = getBackupFrame();
2355 return 1 + (backup == null ? 0 : backup.getCometLength());
2356 }
2357
2358 public void addAnnotation(Text item)
2359 {
2360 if (_annotations == null) {
2361 _annotations = new HashMap<String, Text>();
2362 }
2363
2364 // Check if this item has already been processed
2365 String[] tokens = item.getProcessedText();
2366 if (tokens != null) {
2367 if (tokens.length > 0) {
2368 _annotations.put(tokens[0], item);
2369 }
2370 return;
2371 }
2372
2373 String text = item.getText().trim();
2374 assert (text.charAt(0) == '@');
2375
2376 // Ignore annotations with spaces after the tag symbol
2377 if (text.length() < 2 || !Character.isLetter(text.charAt(1))) {
2378 item.setProcessedText(new String[0]);
2379 return;
2380 }
2381
2382 // The separator char must come before the first non letter otherwise we
2383 // ignore the annotation item
2384 for (int i = 2; i < text.length(); i++) {
2385 char ch = text.charAt(i);
2386 if (!Character.isLetterOrDigit(ch)) {
2387 // Must have an attribute value pair
2388 if (ch == AttributeValuePair.SEPARATOR_CHAR) {
2389 // Get the attribute
2390 String attribute = text.substring(1, i).toLowerCase();
2391 String value = "";
2392 if (text.length() > 1 + i) {
2393 value = text.substring(i + 1).trim();
2394 }
2395 item.setProcessedText(new String[] { attribute, value });
2396 _annotations.put(attribute, item);
2397 return;
2398 } else {
2399 item.setProcessedText(new String[0]);
2400 return;
2401 }
2402 }
2403 }
2404
2405 // If it was nothing but letters and digits save the tag
2406 String lowerCaseText = text.substring(1).toLowerCase();
2407 item.setProcessedText(new String[] { lowerCaseText });
2408 _annotations.put(lowerCaseText, item);
2409 }
2410
2411 public boolean hasAnnotation(String annotation)
2412 {
2413 if (_annotations == null) {
2414 refreshAnnotationList();
2415 }
2416
2417 return _annotations.containsKey(annotation.toLowerCase());
2418 }
2419
2420 /**
2421 * Returns the annotation value in full case.
2422 *
2423 * @param annotation
2424 * the annotation to retrieve the value of.
2425 * @return the annotation item value in full case or null if the annotation
2426 * is not on the frame or has no value.
2427 */
2428 public String getAnnotationValue(String annotation)
2429 {
2430 if (_annotations == null) {
2431 refreshAnnotationList();
2432 }
2433
2434 Text text = _annotations.get(annotation.toLowerCase());
2435 if (text == null) {
2436 return null;
2437 }
2438
2439 String[] tokens = text.getProcessedText();
2440
2441 if (tokens != null && tokens.length > 1) {
2442 return tokens[1];
2443 }
2444
2445 return null;
2446 }
2447
2448 public void clearAnnotations()
2449 {
2450 _annotations = null;
2451 }
2452
2453 public List<Item> getVisibleItems()
2454 {
2455 return getItems(true);
2456 }
2457
2458 private void refreshAnnotationList()
2459 {
2460 if (_annotations == null) {
2461 _annotations = new HashMap<String, Text>();
2462 } else {
2463 _annotations.clear();
2464 }
2465
2466 for (Text text : getTextItems()) {
2467 if (text.isAnnotation()) {
2468 addAnnotation(text);
2469 }
2470 }
2471 }
2472
2473 public Collection<Text> getAnnotationItems()
2474 {
2475 if (_annotations == null) {
2476 refreshAnnotationList();
2477 }
2478
2479 return _annotations.values();
2480 }
2481
2482 /**
2483 * Gets a list of items to be saved to file by text file writers.
2484 *
2485 * @return the list of items to be saved to a text file
2486 */
2487 public List<Item> getItemsToSave()
2488 {
2489 if (!_sorted) {
2490 Collections.sort(_body);
2491 _sorted = true;
2492 }
2493
2494 // iWidgets are handled specially since 8 items are written as one
2495 Collection<Widget> seenWidgets = new LinkedHashSet<Widget>();
2496
2497 List<Item> toSave = new ArrayList<Item>();
2498
2499 for (Item i : _body) {
2500 if (i == null || i.dontSave()) {
2501 continue;
2502 }
2503
2504 // Ensure only one of the WidgetCorners represent a single widget
2505 if (i instanceof WidgetCorner) {
2506 Widget iw = ((WidgetCorner) i).getWidgetSource();
2507 if (seenWidgets.contains(iw)) {
2508 continue;
2509 }
2510 seenWidgets.add(iw);
2511 toSave.add(iw.getSource());
2512 } else if (i instanceof XRayable) {
2513 XRayable x = (XRayable) i;
2514 toSave.addAll(x.getItemsToSave());
2515 // Circle centers are items with attached enclosures
2516 } else if (i.hasEnclosures()) {
2517 continue;
2518 } else {
2519 toSave.add(i);
2520 }
2521 }
2522
2523 for (Vector v : getVectors()) {
2524 toSave.add(v.Source);
2525 }
2526
2527 return toSave;
2528 }
2529
2530 public Collection<Item> getOverlayItems()
2531 {
2532 return _overlayItems;
2533 }
2534
2535 /**
2536 * Returns true if this frame has and overlays for the specified frame.
2537 *
2538 * @param frame
2539 * @return
2540 */
2541 public boolean hasOverlay(Frame frame)
2542 {
2543 return _overlays.containsValue(frame);
2544 }
2545
2546 public Collection<Item> getAllItems()
2547 {
2548 Collection<Item> allItems = new LinkedHashSet<Item>(_body);
2549
2550 allItems.addAll(_overlayItems);
2551 allItems.addAll(_vectorItems);
2552 return allItems;
2553 }
2554
2555 public Collection<Item> getVectorItems()
2556 {
2557 Collection<Item> vectorItems = new LinkedHashSet<Item>(_vectorItems);
2558 vectorItems.addAll(getNonAnnotationItems(false));
2559 return vectorItems;
2560 }
2561
2562 /**
2563 * Gets a list of all the text items on the frame.
2564 *
2565 * @return
2566 */
2567 public Collection<Text> getTextItems()
2568 {
2569 Collection<Text> textItems = new ArrayList<Text>();
2570
2571 for (Item i : getItems(true)) {
2572 // only add up normal body text items
2573 if ((i instanceof Text)) {
2574 textItems.add((Text) i);
2575 }
2576 }
2577
2578 return textItems;
2579 }
2580
2581 public Text getAnnotation(String annotation)
2582 {
2583 if (_annotations == null) {
2584 refreshAnnotationList();
2585 }
2586
2587 return _annotations.get(annotation.toLowerCase());
2588 }
2589
2590 public void recalculate()
2591 {
2592 for (Item i : getItems()) {
2593 if (i.hasFormula() && !i.isAnnotation()) {
2594 i.calculate(i.getFormula());
2595 }
2596 }
2597 }
2598
2599 public void removeObserver(FrameObserver observer)
2600 {
2601 _observers.remove(observer);
2602 }
2603
2604 public void addObserver(FrameObserver observer)
2605 {
2606 _observers.add(observer);
2607 }
2608
2609 public void clearObservers()
2610 {
2611 for (FrameObserver fl : _observers) {
2612 fl.removeSubject(this);
2613 }
2614
2615 // The frame listener will call the frames removeListener method
2616 assert (_observers.size() == 0);
2617 }
2618
2619 public Collection<Text> getNonAnnotationText(boolean removeTitle)
2620 {
2621 Collection<Text> items = new LinkedHashSet<Text>();
2622
2623 for (Item i : getItems(true)) {
2624 // only add up normal body text items
2625 if (i instanceof Text && !i.isAnnotation()) {
2626 items.add((Text) i);
2627 }
2628 }
2629
2630 if (removeTitle) {
2631 items.remove(getTitleItem());
2632 }
2633
2634 return items;
2635 }
2636
2637 public void dispose()
2638 {
2639 clearObservers();
2640
2641 for (Item i : _body) {
2642 i.dispose();
2643 }
2644
2645 _frameName.dispose();
2646 _body = null;
2647 _frameName = null;
2648 }
2649
2650 public void parse()
2651 {
2652 for (Overlay o : getOverlays()) {
2653 o.Frame.parse();
2654 }
2655
2656 // Must parse the frame AFTER the overlays
2657 FrameUtils.Parse(this);
2658 }
2659
2660 public void setPath(String path)
2661 {
2662 this.path = path;
2663 }
2664
2665 public String getPath()
2666 {
2667 return path;
2668 }
2669
2670 public void setLocal(boolean isLocal)
2671 {
2672 this._isLocal = isLocal;
2673 }
2674
2675 public boolean isLocal()
2676 {
2677 return _isLocal;
2678 }
2679
2680 public String getExportFileTagValue()
2681 {
2682 return getAnnotationValue("file");
2683 }
2684
2685 public void assertEquals(Frame frame2)
2686 {
2687 // Check that all the items on the frame are the same
2688 List<Item> items1 = getVisibleItems();
2689 List<Item> items2 = frame2.getVisibleItems();
2690
2691 if (items1.size() != items2.size()) {
2692 throw new UnitTestFailedException(items1.size() + " items", items2.size() + " items");
2693 } else {
2694 for (int i = 0; i < items1.size(); i++) {
2695 Item i1 = items1.get(i);
2696 Item i2 = items2.get(i);
2697 String s1 = i1.getText();
2698 String s2 = i2.getText();
2699 if (!s1.equals(s2)) {
2700 throw new UnitTestFailedException(s1, s2);
2701 }
2702 }
2703 }
2704 }
2705
2706 public boolean hasObservers()
2707 {
2708 return _observers != null && _observers.size() > 0;
2709 }
2710
2711 public List<Item> getBodyItemsWithInsufficientPermissions() {
2712 return _bodyHiddenDueToPermissions;
2713 }
2714
2715 public void moveItemToBodyHiddenDueToPermission(final Item i) {
2716 _body.remove(i);
2717 _bodyHiddenDueToPermissions.add(i);
2718 }
2719
2720 public void moveItemFromBodyHiddenDueToPermission(Item i, PermissionPair newPermission) {
2721 if (_bodyHiddenDueToPermissions.contains(i)) {
2722 _bodyHiddenDueToPermissions.remove(i);
2723 i.setPermission(newPermission);
2724 _body.add(i);
2725 }
2726 }
2727
2728 public Collection<? extends Item> getInteractableItems() {
2729 /*
2730 * TODO: Cache the interactableItems list so we dont have to recreate it
2731 * every time this method is called
2732 */
2733 if (_interactableItems.size() > 0) {
2734 return _interactableItems;
2735 }
2736
2737 for (Item i : _body) {
2738 if (i == null) {
2739 continue;
2740 }
2741 if (i.isVisible()) {
2742 _interactableItems.add(i);
2743 }
2744 }
2745
2746 for (Item i : _overlayItems) {
2747 if (i.hasPermission(UserAppliedPermission.followLinks)) {
2748 _interactableItems.add(i);
2749 }
2750 }
2751
2752 for (Item i : _vectorItems) {
2753 if (i.hasPermission(UserAppliedPermission.none)) {
2754 _interactableItems.add(i);
2755 }
2756 }
2757
2758 return _interactableItems;
2759 }
2760
2761 public String getEncryptionLabel() {
2762 return _encryptionLabel;
2763 }
2764
2765 public void setEncryptionLabel(String label) {
2766 _encryptionLabel = label;
2767 }
2768
2769 private static final class History {
2770
2771 public enum Type {
2772 deletion,
2773 movement
2774 }
2775
2776 public final List<Item> items;
2777
2778 public final Type type;
2779
2780 public History(Collection<Item> items, Type type)
2781 {
2782 this.items = new LinkedList<Item>(items);
2783 this.type = type;
2784 }
2785
2786 @Override
2787 public String toString()
2788 {
2789 return this.type.toString() + ":\n" + this.items.toString();
2790 }
2791 }
2792}
Note: See TracBrowser for help on using the repository browser.