0001: /*******************************************************************************
0002: * Copyright (c) 2000, 2007 IBM Corporation and others.
0003: * All rights reserved. This program and the accompanying materials
0004: * are made available under the terms of the Eclipse Public License v1.0
0005: * which accompanies this distribution, and is available at
0006: * http://www.eclipse.org/legal/epl-v10.html
0007: *
0008: * Contributors:
0009: * IBM Corporation - initial API and implementation
0010: * Tom Schindl - bug 151205
0011: *******************************************************************************/package org.eclipse.jface.viewers;
0012:
0013: import java.util.ArrayList;
0014: import java.util.Arrays;
0015: import java.util.Iterator;
0016: import java.util.List;
0017:
0018: import org.eclipse.core.runtime.ListenerList;
0019: import org.eclipse.core.runtime.Assert;
0020: import org.eclipse.jface.util.IOpenEventListener;
0021: import org.eclipse.jface.util.OpenStrategy;
0022: import org.eclipse.jface.util.SafeRunnable;
0023: import org.eclipse.swt.custom.TableTreeItem;
0024: import org.eclipse.swt.dnd.DragSource;
0025: import org.eclipse.swt.dnd.DragSourceListener;
0026: import org.eclipse.swt.dnd.DropTarget;
0027: import org.eclipse.swt.dnd.DropTargetListener;
0028: import org.eclipse.swt.dnd.Transfer;
0029: import org.eclipse.swt.events.DisposeEvent;
0030: import org.eclipse.swt.events.SelectionAdapter;
0031: import org.eclipse.swt.events.SelectionEvent;
0032: import org.eclipse.swt.events.SelectionListener;
0033: import org.eclipse.swt.graphics.Color;
0034: import org.eclipse.swt.graphics.Font;
0035: import org.eclipse.swt.widgets.Control;
0036: import org.eclipse.swt.widgets.Item;
0037: import org.eclipse.swt.widgets.TableItem;
0038: import org.eclipse.swt.widgets.TreeItem;
0039: import org.eclipse.swt.widgets.Widget;
0040:
0041: /**
0042: * Abstract base implementation for structure-oriented viewers (trees, lists,
0043: * tables). Supports custom sorting, filtering, and rendering.
0044: * <p>
0045: * Any number of viewer filters can be added to this viewer (using
0046: * <code>addFilter</code>). When the viewer receives an update, it asks each
0047: * of its filters if it is out of date, and refilters elements as required.
0048: * </p>
0049: *
0050: * @see ViewerFilter
0051: * @see ViewerComparator
0052: */
0053: public abstract class StructuredViewer extends ContentViewer implements
0054: IPostSelectionProvider {
0055:
0056: /**
0057: * A map from the viewer's model elements to SWT widgets. (key type:
0058: * <code>Object</code>, value type: <code>Widget</code>, or <code>Widget[]</code>).
0059: * <code>null</code> means that the element map is disabled.
0060: */
0061: private CustomHashtable elementMap;
0062:
0063: /**
0064: * The comparer to use for comparing elements, or <code>null</code> to use
0065: * the default <code>equals</code> and <code>hashCode</code> methods on
0066: * the element itself.
0067: */
0068: private IElementComparer comparer;
0069:
0070: /**
0071: * This viewer's comparator used for sorting. <code>null</code> means there is no comparator.
0072: */
0073: private ViewerComparator sorter;
0074:
0075: /**
0076: * This viewer's filters (element type: <code>ViewerFilter</code>).
0077: * <code>null</code> means there are no filters.
0078: */
0079: private List filters;
0080:
0081: /**
0082: * Indicates whether a selection change is in progress on this viewer.
0083: *
0084: * @see #setSelection(ISelection, boolean)
0085: */
0086: private boolean inChange;
0087:
0088: /**
0089: * Used while a selection change is in progress on this viewer to indicates
0090: * whether the selection should be restored.
0091: *
0092: * @see #setSelection(ISelection, boolean)
0093: */
0094: private boolean restoreSelection;
0095:
0096: /**
0097: * List of double-click state listeners (element type:
0098: * <code>IDoubleClickListener</code>).
0099: *
0100: * @see #fireDoubleClick
0101: */
0102: private ListenerList doubleClickListeners = new ListenerList();
0103:
0104: /**
0105: * List of open listeners (element type:
0106: * <code>ISelectionActivateListener</code>).
0107: *
0108: * @see #fireOpen
0109: */
0110: private ListenerList openListeners = new ListenerList();
0111:
0112: /**
0113: * List of post selection listeners (element type:
0114: * <code>ISelectionActivateListener</code>).
0115: *
0116: * @see #firePostSelectionChanged
0117: */
0118: private ListenerList postSelectionChangedListeners = new ListenerList();
0119:
0120: /**
0121: * The colorAndFontCollector is an object used by viewers that
0122: * support the IColorProvider, the IFontProvider and/or the
0123: * IViewerLabelProvider for color and font updates.
0124: * Initialize it to have no color or font providing
0125: * initially.
0126: * @since 3.1
0127: */
0128: private ColorAndFontCollector colorAndFontCollector = new ColorAndFontCollector();
0129:
0130: /**
0131: * Empty array of widgets.
0132: */
0133: private static Widget[] NO_WIDGETS = new Widget[0];
0134:
0135: /**
0136: * The ColorAndFontCollector is a helper class for viewers
0137: * that have color and font support ad optionally decorators.
0138: * @see IColorDecorator
0139: * @see IFontDecorator
0140: * @see IColorProvider
0141: * @see IFontProvider
0142: * @see IDecoration
0143: */
0144: protected class ColorAndFontCollectorWithProviders extends
0145: ColorAndFontCollector {
0146:
0147: IColorProvider colorProvider;
0148:
0149: IFontProvider fontProvider;
0150:
0151: /**
0152: * Create a new instance of the receiver using the supplied
0153: * label provider. If it is an IColorProvider or IFontProvider
0154: * set these values up.
0155: * @param provider IBaseLabelProvider
0156: * @see IColorProvider
0157: * @see IFontProvider
0158: */
0159: public ColorAndFontCollectorWithProviders(
0160: IBaseLabelProvider provider) {
0161: super ();
0162: if (provider instanceof IColorProvider) {
0163: colorProvider = (IColorProvider) provider;
0164: }
0165: if (provider instanceof IFontProvider) {
0166: fontProvider = (IFontProvider) provider;
0167: }
0168: }
0169:
0170: /* (non-Javadoc)
0171: * @see org.eclipse.jface.viewers.StructuredViewer.ColorAndFontManager#setFontsAndColors(java.lang.Object)
0172: */
0173: public void setFontsAndColors(Object element) {
0174:
0175: if (fontProvider != null) {
0176: if (font == null) {
0177: font = fontProvider.getFont(element);
0178: }
0179: }
0180:
0181: if (colorProvider == null) {
0182: return;
0183: }
0184: //Set the colors if they are not set yet
0185: if (background == null) {
0186: background = colorProvider.getBackground(element);
0187: }
0188:
0189: if (foreground == null) {
0190: foreground = colorProvider.getForeground(element);
0191: }
0192: }
0193:
0194: /**
0195: * Apply the fonts and colors to the control if
0196: * required.
0197: * @param control
0198: */
0199: public void applyFontsAndColors(TableItem control) {
0200:
0201: if (colorProvider == null) {
0202: if (usedDecorators) {
0203: //If there is no provider only apply set values
0204: if (background != null) {
0205: control.setBackground(background);
0206: }
0207:
0208: if (foreground != null) {
0209: control.setForeground(foreground);
0210: }
0211: }
0212: } else {
0213: //Always set the value if there is a provider
0214: control.setBackground(background);
0215: control.setForeground(foreground);
0216: }
0217:
0218: if (fontProvider == null) {
0219: if (usedDecorators && font != null) {
0220: control.setFont(font);
0221: }
0222: } else {
0223: control.setFont(font);
0224: }
0225:
0226: clear();
0227: }
0228:
0229: /* (non-Javadoc)
0230: * @see org.eclipse.jface.viewers.StructuredViewer.ColorAndFontManager#applyFontsAndColors(org.eclipse.swt.widgets.TreeItem)
0231: */
0232: public void applyFontsAndColors(TreeItem control) {
0233:
0234: if (colorProvider == null) {
0235: if (usedDecorators) {
0236: //If there is no provider only apply set values
0237: if (background != null) {
0238: control.setBackground(background);
0239: }
0240:
0241: if (foreground != null) {
0242: control.setForeground(foreground);
0243: }
0244: }
0245: } else {
0246: //Always set the value if there is a provider
0247: control.setBackground(background);
0248: control.setForeground(foreground);
0249: }
0250:
0251: if (fontProvider == null) {
0252: if (usedDecorators && font != null) {
0253: control.setFont(font);
0254: }
0255: } else {
0256: control.setFont(font);
0257: }
0258:
0259: clear();
0260: }
0261:
0262: /* (non-Javadoc)
0263: * @see org.eclipse.jface.viewers.StructuredViewer.ColorAndFontManager#applyFontsAndColors(org.eclipse.swt.custom.TableTreeItem)
0264: */
0265: public void applyFontsAndColors(TableTreeItem control) {
0266:
0267: if (colorProvider == null) {
0268: if (usedDecorators) {
0269: //If there is no provider only apply set values
0270: if (background != null) {
0271: control.setBackground(background);
0272: }
0273:
0274: if (foreground != null) {
0275: control.setForeground(foreground);
0276: }
0277: }
0278: } else {
0279: //Always set the value if there is a provider
0280: control.setBackground(background);
0281: control.setForeground(foreground);
0282: }
0283:
0284: if (fontProvider == null) {
0285: if (usedDecorators && font != null) {
0286: control.setFont(font);
0287: }
0288: } else {
0289: control.setFont(font);
0290: }
0291:
0292: clear();
0293: }
0294:
0295: }
0296:
0297: /**
0298: * The ColorAndFontCollector collects fonts and colors without a
0299: * a color or font provider.
0300: *
0301: */
0302: protected class ColorAndFontCollector {
0303:
0304: Color foreground = null;
0305:
0306: Color background = null;
0307:
0308: Font font = null;
0309:
0310: boolean usedDecorators = false;
0311:
0312: /**
0313: * Create a new instance of the receiver with
0314: * no color and font provider.
0315: */
0316: public ColorAndFontCollector() {
0317: super ();
0318: }
0319:
0320: /**
0321: * Clear all of the results.
0322: */
0323: public void clear() {
0324: foreground = null;
0325: background = null;
0326: font = null;
0327: usedDecorators = false;
0328: }
0329:
0330: /**
0331: * Set the initial fonts and colors for the element from the
0332: * content providers.
0333: * @param element Object
0334: */
0335: public void setFontsAndColors(Object element) {
0336: //Do nothing if there are no providers
0337: }
0338:
0339: /**
0340: * Set that decorators were applied.
0341: */
0342: public void setUsedDecorators() {
0343: this .usedDecorators = true;
0344: }
0345:
0346: /**
0347: * Apply the fonts and colors to the control if
0348: * required.
0349: * @param control
0350: */
0351: public void applyFontsAndColors(TableItem control) {
0352:
0353: if (usedDecorators) {
0354: //If there is no provider only apply set values
0355: if (background != null) {
0356: control.setBackground(background);
0357: }
0358:
0359: if (foreground != null) {
0360: control.setForeground(foreground);
0361: }
0362:
0363: if (font != null) {
0364: control.setFont(font);
0365: }
0366: }
0367: clear();
0368: }
0369:
0370: /**
0371: * Apply the fonts and colors to the control if
0372: * required.
0373: * @param control
0374: */
0375: public void applyFontsAndColors(TreeItem control) {
0376: if (usedDecorators) {
0377: //If there is no provider only apply set values
0378: if (background != null) {
0379: control.setBackground(background);
0380: }
0381:
0382: if (foreground != null) {
0383: control.setForeground(foreground);
0384: }
0385:
0386: if (font != null) {
0387: control.setFont(font);
0388: }
0389: }
0390: clear();
0391: }
0392:
0393: /**
0394: * Apply the fonts and colors to the control if
0395: * required.
0396: * @param control
0397: */
0398: public void applyFontsAndColors(TableTreeItem control) {
0399: if (usedDecorators) {
0400: //If there is no provider only apply set values
0401: if (background != null) {
0402: control.setBackground(background);
0403: }
0404:
0405: if (foreground != null) {
0406: control.setForeground(foreground);
0407: }
0408:
0409: if (font != null) {
0410: control.setFont(font);
0411: }
0412: }
0413: clear();
0414: }
0415:
0416: /**
0417: * Set the background color.
0418: * @param background
0419: */
0420: public void setBackground(Color background) {
0421: this .background = background;
0422: }
0423:
0424: /**
0425: * Set the font.
0426: * @param font
0427: */
0428: public void setFont(Font font) {
0429: this .font = font;
0430: }
0431:
0432: /**
0433: * Set the foreground color.
0434: * @param foreground
0435: */
0436: public void setForeground(Color foreground) {
0437: this .foreground = foreground;
0438: }
0439:
0440: }
0441:
0442: /**
0443: * The safe runnable used to update an item.
0444: */
0445: class UpdateItemSafeRunnable extends SafeRunnable {
0446: private Widget widget;
0447:
0448: private Object element;
0449:
0450: private boolean fullMap;
0451:
0452: UpdateItemSafeRunnable(Widget widget, Object element,
0453: boolean fullMap) {
0454: this .widget = widget;
0455: this .element = element;
0456: this .fullMap = fullMap;
0457: }
0458:
0459: public void run() {
0460: doUpdateItem(widget, element, fullMap);
0461: }
0462: }
0463:
0464: /**
0465: * Creates a structured element viewer. The viewer has no input, no content
0466: * provider, a default label provider, no sorter, and no filters.
0467: */
0468: protected StructuredViewer() {
0469: // do nothing
0470: }
0471:
0472: /**
0473: * Adds a listener for double-clicks in this viewer. Has no effect if an
0474: * identical listener is already registered.
0475: *
0476: * @param listener
0477: * a double-click listener
0478: */
0479: public void addDoubleClickListener(IDoubleClickListener listener) {
0480: doubleClickListeners.add(listener);
0481: }
0482:
0483: /**
0484: * Adds a listener for selection-open in this viewer. Has no effect if an
0485: * identical listener is already registered.
0486: *
0487: * @param listener
0488: * a double-click listener
0489: */
0490: public void addOpenListener(IOpenListener listener) {
0491: openListeners.add(listener);
0492: }
0493:
0494: /*
0495: * (non-Javadoc) Method declared on IPostSelectionProvider.
0496: */
0497: public void addPostSelectionChangedListener(
0498: ISelectionChangedListener listener) {
0499: postSelectionChangedListeners.add(listener);
0500: }
0501:
0502: /**
0503: * Adds support for dragging items out of this viewer via a user
0504: * drag-and-drop operation.
0505: *
0506: * @param operations
0507: * a bitwise OR of the supported drag and drop operation types (
0508: * <code>DROP_COPY</code>,<code>DROP_LINK</code>, and
0509: * <code>DROP_MOVE</code>)
0510: * @param transferTypes
0511: * the transfer types that are supported by the drag operation
0512: * @param listener
0513: * the callback that will be invoked to set the drag data and to
0514: * cleanup after the drag and drop operation finishes
0515: * @see org.eclipse.swt.dnd.DND
0516: */
0517: public void addDragSupport(int operations,
0518: Transfer[] transferTypes, DragSourceListener listener) {
0519:
0520: Control myControl = getControl();
0521: final DragSource dragSource = new DragSource(myControl,
0522: operations);
0523: dragSource.setTransfer(transferTypes);
0524: dragSource.addDragListener(listener);
0525: }
0526:
0527: /**
0528: * Adds support for dropping items into this viewer via a user drag-and-drop
0529: * operation.
0530: *
0531: * @param operations
0532: * a bitwise OR of the supported drag and drop operation types (
0533: * <code>DROP_COPY</code>,<code>DROP_LINK</code>, and
0534: * <code>DROP_MOVE</code>)
0535: * @param transferTypes
0536: * the transfer types that are supported by the drop operation
0537: * @param listener
0538: * the callback that will be invoked after the drag and drop
0539: * operation finishes
0540: * @see org.eclipse.swt.dnd.DND
0541: */
0542: public void addDropSupport(int operations,
0543: Transfer[] transferTypes, final DropTargetListener listener) {
0544: Control control = getControl();
0545: DropTarget dropTarget = new DropTarget(control, operations);
0546: dropTarget.setTransfer(transferTypes);
0547: dropTarget.addDropListener(listener);
0548: }
0549:
0550: /**
0551: * Adds the given filter to this viewer, and triggers refiltering and
0552: * resorting of the elements. If you want to add more than one filter
0553: * consider using {@link StructuredViewer#setFilters(ViewerFilter[])}.
0554: *
0555: * @param filter
0556: * a viewer filter
0557: * @see StructuredViewer#setFilters(ViewerFilter[])
0558: */
0559: public void addFilter(ViewerFilter filter) {
0560: if (filters == null) {
0561: filters = new ArrayList();
0562: }
0563: filters.add(filter);
0564: refresh();
0565: }
0566:
0567: /**
0568: * Asserts that the given array of elements is itself non- <code>null</code>
0569: * and contains no <code>null</code> elements.
0570: *
0571: * @param elements
0572: * the array to check
0573: */
0574: protected void assertElementsNotNull(Object[] elements) {
0575: Assert.isNotNull(elements);
0576: for (int i = 0, n = elements.length; i < n; ++i) {
0577: Assert.isNotNull(elements[i]);
0578: }
0579: }
0580:
0581: /**
0582: * Associates the given element with the given widget. Sets the given item's
0583: * data to be the element, and maps the element to the item in the element
0584: * map (if enabled).
0585: *
0586: * @param element
0587: * the element
0588: * @param item
0589: * the widget
0590: */
0591: protected void associate(Object element, Item item) {
0592: Object data = item.getData();
0593: if (data != element) {
0594: if (data != null) {
0595: disassociate(item);
0596: }
0597: item.setData(element);
0598: }
0599: // Always map the element, even if data == element,
0600: // since unmapAllElements() can leave the map inconsistent
0601: // See bug 2741 for details.
0602: mapElement(element, item);
0603: }
0604:
0605: /**
0606: * Disassociates the given SWT item from its corresponding element. Sets the
0607: * item's data to <code>null</code> and removes the element from the
0608: * element map (if enabled).
0609: *
0610: * @param item
0611: * the widget
0612: */
0613: protected void disassociate(Item item) {
0614: Object element = item.getData();
0615: Assert.isNotNull(element);
0616: //Clear the map before we clear the data
0617: unmapElement(element, item);
0618: item.setData(null);
0619: }
0620:
0621: /**
0622: * Returns the widget in this viewer's control which represents the given
0623: * element if it is the viewer's input.
0624: * <p>
0625: * This method is internal to the framework; subclassers should not call
0626: * this method.
0627: * </p>
0628: *
0629: * @param element
0630: * @return the corresponding widget, or <code>null</code> if none
0631: */
0632: protected abstract Widget doFindInputItem(Object element);
0633:
0634: /**
0635: * Returns the widget in this viewer's control which represent the given
0636: * element. This method searches all the children of the input element.
0637: * <p>
0638: * This method is internal to the framework; subclassers should not call
0639: * this method.
0640: * </p>
0641: *
0642: * @param element
0643: * @return the corresponding widget, or <code>null</code> if none
0644: */
0645: protected abstract Widget doFindItem(Object element);
0646:
0647: /**
0648: * Copies the attributes of the given element into the given SWT item. The
0649: * element map is updated according to the value of <code>fullMap</code>.
0650: * If <code>fullMap</code> is <code>true</code> then the current mapping
0651: * from element to widgets is removed and the new mapping is added. If
0652: * full map is <code>false</code> then only the new map gets installed.
0653: * Installing only the new map is necessary in cases where only the order of
0654: * elements changes but not the set of elements.
0655: * <p>
0656: * This method is internal to the framework; subclassers should not call
0657: * this method.
0658: * </p>
0659: *
0660: * @param item
0661: * @param element element
0662: * @param fullMap
0663: * <code>true</code> if mappings are added and removed, and
0664: * <code>false</code> if only the new map gets installed
0665: */
0666: protected abstract void doUpdateItem(Widget item, Object element,
0667: boolean fullMap);
0668:
0669: /**
0670: * Compares two elements for equality. Uses the element comparer if one has
0671: * been set, otherwise uses the default <code>equals</code> method on the
0672: * elements themselves.
0673: *
0674: * @param elementA
0675: * the first element
0676: * @param elementB
0677: * the second element
0678: * @return whether elementA is equal to elementB
0679: */
0680: protected boolean equals(Object elementA, Object elementB) {
0681: if (comparer == null) {
0682: return elementA == null ? elementB == null : elementA
0683: .equals(elementB);
0684: } else {
0685: return elementA == null ? elementB == null : comparer
0686: .equals(elementA, elementB);
0687: }
0688: }
0689:
0690: /**
0691: * Returns the result of running the given elements through the filters.
0692: *
0693: * @param elements
0694: * the elements to filter
0695: * @return only the elements which all filters accept
0696: */
0697: protected Object[] filter(Object[] elements) {
0698: if (filters != null) {
0699: ArrayList filtered = new ArrayList(elements.length);
0700: Object root = getRoot();
0701: for (int i = 0; i < elements.length; i++) {
0702: boolean add = true;
0703: for (int j = 0; j < filters.size(); j++) {
0704: add = ((ViewerFilter) filters.get(j)).select(this ,
0705: root, elements[i]);
0706: if (!add) {
0707: break;
0708: }
0709: }
0710: if (add) {
0711: filtered.add(elements[i]);
0712: }
0713: }
0714: return filtered.toArray();
0715: }
0716: return elements;
0717: }
0718:
0719: /**
0720: * Finds the widget which represents the given element.
0721: * <p>
0722: * The default implementation of this method tries first to find the widget
0723: * for the given element assuming that it is the viewer's input; this is
0724: * done by calling <code>doFindInputItem</code>. If it is not found
0725: * there, it is looked up in the internal element map provided that this
0726: * feature has been enabled. If the element map is disabled, the widget is
0727: * found via <code>doFindInputItem</code>.
0728: * </p>
0729: *
0730: * @param element
0731: * the element
0732: * @return the corresponding widget, or <code>null</code> if none
0733: */
0734: protected final Widget findItem(Object element) {
0735: Widget[] result = findItems(element);
0736: return result.length == 0 ? null : result[0];
0737: }
0738:
0739: /**
0740: * Finds the widgets which represent the given element. The returned array
0741: * must not be changed by clients; it might change upon calling other
0742: * methods on this viewer.
0743: * <p>
0744: * This method was introduced to support multiple equal elements in a viewer
0745: * (@see {@link AbstractTreeViewer}). Multiple equal elements are only
0746: * supported if the element map is enabled by calling
0747: * {@link #setUseHashlookup(boolean)} and passing <code>true</code>.
0748: * </p>
0749: * <p>
0750: * The default implementation of this method tries first to find the widget
0751: * for the given element assuming that it is the viewer's input; this is
0752: * done by calling <code>doFindInputItem</code>. If it is not found
0753: * there, the widgets are looked up in the internal element map provided
0754: * that this feature has been enabled. If the element map is disabled, the
0755: * widget is found via <code>doFindInputItem</code>.
0756: * </p>
0757: *
0758: * @param element
0759: * the element
0760: * @return the corresponding widgets
0761: *
0762: * @since 3.2
0763: */
0764: protected final Widget[] findItems(Object element) {
0765: Widget result = doFindInputItem(element);
0766: if (result != null) {
0767: return new Widget[] { result };
0768: }
0769: // if we have an element map use it, otherwise search for the item.
0770: if (usingElementMap()) {
0771: Object widgetOrWidgets = elementMap.get(element);
0772: if (widgetOrWidgets == null) {
0773: return NO_WIDGETS;
0774: } else if (widgetOrWidgets instanceof Widget) {
0775: return new Widget[] { (Widget) widgetOrWidgets };
0776: } else {
0777: return (Widget[]) widgetOrWidgets;
0778: }
0779: }
0780: result = doFindItem(element);
0781: return result == null ? NO_WIDGETS : new Widget[] { result };
0782: }
0783:
0784: /**
0785: * Notifies any double-click listeners that a double-click has been
0786: * received. Only listeners registered at the time this method is called are
0787: * notified.
0788: *
0789: * @param event
0790: * a double-click event
0791: *
0792: * @see IDoubleClickListener#doubleClick
0793: */
0794: protected void fireDoubleClick(final DoubleClickEvent event) {
0795: Object[] listeners = doubleClickListeners.getListeners();
0796: for (int i = 0; i < listeners.length; ++i) {
0797: final IDoubleClickListener l = (IDoubleClickListener) listeners[i];
0798: SafeRunnable.run(new SafeRunnable() {
0799: public void run() {
0800: l.doubleClick(event);
0801: }
0802: });
0803: }
0804: }
0805:
0806: /**
0807: * Notifies any open event listeners that a open event has been received.
0808: * Only listeners registered at the time this method is called are notified.
0809: *
0810: * @param event
0811: * a double-click event
0812: *
0813: * @see IOpenListener#open(OpenEvent)
0814: */
0815: protected void fireOpen(final OpenEvent event) {
0816: Object[] listeners = openListeners.getListeners();
0817: for (int i = 0; i < listeners.length; ++i) {
0818: final IOpenListener l = (IOpenListener) listeners[i];
0819: SafeRunnable.run(new SafeRunnable() {
0820: public void run() {
0821: l.open(event);
0822: }
0823: });
0824: }
0825: }
0826:
0827: /**
0828: * Notifies any post selection listeners that a post selection event has
0829: * been received. Only listeners registered at the time this method is
0830: * called are notified.
0831: *
0832: * @param event
0833: * a selection changed event
0834: *
0835: * @see #addPostSelectionChangedListener(ISelectionChangedListener)
0836: */
0837: protected void firePostSelectionChanged(
0838: final SelectionChangedEvent event) {
0839: Object[] listeners = postSelectionChangedListeners
0840: .getListeners();
0841: for (int i = 0; i < listeners.length; ++i) {
0842: final ISelectionChangedListener l = (ISelectionChangedListener) listeners[i];
0843: SafeRunnable.run(new SafeRunnable() {
0844: public void run() {
0845: l.selectionChanged(event);
0846: }
0847: });
0848: }
0849: }
0850:
0851: /**
0852: * Returns the comparer to use for comparing elements, or
0853: * <code>null</code> if none has been set. If specified,
0854: * the viewer uses this to compare and hash elements rather
0855: * than the elements' own equals and hashCode methods.
0856: *
0857: * @return the comparer to use for comparing elements or
0858: * <code>null</code>
0859: */
0860: public IElementComparer getComparer() {
0861: return comparer;
0862: }
0863:
0864: /**
0865: * Returns the filtered array of children of the given element. The
0866: * resulting array must not be modified, as it may come directly from the
0867: * model's internal state.
0868: *
0869: * @param parent
0870: * the parent element
0871: * @return a filtered array of child elements
0872: */
0873: protected Object[] getFilteredChildren(Object parent) {
0874: Object[] result = getRawChildren(parent);
0875: if (filters != null) {
0876: for (Iterator iter = filters.iterator(); iter.hasNext();) {
0877: ViewerFilter f = (ViewerFilter) iter.next();
0878: result = f.filter(this , parent, result);
0879: }
0880: }
0881: return result;
0882: }
0883:
0884: /**
0885: * Returns this viewer's filters.
0886: *
0887: * @return an array of viewer filters
0888: * @see StructuredViewer#setFilters(ViewerFilter[])
0889: */
0890: public ViewerFilter[] getFilters() {
0891: if (filters == null) {
0892: return new ViewerFilter[0];
0893: }
0894: ViewerFilter[] result = new ViewerFilter[filters.size()];
0895: filters.toArray(result);
0896: return result;
0897: }
0898:
0899: /**
0900: * Returns the item at the given display-relative coordinates, or
0901: * <code>null</code> if there is no item at that location or
0902: * the underlying SWT-Control is not made up of {@link Item}
0903: * (e.g {@link ListViewer})
0904: * <p>
0905: * The default implementation of this method returns <code>null</code>.
0906: * </p>
0907: *
0908: * @param x
0909: * horizontal coordinate
0910: * @param y
0911: * vertical coordinate
0912: * @return the item, or <code>null</code> if there is no item at the given
0913: * coordinates
0914: * @deprecated This method is deprecated in 3.3 in favor of {@link ColumnViewer#getItemAt(org.eclipse.swt.graphics.Point)}.
0915: * Viewers who are not subclasses of {@link ColumnViewer} should consider using a
0916: * widget relative implementation like {@link ColumnViewer#getItemAt(org.eclipse.swt.graphics.Point)}.
0917: *
0918: */
0919: protected Item getItem(int x, int y) {
0920: return null;
0921: }
0922:
0923: /**
0924: * Returns the children of the given parent without sorting and filtering
0925: * them. The resulting array must not be modified, as it may come directly
0926: * from the model's internal state.
0927: * <p>
0928: * Returns an empty array if the given parent is <code>null</code>.
0929: * </p>
0930: *
0931: * @param parent
0932: * the parent element
0933: * @return the child elements
0934: */
0935: protected Object[] getRawChildren(Object parent) {
0936: Object[] result = null;
0937: if (parent != null) {
0938: IStructuredContentProvider cp = (IStructuredContentProvider) getContentProvider();
0939: if (cp != null) {
0940: result = cp.getElements(parent);
0941: assertElementsNotNull(result);
0942: }
0943: }
0944: return (result != null) ? result : new Object[0];
0945: }
0946:
0947: /**
0948: * Returns the root element.
0949: * <p>
0950: * The default implementation of this framework method forwards to
0951: * <code>getInput</code>. Override if the root element is different from
0952: * the viewer's input element.
0953: * </p>
0954: *
0955: * @return the root element, or <code>null</code> if none
0956: */
0957: protected Object getRoot() {
0958: return getInput();
0959: }
0960:
0961: /**
0962: * The <code>StructuredViewer</code> implementation of this method returns
0963: * the result as an <code>IStructuredSelection</code>.
0964: * <p>
0965: * Subclasses do not typically override this method, but implement
0966: * <code>getSelectionFromWidget(List)</code> instead.
0967: * <p>
0968: * @return ISelection
0969: */
0970: public ISelection getSelection() {
0971: Control control = getControl();
0972: if (control == null || control.isDisposed()) {
0973: return StructuredSelection.EMPTY;
0974: }
0975: List list = getSelectionFromWidget();
0976: return new StructuredSelection(list);
0977: }
0978:
0979: /**
0980: * Retrieves the selection, as a <code>List</code>, from the underlying
0981: * widget.
0982: *
0983: * @return the list of selected elements
0984: */
0985: protected abstract List getSelectionFromWidget();
0986:
0987: /**
0988: * Returns the sorted and filtered set of children of the given element. The
0989: * resulting array must not be modified, as it may come directly from the
0990: * model's internal state.
0991: *
0992: * @param parent
0993: * the parent element
0994: * @return a sorted and filtered array of child elements
0995: */
0996: protected Object[] getSortedChildren(Object parent) {
0997: Object[] result = getFilteredChildren(parent);
0998: if (sorter != null) {
0999: // be sure we're not modifying the original array from the model
1000: result = (Object[]) result.clone();
1001: sorter.sort(this , result);
1002: }
1003: return result;
1004: }
1005:
1006: /**
1007: * Returns this viewer's sorter, or <code>null</code> if it does not have
1008: * one. If this viewer has a comparator that was set via
1009: * <code>setComparator(ViewerComparator)</code> then this method will return
1010: * <code>null</code> if the comparator is not an instance of ViewerSorter.
1011: * <p>
1012: * It is recommended to use <code>getComparator()</code> instead.
1013: * </p>
1014: *
1015: * @return a viewer sorter, or <code>null</code> if none or if the comparator is
1016: * not an instance of ViewerSorter
1017: */
1018: public ViewerSorter getSorter() {
1019: if (sorter instanceof ViewerSorter)
1020: return (ViewerSorter) sorter;
1021: return null;
1022: }
1023:
1024: /**
1025: * Return this viewer's comparator used to sort elements.
1026: * This method should be used instead of <code>getSorter()</code>.
1027: *
1028: * @return a viewer comparator, or <code>null</code> if none
1029: *
1030: * @since 3.2
1031: */
1032: public ViewerComparator getComparator() {
1033: return sorter;
1034: }
1035:
1036: /**
1037: * Handles a double-click select event from the widget.
1038: * <p>
1039: * This method is internal to the framework; subclassers should not call
1040: * this method.
1041: * </p>
1042: *
1043: * @param event
1044: * the SWT selection event
1045: */
1046: protected void handleDoubleSelect(SelectionEvent event) {
1047: // This method is reimplemented in AbstractTreeViewer to fix bug 108102.
1048:
1049: // handle case where an earlier selection listener disposed the control.
1050: Control control = getControl();
1051: if (control != null && !control.isDisposed()) {
1052: // If the double-clicked element can be obtained from the event, use it
1053: // otherwise get it from the control. Some controls like List do
1054: // not have the notion of item.
1055: // For details, see bug 90161 [Navigator] DefaultSelecting folders shouldn't always expand first one
1056: ISelection selection;
1057: if (event.item != null && event.item.getData() != null) {
1058: selection = new StructuredSelection(event.item
1059: .getData());
1060: } else {
1061: selection = getSelection();
1062: updateSelection(selection);
1063: }
1064: fireDoubleClick(new DoubleClickEvent(this , selection));
1065: }
1066: }
1067:
1068: /**
1069: * Handles an open event from the OpenStrategy.
1070: * <p>
1071: * This method is internal to the framework; subclassers should not call
1072: * this method.
1073: * </p>
1074: *
1075: * @param event
1076: * the SWT selection event
1077: */
1078: protected void handleOpen(SelectionEvent event) {
1079: Control control = getControl();
1080: if (control != null && !control.isDisposed()) {
1081: ISelection selection = getSelection();
1082: fireOpen(new OpenEvent(this , selection));
1083: }
1084: }
1085:
1086: /**
1087: * Handles an invalid selection.
1088: * <p>
1089: * This framework method is called if a model change picked up by a viewer
1090: * results in an invalid selection. For instance if an element contained in
1091: * the selection has been removed from the viewer, the viewer is free to
1092: * either remove the element from the selection or to pick another element
1093: * as its new selection. The default implementation of this method calls
1094: * <code>updateSelection</code>. Subclasses may override it to implement
1095: * a different strategy for picking a new selection when the old selection
1096: * becomes invalid.
1097: * </p>
1098: *
1099: * @param invalidSelection
1100: * the selection before the viewer was updated
1101: * @param newSelection
1102: * the selection after the update, or <code>null</code> if none
1103: */
1104: protected void handleInvalidSelection(ISelection invalidSelection,
1105: ISelection newSelection) {
1106: updateSelection(newSelection);
1107: SelectionChangedEvent event = new SelectionChangedEvent(this ,
1108: newSelection);
1109: firePostSelectionChanged(event);
1110: }
1111:
1112: /**
1113: * The <code>StructuredViewer</code> implementation of this
1114: * <code>ContentViewer</code> method calls <code>update</code> if the
1115: * event specifies that the label of a given element has changed, otherwise
1116: * it calls super. Subclasses may reimplement or extend.
1117: * </p>
1118: * @param event the event that generated this update
1119: */
1120: protected void handleLabelProviderChanged(
1121: LabelProviderChangedEvent event) {
1122: Object[] elements = event.getElements();
1123: if (elements != null) {
1124: update(elements, null);
1125: } else {
1126: super .handleLabelProviderChanged(event);
1127: }
1128: }
1129:
1130: /**
1131: * Handles a select event from the widget.
1132: * <p>
1133: * This method is internal to the framework; subclassers should not call
1134: * this method.
1135: * </p>
1136: *
1137: * @param event
1138: * the SWT selection event
1139: */
1140: protected void handleSelect(SelectionEvent event) {
1141: // handle case where an earlier selection listener disposed the control.
1142: Control control = getControl();
1143: if (control != null && !control.isDisposed()) {
1144: updateSelection(getSelection());
1145: }
1146: }
1147:
1148: /**
1149: * Handles a post select event from the widget.
1150: * <p>
1151: * This method is internal to the framework; subclassers should not call
1152: * this method.
1153: * </p>
1154: *
1155: * @param e the SWT selection event
1156: */
1157: protected void handlePostSelect(SelectionEvent e) {
1158: SelectionChangedEvent event = new SelectionChangedEvent(this ,
1159: getSelection());
1160: firePostSelectionChanged(event);
1161: }
1162:
1163: /*
1164: * (non-Javadoc) Method declared on Viewer.
1165: */
1166: protected void hookControl(Control control) {
1167: super .hookControl(control);
1168: OpenStrategy handler = new OpenStrategy(control);
1169: handler.addSelectionListener(new SelectionListener() {
1170: public void widgetSelected(SelectionEvent e) {
1171: // On Windows, selection events may happen during a refresh.
1172: // Ignore these events if we are currently in preservingSelection().
1173: // See bug 184441.
1174: if (!inChange) {
1175: handleSelect(e);
1176: }
1177: }
1178:
1179: public void widgetDefaultSelected(SelectionEvent e) {
1180: handleDoubleSelect(e);
1181: }
1182: });
1183: handler.addPostSelectionListener(new SelectionAdapter() {
1184: public void widgetSelected(SelectionEvent e) {
1185: handlePostSelect(e);
1186: }
1187: });
1188: handler.addOpenListener(new IOpenEventListener() {
1189: public void handleOpen(SelectionEvent e) {
1190: StructuredViewer.this .handleOpen(e);
1191: }
1192: });
1193: }
1194:
1195: /**
1196: * Returns whether this viewer has any filters.
1197: * @return boolean
1198: */
1199: protected boolean hasFilters() {
1200: return filters != null && filters.size() > 0;
1201: }
1202:
1203: /**
1204: * Refreshes this viewer starting at the given element.
1205: *
1206: * @param element
1207: * the element
1208: */
1209: protected abstract void internalRefresh(Object element);
1210:
1211: /**
1212: * Refreshes this viewer starting at the given element. Labels are updated
1213: * as described in <code>refresh(boolean updateLabels)</code>.
1214: * <p>
1215: * The default implementation simply calls
1216: * <code>internalRefresh(element)</code>, ignoring
1217: * <code>updateLabels</code>.
1218: * <p>
1219: * If this method is overridden to do the actual refresh, then
1220: * <code>internalRefresh(Object element)</code> should simply call
1221: * <code>internalRefresh(element, true)</code>.
1222: *
1223: * @param element
1224: * the element
1225: * @param updateLabels
1226: * <code>true</code> to update labels for existing elements,
1227: * <code>false</code> to only update labels as needed, assuming
1228: * that labels for existing elements are unchanged.
1229: *
1230: * @since 2.0
1231: */
1232: protected void internalRefresh(Object element, boolean updateLabels) {
1233: internalRefresh(element);
1234: }
1235:
1236: /**
1237: * Adds the element item pair to the element map.
1238: * <p>
1239: * This method is internal to the framework; subclassers should not call
1240: * this method.
1241: * </p>
1242: *
1243: * @param element
1244: * the element
1245: * @param item
1246: * the corresponding widget
1247: */
1248: protected void mapElement(Object element, Widget item) {
1249: if (elementMap != null) {
1250: Object widgetOrWidgets = elementMap.get(element);
1251: if (widgetOrWidgets == null) {
1252: elementMap.put(element, item);
1253: } else if (widgetOrWidgets instanceof Widget) {
1254: if (widgetOrWidgets != item) {
1255: elementMap.put(element, new Widget[] {
1256: (Widget) widgetOrWidgets, item });
1257: }
1258: } else {
1259: Widget[] widgets = (Widget[]) widgetOrWidgets;
1260: int indexOfItem = Arrays.asList(widgets).indexOf(item);
1261: if (indexOfItem == -1) {
1262: int length = widgets.length;
1263: System
1264: .arraycopy(widgets, 0,
1265: widgets = new Widget[length + 1],
1266: 0, length);
1267: widgets[length] = item;
1268: elementMap.put(element, widgets);
1269: }
1270: }
1271: }
1272: }
1273:
1274: /**
1275: * Determines whether a change to the given property of the given element
1276: * would require refiltering and/or resorting.
1277: * <p>
1278: * This method is internal to the framework; subclassers should not call
1279: * this method.
1280: * </p>
1281: *
1282: * @param element
1283: * the element
1284: * @param property
1285: * the property
1286: * @return <code>true</code> if refiltering is required, and
1287: * <code>false</code> otherwise
1288: */
1289: protected boolean needsRefilter(Object element, String property) {
1290: if (sorter != null
1291: && sorter.isSorterProperty(element, property)) {
1292: return true;
1293: }
1294:
1295: if (filters != null) {
1296: for (int i = 0, n = filters.size(); i < n; ++i) {
1297: ViewerFilter filter = (ViewerFilter) filters.get(i);
1298: if (filter.isFilterProperty(element, property)) {
1299: return true;
1300: }
1301: }
1302: }
1303: return false;
1304: }
1305:
1306: /**
1307: * Returns a new hashtable using the given capacity and this viewer's element comparer.
1308: *
1309: * @param capacity the initial capacity of the hashtable
1310: * @return a new hashtable
1311: *
1312: * @since 3.0
1313: */
1314: CustomHashtable newHashtable(int capacity) {
1315: return new CustomHashtable(capacity, getComparer());
1316: }
1317:
1318: /**
1319: * Attempts to preserves the current selection across a run of the given
1320: * code.
1321: * <p>
1322: * The default implementation of this method:
1323: * <ul>
1324: * <li>discovers the old selection (via <code>getSelection</code>)</li>
1325: * <li>runs the given runnable</li>
1326: * <li>attempts to restore the old selection (using
1327: * <code>setSelectionToWidget</code></li>
1328: * <li>rediscovers the resulting selection (via <code>getSelection</code>)
1329: * </li>
1330: * <li>calls <code>handleInvalidSelection</code> if the resulting selection is different from the old selection</li>
1331: * </ul>
1332: * </p>
1333: *
1334: * @param updateCode
1335: * the code to run
1336: */
1337: protected void preservingSelection(Runnable updateCode) {
1338: preservingSelection(updateCode, false);
1339: }
1340:
1341: /**
1342: * Attempts to preserves the current selection across a run of the given
1343: * code, with a best effort to avoid scrolling if <code>reveal</code> is false,
1344: * or to reveal the selection if <code>reveal</code> is true.
1345: * <p>
1346: * The default implementation of this method:
1347: * <ul>
1348: * <li>discovers the old selection (via <code>getSelection</code>)</li>
1349: * <li>runs the given runnable</li>
1350: * <li>attempts to restore the old selection (using
1351: * <code>setSelectionToWidget</code></li>
1352: * <li>rediscovers the resulting selection (via <code>getSelection</code>)
1353: * </li>
1354: * <li>calls <code>handleInvalidSelection</code> if the selection did not
1355: * take</li>
1356: * <li>calls <code>postUpdateHook</code></li>
1357: * </ul>
1358: * </p>
1359: *
1360: * @param updateCode
1361: * the code to run
1362: * @param reveal
1363: * <code>true</code> if the selection should be made visible,
1364: * <code>false</code> if scrolling should be avoided
1365: * @since 3.3
1366: */
1367: void preservingSelection(Runnable updateCode, boolean reveal) {
1368:
1369: ISelection oldSelection = null;
1370: try {
1371: // preserve selection
1372: oldSelection = getSelection();
1373: inChange = restoreSelection = true;
1374:
1375: // perform the update
1376: updateCode.run();
1377:
1378: } finally {
1379: inChange = false;
1380:
1381: // restore selection
1382: if (restoreSelection) {
1383: setSelectionToWidget(oldSelection, reveal);
1384: }
1385:
1386: // send out notification if old and new differ
1387: ISelection newSelection = getSelection();
1388: if (!newSelection.equals(oldSelection)) {
1389: handleInvalidSelection(oldSelection, newSelection);
1390: }
1391: }
1392: }
1393:
1394: /*
1395: * Non-Javadoc. Method declared on Viewer.
1396: */
1397: public void refresh() {
1398: refresh(getRoot());
1399: }
1400:
1401: /**
1402: * Refreshes this viewer with information freshly obtained from this
1403: * viewer's model. If <code>updateLabels</code> is <code>true</code>
1404: * then labels for otherwise unaffected elements are updated as well.
1405: * Otherwise, it assumes labels for existing elements are unchanged, and
1406: * labels are only obtained as needed (for example, for new elements).
1407: * <p>
1408: * Calling <code>refresh(true)</code> has the same effect as
1409: * <code>refresh()</code>.
1410: * <p>
1411: * Note that the implementation may still obtain labels for existing
1412: * elements even if <code>updateLabels</code> is false. The intent is
1413: * simply to allow optimization where possible.
1414: *
1415: * @param updateLabels
1416: * <code>true</code> to update labels for existing elements,
1417: * <code>false</code> to only update labels as needed, assuming
1418: * that labels for existing elements are unchanged.
1419: *
1420: * @since 2.0
1421: */
1422: public void refresh(boolean updateLabels) {
1423: refresh(getRoot(), updateLabels);
1424: }
1425:
1426: /**
1427: * Refreshes this viewer starting with the given element.
1428: * <p>
1429: * Unlike the <code>update</code> methods, this handles structural changes
1430: * to the given element (e.g. addition or removal of children). If only the
1431: * given element needs updating, it is more efficient to use the
1432: * <code>update</code> methods.
1433: * </p>
1434: *
1435: * @param element
1436: * the element
1437: */
1438: public void refresh(final Object element) {
1439: preservingSelection(new Runnable() {
1440: public void run() {
1441: internalRefresh(element);
1442: }
1443: });
1444: }
1445:
1446: /**
1447: * Refreshes this viewer starting with the given element. Labels are updated
1448: * as described in <code>refresh(boolean updateLabels)</code>.
1449: * <p>
1450: * Unlike the <code>update</code> methods, this handles structural changes
1451: * to the given element (e.g. addition or removal of children). If only the
1452: * given element needs updating, it is more efficient to use the
1453: * <code>update</code> methods.
1454: * </p>
1455: *
1456: * @param element
1457: * the element
1458: * @param updateLabels
1459: * <code>true</code> to update labels for existing elements,
1460: * <code>false</code> to only update labels as needed, assuming
1461: * that labels for existing elements are unchanged.
1462: *
1463: * @since 2.0
1464: */
1465: public void refresh(final Object element, final boolean updateLabels) {
1466: preservingSelection(new Runnable() {
1467: public void run() {
1468: internalRefresh(element, updateLabels);
1469: }
1470: });
1471: }
1472:
1473: /**
1474: *
1475: * Refreshes the given item with the given element. Calls
1476: * <code>doUpdateItem(..., false)</code>.
1477: * <p>
1478: * This method is internal to the framework; subclassers should not call
1479: * this method.
1480: * </p>
1481: * @param widget
1482: * the widget
1483: * @param element
1484: * the element
1485: */
1486: protected final void refreshItem(Widget widget, Object element) {
1487: SafeRunnable.run(new UpdateItemSafeRunnable(widget, element,
1488: true));
1489: }
1490:
1491: /**
1492: * Removes the given open listener from this viewer. Has no affect if an
1493: * identical listener is not registered.
1494: *
1495: * @param listener
1496: * a double-click listener
1497: */
1498: public void removeOpenListener(IOpenListener listener) {
1499: openListeners.remove(listener);
1500: }
1501:
1502: /*
1503: * (non-Javadoc) Method declared on IPostSelectionProvider.
1504: */
1505: public void removePostSelectionChangedListener(
1506: ISelectionChangedListener listener) {
1507: postSelectionChangedListeners.remove(listener);
1508: }
1509:
1510: /**
1511: * Removes the given double-click listener from this viewer. Has no affect
1512: * if an identical listener is not registered.
1513: *
1514: * @param listener
1515: * a double-click listener
1516: */
1517: public void removeDoubleClickListener(IDoubleClickListener listener) {
1518: doubleClickListeners.remove(listener);
1519: }
1520:
1521: /**
1522: * Removes the given filter from this viewer, and triggers refiltering and
1523: * resorting of the elements if required. Has no effect if the identical
1524: * filter is not registered. If you want to remove more than one filter
1525: * consider using {@link StructuredViewer#setFilters(ViewerFilter[])}.
1526: *
1527: * @param filter
1528: * a viewer filter
1529: * @see StructuredViewer#setFilters(ViewerFilter[])
1530: */
1531: public void removeFilter(ViewerFilter filter) {
1532: Assert.isNotNull(filter);
1533: if (filters != null) {
1534: // Note: can't use List.remove(Object). Use identity comparison
1535: // instead.
1536: for (Iterator i = filters.iterator(); i.hasNext();) {
1537: Object o = i.next();
1538: if (o == filter) {
1539: i.remove();
1540: refresh();
1541: if (filters.size() == 0) {
1542: filters = null;
1543: }
1544: return;
1545: }
1546: }
1547: }
1548: }
1549:
1550: /**
1551: * Sets the filters, replacing any previous filters, and triggers
1552: * refiltering and resorting of the elements.
1553: *
1554: * @param filters
1555: * an array of viewer filters
1556: * @since 3.3
1557: */
1558: public void setFilters(ViewerFilter[] filters) {
1559: if (filters.length == 0) {
1560: resetFilters();
1561: } else {
1562: this .filters = new ArrayList(Arrays.asList(filters));
1563: refresh();
1564: }
1565: }
1566:
1567: /**
1568: * Discards this viewer's filters and triggers refiltering and resorting of
1569: * the elements.
1570: */
1571: public void resetFilters() {
1572: if (filters != null) {
1573: filters = null;
1574: refresh();
1575: }
1576: }
1577:
1578: /**
1579: * Ensures that the given element is visible, scrolling the viewer if
1580: * necessary. The selection is unchanged.
1581: *
1582: * @param element
1583: * the element to reveal
1584: */
1585: public abstract void reveal(Object element);
1586:
1587: /*
1588: * (non-Javadoc)
1589: * @see org.eclipse.jface.viewers.ContentViewer#setContentProvider(org.eclipse.jface.viewers.IContentProvider)
1590: */
1591: public void setContentProvider(IContentProvider provider) {
1592: assertContentProviderType(provider);
1593: super .setContentProvider(provider);
1594: }
1595:
1596: /**
1597: * Assert that the content provider is of one of the
1598: * supported types.
1599: * @param provider
1600: */
1601: protected void assertContentProviderType(IContentProvider provider) {
1602: Assert.isTrue(provider instanceof IStructuredContentProvider);
1603: }
1604:
1605: /*
1606: * (non-Javadoc)
1607: * @see org.eclipse.jface.viewers.Viewer#setInput(java.lang.Object)
1608: */
1609: public final void setInput(Object input) {
1610:
1611: try {
1612: // fInChange= true;
1613:
1614: unmapAllElements();
1615:
1616: super .setInput(input);
1617:
1618: } finally {
1619: // fInChange= false;
1620: }
1621: }
1622:
1623: /*
1624: * (non-Javadoc)
1625: * @see org.eclipse.jface.viewers.Viewer#setSelection(org.eclipse.jface.viewers.ISelection, boolean)
1626: */
1627: public void setSelection(ISelection selection, boolean reveal) {
1628: /**
1629: * <p>
1630: * If the new selection differs from the current selection the hook
1631: * <code>updateSelection</code> is called.
1632: * </p>
1633: * <p>
1634: * If <code>setSelection</code> is called from within
1635: * <code>preserveSelection</code>, the call to
1636: * <code>updateSelection</code> is delayed until the end of
1637: * <code>preserveSelection</code>.
1638: * </p>
1639: * <p>
1640: * Subclasses do not typically override this method, but implement
1641: * <code>setSelectionToWidget</code> instead.
1642: * </p>
1643: */
1644: Control control = getControl();
1645: if (control == null || control.isDisposed()) {
1646: return;
1647: }
1648: if (!inChange) {
1649: setSelectionToWidget(selection, reveal);
1650: ISelection sel = getSelection();
1651: updateSelection(sel);
1652: firePostSelectionChanged(new SelectionChangedEvent(this ,
1653: sel));
1654: } else {
1655: restoreSelection = false;
1656: setSelectionToWidget(selection, reveal);
1657: }
1658: }
1659:
1660: /**
1661: * Parlays the given list of selected elements into selections on this
1662: * viewer's control.
1663: * <p>
1664: * Subclasses should override to set their selection based on the given list
1665: * of elements.
1666: * </p>
1667: *
1668: * @param l
1669: * list of selected elements (element type: <code>Object</code>)
1670: * or <code>null</code> if the selection is to be cleared
1671: * @param reveal
1672: * <code>true</code> if the selection is to be made visible,
1673: * and <code>false</code> otherwise
1674: */
1675: protected abstract void setSelectionToWidget(List l, boolean reveal);
1676:
1677: /**
1678: * Converts the selection to a <code>List</code> and calls
1679: * <code>setSelectionToWidget(List, boolean)</code>. The selection is
1680: * expected to be an <code>IStructuredSelection</code> of elements. If
1681: * not, the selection is cleared.
1682: * <p>
1683: * Subclasses do not typically override this method, but implement
1684: * <code>setSelectionToWidget(List, boolean)</code> instead.
1685: *
1686: * @param selection
1687: * an IStructuredSelection of elements
1688: * @param reveal
1689: * <code>true</code> to reveal the first element in the
1690: * selection, or <code>false</code> otherwise
1691: */
1692: protected void setSelectionToWidget(ISelection selection,
1693: boolean reveal) {
1694: if (selection instanceof IStructuredSelection) {
1695: setSelectionToWidget(((IStructuredSelection) selection)
1696: .toList(), reveal);
1697: } else {
1698: setSelectionToWidget((List) null, reveal);
1699: }
1700: }
1701:
1702: /**
1703: * Sets this viewer's sorter and triggers refiltering and resorting of this
1704: * viewer's element. Passing <code>null</code> turns sorting off.
1705: * <p>
1706: * It is recommended to use <code>setComparator()</code> instead.
1707: * </p>
1708: *
1709: * @param sorter
1710: * a viewer sorter, or <code>null</code> if none
1711: */
1712: public void setSorter(ViewerSorter sorter) {
1713: if (this .sorter != sorter) {
1714: this .sorter = sorter;
1715: refresh();
1716: }
1717: }
1718:
1719: /**
1720: * Sets this viewer's comparator to be used for sorting elements, and triggers refiltering and
1721: * resorting of this viewer's element. <code>null</code> turns sorting off.
1722: * To get the viewer's comparator, call <code>getComparator()</code>.
1723: * <p>
1724: * IMPORTANT: This method was introduced in 3.2. If a reference to this viewer object
1725: * is passed to clients who call <code>getSorter()<code>, null may be returned from
1726: * from that method even though the viewer is sorting its elements using the
1727: * viewer's comparator.
1728: * </p>
1729: *
1730: * @param comparator a viewer comparator, or <code>null</code> if none
1731: *
1732: * @since 3.2
1733: */
1734: public void setComparator(ViewerComparator comparator) {
1735: if (this .sorter != comparator) {
1736: this .sorter = comparator;
1737: refresh();
1738: }
1739: }
1740:
1741: /**
1742: * Configures whether this structured viewer uses an internal hash table to
1743: * speeds up the mapping between elements and SWT items. This must be called
1744: * before the viewer is given an input (via <code>setInput</code>).
1745: *
1746: * @param enable
1747: * <code>true</code> to enable hash lookup, and
1748: * <code>false</code> to disable it
1749: */
1750: public void setUseHashlookup(boolean enable) {
1751: Assert
1752: .isTrue(getInput() == null,
1753: "Can only enable the hash look up before input has been set");//$NON-NLS-1$
1754: if (enable) {
1755: elementMap = newHashtable(CustomHashtable.DEFAULT_CAPACITY);
1756: } else {
1757: elementMap = null;
1758: }
1759: }
1760:
1761: /**
1762: * Sets the comparer to use for comparing elements, or <code>null</code>
1763: * to use the default <code>equals</code> and <code>hashCode</code>
1764: * methods on the elements themselves.
1765: *
1766: * @param comparer
1767: * the comparer to use for comparing elements or
1768: * <code>null</code>
1769: */
1770: public void setComparer(IElementComparer comparer) {
1771: this .comparer = comparer;
1772: if (elementMap != null) {
1773: elementMap = new CustomHashtable(elementMap, comparer);
1774: }
1775: }
1776:
1777: /**
1778: * Hook for testing.
1779: * @param element
1780: * @return Widget
1781: */
1782: public Widget testFindItem(Object element) {
1783: return findItem(element);
1784: }
1785:
1786: /**
1787: * Hook for testing.
1788: * @param element
1789: * @return Widget[]
1790: * @since 3.2
1791: */
1792: public Widget[] testFindItems(Object element) {
1793: return findItems(element);
1794: }
1795:
1796: /**
1797: * Removes all elements from the map.
1798: * <p>
1799: * This method is internal to the framework; subclassers should not call
1800: * this method.
1801: * </p>
1802: */
1803: protected void unmapAllElements() {
1804: if (elementMap != null) {
1805: elementMap = newHashtable(CustomHashtable.DEFAULT_CAPACITY);
1806: }
1807: }
1808:
1809: /**
1810: * Removes the given element from the internal element to widget map. Does
1811: * nothing if mapping is disabled. If mapping is enabled, the given element
1812: * must be present.
1813: * <p>
1814: * This method is internal to the framework; subclassers should not call
1815: * this method.
1816: * </p>
1817: *
1818: * @param element
1819: * the element
1820: */
1821: protected void unmapElement(Object element) {
1822: if (elementMap != null) {
1823: elementMap.remove(element);
1824: }
1825: }
1826:
1827: /**
1828: * Removes the given association from the internal element to widget map.
1829: * Does nothing if mapping is disabled, or if the given element does not map
1830: * to the given item.
1831: * <p>
1832: * This method is internal to the framework; subclassers should not call
1833: * this method.
1834: * </p>
1835: *
1836: * @param element
1837: * the element
1838: * @param item the item to unmap
1839: * @since 2.0
1840: */
1841: protected void unmapElement(Object element, Widget item) {
1842: // double-check that the element actually maps to the given item before
1843: // unmapping it
1844: if (elementMap != null) {
1845: Object widgetOrWidgets = elementMap.get(element);
1846: if (widgetOrWidgets == null) {
1847: // item was not mapped, return
1848: return;
1849: } else if (widgetOrWidgets instanceof Widget) {
1850: if (item == widgetOrWidgets) {
1851: elementMap.remove(element);
1852: }
1853: } else {
1854: Widget[] widgets = (Widget[]) widgetOrWidgets;
1855: int indexOfItem = Arrays.asList(widgets).indexOf(item);
1856: if (indexOfItem == -1) {
1857: return;
1858: }
1859: int length = widgets.length;
1860: if (indexOfItem == 0) {
1861: if (length == 1) {
1862: elementMap.remove(element);
1863: } else {
1864: Widget[] updatedWidgets = new Widget[length - 1];
1865: System.arraycopy(widgets, 1, updatedWidgets, 0,
1866: length - 1);
1867: elementMap.put(element, updatedWidgets);
1868: }
1869: } else {
1870: Widget[] updatedWidgets = new Widget[length - 1];
1871: System.arraycopy(widgets, 0, updatedWidgets, 0,
1872: indexOfItem);
1873: System.arraycopy(widgets, indexOfItem + 1,
1874: updatedWidgets, indexOfItem, length
1875: - indexOfItem - 1);
1876: elementMap.put(element, updatedWidgets);
1877: }
1878: }
1879: }
1880: }
1881:
1882: /**
1883: * Updates the given elements' presentation when one or more of their
1884: * properties change. Only the given elements are updated.
1885: * <p>
1886: * This does not handle structural changes (e.g. addition or removal of
1887: * elements), and does not update any other related elements (e.g. child
1888: * elements). To handle structural changes, use the <code>refresh</code>
1889: * methods instead.
1890: * </p>
1891: * <p>
1892: * This should be called when an element has changed in the model, in order
1893: * to have the viewer accurately reflect the model. This method only affects
1894: * the viewer, not the model.
1895: * </p>
1896: * <p>
1897: * Specifying which properties are affected may allow the viewer to optimize
1898: * the update. For example, if the label provider is not affected by changes
1899: * to any of these properties, an update may not actually be required.
1900: * Specifying <code>properties</code> as <code>null</code> forces a full
1901: * update of the given elements.
1902: * </p>
1903: * <p>
1904: * If the viewer has a sorter which is affected by a change to one of the
1905: * properties, the elements' positions are updated to maintain the sort
1906: * order. Note that resorting does not happen if <code>properties</code>
1907: * is <code>null</code>.
1908: * </p>
1909: * <p>
1910: * If the viewer has a filter which is affected by a change to one of the
1911: * properties, elements may appear or disappear if the change affects
1912: * whether or not they are filtered out.
1913: * </p>
1914: *
1915: * @param elements
1916: * the elements
1917: * @param properties
1918: * the properties that have changed, or <code>null</code> to
1919: * indicate unknown
1920: */
1921: public void update(Object[] elements, String[] properties) {
1922: for (int i = 0; i < elements.length; ++i) {
1923: update(elements[i], properties);
1924: }
1925: }
1926:
1927: /**
1928: * Updates the given element's presentation when one or more of its
1929: * properties changes. Only the given element is updated.
1930: * <p>
1931: * This does not handle structural changes (e.g. addition or removal of
1932: * elements), and does not update any other related elements (e.g. child
1933: * elements). To handle structural changes, use the <code>refresh</code>
1934: * methods instead.
1935: * </p>
1936: * <p>
1937: * This should be called when an element has changed in the model, in order
1938: * to have the viewer accurately reflect the model. This method only affects
1939: * the viewer, not the model.
1940: * </p>
1941: * <p>
1942: * Specifying which properties are affected may allow the viewer to optimize
1943: * the update. For example, if the label provider is not affected by changes
1944: * to any of these properties, an update may not actually be required.
1945: * Specifying <code>properties</code> as <code>null</code> forces a full
1946: * update of the element.
1947: * </p>
1948: * <p>
1949: * If the viewer has a sorter which is affected by a change to one of the
1950: * properties, the element's position is updated to maintain the sort order.
1951: * Note that resorting does not happen if <code>properties</code> is
1952: * <code>null</code>.
1953: * </p>
1954: * <p>
1955: * If the viewer has a filter which is affected by a change to one of the
1956: * properties, the element may appear or disappear if the change affects
1957: * whether or not the element is filtered out.
1958: * </p>
1959: *
1960: * @param element
1961: * the element
1962: * @param properties
1963: * the properties that have changed, or <code>null</code> to
1964: * indicate unknown
1965: */
1966: public void update(Object element, String[] properties) {
1967: Assert.isNotNull(element);
1968: Widget[] items = findItems(element);
1969:
1970: for (int i = 0; i < items.length; i++) {
1971: internalUpdate(items[i], element, properties);
1972: }
1973: }
1974:
1975: /**
1976: * Updates the given element's presentation when one or more of its
1977: * properties changes. Only the given element is updated.
1978: * <p>
1979: * EXPERIMENTAL. Not to be used except by JDT.
1980: * This method was added to support JDT's explorations
1981: * into grouping by working sets, which requires viewers to support multiple
1982: * equal elements. See bug 76482 for more details. This support will
1983: * likely be removed in Eclipse 3.3 in favor of proper support for
1984: * multiple equal elements (which was implemented for AbtractTreeViewer in 3.2).
1985: * </p>
1986: * @param widget
1987: * the widget for the element
1988: * @param element
1989: * the element
1990: * @param properties
1991: * the properties that have changed, or <code>null</code> to
1992: * indicate unknown
1993: */
1994: protected void internalUpdate(Widget widget, Object element,
1995: String[] properties) {
1996: boolean needsRefilter = false;
1997: if (properties != null) {
1998: for (int i = 0; i < properties.length; ++i) {
1999: needsRefilter = needsRefilter(element, properties[i]);
2000: if (needsRefilter) {
2001: break;
2002: }
2003: }
2004: }
2005: if (needsRefilter) {
2006: preservingSelection(new Runnable() {
2007: public void run() {
2008: internalRefresh(getRoot());
2009: }
2010: });
2011: return;
2012: }
2013:
2014: boolean needsUpdate;
2015: if (properties == null) {
2016: needsUpdate = true;
2017: } else {
2018: needsUpdate = false;
2019: IBaseLabelProvider labelProvider = getLabelProvider();
2020: for (int i = 0; i < properties.length; ++i) {
2021: needsUpdate = labelProvider.isLabelProperty(element,
2022: properties[i]);
2023: if (needsUpdate) {
2024: break;
2025: }
2026: }
2027: }
2028: if (needsUpdate) {
2029: updateItem(widget, element);
2030: }
2031: }
2032:
2033: /**
2034: * Copies attributes of the given element into the given widget.
2035: * <p>
2036: * This method is internal to the framework; subclassers should not call
2037: * this method. Calls <code>doUpdateItem(widget, element, true)</code>.
2038: * </p>
2039: *
2040: * @param widget
2041: * the widget
2042: * @param element
2043: * the element
2044: */
2045: protected final void updateItem(Widget widget, Object element) {
2046: SafeRunnable.run(new UpdateItemSafeRunnable(widget, element,
2047: true));
2048: }
2049:
2050: /**
2051: * Updates the selection of this viewer.
2052: * <p>
2053: * This framework method should be called when the selection in the viewer
2054: * widget changes.
2055: * </p>
2056: * <p>
2057: * The default implementation of this method notifies all selection change
2058: * listeners recorded in an internal state variable. Overriding this method
2059: * is generally not required; however, if overriding in a subclass,
2060: * <code>super.updateSelection</code> must be invoked.
2061: * </p>
2062: *
2063: * @param selection
2064: * the selection, or <code>null</code> if none
2065: */
2066: protected void updateSelection(ISelection selection) {
2067: SelectionChangedEvent event = new SelectionChangedEvent(this ,
2068: selection);
2069: fireSelectionChanged(event);
2070: }
2071:
2072: /**
2073: * Returns whether this structured viewer is configured to use an internal
2074: * map to speed up the mapping between elements and SWT items.
2075: * <p>
2076: * The default implementation of this framework method checks whether the
2077: * internal map has been initialized.
2078: * </p>
2079: *
2080: * @return <code>true</code> if the element map is enabled, and
2081: * <code>false</code> if disabled
2082: */
2083: protected boolean usingElementMap() {
2084: return elementMap != null;
2085: }
2086:
2087: /* (non-Javadoc)
2088: * @see org.eclipse.jface.viewers.ContentViewer#setLabelProvider(org.eclipse.jface.viewers.IBaseLabelProvider)
2089: */
2090: public void setLabelProvider(IBaseLabelProvider labelProvider) {
2091: if (labelProvider instanceof IColorProvider
2092: || labelProvider instanceof IFontProvider) {
2093: colorAndFontCollector = new ColorAndFontCollectorWithProviders(
2094: labelProvider);
2095: } else {
2096: colorAndFontCollector = new ColorAndFontCollector();
2097: }
2098: super .setLabelProvider(labelProvider);
2099:
2100: }
2101:
2102: /**
2103: * Build a label up for the element using the supplied label provider.
2104: * @param updateLabel The ViewerLabel to collect the result in
2105: * @param element The element being decorated.
2106: */
2107: protected void buildLabel(ViewerLabel updateLabel, Object element) {
2108:
2109: if (getLabelProvider() instanceof IViewerLabelProvider) {
2110: IViewerLabelProvider itemProvider = (IViewerLabelProvider) getLabelProvider();
2111: itemProvider.updateLabel(updateLabel, element);
2112:
2113: colorAndFontCollector.setUsedDecorators();
2114:
2115: if (updateLabel.hasNewBackground()) {
2116: colorAndFontCollector.setBackground(updateLabel
2117: .getBackground());
2118: }
2119:
2120: if (updateLabel.hasNewForeground()) {
2121: colorAndFontCollector.setForeground(updateLabel
2122: .getForeground());
2123: }
2124:
2125: if (updateLabel.hasNewFont()) {
2126: colorAndFontCollector.setFont(updateLabel.getFont());
2127: }
2128: return;
2129:
2130: }
2131:
2132: if (getLabelProvider() instanceof ILabelProvider) {
2133: ILabelProvider labelProvider = (ILabelProvider) getLabelProvider();
2134: updateLabel.setText(labelProvider.getText(element));
2135: updateLabel.setImage(labelProvider.getImage(element));
2136: }
2137:
2138: }
2139:
2140: /**
2141: * Build a label up for the element using the supplied label provider.
2142: * @param updateLabel The ViewerLabel to collect the result in
2143: * @param element The element being decorated.
2144: * @param labelProvider ILabelProvider the labelProvider for the receiver.
2145: */
2146: void buildLabel(ViewerLabel updateLabel, Object element,
2147: IViewerLabelProvider labelProvider) {
2148:
2149: labelProvider.updateLabel(updateLabel, element);
2150:
2151: colorAndFontCollector.setUsedDecorators();
2152:
2153: if (updateLabel.hasNewBackground()) {
2154: colorAndFontCollector.setBackground(updateLabel
2155: .getBackground());
2156: }
2157:
2158: if (updateLabel.hasNewForeground()) {
2159: colorAndFontCollector.setForeground(updateLabel
2160: .getForeground());
2161: }
2162:
2163: if (updateLabel.hasNewFont()) {
2164: colorAndFontCollector.setFont(updateLabel.getFont());
2165: }
2166:
2167: }
2168:
2169: /**
2170: * Build a label up for the element using the supplied label provider.
2171: * @param updateLabel The ViewerLabel to collect the result in
2172: * @param elementPath The path of the element being decorated.
2173: * @param labelProvider ILabelProvider the labelProvider for the receiver.
2174: */
2175: void buildLabel(ViewerLabel updateLabel, TreePath elementPath,
2176: ITreePathLabelProvider labelProvider) {
2177:
2178: labelProvider.updateLabel(updateLabel, elementPath);
2179:
2180: colorAndFontCollector.setUsedDecorators();
2181:
2182: if (updateLabel.hasNewBackground()) {
2183: colorAndFontCollector.setBackground(updateLabel
2184: .getBackground());
2185: }
2186:
2187: if (updateLabel.hasNewForeground()) {
2188: colorAndFontCollector.setForeground(updateLabel
2189: .getForeground());
2190: }
2191:
2192: if (updateLabel.hasNewFont()) {
2193: colorAndFontCollector.setFont(updateLabel.getFont());
2194: }
2195:
2196: }
2197:
2198: /**
2199: * Build a label up for the element using the supplied label provider.
2200: * @param updateLabel The ViewerLabel to collect the result in
2201: * @param element The element being decorated.
2202: * @param labelProvider ILabelProvider the labelProvider for the receiver.
2203: */
2204: void buildLabel(ViewerLabel updateLabel, Object element,
2205: ILabelProvider labelProvider) {
2206: updateLabel.setText(labelProvider.getText(element));
2207: updateLabel.setImage(labelProvider.getImage(element));
2208: }
2209:
2210: /**
2211: * Get the ColorAndFontCollector for the receiver.
2212: * @return ColorAndFontCollector
2213: * @since 3.1
2214: */
2215: protected ColorAndFontCollector getColorAndFontCollector() {
2216: return colorAndFontCollector;
2217: }
2218:
2219: protected void handleDispose(DisposeEvent event) {
2220: super .handleDispose(event);
2221: sorter = null;
2222: comparer = null;
2223: if (filters != null)
2224: filters.clear();
2225: elementMap = newHashtable(1);
2226: openListeners.clear();
2227: doubleClickListeners.clear();
2228: colorAndFontCollector.clear();
2229: postSelectionChangedListeners.clear();
2230: }
2231:
2232: }
|