78
|
1 /*******************************************************************************
|
|
2 * Copyright (c) 2006, 2008 IBM Corporation and others.
|
|
3 * All rights reserved. This program and the accompanying materials
|
|
4 * are made available under the terms of the Eclipse Public License v1.0
|
|
5 * which accompanies this distribution, and is available at
|
|
6 * http://www.eclipse.org/legal/epl-v10.html
|
|
7 *
|
|
8 * Contributors:
|
|
9 * IBM Corporation - initial API and implementation
|
|
10 * Stefan Xenos, IBM - initial API and implementation
|
|
11 *******************************************************************************/
|
|
12 module org.eclipse.jface.internal.databinding.provisional.viewers.UnorderedTreeContentProvider;
|
85
|
13 import org.eclipse.jface.internal.databinding.provisional.viewers.IParentProvider;
|
|
14 import org.eclipse.jface.internal.databinding.provisional.viewers.TreeNode;
|
78
|
15
|
|
16 import java.lang.all;
|
|
17
|
|
18 import java.util.ArrayList;
|
|
19 import java.util.Collections;
|
|
20 import java.util.HashMap;
|
|
21 import java.util.HashSet;
|
|
22 import java.util.Iterator;
|
|
23 import java.util.LinkedList;
|
|
24 import java.util.List;
|
|
25 import java.util.Set;
|
|
26
|
|
27 import org.eclipse.core.databinding.observable.Diffs;
|
|
28 import org.eclipse.core.databinding.observable.set.AbstractObservableSet;
|
|
29 import org.eclipse.core.databinding.observable.set.IObservableSet;
|
|
30 import org.eclipse.core.databinding.observable.set.SetDiff;
|
|
31 import org.eclipse.core.internal.databinding.observable.tree.IUnorderedTreeProvider;
|
|
32 import org.eclipse.jface.databinding.viewers.ObservableListTreeContentProvider;
|
|
33 import org.eclipse.jface.databinding.viewers.ObservableSetTreeContentProvider;
|
|
34 import org.eclipse.jface.viewers.ITreeContentProvider;
|
|
35 import org.eclipse.jface.viewers.ITreePathContentProvider;
|
|
36 import org.eclipse.jface.viewers.ITreeViewerListener;
|
|
37 import org.eclipse.jface.viewers.TreeExpansionEvent;
|
|
38 import org.eclipse.jface.viewers.TreePath;
|
|
39 import org.eclipse.jface.viewers.TreeViewer;
|
|
40 import org.eclipse.jface.viewers.Viewer;
|
|
41
|
|
42 /**
|
|
43 * NON-API - Generic tree content provider to be used with an AbstractTreeViewer based on a IUnorderedTreeProvider.
|
|
44 * @since 1.1
|
|
45 * @deprecated Use {@link ObservableSetTreeContentProvider} or
|
|
46 * {@link ObservableListTreeContentProvider} instead.
|
|
47 */
|
|
48 public class UnorderedTreeContentProvider : ITreeContentProvider, ITreePathContentProvider {
|
|
49
|
85
|
50 private HashMap mapElementToTreeNode;
|
|
51 private LinkedList enqueuedPrefetches;
|
78
|
52 private IParentProvider rootParentProvider = null;
|
85
|
53 private bool useTreePaths_ = false;
|
78
|
54
|
|
55 class KnownElementsSet : AbstractObservableSet {
|
|
56
|
|
57 protected this() {
|
|
58 super();
|
|
59 }
|
|
60
|
|
61 /* (non-Javadoc)
|
|
62 * @see org.eclipse.jface.internal.databinding.provisional.observable.set.AbstractObservableSet#getWrappedSet()
|
|
63 */
|
|
64 protected Set getWrappedSet() {
|
|
65 return mapElementToTreeNode.keySet();
|
|
66 }
|
|
67
|
|
68 void doFireDiff(Set added, Set removed) {
|
|
69 fireSetChange(Diffs.createSetDiff(added, removed));
|
|
70 }
|
|
71
|
|
72 public void fireSetChange(SetDiff diff) {
|
|
73 super.fireSetChange(diff);
|
|
74 }
|
|
75
|
|
76 void doFireStale(bool isStale) {
|
|
77 if (isStale) {
|
|
78 fireStale();
|
|
79 } else {
|
|
80 fireSetChange(Diffs.createSetDiff(Collections.EMPTY_SET, Collections.EMPTY_SET));
|
|
81 }
|
|
82 }
|
|
83
|
|
84 /* (non-Javadoc)
|
|
85 * @see org.eclipse.jface.internal.databinding.provisional.observable.set.IObservableSet#getElementType()
|
|
86 */
|
|
87 public Object getElementType() {
|
|
88 return new Object();
|
|
89 }
|
|
90 }
|
|
91
|
85
|
92 KnownElementsSet elements;
|
78
|
93
|
85
|
94 private ITreeViewerListener expandListener;
|
|
95 class ExpandListener : ITreeViewerListener {
|
78
|
96 public void treeCollapsed(TreeExpansionEvent event) {
|
|
97 }
|
|
98
|
|
99 public void treeExpanded(TreeExpansionEvent event) {
|
|
100 }
|
|
101 };
|
|
102
|
|
103 private IUnorderedTreeProvider provider;
|
|
104 private Object pendingNode;
|
|
105
|
|
106 private int avoidViewerUpdates;
|
|
107
|
|
108 private TreeViewer treeViewer;
|
|
109
|
|
110 private int staleCount = 0;
|
|
111 private bool useRefresh;
|
|
112 private int maxPrefetches = 0;
|
|
113
|
|
114 /**
|
|
115 * Constructs a content provider that will render the given tree in a TreeViewer.
|
|
116 *
|
|
117 * @param provider IObservableTree that provides the contents of the tree
|
|
118 * @param pendingNode element to insert whenever a node is being fetched in the background
|
|
119 * @param useRefresh true = notify the viewer of changes by calling refresh(...), false =
|
|
120 * notify the viewer of changes by calling add(...) and remove(...). Using false
|
|
121 * is more efficient, but may not work with TreeViewer subclasses.
|
|
122 */
|
|
123 public this(IUnorderedTreeProvider provider,
|
|
124 Object pendingNode, bool useRefresh) {
|
85
|
125 mapElementToTreeNode = new HashMap();
|
|
126 enqueuedPrefetches = new LinkedList();
|
|
127 elements = new KnownElementsSet();
|
|
128 expandListener = new ExpandListener();
|
78
|
129 this.provider = provider;
|
|
130 this.pendingNode = pendingNode;
|
|
131 this.useRefresh = useRefresh;
|
|
132 }
|
|
133
|
|
134 /**
|
|
135 * Sets whether this content provider should add/remove elements using
|
|
136 * TreePaths (true) or elements (false).
|
|
137 *
|
|
138 * <p></p>
|
|
139 * <p>When using elements:</p>
|
|
140 *
|
|
141 * <ul>
|
|
142 * <li>Cycles are permitted (elements can be their own ancestor)</li>
|
|
143 * <li>Addition, removal, and refresh are slightly faster</li>
|
|
144 * <li>It is not possible to have more than one content provider per tree</li>
|
|
145 * <li>The setRootPath(...) method is ignored</li>
|
|
146 * </ul>
|
|
147 *
|
|
148 * <p></p>
|
|
149 * <p>When using TreePaths:</p>
|
|
150 *
|
|
151 * <ul>
|
|
152 * <li>Cycles are not permitted (elements cannot be their own parent)</li>
|
|
153 * <li>Addition, removal, and refresh are slightly slower</li>
|
|
154 * <li>It is possible to use more than one content provider in the same tree</li>
|
|
155 * <li>The setRootPath(...) method can be used to direct the output to a particular
|
|
156 * subtree</li>
|
|
157 * </ul>
|
|
158 *
|
|
159 * @param usePaths
|
|
160 */
|
|
161 public void useTreePaths(bool usePaths) {
|
85
|
162 this.useTreePaths_ = usePaths;
|
78
|
163 }
|
|
164
|
|
165 /**
|
|
166 * @param rootParentProvider
|
|
167 */
|
|
168 public void setRootPath(IParentProvider rootParentProvider) {
|
|
169 this.rootParentProvider = rootParentProvider;
|
|
170 }
|
|
171
|
|
172 /**
|
|
173 * @param maxPrefetches
|
|
174 */
|
|
175 public void setMaxPrefetches(int maxPrefetches) {
|
|
176 this.maxPrefetches = maxPrefetches;
|
|
177 }
|
|
178
|
|
179 /* package */ IObservableSet createChildSet(Object element) {
|
|
180 return provider.createChildSet(element);
|
|
181 }
|
|
182
|
|
183 /* package */ void remove(Object element, Set removals, bool lastElement) {
|
|
184 if (removals.isEmpty()) {
|
|
185 return;
|
|
186 }
|
|
187 if (avoidViewerUpdates is 0) {
|
|
188 if (lastElement || useRefresh) {
|
|
189 doRefresh(element);
|
|
190 } else {
|
85
|
191 if (useTreePaths_) {
|
78
|
192 List toRemove = new ArrayList();
|
|
193 TreePath[] parents = getParents(element);
|
|
194 for (int i = 0; i < parents.length; i++) {
|
|
195 TreePath parent = parents[i];
|
|
196
|
|
197 for (Iterator iter = removals.iterator(); iter.hasNext();) {
|
|
198 Object elementToRemove = iter.next();
|
|
199
|
|
200 toRemove.add(parent.createChildPath(element).createChildPath(elementToRemove));
|
|
201 }
|
|
202 }
|
|
203
|
|
204 treeViewer.remove(toRemove.toArray(new TreePath[toRemove.size()]));
|
|
205 } else {
|
|
206 treeViewer.remove(element, removals.toArray());
|
|
207 }
|
|
208 }
|
|
209 for (Iterator iter = removals.iterator(); iter.hasNext();) {
|
|
210 Object next = iter.next();
|
|
211
|
|
212 TreeNode nextNode = cast(TreeNode)mapElementToTreeNode.get(next);
|
|
213 if (nextNode !is null) {
|
|
214 nextNode.removeParent(element);
|
|
215 removeIfUnused(nextNode);
|
|
216 }
|
|
217 }
|
|
218 }
|
|
219 }
|
|
220
|
|
221 /* package */ void add(Object element, Set additions) {
|
|
222 if (additions.isEmpty()) {
|
|
223 return;
|
|
224 }
|
|
225 if (avoidViewerUpdates is 0) {
|
|
226 // Handle new parents
|
|
227 addParent(element, additions);
|
|
228 if (useRefresh) {
|
|
229 doRefresh(element);
|
|
230 } else {
|
85
|
231 if (useTreePaths_) {
|
78
|
232 TreePath[] parents = getParents(element);
|
|
233 for (int i = 0; i < parents.length; i++) {
|
|
234 TreePath parent = parents[i];
|
|
235
|
|
236 treeViewer.add(parent.createChildPath(element), additions.toArray());
|
|
237 }
|
|
238 } else {
|
|
239 treeViewer.add(element, additions.toArray());
|
|
240 }
|
|
241 }
|
|
242 }
|
|
243 }
|
|
244
|
|
245 private void doRefresh(Object element) {
|
|
246 treeViewer.refresh(element);
|
|
247 }
|
|
248
|
|
249 /**
|
|
250 * Ensures that the given set of children have the given parent as
|
|
251 * one of their parents.
|
|
252 *
|
|
253 * @param parent
|
|
254 * @param children
|
|
255 */
|
|
256 private void addParent(Object parent, Set children) {
|
|
257 for (Iterator iter = children.iterator(); iter.hasNext();) {
|
|
258 Object next = iter.next();
|
|
259
|
|
260 TreeNode nextNode = getNode(next);
|
|
261 nextNode.addParent(parent);
|
|
262 }
|
|
263 }
|
|
264
|
|
265 /**
|
|
266 * @return saouesnth
|
|
267 */
|
|
268 public final Object getPendingNode() {
|
|
269 return pendingNode;
|
|
270 }
|
|
271
|
|
272 /**
|
|
273 * @param parent
|
|
274 * @return aueosnht
|
|
275 */
|
|
276 public IObservableSet getChildrenSet(Object parent) {
|
|
277 IObservableSet result = getNode(parent).getChildrenSet();
|
|
278
|
|
279 return result;
|
|
280 }
|
|
281
|
|
282 public void dispose() {
|
|
283 if (treeViewer !is null) {
|
|
284 try {
|
|
285 avoidViewerUpdates++;
|
|
286 enqueuedPrefetches.clear();
|
|
287 Object[] keys = mapElementToTreeNode.keySet().toArray();
|
|
288
|
|
289 for (int i = 0; i < keys.length; i++) {
|
|
290 Object key = keys[i];
|
|
291
|
|
292 TreeNode result = cast(TreeNode)mapElementToTreeNode.get(key);
|
|
293 if (result !is null) {
|
|
294 result.dispose();
|
|
295 }
|
|
296 }
|
|
297 setViewer(null);
|
|
298 } finally {
|
|
299 avoidViewerUpdates--;
|
|
300 }
|
|
301 }
|
|
302 }
|
|
303
|
|
304 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
|
|
305 // This should only ever be called for a single viewer
|
|
306 setViewer(viewer);
|
|
307
|
85
|
308 if (oldInput !is null && newInput !is null && oldInput.opEquals(newInput)) {
|
78
|
309 return;
|
|
310 }
|
|
311
|
|
312 try {
|
|
313 avoidViewerUpdates++;
|
|
314 TreeNode oldNode = cast(TreeNode)mapElementToTreeNode.get(oldInput);
|
|
315 if (oldNode !is null) {
|
|
316 removeIfUnused(oldNode);
|
|
317 }
|
|
318 } finally {
|
|
319 avoidViewerUpdates--;
|
|
320 }
|
|
321 }
|
|
322
|
|
323 private void removeIfUnused(TreeNode toRemove) {
|
|
324 //TreeNode result = cast(TreeNode)mapElementToTreeNode.get(element);
|
|
325 Object element = toRemove.getElement();
|
|
326 if (toRemove.getParent() is null) {
|
|
327 mapElementToTreeNode.remove(element);
|
|
328 elements.doFireDiff(Collections.EMPTY_SET, Collections.singleton(element));
|
|
329 toRemove.dispose();
|
|
330 }
|
|
331 }
|
|
332
|
|
333 private void setViewer(Viewer viewer) {
|
|
334 if (viewer !is null && !(null !is cast(TreeViewer)viewer)) {
|
|
335 throw new IllegalArgumentException("This content provider can only be used with TreeViewers"); //$NON-NLS-1$
|
|
336 }
|
|
337 TreeViewer newTreeViewer = cast(TreeViewer) viewer;
|
|
338
|
|
339 if (newTreeViewer !is treeViewer) {
|
|
340 if (treeViewer !is null) {
|
|
341 treeViewer.removeTreeListener(expandListener);
|
|
342 }
|
|
343
|
|
344 this.treeViewer = newTreeViewer;
|
|
345 if (newTreeViewer !is null) {
|
|
346 newTreeViewer.addTreeListener(expandListener);
|
|
347 }
|
|
348 }
|
|
349 }
|
|
350
|
|
351 public Object[] getChildren(Object parentElement) {
|
|
352 Set result = getNode(parentElement).getChildren();
|
|
353
|
|
354 addParent(parentElement, result);
|
|
355
|
|
356 return result.toArray();
|
|
357 }
|
|
358
|
|
359 private TreeNode getNode(Object parentElement) {
|
|
360 TreeNode result = cast(TreeNode)mapElementToTreeNode.get(parentElement);
|
|
361 if (result is null) {
|
|
362 result = new TreeNode(parentElement, this);
|
|
363 mapElementToTreeNode.put(parentElement, result);
|
|
364 elements.fireSetChange(Diffs.createSetDiff(Collections.singleton(parentElement),
|
|
365 Collections.EMPTY_SET));
|
|
366 }
|
|
367 return result;
|
|
368 }
|
|
369
|
|
370 public Object getParent(Object element) {
|
|
371 Object result = getNode(element).getParent();
|
|
372 if (result is null && rootParentProvider !is null) {
|
|
373 result = rootParentProvider.getParent(element);
|
|
374 }
|
|
375 return result;
|
|
376 }
|
|
377
|
|
378 public bool hasChildren(Object element) {
|
|
379 return getNode(element).shouldShowPlus();
|
|
380 }
|
|
381
|
|
382 public Object[] getElements(Object inputElement) {
|
|
383 return getChildren(inputElement);
|
|
384 }
|
|
385
|
|
386 /**
|
|
387 * @return aouesnth
|
|
388 */
|
|
389 public IObservableSet getKnownElements() {
|
|
390 return elements;
|
|
391 }
|
|
392
|
|
393 /* package */ void changeStale(int staleDelta) {
|
|
394 staleCount += staleDelta;
|
|
395 processPrefetches();
|
|
396 elements.setStale(staleCount !is 0);
|
|
397 }
|
|
398
|
|
399 /**
|
|
400 * @return aoueesnth
|
|
401 */
|
|
402 public TreeViewer getViewer() {
|
|
403 return treeViewer;
|
|
404 }
|
|
405
|
|
406 /**
|
|
407 * @param element
|
|
408 * @return aoeusnth
|
|
409 */
|
|
410 public bool isDirty(Object element) {
|
|
411 return false;
|
|
412 }
|
|
413
|
|
414 /* package */ void enqueuePrefetch(TreeNode node) {
|
|
415 if (maxPrefetches > 0 || maxPrefetches is -1) {
|
|
416 if (staleCount is 0) {
|
|
417 // Call node.getChildren()... this will cause us to start listening to the
|
|
418 // node and will trigger prefetching. Don't call prefetch since this method
|
|
419 // is intended to be called inside getters (which will simply return the
|
|
420 // fetched nodes) and prefetch() is intended to be called inside an asyncExec,
|
|
421 // which will notify the viewer directly of the newly discovered nodes.
|
|
422 node.getChildren();
|
|
423 } else {
|
|
424 enqueuedPrefetches.add(node);
|
|
425 while (maxPrefetches >= 0 && enqueuedPrefetches.size() > maxPrefetches) {
|
|
426 enqueuedPrefetches.removeFirst();
|
|
427 }
|
|
428 }
|
|
429 }
|
|
430 }
|
|
431
|
|
432 private void processPrefetches() {
|
|
433 while (staleCount is 0 && !enqueuedPrefetches.isEmpty()) {
|
|
434 TreeNode next = cast(TreeNode)enqueuedPrefetches.removeLast();
|
|
435
|
|
436 // Note that we don't remove nodes from the prefetch queue when they are disposed,
|
|
437 // so we may encounter disposed nodes at this time.
|
|
438 if (!next.isDisposed()) {
|
|
439 next.prefetch();
|
|
440 }
|
|
441 }
|
|
442 }
|
|
443
|
|
444 public Object[] getChildren(TreePath parentPath) {
|
|
445 return getChildren(parentPath.getLastSegment());
|
|
446 }
|
|
447
|
|
448 public TreePath[] getParents(Object element) {
|
|
449 // Compute all paths that do not contain cycles
|
|
450 /**
|
|
451 * List of Lists
|
|
452 */
|
|
453 List parentPaths = computeParents(element, new HashSet());
|
|
454
|
|
455 /**
|
|
456 * List of TreePath
|
|
457 */
|
|
458 List result = new ArrayList();
|
|
459
|
|
460 for (Iterator iterator = parentPaths.iterator(); iterator.hasNext();) {
|
|
461 List nextPath = cast(List) iterator.next();
|
|
462
|
|
463 LinkedList resultPath = new LinkedList();
|
|
464 resultPath.addAll(nextPath);
|
|
465 Object nextParent = resultPath.isEmpty() ? element : resultPath.getFirst();
|
|
466 for(;nextParent !is null;) {
|
|
467 if (rootParentProvider !is null) {
|
|
468 nextParent = rootParentProvider.getParent(nextParent);
|
|
469 if (nextParent !is null) {
|
|
470 resultPath.addFirst(nextParent);
|
|
471 }
|
|
472 } else {
|
|
473 nextParent = null;
|
|
474 }
|
|
475 }
|
|
476
|
|
477 result.add(new TreePath(resultPath.toArray()));
|
|
478 }
|
|
479
|
|
480 if (result.isEmpty() && rootParentProvider !is null) {
|
|
481 Object nextParent = rootParentProvider.getParent(element);
|
|
482 if (nextParent !is null) {
|
|
483 LinkedList resultPath = new LinkedList();
|
|
484 while (nextParent !is null) {
|
|
485 resultPath.addFirst(nextParent);
|
|
486 nextParent = rootParentProvider.getParent(nextParent);
|
|
487 }
|
|
488
|
|
489 result.add(new TreePath(resultPath.toArray()));
|
|
490 }
|
|
491
|
|
492 }
|
|
493
|
|
494 return cast(TreePath[]) result.toArray(new TreePath[result.size()]);
|
|
495 }
|
|
496
|
|
497 /**
|
|
498 *
|
|
499 * @param node
|
|
500 * @param toIgnore
|
|
501 * @return a list of Lists, indicating all known paths to the given node
|
|
502 */
|
|
503 private List computeParents(Object node, HashSet toIgnore) {
|
|
504 List result = new ArrayList();
|
|
505 bool containedNode = toIgnore.add(node);
|
|
506
|
|
507 TreeNode tn = getNode(node);
|
|
508
|
|
509 HashSet parents = new HashSet();
|
|
510 parents.addAll(tn.getParents());
|
|
511 parents.removeAll(toIgnore);
|
|
512 if (parents.isEmpty()) {
|
|
513 ArrayList newPath = new ArrayList();
|
|
514 result.add(newPath);
|
|
515 } else {
|
|
516 for (Iterator iterator = parents.iterator(); iterator.hasNext();) {
|
|
517 Object parent = iterator.next();
|
|
518
|
|
519 List parentPaths = computeParents(parent, toIgnore);
|
|
520
|
|
521 for (Iterator iterator2 = parentPaths.iterator(); iterator2
|
|
522 .hasNext();) {
|
|
523 List parentPath = cast(List) iterator2.next();
|
|
524
|
|
525 parentPath.add(parent);
|
85
|
526 result.add(cast(Object)parentPath);
|
78
|
527 }
|
|
528 }
|
|
529 }
|
|
530
|
|
531 if (containedNode) {
|
|
532 toIgnore.remove(node);
|
|
533 }
|
|
534 return result;
|
|
535 }
|
|
536
|
|
537 public bool hasChildren(TreePath path) {
|
|
538 return hasChildren(path.getLastSegment());
|
|
539 }
|
|
540 }
|