001: /*******************************************************************************
002: * Copyright (c) 2000, 2007 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.jface.viewers;
011:
012: import java.util.ArrayList;
013: import java.util.List;
014:
015: import org.eclipse.core.runtime.ListenerList;
016: import org.eclipse.core.runtime.Assert;
017: import org.eclipse.jface.util.SafeRunnable;
018: import org.eclipse.swt.SWT;
019: import org.eclipse.swt.events.SelectionEvent;
020: import org.eclipse.swt.widgets.Composite;
021: import org.eclipse.swt.widgets.Control;
022: import org.eclipse.swt.widgets.Item;
023: import org.eclipse.swt.widgets.Tree;
024: import org.eclipse.swt.widgets.TreeItem;
025: import org.eclipse.swt.widgets.Widget;
026:
027: /**
028: * A concrete tree-structured viewer based on an SWT <code>Tree</code>
029: * control with checkboxes on each node.
030: * <p>
031: * This class is not intended to be subclassed outside the viewer framework.
032: * It is designed to be instantiated with a pre-existing SWT tree control and configured
033: * with a domain-specific content provider, label provider, element filter (optional),
034: * and element sorter (optional).
035: * </p>
036: */
037: public class CheckboxTreeViewer extends TreeViewer implements
038: ICheckable {
039:
040: /**
041: * List of check state listeners (element type: <code>ICheckStateListener</code>).
042: */
043: private ListenerList checkStateListeners = new ListenerList();
044:
045: /**
046: * Last item clicked on, or <code>null</code> if none.
047: */
048: private TreeItem lastClickedItem = null;
049:
050: /**
051: * Creates a tree viewer on a newly-created tree control under the given parent.
052: * The tree control is created using the SWT style bits: <code>CHECK</code> and <code>BORDER</code>.
053: * The viewer has no input, no content provider, a default label provider,
054: * no sorter, and no filters.
055: *
056: * @param parent the parent control
057: */
058: public CheckboxTreeViewer(Composite parent) {
059: this (parent, SWT.BORDER);
060: }
061:
062: /**
063: * Creates a tree viewer on a newly-created tree control under the given parent.
064: * The tree control is created using the given SWT style bits, plus the <code>CHECK</code> style bit.
065: * The viewer has no input, no content provider, a default label provider,
066: * no sorter, and no filters.
067: *
068: * @param parent the parent control
069: * @param style the SWT style bits
070: */
071: public CheckboxTreeViewer(Composite parent, int style) {
072: this (new Tree(parent, SWT.CHECK | style));
073: }
074:
075: /**
076: * Creates a tree viewer on the given tree control.
077: * The <code>SWT.CHECK</code> style bit must be set on the given tree control.
078: * The viewer has no input, no content provider, a default label provider,
079: * no sorter, and no filters.
080: *
081: * @param tree the tree control
082: */
083: public CheckboxTreeViewer(Tree tree) {
084: super (tree);
085: }
086:
087: /* (non-Javadoc)
088: * Method declared on ICheckable.
089: */
090: public void addCheckStateListener(ICheckStateListener listener) {
091: checkStateListeners.add(listener);
092: }
093:
094: /**
095: * Applies the checked and grayed states of the given widget and its
096: * descendents.
097: *
098: * @param checked a set of elements (element type: <code>Object</code>)
099: * @param grayed a set of elements (element type: <code>Object</code>)
100: * @param widget the widget
101: */
102: private void applyState(CustomHashtable checked,
103: CustomHashtable grayed, Widget widget) {
104: Item[] items = getChildren(widget);
105: for (int i = 0; i < items.length; i++) {
106: Item item = items[i];
107: if (item instanceof TreeItem) {
108: Object data = item.getData();
109: if (data != null) {
110: TreeItem ti = (TreeItem) item;
111: ti.setChecked(checked.containsKey(data));
112: ti.setGrayed(grayed.containsKey(data));
113: }
114: }
115: applyState(checked, grayed, item);
116: }
117: }
118:
119: /**
120: * Notifies any check state listeners that the check state of an element has changed.
121: * Only listeners registered at the time this method is called are notified.
122: *
123: * @param event a check state changed event
124: *
125: * @see ICheckStateListener#checkStateChanged
126: */
127: protected void fireCheckStateChanged(
128: final CheckStateChangedEvent event) {
129: Object[] array = checkStateListeners.getListeners();
130: for (int i = 0; i < array.length; i++) {
131: final ICheckStateListener l = (ICheckStateListener) array[i];
132: SafeRunnable.run(new SafeRunnable() {
133: public void run() {
134: l.checkStateChanged(event);
135: }
136: });
137: }
138:
139: }
140:
141: /**
142: * Gathers the checked and grayed states of the given widget and its
143: * descendents.
144: *
145: * @param checked a writable set of elements (element type: <code>Object</code>)
146: * @param grayed a writable set of elements (element type: <code>Object</code>)
147: * @param widget the widget
148: */
149: private void gatherState(CustomHashtable checked,
150: CustomHashtable grayed, Widget widget) {
151: Item[] items = getChildren(widget);
152: for (int i = 0; i < items.length; i++) {
153: Item item = items[i];
154: if (item instanceof TreeItem) {
155: Object data = item.getData();
156: if (data != null) {
157: TreeItem ti = (TreeItem) item;
158: if (ti.getChecked()) {
159: checked.put(data, data);
160: }
161: if (ti.getGrayed()) {
162: grayed.put(data, data);
163: }
164: }
165: }
166: gatherState(checked, grayed, item);
167: }
168: }
169:
170: /* (non-Javadoc)
171: * Method declared on ICheckable.
172: */
173: public boolean getChecked(Object element) {
174: Widget widget = findItem(element);
175: if (widget instanceof TreeItem) {
176: return ((TreeItem) widget).getChecked();
177: }
178: return false;
179: }
180:
181: /**
182: * Returns a list of checked elements in this viewer's tree,
183: * including currently hidden ones that are marked as
184: * checked but are under a collapsed ancestor.
185: * <p>
186: * This method is typically used when preserving the interesting
187: * state of a viewer; <code>setCheckedElements</code> is used during the restore.
188: * </p>
189: *
190: * @return the array of checked elements
191: *
192: * @see #setCheckedElements
193: */
194: public Object[] getCheckedElements() {
195: ArrayList v = new ArrayList();
196: Control tree = getControl();
197: internalCollectChecked(v, tree);
198: return v.toArray();
199: }
200:
201: /**
202: * Returns the grayed state of the given element.
203: *
204: * @param element the element
205: * @return <code>true</code> if the element is grayed,
206: * and <code>false</code> if not grayed
207: */
208: public boolean getGrayed(Object element) {
209: Widget widget = findItem(element);
210: if (widget instanceof TreeItem) {
211: return ((TreeItem) widget).getGrayed();
212: }
213: return false;
214: }
215:
216: /**
217: * Returns a list of grayed elements in this viewer's tree,
218: * including currently hidden ones that are marked as
219: * grayed but are under a collapsed ancestor.
220: * <p>
221: * This method is typically used when preserving the interesting
222: * state of a viewer; <code>setGrayedElements</code> is used during the restore.
223: * </p>
224: *
225: * @return the array of grayed elements
226: *
227: * @see #setGrayedElements
228: */
229: public Object[] getGrayedElements() {
230: List result = new ArrayList();
231: internalCollectGrayed(result, getControl());
232: return result.toArray();
233: }
234:
235: /* (non-Javadoc)
236: * Method declared on StructuredViewer.
237: */
238: protected void handleDoubleSelect(SelectionEvent event) {
239:
240: if (lastClickedItem != null) {
241: TreeItem item = lastClickedItem;
242: Object data = item.getData();
243: if (data != null) {
244: boolean state = item.getChecked();
245: setChecked(data, !state);
246: fireCheckStateChanged(new CheckStateChangedEvent(this ,
247: data, !state));
248: }
249: lastClickedItem = null;
250: } else {
251: super .handleDoubleSelect(event);
252: }
253: }
254:
255: /* (non-Javadoc)
256: * Method declared on StructuredViewer.
257: */
258: protected void handleSelect(SelectionEvent event) {
259:
260: lastClickedItem = null;
261: if (event.detail == SWT.CHECK) {
262: TreeItem item = (TreeItem) event.item;
263: lastClickedItem = item;
264: super .handleSelect(event);
265:
266: Object data = item.getData();
267: if (data != null) {
268: fireCheckStateChanged(new CheckStateChangedEvent(this ,
269: data, item.getChecked()));
270: }
271: } else {
272: super .handleSelect(event);
273: }
274: }
275:
276: /**
277: * Gathers the checked states of the given widget and its
278: * descendents, following a pre-order traversal of the tree.
279: *
280: * @param result a writable list of elements (element type: <code>Object</code>)
281: * @param widget the widget
282: */
283: private void internalCollectChecked(List result, Widget widget) {
284: Item[] items = getChildren(widget);
285: for (int i = 0; i < items.length; i++) {
286: Item item = items[i];
287: if (item instanceof TreeItem
288: && ((TreeItem) item).getChecked()) {
289: Object data = item.getData();
290: if (data != null) {
291: result.add(data);
292: }
293: }
294: internalCollectChecked(result, item);
295: }
296: }
297:
298: /**
299: * Gathers the grayed states of the given widget and its
300: * descendents, following a pre-order traversal of the tree.
301: *
302: * @param result a writable list of elements (element type: <code>Object</code>)
303: * @param widget the widget
304: */
305: private void internalCollectGrayed(List result, Widget widget) {
306: Item[] items = getChildren(widget);
307: for (int i = 0; i < items.length; i++) {
308: Item item = items[i];
309: if (item instanceof TreeItem
310: && ((TreeItem) item).getGrayed()) {
311: Object data = item.getData();
312: if (data != null) {
313: result.add(data);
314: }
315: }
316: internalCollectGrayed(result, item);
317: }
318: }
319:
320: /**
321: * Sets the checked state of all items to correspond to the given set of checked elements.
322: *
323: * @param checkedElements the set (element type: <code>Object</code>) of elements which are checked
324: * @param widget the widget
325: */
326: private void internalSetChecked(CustomHashtable checkedElements,
327: Widget widget) {
328: Item[] items = getChildren(widget);
329: for (int i = 0; i < items.length; i++) {
330: TreeItem item = (TreeItem) items[i];
331: Object data = item.getData();
332: if (data != null) {
333: boolean checked = checkedElements.containsKey(data);
334: if (checked != item.getChecked()) {
335: item.setChecked(checked);
336: }
337: }
338: internalSetChecked(checkedElements, item);
339: }
340: }
341:
342: /**
343: * Sets the grayed state of all items to correspond to the given set of grayed elements.
344: *
345: * @param grayedElements the set (element type: <code>Object</code>) of elements which are grayed
346: * @param widget the widget
347: */
348: private void internalSetGrayed(CustomHashtable grayedElements,
349: Widget widget) {
350: Item[] items = getChildren(widget);
351: for (int i = 0; i < items.length; i++) {
352: TreeItem item = (TreeItem) items[i];
353: Object data = item.getData();
354: if (data != null) {
355: boolean grayed = grayedElements.containsKey(data);
356: if (grayed != item.getGrayed()) {
357: item.setGrayed(grayed);
358: }
359: }
360: internalSetGrayed(grayedElements, item);
361: }
362: }
363:
364: /* (non-Javadoc)
365: * Method declared on Viewer.
366: */
367: protected void preservingSelection(Runnable updateCode) {
368:
369: int n = getItemCount(getControl());
370: CustomHashtable checkedNodes = newHashtable(n * 2 + 1);
371: CustomHashtable grayedNodes = newHashtable(n * 2 + 1);
372:
373: gatherState(checkedNodes, grayedNodes, getControl());
374:
375: super .preservingSelection(updateCode);
376:
377: applyState(checkedNodes, grayedNodes, getControl());
378: }
379:
380: /* (non-Javadoc)
381: * Method declared on ICheckable.
382: */
383: public void removeCheckStateListener(ICheckStateListener listener) {
384: checkStateListeners.remove(listener);
385: }
386:
387: /* (non-Javadoc)
388: * Method declared on ICheckable.
389: */
390: public boolean setChecked(Object element, boolean state) {
391: Assert.isNotNull(element);
392: Widget widget = internalExpand(element, false);
393: if (widget instanceof TreeItem) {
394: ((TreeItem) widget).setChecked(state);
395: return true;
396: }
397: return false;
398: }
399:
400: /**
401: * Sets the checked state for the children of the given item.
402: *
403: * @param item the item
404: * @param state <code>true</code> if the item should be checked,
405: * and <code>false</code> if it should be unchecked
406: */
407: private void setCheckedChildren(Item item, boolean state) {
408: createChildren(item);
409: Item[] items = getChildren(item);
410: if (items != null) {
411: for (int i = 0; i < items.length; i++) {
412: Item it = items[i];
413: if (it.getData() != null && (it instanceof TreeItem)) {
414: TreeItem treeItem = (TreeItem) it;
415: treeItem.setChecked(state);
416: setCheckedChildren(treeItem, state);
417: }
418: }
419: }
420: }
421:
422: /**
423: * Sets which elements are checked in this viewer's tree.
424: * The given list contains the elements that are to be checked;
425: * all other elements are to be unchecked.
426: * Does not fire events to check state listeners.
427: * <p>
428: * This method is typically used when restoring the interesting
429: * state of a viewer captured by an earlier call to <code>getCheckedElements</code>.
430: * </p>
431: *
432: * @param elements the array of checked elements
433: * @see #getCheckedElements
434: */
435: public void setCheckedElements(Object[] elements) {
436: assertElementsNotNull(elements);
437: CustomHashtable checkedElements = newHashtable(elements.length * 2 + 1);
438: for (int i = 0; i < elements.length; ++i) {
439: Object element = elements[i];
440: // Ensure item exists for element
441: internalExpand(element, false);
442: checkedElements.put(element, element);
443: }
444: Control tree = getControl();
445: tree.setRedraw(false);
446: internalSetChecked(checkedElements, tree);
447: tree.setRedraw(true);
448: }
449:
450: /**
451: * Sets the grayed state for the given element in this viewer.
452: *
453: * @param element the element
454: * @param state <code>true</code> if the item should be grayed,
455: * and <code>false</code> if it should be ungrayed
456: * @return <code>true</code> if the gray state could be set,
457: * and <code>false</code> otherwise
458: */
459: public boolean setGrayed(Object element, boolean state) {
460: Assert.isNotNull(element);
461: Widget widget = internalExpand(element, false);
462: if (widget instanceof TreeItem) {
463: ((TreeItem) widget).setGrayed(state);
464: return true;
465: }
466: return false;
467: }
468:
469: /**
470: * Check and gray the selection rather than calling both
471: * setGrayed and setChecked as an optimization.
472: * Does not fire events to check state listeners.
473: * @param element the item being checked
474: * @param state a boolean indicating selection or deselection
475: * @return boolean indicating success or failure.
476: */
477: public boolean setGrayChecked(Object element, boolean state) {
478: Assert.isNotNull(element);
479: Widget widget = internalExpand(element, false);
480: if (widget instanceof TreeItem) {
481: TreeItem item = (TreeItem) widget;
482: item.setChecked(state);
483: item.setGrayed(state);
484: return true;
485: }
486: return false;
487: }
488:
489: /**
490: * Sets which elements are grayed in this viewer's tree.
491: * The given list contains the elements that are to be grayed;
492: * all other elements are to be ungrayed.
493: * <p>
494: * This method is typically used when restoring the interesting
495: * state of a viewer captured by an earlier call to <code>getGrayedElements</code>.
496: * </p>
497: *
498: * @param elements the array of grayed elements
499: *
500: * @see #getGrayedElements
501: */
502: public void setGrayedElements(Object[] elements) {
503: assertElementsNotNull(elements);
504: CustomHashtable grayedElements = newHashtable(elements.length * 2 + 1);
505: for (int i = 0; i < elements.length; ++i) {
506: Object element = elements[i];
507: // Ensure item exists for element
508: internalExpand(element, false);
509: grayedElements.put(element, element);
510: }
511: Control tree = getControl();
512: tree.setRedraw(false);
513: internalSetGrayed(grayedElements, tree);
514: tree.setRedraw(true);
515: }
516:
517: /**
518: * Sets the grayed state for the given element and its parents
519: * in this viewer.
520: *
521: * @param element the element
522: * @param state <code>true</code> if the item should be grayed,
523: * and <code>false</code> if it should be ungrayed
524: * @return <code>true</code> if the element is visible and the gray
525: * state could be set, and <code>false</code> otherwise
526: * @see #setGrayed
527: */
528: public boolean setParentsGrayed(Object element, boolean state) {
529: Assert.isNotNull(element);
530: Widget widget = internalExpand(element, false);
531: if (widget instanceof TreeItem) {
532: TreeItem item = (TreeItem) widget;
533: item.setGrayed(state);
534: item = item.getParentItem();
535: while (item != null) {
536: item.setGrayed(state);
537: item = item.getParentItem();
538: }
539: return true;
540: }
541: return false;
542: }
543:
544: /**
545: * Sets the checked state for the given element and its visible
546: * children in this viewer.
547: * Assumes that the element has been expanded before. To enforce
548: * that the item is expanded, call <code>expandToLevel</code>
549: * for the element.
550: * Does not fire events to check state listeners.
551: *
552: * @param element the element
553: * @param state <code>true</code> if the item should be checked,
554: * and <code>false</code> if it should be unchecked
555: * @return <code>true</code> if the checked state could be set,
556: * and <code>false</code> otherwise
557: */
558: public boolean setSubtreeChecked(Object element, boolean state) {
559: Widget widget = internalExpand(element, false);
560: if (widget instanceof TreeItem) {
561: TreeItem item = (TreeItem) widget;
562: item.setChecked(state);
563: setCheckedChildren(item, state);
564: return true;
565: }
566: return false;
567: }
568:
569: /**
570: * Sets to the given value the checked state for all elements in this viewer.
571: * Does not fire events to check state listeners.
572: *
573: * @param state <code>true</code> if the element should be checked,
574: * and <code>false</code> if it should be unchecked
575: *
576: * @since 3.2
577: */
578: public void setAllChecked(boolean state) {
579: setAllChecked(state, getTree().getItems());
580:
581: }
582:
583: /**
584: * Set the checked state of items and their children to state.
585: * @param state
586: * @param items
587: */
588: private void setAllChecked(boolean state, TreeItem[] items) {
589: for (int i = 0; i < items.length; i++) {
590: items[i].setChecked(state);
591: TreeItem[] children = items[i].getItems();
592: setAllChecked(state, children);
593: }
594: }
595: }
|