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

Last change on this file since 998 was 998, checked in by davidb, 8 years ago

Added new versions of last commit that have been tidied up

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