//----------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // // // Implements ParagraphSearcher. // //----------------------------------------------------------------------- namespace Microsoft.Management.UI.Internal { using System.Diagnostics; using System.Windows.Documents; using System.Windows.Media; /// /// Moves through search highlights built in a ParagraphBuilder /// changing the color of the current highlight /// internal class ParagraphSearcher { /// /// Highlight for all matches except the current /// internal static readonly Brush HighlightBrush = Brushes.Yellow; /// /// Highlight for the current match /// private static readonly Brush CurrentHighlightBrush = Brushes.Cyan; /// /// Current match being highlighted in search /// private Run currentHighlightedMatch; /// /// Initializes a new instance of the ParagraphSearcher class /// internal ParagraphSearcher() { } /// /// Move to the next highlight starting at the /// /// true for next false for previous /// caret position /// the next highlight starting at the internal Run MoveAndHighlightNextNextMatch(bool forward, TextPointer caretPosition) { Debug.Assert(caretPosition != null, "a caret position is always valid"); Debug.Assert(caretPosition.Parent != null && caretPosition.Parent is Run, "a caret Parent is always a valid Run"); Run caretRun = (Run)caretPosition.Parent; Run currentRun; if (this.currentHighlightedMatch != null) { // restore the current highlighted background to plain highlighted this.currentHighlightedMatch.Background = ParagraphSearcher.HighlightBrush; } // If the caret is in the end of a highlight we move to the adjacent run // It has to be in the end because if there is a match at the beginning of the file // and the caret has not been touched (so it is in the beginning of the file too) // we want to highlight this first match. // Considering the caller always set the caret to the end of the highlight // The condition below works well for successive searchs // We also need to move to the adjacent run if the caret is at the first run and we // are moving backwards so that a search backwards when the first run is highlighted // and the caret is at the beginning will wrap to the end if ((!forward && IsFirstRun(caretRun)) || ((caretPosition.GetOffsetToPosition(caretRun.ContentEnd) == 0) && ParagraphSearcher.Ishighlighted(caretRun))) { currentRun = ParagraphSearcher.GetNextRun(caretRun, forward); } else { currentRun = caretRun; } currentRun = ParagraphSearcher.GetNextMatch(currentRun, forward); if (currentRun == null) { // if we could not find a next highlight wrap arround currentRun = ParagraphSearcher.GetFirstOrLastRun(caretRun, forward); currentRun = ParagraphSearcher.GetNextMatch(currentRun, forward); } this.currentHighlightedMatch = currentRun; if (this.currentHighlightedMatch != null) { // restore the current highlighted background to current highlighted this.currentHighlightedMatch.Background = ParagraphSearcher.CurrentHighlightBrush; } return currentRun; } /// /// Resets the search for fresh calls to MoveAndHighlightNextNextMatch /// internal void ResetSearch() { this.currentHighlightedMatch = null; } /// /// Returns true if is highlighted /// /// run to check if is highlighted /// true if is highlighted private static bool Ishighlighted(Run run) { if (run == null) { return false; } SolidColorBrush background = run.Background as SolidColorBrush; if (background != null && background == ParagraphSearcher.HighlightBrush) { return true; } return false; } /// /// Get the next or previous run according to /// /// the current run /// true for next false for previous /// the next or previous run according to private static Run GetNextRun(Run currentRun, bool forward) { Bold parentBold = currentRun.Parent as Bold; Inline nextInline; if (forward) { nextInline = parentBold != null ? ((Inline)parentBold).NextInline : currentRun.NextInline; } else { nextInline = parentBold != null ? ((Inline)parentBold).PreviousInline : currentRun.PreviousInline; } return GetRun(nextInline); } /// /// Gets the run of an inline. Inlines in a ParagraphBuilder are either a Run or a Bold /// which contains a Run /// /// inline to get the run from /// the run of the inline private static Run GetRun(Inline inline) { Bold inlineBold = inline as Bold; if (inlineBold != null) { return (Run)inlineBold.Inlines.FirstInline; } return (Run)inline; } /// /// Gets the next highlighted run starting and including /// according to the direction specified in /// /// the current run /// true for next false for previous /// /// the next highlighted run starting and including /// according to the direction specified in /// private static Run GetNextMatch(Run currentRun, bool forward) { while (currentRun != null) { if (ParagraphSearcher.Ishighlighted(currentRun)) { return currentRun; } currentRun = ParagraphSearcher.GetNextRun(currentRun, forward); } return currentRun; } /// /// Gets the run's paragraph /// /// run to get the paragraph from /// the run's paragraph private static Paragraph GetParagraph(Run run) { Bold parentBold = run.Parent as Bold; Paragraph parentParagraph = (parentBold != null ? parentBold.Parent : run.Parent) as Paragraph; Debug.Assert(parentParagraph != null, "the documents we are searching are built with ParagraphBuilder, which builds the document like this"); return parentParagraph; } /// /// Returns true if the run is the first run of the paragraph /// /// run to check /// true if the run is the first run of the paragraph private static bool IsFirstRun(Run run) { Paragraph paragraph = GetParagraph(run); Run firstRun = ParagraphSearcher.GetRun(paragraph.Inlines.FirstInline); return run == firstRun; } /// /// Gets the first or lasr run in the paragraph containing /// /// run containing the caret /// true for first false for last /// the first or last run in the paragraph containing private static Run GetFirstOrLastRun(Run caretRun, bool forward) { Debug.Assert(caretRun != null, "a caret run is always valid"); Paragraph paragraph = GetParagraph(caretRun); Inline firstOrLastInline; if (forward) { firstOrLastInline = paragraph.Inlines.FirstInline; } else { firstOrLastInline = paragraph.Inlines.LastInline; } return GetRun(firstOrLastInline); } } }