blob: f1f81388c4c3a42668fcd276a3721b40c3f1be71 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2004 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.ui.internal.ide.misc;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.commands.common.EventManager;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.util.SafeRunnable;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.CheckboxTableViewer;
import org.eclipse.jface.viewers.CheckboxTreeViewer;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.ITreeViewerListener;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeExpansionEvent;
import org.eclipse.jface.viewers.ViewerSorter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.Tree;
/**
* Workbench-level composite that combines a CheckboxTreeViewer and CheckboxListViewer.
* All viewer selection-driven interactions are handled within this object
*/
public class CheckboxTreeAndListGroup extends EventManager implements
ICheckStateListener, ISelectionChangedListener, ITreeViewerListener {
private Object root;
private Object currentTreeSelection;
private List expandedTreeNodes = new ArrayList();
private Map checkedStateStore = new HashMap(9);
private List whiteCheckedTreeItems = new ArrayList();
private ITreeContentProvider treeContentProvider;
private IStructuredContentProvider listContentProvider;
private ILabelProvider treeLabelProvider;
private ILabelProvider listLabelProvider;
// widgets
private CheckboxTreeViewer treeViewer;
private CheckboxTableViewer listViewer;
/**
* Create an instance of this class. Use this constructor if you wish to specify
* the width and/or height of the combined widget (to only hardcode one of the
* sizing dimensions, specify the other dimension's value as -1)
* @param parent
* @param rootObject
* @param treeContentProvider
* @param treeLabelProvider
* @param listContentProvider
* @param listLabelProvider
* @param style
* @param width
* @param height
*/
public CheckboxTreeAndListGroup(Composite parent, Object rootObject,
ITreeContentProvider treeContentProvider,
ILabelProvider treeLabelProvider,
IStructuredContentProvider listContentProvider,
ILabelProvider listLabelProvider, int style, int width, int height) {
root = rootObject;
this.treeContentProvider = treeContentProvider;
this.listContentProvider = listContentProvider;
this.treeLabelProvider = treeLabelProvider;
this.listLabelProvider = listLabelProvider;
createContents(parent, width, height, style);
}
/**
* This method must be called just before this window becomes visible.
*/
public void aboutToOpen() {
determineWhiteCheckedDescendents(root);
checkNewTreeElements(treeContentProvider.getElements(root));
currentTreeSelection = null;
//select the first element in the list
Object[] elements = treeContentProvider.getElements(root);
Object primary = elements.length > 0 ? elements[0] : null;
if (primary != null) {
treeViewer.setSelection(new StructuredSelection(primary));
}
treeViewer.getControl().setFocus();
}
/**
* Add the passed listener to self's collection of clients
* that listen for changes to element checked states
*
* @param listener ICheckStateListener
*/
public void addCheckStateListener(ICheckStateListener listener) {
addListenerObject(listener);
}
/**
* Add the receiver and all of it's ancestors to the checkedStateStore if they
* are not already there.
*/
private void addToHierarchyToCheckedStore(Object treeElement) {
// if this tree element is already gray then its ancestors all are as well
if (!checkedStateStore.containsKey(treeElement))
checkedStateStore.put(treeElement, new ArrayList());
Object parent = treeContentProvider.getParent(treeElement);
if (parent != null)
addToHierarchyToCheckedStore(parent);
}
/**
* Return a boolean indicating whether all children of the passed tree element
* are currently white-checked
*
* @return boolean
* @param treeElement java.lang.Object
*/
protected boolean areAllChildrenWhiteChecked(Object treeElement) {
Object[] children = treeContentProvider.getChildren(treeElement);
for (int i = 0; i < children.length; ++i) {
if (!whiteCheckedTreeItems.contains(children[i]))
return false;
}
return true;
}
/**
* Return a boolean indicating whether all list elements associated with
* the passed tree element are currently checked
*
* @return boolean
* @param treeElement java.lang.Object
*/
protected boolean areAllElementsChecked(Object treeElement) {
List checkedElements = (List) checkedStateStore.get(treeElement);
if (checkedElements == null) // ie.- tree item not even gray-checked
return false;
return getListItemsSize(treeElement) == checkedElements.size();
}
/**
* Iterate through the passed elements which are being realized for the first
* time and check each one in the tree viewer as appropriate
*/
protected void checkNewTreeElements(Object[] elements) {
for (int i = 0; i < elements.length; ++i) {
Object currentElement = elements[i];
boolean checked = checkedStateStore.containsKey(currentElement);
treeViewer.setChecked(currentElement, checked);
treeViewer.setGrayed(currentElement, checked
&& !whiteCheckedTreeItems.contains(currentElement));
}
}
/**
* An item was checked in one of self's two views. Determine which
* view this occurred in and delegate appropriately
*
* @param event CheckStateChangedEvent
*/
public void checkStateChanged(final CheckStateChangedEvent event) {
//Potentially long operation - show a busy cursor
BusyIndicator.showWhile(treeViewer.getControl().getDisplay(),
new Runnable() {
public void run() {
if (event.getCheckable().equals(treeViewer))
treeItemChecked(event.getElement(), event
.getChecked());
else
listItemChecked(event.getElement(), event
.getChecked(), true);
notifyCheckStateChangeListeners(event);
}
});
}
/**
* Lay out and initialize self's visual components.
*
* @param parent org.eclipse.swt.widgets.Composite
* @param width int
* @param height int
*/
protected void createContents(Composite parent, int width, int height,
int style) {
// group pane
Composite composite = new Composite(parent, style);
GridLayout layout = new GridLayout();
layout.numColumns = 2;
layout.makeColumnsEqualWidth = true;
layout.marginHeight = 0;
layout.marginWidth = 0;
composite.setLayout(layout);
composite.setLayoutData(new GridData(GridData.FILL_BOTH));
composite.setFont(parent.getFont());
createTreeViewer(composite, width / 2, height);
createListViewer(composite, width / 2, height);
initialize();
}
/**
* Create this group's list viewer.
*/
protected void createListViewer(Composite parent, int width, int height) {
listViewer = CheckboxTableViewer.newCheckList(parent, SWT.BORDER);
GridData data = new GridData(GridData.FILL_BOTH);
data.widthHint = width;
data.heightHint = height;
listViewer.getTable().setLayoutData(data);
listViewer.getTable().setFont(parent.getFont());
listViewer.setContentProvider(listContentProvider);
listViewer.setLabelProvider(listLabelProvider);
listViewer.addCheckStateListener(this);
}
/**
* Create this group's tree viewer.
*/
protected void createTreeViewer(Composite parent, int width, int height) {
Tree tree = new Tree(parent, SWT.CHECK | SWT.BORDER);
GridData data = new GridData(GridData.FILL_BOTH);
data.widthHint = width;
data.heightHint = height;
tree.setLayoutData(data);
tree.setFont(parent.getFont());
treeViewer = new CheckboxTreeViewer(tree);
treeViewer.setContentProvider(treeContentProvider);
treeViewer.setLabelProvider(treeLabelProvider);
treeViewer.addTreeListener(this);
treeViewer.addCheckStateListener(this);
treeViewer.addSelectionChangedListener(this);
}
/**
* Returns a boolean indicating whether the passed tree element should be
* at LEAST gray-checked. Note that this method does not consider whether
* it should be white-checked, so a specified tree item which should be
* white-checked will result in a <code>true</code> answer from this method.
* To determine whether a tree item should be white-checked use method
* #determineShouldBeWhiteChecked(Object).
*
* @param treeElement java.lang.Object
* @return boolean
* @see #determineShouldBeWhiteChecked(java.lang.Object)
*/
protected boolean determineShouldBeAtLeastGrayChecked(Object treeElement) {
// if any list items associated with treeElement are checked then it
// retains its gray-checked status regardless of its children
List checked = (List) checkedStateStore.get(treeElement);
if (checked != null && (!checked.isEmpty()))
return true;
// if any children of treeElement are still gray-checked then treeElement
// must remain gray-checked as well
Object[] children = treeContentProvider.getChildren(treeElement);
for (int i = 0; i < children.length; ++i) {
if (checkedStateStore.containsKey(children[i]))
return true;
}
return false;
}
/**
* Returns a boolean indicating whether the passed tree item should be
* white-checked.
*
* @return boolean
* @param treeElement java.lang.Object
*/
protected boolean determineShouldBeWhiteChecked(Object treeElement) {
return areAllChildrenWhiteChecked(treeElement)
&& areAllElementsChecked(treeElement);
}
/**
* Recursively add appropriate tree elements to the collection of
* known white-checked tree elements.
*
* @param treeElement java.lang.Object
*/
protected void determineWhiteCheckedDescendents(Object treeElement) {
// always go through all children first since their white-checked
// statuses will be needed to determine the white-checked status for
// this tree element
Object[] children = treeContentProvider.getElements(treeElement);
for (int i = 0; i < children.length; ++i)
determineWhiteCheckedDescendents(children[i]);
// now determine the white-checked status for this tree element
if (determineShouldBeWhiteChecked(treeElement))
setWhiteChecked(treeElement, true);
}
/**
* Cause the tree viewer to expand all its items
*/
public void expandAll() {
treeViewer.expandAll();
}
/**
* Answer a flat collection of all of the checked elements in the
* list portion of self
*
* @return java.util.Vector
*/
public Iterator getAllCheckedListItems() {
List result = new ArrayList();
Iterator listCollectionsEnum = checkedStateStore.values().iterator();
while (listCollectionsEnum.hasNext()) {
Iterator currentCollection = ((List) listCollectionsEnum.next())
.iterator();
while (currentCollection.hasNext())
result.add(currentCollection.next());
}
return result.iterator();
}
/**
* Answer a collection of all of the checked elements in the tree portion
* of self
*
* @return java.util.Vector
*/
public Set getAllCheckedTreeItems() {
return checkedStateStore.keySet();
}
/**
* Answer the number of elements that have been checked by the
* user.
*
* @return int
*/
public int getCheckedElementCount() {
return checkedStateStore.size();
}
/**
* Return a count of the number of list items associated with a
* given tree item.
*
* @return int
* @param treeElement java.lang.Object
*/
protected int getListItemsSize(Object treeElement) {
Object[] elements = listContentProvider.getElements(treeElement);
return elements.length;
}
/**
* Get the table the list viewer uses.
* @return org.eclipse.swt.widgets.Table
*/
public Table getListTable() {
return this.listViewer.getTable();
}
/**
* Logically gray-check all ancestors of treeItem by ensuring that they
* appear in the checked table
*/
protected void grayCheckHierarchy(Object treeElement) {
// if this tree element is already gray then its ancestors all are as well
if (checkedStateStore.containsKey(treeElement))
return; // no need to proceed upwards from here
checkedStateStore.put(treeElement, new ArrayList());
if (determineShouldBeWhiteChecked(treeElement)) {
setWhiteChecked(treeElement, true);
}
Object parent = treeContentProvider.getParent(treeElement);
if (parent != null)
grayCheckHierarchy(parent);
}
/**
* Set the initial checked state of the passed list element to true.
*/
public void initialCheckListItem(Object element) {
Object parent = treeContentProvider.getParent(element);
currentTreeSelection = parent;
//As this is not done from the UI then set the box for updating from the selection to false
listItemChecked(element, true, false);
updateHierarchy(parent);
}
/**
* Set the initial checked state of the passed element to true,
* as well as to all of its children and associated list elements
*/
public void initialCheckTreeItem(Object element) {
treeItemChecked(element, true);
}
/**
* Initialize this group's viewers after they have been laid out.
*/
protected void initialize() {
treeViewer.setInput(root);
}
/**
* Callback that's invoked when the checked status of an item in the list
* is changed by the user. Do not try and update the hierarchy if we are building the
* initial list.
*/
protected void listItemChecked(Object listElement, boolean state,
boolean updatingFromSelection) {
List checkedListItems = (List) checkedStateStore
.get(currentTreeSelection);
if (state) {
if (checkedListItems == null) {
// since the associated tree item has gone from 0 -> 1 checked
// list items, tree checking may need to be updated
grayCheckHierarchy(currentTreeSelection);
checkedListItems = (List) checkedStateStore
.get(currentTreeSelection);
}
checkedListItems.add(listElement);
} else {
checkedListItems.remove(listElement);
if (checkedListItems.isEmpty()) {
// since the associated tree item has gone from 1 -> 0 checked
// list items, tree checking may need to be updated
ungrayCheckHierarchy(currentTreeSelection);
}
}
if (updatingFromSelection)
updateHierarchy(currentTreeSelection);
}
/**
* Notify all checked state listeners that the passed element has had
* its checked state changed to the passed state
*/
protected void notifyCheckStateChangeListeners(
final CheckStateChangedEvent event) {
Object[] array = getListeners();
for (int i = 0; i < array.length; i++) {
final ICheckStateListener l = (ICheckStateListener) array[i];
Platform.run(new SafeRunnable() {
public void run() {
l.checkStateChanged(event);
}
});
}
}
/**
*Set the contents of the list viewer based upon the specified selected
*tree element. This also includes checking the appropriate list items.
*
*@param treeElement java.lang.Object
*/
protected void populateListViewer(final Object treeElement) {
listViewer.setInput(treeElement);
List listItemsToCheck = (List) checkedStateStore.get(treeElement);
if (listItemsToCheck != null) {
Iterator listItemsEnum = listItemsToCheck.iterator();
while (listItemsEnum.hasNext())
listViewer.setChecked(listItemsEnum.next(), true);
}
}
/**
* Remove the passed listener from self's collection of clients
* that listen for changes to element checked states
*
* @param listener ICheckStateListener
*/
public void removeCheckStateListener(ICheckStateListener listener) {
removeListenerObject(listener);
}
/**
* Handle the selection of an item in the tree viewer
*
* @param event SelectionChangedEvent
*/
public void selectionChanged(SelectionChangedEvent event) {
IStructuredSelection selection = (IStructuredSelection) event
.getSelection();
Object selectedElement = selection.getFirstElement();
if (selectedElement == null) {
currentTreeSelection = null;
listViewer.setInput(currentTreeSelection);
return;
}
// ie.- if not an item deselection
if (selectedElement != currentTreeSelection)
populateListViewer(selectedElement);
currentTreeSelection = selectedElement;
}
/**
* Select or deselect all of the elements in the tree depending on the value of the selection
* boolean. Be sure to update the displayed files as well.
*/
public void setAllSelections(final boolean selection) {
//Potentially long operation - show a busy cursor
BusyIndicator.showWhile(treeViewer.getControl().getDisplay(),
new Runnable() {
public void run() {
setTreeChecked(root, selection);
listViewer.setAllChecked(selection);
}
});
}
/**
* Set the list viewer's providers to those passed
*
* @param contentProvider ITreeContentProvider
* @param labelProvider ILabelProvider
*/
public void setListProviders(IStructuredContentProvider contentProvider,
ILabelProvider labelProvider) {
listViewer.setContentProvider(contentProvider);
listViewer.setLabelProvider(labelProvider);
}
/**
* Set the sorter that is to be applied to self's list viewer
*/
public void setListSorter(ViewerSorter sorter) {
listViewer.setSorter(sorter);
}
/**
* Set the root of the widget to be new Root. Regenerate all of the tables and lists from this
* value.
* @param newRoot
*/
public void setRoot(Object newRoot) {
this.root = newRoot;
initialize();
}
/**
* Set the checked state of the passed tree element appropriately, and
* do so recursively to all of its child tree elements as well
*/
protected void setTreeChecked(Object treeElement, boolean state) {
if (treeElement.equals(currentTreeSelection)) {
listViewer.setAllChecked(state);
}
if (state) {
Object[] listItems = listContentProvider.getElements(treeElement);
List listItemsChecked = new ArrayList();
for (int i = 0; i < listItems.length; ++i)
listItemsChecked.add(listItems[i]);
checkedStateStore.put(treeElement, listItemsChecked);
} else
checkedStateStore.remove(treeElement);
setWhiteChecked(treeElement, state);
treeViewer.setChecked(treeElement, state);
treeViewer.setGrayed(treeElement, false);
// now logically check/uncheck all children as well
Object[] children = treeContentProvider.getChildren(treeElement);
for (int i = 0; i < children.length; ++i) {
setTreeChecked(children[i], state);
}
}
/**
* Set the tree viewer's providers to those passed
*
* @param contentProvider ITreeContentProvider
* @param labelProvider ILabelProvider
*/
public void setTreeProviders(ITreeContentProvider contentProvider,
ILabelProvider labelProvider) {
treeViewer.setContentProvider(contentProvider);
treeViewer.setLabelProvider(labelProvider);
}
/**
* Set the sorter that is to be applied to self's tree viewer
*/
public void setTreeSorter(ViewerSorter sorter) {
treeViewer.setSorter(sorter);
}
/**
* Adjust the collection of references to white-checked tree elements appropriately.
*
* @param treeElement java.lang.Object
* @param isWhiteChecked boolean
*/
protected void setWhiteChecked(Object treeElement, boolean isWhiteChecked) {
if (isWhiteChecked) {
if (!whiteCheckedTreeItems.contains(treeElement))
whiteCheckedTreeItems.add(treeElement);
} else
whiteCheckedTreeItems.remove(treeElement);
}
/**
* Handle the collapsing of an element in a tree viewer
*/
public void treeCollapsed(TreeExpansionEvent event) {
// We don't need to do anything with this
}
/**
* Handle the expansionsion of an element in a tree viewer
*/
public void treeExpanded(TreeExpansionEvent event) {
Object item = event.getElement();
// First see if the children need to be given their checked state at all. If they've
// already been realized then this won't be necessary
if (!expandedTreeNodes.contains(item)) {
expandedTreeNodes.add(item);
checkNewTreeElements(treeContentProvider.getChildren(item));
}
}
/**
* Callback that's invoked when the checked status of an item in the tree
* is changed by the user.
*/
protected void treeItemChecked(Object treeElement, boolean state) {
// recursively adjust all child tree elements appropriately
setTreeChecked(treeElement, state);
Object parent = treeContentProvider.getParent(treeElement);
if (parent == null)
return;
// now update upwards in the tree hierarchy
if (state)
grayCheckHierarchy(parent);
else
ungrayCheckHierarchy(parent);
updateHierarchy(treeElement);
}
/**
* Logically un-gray-check all ancestors of treeItem iff appropriate.
*/
protected void ungrayCheckHierarchy(Object treeElement) {
if (!determineShouldBeAtLeastGrayChecked(treeElement))
checkedStateStore.remove(treeElement);
Object parent = treeContentProvider.getParent(treeElement);
if (parent != null)
ungrayCheckHierarchy(parent);
}
/**
* Set the checked state of self and all ancestors appropriately
*/
protected void updateHierarchy(Object treeElement) {
boolean whiteChecked = determineShouldBeWhiteChecked(treeElement);
boolean shouldBeAtLeastGray = determineShouldBeAtLeastGrayChecked(treeElement);
treeViewer.setChecked(treeElement, shouldBeAtLeastGray);
setWhiteChecked(treeElement, whiteChecked);
if (!whiteChecked)
treeViewer.setGrayed(treeElement, shouldBeAtLeastGray);
// proceed up the tree element hierarchy
Object parent = treeContentProvider.getParent(treeElement);
if (parent != null) {
updateHierarchy(parent);
}
}
/**
* Update the selections of the tree elements in items to reflect the new
* selections provided.
* @param items Map with keys of Object (the tree element) and values of List (the selected
* list elements).
*/
public void updateSelections(final Map items) {
//Potentially long operation - show a busy cursor
BusyIndicator.showWhile(treeViewer.getControl().getDisplay(),
new Runnable() {
public void run() {
Iterator keyIterator = items.keySet().iterator();
//Update the store before the hierarchy to prevent updating parents before all of the children are done
while (keyIterator.hasNext()) {
Object key = keyIterator.next();
//Replace the items in the checked state store with those from the supplied items
List selections = (List) items.get(key);
if (selections.size() == 0)
//If it is empty remove it from the list
checkedStateStore.remove(key);
else {
checkedStateStore.put(key, selections);
// proceed up the tree element hierarchy
Object parent = treeContentProvider
.getParent(key);
if (parent != null) {
addToHierarchyToCheckedStore(parent);
}
}
}
//Now update hierarchies
keyIterator = items.keySet().iterator();
while (keyIterator.hasNext()) {
Object key = keyIterator.next();
updateHierarchy(key);
if (currentTreeSelection != null
&& currentTreeSelection.equals(key)) {
listViewer.setAllChecked(false);
listViewer.setCheckedElements(((List) items
.get(key)).toArray());
}
}
}
});
}
}