diff options
author | Boris Bokowski | 2007-05-01 02:02:55 +0000 |
---|---|---|
committer | Boris Bokowski | 2007-05-01 02:02:55 +0000 |
commit | 73d9a17c9f3145865eba8888e62c3afa14d15a8c (patch) | |
tree | 318867466253fe974dced9e8e75b35e625f0ac3a | |
parent | 999d2beac0dc4bff388c843e03dd052b5518f873 (diff) | |
download | org.eclipse.e4.databinding-73d9a17c9f3145865eba8888e62c3afa14d15a8c.tar.gz org.eclipse.e4.databinding-73d9a17c9f3145865eba8888e62c3afa14d15a8c.tar.xz org.eclipse.e4.databinding-73d9a17c9f3145865eba8888e62c3afa14d15a8c.zip |
Fix for Bug 184755 [DataBinding] internal tree content provider does not workv20070501AI20070503-1400I20070503-1300I20070503-0010I20070502-1800I20070502-1300I20070502-0100I20070502-0010aI20070501-1300I20070501-0010
13 files changed, 739 insertions, 1352 deletions
diff --git a/bundles/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/AbstractObservableSet.java b/bundles/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/AbstractObservableSet.java index a60608e4..39a058b2 100644 --- a/bundles/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/AbstractObservableSet.java +++ b/bundles/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/AbstractObservableSet.java @@ -42,7 +42,15 @@ public abstract class AbstractObservableSet extends AbstractObservable implement protected AbstractObservableSet() { this(Realm.getDefault()); } + + protected void firstListenerAdded() { + super.firstListenerAdded(); + } + protected void lastListenerRemoved() { + super.lastListenerRemoved(); + } + protected AbstractObservableSet(Realm realm) { super(realm); changeSupport = new ChangeSupport(realm){ diff --git a/bundles/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/IUnorderedTreeProvider.java b/bundles/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/IUnorderedTreeProvider.java index 6b5e5f65..81941c80 100644 --- a/bundles/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/IUnorderedTreeProvider.java +++ b/bundles/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/IUnorderedTreeProvider.java @@ -15,27 +15,22 @@ import org.eclipse.core.databinding.observable.Realm; import org.eclipse.core.databinding.observable.set.IObservableSet; /** - * Objects that implement this interface are capable of describing a tree by - * returning the set of children of any given element in the tree. - * - * @since 3.3 + * @since 1.0 + * */ public interface IUnorderedTreeProvider { - /** - * @return the realm shared by all child sets + * @return the realm for the createChildSet method */ public Realm getRealm(); - + /** - * Returns the children of the given element, or null if the element is a - * leaf node. The caller of this method is expected to dispose the result - * set when it is no longer needed. + * Returns the children of the given element, or null if the element is a leaf node. + * The caller of this method is expected to dispose the result set when it is no + * longer needed. * - * @param element - * the tree path of the element to query - * @return the children of the given element, or null if the element is a - * leaf node + * @param element element to query + * @return the children of the given element, or null if the element is a leaf node */ - public IObservableSet createChildSet(TreePath element); + IObservableSet createChildSet(Object element); } diff --git a/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/observable/set/AbstractObservableSet.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/observable/set/AbstractObservableSet.java index a60608e4..39a058b2 100644 --- a/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/observable/set/AbstractObservableSet.java +++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/observable/set/AbstractObservableSet.java @@ -42,7 +42,15 @@ public abstract class AbstractObservableSet extends AbstractObservable implement protected AbstractObservableSet() { this(Realm.getDefault()); } + + protected void firstListenerAdded() { + super.firstListenerAdded(); + } + protected void lastListenerRemoved() { + super.lastListenerRemoved(); + } + protected AbstractObservableSet(Realm realm) { super(realm); changeSupport = new ChangeSupport(realm){ diff --git a/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/observable/tree/IUnorderedTreeProvider.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/observable/tree/IUnorderedTreeProvider.java index 6b5e5f65..81941c80 100644 --- a/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/observable/tree/IUnorderedTreeProvider.java +++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/observable/tree/IUnorderedTreeProvider.java @@ -15,27 +15,22 @@ import org.eclipse.core.databinding.observable.Realm; import org.eclipse.core.databinding.observable.set.IObservableSet; /** - * Objects that implement this interface are capable of describing a tree by - * returning the set of children of any given element in the tree. - * - * @since 3.3 + * @since 1.0 + * */ public interface IUnorderedTreeProvider { - /** - * @return the realm shared by all child sets + * @return the realm for the createChildSet method */ public Realm getRealm(); - + /** - * Returns the children of the given element, or null if the element is a - * leaf node. The caller of this method is expected to dispose the result - * set when it is no longer needed. + * Returns the children of the given element, or null if the element is a leaf node. + * The caller of this method is expected to dispose the result set when it is no + * longer needed. * - * @param element - * the tree path of the element to query - * @return the children of the given element, or null if the element is a - * leaf node + * @param element element to query + * @return the children of the given element, or null if the element is a leaf node */ - public IObservableSet createChildSet(TreePath element); + IObservableSet createChildSet(Object element); } diff --git a/bundles/org.eclipse.jface.databinding/src/org/eclipse/jface/internal/databinding/internal/viewers/IPrefetchingTree.java b/bundles/org.eclipse.jface.databinding/src/org/eclipse/jface/internal/databinding/internal/viewers/IPrefetchingTree.java deleted file mode 100644 index ca443cff..00000000 --- a/bundles/org.eclipse.jface.databinding/src/org/eclipse/jface/internal/databinding/internal/viewers/IPrefetchingTree.java +++ /dev/null @@ -1,37 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2006, 2007 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.jface.internal.databinding.internal.viewers; - -/** - * @since 3.3 - * - */ -public interface IPrefetchingTree { - /** - * Returns true if and only if the content provider should - * try to prefetch the children of the given node. - * Prefetching uses unused CPU cycles to fetch the children - * of visible nodes so that they expand faster. This will - * generally cause the application to run faster so should - * usually be enabled. - * <p> - * In some circumstances computing the children of a node may - * require network resources that need to be conserved, so - * prefetching can be explicitly disabled these nodes. This - * means that the user will need to wait for a "pending" node - * every time they expand the parent node. - * </p> - * - * @param parentNode - * @return true iff the children should be eagerly fetched - */ - boolean shouldPrefetch(Object parentNode); -} diff --git a/bundles/org.eclipse.jface.databinding/src/org/eclipse/jface/internal/databinding/internal/viewers/OrderedTreeContentProvider.java b/bundles/org.eclipse.jface.databinding/src/org/eclipse/jface/internal/databinding/internal/viewers/OrderedTreeContentProvider.java deleted file mode 100644 index 783ded31..00000000 --- a/bundles/org.eclipse.jface.databinding/src/org/eclipse/jface/internal/databinding/internal/viewers/OrderedTreeContentProvider.java +++ /dev/null @@ -1,433 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2006, 2007 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.jface.internal.databinding.internal.viewers; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Set; - -import org.eclipse.core.databinding.observable.Diffs; -import org.eclipse.core.databinding.observable.list.IObservableList; -import org.eclipse.core.databinding.observable.set.AbstractObservableSet; -import org.eclipse.core.databinding.observable.set.IObservableSet; -import org.eclipse.core.databinding.observable.set.SetDiff; -import org.eclipse.core.internal.databinding.observable.tree.IOrderedTreeProvider; -import org.eclipse.jface.viewers.ITreePathContentProvider; -import org.eclipse.jface.viewers.ITreeViewerListener; -import org.eclipse.jface.viewers.TreeExpansionEvent; -import org.eclipse.jface.viewers.TreePath; -import org.eclipse.jface.viewers.TreeViewer; -import org.eclipse.jface.viewers.Viewer; - -/** - * Converts an IOrderedTreeProvider into an ITreeContentProvider that is - * suitable for use with a JFace TreeViewer. - * - * <p> - * This content provider works correctly with trees containing duplicate - * elements. - * </p> - * - * @since 3.3 - */ -public class OrderedTreeContentProvider implements ITreePathContentProvider { - - private HashMap mapElementToTreeNode = new HashMap(); - - private LinkedList enqueuedPrefetches = new LinkedList(); - - class KnownElementsSet extends AbstractObservableSet { - - /** - */ - protected KnownElementsSet() { - super(provider.getRealm()); - } - - /* - * (non-Javadoc) - * - * @see org.eclipse.jface.internal.databinding.provisional.observable.set.AbstractObservableSet#getWrappedSet() - */ - protected Set getWrappedSet() { - return mapElementToTreeNode.keySet(); - } - - void doFireDiff(Set added, Set removed) { - fireSetChange(Diffs.createSetDiff(added, removed)); - } - - void doFireStale(boolean isStale) { - if (isStale) { - fireStale(); - } else { - fireSetChange(Diffs.createSetDiff(Collections.EMPTY_SET, - Collections.EMPTY_SET)); - } - } - - public Object getElementType() { - return Object.class; - } - - protected void fireSetChange(SetDiff diff) { - super.fireSetChange(diff); - } - } - - KnownElementsSet elements; - - private ITreeViewerListener expandListener = new ITreeViewerListener() { - public void treeCollapsed(TreeExpansionEvent event) { - } - - public void treeExpanded(TreeExpansionEvent event) { - } - }; - - private IPrefetchingTree prefetchingTree; - - private IOrderedTreeProvider provider; - - private Object pendingNode; - - private int avoidViewerUpdates; - - private TreeViewer treeViewer; - - private int staleCount = 0; - - private boolean useRefresh; - - private int maxPrefetches = -1; - - /** - * Constructs a content provider that will render the given tree in a - * TreeViewer. - * - * @param provider - * IObservableTree that provides the contents of the tree. The - * given provider may optionally implement IPrefetchingTree if it - * wants to selectively enable or disable prefetching from - * particular nodes. - * @param pendingNode - * element to insert whenever a node is being fetched in the - * background - */ - public OrderedTreeContentProvider(IOrderedTreeProvider provider, - Object pendingNode) { - this(provider, pendingNode, false); - } - - /** - * Constructs a content provider that will render the given tree in a - * TreeViewer. - * - * @param provider - * IObservableTree that provides the contents of the tree - * @param pendingNode - * element to insert whenever a node is being fetched in the - * background - * @param useRefresh - * true = notify the viewer of changes by calling refresh(...), - * false = notify the viewer of changes by calling add(...) and - * remove(...). Using false is more efficient, but may not work - * with TreeViewer subclasses. - */ - public OrderedTreeContentProvider(IOrderedTreeProvider provider, - Object pendingNode, boolean useRefresh) { - this.provider = provider; - this.prefetchingTree = PrefetchingTree.getPrefetchingTree(provider); - this.pendingNode = pendingNode; - this.useRefresh = useRefresh; - elements = new KnownElementsSet(); - } - - /** - * Sets the maximum number of pending prefetches. - * - * @param maxPrefetches - */ - public void setMaxPrefetches(int maxPrefetches) { - this.maxPrefetches = maxPrefetches; - } - - /* package */IObservableList createChildList(TreePath treePath) { - Object[] segments = new Object[treePath.getSegmentCount()]; - for (int i = 0; i < segments.length; i++) { - segments[i] = treePath.getSegment(i); - } - return provider.createChildList(new org.eclipse.core.internal.databinding.observable.tree.TreePath(segments)); - } - - /* package */void remove(Object element, List removals, boolean lastElement) { - if (avoidViewerUpdates == 0) { - for (Iterator iter = removals.iterator(); iter.hasNext();) { - Object next = iter.next(); - - OrderedTreeNode nextNode = (OrderedTreeNode) mapElementToTreeNode - .get(next); - if (nextNode != null) { - nextNode.removeParent(element); - removeIfUnused(nextNode); - } - } - - if (lastElement || useRefresh) { - treeViewer.refresh(element); - } else { - treeViewer.remove(element, removals.toArray()); - } - } - } - - /* package */void add(TreePath element, List additions) { - if (avoidViewerUpdates == 0) { - // Handle new parents - addParent(element, additions); - if (useRefresh) { - treeViewer.refresh(element); - } else { - treeViewer.add(element, additions); - } - } - } - - /* package */void insert(TreePath element, Object addition, int position) { - if (avoidViewerUpdates == 0) { - // Handle new parents - addParent(element, Collections.singletonList(addition)); - if (useRefresh) { - treeViewer.refresh(element); - } else { - treeViewer.insert(element, addition, position); - } - } - } - - /** - * Ensures that the given set of children have the given parent as one of - * their parents. - * - * @param parentPath - * @param children - */ - private void addParent(TreePath parentPath, List children) { - for (Iterator iter = children.iterator(); iter.hasNext();) { - Object next = iter.next(); - - OrderedTreeNode nextNode = getNode(parentPath.createChildPath(next)); - nextNode.addParent(parentPath); - } - } - - /** - * Returns the element that should be inserted into the tree when fetching - * the children of the node that is both stale and empty. - * - * @return the element that should be inserted into the tree when fetching - * the children of a node that is stale and empty - */ - public final Object getPendingNode() { - return pendingNode; - } - - /** - * Returns the IObservableList representing the children of the given node. - * Never null. - * - * @param parent - * parent element. Must be a valid node from the tree. - * @return the list of children of the given parent node - */ - public IObservableList getChildrenList(TreePath parent) { - IObservableList result = getNode(parent).getChildrenList(); - - return result; - } - - public void dispose() { - if (treeViewer != null) { - try { - avoidViewerUpdates++; - enqueuedPrefetches.clear(); - Object[] keys = mapElementToTreeNode.keySet().toArray(); - - for (int i = 0; i < keys.length; i++) { - Object key = keys[i]; - - OrderedTreeNode result = (OrderedTreeNode) mapElementToTreeNode.get(key); - if (result != null) { - result.dispose(); - } - } - } finally { - avoidViewerUpdates--; - } - } - } - - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { - // This should only ever be called for a single viewer - setViewer(viewer); - - if (oldInput != null && newInput != null && oldInput.equals(newInput)) { - return; - } - - try { - avoidViewerUpdates++; - removeIfUnused(oldInput); - } finally { - avoidViewerUpdates--; - } - } - - private void removeIfUnused(Object element) { - OrderedTreeNode result = (OrderedTreeNode) mapElementToTreeNode - .get(element); - if (result != null && result.getParent() == null) { - mapElementToTreeNode.remove(element); - elements.doFireDiff(Collections.EMPTY_SET, Collections - .singleton(element)); - result.dispose(); - } - } - - private void setViewer(Viewer viewer) { - if (!(viewer instanceof TreeViewer)) { - throw new IllegalArgumentException( - "This content provider can only be used with TreeViewers"); //$NON-NLS-1$ - } - TreeViewer newTreeViewer = (TreeViewer) viewer; - - if (newTreeViewer != treeViewer) { - if (treeViewer != null) { - treeViewer.removeTreeListener(expandListener); - } - - this.treeViewer = newTreeViewer; - if (newTreeViewer != null) { - newTreeViewer.addTreeListener(expandListener); - } - } - } - - public Object[] getChildren(TreePath parentElement) { - List result = getNode(parentElement).getChildren(); - - addParent(parentElement, result); - - return result.toArray(); - } - - private OrderedTreeNode getNode(TreePath parentElement) { - OrderedTreeNode result = (OrderedTreeNode) mapElementToTreeNode - .get(parentElement); - if (result == null) { - result = new OrderedTreeNode(parentElement, this); - mapElementToTreeNode.put(parentElement, result); - elements.fireSetChange(Diffs.createSetDiff(Collections - .singleton(parentElement), Collections.EMPTY_SET)); - } - return result; - } - - /** - * Returns the set of all elements that have been discovered in this tree so - * far. Callers must not dispose this set. Never null. - * - * @return the set of all elements that have been discovered in this tree so - * far. - */ - public IObservableSet getKnownElements() { - return elements; - } - - /* package */void changeStale(int staleDelta) { - staleCount += staleDelta; - processPrefetches(); - elements.setStale(staleCount != 0); - } - - /** - * Returns the associated tree viewer. - * - * @return the associated tree viewer - */ - public TreeViewer getViewer() { - return treeViewer; - } - - /** - * Returns true iff the given element is stale. - * - * @param element - * the element to query for staleness. Must exist in the tree. - * @return true iff the given element is stale - */ - public boolean isDirty(TreePath element) { - return getChildrenList(element).isStale(); - } - - /* package */void enqueuePrefetch(OrderedTreeNode node) { - if (prefetchingTree.shouldPrefetch(node.getElement())) { - if (staleCount == 0) { - // Call node.getChildren()... this will cause us to start - // listening to the - // node and will trigger prefetching. Don't call prefetch since - // this method - // is intended to be called inside getters (which will simply - // return the - // fetched nodes) and prefetch() is intended to be called inside - // an asyncExec, - // which will notify the viewer directly of the newly discovered - // nodes. - node.getChildren(); - } else { - enqueuedPrefetches.add(node); - while (maxPrefetches >= 0 - && enqueuedPrefetches.size() > maxPrefetches) { - enqueuedPrefetches.removeFirst(); - } - } - } - } - - private void processPrefetches() { - while (staleCount == 0 && !enqueuedPrefetches.isEmpty()) { - OrderedTreeNode next = (OrderedTreeNode) enqueuedPrefetches.removeLast(); - - // Note that we don't remove nodes from the prefetch queue when they - // are disposed, - // so we may encounter disposed nodes at this time. - if (!next.isDisposed()) { - next.prefetch(); - } - } - } - - public TreePath[] getParents(Object element) { - return new TreePath[0]; - } - - public boolean hasChildren(TreePath path) { - return getNode(path).shouldShowPlus(); - } - - public Object[] getElements(Object inputElement) { - return getChildren(new TreePath(new Object[]{inputElement})); - } - -} diff --git a/bundles/org.eclipse.jface.databinding/src/org/eclipse/jface/internal/databinding/internal/viewers/OrderedTreeNode.java b/bundles/org.eclipse.jface.databinding/src/org/eclipse/jface/internal/databinding/internal/viewers/OrderedTreeNode.java deleted file mode 100644 index 6fd08ae2..00000000 --- a/bundles/org.eclipse.jface.databinding/src/org/eclipse/jface/internal/databinding/internal/viewers/OrderedTreeNode.java +++ /dev/null @@ -1,306 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2006, 2007 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.jface.internal.databinding.internal.viewers; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import org.eclipse.core.databinding.observable.IStaleListener; -import org.eclipse.core.databinding.observable.Observables; -import org.eclipse.core.databinding.observable.StaleEvent; -import org.eclipse.core.databinding.observable.list.IListChangeListener; -import org.eclipse.core.databinding.observable.list.IObservableList; -import org.eclipse.core.databinding.observable.list.ListChangeEvent; -import org.eclipse.core.databinding.observable.list.ListDiffEntry; -import org.eclipse.jface.viewers.TreePath; - -/* package */class OrderedTreeNode implements IListChangeListener, - IStaleListener { - private OrderedTreeContentProvider contentProvider; - - private TreePath element; - - // Stores the set of parents (null if there are less than 2) - private HashSet parents = null; - - // Stores one representative parent. If there is more than one parent, - // the complete set of parents can be found in the parents set. - Object parent; - - /** - * List of child elements. - */ - private IObservableList children; - - private boolean hasPendingNode = false; - - private boolean isStale; - - private boolean listeningToChildren = false; - - private boolean prefetchEnqueued = false; - - /** - * @param treePath - * @param cp - */ - public OrderedTreeNode(TreePath treePath, OrderedTreeContentProvider cp) { - this.element = treePath; - this.contentProvider = cp; - children = contentProvider.createChildList(treePath); - if (children == null) { - children = Observables.emptyObservableList(contentProvider.getKnownElements().getRealm()); - listeningToChildren = true; - } - hasPendingNode = children.isStale(); - } - - /** - * @param parent - */ - public void addParent(Object parent) { - if (this.parent == null) { - this.parent = parent; - } else { - if (parent.equals(this.parent)) { - return; - } - if (parents == null) { - parents = new HashSet(); - parents.add(this.parent); - } - parents.add(parent); - } - } - - /** - * @param parent - */ - public void removeParent(Object parent) { - if (this.parents != null) { - parents.remove(parent); - } - - if (parent == this.parent) { - if (parents == null || parents.isEmpty()) { - this.parent = null; - } else { - this.parent = parents.iterator().next(); - } - } - - if (this.parents != null && this.parents.size() <= 1) { - this.parents = null; - } - } - - /** - * Returns the list of children for this node. If new children are - * discovered later, they will be added directly to the viewer. - * - * @return the list of children of this node - */ - public List getChildren() { - if (!listeningToChildren) { - listeningToChildren = true; - children.addListChangeListener(this); - hasPendingNode = children.isEmpty() && children.isStale(); - children.addStaleListener(this); - updateStale(); - } - - // If the child set is stale and empty, show the "pending" node - if (hasPendingNode) { - Object pendingNode = contentProvider.getPendingNode(); - return Collections.singletonList(pendingNode); - } - return children; - } - - /** - * @return the observable list of children for this node - */ - public IObservableList getChildrenList() { - return children; - } - - private void updateStale() { - boolean willBeStale = children.isStale(); - if (willBeStale != isStale) { - isStale = willBeStale; - - contentProvider.changeStale(isStale ? 1 : -1); - } - } - - /** - * @return whether this node's children will change soon - */ - public boolean isStale() { - return isStale; - } - - /** - * Returns true if the viewer should show a plus sign for expanding this - * node. - * - * @return <code>true</code> if this node has children - */ - public boolean shouldShowPlus() { - if (children == null) { - // if (!hasPendingNode) { - // hasPendingNode = true; - // contentProvider.add(element, - // Collections.singleton(contentProvider.getPendingNode())); - // } - return true; - } - if (!listeningToChildren && !prefetchEnqueued) { - prefetchEnqueued = true; - contentProvider.enqueuePrefetch(this); - } - return !listeningToChildren || hasPendingNode || !children.isEmpty(); - } - - /** - * Disposes this node and removes all remaining children. - */ - public void dispose() { - if (children != null) { - if (listeningToChildren) { - contentProvider.remove(element, children, true); - children.removeListChangeListener(this); - children.removeStaleListener(this); - } - children.dispose(); - children = null; - - if (listeningToChildren && isStale) { - contentProvider.changeStale(-1); - } - } - } - - /** - * @return <code>true</code> if this node is disposed - */ - public boolean isDisposed() { - return children == null; - } - - /** - * Returns one representative parent, or null if this node is unparented. - * Use getParents() to get the complete set of known parents. - * - * @return a parent node, or <code>null</code> - */ - public Object getParent() { - return parent; - } - - /** - * @return the set of known parent nodes - */ - public Set getParents() { - if (parents == null) { - if (parent == null) { - return Collections.EMPTY_SET; - } - return Collections.singleton(parent); - } - return parents; - } - - /** - * Called when the child set changes. Should not be called directly by the - * viewer. - */ - public void handleListChange(ListChangeEvent event) { - boolean shouldHavePendingNode = children.isEmpty() - && children.isStale(); - - List removals = new ArrayList(); - ListDiffEntry[] differences = event.diff.getDifferences(); - for (int i = 0; i < differences.length; i++) { - ListDiffEntry diffEntry = differences[i]; - if (diffEntry.isAddition()) { - contentProvider.insert(element, diffEntry.getElement(), - diffEntry.getPosition()); - } else { - removals.add(diffEntry.getElement()); - } - } - - // Check if we should add the pending node - if (shouldHavePendingNode && !hasPendingNode) { - contentProvider - .insert(element, contentProvider.getPendingNode(), 0); - hasPendingNode = true; - } - - // Check if we should remove the pending node - if (!shouldHavePendingNode && hasPendingNode) { - removals.add(contentProvider.getPendingNode()); - hasPendingNode = false; - } - if (!removals.isEmpty()) { - contentProvider.remove(element, removals, children.isEmpty() - && !hasPendingNode); - } - - updateStale(); - } - - public void handleStale(StaleEvent event) { - boolean shouldHavePendingNode = children.isEmpty() - && children.isStale(); - - // Check if we should add the pending node - if (shouldHavePendingNode && !hasPendingNode) { - hasPendingNode = shouldHavePendingNode; - contentProvider.insert(element, Collections - .singletonList(contentProvider.getPendingNode()), 0); - } - - // Check if we should remove the pending node - if (!shouldHavePendingNode && hasPendingNode) { - hasPendingNode = shouldHavePendingNode; - contentProvider.remove(element, Collections - .singletonList(contentProvider.getPendingNode()), true); - } - - updateStale(); - } - - /** - * @return the element - */ - public Object getElement() { - return element; - } - - /** - * - */ - public void prefetch() { - List children = getChildren(); - if (!children.isEmpty()) { - contentProvider.add(element, children); - } else { - // We need to remove the + sign, and adding/removing elements won't - // do the trick - contentProvider.getViewer().refresh(element); - } - } -} diff --git a/bundles/org.eclipse.jface.databinding/src/org/eclipse/jface/internal/databinding/internal/viewers/PrefetchingTree.java b/bundles/org.eclipse.jface.databinding/src/org/eclipse/jface/internal/databinding/internal/viewers/PrefetchingTree.java deleted file mode 100644 index ac33cd50..00000000 --- a/bundles/org.eclipse.jface.databinding/src/org/eclipse/jface/internal/databinding/internal/viewers/PrefetchingTree.java +++ /dev/null @@ -1,40 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2006, 2007 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.jface.internal.databinding.internal.viewers; - - - -/* package */ class PrefetchingTree implements IPrefetchingTree { - - private static IPrefetchingTree instance; - - private PrefetchingTree() { - } - - public boolean shouldPrefetch(Object parentNode) { - return true; - } - - /** - * @param treeProvider - * @return a prefetching tree - */ - public static IPrefetchingTree getPrefetchingTree(Object treeProvider) { - if (treeProvider instanceof IPrefetchingTree) { - return (IPrefetchingTree)treeProvider; - } - if (instance == null) { - instance = new PrefetchingTree(); - } - return instance; - } - -} diff --git a/bundles/org.eclipse.jface.databinding/src/org/eclipse/jface/internal/databinding/internal/viewers/UnorderedTreeContentProvider.java b/bundles/org.eclipse.jface.databinding/src/org/eclipse/jface/internal/databinding/internal/viewers/UnorderedTreeContentProvider.java deleted file mode 100644 index fbd7dbd4..00000000 --- a/bundles/org.eclipse.jface.databinding/src/org/eclipse/jface/internal/databinding/internal/viewers/UnorderedTreeContentProvider.java +++ /dev/null @@ -1,422 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2006, 2007 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.jface.internal.databinding.internal.viewers; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.Set; - -import org.eclipse.core.databinding.observable.Diffs; -import org.eclipse.core.databinding.observable.set.AbstractObservableSet; -import org.eclipse.core.databinding.observable.set.IObservableSet; -import org.eclipse.core.databinding.observable.set.SetDiff; -import org.eclipse.core.internal.databinding.observable.tree.IUnorderedTreeProvider; -import org.eclipse.jface.viewers.ITreePathContentProvider; -import org.eclipse.jface.viewers.ITreeViewerListener; -import org.eclipse.jface.viewers.TreeExpansionEvent; -import org.eclipse.jface.viewers.TreePath; -import org.eclipse.jface.viewers.TreeViewer; -import org.eclipse.jface.viewers.Viewer; - -/** - * Converts an ITreeProvider into an ITreeContentProvider that is suitable for - * use with a JFace TreeViewer. - * - * <p> - * This content provider works correctly with trees containing duplicate - * elements. - * </p> - * - * @since 3.3 - */ -public class UnorderedTreeContentProvider implements ITreePathContentProvider { - - private HashMap mapElementToTreeNode = new HashMap(); - - private LinkedList enqueuedPrefetches = new LinkedList(); - - class KnownElementsSet extends AbstractObservableSet { - - /** - */ - protected KnownElementsSet() { - super(provider.getRealm()); - } - - /* - * (non-Javadoc) - * - * @see org.eclipse.jface.internal.databinding.provisional.observable.set.AbstractObservableSet#getWrappedSet() - */ - protected Set getWrappedSet() { - return mapElementToTreeNode.keySet(); - } - - void doFireDiff(Set added, Set removed) { - fireSetChange(Diffs.createSetDiff(added, removed)); - } - - void doFireStale(boolean isStale) { - if (isStale) { - fireStale(); - } else { - fireSetChange(Diffs.createSetDiff(Collections.EMPTY_SET, - Collections.EMPTY_SET)); - } - } - - public Object getElementType() { - return new Object(); - } - - protected void fireSetChange(SetDiff diff) { - super.fireSetChange(diff); - } - } - - KnownElementsSet elements; - - private ITreeViewerListener expandListener = new ITreeViewerListener() { - public void treeCollapsed(TreeExpansionEvent event) { - } - - public void treeExpanded(TreeExpansionEvent event) { - } - }; - - private IPrefetchingTree prefetchingTree; - - private IUnorderedTreeProvider provider; - - private Object pendingNode; - - private int avoidViewerUpdates; - - private TreeViewer treeViewer; - - private int staleCount = 0; - - private boolean useRefresh; - - private int maxPrefetches = -1; - - /** - * Constructs a content provider that will render the given tree in a - * TreeViewer. - * - * @param provider - * IObservableTree that provides the contents of the tree. The - * given provider map optionally implement IPrefetchingTree if it - * wants to selectively enable or disable prefetching from - * particular nodes. - * @param pendingNode - * element to insert whenever a node is being fetched in the - * background - */ - public UnorderedTreeContentProvider(IUnorderedTreeProvider provider, - Object pendingNode) { - this(provider, pendingNode, false); - } - - /** - * Constructs a content provider that will render the given tree in a - * TreeViewer. - * - * @param provider - * IObservableTree that provides the contents of the tree - * @param pendingNode - * element to insert whenever a node is being fetched in the - * background - * @param useRefresh - * true = notify the viewer of changes by calling refresh(...), - * false = notify the viewer of changes by calling add(...) and - * remove(...). Using false is more efficient, but may not work - * with TreeViewer subclasses. - */ - public UnorderedTreeContentProvider(IUnorderedTreeProvider provider, - Object pendingNode, boolean useRefresh) { - this.provider = provider; - this.prefetchingTree = PrefetchingTree.getPrefetchingTree(provider); - this.pendingNode = pendingNode; - this.useRefresh = useRefresh; - elements = new KnownElementsSet(); - } - - /** - * Sets the maximum number of pending prefetches. - * - * @param maxPrefetches - */ - public void setMaxPrefetches(int maxPrefetches) { - this.maxPrefetches = maxPrefetches; - } - - /* package */IObservableSet createChildSet(TreePath treePath) { - Object[] segments = new Object[treePath.getSegmentCount()]; - for (int i = 0; i < segments.length; i++) { - segments[i] = treePath.getSegment(i); - } - return provider - .createChildSet(new org.eclipse.core.internal.databinding.observable.tree.TreePath( - segments)); - } - - /* package */void remove(Object element, Set removals, boolean lastElement) { - if (avoidViewerUpdates == 0) { - for (Iterator iter = removals.iterator(); iter.hasNext();) { - Object next = iter.next(); - - UnorderedTreeNode nextNode = (UnorderedTreeNode) mapElementToTreeNode - .get(next); - if (nextNode != null) { - nextNode.removeParent(element); - removeIfUnused(nextNode); - } - } - - if (lastElement || useRefresh) { - treeViewer.refresh(element); - } else { - treeViewer.remove(element, removals.toArray()); - } - } - } - - /* package */void add(TreePath treePath, Set additions) { - if (avoidViewerUpdates == 0) { - // Handle new parents - addParent(treePath, additions); - if (useRefresh) { - treeViewer.refresh(treePath); - } else { - treeViewer.add(treePath, additions.toArray()); - } - } - } - - /** - * Ensures that the given set of children have the given parent as one of - * their parents. - * - * @param parent - * @param children - */ - private void addParent(TreePath parent, Set children) { - for (Iterator iter = children.iterator(); iter.hasNext();) { - Object next = iter.next(); - - UnorderedTreeNode nextNode = getNode(parent.createChildPath(next)); - nextNode.addParent(parent); - } - } - - /** - * Returns the element that should be inserted into the tree when fetching - * the children of the node that is both stale and empty. - * - * @return the element that should be inserted into the tree when fetching - * the children of a node that is stale and empty - */ - public final Object getPendingNode() { - return pendingNode; - } - - /** - * Returns the IObservableSet representing the children of the given node. - * Never null. - * - * @param parent - * parent element. Must be a valid node from the tree. - * @return the set of children of the given parent node - */ - public IObservableSet getChildrenSet(TreePath parent) { - IObservableSet result = getNode(parent).getChildrenSet(); - - return result; - } - - public void dispose() { - if (treeViewer != null) { - try { - avoidViewerUpdates++; - enqueuedPrefetches.clear(); - Object[] keys = mapElementToTreeNode.keySet().toArray(); - - for (int i = 0; i < keys.length; i++) { - Object key = keys[i]; - - UnorderedTreeNode result = (UnorderedTreeNode) mapElementToTreeNode - .get(key); - if (result != null) { - result.dispose(); - } - } - } finally { - avoidViewerUpdates--; - } - } - } - - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { - // This should only ever be called for a single viewer - setViewer(viewer); - - if (oldInput != null && newInput != null && oldInput.equals(newInput)) { - return; - } - - try { - avoidViewerUpdates++; - removeIfUnused(oldInput); - } finally { - avoidViewerUpdates--; - } - } - - private void removeIfUnused(Object element) { - UnorderedTreeNode result = (UnorderedTreeNode) mapElementToTreeNode - .get(element); - if (result != null && result.getParent() == null) { - mapElementToTreeNode.remove(element); - elements.doFireDiff(Collections.EMPTY_SET, Collections - .singleton(element)); - result.dispose(); - } - } - - private void setViewer(Viewer viewer) { - if (!(viewer instanceof TreeViewer)) { - throw new IllegalArgumentException( - "This content provider can only be used with TreeViewers"); //$NON-NLS-1$ - } - TreeViewer newTreeViewer = (TreeViewer) viewer; - - if (newTreeViewer != treeViewer) { - if (treeViewer != null) { - treeViewer.removeTreeListener(expandListener); - } - - this.treeViewer = newTreeViewer; - if (newTreeViewer != null) { - newTreeViewer.addTreeListener(expandListener); - } - } - } - - public Object[] getChildren(TreePath parentPath) { - Set result = getNode(parentPath).getChildren(); - - addParent(parentPath, result); - - return result.toArray(); - } - - private UnorderedTreeNode getNode(TreePath parentPath) { - UnorderedTreeNode result = (UnorderedTreeNode) mapElementToTreeNode - .get(parentPath); - if (result == null) { - result = new UnorderedTreeNode(parentPath, this); - mapElementToTreeNode.put(parentPath, result); - elements.fireSetChange(Diffs.createSetDiff(Collections - .singleton(parentPath), Collections.EMPTY_SET)); - } - return result; - } - - public TreePath[] getParents(Object element) { - return new TreePath[0]; - } - - public boolean hasChildren(TreePath element) { - return getNode(element).shouldShowPlus(); - } - - public Object[] getElements(Object inputElement) { - return getChildren(new TreePath(new Object[]{inputElement})); - } - - /** - * Returns the set of all elements that have been discovered in this tree so - * far. Callers must not dispose this set. Never null. - * - * @return the set of all elements that have been discovered in this tree so - * far. - */ - public IObservableSet getKnownElements() { - return elements; - } - - /* package */void changeStale(int staleDelta) { - staleCount += staleDelta; - processPrefetches(); - elements.setStale(staleCount != 0); - } - - /** - * Returns the associated tree viewer. - * - * @return the associated tree viewer - */ - public TreeViewer getViewer() { - return treeViewer; - } - - /** - * Returns true iff the given element is stale. - * - * @param element - * the element to query for staleness. Must exist in the tree. - * @return true iff the given element is stale - */ - public boolean isDirty(TreePath element) { - return getChildrenSet(element).isStale(); - } - - /* package */void enqueuePrefetch(UnorderedTreeNode node) { - if (prefetchingTree.shouldPrefetch(node.getElement())) { - if (staleCount == 0) { - // Call node.getChildren()... this will cause us to start - // listening to the - // node and will trigger prefetching. Don't call prefetch since - // this method - // is intended to be called inside getters (which will simply - // return the - // fetched nodes) and prefetch() is intended to be called inside - // an asyncExec, - // which will notify the viewer directly of the newly discovered - // nodes. - node.getChildren(); - } else { - enqueuedPrefetches.add(node); - while (maxPrefetches >= 0 - && enqueuedPrefetches.size() > maxPrefetches) { - enqueuedPrefetches.removeFirst(); - } - } - } - } - - private void processPrefetches() { - while (staleCount == 0 && !enqueuedPrefetches.isEmpty()) { - UnorderedTreeNode next = (UnorderedTreeNode) enqueuedPrefetches - .removeLast(); - - // Note that we don't remove nodes from the prefetch queue when they - // are disposed, - // so we may encounter disposed nodes at this time. - if (!next.isDisposed()) { - next.prefetch(); - } - } - } -} diff --git a/bundles/org.eclipse.jface.databinding/src/org/eclipse/jface/internal/databinding/provisional/viewers/IParentProvider.java b/bundles/org.eclipse.jface.databinding/src/org/eclipse/jface/internal/databinding/provisional/viewers/IParentProvider.java new file mode 100644 index 00000000..f4b35bf7 --- /dev/null +++ b/bundles/org.eclipse.jface.databinding/src/org/eclipse/jface/internal/databinding/provisional/viewers/IParentProvider.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 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 + * Stefan Xenos, IBM - initial API and implementation + *******************************************************************************/ +package org.eclipse.jface.internal.databinding.provisional.viewers; + +/** + * Returns the parent of elements in a tree. + * + * @since 3.3 + */ +public interface IParentProvider { + + /** + * TODO: comment me + * + * @param child + * @return TODO: comment + */ + public Object getParent(Object child); +} diff --git a/bundles/org.eclipse.jface.databinding/src/org/eclipse/jface/internal/databinding/internal/viewers/UnorderedTreeNode.java b/bundles/org.eclipse.jface.databinding/src/org/eclipse/jface/internal/databinding/provisional/viewers/TreeNode.java index f9abf733..9748ff8f 100644 --- a/bundles/org.eclipse.jface.databinding/src/org/eclipse/jface/internal/databinding/internal/viewers/UnorderedTreeNode.java +++ b/bundles/org.eclipse.jface.databinding/src/org/eclipse/jface/internal/databinding/provisional/viewers/TreeNode.java @@ -7,8 +7,9 @@ * * Contributors: * IBM Corporation - initial API and implementation + * Stefan Xenos, IBM - initial API and implementation *******************************************************************************/ -package org.eclipse.jface.internal.databinding.internal.viewers; +package org.eclipse.jface.internal.databinding.provisional.viewers; import java.util.Collections; import java.util.HashSet; @@ -20,11 +21,13 @@ import org.eclipse.core.databinding.observable.StaleEvent; import org.eclipse.core.databinding.observable.set.IObservableSet; import org.eclipse.core.databinding.observable.set.ISetChangeListener; import org.eclipse.core.databinding.observable.set.SetChangeEvent; -import org.eclipse.jface.viewers.TreePath; +import org.eclipse.core.databinding.observable.set.SetDiff; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.swt.widgets.Control; -/* package */ class UnorderedTreeNode implements ISetChangeListener, IStaleListener { +/* package */ class TreeNode implements ISetChangeListener, IStaleListener { private UnorderedTreeContentProvider contentProvider; - private TreePath treePath; + private Object element; // Stores the set of parents (null if there are less than 2) private HashSet parents = null; @@ -47,12 +50,12 @@ import org.eclipse.jface.viewers.TreePath; * @param element * @param cp */ - public UnorderedTreeNode(TreePath element, UnorderedTreeContentProvider cp) { - this.treePath = element; + public TreeNode(Object element, UnorderedTreeContentProvider cp) { + this.element = element; this.contentProvider = cp; children = contentProvider.createChildSet(element); if (children == null) { - children = Observables.emptyObservableSet(contentProvider.getKnownElements().getRealm()); + children = Observables.emptyObservableSet(); listeningToChildren = true; } hasPendingNode = children.isStale(); @@ -101,7 +104,7 @@ import org.eclipse.jface.viewers.TreePath; * Returns the set of children for this node. If new children are discovered later, they * will be added directly to the viewer. * - * @return the set of children + * @return TODO */ public Set getChildren() { if (!listeningToChildren) { @@ -121,7 +124,7 @@ import org.eclipse.jface.viewers.TreePath; } /** - * @return the observable set of children + * @return TODO */ public IObservableSet getChildrenSet() { return children; @@ -137,7 +140,7 @@ import org.eclipse.jface.viewers.TreePath; } /** - * @return <code>true</code> if the children of this node will change soon + * @return TODO */ public boolean isStale() { return isStale; @@ -147,7 +150,7 @@ import org.eclipse.jface.viewers.TreePath; * Returns true if the viewer should show a plus sign for expanding this * node. * - * @return <code>true</code> if this node may have children + * @return TODO */ public boolean shouldShowPlus() { if (children == null) { @@ -170,7 +173,7 @@ import org.eclipse.jface.viewers.TreePath; public void dispose() { if (children != null) { if (listeningToChildren) { - contentProvider.remove(treePath, children, true); + contentProvider.remove(element, children, true); children.removeSetChangeListener(this); children.removeStaleListener(this); } @@ -184,7 +187,7 @@ import org.eclipse.jface.viewers.TreePath; } /** - * @return <code>true</code> if this node is disposed + * @return TODO */ public boolean isDisposed() { return children == null; @@ -194,32 +197,49 @@ import org.eclipse.jface.viewers.TreePath; * Returns one representative parent, or null if this node is unparented. Use * getParents() to get the complete set of known parents. * - * @return a parent node, or null + * @return TODO */ public Object getParent() { return parent; } /** - * @return the set of known parent nodes + * + * @return the set of all known parents for this node */ public Set getParents() { if (parents == null) { if (parent == null) { return Collections.EMPTY_SET; + } else { + return Collections.singleton(parent); } - return Collections.singleton(parent); + } else { + return parents; } - return parents; } /** * Called when the child set changes. Should not be called directly by the viewer. */ public void handleSetChange(SetChangeEvent event) { + SetDiff diff = event.diff; + TreeViewer viewer = this.contentProvider.getViewer(); + if (viewer != null) { + Control control = viewer.getControl(); + if (control != null) { + if (control.isDisposed()) { + // If the widgetry was disposed without notifying the content provider, then + // dispose the content provider now and stop processing events. + contentProvider.dispose(); + return; + } + } + } + boolean shouldHavePendingNode = children.isEmpty() && children.isStale(); - Set additions = event.diff.getAdditions(); + Set additions = diff.getAdditions(); // Check if we should add the pending node if (shouldHavePendingNode && !hasPendingNode) { HashSet newAdditions = new HashSet(); @@ -229,7 +249,7 @@ import org.eclipse.jface.viewers.TreePath; hasPendingNode = true; } - Set removals = event.diff.getRemovals(); + Set removals = diff.getRemovals(); // Check if we should remove the pending node if (!shouldHavePendingNode && hasPendingNode) { HashSet newRemovals = new HashSet(); @@ -239,50 +259,75 @@ import org.eclipse.jface.viewers.TreePath; hasPendingNode = false; } if (!additions.isEmpty()) { - contentProvider.add(treePath, additions); + contentProvider.add(element, additions); } if (!removals.isEmpty()) { - contentProvider.remove(treePath, removals, children.isEmpty() && !hasPendingNode); + contentProvider.remove(element, removals, children.isEmpty() && !hasPendingNode); } updateStale(); } - public void handleStale(StaleEvent event) { + public void handleStale(StaleEvent staleEvent) { + TreeViewer viewer = this.contentProvider.getViewer(); + if (viewer != null) { + Control control = viewer.getControl(); + if (control != null) { + if (control.isDisposed()) { + // If the widgetry was disposed without notifying the content provider, then + // dispose the content provider now and stop processing events. + contentProvider.dispose(); + return; + } + } + } + boolean shouldHavePendingNode = children.isEmpty() && children.isStale(); // Check if we should add the pending node if (shouldHavePendingNode && !hasPendingNode) { hasPendingNode = shouldHavePendingNode; - contentProvider.add(treePath, Collections.singleton(contentProvider.getPendingNode())); + contentProvider.add(element, Collections.singleton(contentProvider.getPendingNode())); } // Check if we should remove the pending node if (!shouldHavePendingNode && hasPendingNode) { hasPendingNode = shouldHavePendingNode; - contentProvider.remove(treePath, Collections.singleton(contentProvider.getPendingNode()), true); + contentProvider.remove(element, Collections.singleton(contentProvider.getPendingNode()), true); } updateStale(); } /** - * @return the element + * @return TODO */ public Object getElement() { - return treePath; + return element; } /** * */ public void prefetch() { + TreeViewer viewer = this.contentProvider.getViewer(); + if (viewer != null) { + Control control = viewer.getControl(); + if (control != null) { + if (control.isDisposed()) { + // If the widgetry has been disposed, then avoid sending anything + // to the viewer. + return; + } + } + } + Set children = getChildren(); if (!children.isEmpty()) { - contentProvider.add(treePath, children); + contentProvider.add(element, children); } else { // We need to remove the + sign, and adding/removing elements won't do the trick - contentProvider.getViewer().refresh(treePath); + contentProvider.getViewer().refresh(element); } } } diff --git a/bundles/org.eclipse.jface.databinding/src/org/eclipse/jface/internal/databinding/provisional/viewers/UnorderedTreeContentProvider.java b/bundles/org.eclipse.jface.databinding/src/org/eclipse/jface/internal/databinding/provisional/viewers/UnorderedTreeContentProvider.java new file mode 100644 index 00000000..4a00edb1 --- /dev/null +++ b/bundles/org.eclipse.jface.databinding/src/org/eclipse/jface/internal/databinding/provisional/viewers/UnorderedTreeContentProvider.java @@ -0,0 +1,528 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 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 + * Stefan Xenos, IBM - initial API and implementation + *******************************************************************************/ +package org.eclipse.jface.internal.databinding.provisional.viewers; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import org.eclipse.core.databinding.observable.Diffs; +import org.eclipse.core.databinding.observable.set.AbstractObservableSet; +import org.eclipse.core.databinding.observable.set.IObservableSet; +import org.eclipse.core.databinding.observable.set.SetDiff; +import org.eclipse.core.internal.databinding.observable.tree.IUnorderedTreeProvider; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.ITreePathContentProvider; +import org.eclipse.jface.viewers.ITreeViewerListener; +import org.eclipse.jface.viewers.TreeExpansionEvent; +import org.eclipse.jface.viewers.TreePath; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jface.viewers.Viewer; + +/** + * TODO: comment + * @since 3.3 + * + */ +public class UnorderedTreeContentProvider implements ITreeContentProvider, ITreePathContentProvider { + + private HashMap mapElementToTreeNode = new HashMap(); + private LinkedList enqueuedPrefetches = new LinkedList(); + private IParentProvider rootParentProvider = null; + private boolean useTreePaths = false; + + class KnownElementsSet extends AbstractObservableSet { + + protected KnownElementsSet() { + super(); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.internal.databinding.provisional.observable.set.AbstractObservableSet#getWrappedSet() + */ + protected Set getWrappedSet() { + return mapElementToTreeNode.keySet(); + } + + void doFireDiff(Set added, Set removed) { + fireSetChange(Diffs.createSetDiff(added, removed)); + } + + public void fireSetChange(SetDiff diff) { + super.fireSetChange(diff); + } + + void doFireStale(boolean isStale) { + if (isStale) { + fireStale(); + } else { + fireSetChange(Diffs.createSetDiff(Collections.EMPTY_SET, Collections.EMPTY_SET)); + } + } + + /* (non-Javadoc) + * @see org.eclipse.jface.internal.databinding.provisional.observable.set.IObservableSet#getElementType() + */ + public Object getElementType() { + return new Object(); + } + } + + KnownElementsSet elements = new KnownElementsSet(); + + private ITreeViewerListener expandListener = new ITreeViewerListener() { + public void treeCollapsed(TreeExpansionEvent event) { + } + + public void treeExpanded(TreeExpansionEvent event) { + } + }; + + private IUnorderedTreeProvider provider; + private Object pendingNode; + + private int avoidViewerUpdates; + + private TreeViewer treeViewer; + + private int staleCount = 0; + private boolean useRefresh; + private int maxPrefetches = 0; + + /** + * Constructs a content provider that will render the given tree in a TreeViewer. + * + * @param provider IObservableTree that provides the contents of the tree + * @param pendingNode element to insert whenever a node is being fetched in the background + * @param useRefresh true = notify the viewer of changes by calling refresh(...), false = + * notify the viewer of changes by calling add(...) and remove(...). Using false + * is more efficient, but may not work with TreeViewer subclasses. + */ + public UnorderedTreeContentProvider(IUnorderedTreeProvider provider, + Object pendingNode, boolean useRefresh) { + this.provider = provider; + this.pendingNode = pendingNode; + this.useRefresh = useRefresh; + } + + /** + * Sets whether this content provider should add/remove elements using + * TreePaths (true) or elements (false). + * + * <p></p> + * <p>When using elements:</p> + * + * <ul> + * <li>Cycles are permitted (elements can be their own ancestor)</li> + * <li>Addition, removal, and refresh are slightly faster</li> + * <li>It is not possible to have more than one content provider per tree</li> + * <li>The setRootPath(...) method is ignored</li> + * </ul> + * + * <p></p> + * <p>When using TreePaths:</p> + * + * <ul> + * <li>Cycles are not permitted (elements cannot be their own parent)</li> + * <li>Addition, removal, and refresh are slightly slower</li> + * <li>It is possible to use more than one content provider in the same tree</li> + * <li>The setRootPath(...) method can be used to direct the output to a particular + * subtree</li> + * </ul> + * + * @param usePaths + */ + public void useTreePaths(boolean usePaths) { + this.useTreePaths = usePaths; + } + + /** + * @param rootParentProvider + */ + public void setRootPath(IParentProvider rootParentProvider) { + this.rootParentProvider = rootParentProvider; + } + + /** + * @param maxPrefetches + */ + public void setMaxPrefetches(int maxPrefetches) { + this.maxPrefetches = maxPrefetches; + } + + /* package */ IObservableSet createChildSet(Object element) { + return provider.createChildSet(element); + } + + /* package */ void remove(Object element, Set removals, boolean lastElement) { + if (removals.isEmpty()) { + return; + } + if (avoidViewerUpdates == 0) { + if (lastElement || useRefresh) { + doRefresh(element); + } else { + if (useTreePaths) { + List toRemove = new ArrayList(); + TreePath[] parents = getParents(element); + for (int i = 0; i < parents.length; i++) { + TreePath parent = parents[i]; + + for (Iterator iter = removals.iterator(); iter.hasNext();) { + Object elementToRemove = (Object) iter.next(); + + toRemove.add(parent.createChildPath(element).createChildPath(elementToRemove)); + } + } + + treeViewer.remove((TreePath[]) toRemove.toArray(new TreePath[toRemove.size()])); + } else { + treeViewer.remove(element, removals.toArray()); + } + } + for (Iterator iter = removals.iterator(); iter.hasNext();) { + Object next = (Object) iter.next(); + + TreeNode nextNode = (TreeNode)mapElementToTreeNode.get(next); + if (nextNode != null) { + nextNode.removeParent(element); + removeIfUnused(nextNode); + } + } + } + } + + /* package */ void add(Object element, Set additions) { + if (additions.isEmpty()) { + return; + } + if (avoidViewerUpdates == 0) { + // Handle new parents + addParent(element, additions); + if (useRefresh) { + doRefresh(element); + } else { + if (useTreePaths) { + TreePath[] parents = getParents(element); + for (int i = 0; i < parents.length; i++) { + TreePath parent = parents[i]; + + treeViewer.add(parent.createChildPath(element), additions.toArray()); + } + } else { + treeViewer.add(element, additions.toArray()); + } + } + } + } + + private void doRefresh(Object element) { + treeViewer.refresh(element); + } + + /** + * Ensures that the given set of children have the given parent as + * one of their parents. + * + * @param parent + * @param children + */ + private void addParent(Object parent, Set children) { + for (Iterator iter = children.iterator(); iter.hasNext();) { + Object next = (Object) iter.next(); + + TreeNode nextNode = getNode(next); + nextNode.addParent(parent); + } + } + + /** + * @return saouesnth + */ + public final Object getPendingNode() { + return pendingNode; + } + + /** + * @param parent + * @return aueosnht + */ + public IObservableSet getChildrenSet(Object parent) { + IObservableSet result = getNode(parent).getChildrenSet(); + + return result; + } + + public void dispose() { + if (treeViewer != null) { + try { + avoidViewerUpdates++; + enqueuedPrefetches.clear(); + Object[] keys = mapElementToTreeNode.keySet().toArray(); + + for (int i = 0; i < keys.length; i++) { + Object key = keys[i]; + + TreeNode result = (TreeNode)mapElementToTreeNode.get(key); + if (result != null) { + result.dispose(); + } + } + setViewer(null); + } finally { + avoidViewerUpdates--; + } + } + } + + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + // This should only ever be called for a single viewer + setViewer(viewer); + + if (oldInput != null && newInput != null && oldInput.equals(newInput)) { + return; + } + + try { + avoidViewerUpdates++; + TreeNode oldNode = (TreeNode)mapElementToTreeNode.get(oldInput); + if (oldNode != null) { + removeIfUnused(oldNode); + } + } finally { + avoidViewerUpdates--; + } + } + + private void removeIfUnused(TreeNode toRemove) { + //TreeNode result = (TreeNode)mapElementToTreeNode.get(element); + Object element = toRemove.getElement(); + if (toRemove.getParent() == null) { + mapElementToTreeNode.remove(element); + elements.doFireDiff(Collections.EMPTY_SET, Collections.singleton(element)); + toRemove.dispose(); + } + } + + private void setViewer(Viewer viewer) { + if (viewer != null && !(viewer instanceof TreeViewer)) { + throw new IllegalArgumentException("This content provider can only be used with TreeViewers"); //$NON-NLS-1$ + } + TreeViewer newTreeViewer = (TreeViewer) viewer; + + if (newTreeViewer != treeViewer) { + if (treeViewer != null) { + treeViewer.removeTreeListener(expandListener); + } + + this.treeViewer = newTreeViewer; + if (newTreeViewer != null) { + newTreeViewer.addTreeListener(expandListener); + } + } + } + + public Object[] getChildren(Object parentElement) { + Set result = getNode(parentElement).getChildren(); + + addParent(parentElement, result); + + return result.toArray(); + } + + private TreeNode getNode(Object parentElement) { + TreeNode result = (TreeNode)mapElementToTreeNode.get(parentElement); + if (result == null) { + result = new TreeNode(parentElement, this); + mapElementToTreeNode.put(parentElement, result); + elements.fireSetChange(Diffs.createSetDiff(Collections.singleton(parentElement), + Collections.EMPTY_SET)); + } + return result; + } + + public Object getParent(Object element) { + Object result = getNode(element).getParent(); + if (result == null && rootParentProvider != null) { + result = rootParentProvider.getParent(element); + } + return result; + } + + public boolean hasChildren(Object element) { + return getNode(element).shouldShowPlus(); + } + + public Object[] getElements(Object inputElement) { + return getChildren(inputElement); + } + + /** + * @return aouesnth + */ + public IObservableSet getKnownElements() { + return elements; + } + + /* package */ void changeStale(int staleDelta) { + staleCount += staleDelta; + processPrefetches(); + elements.setStale(staleCount != 0); + } + + /** + * @return aoueesnth + */ + public TreeViewer getViewer() { + return treeViewer; + } + + /** + * @param element + * @return aoeusnth + */ + public boolean isDirty(Object element) { + return false; + } + + /* package */ void enqueuePrefetch(TreeNode node) { + if (maxPrefetches > 0 || maxPrefetches == -1) { + if (staleCount == 0) { + // Call node.getChildren()... this will cause us to start listening to the + // node and will trigger prefetching. Don't call prefetch since this method + // is intended to be called inside getters (which will simply return the + // fetched nodes) and prefetch() is intended to be called inside an asyncExec, + // which will notify the viewer directly of the newly discovered nodes. + node.getChildren(); + } else { + enqueuedPrefetches.add(node); + while (maxPrefetches >= 0 && enqueuedPrefetches.size() > maxPrefetches) { + enqueuedPrefetches.removeFirst(); + } + } + } + } + + private void processPrefetches() { + while (staleCount == 0 && !enqueuedPrefetches.isEmpty()) { + TreeNode next = (TreeNode)enqueuedPrefetches.removeLast(); + + // Note that we don't remove nodes from the prefetch queue when they are disposed, + // so we may encounter disposed nodes at this time. + if (!next.isDisposed()) { + next.prefetch(); + } + } + } + + public Object[] getChildren(TreePath parentPath) { + return getChildren(parentPath.getLastSegment()); + } + + public TreePath[] getParents(Object element) { + // Compute all paths that do not contain cycles + /** + * List of Lists + */ + List parentPaths = computeParents(element, new HashSet()); + + /** + * List of TreePath + */ + List result = new ArrayList(); + + for (Iterator iterator = parentPaths.iterator(); iterator.hasNext();) { + List nextPath = (List) iterator.next(); + + LinkedList resultPath = new LinkedList(); + resultPath.addAll(nextPath); + Object nextParent = resultPath.isEmpty() ? element : resultPath.getFirst(); + for(;nextParent != null;) { + if (rootParentProvider != null) { + nextParent = rootParentProvider.getParent(nextParent); + if (nextParent != null) { + resultPath.addFirst(nextParent); + } + } else { + nextParent = null; + } + } + + result.add(new TreePath(resultPath.toArray())); + } + + if (result.isEmpty() && rootParentProvider != null) { + Object nextParent = rootParentProvider.getParent(element); + if (nextParent != null) { + LinkedList resultPath = new LinkedList(); + while (nextParent != null) { + resultPath.addFirst(nextParent); + nextParent = rootParentProvider.getParent(nextParent); + } + + result.add(new TreePath(resultPath.toArray())); + } + + } + + return (TreePath[]) result.toArray(new TreePath[result.size()]); + } + + /** + * + * @param node + * @param toIgnore + * @return a list of Lists, indicating all known paths to the given node + */ + private List computeParents(Object node, HashSet toIgnore) { + List result = new ArrayList(); + boolean containedNode = toIgnore.add(node); + + TreeNode tn = getNode(node); + + HashSet parents = new HashSet(); + parents.addAll(tn.getParents()); + parents.removeAll(toIgnore); + if (parents.isEmpty()) { + ArrayList newPath = new ArrayList(); + result.add(newPath); + } else { + for (Iterator iterator = parents.iterator(); iterator.hasNext();) { + Object parent = iterator.next(); + + List parentPaths = computeParents(parent, toIgnore); + + for (Iterator iterator2 = parentPaths.iterator(); iterator2 + .hasNext();) { + List parentPath = (List) iterator2.next(); + + parentPath.add(parent); + result.add(parentPath); + } + } + } + + if (containedNode) { + toIgnore.remove(node); + } + return result; + } + + public boolean hasChildren(TreePath path) { + return hasChildren(path.getLastSegment()); + } +} diff --git a/examples/org.eclipse.jface.examples.databinding/src/org/eclipse/jface/examples/databinding/contentprovider/test/TreeContentProviderTest.java b/examples/org.eclipse.jface.examples.databinding/src/org/eclipse/jface/examples/databinding/contentprovider/test/TreeContentProviderTest.java index 5f38985d..80193573 100644 --- a/examples/org.eclipse.jface.examples.databinding/src/org/eclipse/jface/examples/databinding/contentprovider/test/TreeContentProviderTest.java +++ b/examples/org.eclipse.jface.examples.databinding/src/org/eclipse/jface/examples/databinding/contentprovider/test/TreeContentProviderTest.java @@ -16,8 +16,8 @@ import org.eclipse.core.databinding.observable.set.IObservableSet; import org.eclipse.core.databinding.observable.set.UnionSet; import org.eclipse.core.databinding.observable.set.WritableSet; import org.eclipse.core.internal.databinding.observable.tree.IUnorderedTreeProvider; -import org.eclipse.core.internal.databinding.observable.tree.TreePath; -import org.eclipse.jface.internal.databinding.internal.viewers.UnorderedTreeContentProvider; +import org.eclipse.jface.databinding.swt.SWTObservables; +import org.eclipse.jface.internal.databinding.provisional.viewers.UnorderedTreeContentProvider; import org.eclipse.jface.internal.databinding.provisional.viewers.ViewerLabelProvider; import org.eclipse.jface.layout.GridLayoutFactory; import org.eclipse.jface.layout.LayoutConstants; @@ -36,40 +36,41 @@ import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; /** - * Tests UpdatableTreeContentProvider and DirtyIndicationLabelProvider. - * Creates a tree containing three randomly-generated sets of integers, - * and one node that contains the union of the other sets. + * Tests UpdatableTreeContentProvider and DirtyIndicationLabelProvider. Creates + * a tree containing three randomly-generated sets of integers, and one node + * that contains the union of the other sets. * * @since 3.2 */ public class TreeContentProviderTest { - + private Shell shell; private TreeViewer tree; - + // Three randomly-generated sets of doubles private AsynchronousTestSet set1; private AsynchronousTestSet set2; private AsynchronousTestSet set3; - + // The union of the above three sets private UnionSet union; private Button randomize; + public TreeContentProviderTest() { - + // Create the data model set1 = new AsynchronousTestSet(); set2 = new AsynchronousTestSet(); set3 = new AsynchronousTestSet(); - + // A union of the above sets - union = new UnionSet(new IObservableSet[] {set1, set2, set3}); - + union = new UnionSet(new IObservableSet[] { set1, set2, set3 }); + // Create shell shell = new Shell(Display.getCurrent()); - + createTree(); - + Composite buttonBar = new Composite(shell, SWT.NONE); { buttonBar.setLayout(new FillLayout(SWT.HORIZONTAL)); @@ -81,14 +82,17 @@ public class TreeContentProviderTest { super.widgetSelected(e); } }); - + GridLayoutFactory.fillDefaults().generateLayout(buttonBar); } - - GridLayoutFactory.fillDefaults().margins(LayoutConstants.getMargins()).generateLayout(shell); - + + GridLayoutFactory.fillDefaults().margins(LayoutConstants.getMargins()) + .generateLayout(shell); + shell.addDisposeListener(new DisposeListener() { - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see org.eclipse.swt.events.DisposeListener#widgetDisposed(org.eclipse.swt.events.DisposeEvent) */ public void widgetDisposed(DisposeEvent e) { @@ -108,34 +112,42 @@ public class TreeContentProviderTest { } private void createTree() { - // Create the tree provider. This provides the structure of the tree. This tree will - // have an instance of RootNode as the root (which is really a placeholder), several - // SimpleNodes as top-level nodes, and sets of randomly generated Doubles below each + // Create the tree provider. This provides the structure of the tree. + // This tree will + // have an instance of RootNode as the root (which is really a + // placeholder), several + // SimpleNodes as top-level nodes, and sets of randomly generated + // Doubles below each // SimpleNode. IUnorderedTreeProvider treeProvider = new IUnorderedTreeProvider() { - public IObservableSet createChildSet(TreePath treePath) { - // If the parent is the root node, return the union of some randomly-generated + public IObservableSet createChildSet(Object element) { + // If the parent is the root node, return the union of some + // randomly-generated // nodes and some hardcoded nodes - if (treePath.getSegmentCount()==0 || treePath.getLastSegment() == tree.getInput()) { + if (element == tree.getInput()) { // Set of hardcoded nodes WritableSet topElements = new WritableSet(); topElements.add(new SimpleNode("Random Set 1", set1)); topElements.add(new SimpleNode("Random Set 2", set2)); topElements.add(new SimpleNode("Random Set 3", set3)); - topElements.add(new SimpleNode("Union of the other sets", union)); + topElements.add(new SimpleNode("Union of the other sets", + union)); return topElements; } - - // If the parent is a RandomChildrenNode, return a randomly-generated + + // If the parent is a RandomChildrenNode, return a + // randomly-generated // set of Doubles for its children - Object element = treePath.getLastSegment(); if (element instanceof SimpleNode) { - // We return a new DelegatingObservableSet in order to prevent the + // We return a new DelegatingObservableSet in order to + // prevent the // original from being disposed. - return Observables.proxyObservableSet(((SimpleNode)element).getChildren()); + return Observables + .proxyObservableSet(((SimpleNode) element) + .getChildren()); } - - // Otherwise the node is a Double, which will have no children + + // Otherwise the node is a Double, which will have no children return null; } @@ -144,52 +156,58 @@ public class TreeContentProviderTest { return null; } }; - + // Label provider for the tree IViewerLabelProvider labelProvider = new ViewerLabelProvider() { public void updateLabel(ViewerLabel label, Object element) { if (element instanceof SimpleNode) { SimpleNode node = (SimpleNode) element; - + label.setText(node.getNodeName()); } - + if (element instanceof Integer) { Integer node = (Integer) element; - + label.setText("Integer " + node); } } }; - + // Create tree viewer tree = new TreeViewer(shell, SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER); - - // UpdatableTreeContentProvider converts an ITreeProvider into a standard JFace content provider - UnorderedTreeContentProvider contentProvider = new UnorderedTreeContentProvider(treeProvider, - "pending..."); - + + // UpdatableTreeContentProvider converts an ITreeProvider into a + // standard JFace content provider + UnorderedTreeContentProvider contentProvider = new UnorderedTreeContentProvider( + treeProvider, "pending...", false); + tree.setContentProvider(contentProvider); tree.setLabelProvider(labelProvider); - - // For the ITreeProvider above, it doesn't matter what we select as the input. + + // For the ITreeProvider above, it doesn't matter what we select as the + // input. tree.setInput(new Object()); } - + /** * @param args */ public static void main(String[] args) { - Display display = Display.getDefault(); - TreeContentProviderTest test = new TreeContentProviderTest(); - Shell s = test.getShell(); - s.pack(); - s.setVisible(true); - - while (!s.isDisposed()) { - if (!display.readAndDispatch()) - display.sleep(); - } + final Display display = Display.getDefault(); + Realm.runWithDefault(SWTObservables.getRealm(display), new Runnable() { + public void run() { + TreeContentProviderTest test = new TreeContentProviderTest(); + Shell s = test.getShell(); + s.pack(); + s.setVisible(true); + + while (!s.isDisposed()) { + if (!display.readAndDispatch()) + display.sleep(); + } + } + }); display.dispose(); } |