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

Last change on this file since 1430 was 1430, checked in by bln4, 5 years ago

Fixed bug for when there are no surrogates left for a primary.

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