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

Last change on this file since 1102 was 1102, checked in by davidb, 6 years ago

Reworking of the code-base to separate logic from graphics. This version of Expeditee now supports a JFX graphics as an alternative to SWING

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