/**
* FrameUtils.java
* Copyright (C) 2010 New Zealand Digital Library, http://expeditee.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package org.expeditee.gui;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.JarURLConnection;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.function.Consumer;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import org.expeditee.agents.ExistingFramesetException;
import org.expeditee.agents.InvalidFramesetNameException;
import org.expeditee.auth.AuthenticatorBrowser;
import org.expeditee.auth.mail.gui.MailBay;
import org.expeditee.core.Colour;
import org.expeditee.core.Point;
import org.expeditee.core.bounds.AxisAlignedBoxBounds;
import org.expeditee.core.bounds.PolygonBounds;
import org.expeditee.encryption.items.surrogates.Label;
import org.expeditee.gio.EcosystemManager;
import org.expeditee.gio.gesture.StandardGestureActions;
import org.expeditee.gui.management.ResourceManager;
import org.expeditee.gui.management.ResourceUtil;
import org.expeditee.items.Circle;
import org.expeditee.items.Dot;
import org.expeditee.items.FrameBitmap;
import org.expeditee.items.FrameImage;
import org.expeditee.items.Item;
import org.expeditee.items.Item.HighlightMode;
import org.expeditee.items.ItemParentStateChangedEvent;
import org.expeditee.items.ItemUtils;
import org.expeditee.items.JSItem;
import org.expeditee.items.Line;
import org.expeditee.items.PermissionTriple;
import org.expeditee.items.Picture;
import org.expeditee.items.Text;
import org.expeditee.items.UserAppliedPermission;
import org.expeditee.items.XRayable;
import org.expeditee.items.widgets.ButtonWidget;
import org.expeditee.items.widgets.InteractiveWidgetInitialisationFailedException;
import org.expeditee.items.widgets.InteractiveWidgetNotAvailableException;
import org.expeditee.items.widgets.Widget;
import org.expeditee.items.widgets.WidgetCorner;
import org.expeditee.items.widgets.WidgetEdge;
import org.expeditee.setting.Setting;
import org.expeditee.settings.Settings;
import org.expeditee.settings.UserSettings;
import org.expeditee.stats.Logger;
import org.expeditee.stats.SessionStats;
public class FrameUtils {
/**
* The list of known start pages framesets which will have prepopulated links in
* the home frame.
*/
public static final String[] startPages = { "exploratorysearch", "webbrowser" };
private static final int COLUMN_WIDTH = 50;
/**
* Provides a way to monitor the time elapsed between button-down and the
* finished painting.
*/
public static TimeKeeper ResponseTimer = new TimeKeeper();
private static float _ResponseTimeSum = 0;
private static float _LastResponse = 0;
private static Text LastEdited = null;
public static int MINIMUM_INTERITEM_SPACING = -6;
private static Item _tdfcItem = null;
public static float getResponseTimeTotal() {
return _ResponseTimeSum;
}
public static float getLastResponseTime() {
return _LastResponse;
}
/**
* Checks if the given top Item is above the given bottom Item, allowing for the
* X coordinates to be off by a certain width...
*
* @param item1
* The Item to check is above the other Item
* @param item2
* The Item to check is below the top Item
* @return True if top is above bottom, False otherwise.
*/
public static boolean inSameColumn(Item item1, Item item2) {
if (!(item1 instanceof Text) || !(item2 instanceof Text)) {
return false;
}
if (item1.getID() < 0 || item2.getID() < 0) {
return false;
}
int minX = item2.getX();
int maxX = item2.getX() + item2.getBoundsWidth();
int startX = item1.getX();
int endX = item1.getX() + item1.getBoundsWidth();
// Check that the two items left values are close
if (Math.abs(item1.getX() - item2.getX()) > COLUMN_WIDTH) {
return false;
}
// Ensure the two items
if ((minX >= startX && minX <= endX) || (maxX >= startX && maxX <= endX) || (startX >= minX && startX <= maxX)
|| (endX >= minX && endX <= maxX)) {
return true;
}
return false;
}
public static boolean sameBulletType(String bullet1, String bullet2) {
if (bullet1 == null || bullet2 == null) {
return false;
}
if (bullet1.equals("") || bullet2.equals("")) {
return false;
}
if (Character.isLetter(bullet1.charAt(0)) && Character.isLetter(bullet2.charAt(0))) {
return true;
}
if (Character.isDigit(bullet1.charAt(0)) && Character.isDigit(bullet2.charAt(0))) {
return true;
}
// TODO make this more sofisticated
return false;
}
private static boolean needsRenumbering(String s) {
if (s == null || s.equals("")) {
return false;
}
if (!Character.isLetterOrDigit(s.charAt(0))) {
return false;
}
s = s.trim();
// if its all letters then we dont want to auto adjust
if (s.length() > 2) {
for (int i = 0; i < s.length() - 1; i++) {
if (!Character.isLetter(s.charAt(i))) {
return true;
}
}
} else {
return true;
}
return false;
}
/**
*
* @param toAlign
* @param moveAll
* @param adjust
* @return
*/
public static int Align(List toAlign, boolean moveAll, int adjust, List- changedItems) {
Collections.sort(toAlign);
/*
* Single items dont need alignment But if there are two items we may still want
* to format them... ie if they are too close together.
*/
if (toAlign.size() < 1) {
return 0;
}
// get the first item
Text from = toAlign.get(0);
if (from.getParent() == null) {
from = toAlign.get(1);
}
int x = from.getX();
Frame curr = from.getParent();
Text above = curr.getTextAbove(from);
String lastBullet = "";
if (above != null && curr.isNormalTextItem(above)) {
lastBullet = StandardGestureActions.getAutoBullet(above.getText());
} else {
lastBullet = StandardGestureActions.getBullet(toAlign.get(0).getText());
}
if (needsRenumbering(lastBullet)) {
// renumber...
for (int i = 0; i < toAlign.size(); i++) {
Text currentText = toAlign.get(i);
String currentBullet = StandardGestureActions.getAutoBullet(currentText.getText());
if (sameBulletType(lastBullet, currentBullet)) {
String oldText = currentText.getText();
currentText.stripFirstWord();
currentText.setText(lastBullet + currentText.getText());
lastBullet = StandardGestureActions.getAutoBullet(currentText.getText());
// if we changed the item, add to changedItems list
if (changedItems != null && oldText != currentText.getText()
&& !changedItems.contains(currentText)) {
Item copy = currentText.copy();
copy.setID(currentText.getID());
copy.setText(oldText);
changedItems.add(copy);
}
}
}
}
// work out the spacing between the first item and the one above it
int space = 10 + adjust;
// if we are dropping from the title make the space a little bigger
// than normal
// If there are only two items get the gap from the start item on the
// zero frame if there is one
if (above == curr.getTitleItem()) {
Frame zero = FrameIO.LoadFrame(curr.getFramesetName() + '0');
String strGap = zero.getAnnotationValue("start");
if (strGap != null) {
try {
int gap = Integer.parseInt(strGap);
space = gap;
} catch (NumberFormatException nfe) {
}
}
} else if (above != null) {
// Make the gap between all items the same as the gap between
// the first two
space = from.getBounds().getMinY() - above.getBounds().getMaxY();
if (space < MINIMUM_INTERITEM_SPACING) {
space = MINIMUM_INTERITEM_SPACING;
}
if (UserSettings.FormatSpacingMax.get() != null) {
double maxSpace = UserSettings.FormatSpacingMax.get() * above.getSize();
if (maxSpace < space) {
space = (int) Math.round(maxSpace);
}
}
if (UserSettings.FormatSpacingMin.get() != null) {
double minSpace = UserSettings.FormatSpacingMin.get() * above.getSize();
if (minSpace > space) {
space = (int) Math.round(minSpace);
}
}
// Need to do things differently for FORMAT than for DROPPING
if (moveAll && above != curr.getNameItem() && above != curr.getTitleItem()) {
x = above.getX();
int y = above.getBounds().getMaxY() + space + (from.getY() - from.getBounds().getMinY());
if (changedItems != null && (from.getX() != x || from.getY() != y) && !changedItems.contains(from)) {
Item copy = from.copy();
copy.setID(from.getID());
changedItems.add(copy);
}
from.setPosition(x, y);
} else {
x = from.getX();
}
space += adjust;
}
for (int i = 1; i < toAlign.size(); i++) {
Item current = toAlign.get(i);
Item top = toAlign.get(i - 1);
// The bottom of the previous item
int bottom = top.getBounds().getMaxY();
// the difference between the current item's Y coordinate and
// the top of the highlight box
int diff = current.getY() - current.getBounds().getMinY();
int newPos = bottom + space + diff;
if (changedItems != null && ((moveAll && current.getX() != x) || current.getY() != newPos)
&& !changedItems.contains(current)) {
Item copy = current.copy();
copy.setID(current.getID());
changedItems.add(copy);
}
if (moveAll) {
current.setPosition(x, newPos);
} else if (newPos > current.getY()) {
current.setY(newPos);
}
}
// if (insert != null)
// return insert.getY();
// Michael thinks we return the y value for the next new item??
int y = from.getY() + from.getBoundsHeight() + space;
return y;
}
public static int Align(List toAlign, boolean moveAll, int adjust) {
return Align(toAlign, moveAll, adjust, null);
}
public static boolean LeavingFrame(Frame current) {
checkTDFCItemWaiting(current);
// active overlay frames may also require saving if they have been
// changed
for (Overlay o : current.getOverlays()) {
if (!SaveCheck(o.Frame)) {
return false;
}
}
// if the check fails there is no point continuing
if (!SaveCheck(current)) {
return false;
}
for (Item i : current.getSortedItems()) {
i.setHighlightMode(Item.HighlightMode.None);
i.setHighlightColorToDefault();
}
return true;
}
private static boolean SaveCheck(Frame toSave) {
// don't bother saving frames that haven't changed
if (!toSave.hasChanged()) {
return true;
}
// if the frame has been changed, then save it
if (DisplayController.isTwinFramesOn()) {
Frame opposite = DisplayController.getOppositeFrame();
String side = "left";
if (DisplayController.getCurrentSide() == DisplayController.TwinFramesSide.RIGHT) {
side = "right";
}
// if the two frames both have changes, prompt the user for the
// next move
if (opposite.hasChanged() && opposite.equals(toSave)) {
if (EcosystemManager.getGraphicsManager().showDialog("Changes",
"Leaving this frame will discard changes made in the " + side + " Frame. Continue?")) {
FrameIO.SaveFrame(toSave);
DisplayController.Reload(DisplayController.getSideFrameIsOn(opposite));
return true;
} else {
return false;
}
} else if (opposite.hasOverlay(toSave)) {
if (toSave.hasChanged()) {
if (EcosystemManager.getGraphicsManager().showDialog("Changes",
"Leaving this frame will discard changes made in the " + side + " Frame. Continue?")) {
FrameIO.SaveFrame(toSave);
DisplayController.Reload(DisplayController.getSideFrameIsOn(opposite));
return true;
} else {
return false;
}
}
}
// save the current frame and restore the other side
FrameIO.SaveFrame(toSave);
return true;
}
// single-frame mode can just save and return
FrameIO.SaveFrame(toSave);
return true;
}
// TODO: consider reloating this method to Frame class?
protected static Item getAnnotation(Frame frame, String annotationStr) {
Item matched_item = null;
// check for an updated template...
for (Item i : frame.getAnnotationItems()) {
if (ItemUtils.startsWithTag(i, annotationStr)) {
matched_item = i;
break;
}
}
return matched_item;
}
protected static void doFrameTransition(Item frameTransition, Frame from, Frame to) {
String s = frameTransition.getText();
String[] s_array = s.split(":");
if (s_array.length > 1) {
String slide_mode_method = s_array[1].trim();
FrameTransition transition = new FrameTransition(from.getBuffer(), slide_mode_method);
DisplayController.setTransition(from, transition);
System.out.println("Triggered on annotation: " + s);
} else {
System.err.println("Warning: failed to detect frameTransition type");
// TODO: print list as a result of reflection listing
}
}
/**
* Displays the given Frame on the display. If the current frame has changed
* since the last save then it will be saved before the switch is made. The
* caller can also dictate whether the current frame is added to the back-stack
* or not.
*
* @param toDisplay
* The Frame to display on the screen
* @param addToBack
* True if the current Frame should be added to the back-stack, False
* otherwise
*/
public static void DisplayFrame(Frame toDisplay, boolean addToBack, boolean incrementStats) {
if (toDisplay == null) {
return;
}
// Check if group specified that it exists.
String group = toDisplay.getGroup();
Frame groupFrame = null;
if (group != null && group.length() > 0) {
groupFrame = toDisplay.getGroupFrame();
if (groupFrame == null) {
String msg = "This frame specifies the group " + group + " of which you are not a member.";
MessageBay.displayMessage(msg);
}
}
final PermissionTriple framePermissions = toDisplay.getPermission();
if (framePermissions != null
&& framePermissions.getPermission(toDisplay.getOwner(), toDisplay.getGroupMembers()) == UserAppliedPermission.denied) {
MessageBay.errorMessage("Insufficient permissions to navigate to frame: " + toDisplay.getName());
return;
}
Frame current = DisplayController.getCurrentFrame();
// Dont need to do anything if the frame to display is already being
// displayed
if (current.equals(toDisplay)) {
return;
}
// move any anchored connected items
if (FreeItems.hasItemsAttachedToCursor()) {
List
- toAdd = new ArrayList
- ();
List
- toCheck = new ArrayList
- (FreeItems.getInstance());
while (toCheck.size() > 0) {
Item i = toCheck.get(0);
Collection
- connected = i.getAllConnected();
// // Only move completely enclosed items
// if (!toCheck.containsAll(connected)) {
// connected.retainAll(FreeItems.getInstance());
// FreeItems.getInstance().removeAll(connected);
// toCheck.removeAll(connected);
// FrameMouseActions.anchor(connected);
// } else {
// toCheck.removeAll(connected);
// }
// Anchor overlay items where they belong
if (i.getParent() != null && i.getParent() != current) {
FreeItems.getInstance().removeAll(connected);
toCheck.removeAll(connected);
StandardGestureActions.anchor(connected);
} else {
// Add stuff that is partially enclosed
// remove all the connected items from our list to check
toCheck.removeAll(connected);
// Dont add the items that are free
connected.removeAll(FreeItems.getInstance());
toAdd.addAll(connected);
}
}
current.removeAllItems(toAdd);
boolean oldChange = toDisplay.hasChanged();
toDisplay.updateIDs(toAdd);
toDisplay.addAllItems(toAdd);
toDisplay.setChanged(oldChange);
}
if (addToBack && current != toDisplay) {
FrameIO.checkTDFC(current);
}
// if the saving happened properly, we can continue
if (!LeavingFrame(current)) {
MessageBay.displayMessage("Navigation cancelled");
return;
}
if (addToBack && current != toDisplay) {
DisplayController.addToBack(current);
}
Parse(toDisplay);
if (DisplayController.isAudienceMode()) {
// Only need to worry about frame transitions when in Audience Mode
// Test to see if frame transition specified through annotation, and perform it
// if one if found
Item frameTransition = getAnnotation(toDisplay, "@frameTransition");
if (frameTransition != null) {
doFrameTransition(frameTransition, current, toDisplay);
}
}
DisplayController.setCurrentFrame(toDisplay, incrementStats);
StandardGestureActions.updateCursor();
// FrameMouseActions.getInstance().refreshHighlights();
// update response timer
_LastResponse = ResponseTimer.getElapsedSeconds();
_ResponseTimeSum += _LastResponse;
DisplayController.updateTitle();
}
/**
* Loads and displays the Frame with the given framename, and adds the current
* frame to the back-stack if required.
*
* @param framename
* The name of the Frame to load and display
* @param addToBack
* True if the current Frame should be added to the back-stack, false
* otherwise
*/
public static void DisplayFrame(String frameName, boolean addToBack, boolean incrementStats) {
Frame newFrame = getFrame(frameName);
if (newFrame != null) {
// display the frame
DisplayFrame(newFrame, addToBack, incrementStats);
}
}
/**
* Loads and displays the Frame with the given framename and adds the current
* frame to the back-stack. This is the same as calling DisplayFrame(framename,
* true)
*
* @param framename
* The name of the Frame to load and display
*/
public static void DisplayFrame(String framename) {
DisplayFrame(framename, true, true);
}
public static Frame getFrame(String frameName) {
// if the new frame does not exist then tell the user
Frame f = FrameIO.LoadFrame(frameName);
if (f == null) {
MessageBay.errorMessage("Frame '" + frameName + "' could not be loaded.");
}
return f;
}
/**
* Creates a new Picture Item from the given Text source Item and adds it to the
* given Frame.
*
* @return True if the image was created successfully, false otherwise
*/
private static boolean createPicture(Frame frame, Text txt, ItemsList items) {
// attempt to create the picture
Picture pic = ItemUtils.CreatePicture(txt);
// if the picture could not be created successfully
if (pic == null) {
String imagePath = txt.getText();
assert (imagePath != null);
imagePath = new AttributeValuePair(imagePath).getValue().trim();
if (imagePath.length() == 0) {
return false;
// MessageBay.errorMessage("Expected image path after @i:");
} else {
MessageBay.errorMessage("Image " + imagePath + " could not be loaded");
}
return false;
}
frame.addItem(pic, true, items);
return true;
}
private static boolean createPictureInBody(Frame frame, Text txt) {
return createPicture(frame, txt, frame.getBody(false));
}
/**
* Creates an interactive widget and adds it to a frame. If txt has no parent
* the parent will be set to frame.
*
* @param frame
* Frame to add widget to. Must not be null.
*
* @param txt
* Text to create the widget from. Must not be null.
* @param userEdit True if createWidget is being called because a user has made a edit, false otherwise.
*
* @return True if created/added. False if could not create.
*
* @author Brook Novak
*/
private static boolean createWidget(Frame frame, Text txt, ItemsList list, boolean userEdit) {
if (frame == null) {
throw new NullPointerException("frame");
}
if (txt == null) {
throw new NullPointerException("txt");
}
// Safety
if (txt.getParent() == null) {
txt.setParent(frame);
}
Widget iw = null;
try {
iw = Widget.createWidget(txt);
} catch (InteractiveWidgetNotAvailableException e) {
e.printStackTrace();
MessageBay.errorMessage("Cannot create iWidget: " + e.getMessage());
} catch (InteractiveWidgetInitialisationFailedException e) {
e.printStackTrace();
MessageBay.errorMessage("Cannot create iWidget: " + e.getMessage());
} catch (IllegalArgumentException e) {
e.printStackTrace();
MessageBay.errorMessage("Cannot create iWidget: " + e.getMessage());
}
if (iw == null) {
return false;
}
frame.removeItem(txt, true, list);
frame.addAllItems(iw.getItems(), list);
if (userEdit) {
iw.onParentStateChanged(new ItemParentStateChangedEvent(frame, ItemParentStateChangedEvent.EVENT_TYPE_SHOWN));
}
return true;
}
private static boolean createWidgetInBody(Frame frame, Text txt) {
return createWidget(frame, txt, frame.getBody(false), false);
}
public static List ParseProfile(Frame profile) {
List errors = new LinkedList();
if (profile == null) {
return errors;
}
if (profile.getFramesetName().equals(UserSettings.DEFAULT_PROFILE_NAME)) {
return errors;
}
/*
* Make sure the correct cursor shows when turning off the custom cursor and
* reparsing the profile frame
*/
FreeItems.getCursor().clear();
DisplayController.setCursor(Item.HIDDEN_CURSOR);
DisplayController.setCursor(Item.DEFAULT_CURSOR);
// check for settings tags
for (Text item : profile.getBodyTextItems(true)) {
try {
AttributeValuePair avp = new AttributeValuePair(item.getText());
String attributeFullCase = avp.getAttributeOrValue();
if (attributeFullCase == null) {
continue;
}
String attribute = attributeFullCase.trim().toLowerCase().replaceAll("^@", "");
if (attribute.equals("settings")) {
Settings.parseSettings(item);
}
} catch (Exception e) {
if (e.getMessage() != null) {
errors.add(e.getMessage());
} else {
e.printStackTrace();
errors.add("Error parsing [" + item.getText() + "] on " + profile.getName());
}
}
}
// Tell the resource manager that it needs to refresh its context.
ResourceManager.invalidateAllResourceDirectories();
return errors;
}
/**
* Sets the first frame to be displayed.
*
* @param profile
*/
public static void loadFirstFrame(Frame profile) {
if (UserSettings.HomeFrame.get() == null) {
UserSettings.HomeFrame.set(profile.getName());
}
Frame firstFrame = FrameIO.LoadFrame(UserSettings.HomeFrame.get());
if (firstFrame == null) {
MessageBay.warningMessage("Home frame not found: " + UserSettings.HomeFrame);
UserSettings.HomeFrame.set(profile.getName());
DisplayController.setCurrentFrame(profile, true);
} else {
DisplayController.setCurrentFrame(firstFrame, true);
}
}
public static Colour[] getColorWheel(Frame frame) {
if (frame != null) {
List textItems = frame.getBodyTextItems(false);
Colour[] colorList = new Colour[textItems.size() + 1];
for (int i = 0; i < textItems.size(); i++) {
colorList[i] = textItems.get(i).getColor();
}
// Make the last item transparency or default for forecolor
colorList[colorList.length - 1] = null;
return colorList;
}
return new Colour[] { Colour.BLACK, Colour.WHITE, null };
}
public static String getLink(Item item, String alt) {
if (item == null || !(item instanceof Text)) {
return alt;
}
AttributeValuePair avp = new AttributeValuePair(item.getText());
assert (avp != null);
if (avp.hasPair() && avp.getValue().trim().length() != 0) {
item.setLink(avp.getValue());
return avp.getValue();
} else if (item.getLink() != null) {
return item.getAbsoluteLink();
}
return alt;
}
public static String getDir(String name) {
if (name != null) {
File tester = new File(name);
if (tester.exists() && tester.isDirectory()) {
if (name.endsWith(File.separator)) {
return name;
} else {
return name + File.separator;
}
} else {
throw new RuntimeException("Directory not found: " + name);
}
}
throw new RuntimeException("Missing value for profile attribute" + name);
}
public static List getDirs(Item item) {
List dirsToAdd = new ArrayList();
String currentFramesetFlag = ResourceUtil.CURRENT_FRAMESET_FLAG;
boolean need_file_sep_replace = (!File.separator.equals("/"));
String dirListFrameName = item.getAbsoluteLink();
if (dirListFrameName == null) {
return dirsToAdd;
}
Frame dirListFrame = FrameIO.LoadFrame(dirListFrameName);
if (dirListFrame == null) {
return dirsToAdd;
}
List bodyTextItems = dirListFrame.getBodyTextItems(false);
for (Text t: bodyTextItems) {
String dirName = t.getText().trim();
if (need_file_sep_replace) {
dirName = dirName.replace("/",File.separator);
}
boolean isSpecialCase = dirName.startsWith(currentFramesetFlag);
File filePath = Paths.get(FrameIO.PARENT_FOLDER).resolve(dirName).toFile();
boolean locationExists = filePath.exists() && filePath.isDirectory();
//if (isSpecialCase || locationExists) {
if (dirName.endsWith(File.separator)) {
dirsToAdd.add(dirName);
} else {
dirsToAdd.add(dirName + File.separator);
}
//}
}
return dirsToAdd;
}
private static void transformOutOfPlaceItems(Frame toParse, ItemsList toTransform, boolean sendWidgetVisible) {
// Get all items from toTransform that have not been marked as deleted.
List
- items = toParse.getItems(false, toTransform);
// if XRayMode is on, replace pictures with their underlying text
if (DisplayController.isXRayMode()) {
// BROOK: Must handle these a little different
List widgets = toParse.getInteractiveWidgets();
for (Item i : items) {
if (i instanceof XRayable) {
toParse.removeItem(i, true, toTransform);
// Show the items
for (Item item : ((XRayable) i).getConnected()) {
item.setVisible(true);
item.removeEnclosure(i);
}
} else if (i instanceof WidgetCorner) {
toParse.removeItem(i, true, toTransform);
} else if (i instanceof WidgetEdge) {
toParse.removeItem(i, true, toTransform);
} else if (i.hasFormula()) {
i.setText(i.getFormula());
} else if (i.hasOverlay()) {
i.setVisible(true);
// int x = i.getBoundsHeight();
}
}
for (Widget iw : widgets) {
toParse.addItem(iw.getSource(), true, toTransform);
}
}
// disable reading of cached overlays if in twinframes mode
if (DisplayController.isTwinFramesOn()) {
FrameIO.SuspendCache();
}
toParse.clearAnnotations();
// check for any new overlay items
items = toParse.getItems(false, toTransform);
for (Item i : items) {
try {
if (i instanceof WidgetCorner) {
// TODO improve efficiency so it only updates once... using
// observer design pattern
i.update();
} else if (i instanceof Text) {
if (!DisplayController.isXRayMode() && i.isAnnotation()) {
if (ItemUtils.startsWithTag(i, ItemUtils.TAG_IMAGE, true)) {
if (!i.hasEnclosures()) {
createPicture(toParse, (Text) i, toTransform);
}
// check for frame images
} else if (ItemUtils.startsWithTag(i, ItemUtils.TAG_FRAME_IMAGE) && i.getLink() != null
&& !i.getAbsoluteLink().equalsIgnoreCase(toParse.getName())) {
XRayable image = null;
if (i.hasEnclosures()) {
// i.setHidden(true);
// image =
// i.getEnclosures().iterator().next();
// image.refresh();
} else {
image = new FrameImage((Text) i, null);
}
// TODO Add the image when creating new
// FrameImage
toParse.addItem(image, true, toTransform);
} else if (ItemUtils.startsWithTag(i, ItemUtils.TAG_BITMAP_IMAGE) && i.getLink() != null
&& !i.getAbsoluteLink().equalsIgnoreCase(toParse.getName())) {
XRayable image = null;
if (i.hasEnclosures()) {
// image =
// i.getEnclosures().iterator().next();
// image.refresh();
// i.setHidden(true);
} else {
// If a new bitmap is created for a
// frame which already has a bitmap dont
// recreate the bitmap
image = new FrameBitmap((Text) i, null);
}
toParse.addItem(image, true, toTransform);
} else if (ItemUtils.startsWithTag(i, "@c")) {
// Can only have a @c
if (!i.hasEnclosures() && i.getLines().size() == 1) {
Circle circle = new Circle((Text) i);
toParse.addItem(circle, true, toTransform);
}
// Check for JSItem
} else if (ItemUtils.startsWithTag(i, "@js")) {
JSItem jsItem = new JSItem((Text) i);
toParse.addItem(jsItem, true, toTransform);
// Check for interactive widgets
} else if (ItemUtils.startsWithTag(i, ItemUtils.TAG_IWIDGET)) {
createWidget(toParse, (Text) i, toTransform, sendWidgetVisible);
}
// TODO decide exactly what to do here!!
toParse.addAnnotation((Text) i);
} else if (!DisplayController.isXRayMode() && i.hasFormula()) {
i.calculate(i.getFormula());
}
}
} catch (Exception e) {
Logger.Log(e);
e.printStackTrace();
System.err.println("**** Have temporarily supressed MessageBay call, "
+ "as resulted in infinite recursion");
// MessageBay.warningMessage("Exception occured when loading " +
// i.getClass().getSimpleName() + "(ID: "
// + i.getID() + ") " + e.getMessage() != null ? e.getMessage() : "");
}
}
/*
* for (Item i : items) { if (i instanceof Dot) { ((Dot)
* i).setPointType(pointtype); ((Dot) i).useFilledPoints(filledPoints); } }
*/
if (DisplayController.isTwinFramesOn()) {
FrameIO.ResumeCache();
}
}
private static void generatingSupportingItems(Frame toParse,
ItemsList toBuildOff, boolean ignoreAnnotations) {
// Get all items from toBuildOff that have not been marked as deleted.
List
- items = toParse.getItems(false, toBuildOff);
List overlays = new ArrayList();
List vectors = new ArrayList();
// disable reading of cached overlays if in twinframes mode
if (DisplayController.isTwinFramesOn()) {
FrameIO.SuspendCache();
}
UserAppliedPermission permission = toParse.getUserAppliedPermission();
// check for any new overlay items
for (Item i : items) {
try {
// reset overlay permission
i.setOverlayPermission(null);
if (i instanceof Text) {
if (i.isAnnotation()) {
if (!DisplayController.isXRayMode() && ItemUtils.startsWithTag(i, ItemUtils.TAG_VECTOR)
&& i.getLink() != null) {
if (!i.getAbsoluteLink().equals(toParse.getName())) {
addVector(vectors, UserAppliedPermission.none, permission, i);
}
} else if (!DisplayController.isXRayMode()
&& ItemUtils.startsWithTag(i, ItemUtils.TAG_ACTIVE_VECTOR) && i.getLink() != null) {
if (!i.getAbsoluteLink().equals(toParse.getName())) {
addVector(vectors, UserAppliedPermission.followLinks, permission, i);
}
}
// check for new OVERLAY items
else if (!ignoreAnnotations && ItemUtils.startsWithTag(i, ItemUtils.TAG_OVERLAY)
&& i.getLink() != null) {
if (i.getAbsoluteLink().equalsIgnoreCase(toParse.getName())) {
// This frame contains an active overlay which
// points to itself
MessageBay.errorMessage(toParse.getName() + " contains an @o which links to itself");
continue;
}
Frame overlayFrame = FrameIO.LoadFrame(i.getAbsoluteLink());
// Parse(overlay);
if (overlayFrame != null && Overlay.getOverlay(overlays, overlayFrame) == null) {
overlays.add(new Overlay(overlayFrame, UserAppliedPermission.none));
}
}
// check for ACTIVE_OVERLAY items
else if (!ignoreAnnotations && ItemUtils.startsWithTag(i, ItemUtils.TAG_ACTIVE_OVERLAY)
&& i.getLink() != null) {
String link = i.getAbsoluteLink();
if (link.equalsIgnoreCase(toParse.getName())) {
// This frame contains an active overlay which
// points to itself
MessageBay.errorMessage(toParse.getName() + " contains an @ao which links to itself");
continue;
}
Frame overlayFrame = null;
Frame current = DisplayController.getCurrentFrame();
if (current != null) {
for (Overlay o : current.getOverlays()) {
if (o.Frame.getName().equalsIgnoreCase(link)) {
overlayFrame = o.Frame;
}
}
}
if (overlayFrame == null) {
overlayFrame = FrameIO.LoadFrame(link);
}
// get level if specified
String level = new AttributeValuePair(i.getText()).getValue();
// default permission (if none is specified)
PermissionTriple permissionLevel = new PermissionTriple(level,
UserAppliedPermission.followLinks);
if (overlayFrame != null) {
Overlay existingOverlay = Overlay.getOverlay(overlays, overlayFrame);
// If it wasn't in the list create it and add
// it.
if (existingOverlay == null) {
Overlay newOverlay = new Overlay(overlayFrame,
permissionLevel.getPermission(overlayFrame.getOwner(), overlayFrame.getGroupMembers()));
i.setOverlay(newOverlay);
overlays.add(newOverlay);
} else {
existingOverlay.Frame.setPermission(permissionLevel);
}
}
}
}
}
} catch (Exception e) {
Logger.Log(e);
e.printStackTrace();
System.err.println("**** Have temporarily supressed MessageBay call, as resulted in infinite recursion");
//MessageBay.warningMessage("Exception occured when loading " + i.getClass().getSimpleName() + "(ID: "
// + i.getID() + ") " + e.getMessage() != null ? e.getMessage() : "");
}
}
/*
* for (Item i : items) { if (i instanceof Dot) { ((Dot)
* i).setPointType(pointtype); ((Dot) i).useFilledPoints(filledPoints); } }
*/
if (DisplayController.isTwinFramesOn()) {
FrameIO.ResumeCache();
}
toParse.clearOverlays();
toParse.clearVectors();
toParse.addAllOverlays(overlays);
toParse.addAllVectors(vectors);
}
public static void Parse(Frame toParse) {
Parse(toParse, false);
}
/**
* Checks for any special Annotation items and updates the display as necessary.
* Special Items: Images, overlays, sort.
*
*/
public static void Parse(Frame toParse, boolean firstParse) {
Parse(toParse, firstParse, false, false);
}
/**
*
* @param toParse
* @param firstParse
* @param ignoreAnnotations
* used to prevent infinate loops such as when performing TDFC with
* an ao tag linked to a frame with an frameImage of a frame which
* also has an ao tag on it.
* @param userEdit TODO
*/
public static void Parse(Frame toParse, boolean firstParse, boolean ignoreAnnotations, boolean userEdit) {
List accessList = Label.getAccessibleLabelsNames(toParse.getPrimaryBody());
ItemsList primaries = toParse.getPrimaryBody();
ItemsList surrogates = toParse.getSurrogateBody();
transformOutOfPlaceItems(toParse, primaries, userEdit);
transformOutOfPlaceItems(toParse, surrogates, userEdit);
toParse.getInteractableItems().clear();
List
- newBody = parseFromPrimary(primaries, accessList);
toParse.setBody(newBody, accessList);
generatingSupportingItems(toParse, toParse.getBody(false), ignoreAnnotations);
if (firstParse) {
ItemUtils.EnclosedCheck(toParse.getSortedItems());
}
}
private static List
- parseFromPrimary(ItemsList primaryBody, List access) {
List
- parsedBody = new ArrayList
- ();
for (Item item: primaryBody) {
if (item instanceof XRayable && !((XRayable) item).sourceIsAccessible()) {
item = ((XRayable) item).getSource();
}
String encryptionLabel = item.getEncryptionLabel();
if (encryptionLabel == null || encryptionLabel.isEmpty()) {
parsedBody.add(item);
} else if (access.contains(encryptionLabel)) {
parsedBody.add(item);
} else {
parsedBody.addAll(item.getSurrogates());
}
}
return parsedBody;
}
/**
* TODO: Comment. cts16
*
* @param vectors
* @param permission
* @param i
*/
private static void addVector(List vectors, UserAppliedPermission defaultPermission,
UserAppliedPermission framePermission, Item i) {
// TODO It is possible to get into an infinite loop if a
// frame contains an @ao which leads to a frame with an
// @v which points back to the frame with the @ao
Frame vector = FrameIO.LoadFrame(i.getAbsoluteLink());
// Get the permission from off the vector frame
UserAppliedPermission vectorPermission = UserAppliedPermission
.getPermission(vector.getAnnotationValue("permission"), defaultPermission);
// If the frame permission is lower, use that
vectorPermission = UserAppliedPermission.min(vectorPermission, framePermission);
// Highest permissable permission for vectors is copy
vectorPermission = UserAppliedPermission.min(vectorPermission, UserAppliedPermission.copy);
if (vector != null) {
String scaleString = new AttributeValuePair(i.getText()).getValue();
Float scale = 1F;
try {
scale = Float.parseFloat(scaleString);
} catch (Exception e) {
}
Vector newVector = new Vector(vector, vectorPermission, scale, i);
i.setOverlay(newVector);
i.setVisible(false);
vectors.add(newVector);
}
}
public static Item onItem(float floatX, float floatY, boolean changeLastEdited) {
return onItem(DisplayController.getCurrentFrame(), floatX, floatY, changeLastEdited);
}
/**
* Searches through the list of items on this frame to find one at the given x,y
* coordinates.
*
* @param x
* The x coordinate
* @param y
* The y coordinate
* @return The Item at the given coordinates, or NULL if none is found.
*/
public static Item onItem(Frame toCheck, float floatX, float floatY, boolean bResetLastEdited) {
// System.out.println("MouseX: " + floatX + " MouseY: " + floatY);
int x = Math.round(floatX);
int y = Math.round(floatY);
if (toCheck == null) {
return null;
}
List
- possibles = new ArrayList
- (0);
// if the mouse is in the message area
if (y >= DisplayController.getMessageBayPaintArea().getMinY()) {
// check the individual bay items (MessageBay + MailBay)
List
- bayItems = new LinkedList
- ();
if (DisplayController.isMailMode()) {
bayItems.addAll(MailBay.getPreviewMessages());
} else {
bayItems.addAll(MessageBay.getMessages());
}
for (Item message : bayItems) {
if (message != null) {
if (message.contains(new Point(x, y))) {
message.setOverlayPermission(UserAppliedPermission.copy);
possibles.add(message);
} else {
// Not sure why but if the line below is removed then
// several items can be highlighted at once
message.setHighlightMode(Item.HighlightMode.None);
message.setHighlightColorToDefault();
}
}
}
// check the link to the message/mail frame
Item[] linkItems = DisplayController.isMailMode() ? new Item[] { MailBay.getMailLink(), MailBay.getCheckMailAction() } : new Item[] { MessageBay.getMessageLink() };
for (Item linkItem: linkItems) {
if (linkItem != null && linkItem.contains(new Point(x, y))) {
linkItem.setOverlayPermission(UserAppliedPermission.copy);
possibles.add(linkItem);
}
}
// this is taken into account in contains
// y -= FrameGraphics.getMaxFrameSize().height;
// otherwise, the mouse is on the frame
} else {
if (LastEdited != null) {
if (LastEdited.contains(x, y) && !FreeItems.getInstance().contains(LastEdited)
&& LastEdited.getParent() == DisplayController.getCurrentFrame()
&& LastEdited.getParent().getSortedItems().contains(LastEdited)) {
LastEdited.setOverlayPermission(UserAppliedPermission.full);
return LastEdited;
} else if (bResetLastEdited) {
setLastEdited(null);
}
}
ArrayList
- checkList = new ArrayList
- ();
checkList.addAll(toCheck.getInteractableItems());
checkList.add(toCheck.getNameItem());
for (Item i : checkList) {
// do not check annotation items in audience mode
// TODO: Upon hover of Rubbish Bin, Undo and Restore Widgets, flickering occurs
// depending on the mouse distance from a corner. Resolve this.
if (i.isVisible() && !(DisplayController.isAudienceMode() && i.isAnnotation())) {
if (i instanceof WidgetCorner) {
WidgetCorner wc = (WidgetCorner) i;
if (wc.getWidgetSource() instanceof ButtonWidget) {
ButtonWidget bw = (ButtonWidget) wc.getWidgetSource();
if (bw.getdropInteractableStatus() == true) {
Widget iw = wc.getWidgetSource();
if (iw.getBounds().contains(x, y)) {
if (!FreeItems.getInstance().contains(i)) {
possibles.add(i);
}
}
}
}
}
if (i.contains(new Point(x, y))) {
if (!FreeItems.getInstance().contains(i)) {
possibles.add(i);
}
}
}
}
}
// if there are no possible items, return null
if (possibles.size() == 0) {
return null;
}
// if there is only one possibility, return it
if (possibles.size() == 1) {
return possibles.get(0);
}
// return closest x,y pair to mouse
Item closest = possibles.get(0);
int distance = (int) Math.round(
Math.sqrt(Math.pow(Math.abs(closest.getX() - x), 2) + Math.pow(Math.abs(closest.getY() - y), 2)));
for (Item i : possibles) {
int d = (int) Math
.round(Math.sqrt(Math.pow(Math.abs(i.getX() - x), 2) + Math.pow(Math.abs(i.getY() - y), 2)));
// System.out.println(d);
if (d <= distance) {
distance = d;
// dots take precedence over lines
if ((!(closest instanceof Dot && i instanceof Line))
&& (!(closest instanceof Text && i instanceof Line))) {
closest = i;
}
}
}
return closest;
}
/**
* Checks if the mouse is currently over an item.
*
* @return True if the mouse is over any item, false otherwise.
*/
public static boolean hasCurrentItem() {
return getCurrentItem() != null;
}
public synchronized static Item getCurrentItem() {
return onItem(DisplayController.getCurrentFrame(), DisplayController.getMouseX(), DisplayController.getMouseY(),
true);
}
public static PolygonBounds getEnlosingPolygon() {
Collection
- enclosure = getEnclosingLineEnds();
if (enclosure == null || enclosure.size() == 0) {
return null;
}
return enclosure.iterator().next().getEnclosedShape();
}
/**
*
* @param currentItem
* @return
*/
public static Collection
- getCurrentItems() {
return getCurrentItems(getCurrentItem());
}
public static Collection
- getCurrentItems(Item currentItem) {
Collection
- enclosure = getEnclosingLineEnds();
if (enclosure == null || enclosure.size() == 0) {
return null;
}
Item firstItem = enclosure.iterator().next();
Collection
- enclosed = getItemsEnclosedBy(DisplayController.getCurrentFrame(),
firstItem.getEnclosedShape());
// Brook: enclosed widgets are to be fully enclosed, never partially
/*
* MIKE says: but doesn't this mean that widgets are treated differently from
* ALL other object which only need to be partially enclosed to be picked up
*/
List enclosedWidgets = new LinkedList();
for (Item i : enclosed) {
// Don't want to lose the highlighting from the current item
if (i == currentItem || enclosure.contains(i)) {
continue;
}
// Don't want to lose the highlighting of connected Dots
// TODO: this code does nothing (perhaps the continue is meant for the outer
// for loop?). cts16
if (i instanceof Dot && i.getHighlightMode() == HighlightMode.Connected) {
for (Line l : i.getLines()) {
if (l.getOppositeEnd(i).getHighlightMode() == HighlightMode.Normal) {
continue;
}
}
}
if (i instanceof WidgetCorner) {
if (!enclosedWidgets.contains(((WidgetCorner) i).getWidgetSource())) {
enclosedWidgets.add(((WidgetCorner) i).getWidgetSource());
}
}
i.setHighlightMode(Item.HighlightMode.None);
i.setHighlightColorToDefault();
}
for (Widget iw : enclosedWidgets) {
for (Item i : iw.getItems()) {
if (!enclosed.contains(i)) {
enclosed.add(i);
}
}
}
return enclosed;
}
/**
* Gets the collection of Dot items that form the enclosure nearest to the
* current mouse position.
*/
public static Collection
- getEnclosingLineEnds() {
return getEnclosingLineEnds(new Point(DisplayController.getMouseX(), DisplayController.getMouseY()));
}
/**
* Gets the collection of Dot items that form the enclosure nearest to the given
* position.
*/
public static Collection
- getEnclosingLineEnds(Point position) {
// update enclosed shapes
Frame current = DisplayController.getCurrentFrame();
if (current == null) {
return null;
}
List
- items = current.getSortedItems();
// Remove all items that are connected to freeItems
List
- freeItems = new ArrayList
- (FreeItems.getInstance());
while (freeItems.size() > 0) {
Item item = freeItems.get(0);
Collection
- connected = item.getAllConnected();
items.removeAll(connected);
freeItems.removeAll(connected);
}
List
- used = new ArrayList
- (0);
while (items.size() > 0) {
Item i = items.get(0);
items.remove(i);
if (i.isEnclosed()) {
PolygonBounds p = i.getEnclosedShape();
if (p.contains(position)) {
used.add(i);
items.removeAll(i.getEnclosingDots());
}
}
}
if (used.size() == 0) {
return null;
}
// if there is only one possibility, return it
if (used.size() == 1) {
return used.get(0).getEnclosingDots();
// otherwise, determine which polygon is closest to the cursor
} else {
Collections.sort(used, new Comparator
- () {
@Override
public int compare(Item d1, Item d2) {
PolygonBounds p1 = d1.getEnclosedShape();
PolygonBounds p2 = d2.getEnclosedShape();
int closest = Integer.MAX_VALUE;
int close2 = Integer.MAX_VALUE;
int mouseX = DisplayController.getMouseX();
int mouseY = DisplayController.getMouseY();
for (int i = 0; i < p1.getPointCount(); i++) {
int diff = Math.abs(p1.getPoint(i).getX() - mouseX) + Math.abs(p1.getPoint(i).getY() - mouseY);
int diff2 = Integer.MAX_VALUE;
if (i < p2.getPointCount()) {
diff2 = Math.abs(p2.getPoint(i).getX() - mouseX) + Math.abs(p2.getPoint(i).getY() - mouseY);
}
if (diff < Math.abs(closest)) {
close2 = closest;
closest = diff;
} else if (diff < Math.abs(close2)) {
close2 = diff;
}
if (diff2 < Math.abs(closest)) {
close2 = closest;
closest = -diff2;
} else if (diff2 < Math.abs(close2)) {
close2 = diff2;
}
}
if (closest > 0 && close2 > 0) {
return -10;
}
if (closest < 0 && close2 < 0) {
return 10;
}
if (closest > 0) {
return -10;
}
return 10;
}
});
return used.get(0).getEnclosingDots();
}
}
// TODO Remove this method!!
// Can just getItemsWithin be used?
public static Collection
- getItemsEnclosedBy(Frame frame, PolygonBounds poly) {
Collection
- contained = frame.getItemsWithin(poly);
Collection
- results = new LinkedHashSet
- (contained.size());
// check for correct permissions
for (Item item : contained) {
// if the item is on the frame
if (item.getParent() == frame || item.getParent() == null) {
// item.Permission = Permission.full;
results.add(item);
// otherwise, it must be on an overlay frame
} else {
for (Overlay overlay : frame.getOverlays()) {
if (overlay.Frame == item.getParent()) {
item.setOverlayPermission(overlay.permission);
results.add(item);
break;
}
}
}
}
return results;
}
public static void CreateDefaultProfile(String profileFor, Frame profile) {
CreateDefaultProfile(profileFor, profile, null, null);
}
/**
* Copies the content from the default profile to the specified profile.
* @param profileFor Name of profile that is destination of copy.
* @param profile Profile being setup.
* @param specifiedTextSettings text settings to provide a default value for in the new profile
* @param specifiedGenericSettings generic settings to provide a default value for in the new profile
*/
public static void CreateDefaultProfile(String profileFor, Frame profile,
Map specifiedSettings, Map> notifyWhenGenerated) {
// If this is already the default profile then nothing (other than setting
// title) needs to be done.
Text titleItem = profile.getTitleItem();
Text title = titleItem;
if (!profileFor.equals(UserSettings.DEFAULT_PROFILE_NAME)) {
// If this profile is not the default profile, copy it from the default profile
// instead of generating a new profile
// (this allows the possibility of modifying the default profile and having any
// new profiles get those modifications)
Frame defaultFrame = FrameIO.LoadProfile(UserSettings.DEFAULT_PROFILE_NAME);
if (defaultFrame == null) {
try {
// If we do not have a default to copy, create one.
String existingUsername = UserSettings.UserName.get();
UserSettings.UserName.set("default");
FrameIO.changeParentAndSubFolders(FrameIO.PARENT_FOLDER);
UserSettings.setupDefaultFolders();
defaultFrame = FrameIO.CreateNewProfile(UserSettings.DEFAULT_PROFILE_NAME, null, null);
UserSettings.UserName.set(existingUsername);
FrameIO.changeParentAndSubFolders(FrameIO.PARENT_FOLDER);
UserSettings.setupDefaultFolders();
} catch (InvalidFramesetNameException invalidNameEx) {
MessageBay.errorMessage("Failed to create default profile named: "
+ UserSettings.DEFAULT_PROFILE_NAME + ". "
+ "Profile names must start and end with a letter and must contain only letters and numbers.");
return;
} catch (ExistingFramesetException existingFramesetEx) {
MessageBay.errorMessage("Failed to create the desired default frameset: "
+ UserSettings.DEFAULT_PROFILE_NAME + ", "
+ "because it already exists. This should never happen as we shouldn't be asking to create it if it already exists.");
return;
}
}
MessageBay.suppressMessages(true);
int lastNumber = FrameIO.getLastNumber(defaultFrame.getFramesetName());
for (int i = 1; i <= lastNumber; i++) {
// Load in next default, if it doesn't exist continue loop.
defaultFrame = FrameIO.LoadFrame(defaultFrame.getFramesetName() + i);
if (defaultFrame == null) {
continue;
}
// Create the next next (currently blank) profile frame.
// If there is frame gaps in the default (say if there is no 4.exp but there is
// a 5.exp) then retain those gaps.
// This way copied relative links work.
while (profile.getNumber() < defaultFrame.getNumber()) {
profile = FrameIO.CreateFrame(profile.getFramesetName(), null, null);
}
// Ensure we are working from a blank slate.
profile.reset();
profile.removeAllItems(profile.getAllItems());
// For each item on defaultFrame:
// 1. Set all items to be relatively linked so once copied their links correctly
// point to the frame on the created profile rather than the default profile.
// 2. Copy item from defaultFrame to the current profile frame being
// constructed.
// 3. Replace settings values of copied items with those specified in
// specifiedSettings (if present)
for (Item item : defaultFrame.getAllItems()) {
item.setRelativeLink();
}
profile.addAllItems(defaultFrame.getAllItems());
if (i == 1 && titleItem != null) {
titleItem.setText(profileFor + "'s Profile");
}
String category = profile.getTitle();
List settingsKeys = null;
if (specifiedSettings != null) {
settingsKeys = specifiedSettings.keySet().stream().filter(key ->
key.startsWith(category)).collect(Collectors.toList());
}
if (settingsKeys != null) {
for (String key: settingsKeys) {
Setting setting = specifiedSettings.get(key);
String name = setting.getName();
Text representation = setting.generateRepresentation(name, profile.getFramesetName());
Collection canditates = profile.getTextItems();
canditates.removeIf(text -> !text.getText().startsWith(representation.getText().split(" ")[0]));
canditates.forEach(text -> {
Point backupPos = text.getPosition();
Item.DuplicateItem(representation, text);
text.setText(representation.getText());
text.setPosition(backupPos);
});
}
}
if (notifyWhenGenerated != null && notifyWhenGenerated.containsKey(category)) {
notifyWhenGenerated.get(category).accept(profile);
}
FrameIO.SaveFrame(profile);
}
MessageBay.suppressMessages(false);
}
}
private static void checkTDFCItemWaiting(Frame currentFrame) {
Item tdfcItem = FrameUtils.getTdfcItem();
// if there is a TDFC Item waiting
if (tdfcItem != null) {
boolean change = currentFrame.hasChanged();
boolean saved = currentFrame.isSaved();
// Save the parent of the item if it has not been saved
if (!change && !saved) {
tdfcItem.setLink(null);
tdfcItem.getParent().setChanged(true);
FrameIO.SaveFrame(tdfcItem.getParent());
DisplayController.requestRefresh(true);
} else {
SessionStats.CreatedFrame();
}
setTdfcItem(null);
}
}
public static void setTdfcItem(Item _tdfcItem) {
FrameUtils._tdfcItem = _tdfcItem;
}
public static Item getTdfcItem() {
return FrameUtils._tdfcItem;
}
public static void setLastEdited(Text lastEdited) {
// If the lastEdited is being changed then check if its @i
Frame toReparse = null;
Frame toRecalculate = null;
Frame toUpdateObservers = null;
if (LastEdited == null) {
// System.out.print("N");
} else if (LastEdited != null) {
// System.out.print("T");
Frame parent = LastEdited.getParentOrCurrentFrame();
if (lastEdited != LastEdited) {
if (LastEdited.startsWith("@i")) {
// Check if its an image that can be resized to fit a box
// around it
String text = LastEdited.getText();
if (text.startsWith("@i:") && !Character.isDigit(text.charAt(text.length() - 1))) {
Collection
- enclosure = FrameUtils.getEnclosingLineEnds(LastEdited.getPosition());
if (enclosure != null) {
for (Item i : enclosure) {
if (i.isLineEnd() && i.isEnclosed()) {
DisplayController.getCurrentFrame().removeAllItems(enclosure);
AxisAlignedBoxBounds rect = i.getEnclosedBox();
LastEdited.setText(LastEdited.getText() + " " + Math.round(rect.getWidth()));
LastEdited.setPosition(rect.getTopLeft());
LastEdited.setThickness(i.getThickness());
LastEdited.setBorderColor(i.getColor());
break;
}
}
StandardGestureActions.deleteItems(enclosure, false);
}
}
toReparse = parent;
} else if (LastEdited.recalculateWhenChanged()) {
toRecalculate = parent;
}
if (parent.hasObservers()) {
toUpdateObservers = parent;
}
// Update the formula if in XRay mode
if (DisplayController.isXRayMode() && LastEdited.hasFormula()) {
LastEdited.setFormula(LastEdited.getText());
}
}
if (lastEdited != LastEdited && LastEdited.getText().length() == 0 && LastEdited.getMinWidth() == null) {
parent.removeItem(LastEdited);
}
}
LastEdited = lastEdited;
if (!DisplayController.isXRayMode()) {
if (toReparse != null) {
Parse(toReparse, false, false, true);
} else {
if (toRecalculate != null) {
toRecalculate.recalculate();
}
if (toUpdateObservers != null) {
toUpdateObservers.notifyObservers(false);
}
}
}
}
/**
* Extracts files/folders from assets/resources-public and assets/resources-private
* to {Expeditee Home}/resources-public and {Expeditee Home}/resources-private respectively.
* @param force if true, resources will be extracted even ifthey have already been extracted before.
*/
public static void extractResources(boolean force) {
// Ensure groups area exists
if (UserSettings.PublicAndPrivateResources) {
// Extract private resources
Path resourcesPrivate = Paths.get(FrameIO.PARENT_FOLDER).resolve("resources-private");
extractResources("org/expeditee/assets/resources-private", resourcesPrivate, force);
// Extract public resources
Path resourcesPublic = Paths.get(FrameIO.PARENT_FOLDER).resolve("resources-public");
extractResources("org/expeditee/assets/resources-public", resourcesPublic, force);
} else if (AuthenticatorBrowser.isAuthenticationRequired()) {
// Deal with the instance of being in the old regime but using authentication.
// Ensure additional framesets
Path framesetsDir = Paths.get(FrameIO.FRAME_PATH);
boolean extracted = extractResources("org/expeditee/assets/resources-public/framesets", framesetsDir, force);
// Ensure additional images
Path imagesDir = Paths.get(FrameIO.IMAGES_PATH);
extracted |= extractResources("org/expeditee/assets/resources-public/images", imagesDir, force);
// Ensure deaddrops area exists
extracted |= Paths.get(FrameIO.PARENT_FOLDER).resolve("deaddrops").toFile().mkdir();
if (extracted) {
@SuppressWarnings("resource")
Scanner in = new Scanner(System.in);
System.out.println("Extracting resources...In order to use authentication, you need a new default profile frameset.");
System.out.println("This is a destructive process, your existing default profile frameset (if it exists) will be deleted.");
System.out.print("Do you want to proceed? y/N:");
System.out.flush();
String answer = in.nextLine();
if (!answer.toLowerCase().startsWith("y")) {
System.out.println("Exiting...");
System.exit(1);
}
// Ensure the default profile is 'update to date'.
//NB: this limits the potential for those running old regime with authentication the ability to customise the default profile.
Path defaultProfile = Paths.get(FrameIO.PROFILE_PATH).resolve("default");
try {
Files.walkFileTree(defaultProfile, new FileVisitor() {
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
dir.toFile().delete();
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
file.toFile().delete();
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
return FileVisitResult.CONTINUE;
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private static boolean extractResources(String source, Path destination, boolean force) {
// If resources have already been extracted, and we are not forcing the extraction, there is nothing to do.
if (!force && destination.resolve(".res").toFile().exists()) {
return false;
}
System.out.println("Extracting/Installing resources to: " + destination.getFileName());
// Create the destination
destination.getParent().toFile().mkdirs();
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
URL resourceUrl = classLoader.getResource(source);
if (resourceUrl.getProtocol().equals("jar")) {
try {
JarURLConnection ju_connection = (JarURLConnection) resourceUrl.openConnection();
JarFile jf = ju_connection.getJarFile();
Enumeration jarEntries = jf.entries();
extractFromJarFile(classLoader, jarEntries, source, destination);
} catch (IOException e) {
System.err.println("Error: FrameUtils::extractResources. Exception whilst extracting resources from Jar File. Message: " + e.getMessage());
}
} else if (resourceUrl.getProtocol().equals("bundleresource")) {
try {
URLConnection urlConnection = resourceUrl.openConnection();
Class> c = urlConnection.getClass();
java.lang.reflect.Method toInvoke = c.getMethod("getFileURL");
URL fileURL = (URL) toInvoke.invoke(urlConnection);
extractResourcesFromFolder(new File(fileURL.getPath()), source, destination);
} catch (IOException e) {
System.err.println("Error: FrameUtils::extractResources. Problem opening connection to bundleresource. Message: " + e.getMessage());
} catch (NoSuchMethodException e) {
System.err.println("Error: FrameUtils::extractResources. Unable to find method URLConnection::getFileURL. Message: " + e.getMessage());
} catch (InvocationTargetException e) {
System.err.println("Error: FrameUtils::extractResources. Problem invoking URLConnection::getFileURL. Message: " + e.getMessage());
} catch (IllegalAccessException e) {
System.err.println("Error: FrameUtils::extractResources. Problem invoking URLConnection::getFileURL. Message: " + e.getMessage());
}
} else {
try {
File folder = new File(resourceUrl.toURI().getPath());
extractResourcesFromFolder(folder, source, destination);
} catch (URISyntaxException e) {
System.err.println("Error: FrameUtils::extractResources. Problem converting URL to URI. Message: " + e.getMessage());
} catch (IOException e) {
System.err.println("Error: FrameUtils::extractResources. Exception whilst extracting resources from folder. Message: " + e.getMessage());
}
}
// Create the .res file to signal completion
try {
destination.resolve(".res").toFile().createNewFile();
} catch (IOException e) {
System.err.println("Error: FrameUtils::extractResources. Unable to create the .res file to flag that resources have been extracted. Message: " + e.getMessage());
}
return true;
}
private static void extractFromJarFile(ClassLoader classLoader, Enumeration jarEntries, String source, Path destination) throws IOException {
while (jarEntries.hasMoreElements()) {
ZipEntry ze = jarEntries.nextElement();
if (!ze.getName().startsWith(source)) {
continue;
}
File out = destination.resolve(ze.getName().substring(source.length())).toFile();
if (ze.isDirectory()) {
out.mkdirs();
continue;
}
FileOutputStream fOut = null;
InputStream fIn = null;
try {
fOut = new FileOutputStream(out);
fIn = classLoader.getResourceAsStream(ze.getName());
byte[] bBuffer = new byte[1024];
int nLen;
while ((nLen = fIn.read(bBuffer)) > 0) {
fOut.write(bBuffer, 0, nLen);
}
fOut.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fOut != null) {
fOut.close();
}
if (fIn != null) {
fIn.close();
}
}
}
}
private static void extractResourcesFromFolder(File folder, String source, Path destination) throws IOException {
LinkedList items = new LinkedList();
items.addAll(Arrays.asList(folder.listFiles()));
LinkedList files = new LinkedList();
while (items.size() > 0) {
File file = items.remove(0);
if (file.isFile()) {
if (!file.getName().contains(".svn")) {
files.add(file);
}
} else {
if (!file.getName().contains(".svn")) {
items.addAll(Arrays.asList(file.listFiles()));
}
}
}
for (File file : files) {
String path = file.getPath();
System.out.println(path);
Path relativize = folder.toPath().relativize(Paths.get(file.getPath()));
File out = destination.resolve(relativize).toFile();
copyFile(file, out, true);
}
}
/**
* @param src
* @param dst
* @throws IOException
*/
public static void copyFile(File src, File dst, boolean overWrite) throws IOException {
if (!overWrite && dst.exists()) {
return;
}
dst.getParentFile().mkdirs();
FileOutputStream fOut = null;
FileInputStream fIn = null;
try {
// System.out.println(out.getPath());
fOut = new FileOutputStream(dst);
fIn = new FileInputStream(src);
byte[] bBuffer = new byte[1024];
int nLen;
while ((nLen = fIn.read(bBuffer)) > 0) {
fOut.write(bBuffer, 0, nLen);
}
fOut.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fOut != null) {
fOut.close();
}
if (fIn != null) {
fIn.close();
}
}
}
public static Text getLastEdited() {
return LastEdited;
}
public static Collection getCurrentTextItems() {
Collection currentTextItems = new LinkedHashSet();
Collection
- currentItems = getCurrentItems(null);
if (currentItems != null) {
for (Item i : getCurrentItems(null)) {
if (i instanceof Text && !i.isLineEnd()) {
currentTextItems.add((Text) i);
}
}
}
return currentTextItems;
}
}