source: trunk/src/org/expeditee/gui/FrameUtils.java@ 1407

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

Group level permission is now completely a thing. If a frame has a Group property specified, then the value of that property will be used to look up a group frame in FrameIO.GROUP_PATH. It will then look on that frame for a list of members to the group. When doing permission checks, that list is passed in order to decide if the current user is a part of that group.

File size: 64.5 KB
Line 
1/**
2 * FrameUtils.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.io.File;
22import java.io.FileInputStream;
23import java.io.FileOutputStream;
24import java.io.IOException;
25import java.io.InputStream;
26import java.lang.reflect.InvocationTargetException;
27import java.net.JarURLConnection;
28import java.net.URISyntaxException;
29import java.net.URL;
30import java.net.URLConnection;
31import java.nio.file.FileVisitResult;
32import java.nio.file.FileVisitor;
33import java.nio.file.Files;
34import java.nio.file.Path;
35import java.nio.file.Paths;
36import java.nio.file.attribute.BasicFileAttributes;
37import java.util.ArrayList;
38import java.util.Arrays;
39import java.util.Collection;
40import java.util.Collections;
41import java.util.Comparator;
42import java.util.Enumeration;
43import java.util.LinkedHashSet;
44import java.util.LinkedList;
45import java.util.List;
46import java.util.Map;
47import java.util.Scanner;
48import java.util.function.Consumer;
49import java.util.jar.JarEntry;
50import java.util.jar.JarFile;
51import java.util.stream.Collectors;
52import java.util.zip.ZipEntry;
53
54import org.expeditee.agents.ExistingFramesetException;
55import org.expeditee.agents.InvalidFramesetNameException;
56import org.expeditee.auth.AuthenticatorBrowser;
57import org.expeditee.auth.mail.gui.MailBay;
58import org.expeditee.core.Colour;
59import org.expeditee.core.Point;
60import org.expeditee.core.bounds.AxisAlignedBoxBounds;
61import org.expeditee.core.bounds.PolygonBounds;
62import org.expeditee.gio.EcosystemManager;
63import org.expeditee.gio.gesture.StandardGestureActions;
64import org.expeditee.items.Circle;
65import org.expeditee.items.Dot;
66import org.expeditee.items.FrameBitmap;
67import org.expeditee.items.FrameImage;
68import org.expeditee.items.Item;
69import org.expeditee.items.Item.HighlightMode;
70import org.expeditee.items.ItemUtils;
71import org.expeditee.items.JSItem;
72import org.expeditee.items.Line;
73import org.expeditee.items.PermissionTriple;
74import org.expeditee.items.Picture;
75import org.expeditee.items.Text;
76import org.expeditee.items.UserAppliedPermission;
77import org.expeditee.items.XRayable;
78import org.expeditee.items.widgets.ButtonWidget;
79import org.expeditee.items.widgets.InteractiveWidgetInitialisationFailedException;
80import org.expeditee.items.widgets.InteractiveWidgetNotAvailableException;
81import org.expeditee.items.widgets.Widget;
82import org.expeditee.items.widgets.WidgetCorner;
83import org.expeditee.items.widgets.WidgetEdge;
84import org.expeditee.setting.Setting;
85import org.expeditee.settings.Settings;
86import org.expeditee.settings.UserSettings;
87import org.expeditee.settings.templates.TemplateSettings;
88import org.expeditee.stats.Logger;
89import org.expeditee.stats.SessionStats;
90
91public class FrameUtils {
92
93 /**
94 * The list of known start pages framesets which will have prepopulated links in
95 * the home frame.
96 */
97 public static final String[] startPages = { "exploratorysearch", "webbrowser" };
98
99 private static final int COLUMN_WIDTH = 50;
100
101 /**
102 * Provides a way to monitor the time elapsed between button-down and the
103 * finished painting.
104 */
105 public static TimeKeeper ResponseTimer = new TimeKeeper();
106
107 private static float _ResponseTimeSum = 0;
108
109 private static float _LastResponse = 0;
110
111 private static Text LastEdited = null;
112
113 public static int MINIMUM_INTERITEM_SPACING = -6;
114
115 private static Item _tdfcItem = null;
116
117 public static float getResponseTimeTotal() {
118 return _ResponseTimeSum;
119 }
120
121 public static float getLastResponseTime() {
122 return _LastResponse;
123 }
124
125 /**
126 * Checks if the given top Item is above the given bottom Item, allowing for the
127 * X coordinates to be off by a certain width...
128 *
129 * @param item1
130 * The Item to check is above the other Item
131 * @param item2
132 * The Item to check is below the top Item
133 * @return True if top is above bottom, False otherwise.
134 */
135 public static boolean inSameColumn(Item item1, Item item2) {
136 if (!(item1 instanceof Text) || !(item2 instanceof Text)) {
137 return false;
138 }
139
140 if (item1.getID() < 0 || item2.getID() < 0) {
141 return false;
142 }
143
144 int minX = item2.getX();
145 int maxX = item2.getX() + item2.getBoundsWidth();
146
147 int startX = item1.getX();
148 int endX = item1.getX() + item1.getBoundsWidth();
149
150 // Check that the two items left values are close
151 if (Math.abs(item1.getX() - item2.getX()) > COLUMN_WIDTH) {
152 return false;
153 }
154
155 // Ensure the two items
156 if ((minX >= startX && minX <= endX) || (maxX >= startX && maxX <= endX) || (startX >= minX && startX <= maxX)
157 || (endX >= minX && endX <= maxX)) {
158 return true;
159 }
160
161 return false;
162 }
163
164 public static boolean sameBulletType(String bullet1, String bullet2) {
165 if (bullet1 == null || bullet2 == null) {
166 return false;
167 }
168
169 if (bullet1.equals("") || bullet2.equals("")) {
170 return false;
171 }
172
173 if (Character.isLetter(bullet1.charAt(0)) && Character.isLetter(bullet2.charAt(0))) {
174 return true;
175 }
176
177 if (Character.isDigit(bullet1.charAt(0)) && Character.isDigit(bullet2.charAt(0))) {
178 return true;
179 }
180
181 // TODO make this more sofisticated
182
183 return false;
184 }
185
186 private static boolean needsRenumbering(String s) {
187 if (s == null || s.equals("")) {
188 return false;
189 }
190 if (!Character.isLetterOrDigit(s.charAt(0))) {
191 return false;
192 }
193
194 s = s.trim();
195 // if its all letters then we dont want to auto adjust
196 if (s.length() > 2) {
197 for (int i = 0; i < s.length() - 1; i++) {
198 if (!Character.isLetter(s.charAt(i))) {
199 return true;
200 }
201 }
202 } else {
203 return true;
204 }
205
206 return false;
207 }
208
209 /**
210 *
211 * @param toAlign
212 * @param moveAll
213 * @param adjust
214 * @return
215 */
216 public static int Align(List<Text> toAlign, boolean moveAll, int adjust, List<Item> changedItems) {
217 Collections.sort(toAlign);
218
219 /*
220 * Single items dont need alignment But if there are two items we may still want
221 * to format them... ie if they are too close together.
222 */
223 if (toAlign.size() < 1) {
224 return 0;
225 }
226
227 // get the first item
228 Text from = toAlign.get(0);
229 if (from.getParent() == null) {
230 from = toAlign.get(1);
231 }
232 int x = from.getX();
233
234 Frame curr = from.getParent();
235 Text above = curr.getTextAbove(from);
236
237 String lastBullet = "";
238
239 if (above != null && curr.isNormalTextItem(above)) {
240 lastBullet = StandardGestureActions.getAutoBullet(above.getText());
241 } else {
242 lastBullet = StandardGestureActions.getBullet(toAlign.get(0).getText());
243 }
244 if (needsRenumbering(lastBullet)) {
245 // renumber...
246 for (int i = 0; i < toAlign.size(); i++) {
247
248 Text currentText = toAlign.get(i);
249 String currentBullet = StandardGestureActions.getAutoBullet(currentText.getText());
250
251 if (sameBulletType(lastBullet, currentBullet)) {
252 String oldText = currentText.getText();
253
254 currentText.stripFirstWord();
255
256 currentText.setText(lastBullet + currentText.getText());
257 lastBullet = StandardGestureActions.getAutoBullet(currentText.getText());
258
259 // if we changed the item, add to changedItems list
260 if (changedItems != null && oldText != currentText.getText()
261 && !changedItems.contains(currentText)) {
262 Item copy = currentText.copy();
263 copy.setID(currentText.getID());
264 copy.setText(oldText);
265 changedItems.add(copy);
266 }
267 }
268 }
269 }
270
271 // work out the spacing between the first item and the one above it
272
273 int space = 10 + adjust;
274
275 // if we are dropping from the title make the space a little bigger
276 // than normal
277
278 // If there are only two items get the gap from the start item on the
279 // zero frame if there is one
280 if (above == curr.getTitleItem()) {
281 Frame zero = FrameIO.LoadFrame(curr.getFramesetName() + '0');
282 String strGap = zero.getAnnotationValue("start");
283 if (strGap != null) {
284 try {
285 int gap = Integer.parseInt(strGap);
286 space = gap;
287 } catch (NumberFormatException nfe) {
288
289 }
290 }
291 } else if (above != null) {
292 // Make the gap between all items the same as the gap between
293 // the first two
294 space = from.getBounds().getMinY() - above.getBounds().getMaxY();
295
296 if (space < MINIMUM_INTERITEM_SPACING) {
297 space = MINIMUM_INTERITEM_SPACING;
298 }
299
300 if (UserSettings.FormatSpacingMax.get() != null) {
301 double maxSpace = UserSettings.FormatSpacingMax.get() * above.getSize();
302 if (maxSpace < space) {
303 space = (int) Math.round(maxSpace);
304 }
305 }
306
307 if (UserSettings.FormatSpacingMin.get() != null) {
308 double minSpace = UserSettings.FormatSpacingMin.get() * above.getSize();
309 if (minSpace > space) {
310 space = (int) Math.round(minSpace);
311 }
312 }
313
314 // Need to do things differently for FORMAT than for DROPPING
315 if (moveAll && above != curr.getNameItem() && above != curr.getTitleItem()) {
316 x = above.getX();
317 int y = above.getBounds().getMaxY() + space + (from.getY() - from.getBounds().getMinY());
318
319 if (changedItems != null && (from.getX() != x || from.getY() != y) && !changedItems.contains(from)) {
320 Item copy = from.copy();
321 copy.setID(from.getID());
322 changedItems.add(copy);
323 }
324 from.setPosition(x, y);
325 } else {
326 x = from.getX();
327 }
328
329 space += adjust;
330 }
331 for (int i = 1; i < toAlign.size(); i++) {
332 Item current = toAlign.get(i);
333 Item top = toAlign.get(i - 1);
334
335 // The bottom of the previous item
336 int bottom = top.getBounds().getMaxY();
337
338 // the difference between the current item's Y coordinate and
339 // the top of the highlight box
340 int diff = current.getY() - current.getBounds().getMinY();
341
342 int newPos = bottom + space + diff;
343
344 if (changedItems != null && ((moveAll && current.getX() != x) || current.getY() != newPos)
345 && !changedItems.contains(current)) {
346 Item copy = current.copy();
347 copy.setID(current.getID());
348 changedItems.add(copy);
349 }
350
351 if (moveAll) {
352 current.setPosition(x, newPos);
353 } else if (newPos > current.getY()) {
354 current.setY(newPos);
355 }
356
357 }
358
359 // if (insert != null)
360 // return insert.getY();
361
362 // Michael thinks we return the y value for the next new item??
363 int y = from.getY() + from.getBoundsHeight() + space;
364 return y;
365 }
366
367 public static int Align(List<Text> toAlign, boolean moveAll, int adjust) {
368 return Align(toAlign, moveAll, adjust, null);
369 }
370
371 public static boolean LeavingFrame(Frame current) {
372 checkTDFCItemWaiting(current);
373 // active overlay frames may also require saving if they have been
374 // changed
375 for (Overlay o : current.getOverlays()) {
376 if (!SaveCheck(o.Frame)) {
377 return false;
378 }
379 }
380
381 // if the check fails there is no point continuing
382 if (!SaveCheck(current)) {
383 return false;
384 }
385
386 for (Item i : current.getItems()) {
387 i.setHighlightMode(Item.HighlightMode.None);
388 i.setHighlightColorToDefault();
389 }
390 return true;
391 }
392
393 private static boolean SaveCheck(Frame toSave) {
394 // don't bother saving frames that haven't changed
395 if (!toSave.hasChanged()) {
396 return true;
397 }
398
399 // if the frame has been changed, then save it
400 if (DisplayController.isTwinFramesOn()) {
401 Frame opposite = DisplayController.getOppositeFrame();
402
403 String side = "left";
404 if (DisplayController.getCurrentSide() == DisplayController.TwinFramesSide.RIGHT) {
405 side = "right";
406 }
407
408 // if the two frames both have changes, prompt the user for the
409 // next move
410 if (opposite.hasChanged() && opposite.equals(toSave)) {
411 if (EcosystemManager.getGraphicsManager().showDialog("Changes",
412 "Leaving this frame will discard changes made in the " + side + " Frame. Continue?")) {
413 FrameIO.SaveFrame(toSave);
414 DisplayController.Reload(DisplayController.getSideFrameIsOn(opposite));
415 return true;
416 } else {
417 return false;
418 }
419 } else if (opposite.hasOverlay(toSave)) {
420 if (toSave.hasChanged()) {
421 if (EcosystemManager.getGraphicsManager().showDialog("Changes",
422 "Leaving this frame will discard changes made in the " + side + " Frame. Continue?")) {
423 FrameIO.SaveFrame(toSave);
424 DisplayController.Reload(DisplayController.getSideFrameIsOn(opposite));
425 return true;
426 } else {
427 return false;
428 }
429 }
430 }
431
432 // save the current frame and restore the other side
433 FrameIO.SaveFrame(toSave);
434 return true;
435 }
436
437 // single-frame mode can just save and return
438 FrameIO.SaveFrame(toSave);
439 return true;
440 }
441
442 // TODO: consider reloating this method to Frame class?
443 protected static Item getAnnotation(Frame frame, String annotationStr) {
444 Item matched_item = null;
445
446 // check for an updated template...
447 for (Item i : frame.getAnnotationItems()) {
448
449 if (ItemUtils.startsWithTag(i, annotationStr)) {
450
451 matched_item = i;
452 break;
453 }
454 }
455
456 return matched_item;
457 }
458
459 protected static void doFrameTransition(Item frameTransition, Frame from, Frame to) {
460 String s = frameTransition.getText();
461 String[] s_array = s.split(":");
462 if (s_array.length > 1) {
463 String slide_mode_method = s_array[1].trim();
464
465 FrameTransition transition = new FrameTransition(from.getBuffer(), slide_mode_method);
466
467 DisplayController.setTransition(from, transition);
468
469 System.out.println("Triggered on annotation: " + s);
470 } else {
471 System.err.println("Warning: failed to detect frameTransition type");
472 // TODO: print list as a result of reflection listing
473 }
474 }
475
476 /**
477 * Displays the given Frame on the display. If the current frame has changed
478 * since the last save then it will be saved before the switch is made. The
479 * caller can also dictate whether the current frame is added to the back-stack
480 * or not.
481 *
482 * @param toDisplay
483 * The Frame to display on the screen
484 * @param addToBack
485 * True if the current Frame should be added to the back-stack, False
486 * otherwise
487 */
488 public static void DisplayFrame(Frame toDisplay, boolean addToBack, boolean incrementStats) {
489 if (toDisplay == null) {
490 return;
491 }
492
493 // Check if group specified that it exists.
494 String group = toDisplay.getGroup();
495 Frame groupFrame = null;
496 if (group != null && group.length() > 0) {
497 groupFrame = toDisplay.getGroupFrame();
498 if (groupFrame == null) {
499 MessageBay.displayMessage("WARNING: There is no corrosponding " + group + "1 for the specified group " + group + ". Group permissions will not apply.");
500 }
501 }
502
503 final PermissionTriple framePermissions = toDisplay.getPermission();
504 if (framePermissions != null
505 && framePermissions.getPermission(toDisplay.getOwner(), toDisplay.getGroupMembers()) == UserAppliedPermission.denied) {
506 MessageBay.errorMessage("Insufficient permissions to navigate to frame: " + toDisplay.getName());
507 return;
508 }
509
510 Frame current = DisplayController.getCurrentFrame();
511
512 // Dont need to do anything if the frame to display is already being
513 // displayed
514 if (current.equals(toDisplay)) {
515 return;
516 }
517
518 // move any anchored connected items
519 if (FreeItems.hasItemsAttachedToCursor()) {
520 List<Item> toAdd = new ArrayList<Item>();
521 List<Item> toCheck = new ArrayList<Item>(FreeItems.getInstance());
522
523 while (toCheck.size() > 0) {
524 Item i = toCheck.get(0);
525 Collection<Item> connected = i.getAllConnected();
526
527 // // Only move completely enclosed items
528 // if (!toCheck.containsAll(connected)) {
529 // connected.retainAll(FreeItems.getInstance());
530 // FreeItems.getInstance().removeAll(connected);
531 // toCheck.removeAll(connected);
532 // FrameMouseActions.anchor(connected);
533 // } else {
534 // toCheck.removeAll(connected);
535 // }
536
537 // Anchor overlay items where they belong
538 if (i.getParent() != null && i.getParent() != current) {
539 FreeItems.getInstance().removeAll(connected);
540 toCheck.removeAll(connected);
541 StandardGestureActions.anchor(connected);
542 } else {
543 // Add stuff that is partially enclosed
544 // remove all the connected items from our list to check
545 toCheck.removeAll(connected);
546 // Dont add the items that are free
547 connected.removeAll(FreeItems.getInstance());
548 toAdd.addAll(connected);
549 }
550 }
551
552 current.removeAllItems(toAdd);
553
554 boolean oldChange = toDisplay.hasChanged();
555 toDisplay.updateIDs(toAdd);
556 toDisplay.addAllItems(toAdd);
557 toDisplay.setChanged(oldChange);
558 }
559
560 if (addToBack && current != toDisplay) {
561 FrameIO.checkTDFC(current);
562 }
563
564 // if the saving happened properly, we can continue
565 if (!LeavingFrame(current)) {
566 MessageBay.displayMessage("Navigation cancelled");
567 return;
568 }
569
570 if (addToBack && current != toDisplay) {
571 DisplayController.addToBack(current);
572 }
573
574 Parse(toDisplay);
575
576 if (DisplayController.isAudienceMode()) {
577 // Only need to worry about frame transitions when in Audience Mode
578
579 // Test to see if frame transition specified through annotation, and perform it
580 // if one if found
581 Item frameTransition = getAnnotation(toDisplay, "@frameTransition");
582 if (frameTransition != null) {
583 doFrameTransition(frameTransition, current, toDisplay);
584 }
585 }
586
587 DisplayController.setCurrentFrame(toDisplay, incrementStats);
588 StandardGestureActions.updateCursor();
589 // FrameMouseActions.getInstance().refreshHighlights();
590 // update response timer
591 _LastResponse = ResponseTimer.getElapsedSeconds();
592 _ResponseTimeSum += _LastResponse;
593 DisplayController.updateTitle();
594 }
595
596 /**
597 * Loads and displays the Frame with the given framename, and adds the current
598 * frame to the back-stack if required.
599 *
600 * @param framename
601 * The name of the Frame to load and display
602 * @param addToBack
603 * True if the current Frame should be added to the back-stack, false
604 * otherwise
605 */
606 public static void DisplayFrame(String frameName, boolean addToBack, boolean incrementStats) {
607 Frame newFrame = getFrame(frameName);
608
609 if (newFrame != null) {
610 // display the frame
611 DisplayFrame(newFrame, addToBack, incrementStats);
612 }
613 }
614
615 /**
616 * Loads and displays the Frame with the given framename and adds the current
617 * frame to the back-stack. This is the same as calling DisplayFrame(framename,
618 * true)
619 *
620 * @param framename
621 * The name of the Frame to load and display
622 */
623 public static void DisplayFrame(String framename) {
624 DisplayFrame(framename, true, true);
625 }
626
627 public static Frame getFrame(String frameName) {
628 // if the new frame does not exist then tell the user
629 Frame f = FrameIO.LoadFrame(frameName);
630
631 if (f == null) {
632 MessageBay.errorMessage("Frame '" + frameName + "' could not be found.");
633 }
634
635 return f;
636 }
637
638 /**
639 * Creates a new Picture Item from the given Text source Item and adds it to the
640 * given Frame.
641 *
642 * @return True if the image was created successfully, false otherwise
643 */
644 private static boolean createPicture(Frame frame, Text txt) {
645 // attempt to create the picture
646 Picture pic = ItemUtils.CreatePicture(txt);
647
648 // if the picture could not be created successfully
649 if (pic == null) {
650 String imagePath = txt.getText();
651 assert (imagePath != null);
652 imagePath = new AttributeValuePair(imagePath).getValue().trim();
653 if (imagePath.length() == 0) {
654 return false;
655 // MessageBay.errorMessage("Expected image path after @i:");
656 } else {
657 MessageBay.errorMessage("Image " + imagePath + " could not be loaded");
658 }
659 return false;
660 }
661 frame.addItem(pic);
662
663 return true;
664 }
665
666 /**
667 * Creates an interactive widget and adds it to a frame. If txt has no parent
668 * the parent will be set to frame.
669 *
670 * @param frame
671 * Frame to add widget to. Must not be null.
672 *
673 * @param txt
674 * Text to create the widget from. Must not be null.
675 *
676 * @return True if created/added. False if could not create.
677 *
678 * @author Brook Novak
679 */
680 private static boolean createWidget(Frame frame, Text txt) {
681
682 if (frame == null) {
683 throw new NullPointerException("frame");
684 }
685 if (txt == null) {
686 throw new NullPointerException("txt");
687 }
688
689 // Safety
690 if (txt.getParent() == null) {
691 txt.setParent(frame);
692 }
693
694 Widget iw = null;
695
696 try {
697
698 iw = Widget.createWidget(txt);
699
700 } catch (InteractiveWidgetNotAvailableException e) {
701 e.printStackTrace();
702 MessageBay.errorMessage("Cannot create iWidget: " + e.getMessage());
703 } catch (InteractiveWidgetInitialisationFailedException e) {
704 e.printStackTrace();
705 MessageBay.errorMessage("Cannot create iWidget: " + e.getMessage());
706 } catch (IllegalArgumentException e) {
707 e.printStackTrace();
708 MessageBay.errorMessage("Cannot create iWidget: " + e.getMessage());
709 }
710
711 if (iw == null) {
712 return false;
713 }
714
715 frame.removeItem(txt);
716
717 frame.addAllItems(iw.getItems());
718
719 return true;
720 }
721
722 public static List<String> ParseProfile(Frame profile) {
723 List<String> errors = new LinkedList<String>();
724
725 if (profile == null) {
726 return errors;
727 }
728
729 /*
730 * Make sure the correct cursor shows when turning off the custom cursor and
731 * reparsing the profile frame
732 */
733 FreeItems.getCursor().clear();
734 DisplayController.setCursor(Item.HIDDEN_CURSOR);
735 DisplayController.setCursor(Item.DEFAULT_CURSOR);
736
737 // check for settings tags
738 for (Text item : profile.getBodyTextItems(true)) {
739 try {
740
741 AttributeValuePair avp = new AttributeValuePair(item.getText());
742 String attributeFullCase = avp.getAttributeOrValue();
743
744 if (attributeFullCase == null) {
745 continue;
746 }
747
748 String attribute = attributeFullCase.trim().toLowerCase().replaceAll("^@", "");
749
750 if (attribute.equals("settings")) {
751 Settings.parseSettings(item);
752 }
753
754 } catch (Exception e) {
755 if (e.getMessage() != null) {
756 errors.add(e.getMessage());
757 } else {
758 e.printStackTrace();
759 errors.add("Error parsing [" + item.getText() + "] on " + profile.getName());
760 }
761 }
762 }
763
764 return errors;
765 }
766
767 /**
768 * Sets the first frame to be displayed.
769 *
770 * @param profile
771 */
772 public static void loadFirstFrame(Frame profile) {
773 if (UserSettings.HomeFrame.get() == null) {
774 UserSettings.HomeFrame.set(profile.getName());
775 }
776
777 Frame firstFrame = FrameIO.LoadFrame(UserSettings.HomeFrame.get());
778 if (firstFrame == null) {
779 MessageBay.warningMessage("Home frame not found: " + UserSettings.HomeFrame);
780 UserSettings.HomeFrame.set(profile.getName());
781 DisplayController.setCurrentFrame(profile, true);
782 } else {
783 DisplayController.setCurrentFrame(firstFrame, true);
784 }
785
786 }
787
788 public static Colour[] getColorWheel(Frame frame) {
789 if (frame != null) {
790 List<Text> textItems = frame.getBodyTextItems(false);
791 Colour[] colorList = new Colour[textItems.size() + 1];
792 for (int i = 0; i < textItems.size(); i++) {
793 colorList[i] = textItems.get(i).getColor();
794 }
795 // Make the last item transparency or default for forecolor
796 colorList[colorList.length - 1] = null;
797
798 return colorList;
799 }
800 return new Colour[] { Colour.BLACK, Colour.WHITE, null };
801 }
802
803 public static String getLink(Item item, String alt) {
804 if (item == null || !(item instanceof Text)) {
805 return alt;
806 }
807
808 AttributeValuePair avp = new AttributeValuePair(item.getText());
809 assert (avp != null);
810
811 if (avp.hasPair() && avp.getValue().trim().length() != 0) {
812 item.setLink(avp.getValue());
813 return avp.getValue();
814 } else if (item.getLink() != null) {
815 return item.getAbsoluteLink();
816 }
817
818 return alt;
819 }
820
821 public static String getDir(String name) {
822 if (name != null) {
823 File tester = new File(name);
824 if (tester.exists() && tester.isDirectory()) {
825 if (name.endsWith(File.separator)) {
826 return name;
827 } else {
828 return name + File.separator;
829 }
830 } else {
831 throw new RuntimeException("Directory not found: " + name);
832 }
833 }
834 throw new RuntimeException("Missing value for profile attribute" + name);
835 }
836
837 public static ArrayList<String> getDirs(Item item) {
838 ArrayList<String> dirsToAdd = new ArrayList<String>();
839 String dirListFrameName = item.getAbsoluteLink();
840 if (dirListFrameName != null) {
841 Frame dirListFrame = FrameIO.LoadFrame(dirListFrameName);
842 if (dirListFrame != null) {
843 for (Text t : dirListFrame.getBodyTextItems(false)) {
844 String dirName = t.getText().trim();
845 File tester = new File(dirName);
846 if (tester.exists() && tester.isDirectory()) {
847 if (dirName.endsWith(File.separator)) {
848 dirsToAdd.add(dirName);
849 } else {
850 dirsToAdd.add(dirName + File.separator);
851 }
852 }
853 }
854 }
855 }
856
857 return dirsToAdd;
858 }
859
860 public static void Parse(Frame toParse) {
861 Parse(toParse, false);
862 }
863
864 /**
865 * Checks for any special Annotation items and updates the display as necessary.
866 * Special Items: Images, overlays, sort.
867 *
868 */
869 public static void Parse(Frame toParse, boolean firstParse) {
870 Parse(toParse, firstParse, false);
871 }
872
873 /**
874 *
875 * @param toParse
876 * @param firstParse
877 * @param ignoreAnnotations
878 * used to prevent infinate loops such as when performing TDFC with
879 * an ao tag linked to a frame with an frameImage of a frame which
880 * also has an ao tag on it.
881 */
882 public static void Parse(Frame toParse, boolean firstParse, boolean ignoreAnnotations) {
883 // TODO check why we are getting toParse == null... when profile frame
884 // is being created and change the lines below
885 if (toParse == null) {
886 return;
887 }
888
889 if (firstParse) {
890 ItemUtils.EnclosedCheck(toParse.getItems());
891 }
892
893 List<Item> items = toParse.getItems();
894
895 // if XRayMode is on, replace pictures with their underlying text
896 if (DisplayController.isXRayMode()) {
897
898 // BROOK: Must handle these a little different
899 List<Widget> widgets = toParse.getInteractiveWidgets();
900
901 for (Item i : items) {
902 if (i instanceof XRayable) {
903 toParse.removeItem(i);
904 // Show the items
905 for (Item item : ((XRayable) i).getConnected()) {
906 item.setVisible(true);
907 item.removeEnclosure(i);
908 }
909 } else if (i instanceof WidgetCorner) {
910 toParse.removeItem(i);
911 } else if (i instanceof WidgetEdge) {
912 toParse.removeItem(i);
913 } else if (i.hasFormula()) {
914 i.setText(i.getFormula());
915 } else if (i.hasOverlay()) {
916 i.setVisible(true);
917 // int x = i.getBoundsHeight();
918 }
919 }
920
921 for (Widget iw : widgets) {
922 toParse.addItem(iw.getSource());
923 }
924 }
925
926 // Text title = null;
927 // Text template = UserSettingsTemplate.copy();
928
929 List<Overlay> overlays = new ArrayList<Overlay>();
930 List<Vector> vectors = new ArrayList<Vector>();
931
932 // disable reading of cached overlays if in twinframes mode
933 if (DisplayController.isTwinFramesOn()) {
934 FrameIO.SuspendCache();
935 }
936
937 // DotType pointtype = DotType.square;
938 // boolean filledPoints = true;
939
940 UserAppliedPermission permission = toParse.getUserAppliedPermission();
941 toParse.clearAnnotations();
942
943 // check for any new overlay items
944 for (Item i : toParse.getItems()) {
945 try {
946 // reset overlay permission
947 i.setOverlayPermission(null);
948 // i.setPermission(permission);
949 if (i instanceof WidgetCorner) {
950 // TODO improve efficiency so it only updates once... using
951 // observer design pattern
952 i.update();
953 } else if (i instanceof Text) {
954 if (i.isAnnotation()) {
955 if (ItemUtils.startsWithTag(i, ItemUtils.TAG_POINTTYPE)) {
956 Text txt = (Text) i;
957 String line = txt.getFirstLine();
958 line = ItemUtils.StripTag(line, ItemUtils.GetTag(ItemUtils.TAG_POINTTYPE));
959
960 if (line != null) {
961 line = line.toLowerCase();
962 if (line.indexOf(" ") > 0) {
963 String fill = line.substring(line.indexOf(" ") + 1);
964 if (fill.startsWith("nofill")) {
965 // filledPoints = false;
966 } else {
967 // filledPoints = true;
968 }
969 }
970
971 if (line.startsWith("circle")) {
972 // pointtype = DotType.circle;
973 } else {
974 // pointtype = DotType.square;
975 }
976 }
977 } // check for new VECTOR items
978 else if (!DisplayController.isXRayMode() && ItemUtils.startsWithTag(i, ItemUtils.TAG_VECTOR)
979 && i.getLink() != null) {
980 if (!i.getAbsoluteLink().equals(toParse.getName())) {
981 addVector(vectors, UserAppliedPermission.none, permission, i);
982 }
983 } else if (!DisplayController.isXRayMode()
984 && ItemUtils.startsWithTag(i, ItemUtils.TAG_ACTIVE_VECTOR) && i.getLink() != null) {
985 if (!i.getAbsoluteLink().equals(toParse.getName())) {
986 addVector(vectors, UserAppliedPermission.followLinks, permission, i);
987 }
988 }
989 // check for new OVERLAY items
990 else if (!ignoreAnnotations && ItemUtils.startsWithTag(i, ItemUtils.TAG_OVERLAY)
991 && i.getLink() != null) {
992 if (i.getAbsoluteLink().equalsIgnoreCase(toParse.getName())) {
993 // This frame contains an active overlay which
994 // points to itself
995 MessageBay.errorMessage(toParse.getName() + " contains an @o which links to itself");
996 continue;
997 }
998
999 Frame overlayFrame = FrameIO.LoadFrame(i.getAbsoluteLink());
1000 // Parse(overlay);
1001 if (overlayFrame != null && Overlay.getOverlay(overlays, overlayFrame) == null) {
1002 overlays.add(new Overlay(overlayFrame, UserAppliedPermission.none));
1003 }
1004 }
1005 // check for ACTIVE_OVERLAY items
1006 else if (!ignoreAnnotations && ItemUtils.startsWithTag(i, ItemUtils.TAG_ACTIVE_OVERLAY)
1007 && i.getLink() != null) {
1008 String link = i.getAbsoluteLink();
1009 if (link.equalsIgnoreCase(toParse.getName())) {
1010 // This frame contains an active overlay which
1011 // points to itself
1012 MessageBay.errorMessage(toParse.getName() + " contains an @ao which links to itself");
1013 continue;
1014 }
1015 Frame overlayFrame = null;
1016
1017 Frame current = DisplayController.getCurrentFrame();
1018 if (current != null) {
1019 for (Overlay o : current.getOverlays()) {
1020 if (o.Frame.getName().equalsIgnoreCase(link)) {
1021 overlayFrame = o.Frame;
1022 }
1023 }
1024 }
1025 if (overlayFrame == null) {
1026 overlayFrame = FrameIO.LoadFrame(link);
1027 }
1028
1029 // get level if specified
1030 String level = new AttributeValuePair(i.getText()).getValue();
1031 // default permission (if none is specified)
1032 PermissionTriple permissionLevel = new PermissionTriple(level,
1033 UserAppliedPermission.followLinks);
1034
1035 if (overlayFrame != null) {
1036 Overlay existingOverlay = Overlay.getOverlay(overlays, overlayFrame);
1037 // If it wasn't in the list create it and add
1038 // it.
1039 if (existingOverlay == null) {
1040 Overlay newOverlay = new Overlay(overlayFrame,
1041 permissionLevel.getPermission(overlayFrame.getOwner(), overlayFrame.getGroupMembers()));
1042 i.setOverlay(newOverlay);
1043 overlays.add(newOverlay);
1044 } else {
1045 existingOverlay.Frame.setPermission(permissionLevel);
1046 }
1047 }
1048 }
1049 // check for Images and widgets
1050 else {
1051 if (!DisplayController.isXRayMode()) {
1052 if (ItemUtils.startsWithTag(i, ItemUtils.TAG_IMAGE, true)) {
1053 if (!i.hasEnclosures()) {
1054 createPicture(toParse, (Text) i);
1055 }
1056 // check for frame images
1057 } else if (ItemUtils.startsWithTag(i, ItemUtils.TAG_FRAME_IMAGE) && i.getLink() != null
1058 && !i.getAbsoluteLink().equalsIgnoreCase(toParse.getName())) {
1059 XRayable image = null;
1060 if (i.hasEnclosures()) {
1061 // i.setHidden(true);
1062 // image =
1063 // i.getEnclosures().iterator().next();
1064 // image.refresh();
1065 } else {
1066 image = new FrameImage((Text) i, null);
1067 }
1068 // TODO Add the image when creating new
1069 // FrameImage
1070 toParse.addItem(image);
1071 } else if (ItemUtils.startsWithTag(i, ItemUtils.TAG_BITMAP_IMAGE) && i.getLink() != null
1072 && !i.getAbsoluteLink().equalsIgnoreCase(toParse.getName())) {
1073 XRayable image = null;
1074 if (i.hasEnclosures()) {
1075 // image =
1076 // i.getEnclosures().iterator().next();
1077 // image.refresh();
1078 // i.setHidden(true);
1079 } else {
1080 // If a new bitmap is created for a
1081 // frame which already has a bitmap dont
1082 // recreate the bitmap
1083 image = new FrameBitmap((Text) i, null);
1084 }
1085 toParse.addItem(image);
1086 } else if (ItemUtils.startsWithTag(i, "@c")) {
1087 // Can only have a @c
1088 if (!i.hasEnclosures() && i.getLines().size() == 1) {
1089 toParse.addItem(new Circle((Text) i));
1090 }
1091 // Check for JSItem
1092 } else if (ItemUtils.startsWithTag(i, "@js")) {
1093 toParse.addItem(new JSItem((Text) i));
1094 // Check for interactive widgets
1095 } else if (ItemUtils.startsWithTag(i, ItemUtils.TAG_IWIDGET)) {
1096 createWidget(toParse, (Text) i);
1097 }
1098 }
1099 // TODO decide exactly what to do here!!
1100 toParse.addAnnotation((Text) i);
1101 }
1102 } else if (!DisplayController.isXRayMode() && i.hasFormula()) {
1103 i.calculate(i.getFormula());
1104 }
1105 }
1106 } catch (Exception e) {
1107 Logger.Log(e);
1108 e.printStackTrace();
1109 System.err.println("**** Have temporarily supressed MessageBay call, as resulted in infinite recursion");
1110 //MessageBay.warningMessage("Exception occured when loading " + i.getClass().getSimpleName() + "(ID: "
1111 // + i.getID() + ") " + e.getMessage() != null ? e.getMessage() : "");
1112 }
1113 }
1114
1115 /*
1116 * for (Item i : items) { if (i instanceof Dot) { ((Dot)
1117 * i).setPointType(pointtype); ((Dot) i).useFilledPoints(filledPoints); } }
1118 */
1119
1120 FrameIO.ResumeCache();
1121
1122 toParse.clearOverlays();
1123 toParse.clearVectors();
1124 toParse.addAllOverlays(overlays);
1125 toParse.addAllVectors(vectors);
1126
1127 }
1128
1129 /**
1130 * TODO: Comment. cts16
1131 *
1132 * @param vectors
1133 * @param permission
1134 * @param i
1135 */
1136 private static void addVector(List<Vector> vectors, UserAppliedPermission defaultPermission,
1137 UserAppliedPermission framePermission, Item i) {
1138 // TODO It is possible to get into an infinite loop if a
1139 // frame contains an @ao which leads to a frame with an
1140 // @v which points back to the frame with the @ao
1141 Frame vector = FrameIO.LoadFrame(i.getAbsoluteLink());
1142
1143 // Get the permission from off the vector frame
1144 UserAppliedPermission vectorPermission = UserAppliedPermission
1145 .getPermission(vector.getAnnotationValue("permission"), defaultPermission);
1146
1147 // If the frame permission is lower, use that
1148 vectorPermission = UserAppliedPermission.min(vectorPermission, framePermission);
1149
1150 // Highest permissable permission for vectors is copy
1151 vectorPermission = UserAppliedPermission.min(vectorPermission, UserAppliedPermission.copy);
1152 if (vector != null) {
1153 String scaleString = new AttributeValuePair(i.getText()).getValue();
1154 Float scale = 1F;
1155 try {
1156 scale = Float.parseFloat(scaleString);
1157 } catch (Exception e) {
1158 }
1159 Vector newVector = new Vector(vector, vectorPermission, scale, i);
1160 i.setOverlay(newVector);
1161 i.setVisible(false);
1162 vectors.add(newVector);
1163 }
1164 }
1165
1166 public static Item onItem(float floatX, float floatY, boolean changeLastEdited) {
1167 return onItem(DisplayController.getCurrentFrame(), floatX, floatY, changeLastEdited);
1168 }
1169
1170 /**
1171 * Searches through the list of items on this frame to find one at the given x,y
1172 * coordinates.
1173 *
1174 * @param x
1175 * The x coordinate
1176 * @param y
1177 * The y coordinate
1178 * @return The Item at the given coordinates, or NULL if none is found.
1179 */
1180 public static Item onItem(Frame toCheck, float floatX, float floatY, boolean bResetLastEdited) {
1181 // System.out.println("MouseX: " + floatX + " MouseY: " + floatY);
1182 int x = Math.round(floatX);
1183 int y = Math.round(floatY);
1184 if (toCheck == null) {
1185 return null;
1186 }
1187
1188 List<Item> possibles = new ArrayList<Item>(0);
1189
1190 // if the mouse is in the message area
1191 if (y >= DisplayController.getMessageBayPaintArea().getMinY()) {
1192 // check the individual bay items (MessageBay + MailBay)
1193 List<Item> bayItems = new LinkedList<Item>();
1194 if (DisplayController.isMailMode()) {
1195 bayItems.addAll(MailBay.getPreviewMessages());
1196 } else {
1197 bayItems.addAll(MessageBay.getMessages());
1198 }
1199 for (Item message : bayItems) {
1200 if (message != null) {
1201 if (message.contains(new Point(x, y))) {
1202 message.setOverlayPermission(UserAppliedPermission.copy);
1203 possibles.add(message);
1204 } else {
1205 // Not sure why but if the line below is removed then
1206 // several items can be highlighted at once
1207 message.setHighlightMode(Item.HighlightMode.None);
1208 message.setHighlightColorToDefault();
1209 }
1210 }
1211 }
1212
1213 // check the link to the message/mail frame
1214 Item linkItem = DisplayController.isMailMode() ? MailBay.getMailLink() : MessageBay.getMessageLink();
1215 if (linkItem != null && linkItem.contains(new Point(x, y))) {
1216 linkItem.setOverlayPermission(UserAppliedPermission.copy);
1217 possibles.add(linkItem);
1218 }
1219
1220 // this is taken into account in contains
1221 // y -= FrameGraphics.getMaxFrameSize().height;
1222 // otherwise, the mouse is on the frame
1223 } else {
1224 if (LastEdited != null) {
1225 if (LastEdited.contains(x, y) && !FreeItems.getInstance().contains(LastEdited)
1226 && LastEdited.getParent() == DisplayController.getCurrentFrame()
1227 && LastEdited.getParent().getItems().contains(LastEdited)) {
1228 LastEdited.setOverlayPermission(UserAppliedPermission.full);
1229 return LastEdited;
1230 } else if (bResetLastEdited) {
1231 setLastEdited(null);
1232 }
1233 }
1234 ArrayList<Item> checkList = new ArrayList<Item>();
1235 checkList.addAll(toCheck.getInteractableItems());
1236 checkList.add(toCheck.getNameItem());
1237
1238 for (Item i : checkList) {
1239
1240 // do not check annotation items in audience mode
1241 // TODO: Upon hover of Rubbish Bin, Undo and Restore Widgets, flickering occurs
1242 // depending on the mouse distance from a corner. Resolve this.
1243 if (i.isVisible() && !(DisplayController.isAudienceMode() && i.isAnnotation())) {
1244 if (i instanceof WidgetCorner) {
1245 WidgetCorner wc = (WidgetCorner) i;
1246 if (wc.getWidgetSource() instanceof ButtonWidget) {
1247 ButtonWidget bw = (ButtonWidget) wc.getWidgetSource();
1248
1249 if (bw.getdropInteractableStatus() == true) {
1250 Widget iw = wc.getWidgetSource();
1251
1252 if (iw.getBounds().contains(x, y)) {
1253
1254 if (!FreeItems.getInstance().contains(i)) {
1255 possibles.add(i);
1256 }
1257 }
1258 }
1259 }
1260 }
1261
1262 if (i.contains(new Point(x, y))) {
1263 if (!FreeItems.getInstance().contains(i)) {
1264 possibles.add(i);
1265 }
1266 }
1267
1268 }
1269 }
1270 }
1271
1272 // if there are no possible items, return null
1273 if (possibles.size() == 0) {
1274 return null;
1275 }
1276
1277 // if there is only one possibility, return it
1278 if (possibles.size() == 1) {
1279 return possibles.get(0);
1280 }
1281
1282 // return closest x,y pair to mouse
1283 Item closest = possibles.get(0);
1284 int distance = (int) Math.round(
1285 Math.sqrt(Math.pow(Math.abs(closest.getX() - x), 2) + Math.pow(Math.abs(closest.getY() - y), 2)));
1286
1287 for (Item i : possibles) {
1288 int d = (int) Math
1289 .round(Math.sqrt(Math.pow(Math.abs(i.getX() - x), 2) + Math.pow(Math.abs(i.getY() - y), 2)));
1290
1291 // System.out.println(d);
1292 if (d <= distance) {
1293 distance = d;
1294
1295 // dots take precedence over lines
1296 if ((!(closest instanceof Dot && i instanceof Line))
1297 && (!(closest instanceof Text && i instanceof Line))) {
1298 closest = i;
1299 }
1300
1301 }
1302
1303 }
1304
1305 return closest;
1306 }
1307
1308 /**
1309 * Checks if the mouse is currently over an item.
1310 *
1311 * @return True if the mouse is over any item, false otherwise.
1312 */
1313 public static boolean hasCurrentItem() {
1314 return getCurrentItem() != null;
1315 }
1316
1317 public synchronized static Item getCurrentItem() {
1318 return onItem(DisplayController.getCurrentFrame(), DisplayController.getMouseX(), DisplayController.getMouseY(),
1319 true);
1320 }
1321
1322 public static PolygonBounds getEnlosingPolygon() {
1323 Collection<Item> enclosure = getEnclosingLineEnds();
1324
1325 if (enclosure == null || enclosure.size() == 0) {
1326 return null;
1327 }
1328
1329 return enclosure.iterator().next().getEnclosedShape();
1330 }
1331
1332 /**
1333 *
1334 * @param currentItem
1335 * @return
1336 */
1337 public static Collection<Item> getCurrentItems() {
1338 return getCurrentItems(getCurrentItem());
1339 }
1340
1341 public static Collection<Item> getCurrentItems(Item currentItem) {
1342 Collection<Item> enclosure = getEnclosingLineEnds();
1343
1344 if (enclosure == null || enclosure.size() == 0) {
1345 return null;
1346 }
1347
1348 Item firstItem = enclosure.iterator().next();
1349
1350 Collection<Item> enclosed = getItemsEnclosedBy(DisplayController.getCurrentFrame(),
1351 firstItem.getEnclosedShape());
1352
1353 // Brook: enclosed widgets are to be fully enclosed, never partially
1354 /*
1355 * MIKE says: but doesn't this mean that widgets are treated differently from
1356 * ALL other object which only need to be partially enclosed to be picked up
1357 */
1358 List<Widget> enclosedWidgets = new LinkedList<Widget>();
1359 for (Item i : enclosed) {
1360 // Don't want to lose the highlighting from the current item
1361 if (i == currentItem || enclosure.contains(i)) {
1362 continue;
1363 }
1364 // Don't want to lose the highlighting of connected Dots
1365 // TODO: this code does nothing (perhaps the continue is meant for the outer
1366 // for loop?). cts16
1367 if (i instanceof Dot && i.getHighlightMode() == HighlightMode.Connected) {
1368 for (Line l : i.getLines()) {
1369 if (l.getOppositeEnd(i).getHighlightMode() == HighlightMode.Normal) {
1370 continue;
1371 }
1372 }
1373 }
1374
1375 if (i instanceof WidgetCorner) {
1376 if (!enclosedWidgets.contains(((WidgetCorner) i).getWidgetSource())) {
1377 enclosedWidgets.add(((WidgetCorner) i).getWidgetSource());
1378 }
1379 }
1380
1381 i.setHighlightMode(Item.HighlightMode.None);
1382 i.setHighlightColorToDefault();
1383 }
1384
1385 for (Widget iw : enclosedWidgets) {
1386 for (Item i : iw.getItems()) {
1387 if (!enclosed.contains(i)) {
1388 enclosed.add(i);
1389 }
1390 }
1391 }
1392
1393 return enclosed;
1394 }
1395
1396 /**
1397 * Gets the collection of Dot items that form the enclosure nearest to the
1398 * current mouse position.
1399 */
1400 public static Collection<Item> getEnclosingLineEnds() {
1401 return getEnclosingLineEnds(new Point(DisplayController.getMouseX(), DisplayController.getMouseY()));
1402 }
1403
1404 /**
1405 * Gets the collection of Dot items that form the enclosure nearest to the given
1406 * position.
1407 */
1408 public static Collection<Item> getEnclosingLineEnds(Point position) {
1409 // update enclosed shapes
1410 Frame current = DisplayController.getCurrentFrame();
1411 if (current == null) {
1412 return null;
1413 }
1414 List<Item> items = current.getItems();
1415
1416 // Remove all items that are connected to freeItems
1417 List<Item> freeItems = new ArrayList<Item>(FreeItems.getInstance());
1418 while (freeItems.size() > 0) {
1419 Item item = freeItems.get(0);
1420 Collection<Item> connected = item.getAllConnected();
1421 items.removeAll(connected);
1422 freeItems.removeAll(connected);
1423 }
1424
1425 List<Item> used = new ArrayList<Item>(0);
1426
1427 while (items.size() > 0) {
1428 Item i = items.get(0);
1429 items.remove(i);
1430 if (i.isEnclosed()) {
1431 PolygonBounds p = i.getEnclosedShape();
1432 if (p.contains(position)) {
1433 used.add(i);
1434 items.removeAll(i.getEnclosingDots());
1435 }
1436 }
1437 }
1438
1439 if (used.size() == 0) {
1440 return null;
1441 }
1442
1443 // if there is only one possibility, return it
1444 if (used.size() == 1) {
1445 return used.get(0).getEnclosingDots();
1446 // otherwise, determine which polygon is closest to the cursor
1447 } else {
1448 Collections.sort(used, new Comparator<Item>() {
1449 @Override
1450 public int compare(Item d1, Item d2) {
1451 PolygonBounds p1 = d1.getEnclosedShape();
1452 PolygonBounds p2 = d2.getEnclosedShape();
1453
1454 int closest = Integer.MAX_VALUE;
1455 int close2 = Integer.MAX_VALUE;
1456
1457 int mouseX = DisplayController.getMouseX();
1458 int mouseY = DisplayController.getMouseY();
1459
1460 for (int i = 0; i < p1.getPointCount(); i++) {
1461 int diff = Math.abs(p1.getPoint(i).getX() - mouseX) + Math.abs(p1.getPoint(i).getY() - mouseY);
1462 int diff2 = Integer.MAX_VALUE;
1463
1464 if (i < p2.getPointCount()) {
1465 diff2 = Math.abs(p2.getPoint(i).getX() - mouseX) + Math.abs(p2.getPoint(i).getY() - mouseY);
1466 }
1467
1468 if (diff < Math.abs(closest)) {
1469 close2 = closest;
1470 closest = diff;
1471 } else if (diff < Math.abs(close2)) {
1472 close2 = diff;
1473 }
1474
1475 if (diff2 < Math.abs(closest)) {
1476 close2 = closest;
1477 closest = -diff2;
1478 } else if (diff2 < Math.abs(close2)) {
1479 close2 = diff2;
1480 }
1481 }
1482
1483 if (closest > 0 && close2 > 0) {
1484 return -10;
1485 }
1486
1487 if (closest < 0 && close2 < 0) {
1488 return 10;
1489 }
1490
1491 if (closest > 0) {
1492 return -10;
1493 }
1494
1495 return 10;
1496 }
1497
1498 });
1499
1500 return used.get(0).getEnclosingDots();
1501 }
1502 }
1503
1504 // TODO Remove this method!!
1505 // Can just getItemsWithin be used?
1506 public static Collection<Item> getItemsEnclosedBy(Frame frame, PolygonBounds poly) {
1507 Collection<Item> contained = frame.getItemsWithin(poly);
1508
1509 Collection<Item> results = new LinkedHashSet<Item>(contained.size());
1510
1511 // check for correct permissions
1512 for (Item item : contained) {
1513 // if the item is on the frame
1514 if (item.getParent() == frame || item.getParent() == null) {
1515 // item.Permission = Permission.full;
1516 results.add(item);
1517 // otherwise, it must be on an overlay frame
1518 } else {
1519 for (Overlay overlay : frame.getOverlays()) {
1520 if (overlay.Frame == item.getParent()) {
1521 item.setOverlayPermission(overlay.permission);
1522 results.add(item);
1523 break;
1524 }
1525 }
1526 }
1527 }
1528
1529 return results;
1530 }
1531
1532 public static void CreateDefaultProfile(String profileFor, Frame profile) {
1533 CreateDefaultProfile(profileFor, profile, null, null);
1534 }
1535
1536 /**
1537 * Copies the content from the default profile to the specified profile.
1538 * @param profileFor Name of profile that is destination of copy.
1539 * @param profile Profile being setup.
1540 * @param specifiedTextSettings text settings to provide a default value for in the new profile
1541 * @param specifiedGenericSettings generic settings to provide a default value for in the new profile
1542 */
1543 public static void CreateDefaultProfile(String profileFor, Frame profile,
1544 Map<String, Setting> specifiedSettings, Map<String, Consumer<Frame>> notifyWhenGenerated) {
1545 // If this is already the default profile then nothing (other than setting
1546 // title) needs to be done.
1547 Text titleItem = profile.getTitleItem();
1548 Text title = titleItem;
1549 if (!profileFor.equals(UserSettings.DEFAULT_PROFILE_NAME)) {
1550 // If this profile is not the default profile, copy it from the default profile
1551 // instead of generating a new profile
1552 // (this allows the possibility of modifying the default profile and having any
1553 // new profiles get those modifications)
1554 Frame defaultFrame = FrameIO.LoadProfile(UserSettings.DEFAULT_PROFILE_NAME);
1555 if (defaultFrame == null) {
1556 try {
1557 // If we do not have a default to copy, create one.
1558 defaultFrame = FrameIO.CreateNewProfile(UserSettings.DEFAULT_PROFILE_NAME, null, null);
1559 } catch (InvalidFramesetNameException invalidNameEx) {
1560 MessageBay.errorMessage("Failed to create default profile named: "
1561 + UserSettings.DEFAULT_PROFILE_NAME + ". "
1562 + "Profile names must start and end with a letter and must contain only letters and numbers.");
1563 return;
1564 } catch (ExistingFramesetException existingFramesetEx) {
1565 MessageBay.errorMessage("Failed to create the desired default frameset: "
1566 + UserSettings.DEFAULT_PROFILE_NAME + ", "
1567 + "because it already exists. This should never happen as we shouldn't be asking to create it if it already exists.");
1568 return;
1569 }
1570 }
1571
1572 MessageBay.suppressMessages(true);
1573 int lastNumber = FrameIO.getLastNumber(defaultFrame.getFramesetName());
1574 for (int i = 1; i <= lastNumber; i++) {
1575 // Load in next default, if it doesn't exist continue loop.
1576 defaultFrame = FrameIO.LoadFrame(defaultFrame.getFramesetName() + i);
1577 if (defaultFrame == null) {
1578 continue;
1579 }
1580
1581 // Create the next next (currently blank) profile frame.
1582 // If there is frame gaps in the default (say if there is no 4.exp but there is
1583 // a 5.exp) then retain those gaps.
1584 // This way copied relative links work.
1585 while (profile.getNumber() < defaultFrame.getNumber()) {
1586 profile = FrameIO.CreateFrame(profile.getFramesetName(), null, null);
1587 }
1588 // Ensure we are working from a blank slate.
1589 profile.reset();
1590 profile.removeAllItems(profile.getAllItems());
1591
1592 // For each item on defaultFrame:
1593 // 1. Set all items to be relatively linked so once copied their links correctly
1594 // point to the frame on the created profile rather than the default profile.
1595 // 2. Copy item from defaultFrame to the current profile frame being
1596 // constructed.
1597 // 3. Replace settings values of copied items with those specified in
1598 // specifiedSettings (if present)
1599 for (Item item : defaultFrame.getAllItems()) {
1600 item.setRelativeLink();
1601 }
1602 profile.addAllItems(defaultFrame.getAllItems());
1603 if (i == 1 && titleItem != null) {
1604 titleItem.setText(profileFor + "'s Profile");
1605 }
1606 String category = profile.getTitle();
1607 List<String> settingsKeys = null;
1608 if (specifiedSettings != null) {
1609 settingsKeys = specifiedSettings.keySet().stream().filter(key ->
1610 key.startsWith(category)).collect(Collectors.toList());
1611 }
1612 if (settingsKeys != null) {
1613 for (String key: settingsKeys) {
1614 Setting setting = specifiedSettings.get(key);
1615 String name = setting.getName();
1616 Text representation = setting.generateRepresentation(name, profile.getFramesetName());
1617 Collection<Text> canditates = profile.getTextItems();
1618 canditates.removeIf(text -> !text.getText().startsWith(representation.getText().split(" ")[0]));
1619 canditates.forEach(text -> {
1620 Point backupPos = text.getPosition();
1621 Item.DuplicateItem(representation, text);
1622 text.setText(representation.getText());
1623 text.setPosition(backupPos);
1624 });
1625 }
1626 }
1627 if (notifyWhenGenerated != null && notifyWhenGenerated.containsKey(category)) {
1628 notifyWhenGenerated.get(category).accept(profile);
1629 }
1630 FrameIO.SaveFrame(profile);
1631 }
1632 MessageBay.suppressMessages(false);
1633 } else {
1634 title.setText("Default Profile Frame");
1635 int xPos = 300;
1636 int yPos = 100;
1637 Text t;
1638
1639 // Add documentation links
1640 File helpDirectory = new File(FrameIO.HELP_PATH);
1641 if (helpDirectory != null) {
1642 File[] helpFramesets = helpDirectory.listFiles();
1643 if (helpFramesets != null) {
1644
1645 // Add the title for the help index
1646 Text help = profile.addText(xPos, yPos, "@Expeditee Help", null);
1647 help.setSize(25);
1648 help.setFontStyle("Bold");
1649 help.setFamily("SansSerif");
1650 help.setColor(TemplateSettings.ColorWheel.get()[3]);
1651
1652 xPos += 25;
1653 System.out.println("Installing frameset: ");
1654
1655 boolean first_item = true;
1656 for (File helpFrameset : helpFramesets) {
1657 String framesetName = helpFrameset.getName();
1658 if (!FrameIO.isValidFramesetName(framesetName)) {
1659 continue;
1660 }
1661
1662 if (first_item) {
1663 System.out.print(" " + framesetName);
1664 first_item = false;
1665 } else {
1666 System.out.print(", " + framesetName);
1667 }
1668 System.out.flush();
1669
1670 Frame indexFrame = FrameIO.LoadFrame(framesetName + '1');
1671 // Look through the folder for help index pages
1672 if (indexFrame != null && ItemUtils.FindTag(indexFrame.getItems(), "@HelpIndex") != null) {
1673 // yPos += spacing;
1674 yPos += 30;
1675 t = profile.addText(xPos, yPos, '@' + indexFrame.getFramesetName(), null);
1676 t.setLink(indexFrame.getName());
1677 t.setColor(Colour.GREY);
1678 }
1679 }
1680 System.out.println();
1681 }
1682 }
1683
1684 xPos = 50;
1685 yPos = 100;
1686
1687 // Populate Start Pages and Settings
1688 File framesetDirectory = new File(FrameIO.FRAME_PATH);
1689
1690 if (framesetDirectory.exists()) {
1691 File[] startpagesFramesets = framesetDirectory.listFiles();
1692
1693 if (startpagesFramesets != null) {
1694 // Add Start Page title
1695 Text templates = profile.addText(xPos, yPos, "@Start Pages", null);
1696 templates.setSize(25);
1697 templates.setFontStyle("Bold");
1698 templates.setFamily("SansSerif");
1699 templates.setColor(TemplateSettings.ColorWheel.get()[3]);
1700
1701 xPos += 25;
1702
1703 // Start Pages should be the first frame in its own frameset +
1704 // frameset name should be present in FrameUtils.startPages[].
1705 for (File startpagesFrameset : startpagesFramesets) {
1706 String framesetName = startpagesFrameset.getName();
1707
1708 // Only add link if frameset is a startpage
1709 for (int i = 0; i < startPages.length; i++) {
1710 if (framesetName.equals(startPages[i])) {
1711 Frame indexFrame = FrameIO.LoadFrame(framesetName + '1');
1712
1713 // Add start page link
1714 if (indexFrame != null) {
1715 yPos += 30;
1716 t = profile.addText(xPos, yPos, '@' + indexFrame.getFramesetName(), null);
1717 t.setLink(indexFrame.getName());
1718 t.setColor(Colour.GREY);
1719 }
1720 }
1721 }
1722 }
1723 }
1724 }
1725
1726 FrameIO.SaveFrame(profile);
1727
1728 // Populate settings frameset
1729 Settings.Init();
1730 t = profile.addText(550, 100, "@Settings", null);
1731 t.setSize((float) 25.0);
1732 t.setFamily("SansSerif");
1733 t.setFontStyle("Bold");
1734 t.setColor(Colour.GREY);
1735 Settings.generateSettingsTree(t);
1736 System.out.println("@Settings: Default settings generation complete.");
1737
1738 FrameIO.SaveFrame(profile);
1739 }
1740 }
1741
1742 private static void checkTDFCItemWaiting(Frame currentFrame) {
1743 Item tdfcItem = FrameUtils.getTdfcItem();
1744 // if there is a TDFC Item waiting
1745 if (tdfcItem != null) {
1746 boolean change = currentFrame.hasChanged();
1747 boolean saved = currentFrame.isSaved();
1748 // Save the parent of the item if it has not been saved
1749 if (!change && !saved) {
1750 tdfcItem.setLink(null);
1751 tdfcItem.getParent().setChanged(true);
1752 FrameIO.SaveFrame(tdfcItem.getParent());
1753 DisplayController.requestRefresh(true);
1754 } else {
1755 SessionStats.CreatedFrame();
1756 }
1757
1758 setTdfcItem(null);
1759 }
1760 }
1761
1762 public static void setTdfcItem(Item _tdfcItem) {
1763 FrameUtils._tdfcItem = _tdfcItem;
1764 }
1765
1766 public static Item getTdfcItem() {
1767 return FrameUtils._tdfcItem;
1768 }
1769
1770 public static void setLastEdited(Text lastEdited) {
1771 // If the lastEdited is being changed then check if its @i
1772 Frame toReparse = null;
1773 Frame toRecalculate = null;
1774 Frame toUpdateObservers = null;
1775
1776 if (LastEdited == null) {
1777 // System.out.print("N");
1778 } else if (LastEdited != null) {
1779 // System.out.print("T");
1780 Frame parent = LastEdited.getParentOrCurrentFrame();
1781
1782 if (lastEdited != LastEdited) {
1783 if (LastEdited.startsWith("@i")) {
1784 // Check if its an image that can be resized to fit a box
1785 // around it
1786 String text = LastEdited.getText();
1787 if (text.startsWith("@i:") && !Character.isDigit(text.charAt(text.length() - 1))) {
1788 Collection<Item> enclosure = FrameUtils.getEnclosingLineEnds(LastEdited.getPosition());
1789 if (enclosure != null) {
1790 for (Item i : enclosure) {
1791 if (i.isLineEnd() && i.isEnclosed()) {
1792 DisplayController.getCurrentFrame().removeAllItems(enclosure);
1793 AxisAlignedBoxBounds rect = i.getEnclosedBox();
1794 LastEdited.setText(LastEdited.getText() + " " + Math.round(rect.getWidth()));
1795 LastEdited.setPosition(rect.getTopLeft());
1796 LastEdited.setThickness(i.getThickness());
1797 LastEdited.setBorderColor(i.getColor());
1798 break;
1799 }
1800 }
1801 StandardGestureActions.deleteItems(enclosure, false);
1802 }
1803 }
1804 toReparse = parent;
1805 } else if (LastEdited.recalculateWhenChanged()) {
1806 toRecalculate = parent;
1807 }
1808
1809 if (parent.hasObservers()) {
1810 toUpdateObservers = parent;
1811 }
1812 // Update the formula if in XRay mode
1813 if (DisplayController.isXRayMode() && LastEdited.hasFormula()) {
1814 LastEdited.setFormula(LastEdited.getText());
1815 }
1816 }
1817 if (lastEdited != LastEdited && LastEdited.getText().length() == 0 && LastEdited.getMinWidth() == null) {
1818 parent.removeItem(LastEdited);
1819 }
1820 }
1821 LastEdited = lastEdited;
1822
1823 if (!DisplayController.isXRayMode()) {
1824 if (toReparse != null) {
1825 Parse(toReparse, false, false);
1826 } else {
1827 if (toRecalculate != null) {
1828 toRecalculate.recalculate();
1829 }
1830
1831 if (toUpdateObservers != null) {
1832 toUpdateObservers.notifyObservers(false);
1833 }
1834 }
1835 }
1836 }
1837
1838 /**
1839 * Extracts files/folders from assets/resources-public and assets/resources-private
1840 * to {Expeditee Home}/resources-public and {Expeditee Home}/resources-private respectively.
1841 * @param force if true, resources will be extracted even ifthey have already been extracted before.
1842 */
1843 public static void extractResources(boolean force) {
1844 // Ensure groups area exists
1845 if (UserSettings.PublicAndPrivateResources) {
1846 // Extract private resources
1847 Path resourcesPrivate = Paths.get(FrameIO.PARENT_FOLDER).resolve("resources-private");
1848 extractResources("org/expeditee/assets/resources-private", resourcesPrivate, force);
1849
1850 // Extract public resources
1851 Path resourcesPublic = Paths.get(FrameIO.PARENT_FOLDER).resolve("resources-public");
1852 extractResources("org/expeditee/assets/resources-public", resourcesPublic, force);
1853 } else if (AuthenticatorBrowser.isAuthenticationRequired()) {
1854 // Deal with the instance of being in the old regime but using authentication.
1855
1856 // Ensure additional framesets
1857 Path framesetsDir = Paths.get(FrameIO.FRAME_PATH);
1858 boolean extracted = extractResources("org/expeditee/assets/resources-public/framesets", framesetsDir, force);
1859
1860 // Ensure additional images
1861 Path imagesDir = Paths.get(FrameIO.IMAGES_PATH);
1862 extracted |= extractResources("org/expeditee/assets/resources-public/images", imagesDir, force);
1863
1864 // Ensure deaddrops area exists
1865 extracted |= Paths.get(FrameIO.PARENT_FOLDER).resolve("deaddrops").toFile().mkdir();
1866
1867 if (extracted) {
1868 @SuppressWarnings("resource")
1869 Scanner in = new Scanner(System.in);
1870 System.out.println("Extracting resources...In order to use authentication, you need a new default profile frameset.");
1871 System.out.println("This is a destructive process, your existing default profile frameset (if it exists) will be deleted.");
1872 System.out.print("Do you want to proceed? y/N:");
1873 System.out.flush();
1874 String answer = in.nextLine();
1875 if (!answer.toLowerCase().startsWith("y")) {
1876 System.out.println("Exiting...");
1877 System.exit(1);
1878 }
1879
1880 // Ensure the default profile is 'update to date'.
1881 //NB: this limits the potential for those running old regime with authentication the ability to customise the default profile.
1882 Path defaultProfile = Paths.get(FrameIO.PROFILE_PATH).resolve("default");
1883 try {
1884 Files.walkFileTree(defaultProfile, new FileVisitor<Path>() {
1885 @Override
1886 public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
1887 dir.toFile().delete();
1888 return FileVisitResult.CONTINUE;
1889 }
1890 @Override
1891 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
1892 return FileVisitResult.CONTINUE;
1893 }
1894 @Override
1895 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
1896 file.toFile().delete();
1897 return FileVisitResult.CONTINUE;
1898 }
1899 @Override
1900 public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
1901 return FileVisitResult.CONTINUE;
1902 }
1903 });
1904 } catch (IOException e) {
1905 e.printStackTrace();
1906 }
1907 }
1908 }
1909 }
1910
1911 private static boolean extractResources(String source, Path destination, boolean force) {
1912 // If resources have already been extracted, and we are not forcing the extraction, there is nothing to do.
1913 if (!force && destination.resolve(".res").toFile().exists()) {
1914 return false;
1915 }
1916
1917 System.out.println("Extracting/Installing resources to: " + destination.getFileName());
1918
1919 // Create the destination
1920 destination.getParent().toFile().mkdirs();
1921
1922 ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
1923 URL resourceUrl = classLoader.getResource(source);
1924 if (resourceUrl.getProtocol().equals("jar")) {
1925 try {
1926 JarURLConnection ju_connection = (JarURLConnection) resourceUrl.openConnection();
1927 JarFile jf = ju_connection.getJarFile();
1928 Enumeration<JarEntry> jarEntries = jf.entries();
1929 extractFromJarFile(classLoader, jarEntries, source, destination);
1930 } catch (IOException e) {
1931 System.err.println("Error: FrameUtils::extractResources. Exception whilst extracting resources from Jar File. Message: " + e.getMessage());
1932 }
1933 } else if (resourceUrl.getProtocol().equals("bundleresource")) {
1934 try {
1935 URLConnection urlConnection = resourceUrl.openConnection();
1936 Class<?> c = urlConnection.getClass();
1937 java.lang.reflect.Method toInvoke = c.getMethod("getFileURL");
1938 URL fileURL = (URL) toInvoke.invoke(urlConnection);
1939 extractResourcesFromFolder(new File(fileURL.getPath()), source, destination);
1940 } catch (IOException e) {
1941 System.err.println("Error: FrameUtils::extractResources. Problem opening connection to bundleresource. Message: " + e.getMessage());
1942 } catch (NoSuchMethodException e) {
1943 System.err.println("Error: FrameUtils::extractResources. Unable to find method URLConnection::getFileURL. Message: " + e.getMessage());
1944 } catch (InvocationTargetException e) {
1945 System.err.println("Error: FrameUtils::extractResources. Problem invoking URLConnection::getFileURL. Message: " + e.getMessage());
1946 } catch (IllegalAccessException e) {
1947 System.err.println("Error: FrameUtils::extractResources. Problem invoking URLConnection::getFileURL. Message: " + e.getMessage());
1948 }
1949 } else {
1950 try {
1951 File folder = new File(resourceUrl.toURI().getPath());
1952 extractResourcesFromFolder(folder, source, destination);
1953 } catch (URISyntaxException e) {
1954 System.err.println("Error: FrameUtils::extractResources. Problem converting URL to URI. Message: " + e.getMessage());
1955 } catch (IOException e) {
1956 System.err.println("Error: FrameUtils::extractResources. Exception whilst extracting resources from folder. Message: " + e.getMessage());
1957 }
1958 }
1959
1960 // Create the .res file to signal completion
1961 try {
1962 destination.resolve(".res").toFile().createNewFile();
1963 } catch (IOException e) {
1964 System.err.println("Error: FrameUtils::extractResources. Unable to create the .res file to flag that resources have been extracted. Message: " + e.getMessage());
1965 }
1966
1967 return true;
1968 }
1969
1970 private static void extractFromJarFile(ClassLoader classLoader, Enumeration<JarEntry> jarEntries, String source, Path destination) throws IOException {
1971 while (jarEntries.hasMoreElements()) {
1972 ZipEntry ze = jarEntries.nextElement();
1973 if (!ze.getName().startsWith(source)) {
1974 continue;
1975 }
1976 File out = destination.resolve(ze.getName().substring(source.length())).toFile();
1977 if (ze.isDirectory()) {
1978 out.mkdirs();
1979 continue;
1980 }
1981 FileOutputStream fOut = null;
1982 InputStream fIn = null;
1983 try {
1984 fOut = new FileOutputStream(out);
1985 fIn = classLoader.getResourceAsStream(ze.getName());
1986 byte[] bBuffer = new byte[1024];
1987 int nLen;
1988 while ((nLen = fIn.read(bBuffer)) > 0) {
1989 fOut.write(bBuffer, 0, nLen);
1990 }
1991 fOut.flush();
1992 } catch (Exception e) {
1993 e.printStackTrace();
1994 } finally {
1995 if (fOut != null) {
1996 fOut.close();
1997 }
1998 if (fIn != null) {
1999 fIn.close();
2000 }
2001 }
2002 }
2003 }
2004
2005 private static void extractResourcesFromFolder(File folder, String source, Path destination) throws IOException {
2006 LinkedList<File> items = new LinkedList<File>();
2007 items.addAll(Arrays.asList(folder.listFiles()));
2008 LinkedList<File> files = new LinkedList<File>();
2009
2010 while (items.size() > 0) {
2011 File file = items.remove(0);
2012 if (file.isFile()) {
2013 if (!file.getName().contains(".svn")) {
2014 files.add(file);
2015 }
2016 } else {
2017 if (!file.getName().contains(".svn")) {
2018 items.addAll(Arrays.asList(file.listFiles()));
2019 }
2020 }
2021 }
2022 for (File file : files) {
2023 String path = file.getPath();
2024 System.out.println(path);
2025 Path relativize = folder.toPath().relativize(Paths.get(file.getPath()));
2026 File out = destination.resolve(relativize).toFile();
2027 copyFile(file, out, true);
2028 }
2029 }
2030
2031 /**
2032 * @param src
2033 * @param dst
2034 * @throws IOException
2035 */
2036 public static void copyFile(File src, File dst, boolean overWrite) throws IOException {
2037 if (!overWrite && dst.exists()) {
2038 return;
2039 }
2040
2041 dst.getParentFile().mkdirs();
2042 FileOutputStream fOut = null;
2043 FileInputStream fIn = null;
2044 try {
2045 // System.out.println(out.getPath());
2046 fOut = new FileOutputStream(dst);
2047 fIn = new FileInputStream(src);
2048 byte[] bBuffer = new byte[1024];
2049 int nLen;
2050 while ((nLen = fIn.read(bBuffer)) > 0) {
2051 fOut.write(bBuffer, 0, nLen);
2052 }
2053 fOut.flush();
2054 } catch (Exception e) {
2055 e.printStackTrace();
2056 } finally {
2057 if (fOut != null) {
2058 fOut.close();
2059 }
2060 if (fIn != null) {
2061 fIn.close();
2062 }
2063 }
2064 }
2065
2066 public static Text getLastEdited() {
2067 return LastEdited;
2068 }
2069
2070 public static Collection<Text> getCurrentTextItems() {
2071 Collection<Text> currentTextItems = new LinkedHashSet<Text>();
2072 Collection<Item> currentItems = getCurrentItems(null);
2073 if (currentItems != null) {
2074 for (Item i : getCurrentItems(null)) {
2075 if (i instanceof Text && !i.isLineEnd()) {
2076 currentTextItems.add((Text) i);
2077 }
2078 }
2079 }
2080 return currentTextItems;
2081 }
2082}
Note: See TracBrowser for help on using the repository browser.