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

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

Implementation of ProfileManager. Refactor + additional content for how new profiles are created. The refactoring split out the creation of the default profile from user profiles. Refactoring revealed a long term bug that was causing user profiles to generate with incorrect information. The additional content fixed this bug by introducing the ${USER.NAME} variable, so that the default profile frameset can specify resource locations located in the users resource directory.

org.expeditee.auth.AuthenticatorBrowser
org.expeditee.auth.account.Create
org.expeditee.gui.Browser
org.expeditee.gui.management.ProfileManager
org.expeditee.setting.DirectoryListSetting
org.expeditee.setting.ListSetting
org.expeditee.settings.UserSettings

Implementation of ResourceManager as a core location to get resources from the file system. Also the additional variable ${CURRENT_FRAMESET} to represent the current frameset, so that images can be stored in the directory of the current frameset. This increases portability of framesets.

org.expeditee.gui.FrameIO
org.expeditee.gui.management.ResourceManager
org.expeditee.gui.management.ResourceUtil
Audio:

#NB: Audio used to only operate on a single directory. This has been updated to work in a same way as images. That is: when you ask for a specific resouce, it looks to the user settings to find a sequence of directories to look at in order until it manages to find the desired resource.


There is still need however for a single(ish) source of truth for the .banks and .mastermix file. Therefore these files are now always located in resource-<username>\audio.
org.apollo.agents.MelodySearch
org.apollo.audio.structure.AudioStructureModel
org.apollo.audio.util.MultiTrackPlaybackController
org.apollo.audio.util.SoundDesk
org.apollo.gui.FrameLayoutDaemon
org.apollo.io.AudioPathManager
org.apollo.util.AudioPurger
org.apollo.widgets.FramePlayer
org.apollo.widgets.SampledTrack

Images:

org.expeditee.items.ItemUtils

Frames:

org.expeditee.gui.FrameIO

Fixed a error in the FramePlayer class caused by an incorrect use of toArray().

org.apollo.widgets.FramePlayer


Added several short cut keys to allow for the Play/Pause (Ctrl + P), mute (Ctrl + M) and volume up/down (Ctrl + +/-) when hovering over SampledTrack widgets.

org.apollo.widgets.SampledTrack


Changed the way that Authenticate.login parses the new users profile to be more consistance with other similar places in code.

org.expeditee.auth.account.Authenticate


Encapsulated _body, _surrogateItemsBody and _primaryItemsBody in Frame class. Also changed getBody function to take a boolean flag as to if it should respect the current surrogate mode. If it should then it makes sure that labels have not changed since last time getBody was called.

org.expeditee.gui.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.setText(getFramesetName() + _number);
727 _frameName.resetFrameNamePosition();
728 setChanged(oldchange);
729 }
730
731 /**
732 * Returns the number of this Frame.
733 *
734 * @return The Frame number of this Frame or -1 if it is not set.
735 */
736 public int getNumber()
737 {
738 return _number;
739 }
740
741 /**
742 * Increments the version of this Frame to the given String.
743 *
744 * @param version
745 * The version to use for this Frame.
746 */
747 public void setVersion(int version)
748 {
749 _version = version;
750 }
751
752 /**
753 * Sets the protection of this Frame to the given String.
754 *
755 * @param protection
756 * The protection to use for this Frame.
757 */
758 public void setPermission(PermissionTriple permission) {
759 List<String> groupMembers = getGroupMembers();
760
761 if (_permissionTriple != null && !_permissionTriple.getPermission(this._owner, groupMembers).equals(permission.getPermission(this._owner, groupMembers))) {
762 _protectionChanged = true;
763 }
764
765 _permissionTriple = new PermissionTriple(permission);
766
767 if (getBody(false).size() > 0) {
768 refreshItemPermissions(permission.getPermission(_owner, groupMembers));
769 }
770 }
771
772 /**
773 * Sets the owner of this Frame to the given String.
774 *
775 * @param owner
776 * The owner to use for this Frame.
777 */
778 public void setOwner(String owner)
779 {
780 _owner = owner;
781 }
782
783 /**
784 * Sets the created date of this Frame to the given String.
785 *
786 * @param date
787 * The date to use for this Frame.
788 */
789 public void setDateCreated(String date)
790 {
791 _creationDate = date;
792 _modifiedDate = date;
793 for (Item i : getAllFrameItemsRaw()) {
794 i.setDateCreated(date);
795 }
796 }
797
798 /**
799 * Resets the dates and version numbers for newly created frames.
800 *
801 */
802 public void resetDateCreated()
803 {
804 setDateCreated(Formatter.getDateTime());
805 resetTimes();
806 setVersion(0);
807 }
808
809 private void resetTimes()
810 {
811 setActiveTime(new Time(0));
812 setDarkTime(new Time(0));
813 }
814
815 /**
816 * Sets the last modifying user of this Frame to the given String.
817 *
818 * @param user
819 * The user to set as the last modifying user.
820 */
821 public void setLastModifyUser(String user)
822 {
823 _modifiedUser = user;
824 }
825
826 /**
827 * Sets the last modified date of this Frame to the given String.
828 *
829 * @param date
830 * The date to set as the last modified date.
831 * @param precise The millisecond precision last modified date.
832 */
833 public void setLastModifyDate(String date, long precise) {
834 _modifiedDate = date;
835 _modifiedDatePrecise = precise;
836 }
837
838 /**
839 * Sets the last modified date of this Frame to the given String.
840 * Used during startup. If able to be more precise then use the overloaded function.
841 *
842 * @param date
843 * The date to set as the last modified date.
844 */
845 public void setLastModifyDate(String date) {
846 _modifiedDate = date;
847 _modifiedDatePrecise = -1l;
848 }
849
850 /**
851 * Sets the last frozen date of this Frame to the given String.
852 *
853 * @param date
854 * The date to set as the last frozen date.
855 */
856 public void setFrozenDate(String date) {
857 _frozenDate = date;
858 }
859
860 public void invalidateSorted() {
861 getBody(false).invalidateSorted();
862 }
863
864 /**
865 * Adds the given Item to the body of this Frame.
866 *
867 * @param item
868 * The Item to add to this Frame.
869 */
870 public void addItem(Item item) {
871 addItem(item, true);
872 }
873
874 public void addItem(Item item, boolean recalculate) {
875 if (item == null) { return; }
876
877 addItem(item, recalculate, getBody(false));
878 if (item.isSurrogate()) {
879 addItem(item, recalculate, getSurrogateBody());
880 } else {
881 addItem(item, recalculate, getPrimaryBody());
882 }
883 }
884
885 protected void addItem(Item item, boolean recalculate, ItemsList list) {
886 if (item == null || item.equals(_frameName) || list.contains(item)) {
887 return;
888 }
889
890 // When an annotation item is anchored the annotation list must be
891 // refreshed
892 if (item.isAnnotation()) {
893 clearAnnotations();
894 }
895
896 if (item instanceof Line) {
897 _lineCount++;
898 }
899
900 _itemCount = Math.max(_itemCount, item.getID());
901
902 list.add(item);
903 item.setParent(this);
904 item.setFloating(false); // esnure that it is anchored
905
906 item.invalidateCommonTrait(ItemAppearence.Added);
907
908 // If the item is a line end and has constraints with items already
909 // on the frame then make sure the constraints hold
910 if (item.isLineEnd()) {
911 item.setPosition(item.getPosition());
912 }
913
914 list.invalidateSorted();
915
916 // item.setMaxWidth(FrameGraphics.getMaxFrameSize().width);
917 // add widget items to the list of widgets
918 if (item instanceof WidgetCorner) {
919 Widget iw = ((WidgetCorner) item).getWidgetSource();
920 if (!this._iWidgets.contains(iw)) { // A set would have been
921 if (StandardInputEventListeners.kbmStateListener.isKeyDown(Key.CTRL)) {
922 _iWidgets.add(iw);
923 } else {
924 _iWidgets.add(0, iw);
925 }
926 }
927 }
928
929 item.onParentStateChanged(new ItemParentStateChangedEvent(this, ItemParentStateChangedEvent.EVENT_TYPE_ADDED));
930
931 // if (recalculate && item.recalculateWhenChanged())
932 // recalculate();
933
934 change();
935 }
936
937 public void addToSurrogatesOnLoad(Item surrogate, Item parent) {
938 parent.addToSurrogates(surrogate);
939 }
940
941 public void refreshSize()
942 {
943 boolean bReparse = false;
944
945 for (Item i : getSortedItems()) {
946 Integer anchorLeft = i.getAnchorLeft();
947 Integer anchorRight = i.getAnchorRight();
948 Integer anchorTop = i.getAnchorTop();
949 Integer anchorBottom = i.getAnchorBottom();
950
951
952 if (anchorLeft != null) {
953 i.setAnchorLeft(anchorLeft);
954 if (i.hasVector()) {
955 bReparse = true;
956 }
957 }
958
959 if (anchorRight != null) {
960 i.setAnchorRight(anchorRight);
961 if (i.hasVector()) {
962 bReparse = true;
963 }
964 }
965
966 if (anchorTop != null) {
967 i.setAnchorTop(anchorTop);
968 if (i.hasVector()) {
969 bReparse = true;
970 }
971 }
972
973 if (anchorBottom != null) {
974 i.setAnchorBottom(anchorBottom);
975 if (i.hasVector()) {
976 bReparse = true;
977 }
978 }
979 }
980
981 // Do the anchors on the overlays
982 for (Overlay o : getOverlays()) {
983 o.Frame.refreshSize();
984 }
985
986 if (bReparse) {
987 FrameUtils.Parse(this, false);
988 }
989
990 _frameName.resetFrameNamePosition();
991 }
992
993 public void addAllItems(Collection<Item> toAdd) {
994 addAllItems(toAdd, getBody(false));
995 addAllItems(toAdd, getPrimaryBody());
996 }
997
998 protected void addAllItems(Collection<Item> toAdd, ItemsList list) {
999 for (Item i : toAdd) {
1000 // If an annotation is being deleted clear the annotation list
1001 if (i.isAnnotation()) {
1002 i.getParentOrCurrentFrame().clearAnnotations();
1003 }
1004 // TODO Improve efficiency when addAll is called
1005 addItem(i, true, list);
1006 }
1007 }
1008
1009 public void removeAllItems(Collection<Item> toRemove) {
1010 for (Item i : toRemove) {
1011 // If an annotation is being deleted clear the annotation list
1012 if (i.isAnnotation()) {
1013 i.getParentOrCurrentFrame().clearAnnotations();
1014 }
1015 removeItem(i);
1016 }
1017 }
1018
1019 public void removeItem(Item item) {
1020 removeItem(item, true);
1021 }
1022
1023 public void removeItem(Item item, boolean recalculate) {
1024 removeItem(item, recalculate, getBody(false));
1025 if (item.isSurrogate()) {
1026 removeItem(item, recalculate, getSurrogateBody());
1027 Set<Item> primariesSurrogates = item.getPrimary().getSurrogates();
1028 primariesSurrogates.remove(item);
1029 } else {
1030 removeItem(item, recalculate, getPrimaryBody());
1031 }
1032 }
1033
1034 protected void removeItem(Item item, boolean recalculate, ItemsList toRemoveFrom) {
1035 // If an annotation is being deleted clear the annotation list
1036 if (item.isAnnotation()) {
1037 item.getParentOrCurrentFrame().clearAnnotations();
1038 }
1039
1040 if (toRemoveFrom.remove(item)) {
1041 change();
1042 // Remove widgets from the widget list
1043 if (item != null) {
1044 item.onParentStateChanged(new ItemParentStateChangedEvent(this, ItemParentStateChangedEvent.EVENT_TYPE_REMOVED));
1045
1046 if (item instanceof WidgetCorner) {
1047 _iWidgets.remove(((WidgetCorner) item).getWidgetSource());
1048 }
1049
1050 item.invalidateCommonTrait(ItemAppearence.Removed);
1051 }
1052 // TODO Improve efficiency when removeAll is called
1053 // if (recalculate && item.recalculateWhenChanged())
1054 // recalculate();
1055 }
1056 }
1057
1058 /**
1059 * Adds the given History event to the stack.
1060 *
1061 * @param stack The stack to add to
1062 * @param items The items to put in the event
1063 * @param type The type of event that occurred
1064 */
1065 private void addToUndo(ItemsList items, History.Type type) {
1066 if (items.size() < 1) {
1067 return;
1068 }
1069
1070 _undo.push(new History(items, type));
1071 }
1072
1073 public void addToUndoDelete(ItemsList items) {
1074 addToUndo(items, History.Type.deletion);
1075 }
1076
1077 public void addToUndoMove(ItemsList items) {
1078 addToUndo(items, History.Type.movement);
1079 }
1080
1081 public void undo() {
1082 boolean reparse = false;
1083 boolean recalculate = false;
1084
1085 if (_undo.size() <= 0) {
1086 return;
1087 }
1088
1089 History undo = _undo.pop();
1090
1091 // System.out.println("Undoing: " + undo);
1092
1093 switch(undo.type) {
1094 case deletion:
1095 _redo.push(undo);
1096 for(Item i : undo.items) {
1097 this.addItem(i);
1098 reparse |= i.hasOverlay();
1099 recalculate |= i.recalculateWhenChanged();
1100 if (i instanceof Line) {
1101 Line line = (Line) i;
1102 line.getStartItem().addLine(line);
1103 line.getEndItem().addLine(line);
1104 } else {
1105 i.setOffset(0, 0);
1106 }
1107 }
1108 break;
1109 case movement:
1110 ItemsList body = getBody(true);
1111 ItemsList changed = new ItemsList(body);
1112 changed.retainAll(undo.items);
1113 _redo.push(new History(changed, History.Type.movement));
1114 for(Item i : undo.items) {
1115 int index;
1116 if(i.isVisible() && (index = body.indexOf(i)) != -1) {
1117 body.set(index, i);
1118 }
1119 }
1120 break;
1121 }
1122
1123 change();
1124
1125 StandardGestureActions.refreshHighlights();
1126
1127 if (reparse) {
1128 FrameUtils.Parse(this, false, false);
1129 } else {
1130 notifyObservers(recalculate);
1131 }
1132
1133 // always request a refresh otherwise filled shapes
1134 // that were broken by a deletion and then reconnected by the undo
1135 // don't get filled until the user otherwise causes them to redraw
1136 DisplayController.requestRefresh(false);
1137 // ItemUtils.EnclosedCheck(_body);
1138 ItemUtils.Justify(this);
1139 }
1140
1141 public void redo()
1142 {
1143 boolean bReparse = false;
1144 boolean bRecalculate = false;
1145
1146 if (_redo.size() <= 0) {
1147 return;
1148 }
1149
1150 History redo = _redo.pop();
1151
1152 // System.out.println("Redoing: " + redo);
1153
1154 switch(redo.type) {
1155 case deletion:
1156 _undo.push(redo);
1157 for(Item i : redo.items) {
1158 this.removeItem(i);
1159 //_body.remove(i);
1160 bReparse |= i.hasOverlay();
1161 bRecalculate |= i.recalculateWhenChanged();
1162 if (i instanceof Line) {
1163 Line line = (Line) i;
1164 line.getStartItem().removeLine(line);
1165 line.getEndItem().removeLine(line);
1166 } else {
1167 i.setOffset(0, 0);
1168 }
1169 }
1170 break;
1171 case movement:
1172 ItemsList body = getBody(true);
1173 ItemsList changed = new ItemsList(body);
1174 changed.retainAll(redo.items);
1175 _undo.push(new History(changed, History.Type.movement));
1176 for(Item i : redo.items) {
1177 int index;
1178 if(i.isVisible() && (index = body.indexOf(i)) != -1) {
1179 body.set(index, i);
1180 }
1181 }
1182 break;
1183 }
1184
1185 change();
1186
1187 StandardGestureActions.refreshHighlights();
1188
1189 if (bReparse) {
1190 FrameUtils.Parse(this, false, false);
1191 } else {
1192 notifyObservers(bRecalculate);
1193 }
1194
1195 // always request a refresh otherwise filled shapes
1196 // that were broken by a deletion and then reconnected by the undo
1197 // don't get filled until the user otherwise causes them to redraw
1198 DisplayController.requestRefresh(false);
1199 // ItemUtils.EnclosedCheck(_body);
1200 ItemUtils.Justify(this);
1201 }
1202
1203 /**
1204 * Returns the frameset of this Frame
1205 *
1206 * @return The name of this Frame's frameset.
1207 */
1208 public String getFramesetName()
1209 {
1210 return _frameset;
1211 }
1212
1213 public String getName()
1214 {
1215 return getFramesetName() + _number;
1216 }
1217
1218 /**
1219 * Returns the format version of this Frame
1220 *
1221 * @return The version of this Frame.
1222 */
1223 public int getVersion()
1224 {
1225 return _version;
1226 }
1227
1228 public PermissionTriple getPermission() {
1229 return _permissionTriple;
1230 }
1231
1232 public UserAppliedPermission getUserAppliedPermission() {
1233 return getUserAppliedPermission(UserAppliedPermission.full);
1234 }
1235
1236 public UserAppliedPermission getUserAppliedPermission(UserAppliedPermission defaultPermission) {
1237 if (_permissionTriple == null) {
1238 return defaultPermission;
1239 }
1240
1241 return _permissionTriple.getPermission(_owner, getGroupMembers());
1242 }
1243
1244 public String getOwner()
1245 {
1246 return _owner;
1247 }
1248
1249 public String getDateCreated()
1250 {
1251 return _creationDate;
1252 }
1253
1254 public String getLastModifyUser()
1255 {
1256 return _modifiedUser;
1257 }
1258
1259 public String getLastModifyDate() {
1260 return _modifiedDate;
1261 }
1262
1263 public long getLastModifyPrecise() {
1264 return _modifiedDatePrecise;
1265 }
1266
1267 public String getFrozenDate()
1268 {
1269 return _frozenDate;
1270 }
1271
1272 public void setBackgroundColor(Colour back)
1273 {
1274 _background = back;
1275
1276 change();
1277
1278 if (this == DisplayController.getCurrentFrame()) {
1279 DisplayController.requestRefresh(false);
1280 }
1281 }
1282
1283 public Colour getBackgroundColor()
1284 {
1285 return _background;
1286 }
1287
1288 public Colour getPaintBackgroundColor()
1289 {
1290 // If null... return white
1291 if (_background == null) {
1292 return Item.DEFAULT_BACKGROUND;
1293 }
1294
1295 return _background;
1296 }
1297
1298 public void setForegroundColor(Colour front)
1299 {
1300 _foreground = front;
1301
1302 change();
1303 }
1304
1305 public Colour getForegroundColor()
1306 {
1307 return _foreground;
1308 }
1309
1310 public Colour getPaintForegroundColor()
1311 {
1312 final int GRAY = Colour.GREY.getBlue();
1313 final int THRESHOLD = Colour.FromComponent255(10);
1314
1315 if (_foreground == null) {
1316 Colour back = getPaintBackgroundColor();
1317 if (Math.abs(back.getRed() - GRAY) < THRESHOLD
1318 && Math.abs(back.getBlue() - GRAY) < THRESHOLD
1319 && Math.abs(back.getGreen() - GRAY) < THRESHOLD)
1320 {
1321 return Colour.WHITE;
1322 }
1323
1324 Colour fore = back.inverse();
1325
1326 return fore;
1327 }
1328
1329 return _foreground;
1330 }
1331
1332 @Override
1333 public String toString()
1334 {
1335 StringBuilder s = new StringBuilder();
1336 s.append(String.format("Name: %s%d%n", _frameset, _number));
1337 s.append(String.format("Version: %d%n", _version));
1338 // s.append(String.format("Permission: %s%n", _permission.toString()));
1339 // s.append(String.format("Owner: %s%n", _owner));
1340 // s.append(String.format("Date Created: %s%n", _creationDate));
1341 // s.append(String.format("Last Mod. User: %s%n", _modifiedUser));
1342 // s.append(String.format("Last Mod. Date: %s%n", _modifiedDate));
1343 s.append(String.format("Items: %d%n", getAllFrameItemsRaw().size()));
1344 return s.toString();
1345 }
1346
1347 public Text getTextAbove(Text current)
1348 {
1349 Collection<Text> currentTextItems = FrameUtils.getCurrentTextItems();
1350 List<Text> toCheck = new ArrayList<Text>();
1351
1352 if (currentTextItems.contains(current)) {
1353 toCheck.addAll(currentTextItems);
1354 } else {
1355 toCheck.addAll(getTextItems());
1356 }
1357
1358 // Make sure the items are sorted
1359 Collections.sort(toCheck);
1360
1361 int ind = toCheck.indexOf(current);
1362 if (ind == -1) {
1363 return null;
1364 }
1365
1366 // loop through all items above this one, return the first match
1367 for (int i = ind - 1; i >= 0; i--) {
1368 Text check = toCheck.get(i);
1369 if (FrameUtils.inSameColumn(check, current)) {
1370 return check;
1371 }
1372 }
1373
1374 return null;
1375 }
1376
1377 /**
1378 * Gets the text items that are in the same column and below a specified
1379 * item. Frame title and name are excluded from the column list.
1380 *
1381 * @param from
1382 * The Item to get the column for.
1383 */
1384 public List<Text> getColumn(Item from)
1385 {
1386 // Check that this item is on the current frame
1387 if (!getBody(true).contains(from)) {
1388 return null;
1389 }
1390
1391 if (from == null) {
1392 from = getLastNonAnnotationTextItem();
1393 }
1394
1395 if (from == null) {
1396 return null;
1397 }
1398
1399 // Get the enclosedItems
1400 Collection<Text> enclosed = FrameUtils.getCurrentTextItems();
1401 List<Text> toCheck = null;
1402
1403 if (enclosed.contains(from)) {
1404 toCheck = new ArrayList<Text>();
1405 toCheck.addAll(enclosed);
1406 } else {
1407 toCheck = getBodyTextItems(true);
1408 }
1409
1410 List<Text> column = new ArrayList<Text>();
1411
1412 if (toCheck.size() > 0) {
1413 // Make sure the items are sorted
1414 Collections.sort(toCheck);
1415
1416 // Create a list of items consisting of the item 'from' and all the
1417 // items below it which are also in the same column as it
1418 int index = toCheck.indexOf(from);
1419
1420 // If its the title index will be 0
1421 if (index < 0) {
1422 index = 0;
1423 }
1424
1425 for (int i = index; i < toCheck.size(); i++) {
1426 Text item = toCheck.get(i);
1427 if (FrameUtils.inSameColumn(from, item)) {
1428 column.add(item);
1429 }
1430 }
1431 }
1432
1433 return column;
1434 }
1435
1436 /**
1437 * Adds the given Vector to the list of vector Frames being drawn with this
1438 * Frame.
1439 *
1440 * @param vector
1441 * The Vector to add
1442 *
1443 * @throws NullPointerException
1444 * If overlay is null.
1445 */
1446 protected boolean addVector(Vector toAdd)
1447 {
1448 // make sure we dont add this frame as an overlay of itself
1449 if (toAdd.Frame == this) {
1450 return false;
1451 }
1452
1453 _vectors.add(toAdd);
1454
1455 // Items must be notified that they have been added or removed from this
1456 // frame via the vector...
1457 int maxX = 0;
1458 int maxY = 0;
1459
1460 HighlightMode mode = toAdd.Source.getHighlightMode();
1461 if (mode != HighlightMode.None) {
1462 mode = HighlightMode.Connected;
1463 }
1464
1465 Colour highlightColor = toAdd.Source.getHighlightColor();
1466
1467 for (Item i : ItemUtils.CopyItems(toAdd.Frame.getVectorItems(), toAdd)) {
1468 i.onParentStateChanged(new ItemParentStateChangedEvent(this, ItemParentStateChangedEvent.EVENT_TYPE_ADDED_VIA_OVERLAY, toAdd.permission));
1469 i.setEditTarget(toAdd.Source);
1470 i.setHighlightModeAndColour(mode, highlightColor);
1471 _vectorItems.add(i);
1472 i.invalidateAll();
1473 i.invalidateFill();
1474
1475 // Get the right most x and bottom most y pos
1476 int itemRight = i.getX() + i.getBoundsWidth();
1477 if (itemRight > maxX) {
1478 maxX = itemRight;
1479 }
1480
1481 int itemBottom = i.getY() + i.getBoundsHeight();
1482 if (itemBottom > maxY) {
1483 maxY = itemBottom;
1484 }
1485 }
1486
1487 toAdd.setSize(maxX, maxY);
1488
1489 return true;
1490 }
1491
1492 public Collection<Vector> getVectors()
1493 {
1494 Collection<Vector> l = new LinkedList<Vector>();
1495 l.addAll(_vectors);
1496 return l;
1497 }
1498
1499 public Collection<Overlay> getOverlays()
1500 {
1501 return new LinkedList<Overlay>(_overlays.keySet());
1502 }
1503
1504 /**
1505 * @return All vectors seen by this frame (including its vector's vectors).
1506 */
1507 public List<Vector> getVectorsDeep()
1508 {
1509 List<Vector> l = new LinkedList<Vector>();
1510 getVectorsDeep(l, this, new LinkedList<Frame>());
1511 return l;
1512 }
1513
1514 private boolean getVectorsDeep(List<Vector> vectors, Frame vector, List<Frame> seenVectors)
1515 {
1516 if (seenVectors.contains(vector)) {
1517 return false;
1518 }
1519
1520 seenVectors.add(vector);
1521
1522 for (Vector v : vector.getVectors()) {
1523 if (getVectorsDeep(vectors, v.Frame, seenVectors)) {
1524 vectors.add(v);
1525 }
1526 }
1527
1528 return true;
1529 }
1530
1531 public List<Overlay> getOverlaysDeep()
1532 {
1533 List<Overlay> ret = new LinkedList<Overlay>();
1534
1535 getOverlaysDeep(ret, new LinkedList<Frame>());
1536
1537 return ret;
1538 }
1539
1540 private boolean getOverlaysDeep(List<Overlay> overlays, List<Frame> seenOverlays)
1541 {
1542 if (seenOverlays.contains(this)) {
1543 return false;
1544 }
1545
1546 seenOverlays.add(this);
1547
1548 for (Overlay o : this.getOverlays()) {
1549 if (o.Frame.getOverlaysDeep(overlays, seenOverlays)) {
1550 overlays.add(o);
1551 }
1552 }
1553 return true;
1554 }
1555
1556 /**
1557 * Recursive function similar to AddAllOverlayItems.
1558 *
1559 * @param widgets
1560 * The collection the widgets will be added to
1561 * @param overlay
1562 * An "overlay" frame - this initially will be the parent frame
1563 * @param seenOverlays
1564 * Used for state in the recursion stack. Pass as an empty
1565 * (non-null) list.
1566 */
1567 public List<Widget> getAllOverlayWidgets()
1568 {
1569 List<Widget> widgets = new LinkedList<Widget>();
1570
1571 for (Overlay o : getOverlaysDeep()) {
1572 widgets.addAll(o.Frame.getInteractiveWidgets());
1573 }
1574
1575 return widgets;
1576 }
1577
1578 /**
1579 * Gets the overlay on this frame which owns the given item.
1580 *
1581 * @param item
1582 * The item - must not be null.
1583 * @return The overlay that contains the item. Null if no overlay owns the
1584 * item.
1585 */
1586 public Overlay getOverlayOwner(Item item)
1587 {
1588 if (item == null) {
1589 throw new NullPointerException("item");
1590 }
1591
1592 for (Overlay l : getOverlays()) {
1593 if (item.getParent() == l.Frame) {
1594 return l;
1595 }
1596 }
1597
1598 // TODO return the correct vector... not just the first vector matching
1599 // the vector frame
1600 for (Vector v : getVectors()) {
1601 if (item.getParent() == v.Frame) {
1602 return v;
1603 }
1604 }
1605
1606 return null;
1607 }
1608
1609 public void clearVectors()
1610 {
1611 _vectors.clear();
1612
1613 for (Item i : _vectorItems) { // TODO: Rethink where this should live
1614 i.invalidateAll();
1615 i.invalidateFill();
1616 }
1617 _vectorItems.clear();
1618
1619 }
1620
1621 protected boolean removeVector(Vector toRemove)
1622 {
1623 if (!_vectors.remove(toRemove)) {
1624 return false;
1625 }
1626
1627 for (Item i : toRemove.Frame.getVectorItems()) {
1628 i.invalidateAll();
1629 i.invalidateFill();
1630 _vectorItems.remove(i);
1631 i.onParentStateChanged(new ItemParentStateChangedEvent(this,
1632 ItemParentStateChangedEvent.EVENT_TYPE_REMOVED_VIA_OVERLAY,
1633 toRemove.permission));
1634
1635 }
1636
1637 return true;
1638 }
1639
1640 public void clearOverlays()
1641 {
1642 for (Overlay o : _overlays.keySet()) {
1643 for (Item i : o.Frame.getSortedItems()) {
1644 i.onParentStateChanged(new ItemParentStateChangedEvent(
1645 this,
1646 ItemParentStateChangedEvent.EVENT_TYPE_REMOVED_VIA_OVERLAY,
1647 o.permission));
1648 }
1649 }
1650 _overlayItems.clear();
1651 _overlays.clear();
1652 assert (_overlays.isEmpty());
1653 }
1654
1655 protected boolean removeOverlay(Frame f)
1656 {
1657 for (Overlay o : _overlays.keySet()) {
1658 if (o.Frame == f) {
1659 _overlays.remove(o);
1660
1661 for (Item i : f.getSortedItems()) {
1662 _overlayItems.remove(i);
1663 i.onParentStateChanged(new ItemParentStateChangedEvent(
1664 this,
1665 ItemParentStateChangedEvent.EVENT_TYPE_REMOVED_VIA_OVERLAY,
1666 o.permission));
1667 }
1668
1669 return true;
1670 }
1671 }
1672
1673 return false;
1674 }
1675
1676 public void addAllVectors(List<Vector> vectors)
1677 {
1678 for (Vector v : vectors) {
1679 addVector(v);
1680 }
1681 }
1682
1683 public void addAllOverlays(Collection<Overlay> overlays)
1684 {
1685 for (Overlay o : overlays) {
1686 addOverlay(o);
1687 }
1688 }
1689
1690 protected boolean addOverlay(Overlay toAdd)
1691 {
1692 // make sure we dont add this frame as an overlay of itself
1693 if (toAdd.Frame == this) {
1694 return false;
1695 }
1696
1697 // Dont add the overlay if there is already one for this frame
1698 if (_overlays.values().contains(toAdd.Frame)) {
1699 return false;
1700 }
1701
1702 // Add the overlay to the map of overlays on this frame
1703 _overlays.put(toAdd, toAdd.Frame);
1704
1705 // Add all the overlays from the overlay frame to this frame
1706 // TODO: Can this cause a recursion loop? If A and B are overlays of each other? cts16
1707 for (Overlay o : toAdd.Frame.getOverlays()) {
1708 addOverlay(o);
1709 }
1710
1711 // Add all the vectors from the overlay frame to this frame
1712 for (Vector v : toAdd.Frame.getVectors()) {
1713 addVector(v);
1714 }
1715
1716 // Now add the items for this overlay
1717 UserAppliedPermission permission = UserAppliedPermission.min(toAdd.Frame.getUserAppliedPermission(), toAdd.permission);
1718
1719 // Items must be notified that they have been added or removed from this
1720 // frame via the overlay...
1721 for (Item i : toAdd.Frame.getVisibleItems()) {
1722 i.onParentStateChanged(new ItemParentStateChangedEvent(this, ItemParentStateChangedEvent.EVENT_TYPE_ADDED_VIA_OVERLAY, permission));
1723 _overlayItems.add(i);
1724 }
1725
1726 return true;
1727 }
1728
1729 @Override
1730 public boolean equals(Object o)
1731 {
1732 if (o instanceof String) {
1733 return (String.CASE_INSENSITIVE_ORDER.compare((String) o, getName()) == 0);
1734 }
1735
1736 if (o instanceof Frame) {
1737 return getName().equals(((Frame) o).getName());
1738 }
1739
1740 return super.equals(o);
1741 }
1742
1743 /**
1744 * Merge one frames contents into another.
1745 *
1746 * @param toMergeWith
1747 */
1748 private void merge(Frame toMergeWith)
1749 {
1750 if (toMergeWith == null) {
1751 return;
1752 }
1753
1754 List<Item> copies = ItemUtils.CopyItems(toMergeWith.getSortedItems());
1755 copies.remove(toMergeWith.getNameItem());
1756
1757 for (Item i : copies) {
1758 if (i.getID() >= 0) {
1759 i.setID(this.getNextItemID());
1760 addItem(i);
1761 }
1762 }
1763 }
1764
1765 /**
1766 * This method is for merging frames or setting frame attributes via
1767 * injecting a text item into the frameName item.
1768 *
1769 * @param toMerge
1770 * @return the items that cant be merged
1771 */
1772 public List<Item> merge(List<Item> toMerge)
1773 {
1774 ArrayList<Item> remain = new ArrayList<Item>(0);
1775
1776 for (Item i : toMerge) {
1777 if (!(i instanceof Text)) {
1778 remain.add(i);
1779 } else {
1780 if (!AttributeUtils.setAttribute(this, (Text) i)) {
1781 if (i.getLink() != null) {
1782 merge(FrameIO.LoadFrame(i.getAbsoluteLink()));
1783 } else if (FrameIO.isValidFrameName(((Text) i).getFirstLine())) {
1784 // If we get hear we are merging frames
1785 merge(FrameIO.LoadFrame(((Text) i).getFirstLine()));
1786 }
1787 }
1788 }
1789 }
1790
1791 return remain;
1792 }
1793
1794 /**
1795 * Removes all non-title non-annotation items from this Frame. All removed
1796 * items are added to the backup-stack.
1797 */
1798 @Deprecated
1799 public void clearDeprecated(boolean keepAnnotations)
1800 {
1801 ItemsList newBody = new ItemsList();
1802
1803 Item title = getTitleItem();
1804
1805 if (title != null) {
1806 newBody.add(title);
1807 _body.remove(title);
1808 }
1809
1810 if (keepAnnotations) {
1811 for (Item i : _body) {
1812 if (i.isAnnotation()) {
1813 newBody.add(i);
1814 }
1815 }
1816 }
1817
1818 _body.removeAll(newBody);
1819 addToUndoDelete(_body);
1820 _body = newBody;
1821 change();
1822
1823 if (!keepAnnotations && _annotations != null) {
1824 _annotations.clear();
1825 }
1826 }
1827
1828 /**
1829 * Removes all items from the Frame except the Title Item and optionally the annotations.
1830 * All removed items are added to the backup-stack.
1831 *
1832 * @param keepAnnotations true is annotations are not to be removed from the frame.
1833 */
1834 public void clear(boolean keepAnnotations) {
1835 ItemsList body = getBody(true);
1836 ItemsList deleted = new ItemsList();
1837
1838 for (Item bodyItem: body) {
1839 boolean isAnnotationToKeep = bodyItem.isAnnotation() && keepAnnotations;
1840 boolean isFrameTitle = bodyItem.isFrameTitle();
1841 boolean isToBeRetained = isFrameTitle || isAnnotationToKeep;
1842 if (isToBeRetained) {
1843 continue;
1844 }
1845
1846 this.removeItem(bodyItem);
1847 deleted.add(bodyItem);
1848 }
1849
1850 addToUndoDelete(deleted);
1851 change();
1852
1853 if (!keepAnnotations && _annotations != null) {
1854 _annotations.clear();
1855 }
1856 }
1857
1858 /**
1859 * Creates a new text item with the given text.
1860 *
1861 * @param text
1862 * @return
1863 */
1864 public Text createNewText(String text)
1865 {
1866 Text t = createBlankText(text);
1867 t.setText(text);
1868 return t;
1869 }
1870
1871 /**
1872 * Creates a new Text Item with no text. The newly created Item is a copy
1873 * the ItemTemplate if one is present, and inherits all the attributes of
1874 * the Template
1875 *
1876 * @return The newly created Text Item
1877 */
1878 public Text createBlankText(String templateType) {
1879 File file = new File(getFramePathReal());
1880 long fileLastModified = file.lastModified();
1881 long frameLastModified = this.getLastModifyPrecise();
1882 //if (ExpReader.getVersion(getFramePathReal()) > this._version) {
1883 if (fileLastModified > frameLastModified) {
1884 GestureType refreshGestureType = StandardGestureActions.getInstance().gestureType(StandardGestureType.REFRESH);
1885 RefreshGestureData refreshGestureData = new RefreshGestureData(true, false);
1886 try {
1887 StandardGestureActions.getInstance().onGesture(new Gesture(refreshGestureType, refreshGestureData));
1888 EcosystemManager.getMiscManager().beep();
1889 } catch (NullPointerException e) {
1890 //Detected more recent data on file system than on Frame in memory. Unfortunately not in a position to cause a refresh.
1891 }
1892 }
1893
1894 SessionStats.CreatedText();
1895 Text t;
1896
1897 if (templateType.length() == 0) {
1898 t = getItemTemplate().copy();
1899 } else {
1900 t = getItemTemplate(templateType.charAt(0));
1901 }
1902
1903 // reset attributes
1904 t.setID(getNextItemID());
1905 t.setPosition(DisplayController.getMousePosition());
1906 t.setText("");
1907 t.setParent(this);
1908
1909 // Set the width if the template doesnt have a width
1910 // Make it the width of the page
1911 // t.setMaxWidth(FrameGraphics.getMaxFrameSize().width);
1912 // if (t.getWidth() <= 0) {
1913 // String maxWidthString = getAnnotationValue("maxwidth");
1914 // int width = FrameGraphics.getMaxFrameSize().width;
1915 // if (maxWidthString != null) {
1916 // try {
1917 // width = Math.min(width, Integer.parseInt(maxWidthString));
1918 // } catch (NumberFormatException nfe) {
1919 // }
1920 // }
1921 //
1922 // t.setRightMargin(width);
1923 // }
1924 addItem(t);
1925 return t;
1926 }
1927
1928 /**
1929 * Returns the data associated with the frame.
1930 * @return
1931 */
1932 public List<String> getData() {
1933 return _frameData;
1934 }
1935
1936 /**
1937 * Adds a piece of data to be associated with the frame.
1938 * @param dataItem
1939 */
1940 public void addToData(String dataItem) {
1941 if (dataItem != null) {
1942 if (_frameData == null)
1943 _frameData = new LinkedList<String>();
1944 _frameData.add(dataItem);
1945 }
1946 }
1947
1948 /**
1949 * Returns the path (String) to the .exp file that this Frame represents.
1950 * This follows redirects, meaning that it provides the actual file from which
1951 * the frames data is drawn from.
1952 * @return The path to the .exp file that this Frame represents
1953 * @see getFramePathLogical
1954 * @see getFramesetPath
1955 */
1956 public String getFramePathReal() {
1957 String framesetPath = getFramesetPath();
1958 String redirect = ExpReader.redirectTo(getFramePathLogical());
1959
1960 if (redirect == null) {
1961 return getFramePathLogical();
1962 }
1963
1964 while (redirect != null) {
1965 framesetPath = getFramesetPath() + redirect;
1966 redirect = ExpReader.redirectTo(redirect);
1967 }
1968 return framesetPath;
1969 }
1970
1971 /**
1972 * Returns the path (String) to the .exp file that this Frame represents.
1973 * Does not follow redirects, opting to instead provide the logical path to this file.
1974 * @return The path to the .exp file that this Frame represents
1975 * @see getFramePathReal
1976 * @see getFramesetPath
1977 */
1978 public String getFramePathLogical() {
1979 return getFramesetPath() + this.getNumber() + ExpReader.EXTENTION;
1980 }
1981
1982 /**
1983 * Returns the path (String) to the frameset directory that the file that this Frame represents is contained within.
1984 * @return The path to this Frames frameset directory
1985 * @see getFramesetPathLogical
1986 * @see getFramesetPathReal
1987 */
1988 public String getFramesetPath() {
1989 return Paths.get(this.getPath()).resolve(this.getFramesetName()).toString() + File.separator;
1990 }
1991
1992 public Item createDot() {
1993 Item dot = new Dot(DisplayController.getMouseX(), DisplayController.getMouseY(), getNextItemID());
1994
1995 Item template = getTemplate(_dotTemplate, ItemUtils.TAG_DOT_TEMPLATE);
1996 float thickness = template.getThickness();
1997 if (thickness > 0) {
1998 dot.setThickness(template.getThickness());
1999 }
2000 if (template.getLinePattern() != null) {
2001 dot.setLinePattern(template.getLinePattern());
2002 }
2003 dot.setColor(template.getColor());
2004 dot.setFillColor(template.getFillColor());
2005 // reset attributes
2006 dot.setParent(this);
2007 dot.setOwner(template.getOwner());
2008 return dot;
2009 }
2010
2011 private Text getTemplate(Text defaultTemplate, int templateTag)
2012 {
2013 Text t = null;
2014
2015 // check for an updated template...
2016 for (Item i : this.getSortedItems()) {
2017 if (ItemUtils.startsWithTag(i, templateTag)) {
2018 t = (Text) i;
2019 break;
2020 }
2021 }
2022
2023 if (t == null) {
2024 if (defaultTemplate == null) {
2025 return null;
2026 }
2027
2028 t = defaultTemplate;
2029 }
2030
2031 // If the item is linked apply any attribute pairs on the child frame
2032 String link = t.getAbsoluteLink();
2033
2034 // need to get link first because copy doesnt copy the link
2035 t = t.copy();
2036 // If the template does not have a owner then it should be set to the current user.
2037 if (t.getOwner() == null) {
2038 t.setOwner(UserSettings.UserName.get());
2039 }
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 = getSortedItems();
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 = getSortedItems();
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.getBody(false)) {
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 getSortedItems(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> getItemsToSave() {
2604 return getItemsToSave(BodyType.PrimaryBody);
2605 }
2606
2607 /**
2608 * Returns a list of items for the specified BodyType.
2609 *
2610 * Asking for the primary or surrogate items gives you exactly those.
2611 *
2612 * Asking for the body items is a weird case because the body list is
2613 * transitory. Therefore, when asking for the body items, this
2614 * function assumes that you want all items, reguardless of if they
2615 * are primaries or surrogates. As of 20/08/2019, there are no places
2616 * in the code that asks for the body items to save.
2617 * @param type
2618 * @return
2619 */
2620 public List<Item> getItemsToSave(BodyType type) {
2621 assert(!type.equals(BodyType.BodyDisplay));
2622 switch (type) {
2623 case SurrogateBody:
2624 return getItemsToSave(_surrogateItemsBody);
2625 case BodyDisplay:
2626 return getItemsToSave(new ItemsList(getAllFrameItemsRaw()));
2627 case PrimaryBody:
2628 default:
2629 return getItemsToSave(_primaryItemsBody);
2630 }
2631 }
2632
2633 private List<Item> getItemsToSave(ItemsList body) {
2634 body.sort();
2635 List<Widget> seenWidgets = new ArrayList<Widget>();
2636
2637 List<Item> toSave = new ArrayList<Item>();
2638
2639 for (Item item: body) {
2640
2641 if (item == null || item.dontSave()) {
2642 continue;
2643 }
2644
2645 if (item instanceof WidgetCorner) {
2646 // Save the widget source.
2647 // Each widget has multiple WidgetCorner's..ignore them if we already have the source.
2648 Widget iw = ((WidgetCorner) item).getWidgetSource();
2649 if (seenWidgets.contains(iw)) { continue; }
2650 seenWidgets.add(iw);
2651 toSave.add(iw.getSource());
2652 } else if (item instanceof XRayable) {
2653 // XRayable Items have their sources saved.
2654 XRayable x = (XRayable) item;
2655 toSave.addAll(x.getItemsToSave());
2656 } else if (item.hasEnclosures()) {
2657 // Deals with Circle objects only?
2658 continue;
2659 } else {
2660 toSave.add(item);
2661 }
2662 }
2663
2664 return toSave;
2665 }
2666
2667 public Collection<Item> getOverlayItems()
2668 {
2669 return _overlayItems;
2670 }
2671
2672 /**
2673 * Returns true if this frame has and overlays for the specified frame.
2674 *
2675 * @param frame
2676 * @return
2677 */
2678 public boolean hasOverlay(Frame frame)
2679 {
2680 return _overlays.containsValue(frame);
2681 }
2682
2683 public Collection<Item> getAllItems() {
2684 ItemsList allItems = new ItemsList(getBody(true));
2685
2686 allItems.addAll(_overlayItems);
2687 allItems.addAll(_vectorItems);
2688 return allItems.underlying();
2689 }
2690
2691 public Collection<Item> getVectorItems()
2692 {
2693 Collection<Item> vectorItems = new LinkedHashSet<Item>(_vectorItems);
2694 vectorItems.addAll(getNonAnnotationItems(false));
2695 return vectorItems;
2696 }
2697
2698 /**
2699 * Gets a list of all the text items on the frame.
2700 *
2701 * @return
2702 */
2703 public Collection<Text> getTextItems()
2704 {
2705 Collection<Text> textItems = new ArrayList<Text>();
2706
2707 for (Item i : getSortedItems(true)) {
2708 // only add up normal body text items
2709 if ((i instanceof Text)) {
2710 textItems.add((Text) i);
2711 }
2712 }
2713
2714 return textItems;
2715 }
2716
2717 public Text getAnnotation(String annotation)
2718 {
2719 if (_annotations == null) {
2720 refreshAnnotationList();
2721 }
2722
2723 return _annotations.get(annotation.toLowerCase());
2724 }
2725
2726 public void recalculate()
2727 {
2728 for (Item i : getSortedItems()) {
2729 if (i.hasFormula() && !i.isAnnotation()) {
2730 i.calculate(i.getFormula());
2731 }
2732 }
2733 }
2734
2735 public void removeObserver(FrameObserver observer)
2736 {
2737 _observers.remove(observer);
2738 }
2739
2740 public void addObserver(FrameObserver observer)
2741 {
2742 _observers.add(observer);
2743 }
2744
2745 public void clearObservers()
2746 {
2747 for (FrameObserver fl : _observers) {
2748 fl.removeSubject(this);
2749 }
2750
2751 // The frame listener will call the frames removeListener method
2752 assert (_observers.size() == 0);
2753 }
2754
2755 public Collection<Text> getNonAnnotationText(boolean removeTitle)
2756 {
2757 Collection<Text> items = new LinkedHashSet<Text>();
2758
2759 for (Item i : getSortedItems(true)) {
2760 // only add up normal body text items
2761 if (i instanceof Text && !i.isAnnotation()) {
2762 items.add((Text) i);
2763 }
2764 }
2765
2766 if (removeTitle) {
2767 items.remove(getTitleItem());
2768 }
2769
2770 return items;
2771 }
2772
2773 @Deprecated
2774 public void disposeDeprecated() {
2775 clearObservers();
2776
2777 for (Item i : _body) {
2778 i.dispose();
2779 }
2780
2781 _frameName.dispose();
2782 _body = null;
2783 _frameName = null;
2784 }
2785
2786 /**
2787 * Disposes off all references associated with this frame.
2788 * This operation is NOT REVERSEABLE through the history.
2789 */
2790 public void dispose() {
2791 clearObservers();
2792
2793 List<Item> allFrameItems = getAllFrameItemsRaw();
2794
2795 for (Item i: allFrameItems) {
2796 i.dispose();
2797 }
2798
2799 _frameName.dispose();
2800 _frameName = null;
2801 getBody(false).clear();
2802 getPrimaryBody().clear();
2803 getSurrogateBody().clear();
2804 }
2805
2806 public void parse()
2807 {
2808 for (Overlay o : getOverlays()) {
2809 o.Frame.parse();
2810 }
2811
2812 // Must parse the frame AFTER the overlays
2813 FrameUtils.Parse(this);
2814 }
2815
2816 public void setPath(String path)
2817 {
2818 this.path = path;
2819 }
2820
2821 public String getPath()
2822 {
2823 return path;
2824 }
2825
2826 public void setLocal(boolean isLocal)
2827 {
2828 this._isLocal = isLocal;
2829 }
2830
2831 public boolean isLocal()
2832 {
2833 return _isLocal;
2834 }
2835
2836 public String getExportFileTagValue()
2837 {
2838 return getAnnotationValue("file");
2839 }
2840
2841 public void assertEquals(Frame frame2)
2842 {
2843 // Check that all the items on the frame are the same
2844 List<Item> items1 = getVisibleItems();
2845 List<Item> items2 = frame2.getVisibleItems();
2846
2847 if (items1.size() != items2.size()) {
2848 throw new UnitTestFailedException(items1.size() + " items", items2.size() + " items");
2849 } else {
2850 for (int i = 0; i < items1.size(); i++) {
2851 Item i1 = items1.get(i);
2852 Item i2 = items2.get(i);
2853 String s1 = i1.getText();
2854 String s2 = i2.getText();
2855 if (!s1.equals(s2)) {
2856 throw new UnitTestFailedException(s1, s2);
2857 }
2858 }
2859 }
2860 }
2861
2862 public boolean hasObservers()
2863 {
2864 return _observers != null && _observers.size() > 0;
2865 }
2866
2867 public Collection<Item> getBodyItemsWithInsufficientPermissions() {
2868 return _bodyHiddenDueToPermissions.underlying();
2869 }
2870
2871 public void moveItemToBodyHiddenDueToPermission(final Item i) {
2872 getBody(true).remove(i);
2873 _bodyHiddenDueToPermissions.add(i);
2874 }
2875
2876 public void moveItemFromBodyHiddenDueToPermission(Item i, PermissionTriple newPermission) {
2877 if (_bodyHiddenDueToPermissions.contains(i)) {
2878 _bodyHiddenDueToPermissions.remove(i);
2879 i.setPermission(newPermission);
2880 getBody(true).add(i);
2881 }
2882 }
2883
2884 public Collection<? extends Item> getInteractableItems() {
2885 /*
2886 * TODO: Cache the interactableItems list so we dont have to recreate it
2887 * every time this method is called
2888 */
2889 if (_interactableItems.size() > 0) {
2890 return _interactableItems;
2891 }
2892
2893 for (Item i : getBody(false)) {
2894 if (i == null) {
2895 continue;
2896 }
2897 if (i.isVisible()) {
2898 _interactableItems.add(i);
2899 } else {
2900 Collection<? extends XRayable> enclosures = i.getEnclosures();
2901 if (enclosures != null && !enclosures.isEmpty()) {
2902 Iterator<? extends XRayable> iterator = enclosures.iterator();
2903 while (iterator.hasNext()) {
2904 _interactableItems.add(iterator.next());
2905 }
2906 }
2907 }
2908 }
2909
2910 for (Item i : _overlayItems) {
2911 if (i.hasPermission(UserAppliedPermission.followLinks)) {
2912 _interactableItems.add(i);
2913 }
2914 }
2915
2916 for (Item i : _vectorItems) {
2917 if (i.hasPermission(UserAppliedPermission.none)) {
2918 _interactableItems.add(i);
2919 }
2920 }
2921
2922 return _interactableItems;
2923 }
2924
2925 public String getEncryptionLabel() {
2926 return _encryptionLabel;
2927 }
2928
2929 public void setEncryptionLabel(String label) {
2930 this.setChanged(true);
2931 _encryptionLabel = label;
2932 }
2933
2934 public EncryptionPermissionTriple getEncryptionPermission() {
2935 return _encPermissionTriple;
2936 }
2937
2938 public void setEncryptionPermission(EncryptionPermissionTriple p) {
2939 _encPermissionTriple = p;
2940 }
2941
2942 public String getGroup() {
2943 return _groupFrameName;
2944 }
2945
2946 public void setGroup(String _groupFrame) {
2947 this._groupFrameName = _groupFrame;
2948 this._groupFrame = null;
2949 }
2950
2951 public Frame getGroupFrame() {
2952 if (this._groupFrame != null) {
2953 return this._groupFrame;
2954 } else {
2955 this._groupFrame = FrameIO.LoadFrame(this._groupFrameName + 1, FrameIO.GROUP_PATH);
2956 return this._groupFrame;
2957 }
2958 }
2959
2960 public void setGroupFrame(Frame frame) {
2961 this._groupFrame = frame;
2962 }
2963
2964 public List<String> getGroupMembers() {
2965 List<String> members = new ArrayList<String>();
2966 if (getGroupFrame() != null) {
2967 Collection<Text> textItems = getGroupFrame().getTextItems();
2968 Stream<Text> memberLists = textItems.stream().filter(t ->
2969 t.getText().toLowerCase().startsWith("@owner: ") ||
2970 t.getText().toLowerCase().startsWith("@members: "));
2971 for(Text t: memberLists.collect(Collectors.toList())) {
2972 if (t.getText().toLowerCase().startsWith("@owner: ")) {
2973 members.add(t.getText().substring(8));
2974 } else if (t.getText().toLowerCase().startsWith("@members: ")) {
2975 //10
2976 String[] split = t.getText().substring(10).split(",");
2977 for (String m: split) {
2978 members.add(m.trim());
2979 }
2980 }
2981 }
2982 }
2983 return members;
2984 }
2985
2986 public boolean hasSurrogates() {
2987 return !_surrogateItemsBody.isEmpty();
2988 }
2989
2990 private boolean meetsVisibilityRequirements(boolean requireVisible, Item i) {
2991 return i.isVisible() || (!requireVisible && !i.isDeleted());
2992 }
2993
2994 private static final class History {
2995
2996 public enum Type {
2997 deletion,
2998 movement
2999 }
3000
3001 public final ItemsList items;
3002
3003 public final Type type;
3004
3005 public History(ItemsList changed, Type type)
3006 {
3007 this.items = new ItemsList(changed);
3008 this.type = type;
3009 }
3010
3011 @Override
3012 public String toString()
3013 {
3014 return this.type.toString() + ":\n" + this.items.toString();
3015 }
3016 }
3017
3018 protected boolean hasAnnotations() {
3019 return _annotations != null && _annotations.size() > 0;
3020 }
3021
3022 public ItemsList getBody(boolean respectSurrogateMode) {
3023 if (respectSurrogateMode) { ensureBody(); }
3024 return _body;
3025 }
3026
3027 private void ensureBody() {
3028 List<String> accessibleLabelsNames = Label.getAccessibleLabelsNames(getPrimaryBody());
3029 if (!accessibleLabelsNames.equals(labelsOnLastBodySet)) {
3030 this.parse();
3031 }
3032 }
3033
3034 protected void setBody(List<Item> newBody, List<String> labelsOnSet) {
3035 _body.clear();
3036 _body.addAll(newBody);
3037 this.labelsOnLastBodySet = labelsOnSet;
3038 }
3039
3040 protected ItemsList getPrimaryBody() {
3041 return _primaryItemsBody;
3042 }
3043 protected ItemsList getSurrogateBody() {
3044 return _surrogateItemsBody;
3045 }
3046
3047
3048 /**
3049 * Gets all the items on the frame, regardless of whether they are primary or surrogate items.
3050 *
3051 * Bryce says: This function will likely only ever be used inside Frame itself, as callers from
3052 * outside Frame should care about what the state of the Frame.
3053 * @return
3054 */
3055 private List<Item> getAllFrameItemsRaw() {
3056 Collection<Item> primaries = getPrimaryBody().underlying();
3057 Collection<Item> surrogateBody = getSurrogateBody().underlying();
3058 primaries.addAll(surrogateBody);
3059 List<Item> allFrameItems = primaries.stream().distinct().collect(Collectors.toList());
3060 return allFrameItems;
3061 }
3062}
Note: See TracBrowser for help on using the repository browser.