/*
* Open Source Physics software is free software as described near the bottom of this code file.
*
* For additional information and documentation on Open Source Physics please see:
* <http://www.opensourcephysics.org/>
*/
package org.opensourcephysics.tools;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.Stack;
import javax.swing.event.SwingPropertyChangeSupport;
import javax.swing.tree.DefaultTreeSelectionModel;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
/**
* A checkbox tree selection model.
*
* @author Doug Brown
*
* Based on code by Santhosh Kumar T - santhosh@in.fiorano.com
* See http://www.jroller.com/page/santhosh/20050610
*/
public class CheckTreeSelectionModel extends DefaultTreeSelectionModel {
private TreeModel model;
private PropertyChangeSupport support;
/**
* Constructor.
*
* @param model a TreeModel
*/
public CheckTreeSelectionModel(TreeModel model) {
this.model = model;
setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
support = new SwingPropertyChangeSupport(this);
}
/**
* Returns true if neither the path nor any descendant is selected.
*
* @param path the path to test
*/
public boolean isPathUnselected(TreePath path) {
if(isSelectionEmpty()) {
return true; // nothing is selected
}
if(isPathOrAncestorSelected(path)) {
return false; // path is selected
}
TreePath[] selectionPaths = getSelectionPaths();
for(int i = 0; i<selectionPaths.length; i++) {
if(path.isDescendant(selectionPaths[i])) { // found a selected descendant
return false;
}
}
return true;
}
/**
* Returns true if the path or any ancestor is selected.
*
* @param path the path to test
*/
public boolean isPathOrAncestorSelected(TreePath path) {
while((path!=null)&&!isPathSelected(path)) {
path = path.getParentPath();
}
return path!=null;
}
public void setSelectionPaths(TreePath[] paths) {
super.clearSelection();
addSelectionPaths(paths);
}
/**
* Adds paths to the current selection
*
* @param paths the paths to add
*/
public void addSelectionPaths(TreePath[] paths) {
if(paths==null) {
return;
}
TreePath[] prev = getSelectionPaths();
// unselect all descendants of paths
for(int i = 0; i<paths.length; i++) {
if(isSelectionEmpty()) {
break; // nothing to unselect
}
TreePath path = paths[i];
TreePath[] selectionPaths = getSelectionPaths();
ArrayList<TreePath> toBeRemoved = new ArrayList<TreePath>();
for(int j = 0; j<selectionPaths.length; j++) {
if(path.isDescendant(selectionPaths[j])) {
toBeRemoved.add(selectionPaths[j]);
}
}
super.removeSelectionPaths(toBeRemoved.toArray(new TreePath[0]));
}
// if all siblings selected, unselect them and select parent recursively,
// otherwise select the path
for(int i = 0; i<paths.length; i++) {
TreePath path = paths[i];
TreePath temp = null;
while(isSiblingsSelected(path)) {
temp = path;
if(path.getParentPath()==null) {
break;
}
path = path.getParentPath();
}
if(temp!=null) {
if(temp.getParentPath()!=null) {
addSelectionPath(temp.getParentPath());
} else { // selected path is root, so unselect all others
if(!isSelectionEmpty()) {
removeSelectionPaths(getSelectionPaths());
}
super.addSelectionPaths(new TreePath[] {temp});
}
} else {
super.addSelectionPaths(new TreePath[] {path});
}
}
support.firePropertyChange("treepaths", prev, getSelectionPaths()); //$NON-NLS-1$
}
/**
* Removes paths from the current selection
*
* @param paths the paths to remove
*/
public void removeSelectionPaths(TreePath[] paths) {
if(isSelectionEmpty()) {
return; // nothing to remove
}
TreePath[] prev = getSelectionPaths();
for(int i = 0; i<paths.length; i++) {
TreePath path = paths[i];
if(path.getPathCount()==1) { // root node
super.removeSelectionPaths(new TreePath[] {path});
} else {
if(isPathSelected(path)) {
super.removeSelectionPaths(new TreePath[] {path});
} else {
unselectAncestor(path);
}
}
}
support.firePropertyChange("treepaths", prev, getSelectionPaths()); //$NON-NLS-1$
}
/**
* Adds a PropertyChangeListener.
*
* @param listener the object requesting property change notification
*/
public void addPropertyChangeListener(PropertyChangeListener listener) {
support.addPropertyChangeListener(listener);
}
/**
* Removes a PropertyChangeListener.
*
* @param listener the listener requesting removal
*/
public void removePropertyChangeListener(PropertyChangeListener listener) {
support.removePropertyChangeListener(listener);
}
/**
* Determines whether all siblings of given path are selected
*
* @param path the path to test
* @return true if all siblings selected
*/
private boolean isSiblingsSelected(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.equals(node)) {
continue;
}
if(!isPathSelected(parent.pathByAddingChild(childNode))) {
return false;
}
}
return true;
}
/**
* Unselects the ancestor of a path and selects ancestor's descendants
* other than those for which the path is a descendant.
*
* @param path the path
*/
private void unselectAncestor(TreePath path) {
// find selected ancestor
Stack<TreePath> stack = new Stack<TreePath>();
stack.push(path);
TreePath ancestor = path.getParentPath();
while((ancestor!=null)&&!isPathSelected(ancestor)) {
stack.push(ancestor);
ancestor = ancestor.getParentPath();
}
if(ancestor==null) { // no selected ancestors!
return;
}
stack.push(ancestor);
// for each path in the stack, unselect it and select all child nodes
while(!stack.isEmpty()) {
TreePath next = stack.pop();
super.removeSelectionPaths(new TreePath[] {next});
if(stack.isEmpty()) {
return;
}
Object node = next.getLastPathComponent();
int childCount = model.getChildCount(node);
for(int i = 0; i<childCount; i++) {
Object child = model.getChild(node, i);
super.addSelectionPaths(new TreePath[] {next.pathByAddingChild(child)});
}
}
}
}
/*
* Open Source Physics software is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License (GPL) as
* published by the Free Software Foundation; either version 2 of the License,
* or(at your option) any later version.
*
* Code that uses any portion of the code in the org.opensourcephysics package
* or any subpackage (subdirectory) of this package must must also be be
* released under the GNU GPL license.
*
* This software 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; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
* Suite 330, Boston MA 02111-1307 USA or view the license online at
* http://www.gnu.org/copyleft/gpl.html
*
* Copyright (c) 2007 The Open Source Physics project
* http://www.opensourcephysics.org
*/