package ij.plugin; import java.awt.*; import java.awt.event.*; import java.awt.image.*; import java.io.*; import java.util.*; import java.net.*; import java.net.URL; import javax.swing.*; import javax.swing.tree.*; import javax.swing.event.*; import ij.*; import ij.gui.*; import ij.io.*; import ij.plugin.*; import ij.plugin.filter.*; import ij.plugin.frame.PlugInFrame; import ij.util.*; import ij.text.TextWindow; /**ControlPanel. * This plugin displays a panel with ImageJ commands in a hierarchical tree structure. * @author Cezar M. Tigaret <c.tigaret@ucl.ac.uk> */ public class ControlPanel implements PlugIn { /** The platform-specific file separator string.*/ private static final String fileSeparator=System.getProperty("file.separator"); /** The platform-specific file separator character. */ private static final char sep=fileSeparator.charAt(0); private Hashtable panels = new Hashtable(); private Vector visiblePanels = new Vector(); private Vector expandedNodes = new Vector(); private String defaultArg = ""; private boolean savePropsUponClose=true; private boolean propertiesChanged=true; private boolean closeChildPanelOnExpand = true; private boolean requireDoubleClick; private boolean quitting = true; Vector menus = new Vector(); Vector allMenus = new Vector(); Hashtable commands=new Hashtable(); Hashtable menuCommands=new Hashtable(); String[] pluginsArray; Hashtable treeCommands = new Hashtable(); int argLength; private String path=null; private DefaultMutableTreeNode root; MenuItem reloadMI = null; public ControlPanel() { //requireDoubleClick = !(IJ.isWindows() || IJ.isMacintosh()); Java2.setSystemLookAndFeel(); } /** Creates a panel with the hierarchical tree structure of ImageJ's commands. */ public void run(String arg) { load(); } /* *********************************************************************** */ /* Tree logic */ /* *********************************************************************** */ synchronized void load() { commands = Menus.getCommands(); pluginsArray = Menus.getPlugins(); root=doRootFromMenus(); if (root==null || root.getChildCount()==0 ) return; // do nothing if there's no tree or a root w/o children loadProperties(); restoreVisiblePanels(); if (panels.isEmpty()) newPanel(root); } /** Builds up a root tree from ImageJ's menu bar. * The root tree replicates ImageJ's menu bar with its menus and their submenus. * Delegates to the {@link recursesubMenu(Menu, DefaultMutableTreeNode} method to gather the root children. * */ private synchronized DefaultMutableTreeNode doRootFromMenus() { DefaultMutableTreeNode node = new DefaultMutableTreeNode("ImageJ Menus"); if(argLength==0) node.setUserObject("Control Panel"); MenuBar menuBar = Menus.getMenuBar(); for (int i=0; i<menuBar.getMenuCount(); i++) { Menu menu = menuBar.getMenu(i); DefaultMutableTreeNode menuNode = new DefaultMutableTreeNode(menu.getLabel()); recurseSubMenu(menu, menuNode); node.add(menuNode); } return node; } /**Recursively builds up a tree structure from the Menu argument, by populating the TreeNode argument * with children TreeNode objects constructed on the menu items. * Descendants can be intermediate-level nodes (submenus) or leaf nodes (i.e., no children). * Leaf nodes will only be added if there are any commands associated with them, i.e. * their labels correspond to keys in the hashtable returned by <code>ij.Menus.getCommands()</code> * except for the "Reload Plugins" menu item, for which a local action command string is assigned * to avoid clashes with the action fired from ImageJ Plugins->Utilties->Reload Plugins * menu item.<br> * <strong>Note: </strong> this method bypasses the tree buildup based on the * {@link populateNode(Hashtable,DefaultMutableTreeNode)} method. * @param menu The Menu instance to be searched recursively for menu items * @param node The DefaultMutableTreeNode corresponding to the <code>Menu menu</code> argument. */ private void recurseSubMenu(Menu menu, DefaultMutableTreeNode node) { int items = menu.getItemCount(); if(items==0) return; for (int i=0; i<items; i++) { MenuItem mItem = menu.getItem(i); String label = mItem.getLabel(); if (mItem instanceof Menu) { DefaultMutableTreeNode subNode = new DefaultMutableTreeNode(label); recurseSubMenu((Menu)mItem,subNode); node.add(subNode); } else if(mItem instanceof MenuItem) { if (!(label.equals("-"))) { DefaultMutableTreeNode leaf = new DefaultMutableTreeNode(label); node.add(leaf); if(treeCommands==null) treeCommands = new Hashtable(); if (label.equals("Reload Plugins")) { reloadMI = mItem; treeCommands.put(label,"Reload Plugins From Panel"); } } } } } /**Populates the <code>node</code> argument with items retrieved from the collection. * Actually, the method delegates to {@link populateNode{String[], String[], DefaultMutableTreeNode)}. * @param collection Hashtable with `keys' (java.lang.String) representing a tree path which * is to be added to this node, but excludes it; the path extends to the last child (leaf node). * The `values' are (java.lang.String) as labels and user objects for the last child of the path. * Actually, <code><strong>null<strong></code> elements for labels are allowed, in which case * the last child will be constructed on the last token in the `key'. * @param node The TreeNode to be populated. */ private void populateNode(Hashtable collection, DefaultMutableTreeNode node) { Vector labelVector = new Vector(); for (Enumeration e=collection.keys(); e.hasMoreElements();) { String key = (String)e.nextElement(); labelVector.addElement(key); } String[] labels = new String[labelVector.size()]; String[] items = new String[labelVector.size()]; labelVector.copyInto((String[])labels); // keys into labels[] StringSorter.sort(labels); for(int i=0; i<labels.length; i++) { items[i] = (String)collection.get(labels[i]); //values into items[] } populateNode(items,labels,node); } /**Populates the <code>node</code> argument with items retrieved from the two String[] arguments. * Delegates indirectly to {@link buildTreePath(String.String,String,DefaultMutableTreeNode)} to do the job. * If either arguments are empty (i.e., length=0) or have different sizes, the method does nothing. * @param items String array where each element is the source of a tree path (see {@see buildTreePath(String, String, DefaultMutableTreeNode)} * and {@see buildTreePath(String,String,String,DefaultMutableTreeNode)}. * @param labels String array where each element is the label of the root of the tree path * @param node The TreeNode to be populated */ private void populateNode(String[] items, String[] labels, DefaultMutableTreeNode node) { if (items.length==0 || items.length!=labels.length) return; String label=null; for (int i=0; i<items.length; i++) { if(labels!=null && i<labels.length) label = labels[i]; buildTreePath(items[i], label, node); } } /**Short-hand for the four-argument <code>buildTreePath</code> method. * Calls <code>buildTreePath(source,label,<strong>null</strong>,topNode)</code>. * @see buildTreePath(String,String,String,DefaultMutableTreeNode). * */ private void buildTreePath(String source, String label, DefaultMutableTreeNode topNode) { buildTreePath(source, label, null, topNode); } /**Builds up a tree path structure. * Populates the <code>node</code> argument with a tree path constructed as described below: * @param source String to be parsed in a tree path; must be composed of tokens delimited by "/" * @param label The label (String) of the leaf node for this path. If <code><strong>null</strong></code> * then the leafe nod will be constructed from the last token. * @param command The command string of the action event fired upon clicking the leaf node. * If <code><strong>null</strong></code>, then the last token will be taken as action command. * @param topNode The DefaulMutableTreeNode to which this path will be added */ private void buildTreePath(String source, String label, String command, DefaultMutableTreeNode topNode) { String local=source; // will contain the string to be parsed into the tree path String argument=""; // will store any argument for the plugin String delimiter = fileSeparator; // meaning `/' // 1. store plugin arguments (the string between parentheses) in `argument' // then store the rest into `local' // if there aren't any plugin arguments, then local remains the same as `source' int leftParen=source.indexOf('('); int rightParen = source.indexOf(')'); if (leftParen>-1 && rightParen>leftParen) { argument = source.substring(leftParen+1, rightParen); local = source.substring(0,leftParen); } // 2. maybe `local' was passed in from some plugin class finder, and is prefixed by // full path of the plugins directory; if so, then remove this prefix String pluginsPath=Menus.getPlugInsPath(); if (local.startsWith(pluginsPath)) local = local.substring(pluginsPath.length(),local.length()); // 3. convert package/class separators into file separators, // to allow parsing into tree path later local=local.replace('.',delimiter.charAt(0)); // 4. append the plugin arguments, but with file separator so that to the same // plugin with different arguments will show up as children of the same tree branch if (argument.length()>0) local=local.concat(fileSeparator).concat(argument); DefaultMutableTreeNode node=null; // 5. parse the tree path: this code is entirely different from the logic in TreePanel, // so don't hold your breath: // split the string into tokens delimited by file separator // and iterate through the tokens adding an intermediate subnode for each token // // use the name of the token for intermediate nodes, and the `label' argument for // the leaf node if not null; else use the last token for the leaf node // // for leaf node replace `_' with ` ' and trim away the `.class' extension StringTokenizer pathParser = new StringTokenizer(local,delimiter); int tokens = pathParser.countTokens(); while(pathParser.hasMoreTokens()) { String token = pathParser.nextToken(); tokens--; if (topNode.isLeaf()&&topNode.getAllowsChildren()) { if(token.indexOf("ControlPanel")==-1) {// avoid showing this up in the tree // when at leaf level the user object for the node is the `label' if not null, // else is the `token' if(tokens==0) { if(label!=null) token=label; // if label is not null use it instead of token token=token.replace('_',' '); if(token.endsWith(".class")) token = token.substring(0,token.length()-6);//... } node = new DefaultMutableTreeNode(token); // finally, construct the node // when at leaf level, store the `command' (or the `token' if `command' is null) // into our internal table if (tokens==0) { String cmd = (command==null) ? token : command; if(treeCommands==null) treeCommands = new Hashtable(); if(!treeCommands.containsKey(token)) treeCommands.put(token,cmd); } // add this node to the top node, then make it top node and continue iteration topNode.add(node); topNode=node; } continue; } else { // this node may have been visited before, so we avoid duplicate nodes // by recursing through the tree until we find a token that has not been // "made" into a node boolean hasTokenAsNode=false; Enumeration nodes = topNode.children(); while(nodes.hasMoreElements()) { node = (DefaultMutableTreeNode)nodes.nextElement(); if(((String)node.getUserObject()).equals(token)) { hasTokenAsNode = true; topNode = node; break; } } if (!hasTokenAsNode) { if (token.indexOf("ControlPanel")==-1) { if (tokens==0) {// we're at leaf level if(label!=null) token = label; token=token.replace('_',' '); if (token.endsWith(".class")) token=token.substring(0,token.length()-6); // ... } node = new DefaultMutableTreeNode(token); topNode.add(node); topNode=node; } } } } } /* *********************************************************************** */ /* Factories */ /* *********************************************************************** */ /**Constructs a TreePanel rooted at the <code>node</code> argument. * */ TreePanel newPanel(DefaultMutableTreeNode node) { boolean main = node.getUserObject().equals(root.getUserObject()); TreePanel panel = new TreePanel(node, this, main); return panel; } TreePanel newPanel(DefaultMutableTreeNode node, Point location) { boolean main = node.getUserObject().equals(root.getUserObject()); TreePanel panel = new TreePanel(node, this, main, location); return panel; } /**Constructs a TreePanel rooted at the path. * * @param s A string with the structure "[item1,item2,...,itemn]", as returned by * a call to the <code>toString()</code> method in the <code>javax.swing.tree.TreePath</code> class. * */ TreePanel newPanel(String path) { if (path.equals("Control_Panel.@Main")) path = "Control_Panel"; path = key2pStr(path); TreePanel pnl = null; for(Enumeration e = root.breadthFirstEnumeration(); e.hasMoreElements();) { DefaultMutableTreeNode n = (DefaultMutableTreeNode)e.nextElement(); TreePath p = new TreePath(n.getPath()); if (p.toString().equals(path)) pnl=newPanel(n); } return pnl; } /* *************************************************************************** */ /* Various Accessors */ /* *************************************************************************** */ boolean requiresDoubleClick() {return requireDoubleClick;} void setDoubleClick(boolean dc) {requireDoubleClick = dc;} boolean hasPanelForNode(DefaultMutableTreeNode node) { TreePath path = new TreePath(node.getPath()); return panels.containsKey(pStr2Key(path.toString())); } TreePanel getPanelForNode(DefaultMutableTreeNode node) { TreePath path = new TreePath(node.getPath()); String pathString = path.toString(); if (panels.containsKey(pStr2Key(pathString))) return (TreePanel)panels.get(pStr2Key(pathString)); //else return newPanel(node); else return null; } public DefaultMutableTreeNode getRoot() {return root;} Hashtable getPanels() {return panels;} Hashtable getTreeCommands() { return treeCommands; } boolean hasVisiblePanels() { return visiblePanels.size()>0; } int getVisiblePanelsCount() { return visiblePanels.size(); } /* ************************************************************************** */ /* Properties and panels management */ /* ************************************************************************** */ void registerPanel(TreePanel panel) { String key = pStr2Key(panel.getRootPath().toString()); panels.put(key,panel); setPanelShowingProperty(panel.getRootPath().toString()); propertiesChanged=true; } /** All properties related to the ControlPanel have keywords starting with "Control_Panel". * This is to facilitate the integration of these properties with ImageJ's properties database. * The keywords are dot-separated lists of tokens; in each token, spaces are relaced by * underscores. Each token represents a node, and hence the keyword represents a tree path. * The values can be either: * <ol> * <li> a space-separated list of integers, * indicating panel frame geometry <strong>in the following fixed order:</strong> * x coordinate, y coordinate , width, height</li> * <li> or the word "expand" which indicates that the path represented by the keyword is * an expanded branch * </li> * </ol> * */ void loadProperties() { if (IJ.debugMode) IJ.log("CP.loadProperties"); visiblePanels.removeAllElements(); expandedNodes.removeAllElements(); panels.clear(); Properties properties = Prefs.getControlPanelProperties(); for (Enumeration e=properties.keys(); e.hasMoreElements();) { String key = (String)e.nextElement(); if (key.startsWith(".Control_Panel.")) { key = key.substring(1, key.length()); String val = Prefs.get(key, null); if (IJ.debugMode) IJ.log(" "+key+": "+val); if (Character.isDigit(val.charAt(0))) // value starts with digit visiblePanels.addElement(key); else if (val.equals("expand")) expandedNodes.addElement(key); } } } void saveProperties() { if (IJ.debugMode) IJ.log("CP.saveProperties: "+propertiesChanged); if (propertiesChanged) { clearProperties(); for (Enumeration e=visiblePanels.elements(); e.hasMoreElements();) { String s = (String)e.nextElement(); TreePanel p = (TreePanel)panels.get(s); if (p!=null) recordGeometry(p); } for(Enumeration e=expandedNodes.elements(); e.hasMoreElements();) Prefs.set((String)e.nextElement(),"expand"); } propertiesChanged=false; } void clearProperties() { Properties properties = Prefs.getControlPanelProperties(); for (Enumeration e=properties.keys(); e.hasMoreElements();) { String key = (String)e.nextElement(); if (key.startsWith(".Control_Panel.")) properties.remove(key); } } void setExpandedStateProperty(String item) { String s = pStr2Key(item); expandedNodes.addElement(s); propertiesChanged=true; } boolean hasExpandedStateProperty(String item) { String s = pStr2Key(item); return expandedNodes.contains(s); } void unsetExpandedStateProperty(String item) { String s = pStr2Key(item); expandedNodes.remove(s); propertiesChanged=true; } void setPanelShowingProperty(String item) { String s = pStr2Key(item); if (!(visiblePanels.contains(s))) visiblePanels.addElement(s); propertiesChanged=true; } void unsetPanelShowingProperty(String item) { String s = pStr2Key(item); if (visiblePanels.remove(s)) { //IJ.write("removed from showing "+item); } //propertiesChanged=true; } boolean hasPanelShowingProperty(String item) { String s = pStr2Key(item); return visiblePanels.contains(s); } void restoreVisiblePanels() { String[] visPanls = new String[visiblePanels.size()]; visiblePanels.toArray(visPanls); Arrays.sort(visPanls); for (int i=0; i<visPanls.length; i++) { if (!panels.containsKey(visPanls[i])) { TreePanel p = newPanel(visPanls[i]); } } } void recordGeometry(TreePanel panel) { String pTitle = panel.getRootPath().toString(); pTitle = pStr2Key(pTitle); JFrame frame = panel.getFrame(); if (frame!=null) { Rectangle rect=frame.getBounds(); String xCoord = (new Integer(rect.x)).toString(); String yCoord = (new Integer(rect.y)).toString(); String width = (new Integer(rect.width)).toString(); String height = (new Integer(rect.height)).toString(); if (pTitle.equals("Control_Panel")) pTitle = "Control_Panel.@Main"; String geometry = xCoord+" "+yCoord+" "+width+" "+height; if (IJ.debugMode) IJ.log("CP.recordGeometry: "+pTitle+" "+geometry); Prefs.set(pTitle, geometry); } } void restoreGeometry(TreePanel panel) { String pTitle = panel.getRootPath().toString(); pTitle = pStr2Key(pTitle); if (pTitle.equals("Control_Panel")) pTitle = "Control_Panel.@Main"; if (IJ.debugMode) IJ.log("CP.restoreGeometry: "+pTitle); String geom = Prefs.get(pTitle, null); if (geom!=null) { int[] coords = s2ints(geom); if (coords!=null && coords.length==4) panel.setBounds(coords[0],coords[1],coords[2],coords[3]); else { Point pnt = panel.getDefaultLocation(); if (pnt!=null) panel.getFrame().setLocation((int)pnt.getX(),(int)pnt.getY()); } } } void closeAll(boolean die) { quitting = die; if (!visiblePanels.isEmpty()) { propertiesChanged = true; saveProperties(); } for (Enumeration e = panels.elements(); e.hasMoreElements();) { TreePanel p = (TreePanel)e.nextElement(); p.close(); } //if(quitting) panels.clear(); quitting = true; } /* **************************************************************************** */ /* Helper methods */ /* **************************************************************************** */ // /**Removes the leading "[" and trailing "]" from the argument // * @param s A string with the structure "[item1,item2,...,itemn]", as returned by // * a call to the <code>toString()</code> method in the <code>javax.swing.tree.TreePath</code> class. // * @return A string with the structure "item1,item2,...,itemn". // * @see javax.swing.tree.TreePath // */ // String trimPathString(String s) // { // int leftBracket = s.indexOf("["); // int rightBracket = s.indexOf("]"); // return s.substring(leftBracket+1,rightBracket); // } void showHelp() { IJ.showMessage("About Control Panel...", "This plugin displays a panel with ImageJ commands in a hierarchical tree structure.\n"+" \n"+ "Usage:\n"+" \n"+ " Click on a leaf node to launch the corresponding ImageJ command (or plugin)\n"+ " (double-click on X Window Systems)\n"+" \n"+ " Double-click on a tree branch node (folder) to expand or collapse it\n"+" \n"+ " Click and drag on a tree branch node (folder) to display its descendants,\n"+ " in a separate (child) panel (\"tear-off\" mock-up)\n"+" \n"+ " In a child panel, use the \"Show Parent\" menu item to re-open the parent panel\n"+ " if it was accidentally closed\n"+" \n"+ "Author: Cezar M. Tigaret (c.tigaret@ucl.ac.uk)\n"+ "This code is in the public domain." ); } // 1. trim away the eclosing brackets // 2. replace comma-space with dots // 3. replace spaces with underscores String pStr2Key(String pathString) { String keyword = pathString; if(keyword.startsWith("[")) keyword = keyword.substring(keyword.indexOf("[")+1,keyword.length()); if(keyword.endsWith("]")) keyword = keyword.substring(0,keyword.lastIndexOf("]")); StringTokenizer st = new StringTokenizer(keyword,","); String result = ""; while(st.hasMoreTokens()) { String token = st.nextToken(); if(token.startsWith(" ")) token = token.substring(1,token.length()); // remove leading space result+=token+"."; } result = result.substring(0,result.length()-1);//remove trailing dot result = result.replace(' ','_'); return result; } String key2pStr(String keyword) { //keyword = keyword.replace('_',' '); // restore the spaces from underscores StringTokenizer st = new StringTokenizer(keyword,"."); String result = ""; while(st.hasMoreTokens()) { String token = st.nextToken(); result += token +", "; } result = result.substring(0,result.length()-2); // trim away the ending comma-space result = "["+result+"]"; result = result.replace('_', ' '); return result; } // Thank you, Wayne! /** Breaks the specified string into an array of ints. Returns null if there is an error.*/ public int[] s2ints(String s) { StringTokenizer st = new StringTokenizer(s, ", \t"); int nInts = st.countTokens(); if (nInts==0) return null; int[] ints = new int[nInts]; for(int i=0; i<nInts; i++) { try {ints[i] = Integer.parseInt(st.nextToken());} catch (NumberFormatException e) {return null;} } return ints; } //boolean isRunning(){return running;} } // ControlPanel /**TreePanel. * * * <br> * This class lays out the ImageJ user plugins in a vertical, hierarchical tree.. Plugins are launched by double-clicking on their names, in the vertical tree. * * Advantages: uses less screen estate, and provides a realistic graphical presentation of the plugins installed in the file system.<br> * * Created: Thu Nov 23 02:12:12 2000 * @see ControlPanel * @author Cezar M. Tigaret <c.tigaret@ucl.ac.uk> * @version 1.0f */ class TreePanel implements ActionListener, WindowListener, TreeExpansionListener, TreeWillExpandListener { ControlPanel pcp; //Vector childrenPanels = new Vector(); boolean isMainPanel; String title; boolean isDragging=false; //boolean requireDoubleClick=false; Point defaultLocation; private JTree pTree; private JMenuBar pMenuBar; private DefaultMutableTreeNode root; private DefaultMutableTreeNode draggingNode=null; private DefaultTreeModel pTreeModel; private ActionListener listener; private JFrame pFrame; private JCheckBoxMenuItem pMenu_saveOnClose, pMenu_noClutter; private TreePath rootPath; // the "up" arrow private static final int _uparrow1_data[] = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x0d,0x0e,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x0d,0x01,0x01,0x0d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x01,0x0e,0x02,0x01,0x03,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x01,0x0e,0x04,0x05,0x06,0x01,0x07,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x08,0x04,0x09,0x0e,0x02,0x06,0x01, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x08,0x04,0x09,0x0e,0x0e,0x0e, 0x02,0x06,0x01,0x00,0x00,0x00,0x00,0x00,0x08,0x08,0x04,0x09,0x0e,0x0e, 0x0e,0x0e,0x0e,0x02,0x06,0x02,0x00,0x00,0x00,0x08,0x0a,0x0e,0x08,0x0a, 0x0b,0x0b,0x0c,0x0c,0x0c,0x0c,0x0c,0x06,0x02,0x00,0x0e,0x01,0x01,0x01, 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x0e,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00 }; private static final int _uparrow1_ctable[] = { 0x21,0xff000000,0xff303030,0xffaaaaaa,0xffffffff,0xff3c3c3c,0xff252525,0xffb6b6b6,0xff585858,0xffc3c3c3,0xff222222,0xff2b2b2b,0xff2e2e2e,0xffa0a0a0, 0xff808080 }; private static IndexColorModel iconCM = new IndexColorModel(8,_uparrow1_ctable.length,_uparrow1_ctable,0,true,255,DataBuffer.TYPE_BYTE); private static final ImageIcon upIcon = new ImageIcon( Toolkit.getDefaultToolkit().createImage(new MemoryImageSource(16,16,iconCM,_uparrow1_data,0,16))); public TreePanel (DefaultMutableTreeNode root, ControlPanel pcp, boolean isMainPanel) { new TreePanel(root,pcp,isMainPanel,null); } public TreePanel(DefaultMutableTreeNode root, ControlPanel pcp, boolean isMainPanel, Point location) { this.root=root; this.pcp=pcp; this.isMainPanel = isMainPanel; defaultLocation = location; rootPath=new TreePath(root.getPath()); title = (String)root.getUserObject(); buildTreePanel(); pcp.registerPanel(this); } /* ************************************************************************** */ /* GUI factories */ /* ************************************************************************** */ public void buildTreePanel() { pFrame=new JFrame(title); pFrame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); pTreeModel = new DefaultTreeModel(root); pTree=new JTree(pTreeModel); pTree.setEditable(false); pTree.putClientProperty("JTree.lineStyle","Angled"); pTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); pTree.setRootVisible(false); pTree.setShowsRootHandles(true); //pTree.setDragEnabled(true); JScrollPane ptView=new JScrollPane(pTree); addMenu(); pFrame.getContentPane().add(ptView, BorderLayout.CENTER); addListeners(); pFrame.pack(); if (defaultLocation!=null) { if (IJ.debugMode) IJ.log("CP.buildTreePanel: "+defaultLocation); pFrame.setLocation(defaultLocation.x, defaultLocation.y); } else pcp.restoreGeometry(this); //restoreExpandedNodes(); if (pFrame.getLocation().x==0) GUI.center(pFrame); setVisible(); ImageJ ij = IJ.getInstance(); ij.addWindowListener(this); pFrame.addKeyListener(ij); pTree.addKeyListener(ij); } void addMenu() { pMenuBar=new JMenuBar(); Insets ins = new Insets(0,0,0,10); pMenuBar.setMargin(ins); if (isMainPanel) { JMenuItem helpMI = new JMenuItem("Help"); helpMI.addActionListener(this); helpMI.setActionCommand("Help"); pMenuBar.add(helpMI); /* if(pcp.reloadMI!=null) { pcp.reloadMI.addActionListener(this); pMenuBar.add(pcp.reloadMI); }*/ } else { JMenuItem spMI = new JMenuItem("Show Parent",upIcon); spMI.addActionListener(this); spMI.setActionCommand("Show Parent"); pMenuBar.add(spMI); } pFrame.setJMenuBar(pMenuBar); } void addListeners() { addActionListener(this); pFrame.addWindowListener(this); pFrame.addComponentListener(new ComponentAdapter() { public void componentMoved(ComponentEvent e) { Rectangle r = e.getComponent().getBounds(); if (IJ.debugMode) IJ.log("CP.componentMoved: "+r); if (r.x>0) { defaultLocation = new Point(r.x, r.y); recordGeometry(); } } }); pTree.addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { isDragging = false; if(pcp.requiresDoubleClick() && e.getClickCount()!=2) return; int selRow=pTree.getRowForLocation(e.getX(),e.getY()); if (selRow!=-1) toAction(); } public void mouseReleased(MouseEvent e) { if (isDragging) { Point pnt = new Point(e.getX(), e.getY()); SwingUtilities.convertPointToScreen(pnt,pTree); tearOff(null,pnt); } isDragging = false; } }); pTree.addMouseMotionListener(new MouseMotionAdapter() { public void mouseDragged(MouseEvent e) { int selRow = pTree.getRowForLocation(e.getX(), e.getY()); if(selRow!=-1) { if(((DefaultMutableTreeNode)pTree.getLastSelectedPathComponent()).isLeaf()) return; pFrame.setCursor(new Cursor(Cursor.MOVE_CURSOR)); isDragging = true; } } }); pTree.addTreeExpansionListener(this); pTree.addTreeWillExpandListener(this); } /* ************************************************************************** */ /* Accessors -- see also Properties management section */ /* ************************************************************************** */ public String getTitle() {return title;} public TreePath getRootPath() {return rootPath;} public boolean isTheMainPanel() {return isMainPanel;} public JFrame getFrame() {return pFrame;} public JTree getTree() {return pTree;} public DefaultMutableTreeNode getRootNode() {return root;} public Point getDefaultLocation() {return defaultLocation;} /* ************************************************************************** */ /* Properties managmenent */ /* ************************************************************************** */ boolean isVisible() { return pFrame.isVisible(); } void setBounds(int x, int y, int w, int h) { pFrame.setBounds(new Rectangle(x,y,w,h)); defaultLocation = new Point(x,y); } void setAutoSaveProps(boolean autoSave) { if(isTheMainPanel()) pMenu_saveOnClose.setSelected(autoSave); } boolean getAutoSaveProps() {return pMenu_saveOnClose.isSelected();} void restoreExpandedNodes() { if (pTree==null || root==null) return; pTree.removeTreeExpansionListener(this); TreeNode[] rootPath = root.getPath(); for(Enumeration e = root.breadthFirstEnumeration(); e.hasMoreElements();) //for(Enumeration e = root.children(); e.hasMoreElements();) { DefaultMutableTreeNode node = (DefaultMutableTreeNode)e.nextElement(); if(!node.isLeaf() && node != root) { TreeNode[] nodePath = node.getPath(); TreePath nTreePath = new TreePath(nodePath); String npS = nTreePath.toString(); /* if(pcp.hasPanelShowingProperty(npS)) { IJ.write("has panel showing: "+npS); }*/ DefaultMutableTreeNode[] localPath = new DefaultMutableTreeNode[nodePath.length-rootPath.length+1]; for(int i=0; i<localPath.length; i++) { localPath[i]=(DefaultMutableTreeNode)nodePath[i+rootPath.length-1]; } TreePath newPath = new TreePath(localPath); if(pcp.hasExpandedStateProperty(npS) && !pcp.hasPanelShowingProperty(npS)) { if(newPath!=null) { try { pTree.expandPath(newPath); }catch(Throwable t){} } } else if((pcp.hasExpandedStateProperty(npS) || pTree.isExpanded(newPath)) && pcp.hasPanelShowingProperty(npS)) { pTree.collapsePath(newPath); pcp.unsetExpandedStateProperty(npS); } } } pTree.addTreeExpansionListener(this); } /* ************************************************************************** */ /* AWT and Swing events manangement */ /* ************************************************************************** */ public void processEvent(ActionEvent e) { if (listener != null) listener.actionPerformed(e); } public void addActionListener(ActionListener al) { listener=AWTEventMulticaster.add(listener, al); } public void removeActionListener(ActionListener al) { listener=AWTEventMulticaster.remove(listener, al); } public void actionPerformed(ActionEvent e) { String cmd=e.getActionCommand(); //IJ.write(cmd); if(cmd==null) return; if (cmd.equals("Help")) { showHelp(); return; } if(cmd.equals("Show Parent")) { DefaultMutableTreeNode parent = (DefaultMutableTreeNode)root.getParent(); if(parent!=null) { //IJ.write("show parent"); TreePanel panel = pcp.getPanelForNode(parent); if(panel==null) panel = pcp.newPanel(parent); if(panel!=null) panel.setVisible(); } return; } if(cmd.equals("Reload Plugins From Panel")) {// cmd fired by clicking on tree leaf pcp.closeAll(false); IJ.doCommand("Reload Plugins"); } else { if(cmd.equals("Reload Plugins")) // cmd fired from ImageJ menu; don't propagate it further pcp.closeAll(false); else IJ.doCommand(cmd); return; } } // we implement window listener directly so that we can catch ImageJ's window events /** Upon window closing, the panel removes itself from the vector of visible panels (member of pcp). * Then, if this is the only visible panel left, the properties are saved; else, if this is the * main panel, all other visible panels are also closed and properties * are saved */ public void windowClosing(WindowEvent e) { if (IJ.debugMode) IJ.log("CP.windowClosing: "+isMainPanel); if (isMainPanel) pcp.saveProperties(); pcp.unsetPanelShowingProperty(getRootPath().toString()); } public void windowActivated(WindowEvent e) { WindowManager.setWindow(getFrame()); } public void windowClosed(WindowEvent e) {} public void windowDeactivated(WindowEvent e) {} public void windowDeiconified(WindowEvent e) {} public void windowIconified(WindowEvent e) {} public void windowOpened(WindowEvent e) {} public void treeCollapsed (TreeExpansionEvent ev) { String evPathString = ev.getPath().toString(); evPathString = evPathString.substring(evPathString.indexOf("[")+1,evPathString.lastIndexOf("]")); evPathString = evPathString.substring(getTitle().length()+2,evPathString.length()); String rootPath = getRootPath().toString(); rootPath = rootPath.substring(rootPath.indexOf("[")+1,rootPath.lastIndexOf("]")); String path = "["+rootPath +", "+evPathString+"]"; //IJ.write("collapse"); pcp.unsetExpandedStateProperty(path); } public void treeExpanded(TreeExpansionEvent ev) { TreePath evPath = ev.getPath(); //DefaultMutableTreeNode node = (DefaultMutableTreeNode)evPath.getLastPathComponent(); String evPathString = ev.getPath().toString(); evPathString = pcp.pStr2Key(evPathString); evPathString = evPathString.substring(getTitle().length()+1,evPathString.length()); String rootPath = getRootPath().toString(); rootPath = pcp.pStr2Key(rootPath); //String path = rootPath+"."+evPathString; String path = rootPath+"."+evPathString; if (pcp.hasPanelShowingProperty(path)) { Hashtable panels = pcp.getPanels(); TreePanel p = (TreePanel)panels.get(path); if(p!=null) p.close(); } //IJ.write("expansion"); pcp.setExpandedStateProperty(path); } //stub for future development public void treeWillExpand(TreeExpansionEvent ev) {} //stub for future development public void treeWillCollapse(TreeExpansionEvent ev) {} void recordGeometry() {pcp.recordGeometry(this);} /* ************************************************************************** */ /* Actions */ /* ************************************************************************** */ void refreshTree() { pTreeModel.reload(); } void tearOff() { tearOff(null); } void tearOff(DefaultMutableTreeNode node) { tearOff(node, null); } void tearOff(DefaultMutableTreeNode node, Point pnt) { isDragging = false; pFrame.setCursor(Cursor.getDefaultCursor()); if(node==null) node = (DefaultMutableTreeNode)pTree.getLastSelectedPathComponent(); if(node.isLeaf()) return; TreeNode[] nPath = node.getPath(); TreeNode[] rPath = root.getPath(); DefaultMutableTreeNode[] tPath = new DefaultMutableTreeNode[nPath.length-rPath.length+1]; for(int i=0; i<tPath.length; i++) tPath[i] = (DefaultMutableTreeNode)nPath[i+rPath.length-1]; TreePath path = new TreePath(nPath); TreePath localPath = new TreePath(tPath); String pathString = localPath.toString(); //IJ.write("to be collapsed "+pathString); TreePanel p = pcp.getPanelForNode(node); if (p==null) { if(pnt!=null) p = pcp.newPanel(node, pnt); else p = pcp.newPanel(node); pTree.collapsePath(localPath); } else { if (pnt!=null) p.setLocation(pnt); p.setVisible(); pTree.collapsePath(localPath); } } /** Fires an ActionEvent upon double-click on the plugin item (leaf node) in the JTree */ void toAction() { DefaultMutableTreeNode nde=(DefaultMutableTreeNode)pTree.getLastSelectedPathComponent(); // if the node has children then do nothing (return) if (nde.getChildCount()>0) return; String aCmd=nde.toString(); String cmd= aCmd; if(pcp.treeCommands.containsKey(aCmd)) cmd = (String)pcp.treeCommands.get(aCmd); processEvent(new ActionEvent(this,ActionEvent.ACTION_PERFORMED,cmd)); } void setVisible() { //IJ.write("setVisible at "+defaultLocation.getX()+" "+defaultLocation.getY()); if (pFrame!=null && !pFrame.isVisible()) { restoreExpandedNodes(); if (defaultLocation!=null) pFrame.setLocation(defaultLocation); pFrame.setVisible(true); // close expanded path to this panel in its parent panel (if visible and if the path is expanded) DefaultMutableTreeNode parent = (DefaultMutableTreeNode)root.getParent(); if (parent!=null) { TreePanel pnl = pcp.getPanelForNode(parent); if (pnl!=null && pnl.isVisible()) { TreeNode[] rPath = root.getPath(); TreeNode[] pPath = pnl.getRootNode().getPath(); DefaultMutableTreeNode[] tPath = new DefaultMutableTreeNode[rPath.length-pPath.length+1]; for(int i=0; i<tPath.length; i++) tPath[i] = (DefaultMutableTreeNode)rPath[i+pPath.length-1]; //TreePath path = new TreePath(rPath); TreePath localPath = new TreePath(tPath); //IJ.write("root path="+new TreePath(rPath).toString()+"; parent path="+new TreePath(pPath).toString()+"; local="+localPath.toString()); //IJ.write("to be collapsed "+localPath.toString()); pnl.getTree().collapsePath(localPath); } } } if (pcp!=null) pcp.setPanelShowingProperty(getRootPath().toString()); } void setLocation(Point p) { if (p!=null) defaultLocation = p; } void close() { pFrame.dispatchEvent(new WindowEvent(pFrame,WindowEvent.WINDOW_CLOSING)); pcp.unsetPanelShowingProperty(getRootPath().toString()); } private void showHelp() {pcp.showHelp();} } // TreePanel