/* * Copyright 2006,2007 Enrico Boldrini, Lorenzo Bigagli This file is part of * CheckboxTree. CheckboxTree is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or (at your * option) any later version. CheckboxTree 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 CheckboxTree; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ package net.sourceforge.pmd.jedit.checkboxtree; import java.awt.Rectangle; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import javax.swing.JTree; import javax.swing.tree.TreeCellRenderer; import javax.swing.tree.TreeModel; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; /** * A tree whose nodes may be checked (e.g. the component usually found in * software installers, that allows for selecting the features to * install/uninstall). If a node has some child of different checking status is * greyed. You can use the same constructors of Jtree to instantiate a new * CheckboxTree Example from a TreeNode: * * <pre> * DefaultMutableTreeNode root = new DefaultMutableTreeNode("root"); * root.add(new DefaultMutableTreeNode("child A")); * root.add(new DefaultMutableTreeNode("child B")); * CheckboxTree CheckboxTree = new CheckboxTree(root); * </pre> * * Example from a TreeModel: * * <pre> * DefaultTreeModel dtm = new DefaultTreeModel(root); * * CheckboxTree CheckboxTree = new CheckboxTree(root); * </pre> * * Default constructor (useful for gui builders): * * <pre> * CheckboxTree CheckboxTree = new CheckboxTree(); * </pre> * * Then you can set the checking propagation style: * * <pre> * CheckboxTree.getCheckingModel().setCheckingMode(TreeCheckingModel.CheckingMode.SIMPLE); * CheckboxTree.getCheckingModel().setCheckingMode(TreeCheckingModel.CheckingMode.PROPAGATE); * CheckboxTree.getCheckingModel().setCheckingMode(TreeCheckingModel.CheckingMode.PROPAGATE_PRESERVING_CHECK); * CheckboxTree.getCheckingModel().setCheckingMode(TreeCheckingModel.CheckingMode.PROPAGATE_PRESERVING_UNCHECK); * </pre> * * You can also set the model at a later time using: * * <pre> * CheckboxTree.setModel(aTreeModel); * </pre> * * There are two methods that return the paths that are in the checking: * * <pre> * TreePath[] tp = CheckboxTree.getCheckingPaths(); * * TreePath[] tp = CheckboxTree.getCheckingRoots(); * </pre> * * You can also add/remove a listener of a TreeCheckingEvent in this way: * * <pre> * CheckboxTree.addTreeCheckingListener(new TreeCheckingListener() { * public void valueChanged(TreeCheckingEvent e) { * System.out.println("Checked paths changed: user clicked on " + (e.getLeadingPath().getLastPathComponent())); * } * }); * </pre> * * @author Enrico Boldrini * @author Lorenzo Bigagli */ public class CheckboxTree extends JTree { private TreeCheckingModel checkingModel; private class NodeCheckListener extends MouseAdapter { @Override public void mousePressed( MouseEvent e ) { // we use mousePressed instead of mouseClicked for performance int x = e.getX(); int y = e.getY(); int row = getRowForLocation( x, y ); if ( row == -1 ) { // click outside any node return ; } Rectangle rect = getRowBounds( row ); if ( rect == null ) { // clic on an invalid node return ; } if ( ( ( CheckboxTreeCellRenderer ) getCellRenderer() ).isOnHotspot( x - rect.x, y - rect.y ) ) { getCheckingModel().toggleCheckingPath( getPathForRow( row ) ); } } } /** * For GUI builders. It returns a CheckboxTree with a default tree * model to show something interesting. Creates a CheckboxTree with * visible handles, a default CheckboxTreeCellRenderer and a default * TreeCheckingModel. */ public CheckboxTree() { super( getDefaultTreeModel() ); initialize(); } /** * Creates a CheckboxTree with visible handles, a default * CheckboxTreeCellRenderer and a default TreeCheckingModel. The tree is * created using the specified data model. Mouse clicks are validated * against the cell renderer (via isOnCheckBox) and, eventually, passed * on to the checking model. * * @param root the root of the tree */ public CheckboxTree( TreeNode root ) { super( root ); initialize(); } /** * Creates a CheckboxTree with visible handles, a default * CheckboxTreeCellRenderer and a default TreeCheckingModel. The tree is * created using the specified data model. Mouse clicks are validated * against the cell renderer (via isOnCheckBox) and, eventually, passed * on to the checking model. */ public CheckboxTree( TreeModel treemodel ) { super( treemodel ); initialize(); } /** * Convenience initialization method. */ private void initialize() { setCheckingModel( new DefaultTreeCheckingModel( this.treeModel ) ); DefaultCheckboxTreeCellRenderer cellRenderer = new DefaultCheckboxTreeCellRenderer(); cellRenderer.setLeafIcon(null); /// danson, set these to null cellRenderer.setClosedIcon(null); /// cellRenderer.setOpenIcon(null); /// setCellRenderer( cellRenderer ); addMouseListener( new NodeCheckListener() ); this.selectionModel.setSelectionMode( TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION ); setShowsRootHandles( true ); putClientProperty( "JTree.lineStyle", "Angled" ); // for Metal L&F } /** * Sets the <code>CheckboxTreeCellRenderer</code> that will be used to * draw each cell. * * @param x the <code>TreeCellRenderer</code> that is to render each * cell * @beaninfo bound: true description: The TreeCellRenderer that will be * used to draw each cell. */ public void setCellRenderer( CheckboxTreeCellRenderer x ) { super.setCellRenderer( x ); } /** * Sets the <code>TreeCellRenderer</code> that will be used to draw * each cell. This method is deprecated. Use the other setter for * {@link CheckboxTreeCellRenderer} * * @param x the <code>TreeCellRenderer</code> that is to render each * cell * @beaninfo bound: true description: The TreeCellRenderer that will be * used to draw each cell. */ @Override @Deprecated public void setCellRenderer( TreeCellRenderer x ) { super.setCellRenderer( x ); } /** * Sets the TreeModel and links it to the existing checkingModel. */ @Override public void setModel( TreeModel newModel ) { super.setModel( newModel ); if ( this.checkingModel != null ) { this.checkingModel.setTreeModel( newModel ); } } /** * @return Returns the TreeCheckingModel. */ public TreeCheckingModel getCheckingModel() { return this.checkingModel; } /** * Set the checking model of this CheckboxTree. * * @param newCheckingModel The new TreeCheckingModel. */ public void setCheckingModel( TreeCheckingModel newCheckingModel ) { /* * we must unlink the old TreeCheckingModel from the model of this tree * and link the new one to it. */ TreeCheckingModel oldCheckingModel = this.checkingModel; if ( oldCheckingModel != null ) { // null the model in the old TreeCheckingModel to avoid dangling // pointers oldCheckingModel.setTreeModel( null ); } // TODO: check newCheckingModel for == null and optionally substitute // with EmptyCheckingModel... this.checkingModel = newCheckingModel; if ( newCheckingModel != null ) { newCheckingModel.setTreeModel( getModel() ); // add a treeCheckingListener to repaint upon checking // modifications newCheckingModel.addTreeCheckingListener( new TreeCheckingListener() { public void valueChanged( TreeCheckingEvent e ) { repaint(); } } ); } } /** * Return paths that are in the checking. */ public TreePath[] getCheckingPaths() { return getCheckingModel().getCheckingPaths(); } /** * @return Returns the paths that are in the checking set and are the * (upper) roots of checked trees. */ public TreePath[] getCheckingRoots() { return getCheckingModel().getCheckingRoots(); } /** * Clears the checking. */ public void clearChecking() { getCheckingModel().clearChecking(); } /** * Add paths in the checking. */ public void addCheckingPaths( TreePath[] paths ) { getCheckingModel().addCheckingPaths( paths ); } /** * Add a path in the checking. */ public void addCheckingPath( TreePath path ) { getCheckingModel().addCheckingPath( path ); } /** * Set path in the checking. */ public void setCheckingPath( TreePath path ) { getCheckingModel().setCheckingPath( path ); } /** * Set paths that are in the checking. */ public void setCheckingPaths( TreePath[] paths ) { getCheckingModel().setCheckingPaths( paths ); } /** * @return Returns the paths that are in the greying. */ public TreePath[] getGreyingPaths() { return getCheckingModel().getGreyingPaths(); } /** * Adds a listener for <code>TreeChecking</code> events. * * @param tsl the <code>TreeCheckingListener</code> that will be * notified when a node is checked */ public void addTreeCheckingListener( TreeCheckingListener tsl ) { this.checkingModel.addTreeCheckingListener( tsl ); } /** * Removes a <code>TreeChecking</code> listener. * * @param tsl the <code>TreeChckingListener</code> to remove */ public void removeTreeCheckingListener( TreeCheckingListener tsl ) { this.checkingModel.removeTreeCheckingListener( tsl ); } /** * Expand completely a tree */ public void expandAll() { expandSubTree( getPathForRow( 0 ) ); } private void expandSubTree( TreePath path ) { expandPath( path ); Object node = path.getLastPathComponent(); int childrenNumber = getModel().getChildCount( node ); TreePath[] childrenPath = new TreePath[ childrenNumber ]; for ( int childIndex = 0; childIndex < childrenNumber; childIndex++ ) { childrenPath[ childIndex ] = path.pathByAddingChild( getModel().getChild( node, childIndex ) ); expandSubTree( childrenPath[ childIndex ] ); } } /** * @return a string representation of the tree, including the checking, * enabling and greying sets. */ @Override public String toString() { String retVal = super.toString(); TreeCheckingModel tcm = getCheckingModel(); if ( tcm != null ) { return retVal + "\n" + tcm.toString(); } return retVal; } }