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

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

Fixed a bug with injecting properties into a frame name item. In some situations the frame name item did not have a owner, which caused a permission check in StandardGestureActions::deleteItemsAction to fail. In order to fix this, when the frame name item is constructed, the owner is now set to be the owner of the Frame.

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