001: /*
002: * @(#)CheckBoxTree.java 8/11/2005
003: *
004: * Copyright 2002 - 2005 JIDE Software Inc. All rights reserved.
005: */
006: package com.jidesoft.swing;
007:
008: import javax.swing.*;
009: import javax.swing.event.TreeSelectionEvent;
010: import javax.swing.event.TreeSelectionListener;
011: import javax.swing.text.Position;
012: import javax.swing.tree.*;
013: import java.awt.*;
014: import java.awt.event.KeyEvent;
015: import java.awt.event.KeyListener;
016: import java.awt.event.MouseEvent;
017: import java.awt.event.MouseListener;
018: import java.beans.PropertyChangeEvent;
019: import java.beans.PropertyChangeListener;
020: import java.util.Hashtable;
021: import java.util.Vector;
022:
023: /**
024: * CheckBoxTree is a special JTree which uses JCheckBox as the tree renderer.
025: * In addition to regular JTree's features, it also allows you select any number
026: * of tree nodes in the tree by selecting the check boxes.
027: * <p>To select an element, user can mouse click on the check box, or
028: * select one or several tree nodes and press SPACE key to toggle the
029: * check box selection for all selected tree nodes.
030: * <p/>
031: * In order to retrieve which tree paths are selected, you need to call {@link #getCheckBoxTreeSelectionModel()}.
032: * It will return the selection model that keeps track of which tree paths have been checked. For example
033: * {@link CheckBoxTreeSelectionModel#getSelectionPaths()} will give the list of paths which have
034: * been checked.
035: */
036: public class CheckBoxTree extends JTree {
037:
038: public final static String PROPERTY_CHECKBOX_ENABLED = "checkBoxEnabled";
039: public final static String PROPERTY_DIG_IN = "digIn";
040:
041: protected CheckBoxTreeCellRenderer _treeCellRenderer;
042:
043: private CheckBoxTreeSelectionModel _checkBoxTreeSelectionModel;
044:
045: private boolean _checkBoxEnabled = true;
046: private PropertyChangeListener _modelChangeListener;
047:
048: public CheckBoxTree() {
049: init();
050: }
051:
052: public CheckBoxTree(Object[] value) {
053: super (value);
054: init();
055: }
056:
057: public CheckBoxTree(Vector<?> value) {
058: super (value);
059: init();
060: }
061:
062: public CheckBoxTree(Hashtable<?, ?> value) {
063: super (value);
064: init();
065: }
066:
067: public CheckBoxTree(TreeNode root) {
068: super (root);
069: init();
070: }
071:
072: public CheckBoxTree(TreeNode root, boolean asksAllowsChildren) {
073: super (root, asksAllowsChildren);
074: init();
075: }
076:
077: public CheckBoxTree(TreeModel newModel) {
078: super (newModel);
079: init();
080: }
081:
082: /**
083: * Initialize the CheckBoxTree.
084: */
085: protected void init() {
086: _checkBoxTreeSelectionModel = createCheckBoxTreeSelectionModel(getModel());
087: _checkBoxTreeSelectionModel.setTree(this );
088: Handler handler = createHandler();
089: JideSwingUtilities.insertMouseListener(this , handler, 0);
090: addKeyListener(handler);
091: _checkBoxTreeSelectionModel.addTreeSelectionListener(handler);
092:
093: if (_modelChangeListener == null) {
094: _modelChangeListener = new PropertyChangeListener() {
095: public void propertyChange(PropertyChangeEvent evt) {
096: updateRowMapper();
097: }
098: };
099: }
100: addPropertyChangeListener(JTree.SELECTION_MODEL_PROPERTY,
101: _modelChangeListener);
102: updateRowMapper();
103: }
104:
105: /**
106: * Creates the CheckBoxTreeSelectionModel.
107: *
108: * @param model the tree model.
109: * @return the CheckBoxTreeSelectionModel.
110: */
111: protected CheckBoxTreeSelectionModel createCheckBoxTreeSelectionModel(
112: TreeModel model) {
113: return new CheckBoxTreeSelectionModel(model);
114: }
115:
116: /**
117: * RowMapper is necessary for contiguous selection.
118: */
119: private void updateRowMapper() {
120: _checkBoxTreeSelectionModel.setRowMapper(getSelectionModel()
121: .getRowMapper());
122: }
123:
124: @Override
125: public void setModel(TreeModel newModel) {
126: super .setModel(newModel);
127: if (_checkBoxTreeSelectionModel != null) {
128: _checkBoxTreeSelectionModel.setModel(getModel());
129: }
130: }
131:
132: /**
133: * Gets the cell renderer with check box.
134: *
135: * @return CheckBoxTree's own cell renderer which has the check box. The actual cell renderer
136: * you set by setCellRenderer() can be accessed by using {@link #getActualCellRenderer()}.
137: */
138: @Override
139: public TreeCellRenderer getCellRenderer() {
140: TreeCellRenderer cellRenderer = super .getCellRenderer();
141: if (cellRenderer == null) {
142: cellRenderer = new DefaultTreeCellRenderer();
143: }
144: if (_treeCellRenderer == null) {
145: _treeCellRenderer = createCellRenderer(cellRenderer);
146: } else {
147: _treeCellRenderer.setActualTreeRenderer(cellRenderer);
148: }
149: return _treeCellRenderer;
150: }
151:
152: /**
153: * Gets the actual cell renderer. Since CheckBoxTree has its own check box cell renderer, this method
154: * will give you access to the actual cell renderer which is either the default tree cell renderer or
155: * the cell renderer you set using {@link #setCellRenderer(javax.swing.tree.TreeCellRenderer)}.
156: *
157: * @return the actual cell renderer
158: */
159: public TreeCellRenderer getActualCellRenderer() {
160: if (_treeCellRenderer != null) {
161: return _treeCellRenderer.getActualTreeRenderer();
162: } else {
163: return super .getCellRenderer();
164: }
165: }
166:
167: /**
168: * Creates the cell renderer.
169: *
170: * @param renderer the actual renderer for the tree node. This method will
171: * return a cell renderer that use a check box and put the actual renderer inside it.
172: * @return the cell renderer.
173: */
174: protected CheckBoxTreeCellRenderer createCellRenderer(
175: TreeCellRenderer renderer) {
176: final CheckBoxTreeCellRenderer checkBoxTreeCellRenderer = new CheckBoxTreeCellRenderer(
177: renderer);
178: addPropertyChangeListener(CELL_RENDERER_PROPERTY,
179: new PropertyChangeListener() {
180: public void propertyChange(PropertyChangeEvent evt) {
181: checkBoxTreeCellRenderer
182: .setActualTreeRenderer((TreeCellRenderer) evt
183: .getNewValue());
184: }
185: });
186: return checkBoxTreeCellRenderer;
187: }
188:
189: /**
190: * Creates the mouse listener and key listener used by CheckBoxTree.
191: *
192: * @return the Handler.
193: */
194: protected Handler createHandler() {
195: return new Handler(this );
196: }
197:
198: protected static class Handler implements MouseListener,
199: KeyListener, TreeSelectionListener {
200: protected CheckBoxTree _tree;
201: int _hotspot = new JCheckBox().getPreferredSize().width;
202: private int _toggleCount = -1;
203:
204: public Handler(CheckBoxTree tree) {
205: _tree = tree;
206: }
207:
208: protected TreePath getTreePathForMouseEvent(MouseEvent e) {
209: if (!SwingUtilities.isLeftMouseButton(e)) {
210: return null;
211: }
212:
213: if (!_tree.isCheckBoxEnabled()) {
214: return null;
215: }
216:
217: TreePath path = _tree
218: .getPathForLocation(e.getX(), e.getY());
219: if (path == null)
220: return null;
221:
222: if (clicksInCheckBox(e, path)) {
223: return path;
224: } else {
225: return null;
226: }
227: }
228:
229: protected boolean clicksInCheckBox(MouseEvent e, TreePath path) {
230: if (!_tree.isCheckBoxVisible(path)) {
231: return false;
232: } else {
233: Rectangle bounds = _tree.getPathBounds(path);
234: if (_tree.getComponentOrientation().isLeftToRight()) {
235: return e.getX() < bounds.x + _hotspot;
236: } else {
237: return e.getX() > bounds.x + bounds.width
238: - _hotspot;
239: }
240: }
241: }
242:
243: private TreePath preventToggleEvent(MouseEvent e) {
244: TreePath pathForMouseEvent = getTreePathForMouseEvent(e);
245: if (pathForMouseEvent != null) {
246: int toggleCount = _tree.getToggleClickCount();
247: if (toggleCount != -1) {
248: _toggleCount = toggleCount;
249: _tree.setToggleClickCount(-1);
250: }
251: }
252: return pathForMouseEvent;
253: }
254:
255: public void mouseClicked(MouseEvent e) {
256: preventToggleEvent(e);
257: }
258:
259: public void mousePressed(MouseEvent e) {
260: TreePath path = preventToggleEvent(e);
261: if (path != null) {
262: toggleSelection(path);
263: e.consume();
264: }
265: }
266:
267: public void mouseReleased(MouseEvent e) {
268: TreePath path = preventToggleEvent(e);
269: if (path != null) {
270: e.consume();
271: }
272: if (_toggleCount != -1) {
273: _tree.setToggleClickCount(_toggleCount);
274: }
275: }
276:
277: public void mouseEntered(MouseEvent e) {
278: }
279:
280: public void mouseExited(MouseEvent e) {
281: }
282:
283: public void keyPressed(KeyEvent e) {
284: if (e.isConsumed()) {
285: return;
286: }
287:
288: if (!_tree.isCheckBoxEnabled()) {
289: return;
290: }
291:
292: if (e.getModifiers() == 0
293: && e.getKeyChar() == KeyEvent.VK_SPACE)
294: toggleSelections();
295: }
296:
297: public void keyTyped(KeyEvent e) {
298: }
299:
300: public void keyReleased(KeyEvent e) {
301: }
302:
303: public void valueChanged(TreeSelectionEvent e) {
304: _tree.treeDidChange();
305: }
306:
307: private void toggleSelection(TreePath path) {
308: if (!_tree.isEnabled() || !_tree.isCheckBoxEnabled(path)) {
309: return;
310: }
311: CheckBoxTreeSelectionModel selectionModel = _tree
312: .getCheckBoxTreeSelectionModel();
313: boolean selected = selectionModel.isPathSelected(path,
314: selectionModel.isDigIn());
315: selectionModel.removeTreeSelectionListener(this );
316: try {
317: if (!selectionModel.isSingleEventMode()) {
318: selectionModel.setBatchMode(true);
319: }
320: if (selected)
321: selectionModel.removeSelectionPath(path);
322: else
323: selectionModel.addSelectionPath(path);
324: } finally {
325: if (!selectionModel.isSingleEventMode()) {
326: selectionModel.setBatchMode(false);
327: }
328: selectionModel.addTreeSelectionListener(this );
329: _tree.treeDidChange();
330: }
331: }
332:
333: protected void toggleSelections() {
334: TreePath[] treePaths = _tree.getSelectionPaths();
335: if (treePaths == null) {
336: return;
337: }
338: for (TreePath treePath : treePaths) {
339: toggleSelection(treePath);
340: }
341: }
342: }
343:
344: @Override
345: public TreePath getNextMatch(String prefix, int startingRow,
346: Position.Bias bias) {
347: return null;
348: }
349:
350: /**
351: * Gets the selection model for the check boxes. To retrieve the state of check boxes, you should use this selection model.
352: *
353: * @return the selection model for the check boxes.
354: */
355: public CheckBoxTreeSelectionModel getCheckBoxTreeSelectionModel() {
356: return _checkBoxTreeSelectionModel;
357: }
358:
359: /**
360: * Gets the value of property checkBoxEnabled. If true, user can
361: * click on check boxes on each tree node to select and unselect.
362: * If false, user can't click but you as developer can programatically
363: * call API to select/unselect it.
364: *
365: * @return the value of property checkBoxEnabled.
366: */
367: public boolean isCheckBoxEnabled() {
368: return _checkBoxEnabled;
369: }
370:
371: /**
372: * Sets the value of property checkBoxEnabled.
373: *
374: * @param checkBoxEnabled true to allow to check the check box. False to disable it
375: * which means user can see whether a row is checked or not but they cannot change it.
376: */
377: public void setCheckBoxEnabled(boolean checkBoxEnabled) {
378: if (checkBoxEnabled != _checkBoxEnabled) {
379: Boolean oldValue = _checkBoxEnabled ? Boolean.TRUE
380: : Boolean.FALSE;
381: Boolean newValue = checkBoxEnabled ? Boolean.TRUE
382: : Boolean.FALSE;
383: _checkBoxEnabled = checkBoxEnabled;
384: firePropertyChange(PROPERTY_CHECKBOX_ENABLED, oldValue,
385: newValue);
386: repaint();
387: }
388: }
389:
390: /**
391: * Checks if check box is enabled. There is no setter for it. The only way is to override
392: * this method to return true or false.
393: *
394: * @param path the tree path.
395: * @return true or false. If false, the check box on the particular tree path will be disabled.
396: */
397: public boolean isCheckBoxEnabled(TreePath path) {
398: return true;
399: }
400:
401: /**
402: * Checks if check box is visible. There is no setter for it. The only way is to override
403: * this method to return true or false.
404: *
405: * @param path the tree path.
406: * @return true or false. If false, the check box on the particular tree path will be disabled.
407: */
408: public boolean isCheckBoxVisible(TreePath path) {
409: return true;
410: }
411:
412: /**
413: * Gets the dig-in mode. If the CheckBoxTree is in dig-in mode, checking the parent node
414: * will check all the children. Correspondingly, getSelectionPaths() will only return the
415: * parent tree path. If not in dig-in mode, each tree node can be checked or unchecked independently
416: *
417: * @return true or false.
418: */
419: public boolean isDigIn() {
420: return getCheckBoxTreeSelectionModel().isDigIn();
421: }
422:
423: /**
424: * Sets the dig-in mode. If the CheckBoxTree is in dig-in mode, checking the parent node
425: * will check all the children. Correspondingly, getSelectionPaths() will only return the
426: * parent tree path. If not in dig-in mode, each tree node can be checked or unchecked independently
427: *
428: * @param digIn the new digIn mode.
429: */
430: public void setDigIn(boolean digIn) {
431: boolean old = isDigIn();
432: if (old != digIn) {
433: getCheckBoxTreeSelectionModel().setDigIn(digIn);
434: firePropertyChange(PROPERTY_DIG_IN, old, digIn);
435: }
436: }
437: }
|