/******************************************************************************* * Copyright (c) 2005, 2010 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.equinox.internal.p2.ui.dialogs; import java.util.ArrayList; import java.util.Iterator; import org.eclipse.equinox.internal.p2.ui.model.QueriedElement; import org.eclipse.jface.viewers.*; import org.eclipse.swt.widgets.*; /** * Copy of ContainerCheckedTreeViewer which is specialized for use * with DeferredFetchFilteredTree. Originally copied from * org.eclipse.ui.dialogs and altered to achieve the following: * * (1)checking a parent will expand it when we know it's a long * running operation that involves a placeholder. * The modified method is doCheckStateChanged(). * * (2)when preserving selection, we do not want the check state * to be rippled through the child and parent nodes. * Since we know that preservingSelection(Runnable) isn't working * properly, no need to do a bunch of work here. * The added methods is preservingSelection(Runnable). * Modified methods are updateChildrenItems(TreeItem parent) and * updateParentItems(TreeItem parent). * * (3)we correct the problem with preservingSelection(Runnable) by * remembering the check state and restoring it after a refresh. We * fire a check state event so clients monitoring the selection will know * what's going on. Added methods are internalRefresh(Object, boolean), * saveCheckedState(), restoreCheckedState(), and * fireCheckStateChanged(Object, boolean). That last method is public * so that DeferredFetchFilteredTree can do the same thing when it * remembers selections. * * This class does not correct the general problem reported in * https://bugs.eclipse.org/bugs/show_bug.cgi?id=170521 * That is handled by preserving selections additively in * DeferredFetchFilteredTree. This class simply provides the API * needed by that class and manages the parent state according to the * children. */ public class ContainerCheckedTreeViewer extends CheckboxTreeViewer { private boolean rippleCheckMarks = true; private ArrayList<Object> savedCheckState; /** * Constructor for ContainerCheckedTreeViewer. * @see CheckboxTreeViewer#CheckboxTreeViewer(Composite) */ public ContainerCheckedTreeViewer(Composite parent) { super(parent); initViewer(); } /** * Constructor for ContainerCheckedTreeViewer. * @see CheckboxTreeViewer#CheckboxTreeViewer(Composite,int) */ public ContainerCheckedTreeViewer(Composite parent, int style) { super(parent, style); initViewer(); } /** * Constructor for ContainerCheckedTreeViewer. * @see CheckboxTreeViewer#CheckboxTreeViewer(Tree) */ public ContainerCheckedTreeViewer(Tree tree) { super(tree); initViewer(); } private void initViewer() { setUseHashlookup(true); addCheckStateListener(new ICheckStateListener() { public void checkStateChanged(CheckStateChangedEvent event) { doCheckStateChanged(event.getElement()); } }); addTreeListener(new ITreeViewerListener() { public void treeCollapsed(TreeExpansionEvent event) { } public void treeExpanded(TreeExpansionEvent event) { Widget item = findItem(event.getElement()); if (item instanceof TreeItem) { initializeItem((TreeItem) item); } } }); } /** * Update element after a checkstate change. * @param element */ protected void doCheckStateChanged(Object element) { Widget item = findItem(element); if (item instanceof TreeItem) { TreeItem treeItem = (TreeItem) item; treeItem.setGrayed(false); // BEGIN MODIFICATION OF COPIED CLASS if (element instanceof QueriedElement && treeItem.getChecked()) { if (!((QueriedElement) element).hasQueryable()) { // We have checked an element that will take some time // to get its children. Use this opportunity to auto-expand // the tree so that the check mark is not misleading. Don't // update the check state because it will just be a pending // placeholder. expandToLevel(element, 1); return; } } // END MODIFICATION OF COPIED CLASS updateChildrenItems(treeItem); updateParentItems(treeItem.getParentItem()); } } /** * The item has expanded. Updates the checked state of its children. */ private void initializeItem(TreeItem item) { if (item.getChecked() && !item.getGrayed()) { updateChildrenItems(item); } } /** * Updates the check state of all created children */ // MODIFIED to ignore parent state when in the middle of a // selection preserving refresh. private void updateChildrenItems(TreeItem parent) { // We are in the middle of preserving selections, don't // update any children according to parent if (!rippleCheckMarks) return; Item[] children = getChildren(parent); boolean state = parent.getChecked(); for (int i = 0; i < children.length; i++) { TreeItem curr = (TreeItem) children[i]; if (curr.getData() != null && ((curr.getChecked() != state) || curr.getGrayed())) { curr.setChecked(state); curr.setGrayed(false); updateChildrenItems(curr); } } } /** * Updates the check / gray state of all parent items */ private void updateParentItems(TreeItem item) { // We are in the middle of preserving selections, don't // update any parents according to children if (!rippleCheckMarks) return; if (item != null) { Item[] children = getChildren(item); boolean containsChecked = false; boolean containsUnchecked = false; for (int i = 0; i < children.length; i++) { TreeItem curr = (TreeItem) children[i]; containsChecked |= curr.getChecked(); containsUnchecked |= (!curr.getChecked() || curr.getGrayed()); } item.setChecked(containsChecked); item.setGrayed(containsChecked && containsUnchecked); updateParentItems(item.getParentItem()); } } /* (non-Javadoc) * @see org.eclipse.jface.viewers.ICheckable#setChecked(java.lang.Object, boolean) */ public boolean setChecked(Object element, boolean state) { if (super.setChecked(element, state)) { doCheckStateChanged(element); return true; } return false; } /* (non-Javadoc) * @see org.eclipse.jface.viewers.CheckboxTreeViewer#setCheckedElements(java.lang.Object[]) */ public void setCheckedElements(Object[] elements) { super.setCheckedElements(elements); for (int i = 0; i < elements.length; i++) { doCheckStateChanged(elements[i]); } } /* (non-Javadoc) * @see org.eclipse.jface.viewers.AbstractTreeViewer#setExpanded(org.eclipse.swt.widgets.Item, boolean) */ protected void setExpanded(Item item, boolean expand) { super.setExpanded(item, expand); if (expand && item instanceof TreeItem) { initializeItem((TreeItem) item); } } /* (non-Javadoc) * @see org.eclipse.jface.viewers.CheckboxTreeViewer#getCheckedElements() */ public Object[] getCheckedElements() { Object[] checked = super.getCheckedElements(); // add all items that are children of a checked node but not created yet ArrayList<Object> result = new ArrayList<Object>(); for (int i = 0; i < checked.length; i++) { Object curr = checked[i]; result.add(curr); Widget item = findItem(curr); if (item != null) { Item[] children = getChildren(item); // check if contains the dummy node if (children.length == 1 && children[0].getData() == null) { // not yet created collectChildren(curr, result); } } } return result.toArray(); } /** * Recursively add the filtered children of element to the result. * @param element * @param result */ private void collectChildren(Object element, ArrayList<Object> result) { Object[] filteredChildren = getFilteredChildren(element); for (int i = 0; i < filteredChildren.length; i++) { Object curr = filteredChildren[i]; result.add(curr); collectChildren(curr, result); } } // The super implementation doesn't really work because the // non-expanded items are not holding their real elements yet. // Yet the code that records the checked state uses the // elements to remember what checkmarks should be restored. // The result is that non-expanded elements are not up to date // and if anything in there should have been checked, it // won't be. The best we can do is at least turn off all the // rippling checks that happen during this method since we are going // to reset all the checkmarks anyway. protected void preservingSelection(Runnable updateCode) { rippleCheckMarks = false; super.preservingSelection(updateCode); rippleCheckMarks = true; } protected void internalRefresh(Object element, boolean updateLabels) { saveCheckedState(); super.internalRefresh(element, updateLabels); restoreCheckedState(); } // We only remember the leaves. This is specific to our // use case, not necessarily a good idea for fixing the general // problem. private void saveCheckedState() { Object[] checked = getCheckedElements(); savedCheckState = new ArrayList<Object>(checked.length); for (int i = 0; i < checked.length; i++) if (!isExpandable(checked[i]) && !getGrayed(checked[i])) savedCheckState.add(checked[i]); } // Now we restore checked state. private void restoreCheckedState() { setCheckedElements(new Object[0]); setGrayedElements(new Object[0]); Object element = null; // We are assuming that once a leaf, always a leaf. Iterator<Object> iter = savedCheckState.iterator(); while (iter.hasNext()) { element = iter.next(); setChecked(element, true); } // Listeners need to know something changed. if (element != null) fireCheckStateChanged(element, true); } // This method is public so that the DeferredFetchFilteredTree can also // call it. public void fireCheckStateChanged(Object element, boolean state) { fireCheckStateChanged(new CheckStateChangedEvent(this, element, state)); } }