|
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 |
|