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

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

When deleting a picture on a expedite frame; if shift is held down, then the associated image file is now moved to the trash directory (FrameIO.TRASH_PATH).
When undoing the deletion of a picture, the associated image file is restored.

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