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

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

On profile creation a new parameter has been introducted. By passing a map, the user is able to nominate settings frames for which to be notified of their construction.

The above functionalty has been used to programmatically establish the frame number for a users credentials frame.

On user account creation, the users credential frame is migrated to the <username>-credentials folder and a redirect is setup. Functionality for doing this has been generisised and placed in FrameIO.

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