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

Last change on this file since 944 was 923, checked in by jts21, 10 years ago

Justify text items on delete and undo/redo

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