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

Last change on this file since 1508 was 1508, checked in by bnemhaus, 4 years ago

The HetrogeneousEncryptionLabels frame property is now set automatically when the owner of the frame is saving out an encrypted frame.
Also fixed some issues setting frame properties on load. For example, when loading a frame, the FrameEncryptionLabel should be set regardless of if you have permission to set the label.

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