Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/eclipse/jface/viewers/StructuredViewer.java


1   /*******************************************************************************
2    * Copyright (c) 2000, 2004 IBM Corporation and others.
3    * All rights reserved. This program and the accompanying materials 
4    * are made available under the terms of the Common Public License v1.0
5    * which accompanies this distribution, and is available at
6    * http://www.eclipse.org/legal/cpl-v10.html
7    * 
8    * Contributors:
9    *     IBM Corporation - initial API and implementation
10   *******************************************************************************/
11  package org.eclipse.jface.viewers;
12  
13  import java.util.ArrayList;
14  import java.util.Iterator;
15  import java.util.List;
16  
17  import org.eclipse.core.runtime.Platform;
18  
19  import org.eclipse.swt.dnd.DragSource;
20  import org.eclipse.swt.dnd.DragSourceListener;
21  import org.eclipse.swt.dnd.DropTarget;
22  import org.eclipse.swt.dnd.DropTargetListener;
23  import org.eclipse.swt.dnd.Transfer;
24  import org.eclipse.swt.events.SelectionAdapter;
25  import org.eclipse.swt.events.SelectionEvent;
26  import org.eclipse.swt.events.SelectionListener;
27  import org.eclipse.swt.widgets.Control;
28  import org.eclipse.swt.widgets.Item;
29  import org.eclipse.swt.widgets.Widget;
30  
31  import org.eclipse.jface.util.Assert;
32  import org.eclipse.jface.util.IOpenEventListener;
33  import org.eclipse.jface.util.ListenerList;
34  import org.eclipse.jface.util.OpenStrategy;
35  import org.eclipse.jface.util.SafeRunnable;
36  
37  /**
38   * Abstract base implementation for structure-oriented viewers (trees, lists,
39   * tables). Supports custom sorting, filtering, and rendering.
40   * <p>
41   * Any number of viewer filters can be added to this viewer (using
42   * <code>addFilter</code>). When the viewer receives an update, it asks each
43   * of its filters if it is out of date, and refilters elements as required.
44   * </p>
45   * 
46   * @see ViewerFilter
47   * @see ViewerSorter
48   */
49  public abstract class StructuredViewer extends ContentViewer
50      implements
51        IPostSelectionProvider {
52  
53    /**
54     * A map from the viewer's model elements to SWT widgets. (key type:
55     * <code>Object</code>, value type: <code>Widget</code>).
56     * <code>null</code> means that the element map is disabled.
57     */
58    private CustomHashtable elementMap;
59  
60    /**
61     * The comparer to use for comparing elements, or <code>null</code> to use
62     * the default <code>equals</code> and <code>hashCode</code> methods on
63     * the element itself.
64     */
65    private IElementComparer comparer;
66  
67    /**
68     * This viewer's sorter. <code>null</code> means there is no sorter.
69     */
70    private ViewerSorter sorter;
71  
72    /**
73     * This viewer's filters (element type: <code>ViewerFilter</code>).
74     * <code>null</code> means there are no filters.
75     */
76    private List filters;
77  
78    /**
79     * Indicates whether a selection change is in progress on this viewer.
80     * 
81     * @see #setSelection(ISelection, boolean)
82     */
83    private boolean inChange;
84  
85    /**
86     * Used while a selection change is in progress on this viewer to indicates
87     * whether the selection should be restored.
88     * 
89     * @see #setSelection(ISelection, boolean)
90     */
91    private boolean restoreSelection;
92  
93    /**
94     * List of double-click state listeners (element type:
95     * <code>IDoubleClickListener</code>).
96     * 
97     * @see #fireDoubleClick
98     */
99    private ListenerList doubleClickListeners = new ListenerList(1);
100   /**
101    * List of open listeners (element type:
102    * <code>ISelectionActivateListener</code>).
103    * 
104    * @see #fireOpen
105    */
106   private ListenerList openListeners = new ListenerList(1);
107   /**
108    * List of post selection listeners (element type:
109    * <code>ISelectionActivateListener</code>).
110    * 
111    * @see #firePostSelectionChanged
112    */
113   private ListenerList postSelectionChangedListeners = new ListenerList(1);
114 
115   /**
116    * The safe runnable used to update an item.
117    */
118   class UpdateItemSafeRunnable extends SafeRunnable {
119     private Widget widget;
120     private Object element;
121     private boolean fullMap;
122     UpdateItemSafeRunnable(Widget widget, Object element, boolean fullMap) {
123       this.widget = widget;
124       this.element = element;
125       this.fullMap = fullMap;
126     }
127     public void run() {
128       doUpdateItem(widget, element, fullMap);
129     }
130   }
131 
132   /**
133    * Creates a structured element viewer. The viewer has no input, no content
134    * provider, a default label provider, no sorter, and no filters.
135    */
136   protected StructuredViewer() {
137     // do nothing
138   }
139   /**
140    * Adds a listener for double-clicks in this viewer. Has no effect if an
141    * identical listener is already registered.
142    * 
143    * @param listener
144    *            a double-click listener
145    */
146   public void addDoubleClickListener(IDoubleClickListener listener) {
147     doubleClickListeners.add(listener);
148   }
149   /**
150    * Adds a listener for selection-open in this viewer. Has no effect if an
151    * identical listener is already registered.
152    * 
153    * @param listener
154    *            a double-click listener
155    */
156   public void addOpenListener(IOpenListener listener) {
157     openListeners.add(listener);
158   }
159 
160   /*
161    * (non-Javadoc) Method declared on IPostSelectionProvider.
162    */
163   public void addPostSelectionChangedListener(
164       ISelectionChangedListener listener) {
165     postSelectionChangedListeners.add(listener);
166   }
167   /**
168    * Adds support for dragging items out of this viewer via a user
169    * drag-and-drop operation.
170    * 
171    * @param operations
172    *            a bitwise OR of the supported drag and drop operation types (
173    *            <code>DROP_COPY</code>,<code>DROP_LINK</code>, and
174    *            <code>DROP_MOVE</code>)
175    * @param transferTypes
176    *            the transfer types that are supported by the drag operation
177    * @param listener
178    *            the callback that will be invoked to set the drag data and to
179    *            cleanup after the drag and drop operation finishes
180    * @see org.eclipse.swt.dnd.DND
181    */
182   public void addDragSupport(int operations, Transfer[] transferTypes,
183       DragSourceListener listener) {
184 
185     Control myControl = getControl();
186     final DragSource dragSource = new DragSource(myControl, operations);
187     dragSource.setTransfer(transferTypes);
188     dragSource.addDragListener(listener);
189   }
190   /**
191    * Adds support for dropping items into this viewer via a user drag-and-drop
192    * operation.
193    * 
194    * @param operations
195    *            a bitwise OR of the supported drag and drop operation types (
196    *            <code>DROP_COPY</code>,<code>DROP_LINK</code>, and
197    *            <code>DROP_MOVE</code>)
198    * @param transferTypes
199    *            the transfer types that are supported by the drop operation
200    * @param listener
201    *            the callback that will be invoked after the drag and drop
202    *            operation finishes
203    * @see org.eclipse.swt.dnd.DND
204    */
205   public void addDropSupport(int operations, Transfer[] transferTypes,
206       final DropTargetListener listener) {
207     Control control = getControl();
208     DropTarget dropTarget = new DropTarget(control, operations);
209     dropTarget.setTransfer(transferTypes);
210     dropTarget.addDropListener(listener);
211   }
212   /**
213    * Adds the given filter to this viewer, and triggers refiltering and
214    * resorting of the elements.
215    * 
216    * @param filter
217    *            a viewer filter
218    */
219   public void addFilter(ViewerFilter filter) {
220     if (filters == null)
221       filters = new ArrayList();
222     filters.add(filter);
223     refresh();
224   }
225 
226   /**
227    * Asserts that the given array of elements is itself non- <code>null</code>
228    * and contains no <code>null</code> elements.
229    * 
230    * @param elements
231    *            the array to check
232    */
233   protected void assertElementsNotNull(Object[] elements) {
234     Assert.isNotNull(elements);
235     for (int i = 0, n = elements.length; i < n; ++i) {
236       Assert.isNotNull(elements[i]);
237     }
238   }
239 
240   /**
241    * Associates the given element with the given widget. Sets the given item's
242    * data to be the element, and maps the element to the item in the element
243    * map (if enabled).
244    * 
245    * @param element
246    *            the element
247    * @param item
248    *            the widget
249    */
250   protected void associate(Object element, Item item) {
251     Object data = item.getData();
252     if (data != element) {
253       if (data != null)
254         disassociate(item);
255       item.setData(element);
256     }
257     // Always map the element, even if data == element,
258     // since unmapAllElements() can leave the map inconsistent
259     // See bug 2741 for details.
260     mapElement(element, item);
261   }
262   /**
263    * Disassociates the given SWT item from its corresponding element. Sets the
264    * item's data to <code>null</code> and removes the element from the
265    * element map (if enabled).
266    * 
267    * @param item
268    *            the widget
269    */
270   protected void disassociate(Item item) {
271     Object element = item.getData();
272     Assert.isNotNull(element);
273     //Clear the map before we clear the data
274     unmapElement(element, item);
275     item.setData(null);
276   }
277   /**
278    * Returns the widget in this viewer's control which represents the given
279    * element if it is the viewer's input.
280    * <p>
281    * This method is internal to the framework; subclassers should not call
282    * this method.
283    * </p>
284    * 
285    * @param element
286    * @return the corresponding widget, or <code>null</code> if none
287    */
288   protected abstract Widget doFindInputItem(Object element);
289   /**
290    * Returns the widget in this viewer's control which represent the given
291    * element. This method searchs all the children of the input element.
292    * <p>
293    * This method is internal to the framework; subclassers should not call
294    * this method.
295    * </p>
296    * 
297    * @param element
298    * @return the corresponding widget, or <code>null</code> if none
299    */
300   protected abstract Widget doFindItem(Object element);
301   /**
302    * Copies the attributes of the given element into the given SWT item. The
303    * element map is updated according to the value of <code>fullMap</code>.
304    * If <code>fullMap</code> is <code>true</code> then the current mapping
305    * from element to widgets is removed and the new mapping is added. If
306    * fullmap is <code>false</code> then only the new map gets installed.
307    * Installing only the new map is necessary in cases where only the order of
308    * elements changes but not the set of elements.
309    * <p>
310    * This method is internal to the framework; subclassers should not call
311    * this method.
312    * </p>
313    * 
314    * @param item
315    * @param element element
316    * @param fullMap
317    *            <code>true</code> if mappings are added and removed, and
318    *            <code>false</code> if only the new map gets installed
319    */
320   protected abstract void doUpdateItem(Widget item, Object element,
321       boolean fullMap);
322 
323   /**
324    * Compares two elements for equality. Uses the element comparer if one has
325    * been set, otherwise uses the default <code>equals</code> method on the
326    * elements themselves.
327    * 
328    * @param elementA
329    *            the first element
330    * @param elementB
331    *            the second element
332    * @return whether elementA is equal to elementB
333    */
334   protected boolean equals(Object elementA, Object elementB) {
335     if (comparer == null)
336       return elementA == null ? elementB == null : elementA
337           .equals(elementB);
338     else
339       return elementA == null ? elementB == null : comparer.equals(
340           elementA, elementB);
341   }
342 
343   /**
344    * Returns the result of running the given elements through the filters.
345    * 
346    * @param elements
347    *            the elements to filter
348    * @return only the elements which all filters accept
349    */
350   protected Object[] filter(Object[] elements) {
351     if (filters != null) {
352       ArrayList filtered = new ArrayList(elements.length);
353       Object root = getRoot();
354       for (int i = 0; i < elements.length; i++) {
355         boolean add = true;
356         for (int j = 0; j < filters.size(); j++) {
357           add = ((ViewerFilter) filters.get(j)).select(this, root,
358               elements[i]);
359           if (!add)
360             break;
361         }
362         if (add)
363           filtered.add(elements[i]);
364       }
365       return filtered.toArray();
366     }
367     return elements;
368   }
369   /**
370    * Finds the widget which represents the given element.
371    * <p>
372    * The default implementation of this method tries first to find the widget
373    * for the given element assuming that it is the viewer's input; this is
374    * done by calling <code>doFindInputItem</code>. If it is not found
375    * there, it is looked up in the internal element map provided that this
376    * feature has been enabled. If the element map is disabled, the widget is
377    * found via <code>doFindInputItem</code>.
378    * </p>
379    * 
380    * @param element
381    *            the element
382    * @return the corresponding widget, or <code>null</code> if none
383    */
384   protected final Widget findItem(Object element) {
385     Widget result = doFindInputItem(element);
386     if (result != null)
387       return result;
388     // if we have an element map use it, otherwise search for the item.
389     if (elementMap != null)
390       return (Widget) elementMap.get(element);
391     return doFindItem(element);
392   }
393   /**
394    * Notifies any double-click listeners that a double-click has been
395    * received. Only listeners registered at the time this method is called are
396    * notified.
397    * 
398    * @param event
399    *            a double-click event
400    * 
401    * @see IDoubleClickListener#doubleClick
402    */
403   protected void fireDoubleClick(final DoubleClickEvent event) {
404     Object[] listeners = doubleClickListeners.getListeners();
405     for (int i = 0; i < listeners.length; ++i) {
406       final IDoubleClickListener l = (IDoubleClickListener) listeners[i];
407       Platform.run(new SafeRunnable() {
408         public void run() {
409           l.doubleClick(event);
410         }
411       });
412     }
413   }
414   /**
415    * Notifies any open event listeners that a open event has been received.
416    * Only listeners registered at the time this method is called are notified.
417    * 
418    * @param event
419    *            a double-click event
420    * 
421    * @see IOpenListener#open(OpenEvent)
422    */
423   protected void fireOpen(final OpenEvent event) {
424     Object[] listeners = openListeners.getListeners();
425     for (int i = 0; i < listeners.length; ++i) {
426       final IOpenListener l = (IOpenListener) listeners[i];
427       Platform.run(new SafeRunnable() {
428         public void run() {
429           l.open(event);
430         }
431       });
432     }
433   }
434   /**
435    * Notifies any post selection listeners that a post selection event has
436    * been received. Only listeners registered at the time this method is
437    * called are notified.
438    * 
439    * @param event
440    *            a selection changed event
441    * 
442    * @see #addPostSelectionChangedListener(ISelectionChangedListener)
443    */
444   protected void firePostSelectionChanged(final SelectionChangedEvent event) {
445     Object[] listeners = postSelectionChangedListeners.getListeners();
446     for (int i = 0; i < listeners.length; ++i) {
447       final ISelectionChangedListener l = (ISelectionChangedListener) listeners[i];
448       Platform.run(new SafeRunnable() {
449         public void run() {
450           l.selectionChanged(event);
451         }
452       });
453     }
454   }
455 
456   /**
457    * Returns the comparator to use for comparing elements, or
458    * <code>null</code> if none has been set.
459    *           
460    * @return IElementComparer  the comparator to use for comparing elements or
461    *            <code>null</code>
462    */
463   public IElementComparer getComparer() {
464     return comparer;
465   }
466 
467   /**
468    * Returns the filtered array of children of the given element. The
469    * resulting array must not be modified, as it may come directly from the
470    * model's internal state.
471    * 
472    * @param parent
473    *            the parent element
474    * @return a filtered array of child elements
475    */
476   protected Object[] getFilteredChildren(Object parent) {
477     Object[] result = getRawChildren(parent);
478     if (filters != null) {
479       for (Iterator iter = filters.iterator(); iter.hasNext();) {
480         ViewerFilter f = (ViewerFilter) iter.next();
481         result = f.filter(this, parent, result);
482       }
483     }
484     return result;
485   }
486   /**
487    * Returns this viewer's filters.
488    * 
489    * @return an array of viewer filters
490    */
491   public ViewerFilter[] getFilters() {
492     if (filters == null)
493       return new ViewerFilter[0];
494     ViewerFilter[] result = new ViewerFilter[filters.size()];
495     filters.toArray(result);
496     return result;
497   }
498   /**
499    * Returns the item at the given display-relative coordinates, or
500    * <code>null</code> if there is no item at that location.
501    * <p>
502    * The default implementation of this method returns <code>null</code>.
503    * </p>
504    * 
505    * @param x
506    *            horizontal coordinate
507    * @param y
508    *            vertical coordinate
509    * @return the item, or <code>null</code> if there is no item at the given
510    *         coordinates
511    */
512   protected Item getItem(int x, int y) {
513     return null;
514   }
515   /**
516    * Returns the children of the given parent without sorting and filtering
517    * them. The resulting array must not be modified, as it may come directly
518    * from the model's internal state.
519    * <p>
520    * Returns an empty array if the given parent is <code>null</code>.
521    * </p>
522    * 
523    * @param parent
524    *            the parent element
525    * @return the child elements
526    */
527   protected Object[] getRawChildren(Object parent) {
528     Object[] result = null;
529     if (parent != null) {
530       IStructuredContentProvider cp = (IStructuredContentProvider) getContentProvider();
531       if (cp != null) {
532         result = cp.getElements(parent);
533         assertElementsNotNull(result);
534       }
535     }
536     return (result != null) ? result : new Object[0];
537   }
538   /**
539    * Returns the root element.
540    * <p>
541    * The default implementation of this framework method forwards to
542    * <code>getInput</code>. Override if the root element is different from
543    * the viewer's input element.
544    * </p>
545    * 
546    * @return the root element, or <code>null</code> if none
547    */
548   protected Object getRoot() {
549     return getInput();
550   }
551   /**
552    * The <code>StructuredViewer</code> implementation of this method returns
553    * the result as an <code>IStructuredSelection</code>.
554    * <p>
555    * Subclasses do not typically override this method, but implement
556    * <code>getSelectionFromWidget(List)</code> instead.
557    * <p>
558    * @return ISelection
559    */
560   public ISelection getSelection() {
561     Control control = getControl();
562     if (control == null || control.isDisposed()) {
563       return StructuredSelection.EMPTY;
564     }
565     List list = getSelectionFromWidget();
566     return new StructuredSelection(list);
567   }
568   /**
569    * Retrieves the selection, as a <code>List</code>, from the underlying
570    * widget.
571    * 
572    * @return the list of selected elements
573    */
574   protected abstract List getSelectionFromWidget();
575   /**
576    * Returns the sorted and filtered set of children of the given element. The
577    * resulting array must not be modified, as it may come directly from the
578    * model's internal state.
579    * 
580    * @param parent
581    *            the parent element
582    * @return a sorted and filtered array of child elements
583    */
584   protected Object[] getSortedChildren(Object parent) {
585     Object[] result = getFilteredChildren(parent);
586     if (sorter != null) {
587       // be sure we're not modifying the original array from the model
588       result = (Object[]) result.clone();
589       sorter.sort(this, result);
590     }
591     return result;
592   }
593   /**
594    * Returns this viewer's sorter, or <code>null</code> if it does not have
595    * one.
596    * 
597    * @return a viewer sorter, or <code>null</code> if none
598    */
599   public ViewerSorter getSorter() {
600     return sorter;
601   }
602   /**
603    * Handles a double-click select event from the widget.
604    * <p>
605    * This method is internal to the framework; subclassers should not call
606    * this method.
607    * </p>
608    * 
609    * @param event
610    *            the SWT selection event
611    */
612   protected void handleDoubleSelect(SelectionEvent event) {
613     // handle case where an earlier selection listener disposed the control.
614     Control control = getControl();
615     if (control != null && !control.isDisposed()) {
616       ISelection selection = getSelection();
617       updateSelection(selection);
618       fireDoubleClick(new DoubleClickEvent(this, selection));
619     }
620   }
621   /**
622    * Handles an open event from the OpenStrategy.
623    * <p>
624    * This method is internal to the framework; subclassers should not call
625    * this method.
626    * </p>
627    * 
628    * @param event
629    *            the SWT selection event
630    */
631   protected void handleOpen(SelectionEvent event) {
632     Control control = getControl();
633     if (control != null && !control.isDisposed()) {
634       ISelection selection = getSelection();
635       fireOpen(new OpenEvent(this, selection));
636     }
637   }
638   /**
639    * Handles an invalid selection.
640    * <p>
641    * This framework method is called if a model change picked up by a viewer
642    * results in an invalid selection. For instance if an element contained in
643    * the selection has been removed from the viewer, the viewer is free to
644    * either remove the element from the selection or to pick another element
645    * as its new selection. The default implementation of this method calls
646    * <code>updateSelection</code>. Subclasses may override it to implement
647    * a different strategy for picking a new selection when the old selection
648    * becomes invalid.
649    * </p>
650    * 
651    * @param invalidSelection
652    *            the selection before the viewer was updated
653    * @param newSelection
654    *            the selection after the update, or <code>null</code> if none
655    */
656   protected void handleInvalidSelection(ISelection invalidSelection,
657       ISelection newSelection) {
658     updateSelection(newSelection);
659     SelectionChangedEvent event = new SelectionChangedEvent(this,
660         newSelection);
661     firePostSelectionChanged(event);
662   }
663   /**
664    * The <code>StructuredViewer</code> implementation of this
665    * <code>ContentViewer</code> method calls <code>update</code> if the
666    * event specifies that the label of a given element has changed, otherwise
667    * it calls super. Subclasses may reimplement or extend.
668    * </p>
669    * @param event the event that generated this update
670    */
671   protected void handleLabelProviderChanged(LabelProviderChangedEvent event) {
672     Object[] elements = event.getElements();
673     if (elements != null) {
674       update(elements, null);
675     } else {
676       super.handleLabelProviderChanged(event);
677     }
678   }
679   /**
680    * Handles a select event from the widget.
681    * <p>
682    * This method is internal to the framework; subclassers should not call
683    * this method.
684    * </p>
685    * 
686    * @param event
687    *            the SWT selection event
688    */
689   protected void handleSelect(SelectionEvent event) {
690     // handle case where an earlier selection listener disposed the control.
691     Control control = getControl();
692     if (control != null && !control.isDisposed()) {
693       updateSelection(getSelection());
694     }
695   }
696   /**
697    * Handles a post select event from the widget.
698    * <p>
699    * This method is internal to the framework; subclassers should not call
700    * this method.
701    * </p>
702    * 
703    * @param e the SWT selection event
704    */
705   protected void handlePostSelect(SelectionEvent e) {
706     SelectionChangedEvent event = new SelectionChangedEvent(this,
707         getSelection());
708     firePostSelectionChanged(event);
709   }
710   /*
711    * (non-Javadoc) Method declared on Viewer.
712    */
713   protected void hookControl(Control control) {
714     super.hookControl(control);
715     OpenStrategy handler = new OpenStrategy(control);
716     handler.addSelectionListener(new SelectionListener() {
717       public void widgetSelected(SelectionEvent e) {
718         handleSelect(e);
719       }
720       public void widgetDefaultSelected(SelectionEvent e) {
721         handleDoubleSelect(e);
722       }
723     });
724     handler.addPostSelectionListener(new SelectionAdapter() {
725       public void widgetSelected(SelectionEvent e) {
726         handlePostSelect(e);
727       }
728     });
729     handler.addOpenListener(new IOpenEventListener() {
730       public void handleOpen(SelectionEvent e) {
731         StructuredViewer.this.handleOpen(e);
732       }
733     });
734   }
735   /**
736    * Returns whether this viewer has any filters.
737    * @return boolean
738    */
739   protected boolean hasFilters() {
740     return filters != null && filters.size() > 0;
741   }
742   /**
743    * Refreshes this viewer starting at the given element.
744    * 
745    * @param element
746    *            the element
747    */
748   protected abstract void internalRefresh(Object element);
749 
750   /**
751    * Refreshes this viewer starting at the given element. Labels are updated
752    * as described in <code>refresh(boolean updateLabels)</code>.
753    * <p>
754    * The default implementation simply calls
755    * <code>internalRefresh(element)</code>, ignoring
756    * <code>updateLabels</code>.
757    * <p>
758    * If this method is overridden to do the actual refresh, then
759    * <code>internalRefresh(Object element)</code> should simply call
760    * <code>internalRefresh(element, true)</code>.
761    * 
762    * @param element
763    *            the element
764    * @param updateLabels
765    *            <code>true</code> to update labels for existing elements,
766    *            <code>false</code> to only update labels as needed, assuming
767    *            that labels for existing elements are unchanged.
768    * 
769    * @since 2.0
770    */
771   protected void internalRefresh(Object element, boolean updateLabels) {
772     internalRefresh(element);
773   }
774 
775   /**
776    * Adds the element item pair to the element map.
777    * <p>
778    * This method is internal to the framework; subclassers should not call
779    * this method.
780    * </p>
781    * 
782    * @param element
783    *            the element
784    * @param item
785    *            the corresponding widget
786    */
787   protected void mapElement(Object element, Widget item) {
788     if (elementMap != null)
789       elementMap.put(element, item);
790   }
791   /**
792    * Determines whether a change to the given property of the given element
793    * would require refiltering and/or resorting.
794    * <p>
795    * This method is internal to the framework; subclassers should not call
796    * this method.
797    * </p>
798    * 
799    * @param element
800    *            the element
801    * @param property
802    *            the property
803    * @return <code>true</code> if refiltering is required, and
804    *         <code>false</code> otherwise
805    */
806   protected boolean needsRefilter(Object element, String property) {
807     if (sorter != null && sorter.isSorterProperty(element, property))
808       return true;
809 
810     if (filters != null) {
811       for (int i = 0, n = filters.size(); i < n; ++i) {
812         ViewerFilter filter = (ViewerFilter) filters.get(i);
813         if (filter.isFilterProperty(element, property))
814           return true;
815       }
816     }
817     return false;
818   }
819 
820   /**
821    * Returns a new hashtable using the given capacity and this viewer's element comparer.
822    * 
823    * @param capacity the initial capacity of the hashtable
824    * @return a new hashtable
825    * 
826    * @since 3.0
827    */
828   CustomHashtable newHashtable(int capacity) {
829       return new CustomHashtable(capacity, getComparer());
830   }
831   
832   /**
833    * Attempts to preserves the current selection across a run of the given
834    * code.
835    * <p>
836    * The default implementation of this method:
837    * <ul>
838    * <li>discovers the old selection (via <code>getSelection</code>)</li>
839    * <li>runs the given runnable</li>
840    * <li>attempts to restore the old selection (using
841    * <code>setSelectionToWidget</code></li>
842    * <li>rediscovers the resulting selection (via <code>getSelection</code>)
843    * </li>
844    * <li>calls <code>handleInvalidSelection</code> if the selection did not
845    * take</li>
846    * <li>calls <code>postUpdateHook</code></li>
847    * </ul>
848    * </p>
849    * 
850    * @param updateCode
851    *            the code to run
852    */
853   protected void preservingSelection(Runnable updateCode) {
854 
855     ISelection oldSelection = null;
856     try {
857       // preserve selection
858       oldSelection = getSelection();
859       inChange = restoreSelection = true;
860 
861       // perform the update
862       updateCode.run();
863 
864     } finally {
865       inChange = false;
866 
867       // restore selection
868       if (restoreSelection)
869         setSelectionToWidget(oldSelection, false);
870 
871       // send out notification if old and new differ
872       ISelection newSelection = getSelection();
873       if (!newSelection.equals(oldSelection))
874         handleInvalidSelection(oldSelection, newSelection);
875     }
876   }
877   /*
878    * Non-Javadoc. Method declared on Viewer.
879    */
880   public void refresh() {
881     refresh(getRoot());
882   }
883 
884   /**
885    * Refreshes this viewer with information freshly obtained from this
886    * viewer's model. If <code>updateLabels</code> is <code>true</code>
887    * then labels for otherwise unaffected elements are updated as well.
888    * Otherwise, it assumes labels for existing elements are unchanged, and
889    * labels are only obtained as needed (for example, for new elements).
890    * <p>
891    * Calling <code>refresh(true)</code> has the same effect as
892    * <code>refresh()</code>.
893    * <p>
894    * Note that the implementation may still obtain labels for existing
895    * elements even if <code>updateLabels</code> is false. The intent is
896    * simply to allow optimization where possible.
897    * 
898    * @param updateLabels
899    *            <code>true</code> to update labels for existing elements,
900    *            <code>false</code> to only update labels as needed, assuming
901    *            that labels for existing elements are unchanged.
902    * 
903    * @since 2.0
904    */
905   public void refresh(boolean updateLabels) {
906     refresh(getRoot(), updateLabels);
907   }
908 
909   /**
910    * Refreshes this viewer starting with the given element.
911    * <p>
912    * Unlike the <code>update</code> methods, this handles structural changes
913    * to the given element (e.g. addition or removal of children). If only the
914    * given element needs updating, it is more efficient to use the
915    * <code>update</code> methods.
916    * </p>
917    * 
918    * @param element
919    *            the element
920    */
921   public void refresh(final Object element) {
922     preservingSelection(new Runnable() {
923       public void run() {
924         internalRefresh(element);
925       }
926     });
927   }
928 
929   /**
930    * Refreshes this viewer starting with the given element. Labels are updated
931    * as described in <code>refresh(boolean updateLabels)</code>.
932    * <p>
933    * Unlike the <code>update</code> methods, this handles structural changes
934    * to the given element (e.g. addition or removal of children). If only the
935    * given element needs updating, it is more efficient to use the
936    * <code>update</code> methods.
937    * </p>
938    * 
939    * @param element
940    *            the element
941    * @param updateLabels
942    *            <code>true</code> to update labels for existing elements,
943    *            <code>false</code> to only update labels as needed, assuming
944    *            that labels for existing elements are unchanged.
945    * 
946    * @since 2.0
947    */
948   public void refresh(final Object element, final boolean updateLabels) {
949     preservingSelection(new Runnable() {
950       public void run() {
951         internalRefresh(element, updateLabels);
952       }
953     });
954   }
955 
956   /**
957    * 
958    * Refreshes the given TableItem with the given element. Calls
959    * <code>doUpdateItem(..., false)</code>.
960    * <p>
961    * This method is internal to the framework; subclassers should not call
962    * this method.
963    * </p>
964    * @param widget
965    * @param element
966    */
967   protected final void refreshItem(Widget widget, Object element) {
968     Platform.run(new UpdateItemSafeRunnable(widget, element, true));
969   }
970   /**
971    * Removes the given open listener from this viewer. Has no affect if an
972    * identical listener is not registered.
973    * 
974    * @param listener
975    *            a double-click listener
976    */
977   public void removeOpenListener(IOpenListener listener) {
978     openListeners.remove(listener);
979   }
980   /*
981    * (non-Javadoc) Method declared on IPostSelectionProvider.
982    */
983   public void removePostSelectionChangedListener(
984       ISelectionChangedListener listener) {
985     postSelectionChangedListeners.remove(listener);
986   }
987   /**
988    * Removes the given double-click listener from this viewer. Has no affect
989    * if an identical listener is not registered.
990    * 
991    * @param listener
992    *            a double-click listener
993    */
994   public void removeDoubleClickListener(IDoubleClickListener listener) {
995     doubleClickListeners.remove(listener);
996   }
997   /**
998    * Removes the given filter from this viewer, and triggers refiltering and
999    * resorting of the elements if required. Has no effect if the identical
1000   * filter is not registered.
1001   * 
1002   * @param filter
1003   *            a viewer filter
1004   */
1005  public void removeFilter(ViewerFilter filter) {
1006    Assert.isNotNull(filter);
1007    if (filters != null) {
1008      // Note: can't use List.remove(Object). Use identity comparison
1009      // instead.
1010      for (Iterator i = filters.iterator(); i.hasNext();) {
1011        Object o = i.next();
1012        if (o == filter) {
1013          i.remove();
1014          refresh();
1015          if (filters.size() == 0)
1016            filters = null;
1017          return;
1018        }
1019      }
1020    }
1021  }
1022  /**
1023   * Discards this viewer's filters and triggers refiltering and resorting of
1024   * the elements.
1025   */
1026  public void resetFilters() {
1027    if (filters != null) {
1028      filters = null;
1029      refresh();
1030    }
1031  }
1032  /**
1033   * Ensures that the given element is visible, scrolling the viewer if
1034   * necessary. The selection is unchanged.
1035   * 
1036   * @param element
1037   *            the element to reveal
1038   */
1039  public abstract void reveal(Object element);
1040  /*
1041   *  (non-Javadoc)
1042   * @see org.eclipse.jface.viewers.ContentViewer#setContentProvider(org.eclipse.jface.viewers.IContentProvider)
1043   */
1044  public void setContentProvider(IContentProvider provider) {
1045    Assert.isTrue(provider instanceof IStructuredContentProvider);
1046    super.setContentProvider(provider);
1047  }
1048  /*
1049   *  (non-Javadoc)
1050   * @see org.eclipse.jface.viewers.Viewer#setInput(java.lang.Object)
1051   */
1052  public final void setInput(Object input) {
1053
1054    try {
1055      //    fInChange= true;
1056
1057      unmapAllElements();
1058
1059      super.setInput(input);
1060
1061    } finally {
1062      //    fInChange= false;
1063    }
1064  }
1065  /*
1066   *  (non-Javadoc)
1067   * @see org.eclipse.jface.viewers.Viewer#setSelection(org.eclipse.jface.viewers.ISelection, boolean)
1068   */
1069  public void setSelection(ISelection selection, boolean reveal) {
1070    /**
1071     * <p>
1072     * If the new selection differs from the current selection the hook
1073     * <code>updateSelection</code> is called.
1074     * </p>
1075     * <p>
1076     * If <code>setSelection</code> is called from within
1077     * <code>preserveSelection</code>, the call to
1078     * <code>updateSelection</code> is delayed until the end of
1079     * <code>preserveSelection</code>.
1080     * </p>
1081     * <p>
1082     * Subclasses do not typically override this method, but implement
1083     * <code>setSelectionToWidget</code> instead.
1084     * </p>
1085     */
1086    Control control = getControl();
1087    if (control == null || control.isDisposed()) {
1088      return;
1089    }
1090    if (!inChange) {
1091      setSelectionToWidget(selection, reveal);
1092      ISelection sel = getSelection();
1093      updateSelection(sel);
1094      firePostSelectionChanged(new SelectionChangedEvent(this, sel));
1095    } else {
1096      restoreSelection = false;
1097      setSelectionToWidget(selection, reveal);
1098    }
1099  }
1100  /**
1101   * Parlays the given list of selected elements into selections on this
1102   * viewer's control.
1103   * <p>
1104   * Subclasses should override to set their selection based on the given list
1105   * of elements.
1106   * </p>
1107   * 
1108   * @param l
1109   *            list of selected elements (element type: <code>Object</code>)
1110   *            or <code>null</code> if the selection is to be cleared
1111   * @param reveal
1112   *            <code>true</code> if the selection is to be made visible,
1113   *            and <code>false</code> otherwise
1114   */
1115  protected abstract void setSelectionToWidget(List l, boolean reveal);
1116  /**
1117   * Converts the selection to a <code>List</code> and calls
1118   * <code>setSelectionToWidget(List, boolean)</code>. The selection is
1119   * expected to be an <code>IStructuredSelection</code> of elements. If
1120   * not, the selection is cleared.
1121   * <p>
1122   * Subclasses do not typically override this method, but implement
1123   * <code>setSelectionToWidget(List, boolean)</code> instead.
1124   * 
1125   * @param selection
1126   *            an IStructuredSelection of elements
1127   * @param reveal
1128   *            <code>true</code> to reveal the first element in the
1129   *            selection, or <code>false</code> otherwise
1130   */
1131  protected void setSelectionToWidget(ISelection selection, boolean reveal) {
1132    if (selection instanceof IStructuredSelection)
1133      setSelectionToWidget(((IStructuredSelection) selection).toList(),
1134          reveal);
1135    else
1136      setSelectionToWidget((List) null, reveal);
1137  }
1138  /**
1139   * Sets this viewer's sorter and triggers refiltering and resorting of this
1140   * viewer's element. Passing <code>null</code> turns sorting off.
1141   * 
1142   * @param sorter
1143   *            a viewer sorter, or <code>null</code> if none
1144   */
1145  public void setSorter(ViewerSorter sorter) {
1146    if (this.sorter != sorter) {
1147      this.sorter = sorter;
1148      refresh();
1149    }
1150  }
1151  /**
1152   * Configures whether this structured viewer uses an internal hash table to
1153   * speeds up the mapping between elements and SWT items. This must be called
1154   * before the viewer is given an input (via <code>setInput</code>).
1155   * 
1156   * @param enable
1157   *            <code>true</code> to enable hash lookup, and
1158   *            <code>false</code> to disable it
1159   */
1160  public void setUseHashlookup(boolean enable) {
1161    Assert.isTrue(getInput() == null,
1162        "Can only enable the hash look up before input has been set");//$NON-NLS-1$
1163    if (enable) {
1164      elementMap = newHashtable(CustomHashtable.DEFAULT_CAPACITY);
1165    } else {
1166      elementMap = null;
1167    }
1168  }
1169
1170  /**
1171   * Sets the comparator to use for comparing elements, or <code>null</code>
1172   * to use the default <code>equals</code> and <code>hashCode</code>
1173   * methods on the elements themselves.
1174   * 
1175   * @param comparer
1176   *            the comparator to use for comparing elements or
1177   *            <code>null</code>
1178   */
1179  public void setComparer(IElementComparer comparer) {
1180    this.comparer = comparer;
1181    if (elementMap != null) {
1182      elementMap = new CustomHashtable(elementMap, comparer);
1183    }
1184  }
1185
1186  /**
1187   * Hook for testing.
1188   * @param element
1189   * @return Widget
1190   */
1191  public Widget testFindItem(Object element) {
1192    return findItem(element);
1193  }
1194  /**
1195   * Removes all elements from the map.
1196   * <p>
1197   * This method is internal to the framework; subclassers should not call
1198   * this method.
1199   * </p>
1200   */
1201  protected void unmapAllElements() {
1202    if (elementMap != null) {
1203      elementMap = newHashtable(CustomHashtable.DEFAULT_CAPACITY);
1204    }
1205  }
1206  /**
1207   * Removes the given element from the internal element to widget map. Does
1208   * nothing if mapping is disabled. If mapping is enabled, the given element
1209   * must be present.
1210   * <p>
1211   * This method is internal to the framework; subclassers should not call
1212   * this method.
1213   * </p>
1214   * 
1215   * @param element
1216   *            the element
1217   */
1218  protected void unmapElement(Object element) {
1219    if (elementMap != null) {
1220      elementMap.remove(element);
1221    }
1222  }
1223  /**
1224   * Removes the given association from the internal element to widget map.
1225   * Does nothing if mapping is disabled, or if the given element does not map
1226   * to the given item.
1227   * <p>
1228   * This method is internal to the framework; subclassers should not call
1229   * this method.
1230   * </p>
1231   * 
1232   * @param element
1233   *            the element
1234   * @param item the item to unmap
1235   * @since 2.0
1236   */
1237  protected void unmapElement(Object element, Widget item) {
1238    // double-check that the element actually maps to the given item before
1239    // unmapping it
1240    if (elementMap != null && elementMap.get(element) == item) {
1241      // call unmapElement for backwards compatibility
1242      unmapElement(element);
1243    }
1244  }
1245
1246  /**
1247   * Updates the given elements' presentation when one or more of their
1248   * properties change. Only the given elements are updated.
1249   * <p>
1250   * This does not handle structural changes (e.g. addition or removal of
1251   * elements), and does not update any other related elements (e.g. child
1252   * elements). To handle structural changes, use the <code>refresh</code>
1253   * methods instead.
1254   * </p>
1255   * <p>
1256   * This should be called when an element has changed in the model, in order
1257   * to have the viewer accurately reflect the model. This method only affects
1258   * the viewer, not the model.
1259   * </p>
1260   * <p>
1261   * Specifying which properties are affected may allow the viewer to optimize
1262   * the update. For example, if the label provider is not affected by changes
1263   * to any of these properties, an update may not actually be required.
1264   * Specifing <code>properties</code> as <code>null</code> forces a full
1265   * update of the given elements.
1266   * </p>
1267   * <p>
1268   * If the viewer has a sorter which is affected by a change to one of the
1269   * properties, the elements' positions are updated to maintain the sort
1270   * order.
1271   * </p>
1272   * <p>
1273   * If the viewer has a filter which is affected by a change to one of the
1274   * properties, elements may appear or disappear if the change affects
1275   * whether or not they are filtered out.
1276   * </p>
1277   * 
1278   * @param elements
1279   *            the elements
1280   * @param properties
1281   *            the properties that have changed, or <code>null</code> to
1282   *            indicate unknown
1283   */
1284  public void update(Object[] elements, String[] properties) {
1285    for (int i = 0; i < elements.length; ++i)
1286      update(elements[i], properties);
1287  }
1288  /**
1289   * Updates the given element's presentation when one or more of its
1290   * properties changes. Only the given element is updated.
1291   * <p>
1292   * This does not handle structural changes (e.g. addition or removal of
1293   * elements), and does not update any other related elements (e.g. child
1294   * elements). To handle structural changes, use the <code>refresh</code>
1295   * methods instead.
1296   * </p>
1297   * <p>
1298   * This should be called when an element has changed in the model, in order
1299   * to have the viewer accurately reflect the model. This method only affects
1300   * the viewer, not the model.
1301   * </p>
1302   * <p>
1303   * Specifying which properties are affected may allow the viewer to optimize
1304   * the update. For example, if the label provider is not affected by changes
1305   * to any of these properties, an update may not actually be required.
1306   * Specifing <code>properties</code> as <code>null</code> forces a full
1307   * update of the element.
1308   * </p>
1309   * <p>
1310   * If the viewer has a sorter which is affected by a change to one of the
1311   * properties, the element's position is updated to maintain the sort order.
1312   * </p>
1313   * <p>
1314   * If the viewer has a filter which is affected by a change to one of the
1315   * properties, the element may appear or disappear if the change affects
1316   * whether or not the element is filtered out.
1317   * </p>
1318   * 
1319   * @param element
1320   *            the element
1321   * @param properties
1322   *            the properties that have changed, or <code>null</code> to
1323   *            indicate unknown
1324   */
1325  public void update(Object element, String[] properties) {
1326    Assert.isNotNull(element);
1327    Widget item = findItem(element);
1328    if (item == null)
1329      return;
1330
1331    boolean needsRefilter = false;
1332    if (properties != null) {
1333      for (int i = 0; i < properties.length; ++i) {
1334        needsRefilter = needsRefilter(element, properties[i]);
1335        if (needsRefilter)
1336          break;
1337      }
1338    }
1339    if (needsRefilter) {
1340      refresh();
1341      return;
1342    }
1343
1344    boolean needsUpdate;
1345    if (properties == null) {
1346      needsUpdate = true;
1347    } else {
1348      needsUpdate = false;
1349      IBaseLabelProvider labelProvider = getLabelProvider();
1350      for (int i = 0; i < properties.length; ++i) {
1351        needsUpdate = labelProvider.isLabelProperty(element,
1352            properties[i]);
1353        if (needsUpdate)
1354          break;
1355      }
1356    }
1357    if (needsUpdate) {
1358      updateItem(item, element);
1359    }
1360  }
1361  /**
1362   * Copies attributes of the given element into the given widget.
1363   * <p>
1364   * This method is internal to the framework; subclassers should not call
1365   * this method. Calls <code>doUpdateItem(widget, element, true)</code>.
1366   * </p>
1367   * 
1368   * @param widget
1369   *            the widget
1370   * @param element
1371   *            the element
1372   */
1373  protected final void updateItem(Widget widget, Object element) {
1374    Platform.run(new UpdateItemSafeRunnable(widget, element, true));
1375  }
1376  /**
1377   * Updates the selection of this viewer.
1378   * <p>
1379   * This framework method should be called when the selection in the viewer
1380   * widget changes.
1381   * </p>
1382   * <p>
1383   * The default implementation of this method notifies all selection change
1384   * listeners recorded in an internal state variable. Overriding this method
1385   * is generally not required; however, if overriding in a subclass,
1386   * <code>super.updateSelection</code> must be invoked.
1387   * </p>
1388   * 
1389   * @param selection
1390   *            the selection, or <code>null</code> if none
1391   */
1392  protected void updateSelection(ISelection selection) {
1393    SelectionChangedEvent event = new SelectionChangedEvent(this, selection);
1394    fireSelectionChanged(event);
1395  }
1396  /**
1397   * Returns whether this structured viewer is configured to use an internal
1398   * map to speed up the mapping between elements and SWT items.
1399   * <p>
1400   * The default implementation of this framework method checks whether the
1401   * internal map has been initialized.
1402   * </p>
1403   * 
1404   * @return <code>true</code> if the element map is enabled, and
1405   *         <code>false</code> if disabled
1406   */
1407  protected boolean usingElementMap() {
1408    return elementMap != null;
1409  }
1410
1411}