1 /*******************************************************************************
2  * Copyright (c) 2005, 2013 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *******************************************************************************/
14 package org.eclipse.debug.internal.ui.viewers;
15 
16 import java.util.HashMap;
17 import java.util.Iterator;
18 import java.util.List;
19 import java.util.Map;
20 
21 import org.eclipse.core.runtime.Assert;
22 import org.eclipse.core.runtime.IAdaptable;
23 import org.eclipse.core.runtime.IProgressMonitor;
24 import org.eclipse.core.runtime.IStatus;
25 import org.eclipse.core.runtime.Status;
26 import org.eclipse.debug.internal.ui.DebugUIPlugin;
27 import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelChangedListener;
28 import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelProxy;
29 import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelSelectionPolicy;
30 import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelSelectionPolicyFactory;
31 import org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext;
32 import org.eclipse.debug.internal.ui.viewers.model.provisional.IStatusMonitor;
33 import org.eclipse.debug.internal.ui.viewers.model.provisional.PresentationContext;
34 import org.eclipse.jface.resource.ImageDescriptor;
35 import org.eclipse.jface.viewers.ISelection;
36 import org.eclipse.jface.viewers.IStructuredContentProvider;
37 import org.eclipse.jface.viewers.IStructuredSelection;
38 import org.eclipse.jface.viewers.SelectionChangedEvent;
39 import org.eclipse.jface.viewers.StructuredSelection;
40 import org.eclipse.jface.viewers.StructuredViewer;
41 import org.eclipse.jface.viewers.Viewer;
42 import org.eclipse.swt.SWT;
43 import org.eclipse.swt.events.SelectionEvent;
44 import org.eclipse.swt.graphics.Color;
45 import org.eclipse.swt.graphics.Font;
46 import org.eclipse.swt.graphics.FontData;
47 import org.eclipse.swt.graphics.Image;
48 import org.eclipse.swt.graphics.RGB;
49 import org.eclipse.swt.widgets.Control;
50 import org.eclipse.swt.widgets.Event;
51 import org.eclipse.swt.widgets.Item;
52 import org.eclipse.swt.widgets.Listener;
53 import org.eclipse.swt.widgets.Widget;
54 import org.eclipse.ui.progress.WorkbenchJob;
55 
56 /**
57  * A viewer that retrieves labels and content asynchronously via adapters and supports
58  * duplicate elements in the viewer. Retrieving content and labels asynchronously allows
59  * for arbitrary latency without blocking the UI thread.
60  * <p>
61  * This viewer uses adapters to retrieve labels and content rather than
62  * a label provider and content provider. As such, the label provider for this viewer
63  * is <code>null</code> by default. The content provider returned by this viewer is
64  * non-<code>null</code> to conform to the viewer specification, but performs no
65  * useful function.
66  * </p>
67  * <p>
68  * The selection in this viewer is also set asynchronously. When the selection is set,
69  * the viewer attempts to perform the selection. If the elements in the specified selection
70  * are not yet in the viewer, the portion of the selection that could not be honored
71  * becomes a pending selection. As more elements are added to viewer, the pending selection
72  * is attempted to be set.
73  * </p>
74  * @since 3.2
75  */
76 public abstract class AsynchronousViewer extends StructuredViewer implements Listener {
77 
78 	/**
79 	 * Model of elements for this viewer
80 	 */
81 	private AsynchronousModel fModel;
82 
83 	/**
84 	 * Cache of images used for elements in this tree viewer. Label updates
85 	 * use the method <code>getImage(...)</code> to cache images for
86 	 * image descriptors. The images are disposed when this viewer is disposed.
87 	 */
88 	private Map<ImageDescriptor, Image> fImageCache = new HashMap<>();
89 
90 	/**
91 	 * Cache of the fonts used for elements in this tree viewer. Label updates
92 	 * use the method <code>getFont(...)</code> to cache fonts for
93 	 * FontData objects. The fonts are disposed with the viewer.
94 	 */
95 	private Map<FontData, Font> fFontCache = new HashMap<>();
96 
97 	/**
98 	 * Cache of the colors used for elements in this tree viewer. Label updates
99 	 * use the method <code>getColor(...)</code> to cache colors for
100 	 * RGB values. The colors are disposed with the viewer.
101 	 */
102 	private Map<RGB, Color> fColorCache = new HashMap<>();
103 
104 	/**
105 	 * The context in which this viewer is being used - i.e. what part it is contained
106 	 * in any any preference settings associated with it.
107 	 */
108 	private IPresentationContext fContext;
109 
110 	private ISelection fPendingSelection;
111 
112 	private ISelection fCurrentSelection;
113 
114 	/**
115 	 * Array used to store indices of the path to an item in the viewer being mapped
116 	 * by a 'set data' callback. Indices are bottom up. For example when 'set data' for
117 	 * the 3rd child of the 4th child of the 2nd root element were being asked for,
118 	 * the first 3 indices would look like: [3, 4, 2, ....]. We re-use an array to avoid
119 	 * creating a new one all the time. The array grows as needed to accommodate deep
120 	 * elements.
121 	 */
122 	private int[] fSetDataIndicies = new int[5];
123 
124 	/**
125 	 * The update policy for this viewer.
126 	 */
127 	private AbstractUpdatePolicy fUpdatePolicy;
128 
129 	protected static final String OLD_LABEL = "old_label"; //$NON-NLS-1$
130 	protected static final String OLD_IMAGE = "old_image"; //$NON-NLS-1$
131 
132 	/**
133 	 * Creates a new viewer
134 	 */
AsynchronousViewer()135 	protected AsynchronousViewer() {
136 		setContentProvider(new NullContentProvider());
137 		setUseHashlookup(true);
138 	}
139 
140 	/**
141 	 * Hash lookup is required, don't let subclasses change behavior.
142 	 * @param enable if hash lookup should be used in the viewer
143 	 */
144 	@Override
setUseHashlookup(boolean enable)145 	public final void setUseHashlookup(boolean enable) {
146 		Assert.isTrue(enable);
147 		super.setUseHashlookup(enable);
148 	}
149 
150 	@Override
hookControl(Control control)151 	protected void hookControl(Control control) {
152 		super.hookControl(control);
153 		control.addListener(SWT.SetData, this);
154 	}
155 
156 	/**
157 	 * Clients must call this methods when this viewer is no longer needed
158 	 * so it can perform cleanup.
159 	 */
dispose()160 	public synchronized void dispose() {
161 		for (Image image : fImageCache.values()) {
162 			image.dispose();
163 		}
164 		fImageCache.clear();
165 		for (Font font : fFontCache.values()) {
166 			font.dispose();
167 		}
168 		fFontCache.clear();
169 		for (Color color : fColorCache.values()) {
170 			color.dispose();
171 		}
172 		fColorCache.clear();
173 
174 		if (fModel != null) {
175 			fModel.dispose();
176 		}
177 		if (fUpdatePolicy != null) {
178 			fUpdatePolicy.dispose();
179 		}
180 		if (fContext != null) {
181 			((PresentationContext)fContext).dispose();
182 		}
183 	}
184 
185 	/**
186 	 * Updates all occurrences of the given element in this viewer.
187 	 *
188 	 * @param element element to update
189 	 */
update(Object element)190 	public void update(Object element) {
191 		ModelNode[] nodes = getModel().getNodes(element);
192 		if (nodes != null) {
193 			for (ModelNode node : nodes) {
194 				updateLabel(node);
195 			}
196 		}
197 	}
198 
199 	/**
200 	 * Updates the label for a specific element (node) in the model.
201 	 *
202 	 * @param node node to update
203 	 */
updateLabel(ModelNode node)204 	protected void updateLabel(ModelNode node) {
205 		// the input is not displayed
206 		if (!node.getElement().equals(getInput())) {
207 			getModel().updateLabel(node);
208 		}
209 	}
210 
211 	/**
212 	 * Returns the presentation context to be used in update requests.
213 	 * Clients may override this method if required to provide special
214 	 * implementations of contexts.
215 	 *
216 	 * @return presentation context
217 	 */
getPresentationContext()218 	public IPresentationContext getPresentationContext() {
219 		return fContext;
220 	}
221 
222 	@Override
unmapAllElements()223 	protected synchronized void unmapAllElements() {
224 		super.unmapAllElements();
225 		AsynchronousModel model = getModel();
226 		if (model != null) {
227 			model.dispose();
228 		}
229 	}
230 
231 	@Override
inputChanged(Object input, Object oldInput)232 	protected synchronized void inputChanged(Object input, Object oldInput) {
233 		fPendingSelection = null;
234 		if (fCurrentSelection != null) {
235 			updateSelection(new StructuredSelection());
236 			fCurrentSelection = null;
237 		}
238 		if (fUpdatePolicy == null) {
239 			fUpdatePolicy = createUpdatePolicy();
240 			fUpdatePolicy.init(this);
241 		}
242 		if (fModel != null) {
243 			fModel.dispose();
244 		}
245 		fModel = createModel();
246 		fModel.init(input);
247 		if (input != null) {
248 			mapElement(fModel.getRootNode(), getControl());
249 			getControl().setData(fModel.getRootNode().getElement());
250 		} else {
251 			unmapAllElements();
252 			getControl().setData(null);
253 		}
254 		refresh();
255 	}
256 
257 	/**
258 	 * Creates a new empty model for this viewer that
259 	 * is *not* initialized.
260 	 *
261 	 * @return a new model
262 	 */
createModel()263 	protected abstract AsynchronousModel createModel();
264 
265 	/**
266 	 * Creates and returns this viewers update policy.
267 	 * @return update policy
268 	 */
createUpdatePolicy()269 	public abstract AbstractUpdatePolicy createUpdatePolicy();
270 
getImages(ImageDescriptor[] descriptors)271 	Image[] getImages(ImageDescriptor[] descriptors) {
272 		if (descriptors == null || descriptors.length == 0) {
273 			String[] columns = getPresentationContext().getColumns();
274 			if (columns == null) {
275 				return new Image[1];
276 			} else {
277 				return new Image[columns.length];
278 			}
279 		}
280 		Image[] images = new Image[descriptors.length];
281 		for (int i = 0; i < images.length; i++) {
282 			images[i] = getImage(descriptors[i]);
283 		}
284 		return images;
285 	}
286 
287 	/**
288 	 * Returns an image for the given image descriptor or <code>null</code>. Adds the image
289 	 * to a cache of images if it does not already exist. The cache is cleared when this viewer
290 	 * is disposed.
291 	 *
292 	 * @param descriptor image descriptor or <code>null</code>
293 	 * @return image or <code>null</code>
294 	 */
getImage(ImageDescriptor descriptor)295 	protected Image getImage(ImageDescriptor descriptor) {
296 		if (descriptor == null) {
297 			return null;
298 		}
299 		Image image = fImageCache.get(descriptor);
300 		if (image == null) {
301 			image = new Image(getControl().getDisplay(), descriptor.getImageData());
302 			fImageCache.put(descriptor, image);
303 		}
304 		return image;
305 	}
306 
getFonts(FontData[] fontDatas)307 	protected Font[] getFonts(FontData[] fontDatas) {
308 		if (fontDatas == null || fontDatas.length == 0) {
309 			String[] columns = getPresentationContext().getColumns();
310 			if (columns == null) {
311 				return new Font[1];
312 			} else {
313 				return new Font[columns.length];
314 			}
315 		}
316 
317 		Font[] fonts = new Font[fontDatas.length];
318 		for (int i = 0; i < fonts.length; i++) {
319 			fonts[i] = getFont(fontDatas[i]);
320 		}
321 		return fonts;
322 	}
323 
324 	/**
325 	 * Returns a font for the given font data or <code>null</code>. Adds the font to this viewer's font
326 	 * cache which is disposed when this viewer is disposed.
327 	 *
328 	 * @param fontData font data or <code>null</code>
329 	 * @return font font or <code>null</code>
330 	 */
getFont(FontData fontData)331 	protected Font getFont(FontData fontData) {
332 		if (fontData == null) {
333 			return null;
334 		}
335 		Font font = fFontCache.get(fontData);
336 		if (font == null) {
337 			font = new Font(getControl().getDisplay(), fontData);
338 			fFontCache.put(fontData, font);
339 		}
340 		return font;
341 	}
342 
getColors(RGB[] rgb)343 	protected Color[] getColors(RGB[] rgb) {
344 		if (rgb == null || rgb.length == 0) {
345 			String[] columns = getPresentationContext().getColumns();
346 			if (columns == null) {
347 				return new Color[1];
348 			} else {
349 				return new Color[columns.length];
350 			}
351 		}
352 		Color[] colors = new Color[rgb.length];
353 		for (int i = 0; i < colors.length; i++) {
354 			colors[i] = getColor(rgb[i]);
355 		}
356 		return colors;
357 	}
358 	/**
359 	 * Returns a color for the given RGB or <code>null</code>. Adds the color to this viewer's color
360 	 * cache which is disposed when this viewer is disposed.
361 	 *
362 	 * @param rgb RGB or <code>null</code>
363 	 * @return color or <code>null</code>
364 	 */
getColor(RGB rgb)365 	protected Color getColor(RGB rgb) {
366 		if (rgb == null) {
367 			return null;
368 		}
369 		Color color = fColorCache.get(rgb);
370 		if (color == null) {
371 			color = new Color(getControl().getDisplay(), rgb);
372 			fColorCache.put(rgb, color);
373 		}
374 		return color;
375 	}
376 
377 	/**
378 	 * Sets the context for this viewer.
379 	 *
380 	 * @param context the presentation context
381 	 */
setContext(IPresentationContext context)382 	public void setContext(IPresentationContext context) {
383 		fContext = context;
384 	}
385 
386 	@Override
doFindItem(Object element)387 	protected Widget doFindItem(Object element) {
388 		// this viewer maps model nodes to widgets, so the element is a ModelNode
389 		AsynchronousModel model = getModel();
390 		if (model != null) {
391 			if (element.equals(model.getRootNode())) {
392 				return doFindInputItem(element);
393 			}
394 			Widget[] widgets = findItems(element);
395 			if (widgets.length > 0) {
396 				return widgets[0];
397 			}
398 		}
399 		return null;
400 	}
401 
402 	@Override
doFindInputItem(Object element)403 	protected Widget doFindInputItem(Object element) {
404 		if (element instanceof ModelNode) {
405 			ModelNode node = (ModelNode) element;
406 			if (node.getElement().equals(getInput())) {
407 				return getControl();
408 			}
409 		}
410 		return null;
411 	}
412 
413 	@Override
doUpdateItem(Widget item, Object element, boolean fullMap)414 	protected void doUpdateItem(Widget item, Object element, boolean fullMap) {
415 	}
416 
417 	@Override
internalRefresh(Object element)418 	protected void internalRefresh(Object element) {
419 		// get the nodes in the model
420 		AsynchronousModel model = getModel();
421 		if (model != null) {
422 			ModelNode[] nodes = model.getNodes(element);
423 			if (nodes != null) {
424 				for (ModelNode node : nodes) {
425 					// get the widget for the node
426 					Widget item = findItem(node);
427 					if (item != null) {
428 						internalRefresh(node);
429 					}
430 				}
431 			}
432 		}
433 	}
434 
435 	/**
436 	 * Refreshes a specific occurrence of an element (a node).
437 	 *
438 	 * @param node node to update
439 	 *
440 	 * Subclasses should override and call super
441 	 */
internalRefresh(ModelNode node)442 	protected void internalRefresh(ModelNode node) {
443 		updateLabel(node);
444 	}
445 
446 	@Override
setSelection(ISelection selection, boolean reveal)447 	public synchronized void setSelection(ISelection selection, boolean reveal) {
448 		setSelection(selection, reveal, false);
449 	}
450 
451 	/**
452 	 * Sets the selection in this viewer.
453 	 *
454 	 * @param selection new selection
455 	 * @param reveal whether to reveal the selection
456 	 * @param force whether to force the selection change without consulting the model
457 	 *  selection policy
458 	 */
setSelection(ISelection selection, final boolean reveal, boolean force)459 	public synchronized void setSelection(ISelection selection, final boolean reveal, boolean force) {
460 		Control control = getControl();
461 		if (control == null || control.isDisposed()) {
462 			return;
463 		}
464 		if (!acceptsSelection(selection)) {
465 			selection = getEmptySelection();
466 		}
467 		if (!force && !overrideSelection(fCurrentSelection, selection)) {
468 			return;
469 		}
470 
471 		fPendingSelection = selection;
472 
473 		if (getControl().getDisplay().getThread() == Thread.currentThread()) {
474 			attemptSelection(reveal);
475 		} else {
476 			WorkbenchJob job = new WorkbenchJob("attemptSelection") { //$NON-NLS-1$
477 				@Override
478 				public IStatus runInUIThread(IProgressMonitor monitor) {
479 					attemptSelection(reveal);
480 					return Status.OK_STATUS;
481 				}
482 
483 			};
484 			job.setSystem(true);
485 			job.schedule();
486 		}
487 	}
488 
489 
490 	/**
491 	 * Returns whether the candidate selection should override the current
492 	 * selection.
493 	 *
494 	 * @param current the current selection
495 	 * @param candidate the new selection
496 	 * @return if the selection should be overridden
497 	 */
overrideSelection(ISelection current, ISelection candidate)498 	protected boolean overrideSelection(ISelection current, ISelection candidate) {
499 		IModelSelectionPolicy selectionPolicy = getSelectionPolicy(current);
500 		if (selectionPolicy == null) {
501 			return true;
502 		}
503 		if (selectionPolicy.contains(candidate, getPresentationContext())) {
504 			return selectionPolicy.overrides(current, candidate, getPresentationContext());
505 		}
506 		return !selectionPolicy.isSticky(current, getPresentationContext());
507 	}
508 
509 	@Override
getSelection()510 	public ISelection getSelection() {
511 		Control control = getControl();
512 		if (control == null || control.isDisposed() || fCurrentSelection == null) {
513 			return StructuredSelection.EMPTY;
514 		}
515 		return fCurrentSelection;
516 	}
517 
518 	@Override
handleSelect(SelectionEvent event)519 	protected void handleSelect(SelectionEvent event) {
520 		// handle case where an earlier selection listener disposed the control.
521 		Control control = getControl();
522 		if (control != null && !control.isDisposed()) {
523 			updateSelection(newSelectionFromWidget());
524 		}
525 	}
526 
527 	@Override
handlePostSelect(SelectionEvent e)528 	protected void handlePostSelect(SelectionEvent e) {
529 		SelectionChangedEvent event = new SelectionChangedEvent(this, newSelectionFromWidget());
530 		firePostSelectionChanged(event);
531 	}
532 
533 	/**
534 	 * Creates and returns a new selection from this viewer, based on the selected
535 	 * elements in the widget.
536 	 *
537 	 * @return a new selection
538 	 */
newSelectionFromWidget()539 	protected abstract ISelection newSelectionFromWidget();
540 
541 	/**
542 	 * Returns the selection policy associated with the given selection
543 	 * or <code>null</code> if none.
544 	 *
545 	 * @param selection or <code>null</code>
546 	 * @return selection policy or <code>null</code>
547 	 */
getSelectionPolicy(ISelection selection)548 	protected IModelSelectionPolicy getSelectionPolicy(ISelection selection) {
549 		if (selection instanceof IStructuredSelection) {
550 			IStructuredSelection ss = (IStructuredSelection) selection;
551 			Object element = ss.getFirstElement();
552 			if (element instanceof IAdaptable) {
553 				IAdaptable adaptable = (IAdaptable) element;
554 				IModelSelectionPolicyFactory factory =  adaptable.getAdapter(IModelSelectionPolicyFactory.class);
555 				if (factory != null) {
556 					return factory.createModelSelectionPolicyAdapter(adaptable, getPresentationContext());
557 				}
558 			}
559 		}
560 		return null;
561 	}
562 
563 	@Override
setSelectionToWidget(ISelection selection, final boolean reveal)564 	final protected void setSelectionToWidget(ISelection selection, final boolean reveal) {
565 		// NOT USED
566 		throw new IllegalArgumentException("This method should not be called"); //$NON-NLS-1$
567 	}
568 
569 	@Override
setSelectionToWidget(List l, boolean reveal)570 	final protected void setSelectionToWidget(List l, boolean reveal) {
571 		// NOT USED
572 		throw new IllegalArgumentException("This method should not be called"); //$NON-NLS-1$
573 	}
574 
575 	/**
576 	 * Attempts to update any pending selection.
577 	 *
578 	 * @param reveal whether to reveal the selection
579 	 */
attemptSelection(boolean reveal)580 	protected void attemptSelection(boolean reveal) {
581 		ISelection currentSelection = null;
582 		synchronized (this) {
583 			if (fPendingSelection != null) {
584 				ISelection remaining = doAttemptSelectionToWidget(fPendingSelection, reveal);
585 				if (remaining.isEmpty()) {
586 					remaining = null;
587 				}
588 				if (!fPendingSelection.equals(remaining)) {
589 					fPendingSelection = remaining;
590 					currentSelection = newSelectionFromWidget();
591 					if (isSuppressEqualSelections() && currentSelection.equals(fCurrentSelection)) {
592 						return;
593 					}
594 				}
595 			}
596 		}
597 		if (currentSelection != null) {
598 			updateSelection(currentSelection);
599 			firePostSelectionChanged(new SelectionChangedEvent(this, currentSelection));
600 		}
601 	}
602 
603 	/**
604 	 * Controls whether selection change notification is sent even when
605 	 * successive selections are equal.
606 	 *
607 	 * TODO: what we really want is to fire selection change on ACTIVATE model
608 	 * change, even when selection is the same.
609 	 *
610 	 * @return whether to suppress change notification for equal successive
611 	 *         selections
612 	 */
isSuppressEqualSelections()613 	protected boolean isSuppressEqualSelections() {
614 		return true;
615 	}
616 
617 	/**
618 	 * Attempts to selection the specified selection and returns a selection
619 	 * representing the portion of the selection that could not be honored
620 	 * and still needs to be selected.
621 	 *
622 	 * @param selection selection to attempt
623 	 * @param reveal whether to reveal the selection
624 	 * @return remaining selection
625 	 */
doAttemptSelectionToWidget(ISelection selection, boolean reveal)626 	protected abstract ISelection doAttemptSelectionToWidget(ISelection selection, boolean reveal);
627 
628 	/**
629 	 * Returns whether this viewer supports the given selection.
630 	 *
631 	 * @param selection a selection
632 	 * @return whether this viewer supports the given selection
633 	 */
acceptsSelection(ISelection selection)634 	protected abstract boolean acceptsSelection(ISelection selection);
635 
636 	/**
637 	 * Returns an empty selection supported by this viewer.
638 	 *
639 	 * @return an empty selection supported by this viewer
640 	 */
getEmptySelection()641 	protected abstract ISelection getEmptySelection();
642 
643 	/**
644 	 * A content provider that does nothing.
645 	 */
646 	private class NullContentProvider implements IStructuredContentProvider {
647 		@Override
dispose()648 		public void dispose() {
649 		}
650 
651 		@Override
inputChanged(Viewer viewer, Object oldInput, Object newInput)652 		public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
653 		}
654 
655 		@Override
getElements(Object inputElement)656 		public Object[] getElements(Object inputElement) {
657 			return null;
658 		}
659 	}
660 
661 	/**
662 	 * Notification that a presentation update has failed.
663 	 * Subclasses may override as required. The default implementation
664 	 * does nothing.
665 	 *
666 	 * @param monitor monitor for the presentation request that failed
667 	 * @param status status of update
668 	 */
handlePresentationFailure(IStatusMonitor monitor, IStatus status)669 	protected void handlePresentationFailure(IStatusMonitor monitor, IStatus status) {
670 	}
671 
672 	@Override
preservingSelection(Runnable updateCode)673 	protected synchronized void preservingSelection(Runnable updateCode) {
674 		if (fPendingSelection == null || fPendingSelection.isEmpty()) {
675 			ISelection oldSelection = null;
676 			try {
677 				// preserve selection
678 				oldSelection = fCurrentSelection;
679 				// perform the update
680 				updateCode.run();
681 			} finally {
682 				// restore selection
683 				if (oldSelection == null) {
684 					oldSelection = new StructuredSelection();
685 				}
686 				if (getControl().getDisplay().getThread() == Thread.currentThread()) {
687 					if (!oldSelection.equals(newSelectionFromWidget())) {
688 						restoreSelection(oldSelection);
689 					}
690 				} else {
691 					WorkbenchJob job = new WorkbenchJob("attemptSelection") { //$NON-NLS-1$
692 						@Override
693 						public IStatus runInUIThread(IProgressMonitor monitor) {
694 							synchronized (AsynchronousViewer.this) {
695 								if (!getControl().isDisposed()) {
696 									if (fPendingSelection == null || fPendingSelection.isEmpty()) {
697 										ISelection tempSelection = fCurrentSelection;
698 										if (tempSelection == null) {
699 											tempSelection = new StructuredSelection();
700 										}
701 										if (!tempSelection.equals(newSelectionFromWidget())) {
702 											restoreSelection(tempSelection);
703 										}
704 									}
705 								}
706 							}
707 							return Status.OK_STATUS;
708 						}
709 
710 					};
711 					job.setSystem(true);
712 					job.schedule();
713 				}
714 			}
715 		} else {
716 			updateCode.run();
717 		}
718 	}
719 
restoreSelection(ISelection oldSelection)720 	protected synchronized void restoreSelection(ISelection oldSelection) {
721 		ISelection remaining = doAttemptSelectionToWidget(oldSelection, false);
722 		// send out notification if old and new differ
723 		fCurrentSelection = newSelectionFromWidget();
724 		if (!selectionExists(fCurrentSelection)) {
725 			if (selectionExists(oldSelection)) {
726 				// old selection exists in the model, but not widget
727 				fCurrentSelection = oldSelection;
728 			} else {
729 				fCurrentSelection = getEmptySelection();
730 			}
731 		}
732 		if (!fCurrentSelection.equals(oldSelection)) {
733 			handleInvalidSelection(oldSelection, fCurrentSelection);
734 			// if the remaining selection still exists in the model, make it pending
735 			if (selectionExists(remaining)) {
736 				setSelection(remaining);
737 			}
738 		}
739 	}
740 
741 	/**
742 	 * Returns whether the selection exists in the model
743 	 * @param selection the selection context
744 	 * @return <code>true</code> if the selecton exists in the model <code>false</code> otherwise
745 	 */
selectionExists(ISelection selection)746 	protected boolean selectionExists(ISelection selection) {
747 		if (selection.isEmpty()) {
748 			return false;
749 		}
750 		if (selection instanceof IStructuredSelection) {
751 			IStructuredSelection ss = (IStructuredSelection) selection;
752 			Iterator<?> iterator = ss.iterator();
753 			while (iterator.hasNext()) {
754 				Object element = iterator.next();
755 				if (getModel().getNodes(element) == null) {
756 					return false;
757 				}
758 			}
759 		}
760 		return true;
761 	}
762 
763 	/**
764 	 * Sets the color attributes of the given widget.
765 	 *
766 	 * @param widget the widget to update
767 	 * @param foreground foreground color of the widget or <code>null</code> if default
768 	 * @param background background color of the widget or <code>null</code> if default
769 	 */
setColors(Widget widget, RGB foreground[], RGB background[])770 	protected abstract void setColors(Widget widget, RGB foreground[], RGB background[]);
771 
772 	/**
773 	 * Sets the label attributes of the given widget.
774 	 *
775 	 * @param widget the widget to update
776 	 * @param text label text
777 	 * @param image label image or <code>null</code>
778 	 */
setLabels(Widget widget, String[] text, ImageDescriptor[] image)779 	protected abstract void setLabels(Widget widget, String[] text, ImageDescriptor[] image);
780 
781 	/**
782 	 * Sets the font attributes of the given widget.
783 	 *
784 	 * @param widget widget to update
785 	 * @param font font of the widget or <code>null</code> if default.
786 	 */
setFonts(Widget widget, FontData[] font)787 	protected abstract void setFonts(Widget widget, FontData[] font);
788 
789 	@Override
updateSelection(ISelection selection)790 	protected synchronized void updateSelection(ISelection selection) {
791 		fCurrentSelection = selection;
792 		super.updateSelection(selection);
793 	}
794 
795 
796 
797 	/**
798 	 * Notification the given model proxy has been added to this viewer's model.
799 	 *
800 	 * @param proxy the model proxy that has been added
801 	 */
modelProxyAdded(IModelProxy proxy)802 	protected void modelProxyAdded(IModelProxy proxy) {
803 		if (fUpdatePolicy instanceof IModelChangedListener) {
804 			proxy.addModelChangedListener((IModelChangedListener)fUpdatePolicy);
805 		}
806 	}
807 
808 	/**
809 	 * Notification the given model proxy has been removed from this viewer's model.
810 	 *
811 	 * @param proxy the model proxy that has been removed
812 	 */
modelProxyRemoved(IModelProxy proxy)813 	protected void modelProxyRemoved(IModelProxy proxy) {
814 		if (fUpdatePolicy instanceof IModelChangedListener) {
815 			proxy.removeModelChangedListener((IModelChangedListener)fUpdatePolicy);
816 		}
817 	}
818 
819 	/**
820 	 * Returns this viewer's model
821 	 *
822 	 * @return model
823 	 */
getModel()824 	protected AsynchronousModel getModel() {
825 		return fModel;
826 	}
827 
828 	/**
829 	 * A node in the model has been updated
830 	 *
831 	 * @param node the model node that has been changed
832 	 */
nodeChanged(ModelNode node)833 	protected void nodeChanged(ModelNode node) {
834 		Widget widget = findItem(node);
835 		if (widget != null) {
836 			clear(widget);
837 			attemptPendingUpdates();
838 		}
839 	}
840 
841 	/**
842 	 * @return if there are any more pending updates in the viewer
843 	 */
hasPendingUpdates()844 	public synchronized boolean hasPendingUpdates() {
845 		return getModel().hasPendingUpdates();
846 	}
847 
848 	/**
849 	 * Notification from the model that the update for the given request
850 	 * has completed.
851 	 *
852 	 * @param monitor the monitor
853 	 */
updateComplete(IStatusMonitor monitor)854 	protected void updateComplete(IStatusMonitor monitor) {
855 	}
856 
857 	/**
858 	 * Clears the given widget
859 	 *
860 	 * @param item the widget
861 	 */
clear(Widget item)862 	protected abstract void clear(Widget item);
863 
864 	/**
865 	 * Clears the children of the widget.
866 	 *
867 	 * @param item the widget to clear children from
868 	 */
clearChildren(Widget item)869 	protected abstract void clearChildren(Widget item);
870 
871 	/**
872 	 * Clears the child at the given index.
873 	 *
874 	 * @param parent the parent widget
875 	 * @param childIndex the index of the child widget to clear
876 	 */
clearChild(Widget parent, int childIndex)877 	protected abstract void clearChild(Widget parent, int childIndex);
878 
879 	/**
880 	 * Returns the child widget at the given index for the given parent or
881 	 * <code>null</code>
882 	 *
883 	 * @param parent the parent widget
884 	 * @param index the index of the child in the parent widget
885 	 * @return the widget at the given index in the parent or <code>null</code>
886 	 */
getChildWidget(Widget parent, int index)887 	protected abstract Widget getChildWidget(Widget parent, int index);
888 
889 	/**
890 	 * Sets the item count for a parent widget
891 	 *
892 	 * @param parent the parent widget
893 	 * @param itemCount the new item count to set
894 	 */
setItemCount(Widget parent, int itemCount)895 	protected abstract void setItemCount(Widget parent, int itemCount);
896 
897 	/**
898 	 * Attempt pending updates. Subclasses may override but should call super.
899 	 */
attemptPendingUpdates()900 	protected void attemptPendingUpdates() {
901 		attemptSelection(false);
902 	}
903 
904 	/**
905 	 * Notification a node's children have changed.
906 	 * Updates the child count for the parent's widget
907 	 * and clears children to be updated.
908 	 *
909 	 * @param parentNode the parent model node
910 	 */
nodeChildrenChanged(ModelNode parentNode)911 	protected void nodeChildrenChanged(ModelNode parentNode) {
912 		Widget widget = findItem(parentNode);
913 		if (widget != null && !widget.isDisposed()) {
914 			int childCount = parentNode.getChildCount();
915 			setItemCount(widget, childCount);
916 			clearChildren(widget);
917 			attemptPendingUpdates();
918 		}
919 	}
920 
921 	/**
922 	 * Notification children have been added to the end
923 	 * of the given parent.
924 	 *
925 	 * @param parentNode the parent model node
926 	 */
nodeChildrenAdded(ModelNode parentNode)927 	protected void nodeChildrenAdded(ModelNode parentNode) {
928 		Widget widget = findItem(parentNode);
929 		if (widget != null && !widget.isDisposed()) {
930 			int childCount = parentNode.getChildCount();
931 			setItemCount(widget, childCount);
932 			attemptPendingUpdates();
933 		}
934 	}
935 
936 	/**
937 	 * Notification children have been added to the end
938 	 * of the given parent.
939 	 *
940 	 * @param parentNode the parent model node
941 	 * @param index the index of the child that was removed
942 	 */
nodeChildRemoved(ModelNode parentNode, int index)943 	protected void nodeChildRemoved(ModelNode parentNode, int index) {
944 		Widget widget = findItem(parentNode);
945 		if (widget != null && !widget.isDisposed()) {
946 			Widget childWidget = getChildWidget(widget, index);
947 			int childCount = parentNode.getChildCount();
948 			// if the child widget exists, dispose it so item state remains, otherwise update child count
949 			if (childWidget == null) {
950 				setItemCount(widget, childCount);
951 			} else {
952 				childWidget.dispose();
953 			}
954 			for (int i = index; i < childCount; i ++) {
955 				clearChild(widget, i);
956 			}
957 			attemptPendingUpdates();
958 		}
959 	}
960 
961 	/**
962 	 * Unmaps the node from its widget and all of its children nodes from
963 	 * their widgets.
964 	 *
965 	 * @param node the model node
966 	 */
unmapNode(ModelNode node)967 	protected void unmapNode(ModelNode node) {
968 		unmapElement(node);
969 		ModelNode[] childrenNodes = node.getChildrenNodes();
970 		if (childrenNodes != null) {
971 			for (ModelNode childNode : childrenNodes) {
972 				unmapNode(childNode);
973 			}
974 		}
975 	}
976 
977 	/**
978 	 * Returns the node corresponding to the given widget or <code>null</code>
979 	 * @param widget widget for which a node is requested
980 	 * @return node or <code>null</code>
981 	 */
findNode(Widget widget)982 	protected ModelNode findNode(Widget widget) {
983 		ModelNode[] nodes = getModel().getNodes(widget.getData());
984 		if (nodes != null) {
985 			for (ModelNode node : nodes) {
986 				Widget item = findItem(node);
987 				if (widget == item) {
988 					return node;
989 				}
990 			}
991 		}
992 		return null;
993 	}
994 
995 	/**
996 	 * Returns the item for the node or <code>null</code>
997 	 * @param node the model node
998 	 * @return the widget or <code>null</code>
999 	 */
findItem(ModelNode node)1000 	protected Widget findItem(ModelNode node) {
1001 		return findItem((Object)node);
1002 	}
1003 
1004 	/*
1005 	 * A virtual item has been exposed in the control, map its data.
1006 	 */
1007 	@Override
handleEvent(final Event event)1008 	public void handleEvent(final Event event) {
1009 		update((Item)event.item, event.index);
1010 	}
1011 
1012 	/**
1013 	 * Update the given item.
1014 	 *
1015 	 * @param item item to update
1016 	 * @param index index of item in parent's children
1017 	 */
update(Item item, int index)1018 	protected void update(Item item, int index) {
1019 		restoreLabels(item);
1020 		int level = 0;
1021 
1022 		Widget parentItem = getParentWidget(item);
1023 		if (DebugUIPlugin.DEBUG_VIEWER) {
1024 			DebugUIPlugin.trace("SET DATA [" + index + "]: " + parentItem);  //$NON-NLS-1$//$NON-NLS-2$
1025 		}
1026 		ModelNode node = null;
1027 		// first, see if the parent element is in the model
1028 		// and look directly for the child
1029 		if (parentItem != null) {
1030 			ModelNode[] nodes = getModel().getNodes(parentItem.getData());
1031 			if (nodes != null) {
1032 				for (ModelNode parentNode : nodes) {
1033 					Widget parentWidget = findItem(parentNode);
1034 					if (parentWidget == parentItem) {
1035 						ModelNode[] childrenNodes = parentNode.getChildrenNodes();
1036 						if (childrenNodes != null && index < childrenNodes.length) {
1037 							node = childrenNodes[index];
1038 						}
1039 					}
1040 				}
1041 			}
1042 		}
1043 
1044 		// otherwise, build a path to the model node
1045 		if (node == null) {
1046 			setNodeIndex(index, level);
1047 			while (parentItem instanceof Item) {
1048 				level++;
1049 				Widget parent = getParentWidget(parentItem);
1050 				int pindex = indexOf(parent, parentItem);
1051 				if (pindex < 0) {
1052 					return;
1053 				}
1054 				setNodeIndex(pindex, level);
1055 				parentItem = parent;
1056 			}
1057 
1058 			node = getModel().getRootNode();
1059 			if (node == null) {
1060 				if (DebugUIPlugin.DEBUG_VIEWER) {
1061 					DebugUIPlugin.trace("\tFAILED - root model node is null"); //$NON-NLS-1$
1062 				}
1063 				return;
1064 			}
1065 			for (int i = level; i >= 0; i--) {
1066 				ModelNode[] childrenNodes = node.getChildrenNodes();
1067 				if (childrenNodes == null) {
1068 					if (DebugUIPlugin.DEBUG_VIEWER) {
1069 						DebugUIPlugin.trace("\tFAILED - no children nodes for " + node); //$NON-NLS-1$
1070 					}
1071 					return;
1072 				}
1073 				int pindex = getNodeIndex(i);
1074 				if (pindex < childrenNodes.length) {
1075 					node = childrenNodes[pindex];
1076 				} else {
1077 					if (DebugUIPlugin.DEBUG_VIEWER) {
1078 						DebugUIPlugin.trace("\tFAILED - no children nodes for " + node); //$NON-NLS-1$
1079 					}
1080 					return;
1081 				}
1082 			}
1083 		}
1084 
1085 
1086 		// map the node to the element and refresh it
1087 		if (node != null) {
1088 			mapElement(node, item);
1089 			item.setData(node.getElement());
1090 			if (DebugUIPlugin.DEBUG_VIEWER) {
1091 				DebugUIPlugin.trace("\titem mapped: " + node); //$NON-NLS-1$
1092 			}
1093 			internalRefresh(node);
1094 		} else {
1095 			if (DebugUIPlugin.DEBUG_VIEWER) {
1096 				DebugUIPlugin.trace("\tFAILED - unable to find corresponding node"); //$NON-NLS-1$
1097 			}
1098 		}
1099 	}
1100 
1101 	/**
1102 	 * Sets the index of a child node being mapped at the given expansion level
1103 	 * in the tree.
1104 	 *
1105 	 * @param nodeIndex the index of the node
1106 	 * @param level the expansion level
1107 	 */
setNodeIndex(int nodeIndex, int level)1108 	private void setNodeIndex(int nodeIndex, int level) {
1109 		if (level > (fSetDataIndicies.length - 1)) {
1110 			// grow the array
1111 			int[] next = new int[level+5];
1112 			System.arraycopy(fSetDataIndicies, 0, next, 0, fSetDataIndicies.length);
1113 			fSetDataIndicies = next;
1114 		}
1115 		fSetDataIndicies[level] = nodeIndex;
1116 	}
1117 
1118 	/**
1119 	 * Returns the index of a child node being mapped at the given expansion level in
1120 	 * the tree.
1121 	 *
1122 	 * @param level the expansion level
1123 	 * @return the child index
1124 	 */
getNodeIndex(int level)1125 	private int getNodeIndex(int level) {
1126 		return fSetDataIndicies[level];
1127 	}
1128 
indexOf(Widget parent, Widget child)1129 	protected abstract int indexOf(Widget parent, Widget child);
1130 
restoreLabels(Item item)1131 	protected abstract void restoreLabels(Item item);
1132 
1133 	/**
1134 	 * Returns the parent widget for the given widget or <code>null</code>
1135 	 *
1136 	 * @param widget the widget to get the parent from
1137 	 * @return parent widget or <code>null</code>
1138 	 */
getParentWidget(Widget widget)1139 	protected abstract Widget getParentWidget(Widget widget);
1140 
1141 	/**
1142 	 * Updates the children of the given node.
1143 	 *
1144 	 * @param parent
1145 	 *            node of which to update children
1146 	 */
updateChildren(ModelNode parent)1147 	protected void updateChildren(ModelNode parent) {
1148 		getModel().updateChildren(parent);
1149 	}
1150 }
1151