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

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

Added support for running with the old regime but with authentication. There is a slight limitation in that the new default profile frameset must be copied over upon resources extraction. This means that any existing changes to the default frameset are whipped when this occurs. However, this only occurs if resources are being extracted, so should only ever happen once. This means the user is able to restore their changes to the default after this has happened.

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 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.