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

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

Frame::Parse has been updated to include a new boolean parameter. When true, widgets that are created as a result of the parse send not only notify the widget framework that they have been added, but are also visible. When false, they only notify they have been added.

The widget framework now distinguishes between added and visible widgets, this fixes a bug. Bug: when programmatically adding a widget to not the current frame, it never gets properly removed and therefore still catches click events from users. By distinguishing between adding and making visible this is avoided.


Another bug has been fixed. Bug: When setting a text item to have a right anchor, and then subsequently reducing the size of the window, this text item would get a width of zero assigned. This was caused by some issues with the logic of how right margins for items were calculated.

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