| ControlPanel |

    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 javax.swing.plaf.*;
    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.
     *
     *
     *
     * <br>Created: Tue Dec  5 00:52:15 2000; Modified 05/03/2004
     * <p>
     * </p>
     * @author Cezar M. Tigaret <c.tigaret@ucl.ac.uk>
     * @version 1.0g
     */
    public class ControlPanel implements PlugIn
    {

        private static final String pluginsPath=Menus.getPlugInsPath();

        private static final String pcpVersion="1.0g";

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

        /** The JVM version we're using */
        private static final String jvmversion=System.getProperty("java.version");

        /** The "major" part of the JVM version string. */
        private static final String jvmversionMajor=jvmversion.substring(0,jvmversion.lastIndexOf('.'));

        /** The instance of ImageJ application we're running */
        private static ImageJ ij=IJ.getInstance();

        private Hashtable panels = new Hashtable();
        private Vector visiblePanels = new Vector();
        private Vector expandedNodes = new Vector();
        private String defaultArg = "";
        String currentArg = "";

        private Properties pcpProperties=new Properties();
        private File pcpPropsFile=new File(System.getProperty("user.home")+System.getProperty("file.separator")+"PCPanel2.ini");
        private boolean savePropsUponClose=true;
        private boolean propertiesChanged=true;
        private boolean closeChildPanelOnExpand = true;
        private boolean requireDoubleClick=false;
        private boolean quitting = true;

        //private static boolean running = true;

        Vector menus = new Vector();
        Vector allMenus = new Vector();
        String[] installableMenuLabels = {"About Plugins","Filters","Import","Plugins","Save As","Shortcuts","Tools","Utilities"};
        Hashtable commands=new Hashtable();
        Hashtable menuCommands=new Hashtable();
        String[] pluginsArray;
        Hashtable treeCommands = new Hashtable();
        int argLength=0;

        private String path=null;
        private DefaultMutableTreeNode root;

        MenuItem reloadMI = null;

        //private static String pcpDir=null;

        public ControlPanel()
        {
            if (!IJ.isJava2()) { //wsr
                IJ.error("This command requires Java 1.2 or later");
                return;
            }
            requireDoubleClick = !(IJ.isWindows() || IJ.isMacintosh());
            Java2.setSystemLookAndFeel();
        }

        /** Creates a panel with the hierarchical tree structure of (some of the) ImageJ's commands according to the structure
         * of the String argument (see below).
         *
         * @param arg String (optional): a semi-colon - separated list of one or more tokens:<br>
         * <dl>
         * <dt>"imagej menus"</dt><dd>creates a tree with all of ImageJ's menu structure, that means it replicates in tree
         * form the ImageJ's menu bar; this includes any jar plugin and user plugins</dd>
         * <dt>"user plugins"</dt><dd>creates a tree with loose user plugins (not "jar plugins")</dd>
         * <dt>"imagej commands"</dt><dd>creates a tree with all ImageJ's commands</dd>
         * <dt>"about"</dt><dd>will not create a tree panel; instead, it will show a brief help message</dd>
         * </dl>
         * If there is more than one token, a subtree will be created for each token, and added to a common root tree.
         * If the "about" token is also present, a help essage will be displayed<br>
         * If the argument is missing, a panel with all of ImageJ's menus will be created as if "imagej menus" was passed as argument.<br>
         * Please note that when no arguments are passed, ImageJ's menus will be shown as a unique tree named "Control Panel"; if "imagej menus"<br>
         * is part of a multi-token argument, them ImageJ menus will be created as a sub tree called "ImageJ Menus" (what else ? :-)...) and the main
         * tree will be called "Control Panel"
         *
         */
        public void run(String arg) {
            //IJ.write("***** MARK *****");
            if (!IJ.isJava2()) //wsr
                return;
            currentArg = (arg.length()==0) ? defaultArg : arg;
            argLength = currentArg.length();
    //      IJ.getInstance().setControlPanel(this);
            load();
            //IJ.write("thread: "+Thread.currentThread().getName());
        }

        /* *********************************************************************** */
        /*                             Tree logic                                  */
        /* *********************************************************************** */

        synchronized void load()
        {
            commands = Menus.getCommands();
            pluginsArray = Menus.getPlugins();
            root=buildTree(currentArg);
            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())
            {
                //IJ.write("no panels");
                newPanel(root);
            }
        }

        /**Constructs the root TreeNode of the ControlPanel.
         * If a non-empty argument is passed, then for each token in the argument this method
         * delegates the construction of the TreeNode to the
         * <code>doRoot(String)</code> method. Else, this method delegates to the
         * <code>doRootFromMenus()</code> method.
         * @param arg See {@see run(String)} comments for what arguments are allowed.
         * @return a DefaultMutableTreeNode populated according to the argument.
         * @see doRoot(String)
         */
        DefaultMutableTreeNode buildTree(String arg)
        {
            DefaultMutableTreeNode rootNode = null;
            if(arg.length()==0) return doRootFromMenus();
            StringTokenizer argParser = new StringTokenizer(arg,";");
            int tokens = argParser.countTokens();
            if(tokens==1)
            {
                rootNode = doRoot(arg);
            }
            else
            {
                rootNode = new DefaultMutableTreeNode("Control Panel");
                while(argParser.hasMoreTokens())
                {
                    String token = argParser.nextToken();
                    DefaultMutableTreeNode node = doRoot(token);
                    if(node!=null) rootNode.add(node);
                }
            }
            return rootNode;
        }

        /**Constructs a TreeNode according to the argument.
         * The argument has the canonical form described in the {@link run()} method. If the argument
         * is <strong><code>null</code></strong> or empty, this method returns a TreeNode with an
         * empty string as its user object and without children.<br>
         * For the token "imagej menus", this method delegates to the {@link doRootFromMenus()};
         * for any other valid token, the method delegates to {@link populateNode(Hashtable, DefaultMutableTreeNode)}.
         * @param arg See {@see run(String)} comments for a full description.
         * @return a DefaultMutableTreeNode constructed on the String argument as its "user object"
         * and populated with children according to the rules stated above.
         */
        private DefaultMutableTreeNode doRoot(String arg)
        {
            DefaultMutableTreeNode node = null;
            if(arg==null || arg.length()==0) node = new DefaultMutableTreeNode("");
            if(arg.equals("user plugins"))
            {
                node = new DefaultMutableTreeNode("User Plugins");
                if(argLength==0) node.setUserObject("Control Panel");
                populateNode(pluginsArray,null,node);
            }
            if(arg.equals("imagej menus"))
            {
                node=doRootFromMenus();
            }
            if(arg.equals("imagej commands"))
            {
                node = new DefaultMutableTreeNode("ImageJ Commands");
                if(argLength==0) node.setUserObject("Control Panel");
                populateNode(commands,node);
            }
            if(arg.equals("about"))
            {
                showHelp();
            }
            return node;
        }

        /** 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
        )
        {
    /*      IJ.write("source "+source);
            if(source.endsWith("Reload Plugins"))
                buildTreePath(source, label, "Reload Plugins From Panel", topNode);
            else*/
            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
            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(token.equals("Reload Plugins"))
                                IJ.write("node cmd: "+cmd);*/
                            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)
        {
            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))
                {
                    //IJ.write("creating "+p.toString());
                    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)))
            {
                //IJ.write("get panel for node "+pStr2Key(pathString));
                return (TreePanel)panels.get(pStr2Key(pathString));
            }
            //else return newPanel(node);
            else return null;
        }

        String getPluginsPath(){
            return pluginsPath;
        }

        public String getVersion(){
            return pcpVersion;
        }

        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());
            //IJ.write("register "+key);
            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()
        {
            visiblePanels.removeAllElements();
            expandedNodes.removeAllElements();
            panels.clear();
            try
            {
                if(pcpPropsFile.exists() && pcpPropsFile.canRead())
                {
                    pcpProperties.load(new FileInputStream(pcpPropsFile));
                    for(Enumeration e=pcpProperties.keys(); e.hasMoreElements();)
                    {
                        String key = (String)e.nextElement();
                        if(key.startsWith("Control_Panel"))
                        {
                            String val = pcpProperties.getProperty(key);
                            if(Character.isDigit(val.charAt(0))) // value starts with digit
                            {
                                visiblePanels.addElement(key);
                            }
                            if(val.equals("expand"))
                                expandedNodes.addElement(key);
                        }
                    }
                }
            } catch (Exception e) {}
        }

        void saveProperties()
        {
            if(propertiesChanged)
            {
                pcpProperties.clear();
                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();)
                {
                    pcpProperties.setProperty((String)e.nextElement(),"expand");
                }
                try
                {
                    if(pcpPropsFile.exists() && !pcpPropsFile.canWrite()) return;
                    pcpProperties.store(new FileOutputStream(pcpPropsFile),"Plugins Control Panel properties");
                }
                catch (Exception e) {}
            }
            propertiesChanged=false;
        }

        void setExpandedStateProperty(String item)
        {
            String s = pStr2Key(item);
            //IJ.write("set expanded "+s);
            expandedNodes.addElement(s);
            propertiesChanged=true;
        }

        boolean hasExpandedStateProperty(String item)
        {
            String s = pStr2Key(item);
            //IJ.write("has expanded prop "+s);
            return expandedNodes.contains(s);
        }

        void unsetExpandedStateProperty(String item)
        {
            String s = pStr2Key(item);
            //IJ.write("unset expanded "+s);
            expandedNodes.remove(s);
            propertiesChanged=true;
        }

        void setPanelShowingProperty(String item)
        {
            String s = pStr2Key(item);
            //IJ.write("set showing "+s);
            if(!(visiblePanels.contains(s)))
            {
                visiblePanels.addElement(s);
            }
            propertiesChanged=true;
        }

        void unsetPanelShowingProperty(String item)
        {
            String s = pStr2Key(item);
            //IJ.write("unset showing "+s);
            if(visiblePanels.remove(s))
            {
                //IJ.write("removed from showing "+item);
            }
            propertiesChanged=true;
        }

        boolean hasPanelShowingProperty(String item)
        {
            String s = pStr2Key(item);
            //IJ.write("has showing "+s);
            return visiblePanels.contains(s);
        }

        void restoreVisiblePanels()
        {
            //IJ.write("restoring "+visiblePanels.size()+" visible panels ...");
            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]);
                }
            }
            //IJ.write("restoring done");
        }

        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();
                String geometry = xCoord+" "+yCoord+" "+width+" "+height;
                pcpProperties.setProperty(pTitle,geometry);
            }
        }

        void restoreGeometry(TreePanel panel)
        {
            if(!pcpProperties.isEmpty())
            {
                String pTitle = panel.getRootPath().toString();
                pTitle = pStr2Key(pTitle);
                if(pcpProperties.containsKey(pTitle))
                {
                    String geom = pcpProperties.getProperty(pTitle);
                    int[] coords = s2ints(geom);
                    if(coords!=null && coords.length==4) //wsr
                    {
                        panel.setBounds(coords[0],coords[1],coords[2],coords[3]);
                    }
                    else
                    {
                        Point pnt = panel.getDefaultLocation();
                        if(pnt!=null)
                        {
                            //IJ.write("restore for no geometry "+pnt.getX()+ " "+pnt.getY());
                            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;
        }

        void verifyQuit()
        {
            //IJ.write("shall I quit?");
            if(quitting && visiblePanels.isEmpty())
            {
                //IJ.write("no more visible panels");
                //closeAll(true);
    //          IJ.getInstance().setControlPanel(null);
            }
        }

        /* **************************************************************************** */
        /*                              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"+
            "Version: "+pcpVersion+"\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>
    *
    * Requires: ImageJ 1.20b or newer, and Java 2 Platform v 1.3 or newer.<br>
    * NB! This plugin will NOT work with Java platform 1 even if you have the Swing components (Java Foundation Classes) installed.
    *
    * 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 = null;

        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 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 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();
            //IJ.write("new panel "+pcp.pStr2Key(rootPath.toString()));
            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)
                pFrame.setLocation((int)defaultLocation.getX(),(int)defaultLocation.getY());
            else pcp.restoreGeometry(this);
            //restoreExpandedNodes();
            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();
                    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()
        {
            //IJ.write("restore exp nodes");
            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(isMainPanel)
                pcp.saveProperties();
            pcp.unsetPanelShowingProperty(getRootPath().toString());
            pcp.verifyQuit();
        }

        public void windowActivated(WindowEvent e){}
        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

| ControlPanel |