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}