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

Last change on this file was 1543, checked in by davidb, 4 years ago

When unpacking resources from a JAR file, important that when specifying a directory, it ends in the directory separator

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