package org.expeditee.actions; import java.awt.Color; import java.awt.Image; import java.awt.image.BufferedImage; import java.awt.image.VolatileImage; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.List; import javax.imageio.ImageIO; import org.expeditee.gui.DisplayIO; import org.expeditee.gui.Frame; import org.expeditee.gui.FrameGraphics; import org.expeditee.gui.FrameIO; import org.expeditee.gui.FrameMouseActions; import org.expeditee.gui.MessageBay; import org.expeditee.gui.TimeKeeper; import org.expeditee.items.Item; import org.expeditee.items.ItemUtils; import org.expeditee.items.Text; import org.expeditee.math.ExpediteeJEP; import org.expeditee.stats.CometStats; 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 and Actions specific to Expeditee * */ public class Misc { /** * Causes the system to beep */ public static void beep() { java.awt.Toolkit.getDefaultToolkit().beep(); } /** * Forces a repaint of the current Frame */ public static void display() { FrameGraphics.refresh(false); } /** * 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() { FrameGraphics.ToggleAudienceMode(); } /** * Toggles TwinFrames mode on or off */ public static void toggleTwinFramesMode() { DisplayIO.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.PerformAction(DisplayIO.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 = DisplayIO.getCurrentFrame(); String deletedFrame = toDelete.getName(); String deletedFrameNameLowercase = deletedFrame.toLowerCase(); String errorMessage = "Error deleting " + deletedFrame; try { String deletedFrameName = FrameIO.DeleteFrame(toDelete); if (deletedFrameName != null) { DisplayIO.Back(); // Remove any links on the previous frame to the one being // deleted Frame current = DisplayIO.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(DisplayIO.getMouseX(), FrameMouseActions.getY()); } 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 = DisplayIO.getMouseX() - first.getX(); float deltaY = FrameMouseActions.getY() - first.getY(); for (Item i : copies) { if (i.isVisible()) i.setXY(i.getX() + deltaX, i.getY() + deltaY); i.setParent(null); } FrameMouseActions.pickup(copies); FrameGraphics.Repaint(); } /** * 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, Color toUse) { if (toChange == null) return; toChange.setBackgroundColor(toUse); FrameGraphics.Repaint(); } /** * 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, Color toUse) { if (toChange == null) return; toChange.setColor(toUse); FrameGraphics.Repaint(); } /** * 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; } /** * 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 = DisplayIO.getCurrentFrame(); Item text = current.getStatsTextItem(itemText); FrameMouseActions.pickup(text); FrameGraphics.Repaint(); } public static void attachTextToCursor(String itemText) { SessionStats.CreatedText(); Frame current = DisplayIO.getCurrentFrame(); Item text = current.getTextItem(itemText); FrameMouseActions.pickup(text); FrameGraphics.Repaint(); } /** * 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(); } 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); } 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(DisplayIO.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() { ImageFrame(DisplayIO.getCurrentFrame().getName(), "PNG"); } public static String SaveImage(BufferedImage screen, String format, String directory, String fileName) { // Check if we need to append the suffix if (fileName.indexOf('.') < 0) fileName += "." + format.toLowerCase(); try { // set up the file for output String fullFileName = directory + fileName; File out = new File(fullFileName); if (!out.getParentFile().exists()) out.mkdirs(); // If the image is successfully written out return the fileName if (ImageIO.write(screen, 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 volitile frames // Png transparency only works with bufferedImage form Image frameBuffer = FrameGraphics.getBuffer(frame, false, format .equalsIgnoreCase("jpeg")); // Make sure overlay stuff doesnt disapear on the frame visible on the // screen frame.setBuffer(oldBuffer); BufferedImage screen = null; if (frameBuffer instanceof VolatileImage) { // If its the current frame it will be a volitive image screen = ((VolatileImage) frameBuffer).getSnapshot(); } else { assert (frameBuffer instanceof BufferedImage); screen = (BufferedImage) frameBuffer; } return SaveImage(screen, 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.IMAGES_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."); } } /** * 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 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); FrameGraphics.Repaint(); } 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()); text.setFormula(formulaFullCase); if (text.isFloating()) { text.setPosition(FrameMouseActions.MouseX, FrameMouseActions.MouseY); FrameMouseActions.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); FrameMouseActions.pickup(item); FrameGraphics.Repaint(); } }