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

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

Implemented surrogates for images. When you add an encryption label to a picture, the default is a on-the-fly generated image of noise. This generated image has the same size specifications as the primary image.

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