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

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

More amendments to the way surrogates are handled. Including renaming classic to primary.

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