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

Last change on this file since 1264 was 1244, checked in by davidb, 5 years ago

After change to have resources-public and resources-private, some changes needed to support running Expeditee for a single user; other main change is to allow FrameDirs to specify relative directory paths, to help with when Expeditee is run on the cloud -- similar work still needs to occurr for ImageDir and AudioDir; some other minor changes also made.

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