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