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

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

Changed surrogates to work the way discussed with David. EncryptedExpReader/Writer updated to work with this.

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