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