/******************************************************************************* * Copyright (c) 2009 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 * Zend Technologies *******************************************************************************/ package org.eclipse.php.internal.ui.phar.wizard; import java.util.*; import java.util.Map.Entry; import org.eclipse.dltk.internal.core.ScriptFolder; import org.eclipse.dltk.internal.core.SourceModule; 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.Composite; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.Tree; /** * Combines a CheckboxTreeViewer and CheckboxListViewer. All viewer * selection-driven interactions are handled within this viewer */ public class CheckboxTreeAndListGroup implements ICheckStateListener, ISelectionChangedListener, 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 IStructuredContentProvider fListContentProvider; private ILabelProvider fTreeLabelProvider; private ILabelProvider fListLabelProvider; // widgets private CheckboxTreeViewer fTreeViewer; private CheckboxTableViewer fListViewer; /** * 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; fListContentProvider = listContentProvider; fTreeLabelProvider = treeLabelProvider; fListLabelProvider = listLabelProvider; createContents(parent, width, height, style); } /** * This method must be called just before this window becomes visible. */ public void aboutToOpen() { determineWhiteCheckedDescendents(fRoot); checkNewTreeElements(getTreeChildren(fRoot)); fCurrentTreeSelection = null; // select the first element in the list Object[] elements = getTreeChildren(fRoot); Object primary = elements.length > 0 ? elements[0] : null; if (primary != null) { fTreeViewer.setSelection(new StructuredSelection(primary)); } fTreeViewer.getControl().setFocus(); } /** * 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); } /** * 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; } /** * Returns 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) fCheckedStateStore.get(treeElement); if (checkedElements == null) // ie.- tree item not even gray-checked return false; return getListItemsSize(treeElement) == checkedElements.size(); } /** * 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 */ @Override public void checkStateChanged(final CheckStateChangedEvent event) { // Potentially long operation - show a busy cursor BusyIndicator.showWhile(fTreeViewer.getControl().getDisplay(), new Runnable() { @Override public void run() { if (event.getCheckable().equals(fTreeViewer)) 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 * @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 = 2; layout.makeColumnsEqualWidth = true; layout.marginHeight = 0; layout.marginWidth = 0; composite.setLayout(layout); composite.setLayoutData(new GridData(GridData.FILL_BOTH)); createTreeViewer(composite, width / 2, height); createListViewer(composite, width / 2, height); initialize(); } /** * Creates this group's list viewer. * * @param parent * the parent composite * @param width * the width * @param height * the height */ protected void createListViewer(Composite parent, int width, int height) { fListViewer = CheckboxTableViewer.newCheckList(parent, SWT.BORDER); fListViewer.setUseHashlookup(true); GridData data = new GridData(GridData.FILL_BOTH); data.widthHint = width; data.heightHint = height; fListViewer.getTable().setLayoutData(data); fListViewer.setContentProvider(fListContentProvider); fListViewer.setLabelProvider(fListLabelProvider); fListViewer.addCheckStateListener(this); } /** * 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.widthHint = width; data.heightHint = height; tree.setLayoutData(data); fTreeViewer = new CheckboxTreeViewer(tree); fTreeViewer.setUseHashlookup(true); fTreeViewer.setContentProvider(fTreeContentProvider); fTreeViewer.setLabelProvider(fTreeLabelProvider); fTreeViewer.addTreeListener(this); fTreeViewer.addCheckStateListener(this); fTreeViewer.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) 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); } /** * Recursively adds 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 = getTreeChildren(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); } /** * 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() { Set result = new HashSet(); Iterator listCollectionsEnum = fCheckedStateStore.values().iterator(); while (listCollectionsEnum.hasNext()) result.addAll((List) listCollectionsEnum.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 new HashSet(fCheckedStateStore.keySet()); } /** * Answers the number of elements that have been checked by the user. * * @return int */ public int getCheckedElementCount() { return fCheckedStateStore.size(); } /** * Returns 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 = getListElements(treeElement); return elements.length; } /** * Gets the table that displays the folder content * * @return the table used to show the list */ public Table getTable() { return fListViewer.getTable(); } /** * 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); } /** * Adds the given filter to the list viewer and triggers refiltering and * resorting of the elements. * * @param filter * a viewer filter */ public void addListFilter(ViewerFilter filter) { fListViewer.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); // if the php files are the direct children of a project, // even when they are selected,but they will not choosen by default, // so make the parent of these php files is ScriptProject instead of // ScriptFolder is ok if (element instanceof SourceModule && parent instanceof ScriptFolder) { SourceModule sourceModule = (SourceModule) element; ScriptFolder scriptFolder = (ScriptFolder) parent; if (scriptFolder.getPath().segmentCount() == 1) { parent = sourceModule.getScriptProject(); // element = sourceModule.getResource(); } } 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); } /** * Sets 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) { if (treeElement == fCurrentTreeSelection) return; fCurrentTreeSelection = treeElement; fListViewer.setInput(treeElement); List listItemsToCheck = (List) fCheckedStateStore.get(treeElement); if (listItemsToCheck != null) { Iterator listItemsEnum = listItemsToCheck.iterator(); while (listItemsEnum.hasNext()) fListViewer.setChecked(listItemsEnum.next(), true); } } /** * 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); } /** * Handles the selection of an item in the tree viewer * * @param event * ISelection */ @Override public void selectionChanged(final SelectionChangedEvent event) { BusyIndicator.showWhile(getTable().getShell().getDisplay(), new Runnable() { @Override public void run() { IStructuredSelection selection = (IStructuredSelection) event.getSelection(); Object selectedElement = selection.getFirstElement(); if (selectedElement == null) { fCurrentTreeSelection = null; fListViewer.setInput(fCurrentTreeSelection); return; } populateListViewer(selectedElement); } }); } /** * 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() { @Override public void run() { setTreeChecked(fRoot, selection); fListViewer.setAllChecked(selection); } }); } /** * Sets the list viewer's providers to those passed * * @param contentProvider * ITreeContentProvider * @param labelProvider * ILabelProvider */ public void setListProviders(IStructuredContentProvider contentProvider, ILabelProvider labelProvider) { fListViewer.setContentProvider(contentProvider); fListViewer.setLabelProvider(labelProvider); } /** * Sets the sorter that is to be applied to self's list viewer * * @param comparator */ public void setListComparator(ViewerComparator comparator) { fListViewer.setComparator(comparator); } /** * 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) { if (treeElement.equals(fCurrentTreeSelection)) { fListViewer.setAllChecked(state); } if (state) { Object[] listItems = getListElements(treeElement); List listItemsChecked = new ArrayList(); for (int i = 0; i < listItems.length; ++i) listItemsChecked.add(listItems[i]); fCheckedStateStore.put(treeElement, listItemsChecked); } else fCheckedStateStore.remove(treeElement); setWhiteChecked(treeElement, state); fTreeViewer.setChecked(treeElement, state); fTreeViewer.setGrayed(treeElement, false); // now logically check/uncheck all children as well Object[] children = getTreeChildren(treeElement); for (int i = 0; i < children.length; ++i) { setTreeChecked(children[i], 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 */ @Override 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 */ @Override 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 setTreeChecked(treeElement, state); Object parent = fTreeContentProvider.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. * * @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) { boolean whiteChecked = determineShouldBeWhiteChecked(treeElement); boolean shouldBeAtLeastGray = determineShouldBeAtLeastGrayChecked(treeElement); fTreeViewer.setChecked(treeElement, whiteChecked || shouldBeAtLeastGray); setWhiteChecked(treeElement, whiteChecked); if (whiteChecked) fTreeViewer.setGrayed(treeElement, false); else fTreeViewer.setGrayed(treeElement, shouldBeAtLeastGray); // proceed up the tree element hierarchy Object parent = fTreeContentProvider.getParent(treeElement); if (parent != null) { updateHierarchy(parent); } } /** * 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() { @Override 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)); } private Object[] getListElements(Object element) { return filter(fListViewer.getFilters(), fListContentProvider.getElements(element)); } public Set getWhiteCheckedTreeItems() { return new HashSet(fWhiteCheckedTreeItems); } private void handleUpdateSelection(Map items) { Iterator<Entry> iterator = items.entrySet().iterator(); // Update the store before the hierarchy to prevent updating parents // before all of the children are done while (iterator.hasNext()) { Entry entry = iterator.next(); Object key = entry.getKey(); // Replace the items in the checked state store with those from the // supplied items List selections = (List) entry.getValue(); 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 iterator = items.entrySet().iterator(); while (iterator.hasNext()) { Entry entry = iterator.next(); Object key = entry.getKey(); updateHierarchy(key); if (fCurrentTreeSelection != null && fCurrentTreeSelection.equals(key)) { fListViewer.setAllChecked(false); fListViewer.setCheckedElements(((List) entry.getValue()).toArray()); } } } /** * 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); } }