
com.jidesoft.swing.CheckBoxTree Maven / Gradle / Ivy
/*
* @(#)CheckBoxTree.java 8/11/2005
*
* Copyright 2002 - 2005 JIDE Software Inc. All rights reserved.
*/
package com.jidesoft.swing;
import javax.swing.*;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.text.Position;
import javax.swing.tree.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.Serializable;
import java.util.Hashtable;
import java.util.Vector;
import java.util.List;
import java.util.ArrayList;
/**
* CheckBoxTree is a special JTree which uses JCheckBox as the tree renderer. In addition to regular JTree's features,
* it also allows you select any number of tree nodes in the tree by selecting the check boxes. To select an element,
* user can mouse click on the check box, or select one or several tree nodes and press SPACE key to toggle the check
* box selection for all selected tree nodes.
*
* In order to retrieve which tree paths are selected, you need to call {@link #getCheckBoxTreeSelectionModel()}. It
* will return the selection model that keeps track of which tree paths have been checked. For example {@link
* CheckBoxTreeSelectionModel#getSelectionPaths()} will give the list of paths which have been checked.
*/
public class CheckBoxTree extends JTree {
public static final String PROPERTY_CHECKBOX_ENABLED = "checkBoxEnabled";
public static final String PROPERTY_CLICK_IN_CHECKBOX_ONLY = "clickInCheckBoxOnly";
public static final String PROPERTY_DIG_IN = "digIn";
protected CheckBoxTreeCellRenderer _treeCellRenderer;
private CheckBoxTreeSelectionModel _checkBoxTreeSelectionModel;
private boolean _checkBoxEnabled = true;
private boolean _clickInCheckBoxOnly = true;
private PropertyChangeListener _modelChangeListener;
private TristateCheckBox _checkBox;
private boolean _selectPartialOnToggling = true;
public CheckBoxTree() {
init();
}
public CheckBoxTree(Object[] value) {
super(value);
init();
}
public CheckBoxTree(Vector> value) {
super(value);
init();
}
public CheckBoxTree(Hashtable, ?> value) {
super(value);
init();
}
public CheckBoxTree(TreeNode root) {
super(root);
init();
}
public CheckBoxTree(TreeNode root, boolean asksAllowsChildren) {
super(root, asksAllowsChildren);
init();
}
public CheckBoxTree(TreeModel newModel) {
super(newModel);
init();
}
/**
* Initialize the CheckBoxTree.
*/
protected void init() {
_checkBoxTreeSelectionModel = createCheckBoxTreeSelectionModel(getModel());
_checkBoxTreeSelectionModel.setTree(this);
Handler handler = createHandler();
JideSwingUtilities.insertMouseListener(this, handler, 0);
addKeyListener(handler);
_checkBoxTreeSelectionModel.addTreeSelectionListener(handler);
if (_modelChangeListener == null) {
_modelChangeListener = new ModelPropertyChangeListener();
}
addPropertyChangeListener(JTree.SELECTION_MODEL_PROPERTY, _modelChangeListener);
addPropertyChangeListener("model", _modelChangeListener);
updateRowMapper();
}
private class ModelPropertyChangeListener implements PropertyChangeListener, Serializable {
private static final long serialVersionUID = 521336220746879977L;
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (JTree.SELECTION_MODEL_PROPERTY.equals(evt.getPropertyName())) {
updateRowMapper();
}
if ("model".equals(evt.getPropertyName()) && evt.getNewValue() instanceof TreeModel) {
_checkBoxTreeSelectionModel.setModel((TreeModel) evt.getNewValue());
}
}
}
/**
* Creates the CheckBoxTreeSelectionModel.
*
* @param model the tree model.
* @return the CheckBoxTreeSelectionModel.
*/
protected CheckBoxTreeSelectionModel createCheckBoxTreeSelectionModel(TreeModel model) {
return new CheckBoxTreeSelectionModel(model);
}
/**
* RowMapper is necessary for contiguous selection.
*/
private void updateRowMapper() {
_checkBoxTreeSelectionModel.setRowMapper(getSelectionModel().getRowMapper());
}
private TreeCellRenderer _defaultRenderer;
/**
* Gets the cell renderer with check box.
*
* @return CheckBoxTree's own cell renderer which has the check box. The actual cell renderer you set by
* setCellRenderer() can be accessed by using {@link #getActualCellRenderer()}.
*/
@Override
public TreeCellRenderer getCellRenderer() {
TreeCellRenderer cellRenderer = getActualCellRenderer();
if (cellRenderer == null) {
cellRenderer = getDefaultRenderer();
}
if (_treeCellRenderer == null) {
_treeCellRenderer = createCellRenderer(cellRenderer);
}
else {
_treeCellRenderer.setActualTreeRenderer(cellRenderer);
}
return _treeCellRenderer;
}
private TreeCellRenderer getDefaultRenderer() {
if (_defaultRenderer == null)
_defaultRenderer = new DefaultTreeCellRenderer();
return _defaultRenderer;
}
/**
* Gets the actual cell renderer. Since CheckBoxTree has its own check box cell renderer, this method will give you
* access to the actual cell renderer which is either the default tree cell renderer or the cell renderer you set
* using {@link #setCellRenderer(javax.swing.tree.TreeCellRenderer)}.
*
* @return the actual cell renderer
*/
public TreeCellRenderer getActualCellRenderer() {
if (_treeCellRenderer != null) {
return _treeCellRenderer.getActualTreeRenderer();
}
else {
return super.getCellRenderer();
}
}
@Override
public void setCellRenderer(TreeCellRenderer x) {
if (x == null) {
x = getDefaultRenderer();
}
super.setCellRenderer(x);
if (_treeCellRenderer != null) {
_treeCellRenderer.setActualTreeRenderer(x);
}
}
/**
* Creates the cell renderer.
*
* @param renderer the actual renderer for the tree node. This method will return a cell renderer that use a check
* box and put the actual renderer inside it.
* @return the cell renderer.
*/
protected CheckBoxTreeCellRenderer createCellRenderer(TreeCellRenderer renderer) {
final CheckBoxTreeCellRenderer checkBoxTreeCellRenderer = new CheckBoxTreeCellRenderer(renderer, getCheckBox());
addPropertyChangeListener(CELL_RENDERER_PROPERTY, new CellRendererPropertyChangeListener(checkBoxTreeCellRenderer));
return checkBoxTreeCellRenderer;
}
private class CellRendererPropertyChangeListener implements PropertyChangeListener, Serializable {
private static final long serialVersionUID = -5127656346976320157L;
private CheckBoxTreeCellRenderer _checkBoxTreeCellRenderer;
public CellRendererPropertyChangeListener(CheckBoxTreeCellRenderer renderer) {
_checkBoxTreeCellRenderer = renderer;
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
TreeCellRenderer treeCellRenderer = (TreeCellRenderer) evt.getNewValue();
if (treeCellRenderer != _checkBoxTreeCellRenderer) {
_checkBoxTreeCellRenderer.setActualTreeRenderer(treeCellRenderer);
}
else {
_checkBoxTreeCellRenderer.setActualTreeRenderer(null);
}
}
}
/**
* Creates the mouse listener and key listener used by CheckBoxTree.
*
* @return the Handler.
*/
protected Handler createHandler() {
return new Handler(this);
}
/**
* Get the CheckBox used for CheckBoxTreeCellRenderer.
*
* @see #setCheckBox(TristateCheckBox)
* @return the check box.
*/
public TristateCheckBox getCheckBox() {
return _checkBox;
}
/**
* Set the CheckBox used for CheckBoxTreeCellRenderer.
*
* By default, it's null. CheckBoxTreeCellRenderer then will create a default TristateCheckBox.
*
* @param checkBox the check box
*/
public void setCheckBox(TristateCheckBox checkBox) {
if (_checkBox != checkBox) {
_checkBox = checkBox;
_treeCellRenderer = null;
revalidate();
repaint();
}
}
/**
* Gets the flag indicating if toggling should select or deselect the partially selected node.
*
* @return true if select first. Otherwise false.
* @see #setSelectPartialOnToggling(boolean)
*/
public boolean isSelectPartialOnToggling() {
return _selectPartialOnToggling;
}
/**
* Sets the flag indicating if toggling should select or deselect the partially selected node.
*
* By default, the value is true to keep original behavior.
*
* @param selectPartialOnToggling the flag
*/
public void setSelectPartialOnToggling(boolean selectPartialOnToggling) {
_selectPartialOnToggling = selectPartialOnToggling;
}
/**
* CheckBoxTree's mouse event handler, key event handler and tree selection event handler.
*/
protected static class Handler implements MouseListener, KeyListener, TreeSelectionListener {
protected CheckBoxTree _tree;
int _hotspot = new JCheckBox().getPreferredSize().width;
private int _toggleCount = -1;
/**
* The constructor.
*
* @param tree the CheckBoxTree
*/
public Handler(CheckBoxTree tree) {
_tree = tree;
}
/**
* Gets the tree path according to the mouse event.
*
* @param e the mouse event
* @return the tree path the mouse is over. null if no tree node is under the mouse position.
*/
protected TreePath getTreePathForMouseEvent(MouseEvent e) {
if (!SwingUtilities.isLeftMouseButton(e)) {
return null;
}
if (!_tree.isCheckBoxEnabled()) {
return null;
}
TreePath path = _tree.getPathForLocation(e.getX(), e.getY());
if (path == null)
return null;
if (clicksInCheckBox(e, path) || !_tree.isClickInCheckBoxOnly()) {
return path;
}
else {
return null;
}
}
/**
* Checks if the mouse event happens for the tree path.
*
* @param e the mouse event
* @param path the tree path
* @return true if the mouse event need change the state of the tree node. Otherwise false.
*/
protected boolean clicksInCheckBox(MouseEvent e, TreePath path) {
if (!_tree.isCheckBoxVisible(path)) {
return false;
}
else {
Rectangle bounds = _tree.getPathBounds(path);
if (_tree.getComponentOrientation().isLeftToRight()) {
return e.getX() < bounds.x + _hotspot;
}
else {
return e.getX() > bounds.x + bounds.width - _hotspot;
}
}
}
private TreePath preventToggleEvent(MouseEvent e) {
TreePath pathForMouseEvent = getTreePathForMouseEvent(e);
if (pathForMouseEvent != null) {
int toggleCount = _tree.getToggleClickCount();
if (toggleCount != -1) {
_toggleCount = toggleCount;
_tree.setToggleClickCount(-1);
}
}
return pathForMouseEvent;
}
public void mouseClicked(MouseEvent e) {
if (e.isConsumed()) {
return;
}
preventToggleEvent(e);
}
public void mousePressed(MouseEvent e) {
if (e.isConsumed()) {
return;
}
TreePath path = preventToggleEvent(e);
if (path != null) {
toggleSelections(new TreePath[] {path});
e.consume();
}
}
public void mouseReleased(MouseEvent e) {
if (e.isConsumed()) {
return;
}
TreePath path = preventToggleEvent(e);
if (path != null) {
e.consume();
}
if (_toggleCount != -1) {
_tree.setToggleClickCount(_toggleCount);
}
}
public void mouseEntered(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
}
public void keyPressed(KeyEvent e) {
if (e.isConsumed()) {
return;
}
if (!_tree.isCheckBoxEnabled()) {
return;
}
if (e.getModifiers() == 0 && e.getKeyChar() == KeyEvent.VK_SPACE)
toggleSelections();
}
public void keyTyped(KeyEvent e) {
}
public void keyReleased(KeyEvent e) {
}
public void valueChanged(TreeSelectionEvent e) {
_tree.treeDidChange();
}
/**
* Toggles the selected paths' selection state.
*/
protected void toggleSelections() {
TreePath[] treePaths = _tree.getSelectionPaths();
toggleSelections(treePaths);
}
private void toggleSelections(TreePath[] treePaths) {
if (treePaths == null || treePaths.length == 0 || !_tree.isEnabled()) {
return;
}
if (treePaths.length == 1 && !_tree.isCheckBoxEnabled(treePaths[0])) {
return;
}
CheckBoxTreeSelectionModel selectionModel = _tree.getCheckBoxTreeSelectionModel();
List pathToAdded = new ArrayList();
List pathToRemoved = new ArrayList();
for (TreePath treePath : treePaths) {
boolean selected = selectionModel.isPathSelected(treePath, selectionModel.isDigIn());
if (selected) {
pathToRemoved.add(treePath);
}
else {
if (!_tree.isSelectPartialOnToggling() && selectionModel.isPartiallySelected(treePath)) {
TreePath[] selectionPaths = selectionModel.getSelectionPaths();
if (selectionPaths != null) {
for (TreePath selectionPath : selectionPaths) {
if (selectionModel.isDescendant(selectionPath, treePath)) {
pathToRemoved.add(selectionPath);
}
}
}
}
else {
pathToAdded.add(treePath);
}
}
}
selectionModel.removeTreeSelectionListener(this);
try {
if (pathToAdded.size() > 0) {
selectionModel.addSelectionPaths(pathToAdded.toArray(new TreePath[pathToAdded.size()]));
}
if (pathToRemoved.size() > 0) {
selectionModel.removeSelectionPaths(pathToRemoved.toArray(new TreePath[pathToRemoved.size()]));
}
}
finally {
selectionModel.addTreeSelectionListener(this);
_tree.treeDidChange();
}
}
}
@Override
public TreePath getNextMatch(String prefix, int startingRow, Position.Bias bias) {
return null;
}
/**
* Gets the selection model for the check boxes. To retrieve the state of check boxes, you should use this selection
* model.
*
* @return the selection model for the check boxes.
*/
public CheckBoxTreeSelectionModel getCheckBoxTreeSelectionModel() {
return _checkBoxTreeSelectionModel;
}
/**
* Gets the value of property checkBoxEnabled. If true, user can click on check boxes on each tree node to select
* and deselect. If false, user can't click but you as developer can programmatically call API to select/deselect
* it.
*
* @return the value of property checkBoxEnabled.
*/
public boolean isCheckBoxEnabled() {
return _checkBoxEnabled;
}
/**
* Sets the value of property checkBoxEnabled.
*
* @param checkBoxEnabled true to allow to check the check box. False to disable it which means user can see whether
* a row is checked or not but they cannot change it.
*/
public void setCheckBoxEnabled(boolean checkBoxEnabled) {
if (checkBoxEnabled != _checkBoxEnabled) {
Boolean oldValue = _checkBoxEnabled ? Boolean.TRUE : Boolean.FALSE;
Boolean newValue = checkBoxEnabled ? Boolean.TRUE : Boolean.FALSE;
_checkBoxEnabled = checkBoxEnabled;
firePropertyChange(PROPERTY_CHECKBOX_ENABLED, oldValue, newValue);
repaint();
}
}
/**
* Checks if check box is enabled. There is no setter for it. The only way is to override this method to return true
* or false.
*
* However, in digIn mode, user can still select the disabled node by selecting all children nodes of that node.
* Also if user selects the parent node, the disabled children nodes will be selected too.
*
* @param path the tree path.
* @return true or false. If false, the check box on the particular tree path will be disabled.
*/
public boolean isCheckBoxEnabled(TreePath path) {
return true;
}
/**
* Checks if check box is visible. There is no setter for it. The only way is to override this method to return true
* or false.
*
* @param path the tree path.
* @return true or false. If false, the check box on the particular tree path will be disabled.
*/
public boolean isCheckBoxVisible(TreePath path) {
return true;
}
/**
* 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 getCheckBoxTreeSelectionModel().isDigIn();
}
/**
* 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 the new digIn mode.
*/
public void setDigIn(boolean digIn) {
boolean old = isDigIn();
if (old != digIn) {
getCheckBoxTreeSelectionModel().setDigIn(digIn);
firePropertyChange(PROPERTY_DIG_IN, old, digIn);
}
}
/**
* Gets the value of property clickInCheckBoxOnly. If true, user can click on check boxes on each tree node to
* select and deselect. If false, user can't click but you as developer can programmatically call API to
* select/deselect it.
*
* @return the value of property clickInCheckBoxOnly.
*/
public boolean isClickInCheckBoxOnly() {
return _clickInCheckBoxOnly;
}
/**
* Sets the value of property clickInCheckBoxOnly.
*
* @param clickInCheckBoxOnly true to allow to check the check box. False to disable it which means user can see
* whether a row is checked or not but they cannot change it.
*/
public void setClickInCheckBoxOnly(boolean clickInCheckBoxOnly) {
if (clickInCheckBoxOnly != _clickInCheckBoxOnly) {
boolean old = _clickInCheckBoxOnly;
_clickInCheckBoxOnly = clickInCheckBoxOnly;
firePropertyChange(PROPERTY_CLICK_IN_CHECKBOX_ONLY, old, _clickInCheckBoxOnly);
}
}
}