blob: 7b0a7a016f959fa5db804774fa716eb3dd340f1e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2005 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jface.viewers;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.jface.util.Assert;
import org.eclipse.jface.util.SafeRunnable;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Item;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.swt.widgets.Widget;
/**
* A concrete tree-structured viewer based on an SWT <code>Tree</code>
* control with checkboxes on each node.
* <p>
* This class is not intended to be subclassed outside the viewer framework.
* It is designed to be instantiated with a pre-existing SWT tree control and configured
* with a domain-specific content provider, label provider, element filter (optional),
* and element sorter (optional).
* </p>
*/
public class CheckboxTreeViewer extends TreeViewer implements ICheckable {
/**
* List of check state listeners (element type: <code>ICheckStateListener</code>).
*/
private ListenerList checkStateListeners = new ListenerList();
/**
* Last item clicked on, or <code>null</code> if none.
*/
private TreeItem lastClickedItem = null;
/**
* Creates a tree viewer on a newly-created tree control under the given parent.
* The tree control is created using the SWT style bits: <code>CHECK</code> and <code>BORDER</code>.
* The viewer has no input, no content provider, a default label provider,
* no sorter, and no filters.
*
* @param parent the parent control
*/
public CheckboxTreeViewer(Composite parent) {
this(parent, SWT.BORDER);
}
/**
* Creates a tree viewer on a newly-created tree control under the given parent.
* The tree control is created using the given SWT style bits, plus the <code>CHECK</code> style bit.
* The viewer has no input, no content provider, a default label provider,
* no sorter, and no filters.
*
* @param parent the parent control
* @param style the SWT style bits
*/
public CheckboxTreeViewer(Composite parent, int style) {
this(new Tree(parent, SWT.CHECK | style));
}
/**
* Creates a tree viewer on the given tree control.
* The <code>SWT.CHECK</code> style bit must be set on the given tree control.
* The viewer has no input, no content provider, a default label provider,
* no sorter, and no filters.
*
* @param tree the tree control
*/
public CheckboxTreeViewer(Tree tree) {
super(tree);
}
/* (non-Javadoc)
* Method declared on ICheckable.
*/
public void addCheckStateListener(ICheckStateListener listener) {
checkStateListeners.add(listener);
}
/**
* Applies the checked and grayed states of the given widget and its
* descendents.
*
* @param checked a set of elements (element type: <code>Object</code>)
* @param grayed a set of elements (element type: <code>Object</code>)
* @param widget the widget
*/
private void applyState(CustomHashtable checked, CustomHashtable grayed,
Widget widget) {
Item[] items = getChildren(widget);
for (int i = 0; i < items.length; i++) {
Item item = items[i];
if (item instanceof TreeItem) {
Object data = item.getData();
if (data != null) {
TreeItem ti = (TreeItem) item;
ti.setChecked(checked.containsKey(data));
ti.setGrayed(grayed.containsKey(data));
}
}
applyState(checked, grayed, item);
}
}
/**
* Notifies any check state listeners that the check state of an element has changed.
* Only listeners registered at the time this method is called are notified.
*
* @param event a check state changed event
*
* @see ICheckStateListener#checkStateChanged
*/
protected void fireCheckStateChanged(final CheckStateChangedEvent event) {
Object[] array = checkStateListeners.getListeners();
for (int i = 0; i < array.length; i++) {
final ICheckStateListener l = (ICheckStateListener) array[i];
SafeRunnable.run(new SafeRunnable() {
public void run() {
l.checkStateChanged(event);
}
});
}
}
/**
* Gathers the checked and grayed states of the given widget and its
* descendents.
*
* @param checked a writeable set of elements (element type: <code>Object</code>)
* @param grayed a writeable set of elements (element type: <code>Object</code>)
* @param widget the widget
*/
private void gatherState(CustomHashtable checked, CustomHashtable grayed,
Widget widget) {
Item[] items = getChildren(widget);
for (int i = 0; i < items.length; i++) {
Item item = items[i];
if (item instanceof TreeItem) {
Object data = item.getData();
if (data != null) {
TreeItem ti = (TreeItem) item;
if (ti.getChecked())
checked.put(data, data);
if (ti.getGrayed())
grayed.put(data, data);
}
}
gatherState(checked, grayed, item);
}
}
/* (non-Javadoc)
* Method declared on ICheckable.
*/
public boolean getChecked(Object element) {
Widget widget = findItem(element);
if (widget instanceof TreeItem)
return ((TreeItem) widget).getChecked();
return false;
}
/**
* Returns a list of checked elements in this viewer's tree,
* including currently hidden ones that are marked as
* checked but are under a collapsed ancestor.
* <p>
* This method is typically used when preserving the interesting
* state of a viewer; <code>setCheckedElements</code> is used during the restore.
* </p>
*
* @return the array of checked elements
*
* @see #setCheckedElements
*/
public Object[] getCheckedElements() {
ArrayList v = new ArrayList();
Control tree = getControl();
internalCollectChecked(v, tree);
return v.toArray();
}
/**
* Returns the grayed state of the given element.
*
* @param element the element
* @return <code>true</code> if the element is grayed,
* and <code>false</code> if not grayed
*/
public boolean getGrayed(Object element) {
Widget widget = findItem(element);
if (widget instanceof TreeItem) {
return ((TreeItem) widget).getGrayed();
}
return false;
}
/**
* Returns a list of grayed elements in this viewer's tree,
* including currently hidden ones that are marked as
* grayed but are under a collapsed ancestor.
* <p>
* This method is typically used when preserving the interesting
* state of a viewer; <code>setGrayedElements</code> is used during the restore.
* </p>
*
* @return the array of grayed elements
*
* @see #setGrayedElements
*/
public Object[] getGrayedElements() {
List result = new ArrayList();
internalCollectGrayed(result, getControl());
return result.toArray();
}
/* (non-Javadoc)
* Method declared on StructuredViewer.
*/
protected void handleDoubleSelect(SelectionEvent event) {
if (lastClickedItem != null) {
TreeItem item = lastClickedItem;
Object data = item.getData();
if (data != null) {
boolean state = item.getChecked();
setChecked(data, !state);
fireCheckStateChanged(new CheckStateChangedEvent(this, data,
!state));
}
lastClickedItem = null;
} else
super.handleDoubleSelect(event);
}
/* (non-Javadoc)
* Method declared on StructuredViewer.
*/
protected void handleSelect(SelectionEvent event) {
lastClickedItem = null;
if (event.detail == SWT.CHECK) {
TreeItem item = (TreeItem) event.item;
lastClickedItem = item;
super.handleSelect(event);
Object data = item.getData();
if (data != null) {
fireCheckStateChanged(new CheckStateChangedEvent(this, data,
item.getChecked()));
}
} else
super.handleSelect(event);
}
/**
* Gathers the checked states of the given widget and its
* descendents, following a pre-order traversal of the tree.
*
* @param result a writeable list of elements (element type: <code>Object</code>)
* @param widget the widget
*/
private void internalCollectChecked(List result, Widget widget) {
Item[] items = getChildren(widget);
for (int i = 0; i < items.length; i++) {
Item item = items[i];
if (item instanceof TreeItem && ((TreeItem) item).getChecked()) {
Object data = item.getData();
if (data != null)
result.add(data);
}
internalCollectChecked(result, item);
}
}
/**
* Gathers the grayed states of the given widget and its
* descendents, following a pre-order traversal of the tree.
*
* @param result a writeable list of elements (element type: <code>Object</code>)
* @param widget the widget
*/
private void internalCollectGrayed(List result, Widget widget) {
Item[] items = getChildren(widget);
for (int i = 0; i < items.length; i++) {
Item item = items[i];
if (item instanceof TreeItem && ((TreeItem) item).getGrayed()) {
Object data = item.getData();
if (data != null)
result.add(data);
}
internalCollectGrayed(result, item);
}
}
/**
* Sets the checked state of all items to correspond to the given set of checked elements.
*
* @param checkedElements the set (element type: <code>Object</code>) of elements which are checked
* @param widget the widget
*/
private void internalSetChecked(CustomHashtable checkedElements,
Widget widget) {
Item[] items = getChildren(widget);
for (int i = 0; i < items.length; i++) {
TreeItem item = (TreeItem) items[i];
Object data = item.getData();
if (data != null) {
boolean checked = checkedElements.containsKey(data);
if (checked != item.getChecked()) {
item.setChecked(checked);
}
}
internalSetChecked(checkedElements, item);
}
}
/**
* Sets the grayed state of all items to correspond to the given set of grayed elements.
*
* @param grayedElements the set (element type: <code>Object</code>) of elements which are grayed
* @param widget the widget
*/
private void internalSetGrayed(CustomHashtable grayedElements, Widget widget) {
Item[] items = getChildren(widget);
for (int i = 0; i < items.length; i++) {
TreeItem item = (TreeItem) items[i];
Object data = item.getData();
if (data != null) {
boolean grayed = grayedElements.containsKey(data);
if (grayed != item.getGrayed()) {
item.setGrayed(grayed);
}
}
internalSetGrayed(grayedElements, item);
}
}
/* (non-Javadoc)
* Method declared on Viewer.
*/
protected void preservingSelection(Runnable updateCode) {
int n = getItemCount(getControl());
CustomHashtable checkedNodes = newHashtable(n * 2 + 1);
CustomHashtable grayedNodes = newHashtable(n * 2 + 1);
gatherState(checkedNodes, grayedNodes, getControl());
super.preservingSelection(updateCode);
applyState(checkedNodes, grayedNodes, getControl());
}
/* (non-Javadoc)
* Method declared on ICheckable.
*/
public void removeCheckStateListener(ICheckStateListener listener) {
checkStateListeners.remove(listener);
}
/* (non-Javadoc)
* Method declared on ICheckable.
*/
public boolean setChecked(Object element, boolean state) {
Assert.isNotNull(element);
Widget widget = internalExpand(element, false);
if (widget instanceof TreeItem) {
((TreeItem) widget).setChecked(state);
return true;
}
return false;
}
/**
* Sets the checked state for the children of the given item.
*
* @param item the item
* @param state <code>true</code> if the item should be checked,
* and <code>false</code> if it should be unchecked
*/
private void setCheckedChildren(Item item, boolean state) {
createChildren(item);
Item[] items = getChildren(item);
if (items != null) {
for (int i = 0; i < items.length; i++) {
Item it = items[i];
if (it.getData() != null && (it instanceof TreeItem)) {
TreeItem treeItem = (TreeItem) it;
treeItem.setChecked(state);
setCheckedChildren(treeItem, state);
}
}
}
}
/**
* Sets which elements are checked in this viewer's tree.
* The given list contains the elements that are to be checked;
* all other elements are to be unchecked.
* <p>
* This method is typically used when restoring the interesting
* state of a viewer captured by an earlier call to <code>getCheckedElements</code>.
* </p>
*
* @param elements the array of checked elements
* @see #getCheckedElements
*/
public void setCheckedElements(Object[] elements) {
assertElementsNotNull(elements);
CustomHashtable checkedElements = newHashtable(elements.length * 2 + 1);
for (int i = 0; i < elements.length; ++i) {
Object element = elements[i];
// Ensure item exists for element
internalExpand(element, false);
checkedElements.put(element, element);
}
Control tree = getControl();
tree.setRedraw(false);
internalSetChecked(checkedElements, tree);
tree.setRedraw(true);
}
/**
* Sets the grayed state for the given element in this viewer.
*
* @param element the element
* @param state <code>true</code> if the item should be grayed,
* and <code>false</code> if it should be ungrayed
* @return <code>true</code> if the gray state could be set,
* and <code>false</code> otherwise
*/
public boolean setGrayed(Object element, boolean state) {
Assert.isNotNull(element);
Widget widget = internalExpand(element, false);
if (widget instanceof TreeItem) {
((TreeItem) widget).setGrayed(state);
return true;
}
return false;
}
/**
* Check and gray the selection rather than calling both
* setGrayed and setChecked as an optimization.
* @param element the item being checked
* @param state a boolean indicating selection or deselection
* @return boolean indicating success or failure.
*/
public boolean setGrayChecked(Object element, boolean state) {
Assert.isNotNull(element);
Widget widget = internalExpand(element, false);
if (widget instanceof TreeItem) {
TreeItem item = (TreeItem) widget;
item.setChecked(state);
item.setGrayed(state);
return true;
}
return false;
}
/**
* Sets which elements are grayed in this viewer's tree.
* The given list contains the elements that are to be grayed;
* all other elements are to be ungrayed.
* <p>
* This method is typically used when restoring the interesting
* state of a viewer captured by an earlier call to <code>getGrayedElements</code>.
* </p>
*
* @param elements the array of grayed elements
*
* @see #getGrayedElements
*/
public void setGrayedElements(Object[] elements) {
assertElementsNotNull(elements);
CustomHashtable grayedElements = newHashtable(elements.length * 2 + 1);
for (int i = 0; i < elements.length; ++i) {
Object element = elements[i];
// Ensure item exists for element
internalExpand(element, false);
grayedElements.put(element, element);
}
Control tree = getControl();
tree.setRedraw(false);
internalSetGrayed(grayedElements, tree);
tree.setRedraw(true);
}
/**
* Sets the grayed state for the given element and its parents
* in this viewer.
*
* @param element the element
* @param state <code>true</code> if the item should be grayed,
* and <code>false</code> if it should be ungrayed
* @return <code>true</code> if the element is visible and the gray
* state could be set, and <code>false</code> otherwise
* @see #setGrayed
*/
public boolean setParentsGrayed(Object element, boolean state) {
Assert.isNotNull(element);
Widget widget = internalExpand(element, false);
if (widget instanceof TreeItem) {
TreeItem item = (TreeItem) widget;
item.setGrayed(state);
item = item.getParentItem();
while (item != null) {
item.setGrayed(state);
item = item.getParentItem();
}
return true;
}
return false;
}
/**
* Sets the checked state for the given element and its visible
* children in this viewer.
* Assumes that the element has been expanded before. To enforce
* that the item is expanded, call <code>expandToLevel</code>
* for the element.
*
* @param element the element
* @param state <code>true</code> if the item should be checked,
* and <code>false</code> if it should be unchecked
* @return <code>true</code> if the checked state could be set,
* and <code>false</code> otherwise
*/
public boolean setSubtreeChecked(Object element, boolean state) {
Widget widget = internalExpand(element, false);
if (widget instanceof TreeItem) {
TreeItem item = (TreeItem) widget;
item.setChecked(state);
setCheckedChildren(item, state);
return true;
}
return false;
}
/**
* Sets to the given value the checked state for all elements in this viewer.
*
* @param state <code>true</code> if the element should be checked,
* and <code>false</code> if it should be unchecked
*/
public void setAllChecked(boolean state) {
setAllChecked(state, getTree().getItems());
}
/**
* Set the checked state of items and thier children to state.
* @param state
* @param items
*/
private void setAllChecked(boolean state, TreeItem[] items) {
for (int i = 0; i < items.length; i++) {
items[i].setChecked(state);
TreeItem[] children = items[i].getItems();
setAllChecked(state, children);
}
}
}