### Eclipse Workspace Patch 1.0 #P org.eclipse.jface.examples.databinding Index: src/org/eclipse/jface/examples/databinding/snippets/Snippet024AsyncAndCheckAndFilter.java =================================================================== RCS file: src/org/eclipse/jface/examples/databinding/snippets/Snippet024AsyncAndCheckAndFilter.java diff -N src/org/eclipse/jface/examples/databinding/snippets/Snippet024AsyncAndCheckAndFilter.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/jface/examples/databinding/snippets/Snippet024AsyncAndCheckAndFilter.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,454 @@ +/******************************************************************************* + * Copyright (c) 2008 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.examples.databinding.snippets; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import org.eclipse.core.databinding.observable.IObservable; +import org.eclipse.core.databinding.observable.Observables; +import org.eclipse.core.databinding.observable.Realm; +import org.eclipse.core.databinding.observable.list.ComputedList; +import org.eclipse.core.databinding.observable.list.IObservableList; +import org.eclipse.core.databinding.observable.list.WritableList; +import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory; +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.WritableSet; +import org.eclipse.core.databinding.observable.value.IObservableValue; +import org.eclipse.jface.databinding.swt.ISWTObservableValue; +import org.eclipse.jface.databinding.swt.SWTObservables; +import org.eclipse.jface.databinding.viewers.ObservableListTreeContentProvider; +import org.eclipse.jface.databinding.viewers.TreeStructureAdvisor; +import org.eclipse.jface.layout.GridDataFactory; +import org.eclipse.jface.viewers.CheckStateChangedEvent; +import org.eclipse.jface.viewers.CheckboxTreeViewer; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.ICheckStateListener; +import org.eclipse.jface.viewers.IDoubleClickListener; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +public class Snippet024AsyncAndCheckAndFilter { + + public static void main(String[] args) { + final Display display = new Display(); + final Shell shell = new Shell(display); + shell.setLayout(new GridLayout(1, false)); + + Realm.runWithDefault(SWTObservables.getRealm(display), new Runnable() { + public void run() { + new Snippet024AsyncAndCheckAndFilter().createControls(shell); + + shell.pack(); + shell.open(); + while (!shell.isDisposed()) { + if (!display.readAndDispatch()) + display.sleep(); + } + } + }); + display.dispose(); + } + + static abstract class Node { + private final String name; + private final Node parent; + + public Node getParent() { + return parent; + } + + Node(Node parent, String name) { + this.parent = parent; + this.name = name; + } + + public String toString() { + return name; + } + + public abstract IObservableList createChildren(); + } + + static class Pending extends Node { + Pending(Node parent) { + super(parent, "Pending..."); + } + + public IObservableList createChildren() { + return null; + } + } + + static class Root extends Node { + private final IObservableSet checked; + private final IObservableValue filter; + private final ObservableListTreeContentProvider provider; + + Root(IObservableSet checkedElements, IObservableValue filter, + ObservableListTreeContentProvider contentProvider) { + super(null, "root"); + checked = checkedElements; + this.filter = filter; + provider = contentProvider; + } + + public IObservableList createChildren() { + final List repositories = new ArrayList(); + repositories.add(new Repository(this, "Eclipse SDK Update Site", + checked, filter)); + repositories.add(new Repository(this, "EMF Update Site", checked, + filter)); + repositories.add(new Repository(this, "Ganymede Update Site", + checked, filter)); + repositories.add(new Repository(this, "Mylyn Update Site", checked, + filter)); + return new ComputedList() { + protected List calculate() { + if (((String) filter.getValue()).length() == 0) { + return Observables.staticObservableList(repositories); + } + List result = new ArrayList(); + for (Iterator it = repositories.iterator(); it.hasNext();) { + Object object = it.next(); + if (!provider.getObservableChildren(object).isEmpty()) { + result.add(object); + } + } + return result; + } + }; + } + } + + static class Repository extends Node { + private WritableList iuList; + protected Pending pendingNode; + private final IObservableSet checked; + private final IObservableValue filter; + + Repository(Root root, String name, IObservableSet checkedElements, + IObservableValue filter) { + super(root, name); + checked = checkedElements; + this.filter = filter; + } + + public IObservableList getIUList() { + return new ComputedList() { + protected List calculate() { + List result = new ArrayList(); + for (Iterator it = getUnfilteredIUList().iterator(); it + .hasNext();) { + Node nodeObject = (Node) it.next(); + String filterString = (String) filter.getValue(); + if (filterString.length() == 0 + || nodeObject.name.toLowerCase().indexOf( + filterString.toLowerCase()) != -1) { + result.add(nodeObject); + } + } + return result; + } + }; + } + + private IObservableList getUnfilteredIUList() { + if (iuList == null) { + iuList = new WritableList(); + refreshIUList(); + } + return iuList; + } + + public void refreshIUList() { + if (iuList.isStale()) { + // we are already refreshing + return; + } + iuList.setStale(true); + iuList.clear(); + Display.getDefault().timerExec(3000, new Runnable() { + public void run() { + List bulkAdd = new ArrayList(); + Category c1 = new Category(Repository.this, + "Alpha Category"); + bulkAdd.add(new IU(c1, "Alpha Bar")); + bulkAdd.add(new IU(c1, "Alpha Bas")); + bulkAdd.add(new IU(c1, "Alpha Foo")); + bulkAdd.add(new IU(c1, "Alpha Meh")); + bulkAdd.add(new IU(c1, "Alpha Moo")); + Category c2 = new Category(Repository.this, "Beta Category"); + bulkAdd.add(new IU(c2, "Beta Bar")); + bulkAdd.add(new IU(c2, "Beta Bas")); + bulkAdd.add(new IU(c2, "Beta Foo")); + bulkAdd.add(new IU(c2, "Beta Meh")); + bulkAdd.add(new IU(c2, "Beta Moo")); + Category c3 = new Category(Repository.this, + "Gamma Category"); + bulkAdd.add(new IU(c3, "Gamma Bar")); + bulkAdd.add(new IU(c3, "Gamma Bas")); + bulkAdd.add(new IU(c3, "Gamma Foo")); + bulkAdd.add(new IU(c3, "Gamma Meh")); + bulkAdd.add(new IU(c3, "Gamma Moo")); + iuList.setStale(false); + // System.out.println("finished retrieval, pendingNode=" + // + pendingNode); + if (pendingNode != null) { + if (checked.contains(pendingNode)) { + // System.out + // .println( + // "pending node was checked, adding nodes to checked set:" + // + bulkAdd.size()); + checked.remove(pendingNode); + checked.add(c1); + checked.add(c2); + checked.add(c3); + checked.addAll(bulkAdd); + } + pendingNode = null; + } + iuList.addAll(bulkAdd); + } + }); + } + + public IObservableList createChildren() { + return new ComputedList() { + protected List calculate() { + if (getIUList().isStale()) { + if (pendingNode == null) { + pendingNode = new Pending(Repository.this); + } + return Collections.singletonList(pendingNode); + } + List result = new ArrayList(); + for (Iterator it = getIUList().iterator(); it.hasNext();) { + IU iu = (IU) it.next(); + if (!result.contains(iu.getParent())) { + result.add(iu.getParent()); + } + } + return result; + } + }; + } + } + + static class Category extends Node { + Category(Repository repo, String name) { + super(repo, name); + } + + public IObservableList createChildren() { + return new ComputedList() { + protected List calculate() { + List result = new ArrayList(); + for (Iterator it = ((Repository) getParent()).getIUList() + .iterator(); it.hasNext();) { + IU iu = (IU) it.next(); + if (iu.getParent() == Category.this) { + result.add(iu); + } + } + // System.out.println("returning children, " + + // result.size()); + return result; + } + }; + } + } + + static class IU extends Node { + IU(Category category, String name) { + super(category, name); + } + + public IObservableList createChildren() { + return null; + } + } + + private CheckboxTreeViewer viewer; + private IObservableSet realizedElements; + private IObservableSet checkedElements; + private IObservableSet grayedElements; + private ObservableListTreeContentProvider contentProvider; + private TreeStructureAdvisor treeStructureAdvisor; + private ISWTObservableValue delayedFilterValue; + + boolean viewerIsDisposed; + + protected void createControls(Shell shell) { + Text filter = new Text(shell, SWT.BORDER | SWT.SEARCH); + ISWTObservableValue filterValue = SWTObservables.observeText(filter, + SWT.Modify); + delayedFilterValue = SWTObservables.observeDelayedValue(200, + filterValue); + viewer = new CheckboxTreeViewer(shell) { + protected void hookControl(Control control) { + control.addDisposeListener(new DisposeListener() { + public void widgetDisposed(DisposeEvent event) { + viewerIsDisposed = true; + } + }); + super.hookControl(control); + } + }; + GridDataFactory.defaultsFor(viewer.getControl()).hint(400, 600) + .applyTo(viewer.getControl()); + treeStructureAdvisor = new TreeStructureAdvisor() { + public Object getParent(Object element) { + if (element instanceof Node) { + return ((Node) element).getParent(); + } + return null; + } + + public Boolean hasChildren(Object element) { + return (element instanceof IU || element instanceof Pending) ? Boolean.FALSE + : Boolean.TRUE; + } + }; + IObservableFactory listFactory = new IObservableFactory() { + public IObservable createObservable(Object target) { + return ((Node) target).createChildren(); + } + }; + contentProvider = new ObservableListTreeContentProvider(listFactory, + treeStructureAdvisor); + viewer.setContentProvider(contentProvider); + realizedElements = contentProvider.getRealizedElements(); + checkedElements = new WritableSet(); + grayedElements = new WritableSet(); + viewer.addCheckStateListener(new ICheckStateListener() { + public void checkStateChanged(CheckStateChangedEvent event) { + handleChecked(event.getElement(), event.getChecked()); + } + }); + realizedElements.addSetChangeListener(new ISetChangeListener() { + public void handleSetChange(SetChangeEvent event) { + if (viewerIsDisposed) { + return; + } + for (Iterator it = event.diff.getRemovals().iterator(); it + .hasNext();) { + Object removed = it.next(); + if (removed instanceof IU) { + Object parent = treeStructureAdvisor.getParent(removed); + Object[] children = contentProvider.getChildren(parent); + boolean atLeastOneChecked = false; + boolean atLeastOneUnchecked = false; + for (int i = 0; i < children.length; i++) { + Object child = children[i]; + if (checkedElements.contains(child)) { + atLeastOneChecked = true; + } else { + atLeastOneUnchecked = true; + } + } + checkParentPath(parent, !atLeastOneUnchecked, + atLeastOneChecked && atLeastOneUnchecked); + } + } + for (Iterator it = event.diff.getAdditions().iterator(); it + .hasNext();) { + Object added = it.next(); + if (added instanceof IU) { + boolean checked = checkedElements.contains(added); + viewer.setChecked(added, checked); + handleChecked(added, checked); + } + } + } + }); + viewer.addDoubleClickListener(new IDoubleClickListener() { + public void doubleClick(DoubleClickEvent event) { + IStructuredSelection selection = (IStructuredSelection) event + .getSelection(); + if (selection.getFirstElement() instanceof Repository) { + ((Repository) selection.getFirstElement()).refreshIUList(); + } + } + }); + viewer.setInput(new Root(checkedElements, delayedFilterValue, + contentProvider)); + } + + protected void handleChecked(Object element, boolean checked) { + checkSubtree(element, checked); + checkParentPath(element, checked, false); + } + + private void checkParentPath(Object element, boolean checked, boolean grayed) { + if (element == null) { + return; + } + if (grayed) { + checked = true; + } else { + Object[] children = contentProvider.getChildren(element); + for (int i = 0; i < children.length; i++) { + Object child = children[i]; + if (grayedElements.contains(child) + || checked != checkedElements.contains(child)) { + checked = grayed = true; + break; + } + } + } + updateCheckedGrayed(element, checked, grayed); + checkParentPath(treeStructureAdvisor.getParent(element), checked, + grayed); + } + + private void checkSubtree(Object element, boolean checked) { + updateCheckedGrayed(element, checked, false); + if (!(element instanceof Pending)) { + Object[] children = contentProvider.getChildren(element); + for (int i = 0; i < children.length; i++) { + Object child = children[i]; + checkSubtree(child, checked); + } + } + } + + private void updateCheckedGrayed(Object element, boolean checked, + boolean grayed) { + if (checked) { + checkedElements.add(element); + } else { + checkedElements.remove(element); + } + if (grayed) { + grayedElements.add(element); + } else { + grayedElements.remove(element); + } + if (realizedElements.contains(element)) { + viewer.setChecked(element, checked); + viewer.setGrayed(element, grayed); + } + } + +} #P org.eclipse.jface.databinding Index: src/org/eclipse/jface/databinding/viewers/ObservableSetTreeContentProvider.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jface.databinding/src/org/eclipse/jface/databinding/viewers/ObservableSetTreeContentProvider.java,v retrieving revision 1.4 diff -u -r1.4 ObservableSetTreeContentProvider.java --- src/org/eclipse/jface/databinding/viewers/ObservableSetTreeContentProvider.java 4 Sep 2008 22:20:45 -0000 1.4 +++ src/org/eclipse/jface/databinding/viewers/ObservableSetTreeContentProvider.java 4 Sep 2008 22:53:39 -0000 @@ -17,6 +17,7 @@ import org.eclipse.core.databinding.observable.IObservableCollection; import org.eclipse.core.databinding.observable.IObservablesListener; +import org.eclipse.core.databinding.observable.list.IObservableList; import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory; import org.eclipse.core.databinding.observable.set.IObservableSet; import org.eclipse.core.databinding.observable.set.ISetChangeListener; @@ -189,4 +190,13 @@ public IObservableSet getRealizedElements() { return impl.getRealizedElements(); } + + /** + * @param element + * @return bla + * @since 1.3 + */ + public IObservableList getObservableChildren(Object element) { + return (IObservableList) impl.getObservableChildren(element); + } } \ No newline at end of file Index: src/org/eclipse/jface/databinding/viewers/ObservableListTreeContentProvider.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jface.databinding/src/org/eclipse/jface/databinding/viewers/ObservableListTreeContentProvider.java,v retrieving revision 1.5 diff -u -r1.5 ObservableListTreeContentProvider.java --- src/org/eclipse/jface/databinding/viewers/ObservableListTreeContentProvider.java 4 Sep 2008 22:20:45 -0000 1.5 +++ src/org/eclipse/jface/databinding/viewers/ObservableListTreeContentProvider.java 4 Sep 2008 22:53:39 -0000 @@ -227,4 +227,13 @@ public IObservableSet getRealizedElements() { return impl.getRealizedElements(); } + + /** + * @param element + * @return bla + * @since 1.3 + */ + public IObservableList getObservableChildren(Object element) { + return (IObservableList) impl.getObservableChildren(element); + } } \ No newline at end of file Index: src/org/eclipse/jface/internal/databinding/viewers/ObservableCollectionTreeContentProvider.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jface.databinding/src/org/eclipse/jface/internal/databinding/viewers/ObservableCollectionTreeContentProvider.java,v retrieving revision 1.7 diff -u -r1.7 ObservableCollectionTreeContentProvider.java --- src/org/eclipse/jface/internal/databinding/viewers/ObservableCollectionTreeContentProvider.java 4 Sep 2008 22:20:45 -0000 1.7 +++ src/org/eclipse/jface/internal/databinding/viewers/ObservableCollectionTreeContentProvider.java 4 Sep 2008 22:53:39 -0000 @@ -389,6 +389,16 @@ return comparer.equals(left, right); } + /** + * @param element + * @return bla + */ + public IObservableCollection getObservableChildren(Object element) { + TreeNode node = getOrCreateNode(element); + node.initChildren(); + return node.children; + } + protected final class TreeNode { private Object element; Index: src/org/eclipse/jface/internal/databinding/swt/DelayedObservableValue.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jface.databinding/src/org/eclipse/jface/internal/databinding/swt/DelayedObservableValue.java,v retrieving revision 1.1 diff -u -r1.1 DelayedObservableValue.java --- src/org/eclipse/jface/internal/databinding/swt/DelayedObservableValue.java 19 Mar 2008 23:03:22 -0000 1.1 +++ src/org/eclipse/jface/internal/databinding/swt/DelayedObservableValue.java 4 Sep 2008 22:53:39 -0000 @@ -138,7 +138,11 @@ protected Object doGetValue() { if (dirty) { - cachedValue = observable.getValue(); + ObservableTracker.runAndIgnore(new Runnable(){ + public void run() { + cachedValue = observable.getValue(); + } + }); dirty = false; if (updater != null && !updater.running) { #P org.eclipse.core.databinding Index: src/org/eclipse/core/databinding/observable/list/AbstractObservableList.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.core.databinding/src/org/eclipse/core/databinding/observable/list/AbstractObservableList.java,v retrieving revision 1.11 diff -u -r1.11 AbstractObservableList.java --- src/org/eclipse/core/databinding/observable/list/AbstractObservableList.java 7 Apr 2008 21:16:18 -0000 1.11 +++ src/org/eclipse/core/databinding/observable/list/AbstractObservableList.java 4 Sep 2008 22:53:41 -0000 @@ -76,33 +76,47 @@ } public synchronized void addListChangeListener(IListChangeListener listener) { - changeSupport.addListener(ListChangeEvent.TYPE, listener); + if (changeSupport != null) { + changeSupport.addListener(ListChangeEvent.TYPE, listener); + } } public synchronized void removeListChangeListener(IListChangeListener listener) { - changeSupport.removeListener(ListChangeEvent.TYPE, listener); + if (changeSupport != null) { + changeSupport.removeListener(ListChangeEvent.TYPE, listener); + } } protected void fireListChange(ListDiff diff) { // fire general change event first fireChange(); - changeSupport.fireEvent(new ListChangeEvent(this, diff)); + if (changeSupport != null) { + changeSupport.fireEvent(new ListChangeEvent(this, diff)); + } } public synchronized void addChangeListener(IChangeListener listener) { - changeSupport.addChangeListener(listener); + if (changeSupport != null) { + changeSupport.addChangeListener(listener); + } } public synchronized void removeChangeListener(IChangeListener listener) { - changeSupport.removeChangeListener(listener); + if (changeSupport != null) { + changeSupport.removeChangeListener(listener); + } } public synchronized void addStaleListener(IStaleListener listener) { - changeSupport.addStaleListener(listener); + if (changeSupport != null) { + changeSupport.addStaleListener(listener); + } } public synchronized void removeStaleListener(IStaleListener listener) { - changeSupport.removeStaleListener(listener); + if (changeSupport != null) { + changeSupport.removeStaleListener(listener); + } } /** Index: src/org/eclipse/core/databinding/observable/list/ComputedList.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.core.databinding/src/org/eclipse/core/databinding/observable/list/ComputedList.java,v retrieving revision 1.2 diff -u -r1.2 ComputedList.java --- src/org/eclipse/core/databinding/observable/list/ComputedList.java 5 Dec 2007 04:05:15 -0000 1.2 +++ src/org/eclipse/core/databinding/observable/list/ComputedList.java 4 Sep 2008 22:53:41 -0000 @@ -121,12 +121,14 @@ } public void handleStale(StaleEvent event) { - if (!dirty) + if (!disposed && !dirty) makeStale(); } public void handleChange(ChangeEvent event) { - makeDirty(); + if (!disposed) { + makeDirty(); + } } } @@ -134,6 +136,8 @@ private Object elementType; + private boolean disposed; + protected int doGetSize() { return doGetList().size(); } @@ -154,19 +158,23 @@ // - Run the calculate method // - While doing so, add any observable that is touched to the // dependencies list - IObservable[] newDependencies = ObservableTracker.runAndMonitor( + final IObservable[] newDependencies = ObservableTracker.runAndMonitor( privateInterface, privateInterface, null); - // If any dependencies are stale, a stale event will be fired here - // even if we were already stale before recomputing. This is in case - // clients assume that a list change is indicative of non-staleness. - stale = false; - for (int i = 0; i < newDependencies.length; i++) { - if (newDependencies[i].isStale()) { - makeStale(); - break; + ObservableTracker.runAndIgnore(new Runnable() { + public void run() { + // If any dependencies are stale, a stale event will be fired here + // even if we were already stale before recomputing. This is in case + // clients assume that a list change is indicative of non-staleness. + stale = false; + for (int i = 0; i < newDependencies.length; i++) { + if (newDependencies[i].isStale()) { + makeStale(); + break; + } + } } - } + }); if (!stale) { for (int i = 0; i < newDependencies.length; i++) { @@ -231,13 +239,16 @@ } private void makeStale() { - if (!stale) { + if (!disposed && !stale) { stale = true; fireStale(); } } public boolean isStale() { + if (disposed) { + return false; + } // recalculate list if dirty, to ensure staleness is correct. getList(); return stale; @@ -284,6 +295,7 @@ } public synchronized void dispose() { + disposed = true; stopListening(); super.dispose(); }