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

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

Support for new regime in the form of new fields and conditional setting of all paths fields.

Settings are now able to generate their own representation. This allows for the user to explicitly inspect the default values.

When profiles are created, an optional parameter may now be provided. If not null, the new map parameter can contain default values for settings to apply to this profile. This allows for the creation of profiles to that have (for example), their username set to an explicit value. Multiuser mode uses this functionality for usernames and key values among other things.

Frames can now be asked were they are located on the file system. Furthermore, frame indirection is now a thing. Rather than containing data to display, an exp file can contain a line in the format of "REDIRECT:<path>" to go looking for that data. Frames are able to return both their logical (their exp file) and real (the file actually containing the data) paths.

Frames can now store data.

Further fixes to how edits from other users are loaded in.

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