| /******************************************************************************* |
| * Copyright (c) 2000, 2003 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Common Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/cpl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.ui.internal.ide.misc; |
| |
| import java.util.*; |
| import java.util.List; |
| |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.jface.util.ListenerList; |
| import org.eclipse.jface.util.SafeRunnable; |
| import org.eclipse.jface.viewers.*; |
| 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.*; |
| |
| /** |
| * Workbench-level composite that combines a CheckboxTreeViewer and CheckboxListViewer. |
| * All viewer selection-driven interactions are handled within this object |
| */ |
| public class CheckboxTreeAndListGroup 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 ListenerList listeners = new ListenerList(); |
| |
| 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 org.eclipse.swt.widgets.Composite |
| * @param style int |
| * @param rootObject java.lang.Object |
| * @param childPropertyName java.lang.String |
| * @param parentPropertyName java.lang.String |
| * @param listPropertyName java.lang.String |
| * @param width int |
| * @param height int |
| */ |
| 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) { |
| listeners.add(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 element 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 = listeners.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); |
| } |
| public void handleException(Throwable e) { |
| super.handleException(e); |
| //If and unexpected exception happens, remove it |
| //to make sure the workbench keeps running. |
| removeCheckStateListener(l); |
| } |
| }); |
| } |
| } |
| /** |
| *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) { |
| listeners.remove(listener); |
| } |
| /** |
| * Handle the selection of an item in the tree viewer |
| * |
| * @param selection ISelection |
| */ |
| 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 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()); |
| } |
| } |
| } |
| }); |
| |
| } |
| } |