/******************************************************************************* * Copyright (c) 2000, 2017 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * Tom Schindl - bug 151205 * Lars Vogel <Lars.Vogel@vogella.com> - Bug 402439, 475689 * Thorsten Maack <tm@tmaack.de> - Bug 482163 * Jan-Ove Weichel <janove.weichel@vogella.com> - Bug 481490 *******************************************************************************/ package org.eclipse.jface.viewers; import static org.eclipse.swt.events.SelectionListener.widgetSelectedAdapter; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.ListenerList; import org.eclipse.core.runtime.Status; import org.eclipse.jface.internal.InternalPolicy; import org.eclipse.jface.util.OpenStrategy; import org.eclipse.jface.util.Policy; import org.eclipse.jface.util.SafeRunnable; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.TableTreeItem; import org.eclipse.swt.dnd.DragSource; import org.eclipse.swt.dnd.DragSourceListener; import org.eclipse.swt.dnd.DropTarget; import org.eclipse.swt.dnd.DropTargetListener; import org.eclipse.swt.dnd.Transfer; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Item; import org.eclipse.swt.widgets.TableItem; import org.eclipse.swt.widgets.TreeItem; import org.eclipse.swt.widgets.Widget; /** * Abstract base implementation for structure-oriented viewers (trees, lists, * tables). Supports custom sorting, filtering, and rendering. * <p> * Any number of viewer filters can be added to this viewer (using * <code>addFilter</code>). When the viewer receives an update, it asks each * of its filters if it is out of date, and refilters elements as required. * </p> * * @see ViewerFilter * @see ViewerComparator */ public abstract class StructuredViewer extends ContentViewer implements IPostSelectionProvider { /** * A map from the viewer's model elements to SWT widgets. (key type: * <code>Object</code>, value type: <code>Widget</code>, or <code>Widget[]</code>). * <code>null</code> means that the element map is disabled. */ private CustomHashtable elementMap; /** * The comparer to use for comparing elements, or <code>null</code> to use * the default <code>equals</code> and <code>hashCode</code> methods on * the element itself. */ private IElementComparer comparer; /** * This viewer's comparator used for sorting. <code>null</code> means there is no comparator. */ private ViewerComparator sorter; /** * This viewer's filters (element type: <code>ViewerFilter</code>). * <code>null</code> means there are no filters. */ private List<ViewerFilter> filters; /** * Indicates whether the viewer should attempt to preserve the selection * across update operations. * * @see #setSelection(ISelection, boolean) */ private boolean preserveSelection = true; /** * Indicates whether a selection change is in progress on this viewer. * * @see #setSelection(ISelection, boolean) */ private boolean inChange; /** * Used while a selection change is in progress on this viewer to indicates * whether the selection should be restored. * * @see #setSelection(ISelection, boolean) */ private boolean restoreSelection; /** * List of double-click state listeners (element type: * <code>IDoubleClickListener</code>). * * @see #fireDoubleClick */ private ListenerList<IDoubleClickListener> doubleClickListeners = new ListenerList<>(); /** * List of open listeners (element type: * <code>ISelectionActivateListener</code>). * * @see #fireOpen */ private ListenerList<IOpenListener> openListeners = new ListenerList<>(); /** * List of post selection listeners (element type: * <code>ISelectionActivateListener</code>). * * @see #firePostSelectionChanged */ private ListenerList<ISelectionChangedListener> postSelectionChangedListeners = new ListenerList<>(); /** * The colorAndFontCollector is an object used by viewers that * support the IColorProvider, the IFontProvider and/or the * IViewerLabelProvider for color and font updates. * Initialize it to have no color or font providing * initially. * @since 3.1 */ private ColorAndFontCollector colorAndFontCollector = new ColorAndFontCollector(); /** * Calls when associate() and disassociate() are called */ private StructuredViewerInternals.AssociateListener associateListener; /** * Empty array of widgets. */ private static Widget[] NO_WIDGETS = new Widget[0]; /** * The ColorAndFontCollector is a helper class for viewers * that have color and font support ad optionally decorators. * @see IColorDecorator * @see IFontDecorator * @see IColorProvider * @see IFontProvider * @see IDecoration */ protected class ColorAndFontCollectorWithProviders extends ColorAndFontCollector{ IColorProvider colorProvider; IFontProvider fontProvider; /** * Create a new instance of the receiver using the supplied * label provider. If it is an IColorProvider or IFontProvider * set these values up. * @param provider IBaseLabelProvider * @see IColorProvider * @see IFontProvider */ public ColorAndFontCollectorWithProviders(IBaseLabelProvider provider) { super(); if (provider instanceof IColorProvider) { colorProvider = (IColorProvider) provider; } if (provider instanceof IFontProvider) { fontProvider = (IFontProvider) provider; } } @Override public void setFontsAndColors(Object element){ if(fontProvider != null){ if(font == null) { font = fontProvider.getFont(element); } } if(colorProvider == null) { return; } //Set the colors if they are not set yet if(background == null) { background = colorProvider.getBackground(element); } if(foreground == null) { foreground = colorProvider.getForeground(element); } } @Override public void applyFontsAndColors(TableItem control) { if(colorProvider == null){ if(usedDecorators){ //If there is no provider only apply set values if(background != null) { control.setBackground(background); } if(foreground != null) { control.setForeground(foreground); } } } else{ //Always set the value if there is a provider control.setBackground(background); control.setForeground(foreground); } if(fontProvider == null){ if(usedDecorators && font != null) { control.setFont(font); } } else { control.setFont(font); } clear(); } @Override public void applyFontsAndColors(TreeItem control) { if(colorProvider == null){ if(usedDecorators){ //If there is no provider only apply set values if(background != null) { control.setBackground(background); } if(foreground != null) { control.setForeground(foreground); } } } else{ //Always set the value if there is a provider control.setBackground(background); control.setForeground(foreground); } if(fontProvider == null){ if(usedDecorators && font != null) { control.setFont(font); } } else { control.setFont(font); } clear(); } @Override public void applyFontsAndColors(TableTreeItem control) { if(colorProvider == null){ if(usedDecorators){ //If there is no provider only apply set values if(background != null) { control.setBackground(background); } if(foreground != null) { control.setForeground(foreground); } } } else{ //Always set the value if there is a provider control.setBackground(background); control.setForeground(foreground); } if(fontProvider == null){ if(usedDecorators && font != null) { control.setFont(font); } } else { control.setFont(font); } clear(); } } /** * The ColorAndFontCollector collects fonts and colors without a * a color or font provider. * */ protected class ColorAndFontCollector { Color foreground = null; Color background = null; Font font = null; boolean usedDecorators = false; /** * Create a new instance of the receiver with * no color and font provider. */ public ColorAndFontCollector(){ super(); } /** * Clear all of the results. */ public void clear() { foreground = null; background = null; font = null; usedDecorators = false; } /** * Set the initial fonts and colors for the element from the * content providers. * @param element Object */ public void setFontsAndColors(Object element){ //Do nothing if there are no providers } /** * Set that decorators were applied. */ public void setUsedDecorators() { this.usedDecorators = true; } /** * Apply the fonts and colors to the control if * required. * @param control */ public void applyFontsAndColors(TableItem control) { if(usedDecorators){ //If there is no provider only apply set values if(background != null) { control.setBackground(background); } if(foreground != null) { control.setForeground(foreground); } if(font != null) { control.setFont(font); } } clear(); } /** * Apply the fonts and colors to the control if * required. * @param control */ public void applyFontsAndColors(TreeItem control) { if(usedDecorators){ //If there is no provider only apply set values if(background != null) { control.setBackground(background); } if(foreground != null) { control.setForeground(foreground); } if(font != null) { control.setFont(font); } } clear(); } /** * Apply the fonts and colors to the control if * required. * @param control */ public void applyFontsAndColors(TableTreeItem control) { if(usedDecorators){ //If there is no provider only apply set values if(background != null) { control.setBackground(background); } if(foreground != null) { control.setForeground(foreground); } if(font != null) { control.setFont(font); } } clear(); } /** * Set the background color. * @param background */ public void setBackground(Color background) { this.background = background; } /** * Set the font. * @param font */ public void setFont(Font font) { this.font = font; } /** * Set the foreground color. * @param foreground */ public void setForeground(Color foreground) { this.foreground = foreground; } } /** * The safe runnable used to update an item. */ class UpdateItemSafeRunnable extends SafeRunnable { private Widget widget; private Object element; private boolean fullMap; UpdateItemSafeRunnable(Widget widget, Object element, boolean fullMap) { this.widget = widget; this.element = element; this.fullMap = fullMap; } @Override public void run() { doUpdateItem(widget, element, fullMap); } } /** * Creates a structured element viewer. The viewer has no input, no content * provider, a default label provider, no sorter, and no filters. */ protected StructuredViewer() { // do nothing } /** * Adds a listener for double-clicks in this viewer. Has no effect if an * identical listener is already registered. * * @param listener * a double-click listener */ public void addDoubleClickListener(IDoubleClickListener listener) { doubleClickListeners.add(listener); } /** * Adds a listener for selection-open in this viewer. Has no effect if an * identical listener is already registered. * * @param listener * an open listener */ public void addOpenListener(IOpenListener listener) { openListeners.add(listener); } @Override public void addPostSelectionChangedListener(ISelectionChangedListener listener) { postSelectionChangedListeners.add(listener); } /** * Adds support for dragging items out of this viewer via a user * drag-and-drop operation. * * @param operations * a bitwise OR of the supported drag and drop operation types ( * <code>DROP_COPY</code>,<code>DROP_LINK</code>, and * <code>DROP_MOVE</code>) * @param transferTypes * the transfer types that are supported by the drag operation * @param listener * the callback that will be invoked to set the drag data and to * cleanup after the drag and drop operation finishes * @see org.eclipse.swt.dnd.DND */ public void addDragSupport(int operations, Transfer[] transferTypes, DragSourceListener listener) { Control myControl = getControl(); final DragSource dragSource = new DragSource(myControl, operations); dragSource.setTransfer(transferTypes); dragSource.addDragListener(listener); } /** * Adds support for dropping items into this viewer via a user drag-and-drop * operation. * * @param operations * a bitwise OR of the supported drag and drop operation types ( * <code>DROP_COPY</code>,<code>DROP_LINK</code>, and * <code>DROP_MOVE</code>) * @param transferTypes * the transfer types that are supported by the drop operation * @param listener * the callback that will be invoked after the drag and drop * operation finishes * @see org.eclipse.swt.dnd.DND */ public void addDropSupport(int operations, Transfer[] transferTypes, final DropTargetListener listener) { Control control = getControl(); DropTarget dropTarget = new DropTarget(control, operations); dropTarget.setTransfer(transferTypes); dropTarget.addDropListener(listener); } /** * Adds the given filter to this viewer, and triggers refiltering and * resorting of the elements. If you want to add more than one filter * consider using {@link StructuredViewer#setFilters(ViewerFilter...)}. * * @param filter * a viewer filter * @see StructuredViewer#setFilters(ViewerFilter...) */ public void addFilter(ViewerFilter filter) { if (filters == null) { filters = new ArrayList<>(); } filters.add(filter); refresh(); } /** * Asserts that the given array of elements is itself non- <code>null</code> * and contains no <code>null</code> elements. * * @param elements * the array to check */ protected void assertElementsNotNull(Object[] elements) { Assert.isNotNull(elements); for (Object element : elements) { Assert.isNotNull(element); } if (InternalPolicy.DEBUG_LOG_EQUAL_VIEWER_ELEMENTS && elements.length > 1) { CustomHashtable elementSet = newHashtable(elements.length * 2); for (Object element : elements) { Object old = elementSet.put(element, element); if (old != null) { String message = "Sibling elements in viewer must not be equal:\n " //$NON-NLS-1$ + old + ",\n " + element; //$NON-NLS-1$ Policy.getLog().log( new Status(IStatus.WARNING, Policy.JFACE, message, new RuntimeException())); return; } } } } /** * Associates the given element with the given widget. Sets the given item's * data to be the element, and maps the element to the item in the element * map (if enabled). * * @param element * the element * @param item * the widget */ protected void associate(Object element, Item item) { Object data = item.getData(); if (data != element) { if (data != null) { disassociate(item); } item.setData(element); mapElement(element, item); } else { // Always map the element, even if data == element, // since unmapAllElements() can leave the map inconsistent // See bug 2741 for details. mapElement(element, item); } if (associateListener != null) associateListener.associate(element, item); } /** * Disassociates the given SWT item from its corresponding element. Sets the * item's data to <code>null</code> and removes the element from the * element map (if enabled). * * @param item * the widget */ protected void disassociate(Item item) { if (associateListener != null) associateListener.disassociate(item); Object element = item.getData(); Assert.isNotNull(element); //Clear the map before we clear the data unmapElement(element, item); item.setData(null); } /** * Returns the widget in this viewer's control which represents the given * element if it is the viewer's input. * <p> * This method is internal to the framework; subclassers should not call * this method. * </p> * * @param element * @return the corresponding widget, or <code>null</code> if none */ protected abstract Widget doFindInputItem(Object element); /** * Returns the widget in this viewer's control which represent the given * element. This method searches all the children of the input element. * <p> * This method is internal to the framework; subclassers should not call * this method. * </p> * * @param element * @return the corresponding widget, or <code>null</code> if none */ protected abstract Widget doFindItem(Object element); /** * Copies the attributes of the given element into the given SWT item. The * element map is updated according to the value of <code>fullMap</code>. * If <code>fullMap</code> is <code>true</code> then the current mapping * from element to widgets is removed and the new mapping is added. If * full map is <code>false</code> then only the new map gets installed. * Installing only the new map is necessary in cases where only the order of * elements changes but not the set of elements. * <p> * This method is internal to the framework; subclassers should not call * this method. * </p> * * @param item * @param element element * @param fullMap * <code>true</code> if mappings are added and removed, and * <code>false</code> if only the new map gets installed */ protected abstract void doUpdateItem(Widget item, Object element, boolean fullMap); /** * Compares two elements for equality. Uses the element comparer if one has * been set, otherwise uses the default <code>equals</code> method on the * elements themselves. * * @param elementA * the first element * @param elementB * the second element * @return whether elementA is equal to elementB */ protected boolean equals(Object elementA, Object elementB) { if (comparer == null) { return elementA == null ? elementB == null : elementA.equals(elementB); } else { return elementA == null ? elementB == null : comparer.equals(elementA, elementB); } } /** * Returns the result of running the given elements through the filters. * * @param elements * the elements to filter * @return only the elements which all filters accept */ protected Object[] filter(Object[] elements) { if (filters != null) { ArrayList filtered = new ArrayList(elements.length); Object root = getRoot(); for (Object element : elements) { boolean add = true; for (int j = 0; j < filters.size(); j++) { add = filters.get(j).select(this, root, element); if (!add) { break; } } if (add) { filtered.add(element); } else { if (associateListener != null) associateListener.filteredOut(element); } } return filtered.toArray(); } return elements; } /** * Finds the widget which represents the given element. * <p> * The default implementation of this method tries first to find the widget * for the given element assuming that it is the viewer's input; this is * done by calling <code>doFindInputItem</code>. If it is not found * there, it is looked up in the internal element map provided that this * feature has been enabled. If the element map is disabled, the widget is * found via <code>doFindInputItem</code>. * </p> * * @param element * the element * @return the corresponding widget, or <code>null</code> if none */ protected final Widget findItem(Object element) { Widget[] result = findItems(element); return result.length == 0 ? null : result[0]; } /** * Finds the widgets which represent the given element. The returned array * must not be changed by clients; it might change upon calling other * methods on this viewer. * <p> * This method was introduced to support multiple equal elements in a viewer * (@see {@link AbstractTreeViewer}). Multiple equal elements are only * supported if the element map is enabled by calling * {@link #setUseHashlookup(boolean)} and passing <code>true</code>. * </p> * <p> * The default implementation of this method tries first to find the widget * for the given element assuming that it is the viewer's input; this is * done by calling <code>doFindInputItem</code>. If it is not found * there, the widgets are looked up in the internal element map provided * that this feature has been enabled. If the element map is disabled, the * widget is found via <code>doFindItem</code>. * </p> * * @param element * the element * @return the corresponding widgets * * @since 3.2 */ protected final Widget[] findItems(Object element) { Widget result = doFindInputItem(element); if (result != null) { return new Widget[] { result }; } // if we have an element map use it, otherwise search for the item. if (usingElementMap()) { Object widgetOrWidgets = elementMap.get(element); if (widgetOrWidgets==null) { return NO_WIDGETS; } else if (widgetOrWidgets instanceof Widget) { return new Widget[] {(Widget) widgetOrWidgets}; } else { return (Widget[])widgetOrWidgets; } } result = doFindItem(element); return result == null ? NO_WIDGETS : new Widget[] { result }; } /** * Notifies any double-click listeners that a double-click has been * received. Only listeners registered at the time this method is called are * notified. * * @param event * a double-click event * * @see IDoubleClickListener#doubleClick */ protected void fireDoubleClick(final DoubleClickEvent event) { for (IDoubleClickListener l : doubleClickListeners) { SafeRunnable.run(new SafeRunnable() { @Override public void run() { l.doubleClick(event); } }); } } /** * Notifies any open event listeners that a open event has been received. * Only listeners registered at the time this method is called are notified. * * @param event * a double-click event * * @see IOpenListener#open(OpenEvent) */ protected void fireOpen(final OpenEvent event) { for (IOpenListener l : openListeners) { SafeRunnable.run(new SafeRunnable() { @Override public void run() { l.open(event); } }); } } /** * Notifies any post selection listeners that a post selection event has * been received. Only listeners registered at the time this method is * called are notified. * * @param event * a selection changed event * * @see #addPostSelectionChangedListener(ISelectionChangedListener) */ protected void firePostSelectionChanged(final SelectionChangedEvent event) { for (ISelectionChangedListener l : postSelectionChangedListeners) { SafeRunnable.run(new SafeRunnable() { @Override public void run() { l.selectionChanged(event); } }); } } /** * Returns the comparer to use for comparing elements, or * <code>null</code> if none has been set. If specified, * the viewer uses this to compare and hash elements rather * than the elements' own equals and hashCode methods. * * @return the comparer to use for comparing elements or * <code>null</code> */ public IElementComparer getComparer() { return comparer; } /** * Returns the filtered array of children of the given element. The * resulting array must not be modified, as it may come directly from the * model's internal state. * * @param parent * the parent element * @return a filtered array of child elements */ protected Object[] getFilteredChildren(Object parent) { Object[] result = getRawChildren(parent); if (filters != null) { for (Object element : filters) { ViewerFilter f = (ViewerFilter) element; Object[] filteredResult = f.filter(this, parent, result); if (associateListener != null && filteredResult.length != result.length) { notifyFilteredOut(result, filteredResult); } result = filteredResult; } } return result; } /** * Notifies an AssociateListener of the elements that have been filtered out. * * @param rawResult * @param filteredResult */ private void notifyFilteredOut(Object[] rawResult, Object[] filteredResult) { int rawIndex = 0; int filteredIndex = 0; for (; filteredIndex < filteredResult.length; ) { if (rawResult[rawIndex] != filteredResult[filteredIndex]) { associateListener.filteredOut(rawResult[rawIndex++]); continue; } rawIndex++; filteredIndex++; } for (; rawIndex < rawResult.length; rawIndex++) { associateListener.filteredOut(rawResult[rawIndex]); } } /** * Returns this viewer's filters. * * @return an array of viewer filters * @see StructuredViewer#setFilters(ViewerFilter...) */ public ViewerFilter[] getFilters() { if (filters == null) { return new ViewerFilter[0]; } ViewerFilter[] result = new ViewerFilter[filters.size()]; filters.toArray(result); return result; } /** * Returns the item at the given display-relative coordinates, or * <code>null</code> if there is no item at that location or * the underlying SWT-Control is not made up of {@link Item} * (e.g {@link ListViewer}) * <p> * The default implementation of this method returns <code>null</code>. * </p> * * @param x * horizontal coordinate * @param y * vertical coordinate * @return the item, or <code>null</code> if there is no item at the given * coordinates * @deprecated This method is deprecated in 3.3 in favor of {@link ColumnViewer#getItemAt(org.eclipse.swt.graphics.Point)}. * Viewers who are not subclasses of {@link ColumnViewer} should consider using a * widget relative implementation like {@link ColumnViewer#getItemAt(org.eclipse.swt.graphics.Point)}. * */ @Deprecated protected Item getItem(int x, int y) { return null; } /** * Returns the children of the given parent without sorting and filtering * them. The resulting array must not be modified, as it may come directly * from the model's internal state. * <p> * Returns an empty array if the given parent is <code>null</code>. * </p> * * @param parent * the parent element * @return the child elements */ protected Object[] getRawChildren(Object parent) { Object[] result = null; if (parent != null) { IStructuredContentProvider cp = (IStructuredContentProvider) getContentProvider(); if (cp != null) { result = cp.getElements(parent); assertElementsNotNull(result); } } return (result != null) ? result : new Object[0]; } /** * Returns the root element. * <p> * The default implementation of this framework method forwards to * <code>getInput</code>. Override if the root element is different from * the viewer's input element. * </p> * * @return the root element, or <code>null</code> if none */ protected Object getRoot() { return getInput(); } /** * The <code>StructuredViewer</code> implementation of this method returns * the result as an <code>IStructuredSelection</code>. * <p> * Call {@link #getStructuredSelection()} instead to get an instance of * <code>IStructuredSelection</code> directly. * </p> * Subclasses do not typically override this method, but implement * <code>getSelectionFromWidget(List)</code> instead. If they override this * method, they should return an <code>IStructuredSelection</code> as well. * * @return ISelection */ @Override public ISelection getSelection() { Control control = getControl(); if (control == null || control.isDisposed()) { return StructuredSelection.EMPTY; } List list = getSelectionFromWidget(); return new StructuredSelection(list, comparer); } /** * Returns the <code>IStructuredSelection</code> of this viewer. * <p> * Subclasses whose {@link #getSelection()} specifies to return a more * specific type should also override this method and return that type. * </p> * * @return IStructuredSelection * @throws ClassCastException * if the selection of the viewer is not an instance of * IStructuredSelection * @since 3.11 */ public IStructuredSelection getStructuredSelection() throws ClassCastException { ISelection selection = getSelection(); if (selection instanceof IStructuredSelection) { return (IStructuredSelection) selection; } throw new ClassCastException("StructuredViewer should return an instance of IStructuredSelection from its getSelection() method."); //$NON-NLS-1$ } /** * Retrieves the selection, as a <code>List</code>, from the underlying * widget. * * @return the list of selected elements */ protected abstract List getSelectionFromWidget(); /** * Returns the sorted and filtered set of children of the given element. The * resulting array must not be modified, as it may come directly from the * model's internal state. * * @param parent * the parent element * @return a sorted and filtered array of child elements */ protected Object[] getSortedChildren(Object parent) { Object[] result = getFilteredChildren(parent); if (sorter != null) { // be sure we're not modifying the original array from the model result = result.clone(); sorter.sort(this, result); } return result; } /** * Returns this viewer's sorter, or <code>null</code> if it does not have * one. If this viewer has a comparator that was set via * <code>setComparator(ViewerComparator)</code> then this method will return * <code>null</code> if the comparator is not an instance of ViewerSorter. * <p> * It is recommended to use <code>getComparator()</code> instead. * </p> * * @return a viewer sorter, or <code>null</code> if none or if the comparator is * not an instance of ViewerSorter */ public ViewerSorter getSorter() { if (sorter instanceof ViewerSorter) return (ViewerSorter)sorter; return null; } /** * Return this viewer's comparator used to sort elements. * This method should be used instead of <code>getSorter()</code>. * * @return a viewer comparator, or <code>null</code> if none * * @since 3.2 */ public ViewerComparator getComparator(){ return sorter; } /** * Handles a double-click select event from the widget. * <p> * This method is internal to the framework; subclassers should not call * this method. * </p> * * @param event * the SWT selection event */ protected void handleDoubleSelect(SelectionEvent event) { // This method is reimplemented in AbstractTreeViewer to fix bug 108102. // handle case where an earlier selection listener disposed the control. Control control = getControl(); if (control != null && !control.isDisposed()) { // If the double-clicked element can be obtained from the event, use it // otherwise get it from the control. Some controls like List do // not have the notion of item. // For details, see bug 90161 [Navigator] DefaultSelecting folders shouldn't always expand first one ISelection selection; if (event.item != null && event.item.getData() != null) { selection = new StructuredSelection(event.item.getData()); } else { selection = getSelection(); updateSelection(selection); } fireDoubleClick(new DoubleClickEvent(this, selection)); } } /** * Handles an open event from the OpenStrategy. * <p> * This method is internal to the framework; subclassers should not call * this method. * </p> * * @param event * the SWT selection event */ protected void handleOpen(SelectionEvent event) { Control control = getControl(); if (control != null && !control.isDisposed()) { ISelection selection = getSelection(); fireOpen(new OpenEvent(this, selection)); } } /** * Handles an invalid selection. * <p> * This framework method is called if a model change picked up by a viewer * results in an invalid selection. For instance if an element contained in * the selection has been removed from the viewer, the viewer is free to * either remove the element from the selection or to pick another element * as its new selection. The default implementation of this method calls * <code>updateSelection</code>. Subclasses may override it to implement * a different strategy for picking a new selection when the old selection * becomes invalid. * </p> * * @param invalidSelection * the selection before the viewer was updated * @param newSelection * the selection after the update, or <code>null</code> if none */ protected void handleInvalidSelection(ISelection invalidSelection, ISelection newSelection) { updateSelection(newSelection); SelectionChangedEvent event = new SelectionChangedEvent(this, newSelection); firePostSelectionChanged(event); } /** * The <code>StructuredViewer</code> implementation of this * <code>ContentViewer</code> method calls <code>update</code> if the * event specifies that the label of a given element has changed, otherwise * it calls super. Subclasses may reimplement or extend. * </p> * @param event the event that generated this update */ @Override protected void handleLabelProviderChanged(LabelProviderChangedEvent event) { Object[] elements = event.getElements(); if (elements != null) { update(elements, null); } else { super.handleLabelProviderChanged(event); } } /** * Handles a select event from the widget. * <p> * This method is internal to the framework; subclassers should not call * this method. * </p> * * @param event * the SWT selection event */ protected void handleSelect(SelectionEvent event) { // handle case where an earlier selection listener disposed the control. Control control = getControl(); if (control != null && !control.isDisposed()) { updateSelection(getSelection()); } } /** * Handles a post select event from the widget. * <p> * This method is internal to the framework; subclassers should not call * this method. * </p> * * @param e the SWT selection event */ protected void handlePostSelect(SelectionEvent e) { SelectionChangedEvent event = new SelectionChangedEvent(this, getSelection()); firePostSelectionChanged(event); } @Override protected void hookControl(Control control) { super.hookControl(control); OpenStrategy handler = new OpenStrategy(control); handler.addSelectionListener(new SelectionListener() { @Override public void widgetSelected(SelectionEvent e) { // On Windows, selection events may happen during a refresh. // Ignore these events if we are currently in preservingSelection(). // See bug 184441. if (!inChange) { handleSelect(e); } } @Override public void widgetDefaultSelected(SelectionEvent e) { handleDoubleSelect(e); } }); handler.addPostSelectionListener(widgetSelectedAdapter(e -> handlePostSelect(e))); handler.addOpenListener(StructuredViewer.this::handleOpen); } /** * Returns whether this viewer has any filters. * @return boolean */ protected boolean hasFilters() { return filters != null && filters.size() > 0; } /** * Refreshes this viewer starting at the given element. * * @param element * the element */ protected abstract void internalRefresh(Object element); /** * Refreshes this viewer starting at the given element. Labels are updated * as described in <code>refresh(boolean updateLabels)</code>. * <p> * The default implementation simply calls * <code>internalRefresh(element)</code>, ignoring * <code>updateLabels</code>. * <p> * If this method is overridden to do the actual refresh, then * <code>internalRefresh(Object element)</code> should simply call * <code>internalRefresh(element, true)</code>. * * @param element * the element * @param updateLabels * <code>true</code> to update labels for existing elements, * <code>false</code> to only update labels as needed, assuming * that labels for existing elements are unchanged. * * @since 2.0 */ protected void internalRefresh(Object element, boolean updateLabels) { internalRefresh(element); } /** * Adds the element item pair to the element map. * <p> * This method is internal to the framework; subclassers should not call * this method. * </p> * * @param element * the element * @param item * the corresponding widget */ protected void mapElement(Object element, Widget item) { if (elementMap != null) { Object widgetOrWidgets = elementMap.get(element); if (widgetOrWidgets == null) { elementMap.put(element, item); } else if (widgetOrWidgets instanceof Widget) { if (widgetOrWidgets != item) { elementMap.put(element, new Widget[] { (Widget) widgetOrWidgets, item }); } } else { Widget[] widgets = (Widget[]) widgetOrWidgets; int indexOfItem = Arrays.asList(widgets).indexOf(item); if (indexOfItem == -1) { int length = widgets.length; System.arraycopy(widgets, 0, widgets = new Widget[length + 1], 0, length); widgets[length] = item; elementMap.put(element, widgets); } } } } /** * Determines whether a change to the given property of the given element * would require refiltering and/or resorting. * <p> * This method is internal to the framework; subclassers should not call * this method. * </p> * * @param element * the element * @param property * the property * @return <code>true</code> if refiltering is required, and * <code>false</code> otherwise */ protected boolean needsRefilter(Object element, String property) { if (sorter != null && sorter.isSorterProperty(element, property)) { return true; } if (filters != null) { for (int i = 0, n = filters.size(); i < n; ++i) { ViewerFilter filter = filters.get(i); if (filter.isFilterProperty(element, property)) { return true; } } } return false; } /** * Returns a new hashtable using the given capacity and this viewer's element comparer. * * @param capacity the initial capacity of the hashtable * @return a new hashtable * * @since 3.0 */ CustomHashtable newHashtable(int capacity) { return new CustomHashtable(capacity, getComparer()); } /** * Attempts to preserves the current selection across a run of the given * code. This method should not preserve the selection if * {link #getPreserveSelection()} returns false. * <p> * The default implementation of this method: * <ul> * <li>discovers the old selection (via <code>getSelection</code>)</li> * <li>runs the given runnable</li> * <li>attempts to restore the old selection (using * <code>setSelectionToWidget</code></li> * <li>rediscovers the resulting selection (via <code>getSelection</code>)</li> * <li>calls <code>handleInvalidSelection</code> if the resulting selection * is different from the old selection</li> * </ul> * </p> * * @param updateCode * the code to run * * see #getPreserveSelection() */ protected void preservingSelection(Runnable updateCode) { preservingSelection(updateCode, false); } /** * Attempts to preserves the current selection across a run of the given * code, with a best effort to avoid scrolling if <code>reveal</code> is * false, or to reveal the selection if <code>reveal</code> is true. * <p> * The default implementation of this method: * <ul> * <li>discovers the old selection (via <code>getSelection</code>)</li> * <li>runs the given runnable</li> * <li>attempts to restore the old selection (using * <code>setSelectionToWidget</code></li> * <li>rediscovers the resulting selection (via <code>getSelection</code>)</li> * <li>calls <code>handleInvalidSelection</code> if the selection did not * take</li> * </ul> * </p> * * @param updateCode * the code to run * @param reveal * <code>true</code> if the selection should be made visible, * <code>false</code> if scrolling should be avoided * @since 3.3 */ void preservingSelection(Runnable updateCode, boolean reveal) { if (!preserveSelection) { return; } ISelection oldSelection = null; try { // preserve selection oldSelection = getSelection(); inChange = restoreSelection = true; // perform the update updateCode.run(); } finally { inChange = false; // restore selection if (restoreSelection) { setSelectionToWidget(oldSelection, reveal); } // send out notification if old and new differ ISelection newSelection = getSelection(); if (!newSelection.equals(oldSelection)) { handleInvalidSelection(oldSelection, newSelection); } } } @Override public void refresh() { refresh(getRoot()); } /** * Refreshes this viewer with information freshly obtained from this * viewer's model. If <code>updateLabels</code> is <code>true</code> * then labels for otherwise unaffected elements are updated as well. * Otherwise, it assumes labels for existing elements are unchanged, and * labels are only obtained as needed (for example, for new elements). * <p> * Calling <code>refresh(true)</code> has the same effect as * <code>refresh()</code>. * <p> * Note that the implementation may still obtain labels for existing * elements even if <code>updateLabels</code> is false. The intent is * simply to allow optimization where possible. * * @param updateLabels * <code>true</code> to update labels for existing elements, * <code>false</code> to only update labels as needed, assuming * that labels for existing elements are unchanged. * * @since 2.0 */ public void refresh(boolean updateLabels) { refresh(getRoot(), updateLabels); } /** * Refreshes this viewer starting with the given element. * <p> * Unlike the <code>update</code> methods, this handles structural changes * to the given element (e.g. addition or removal of children). If only the * given element needs updating, it is more efficient to use the * <code>update</code> methods. * </p> * * @param element * the element */ public void refresh(final Object element) { preservingSelection(() -> internalRefresh(element)); } /** * Refreshes this viewer starting with the given element. Labels are updated * as described in <code>refresh(boolean updateLabels)</code>. * <p> * Unlike the <code>update</code> methods, this handles structural changes * to the given element (e.g. addition or removal of children). If only the * given element needs updating, it is more efficient to use the * <code>update</code> methods. * </p> * * @param element * the element * @param updateLabels * <code>true</code> to update labels for existing elements, * <code>false</code> to only update labels as needed, assuming * that labels for existing elements are unchanged. * * @since 2.0 */ public void refresh(final Object element, final boolean updateLabels) { preservingSelection(() -> internalRefresh(element, updateLabels)); } /** * * Refreshes the given item with the given element. Calls * <code>doUpdateItem(..., false)</code>. * <p> * This method is internal to the framework; subclassers should not call * this method. * </p> * @param widget * the widget * @param element * the element */ protected final void refreshItem(Widget widget, Object element) { SafeRunnable.run(new UpdateItemSafeRunnable(widget, element, true)); } /** * Removes the given open listener from this viewer. Has no effect if an * identical listener is not registered. * * @param listener * an open listener */ public void removeOpenListener(IOpenListener listener) { openListeners.remove(listener); } @Override public void removePostSelectionChangedListener(ISelectionChangedListener listener) { postSelectionChangedListeners.remove(listener); } /** * Removes the given double-click listener from this viewer. Has no effect * if an identical listener is not registered. * * @param listener * a double-click listener */ public void removeDoubleClickListener(IDoubleClickListener listener) { doubleClickListeners.remove(listener); } /** * Removes the given filter from this viewer, and triggers refiltering and * resorting of the elements if required. Has no effect if the identical * filter is not registered. If you want to remove more than one filter * consider using {@link StructuredViewer#setFilters(ViewerFilter...)}. * * @param filter * a viewer filter * @see StructuredViewer#setFilters(ViewerFilter...) */ public void removeFilter(ViewerFilter filter) { Assert.isNotNull(filter); if (filters != null) { // Note: can't use List.remove(Object). Use identity comparison // instead. for (Iterator i = filters.iterator(); i.hasNext();) { Object o = i.next(); if (o == filter) { i.remove(); refresh(); if (filters.size() == 0) { filters = null; } return; } } } } void setAssociateListener(StructuredViewerInternals.AssociateListener l) { associateListener = l; } /** * Sets the filters, replacing any previous filters, and triggers * refiltering and resorting of the elements. * * @param filters * an varargs of viewer filters * @since 3.3 */ public void setFilters(ViewerFilter... filters) { if (filters.length == 0) { resetFilters(); } else { this.filters = new ArrayList<>(Arrays.asList(filters)); refresh(); } } /** * Discards this viewer's filters and triggers refiltering and resorting of * the elements. */ public void resetFilters() { if (filters != null) { filters = null; refresh(); } } /** * Ensures that the given element is visible, scrolling the viewer if * necessary. The selection is unchanged. * * @param element * the element to reveal */ public abstract void reveal(Object element); /** * {@inheritDoc} * <p> * The <code>StructuredViewer</code> implementation of this method calls * {@link #assertContentProviderType(IContentProvider)} to validate the * content provider. For a <code>StructuredViewer</code>, the content * provider must implement {@link IStructuredContentProvider}. * </p> */ @Override public void setContentProvider(IContentProvider provider) { assertContentProviderType(provider); super.setContentProvider(provider); } /** * Assert that the content provider is of one of the * supported types. * @param provider */ protected void assertContentProviderType(IContentProvider provider) { Assert.isTrue(provider instanceof IStructuredContentProvider); } @Override public final void setInput(Object input) { Control control = getControl(); if (control == null || control.isDisposed()) { throw new IllegalStateException( "Need an underlying widget to be able to set the input." + //$NON-NLS-1$ "(Has the widget been disposed?)"); //$NON-NLS-1$ } try { // fInChange= true; unmapAllElements(); super.setInput(input); } finally { // fInChange= false; } } @Override public void setSelection(ISelection selection, boolean reveal) { /** * <p> * If the new selection differs from the current selection the hook * <code>updateSelection</code> is called. * </p> * <p> * If <code>setSelection</code> is called from within * <code>preserveSelection</code>, the call to * <code>updateSelection</code> is delayed until the end of * <code>preserveSelection</code>. * </p> * <p> * Subclasses do not typically override this method, but implement * <code>setSelectionToWidget</code> instead. * </p> */ Control control = getControl(); if (control == null || control.isDisposed()) { return; } if (!inChange) { setSelectionToWidget(selection, reveal); ISelection sel = getSelection(); updateSelection(sel); firePostSelectionChanged(new SelectionChangedEvent(this, sel)); } else { restoreSelection = false; setSelectionToWidget(selection, reveal); } } /** * Parlays the given list of selected elements into selections on this * viewer's control. * <p> * Subclasses should override to set their selection based on the given list * of elements. * </p> * * @param l * list of selected elements (element type: <code>Object</code>) * or <code>null</code> if the selection is to be cleared * @param reveal * <code>true</code> if the selection is to be made visible, * and <code>false</code> otherwise */ protected abstract void setSelectionToWidget(List l, boolean reveal); /** * Converts the selection to a <code>List</code> and calls * <code>setSelectionToWidget(List, boolean)</code>. The selection is * expected to be an <code>IStructuredSelection</code> of elements. If * not, the selection is cleared. * <p> * Subclasses do not typically override this method, but implement * <code>setSelectionToWidget(List, boolean)</code> instead. * * @param selection * an IStructuredSelection of elements * @param reveal * <code>true</code> to reveal the first element in the * selection, or <code>false</code> otherwise */ protected void setSelectionToWidget(ISelection selection, boolean reveal) { if (selection instanceof IStructuredSelection) { setSelectionToWidget(((IStructuredSelection) selection).toList(), reveal); } else { setSelectionToWidget((List) null, reveal); } } /** * Sets this viewer's sorter and triggers refiltering and resorting of this * viewer's element. Passing <code>null</code> turns sorting off. * <p> * * @deprecated use <code>setComparator()</code> instead. * </p> * * @param sorter * a viewer sorter, or <code>null</code> if none */ @Deprecated public void setSorter(ViewerSorter sorter) { if (this.sorter != sorter) { this.sorter = sorter; refresh(); } } /** * Sets this viewer's comparator to be used for sorting elements, and triggers refiltering and * resorting of this viewer's element. <code>null</code> turns sorting off. * To get the viewer's comparator, call <code>getComparator()</code>. * <p> * IMPORTANT: This method was introduced in 3.2. If a reference to this viewer object * is passed to clients who call <code>getSorter()</code>, null may be returned from * from that method even though the viewer is sorting its elements using the * viewer's comparator. * </p> * * @param comparator a viewer comparator, or <code>null</code> if none * * @since 3.2 */ public void setComparator(ViewerComparator comparator){ if (this.sorter != comparator){ this.sorter = comparator; refresh(); } } /** * Configures whether this structured viewer uses an internal hash table to * speeds up the mapping between elements and SWT items. This must be called * before the viewer is given an input (via <code>setInput</code>). * * @param enable * <code>true</code> to enable hash lookup, and * <code>false</code> to disable it */ public void setUseHashlookup(boolean enable) { Assert.isTrue(getInput() == null, "Can only enable the hash look up before input has been set");//$NON-NLS-1$ if (enable) { elementMap = newHashtable(CustomHashtable.DEFAULT_CAPACITY); } else { elementMap = null; } } /** * Sets the comparer to use for comparing elements, or <code>null</code> * to use the default <code>equals</code> and <code>hashCode</code> * methods on the elements themselves. * * @param comparer * the comparer to use for comparing elements or * <code>null</code> */ public void setComparer(IElementComparer comparer) { this.comparer = comparer; if (elementMap != null) { elementMap = new CustomHashtable(elementMap, comparer); } } /** * NON-API - to be removed - see bug 200214 * Enable or disable the preserve selection behavior of this viewer. The * default is that the viewer attempts to preserve the selection across * update operations. This is an advanced option, to support clients that * manage the selection without relying on the viewer, or clients running * into performance problems when using viewers and {@link SWT#VIRTUAL}. * Note that this method has been introduced in 3.5 and that trying to * disable the selection behavior may not be possible for all subclasses of * <code>StructuredViewer</code>, or may cause program errors. This method * is supported for {@link TableViewer}, {@link TreeViewer}, * {@link ListViewer}, {@link CheckboxTableViewer}, * {@link CheckboxTreeViewer}, and {@link ComboViewer}, but no promises are * made for other subclasses of StructuredViewer, or subclasses of the * listed viewer classes. * * @param preserve * <code>true</code> if selection should be preserved, * <code>false</code> otherwise */ void setPreserveSelection(boolean preserve) { this.preserveSelection = preserve; } /** * NON-API - to be removed - see bug 200214 * Returns whether an attempt should be made to preserve selection across * update operations. To be used by subclasses that override * {@link #preservingSelection(Runnable)}. * * @return <code>true</code> if selection should be preserved, * <code>false</code> otherwise */ boolean getPreserveSelection() { return this.preserveSelection; } /** * Hook for testing. * @param element * @return Widget */ public Widget testFindItem(Object element) { return findItem(element); } /** * Hook for testing. * @param element * @return Widget[] * @since 3.2 */ public Widget[] testFindItems(Object element) { return findItems(element); } /** * Removes all elements from the map. * <p> * This method is internal to the framework; subclassers should not call * this method. * </p> */ protected void unmapAllElements() { if (elementMap != null) { elementMap = newHashtable(CustomHashtable.DEFAULT_CAPACITY); } } /** * Removes the given element from the internal element to widget map. Does * nothing if mapping is disabled. If mapping is enabled, the given element * must be present. * <p> * This method is internal to the framework; subclassers should not call * this method. * </p> * * @param element * the element */ protected void unmapElement(Object element) { if (elementMap != null) { elementMap.remove(element); } } /** * Removes the given association from the internal element to widget map. * Does nothing if mapping is disabled, or if the given element does not map * to the given item. * <p> * This method is internal to the framework; subclassers should not call * this method. * </p> * * @param element * the element * @param item the item to unmap * @since 2.0 */ protected void unmapElement(Object element, Widget item) { // double-check that the element actually maps to the given item before // unmapping it if (elementMap != null) { Object widgetOrWidgets = elementMap.get(element); if (widgetOrWidgets == null) { // item was not mapped, return return; } else if (widgetOrWidgets instanceof Widget) { if (item == widgetOrWidgets) { elementMap.remove(element); } } else { Widget[] widgets = (Widget[]) widgetOrWidgets; int indexOfItem = Arrays.asList(widgets).indexOf(item); if (indexOfItem == -1) { return; } int length = widgets.length; if (indexOfItem == 0) { if(length == 1) { elementMap.remove(element); } else { Widget[] updatedWidgets = new Widget[length - 1]; System.arraycopy(widgets, 1, updatedWidgets, 0, length -1 ); elementMap.put(element, updatedWidgets); } } else { Widget[] updatedWidgets = new Widget[length - 1]; System.arraycopy(widgets, 0, updatedWidgets, 0, indexOfItem); System.arraycopy(widgets, indexOfItem + 1, updatedWidgets, indexOfItem, length - indexOfItem - 1); elementMap.put(element, updatedWidgets); } } } } // flag to indicate that a full refresh took place. See bug 102440. private boolean refreshOccurred; /** * Updates the given elements' presentation when one or more of their * properties change. Only the given elements are updated. * <p> * This does not handle structural changes (e.g. addition or removal of * elements), and does not update any other related elements (e.g. child * elements). To handle structural changes, use the <code>refresh</code> * methods instead. * </p> * <p> * This should be called when an element has changed in the model, in order * to have the viewer accurately reflect the model. This method only affects * the viewer, not the model. * </p> * <p> * Specifying which properties are affected may allow the viewer to optimize * the update. For example, if the label provider is not affected by changes * to any of these properties, an update may not actually be required. * Specifying <code>properties</code> as <code>null</code> forces a full * update of the given elements. * </p> * <p> * If the viewer has a sorter which is affected by a change to one of the * properties, the elements' positions are updated to maintain the sort * order. Note that resorting may not happen if <code>properties</code> * is <code>null</code>. * </p> * <p> * If the viewer has a filter which is affected by a change to one of the * properties, elements may appear or disappear if the change affects * whether or not they are filtered out. Note that resorting may not happen * if <code>properties</code> is <code>null</code>. * </p> * * @param elements * the elements * @param properties * the properties that have changed, or <code>null</code> to * indicate unknown */ public void update(Object[] elements, String[] properties) { boolean previousValue = refreshOccurred; refreshOccurred = false; try { for (Object element : elements) { update(element, properties); if (refreshOccurred) { return; } } } finally { refreshOccurred = previousValue; } } /** * Updates the given element's presentation when one or more of its * properties changes. Only the given element is updated. * <p> * This does not handle structural changes (e.g. addition or removal of * elements), and does not update any other related elements (e.g. child * elements). To handle structural changes, use the <code>refresh</code> * methods instead. * </p> * <p> * This should be called when an element has changed in the model, in order * to have the viewer accurately reflect the model. This method only affects * the viewer, not the model. * </p> * <p> * Specifying which properties are affected may allow the viewer to optimize * the update. For example, if the label provider is not affected by changes * to any of these properties, an update may not actually be required. * Specifying <code>properties</code> as <code>null</code> forces a full * update of the element. * </p> * <p> * If the viewer has a sorter which is affected by a change to one of the * properties, the element's position is updated to maintain the sort order. * Note that resorting may not happen if <code>properties</code> is * <code>null</code>. * </p> * <p> * If the viewer has a filter which is affected by a change to one of the * properties, the element may appear or disappear if the change affects * whether or not the element is filtered out. Note that filtering may not * happen if <code>properties</code> is <code>null</code>. * </p> * * @param element * the element * @param properties * the properties that have changed, or <code>null</code> to * indicate unknown */ public void update(Object element, String[] properties) { Assert.isNotNull(element); Widget[] items = findItems(element); boolean mayExitEarly = !refreshOccurred; for (Widget item : items) { internalUpdate(item, element, properties); if (mayExitEarly && refreshOccurred) { // detected a change from refreshOccurred==false to refreshOccurred==true return; } } } /** * Updates the given element's presentation when one or more of its * properties changes. Only the given element is updated. * <p> * EXPERIMENTAL. Not to be used except by JDT. * This method was added to support JDT's explorations * into grouping by working sets, which requires viewers to support multiple * equal elements. See bug 76482 for more details. This support will * likely be removed in Eclipse 3.3 in favor of proper support for * multiple equal elements (which was implemented for AbtractTreeViewer in 3.2). * </p> * @param widget * the widget for the element * @param element * the element * @param properties * the properties that have changed, or <code>null</code> to * indicate unknown */ protected void internalUpdate(Widget widget, Object element, String[] properties) { boolean needsRefilter = false; if (properties != null) { for (String property : properties) { needsRefilter = needsRefilter(element, property); if (needsRefilter) { break; } } } if (needsRefilter) { preservingSelection(() -> { internalRefresh(getRoot()); refreshOccurred = true; }); return; } boolean needsUpdate; if (properties == null) { needsUpdate = true; } else { needsUpdate = false; IBaseLabelProvider labelProvider = getLabelProvider(); for (String property : properties) { needsUpdate = labelProvider.isLabelProperty(element, property); if (needsUpdate) { break; } } } if (needsUpdate) { updateItem(widget, element); } } /** * Copies attributes of the given element into the given widget. * <p> * This method is internal to the framework; subclassers should not call * this method. Calls <code>doUpdateItem(widget, element, true)</code>. * </p> * * @param widget * the widget * @param element * the element */ protected final void updateItem(Widget widget, Object element) { SafeRunnable.run(new UpdateItemSafeRunnable(widget, element, true)); } /** * Updates the selection of this viewer. * <p> * This framework method should be called when the selection in the viewer * widget changes. * </p> * <p> * The default implementation of this method notifies all selection change * listeners recorded in an internal state variable. Overriding this method * is generally not required; however, if overriding in a subclass, * <code>super.updateSelection</code> must be invoked. * </p> * * @param selection * the selection, or <code>null</code> if none */ protected void updateSelection(ISelection selection) { SelectionChangedEvent event = new SelectionChangedEvent(this, selection); fireSelectionChanged(event); } /** * Returns whether this structured viewer is configured to use an internal * map to speed up the mapping between elements and SWT items. * <p> * The default implementation of this framework method checks whether the * internal map has been initialized. * </p> * * @return <code>true</code> if the element map is enabled, and * <code>false</code> if disabled */ protected boolean usingElementMap() { return elementMap != null; } @Override public void setLabelProvider(IBaseLabelProvider labelProvider) { if (labelProvider instanceof IColorProvider || labelProvider instanceof IFontProvider) { colorAndFontCollector = new ColorAndFontCollectorWithProviders(labelProvider); } else { colorAndFontCollector = new ColorAndFontCollector(); } super.setLabelProvider(labelProvider); } /** * Build a label up for the element using the supplied label provider. * @param updateLabel The ViewerLabel to collect the result in * @param element The element being decorated. */ protected void buildLabel(ViewerLabel updateLabel, Object element){ if (getLabelProvider() instanceof IViewerLabelProvider) { IViewerLabelProvider itemProvider = (IViewerLabelProvider) getLabelProvider(); itemProvider.updateLabel(updateLabel, element); colorAndFontCollector.setUsedDecorators(); if(updateLabel.hasNewBackground()) { colorAndFontCollector.setBackground(updateLabel.getBackground()); } if(updateLabel.hasNewForeground()) { colorAndFontCollector.setForeground(updateLabel.getForeground()); } if(updateLabel.hasNewFont()) { colorAndFontCollector.setFont(updateLabel.getFont()); } return; } if(getLabelProvider() instanceof ILabelProvider){ ILabelProvider labelProvider = (ILabelProvider) getLabelProvider(); updateLabel.setText(labelProvider.getText(element)); updateLabel.setImage(labelProvider.getImage(element)); } } /** * Build a label up for the element using the supplied label provider. * @param updateLabel The ViewerLabel to collect the result in * @param element The element being decorated. * @param labelProvider ILabelProvider the labelProvider for the receiver. */ void buildLabel(ViewerLabel updateLabel, Object element,IViewerLabelProvider labelProvider){ labelProvider.updateLabel(updateLabel, element); colorAndFontCollector.setUsedDecorators(); if(updateLabel.hasNewBackground()) { colorAndFontCollector.setBackground(updateLabel.getBackground()); } if(updateLabel.hasNewForeground()) { colorAndFontCollector.setForeground(updateLabel.getForeground()); } if(updateLabel.hasNewFont()) { colorAndFontCollector.setFont(updateLabel.getFont()); } } /** * Build a label up for the element using the supplied label provider. * @param updateLabel The ViewerLabel to collect the result in * @param elementPath The path of the element being decorated. * @param labelProvider ILabelProvider the labelProvider for the receiver. */ void buildLabel(ViewerLabel updateLabel, TreePath elementPath,ITreePathLabelProvider labelProvider){ labelProvider.updateLabel(updateLabel, elementPath); colorAndFontCollector.setUsedDecorators(); if(updateLabel.hasNewBackground()) { colorAndFontCollector.setBackground(updateLabel.getBackground()); } if(updateLabel.hasNewForeground()) { colorAndFontCollector.setForeground(updateLabel.getForeground()); } if(updateLabel.hasNewFont()) { colorAndFontCollector.setFont(updateLabel.getFont()); } } /** * Build a label up for the element using the supplied label provider. * @param updateLabel The ViewerLabel to collect the result in * @param element The element being decorated. * @param labelProvider ILabelProvider the labelProvider for the receiver. */ void buildLabel(ViewerLabel updateLabel, Object element,ILabelProvider labelProvider){ updateLabel.setText(labelProvider.getText(element)); updateLabel.setImage(labelProvider.getImage(element)); } /** * Get the ColorAndFontCollector for the receiver. * @return ColorAndFontCollector * @since 3.1 */ protected ColorAndFontCollector getColorAndFontCollector() { return colorAndFontCollector; } @Override protected void handleDispose(DisposeEvent event) { super.handleDispose(event); sorter = null; comparer = null; if (filters != null) filters.clear(); elementMap = newHashtable(1); openListeners.clear(); doubleClickListeners.clear(); colorAndFontCollector.clear(); postSelectionChangedListeners.clear(); } }