/******************************************************************************* * 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.Collections; import java.util.HashSet; 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.set.IObservableSet; import org.eclipse.core.databinding.observable.set.ISetChangeListener; import org.eclipse.core.databinding.observable.set.SetChangeEvent; import org.eclipse.core.databinding.observable.set.SetDiff; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.swt.widgets.Control; /* package */ class TreeNode implements ISetChangeListener, IStaleListener { private UnorderedTreeContentProvider contentProvider; private Object 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; /** * Set of child elements. */ private IObservableSet children; private boolean hasPendingNode = false; private boolean isStale; private boolean listeningToChildren = false; private boolean prefetchEnqueued = false; /** * @param element * @param cp */ public TreeNode(Object element, UnorderedTreeContentProvider cp) { this.element = element; this.contentProvider = cp; children = contentProvider.createChildSet(element); if (children == null) { children = Observables.emptyObservableSet(); 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 set of children for this node. If new children are discovered later, they * will be added directly to the viewer. * * @return TODO */ public Set getChildren() { if (!listeningToChildren) { listeningToChildren = true; children.addSetChangeListener(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.singleton(pendingNode); } return children; } /** * @return TODO */ public IObservableSet getChildrenSet() { return children; } private void updateStale() { boolean willBeStale = children.isStale(); if (willBeStale != isStale) { isStale = willBeStale; contentProvider.changeStale(isStale? 1 : -1); } } /** * @return TODO */ public boolean isStale() { return isStale; } /** * Returns true if the viewer should show a plus sign for expanding this * node. * * @return TODO */ 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.removeSetChangeListener(this); children.removeStaleListener(this); } children.dispose(); children = null; if (listeningToChildren && isStale) { contentProvider.changeStale(-1); } } } /** * @return TODO */ 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 TODO */ public Object getParent() { return parent; } /** * * @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); } } else { 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 = diff.getAdditions(); // Check if we should add the pending node if (shouldHavePendingNode && !hasPendingNode) { HashSet newAdditions = new HashSet(); newAdditions.addAll(additions); newAdditions.add(contentProvider.getPendingNode()); additions = newAdditions; hasPendingNode = true; } Set removals = diff.getRemovals(); // Check if we should remove the pending node if (!shouldHavePendingNode && hasPendingNode) { HashSet newRemovals = new HashSet(); newRemovals.addAll(removals); newRemovals.add(contentProvider.getPendingNode()); removals = newRemovals; hasPendingNode = false; } if (!additions.isEmpty()) { contentProvider.add(element, additions); } if (!removals.isEmpty()) { contentProvider.remove(element, removals, children.isEmpty() && !hasPendingNode); } updateStale(); } 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(element, Collections.singleton(contentProvider.getPendingNode())); } // Check if we should remove the pending node if (!shouldHavePendingNode && hasPendingNode) { hasPendingNode = shouldHavePendingNode; contentProvider.remove(element, Collections.singleton(contentProvider.getPendingNode()), true); } updateStale(); } /** * @return TODO */ public Object getElement() { 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(element, children); } else { // We need to remove the + sign, and adding/removing elements won't do the trick contentProvider.getViewer().refresh(element); } } }