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

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

Frame::Parse has been updated to include a new boolean parameter. When true, widgets that are created as a result of the parse send not only notify the widget framework that they have been added, but are also visible. When false, they only notify they have been added.

The widget framework now distinguishes between added and visible widgets, this fixes a bug. Bug: when programmatically adding a widget to not the current frame, it never gets properly removed and therefore still catches click events from users. By distinguishing between adding and making visible this is avoided.


Another bug has been fixed. Bug: When setting a text item to have a right anchor, and then subsequently reducing the size of the window, this text item would get a width of zero assigned. This was caused by some issues with the logic of how right margins for items were calculated.

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