/**
* Misc.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.actions;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import org.expeditee.core.Colour;
import org.expeditee.core.Image;
import org.expeditee.core.Point;
import org.expeditee.gio.EcosystemManager;
import org.expeditee.gio.gesture.StandardGestureActions;
import org.expeditee.gui.AttributeUtils;
import org.expeditee.gui.DisplayController;
import org.expeditee.gui.Frame;
import org.expeditee.gui.FrameGraphics;
import org.expeditee.gui.FrameIO;
import org.expeditee.gui.FrameUtils;
import org.expeditee.gui.FreeItems;
import org.expeditee.gui.MessageBay;
import org.expeditee.gui.MessageBay.Progress;
import org.expeditee.gui.Reminders;
import org.expeditee.gui.TimeKeeper;
import org.expeditee.io.Conversion;
import org.expeditee.io.ExpReader;
import org.expeditee.items.Item;
import org.expeditee.items.ItemUtils;
import org.expeditee.items.Line;
import org.expeditee.items.Picture;
import org.expeditee.items.Text;
import org.expeditee.items.XRayable;
import org.expeditee.items.widgets.Widget;
import org.expeditee.items.widgets.WidgetCorner;
import org.expeditee.items.widgets.WidgetEdge;
import org.expeditee.math.ExpediteeJEP;
import org.expeditee.settings.UserSettings;
import org.expeditee.simple.SString;
import org.expeditee.stats.CometStats;
import org.expeditee.stats.DocumentStatsFast;
import org.expeditee.stats.SessionStats;
import org.expeditee.stats.StatsLogger;
import org.expeditee.stats.TreeStats;
import org.nfunk.jep.Node;
import org.nfunk.jep.ParseException;
/**
* A list of miscellaneous Actions specific to Expeditee
*
*/
public class Misc {
/**
* Causes the system to beep
*/
public static void beep()
{
EcosystemManager.getMiscManager().beep();
}
/**
* Returns an Item located at the specified position.
* kgas1 - 23/01/2012
* @param x
* @param y
* @return
*/
public static Item getItemAtPosition(int x, int y, Frame f)
{
Frame current = f;
List- allItems = current.getItems();
for(Item i : allItems)
{
if(i.getX() == x && i.getY() == y)
return i;
}
return null;
}
/**
* Returns an item containing a specified piece of data.
* kgas1 - 7/06/2012
* @param s
* @param f
* @return
*/
public static Item getItemContainingData(String s, Frame f){
Frame current = f;
List
- allItems = current.getItems();
for(Item i : allItems){
if(i.getData() != null && i.getData().size() > 0){
if(i.getData().contains(s)){
return i;
}
}
}
return null;
}
public static void openURL(Item item){
if(item.getData() != null && item.getData().size() > 0){
String url = item.getData().get(0);
openURL(url);
}
//openURL("http://www.google.com");
}
/**
* Treats a string as a URL and attempts to open it
*/
public static void openURL(String siteURL)
{
boolean success = EcosystemManager.getMiscManager().browse(siteURL);
if (!success) MessageBay.displayMessage("'" + siteURL + "' is not a valid URL");
}
/**
* Backs up the current frame as a default and saves it to the file
*/
public static void setRestorePoint()
{
Frame current = DisplayController.getCurrentFrame();
current.change();
FrameIO.SaveFrameAsRestore(current, true, true);
}
/**
* Forces a repaint of the current Frame
*/
public static void display() {
DisplayController.requestRefresh(false);
}
public static String getWindowSize() {
return EcosystemManager.getGraphicsManager().getWindowSize().toString();
}
/**
* Restores the current frame to the last saved version currently on the
* hard disk
*/
public static void restore() {
FrameIO.Reload();
// MessageBay.displayMessage("Restoration complete.");
}
/**
* Toggles AudienceMode on or off
*/
public static void toggleAudienceMode() {
DisplayController.ToggleAudienceMode();
}
/**
* Toggles TwinFrames mode on or off
*/
public static void toggleTwinFramesMode() {
DisplayController.toggleTwinFrames();
}
/**
* If the given Item is a Text Item, then the text of the Item is
* interpreted as actions, if not this method does nothing.
*
* @param current
* The Item to read the Actions from
*/
public static void runItem(Item current) throws Exception {
if (current instanceof Text) {
List actions = ((Text) current).getTextList();
for (String action : actions) {
if (!action.equalsIgnoreCase("runitem")) {
Actions.LegacyPerformAction(DisplayController.getCurrentFrame(), current,
action);
}
}
} else {
MessageBay.errorMessage("Item must be a text item.");
}
}
/**
* Prompts the user to confirm deletion of the current Frame, and deletes if
* the user chooses. After deletion this action calls back(), to ensure the
* deleted frame is not still being shown
*
*/
public static void DeleteFrame(Frame toDelete) {
String deletedFrame = toDelete.getName();
String deletedFrameNameLowercase = deletedFrame.toLowerCase();
String errorMessage = "Error deleting " + deletedFrame;
try {
String deletedFrameName = FrameIO.DeleteFrame(toDelete);
if (deletedFrameName != null) {
DisplayController.Back();
// Remove any links on the previous frame to the one being
// deleted
Frame current = DisplayController.getCurrentFrame();
for (Item i : current.getItems())
if (i.getLink() != null
&& i.getAbsoluteLink().toLowerCase().equals(
deletedFrameNameLowercase)) {
i.setLink(null);
}
MessageBay.displayMessage(deletedFrame + " renamed "
+ deletedFrameName);
// FrameGraphics.Repaint();
return;
}
} catch (IOException ioe) {
if (ioe.getMessage() != null)
errorMessage += ". " + ioe.getMessage();
} catch (SecurityException se) {
if (se.getMessage() != null)
errorMessage += ". " + se.getMessage();
} catch (Exception e) {
e.printStackTrace();
}
MessageBay.errorMessage(errorMessage);
}
/**
* Loads the Frame linked to by the given Item. The first Item on the Frame
* that is not the title or name is then placed on the cursor. If the given
* Item has no link, or no item is found then this is a no-op.
*
* @param current
* The Item that links to the Frame that the Item will be loaded
* from.
*/
public static Item GetItemFromChildFrame(Item current) {
return getFromChildFrame(current, false);
}
public static void GetItemsFromChildFrame(Item current) {
getItemsFromChildFrame(current, false);
}
/**
* Loads the Frame linked to by the given Item. The first Text Item on the
* Frame that is not the title or name is then placed on the cursor. If the
* given Item has no link, or no item is found then this is a no-op.
*
* @param current
* The Item that links to the Frame that the Item will be loaded
* from.
*/
public static Item GetTextFromChildFrame(Item current) {
return getFromChildFrame(current, true);
}
private static Item getFromChildFrame(Item current, boolean textOnly) {
Item item = getFirstBodyItemOnChildFrame(current, textOnly);
// if no item was found
if (item != null) {
// copy the item and switch
item = item.copy();
item.setPosition(DisplayController.getMousePosition());
}
return item;
}
private static void getItemsFromChildFrame(Item current, boolean textOnly) {
Collection
- items = getItemsOnChildFrame(current, textOnly);
// if no item was found
if (items == null || items.size() == 0) {
return;
}
// copy the item and switch
Collection
- copies = ItemUtils.CopyItems(items);
Item first = items.iterator().next();
float deltaX = DisplayController.getMouseX() - first.getX();
float deltaY = DisplayController.getMouseY() - first.getY();
for (Item i : copies) {
if (i.isVisible())
i.setXY(i.getX() + deltaX, i.getY() + deltaY);
i.setParent(null);
}
StandardGestureActions.pickup(copies);
DisplayController.requestRefresh(true);
}
/**
* Sets the given Item to have the Given Color. Color can be null (for
* default)
*
* @param toChange
* The Item to set the Color.
* @param toUse
* The Color to give the Item.
*/
public static void SetItemBackgroundColor(Item toChange, Colour toUse) {
if (toChange == null)
return;
toChange.setBackgroundColor(toUse);
DisplayController.requestRefresh(true);
}
/**
* Sets the given Item to have the Given Color. Color can be null (for
* default)
*
* @param toChange
* The Item to set the Color.
* @param toUse
* The Color to give the Item.
*/
public static void SetItemColor(Item toChange, Colour toUse) {
if (toChange == null)
return;
toChange.setColor(toUse);
DisplayController.requestRefresh(true);
}
/**
* Creates a new Text Object containing general statistics for the current
* session. The newly created Text Object is then attached to the cursor via
* FrameMouseActions.pickup(Item)
*/
public static void GetSessionStats() {
attachStatsToCursor(SessionStats.getCurrentStats());
}
/**
* Creates a new Text Object containing statistics for the current tree.
*/
public static String GetCometStats(Frame frame) {
TimeKeeper timer = new TimeKeeper();
MessageBay.displayMessage("Computing comet stats...");
CometStats cometStats = new CometStats(frame);
String result = cometStats.toString();
MessageBay.overwriteMessage("Comet stats time: "
+ timer.getElapsedStringSeconds());
return result;
}
public static String GetTreeStats(Frame frame) {
TimeKeeper timer = new TimeKeeper();
MessageBay.displayMessage("Computing tree stats...");
TreeStats treeStats = new TreeStats(frame);
String result = treeStats.toString();
MessageBay.overwriteMessage("Tree stats time: "
+ timer.getElapsedStringSeconds());
return result;
}
public static String GetDocumentStats(Frame frame) {
TimeKeeper timer = new TimeKeeper();
MessageBay.displayMessage("Computing document stats...");
FrameIO.ForceSaveFrame(frame);
DocumentStatsFast docStats = new DocumentStatsFast(frame.getName(),
frame.getTitle());
String result = docStats.toString();
MessageBay.overwriteMessage("Document stats time: "
+ timer.getElapsedStringSeconds());
return result;
}
/**
* Creates a text item and attaches it to the cursor.
*
* @param itemText
* the text to attach to the cursor
*/
public static void attachStatsToCursor(String itemText) {
SessionStats.CreatedText();
Frame current = DisplayController.getCurrentFrame();
Item text = current.getStatsTextItem(itemText);
StandardGestureActions.pickup(text);
DisplayController.requestRefresh(true);
}
public static void attachTextToCursor(String itemText) {
SessionStats.CreatedText();
Frame current = DisplayController.getCurrentFrame();
Item text = current.getTextItem(itemText);
StandardGestureActions.pickup(text);
DisplayController.requestRefresh(true);
}
/**
* Creates a new Text Object containing statistics for moving, deleting and
* creating items in the current session. The newly created Text Object is
* then attached to the cursor via FrameMouseActions.pickup(Item)
*/
public static String getItemStats() {
return SessionStats.getItemStats();
}
/**
* Creates a new Text Object containing statistics for the time between
* events triggered by the user through mouse clicks and key presses. The
* newly created Text Object is then attached to the cursor via
* FrameMouseActions.pickup(Item)
*/
public static String getEventStats() {
return SessionStats.getEventStats();
}
/**
* Creates a new Text Object containing the contents of the current frames
* file.
*/
public static String getFrameFile(Frame frame) {
return FrameIO.ForceSaveFrame(frame);
}
/**
* Creates a new Text Object containing the available fonts.
*/
public static String getFontNames() {
Collection availableFonts = Actions.getFonts().values();
StringBuilder fontsList = new StringBuilder();
for (String s : availableFonts) {
fontsList.append(s).append(Text.LINE_SEPARATOR);
}
fontsList.deleteCharAt(fontsList.length() - 1);
return fontsList.toString();
}
/**
* Creates a new Text Object containing the available fonts already loaded into Expeditee.
*/
public static String getExpediteeFontNames(){
StringBuilder fontsList = new StringBuilder();
for (String s: Text.FONT_WHEEL){
fontsList.append(s).append(Text.LINE_SEPARATOR);
}
for (Entry entry: Text.FONT_WHEEL_ADDITIONAL_LOOKUP.entrySet()){
String fontName = entry.getKey();
fontsList.append(fontName).append(Text.LINE_SEPARATOR);
}
//add the default soon too
fontsList.deleteCharAt(fontsList.length() - 1);
return fontsList.toString();
}
public static String getUnicodeCharacters(int start, int finish) {
if (start < 0 && finish < 0) {
throw new RuntimeException("Parameters must be non negative");
}
// Swap the start and finish if they are inthe wrong order
if (start > finish) {
start += finish;
finish = start - finish;
start = start - finish;
}
StringBuilder charList = new StringBuilder();
int count = 0;
charList.append(String.format("Unicode block 0x%x - 0x%x", start,
finish));
System.out.println();
// charList.append("Unicode block: ").append(String.format(format,
// args))
for (char i = (char) start; i < (char) finish; i++) {
if (Character.isDefined(i)) {
if (count++ % 64 == 0)
charList.append(Text.LINE_SEPARATOR);
charList.append(Character.valueOf(i));
}
}
return charList.toString();
}
/**
* Gets a single block of Unicode characters.
*
* @param start
* the start of the block
*/
public static String getUnicodeCharacters(int start) {
return getUnicodeCharacters(start, start + 256);
}
/**
* Get a single Unicode character
*
* @param codePoint
* the Unicode codePoint
*/
public static String getUnicodeCharacter(int codePoint) {
char codePointChar = (char) codePoint;
if (Character.isDefined(codePointChar)) {
return Character.valueOf(codePointChar).toString();
}
else {
MessageBay.errorMessage("Character value '" + codePoint +"' not defined");
return "";
}
}
public static String getMathSymbols() {
return getUnicodeCharacters('\u2200', '\u2300');
}
/**
* Resets the statistics back to zero.
*/
public static void repaint() {
StatsLogger.WriteStatsFile();
SessionStats.resetStats();
}
/**
* Loads a frame with the given name and saves it as a JPEG image.
*
* @param framename
* The name of the Frame to save
*/
public static void jpegFrame(String framename) {
ImageFrame(framename, "JPEG");
}
/**
* Saves the current frame as a JPEG image. This is the same as calling
* JpegFrame(currentFrame.getName())
*/
public static void jpegFrame() {
ImageFrame(DisplayController.getCurrentFrame().getName(), "JPEG");
}
public static void jpgFrame() {
jpegFrame();
}
/**
* Loads a frame with the given name and saves it as a PNG image.
*
* @param framename
* The name of the Frame to save
*/
public static void PNGFrame(String framename) {
ImageFrame(framename, "PNG");
}
/**
* Saves the current frame as a PNG image. This is the same as calling
* PNGFrame(currentFrame.getName())
*/
public static void PNGFrame(Frame frame) {
ImageFrame(frame.getName(), "PNG");
}
public static String SaveImage(Image screen, String format,
String directory, String fileName) {
String suffix = "." + format.toLowerCase();
String shortFileName = fileName;
// Check if we need to append the suffix
if (fileName.indexOf('.') < 0)
fileName += suffix;
else
shortFileName = fileName.substring(0, fileName.length() - suffix.length());
try {
int count = 2;
// set up the file for output
File out = new File(directory + fileName);
while (out.exists()) {
fileName = shortFileName + "_" + count++ + suffix;
out = new File(directory + fileName);
}
if (!out.getParentFile().exists())
out.mkdirs();
// If the image is successfully written out return the fileName
if (screen.writeToDisk(format, out))
return fileName;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static String ImageFrame(Frame frame, String format, String directory) {
assert (frame != null);
Image oldBuffer = frame.getBuffer();
frame.setBuffer(null);
// Jpeg only works properly with volatile frames
// Png transparency only works with bufferedImage form
Image frameBuffer = FrameGraphics.getFrameImage(frame, null, null, false, format.equalsIgnoreCase("jpeg"));
// Make sure overlay stuff doesnt disappear on the frame visible on the
// screen
frame.setBuffer(oldBuffer);
return SaveImage(frameBuffer, format, directory, frame.getExportFileName());
}
/**
* Saves the Frame with the given Framename as an image of the given format.
*
* @param framename
* The name of the Frame to save as an image
* @param format
* The Image format to use (i.e. "PNG", "BMP", etc)
*/
public static void ImageFrame(String framename, String format) {
Frame loaded = FrameIO.LoadFrame(framename);
// if the frame was loaded successfully
if (loaded != null) {
String path = FrameIO.EXPORTS_PATH;
String frameName = ImageFrame(loaded, format, path);
if (frameName != null)
MessageBay.displayMessage("Frame successfully saved to " + path
+ frameName);
else
MessageBay.errorMessage("Could not find image writer for "
+ format + " format");
// if the frame was not loaded successfully, alert the user
} else {
MessageBay.displayMessage("Frame '" + framename
+ "' could not be found.");
}
}
public static void MessageLn(Item message) {
if (message instanceof Text)
MessageBay.displayMessage((Text) message);
}
/**
* Displays a message in the message box area.
*
* @param message
* the message to display
*/
public static void MessageLn(String message) {
MessageBay.displayMessage(message);
}
public static void MessageLn2(String message, String message2) {
MessageBay.displayMessage(message + " " + message2);
}
public static void FramesetMigrateImages() {
final Frame current = DisplayController.getCurrentFrame();
final String frameset = current.getFramesetName();
final int lastNumber = FrameIO.getLastNumber(frameset);
for(int i = 0; i <= lastNumber; i++) {
MigrateImages(FrameUtils.getFrame(frameset + i));
}
}
public static void MigrateImages() { MigrateImages(DisplayController.getCurrentFrame()); }
public static void MigrateImages(Frame frame) {
//Collect the images on frame
final Collection
- items = frame.getItems();
final Collection
- imagesTextItems = new LinkedList
- ();
items.forEach(i -> { if(i.getText().startsWith("@i")) imagesTextItems.add(i); });
final Map
- > images = new HashMap
- >();
imagesTextItems.forEach(it -> {
final Collection extends XRayable> enclosures = it.getEnclosures();
final Collection paths = new LinkedList();
enclosures.forEach(enc -> { if(enc instanceof Picture) paths.add(Paths.get(enc.getName())); });
images.put(it, paths);
});
//Separate into categories: absolute external, absolute internal. Discard relative.
final Map
- > imagesAbsolute = new HashMap
- >();
images.keySet().forEach(key -> images.get(key).forEach(path -> {
if(path.isAbsolute())
if(imagesAbsolute.containsKey(key)) imagesAbsolute.get(key).add(path);
else {
final Collection paths = new LinkedList();
paths.add(path);
imagesAbsolute.put(key, paths);
}
}));
final Path imagesPath = Paths.get(FrameIO.IMAGES_PATH);
final Map
- > imagesAbsoluteInternal = new HashMap
- >();
imagesAbsolute.keySet().forEach(key -> imagesAbsolute.get(key).forEach(path -> {
if(path.startsWith(imagesPath))
if(imagesAbsoluteInternal.containsKey(key)) imagesAbsoluteInternal.get(key).add(path);
else {
final Collection paths = new LinkedList();
paths.add(path);
imagesAbsoluteInternal.put(key, paths);
}
}));
final Map
- > imagesAbsoluteExternal = new HashMap
- >();
imagesAbsolute.keySet().forEach(key -> imagesAbsolute.get(key).forEach(path -> {
if(!path.startsWith(imagesPath))
if(imagesAbsoluteExternal.containsKey(key)) imagesAbsoluteExternal.get(key).add(path);
else {
final Collection paths = new LinkedList();
paths.add(path);
imagesAbsoluteExternal.put(key, paths);
}
}));
//Bryce: I am not sure why each Item is programmed to be able to have a collection of XRayables rather
//than a single one. Up until this point I have programmed defensively to retain this possibility. At
//this point the code will begin to simply use the first XRayable to determine the content of the @i.
//Transform absolute internal images into relative.
imagesAbsoluteInternal.keySet().forEach(key -> {
final Path imagePath = imagesAbsoluteInternal.get(key).iterator().next();
final Path relative = imagesPath.relativize(imagePath);
key.setText(key.getText().replace(imagePath.toString(), relative.toString()));
MessageBay.displayMessage("Migrated image: " + imagePath + ". It now uses the relative path: " + relative);
});
//Transform absolute external images into relative
imagesAbsoluteExternal.keySet().forEach(key -> {
final Path imagePath = imagesAbsoluteExternal.get(key).iterator().next();
try {
Path p = imagesPath.resolve(imagePath.getFileName());
FrameIO.copyFile(imagePath.toString(), p.toString());
final Path relative = imagesPath.relativize(p);
key.setText(key.getText().replace(imagePath.toString(), relative.toString()));
MessageBay.displayMessage("Migrated image: " + imagePath + ". It now uses the relative path: " + relative);
} catch (IOException e) {
MessageBay.displayMessage("Unable to Migrate file: " + imagePath);
}
});
}
public static void CopyFile(String existingFile, String newFileName) {
try {
// TODO is there a built in method which will do this faster?
MessageBay.displayMessage("Copying file " + existingFile + " to "
+ newFileName + "...");
FrameIO.copyFile(existingFile, newFileName);
MessageBay.displayMessage("File copied successfully");
} catch (FileNotFoundException e) {
MessageBay.displayMessage("Error opening file: " + existingFile);
} catch (Exception e) {
MessageBay.displayMessage("File could not be copied");
}
}
/**
* Runs two methods alternatively a specified number of times and reports on
* the time spent running each method.
*
* @param fullMethodNameA
* @param fullMethodNameB
* @param repsPerTest
* the number of time each method is run per test
* @param tests
* the number of tests to conduct
*
*/
public static void CompareMethods(String fullMethodNameA,
String fullMethodNameB, int repsPerTest, int tests) {
try {
String classNameA = getClassName(fullMethodNameA);
String classNameB = getClassName(fullMethodNameB);
String methodNameA = getMethodName(fullMethodNameA);
String methodNameB = getMethodName(fullMethodNameB);
Class> classA = Class.forName(classNameA);
Class> classB = Class.forName(classNameB);
Method methodA = classA.getDeclaredMethod(methodNameA,
new Class[] {});
Method methodB = classB.getDeclaredMethod(methodNameB,
new Class[] {});
TimeKeeper timeKeeper = new TimeKeeper();
long timeA = 0;
long timeB = 0;
// Run the tests
for (int i = 0; i < tests; i++) {
// Test methodA
timeKeeper.restart();
for (int j = 0; j < repsPerTest; j++) {
methodA.invoke((Object) null, new Object[] {});
}
timeA += timeKeeper.getElapsedMillis();
timeKeeper.restart();
// Test methodB
for (int j = 0; j < repsPerTest; j++) {
methodB.invoke((Object) null, new Object[] {});
}
timeB += timeKeeper.getElapsedMillis();
}
float aveTimeA = timeA * 1000F / repsPerTest / tests;
float aveTimeB = timeB * 1000F / repsPerTest / tests;
// Display Results
MessageBay.displayMessage("Average Execution Time");
MessageBay.displayMessage(methodNameA + ": "
+ TimeKeeper.Formatter.format(aveTimeA) + "us");
MessageBay.displayMessage(methodNameB + ": "
+ TimeKeeper.Formatter.format(aveTimeB) + "us");
} catch (Exception e) {
MessageBay.errorMessage(e.getClass().getSimpleName() + ": "
+ e.getMessage());
}
}
public static String getClassName(String fullMethodName) {
assert (fullMethodName != null);
assert (fullMethodName.length() > 0);
int lastPeriod = fullMethodName.lastIndexOf('.');
if (lastPeriod > 0 && lastPeriod < fullMethodName.length() - 1)
return fullMethodName.substring(0, lastPeriod);
throw new RuntimeException("Invalid method name: " + fullMethodName);
}
public static String getMethodName(String methodName) {
assert (methodName != null);
assert (methodName.length() > 0);
int lastPeriod = methodName.lastIndexOf('.');
if (lastPeriod > 0 && lastPeriod < methodName.length() - 1)
return methodName.substring(1 + lastPeriod);
throw new RuntimeException("Invalid method name: " + methodName);
}
/**
* Loads the Frame linked to by the given Item. The first Item on the Frame
* that is not the title or name is then placed on the current frame. The
* item that was clicked on is placed on the frame it was linked to and the
* link is switched to the item from the child frame. If the given Item has
* no link, or no item is found then this is a no-op.
*
* @param current
* The Item that links to the Frame that the Item will be loaded
* from.
*/
public static void SwapItemWithItemOnChildFrame(Item current) {
Item item = getFirstBodyItemOnChildFrame(current, false);
// if no item was found
if (item == null) {
return;
}
// swap the items parents
Frame parentFrame = current.getParent();
Frame childFrame = item.getParent();
current.setParent(childFrame);
item.setParent(parentFrame);
// swap the items on the frames
parentFrame.removeItem(current);
childFrame.removeItem(item);
parentFrame.addItem(item);
childFrame.addItem(current);
// swap the items links
item.setActions(current.getAction());
item.setLink(childFrame.getName());
current.setLink(parentFrame.getName());
// current.setLink(null);
current.setActions(null);
DisplayController.requestRefresh(true);
}
private static Item getFirstBodyItemOnChildFrame(Item current,
boolean textOnly) {
// the item must link to a frame
if (current.getLink() == null) {
MessageBay
.displayMessage("Cannot get item from child - this item has no link");
return null;
}
Frame child = FrameIO.LoadFrame(current.getAbsoluteLink());
// if the frame could not be loaded
if (child == null) {
MessageBay.errorMessage("Could not load child frame.");
return null;
}
// find the first non-title and non-name item
List
- body = new ArrayList
- ();
if (textOnly)
body.addAll(child.getBodyTextItems(false));
else
body.addAll(child.getItems());
Item item = null;
for (Item i : body)
if (i != child.getTitleItem() && !i.isAnnotation()) {
item = i;
break;
}
// if no item was found
if (item == null) {
MessageBay.displayMessage("No item found to copy");
return null;
}
return item;
}
private static Collection
- getItemsOnChildFrame(Item current,
boolean textOnly) {
// the item must link to a frame
if (current.getLink() == null) {
MessageBay
.displayMessage("Cannot get item from child - this item has no link");
return null;
}
Frame child = FrameIO.LoadFrame(current.getAbsoluteLink());
// if the frame could not be loaded
if (child == null) {
MessageBay.errorMessage("Could not load child frame.");
return null;
}
// find the first non-title and non-name item
Collection
- body = new ArrayList
- ();
if (textOnly)
body.addAll(child.getBodyTextItems(false));
else
body.addAll(child.getItems());
return body;
}
public static void calculate(Frame frame, Item toCalculate) {
if (toCalculate instanceof Text) {
Text text = (Text) toCalculate;
ExpediteeJEP myParser = new ExpediteeJEP();
myParser.addVariables(frame);
String linkedFrame = toCalculate.getAbsoluteLink();
if (linkedFrame != null) {
myParser.addVariables(FrameIO.LoadFrame(linkedFrame));
}
myParser.resetObserver();
// Do the calculation
String formulaFullCase = text.getText().replace('\n', ' ');
String formula = formulaFullCase.toLowerCase();
try {
Node node = myParser.parse(formula);
Object result = myParser.evaluate(node);
text.setText(result.toString(), true);
text.setFormula(formulaFullCase);
if (text.isFloating()) {
Point cursorPos = EcosystemManager.getInputManager().getCursorPosition();
text.setPosition(cursorPos.getX(), cursorPos.getY());
StandardGestureActions.resetOffset();
} else {
text.getParentOrCurrentFrame().change();
}
} catch (ParseException e) {
MessageBay.errorMessage("Parse error "
+ e.getMessage().replace("\n", ""));
} catch (Exception e) {
MessageBay.errorMessage("evaluation error "
+ e.getMessage().replace("\n", ""));
e.printStackTrace();
}
}
}
/**
* Attach an item to the cursor.
*
* @param item
*/
public static void attachToCursor(Item item) {
item.setParent(null);
StandardGestureActions.pickup(item);
DisplayController.requestRefresh(true);
}
public static void attachToCursor(Collection
- items) {
for (Item i : items) {
i.setParent(null);
i.invalidateAll();
}
StandardGestureActions.pickup(items);
// TODO figure out why this isnt repainting stuff immediately
// All of text item doesnt repaint until the cursor is moved
DisplayController.requestRefresh(true);
}
public static void importFiles(Item item) {
List files = new LinkedList();
for (String s : item.getText().split("\\s+")) {
File file = new File(s.trim());
if (file.exists()) {
files.add(file);
}
}
try {
EcosystemManager.getDragAndDropManager().importFileList(files, EcosystemManager.getInputManager().getCursorPosition());
} catch (Exception e) {
}
}
public static void importFile(Item item) {
File file = new File(item.getText().trim());
if (file.exists()) {
try {
EcosystemManager.getDragAndDropManager().importFile(file, EcosystemManager.getInputManager().getCursorPosition());
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static Item createPolygon(Item item, int sides) {
if (item instanceof Text) {
try {
SString s = new SString(item.getText());
sides = s.integerValue().intValue();
} catch (NumberFormatException e) {
}
}
if (sides < 3) {
MessageBay.errorMessage("Shapes must have at least 3 sides");
}
double angle = -(180 - ((sides - 2) * 180.0F) / sides);
double curAngle = 0;
double size = 50F;
if (item.isLineEnd() && item.getLines().size() > 0) {
item = item.getLines().get(0);
}
// Use line length to determine the size of the shape
if (item instanceof Line) {
size = ((Line) item).getLength();
}
Point cursorPos = DisplayController.getMousePosition();
float curX = cursorPos.getX();
float curY = cursorPos.getY();
Collection
- newItems = new LinkedList
- ();
Item[] d = new Item[sides];
// create dots
Frame current = DisplayController.getCurrentFrame();
for (int i = 0; i < d.length; i++) {
d[i] = current.createDot();
newItems.add(d[i]);
d[i].setPosition(curX, curY);
curX += (float) (Math.cos((curAngle) * Math.PI / 180.0) * size);
curY += (float) (Math.sin((curAngle) * Math.PI / 180.0) * size);
curAngle += angle;
}
// create lines
for (int i = 1; i < d.length; i++) {
newItems.add(new Line(d[i - 1], d[i], current.getNextItemID()));
}
newItems.add(new Line(d[d.length - 1], d[0], current.getNextItemID()));
current.addAllItems(newItems);
if (item instanceof Text) {
for (Item i : item.getAllConnected()) {
if (i instanceof Line) {
item = i;
break;
}
}
}
Colour newColor = item.getColor();
if (newColor != null) {
d[0].setColor(item.getColor());
if (item instanceof Text && item.getBackgroundColor() != null) {
d[0].setFillColor(item.getBackgroundColor());
} else {
d[0].setFillColor(item.getFillColor());
}
}
float newThickness = item.getThickness();
if (newThickness > 0) {
d[0].setThickness(newThickness);
}
ItemUtils.EnclosedCheck(newItems);
DisplayController.requestRefresh(false);
return d[0];
}
public static void StopReminder() {
Reminders.stop();
}
public static void print(String file)
{
String errorMessage = EcosystemManager.getMiscManager().print(file);
if (errorMessage != null) MessageBay.errorMessage("Printing error: " + errorMessage);
}
public static int wordCount(String paragraph) {
return paragraph.trim().split("\\s+").length + 1;
}
public static int wordCount(Frame frame) {
int count = 0;
for (Text t : frame.getBodyTextItems(false)) {
count += wordCount(t.getText());
}
return count;
}
public static void moveToPublic(Frame frame) {
FrameIO.moveFrameset(frame.getFramesetName(), FrameIO.PUBLIC_PATH, false);
}
public static void moveToPrivate(Frame frame) {
FrameIO.moveFrameset(frame.getFramesetName(), FrameIO.FRAME_PATH, false);
}
/**
* Returns the value of a specified item attribute.
*
* @param item
* from which to extract the value
* @param attribute
* name of an items attribute
* @return the value of the attribute
*/
public static String extract(Item item, String attribute) {
return AttributeUtils.getAttribute(item, attribute);
}
/**
* Launches items.widgets.Browser and uses Text item as URL.
* @param text Text item which passes contents as URL for browser.
* @throws Exception
*/
/* public static void startLoboBrowser(Item text) throws Exception {
if (!(text instanceof Text)) {
MessageBay.errorMessage("Must be a text item.");
return;
}
if(text.getLink() != null) {
MessageBay.errorMessage("Text item cannot have link.");
return;
}
FreeItems.getInstance().clear(); // remove url text from cursor
Text wt = new Text("@iw:org.expeditee.items.widgets.Browser"); // create new text item for browser widget
wt.setParent(DisplayIO.getCurrentFrame()); // set parent of text source for InteractiveWidget.createWidget()
wt.setXY(FrameMouseActions.getX(), FrameMouseActions.getY());
// create widget from text item
org.expeditee.items.widgets.Browser browser = (org.expeditee.items.widgets.Browser) InteractiveWidget.createWidget(wt);
if(FreeItems.textOnlyAttachedToCursor()) { // navigates to url specified by the text item
browser.navigate(text.getText());
} else {
browser.navigate("http://www.waikato.ac.nz");
}
FrameMouseActions.pickup(browser.getItems()); // attach browser widget to mouse
}*/
/**
* Text item becomes link to new frame containing items.widgets.Browser and uses Text item as URL for browser.
* @param text Text item which passes contents as URL for browser and becomes link to the browser's new frame.
* @throws Exception
*/
public static void startLoboBrowserNewFrame(Item text) throws Exception
{
if (!(text instanceof Text)) {
MessageBay.errorMessage("Must be a text item.");
return;
}
if(text.getLink() != null) { // text item can't already have a link
MessageBay.errorMessage("Text item already has link.");
return;
}
// Create new frame and text item for browser widget and parse created frame; loads browser widget
Frame frame = FrameIO.CreateNewFrame(text);
frame.addText(0, 50, "@iw:org.expeditee.items.widgets.Browser", null);
FrameUtils.Parse(frame);
for(Widget iw : frame.getInteractiveWidgets()) { // may be other widgets on frame
if(iw instanceof org.expeditee.items.widgets.Browser) {
// Set browser to 'full screen'
iw.setSize(-1, -1, -1, -1, DisplayController.getFramePaintAreaWidth(), DisplayController.getFramePaintAreaHeight() - 80);
// If there is a text item attached to cursor use it as url for browser
if (FreeItems.textOnlyAttachedToCursor()) {
text.setLink("" + frame.getNumber());
((org.expeditee.items.widgets.Browser)iw).navigate(text.getText());
} else {
// Navigate to www.waikato.ac.nz by default if no url supplied and create new text item to be the link
((org.expeditee.items.widgets.Browser)iw).navigate("http://www.waikato.ac.nz");
Text t = new Text("http://www.waikato.ac.nz");
t.setParent(DisplayController.getCurrentFrame()); // set parent of text source for InteractiveWidget.createWidget()
t.setXY(DisplayController.getFloatMouseX(), DisplayController.getFloatMouseY());
t.setLink("" + frame.getNumber()); // link url text to new browser frame
StandardGestureActions.pickup(t); // Attach new text link to cursor
}
}
}
FrameIO.SaveFrame(frame); // save frame to disk
}
private static boolean startWidget(String name) throws Exception
{
String fullName = Actions.getClassName(name);
if(fullName == null) {
return false;
}
MessageBay.displayMessage("Creating new \"" + fullName + "\"");
FreeItems.getInstance().clear();
Text wt = new Text("@iw:" + fullName); // create new text item for browser widget
wt.setParent(DisplayController.getCurrentFrame()); // set parent of text source for InteractiveWidget.createWidget()
wt.setXY(DisplayController.getMouseX(), DisplayController.getMouseY()); // move to the mouse cursor
Widget widget = Widget.createWidget(wt);
StandardGestureActions.pickup(widget.getItems());
return true;
}
private static void runUnknown(String command) throws Exception {
if(startWidget(command)) {
return;
}
Actions.PerformAction(DisplayController.getCurrentFrame(), null, command, null);
}
public static void run(String command) throws Exception {
if(command == null) {
MessageBay.warningMessage("Please provide a command to run");
return;
}
int firstSpace = command.indexOf(" ");
if(firstSpace == -1) {
runUnknown(command);
return;
}
String argLower = command.toLowerCase();
String name = argLower.substring(0, firstSpace).trim(); // first word
String args = argLower.substring(firstSpace).trim(); // remainder after first word
if(name == "action" || name == "agent") {
if(args.length() > 0) {
Actions.LegacyPerformAction(DisplayController.getCurrentFrame(), null, args);
} else {
MessageBay.displayMessage("Please specify an action/agent name");
}
} else if(name == "widget") {
if(args.length() > 0) {
if(!startWidget(args)) {
MessageBay.displayMessage("Widget \"" + name + "\" does not exist");
}
} else {
MessageBay.displayMessage("Please specify a widget name");
}
} else {
runUnknown(command);
}
}
public static void run(Item item) throws Exception {
if(item == null) {
MessageBay.warningMessage("Please provide a command to run");
return;
}
run(((Text)item).getText());
}
/**
* Rebuilds the home frame restoring its original presentation.
* Basically removes all items on the frame and reruns FrameUtils.CreateDefaultProfile().
*/
public static void resetHomeFrame() {
Frame homeFrame = FrameIO.LoadFrame(UserSettings.HomeFrame.get());
homeFrame.removeAllItems(homeFrame.getItems());
homeFrame.addText(0, 0, "title", null);
FrameUtils.CreateDefaultProfile(UserSettings.UserName.get(), homeFrame, null, null);
}
/**
* Loads and runs an executable jar file in a new Thread
* @param jar path to the jar file to run
*/
public static void runJar(String jar) throws Exception {
File jf = new File(jar);
if(!jf.exists()) {
System.err.println("jar '" + jar + "' could not be found");
return;
}
JarFile jarFile = new JarFile(jf);
String mainClassName = (String) jarFile.getManifest().getMainAttributes().get(new Attributes.Name("Main-Class"));
if(mainClassName == null) {
System.err.println("jar '" + jar + "' does not have a Main-Class entry");
jarFile.close();
return;
}
jarFile.close();
System.out.println("Main-Class = " + mainClassName);
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
Method addURL = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
addURL.setAccessible(true);
addURL.invoke(classLoader, jf.toURI().toURL());
final Class> jarClass = classLoader.loadClass(mainClassName);
final Method main = jarClass.getDeclaredMethod("main", String[].class);
new Thread(new Runnable() {
public void run() {
try {
main.invoke(jarClass, new Object[] {new String[0]});
} catch (Exception e) {
System.out.println("Failed to start jar");
e.printStackTrace();
}
}
}).start();
}
public static void pan(Frame frame, int x, int y) {
for (Item i : frame.getAllItems()) {
if (i instanceof WidgetEdge || i instanceof WidgetCorner) {
continue;
} else {
int new_x = i.getX();
int new_y = i.getY();
if (!i.isAnchoredX()) {
new_x += x;
}
if (!i.isAnchoredY()) {
new_y += y;
}
if(i instanceof XRayable) {
i.setPosition(new_x,new_y);
}
else {
i.setXY(new_x,new_y);
}
}
// update the polygon, otherwise stuff moves but leaves it's outline behind
i.invalidateBounds();
}
for (Widget iw : frame.getInteractiveWidgets()) {
int new_x = iw.getX();
int new_y = iw.getY();
if (!iw.isAnchoredX()) {
new_x += x;
}
if (!iw.isAnchoredY()) {
new_y += y;
}
iw.setPosition(new_x,new_y);
}
// make sure we save the panning of the frame
frame.change();
// redraw everything
StandardGestureActions.Refresh();
}
public static void pan(Frame frame, String pan) {
String[] split = pan.split("\\s+");
int x = 0;
int y = 0;
try {
if(split.length != 2) throw new Exception();
x = Integer.parseInt(split[0]);
y = Integer.parseInt(split[1]);
} catch(Exception e) {
MessageBay.errorMessage("Panning takes 2 integer arguments");
return;
}
pan(frame, x, y);
}
public static void pan(Frame frame, Text pan) {
pan(frame, pan.getText());
}
public static String exec(String cmd) throws Exception {
String[] command;
// run command through sh if possible
if(System.getProperty("os.name").toLowerCase().indexOf("win") == -1) {
command = new String[] { "sh", "-c", cmd };
} else {
command = cmd.split("\\s+");
}
ProcessBuilder pb = new ProcessBuilder(command);
pb.redirectErrorStream(true);
Process ps = pb.start();
BufferedReader in = new BufferedReader(new InputStreamReader(ps.getInputStream()));
StringBuffer sb = new StringBuffer();
String line;
while ((line = in.readLine()) != null) {
sb.append(line).append('\n');
}
ps.waitFor();
in.close();
if(sb.length() > 0) {
sb.deleteCharAt(sb.length() - 1);
}
return sb.toString();
}
public static void testProgress() {
new Thread(new Runnable() {
@Override
public void run() {
Progress p = MessageBay.displayProgress("Loading something");
for(int i = 1; i <= 100; i++) {
try {
Thread.sleep(100);
p.set(i);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}).start();
}
public static void getIDs(Frame f) {
for(Item i : f.getAllItems()) {
System.out.println(i + " (" + i.getID() + ")");
}
}
public static void flushResources() {
FrameUtils.extractResources(true);
MessageBay.displayMessage("Re-extracted resources, Expeditee may need to be restarted for certain resources to be reloaded");
}
// Some experimental actions to do with keeping framesets stored within a Git repository
// For custom merge, some potentially useful information at:
// http://stackoverflow.com/questions/23140240/git-how-do-i-add-a-custom-merge-strategy
// http://stackoverflow.com/questions/7607125/git-merge-conflict-to-always-take-the-newest-file
protected static String gitexe = "git";
protected static void runGitCommand(Frame frame, List cmd_array) {
String framePath = frame.getPath();
String frameName = frame.getName();
String frameDir = frame.getFramesetPath(); //framePath + Conversion.getFramesetName(frameName) + File.separator;
String localFname = Conversion.getFrameNumber(frameName)
+ ExpReader.EXTENTION;
ProcessBuilder process_builder = new ProcessBuilder(cmd_array);
process_builder.directory(new File(frameDir));
/*
System.err.println("\nPATH:");
Map env_map = process_builder.environment();
for (Entry entry: env_map.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
System.err.println(key + " = " + value);
}
*/
try {
final Process process = process_builder.start();
InputStream is = process.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
System.out.println("Program terminated!");
}
catch (Exception e) {
e.printStackTrace();
}
}
public static void GitPushFrame() {
StandardGestureActions.Save();
Frame current = DisplayController.getCurrentFrame();
String userName = UserSettings.ProfileName.get();
String frameName = current.getName();
String localFname = Conversion.getFrameNumber(frameName)+ ExpReader.EXTENTION;
List status_cmd_array = new ArrayList();
status_cmd_array.add(gitexe);
status_cmd_array.add("status");
status_cmd_array.add(".");
List add_cmd_array = new ArrayList();
add_cmd_array.add(gitexe);
add_cmd_array.add("add");
add_cmd_array.add(localFname);
add_cmd_array.add("frame.inf");
runGitCommand(current,add_cmd_array);
List commit_cmd_array = new ArrayList();
commit_cmd_array.add(gitexe);
commit_cmd_array.add("commit");
commit_cmd_array.add("-m");
commit_cmd_array.add("expeditee-edit-"+userName);
runGitCommand(current,commit_cmd_array);
List push_cmd_array = new ArrayList();
push_cmd_array.add(gitexe);
push_cmd_array.add("push");
push_cmd_array.add("origin");
push_cmd_array.add("master");
runGitCommand(current,push_cmd_array);
}
public static void GitPullFrame() {
Frame current = DisplayController.getCurrentFrame();
List cmd_array = new ArrayList();
cmd_array.add(gitexe);
cmd_array.add("pull");
cmd_array.add("origin");
cmd_array.add("master");
runGitCommand(current,cmd_array);
FrameIO.Reload();
StandardGestureActions.Refresh();
}
}