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

Last change on this file since 1488 was 1488, checked in by davidb, 4 years ago

added helper method

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