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

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

You now have the ability to anchor Items to the center of the frame. AnchorCenterX: 0 will anchor a item directly to the vertical center of the frame. AnchorCenterY: 0 to the horizontal center of the frame. Negative numbers go left/up from the center, positive numbers right/down.

More work to come to deal with connected items such as rectangles made up on connected dots.

File size: 82.5 KB
Line 
1/**
2 * Frame.java
3 * Copyright (C) 2010 New Zealand Digital Library, http://expeditee.org
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19package org.expeditee.gui;
20
21import java.io.File;
22import java.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 anchorCenterX = i.getAnchorCenterX();
1007 Integer anchorRight = i.getAnchorRight();
1008 Integer anchorTop = i.getAnchorTop();
1009 Integer anchorCenterY = i.getAnchorCenterY();
1010 Integer anchorBottom = i.getAnchorBottom();
1011
1012
1013 if (anchorLeft != null) {
1014 i.setAnchorLeft(anchorLeft);
1015 if (i.hasVector()) {
1016 bReparse = true;
1017 }
1018 }
1019
1020 if (anchorCenterX != null) {
1021 i.setAnchorCenterX(anchorCenterX);
1022 if (i.hasVector()) {
1023 bReparse = true;
1024 }
1025 }
1026
1027 if (anchorRight != null) {
1028 i.setAnchorRight(anchorRight);
1029 if (i.hasVector()) {
1030 bReparse = true;
1031 }
1032 }
1033
1034 if (anchorTop != null) {
1035 i.setAnchorTop(anchorTop);
1036 if (i.hasVector()) {
1037 bReparse = true;
1038 }
1039 }
1040
1041 if (anchorCenterY != null) {
1042 i.setAnchorCenterY(anchorCenterY);
1043 if (i.hasVector()) {
1044 bReparse = true;
1045 }
1046 }
1047
1048 if (anchorBottom != null) {
1049 i.setAnchorBottom(anchorBottom);
1050 if (i.hasVector()) {
1051 bReparse = true;
1052 }
1053 }
1054 }
1055
1056 // Do the anchors on the overlays
1057 for (Overlay o : getOverlays()) {
1058 o.Frame.refreshSize();
1059 }
1060
1061 if (bReparse) {
1062 FrameUtils.Parse(this, false);
1063 }
1064
1065 _frameName.resetFrameNamePosition();
1066 }
1067
1068 public void addAllItems(Collection<Item> toAdd) {
1069 addAllItems(toAdd, getBody(false));
1070 addAllItems(toAdd, getPrimaryBody());
1071 }
1072
1073 protected void addAllItems(Collection<Item> toAdd, ItemsList list) {
1074 for (Item i : toAdd) {
1075 // If an annotation is being deleted clear the annotation list
1076 if (i.isAnnotation()) {
1077 i.getParentOrCurrentFrame().clearAnnotations();
1078 }
1079 // TODO Improve efficiency when addAll is called
1080 addItem(i, true, list);
1081 }
1082 }
1083
1084 public void removeAllItems(Collection<Item> toRemove) {
1085 for (Item i : toRemove) {
1086 // If an annotation is being deleted clear the annotation list
1087 if (i.isAnnotation()) {
1088 i.getParentOrCurrentFrame().clearAnnotations();
1089 }
1090 removeItem(i);
1091 }
1092 }
1093
1094 public void removeItem(Item item) {
1095 removeItem(item, true);
1096 }
1097
1098 public void removeItem(Item item, boolean recalculate) {
1099 removeItem(item, recalculate, getBody(false));
1100 if (item.isSurrogate()) {
1101 removeItem(item, recalculate, getSurrogateBody());
1102 Set<Item> primariesSurrogates = item.getPrimary().getSurrogates();
1103 primariesSurrogates.remove(item);
1104 } else {
1105 removeItem(item, recalculate, getPrimaryBody());
1106 }
1107 }
1108
1109 protected void removeItem(Item item, boolean recalculate, ItemsList toRemoveFrom) {
1110 // If an annotation is being deleted clear the annotation list
1111 if (item.isAnnotation()) {
1112 item.getParentOrCurrentFrame().clearAnnotations();
1113 }
1114
1115 if (toRemoveFrom.remove(item)) {
1116 change();
1117 // Remove widgets from the widget list
1118 if (item != null) {
1119 item.onParentStateChanged(new ItemParentStateChangedEvent(this, ItemParentStateChangedEvent.EVENT_TYPE_REMOVED));
1120
1121 if (item instanceof WidgetCorner) {
1122 _iWidgets.remove(((WidgetCorner) item).getWidgetSource());
1123 }
1124
1125 item.invalidateCommonTrait(ItemAppearence.Removed);
1126 }
1127 // TODO Improve efficiency when removeAll is called
1128 // if (recalculate && item.recalculateWhenChanged())
1129 // recalculate();
1130 }
1131 }
1132
1133 /**
1134 * Adds the given History event to the stack.
1135 * @param items The items to put in the event
1136 * @param type The type of event that occurred
1137 * @param undoDeleteAssociatedFiles TODO
1138 * @param stack The stack to add to
1139 */
1140 private void addToUndo(ItemsList items, History.Type type, boolean undoDeleteAssociatedFiles) {
1141 if (items.size() < 1) {
1142 return;
1143 }
1144
1145 _undo.push(new History(items, type, undoDeleteAssociatedFiles));
1146 }
1147
1148 public void addToUndoDelete(ItemsList items, boolean undoDeleteAssociatedFiles) {
1149 addToUndo(items, History.Type.deletion, undoDeleteAssociatedFiles);
1150 }
1151
1152 public void addToUndoMove(ItemsList items) {
1153 addToUndo(items, History.Type.movement, false);
1154 }
1155
1156 public void undo() {
1157 boolean reparse = false;
1158 boolean recalculate = false;
1159
1160 if (_undo.size() <= 0) {
1161 return;
1162 }
1163
1164 History undo = _undo.pop();
1165
1166 // System.out.println("Undoing: " + undo);
1167
1168 switch(undo.type) {
1169 case deletion:
1170 _redo.push(undo);
1171 for(Item i : undo.items) {
1172 if (i instanceof org.expeditee.items.Picture && undo.undoDeleteAssociatedFiles) {
1173 String destination = ((Picture) i).getPath();
1174 Path destinationPath = Paths.get(destination);
1175 Path sourcePath = Paths.get(FrameIO.TRASH_PATH).resolve(destinationPath.getFileName());
1176 try {
1177 Files.move(sourcePath, destinationPath, StandardCopyOption.ATOMIC_MOVE);
1178 } catch (IOException e) {
1179 MessageBay.displayMessage("Unable to restore image file from trash, not undoing deletion of image.");
1180 continue;
1181 }
1182 }
1183 this.addItem(i);
1184 reparse |= i.hasOverlay();
1185 recalculate |= i.recalculateWhenChanged();
1186 if (i instanceof Line) {
1187 Line line = (Line) i;
1188 line.getStartItem().addLine(line);
1189 line.getEndItem().addLine(line);
1190 } else {
1191 i.setOffset(0, 0);
1192 }
1193 }
1194 break;
1195 case movement:
1196 ItemsList body = getBody(true);
1197 ItemsList changed = new ItemsList(body);
1198 changed.retainAll(undo.items);
1199 _redo.push(new History(changed, History.Type.movement, false));
1200 for(Item i : undo.items) {
1201 int index;
1202 if(i.isVisible() && (index = body.indexOf(i)) != -1) {
1203 body.set(index, i);
1204 }
1205 }
1206 break;
1207 }
1208
1209 change();
1210
1211 StandardGestureActions.refreshHighlights();
1212
1213 if (reparse) {
1214 FrameUtils.Parse(this, false, false, false);
1215 } else {
1216 notifyObservers(recalculate);
1217 }
1218
1219 // always request a refresh otherwise filled shapes
1220 // that were broken by a deletion and then reconnected by the undo
1221 // don't get filled until the user otherwise causes them to redraw
1222 DisplayController.requestRefresh(false);
1223 // ItemUtils.EnclosedCheck(_body);
1224 ItemUtils.Justify(this);
1225 }
1226
1227 public void redo()
1228 {
1229 boolean bReparse = false;
1230 boolean bRecalculate = false;
1231
1232 if (_redo.size() <= 0) {
1233 return;
1234 }
1235
1236 History redo = _redo.pop();
1237
1238 // System.out.println("Redoing: " + redo);
1239
1240 switch(redo.type) {
1241 case deletion:
1242 _undo.push(redo);
1243 for(Item i : redo.items) {
1244 this.removeItem(i);
1245 //_body.remove(i);
1246 bReparse |= i.hasOverlay();
1247 bRecalculate |= i.recalculateWhenChanged();
1248 if (i instanceof Line) {
1249 Line line = (Line) i;
1250 line.getStartItem().removeLine(line);
1251 line.getEndItem().removeLine(line);
1252 } else {
1253 i.setOffset(0, 0);
1254 }
1255 }
1256 break;
1257 case movement:
1258 ItemsList body = getBody(true);
1259 ItemsList changed = new ItemsList(body);
1260 changed.retainAll(redo.items);
1261 _undo.push(new History(changed, History.Type.movement, false));
1262 for(Item i : redo.items) {
1263 int index;
1264 if(i.isVisible() && (index = body.indexOf(i)) != -1) {
1265 body.set(index, i);
1266 }
1267 }
1268 break;
1269 }
1270
1271 change();
1272
1273 StandardGestureActions.refreshHighlights();
1274
1275 if (bReparse) {
1276 FrameUtils.Parse(this, false, false, false);
1277 } else {
1278 notifyObservers(bRecalculate);
1279 }
1280
1281 // always request a refresh otherwise filled shapes
1282 // that were broken by a deletion and then reconnected by the undo
1283 // don't get filled until the user otherwise causes them to redraw
1284 DisplayController.requestRefresh(false);
1285 // ItemUtils.EnclosedCheck(_body);
1286 ItemUtils.Justify(this);
1287 }
1288
1289 /**
1290 * Returns the frameset of this Frame
1291 *
1292 * @return The name of this Frame's frameset.
1293 */
1294 public String getFramesetName()
1295 {
1296 return _frameset;
1297 }
1298
1299 public String getName()
1300 {
1301 return getFramesetName() + _number;
1302 }
1303
1304 /**
1305 * Returns the format version of this Frame
1306 *
1307 * @return The version of this Frame.
1308 */
1309 public int getVersion()
1310 {
1311 return _version;
1312 }
1313
1314 public boolean isBayFrameset()
1315 {
1316 boolean is_message_bay = _frameset.equalsIgnoreCase(MessageBay.MESSAGES_FRAMESET_NAME);
1317 boolean is_mail_bay = _frameset.equalsIgnoreCase(MailBay.EXPEDITEE_MAIL_FRAMESET_NAME);
1318
1319 boolean is_bay = is_message_bay || is_mail_bay;
1320
1321 return is_bay;
1322 }
1323
1324 public PermissionTriple getPermission() {
1325 return _permissionTriple;
1326 }
1327
1328 public UserAppliedPermission getUserAppliedPermission() {
1329 return getUserAppliedPermission(UserAppliedPermission.full);
1330 }
1331
1332 public UserAppliedPermission getUserAppliedPermission(UserAppliedPermission defaultPermission) {
1333 if (_permissionTriple == null) {
1334 return defaultPermission;
1335 }
1336
1337 return _permissionTriple.getPermission(_owner, getGroupMembers());
1338 }
1339
1340 public String getOwner()
1341 {
1342 return _owner;
1343 }
1344
1345 public String getDateCreated()
1346 {
1347 return _creationDate;
1348 }
1349
1350 public String getLastModifyUser()
1351 {
1352 return _modifiedUser;
1353 }
1354
1355 public String getLastModifyDate() {
1356 return _modifiedDate;
1357 }
1358
1359 public long getLastModifyPrecise() {
1360 return _modifiedDatePrecise;
1361 }
1362
1363 public String getFrozenDate()
1364 {
1365 return _frozenDate;
1366 }
1367
1368 public void setBackgroundColor(Colour back)
1369 {
1370 _background = back;
1371
1372 change();
1373
1374 if (this == DisplayController.getCurrentFrame()) {
1375 DisplayController.requestRefresh(false);
1376 }
1377 }
1378
1379 public Colour getBackgroundColor()
1380 {
1381 return _background;
1382 }
1383
1384 public Colour getPaintBackgroundColor()
1385 {
1386 // If null... return white
1387 if (_background == null) {
1388 return Item.DEFAULT_BACKGROUND;
1389 }
1390
1391 return _background;
1392 }
1393
1394 public void setForegroundColor(Colour front)
1395 {
1396 _foreground = front;
1397
1398 change();
1399 }
1400
1401 public Colour getForegroundColor()
1402 {
1403 return _foreground;
1404 }
1405
1406 public Colour getPaintForegroundColor()
1407 {
1408 final int GRAY = Colour.GREY.getBlue();
1409 final int THRESHOLD = Colour.FromComponent255(10);
1410
1411 if (_foreground == null) {
1412 Colour back = getPaintBackgroundColor();
1413 if (Math.abs(back.getRed() - GRAY) < THRESHOLD
1414 && Math.abs(back.getBlue() - GRAY) < THRESHOLD
1415 && Math.abs(back.getGreen() - GRAY) < THRESHOLD)
1416 {
1417 return Colour.WHITE;
1418 }
1419
1420 Colour fore = back.inverse();
1421
1422 return fore;
1423 }
1424
1425 return _foreground;
1426 }
1427
1428 @Override
1429 public String toString()
1430 {
1431 StringBuilder s = new StringBuilder();
1432 s.append(String.format("Name: %s%d%n", _frameset, _number));
1433 s.append(String.format("Version: %d%n", _version));
1434 // s.append(String.format("Permission: %s%n", _permission.toString()));
1435 // s.append(String.format("Owner: %s%n", _owner));
1436 // s.append(String.format("Date Created: %s%n", _creationDate));
1437 // s.append(String.format("Last Mod. User: %s%n", _modifiedUser));
1438 // s.append(String.format("Last Mod. Date: %s%n", _modifiedDate));
1439 s.append(String.format("Items: %d%n", getAllFrameItemsRaw().size()));
1440 return s.toString();
1441 }
1442
1443 public Text getTextAbove(Text current)
1444 {
1445 Collection<Text> currentTextItems = FrameUtils.getCurrentTextItems();
1446 List<Text> toCheck = new ArrayList<Text>();
1447
1448 if (currentTextItems.contains(current)) {
1449 toCheck.addAll(currentTextItems);
1450 } else {
1451 toCheck.addAll(getTextItems());
1452 }
1453
1454 // Make sure the items are sorted
1455 Collections.sort(toCheck);
1456
1457 int ind = toCheck.indexOf(current);
1458 if (ind == -1) {
1459 return null;
1460 }
1461
1462 // loop through all items above this one, return the first match
1463 for (int i = ind - 1; i >= 0; i--) {
1464 Text check = toCheck.get(i);
1465 if (FrameUtils.inSameColumn(check, current)) {
1466 return check;
1467 }
1468 }
1469
1470 return null;
1471 }
1472
1473 /**
1474 * Gets the text items that are in the same column and below a specified
1475 * item. Frame title and name are excluded from the column list.
1476 *
1477 * @param from
1478 * The Item to get the column for.
1479 */
1480 public List<Text> getColumn(Item from)
1481 {
1482 // Check that this item is on the current frame
1483 if (!getBody(true).contains(from)) {
1484 return null;
1485 }
1486
1487 if (from == null) {
1488 from = getLastNonAnnotationTextItem();
1489 }
1490
1491 if (from == null) {
1492 return null;
1493 }
1494
1495 // Get the enclosedItems
1496 Collection<Text> enclosed = FrameUtils.getCurrentTextItems();
1497 List<Text> toCheck = null;
1498
1499 if (enclosed.contains(from)) {
1500 toCheck = new ArrayList<Text>();
1501 toCheck.addAll(enclosed);
1502 } else {
1503 toCheck = getBodyTextItems(true);
1504 }
1505
1506 List<Text> column = new ArrayList<Text>();
1507
1508 if (toCheck.size() > 0) {
1509 // Make sure the items are sorted
1510 Collections.sort(toCheck);
1511
1512 // Create a list of items consisting of the item 'from' and all the
1513 // items below it which are also in the same column as it
1514 int index = toCheck.indexOf(from);
1515
1516 // If its the title index will be 0
1517 if (index < 0) {
1518 index = 0;
1519 }
1520
1521 for (int i = index; i < toCheck.size(); i++) {
1522 Text item = toCheck.get(i);
1523 if (FrameUtils.inSameColumn(from, item)) {
1524 column.add(item);
1525 }
1526 }
1527 }
1528
1529 return column;
1530 }
1531
1532 /**
1533 * Adds the given Vector to the list of vector Frames being drawn with this
1534 * Frame.
1535 *
1536 * @param vector
1537 * The Vector to add
1538 *
1539 * @throws NullPointerException
1540 * If overlay is null.
1541 */
1542 protected boolean addVector(Vector toAdd)
1543 {
1544 // make sure we dont add this frame as an overlay of itself
1545 if (toAdd.Frame == this) {
1546 return false;
1547 }
1548
1549 _vectors.add(toAdd);
1550
1551 // Items must be notified that they have been added or removed from this
1552 // frame via the vector...
1553 int maxX = 0;
1554 int maxY = 0;
1555
1556 HighlightMode mode = toAdd.Source.getHighlightMode();
1557 if (mode != HighlightMode.None) {
1558 mode = HighlightMode.Connected;
1559 }
1560
1561 Colour highlightColor = toAdd.Source.getHighlightColor();
1562
1563 for (Item i : ItemUtils.CopyItems(toAdd.Frame.getVectorItems(), toAdd)) {
1564 i.onParentStateChanged(new ItemParentStateChangedEvent(this, ItemParentStateChangedEvent.EVENT_TYPE_ADDED_VIA_OVERLAY, toAdd.permission));
1565 i.setEditTarget(toAdd.Source);
1566 i.setHighlightModeAndColour(mode, highlightColor);
1567 _vectorItems.add(i);
1568 i.invalidateAll();
1569 i.invalidateFill();
1570
1571 // Get the right most x and bottom most y pos
1572 int itemRight = i.getX() + i.getBoundsWidth();
1573 if (itemRight > maxX) {
1574 maxX = itemRight;
1575 }
1576
1577 int itemBottom = i.getY() + i.getBoundsHeight();
1578 if (itemBottom > maxY) {
1579 maxY = itemBottom;
1580 }
1581 }
1582
1583 toAdd.setSize(maxX, maxY);
1584
1585 return true;
1586 }
1587
1588 public Collection<Vector> getVectors()
1589 {
1590 Collection<Vector> l = new LinkedList<Vector>();
1591 l.addAll(_vectors);
1592 return l;
1593 }
1594
1595 public Collection<Overlay> getOverlays()
1596 {
1597 return new LinkedList<Overlay>(_overlays.keySet());
1598 }
1599
1600 /**
1601 * @return All vectors seen by this frame (including its vector's vectors).
1602 */
1603 public List<Vector> getVectorsDeep()
1604 {
1605 List<Vector> l = new LinkedList<Vector>();
1606 getVectorsDeep(l, this, new LinkedList<Frame>());
1607 return l;
1608 }
1609
1610 private boolean getVectorsDeep(List<Vector> vectors, Frame vector, List<Frame> seenVectors)
1611 {
1612 if (seenVectors.contains(vector)) {
1613 return false;
1614 }
1615
1616 seenVectors.add(vector);
1617
1618 for (Vector v : vector.getVectors()) {
1619 if (getVectorsDeep(vectors, v.Frame, seenVectors)) {
1620 vectors.add(v);
1621 }
1622 }
1623
1624 return true;
1625 }
1626
1627 public List<Overlay> getOverlaysDeep()
1628 {
1629 List<Overlay> ret = new LinkedList<Overlay>();
1630
1631 getOverlaysDeep(ret, new LinkedList<Frame>());
1632
1633 return ret;
1634 }
1635
1636 private boolean getOverlaysDeep(List<Overlay> overlays, List<Frame> seenOverlays)
1637 {
1638 if (seenOverlays.contains(this)) {
1639 return false;
1640 }
1641
1642 seenOverlays.add(this);
1643
1644 for (Overlay o : this.getOverlays()) {
1645 if (o.Frame.getOverlaysDeep(overlays, seenOverlays)) {
1646 overlays.add(o);
1647 }
1648 }
1649 return true;
1650 }
1651
1652 /**
1653 * Recursive function similar to AddAllOverlayItems.
1654 *
1655 * @param widgets
1656 * The collection the widgets will be added to
1657 * @param overlay
1658 * An "overlay" frame - this initially will be the parent frame
1659 * @param seenOverlays
1660 * Used for state in the recursion stack. Pass as an empty
1661 * (non-null) list.
1662 */
1663 public List<Widget> getAllOverlayWidgets()
1664 {
1665 List<Widget> widgets = new LinkedList<Widget>();
1666
1667 for (Overlay o : getOverlaysDeep()) {
1668 widgets.addAll(o.Frame.getInteractiveWidgets());
1669 }
1670
1671 return widgets;
1672 }
1673
1674 /**
1675 * Gets the overlay on this frame which owns the given item.
1676 *
1677 * @param item
1678 * The item - must not be null.
1679 * @return The overlay that contains the item. Null if no overlay owns the
1680 * item.
1681 */
1682 public Overlay getOverlayOwner(Item item)
1683 {
1684 if (item == null) {
1685 throw new NullPointerException("item");
1686 }
1687
1688 for (Overlay l : getOverlays()) {
1689 if (item.getParent() == l.Frame) {
1690 return l;
1691 }
1692 }
1693
1694 // TODO return the correct vector... not just the first vector matching
1695 // the vector frame
1696 for (Vector v : getVectors()) {
1697 if (item.getParent() == v.Frame) {
1698 return v;
1699 }
1700 }
1701
1702 return null;
1703 }
1704
1705 public void clearVectors()
1706 {
1707 _vectors.clear();
1708
1709 for (Item i : _vectorItems) { // TODO: Rethink where this should live
1710 i.invalidateAll();
1711 i.invalidateFill();
1712 }
1713 _vectorItems.clear();
1714
1715 }
1716
1717 protected boolean removeVector(Vector toRemove)
1718 {
1719 if (!_vectors.remove(toRemove)) {
1720 return false;
1721 }
1722
1723 for (Item i : toRemove.Frame.getVectorItems()) {
1724 i.invalidateAll();
1725 i.invalidateFill();
1726 _vectorItems.remove(i);
1727 i.onParentStateChanged(new ItemParentStateChangedEvent(this,
1728 ItemParentStateChangedEvent.EVENT_TYPE_REMOVED_VIA_OVERLAY,
1729 toRemove.permission));
1730
1731 }
1732
1733 return true;
1734 }
1735
1736 public void clearOverlays()
1737 {
1738 for (Overlay o : _overlays.keySet()) {
1739 for (Item i : o.Frame.getSortedItems()) {
1740 i.onParentStateChanged(new ItemParentStateChangedEvent(
1741 this,
1742 ItemParentStateChangedEvent.EVENT_TYPE_REMOVED_VIA_OVERLAY,
1743 o.permission));
1744 }
1745 }
1746 _overlayItems.clear();
1747 _overlays.clear();
1748 assert (_overlays.isEmpty());
1749 }
1750
1751 protected boolean removeOverlay(Frame f)
1752 {
1753 for (Overlay o : _overlays.keySet()) {
1754 if (o.Frame == f) {
1755 _overlays.remove(o);
1756
1757 for (Item i : f.getSortedItems()) {
1758 _overlayItems.remove(i);
1759 i.onParentStateChanged(new ItemParentStateChangedEvent(
1760 this,
1761 ItemParentStateChangedEvent.EVENT_TYPE_REMOVED_VIA_OVERLAY,
1762 o.permission));
1763 }
1764
1765 return true;
1766 }
1767 }
1768
1769 return false;
1770 }
1771
1772 public void addAllVectors(List<Vector> vectors)
1773 {
1774 for (Vector v : vectors) {
1775 addVector(v);
1776 }
1777 }
1778
1779 public void addAllOverlays(Collection<Overlay> overlays)
1780 {
1781 for (Overlay o : overlays) {
1782 addOverlay(o);
1783 }
1784 }
1785
1786 protected boolean addOverlay(Overlay toAdd)
1787 {
1788 // make sure we dont add this frame as an overlay of itself
1789 if (toAdd.Frame == this) {
1790 return false;
1791 }
1792
1793 // Dont add the overlay if there is already one for this frame
1794 if (_overlays.values().contains(toAdd.Frame)) {
1795 return false;
1796 }
1797
1798 // Add the overlay to the map of overlays on this frame
1799 _overlays.put(toAdd, toAdd.Frame);
1800
1801 // Add all the overlays from the overlay frame to this frame
1802 // TODO: Can this cause a recursion loop? If A and B are overlays of each other? cts16
1803 for (Overlay o : toAdd.Frame.getOverlays()) {
1804 addOverlay(o);
1805 }
1806
1807 // Add all the vectors from the overlay frame to this frame
1808 for (Vector v : toAdd.Frame.getVectors()) {
1809 addVector(v);
1810 }
1811
1812 // Now add the items for this overlay
1813 UserAppliedPermission permission = UserAppliedPermission.min(toAdd.Frame.getUserAppliedPermission(), toAdd.permission);
1814
1815 // Items must be notified that they have been added or removed from this
1816 // frame via the overlay...
1817 for (Item i : toAdd.Frame.getVisibleItems()) {
1818 i.onParentStateChanged(new ItemParentStateChangedEvent(this, ItemParentStateChangedEvent.EVENT_TYPE_ADDED_VIA_OVERLAY, permission));
1819 _overlayItems.add(i);
1820 }
1821
1822 return true;
1823 }
1824
1825 @Override
1826 public boolean equals(Object o)
1827 {
1828 if (o instanceof String) {
1829 return (String.CASE_INSENSITIVE_ORDER.compare((String) o, getName()) == 0);
1830 }
1831
1832 if (o instanceof Frame) {
1833 return getName().equals(((Frame) o).getName());
1834 }
1835
1836 return super.equals(o);
1837 }
1838
1839 /**
1840 * Merge one frames contents into another.
1841 *
1842 * @param toMergeWith
1843 */
1844 private void merge(Frame toMergeWith)
1845 {
1846 if (toMergeWith == null) {
1847 return;
1848 }
1849
1850 List<Item> copies = ItemUtils.CopyItems(toMergeWith.getSortedItems());
1851 copies.remove(toMergeWith.getNameItem());
1852
1853 for (Item i : copies) {
1854 if (i.getID() >= 0) {
1855 i.setID(this.getNextItemID());
1856 addItem(i);
1857 }
1858 }
1859 }
1860
1861 /**
1862 * This method is for merging frames or setting frame attributes via
1863 * injecting a text item into the frameName item.
1864 *
1865 * @param toMerge
1866 * @return the items that cant be merged
1867 */
1868 public List<Item> merge(List<Item> toMerge)
1869 {
1870 ArrayList<Item> remain = new ArrayList<Item>(0);
1871
1872 for (Item i : toMerge) {
1873 if (!(i instanceof Text)) {
1874 remain.add(i);
1875 } else {
1876 if (!AttributeUtils.setAttribute(this, (Text) i)) {
1877 if (i.getLink() != null) {
1878 merge(FrameIO.LoadFrame(i.getAbsoluteLink()));
1879 } else if (FrameIO.isValidFrameName(((Text) i).getFirstLine())) {
1880 // If we get hear we are merging frames
1881 merge(FrameIO.LoadFrame(((Text) i).getFirstLine()));
1882 }
1883 }
1884 }
1885 }
1886
1887 return remain;
1888 }
1889
1890 /**
1891 * Removes all non-title non-annotation items from this Frame. All removed
1892 * items are added to the backup-stack.
1893 */
1894 @Deprecated
1895 public void clearDeprecated(boolean keepAnnotations)
1896 {
1897 ItemsList newBody = new ItemsList();
1898
1899 Item title = getTitleItem();
1900
1901 if (title != null) {
1902 newBody.add(title);
1903 _body.remove(title);
1904 }
1905
1906 if (keepAnnotations) {
1907 for (Item i : _body) {
1908 if (i.isAnnotation()) {
1909 newBody.add(i);
1910 }
1911 }
1912 }
1913
1914 _body.removeAll(newBody);
1915 addToUndoDelete(_body, false);
1916 _body = newBody;
1917 change();
1918
1919 if (!keepAnnotations && _annotations != null) {
1920 _annotations.clear();
1921 }
1922 }
1923
1924 /**
1925 * Removes all items from the Frame except the Title Item and optionally the annotations.
1926 * All removed items are added to the backup-stack.
1927 *
1928 * @param keepAnnotations true is annotations are not to be removed from the frame.
1929 */
1930 public void clear(boolean keepAnnotations) {
1931 ItemsList body = getBody(true);
1932 ItemsList deleted = new ItemsList();
1933
1934 for (Item bodyItem: body) {
1935 boolean isAnnotationToKeep = bodyItem.isAnnotation() && keepAnnotations;
1936 boolean isFrameTitle = bodyItem.isFrameTitle();
1937 boolean isToBeRetained = isFrameTitle || isAnnotationToKeep;
1938 if (isToBeRetained) {
1939 continue;
1940 }
1941
1942 this.removeItem(bodyItem);
1943 deleted.add(bodyItem);
1944 }
1945
1946 addToUndoDelete(deleted, false);
1947 change();
1948
1949 if (!keepAnnotations && _annotations != null) {
1950 _annotations.clear();
1951 }
1952 }
1953
1954 /**
1955 * Creates a new text item with the given text.
1956 *
1957 * @param text
1958 * @return
1959 */
1960 public Text createNewText(String text)
1961 {
1962 Text t = createBlankText(text);
1963 t.setText(text);
1964 return t;
1965 }
1966
1967 /**
1968 * Creates a new Text Item with no text. The newly created Item is a copy
1969 * the ItemTemplate if one is present, and inherits all the attributes of
1970 * the Template
1971 *
1972 * @return The newly created Text Item
1973 */
1974 public Text createBlankText(String templateType) {
1975 File file = new File(getFramePathReal());
1976 long fileLastModified = file.lastModified();
1977 long frameLastModified = this.getLastModifyPrecise();
1978 //if (ExpReader.getVersion(getFramePathReal()) > this._version) {
1979 if (fileLastModified > frameLastModified) {
1980 GestureType refreshGestureType = StandardGestureActions.getInstance().gestureType(StandardGestureType.REFRESH);
1981 RefreshGestureData refreshGestureData = new RefreshGestureData(true, false);
1982 try {
1983 StandardGestureActions.getInstance().onGesture(new Gesture(refreshGestureType, refreshGestureData));
1984 EcosystemManager.getMiscManager().beep();
1985 } catch (NullPointerException e) {
1986 //Detected more recent data on file system than on Frame in memory. Unfortunately not in a position to cause a refresh.
1987 }
1988 }
1989
1990 SessionStats.CreatedText();
1991 Text t;
1992
1993 if (templateType.length() == 0) {
1994 t = getItemTemplate().copy();
1995 } else {
1996 t = getItemTemplate(templateType.charAt(0));
1997 }
1998
1999 // reset attributes
2000 t.setID(getNextItemID());
2001 t.setPosition(DisplayController.getMousePosition());
2002 t.setText("");
2003 t.setParent(this);
2004
2005 // Set the width if the template doesnt have a width
2006 // Make it the width of the page
2007 // t.setMaxWidth(FrameGraphics.getMaxFrameSize().width);
2008 // if (t.getWidth() <= 0) {
2009 // String maxWidthString = getAnnotationValue("maxwidth");
2010 // int width = FrameGraphics.getMaxFrameSize().width;
2011 // if (maxWidthString != null) {
2012 // try {
2013 // width = Math.min(width, Integer.parseInt(maxWidthString));
2014 // } catch (NumberFormatException nfe) {
2015 // }
2016 // }
2017 //
2018 // t.setRightMargin(width);
2019 // }
2020 addItem(t);
2021 return t;
2022 }
2023
2024 /**
2025 * Returns the data associated with the frame.
2026 * @return
2027 */
2028 public List<String> getData() {
2029 return _frameData;
2030 }
2031
2032 /**
2033 * Adds a piece of data to be associated with the frame.
2034 * @param dataItem
2035 */
2036 public void addToData(String dataItem) {
2037 if (dataItem != null) {
2038 if (_frameData == null)
2039 _frameData = new LinkedList<String>();
2040 _frameData.add(dataItem);
2041 }
2042 }
2043
2044 /**
2045 * Returns the path (String) to the .exp file that this Frame represents.
2046 * This follows redirects, meaning that it provides the actual file from which
2047 * the frames data is drawn from.
2048 * @return The path to the .exp file that this Frame represents
2049 * @see getFramePathLogical
2050 * @see getFramesetPath
2051 */
2052 public String getFramePathReal() {
2053 String framesetPath = getFramesetPath();
2054 String redirect = ExpReader.redirectTo(getFramePathLogical());
2055
2056 if (redirect == null) {
2057 return getFramePathLogical();
2058 }
2059
2060 while (redirect != null) {
2061 framesetPath = getFramesetPath() + redirect;
2062 redirect = ExpReader.redirectTo(redirect);
2063 }
2064 return framesetPath;
2065 }
2066
2067 /**
2068 * Returns the path (String) to the .exp file that this Frame represents.
2069 * Does not follow redirects, opting to instead provide the logical path to this file.
2070 * @return The path to the .exp file that this Frame represents
2071 * @see getFramePathReal
2072 * @see getFramesetPath
2073 */
2074 public String getFramePathLogical() {
2075 return getFramesetPath() + this.getNumber() + ExpReader.EXTENTION;
2076 }
2077
2078 /**
2079 * Returns the path (String) to the frameset directory that the file that this Frame represents is contained within.
2080 * @return The path to this Frames frameset directory
2081 * @see getFramesetPathLogical
2082 * @see getFramesetPathReal
2083 */
2084 public String getFramesetPath() {
2085 return Paths.get(this.getPath()).resolve(this.getFramesetName()).toString() + File.separator;
2086 }
2087
2088 public Item createDot() {
2089 Item dot = new Dot(DisplayController.getMouseX(), DisplayController.getMouseY(), getNextItemID());
2090
2091 Item template = getTemplate(_dotTemplate, ItemUtils.TAG_DOT_TEMPLATE);
2092 float thickness = template.getThickness();
2093 if (thickness > 0) {
2094 dot.setThickness(template.getThickness());
2095 }
2096 if (template.getLinePattern() != null) {
2097 dot.setLinePattern(template.getLinePattern());
2098 }
2099 dot.setColor(template.getColor());
2100 dot.setFillColor(template.getFillColor());
2101 // reset attributes
2102 dot.setParent(this);
2103 dot.setOwner(template.getOwner());
2104 return dot;
2105 }
2106
2107 private Text getTemplate(Text defaultTemplate, int templateTag)
2108 {
2109 Text t = null;
2110
2111 // check for an updated template...
2112 for (Item i : this.getSortedItems()) {
2113 if (ItemUtils.startsWithTag(i, templateTag)) {
2114 t = (Text) i;
2115 break;
2116 }
2117 }
2118
2119 if (t == null) {
2120 if (defaultTemplate == null) {
2121 return null;
2122 }
2123
2124 t = defaultTemplate;
2125 }
2126
2127 // If the item is linked apply any attribute pairs on the child frame
2128 String link = t.getAbsoluteLink();
2129
2130 // need to get link first because copy doesnt copy the link
2131 t = t.copy();
2132 // If the template does not have a owner then it should be set to the current user.
2133 if (t.getOwner() == null) {
2134 t.setOwner(UserSettings.UserName.get());
2135 }
2136 t.setTooltip(null);
2137 if (link != null) {
2138 t.setLink(null);
2139 Frame childFrame = FrameIO.LoadFrame(link);
2140 if (childFrame != null) {
2141 // read in attribute value pairs
2142 for (Text attribute : childFrame.getBodyTextItems(false)) {
2143 AttributeUtils.setAttribute(t, attribute);
2144 }
2145 }
2146 }
2147 return t;
2148 }
2149
2150 /**
2151 * TODO: Comment. cts16
2152 * TODO: Remove magic constants. cts16
2153 */
2154 public Text getItemTemplate(char firstChar)
2155 {
2156 switch (firstChar) {
2157 case '@':
2158 return getAnnotationTemplate();
2159 case '/':
2160 case '#':
2161 return getCodeCommentTemplate();
2162 default:
2163 return getItemTemplate();
2164 }
2165 }
2166
2167 public Text createNewText()
2168 {
2169 return createNewText("");
2170 }
2171
2172 public Text addText(int x, int y, String text, String action)
2173 {
2174 Text t = createNewText(text);
2175 t.setPosition(x, y);
2176 t.addAction(action);
2177 return t;
2178 }
2179
2180 public Text addText(int x, int y, String text, String action, String link)
2181 {
2182 Text t = addText(x, y, text, action);
2183 t.setLink(link);
2184 return t;
2185 }
2186
2187 public Dot addDot(int x, int y)
2188 {
2189 Dot d = new Dot(x, y, getNextItemID());
2190 addItem(d);
2191 return d;
2192 }
2193
2194 /**
2195 * Adds a rectangle to the frame
2196 *
2197 * @param x
2198 * X coordinate of the top-left corner of the rectangle
2199 * @param y
2200 * Y coordinate of the top-left corner of the rectangle
2201 * @param width
2202 * Width of the rectangle
2203 * @param height
2204 * Height of the rectangle
2205 * @param borderThickness
2206 * Thickness, in pixels, of the rectangle's border/outline
2207 * @param borderColor
2208 * Color of the rectangle's border/outline
2209 * @param fillColor
2210 * Color to fill the rectangle with
2211 */
2212 public List<Item> addRectangle(int x, int y, int width, int height, float borderThickness, Colour borderColor, Colour fillColor)
2213 {
2214 List<Item> rectComponents = new ArrayList<Item>();
2215 Item[] corners = new Item[4];
2216
2217 // Top Left
2218 corners[0] = this.createDot();
2219 corners[0].setPosition(x, y);
2220
2221 // Top Right
2222 corners[1] = this.createDot();
2223 corners[1].setPosition(x + width, y);
2224
2225 // Bottom Right
2226 corners[2] = this.createDot();
2227 corners[2].setPosition(x + width, y + height);
2228
2229 // Bottom Left
2230 corners[3] = this.createDot();
2231 corners[3].setPosition(x, y + height);
2232
2233 // Add corners to the collection and setting their attributes
2234 for (int i = 0; i < corners.length; i++) {
2235 corners[i].setThickness(borderThickness);
2236 corners[i].setColor(borderColor);
2237 corners[i].setFillColor(fillColor);
2238 rectComponents.add(corners[i]);
2239 }
2240
2241 // create lines between the corners
2242 rectComponents.add(new Line(corners[0], corners[1], this.getNextItemID()));
2243 rectComponents.add(new Line(corners[1], corners[2], this.getNextItemID()));
2244 rectComponents.add(new Line(corners[2], corners[3], this.getNextItemID()));
2245 rectComponents.add(new Line(corners[3], corners[0], this.getNextItemID()));
2246
2247 // Add constraints between each corner
2248 new Constraint(corners[0], corners[1], this.getNextItemID(), Constraint.HORIZONTAL);
2249 new Constraint(corners[2], corners[3], this.getNextItemID(), Constraint.HORIZONTAL);
2250 new Constraint(corners[1], corners[2], this.getNextItemID(), Constraint.VERTICAL);
2251 new Constraint(corners[3], corners[0], this.getNextItemID(), Constraint.VERTICAL);
2252
2253 List<Item> rect = new ArrayList<Item>(rectComponents);
2254 this.addAllItems(rectComponents);
2255 StandardGestureActions.anchor(rectComponents);
2256 return rect;
2257 }
2258
2259 public boolean isSaved()
2260 {
2261 return _saved;
2262 }
2263
2264 public void setSaved()
2265 {
2266 _saved = true;
2267 _change = false;
2268 }
2269
2270 public static boolean rubberbandingLine()
2271 {
2272 return FreeItems.getInstance().size() == 2 &&
2273 (FreeItems.getInstance().get(0) instanceof Line || FreeItems.getInstance().get(1) instanceof Line);
2274 }
2275
2276 /**
2277 * Tests if an item is a non title, non frame name, non special annotation
2278 * text item.
2279 *
2280 * @param it
2281 * the item to be tested
2282 * @return true if the item is a normal text item
2283 */
2284 public boolean isNormalTextItem(Item it)
2285 {
2286 if (it instanceof Text && it != getTitleItem() && it != _frameName && !((Text) it).isSpecialAnnotation()) {
2287 return true;
2288 }
2289
2290 return false;
2291 }
2292
2293 /**
2294 * Moves the mouse to the end of the text item with a specified index.
2295 *
2296 * @param index
2297 */
2298 public boolean moveMouseToTextItem(int index)
2299 {
2300 List<Item> items = getSortedItems();
2301 int itemsFound = 0;
2302 for (int i = 0; i < items.size(); i++) {
2303 Item it = items.get(i);
2304 if (isNormalTextItem(it)) {
2305 itemsFound++;
2306 }
2307 if (itemsFound > index) {
2308 DisplayController.setCursorPosition(((Text) it).getParagraphEndPosition().getX(), it.getY());
2309 DisplayController.resetCursorOffset();
2310 DisplayController.requestRefresh(true);
2311 return true;
2312 }
2313 }
2314
2315 return false;
2316 }
2317
2318 /**
2319 * Searches for an annotation item called start to be used as the default
2320 * cursor location when TDFC occurs.
2321 *
2322 * TODO: Remove magic constants. cts16
2323 */
2324 public boolean moveMouseToDefaultLocation()
2325 {
2326 List<Item> items = getSortedItems();
2327
2328 for (Item it : items) {
2329 if (it instanceof Text) {
2330 Text t = (Text) it;
2331 if (t.getText().toLowerCase().startsWith("@start") || t.getText().toLowerCase().equals("@start:")) {
2332 // Used to allow users the option of putting an initial
2333 // bullet after the @start
2334 // This was replaced by width
2335 // t.stripFirstWord();
2336 t.setText("");
2337
2338 if (t.getText().equals("")) {
2339 DisplayController.getCurrentFrame().removeItem(t);
2340 }
2341
2342 if (!FreeItems.hasItemsAttachedToCursor()) {
2343 DisplayController.setCursorPosition(((Text) it).getParagraphEndPosition());
2344 DisplayController.resetCursorOffset();
2345 }
2346
2347 DisplayController.requestRefresh(true);
2348
2349 return true;
2350 }
2351 }
2352 }
2353
2354 return false;
2355 }
2356
2357 /**
2358 * Gets the file name that actions should use to export files created by
2359 * running actions from this frame.
2360 *
2361 * @return the fileName if the frame contains an '@file' tag. Returns the
2362 * name of the frame if the tag isnt on the frame.
2363 */
2364 public String getExportFileName()
2365 {
2366 String fileName = getExportFileTagValue();
2367
2368 if (fileName == null) {
2369 fileName = getTitle();
2370
2371 if (fileName == null) {
2372 fileName = getName();
2373 }
2374 }
2375
2376 return fileName;
2377 }
2378
2379 public void toggleBackgroundColor()
2380 {
2381 setBackgroundColor(ColorUtils.getNextColor(_background, TemplateSettings.BackgroundColorWheel.get(), null));
2382 }
2383
2384 public void setName(String frameset, int i)
2385 {
2386 setFrameset(frameset);
2387 setFrameNumber(i);
2388 }
2389
2390 /**
2391 * Sets the item permissions to match the protection for the frame.
2392 * No longer sets item permissions, since items can have their own permissions now (but still default to frame permissions)
2393 *
2394 */
2395 public void refreshItemPermissions(UserAppliedPermission maxPermission)
2396 {
2397 if(_frameName == null) {
2398 return;
2399 }
2400
2401 UserAppliedPermission permission = UserAppliedPermission.min(maxPermission, getUserAppliedPermission());
2402
2403 switch (permission) {
2404 case none:
2405 _frameName.setBackgroundColor(FRAME_NAME_BACKGROUND_COLOUR_FOR_PERMISSION_NONE);
2406 break;
2407 case followLinks:
2408 _frameName.setBackgroundColor(FRAME_NAME_BACKGROUND_COLOUR_FOR_PERMISSION_FOLLOW_LINKS);
2409 break;
2410 case copy:
2411 _frameName.setBackgroundColor(FRAME_NAME_BACKGROUND_COLOUR_FOR_PERMISSION_COPY);
2412 break;
2413 case createFrames:
2414 _frameName.setBackgroundColor(FRAME_NAME_BACKGROUND_COLOUR_FOR_PERMISSION_CREATE_FRAMES);
2415 break;
2416 case full:
2417 _frameName.setBackgroundColor(FRAME_NAME_BACKGROUND_COLOUR_FOR_PERMISSION_FULL);
2418 break;
2419 default:
2420 assert (false);
2421 break;
2422 }
2423
2424 for (Overlay o : getOverlays()) {
2425 for(Item i : o.Frame.getBody(false)) {
2426 i.setOverlayPermission(o.permission);
2427 }
2428 o.Frame.refreshItemPermissions(o.permission);
2429 }
2430 }
2431
2432 public boolean isTestFrame()
2433 {
2434 Text title = getTitleItem();
2435 if (title == null) {
2436 return false;
2437 }
2438 String action = title.getFirstAction();
2439 if (action == null) {
2440 return false;
2441 }
2442 action = action.toLowerCase();
2443 return action.startsWith(Simple.RUN_FRAME_ACTION) || action.startsWith(Simple.DEBUG_FRAME_ACTION);
2444 }
2445
2446 public void setActiveTime(String activeTime)
2447 {
2448 try {
2449 _activeTime = new Time(Time.valueOf(activeTime).getTime() + 12 * 60 * 60 * 1000);
2450 } catch (Exception e) {
2451 _activeTime = new Time(0);
2452 }
2453 }
2454
2455 public void setActiveTime(Time activeTime)
2456 {
2457 _activeTime = activeTime;
2458 }
2459
2460 public void setDarkTime(Time darkTime)
2461 {
2462 _darkTime = darkTime;
2463 }
2464
2465 public void setDarkTime(String darkTime)
2466 {
2467 try {
2468 _darkTime = new Time(Time.valueOf(darkTime).getTime() + 12 * 60 * 60 * 1000);
2469 } catch (Exception e) {
2470 _darkTime = new Time(0);
2471 }
2472 }
2473
2474 /**
2475 * Returns null if their is no backup frame or if it is invalid.
2476 *
2477 * @return the backup frame for this frame
2478 */
2479 public Frame getBackupFrame()
2480 {
2481 Text backupTag = _annotations.get("old");
2482 if (backupTag == null) {
2483 return null;
2484 }
2485
2486 // TODO want another way to deal with updating of annotations items
2487 // without F12 refresh
2488 // Reparse the frame if annotation item has been modified
2489 String[] processedText = backupTag.getProcessedText();
2490 if (processedText == null) {
2491 // Reparse the frame if this item has not yet been parsed
2492 FrameUtils.Parse(this);
2493 return getBackupFrame();
2494 }
2495
2496 // Now return the name of the backed up frame
2497 String link = backupTag.getAbsoluteLink();
2498 if (link == null || link.equalsIgnoreCase(getName())) {
2499 return null;
2500 }
2501
2502 Frame backup = FrameIO.LoadFrame(link);
2503 return backup;
2504 }
2505
2506 public Time getDarkTime()
2507 {
2508 return _darkTime;
2509 }
2510
2511 public Time getActiveTime()
2512 {
2513 return _activeTime;
2514 }
2515
2516 /**
2517 * Gets the number of backed up versions of this frame are saved plus 1 for
2518 * this frame.
2519 *
2520 * @return the number of frames in the backed up comet
2521 */
2522 public int getCometLength()
2523 {
2524 Frame backup = getBackupFrame();
2525 return 1 + (backup == null ? 0 : backup.getCometLength());
2526 }
2527
2528 public void addAnnotation(Text item)
2529 {
2530 if (_annotations == null) {
2531 _annotations = new HashMap<String, Text>();
2532 }
2533
2534 // Check if this item has already been processed
2535 String[] tokens = item.getProcessedText();
2536 if (tokens != null) {
2537 if (tokens.length > 0) {
2538 _annotations.put(tokens[0], item);
2539 }
2540 return;
2541 }
2542
2543 String text = item.getText().trim();
2544 assert (text.charAt(0) == '@');
2545
2546 // Ignore annotations with spaces after the tag symbol
2547 if (text.length() < 2 || !Character.isLetter(text.charAt(1))) {
2548 item.setProcessedText(new String[0]);
2549 return;
2550 }
2551
2552 // The separator char must come before the first non letter otherwise we
2553 // ignore the annotation item
2554 for (int i = 2; i < text.length(); i++) {
2555 char ch = text.charAt(i);
2556 if (!Character.isLetterOrDigit(ch)) {
2557 // Must have an attribute value pair
2558 if (ch == AttributeValuePair.SEPARATOR_CHAR) {
2559 // Get the attribute
2560 String attribute = text.substring(1, i).toLowerCase();
2561 String value = "";
2562 if (text.length() > 1 + i) {
2563 value = text.substring(i + 1).trim();
2564 }
2565 item.setProcessedText(new String[] { attribute, value });
2566 _annotations.put(attribute, item);
2567 return;
2568 } else {
2569 item.setProcessedText(new String[0]);
2570 return;
2571 }
2572 }
2573 }
2574
2575 // If it was nothing but letters and digits save the tag
2576 String lowerCaseText = text.substring(1).toLowerCase();
2577 item.setProcessedText(new String[] { lowerCaseText });
2578 _annotations.put(lowerCaseText, item);
2579 }
2580
2581 public boolean hasAnnotation(String annotation)
2582 {
2583 if (_annotations == null) {
2584 refreshAnnotationList();
2585 }
2586
2587 return _annotations.containsKey(annotation.toLowerCase());
2588 }
2589
2590 /**
2591 * Returns the annotation value in full case.
2592 *
2593 * @param annotation
2594 * the annotation to retrieve the value of.
2595 * @return the annotation item value in full case or null if the annotation
2596 * is not on the frame or has no value.
2597 */
2598 public String getAnnotationValue(String annotation)
2599 {
2600 if (_annotations == null) {
2601 refreshAnnotationList();
2602 }
2603
2604 Text text = _annotations.get(annotation.toLowerCase());
2605 if (text == null) {
2606 return null;
2607 }
2608
2609 String[] tokens = text.getProcessedText();
2610
2611 if (tokens != null && tokens.length > 1) {
2612 return tokens[1];
2613 }
2614
2615 return null;
2616 }
2617
2618 public void clearAnnotations()
2619 {
2620 _annotations = null;
2621 }
2622
2623 public List<Item> getVisibleItems()
2624 {
2625 return getSortedItems(true);
2626 }
2627
2628 private void refreshAnnotationList()
2629 {
2630 if (_annotations == null) {
2631 _annotations = new HashMap<String, Text>();
2632 } else {
2633 _annotations.clear();
2634 }
2635
2636 for (Text text : getTextItems()) {
2637 if (text.isAnnotation()) {
2638 addAnnotation(text);
2639 }
2640 }
2641 }
2642
2643 public Collection<Text> getAnnotationItems()
2644 {
2645 if (_annotations == null) {
2646 refreshAnnotationList();
2647 }
2648
2649 return _annotations.values();
2650 }
2651
2652 /**
2653 * Gets a list of items to be saved to file by text file writers.
2654 *
2655 * @return the list of items to be saved to a text file
2656 */
2657 /*public List<Item> getItemsToSave() {
2658 if (!_sorted) {
2659 Collections.sort(_body);
2660 _sorted = true;
2661 }
2662
2663 // iWidgets are handled specially since 8 items are written as one
2664 Collection<Widget> seenWidgets = new LinkedHashSet<Widget>();
2665
2666 List<Item> toSave = new ArrayList<Item>();
2667
2668 for (Item i : _body) {
2669 if (i == null || i.dontSave()) {
2670 continue;
2671 }
2672
2673 // Ensure only one of the WidgetCorners represent a single widget
2674 if (i instanceof WidgetCorner) {
2675 Widget iw = ((WidgetCorner) i).getWidgetSource();
2676 if (seenWidgets.contains(iw)) {
2677 continue;
2678 }
2679 seenWidgets.add(iw);
2680 toSave.add(iw.getSource());
2681 } else if (i instanceof XRayable) {
2682 XRayable x = (XRayable) i;
2683 toSave.addAll(x.getItemsToSave());
2684 // Circle centers are items with attached enclosures
2685 } else if (i.hasEnclosures()) {
2686 continue;
2687 } else {
2688 toSave.add(i);
2689 }
2690 }
2691
2692 for (Vector v : getVectors()) {
2693 toSave.add(v.Source);
2694 }
2695
2696 return toSave;
2697 }*/
2698
2699 public List<Item> getItemsToSave() {
2700 return getItemsToSave(BodyType.PrimaryBody);
2701 }
2702
2703 /**
2704 * Returns a list of items for the specified BodyType.
2705 *
2706 * Asking for the primary or surrogate items gives you exactly those.
2707 *
2708 * Asking for the body items is a weird case because the body list is
2709 * transitory. Therefore, when asking for the body items, this
2710 * function assumes that you want all items, reguardless of if they
2711 * are primaries or surrogates. As of 20/08/2019, there are no places
2712 * in the code that asks for the body items to save.
2713 * @param type
2714 * @return
2715 */
2716 public List<Item> getItemsToSave(BodyType type) {
2717 assert(!type.equals(BodyType.BodyDisplay));
2718 switch (type) {
2719 case SurrogateBody:
2720 return getItemsToSave(_surrogateItemsBody);
2721 case BodyDisplay:
2722 return getItemsToSave(new ItemsList(getAllFrameItemsRaw()));
2723 case PrimaryBody:
2724 default:
2725 return getItemsToSave(_primaryItemsBody);
2726 }
2727 }
2728
2729 private List<Item> getItemsToSave(ItemsList body) {
2730 body.sort();
2731 List<Widget> seenWidgets = new ArrayList<Widget>();
2732
2733 List<Item> toSave = new ArrayList<Item>();
2734
2735 for (Item item: body) {
2736
2737 if (item == null || item.dontSave()) {
2738 continue;
2739 }
2740
2741 if (item instanceof WidgetCorner) {
2742 // Save the widget source.
2743 // Each widget has multiple WidgetCorner's..ignore them if we already have the source.
2744 Widget iw = ((WidgetCorner) item).getWidgetSource();
2745 if (seenWidgets.contains(iw)) { continue; }
2746 seenWidgets.add(iw);
2747 toSave.add(iw.getSource());
2748 } else if (item instanceof XRayable) {
2749 // XRayable Items have their sources saved.
2750 XRayable x = (XRayable) item;
2751 toSave.addAll(x.getItemsToSave());
2752 } else if (item.hasEnclosures()) {
2753 // Deals with Circle objects only?
2754 continue;
2755 } else {
2756 toSave.add(item);
2757 }
2758 }
2759
2760 return toSave;
2761 }
2762
2763 public Collection<Item> getOverlayItems()
2764 {
2765 return _overlayItems;
2766 }
2767
2768 /**
2769 * Returns true if this frame has and overlays for the specified frame.
2770 *
2771 * @param frame
2772 * @return
2773 */
2774 public boolean hasOverlay(Frame frame)
2775 {
2776 return _overlays.containsValue(frame);
2777 }
2778
2779 public Collection<Item> getAllItems() {
2780 Collection<Item> allItems = getBody(true).cloneList();
2781
2782 allItems.addAll(_overlayItems);
2783 allItems.addAll(_vectorItems);
2784 return allItems;
2785 }
2786
2787 public Collection<Item> getVectorItems()
2788 {
2789 Collection<Item> vectorItems = new LinkedHashSet<Item>(_vectorItems);
2790 vectorItems.addAll(getNonAnnotationItems(false));
2791 return vectorItems;
2792 }
2793
2794 /**
2795 * Gets a list of all the text items on the frame.
2796 *
2797 * @return
2798 */
2799 public Collection<Text> getTextItems()
2800 {
2801 Collection<Text> textItems = new ArrayList<Text>();
2802
2803 for (Item i : getSortedItems(true)) {
2804 // only add up normal body text items
2805 if ((i instanceof Text)) {
2806 textItems.add((Text) i);
2807 }
2808 }
2809
2810 return textItems;
2811 }
2812
2813 public Text getAnnotation(String annotation)
2814 {
2815 if (_annotations == null) {
2816 refreshAnnotationList();
2817 }
2818
2819 return _annotations.get(annotation.toLowerCase());
2820 }
2821
2822 public void recalculate()
2823 {
2824 for (Item i : getSortedItems()) {
2825 if (i.hasFormula() && !i.isAnnotation()) {
2826 i.calculate(i.getFormula());
2827 }
2828 }
2829 }
2830
2831 public void removeObserver(FrameObserver observer)
2832 {
2833 _observers.remove(observer);
2834 }
2835
2836 public void addObserver(FrameObserver observer)
2837 {
2838 _observers.add(observer);
2839 }
2840
2841 public void clearObservers()
2842 {
2843 for (FrameObserver fl : _observers) {
2844 fl.removeSubject(this);
2845 }
2846
2847 // The frame listener will call the frames removeListener method
2848 assert (_observers.size() == 0);
2849 }
2850
2851 public Collection<Text> getNonAnnotationText(boolean removeTitle)
2852 {
2853 Collection<Text> items = new LinkedHashSet<Text>();
2854
2855 for (Item i : getSortedItems(true)) {
2856 // only add up normal body text items
2857 if (i instanceof Text && !i.isAnnotation()) {
2858 items.add((Text) i);
2859 }
2860 }
2861
2862 if (removeTitle) {
2863 items.remove(getTitleItem());
2864 }
2865
2866 return items;
2867 }
2868
2869 @Deprecated
2870 public void disposeDeprecated() {
2871 clearObservers();
2872
2873 for (Item i : _body) {
2874 i.dispose();
2875 }
2876
2877 _frameName.dispose();
2878 _body = null;
2879 _frameName = null;
2880 }
2881
2882 /**
2883 * Disposes off all references associated with this frame.
2884 * This operation is NOT REVERSEABLE through the history.
2885 */
2886 public void dispose() {
2887 clearObservers();
2888
2889 List<Item> allFrameItems = getAllFrameItemsRaw();
2890
2891 for (Item i: allFrameItems) {
2892 i.dispose();
2893 }
2894
2895 _frameName.dispose();
2896 _frameName = null;
2897 getBody(false).clear();
2898 getPrimaryBody().clear();
2899 getSurrogateBody().clear();
2900 }
2901
2902 public void parse()
2903 {
2904 for (Overlay o : getOverlays()) {
2905 o.Frame.parse();
2906 }
2907
2908 // Must parse the frame AFTER the overlays
2909 FrameUtils.Parse(this);
2910 }
2911
2912 public void setPath(String path)
2913 {
2914 this.path = path;
2915 }
2916
2917 public String getPath()
2918 {
2919 return path;
2920 }
2921
2922 public void setLocal(boolean isLocal)
2923 {
2924 this._isLocal = isLocal;
2925 }
2926
2927 public boolean isLocal()
2928 {
2929 return _isLocal;
2930 }
2931
2932 public String getExportFileTagValue()
2933 {
2934 return getAnnotationValue("file");
2935 }
2936
2937 public void assertEquals(Frame frame2)
2938 {
2939 // Check that all the items on the frame are the same
2940 List<Item> items1 = getVisibleItems();
2941 List<Item> items2 = frame2.getVisibleItems();
2942
2943 if (items1.size() != items2.size()) {
2944 throw new UnitTestFailedException(items1.size() + " items", items2.size() + " items");
2945 } else {
2946 for (int i = 0; i < items1.size(); i++) {
2947 Item i1 = items1.get(i);
2948 Item i2 = items2.get(i);
2949 String s1 = i1.getText();
2950 String s2 = i2.getText();
2951 if (!s1.equals(s2)) {
2952 throw new UnitTestFailedException(s1, s2);
2953 }
2954 }
2955 }
2956 }
2957
2958 public boolean hasObservers()
2959 {
2960 return _observers != null && _observers.size() > 0;
2961 }
2962
2963 public Collection<Item> getBodyItemsWithInsufficientPermissions() {
2964 return _bodyHiddenDueToPermissions.cloneList();
2965 }
2966
2967 public void moveItemToBodyHiddenDueToPermission(final Item i) {
2968 getBody(true).remove(i);
2969 _bodyHiddenDueToPermissions.add(i);
2970 }
2971
2972 public void moveItemFromBodyHiddenDueToPermission(Item i, PermissionTriple newPermission) {
2973 if (_bodyHiddenDueToPermissions.contains(i)) {
2974 _bodyHiddenDueToPermissions.remove(i);
2975 i.setPermission(newPermission);
2976 getBody(true).add(i);
2977 }
2978 }
2979
2980 public Collection<? extends Item> getInteractableItems() {
2981 /*
2982 * TODO: Cache the interactableItems list so we dont have to recreate it
2983 * every time this method is called
2984 */
2985 if (_interactableItems.size() > 0) {
2986 return _interactableItems;
2987 }
2988
2989 for (Item i : getBody(false)) {
2990 if (i == null) {
2991 continue;
2992 }
2993 if (i.isVisible()) {
2994 _interactableItems.add(i);
2995 } else {
2996 Collection<? extends XRayable> enclosures = i.getEnclosures();
2997 if (enclosures != null && !enclosures.isEmpty()) {
2998 Iterator<? extends XRayable> iterator = enclosures.iterator();
2999 while (iterator.hasNext()) {
3000 _interactableItems.add(iterator.next());
3001 }
3002 }
3003 }
3004 }
3005
3006 for (Item i : _overlayItems) {
3007 if (i.hasPermission(UserAppliedPermission.followLinks)) {
3008 _interactableItems.add(i);
3009 }
3010 }
3011
3012 for (Item i : _vectorItems) {
3013 if (i.hasPermission(UserAppliedPermission.none)) {
3014 _interactableItems.add(i);
3015 }
3016 }
3017
3018 return _interactableItems;
3019 }
3020
3021 /** Encryption related functions start **/
3022 public String getFrameEncryptionLabel() {
3023 return _encryptionLabel;
3024 }
3025
3026 public void setFrameEncryptionLabelOnLoad(String label) {
3027 _encryptionLabel = label;
3028 }
3029
3030 public void setFrameEncryptionLabel(String label) {
3031 if (label == null || label.equals(EncryptedExpWriter.getLabelNone())) {
3032 _encryptionLabel = label;
3033 return;
3034 }
3035
3036 String errorMessage = "You do not have sufficient permissions to set the encryption label to " + label + " on frame " + getName();
3037 UserAppliedEncryptionPermission p = getFrameEncryptionPermission().getPermission(this.getOwner(), this.getGroupMembers());
3038 String homogeneousEncryptionLabel = getHomogeneousEncryptionLabel();
3039 if (homogeneousEncryptionLabel == null) { homogeneousEncryptionLabel = getFrameEncryptionLabel(); }
3040 if (homogeneousEncryptionLabel == null) { homogeneousEncryptionLabel = EncryptedExpWriter.getLabelNone(); }
3041 List<String> hetrogeneousFrameOwnerLabels = getHetrogeneousFrameOwnerLabels();
3042
3043 switch (p) {
3044 case none:
3045 MessageBay.errorMessage(errorMessage);
3046 return;
3047 case homogeneous:
3048 if (!homogeneousEncryptionLabel.equals(label)) {
3049 MessageBay.errorMessage(errorMessage);
3050 return;
3051 }
3052 break;
3053 case hetrogeneous_owner:
3054 if (!homogeneousEncryptionLabel.equals(label) && !hetrogeneousFrameOwnerLabels.contains(label)) {
3055 MessageBay.errorMessage(errorMessage);
3056 return;
3057 }
3058 break;
3059 case hetrogeneous: break;
3060 }
3061
3062 LabelInfo labelResult = Label.getLabel(label);
3063 boolean isProfileOrNone = label.equals("Profile") || label.equals("None");
3064 if (!isProfileOrNone && !labelResult.is(LabelResult.SuccessResolveLabelToKey)) {
3065 MessageBay.displayMessage(labelResult.toString());
3066 this._encryptionLabel = null;
3067 return;
3068 }
3069
3070 this.setChanged(true);
3071 _encryptionLabel = label;
3072 }
3073
3074 public boolean hasEncryptionPermissionOrHigher(UserAppliedEncryptionPermission permissionLevel) {
3075 UserAppliedEncryptionPermission p = getFrameEncryptionPermission().getPermission(this.getOwner(), this.getGroupMembers());
3076 return p.ordinal() >= permissionLevel.ordinal();
3077 }
3078
3079 public EncryptionPermissionTriple getFrameEncryptionPermission() {
3080 if (_frameEncryptionPermission == null) {
3081 _frameEncryptionPermission = EncryptionPermissionTriple.convertString("300");
3082 }
3083 return _frameEncryptionPermission;
3084 }
3085
3086 public void setFrameEncryptionPermission(EncryptionPermissionTriple p) {
3087 _frameEncryptionPermission = p;
3088 }
3089
3090 public String getHomogeneousEncryptionLabel() {
3091 return _homogeneousEncryptionLabel;
3092 }
3093
3094 public List<String> getHetrogeneousFrameOwnerLabels() {
3095 if (_hetrogeneousEncryptionLabels == null) {
3096 _hetrogeneousEncryptionLabels = new ArrayList<String>();
3097 }
3098 return _hetrogeneousEncryptionLabels;
3099 }
3100
3101 public void addToHetrogeneousFrameOwnerLabels(String label) {
3102 if (_hetrogeneousEncryptionLabels == null) {
3103 _hetrogeneousEncryptionLabels = new ArrayList<String>();
3104 }
3105 if (_hetrogeneousEncryptionLabels.contains(label)) {
3106 _hetrogeneousEncryptionLabels.remove(label);
3107 } else {
3108 _hetrogeneousEncryptionLabels.add(label);
3109 }
3110 }
3111
3112 public void clearHetrogeneousFrameOwnerLabels() {
3113 if (_hetrogeneousEncryptionLabels != null) {
3114 _hetrogeneousEncryptionLabels.clear();
3115 }
3116 }
3117
3118 public void setHomogeneousEncryptionLabelOnLoad(String label) {
3119 this.setChanged(true);
3120 _homogeneousEncryptionLabel = label;
3121 }
3122
3123 public void setHomogeneousEncryptionLabel(String label) {
3124 LabelInfo labelResult = Label.getLabel(label);
3125 boolean isProfileOrNone = label.equals("Profile") || label.equals("None");
3126 if (!isProfileOrNone && !labelResult.is(LabelResult.SuccessResolveLabelToKey)) {
3127 MessageBay.displayMessage(labelResult.toString());
3128 MessageBay.displayMessage("You will not be able to set the FrameEncryptionLabel to the Homogeneous Label.");
3129 }
3130
3131 this.setChanged(true);
3132 _homogeneousEncryptionLabel = label;
3133 }
3134
3135 public EncryptionPermissionTriple getEncryptionPermission() {
3136 if (_itemEncryptionPermission == null) {
3137 _itemEncryptionPermission = EncryptionPermissionTriple.convertString("300");
3138 }
3139 return _itemEncryptionPermission;
3140 }
3141
3142 public void setEncryptionPermission(EncryptionPermissionTriple p) {
3143 _itemEncryptionPermission = p;
3144 }
3145 /** Encryption related functions end **/
3146
3147 public String getGroup() {
3148 return _groupFrameName;
3149 }
3150
3151 public void setGroup(String _groupFrame) {
3152 this._groupFrameName = _groupFrame;
3153 this._groupFrame = null;
3154 }
3155
3156 public Frame getGroupFrame() {
3157 if (this._groupFrame != null) {
3158 return this._groupFrame;
3159 } else if (this._groupFrameName != null){
3160 this._groupFrame = FrameIO.LoadFrame(this._groupFrameName + 1, FrameIO.GROUP_PATH);
3161 return this._groupFrame;
3162 } else {
3163 return null;
3164 }
3165 }
3166
3167 public void setGroupFrame(Frame frame) {
3168 this._groupFrame = frame;
3169 }
3170
3171 public List<String> getGroupMembers() {
3172 List<String> members = new ArrayList<String>();
3173 if (getGroupFrame() != null) {
3174 Collection<Text> textItems = getGroupFrame().getTextItems();
3175 String membersTag = "@members: ";
3176 Stream<Text> memberLists = textItems.stream().filter(t ->
3177 t.getText().toLowerCase().startsWith("@owner: ") ||
3178 t.getText().toLowerCase().startsWith(membersTag));
3179 for(Text t: memberLists.collect(Collectors.toList())) {
3180 if (t.getText().toLowerCase().startsWith("@owner: ")) {
3181 members.add(t.getText().substring(8));
3182 } else if (t.getText().toLowerCase().startsWith(membersTag)) {
3183 String[] split = t.getText().substring(membersTag.length()).split(",");
3184 for (String m: split) {
3185 members.add(m.trim());
3186 }
3187 }
3188 }
3189 }
3190 return members;
3191 }
3192
3193 public boolean hasSurrogates() {
3194 return !_surrogateItemsBody.isEmpty();
3195 }
3196
3197 private boolean meetsVisibilityRequirements(boolean requireVisible, Item i) {
3198 return i.isVisible() || (!requireVisible && !i.isDeleted());
3199 }
3200
3201 private static final class History {
3202
3203 public enum Type {
3204 deletion,
3205 movement
3206 }
3207
3208 public final ItemsList items;
3209
3210 public final Type type;
3211
3212 public final boolean undoDeleteAssociatedFiles;
3213
3214 public History(ItemsList changed, Type type, boolean undoDeleteAssociatedFiles)
3215 {
3216 this.undoDeleteAssociatedFiles = undoDeleteAssociatedFiles;
3217 this.items = new ItemsList(changed);
3218 this.type = type;
3219 }
3220
3221 @Override
3222 public String toString()
3223 {
3224 return this.type.toString() + ":\n" + this.items.toString();
3225 }
3226 }
3227
3228 protected boolean hasAnnotations() {
3229 return _annotations != null && _annotations.size() > 0;
3230 }
3231
3232 public ItemsList getBody(boolean respectSurrogateMode) {
3233 if (respectSurrogateMode) { ensureBody(); }
3234 return _body;
3235 }
3236
3237 private void ensureBody() {
3238 List<String> accessibleLabelsNames = Label.getAccessibleLabelsNames(getPrimaryBody());
3239 if (!accessibleLabelsNames.equals(labelsOnLastBodySet)) {
3240 this.parse();
3241 }
3242 }
3243
3244 protected void setBody(List<Item> newBody, List<String> labelsOnSet) {
3245 _body.clear();
3246 _body.addAll(newBody);
3247 this.labelsOnLastBodySet = labelsOnSet;
3248 }
3249
3250 public ItemsList getPrimaryBody() {
3251 return _primaryItemsBody;
3252 }
3253 public ItemsList getSurrogateBody() {
3254 return _surrogateItemsBody;
3255 }
3256
3257
3258 /**
3259 * Gets all the items on the frame, regardless of whether they are primary or surrogate items.
3260 *
3261 * Bryce says: This function will likely only ever be used inside Frame itself, as callers from
3262 * outside Frame should care about what the state of the Frame.
3263 * @return
3264 */
3265 private List<Item> getAllFrameItemsRaw() {
3266 List<Item> primaries = getPrimaryBody().cloneList();
3267 List<Item> surrogateBody = getSurrogateBody().cloneList();
3268 primaries.addAll(surrogateBody);
3269 List<Item> allFrameItems = primaries.stream().distinct().collect(Collectors.toList());
3270 return allFrameItems;
3271 }
3272}
Note: See TracBrowser for help on using the repository browser.