// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Diagnostics;
using System.Windows.Documents;
using System.Windows.Media;
namespace Microsoft.Management.UI.Internal
{
///
/// 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 allways valid");
Debug.Assert(caretPosition.Parent != null && caretPosition.Parent is Run, "a caret PArent is allways a valid Run");
Run caretRun = (Run)caretPosition.Parent;
Run currentRun;
if (this.currentHighlightedMatch != null)
{
// restore the curent 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 begining 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 allways 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 curent highligthed 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 ParagrahBuilder 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 saerching are built with ParagraphBuilder, which builds the document like this");
return parentParagraph;
}
///
/// Returns true if the run is the fiorst run of the paragraph.
///
/// Run to check.
/// True if the run is the fiorst 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 allways valid");
Paragraph paragraph = GetParagraph(caretRun);
Inline firstOrLastInline;
if (forward)
{
firstOrLastInline = paragraph.Inlines.FirstInline;
}
else
{
firstOrLastInline = paragraph.Inlines.LastInline;
}
return GetRun(firstOrLastInline);
}
}
}