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

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

Fixed bug were saving lost you the lines from the primaries list.

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