001: package com.jidesoft.swing;
002:
003: import javax.swing.event.TreeSelectionEvent;
004: import javax.swing.tree.DefaultTreeSelectionModel;
005: import javax.swing.tree.TreeModel;
006: import javax.swing.tree.TreePath;
007: import javax.swing.tree.TreeSelectionModel;
008: import java.util.ArrayList;
009: import java.util.List;
010: import java.util.Stack;
011: import java.util.Vector;
012:
013: /**
014: * <code>CheckBoxTreeSelectionModel</code> is a selection _model based on {@link DefaultTreeSelectionModel} and use
015: * in {@link CheckBoxTree} to keep track of the checked tree paths.
016: *
017: * @author Santhosh Kumar T
018: */
019: public class CheckBoxTreeSelectionModel extends
020: DefaultTreeSelectionModel {
021: private TreeModel _model;
022: private boolean _digIn = true;
023: private CheckBoxTree _tree;
024:
025: private boolean _singleEventMode = false;
026:
027: public CheckBoxTreeSelectionModel(TreeModel model) {
028: _model = model;
029: setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
030: }
031:
032: void setTree(CheckBoxTree tree) {
033: _tree = tree;
034: }
035:
036: public CheckBoxTreeSelectionModel(TreeModel model, boolean digIn) {
037: _model = model;
038: _digIn = digIn;
039: }
040:
041: public TreeModel getModel() {
042: return _model;
043: }
044:
045: public void setModel(TreeModel model) {
046: _model = model;
047: }
048:
049: /**
050: * Gets the dig-in mode. If the CheckBoxTree is in dig-in mode, checking the parent node
051: * will check all the children. Correspondingly, getSelectionPaths() will only return the
052: * parent tree path. If not in dig-in mode, each tree node can be checked or unchecked independently
053: *
054: * @return true or false.
055: */
056: public boolean isDigIn() {
057: return _digIn;
058: }
059:
060: /**
061: * Sets the dig-in mode. If the CheckBoxTree is in dig-in mode, checking the parent node
062: * will check all the children. Correspondingly, getSelectionPaths() will only return the
063: * parent tree path. If not in dig-in mode, each tree node can be checked or unchecked independently
064: *
065: * @param digIn true to enable dig-in mode. False to disable it.
066: */
067: public void setDigIn(boolean digIn) {
068: _digIn = digIn;
069: }
070:
071: /**
072: * Tests whether there is any unselected node in the subtree of given path.
073: *
074: * @param path check if the path is partially selected.
075: * @return true i fpartially. Otherwise false.
076: */
077: public boolean isPartiallySelected(TreePath path) {
078: if (isPathSelected(path, true))
079: return false;
080: TreePath[] selectionPaths = getSelectionPaths();
081: if (selectionPaths == null)
082: return false;
083: for (TreePath selectionPath : selectionPaths) {
084: if (isDescendant(selectionPath, path))
085: return true;
086: }
087: return false;
088: }
089:
090: /**
091: * Tells whether given path is selected. if dig is true,
092: * then a path is assumed to be selected, if one of its ancestor is selected.
093: *
094: * @param path check if the path is selected.
095: * @param digIn whether we will check its descendants.
096: * @return true if the path is selected.
097: */
098: public boolean isPathSelected(TreePath path, boolean digIn) {
099: if (!digIn)
100: return super .isPathSelected(path);
101:
102: while (path != null && !super .isPathSelected(path)) {
103: path = path.getParentPath();
104: }
105: return path != null;
106: }
107:
108: /**
109: * is path1 descendant of path2.
110: *
111: * @param path1 the first path
112: * @param path2 the second path
113: * @return true if the first path is the descendant of the second path.
114: */
115: private boolean isDescendant(TreePath path1, TreePath path2) {
116: Object obj1[] = path1.getPath();
117: Object obj2[] = path2.getPath();
118: for (int i = 0; i < obj2.length; i++) {
119: if (obj1[i] != obj2[i])
120: return false;
121: }
122: return true;
123: }
124:
125: private boolean _fireEvent = true;
126:
127: @SuppressWarnings({"RawUseOfParameterizedType"})
128: @Override
129: protected void notifyPathChange(Vector changedPaths,
130: TreePath oldLeadSelection) {
131: if (_fireEvent) {
132: super .notifyPathChange(changedPaths, oldLeadSelection);
133: }
134: }
135:
136: /**
137: * Overrides the method in DefaultTreeSelectionModel to consider digIn mode.
138: *
139: * @param pPaths the tree paths to be selected.
140: */
141: @Override
142: public void setSelectionPaths(TreePath[] pPaths) {
143: if (!isDigIn()) {
144: super .setSelectionPaths(pPaths);
145: } else {
146: clearSelection();
147: addSelectionPaths(pPaths);
148: }
149: }
150:
151: /**
152: * Overrides the method in DefaultTreeSelectionModel to consider digIn mode.
153: *
154: * @param paths the tree paths to be added to selection paths.
155: */
156: @Override
157: public void addSelectionPaths(TreePath[] paths) {
158: if (!isDigIn()) {
159: super .addSelectionPaths(paths);
160: return;
161: }
162:
163: boolean fireEventAtTheEnd = false;
164: if (isSingleEventMode() && _fireEvent) {
165: _fireEvent = false;
166: fireEventAtTheEnd = true;
167: }
168:
169: try {
170: // unselect all descendants of paths[]
171: List<TreePath> toBeRemoved = new ArrayList<TreePath>();
172: for (TreePath path : paths) {
173: TreePath[] selectionPaths = getSelectionPaths();
174: if (selectionPaths == null)
175: break;
176: for (TreePath selectionPath : selectionPaths) {
177: if (isDescendant(selectionPath, path))
178: toBeRemoved.add(selectionPath);
179: }
180: }
181: if (toBeRemoved.size() > 0) {
182: delegateRemoveSelectionPaths(toBeRemoved
183: .toArray(new TreePath[toBeRemoved.size()]));
184: }
185:
186: // if all siblings are selected then unselect them and select parent recursively
187: // otherwize just select that path.
188: for (TreePath path : paths) {
189: TreePath temp = null;
190: while (areSiblingsSelected(path)) {
191: temp = path;
192: if (path.getParentPath() == null)
193: break;
194: path = path.getParentPath();
195: }
196: if (temp != null) {
197: if (temp.getParentPath() != null) {
198: addSelectionPath(temp.getParentPath());
199: } else {
200: if (!isSelectionEmpty()) {
201: removeSelectionPaths(getSelectionPaths());
202: }
203: delegateAddSelectionPaths(new TreePath[] { temp });
204: }
205: } else {
206: delegateAddSelectionPaths(new TreePath[] { path });
207: }
208: }
209: } finally {
210: _fireEvent = true;
211: if (isSingleEventMode() && fireEventAtTheEnd) {
212: notifyPathChange(paths, true, paths[0]);
213: }
214: }
215: }
216:
217: /**
218: * tells whether all siblings of given path are selected.
219: *
220: * @param path the tree path
221: * @return true if the siblings are all selected.
222: */
223: private boolean areSiblingsSelected(TreePath path) {
224: TreePath parent = path.getParentPath();
225: if (parent == null)
226: return true;
227: Object node = path.getLastPathComponent();
228: Object parentNode = parent.getLastPathComponent();
229:
230: int childCount = _model.getChildCount(parentNode);
231: for (int i = 0; i < childCount; i++) {
232: Object childNode = _model.getChild(parentNode, i);
233: if (childNode == node)
234: continue;
235: TreePath childPath = parent.pathByAddingChild(childNode);
236: if (_tree != null && !_tree.isCheckBoxVisible(childPath))
237: continue;
238: if (!isPathSelected(childPath)) {
239: return false;
240: }
241: }
242: return true;
243: }
244:
245: @Override
246: public void removeSelectionPaths(TreePath[] paths) {
247: if (!isDigIn()) {
248: super .removeSelectionPaths(paths);
249: return;
250: }
251:
252: List<TreePath> toBeRemoved = new ArrayList<TreePath>();
253: for (TreePath path : paths) {
254: if (path.getPathCount() == 1) {
255: toBeRemoved.add(path);
256: } else {
257: toggleRemoveSelection(path);
258: }
259: }
260: if (toBeRemoved.size() > 0) {
261: delegateRemoveSelectionPaths(toBeRemoved
262: .toArray(new TreePath[toBeRemoved.size()]));
263: }
264: }
265:
266: /**
267: * If any ancestor node of given path is selected then unselect
268: * it and selection all its descendants except given path and descendants.
269: * Otherwise just unselect the given path
270: *
271: * @param path the tree path
272: */
273: private void toggleRemoveSelection(TreePath path) {
274: boolean fireEventAtTheEnd = false;
275: if (isSingleEventMode() && _fireEvent) {
276: _fireEvent = false;
277: fireEventAtTheEnd = true;
278: }
279:
280: try {
281: Stack<TreePath> stack = new Stack<TreePath>();
282: TreePath parent = path.getParentPath();
283: while (parent != null && !isPathSelected(parent)) {
284: stack.push(parent);
285: parent = parent.getParentPath();
286: }
287: if (parent != null)
288: stack.push(parent);
289: else {
290: delegateRemoveSelectionPaths(new TreePath[] { path });
291: return;
292: }
293:
294: List<TreePath> toBeAdded = new ArrayList<TreePath>();
295: while (!stack.isEmpty()) {
296: TreePath temp = stack.pop();
297: TreePath peekPath = stack.isEmpty() ? path : stack
298: .peek();
299: Object node = temp.getLastPathComponent();
300: Object peekNode = peekPath.getLastPathComponent();
301: int childCount = _model.getChildCount(node);
302: for (int i = 0; i < childCount; i++) {
303: Object childNode = _model.getChild(node, i);
304: if (childNode != peekNode) {
305: TreePath treePath = temp
306: .pathByAddingChild(childNode);
307: if (_tree.isCheckBoxVisible(treePath)
308: && _tree.isCheckBoxEnabled(treePath)) {
309: toBeAdded.add(treePath);
310: }
311: }
312: }
313: }
314: if (toBeAdded.size() > 0) {
315: delegateAddSelectionPaths(toBeAdded
316: .toArray(new TreePath[toBeAdded.size()]));
317: }
318: delegateRemoveSelectionPaths(new TreePath[] { parent });
319: } finally {
320: _fireEvent = true;
321: if (isSingleEventMode() && fireEventAtTheEnd) {
322: notifyPathChange(new TreePath[] { path }, false, path);
323: }
324: }
325: }
326:
327: public boolean isSingleEventMode() {
328: return _singleEventMode;
329: }
330:
331: /**
332: * Single event mode is a mode that always fires only one event when you select or unselect a tree node.
333: * <p/>
334: * Taking this tree as an example,
335: * <p/>
336: * <code><pre>
337: * A -- a
338: * |- b
339: * |- c
340: * </code></pre>
341: * Case 1: Assuming b and c are selected at this point, you click on a.
342: * <br>
343: * <ul>
344: * <li>In non-single event mode, you will get select-A, deselect-b and deselect-c three events
345: * <li>In single event mode, you will only get select-a.
346: * </ul>
347: * <p/>
348: * Case 2: Assuming none of the nodes are selected, you click on A. In this case, both modes result in the same behavior.
349: * <ul>
350: * <li>In non-single event mode, you will get only select-A event.
351: * <li>In single event mode, you will only get select-A too.
352: * </ul>
353: * Case 3: Assuming b and c are selected and now you click on A.
354: * <ul>
355: * <li>In non-single event mode, you will get select-A event as well as deselect-b and deselect-c event.
356: * <li>In single event mode, you will only get select-A.
357: * </ul>
358: * As you can see, single event mode will always fire the event on the nodes you select. However it doesn't reflect
359: * what really happened inside the selection model. So if you want to get
360: * a complete picture of the selection state inside selection model, you should use {@link #getSelectionPaths()} to find out.
361: * In non-single event mode, the events reflect what happened inside the selection model. So you can get a complete picture
362: * of the exact state without asking the selection model. The downside is it will generate too many events. With this option, you
363: * can decide which mode you want to use that is the best for your case.
364: * <p/>
365: * By default, singleEventMode is set to false to be compatible with the older versions that don't have this option.
366: *
367: * @param singleEventMode true or false.
368: */
369: public void setSingleEventMode(boolean singleEventMode) {
370: _singleEventMode = singleEventMode;
371: }
372:
373: /**
374: * Notifies listeners of a change in path. changePaths should contain
375: * instances of PathPlaceHolder.
376: *
377: * @param changedPaths the paths that are changed.
378: * @param isNew is it a new path.
379: * @param oldLeadSelection the old selection.
380: */
381: protected void notifyPathChange(TreePath[] changedPaths,
382: boolean isNew, TreePath oldLeadSelection) {
383: int cPathCount = changedPaths.length;
384: boolean[] newness = new boolean[cPathCount];
385:
386: for (int counter = 0; counter < cPathCount; counter++) {
387: newness[counter] = isNew;
388: }
389:
390: TreeSelectionEvent event = new TreeSelectionEvent(this ,
391: changedPaths, newness, oldLeadSelection, leadPath);
392:
393: fireValueChanged(event);
394: }
395:
396: // do not use it for now
397: private boolean _batchMode = false;
398:
399: private boolean isBatchMode() {
400: return _batchMode;
401: }
402:
403: public void setBatchMode(boolean batchMode) {
404: _batchMode = batchMode;
405: if (!_batchMode) {
406: super .addSelectionPaths(_toBeAdded
407: .toArray(new TreePath[_toBeAdded.size()]));
408: _toBeAdded.clear();
409: super .removeSelectionPaths(_toBeRemoved
410: .toArray(new TreePath[_toBeRemoved.size()]));
411: _toBeRemoved.clear();
412: }
413: }
414:
415: private List<TreePath> _toBeAdded = new ArrayList();
416: private List<TreePath> _toBeRemoved = new ArrayList();
417:
418: private void delegateRemoveSelectionPaths(TreePath[] paths) {
419: if (!isBatchMode()) {
420: super .removeSelectionPaths(paths);
421: } else {
422: for (TreePath path : paths) {
423: _toBeRemoved.add(path);
424: _toBeAdded.remove(path);
425: }
426: }
427: }
428:
429: private void delegateRemoveSelectionPath(TreePath path) {
430: if (!isBatchMode()) {
431: super .removeSelectionPath(path);
432: } else {
433: _toBeRemoved.add(path);
434: _toBeAdded.remove(path);
435: }
436:
437: }
438:
439: private void delegateAddSelectionPaths(TreePath[] paths) {
440: if (!isBatchMode()) {
441: super .addSelectionPaths(paths);
442: } else {
443: for (TreePath path : paths) {
444: _toBeAdded.add(path);
445: _toBeRemoved.remove(path);
446: }
447: }
448: }
449:
450: private void delegateAddSelectionPath(TreePath path) {
451: if (!isBatchMode()) {
452: super.addSelectionPath(path);
453: } else {
454: _toBeAdded.add(path);
455: _toBeRemoved.remove(path);
456: }
457: }
458: }
|