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

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

Added debug statement and ability to only show debug statements when requested.

Add item needed to adhere to the current surrogate mode when inserting a item. There are now four cases: an item is either a primary or surrogate and it can either can be currently seen or not.

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