001: /*******************************************************************************
002: * Copyright (c) 2004, 2007 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.ui.dialogs;
011:
012: import org.eclipse.core.runtime.IProgressMonitor;
013: import org.eclipse.core.runtime.IStatus;
014: import org.eclipse.core.runtime.Status;
015: import org.eclipse.core.runtime.jobs.Job;
016: import org.eclipse.jface.action.Action;
017: import org.eclipse.jface.action.IAction;
018: import org.eclipse.jface.action.ToolBarManager;
019: import org.eclipse.jface.resource.ImageDescriptor;
020: import org.eclipse.jface.resource.JFaceResources;
021: import org.eclipse.jface.viewers.IContentProvider;
022: import org.eclipse.jface.viewers.ISelection;
023: import org.eclipse.jface.viewers.TreeViewer;
024: import org.eclipse.swt.SWT;
025: import org.eclipse.swt.accessibility.AccessibleAdapter;
026: import org.eclipse.swt.accessibility.AccessibleEvent;
027: import org.eclipse.swt.events.DisposeEvent;
028: import org.eclipse.swt.events.DisposeListener;
029: import org.eclipse.swt.events.FocusAdapter;
030: import org.eclipse.swt.events.FocusEvent;
031: import org.eclipse.swt.events.KeyAdapter;
032: import org.eclipse.swt.events.KeyEvent;
033: import org.eclipse.swt.events.ModifyEvent;
034: import org.eclipse.swt.events.ModifyListener;
035: import org.eclipse.swt.events.TraverseEvent;
036: import org.eclipse.swt.events.TraverseListener;
037: import org.eclipse.swt.graphics.Color;
038: import org.eclipse.swt.graphics.Font;
039: import org.eclipse.swt.layout.GridData;
040: import org.eclipse.swt.layout.GridLayout;
041: import org.eclipse.swt.widgets.Composite;
042: import org.eclipse.swt.widgets.Control;
043: import org.eclipse.swt.widgets.Display;
044: import org.eclipse.swt.widgets.Text;
045: import org.eclipse.swt.widgets.TreeItem;
046: import org.eclipse.ui.IWorkbenchPreferenceConstants;
047: import org.eclipse.ui.PlatformUI;
048: import org.eclipse.ui.internal.WorkbenchMessages;
049: import org.eclipse.ui.plugin.AbstractUIPlugin;
050: import org.eclipse.ui.progress.WorkbenchJob;
051:
052: /**
053: * A simple control that provides a text widget and a tree viewer. The contents
054: * of the text widget are used to drive a PatternFilter that is on the viewer.
055: *
056: * @see org.eclipse.ui.dialogs.PatternFilter
057: * @since 3.2
058: */
059: public class FilteredTree extends Composite {
060:
061: /**
062: * The filter text widget to be used by this tree. This value may be
063: * <code>null</code> if there is no filter widget, or if the controls have
064: * not yet been created.
065: */
066: protected Text filterText;
067:
068: /**
069: * The control representing the clear button for the filter text entry. This
070: * value may be <code>null</code> if no such button exists, or if the
071: * controls have not yet been created.
072: */
073: protected ToolBarManager filterToolBar;
074:
075: /**
076: * The viewer for the filtered tree. This value should never be <code>null</code>
077: * after the widget creation methods are complete.
078: */
079: protected TreeViewer treeViewer;
080:
081: /**
082: * The Composite on which the filter controls are created. This is used to set
083: * the background color of the filter controls to match the surrounding controls.
084: */
085: protected Composite filterComposite;
086:
087: /**
088: * The pattern filter for the tree. This value must not be <code>null</code>.
089: */
090: private PatternFilter patternFilter;
091:
092: /**
093: * The text to initially show in the filter text control.
094: */
095: protected String initialText = ""; //$NON-NLS-1$
096:
097: /**
098: * The job used to refresh the tree.
099: */
100: private Job refreshJob;
101:
102: /**
103: * The parent composite of the filtered tree.
104: * @since 3.3
105: */
106: protected Composite parent;
107:
108: /**
109: * Whether or not to show the filter controls (text and clear button).
110: * The default is to show these controls. This can be overridden by
111: * providing a setting in the product configuration file. The setting
112: * to add to not show these controls is:
113: *
114: * org.eclipse.ui/SHOW_FILTERED_TEXTS=false
115: */
116: protected boolean showFilterControls;
117:
118: /**
119: * @since 3.3
120: */
121: protected Composite treeComposite;
122:
123: /**
124: * Image descriptor for enabled clear button.
125: */
126: private static final String CLEAR_ICON = "org.eclipse.ui.internal.dialogs.CLEAR_ICON"; //$NON-NLS-1$
127:
128: /**
129: * Image descriptor for disabled clear button.
130: */
131: private static final String DCLEAR_ICON = "org.eclipse.ui.internal.dialogs.DCLEAR_ICON"; //$NON-NLS-1$
132:
133: /**
134: * Maximum time spent expanding the tree after the filter text has been updated
135: * (this is only used if we were able to at least expand the visible nodes)
136: */
137: private static final long SOFT_MAX_EXPAND_TIME = 200;
138:
139: /**
140: * Get image descriptors for the clear button.
141: */
142: static {
143: ImageDescriptor descriptor = AbstractUIPlugin
144: .imageDescriptorFromPlugin(PlatformUI.PLUGIN_ID,
145: "$nl$/icons/full/etool16/clear_co.gif"); //$NON-NLS-1$
146: if (descriptor != null) {
147: JFaceResources.getImageRegistry().put(CLEAR_ICON,
148: descriptor);
149: }
150: descriptor = AbstractUIPlugin.imageDescriptorFromPlugin(
151: PlatformUI.PLUGIN_ID,
152: "$nl$/icons/full/dtool16/clear_co.gif"); //$NON-NLS-1$
153: if (descriptor != null) {
154: JFaceResources.getImageRegistry().put(DCLEAR_ICON,
155: descriptor);
156: }
157: }
158:
159: /**
160: * Create a new instance of the receiver.
161: *
162: * @param parent
163: * the parent <code>Composite</code>
164: * @param treeStyle
165: * the style bits for the <code>Tree</code>
166: * @param filter
167: * the filter to be used
168: */
169: public FilteredTree(Composite parent, int treeStyle,
170: PatternFilter filter) {
171: super (parent, SWT.NONE);
172: this .parent = parent;
173: init(treeStyle, filter);
174: }
175:
176: /**
177: * Create a new instance of the receiver. Subclasses that wish to override
178: * the default creation behavior may use this constructor, but must ensure
179: * that the <code>init(composite, int, PatternFilter)</code> method is
180: * called in the overriding constructor.
181: *
182: * @param parent
183: * the parent <code>Composite</code>
184: * @see #init(int, PatternFilter)
185: *
186: * @since 3.3
187: */
188: protected FilteredTree(Composite parent) {
189: super (parent, SWT.NONE);
190: this .parent = parent;
191: }
192:
193: /**
194: * Create the filtered tree.
195: *
196: * @param treeStyle
197: * the style bits for the <code>Tree</code>
198: * @param filter
199: * the filter to be used
200: *
201: * @since 3.3
202: */
203: protected void init(int treeStyle, PatternFilter filter) {
204: patternFilter = filter;
205: showFilterControls = PlatformUI
206: .getPreferenceStore()
207: .getBoolean(
208: IWorkbenchPreferenceConstants.SHOW_FILTERED_TEXTS);
209: createControl(parent, treeStyle);
210: createRefreshJob();
211: setInitialText(WorkbenchMessages.FilteredTree_FilterMessage);
212: setFont(parent.getFont());
213: }
214:
215: /**
216: * Create the filtered tree's controls.
217: * Subclasses should override.
218: *
219: * @param parent
220: * @param treeStyle
221: */
222: protected void createControl(Composite parent, int treeStyle) {
223: GridLayout layout = new GridLayout();
224: layout.marginHeight = 0;
225: layout.marginWidth = 0;
226: setLayout(layout);
227: setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
228:
229: if (showFilterControls) {
230: filterComposite = new Composite(this , SWT.NONE);
231: GridLayout filterLayout = new GridLayout(2, false);
232: filterLayout.marginHeight = 0;
233: filterLayout.marginWidth = 0;
234: filterComposite.setLayout(filterLayout);
235: filterComposite.setFont(parent.getFont());
236:
237: createFilterControls(filterComposite);
238: filterComposite.setLayoutData(new GridData(SWT.FILL,
239: SWT.BEGINNING, true, false));
240: }
241:
242: treeComposite = new Composite(this , SWT.NONE);
243: GridLayout treeCompositeLayout = new GridLayout();
244: treeCompositeLayout.marginHeight = 0;
245: treeCompositeLayout.marginWidth = 0;
246: treeComposite.setLayout(treeCompositeLayout);
247: GridData data = new GridData(SWT.FILL, SWT.FILL, true, true);
248: treeComposite.setLayoutData(data);
249: createTreeControl(treeComposite, treeStyle);
250: }
251:
252: /**
253: * Create the filter controls. By default, a text and corresponding tool bar
254: * button that clears the contents of the text is created.
255: * Subclasses may override.
256: *
257: * @param parent parent <code>Composite</code> of the filter controls
258: * @return the <code>Composite</code> that contains the filter controls
259: */
260: protected Composite createFilterControls(Composite parent) {
261: createFilterText(parent);
262: createClearText(parent);
263: if (filterToolBar != null) {
264: filterToolBar.update(false);
265: // initially there is no text to clear
266: filterToolBar.getControl().setVisible(false);
267: }
268: return parent;
269: }
270:
271: /**
272: * Creates and set up the tree and tree viewer. This method calls
273: * {@link #doCreateTreeViewer(Composite, int)} to create the tree viewer.
274: * Subclasses should override {@link #doCreateTreeViewer(Composite, int)}
275: * instead of overriding this method.
276: *
277: * @param parent
278: * parent <code>Composite</code>
279: * @param style
280: * SWT style bits used to create the tree
281: * @return the tree
282: */
283: protected Control createTreeControl(Composite parent, int style) {
284: treeViewer = doCreateTreeViewer(parent, style);
285: GridData data = new GridData(SWT.FILL, SWT.FILL, true, true);
286: treeViewer.getControl().setLayoutData(data);
287: treeViewer.getControl().addDisposeListener(
288: new DisposeListener() {
289: /* (non-Javadoc)
290: * @see org.eclipse.swt.events.DisposeListener#widgetDisposed(org.eclipse.swt.events.DisposeEvent)
291: */
292: public void widgetDisposed(DisposeEvent e) {
293: refreshJob.cancel();
294: }
295: });
296: if (treeViewer instanceof NotifyingTreeViewer) {
297: patternFilter.setUseCache(true);
298: }
299: treeViewer.addFilter(patternFilter);
300: return treeViewer.getControl();
301: }
302:
303: /**
304: * Creates the tree viewer. Subclasses may override.
305: *
306: * @param parent the parent composite
307: * @param style SWT style bits used to create the tree viewer
308: * @return the tree viewer
309: *
310: * @since 3.3
311: */
312: protected TreeViewer doCreateTreeViewer(Composite parent, int style) {
313: return new NotifyingTreeViewer(parent, style);
314: }
315:
316: /**
317: * Return the first item in the tree that matches the filter pattern.
318: *
319: * @param items
320: * @return the first matching TreeItem
321: */
322: private TreeItem getFirstMatchingItem(TreeItem[] items) {
323: for (int i = 0; i < items.length; i++) {
324: if (patternFilter.isLeafMatch(treeViewer, items[i]
325: .getData())
326: && patternFilter.isElementSelectable(items[i]
327: .getData())) {
328: return items[i];
329: }
330: return getFirstMatchingItem(items[i].getItems());
331: }
332: return null;
333: }
334:
335: /**
336: * Create the refresh job for the receiver.
337: *
338: */
339: private void createRefreshJob() {
340: refreshJob = new WorkbenchJob("Refresh Filter") {//$NON-NLS-1$
341: /* (non-Javadoc)
342: * @see org.eclipse.ui.progress.UIJob#runInUIThread(org.eclipse.core.runtime.IProgressMonitor)
343: */
344: public IStatus runInUIThread(IProgressMonitor monitor) {
345: if (treeViewer.getControl().isDisposed()) {
346: return Status.CANCEL_STATUS;
347: }
348:
349: String text = getFilterString();
350: if (text == null) {
351: return Status.OK_STATUS;
352: }
353:
354: boolean initial = initialText != null
355: && initialText.equals(text);
356: if (initial) {
357: patternFilter.setPattern(null);
358: } else if (text != null) {
359: patternFilter.setPattern(text);
360: }
361:
362: Control redrawFalseControl = treeComposite != null ? treeComposite
363: : treeViewer.getControl();
364: try {
365: // don't want the user to see updates that will be made to the tree
366: // we are setting redraw(false) on the composite to avoid dancing scrollbar
367: redrawFalseControl.setRedraw(false);
368: if (!narrowingDown) {
369: // collapse all
370: TreeItem[] is = treeViewer.getTree().getItems();
371: for (int i = 0; i < is.length; i++) {
372: TreeItem item = is[i];
373: if (item.getExpanded()) {
374: treeViewer.setExpandedState(item
375: .getData(), false);
376: }
377: }
378: }
379: treeViewer.refresh(true);
380:
381: if (text.length() > 0 && !initial) {
382: /* Expand elements one at a time. After each is expanded, check
383: * to see if the filter text has been modified. If it has, then
384: * cancel the refresh job so the user doesn't have to endure
385: * expansion of all the nodes.
386: */
387: TreeItem[] items = getViewer().getTree()
388: .getItems();
389: int treeHeight = getViewer().getTree()
390: .getBounds().height;
391: int numVisibleItems = treeHeight
392: / getViewer().getTree().getItemHeight();
393: long stopTime = SOFT_MAX_EXPAND_TIME
394: + System.currentTimeMillis();
395: if (items.length > 0
396: && recursiveExpand(items, monitor,
397: stopTime,
398: new int[] { numVisibleItems })) {
399: return Status.CANCEL_STATUS;
400: }
401:
402: // enabled toolbar - there is text to clear
403: // and the list is currently being filtered
404: updateToolbar(true);
405: } else {
406: // disabled toolbar - there is no text to clear
407: // and the list is currently not filtered
408: updateToolbar(false);
409: }
410: } finally {
411: // done updating the tree - set redraw back to true
412: TreeItem[] items = getViewer().getTree().getItems();
413: if (items.length > 0
414: && getViewer().getTree()
415: .getSelectionCount() == 0) {
416: treeViewer.getTree().setTopItem(items[0]);
417: }
418: redrawFalseControl.setRedraw(true);
419: }
420: return Status.OK_STATUS;
421: }
422:
423: /**
424: * Returns true if the job should be canceled (because of timeout or actual cancellation).
425: * @param items
426: * @param provider
427: * @param monitor
428: * @param cancelTime
429: * @param numItemsLeft
430: * @return true if canceled
431: */
432: private boolean recursiveExpand(TreeItem[] items,
433: IProgressMonitor monitor, long cancelTime,
434: int[] numItemsLeft) {
435: boolean canceled = false;
436: for (int i = 0; !canceled && i < items.length; i++) {
437: TreeItem item = items[i];
438: boolean visible = numItemsLeft[0]-- >= 0;
439: if (monitor.isCanceled()
440: || (!visible && System.currentTimeMillis() > cancelTime)) {
441: canceled = true;
442: } else {
443: Object itemData = item.getData();
444: if (itemData != null) {
445: if (!item.getExpanded()) {
446: // do the expansion through the viewer so that it can refresh children appropriately.
447: treeViewer.setExpandedState(itemData,
448: true);
449: }
450: TreeItem[] children = item.getItems();
451: if (items.length > 0) {
452: canceled = recursiveExpand(children,
453: monitor, cancelTime,
454: numItemsLeft);
455: }
456: }
457: }
458: }
459: return canceled;
460: }
461:
462: };
463: refreshJob.setSystem(true);
464: }
465:
466: protected void updateToolbar(boolean visible) {
467: if (filterToolBar != null) {
468: filterToolBar.getControl().setVisible(visible);
469: }
470: }
471:
472: /**
473: * Creates the filter text and adds listeners. This method calls
474: * {@link #doCreateFilterText(Composite)} to create the text control.
475: * Subclasses should override {@link #doCreateFilterText(Composite)}
476: * instead of overriding this method.
477: *
478: * @param parent <code>Composite</code> of the filter text
479: */
480: protected void createFilterText(Composite parent) {
481: filterText = doCreateFilterText(parent);
482: filterText.getAccessible().addAccessibleListener(
483: new AccessibleAdapter() {
484: /* (non-Javadoc)
485: * @see org.eclipse.swt.accessibility.AccessibleListener#getName(org.eclipse.swt.accessibility.AccessibleEvent)
486: */
487: public void getName(AccessibleEvent e) {
488: String filterTextString = filterText.getText();
489: if (filterTextString.length() == 0) {
490: e.result = initialText;
491: } else {
492: e.result = filterTextString;
493: }
494: }
495: });
496:
497: filterText.addFocusListener(new FocusAdapter() {
498: /* (non-Javadoc)
499: * @see org.eclipse.swt.events.FocusListener#focusLost(org.eclipse.swt.events.FocusEvent)
500: */
501: public void focusGained(FocusEvent e) {
502: /* Running in an asyncExec because the selectAll() does not
503: * appear to work when using mouse to give focus to text.
504: */
505: Display display = filterText.getDisplay();
506: display.asyncExec(new Runnable() {
507: public void run() {
508: if (!filterText.isDisposed()) {
509: if (getInitialText().equals(
510: filterText.getText().trim())) {
511: filterText.selectAll();
512: }
513: }
514: }
515: });
516: }
517: });
518:
519: filterText.addKeyListener(new KeyAdapter() {
520: /*
521: * (non-Javadoc)
522: *
523: * @see org.eclipse.swt.events.KeyAdapter#keyReleased(org.eclipse.swt.events.KeyEvent)
524: */
525: public void keyPressed(KeyEvent e) {
526: // on a CR we want to transfer focus to the list
527: boolean hasItems = getViewer().getTree().getItemCount() > 0;
528: if (hasItems && e.keyCode == SWT.ARROW_DOWN) {
529: treeViewer.getTree().setFocus();
530: } else if (e.character == SWT.CR) {
531: return;
532: }
533: }
534: });
535:
536: // enter key set focus to tree
537: filterText.addTraverseListener(new TraverseListener() {
538: public void keyTraversed(TraverseEvent e) {
539: if (e.detail == SWT.TRAVERSE_RETURN) {
540: e.doit = false;
541: if (getViewer().getTree().getItemCount() == 0) {
542: Display.getCurrent().beep();
543: } else {
544: // if the initial filter text hasn't changed, do not try to match
545: boolean hasFocus = getViewer().getTree()
546: .setFocus();
547: boolean textChanged = !getInitialText().equals(
548: filterText.getText().trim());
549: if (hasFocus
550: && textChanged
551: && filterText.getText().trim().length() > 0) {
552: TreeItem item = getFirstMatchingItem(getViewer()
553: .getTree().getItems());
554: if (item != null) {
555: getViewer().getTree().setSelection(
556: new TreeItem[] { item });
557: ISelection sel = getViewer()
558: .getSelection();
559: getViewer().setSelection(sel, true);
560: }
561: }
562: }
563: }
564: }
565: });
566:
567: filterText.addModifyListener(new ModifyListener() {
568: /* (non-Javadoc)
569: * @see org.eclipse.swt.events.ModifyListener#modifyText(org.eclipse.swt.events.ModifyEvent)
570: */
571: public void modifyText(ModifyEvent e) {
572: textChanged();
573: }
574: });
575:
576: GridData gridData = new GridData(SWT.FILL, SWT.BEGINNING, true,
577: false);
578: // if the text widget supported cancel then it will have it's own
579: // integrated button. We can take all of the space.
580: if ((filterText.getStyle() & SWT.CANCEL) != 0)
581: gridData.horizontalSpan = 2;
582: filterText.setLayoutData(gridData);
583: }
584:
585: /**
586: * Creates the text control for entering the filter text. Subclasses may
587: * override.
588: *
589: * @param parent
590: * the parent composite
591: * @return the text widget
592: *
593: * @since 3.3
594: */
595: protected Text doCreateFilterText(Composite parent) {
596: return new Text(parent, SWT.SINGLE | SWT.BORDER | SWT.SEARCH
597: | SWT.CANCEL);
598: }
599:
600: private String previousFilterText;
601:
602: private boolean narrowingDown;
603:
604: /**
605: * Update the receiver after the text has changed.
606: */
607: protected void textChanged() {
608: narrowingDown = previousFilterText == null
609: || getFilterString().startsWith(previousFilterText);
610: previousFilterText = getFilterString();
611: // cancel currently running job first, to prevent unnecessary redraw
612: refreshJob.cancel();
613: refreshJob.schedule(200);
614: }
615:
616: /**
617: * Set the background for the widgets that support the filter text area.
618: *
619: * @param background background <code>Color</code> to set
620: */
621: public void setBackground(Color background) {
622: super .setBackground(background);
623: if (filterComposite != null) {
624: filterComposite.setBackground(background);
625: }
626: if (filterToolBar != null && filterToolBar.getControl() != null) {
627: filterToolBar.getControl().setBackground(background);
628: }
629: }
630:
631: /**
632: * Create the button that clears the text.
633: *
634: * @param parent parent <code>Composite</code> of toolbar button
635: */
636: private void createClearText(Composite parent) {
637: // only create the button if the text widget doesn't support one natively
638: if ((filterText.getStyle() & SWT.CANCEL) == 0) {
639: filterToolBar = new ToolBarManager(SWT.FLAT
640: | SWT.HORIZONTAL);
641: filterToolBar.createControl(parent);
642:
643: IAction clearTextAction = new Action(
644: "", IAction.AS_PUSH_BUTTON) {//$NON-NLS-1$
645: /*
646: * (non-Javadoc)
647: *
648: * @see org.eclipse.jface.action.Action#run()
649: */
650: public void run() {
651: clearText();
652: }
653: };
654:
655: clearTextAction
656: .setToolTipText(WorkbenchMessages.FilteredTree_ClearToolTip);
657: clearTextAction.setImageDescriptor(JFaceResources
658: .getImageRegistry().getDescriptor(CLEAR_ICON));
659: clearTextAction.setDisabledImageDescriptor(JFaceResources
660: .getImageRegistry().getDescriptor(DCLEAR_ICON));
661:
662: filterToolBar.add(clearTextAction);
663: }
664: }
665:
666: /**
667: * Clears the text in the filter text widget. Also removes the optional
668: * additional filter that is provided via addFilter(ViewerFilter).
669: */
670: protected void clearText() {
671: setFilterText(""); //$NON-NLS-1$
672: textChanged();
673: }
674:
675: /**
676: * Set the text in the filter control.
677: * @param string
678: */
679: protected void setFilterText(String string) {
680: if (filterText != null) {
681: filterText.setText(string);
682: selectAll();
683: }
684: }
685:
686: /**
687: * Returns the pattern filter used by this tree.
688: *
689: * @return The pattern filter; never <code>null</code>.
690: */
691: public final PatternFilter getPatternFilter() {
692: return patternFilter;
693: }
694:
695: /**
696: * Get the tree viewer of the receiver.
697: *
698: * @return the tree viewer
699: */
700: public TreeViewer getViewer() {
701: return treeViewer;
702: }
703:
704: /**
705: * Get the filter text for the receiver, if it was created.
706: * Otherwise return <code>null</code>.
707: *
708: * @return the filter Text, or null if it was not created
709: */
710: public Text getFilterControl() {
711: return filterText;
712: }
713:
714: /**
715: * Convenience method to return the text of the filter control.
716: * If the text widget is not created, then null is returned.
717: *
718: * @return String in the text, or null if the text does not exist
719: */
720: protected String getFilterString() {
721: return filterText != null ? filterText.getText() : null;
722: }
723:
724: /**
725: * Set the text that will be shown until the first focus.
726: * A default value is provided, so this method only need be
727: * called if overriding the default initial text is desired.
728: *
729: * @param text initial text to appear in text field
730: */
731: public void setInitialText(String text) {
732: initialText = text;
733: setFilterText(initialText);
734: textChanged();
735: }
736:
737: /**
738: * Select all text in the filter text field.
739: *
740: */
741: protected void selectAll() {
742: if (filterText != null) {
743: filterText.selectAll();
744: }
745: }
746:
747: /**
748: * Get the initial text for the receiver.
749: * @return String
750: */
751: protected String getInitialText() {
752: return initialText;
753: }
754:
755: /**
756: * Return a bold font if the given element matches the given pattern.
757: * Clients can opt to call this method from a Viewer's label provider to get
758: * a bold font for which to highlight the given element in the tree.
759: *
760: * @param element
761: * element for which a match should be determined
762: * @param tree
763: * FilteredTree in which the element resides
764: * @param filter
765: * PatternFilter which determines a match
766: *
767: * @return bold font
768: */
769: public static Font getBoldFont(Object element, FilteredTree tree,
770: PatternFilter filter) {
771: String filterText = tree.getFilterString();
772:
773: if (filterText == null) {
774: return null;
775: }
776:
777: // Do nothing if it's empty string
778: String initialText = tree.getInitialText();
779: if (!("".equals(filterText) || initialText.equals(filterText))) {//$NON-NLS-1$
780: boolean initial = initialText != null
781: && initialText.equals(filterText);
782: if (initial) {
783: filter.setPattern(null);
784: } else if (filterText != null) {
785: filter.setPattern(filterText);
786: }
787:
788: if (filter.isElementVisible(tree.getViewer(), element)
789: && filter.isLeafMatch(tree.getViewer(), element)) {
790: return JFaceResources.getFontRegistry().getBold(
791: JFaceResources.DIALOG_FONT);
792: }
793: }
794: return null;
795: }
796:
797: /**
798: * Custom tree viewer subclass that clears the caches in patternFilter on
799: * any change to the tree. See bug 187200.
800: *
801: * @since 3.3
802: *
803: */
804: class NotifyingTreeViewer extends TreeViewer {
805:
806: /**
807: * @param parent
808: * @param style
809: */
810: public NotifyingTreeViewer(Composite parent, int style) {
811: super (parent, style);
812: }
813:
814: public void add(Object parentElementOrTreePath,
815: Object childElement) {
816: getPatternFilter().clearCaches();
817: super .add(parentElementOrTreePath, childElement);
818: }
819:
820: public void add(Object parentElementOrTreePath,
821: Object[] childElements) {
822: getPatternFilter().clearCaches();
823: super .add(parentElementOrTreePath, childElements);
824: }
825:
826: protected void inputChanged(Object input, Object oldInput) {
827: getPatternFilter().clearCaches();
828: super .inputChanged(input, oldInput);
829: }
830:
831: public void insert(Object parentElementOrTreePath,
832: Object element, int position) {
833: getPatternFilter().clearCaches();
834: super .insert(parentElementOrTreePath, element, position);
835: }
836:
837: public void refresh() {
838: getPatternFilter().clearCaches();
839: super .refresh();
840: }
841:
842: public void refresh(boolean updateLabels) {
843: getPatternFilter().clearCaches();
844: super .refresh(updateLabels);
845: }
846:
847: public void refresh(Object element) {
848: getPatternFilter().clearCaches();
849: super .refresh(element);
850: }
851:
852: public void refresh(Object element, boolean updateLabels) {
853: getPatternFilter().clearCaches();
854: super .refresh(element, updateLabels);
855: }
856:
857: public void remove(Object elementsOrTreePaths) {
858: getPatternFilter().clearCaches();
859: super .remove(elementsOrTreePaths);
860: }
861:
862: public void remove(Object parent, Object[] elements) {
863: getPatternFilter().clearCaches();
864: super .remove(parent, elements);
865: }
866:
867: public void remove(Object[] elementsOrTreePaths) {
868: getPatternFilter().clearCaches();
869: super .remove(elementsOrTreePaths);
870: }
871:
872: public void replace(Object parentElementOrTreePath, int index,
873: Object element) {
874: getPatternFilter().clearCaches();
875: super .replace(parentElementOrTreePath, index, element);
876: }
877:
878: public void setChildCount(Object elementOrTreePath, int count) {
879: getPatternFilter().clearCaches();
880: super .setChildCount(elementOrTreePath, count);
881: }
882:
883: public void setContentProvider(IContentProvider provider) {
884: getPatternFilter().clearCaches();
885: super .setContentProvider(provider);
886: }
887:
888: public void setHasChildren(Object elementOrTreePath,
889: boolean hasChildren) {
890: getPatternFilter().clearCaches();
891: super.setHasChildren(elementOrTreePath, hasChildren);
892: }
893:
894: }
895:
896: }
|