/* * Copyright 2007-2010 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 it.cnr.imaa.essi.lablib.gui.checkboxtree; import java.awt.Rectangle; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.util.ArrayList; import java.util.Arrays; import java.util.Random; import javax.swing.JFrame; import javax.swing.JTree; import javax.swing.WindowConstants; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.TreeModel; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; /* * questa รจ la classe del francese (ticket #5 ). Non mi funziona. Da togliere * appena possibile. */ /** * A tree whose nodes may be checked (e.g. the widget usually found in software * installers, that allows to select which 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: * *
 * DefaultMutableTreeNode root = new DefaultMutableTreeNode("root");
 * root.add(new DefaultMutableTreeNode("child A"));
 * root.add(new DefaultMutableTreeNode("child B"));
 * CheckboxTree CheckboxTree = new CheckboxTree(root);
 * 
* * Example from a TreeModel: * *
 * DefaultTreeModel dtm = new DefaultTreeModel(root);
 * 
 * CheckboxTree CheckboxTree = new CheckboxTree(root);
 * 
* * Default constructor (useful for gui builders): * *
 * CheckboxTree CheckboxTree = new CheckboxTree();
 * 
* * Then you can set the checking propagation style: * *
 * 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);
 * 
* * You can also set the model at a later time using: * *
 * CheckboxTree.setModel(aTreeModel);
 * 
* * There are two methods that return the paths that are in the checking: * *
 * TreePath[] tp = CheckboxTree.getCheckingPaths();
 * 
 * TreePath[] tp = CheckboxTree.getCheckingRoots();
 * 
* * You can also add/remove a listener of a TreeCheckingEvent in this way: * *
 * CheckboxTree.addTreeCheckingListener(new TreeCheckingListener() {
 *     public void valueChanged(TreeCheckingEvent e) {
 * 	System.out.println("Checked paths changed: user clicked on " + (e.getLeadingPath().getLastPathComponent()));
 *     }
 * });
 * 
* * @author Enrico Boldrini * @author Lorenzo Bigagli */ public class CheckboxTreeOlivier extends JTree { private class NodeCheckListener extends MouseAdapter { @Override public void mousePressed(MouseEvent e) { if (!isEnabled()) { return; } // 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)) { // NEW TreePath clickedPath = getPathForRow(row); getCheckingModel().toggleCheckingPath(clickedPath); // if the clicked node was selected with another ones, set (or // unset) all of them TreePath[] selectionPaths = getSelectionPaths(); if (selectionPaths != null && Arrays.asList(selectionPaths).contains(clickedPath)) { if (getCheckingModel().isPathChecked(getPathForRow(row))) { getCheckingModel().addCheckingPaths(selectionPaths); } else { getCheckingModel().removeCheckingPaths(selectionPaths); } } } } }; /* * Temporary solution for enabling spacebar checking. Should make use of * InputMaps? */ private class SpaceListener extends KeyAdapter { @Override public void keyPressed(KeyEvent e) { if (!isEnabled()) { return; } TreePath path = CheckboxTreeOlivier.this.getSelectionPath(); if (e.getKeyCode() == KeyEvent.VK_SPACE) { if (path != null) { TreeCheckingModel cm = CheckboxTreeOlivier.this.getCheckingModel(); cm.toggleCheckingPath(path); } } } @Override public void keyReleased(KeyEvent e) { } @Override public void keyTyped(KeyEvent e) { } } private TreeCheckingModel checkingModel; /** * 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 CheckboxTreeOlivier() { super(getDefaultTreeModel()); initialize(); } /** * Creates a CheckboxTree with visible handles, a default * CheckboxTreeCellRenderer and a default TreeCheckingModel. The tree is * based on the specified tree model. */ public CheckboxTreeOlivier(TreeModel treemodel) { super(treemodel); initialize(); } /** * Creates a CheckboxTree with visible handles, a default * CheckboxTreeCellRenderer and a default TreeCheckingModel. The tree root * is the specified tree node. * * @param root the root of the tree */ public CheckboxTreeOlivier(TreeNode root) { super(root); initialize(); } /** * Add a path in the checking. */ public void addCheckingPath(TreePath path) { getCheckingModel().addCheckingPath(path); } /** * Add paths in the checking. */ public void addCheckingPaths(TreePath[] paths) { getCheckingModel().addCheckingPaths(paths); } /** * Adds a listener for TreeChecking events. * * @param tsl the TreeCheckingListener that will be notified * when a node is checked */ public void addTreeCheckingListener(TreeCheckingListener tsl) { this.checkingModel.addTreeCheckingListener(tsl); } /** * Clears the checking. */ public void clearChecking() { getCheckingModel().clearChecking(); } /** * 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 Returns the TreeCheckingModel. */ public TreeCheckingModel getCheckingModel() { return this.checkingModel; } /** * 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(); } /** * @return Returns the paths that are in the greying. */ public TreePath[] getGreyingPaths() { return getCheckingModel().getGreyingPaths(); } /** * Convenience initialization method. NEW */ private void initialize() { setCheckingModel(new DefaultTreeCheckingModel(this.treeModel)); setCellRenderer(new DefaultCheckboxTreeCellRenderer()); MouseListener[] listener = getMouseListeners(); for (MouseListener mouseListener : listener) removeMouseListener(mouseListener); addMouseListener(new NodeCheckListener()); for (MouseListener mouseListener : listener) addMouseListener(mouseListener); addKeyListener(new SpaceListener()); this.selectionModel.setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION); setShowsRootHandles(true); putClientProperty("JTree.lineStyle", "Angled");// for Metal L&F } /** * Returns true if the item identified by the path is currently checked. * * @param path a TreePath identifying a node * @return true if the node is checked */ public boolean isPathChecked(TreePath path) { return getCheckingModel().isPathChecked(path); } /** * Remove a path from the checking. */ public void removeCheckingPath(TreePath path) { getCheckingModel().removeCheckingPath(path); } /** * Remove paths from the checking. */ public void removeCheckingPaths(TreePath[] paths) { getCheckingModel().removeCheckingPaths(paths); } /** * Removes a TreeChecking listener. * * @param tsl the TreeChckingListener to remove */ public void removeTreeCheckingListener(TreeCheckingListener tsl) { this.checkingModel.removeTreeCheckingListener(tsl); } /** * Sets the CheckboxTreeCellRenderer that will be used to draw * each cell. * * @param x the TreeCellRenderer that is to render each cell */ public void setCellRenderer(CheckboxTreeCellRenderer x) { super.setCellRenderer(x); } /** * Set the checking model of this CheckboxTree. * * @param newCheckingModel The new TreeCheckingModel. */ public void setCheckingModel(TreeCheckingModel newCheckingModel) { /* * in case we are dealing with DefaultTreeCheckingModel, we link/unlink * it from the model of this tree */ TreeCheckingModel oldCheckingModel = this.checkingModel; if (oldCheckingModel != null && oldCheckingModel instanceof DefaultTreeCheckingModel) { // null the model to avoid dangling pointers ((DefaultTreeCheckingModel) oldCheckingModel).setTreeModel(null); } // TODO: what if newCheckingModel == null ? this.checkingModel = newCheckingModel; if (newCheckingModel != null) { if (newCheckingModel instanceof DefaultTreeCheckingModel) { ((DefaultTreeCheckingModel) newCheckingModel).setTreeModel(getModel()); } // add a treeCheckingListener to repaint upon checking modifications newCheckingModel.addTreeCheckingListener(new TreeCheckingListener() { public void valueChanged(TreeCheckingEvent e) { repaint(); } }); } } /** * 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); } /** * Sets the TreeModel and links it to the existing checkingModel. */ @Override public void setModel(TreeModel newModel) { super.setModel(newModel); if (checkingModel != null && checkingModel instanceof DefaultTreeCheckingModel) { ((DefaultTreeCheckingModel) checkingModel).setTreeModel(newModel); } } /** * @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; } /** * Test function. NEW * * @param args */ public static void main(String[] args) { JFrame frame = new JFrame(); frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); DefaultMutableTreeNode root = new DefaultMutableTreeNode("root"); Random rand = new Random(); java.util.List nodes = new ArrayList(); for (int i = 0; i < 100; i++) { DefaultMutableTreeNode other = new DefaultMutableTreeNode("n " + i); int hazardous = rand.nextInt(100); if (hazardous < i) { nodes.get(hazardous).add(other); } else { root.add(other); } nodes.add(other); } CheckboxTreeOlivier tree = new CheckboxTreeOlivier(root); frame.add(tree); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } }