/*
* Copyright (C) 2004 The Concord Consortium, Inc.,
* 10 Concord Crossing, Concord, MA 01742
*
* Web Site: http://www.concord.org
* Email: info@concord.org
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* END LICENSE */
package org.concord.swing;
import java.awt.Color;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Vector;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTree;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
public class CCJCheckBoxTree extends JTree implements TreeSelectionListener{
protected DefaultMutableTreeNode rootNode;
protected DefaultTreeModel treeModel;
private Toolkit toolkit = Toolkit.getDefaultToolkit();
// allnodes in the tree
private Vector allNodes = new Vector();
private HashMap nodesMap = new HashMap();
private TreePath lastSelectedPath = null;
private MouseListener[] mouseListeners;
public CCJCheckBoxTree() {
this(new DefaultMutableTreeNode());
}
public CCJCheckBoxTree(TreeNode node) {
this(node, false);
}
public CCJCheckBoxTree(String rootName) {
this(new DefaultMutableTreeNode(new NodeHolder(rootName), true));
}
public CCJCheckBoxTree(TreeNode node, boolean asksAllowsChildren) {
super(node, asksAllowsChildren);
rootNode = (DefaultMutableTreeNode)node;
NodeHolder rootBox = (NodeHolder) rootNode.getUserObject();
allNodes.addElement(rootBox);
nodesMap.put(rootBox, rootNode);
treeModel = new DefaultTreeModel(rootNode);
//treeModel.addTreeModelListener(new MyTreeModelListener());
cellRenderer = new CCJCheckBoxRenderer();
setModel(treeModel);
setCellRenderer(cellRenderer);
setEditable(false);
getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
setShowsRootHandles(true);
setToolTipText("Click to select; double-click to check/uncheck");
mouseListeners = this.getMouseListeners();
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
for(int i = 0; i < mouseListeners.length; i++)
removeMouseListener(mouseListeners[i]);
}
public void mouseReleased(MouseEvent e) {
TreePath path = getPathForLocation(e.getX(), e.getY());
Rectangle bounds = getPathBounds(path);
if(path != null) { // selected a path
DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
Object obj = node.getUserObject();
if(obj instanceof CCJCheckBoxTree.NodeHolder) {
CCJCheckBoxTree.NodeHolder nodeHolder = (CCJCheckBoxTree.NodeHolder)obj;
// if clicked on the checkbox:
// if nothing highlighted, highlight it;
// if it is checked and highlighted, uncheck it and highlight nothing;
// if it is checked and not hightlight, just check it;
// if it is unchecked, check it
//
// if clicked on the text:
// highlight the path;
// if unchecked, check it
//
// if clicked anywhere else:
// highlight nothing
if(e.getX() < bounds.x + 20 && // clicked on checkbox:
e.getX() > bounds.x &&
e.getY() < bounds.getCenterY() + bounds.height/2 &&
e.getY() > bounds.getCenterY() - bounds.height/2) {
nodeHolder.checked = !nodeHolder.checked;
// highlight nothing if a highlighted path is unchecked
if(!nodeHolder.checked) {
if(lastSelectedPath == path) {
lastSelectedPath = null;
}
} else {
if(lastSelectedPath == null) {
lastSelectedPath = path;
}
}
setSelectionPath(lastSelectedPath);
} else if(e.getX() > bounds.x + 20 && // clicked on text:
e.getX() < bounds.x + bounds.getWidth() &&
e.getY() < bounds.getCenterY() + bounds.height/2 &&
e.getY() > bounds.getCenterY() - bounds.height/2) {
for(int i = 0; i < mouseListeners.length; i++)
addMouseListener(mouseListeners[i]);
if(!nodeHolder.checked) nodeHolder.checked = true;
lastSelectedPath = path;
setSelectionPath(lastSelectedPath);
} else { // clicked elsewhere:
for(int i = 0; i < mouseListeners.length; i++)
addMouseListener(mouseListeners[i]);
if(e.getY() > getVisibleHeight())
lastSelectedPath = null;
else
lastSelectedPath = path;
setSelectionPath(lastSelectedPath);
}
checkRelatedNodeFor(node);
}
repaint();
} else { // not selected any path: remove current selected path
for(int i = 0; i < mouseListeners.length; i++)
addMouseListener(mouseListeners[i]);
lastSelectedPath = null;
setSelectionPath(lastSelectedPath);
}
}
});
}
public String renameCurrentNode() {
String name = null;
TreePath currentSelection = getSelectionPath();
if (currentSelection != null) {
DefaultMutableTreeNode currentNode = (DefaultMutableTreeNode)
(currentSelection.getLastPathComponent());
CCJCheckBoxTree.NodeHolder box = (CCJCheckBoxTree.NodeHolder)currentNode.getUserObject();
String newText = JOptionPane.showInputDialog(this, "Enter new name", box.name);
if(newText != null && newText.trim().length() > 0) {
box.name = newText;
name = newText;
repaint();
}
}
return name;
}
public Object removeCurrentNode() {
getUI().cancelEditing(this);
//SwingUtilities.invokeLater(new Runnable() {
//public void run() {
TreePath currentSelection = getSelectionPath();
if (currentSelection != null) {
DefaultMutableTreeNode currentNode = (DefaultMutableTreeNode)
(currentSelection.getLastPathComponent());
MutableTreeNode parent = (MutableTreeNode)(currentNode.getParent());
if (parent != null) {
Object obj = currentNode.getUserObject();
treeModel.removeNodeFromParent(currentNode);
allNodes.removeElement(obj);
nodesMap.remove(obj);
return obj;
}
}
// Either there was no selection, or the root was selected.
toolkit.beep();
//}
//});
return null;
}
/** Add child to the currently selected node. */
public DefaultMutableTreeNode addObject(Object child) {
DefaultMutableTreeNode parentNode = null;
TreePath parentPath = getSelectionPath();
if (parentPath == null) {
parentNode = rootNode;
} else {
parentNode = (DefaultMutableTreeNode)
(parentPath.getLastPathComponent());
}
NodeHolder nodeHolder = null;
if(!(child instanceof NodeHolder)) {
nodeHolder = new NodeHolder(child.toString());
} else {
nodeHolder = (NodeHolder)child;
}
return addObject(parentNode, nodeHolder, true);
}
public DefaultMutableTreeNode addObject(DefaultMutableTreeNode parent,
Object child) {
return addObject(parent, child, false);
}
public DefaultMutableTreeNode addObject(DefaultMutableTreeNode parent,
Object child,
boolean shouldBeVisible) {
DefaultMutableTreeNode childNode =
new DefaultMutableTreeNode(child);
if (parent == null) {
parent = rootNode;
}
treeModel.insertNodeInto(childNode, parent,
parent.getChildCount());
if (shouldBeVisible) {
scrollPathToVisible(new TreePath(childNode.getPath()));
}
allNodes.addElement(child);
nodesMap.put(child, childNode);
return childNode;
}
public Vector getCheckedNodes() {
int size = allNodes.size();
Vector checkedBoxes = new Vector();
for(int i = 0; i < size; i++) {
NodeHolder box = (NodeHolder)allNodes.elementAt(i);
if(box.checked) {
checkedBoxes.addElement(box);
}
}
return checkedBoxes;
}
public Vector getAllNodes() {
return allNodes;
}
public HashMap getNodesMap() {
return nodesMap;
}
class MyTreeModelListener implements TreeModelListener {
public void treeNodesChanged(TreeModelEvent e) {
DefaultMutableTreeNode node;
node = (DefaultMutableTreeNode)
(e.getTreePath().getLastPathComponent());
try {
int index = e.getChildIndices()[0];
node = (DefaultMutableTreeNode)
(node.getChildAt(index));
} catch (NullPointerException exc) {}
}
public void treeNodesInserted(TreeModelEvent e) {
System.out.println("node inserted");
}
public void treeNodesRemoved(TreeModelEvent e) {
System.out.println("node deleted");
}
public void treeStructureChanged(TreeModelEvent e) {
System.out.println("tree changed");
}
}
//When a node is checked, all the children should be checked;
// when it is unchecked, all the parents should be unchecked.
private void checkRelatedNodeFor(DefaultMutableTreeNode node) {
Object obj = node.getUserObject();
NodeHolder nodeHolder = (NodeHolder)obj;
if(nodeHolder.checked) {
Enumeration enu = node.depthFirstEnumeration();
while(enu.hasMoreElements()) {
DefaultMutableTreeNode childNode =
(DefaultMutableTreeNode)enu.nextElement();
((NodeHolder)childNode.getUserObject()).checked =
nodeHolder.checked;
}
} else {
TreeNode[] parents = (TreeNode[])node.getPath();
if(parents != null && parents.length > 0) {
for(int i = 0; i < parents.length; i++) {
DefaultMutableTreeNode newNode = (DefaultMutableTreeNode)parents[i];
((CCJCheckBoxTree.NodeHolder)newNode.getUserObject()).checked = nodeHolder.checked;
}
}
}
}
public void valueChanged(TreeSelectionEvent e) {
//System.out.println("tree selectyion changed to: dima " + e.getPath());
Object path = e.getPath();
System.out.println(path.getClass());
if(path instanceof CCJCheckBoxTree.NodeHolder)
((CCJCheckBoxTree.NodeHolder)path).checked = !((CCJCheckBoxTree.NodeHolder)path).checked;
}
public TreeNode getRootNode() {
return rootNode;
}
public static class NodeHolder extends JPanel{
public String name;
public boolean checked;
public Color color;
public NodeHolder(String name, boolean checked, Color color) {
this.name = name;
this.checked = checked;
this.color = color;
}
public NodeHolder(String name, boolean checked) {
this(name, checked, Color.BLACK);
}
public NodeHolder(String name) {
this(name, false);
}
public String toString() {
return name;
}
}
public double getVisibleHeight() {
Rectangle rect = this.getPathBounds(this.getPathForRow(this.getRowCount()-1));
if(rect != null)
return rect.getCenterY() + rect.height/2;
return 0;
}
public void setPathChecked(TreePath path, boolean checked) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getPathComponent(0);
NodeHolder holder = (NodeHolder)node.getUserObject();
holder.checked = checked;
}
public boolean isPathChecked(TreePath path) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
NodeHolder holder = (NodeHolder)node.getUserObject();
return holder.checked;
}
}