All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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.*;

/**
 * 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;
    /**
     * Used in {@link #areSiblingsSelected(javax.swing.tree.TreePath)} for those paths pending added so that they are not
     * in the selection model right now.
     */
    protected Set _pathHasAdded;

    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) {
            clearSelection();
            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 (!isDigIn()) { return isPathSelected(path, false); } 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; } @Override public boolean isPathSelected(TreePath path) { return super.isPathSelected(path); // Cannot return isPathSelected(path, isDigIn()). Otherwise FilterableCheckBoxTreeSelectionModel will have problem. FilterableCheckBoxTreeTest } /** * 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 (getChildrenCount(node) == 0) { return false; } // find out if all children are selected boolean allChildrenSelected = true; for (int i = 0; i < getChildrenCount(node); i++) { Object childNode = 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}, false); } 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. */ 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) { addSelectionPaths(paths, !_avoidCheckPathSelection); } private boolean _avoidCheckPathSelection = false; /** * Add the selection paths. * * @param paths the paths to be added * @param needCheckPathSelection the flag to indicating if the path selection should be checked to improve performance */ protected void addSelectionPaths(TreePath[] paths, boolean needCheckPathSelection) { if (!isDigIn()) { super.addSelectionPaths(paths); return; } setBatchMode(true); boolean fireEventAtTheEnd = false; if (isSingleEventMode() && _fireEvent) { _fireEvent = false; fireEventAtTheEnd = true; } try { if (needCheckPathSelection) { _pathHasAdded = new HashSet(); for (TreePath path : paths) { if (isPathSelected(path, isDigIn())) { continue; // for non batch mode scenario, check if it is already selected by adding its parent possibly } // if the path itself is added by other insertion, just remove it if (_toBeAdded.contains(path)) { addToExistingSet(_pathHasAdded, path); continue; } // check if its ancestor has already been added. If so, do nothing boolean findAncestor = false; for (TreePath addPath : _pathHasAdded) { if (addPath.isDescendant(path)) { findAncestor = true; break; } } if (findAncestor) { continue; } TreePath temp = null; // if all siblings are selected then deselect them and select parent recursively // otherwise just select that path. while (areSiblingsSelected(path)) { temp = path; if (path.getParentPath() == null) break; path = path.getParentPath(); } if (temp != null) { if (temp.getParentPath() != null) { delegateAddSelectionPaths(new TreePath[] {temp.getParentPath()}); } else { delegateAddSelectionPaths(new TreePath[]{temp}); } } else { delegateAddSelectionPaths(new TreePath[]{path}); } addToExistingSet(_pathHasAdded, path); } // deselect all descendants of paths[] List toBeRemoved = new ArrayList(); for (TreePath path : _toBeAdded) { 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()])); } } else { // 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) { try { _avoidCheckPathSelection = true; addSelectionPath(temp.getParentPath()); } finally { _avoidCheckPathSelection = false; } } else { if (!isSelectionEmpty()) { removeSelectionPaths(getSelectionPaths(), !fireEventAtTheEnd); } delegateAddSelectionPaths(new TreePath[]{temp}); } } else { delegateAddSelectionPaths(new TreePath[]{path}); } } } } finally { if (isSingleEventMode() && fireEventAtTheEnd) { setBatchMode(false); } _fireEvent = true; if (isSingleEventMode() && fireEventAtTheEnd) { notifyPathChange(paths, true, paths[0]); } else { setBatchMode(false); } } } /** * 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 = getChildrenCount(parentNode); for (int i = 0; i < childCount; i++) { Object childNode = 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) && (_pathHasAdded == null || !_pathHasAdded.contains(childPath))) { return false; } } if (!isPathSelected(childPath) && (_pathHasAdded == null || !_pathHasAdded.contains(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; } boolean fireEventAtTheEnd = false; if (doFireEvent) { if (isSingleEventMode() && _fireEvent) { _fireEvent = false; fireEventAtTheEnd = true; } } setBatchMode(true); try { Set pathHasRemoved = new HashSet(); for (TreePath path : paths) { if (!isPathSelected(path, isDigIn())) { continue; // for non batch mode scenario, check if it is already deselected by removing its parent possibly } TreePath upperMostSelectedAncestor = null; if (_toBeAdded.contains(path)) { _toBeAdded.remove(path); addToExistingSet(pathHasRemoved, path); continue; } // check if its ancestor has already been removed. If so, do nothing boolean findAncestor = false; for (TreePath removedPath : pathHasRemoved) { if (removedPath.isDescendant(path)) { findAncestor = true; break; } } if (findAncestor) { continue; } // remove all children path added by other removal Set pathToRemoved = new HashSet(); for (TreePath pathToAdded : _toBeAdded) { if (path.isDescendant(pathToAdded)) { pathToRemoved.add(pathToAdded); } } _toBeAdded.removeAll(pathToRemoved); // find a parent path added by other removal, then use that parent to do following actions for (TreePath pathToAdded : _toBeAdded) { if (pathToAdded.isDescendant(path)) { upperMostSelectedAncestor = pathToAdded; break; } } TreePath parent = path.getParentPath(); Stack stack = new Stack(); while (parent != null && (upperMostSelectedAncestor == null ? !isPathSelected(parent) : parent != upperMostSelectedAncestor)) { stack.push(parent); parent = parent.getParentPath(); } if (parent != null) stack.push(parent); else { delegateRemoveSelectionPaths(new TreePath[]{path}); addToExistingSet(pathHasRemoved, path); continue; } 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 = getChildrenCount(node); for (int i = 0; i < childCount; i++) { Object childNode = getChild(node, i); if (!JideSwingUtilities.equals(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}); addToExistingSet(pathHasRemoved, path); } } finally { if (isSingleEventMode() && fireEventAtTheEnd) { setBatchMode(false); } _fireEvent = true; if (isSingleEventMode() && fireEventAtTheEnd) { notifyPathChange(paths, false, paths[0]); } else { setBatchMode(false); } } } /** * Get the child of node in the designated index. * * @param node the parent node * @param i the child index * @return the child node */ protected Object getChild(Object node, int i) { return _model.getChild(node, i); } /** * Get the children count * * @param node the parent node * @return the children count of the parent node. */ protected int getChildrenCount(Object node) { return _model.getChildCount(node); } private void addToExistingSet(Set pathHasOperated, TreePath pathToOperate) { if (pathHasOperated.contains(pathToOperate)) { return; // it is already removed } for (TreePath path : pathHasOperated) { if (path.isDescendant(pathToOperate)) { return; // its parent is removed, no need to add it } } // remove all children path exists in the set Set duplicatePathToErase = new HashSet(); for (TreePath path : pathHasOperated) { if (pathToOperate.isDescendant(path)) { duplicatePathToErase.add(path); } } pathHasOperated.removeAll(duplicatePathToErase); pathHasOperated.add(pathToOperate); } 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. *
*

* Case 2: Assuming none of the nodes are selected, you click on A. In this case, both modes result in the same * behavior.

  • In non-single event mode, you will get only select-A event.
  • In single event mode, you will * only get select-A too.
Case 3: Assuming b and c are selected and now you click on A.
  • 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.
As you can see, single event mode will always fire the event on the nodes you * select. However it doesn't reflect what really happened inside the selection model. So if you want to get a * complete picture of the selection state inside selection model, you should use {@link #getSelectionPaths()} to * find out. In non-single event mode, the events reflect what happened inside the selection model. So you can get a * complete picture of the exact state without asking the selection model. The downside is it will generate too many * events. With this option, you can decide which mode you want to use that is the best for your case. *

* By default, singleEventMode is set to false to be compatible with the older versions that don't have this * option. * * @param singleEventMode true or false. */ public void setSingleEventMode(boolean singleEventMode) { _singleEventMode = singleEventMode; } /** * Notifies listeners of a change in path. changePaths should contain instances of PathPlaceHolder. * * @param changedPaths the paths that are changed. * @param isNew is it a new path. * @param oldLeadSelection the old selection. */ protected void notifyPathChange(TreePath[] changedPaths, boolean isNew, TreePath oldLeadSelection) { if (_fireEvent) { int cPathCount = changedPaths.length; boolean[] newness = new boolean[cPathCount]; for (int counter = 0; counter < cPathCount; counter++) { newness[counter] = isNew; } TreeSelectionEvent event = new TreeSelectionEvent (this, changedPaths, newness, oldLeadSelection, leadPath); fireValueChanged(event); } } // do not use it for now private boolean _batchMode = false; boolean isBatchMode() { return _batchMode; } public void setBatchMode(boolean batchMode) { _batchMode = batchMode; if (!_batchMode) { TreePath[] treePaths = _toBeRemoved.toArray(new TreePath[_toBeRemoved.size()]); _toBeRemoved.clear(); super.removeSelectionPaths(treePaths); treePaths = _toBeAdded.toArray(new TreePath[_toBeAdded.size()]); _toBeAdded.clear(); super.addSelectionPaths(treePaths); } } private Set _toBeAdded = new HashSet(); private Set _toBeRemoved = new HashSet(); private void delegateRemoveSelectionPaths(TreePath[] paths) { if (!isBatchMode()) { super.removeSelectionPaths(paths); } else { for (TreePath path : paths) { _toBeRemoved.add(path); _toBeAdded.remove(path); } } } // private void delegateRemoveSelectionPath(TreePath path) { // if (!isBatchMode()) { // super.removeSelectionPath(path); // } // else { // _toBeRemoved.add(path); // _toBeAdded.remove(path); // } // // } // private void delegateAddSelectionPaths(TreePath[] paths) { if (!isBatchMode()) { super.addSelectionPaths(paths); } else { for (TreePath path : paths) { addToExistingSet(_toBeAdded, path); _toBeRemoved.remove(path); } } } // private void delegateAddSelectionPath(TreePath path) { // if (!isBatchMode()) { // super.addSelectionPath(path); // } // else { // _toBeAdded.add(path); // _toBeRemoved.remove(path); // } // } // public void treeNodesChanged(TreeModelEvent e) { revalidateSelectedTreePaths(); } public void treeNodesInserted(TreeModelEvent e) { } public void treeNodesRemoved(TreeModelEvent e) { revalidateSelectedTreePaths(); } private boolean isTreePathValid(TreePath path) { Object parent = _model.getRoot(); for (int i = 0; i < path.getPathCount(); i++) { Object pathComponent = path.getPathComponent(i); if (i == 0) { if (pathComponent != parent) { return false; } } else { boolean found = false; for (int j = 0; j < getChildrenCount(parent); j++) { Object child = getChild(parent, j); if (child == pathComponent) { found = true; break; } } if (!found) { return false; } parent = pathComponent; } } return true; } public void treeStructureChanged(TreeModelEvent e) { revalidateSelectedTreePaths(); } private void revalidateSelectedTreePaths() { TreePath[] treePaths = getSelectionPaths(); if (treePaths != null) { for (TreePath treePath : treePaths) { if (treePath != null && !isTreePathValid(treePath)) { super.removeSelectionPath(treePath); } } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy