/**
* NanoDoA - File based document archive
*
* Copyright (C) 2011-2012 Christian Packenius, christian.packenius@googlemail.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.chris_soft.utilities.swing.tree;
import java.awt.BorderLayout;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.event.TreeExpansionListener;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
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;
import de.chris_soft.utilities.LogUtils;
import de.chris_soft.utilities.swing.SwingUtils;
/**
* Dynamic JTree with a possibility of single selection.
* @author Christian Packenius.
*/
public class DynamicTree extends JPanel {
/**
* serialVersionUID.
*/
private static final long serialVersionUID = -1532733139607975119L;
/**
* Physical tree for tree displaying.
*/
final JTree tree;
/**
* Tree model.
*/
public final DefaultTreeModel treeModel;
/**
* Root node of this tree.
*/
public final DefaultMutableTreeNode rootNode;
final Map<Object, DefaultMutableTreeNode> obj2node = new HashMap<Object, DefaultMutableTreeNode>();
/**
* User object for root node.
*/
public final Object rootObject;
/**
* Constructor.
* @param rootObject Title of the root.
*/
public DynamicTree(Object rootObject) {
super(new BorderLayout());
this.rootObject = rootObject;
rootNode = new DefaultMutableTreeNode(rootObject);
obj2node.put(rootObject, rootNode);
treeModel = new DefaultTreeModel(rootNode);
tree = createTreeComposite();
DefaultTreeCellRenderer renderer = (DefaultTreeCellRenderer) tree.getCellRenderer();
renderer.setBackgroundNonSelectionColor(getBackground());
JScrollPane scrollPane = new JScrollPane(tree);
add(scrollPane);
}
private JTree createTreeComposite() {
JTree tree = new JTree(treeModel);
tree.setBackground(getBackground());
tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
tree.setShowsRootHandles(true);
tree.setExpandsSelectedPaths(true);
tree.setRootVisible(true);
return tree;
}
/**
* Removes all nodes from tree (except root node).
*/
public void removeAllNodes() {
removeAllChildren(rootNode);
treeModel.reload();
}
/**
* Removes the current node from tree if possible.
*/
public void removeCurrentNode() {
TreePath selection = tree.getSelectionPath();
if (selection != null) {
DefaultMutableTreeNode currentNode = (DefaultMutableTreeNode) selection.getLastPathComponent();
MutableTreeNode parent = (MutableTreeNode) currentNode.getParent();
if (parent != null) {
treeModel.removeNodeFromParent(currentNode);
}
}
}
/**
* Removes a node from the tree.
* @param obj Object of the node to remove.
*/
public void removeNode(Object obj) {
DefaultMutableTreeNode node = obj2node.get(obj);
if (node != null) {
treeModel.removeNodeFromParent(node);
}
}
/**
* Refreshed a node on the tree.
* @param obj Object of the node to refresh.
*/
public void refreshNode(Object obj) {
DefaultMutableTreeNode node = obj2node.get(obj);
if (node != null) {
treeModel.reload(node);
}
}
/**
* Adds a child node to a parent node.
* @param parentObject Parent node. May be <i>null</i> for root as parent.
* @param childObject Child object with data for child node.
*/
public void addNode(final Object parentObject, final Object childObject) {
Runnable doRun = new Runnable() {
@Override
public void run() {
DefaultMutableTreeNode parentNode = getParentNodeFromParentObject(parentObject);
DefaultMutableTreeNode childNode = new DefaultMutableTreeNode(childObject);
obj2node.put(childObject, childNode);
int childIndex = getNewChildIndex(childObject, parentNode);
treeModel.insertNodeInto(childNode, parentNode, childIndex);
if (parentObject == null) {
tree.expandPath(new TreePath(new Object[] {rootNode}));
}
}
private DefaultMutableTreeNode getParentNodeFromParentObject(final Object parentObject) {
DefaultMutableTreeNode parentNode;
if (parentObject == null) {
parentNode = rootNode;
}
else {
parentNode = obj2node.get(parentObject);
}
return parentNode;
}
private int getNewChildIndex(final Object childObject, DefaultMutableTreeNode parentNode) {
int childIndex = parentNode.getChildCount();
for (int i = 0; i < childIndex; i++) {
TreeNode child = parentNode.getChildAt(i);
if (child.toString().compareToIgnoreCase(childObject.toString()) > 0) {
childIndex = i;
}
}
return childIndex;
}
};
try {
SwingUtils.runInGuiNow(doRun);
} catch (Exception exception) {
LogUtils.log(exception);
}
}
/**
* Returns the object of the current node.
* @return Objekt of the current node or <i>null</i> if there is no current node (or if the root node is selected).
*/
public Object getCurrentNode() {
TreePath selection = tree.getSelectionPath();
if (selection != null) {
DefaultMutableTreeNode currentNode = (DefaultMutableTreeNode) selection.getLastPathComponent();
if (currentNode != null) {
return currentNode.getUserObject();
}
}
return null;
}
/**
* Sets the current node.
* @param obj Object of the node to set to.
* @return <i>true</i> if the node is set.
*/
public boolean setCurrentNode(Object obj) {
DefaultMutableTreeNode node = obj2node.get(obj);
if (node != null) {
tree.setSelectionPath(new TreePath(node.getPath()));
tree.scrollPathToVisible(new TreePath(node.getPath()));
return true;
}
return false;
}
/**
* Tells if there is a node with the given object.
* @param obj Object.
* @return <i>true</i> if there is a node with this object.
*/
public boolean hasNode(Object obj) {
return obj2node.containsKey(obj);
}
/**
* Returns one by one all node objects.
* @param obj Object of the previous node (or null for getting root object).
* @return Next object or root object if <i>null</i>.
*/
public Object getNextNode(Object obj) {
if (obj == null) {
return rootNode.getUserObject();
}
DefaultMutableTreeNode node = obj2node.get(obj);
DefaultMutableTreeNode nextNode = node.getNextNode();
if (nextNode == null) {
return null;
}
return nextNode.getUserObject();
}
/**
* Removes all children of a tree element.
* @param parentNode Parent node of the children to remove.
*/
public void removeAllChildren(DefaultMutableTreeNode parentNode) {
innerChildRemoval(parentNode);
parentNode.removeAllChildren();
}
private void innerChildRemoval(DefaultMutableTreeNode parentNode) {
Enumeration<?> children = parentNode.children();
while (children.hasMoreElements()) {
Object oChild = children.nextElement();
DefaultMutableTreeNode nChild = (DefaultMutableTreeNode) oChild;
innerChildRemoval(nChild);
}
obj2node.remove(parentNode.getUserObject());
}
/**
* Adds a tree selection listener to the tree.
* @param listener Tree selection listener.
*/
public void addTreeSelectionListener(TreeSelectionListener listener) {
tree.addTreeSelectionListener(listener);
}
/**
* Adds a tree expansion listener to the tree.
* @param listener Tree expansion listener.
*/
public void addTreeExpansionListener(TreeExpansionListener listener) {
tree.addTreeExpansionListener(listener);
}
/**
* Returns a list of all user objects of the corresponding parent tree node.
* @param parentUserObject User object of the corresponding parent tree node.
* @return List of all childrens user objects.
*/
public List<Object> getChildUserObjects(Object parentUserObject) {
DefaultMutableTreeNode parent = obj2node.get(parentUserObject);
List<Object> list = new ArrayList<Object>();
if (parent != null) {
Enumeration<?> children = parent.children();
while (children.hasMoreElements()) {
DefaultMutableTreeNode child = (DefaultMutableTreeNode) children.nextElement();
list.add(child.getUserObject());
}
}
return list;
}
/**
* Returns the JTree component.
* @return JTree component of this dynamic tree.
*/
public JTree getTree() {
return tree;
}
/**
* Set a child node of the current node with the given user object.
* @param userObject User object of the child node.
*/
public void setCurrentChild(Object userObject) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getSelectionPath().getLastPathComponent();
Enumeration<?> children = node.children();
while (children.hasMoreElements()) {
DefaultMutableTreeNode child = (DefaultMutableTreeNode) children.nextElement();
Object childObject = child.getUserObject();
if (childObject.equals(userObject)) {
setCurrentNode(childObject);
}
}
}
/**
* Removes the last node of the given path of user objects.
* @param objects User Objects, starting with root object (not <i>null</i>!).
*/
public void removeUserObjectPath(Object[] objects) {
DefaultMutableTreeNode node = rootNode;
for (int i = 1; i < objects.length && node != null; i++) {
Enumeration<?> en = node.children();
boolean found = false;
while (en.hasMoreElements() && !found) {
DefaultMutableTreeNode child = (DefaultMutableTreeNode) en.nextElement();
if (child.getUserObject().equals(objects[i])) {
node = child;
found = true;
}
}
if (!found) {
return;
}
}
treeModel.removeNodeFromParent(node);
}
}