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

Last change on this file since 1450 was 1450, checked in by bnemhaus, 5 years ago

Fixed bug I introduced in the delete image file on delete expeditee picture. Bug was causing issues with non-shift delete of pictures, this is fixed now.

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