/*
* CheckTreeSelectionModel.java
*/
package net.sf.openrocket.gui.print.components;
import javax.swing.tree.DefaultTreeSelectionModel;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import java.util.ArrayList;
import java.util.Stack;
/**
* This class implements the selection model for the checkbox tree. This specifically is used to keep
* track of the TreePaths that have a selected CheckBox.
*/
public class CheckTreeSelectionModel extends DefaultTreeSelectionModel {
/**
* The tree model.
*/
private TreeModel model;
/**
* Constructor.
*
* @param theModel the model in use for the tree
*/
public CheckTreeSelectionModel (TreeModel theModel) {
model = theModel;
setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
}
/**
* Tests whether there is any selected node in the subtree of given path.
*
* @param path the path to walk
*
* @return true if any item in the path or its descendants are selected
*/
public boolean isPartiallySelected (TreePath path) {
if (isPathSelected(path, true)) {
return false;
}
TreePath[] selectionPaths = getSelectionPaths();
if (selectionPaths == null) {
return false;
}
for (TreePath selectionPath : selectionPaths) {
if (isDescendant(selectionPath, path)) {
return true;
}
}
return false;
}
/**
* Tells whether given path is selected. If dig is true, then a path is assumed to be selected, if one of its
* ancestor is selected.
*
* @param path the path to interrogate
* @param dig if true then check if an ancestor is selected
*
* @return true if the path is selected
*/
public boolean isPathSelected (TreePath path, boolean dig) {
if (!dig) {
return super.isPathSelected(path);
}
while (path != null && !super.isPathSelected(path)) {
path = path.getParentPath();
}
return path != null;
}
/**
* Determines if path1 is a descendant of path2.
*
* @param path1 descendant?
* @param path2 ancestor?
*
* @return true if path1 is a descendant of path2
*/
private boolean isDescendant (TreePath path1, TreePath path2) {
Object obj1[] = path1.getPath();
Object obj2[] = path2.getPath();
for (int i = 0; i < obj2.length; i++) {
if (i < obj1.length) {
if (obj1[i] != obj2[i]) {
return false;
}
}
else {
return false;
}
}
return true;
}
/**
* Unsupported exception.
*
* @param pPaths an array of paths
*/
@Override
public void setSelectionPaths (TreePath[] pPaths) {
TreePath selected[] = getSelectionPaths();
for (TreePath aSelected : selected) {
removeSelectionPath(aSelected);
}
for (TreePath pPath : pPaths) {
addSelectionPath(pPath);
}
}
/**
* Add a set of TreePath nodes to the selection model.
*
* @param paths an array of tree path nodes
*/
@Override
public void addSelectionPaths (TreePath[] paths) {
// deselect all descendants of paths[]
for (TreePath path : paths) {
TreePath[] selectionPaths = getSelectionPaths();
if (selectionPaths == null) {
break;
}
ArrayList<TreePath> toBeRemoved = new ArrayList<TreePath>();
for (TreePath selectionPath : selectionPaths) {
if (isDescendant(selectionPath, path)) {
toBeRemoved.add(selectionPath);
}
}
super.removeSelectionPaths(toBeRemoved.toArray(new TreePath[toBeRemoved.size()]));
}
// if all siblings are selected then deselect them and select parent recursively
// otherwise just select that path.
for (TreePath path : paths) {
TreePath temp = null;
while (areSiblingsSelected(path)) {
temp = path;
if (path.getParentPath() == null) {
break;
}
path = path.getParentPath();
}
if (temp != null) {
if (temp.getParentPath() != null) {
addSelectionPath(temp.getParentPath());
}
else {
if (!isSelectionEmpty()) {
removeSelectionPaths(getSelectionPaths());
}
super.addSelectionPaths(new TreePath[]{temp});
}
}
else {
super.addSelectionPaths(new TreePath[]{path});
}
}
}
/**
* Tells whether all siblings of given path are selected.
*
* @param path the tree path node
*
* @return true if all sibling nodes are selected
*/
private boolean areSiblingsSelected (TreePath path) {
TreePath parent = path.getParentPath();
if (parent == null) {
return true;
}
Object node = path.getLastPathComponent();
Object parentNode = parent.getLastPathComponent();
int childCount = model.getChildCount(parentNode);
for (int i = 0; i < childCount; i++) {
Object childNode = model.getChild(parentNode, i);
if (childNode == node) {
continue;
}
if (!isPathSelected(parent.pathByAddingChild(childNode))) {
return false;
}
}
return true;
}
/**
* Remove paths from the selection model.
*
* @param paths the array of path nodes
*/
@Override
public void removeSelectionPaths (TreePath[] paths) {
for (TreePath path : paths) {
if (path.getPathCount() == 1) {
super.removeSelectionPaths(new TreePath[]{path});
}
else {
toggleRemoveSelection(path);
}
}
}
/**
* If any ancestor node of given path is selected then deselect it and selection all its descendants except given
* path and descendants. otherwise just deselect the given path.
*
* @param path the tree path node
*/
private void toggleRemoveSelection (TreePath path) {
Stack<TreePath> stack = new Stack<TreePath>();
TreePath parent = path.getParentPath();
while (parent != null && !isPathSelected(parent)) {
stack.push(parent);
parent = parent.getParentPath();
}
if (parent != null) {
stack.push(parent);
}
else {
super.removeSelectionPaths(new TreePath[]{path});
return;
}
while (!stack.isEmpty()) {
TreePath temp = stack.pop();
TreePath peekPath = stack.isEmpty() ? path : stack.peek();
Object node = temp.getLastPathComponent();
Object peekNode = peekPath.getLastPathComponent();
int childCount = model.getChildCount(node);
for (int i = 0; i < childCount; i++) {
Object childNode = model.getChild(node, i);
if (childNode != peekNode) {
super.addSelectionPaths(new TreePath[]{temp.pathByAddingChild(childNode)});
}
}
}
super.removeSelectionPaths(new TreePath[]{parent});
}
}