134165: move task editor find implementation to separate class

Change-Id: I348a4b189dd95ee58cc6b11f38cc25f6702d270f
Task-Url: https://bugs.eclipse.org/bugs/show_bug.cgi?id=134165
Signed-off-by: Sam Davis <sam.davis@tasktop.com>
diff --git a/org.eclipse.mylyn.sandbox.ui/src/org/eclipse/mylyn/internal/sandbox/ui/editors/ExtensibleBugzillaTaskEditorPage.java b/org.eclipse.mylyn.sandbox.ui/src/org/eclipse/mylyn/internal/sandbox/ui/editors/ExtensibleBugzillaTaskEditorPage.java
index f146df8..eef3173 100644
--- a/org.eclipse.mylyn.sandbox.ui/src/org/eclipse/mylyn/internal/sandbox/ui/editors/ExtensibleBugzillaTaskEditorPage.java
+++ b/org.eclipse.mylyn.sandbox.ui/src/org/eclipse/mylyn/internal/sandbox/ui/editors/ExtensibleBugzillaTaskEditorPage.java
@@ -11,197 +11,28 @@
 
 package org.eclipse.mylyn.internal.sandbox.ui.editors;
 
-import java.util.ArrayList;
-import java.util.List;
 import java.util.Set;
 
-import org.eclipse.jface.action.Action;
-import org.eclipse.jface.action.ControlContribution;
 import org.eclipse.jface.action.IToolBarManager;
-import org.eclipse.mylyn.commons.ui.CommonImages;
-import org.eclipse.mylyn.commons.workbench.forms.CommonFormUtil;
 import org.eclipse.mylyn.internal.bugzilla.ui.editor.BugzillaTaskEditorPage;
-import org.eclipse.mylyn.internal.tasks.ui.editors.RichTextEditor;
-import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorCommentPart;
-import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorCommentPart.CommentGroupViewer;
-import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorCommentPart.CommentViewer;
-import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorDescriptionPart;
-import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorPlanningPart;
-import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorSummaryPart;
-import org.eclipse.mylyn.tasks.core.data.TaskAttribute;
-import org.eclipse.mylyn.tasks.core.data.TaskData;
 import org.eclipse.mylyn.tasks.ui.editors.AbstractTaskEditorPart;
 import org.eclipse.mylyn.tasks.ui.editors.TaskEditor;
 import org.eclipse.mylyn.tasks.ui.editors.TaskEditorPartDescriptor;
-import org.eclipse.osgi.util.NLS;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.custom.StyleRange;
-import org.eclipse.swt.custom.StyledText;
-import org.eclipse.swt.events.ModifyEvent;
-import org.eclipse.swt.events.ModifyListener;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.graphics.Color;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Text;
-import org.eclipse.ui.IWorkbenchActionConstants;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorSite;
 import org.eclipse.ui.actions.ActionFactory;
-import org.eclipse.ui.forms.IFormPart;
-import org.eclipse.ui.forms.events.HyperlinkAdapter;
-import org.eclipse.ui.forms.events.HyperlinkEvent;
-import org.eclipse.ui.forms.widgets.ExpandableComposite;
-import org.eclipse.ui.forms.widgets.FormToolkit;
 
 /**
- * A bugzilla task editor page that has find functionality
+ * A bugzilla task editor page that has wiki facilities.
  * 
  * @author Jingwen Ou
- * @author Lily Guo
  */
 public class ExtensibleBugzillaTaskEditorPage extends BugzillaTaskEditorPage {
 
-	private Action toggleFindAction;
-
-	private static final Color HIGHLIGHTER_YELLOW = new Color(Display.getDefault(), 255, 238, 99);
-
-	private static final Color ERROR_NO_RESULT = new Color(Display.getDefault(), 255, 150, 150);
-
-	private final List<StyledText> styledTexts = new ArrayList<StyledText>();
-
-	private final List<CommentGroupViewer> commentGroupViewers = new ArrayList<CommentGroupViewer>();;
-
 	public ExtensibleBugzillaTaskEditorPage(TaskEditor editor) {
 		super(editor);
 	}
 
-	private void addFindAction(IToolBarManager toolBarManager) {
-		if (toggleFindAction != null && toggleFindAction.isChecked()) {
-			ControlContribution findTextboxControl = new ControlContribution("Find") {
-				@Override
-				protected Control createControl(Composite parent) {
-					FormToolkit toolkit = getTaskEditor().getHeaderForm().getToolkit();
-					final Composite findComposite = toolkit.createComposite(parent);
-
-					GridLayout findLayout = new GridLayout();
-					findLayout.marginHeight = 4;
-					findComposite.setLayout(findLayout);
-					findComposite.setBackground(null);
-
-					final Text findText = toolkit.createText(findComposite, "", SWT.FLAT);
-					findText.setLayoutData(new GridData(100, SWT.DEFAULT));
-					findText.setData(FormToolkit.KEY_DRAW_BORDER, FormToolkit.TEXT_BORDER);
-					findText.setFocus();
-					toolkit.adapt(findText, false, false);
-
-					findText.addModifyListener(new ModifyListener() {
-						@Override
-						public void modifyText(ModifyEvent e) {
-							if (findText.getText().equals("")) {
-								clearSearchResults();
-								findText.setBackground(null);
-							}
-						}
-					});
-
-					findText.addSelectionListener(new SelectionAdapter() {
-						@Override
-						public void widgetDefaultSelected(SelectionEvent event) {
-							searchTaskEditor(findText);
-						}
-					});
-					toolkit.paintBordersFor(findComposite);
-					return findComposite;
-				}
-
-			};
-			toolBarManager.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, findTextboxControl);
-		}
-
-		if (toggleFindAction == null) {
-			toggleFindAction = new Action("", SWT.TOGGLE) {
-				@Override
-				public void run() {
-					if (!this.isChecked()) {
-						clearSearchResults();
-					}
-					getTaskEditor().updateHeaderToolBar();
-				}
-
-			};
-			toggleFindAction.setImageDescriptor(CommonImages.FIND);
-			toggleFindAction.setToolTipText("Find");
-		}
-		toolBarManager.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, toggleFindAction);
-	}
-
-	protected void searchTaskEditor(final Text findBox) {
-		try {
-			setReflow(false);
-			findBox.setBackground(null);
-			if (findBox.getText().equals("")) {
-				return;
-			}
-			clearSearchResults();
-			String searchString = findBox.getText().toLowerCase();
-			for (IFormPart part : getManagedForm().getParts()) {
-				if (!(part instanceof AbstractTaskEditorPart)) {
-					continue;
-				}
-				Control control = ((AbstractTaskEditorPart) part).getControl();
-				if (part instanceof TaskEditorSummaryPart) {
-					if (contains(getModel().getTaskData(), TaskAttribute.SUMMARY, searchString)) {
-						gatherStyledTexts(control, styledTexts);
-					}
-				} else if (part instanceof TaskEditorPlanningPart) {
-					RichTextEditor noteEditor = ((TaskEditorPlanningPart) part).getPlanningPart().getNoteEditor();
-					if (noteEditor != null && noteEditor.getText() != null
-							&& noteEditor.getText().toLowerCase().contains(searchString)) {
-						gatherStyledTexts(control, styledTexts);
-					}
-				} else if (part instanceof TaskEditorDescriptionPart) {
-					if (contains(getModel().getTaskData(), TaskAttribute.DESCRIPTION, searchString)) {
-						gatherStyledTexts(control, styledTexts);
-					}
-				} else if (part instanceof TaskEditorCommentPart) {
-					commentGroupViewers.clear();
-					commentGroupViewers.addAll(((TaskEditorCommentPart) part).getCommentGroupViewers());
-					searchCommentPart(searchString, (TaskEditorCommentPart) part, commentGroupViewers, styledTexts);
-				}
-			}
-
-			for (StyledText styledText : styledTexts) {
-				highlightMatches(searchString, styledText);
-			}
-			if (styledTexts.isEmpty()) {
-				findBox.setBackground(ERROR_NO_RESULT);
-			}
-		} finally {
-			setReflow(true);
-		}
-		reflow();
-		findBox.setFocus();
-	}
-
-	protected static boolean contains(TaskData taskData, String attributeId, String searchString) {
-		TaskAttribute attribute = taskData.getRoot().getMappedAttribute(attributeId);
-		if (attribute != null) {
-			return attribute.getValue().toLowerCase().contains(searchString);
-		}
-		return false;
-	}
-
-	@Override
-	public boolean canPerformAction(String actionId) {
-		if (actionId.equals(ActionFactory.FIND.getId())) {
-			return true;
-		}
-		return super.canPerformAction(actionId);
-	}
-
 	@Override
 	protected Set<TaskEditorPartDescriptor> createPartDescriptors() {
 		Set<TaskEditorPartDescriptor> descriptors = super.createPartDescriptors();
@@ -224,13 +55,37 @@
 		return descriptors;
 	}
 
+	/*
+	 * Find implementation. To be moved to AbstractTaskEditorPage.
+	 */
+
+	private TaskEditorFindSupport findSupport;
+
+	@Override
+	public void init(IEditorSite site, IEditorInput input) {
+		super.init(site, input);
+		findSupport = createFindSupport();
+	}
+
+	/**
+	 * Subclasses may return null to disable the find functionality.
+	 */
+	protected TaskEditorFindSupport createFindSupport() {
+		return new TaskEditorFindSupport(this);
+	}
+
+	@Override
+	public boolean canPerformAction(String actionId) {
+		if (findSupport != null && actionId.equals(ActionFactory.FIND.getId())) {
+			return true;
+		}
+		return super.canPerformAction(actionId);
+	}
+
 	@Override
 	public void doAction(String actionId) {
-		if (actionId.equals(ActionFactory.FIND.getId())) {
-			if (toggleFindAction != null) {
-				toggleFindAction.setChecked(!toggleFindAction.isChecked());
-				toggleFindAction.run();
-			}
+		if (findSupport != null && actionId.equals(ActionFactory.FIND.getId())) {
+			findSupport.toggleFind();
 		}
 		super.doAction(actionId);
 	}
@@ -238,183 +93,8 @@
 	@Override
 	public void fillToolBar(IToolBarManager toolBarManager) {
 		super.fillToolBar(toolBarManager);
-		addFindAction(toolBarManager);
-	}
-
-	private void searchCommentPart(final String searchString, final TaskEditorCommentPart part,
-			List<CommentGroupViewer> commentGroupViewers, final List<StyledText> styledTexts) {
-		TaskData taskData = getModel().getTaskData();
-		List<TaskAttribute> commentAttributes = taskData.getAttributeMapper().getAttributesByType(taskData,
-				TaskAttribute.TYPE_COMMENT);
-
-		if (!anyCommentContains(commentAttributes, searchString)) {
-			return;
-		}
-
-		if (!part.isCommentSectionExpanded()) {
-			try {
-				part.setReflow(false);
-				part.expandAllComments(false);
-			} finally {
-				part.setReflow(true);
-			}
-		}
-
-		int end = commentAttributes.size();
-		boolean expandMatchingGroup = true;
-		for (int i = commentGroupViewers.size() - 1; i >= 0; i--) {
-			final CommentGroupViewer group = commentGroupViewers.get(i);
-			List<CommentViewer> commentViewers = group.getCommentViewers();
-			int start = end - commentViewers.size();
-			List<TaskAttribute> groupAttributes = commentAttributes.subList(start, end);
-			if (expandMatchingGroup && anyCommentContains(groupAttributes, searchString)) {
-				if (!group.isExpanded()) {
-					try {
-						part.setReflow(false);
-						group.setExpanded(true);
-					} finally {
-						part.setReflow(true);
-					}
-				}
-				// once we've seen a matching group, don't expand any more groups
-				expandMatchingGroup = false;
-			}
-			final List<CommentViewer> matchingViewers = searchComments(groupAttributes, commentViewers, searchString);
-			if (!group.isRenderedInSubSection() || group.isExpanded()) {
-				try {
-					part.setReflow(false);
-					gatherStyledTexts(matchingViewers, styledTexts);
-				} finally {
-					part.setReflow(true);
-				}
-				group.clearSectionHyperlink();
-			} else if (!matchingViewers.isEmpty()) {
-				addShowMoreLink(group, matchingViewers, part, searchString, styledTexts);
-			} else {
-				group.clearSectionHyperlink();
-			}
-			end = start;
-		}
-	}
-
-	protected void addShowMoreLink(final CommentGroupViewer group, final List<CommentViewer> matchingViewers,
-			final TaskEditorCommentPart part, final String searchString, final List<StyledText> styledTexts) {
-		HyperlinkAdapter listener = new HyperlinkAdapter() {
-			@Override
-			public void linkActivated(HyperlinkEvent e) {
-				List<StyledText> commentStyledTexts = new ArrayList<StyledText>();
-				try {
-					setReflow(false);
-					part.setReflow(false);
-					group.setExpanded(true);
-					gatherStyledTexts(matchingViewers, commentStyledTexts);
-				} finally {
-					setReflow(true);
-					part.setReflow(true);
-				}
-				for (StyledText styledText : commentStyledTexts) {
-					highlightMatches(searchString, styledText);
-					styledTexts.add(styledText);
-				}
-				group.clearSectionHyperlink();
-				reflow();
-			}
-		};
-		group.createSectionHyperlink(
-				NLS.bind(Messages.ExtensibleBugzillaTaskEditorPage_showNumResults, matchingViewers.size()), listener);
-	}
-
-	private static boolean anyCommentContains(List<TaskAttribute> commentAttributes, String text) {
-		for (TaskAttribute commentAttribute : commentAttributes) {
-			if (commentContains(commentAttribute, text)) {
-				return true;
-			}
-		}
-		return false;
-	}
-
-	private static boolean commentContains(TaskAttribute commentAttribute, String searchString) {
-		TaskAttribute attribute = commentAttribute.getMappedAttribute(TaskAttribute.COMMENT_TEXT);
-		return attribute.getValue().toLowerCase().contains(searchString);
-	}
-
-	private static List<CommentViewer> searchComments(List<TaskAttribute> commentAttributes,
-			List<CommentViewer> commentViewers, String searchString) {
-		List<CommentViewer> matchingViewers = new ArrayList<TaskEditorCommentPart.CommentViewer>();
-		for (int i = 0; i < commentViewers.size(); i++) {
-			CommentViewer viewer = commentViewers.get(i);
-			if (commentContains(commentAttributes.get(i), searchString)) {
-				matchingViewers.add(viewer);
-			}
-		}
-		return matchingViewers;
-	}
-
-	protected static void gatherStyledTexts(List<CommentViewer> commentViewers, List<StyledText> styledTexts) {
-		for (CommentViewer viewer : commentViewers) {
-			try {
-				ExpandableComposite composite = (ExpandableComposite) viewer.getControl();
-				viewer.suppressSelectionChanged(true);
-				if (composite != null && !composite.isExpanded()) {
-					CommonFormUtil.setExpanded(composite, true);
-				}
-				gatherStyledTextsInComposite(composite, styledTexts);
-			} finally {
-				viewer.suppressSelectionChanged(false);
-			}
-		}
-	}
-
-	private static void gatherStyledTexts(Control control, List<StyledText> result) {
-		if (control instanceof ExpandableComposite) {
-			ExpandableComposite composite = (ExpandableComposite) control;
-			if (!composite.isExpanded()) {
-				CommonFormUtil.setExpanded(composite, true);
-			}
-			gatherStyledTextsInComposite(composite, result);
-		} else if (control instanceof Composite) {
-			gatherStyledTextsInComposite((Composite) control, result);
-		}
-	}
-
-	private static void gatherStyledTextsInComposite(Composite composite, List<StyledText> result) {
-		if (composite != null && !composite.isDisposed()) {
-			for (Control child : composite.getChildren()) {
-				if (child instanceof StyledText) {
-					result.add((StyledText) child);
-				} else if (child instanceof Composite) {
-					gatherStyledTextsInComposite((Composite) child, result);
-				}
-			}
-		}
-	}
-
-	private static void highlightMatches(String searchString, StyledText styledText) {
-		String text = styledText.getText().toLowerCase();
-		for (int index = 0; index < text.length(); index += searchString.length()) {
-			index = text.indexOf(searchString, index);
-			if (index == -1) {
-				break;
-			}
-			styledText.setStyleRange(new StyleRange(index, searchString.length(), null, HIGHLIGHTER_YELLOW));
-		}
-	}
-
-	private void clearSearchResults() {
-		for (StyledText oldText : styledTexts) {
-			List<StyleRange> otherRanges = new ArrayList<StyleRange>();
-			if (!oldText.isDisposed()) {
-				for (StyleRange styleRange : oldText.getStyleRanges()) {
-					if (styleRange.background == null || !styleRange.background.equals(HIGHLIGHTER_YELLOW)) {
-						otherRanges.add(styleRange); // preserve ranges that aren't from highlighting search results
-					}
-				}
-				oldText.setStyleRanges(otherRanges.toArray(new StyleRange[otherRanges.size()]));
-			}
-		}
-		styledTexts.clear();
-		for (CommentGroupViewer group : commentGroupViewers) {
-			group.clearSectionHyperlink();
+		if (findSupport != null) {
+			findSupport.addFindAction(toolBarManager);
 		}
 	}
 }
diff --git a/org.eclipse.mylyn.sandbox.ui/src/org/eclipse/mylyn/internal/sandbox/ui/editors/TaskEditorFindSupport.java b/org.eclipse.mylyn.sandbox.ui/src/org/eclipse/mylyn/internal/sandbox/ui/editors/TaskEditorFindSupport.java
new file mode 100644
index 0000000..091eacf
--- /dev/null
+++ b/org.eclipse.mylyn.sandbox.ui/src/org/eclipse/mylyn/internal/sandbox/ui/editors/TaskEditorFindSupport.java
@@ -0,0 +1,408 @@
+/*******************************************************************************

+ * Copyright (c) 2013 Tasktop Technologies and others.

+ * All rights reserved. This program and the accompanying materials

+ * are made available under the terms of the Eclipse Public License v1.0

+ * which accompanies this distribution, and is available at

+ * http://www.eclipse.org/legal/epl-v10.html

+ *

+ * Contributors:

+ *     Tasktop Technologies - initial API and implementation

+ *******************************************************************************/

+

+package org.eclipse.mylyn.internal.sandbox.ui.editors;

+

+import java.util.ArrayList;

+import java.util.List;

+

+import org.eclipse.core.runtime.Assert;

+import org.eclipse.jface.action.Action;

+import org.eclipse.jface.action.ControlContribution;

+import org.eclipse.jface.action.IToolBarManager;

+import org.eclipse.mylyn.commons.ui.CommonImages;

+import org.eclipse.mylyn.commons.workbench.forms.CommonFormUtil;

+import org.eclipse.mylyn.internal.tasks.ui.editors.RichTextEditor;

+import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorCommentPart;

+import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorCommentPart.CommentGroupViewer;

+import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorCommentPart.CommentViewer;

+import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorDescriptionPart;

+import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorPlanningPart;

+import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorSummaryPart;

+import org.eclipse.mylyn.tasks.core.data.TaskAttribute;

+import org.eclipse.mylyn.tasks.core.data.TaskData;

+import org.eclipse.mylyn.tasks.ui.editors.AbstractTaskEditorPage;

+import org.eclipse.mylyn.tasks.ui.editors.AbstractTaskEditorPart;

+import org.eclipse.osgi.util.NLS;

+import org.eclipse.swt.SWT;

+import org.eclipse.swt.custom.StyleRange;

+import org.eclipse.swt.custom.StyledText;

+import org.eclipse.swt.events.ModifyEvent;

+import org.eclipse.swt.events.ModifyListener;

+import org.eclipse.swt.events.SelectionAdapter;

+import org.eclipse.swt.events.SelectionEvent;

+import org.eclipse.swt.graphics.Color;

+import org.eclipse.swt.layout.GridData;

+import org.eclipse.swt.layout.GridLayout;

+import org.eclipse.swt.widgets.Composite;

+import org.eclipse.swt.widgets.Control;

+import org.eclipse.swt.widgets.Display;

+import org.eclipse.swt.widgets.Text;

+import org.eclipse.ui.IWorkbenchActionConstants;

+import org.eclipse.ui.forms.IFormPart;

+import org.eclipse.ui.forms.events.HyperlinkAdapter;

+import org.eclipse.ui.forms.events.HyperlinkEvent;

+import org.eclipse.ui.forms.widgets.ExpandableComposite;

+import org.eclipse.ui.forms.widgets.FormToolkit;

+

+/**

+ * Adds support for finding text to the task editor.

+ * 

+ * @author Jingwen Ou

+ * @author Lily Guo

+ * @author Sam Davis

+ */

+public class TaskEditorFindSupport {

+

+	private Action toggleFindAction;

+

+	private static final Color HIGHLIGHTER_YELLOW = new Color(Display.getDefault(), 255, 238, 99);

+

+	private static final Color ERROR_NO_RESULT = new Color(Display.getDefault(), 255, 150, 150);

+

+	private final List<StyledText> styledTexts = new ArrayList<StyledText>();

+

+	private final List<CommentGroupViewer> commentGroupViewers = new ArrayList<CommentGroupViewer>();

+

+	private final AbstractTaskEditorPage taskEditorPage;;

+

+	public TaskEditorFindSupport(AbstractTaskEditorPage page) {

+		Assert.isNotNull(page);

+		this.taskEditorPage = page;

+	}

+

+	public void toggleFind() {

+		if (toggleFindAction != null) {

+			toggleFindAction.setChecked(!toggleFindAction.isChecked());

+			toggleFindAction.run();

+		}

+	}

+

+	public void addFindAction(IToolBarManager toolBarManager) {

+		if (toggleFindAction != null && toggleFindAction.isChecked()) {

+			ControlContribution findTextboxControl = new ControlContribution("Find") {

+				@Override

+				protected Control createControl(Composite parent) {

+					FormToolkit toolkit = taskEditorPage.getEditor().getHeaderForm().getToolkit();

+					final Composite findComposite = toolkit.createComposite(parent);

+

+					GridLayout findLayout = new GridLayout();

+					findLayout.marginHeight = 4;

+					findComposite.setLayout(findLayout);

+					findComposite.setBackground(null);

+

+					final Text findText = toolkit.createText(findComposite, "", SWT.FLAT);

+					findText.setLayoutData(new GridData(100, SWT.DEFAULT));

+					findText.setData(FormToolkit.KEY_DRAW_BORDER, FormToolkit.TEXT_BORDER);

+					findText.setFocus();

+					toolkit.adapt(findText, false, false);

+

+					findText.addModifyListener(new ModifyListener() {

+						@Override

+						public void modifyText(ModifyEvent e) {

+							if (findText.getText().equals("")) {

+								clearSearchResults();

+								findText.setBackground(null);

+							}

+						}

+					});

+

+					findText.addSelectionListener(new SelectionAdapter() {

+						@Override

+						public void widgetDefaultSelected(SelectionEvent event) {

+							searchTaskEditor(findText);

+						}

+					});

+					toolkit.paintBordersFor(findComposite);

+					return findComposite;

+				}

+

+			};

+			toolBarManager.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, findTextboxControl);

+		}

+

+		if (toggleFindAction == null) {

+			toggleFindAction = new Action("", SWT.TOGGLE) {

+				@Override

+				public void run() {

+					if (!this.isChecked()) {

+						clearSearchResults();

+					}

+					taskEditorPage.getEditor().updateHeaderToolBar();

+				}

+

+			};

+			toggleFindAction.setImageDescriptor(CommonImages.FIND);

+			toggleFindAction.setToolTipText("Find");

+		}

+		toolBarManager.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, toggleFindAction);

+	}

+

+	protected void searchTaskEditor(final Text findBox) {

+		try {

+			taskEditorPage.setReflow(false);

+			findBox.setBackground(null);

+			if (findBox.getText().equals("")) {

+				return;

+			}

+			clearSearchResults();

+			String searchString = findBox.getText().toLowerCase();

+			for (IFormPart part : taskEditorPage.getManagedForm().getParts()) {

+				if (!(part instanceof AbstractTaskEditorPart)) {

+					continue;

+				}

+				Control control = ((AbstractTaskEditorPart) part).getControl();

+				if (part instanceof TaskEditorSummaryPart) {

+					if (contains(taskEditorPage.getModel().getTaskData(), TaskAttribute.SUMMARY, searchString)) {

+						gatherStyledTexts(control, styledTexts);

+					}

+				} else if (part instanceof TaskEditorPlanningPart) {

+					RichTextEditor noteEditor = ((TaskEditorPlanningPart) part).getPlanningPart().getNoteEditor();

+					if (noteEditor != null && noteEditor.getText() != null

+							&& noteEditor.getText().toLowerCase().contains(searchString)) {

+						gatherStyledTexts(control, styledTexts);

+					}

+				} else if (part instanceof TaskEditorDescriptionPart) {

+					if (contains(taskEditorPage.getModel().getTaskData(), TaskAttribute.DESCRIPTION, searchString)) {

+						gatherStyledTexts(control, styledTexts);

+					}

+				} else if (part instanceof TaskEditorCommentPart) {

+					commentGroupViewers.clear();

+					commentGroupViewers.addAll(((TaskEditorCommentPart) part).getCommentGroupViewers());

+					searchCommentPart(searchString, (TaskEditorCommentPart) part, commentGroupViewers, styledTexts);

+				}

+			}

+

+			for (StyledText styledText : styledTexts) {

+				highlightMatches(searchString, styledText);

+			}

+			if (styledTexts.isEmpty()) {

+				findBox.setBackground(ERROR_NO_RESULT);

+			}

+		} finally {

+			taskEditorPage.setReflow(true);

+		}

+		taskEditorPage.reflow();

+		findBox.setFocus();

+	}

+

+	protected static boolean contains(TaskData taskData, String attributeId, String searchString) {

+		TaskAttribute attribute = taskData.getRoot().getMappedAttribute(attributeId);

+		if (attribute != null) {

+			return attribute.getValue().toLowerCase().contains(searchString);

+		}

+		return false;

+	}

+

+//	@Override

+//	public boolean canPerformAction(String actionId) {

+//		if (actionId.equals(ActionFactory.FIND.getId())) {

+//			return true;

+//		}

+//		return super.canPerformAction(actionId);

+//	}

+//

+//

+//	@Override

+//	public void doAction(String actionId) {

+//		if (actionId.equals(ActionFactory.FIND.getId())) {

+//			if (toggleFindAction != null) {

+//				toggleFindAction.setChecked(!toggleFindAction.isChecked());

+//				toggleFindAction.run();

+//			}

+//		}

+//		super.doAction(actionId);

+//	}

+//

+//	@Override

+//	public void fillToolBar(IToolBarManager toolBarManager) {

+//		super.fillToolBar(toolBarManager);

+//		addFindAction(toolBarManager);

+//	}

+

+	private void searchCommentPart(final String searchString, final TaskEditorCommentPart part,

+			List<CommentGroupViewer> commentGroupViewers, final List<StyledText> styledTexts) {

+		TaskData taskData = taskEditorPage.getModel().getTaskData();

+		List<TaskAttribute> commentAttributes = taskData.getAttributeMapper().getAttributesByType(taskData,

+				TaskAttribute.TYPE_COMMENT);

+

+		if (!anyCommentContains(commentAttributes, searchString)) {

+			return;

+		}

+

+		if (!part.isCommentSectionExpanded()) {

+			try {

+				part.setReflow(false);

+				part.expandAllComments(false);

+			} finally {

+				part.setReflow(true);

+			}

+		}

+

+		int end = commentAttributes.size();

+		boolean expandMatchingGroup = true;

+		for (int i = commentGroupViewers.size() - 1; i >= 0; i--) {

+			final CommentGroupViewer group = commentGroupViewers.get(i);

+			List<CommentViewer> commentViewers = group.getCommentViewers();

+			int start = end - commentViewers.size();

+			List<TaskAttribute> groupAttributes = commentAttributes.subList(start, end);

+			if (expandMatchingGroup && anyCommentContains(groupAttributes, searchString)) {

+				if (!group.isExpanded()) {

+					try {

+						part.setReflow(false);

+						group.setExpanded(true);

+					} finally {

+						part.setReflow(true);

+					}

+				}

+				// once we've seen a matching group, don't expand any more groups

+				expandMatchingGroup = false;

+			}

+			final List<CommentViewer> matchingViewers = searchComments(groupAttributes, commentViewers, searchString);

+			if (!group.isRenderedInSubSection() || group.isExpanded()) {

+				try {

+					part.setReflow(false);

+					gatherStyledTexts(matchingViewers, styledTexts);

+				} finally {

+					part.setReflow(true);

+				}

+				group.clearSectionHyperlink();

+			} else if (!matchingViewers.isEmpty()) {

+				addShowMoreLink(group, matchingViewers, part, searchString, styledTexts);

+			} else {

+				group.clearSectionHyperlink();

+			}

+			end = start;

+		}

+	}

+

+	protected void addShowMoreLink(final CommentGroupViewer group, final List<CommentViewer> matchingViewers,

+			final TaskEditorCommentPart part, final String searchString, final List<StyledText> styledTexts) {

+		HyperlinkAdapter listener = new HyperlinkAdapter() {

+			@Override

+			public void linkActivated(HyperlinkEvent e) {

+				List<StyledText> commentStyledTexts = new ArrayList<StyledText>();

+				try {

+					taskEditorPage.setReflow(false);

+					part.setReflow(false);

+					group.setExpanded(true);

+					gatherStyledTexts(matchingViewers, commentStyledTexts);

+				} finally {

+					taskEditorPage.setReflow(true);

+					part.setReflow(true);

+				}

+				for (StyledText styledText : commentStyledTexts) {

+					highlightMatches(searchString, styledText);

+					styledTexts.add(styledText);

+				}

+				group.clearSectionHyperlink();

+				taskEditorPage.reflow();

+			}

+		};

+		group.createSectionHyperlink(

+				NLS.bind(Messages.ExtensibleBugzillaTaskEditorPage_showNumResults, matchingViewers.size()), listener);

+	}

+

+	private static boolean anyCommentContains(List<TaskAttribute> commentAttributes, String text) {

+		for (TaskAttribute commentAttribute : commentAttributes) {

+			if (commentContains(commentAttribute, text)) {

+				return true;

+			}

+		}

+		return false;

+	}

+

+	private static boolean commentContains(TaskAttribute commentAttribute, String searchString) {

+		TaskAttribute attribute = commentAttribute.getMappedAttribute(TaskAttribute.COMMENT_TEXT);

+		return attribute.getValue().toLowerCase().contains(searchString);

+	}

+

+	private static List<CommentViewer> searchComments(List<TaskAttribute> commentAttributes,

+			List<CommentViewer> commentViewers, String searchString) {

+		List<CommentViewer> matchingViewers = new ArrayList<TaskEditorCommentPart.CommentViewer>();

+		for (int i = 0; i < commentViewers.size(); i++) {

+			CommentViewer viewer = commentViewers.get(i);

+			if (commentContains(commentAttributes.get(i), searchString)) {

+				matchingViewers.add(viewer);

+			}

+		}

+		return matchingViewers;

+	}

+

+	protected static void gatherStyledTexts(List<CommentViewer> commentViewers, List<StyledText> styledTexts) {

+		for (CommentViewer viewer : commentViewers) {

+			try {

+				ExpandableComposite composite = (ExpandableComposite) viewer.getControl();

+				viewer.suppressSelectionChanged(true);

+				if (composite != null && !composite.isExpanded()) {

+					CommonFormUtil.setExpanded(composite, true);

+				}

+				gatherStyledTextsInComposite(composite, styledTexts);

+			} finally {

+				viewer.suppressSelectionChanged(false);

+			}

+		}

+	}

+

+	private static void gatherStyledTexts(Control control, List<StyledText> result) {

+		if (control instanceof ExpandableComposite) {

+			ExpandableComposite composite = (ExpandableComposite) control;

+			if (!composite.isExpanded()) {

+				CommonFormUtil.setExpanded(composite, true);

+			}

+			gatherStyledTextsInComposite(composite, result);

+		} else if (control instanceof Composite) {

+			gatherStyledTextsInComposite((Composite) control, result);

+		}

+	}

+

+	private static void gatherStyledTextsInComposite(Composite composite, List<StyledText> result) {

+		if (composite != null && !composite.isDisposed()) {

+			for (Control child : composite.getChildren()) {

+				if (child instanceof StyledText) {

+					result.add((StyledText) child);

+				} else if (child instanceof Composite) {

+					gatherStyledTextsInComposite((Composite) child, result);

+				}

+			}

+		}

+	}

+

+	private static void highlightMatches(String searchString, StyledText styledText) {

+		String text = styledText.getText().toLowerCase();

+		for (int index = 0; index < text.length(); index += searchString.length()) {

+			index = text.indexOf(searchString, index);

+			if (index == -1) {

+				break;

+			}

+			styledText.setStyleRange(new StyleRange(index, searchString.length(), null, HIGHLIGHTER_YELLOW));

+		}

+	}

+

+	private void clearSearchResults() {

+		for (StyledText oldText : styledTexts) {

+			List<StyleRange> otherRanges = new ArrayList<StyleRange>();

+			if (!oldText.isDisposed()) {

+				for (StyleRange styleRange : oldText.getStyleRanges()) {

+					if (styleRange.background == null || !styleRange.background.equals(HIGHLIGHTER_YELLOW)) {

+						otherRanges.add(styleRange); // preserve ranges that aren't from highlighting search results

+					}

+				}

+				oldText.setStyleRanges(otherRanges.toArray(new StyleRange[otherRanges.size()]));

+			}

+		}

+		styledTexts.clear();

+		for (CommentGroupViewer group : commentGroupViewers) {

+			group.clearSectionHyperlink();

+		}

+	}

+

+}