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

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

Added label existance check for when setting encryption label on a frame.

Added padlock icon on items that are encrypted.

Added key icon on items with KeyImage property set to 'PartialKey' or 'FullKey'. This will hopefully soon transformed into automatically setting these properties on key items that are on the secrets frame. The property should not be set-able by user once fully implemented and is only atm for debug purposes.

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 protected 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.