/******************************************************************************* * Copyright (c) 2007, 2008 Gregory Jordan * * This file is part of PhyloWidget. * * PhyloWidget 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 2 of the License, or (at your option) any later * version. * * PhyloWidget 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 * PhyloWidget. If not, see <http://www.gnu.org/licenses/>. */ package org.phylowidget.ui; import java.awt.BorderLayout; import java.awt.Component; import java.awt.FileDialog; import java.awt.Frame; import java.awt.Label; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.StringReader; import java.lang.reflect.Field; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.swing.SwingUtilities; import org.andrewberman.ui.EventManager; import org.andrewberman.ui.FocusManager; import org.andrewberman.ui.ShortcutManager; import org.andrewberman.ui.menu.MenuItem; import org.andrewberman.ui.menu.ToolDock; import org.andrewberman.ui.menu.Toolbar; import org.andrewberman.ui.unsorted.FileUtils; import org.andrewberman.ui.unsorted.MethodAndFieldSetter; import org.phylowidget.PWContext; import org.phylowidget.PWPlatform; import org.phylowidget.PhyloTree; import org.phylowidget.PhyloWidget; import org.phylowidget.net.PhyloTransformServices; import org.phylowidget.net.SecurityChecker; import org.phylowidget.render.BasicTreeRenderer; import org.phylowidget.render.LayoutCladogram; import org.phylowidget.render.NodeRange; import org.phylowidget.tree.CachedRootedTree; import org.phylowidget.tree.PhyloNode; import org.phylowidget.tree.RootedTree; import org.phylowidget.tree.TreeIO; import processing.core.PApplet; public class PhyloUI implements Runnable { PhyloWidget p; PWContext context; public FocusManager focus; public EventManager event; public ShortcutManager keys; public TreeClipboard clipboard; // public NearestNodeFinder nearest; // public NodeTraverser traverser; public PhyloTextField text; public PhyloContextMenu contextMenu; public Toolbar toolbar; public SearchBox search; public PhyloUI(PhyloWidget p) { this.p = p; context = PWPlatform.getInstance().getThisAppContext(); } public Thread thread; public void setup() { focus = context.focus(); event = context.event(); keys = context.shortcuts(); text = new PhyloTextField(p); clipboard = new TreeClipboard(p); thread = context.createThread(this); thread.start(); } public ArrayList<MenuItem> menus; public void run() { /* * Then, load properties from the applet. */ try { p.callMethod("setMenusIfNull"); loadFromAppletParams(p); } catch (Exception e) { e.printStackTrace(); // Do nothing. Continue on... } checkPermissions(); layout(); } public void setMenusIfNull() { if (menus == null) setMenus(); } boolean runningInBrowser() { String appViewer = p.getAppletContext().getClass().getCanonicalName(); // System.out.println(appViewer); if (appViewer.toLowerCase().contains("appletviewer")) return false; else return true; } void disposeMenus() { /* * Menu file should be loaded first. */ if (menus != null) { for (MenuItem i : menus) { i.dispose(); } } } public synchronized void setMenus() { disposeMenus(); // if (context.config().debug) // System.out.println("Disposed!"); String[] menuFiles = context.config().menus.split(";"); ArrayList<MenuItem> allMenus = new ArrayList<MenuItem>(); for (String menuFile : menuFiles) { if (menuFile.trim().length() == 0) continue; if (context.config().debug) System.out.println("PhyloUI setMenus(): " + menuFile); // GJ 27-8-08: remove quotes from menu specification. menuFile.replaceAll("'", ""); menuFile.replaceAll("\"", ""); PhyloMenuIO io = new PhyloMenuIO(); Reader r = null; InputStream in = null; Exception asdf = null; if (menuFile.toLowerCase().startsWith("http")) { try { URL url = new URL(menuFile); in = url.openStream(); } catch (Exception e) { e.printStackTrace(); } } else { try { in = p.createInput("menus/" + menuFile); // in = p.openStream("menus/" + menuFile); if (in == null) { // in = p.openStream(menuFile); // in = p.openStream(menuFile); in = p.createInput(menuFile); } if (in == null) { String path = p.getDocumentBase().toString(); int ind = path.lastIndexOf("/"); if (ind != -1) path = path.substring(0, ind); // System.out.println(path); in = p.createInput(path + "/" + menuFile); } } catch (Exception e) { asdf = e; } } /* * If "in" is STILL null, then let's try just loading the string itself. */ if (in == null) { r = new StringReader(menuFile); } else { r = new InputStreamReader(in); // r = new StringReader(fileS); } if (in == null) { asdf.printStackTrace(); } ArrayList<MenuItem> theseMenus = io.loadFromXML(r, p, context.ui(), p, context.config()); // System.out.println(menuFile); configureMenus(theseMenus); allMenus.addAll(theseMenus); } this.menus = allMenus; if (context.config().debug) System.out.println("Finished!"); } String streamToString(InputStream in) { int c = 0; InputStreamReader r = new InputStreamReader(in); StringBuffer sb = new StringBuffer(); try { while ((c = r.read()) != -1) { sb.append((char) c); } return sb.toString(); } catch (Exception e) { return ""; } } public void loadFromAppletParams(PApplet app) throws Exception { /* * Let's first load the URL query parameters. */ // System.out.println(app.getDocumentBase()); /* * The Javascript-defined parameters should take precedence, so now we'll load those up: */ HashMap<String, String> map = new HashMap<String, String>(); Field[] fields = PhyloConfig.class.getDeclaredFields(); for (Field f : fields) { try { String param = app.getParameter(f.getName()); if (param != null) { // map.put(f.getName(), param); p.changeSetting(f.getName(), param); } } catch (Exception e) { // e.printStackTrace(); } } // System.out.println(map); // MethodAndFieldSetter.setMethodsAndFields(context.config(), map); } protected synchronized void configureMenus(ArrayList menus) { /* * Some special handling of specific menus. */ for (int i = 0; i < menus.size(); i++) { MenuItem menu = (MenuItem) menus.get(i); if (menu instanceof PhyloContextMenu) { // System.out.println("ASDF"); this.contextMenu = (PhyloContextMenu) menu; continue; } else if (menu.getClass() == Toolbar.class) { toolbar = (Toolbar) menu; MenuItem item = toolbar.get("Search:"); if (item != null) { search = (SearchBox) item; search.setText(context.config().search); } } else if (menu.getClass() == ToolDock.class) { ToolDock td = (ToolDock) menu; } } } void checkPermissions() { SecurityChecker sc = new SecurityChecker(context.getPW()); canWriteFiles = sc.canWriteFiles(); canReadFiles = sc.canReadFiles(); canAccessInternet = sc.canAccessInternet(); } /** * Some utility methods and fields for UI items dependent on security * permissions. */ boolean canWriteFiles; boolean canReadFiles; boolean canAccessInternet; public boolean canWriteFiles() { return canWriteFiles; } public boolean canReadFiles() { return canReadFiles; } public boolean canAccessInternet() { return canAccessInternet; } public RootedTree getCurTree() { return context.trees().getTree(); } public PhyloTree getTree() { return (PhyloTree) getCurTree(); } public void layout() { if (context.trees().getRenderer() != null) context.trees().getRenderer().layoutTrigger(); context.trees().fireCallback(); } public void forceLayout() { if (context.trees().getRenderer() != null) { BasicTreeRenderer render = (BasicTreeRenderer) context.trees().getRenderer(); render.forceLayout(); } context.trees().fireCallback(); } public PhyloNode getCurNode() { if (curRange() == null) return null; else return curRange().node; } public void search() { if (search != null) { search.setText(context.config().search); } else { PhyloTree t = (PhyloTree) getCurTree(); if (t != null) t.searchAndMarkFound(context.config().search); } } public NodeRange curRange() { if (contextMenu == null) return null; return contextMenu.curNodeRange; } public void nodeEditBranchLength() { text.startEditing(curRange(), PhyloTextField.BRANCH_LENGTH); } public void nodeEditName() { text.startEditing(curRange(), PhyloTextField.LABEL); } FontChooserDialog fontChooser; public void changeFont() { SwingUtilities.invokeLater(new Runnable() { public void run() { if (fontChooser == null) fontChooser = new FontChooserDialog(getFrame(), context); fontChooser.setVisible(true); } }); } AnnotationEditorDialog annotation; public void nodeEditAnnotation() { SwingUtilities.invokeLater(new Runnable() { public void run() { if (annotation == null) annotation = new AnnotationEditorDialog(getFrame(), p); annotation.setNode(getCurNode()); annotation.setVisible(true); } }); } public void nodeReroot() { NodeRange r = curRange(); synchronized (r.render.getTree()) { r.render.getTree().reroot(getCurNode()); } } public void reroot() { nodeReroot(); } public void nodeSwitchChildren() { NodeRange r = curRange(); r.render.getTree().flipChildren(getCurNode()); r.render.layoutTrigger(); } public void nodeFlipSubtree() { NodeRange r = curRange(); r.render.getTree().reverseSubtree(getCurNode()); getCurTree().modPlus(); r.render.layoutTrigger(); } public void nodeAddSister() { NodeRange r = curRange(); RootedTree tree = r.render.getTree(); PhyloNode sis = (PhyloNode) tree.createAndAddVertex(); tree.addSisterNode(getCurNode(), sis); } public void nodeAddChild() { NodeRange r = curRange(); RootedTree tree = r.render.getTree(); tree.addChildNode(getCurNode()); } public void nodeCut() { NodeRange r = curRange(); clipboard.cut(r.render.getTree(), r.node); } public void nodeCopy() { NodeRange r = curRange(); clipboard.copy(r.render.getTree(), r.node); } public void selectNode(String s) { PhyloTree tree = (PhyloTree) getCurTree(); List<PhyloNode> nodes = tree.search(s); if (nodes.size() == 0) { System.err.println("Node " + s + " not found!"); } contextMenu.curNodeRange = nodes.get(0).range; } void setMessage(String s) { context.getPW().setMessage(s); } public void nodeSwap() { final NodeRange r = curRange(); setMessage("Swapping clipboard..."); new Thread() { public void run() { try { synchronized (r.render.getTree()) { clipboard.swap(r.render.getTree(), r.node); } setMessage(""); } catch (Exception e) { e.printStackTrace(); setMessage("Swap failed! Make sure the clipboard is not empty."); } } }.start(); } public void nodePaste() { final NodeRange r = curRange(); setMessage("Pasting clipboard..."); new Thread() { public void run() { try { clipboard.paste((CachedRootedTree) r.render.getTree(), r.node); setMessage(""); } catch (Exception e) { e.printStackTrace(); setMessage("Paste failed! Make sure the clipboard is not empty."); } } }.start(); } public void nodeClearClipboard() { clipboard.clearClipboard(); } public void nodeDelete() { NodeRange r = curRange(); RootedTree g = r.render.getTree(); synchronized (g) { g.deleteNode(getCurNode()); } } public void nodeDeleteSubtree() { NodeRange r = curRange(); RootedTree g = r.render.getTree(); final PhyloNode n = getCurNode(); synchronized (g) { g.deleteSubtree(n); } } public void nodeCollapse() { NodeRange r = curRange(); RootedTree g = r.render.getTree(); PhyloNode n = getCurNode(); n.setAnnotation("layout_size", g.getNumEnclosedLeaves(n)); g.collapseNode(n); g.modPlus(); layout(); } /* * View actions. */ public void viewUnrooted() { context.config().setLayout("unrooted"); } public void viewRectangular() { context.config().setLayout("rectangle"); } public void viewDiagonal() { context.config().setLayout("diagonal"); } public void viewCircular() { context.config().setLayout("circular"); } public void zoomToFull() { // TreeManager.camera.zoomCenterTo(0, 0, p.width, p.height); context.trees().fillScreen(); } /* * Tree Actions. */ public void treeNew() { synchronized (context.trees().getTree()) { context.trees().setTree(PhyloConfig.DEFAULT_TREE); } layout(); } public void treeFlip() { PhyloTree t = (PhyloTree) getCurTree(); t.reverseSubtree(t.getRoot()); t.modPlus(); layout(); } public void treeAutoSort() { RootedTree tree = getCurTree(); tree.ladderizeSubtree(tree.getRoot()); layout(); } public void treeRemoveElbows() { RootedTree tree = getCurTree(); synchronized (tree) { tree.removeElbowsBelow(tree.getRoot()); } layout(); } public void treeUncollapseAll() { getCurTree().uncollapseAllNodes(); layout(); } /* * Aligns the leaves of the tree, changing branch lengths accordingly. */ public void treeAlignLeaves() { new Thread() { @Override public void run() { setMessage("Aligning leaves..."); RootedTree tree = getCurTree(); // tree.alignLeaves(); tree.makeSubtreeUltrametric(tree.getRoot()); layout(); setMessage(""); } }.start(); } public void treeLogTransform() { new Thread() { @Override public void run() { setMessage("Log transforming tree..."); RootedTree tree = getCurTree(); tree.logTransform(tree.getRoot(),1000); layout(); setMessage(""); } }.start(); } public void treeMutateOnce() { context.trees().mutateTree(); } public void treeMutateSlow() { context.trees().startMutatingTree(1000); } public void treeMutateFast() { context.trees().startMutatingTree(50); } public void treeStopMutating() { context.trees().stopMutatingTree(); } public void treeSaveConfigIntoTree() { Map<String,String> changedFields = PhyloConfig.getConfigSnapshot(context.config()); PhyloNode root = (PhyloNode) getCurTree().getRoot(); for (String key : changedFields.keySet()) { root.setAnnotation(key, changedFields.get(key)); } } public void nodeLoadImage() { new Thread() { public void run() { getCurNode().loadThumbImage(); } }.start(); } public void treeLoadImages() { new Thread() { public void run() { RootedTree tree = getCurTree(); ArrayList<PhyloNode> leaves = new ArrayList<PhyloNode>(); tree.getAll(tree.getRoot(), leaves, null); for (PhyloNode n : leaves) { n.loadThumbImage(); } } }.start(); } public void treeSave() { FileDialog fd = new FileDialog(context.ui().getFrame(), "Choose your desination file. Tree will be in Newick / NHX format.", FileDialog.SAVE); fd.pack(); fd.setVisible(true); String directory = fd.getDirectory(); String filename = fd.getFile(); if (filename == null) { context.getPW().setMessage("Tree save cancelled."); return; } final File f = new File(directory, filename); setMessage("Saving tree..."); new Thread() { public void run() { p.noLoop(); File dir = f.getParentFile(); TreeIO.outputTreeImages(context.trees().getTree(), dir); // Get the extension. String ext = FileUtils.getFileExtension(f); String s; RootedTree tree = context.trees().getTree(); if (ext.equals("nh")) { s = tree.getNewick(); } else if (ext.equals("xml")) { s = tree.getNeXML(); } else { s = tree.getNHX(); } // String s = TreeIO.createNHXString(context.trees().getTree()); try { f.createNewFile(); BufferedWriter r = new BufferedWriter(new FileWriter(f)); r.append(s); r.close(); p.loop(); setMessage(""); } catch (IOException e) { e.printStackTrace(); p.loop(); setMessage("Error writing file. Whoops!"); try { Thread.sleep(2000); } catch (InterruptedException e1) { e1.printStackTrace(); } setMessage(""); layout(); return; } } }.start(); } public void treeLoad() { FileDialog fd = new FileDialog(context.ui().getFrame(), "Locate a Newick/NHX/Nexus format file.", FileDialog.LOAD); fd.pack(); fd.setVisible(true); String directory = fd.getDirectory(); String filename = fd.getFile(); if (filename == null) { context.getPW().setMessage("Tree load cancelled."); return; } final File f = new File(directory, filename); setMessage("Loading tree..."); new Thread() { public void run() { PhyloTree t = (PhyloTree) TreeIO.parseFile(new PhyloTree(), f); p.noLoop(); if (t != null) { context.trees().setTree(t); setMessage(""); } else { setMessage("Error loading tree!"); } p.loop(); layout(); } }.start(); } public Frame getFrame() { Frame parentFrame = null; Component comp = p.getParent(); while (comp != null) { if (comp instanceof Frame) { parentFrame = (Frame) comp; break; } comp = comp.getParent(); } if (parentFrame == null) parentFrame = new Frame(); return parentFrame; } public PhyloNode getHoveredNode() { PhyloNode nearest = contextMenu.getNearestNode(); if (nearest != null) { // Test whether it's hovered or not. boolean contains = contextMenu.traverser.containsPoint(nearest.range, contextMenu.traverser.pt); if (contains) return nearest; } return null; } public void treeInput() { Frame parentFrame = getFrame(); final InputDialog d = new InputDialog(parentFrame, "Enter your Newick-formatted tree here."); SecurityChecker sc = new SecurityChecker(p); if (sc.canAccessInternet()) { Label l = new Label("A URL pointing to a Newick/NHX/Nexus file is also valid input."); d.add(l, BorderLayout.NORTH); } d.addWindowListener(new WindowAdapter() { @Override public void windowClosed(WindowEvent e) { super.windowClosed(e); final String treeString = d.text.getText(); if (treeString == null || treeString.length() == 0) return; new Thread() { public void run() { setMessage("Loading tree..."); PhyloTree t = null; try { t = (PhyloTree) TreeIO.parseNewickString(new PhyloTree(), treeString); p.noLoop(); } catch (Exception e) { t = null; } if (t != null) { context.trees().setTree(t); setMessage(""); } else { setMessage("Error loading tree!"); } p.loop(); layout(); } }.start(); } }); d.setVisible(true); } /* * File actions. */ public void fileOutput() { ImageExportDialog ied = new ImageExportDialog(getFrame()); } /* * Conditional calls. */ public boolean hasClipboard() { return !clipboard.isEmpty(); } public boolean isNotRoot() { if (getCurTree() == null || getCurNode() == null) return true; return (getCurTree().getRoot() != getCurNode()); } public boolean isLeafNode() { return getCurTree().isLeaf(getCurNode()); } public boolean isInternalNode() { if (getCurTree() == null || getCurNode() == null) return true; return !getCurTree().isLeaf(getCurNode()); } public boolean isRectangleRender() { if (context.trees() == null) return false; if (context.trees().getRenderer() == null) return false; BasicTreeRenderer rend = context.trees().getRenderer(); return rend.getLayout() instanceof LayoutCladogram; } public void destroy() { if (annotation != null) annotation.dispose(); annotation = null; disposeMenus(); p = null; focus = null; event = null; keys = null; clipboard = null; // nearest = null; // traverser = null; text = null; contextMenu = null; toolbar = null; search = null; thread = null; menus = null; } }