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

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

Changed surrogates to work the way discussed with David. EncryptedExpReader/Writer updated to work with this.

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