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

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

Renamed Frame.getItems() to Frame.getSortedItems() to better represent its functionality.

-> org.apollo.ApolloGestureActions
-> org.apollo.ApolloSystem
-> org.expeditee.actions.Actions
-> org.expeditee.actions.Debug
-> org.expeditee.actions.ExploratorySearchActions
-> org.expeditee.actions.JfxBrowserActions
-> org.expeditee.actions.Misc
-> org.expeditee.actions.Navigation
-> org.expeditee.actions.ScriptBase
-> org.expeditee.actions.Simple
-> org.expeditee.agents.ComputeTree
-> org.expeditee.agents.CopyTree
-> org.expeditee.agents.DisplayComet
-> org.expeditee.agents.DisplayTree
-> org.expeditee.agents.DisplayTreeLeaves
-> org.expeditee.agents.GraphFramesetLinks
-> org.expeditee.agents.TreeProcessor
-> org.expeditee.gio.gesture.StandardGestureActions
-> org.expeditee.gui.DisplayController
-> org.expeditee.gui.FrameCreator
-> org.expeditee.gui.FrameIO
-> org.expeditee.io.DefaultTreeWriter
-> org.expeditee.io.JavaWriter
-> org.expeditee.io.PDF2Writer
-> org.expeditee.io.TXTWriter
-> org.expeditee.io.WebParser
-> org.expeditee.io.flowlayout.XGroupItem
-> org.expeditee.items.Dot
-> org.expeditee.items.Item
-> org.expeditee.items.ItemUtils
-> org.expeditee.network.FrameShare
-> org.expeditee.stats.TreeStats


Created ItemsList class to wrap ArrayList<Item>. Frames now use this new class to store its body list (used for display) as well as its primaryBody and surrogateBody.

-> org.expeditee.agents.Format
-> org.expeditee.agents.HFormat
-> org.expeditee.gio.gesture.StandardGestureActions
-> org.expeditee.gui.Frame
-> org.expeditee.gui.FrameUtils


Refactorted Frame.setResort(bool) to Frame.invalidateSorted() to better function how it is intended to with a more accurate name.

-> org.expeditee.agents.Sort


When writing out .exp files and getting attributes to respond to LEFT + RIGHT click, boolean items are by default true. This has always been the case. An ammendment to this is that defaults can now be established.
Also added 'EnterClick' functionality. If cursored over a item with this property and you press enter, it acts as if you have clicked on it instead.

-> org.expeditee.assets.resources-public.framesets.authentication.1.exp to 6.exp
-> org.expeditee.gio.gesture.StandardGestureActions
-> org.expeditee.gio.input.KBMInputEvent
-> org.expeditee.gio.javafx.JavaFXConversions
-> org.expeditee.gio.swing.SwingConversions
-> org.expeditee.gui.AttributeUtils
-> org.expeditee.io.Conversion
-> org.expeditee.io.DefaultFrameWriter
-> org.expeditee.items.Item


Fixed a bug caused by calling Math.abs on Integer.MIN_VALUE returning unexpected result. Due to zero being a thing, you cannot represent Math.abs(Integer.MIN_VALUE) in a Integer object. The solution is to use Integer.MIN_VALUE + 1 instead of Integer.MIN_VALUE.

-> org.expeditee.core.bounds.CombinationBounds
-> org.expeditee.io.flowlayout.DimensionExtent


Recoded the contains function in EllipticalBounds so that intersection tests containing circles work correctly.

-> org.expeditee.core.bounds.EllipticalBounds


Added toString() to PolygonBounds to allow for useful printing during debugging.

-> org.expeditee.core.bounds.PolygonBounds

Implemented Surrogate Mode!

-> org.expeditee.encryption.io.EncryptedExpReader
-> org.expeditee.encryption.io.EncryptedExpWriter
-> org.expeditee.encryption.items.surrogates.EncryptionDetail
-> org.expeditee.encryption.items.surrogates.Label
-> org.expeditee.gui.FrameUtils
-> org.expeditee.gui.ItemsList
-> org.expeditee.items.Item
-> org.expeditee.items.Text


???? Use Integer.MAX_VALUE cast to a float instead of Float.MAX_VALUE. This fixed some bug which I cannot remember.

-> org.expeditee.gio.TextLayoutManager
-> org.expeditee.gio.swing.SwingTextLayoutManager


Improved solution for dealing with the F10 key taking focus away from Expeditee due to it being a assessibility key.

-> org.expeditee.gio.swing.SwingInputManager


Renamed variable visibleItems in FrameGraphics.paintFrame to itemsToPaintCanditates to better represent functional intent.

-> org.expeditee.gui.FrameGraphics


Improved checking for if personal resources exist before recreating them

-> org.expeditee.gui.FrameIO


Repeated messages to message bay now have a visual feedback instead of just a beep. This visual feedback is in the form of a count of the amount of times it has repeated.

-> org.expeditee.gui.MessageBay


Updated comment on the Vector class to explain what vectors are.

-> org.expeditee.gui.Vector


Added constants to represent all of the property keys in DefaultFrameReader and DefaultFrameWriter.

-> org.expeditee.io.DefaultFrameReader
-> org.expeditee.io.DefaultFrameWriter


Updated the KeyList setting to be more heirarcial with how users will store their Secrets.

-> org.expeditee.settings.identity.secrets.KeyList

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