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

Last change on this file was 1531, checked in by bnemhaus, 4 years ago

When looking for templates, now look at all interactable items rather than just items on the frame. In other words, now includes @ItemTemplate from active overlays

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