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

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

New Attributes (and repurposed old ones) to be used for encryption of frames:

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