
com.jidesoft.swing.CheckBoxTreeSelectionModel Maven / Gradle / Ivy
package com.jidesoft.swing;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.event.TreeSelectionEvent;
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.List;
import java.util.Stack;
import java.util.Vector;
/**
* CheckBoxTreeSelectionModel
is a selection _model based on {@link DefaultTreeSelectionModel} and use in
* {@link CheckBoxTree} to keep track of the checked tree paths.
*
* @author Santhosh Kumar T
*/
public class CheckBoxTreeSelectionModel extends DefaultTreeSelectionModel implements TreeModelListener {
private TreeModel _model;
private boolean _digIn = true;
private CheckBoxTree _tree;
private boolean _singleEventMode = false;
private static final long serialVersionUID = 1368502059666946634L;
public CheckBoxTreeSelectionModel(TreeModel model) {
setModel(model);
setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
}
void setTree(CheckBoxTree tree) {
_tree = tree;
}
public CheckBoxTreeSelectionModel(TreeModel model, boolean digIn) {
setModel(model);
_digIn = digIn;
}
public TreeModel getModel() {
return _model;
}
public void setModel(TreeModel model) {
if (_model != model) {
if (_model != null) {
_model.removeTreeModelListener(this);
}
_model = model;
if (_model != null) {
_model.addTreeModelListener(this);
}
}
}
/**
* Gets the dig-in mode. If the CheckBoxTree is in dig-in mode, checking the parent node will check all the
* children. Correspondingly, getSelectionPaths() will only return the parent tree path. If not in dig-in mode, each
* tree node can be checked or unchecked independently
*
* @return true or false.
*/
public boolean isDigIn() {
return _digIn;
}
/**
* Sets the dig-in mode. If the CheckBoxTree is in dig-in mode, checking the parent node will check all the
* children. Correspondingly, getSelectionPaths() will only return the parent tree path. If not in dig-in mode, each
* tree node can be checked or unchecked independently
*
* @param digIn true to enable dig-in mode. False to disable it.
*/
public void setDigIn(boolean digIn) {
_digIn = digIn;
}
/**
* Tests whether there is any unselected node in the subtree of given path.
*
* Inherited from JTree, the TreePath must be a path instance inside the tree model. If you populate a new TreePath
* instance on the fly, it would not work.
*
* @param path check if the path is partially selected.
* @return true if partially. Otherwise false.
*/
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;
}
@Override
public boolean isRowSelected(int row) {
return isPathSelected(_tree.getPathForRow(row), _tree.isDigIn());
}
/**
* Check if the parent path is really selected.
*
* The default implementation is just return true. In filterable scenario, you could override this method to check
* more.
*
* Inherited from JTree, the TreePath must be a path instance inside the tree model. If you populate a new TreePath
* instance on the fly, it would not work.
*
* @param path the original path to be checked
* @param parent the parent part which is closest to the original path and is selected
* @return true if the path is actually selected without any doubt. Otherwise false.
*/
protected boolean isParentActuallySelected(TreePath path, TreePath parent) {
return true;
}
/**
* 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.
*
* Inherited from JTree, the TreePath must be a path instance inside the tree model. If you populate a new TreePath
* instance on the fly, it would not work.
*
* @param path check if the path is selected.
* @param digIn whether we will check its descendants.
* @return true if the path is selected.
*/
public boolean isPathSelected(TreePath path, boolean digIn) {
if (path == null) {
return false;
}
if (!digIn)
return super.isPathSelected(path);
TreePath parent = path;
while (parent != null && !super.isPathSelected(parent)) {
parent = parent.getParentPath();
}
if (parent != null) {
return isParentActuallySelected(path, parent);
}
if (_model == null) {
return true;
}
Object node = path.getLastPathComponent();
if (_model.getChildCount(node) == 0) {
return false;
}
// find out if all children are selected
boolean allChildrenSelected = true;
for (int i = 0; i < _model.getChildCount(node); i++) {
Object childNode = _model.getChild(node, i);
if (!isPathSelected(path.pathByAddingChild(childNode), true)) {
allChildrenSelected = false;
break;
}
}
// if all children are selected, let's select the parent path only
if (_tree.isCheckBoxVisible(path) && allChildrenSelected) {
addSelectionPaths(new TreePath[]{path});
}
return allChildrenSelected;
}
/**
* is path1 descendant of path2.
*
* Inherited from JTree, the TreePath must be a path instance inside the tree model. If you populate a new TreePath
* instance on the fly, it would not work.
*
* @param path1 the first path
* @param path2 the second path
* @return true if the first path is the descendant of the second path.
*/
private boolean isDescendant(TreePath path1, TreePath path2) {
Object obj1[] = path1.getPath();
Object obj2[] = path2.getPath();
if (obj1.length < obj2.length)
return false;
for (int i = 0; i < obj2.length; i++) {
if (obj1[i] != obj2[i])
return false;
}
return true;
}
private boolean _fireEvent = true;
@SuppressWarnings({"RawUseOfParameterizedType"})
@Override
protected void notifyPathChange(Vector changedPaths, TreePath oldLeadSelection) {
if (_fireEvent) {
super.notifyPathChange(changedPaths, oldLeadSelection);
}
}
/**
* Overrides the method in DefaultTreeSelectionModel to consider digIn mode.
*
* Inherited from JTree, the TreePath must be a path instance inside the tree model. If you populate a new TreePath
* instance on the fly, it would not work.
*
* @param pPaths the tree paths to be selected.
*/
@Override
public void setSelectionPaths(TreePath[] pPaths) {
if (!isDigIn() || selectionMode == TreeSelectionModel.SINGLE_TREE_SELECTION) {
super.setSelectionPaths(pPaths);
}
else {
clearSelection();
addSelectionPaths(pPaths);
}
}
/**
* Overrides the method in DefaultTreeSelectionModel to consider digIn mode.
*
* Inherited from JTree, the TreePath must be a path instance inside the tree model. If you populate a new TreePath
* instance on the fly, it would not work.
*
* @param paths the tree paths to be added to selection paths.
*/
@Override
public void addSelectionPaths(TreePath[] paths) {
if (!isDigIn()) {
super.addSelectionPaths(paths);
return;
}
boolean fireEventAtTheEnd = false;
if (isSingleEventMode() && _fireEvent) {
_fireEvent = false;
fireEventAtTheEnd = true;
}
try {
// deselect all descendants of paths[]
List toBeRemoved = new ArrayList();
for (TreePath path : paths) {
TreePath[] selectionPaths = getSelectionPaths();
if (selectionPaths == null)
break;
for (TreePath selectionPath : selectionPaths) {
if (isDescendant(selectionPath, path))
toBeRemoved.add(selectionPath);
}
}
if (toBeRemoved.size() > 0) {
delegateRemoveSelectionPaths(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(), !fireEventAtTheEnd);
}
delegateAddSelectionPaths(new TreePath[]{temp});
}
}
else {
delegateAddSelectionPaths(new TreePath[]{path});
}
}
}
finally {
_fireEvent = true;
if (isSingleEventMode() && fireEventAtTheEnd) {
notifyPathChange(paths, true, paths[0]);
}
}
}
/**
* tells whether all siblings of given path are selected.
*
* Inherited from JTree, the TreePath must be a path instance inside the tree model. If you populate a new TreePath
* instance on the fly, it would not work.
*
* @param path the tree path
* @return true if the siblings are all selected.
*/
protected 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;
TreePath childPath = parent.pathByAddingChild(childNode);
if (_tree != null && !_tree.isCheckBoxVisible(childPath)) {
// if the checkbox is not visible, we check its children
if (!isPathSelected(childPath, true)) {
return false;
}
}
if (!isPathSelected(childPath)) {
return false;
}
}
return true;
}
@Override
public void removeSelectionPaths(TreePath[] paths) {
removeSelectionPaths(paths, true);
}
public void removeSelectionPaths(TreePath[] paths, boolean doFireEvent) {
if (!isDigIn()) {
super.removeSelectionPaths(paths);
return;
}
List toBeRemoved = new ArrayList();
for (TreePath path : paths) {
if (path.getPathCount() == 1) {
toBeRemoved.add(path);
}
else {
toggleRemoveSelection(path, doFireEvent);
}
}
if (toBeRemoved.size() > 0) {
delegateRemoveSelectionPaths((TreePath[]) toBeRemoved.toArray(new TreePath[toBeRemoved.size()]));
}
}
/**
* 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
*
* Inherited from JTree, the TreePath must be a path instance inside the tree model. If you populate a new TreePath
* instance on the fly, it would not work.
*
* @param path the tree path
* @param doFireEvent the flag indicating if firing event
*/
private void toggleRemoveSelection(TreePath path, boolean doFireEvent) {
boolean fireEventAtTheEnd = false;
if (doFireEvent) {
if (isSingleEventMode() && _fireEvent) {
_fireEvent = false;
fireEventAtTheEnd = true;
}
}
try {
Stack stack = new Stack();
TreePath parent = path.getParentPath();
while (parent != null && !isPathSelected(parent)) {
stack.push(parent);
parent = parent.getParentPath();
}
if (parent != null)
stack.push(parent);
else {
delegateRemoveSelectionPaths(new TreePath[]{path});
return;
}
List toBeAdded = new ArrayList();
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) {
TreePath treePath = temp.pathByAddingChild(childNode);
toBeAdded.add(treePath);
}
}
}
if (toBeAdded.size() > 0) {
delegateAddSelectionPaths(toBeAdded.toArray(new TreePath[toBeAdded.size()]));
}
delegateRemoveSelectionPaths(new TreePath[]{parent});
}
finally {
if (doFireEvent) {
_fireEvent = true;
if (isSingleEventMode() && fireEventAtTheEnd) {
notifyPathChange(new TreePath[]{path}, false, path);
}
}
}
}
public boolean isSingleEventMode() {
return _singleEventMode;
}
/**
* Single event mode is a mode that always fires only one event when you select or deselect a tree node.
*
* Taking this tree as an example,
*
*
* A -- a
* |- b
* |- c
*
* Case 1: Assuming b and c are selected at this point, you click on a. - In non-single event mode, you * will get select-A, deselect-b and deselect-c three events
- In single event mode, you will only get select-a. *
- In non-single event mode, you will get only select-A event.
- In single event mode, you will * only get select-A too.
- In non-single * event mode, you will get select-A event as well as deselect-b and deselect-c event.
- In single event mode, you * will only get select-A.
© 2015 - 2025 Weber Informatics LLC | Privacy Policy