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

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