/******************************************************************************* * Copyright © 2000, 2013 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.edt.ide.ui.internal.eglarpackager; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.jface.viewers.CheckStateChangedEvent; import org.eclipse.jface.viewers.CheckboxTreeViewer; import org.eclipse.jface.viewers.ICheckStateListener; import org.eclipse.jface.viewers.ILabelProvider; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.IStructuredContentProvider; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.ITreeViewerListener; import org.eclipse.jface.viewers.TreeExpansionEvent; import org.eclipse.jface.viewers.ViewerComparator; import org.eclipse.jface.viewers.ViewerFilter; 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.Tree; public class CheckboxTreeAndListGroup implements ICheckStateListener, ITreeViewerListener { private Object fRoot; private Object fCurrentTreeSelection; private List fExpandedTreeNodes= new ArrayList(); private Map fCheckedStateStore= new HashMap(9); private List fWhiteCheckedTreeItems= new ArrayList(); private List fListeners= new ArrayList(); private ITreeContentProvider fTreeContentProvider; private ILabelProvider fTreeLabelProvider; private ILabelProvider fListLabelProvider; // widgets private CheckboxTreeViewer fTreeViewer; /** * Creates 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 parent composite * @param rootObject * @param treeContentProvider * @param treeLabelProvider * @param listContentProvider * @param listLabelProvider * @param style * @param width the width * @param height the height */ public CheckboxTreeAndListGroup( Composite parent, Object rootObject, ITreeContentProvider treeContentProvider, ILabelProvider treeLabelProvider, // IStructuredContentProvider listContentProvider, ILabelProvider listLabelProvider, int style, int width, int height) { fRoot= rootObject; fTreeContentProvider= treeContentProvider; fTreeLabelProvider= treeLabelProvider; fListLabelProvider= listLabelProvider; createContents(parent, width, height, style); } /** * Adds the receiver and all of it's ancestors to the checkedStateStore if they * are not already there. * @param treeElement */ private void addToHierarchyToCheckedStore(Object treeElement) { // if this tree element is already gray then its ancestors all are as well if (!fCheckedStateStore.containsKey(treeElement)) fCheckedStateStore.put(treeElement, new ArrayList()); Object parent= fTreeContentProvider.getParent(treeElement); if (parent != null) addToHierarchyToCheckedStore(parent); } /** * Returns 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= getTreeChildren(treeElement); for (int i= 0; i < children.length; ++i) { if (!fWhiteCheckedTreeItems.contains(children[i])) return false; } return true; } /** * Iterates through the passed elements which are being realized for the first * time and check each one in the tree viewer as appropriate * @param elements */ protected void checkNewTreeElements(Object[] elements) { for (int i= 0; i < elements.length; ++i) { Object currentElement= elements[i]; boolean checked= fCheckedStateStore.containsKey(currentElement); fTreeViewer.setChecked(currentElement, checked); fTreeViewer.setGrayed( currentElement, checked && !fWhiteCheckedTreeItems.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(fTreeViewer.getControl().getDisplay(), new Runnable() { public void run() { notifyCheckStateChangeListeners(event); } }); } /** * Lay out and initialize self's visual components. * * @param parent org.eclipse.swt.widgets.Composite * @param width int * @param height int * @param style */ 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= 1; layout.makeColumnsEqualWidth= true; layout.marginHeight= 0; layout.marginWidth= 0; composite.setLayout(layout); composite.setLayoutData(new GridData(GridData.FILL_BOTH)); createTreeViewer(composite, width / 2, height); initialize(); } /** * Creates this group's tree viewer. * @param parent parent composite * @param width the width * @param height the height */ 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.heightHint= height; tree.setLayoutData(data); fTreeViewer= new CheckboxTreeViewer(tree); fTreeViewer.setUseHashlookup(true); fTreeViewer.setContentProvider(fTreeContentProvider); fTreeViewer.setLabelProvider(fTreeLabelProvider); fTreeViewer.addCheckStateListener(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) fCheckedStateStore.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= getTreeChildren(treeElement); for (int i= 0; i < children.length; ++i) { if (fCheckedStateStore.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); } /** * Causes the tree viewer to expand all its items */ public void expandAll() { fTreeViewer.expandAll(); } /** * Answers a flat collection of all of the checked elements in the * list portion of self * * @return java.util.Vector */ public Iterator getAllCheckedListItems() { ArrayList ret = new ArrayList(); Object obj = fTreeViewer.getCheckedElements(); if(obj instanceof Object[]) { Object[] selects = (Object[])obj; for(Object temp : selects) { ret.add(temp); } } return ret.iterator(); } /** * Answer a collection of all of the checked elements in the tree portion * of self * * @return java.util.Vector */ public Set getAllCheckedTreeItems() { return new HashSet(fCheckedStateStore.keySet()); } /** * Answers the number of elements that have been checked by the * user. * * @return int */ public int getCheckedElementCount() { return fCheckedStateStore.size(); } /** * Gets the tree that displays the list for a folder * * @return the tree used to show the folders */ public Tree getTree() { return fTreeViewer.getTree(); } /** * Adds the given filter to the tree viewer and * triggers refiltering and resorting of the elements. * * @param filter a viewer filter */ public void addTreeFilter(ViewerFilter filter) { fTreeViewer.addFilter(filter); } /** * Logically gray-check all ancestors of treeItem by ensuring that they * appear in the checked table * @param treeElement */ protected void grayCheckHierarchy(Object treeElement) { // if this tree element is already gray then its ancestors all are as well if (fCheckedStateStore.containsKey(treeElement)) return; // no need to proceed upwards from here fCheckedStateStore.put(treeElement, new ArrayList()); if (determineShouldBeWhiteChecked(treeElement)) { setWhiteChecked(treeElement, true); } Object parent= fTreeContentProvider.getParent(treeElement); if (parent != null) grayCheckHierarchy(parent); } /** * Sets the initial checked state of the passed list element to true. * @param element */ public void initialCheckListItem(Object element) { Object parent= fTreeContentProvider.getParent(element); fCurrentTreeSelection= 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); } /** * Sets the initial checked state of the passed element to true, * as well as to all of its children and associated list elements * @param element */ public void initialCheckTreeItem(Object element) { treeItemChecked(element, true); } /** * Initializes this group's viewers after they have been laid out. */ protected void initialize() { fTreeViewer.setInput(fRoot); } /** * 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. * @param listElement * @param state * @param updatingFromSelection */ protected void listItemChecked( Object listElement, boolean state, boolean updatingFromSelection) { List checkedListItems= (List) fCheckedStateStore.get(fCurrentTreeSelection); 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(fCurrentTreeSelection); checkedListItems= (List) fCheckedStateStore.get(fCurrentTreeSelection); } 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(fCurrentTreeSelection); } } if (updatingFromSelection) updateHierarchy(fCurrentTreeSelection); } /** * Notifies all checked state listeners that the passed element has had * its checked state changed to the passed state * @param event */ protected void notifyCheckStateChangeListeners(CheckStateChangedEvent event) { Iterator listenersEnum= fListeners.iterator(); while (listenersEnum.hasNext()) ((ICheckStateListener) listenersEnum.next()).checkStateChanged(event); } /** * Removes 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) { fListeners.remove(listener); } /** * Adds 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) { fListeners.add(listener); } /** * Selects 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. * @param selection */ public void setAllSelections(final boolean selection) { //Potentially long operation - show a busy cursor BusyIndicator.showWhile(fTreeViewer.getControl().getDisplay(), new Runnable() { public void run() { setTreeChecked(fRoot, selection); } }); } /** * Sets the list viewer's providers to those passed * * @param contentProvider ITreeContentProvider * @param labelProvider ILabelProvider */ public void setListProviders( IStructuredContentProvider contentProvider, ILabelProvider labelProvider) { } /** * Sets 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.fRoot= newRoot; initialize(); } /** * Sets the checked state of the passed tree element appropriately, and * do so recursively to all of its child tree elements as well * @param treeElement * @param state */ protected void setTreeChecked(Object treeElement, boolean state) { fTreeViewer.setAllChecked(state); } /** * Sets the tree viewer's providers to those passed * * @param contentProvider ITreeContentProvider * @param labelProvider ILabelProvider */ public void setTreeProviders( ITreeContentProvider contentProvider, ILabelProvider labelProvider) { fTreeViewer.setContentProvider(contentProvider); fTreeViewer.setLabelProvider(labelProvider); } /** * Sets the sorter that is to be applied to self's tree viewer * @param sorter */ public void setTreeComparator(ViewerComparator sorter) { fTreeViewer.setComparator(sorter); } /** * Adjusts 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 (!fWhiteCheckedTreeItems.contains(treeElement)) fWhiteCheckedTreeItems.add(treeElement); } else fWhiteCheckedTreeItems.remove(treeElement); } /** * Handle the collapsing of an element in a tree viewer * @param event */ public void treeCollapsed(TreeExpansionEvent event) { // We don't need to do anything with this } /** * Handles the expansionsion of an element in a tree viewer * @param event */ 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 (!fExpandedTreeNodes.contains(item)) { fExpandedTreeNodes.add(item); checkNewTreeElements(getTreeChildren(item)); } } /** * Callback that's invoked when the checked status of an item in the tree * is changed by the user. * @param treeElement * @param state */ protected void treeItemChecked(Object treeElement, boolean state) { // recursively adjust all child tree elements appropriately Object parent= fTreeContentProvider.getParent(treeElement); if (parent == null) return; fTreeViewer.setChecked(treeElement, true); } /** * Logically un-gray-check all ancestors of treeItem iff appropriate. * @param treeElement */ protected void ungrayCheckHierarchy(Object treeElement) { if (!determineShouldBeAtLeastGrayChecked(treeElement)) fCheckedStateStore.remove(treeElement); Object parent= fTreeContentProvider.getParent(treeElement); if (parent != null) ungrayCheckHierarchy(parent); } /** * Sets the checked state of self and all ancestors appropriately * @param treeElement */ protected void updateHierarchy(Object treeElement) { fTreeViewer.setChecked(treeElement, true); } /** * Update the selections of the tree elements in items to reflect the new * selections provided. * * @param items 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(fTreeViewer.getControl().getDisplay(), new Runnable() { public void run() { handleUpdateSelection(items); } }); } /** * Returns the result of running the given elements through the filters. * @param filters * * @param elements the elements to filter * @return only the elements which all filters accept */ protected Object[] filter(ViewerFilter[] filters, Object[] elements) { if (filters != null) { ArrayList filtered = new ArrayList(elements.length); for (int i = 0; i < elements.length; i++) { boolean add = true; for (int j = 0; j < filters.length; j++) { add = filters[j].select(null, null, elements[i]); if (!add) break; } if (add) filtered.add(elements[i]); } return filtered.toArray(); } return elements; } private Object[] getTreeChildren(Object element) { return filter(fTreeViewer.getFilters(), fTreeContentProvider.getChildren(element)); } public Set getWhiteCheckedTreeItems() { return new HashSet(fWhiteCheckedTreeItems); } private void handleUpdateSelection(Map items) { 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 fCheckedStateStore.remove(key); else { fCheckedStateStore.put(key, selections); // proceed up the tree element hierarchy Object parent= fTreeContentProvider.getParent(key); if (parent != null) { addToHierarchyToCheckedStore(parent); } } } //Now update hierarchies keyIterator= items.keySet().iterator(); while (keyIterator.hasNext()) { Object key= keyIterator.next(); updateHierarchy(key); } } /** * Checks if an element is grey checked. * @param object * @return if an element is grey checked. */ public boolean isTreeItemGreyChecked(Object object) { return fTreeViewer.getGrayed(object); } /** * For a given element, expand its chidren to a level. * @param object * @param level */ public void expandTreeToLevel(Object object, int level) { fTreeViewer.expandToLevel(object, level); } /** * @param selection */ public void setTreeSelection(ISelection selection) { fTreeViewer.setSelection(selection); } }