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

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

Window title now notifies you when you are in surrogate mode.
Window title also now notifies you when you are running demo mode.

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