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

Last change on this file since 1509 was 1509, checked in by bnemhaus, 4 years ago

ItemEncryptionPermission is now respected.

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