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

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

Implemented EncryptionPermission attribute.

EncryptionPermission is a triple. When specifying the value, if you do not specify the 2nd (group) or 3rd (other) then they default to permission level zero.

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