242 lines
9.2 KiB
C#
242 lines
9.2 KiB
C#
// 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
|
|
{
|
|
/// <summary>
|
|
/// Moves through search highlights built in a ParagraphBuilder
|
|
/// changing the color of the current highlight.
|
|
/// </summary>
|
|
internal class ParagraphSearcher
|
|
{
|
|
/// <summary>
|
|
/// Highlight for all matches except the current.
|
|
/// </summary>
|
|
internal static readonly Brush HighlightBrush = Brushes.Yellow;
|
|
|
|
/// <summary>
|
|
/// Highlight for the current match.
|
|
/// </summary>
|
|
private static readonly Brush CurrentHighlightBrush = Brushes.Cyan;
|
|
|
|
/// <summary>
|
|
/// Current match being highlighted in search.
|
|
/// </summary>
|
|
private Run currentHighlightedMatch;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the ParagraphSearcher class.
|
|
/// </summary>
|
|
internal ParagraphSearcher()
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Move to the next highlight starting at the <paramref name="caretPosition"/>.
|
|
/// </summary>
|
|
/// <param name="forward">True for next false for previous.</param>
|
|
/// <param name="caretPosition">Caret position.</param>
|
|
/// <returns>The next highlight starting at the <paramref name="caretPosition"/>.</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resets the search for fresh calls to MoveAndHighlightNextNextMatch.
|
|
/// </summary>
|
|
internal void ResetSearch()
|
|
{
|
|
this.currentHighlightedMatch = null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if <paramref name="run"/> is highlighted.
|
|
/// </summary>
|
|
/// <param name="run">Run to check if is highlighted.</param>
|
|
/// <returns>True if <paramref name="run"/> is highlighted.</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the next or previous run according to <paramref name="forward"/>.
|
|
/// </summary>
|
|
/// <param name="currentRun">The current run.</param>
|
|
/// <param name="forward">True for next false for previous.</param>
|
|
/// <returns>The next or previous run according to <paramref name="forward"/>.</returns>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the run of an inline. Inlines in a ParagrahBuilder are either a Run or a Bold
|
|
/// which contains a Run.
|
|
/// </summary>
|
|
/// <param name="inline">Inline to get the run from.</param>
|
|
/// <returns>The run of the inline.</returns>
|
|
private static Run GetRun(Inline inline)
|
|
{
|
|
Bold inlineBold = inline as Bold;
|
|
if (inlineBold != null)
|
|
{
|
|
return (Run)inlineBold.Inlines.FirstInline;
|
|
}
|
|
|
|
return (Run)inline;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the next highlighted run starting and including <paramref name="currentRun"/>
|
|
/// according to the direction specified in <paramref name="forward"/>.
|
|
/// </summary>
|
|
/// <param name="currentRun">The current run.</param>
|
|
/// <param name="forward">True for next false for previous.</param>
|
|
/// <returns>
|
|
/// the next highlighted run starting and including <paramref name="currentRun"/>
|
|
/// according to the direction specified in <paramref name="forward"/>.
|
|
/// </returns>
|
|
private static Run GetNextMatch(Run currentRun, bool forward)
|
|
{
|
|
while (currentRun != null)
|
|
{
|
|
if (ParagraphSearcher.Ishighlighted(currentRun))
|
|
{
|
|
return currentRun;
|
|
}
|
|
|
|
currentRun = ParagraphSearcher.GetNextRun(currentRun, forward);
|
|
}
|
|
|
|
return currentRun;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the run's paragraph.
|
|
/// </summary>
|
|
/// <param name="run">Run to get the paragraph from.</param>
|
|
/// <returns>The run's paragraph.</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if the run is the fiorst run of the paragraph.
|
|
/// </summary>
|
|
/// <param name="run">Run to check.</param>
|
|
/// <returns>True if the run is the fiorst run of the paragraph.</returns>
|
|
private static bool IsFirstRun(Run run)
|
|
{
|
|
Paragraph paragraph = GetParagraph(run);
|
|
Run firstRun = ParagraphSearcher.GetRun(paragraph.Inlines.FirstInline);
|
|
return run == firstRun;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the first or lasr run in the paragraph containing <paramref name="caretRun"/>.
|
|
/// </summary>
|
|
/// <param name="caretRun">Run containing the caret.</param>
|
|
/// <param name="forward">True for first false for last.</param>
|
|
/// <returns>The first or last run in the paragraph containing <paramref name="caretRun"/>.</returns>
|
|
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);
|
|
}
|
|
}
|
|
}
|