/** * 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; } }