source: trunk/src/org/expeditee/items/ItemUtils.java@ 922

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

Improved ItemUtils.Justify (now justifies against Frame edge too)

File size: 30.3 KB
Line 
1/**
2 * ItemUtils.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.items;
20
21import java.awt.Color;
22import java.awt.Rectangle;
23import java.awt.image.ImageObserver;
24import java.io.File;
25import java.io.UnsupportedEncodingException;
26import java.net.URL;
27import java.net.URLDecoder;
28import java.util.ArrayList;
29import java.util.Collection;
30import java.util.HashMap;
31import java.util.LinkedHashSet;
32import java.util.LinkedList;
33import java.util.List;
34import java.util.Map;
35
36import org.expeditee.gui.DisplayIO;
37import org.expeditee.gui.Frame;
38import org.expeditee.gui.FrameUtils;
39import org.expeditee.gui.FreeItems;
40import org.expeditee.gui.Vector;
41import org.expeditee.items.Item.HighlightMode;
42import org.expeditee.items.widgets.InteractiveWidget;
43import org.expeditee.items.widgets.InteractiveWidgetInitialisationFailedException;
44import org.expeditee.items.widgets.InteractiveWidgetNotAvailableException;
45import org.expeditee.items.widgets.WidgetCorner;
46import org.expeditee.items.widgets.WidgetEdge;
47import org.expeditee.network.FrameShare;
48import org.expeditee.settings.folders.FolderSettings;
49
50//Static methods that provide functions for the objects\
51//mostly to transform values (string -> color etc).
52
53/**
54 * Static methods that provide functions for use in Items.
55 */
56public class ItemUtils {
57 // Tag constants
58 public static final int TAG_SORT = 0;
59
60 public static final int TAG_JOIN = 1;
61
62 public static final int TAG_INDENT = 2;
63
64 public static final int TAG_OVERLAY = 3;
65
66 public static final int TAG_ACTIVE_OVERLAY = 4;
67
68 public static final int TAG_IMAGE = 5;
69
70 public static final int TAG_ITEM_TEMPLATE = 6;
71
72 public static final int TAG_ANNOTATION_TEMPLATE = 7;
73
74 public static final int TAG_CODE_COMMENT_TEMPLATE = 8;
75
76 public static final int TAG_MENU = 9;
77
78 public static final int TAG_MENU_NEXT = 10;
79
80 public static final int TAG_PARENT = 11;
81
82 public static final int TAG_LITERAL = 12;
83
84 public static final int TAG_FRAME_IMAGE = 13;
85
86 public static final int TAG_BACKUP = 14;
87
88 public static final int TAG_POINTTYPE = 15;
89
90 // Brook: Im claiming this number!
91 public static final int TAG_IWIDGET = 16;
92
93 public static final int TAG_DOT_TEMPLATE = 17;
94
95 public static final int TAG_STAT_TEMPLATE = 18;
96
97 public static final int TAG_VECTOR = 19;
98
99 public static final int TAG_ACTIVE_VECTOR = 21;
100
101 public static final int TAG_BITMAP_IMAGE = 20;
102
103 public static final int TAG_MIN = 0;
104
105 public static final int TAG_MAX = 21;
106
107 /**
108 * Determines if the given List of Items contains an Item that is one of the
109 * pre-defined tags.
110 *
111 * @param items
112 * The list of Items to search through
113 * @param tag
114 * The Tag to search for, this should correspond to one of the
115 * predefined constants in this class
116 * @return True if an Item was found that is the given Tag, False otherwise.
117 */
118 public static boolean ContainsTag(Collection<Item> items, int tag) {
119 return ContainsTag(items, GetTag(tag));
120 }
121
122 public static boolean ContainsTag(Collection<Item> items, String tag) {
123 return (FindTag(items, tag) != null);
124 }
125
126 public static boolean ContainsExactTag(Collection<Item> items, int tag) {
127 return ContainsExactTag(items, GetTag(tag));
128 }
129
130 public static boolean ContainsExactTag(Collection<Item> items, String tag) {
131 return (FindExactTag(items, tag) != null);
132 }
133
134 /**
135 * Searches the given List of Items for an Item that is one of the
136 * pre-defined tags.
137 *
138 * @param items
139 * The list of Items to search through
140 * @param tag
141 * The Tag to search for, this should correspond to one of the
142 * predefined constants in this class
143 * @return The Item that is the given tag if one is found, or False if none
144 * is found
145 */
146 public static Item FindTag(List<Item> items, int tag) {
147 return FindTag(items, GetTag(tag));
148 }
149
150 /**
151 * Searches the given List of Items for an Item that is the given tag
152 *
153 * @param items
154 * The list of Items to search through
155 * @param toFind
156 * The Tag to search for, this should include the at (@) symbol
157 * @return The Item that is the given tag if one is found, or False if none
158 * is found
159 */
160 public static Text FindTag(Collection<Item> items, String toFind) {
161 for (Item i : items) {
162 if (i instanceof Text && i.isAnnotation())
163 if (((Text) i).startsWith(toFind))
164 return (Text) i;
165 }
166 return null;
167 }
168
169 public static Item FindExactTag(Collection<Item> items, String toFind) {
170 for (Item i : items) {
171 if (i instanceof Text && i.isAnnotation())
172 if (((Text) i).getText().trim().equalsIgnoreCase(toFind))
173 return (Item) i;
174 }
175
176 return null;
177 }
178
179 public static Item FindExactTag(List<Item> items, int tag) {
180 return FindExactTag(items, GetTag(tag));
181 }
182
183 /**
184 * Determines if the given Item is one of the pre-defined tags in this class
185 *
186 * @param toCheck
187 * The Item to check
188 * @param tag
189 * The tag to check the Item against, this should correspond to
190 * one of the constants defined in this class
191 * @return True if the Item matches the given tag, false otherwise
192 */
193 public static boolean startsWithTag(Item toCheck, int tag) {
194 return startsWithTag(toCheck, GetTag(tag));
195 }
196
197 public static boolean startsWithTag(Item toCheck, int tag, boolean hasValue) {
198 return startsWithTag(toCheck, GetTag(tag), hasValue);
199 }
200
201 /**
202 * Checks if the given Item begins with the desired tag (case insensitive).
203 *
204 * @param toCheck
205 * The Item to check for the given tag
206 * @param tag
207 * The tag to check for in the given Item
208 * @param tagOnly
209 * True if the tag does not have a value
210 * @return True if the tag is found in the given Item, False otherwise.
211 */
212 public static boolean startsWithTag(Item toCheck, String tag,
213 boolean valueAllowed) {
214 if (!(toCheck instanceof Text))
215 return false;
216
217 Text txt = (Text) toCheck;
218 String value = ItemUtils.StripTag(txt.getText(), tag);
219
220 if (value == null)
221 return false;
222 return valueAllowed || value.equals("");
223 }
224
225 /**
226 * Checks if the item begins with the desired tag.
227 *
228 * @param toCheck
229 * @param tag
230 * @return
231 */
232 public static boolean startsWithTag(Item toCheck, String tag) {
233 return startsWithTag(toCheck, tag, true);
234 }
235
236 /**
237 * Strips off the given tag from the given String, and returns wathever is
238 * left. Dont put the colon after tags as it is not needed.
239 *
240 * @param toStrip
241 * The String to strip the Tag from
242 * @param tag
243 * The tag to remove from the String
244 * @return The String that results from removing the given Tag from the
245 * given String, or null if the given String is not the given Tag
246 */
247 public static String StripTag(String toStrip, String tag) {
248 if (toStrip == null)
249 return null;
250 toStrip = toStrip.trim();
251 if (!toStrip.toLowerCase().startsWith(tag.toLowerCase()))
252 return null;
253
254 if (toStrip.length() == tag.length())
255 return "";
256 // remove tag and ensure the char is the tag separator
257 char separator = toStrip.charAt(tag.length());
258 if (separator != ':')
259 return null;
260
261 if (toStrip.length() == tag.length() + 1)
262 return "";
263
264 return toStrip.substring(tag.length() + 1).trim();
265 }
266
267 /**
268 * Strips the first character from a string if it is the tag symbol and
269 * returns the remainder.
270 *
271 * @param toStrip
272 * the string to be stripped
273 * @return the stripped version of the string.
274 */
275 public static String StripTagSymbol(String toStrip) {
276 // there must be something left after stripping
277 if (toStrip != null) {
278 toStrip = toStrip.trim();
279 if (toStrip.length() > 0) {
280 if (toStrip.charAt(0) == '@') {
281 return toStrip.substring(1);
282 }
283 }
284 }
285 return toStrip;
286 }
287
288 /**
289 * Converts the given int to the String tag. The int should correspond to
290 * one of the constants in this class, if it does not this method will
291 * return null.
292 *
293 * @param tag
294 * The int corresponding to the constants in this class of which
295 * tag to return
296 * @return The String representation of the given Tag, or null if the given
297 * value does not have a tag associated
298 */
299 public static String GetTag(int tag) {
300 // TODO refactor so that this uses a map for INT to tags
301 switch (tag) {
302 case TAG_SORT:
303 return "@sort";
304 case TAG_JOIN:
305 return "@join";
306 case TAG_INDENT:
307 return "@indent";
308 case TAG_OVERLAY:
309 return "@o";
310 case TAG_VECTOR:
311 return "@v";
312 case TAG_ACTIVE_VECTOR:
313 return "@av";
314 case TAG_ACTIVE_OVERLAY:
315 return "@ao";
316 case TAG_IMAGE:
317 return "@i";
318 case TAG_ITEM_TEMPLATE:
319 return "@itemtemplate";
320 case TAG_ANNOTATION_TEMPLATE:
321 return "@annotationtemplate";
322 case TAG_STAT_TEMPLATE:
323 return "@stattemplate";
324 case TAG_CODE_COMMENT_TEMPLATE:
325 return "@commenttemplate";
326 case TAG_MENU:
327 return "@menu";
328 case TAG_MENU_NEXT:
329 return "@nextmenu";
330 case TAG_PARENT:
331 return "@parent";
332 case TAG_LITERAL:
333 return "@lit";
334 case TAG_FRAME_IMAGE:
335 return "@f";
336 case TAG_BITMAP_IMAGE:
337 return "@b";
338 case TAG_BACKUP:
339 return "@old";
340 case TAG_POINTTYPE:
341 return "@pointtype";
342 case TAG_IWIDGET:
343 return "@iw";
344 case TAG_DOT_TEMPLATE:
345 return "@dottemplate";
346 default:
347 return null;
348 }
349 }
350
351 /**
352 * Creates a picture object from the information stored in the given Text
353 * object. <br>
354 * The paths searched are in the following order:<br>
355 * /images/<br>
356 * the source text as a relative path (from program root folder). <br>
357 * the source text as an absolute path <br>
358 * <br>
359 * If the Image file cannot be found on disk null is returned.
360 *
361 * @param source
362 * The Text file containing the Picture infomation
363 * @return The Picture object representing the file, or Null if the file is
364 * not found.
365 */
366 public static Picture CreatePicture(Text source, ImageObserver observer, boolean tryRemote) {
367 String text = source.getText();
368 String path = "";
369 String fileName = "";
370 String size = "";
371
372 try {
373 // remove @i tag
374 text = text.replaceFirst("@i:", "");
375 text = text.replaceAll("\n", "");
376 text = text.trim();
377
378 int fileSuffixChar = text.indexOf('.');
379 if (fileSuffixChar < 0)
380 return null;
381 int endOfFileName = text.indexOf(' ', fileSuffixChar);
382 if (endOfFileName < 0) {
383 path = text;
384 size = "";
385 } else {
386 path = text.substring(0, endOfFileName);
387 size = text.substring(endOfFileName).trim();
388 }
389 fileName = path;
390
391 // try images subdirectory
392 File file = null;
393
394 for (String dir : FolderSettings.ImageDirs.get()) {
395 file = new File(dir + path);
396 if (file.exists() && !file.isDirectory())
397 break;
398 }
399
400 if (file == null || !file.exists() || file.isDirectory())
401 file = new File(path);
402
403 // try relative path
404 if (!file.exists() || file.isDirectory()) {
405 URL picture = new Object().getClass().getResource(path);
406
407 // decode to remove %20 in windows folder names
408 if (picture != null) {
409 try {
410 path = URLDecoder.decode(picture.getFile(), "UTF-8");
411 } catch (UnsupportedEncodingException e) {
412 // TODO Auto-generated catch block
413 e.printStackTrace();
414 }
415 }
416
417 } else
418 path = file.getPath();
419
420 // if the image isn't found by now, try remote servers
421 file = new File(path);
422 if (!file.exists() || file.isDirectory()) {
423 if(tryRemote && FrameShare.getInstance().loadImage(fileName, null)) {
424 // call CreatePicture again, but with tryRemote set to false so we won't get into an infinite loop
425 // if something goes wrong with finding the downloaded image
426 return CreatePicture(source, observer, false);
427 }
428 return null;
429 }
430
431 } catch (Exception e) {
432 return null;
433 }
434
435 try {
436 Picture pic = new Picture(source, fileName, path, size, observer);
437
438 return pic;
439 } catch (Exception e) {
440 e.printStackTrace();
441 return null;
442 }
443
444 }
445
446 public static Picture CreatePicture(Text source, ImageObserver observer) {
447 return CreatePicture(source, observer, true);
448 }
449
450 /**
451 * Creates a deep copy of the given List of Items.
452 *
453 * @param toCopy
454 * The list of Items to copy
455 * @return A list containing a copy of all Items in the given List
456 */
457 public static List<Item> CopyItems(Collection<Item> toCopy) {
458 return CopyItems(toCopy, false, null);
459 }
460
461 public static List<Item> CopyItems(Collection<Item> toCopy, Vector v) {
462 return CopyItems(toCopy, false, v);
463 }
464
465 public static List<Item> CopyItems(Collection<Item> toCopy, boolean extrude) {
466 return CopyItems(toCopy, extrude, null);
467 }
468
469 public static List<Item> CopyItems(Collection<Item> toCopy,
470 boolean extrude, Vector v) {
471 // The copies to return
472 List<Item> copies = new ArrayList<Item>();
473
474 // list of dots at the end of lines
475 Collection<Item> lineEnds = new LinkedHashSet<Item>();
476 Collection<Line> lines = new LinkedHashSet<Line>();
477 Collection<XRayable> xrayables = new LinkedHashSet<XRayable>();
478 Collection<Constraint> constraints = new LinkedHashSet<Constraint>();
479
480 Collection<Item> singles = new LinkedHashSet<Item>();
481
482 Map<Item, Item> lineEndMap = new HashMap<Item, Item>();
483
484 // Widgets are super special
485 List<InteractiveWidget> widgets = new ArrayList<InteractiveWidget>();
486
487 for (Item i : toCopy) {
488 // Dont copy parts of a vector
489 if (i == null || !i.hasPermission(UserAppliedPermission.copy))
490 continue;
491
492 // BROOK
493 if (i instanceof WidgetCorner) { // dont add these
494 if (!widgets.contains(((WidgetCorner) i).getWidgetSource()))
495 widgets.add(((WidgetCorner) i).getWidgetSource());
496 // BROOK
497 } else if (i instanceof WidgetEdge) { // dont add these
498 // lines are recreated later
499 } else if (i instanceof Line) {
500 lines.add((Line) i);
501 } else if (i instanceof XRayable) {
502 xrayables.add((XRayable) i);
503 } else {
504 if (i.isLineEnd()) {
505 lineEnds.add(i);
506 constraints.addAll(i.getConstraints());
507 } else {
508 singles.add(i);
509 }
510 }
511 }
512
513 // Dont copy the other items that are part of the circle
514 for (XRayable x : xrayables) {
515 Collection<Item> connected = x.getConnected();
516 singles.removeAll(connected);
517 lineEnds.removeAll(connected);
518 lines.removeAll(connected);
519 Item xCopy = x.copy();
520 copies.addAll(xCopy.getConnected());
521 // Scale items that are from a vector frame
522 if (v != null) {
523 scaleItem(v, xCopy);
524 }
525 }
526
527 // copy all single items
528 for (Item i : singles) {
529 Item copy = i.copy();
530 Frame parent = i.getParent();
531 if (parent != null) {
532 // Items copied from overlay will be anchored onto the current
533 // frame
534 copy.setParent(parent);
535 // if this is the frame name, make sure the frame is saved (in
536 // case it is a TDFC frame)
537 if (i.isFrameName()) {
538 parent.setChanged(true);
539 }// check if the item is being copied from a vector
540 else if (v != null) {
541 // Find the vector this item is from
542 assert (v.Frame == parent);
543 scaleItem(v, copy);
544 }
545 }
546 copies.add(copy);
547 }
548
549 // replace line ends with their copies
550 // this is done here so that copied lines can still share end points
551 for (Item i : lineEnds) {
552 // create a copy of the line end
553 Item copy = i.copy();
554 copy.removeAllLines();
555 copy.removeAllConstraints();
556
557 if (extrude) {
558 Frame frame = i.getParentOrCurrentFrame();
559 Line newLine = new Line(i, copy, frame.getNextItemID());
560 // make sure overlay items are put back on the overlay
561 newLine.setParent(frame);
562 frame.addItem(newLine);
563 copies.add(newLine);
564 }
565 copies.add(copy);
566 lineEndMap.put(i, copy);
567 // Scale items that are from a vector frame
568 if (v != null) {
569 scaleItem(v, copy);
570 }
571 }
572
573 // recreate lines
574 for (Line line : lines) {
575 Line lineCopy = line.copy();
576 // get the lineEnd we copied above if it is in the MAPPING
577 Item originalLineEnd = line.getEndItem();
578 Item actualLineEnd = lineEndMap.get(originalLineEnd);
579 if (actualLineEnd == null)
580 lineCopy.setEndItem(originalLineEnd);
581 else
582 lineCopy.setEndItem(actualLineEnd);
583
584 Item originalLineStart = line.getStartItem();
585 Item actualLineStart = lineEndMap.get(originalLineStart);
586 if (actualLineStart == null)
587 lineCopy.setStartItem(originalLineStart);
588 else
589 lineCopy.setStartItem(actualLineStart);
590
591 copies.add(lineCopy);
592 }
593
594 // recreate constraints
595 for (Constraint c : constraints) {
596 Item start = lineEndMap.get(c.getStart());
597 Item end = lineEndMap.get(c.getEnd());
598 int id = DisplayIO.getCurrentFrame().getNextItemID();
599 if (start != null && end != null) {
600 new Constraint(start, end, id, c.getType());
601 }
602 }
603
604 // BROOK
605 for (InteractiveWidget iw : widgets) {
606 try {
607
608 InteractiveWidget icopy = iw.copy();
609 copies.addAll(icopy.getItems());
610
611 } catch (InteractiveWidgetNotAvailableException e) {
612 e.printStackTrace();
613 } catch (InteractiveWidgetInitialisationFailedException e) {
614 e.printStackTrace();
615 }
616
617 }
618
619 // Make sure filled rectangles are shown filled on vector overlays
620 if (v != null)
621 EnclosedCheck(copies);
622
623 return copies;
624 }
625
626 /**
627 * Attempts to create a new line that starts from the given Item
628 * ('unreeling'). The Item must already have at least one line, and not be a
629 * line itself to be unreeled from.
630 *
631 * @param toUnreelFrom
632 * The Item that will be one end point of the new line
633 * @return A List containing the newly created Item and Line that unreel
634 * from the given Item, or null if this Item cannot be unreeled
635 * from.
636 */
637 public static List<Item> UnreelLine(Item toUnreelFrom, boolean constrain) {
638 // the Item must already have one line to be unreeled from
639 if (toUnreelFrom == null || toUnreelFrom.getLines().size() < 1)
640 return null;
641
642 List<Item> unreel = new ArrayList<Item>(2);
643 unreel.add(toUnreelFrom);
644 unreel.addAll(toUnreelFrom.getLines());
645 return UnreelLine(unreel, constrain);
646 }
647
648 /**
649 * Attempts to create a new line that starts from the given list of Items
650 * ('unreeling'). The List must contain only one non-line Item. The non-line
651 * Item must already have at least one line to be unreeled from.
652 *
653 * @param toUnreel
654 * The List containing the Item that will be one end point of the
655 * new line
656 * @return A List of the newly created Item and Line that unreel from the
657 * Item in the given List, or null if this List cannot be unreeled
658 * from.
659 */
660 public static List<Item> UnreelLine(List<Item> toUnreel, boolean constrain) {
661 Item origEnd = null;
662 // find the end being unreeled from
663 for (Item item : toUnreel) {
664 // we dont want to unreel anything other than lines
665 if (item.hasEnclosures()
666 || !(item.isLineEnd() || item instanceof Line)) {
667 return null;
668 }
669 // find the dot to unreel from
670 if (item.isLineEnd()) {
671 // if there are multiple ends in the list, return
672 if (origEnd != null)
673 return null;
674
675 origEnd = item;
676 }
677 }
678
679 // copy the original endpoint
680 Item copy = origEnd.copy();
681 origEnd.setHighlightMode(HighlightMode.None);
682 copy.removeAllLines();
683 copy.removeAllConstraints();
684
685 for (Line l : origEnd.getLines()) {
686 l.invalidateAll();
687 }
688
689 // create a new line
690 Frame currentFrame = DisplayIO.getCurrentFrame();
691 Line line = new Line(origEnd, copy, currentFrame.getNextItemID());
692 // if the previous line was constrained then make the new line
693 // constrained if it was a single line
694 // TODO add later a diagonal constraint if getLines() == 3 or 4
695 Collection<Constraint> constraints = origEnd.getConstraints();
696 if (constrain && constraints.size() > 0
697 && origEnd.getLines().size() == 2) {
698 Integer type = null;
699 for (Constraint c : constraints) {
700 if (c.getType() == Constraint.HORIZONTAL) {
701 type = Constraint.VERTICAL;
702 } else if (c.getType() == Constraint.VERTICAL) {
703 type = Constraint.HORIZONTAL;
704 }
705 if (c.getType() == Constraint.DIAGONAL_NEG) {
706 type = Constraint.DIAGONAL_POS;
707 } else if (c.getType() == Constraint.DIAGONAL_POS) {
708 type = Constraint.DIAGONAL_NEG;
709 }
710 }
711 if (type != null) {
712 new Constraint(origEnd, copy, currentFrame.getNextItemID(),
713 type);
714 }
715 }
716
717 // copy.setFloating(true);
718 origEnd.setArrowheadLength(0);
719 // copy.setArrowheadLength(0);
720
721 List<Item> toReturn = new LinkedList<Item>();
722 toReturn.add(copy);
723 toReturn.add(line);
724 return toReturn;
725 }
726
727 public static void New() {
728 EnclosedCheck(DisplayIO.getCurrentFrame().getItems());
729 }
730
731 public static void Old() {
732 OldEnclosedCheck(DisplayIO.getCurrentFrame().getItems());
733 }
734
735 /**
736 * Updates the connectedToAnnotation flags for all items
737 */
738 public static void UpdateConnectedToAnnotations(Collection<Item> items) {
739 // get all lineEnds on the Frame
740 Collection<Item> lineEnds = new LinkedHashSet<Item>();
741 for (Item i : items) {
742 i.setConnectedToAnnotation(false);
743 if (i.isLineEnd()) {
744 lineEnds.add(i);
745 }
746 }
747
748 // if there are no line endpoints on the Frame, then there can't be an
749 // enclosure
750 if (lineEnds.size() == 0)
751 return;
752
753 // Now find go through line ends and see if any are annotation items
754 while (lineEnds.size() > 0) {
755 Item item = lineEnds.iterator().next();
756 // If its an annotation item then set the flag for all its connected
757 // items
758 if (item.isAnnotation()) {
759 Collection<Item> connected = item.getAllConnected();
760 for (Item i : connected)
761 i.setConnectedToAnnotation(true);
762 lineEnds.removeAll(connected);
763 }
764 lineEnds.remove(item);
765 }
766 }
767
768 /**
769 * Checks through all Lines and Dots on the current Frame to detect if any
770 * form an enclosure, which can then be used to manipulate items within the
771 * polygon. If an enclosure is found, then the dots will have their
772 * enclosure value set to true, and a List is created that contains all the
773 * Dots in the order they were processed. Actual calculation of the Polygon
774 * is done dynamically (to account for Dots being moved).
775 */
776 public static void EnclosedCheck(Collection<Item> items) {
777 // get all lineEnds on the Frame
778 List<Item> lineEnds = new LinkedList<Item>();
779 for (Item i : items) {
780 if (i.isLineEnd()) {
781 i.setEnclosedList(null);
782 // Add line ends joined to 2 other lines
783 if (i.getLines().size() == 2)
784 lineEnds.add(i);
785 }
786 }
787
788 // if there are no line endpoints on the Frame, then there can't be an
789 // enclosure
790 if (lineEnds.size() == 0)
791 return;
792
793 // New approach
794 while (lineEnds.size() > 0) {
795 Item item = lineEnds.get(0);
796 // Get the lineEnds connected to this item
797 Collection<Item> connected = item.getAllConnected();
798 Collection<Item> connectedLineEnds = new LinkedHashSet<Item>();
799 for (Item itemToCheck : connected) {
800 if (itemToCheck.isLineEnd())
801 connectedLineEnds.add(itemToCheck);
802 }
803 // Check that all the line ends are in our lineEnds list
804 int oldSize = lineEnds.size();
805 // Remove all the items from our line ends list
806 lineEnds.removeAll(connectedLineEnds);
807 int newSize = lineEnds.size();
808 int connectedSize = connectedLineEnds.size();
809 // Check if all the connectedItems were in the lineEnds collection
810 if (oldSize == newSize + connectedSize) {
811 // Set them to be the enclosed list for each of the items
812 for (Item enclosedLineEnd : connectedLineEnds) {
813 enclosedLineEnd.setEnclosedList(connectedLineEnds);
814 }
815 }
816 }
817 }
818
819 /**
820 * Checks through all Lines and Dots on the current Frame to detect if any
821 * form an enclosure, which can then be used to manipulate items within the
822 * polygon. If an enclosure is found, then the dots will have their
823 * enclosure value set to true, and a List is created that contains all the
824 * Dots in the order they were processed. Actual calculation of the Polygon
825 * is done dynamically (to account for Dots being moved).
826 */
827 public static void OldEnclosedCheck(Collection<Item> items) {
828 _seen.clear();
829
830 // get all lineEnds on the Frame
831 List<Item> lineEnds = new ArrayList<Item>(0);
832 for (Item i : items) {
833 if (i.isLineEnd()) {
834 i.setEnclosedList(null);
835
836 if (i.getLines().size() == 2)
837 lineEnds.add(i);
838 }
839 }
840
841 // if there are no line endpoints on the Frame, then there can't be an
842 // enclosure
843 if (lineEnds.size() == 0)
844 return;
845
846 // TODO optimise this code!!
847 // iterate through all the lineEnds
848 for (Item searchFor : lineEnds) {
849 _seen.clear();
850
851 for (Line l : searchFor.getLines()) {
852 _seen.add(l);
853 if (traverse(searchFor, l.getOppositeEnd(searchFor))) {
854 _path.add(l.getOppositeEnd(searchFor));
855
856 for (Item i : _path)
857 i.setEnclosedList(_path);
858
859 _path = new ArrayList<Item>(0);
860
861 break;
862 }
863 }
864 }
865 }
866
867 private static List<Line> _seen = new ArrayList<Line>();
868
869 private static List<Item> _path = new ArrayList<Item>();
870
871 private static boolean traverse(Item toFind, Item searchFrom) {
872 if (toFind == null || searchFrom == null || !searchFrom.isLineEnd())
873 return false;
874
875 if (searchFrom.getLines().size() != 2)
876 return false;
877
878 if (toFind == searchFrom)
879 return true;
880
881 for (Line l : searchFrom.getLines()) {
882 if (!(_seen.contains(l))) {
883 _seen.add(l);
884 if (traverse(toFind, l.getOppositeEnd(searchFrom))) {
885 _path.add(l.getOppositeEnd(searchFrom));
886 return true;
887 }
888 }
889
890 }
891
892 return false;
893 }
894
895 /**
896 * Determines if an item is visible from a the current frame(s). If the item
897 * is free then it is considered visible.
898 *
899 * @param i
900 * The item to check
901 * @return True if visible/free from given frame.
902 */
903 public static boolean isVisible(Item i) {
904 if (DisplayIO.isTwinFramesOn()) {
905 if (!isVisible(DisplayIO.getFrames()[0], i)) {
906 return isVisible(DisplayIO.getFrames()[1], i);
907 } else {
908 return true;
909 }
910 } else {
911 return isVisible(DisplayIO.getCurrentFrame(), i);
912 }
913 }
914
915 /**
916 * Determines if an item is visible from a given frame. If the item is free
917 * then it is considered visible.
918 *
919 * @param fromFrame
920 * The frame to check from.
921 * @param i
922 * The item to check
923 * @return True if visible/free from given frame.
924 */
925 public static boolean isVisible(Frame fromFrame, Item i) {
926 if (fromFrame == null)
927 return false;
928
929 Frame parent = i.getParent();
930
931 if (parent == fromFrame)
932 return true;
933
934 else if (parent == null)
935 return FreeItems.getInstance().contains(i)
936 || FreeItems.getCursor().contains(i);
937
938 return fromFrame.getAllItems().contains(i) && i.isVisible();
939 }
940
941 public static Rectangle expandRectangle(Rectangle r, int n) {
942 return new Rectangle(r.x - (n >> 1), r.y - (n >> 1), r.width + n,
943 r.height + n);
944 }
945
946 /*
947 * FrameMouseActions while (!copies.isEmpty()) { Iterator<Item> iterator =
948 * copies.iterator(); Item item = iterator.next(); // Dont paint annotation
949 * items for @v if (!item.isVisible() || item.isAnnotation()) {
950 * iterator.remove(); continue; }
951 *
952 * if (!(item instanceof Line)) { item.setThickness(item.getThickness() *
953 * scale); if (item instanceof XRayable || item.hasEnclosures()) {
954 * scaleItem(scale, defaultForeground, defaultBackground, origin.x,
955 * origin.y, item); items.add(item); copies.remove(item); } else {
956 * Collection<Item> connected = item.getAllConnected(); // Get all the
957 * connected items because we can only set the // thickness ONCE for (Item i :
958 * connected) { scaleItem(scale, defaultForeground, defaultBackground,
959 * origin.x, origin.y, i); } items.addAll(connected);
960 * copies.removeAll(connected); } } else { iterator.remove(); } }
961 */
962
963 /**
964 * @param scale
965 * @param defaultForeground
966 * @param defaultBackground
967 * @param originX
968 * @param originY
969 * @param item
970 */
971 private static void scaleItem(Vector v, Item item) {
972 Float scale = v.Scale;
973 int originX = v.Origin.x;
974 int originY = v.Origin.y;
975 Color defaultForeground = v.Foreground;
976 Color defaultBackground = v.Background;
977 UserAppliedPermission permission = v.permission;
978 // TODO should this be checking if the frame has the
979 // same permissions as the vector
980 // and if so don't set the item's permissions?
981 item.setOverlayPermission(permission);
982
983 // TODO encapsulate this somewhere inside of circle class!
984 // if(item instanceof Circle){
985 // scaleItem(v, ((Circle)item).getCenter());
986 // }
987
988 if (!(item instanceof Line)) {
989 if (item.getColor() == null) {
990 item.setColor(defaultForeground);
991 }
992 if (item.getBackgroundColor() == null) {
993 item.setBackgroundColor(defaultBackground);
994 }
995 if (item.getFillColor() == null) {
996 item.setFillColor(defaultBackground);
997 }
998
999 if (permission.equals(UserAppliedPermission.none)) {
1000 item.setLinkMark(false);
1001 item.setActionMark(false);
1002 }
1003
1004 item.scale(scale, originX, originY);
1005 }
1006 }
1007
1008 /**
1009 * Extracts widgets from an item list.
1010 *
1011 * @param items
1012 * Items to extract from. Must not be null.
1013 *
1014 * @return List of (unique)widgets in items. Never null.
1015 */
1016 public static List<InteractiveWidget> extractWidgets(List<Item> items) {
1017 assert (items != null);
1018
1019 List<InteractiveWidget> iWidgets = new LinkedList<InteractiveWidget>();
1020
1021 for (Item i : items) {
1022 if (i instanceof WidgetEdge) {
1023 WidgetEdge we = (WidgetEdge) i;
1024 if (!iWidgets.contains(we.getWidgetSource()))
1025 iWidgets.add(we.getWidgetSource());
1026 } else if (i instanceof WidgetCorner) {
1027 WidgetCorner wc = (WidgetCorner) i;
1028 if (!iWidgets.contains(wc.getWidgetSource()))
1029 iWidgets.add(wc.getWidgetSource());
1030 }
1031 }
1032
1033 return iWidgets;
1034 }
1035
1036 /**
1037 * Wraps any text items to the size of their container, or the frame size if they have not container
1038 *
1039 * @param items A list of Items to wrap (non-Text items are ignored)
1040 */
1041 public static void Justify(Collection<Item> items) {
1042 for (Item i : items) {
1043 if (i instanceof Text) {
1044 Collection<Item> enclosure = FrameUtils.getEnclosingLineEnds(i.getPosition());
1045 ((Text)i).justify(false, enclosure != null ? enclosure.iterator().next().getEnclosedShape() : null);
1046 }
1047 }
1048 }
1049
1050 /**
1051 * Recalculates containers on the frame, then wraps all text items
1052 *
1053 * @param frame
1054 */
1055 public static void Justify(Frame frame) {
1056 EnclosedCheck(frame.getItems());
1057 Justify(frame.getItems());
1058 }
1059}
Note: See TracBrowser for help on using the repository browser.