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

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

org.expeditee.gio.gesture.ExpediteeKBMGestureTranslator ->
org.expeditee.gio.gesture.StandardGestureActions ->
org.expeditee.gui.Frame ->
org.expeditee.gui.ItemsList ->

Added debug gesutre that prints out body, primary and surrogates before and after a reparse. Ran with command Ctrl + Shift + R

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