Code:
/ Net / Net / 3.5.50727.3053 / DEVDIV / depot / DevDiv / releases / Orcas / SP / wpf / src / Framework / MS / Internal / Annotations / Component / AnnotationHighlightLayer.cs / 1 / AnnotationHighlightLayer.cs
//----------------------------------------------------------------------------
//
//
// Copyright (C) Microsoft Corporation. All rights reserved.
//
//
// Description: HighlightLayer for annotations. It handles highlights
// and StickyNote anchors as well.
//
// History:
// 01/27/2005 ssimova - Created
//
//---------------------------------------------------------------------------
using System;
using MS.Internal;
using System.Diagnostics;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Documents;
using MS.Internal.Documents;
using System.Windows.Media;
using MS.Internal.Text;
using System.Windows.Shapes;
using MS.Internal.Annotations.Anchoring;
using System.Windows.Controls;
namespace MS.Internal.Annotations.Component
{
// Highlight rendering for the Annotation highlight and sticky note anchor.
internal class AnnotationHighlightLayer : HighlightLayer
{
//-----------------------------------------------------
//
// Constructors
//
//-----------------------------------------------------
#region Constructors
///
/// Creates a new instance of the highlight layer
///
internal AnnotationHighlightLayer()
{
_segments = new List();
}
#endregion Constructors
//------------------------------------------------------
//
// Internal Methods
//
//-----------------------------------------------------
#region Internal Methods
///
/// Adds a new text range to the layer
///
///
/// If the range does not overlap any of the existing segments it is added on the appropriate position
/// in the _segments array - the _segments array contains non overlapping HighlightSegments sorted by ITextPointers.
/// If the range overlaps some of the existing segments its IHighlightRange is added to the list of owners
/// for those segments. If the input range overlaps partially some of the existing segments they will be split into
/// new segments by the corresponding range end points. For the overlapping parts the IHighlightRange of the new
/// range will be added. If the new range overlaps parts of existing segments and some nonhighlighted areas, new
/// HighlightSegments will be generated for the nonhighlighted areas. Example:
/// 1. text "1234567" has 2 HighlightSegments covereing "234" and "67"
/// 2. Add a range that covers "3456"
/// 3.The result will be 5 HighlightSegments - "2", "34", "5", "6" and "7". "2" and "7" will keep their original owners array.
/// "5" will have only one owner - the new IHighlightRange, "34" and "6" will have their previous owners + the new one.
///
/// the highlight range owner
internal void AddRange(IHighlightRange highlightRange)
{
Invariant.Assert(highlightRange != null, "the owner is null");
ITextPointer start = highlightRange.Range.Start;
ITextPointer end = highlightRange.Range.End;
//check input data
Debug.Assert(start != null, "start pointer is null");
Debug.Assert(end != null, "end pointer is null");
Debug.Assert(start.CompareTo(end) <= 0, "end pointer before start");
if (start.CompareTo(end) == 0)
{
//it is a 0 length highlight - do not render it
return;
}
if (_segments.Count == 0)
{
//set container type
object textContainer = start.TextContainer;
IsFixedContainer = textContainer is FixedTextContainer || textContainer is DocumentSequenceTextContainer;
}
ITextPointer invalidateStart;
ITextPointer invalidateEnd;
ProcessOverlapingSegments(highlightRange, out invalidateStart, out invalidateEnd);
//fire event - do it only for fixed to avoid disposing of the page in flow. Needs to be changed in V2.
if ((Changed != null) && IsFixedContainer)
{
Changed(this, new AnnotationHighlightChangedEventArgs(invalidateStart, invalidateEnd));
}
}
///
/// RemoveRange from the highlight layer. The corresponding IHighlightRange object will be removed
/// from all HighlightSegments that belong to this range. All ranges with no more owners will also be removed.
/// The visual properties of ranges that have more owners might change.
///
/// The highlight range owner
/// true if the range is successfuly removed
internal void RemoveRange(IHighlightRange highlightRange)
{
Debug.Assert(highlightRange != null, "null range data");
//if range is 0 length do nothing
if (highlightRange.Range.Start.CompareTo(highlightRange.Range.End) == 0)
return;
int startSeg;
int endSeg;
GetSpannedSegments(highlightRange.Range.Start, highlightRange.Range.End, out startSeg, out endSeg);
//get invalidate start and end point
ITextPointer invalidateStart = _segments[startSeg].Segment.Start;
ITextPointer invalidateEnd = _segments[endSeg].Segment.End;
for (int i = startSeg; i <= endSeg; )
{
HighlightSegment highlightSegment = _segments[i];
int count = highlightSegment.RemoveOwner(highlightRange);
if (count == 0)
{
_segments.Remove(highlightSegment);
endSeg--;
}
else
{
i++;
}
}
//TBD:Should do something against fragmentation
//fire event - do it only for fixed to avoid disposing of the page in flow. Needs to be changed in V2.
if ((Changed != null) && IsFixedContainer)
{
Changed(this, new AnnotationHighlightChangedEventArgs(invalidateStart, invalidateEnd));
}
}
///
/// Notifies segments and listeners that a IHighlightRange colors have been modified
///
/// the highlight range to be modified
/// true if successfuly modified
internal void ModifiedRange(IHighlightRange highlightRange)
{
Invariant.Assert(highlightRange != null, "null range data");
//if range is 0 length do nothing
if (highlightRange.Range.Start.CompareTo(highlightRange.Range.End) == 0)
return;
int startSeg;
int endSeg;
GetSpannedSegments(highlightRange.Range.Start, highlightRange.Range.End, out startSeg, out endSeg);
//update colors
for (int seg = startSeg; seg < endSeg; seg++)
{
//the owners have not changed so this will only update the colors
_segments[seg].UpdateOwners();
}
//get invalidate start and end point
ITextPointer invalidateStart = _segments[startSeg].Segment.Start;
ITextPointer invalidateEnd = _segments[endSeg].Segment.End;
//fire event - do it only for fixed to avoid disposing of the page in flow. Needs to be changed in V2.
if ((Changed != null) && IsFixedContainer)
{
Changed(this, new AnnotationHighlightChangedEventArgs(invalidateStart, invalidateEnd));
}
}
///
/// Activate/Deactivate highlight range
///
/// the text range to be modified
/// true - activate, false - deactivate
internal void ActivateRange(IHighlightRange highlightRange, bool activate)
{
Invariant.Assert(highlightRange != null, "null range data");
//if range is 0 length do nothing
if (highlightRange.Range.Start.CompareTo(highlightRange.Range.End) == 0)
return;
int startSeg;
int endSeg;
GetSpannedSegments(highlightRange.Range.Start, highlightRange.Range.End, out startSeg, out endSeg);
//get invalidate start and end point
ITextPointer invalidateStart = _segments[startSeg].Segment.Start;
ITextPointer invalidateEnd = _segments[endSeg].Segment.End;
//set them as active
for (int i = startSeg; i <= endSeg; i++)
{
if (activate)
_segments[i].AddActiveOwner(highlightRange);
else
_segments[i].RemoveActiveOwner(highlightRange);
}
//fire event - do it only for fixed to avoid disposing of the page in flow. Needs to be changed in V2.
if ((Changed != null) && IsFixedContainer)
{
Changed(this, new AnnotationHighlightChangedEventArgs(invalidateStart, invalidateEnd));
}
}
///
/// Returns the value of a property stored on scoping highlight, if any.
/// If no property value is set, returns null.
///
///
/// We scan all the segments in a loop end for each segment make two checks:
/// 1. Is the textPosition before the beginning of the segment, or at the begining with
/// GicalDirection.Backward. If this is true our point is before the current segment.
/// We know that if it belongs to any of the previous segments the loop will stop, so that
/// means the point is outside any segments - break the loop.
/// 2. If the textPosition is not before the current segment, check if it is on the current segment
/// or at the end with LogicalDirection.Backward. If this is true the point belongs to the
/// current segment - save it and break the loop.
///
/// position to check for
/// logical direction
///
internal override object GetHighlightValue(StaticTextPointer textPosition, LogicalDirection direction)
{
object value = DependencyProperty.UnsetValue;
HighlightSegment highlightSegment = null;
for (int i = 0; i < _segments.Count; i++)
{
highlightSegment = _segments[i];
if ((highlightSegment.Segment.Start.CompareTo(textPosition) > 0) ||
((highlightSegment.Segment.Start.CompareTo(textPosition) == 0) && (direction == LogicalDirection.Backward)))
{
// the point is outside highlights
break;
}
if ((highlightSegment.Segment.End.CompareTo(textPosition) > 0) ||
((highlightSegment.Segment.End.CompareTo(textPosition) == 0) && (direction == LogicalDirection.Backward)))
{
value = highlightSegment;
break;
}
}
return value;
}
// Returns true if the indicated content has scoping highlights.
internal override bool IsContentHighlighted(StaticTextPointer staticTextPosition, LogicalDirection direction)
{
return GetHighlightValue(staticTextPosition, direction) != DependencyProperty.UnsetValue;
}
// Returns the position of the next highlight start or end in an
// indicated direction, or null if there is no such position.
internal override StaticTextPointer GetNextChangePosition(StaticTextPointer textPosition, LogicalDirection direction)
{
ITextPointer dynamicPosition;
if (direction == LogicalDirection.Forward)
{
dynamicPosition = GetNextForwardPosition(textPosition);
}
else
{
dynamicPosition = GetNextBackwardPosition(textPosition);
}
return dynamicPosition == null ? StaticTextPointer.Null : dynamicPosition.CreateStaticPointer();
}
#endregion Internal Methods
//------------------------------------------------------
//
// Internal Properties
//
//------------------------------------------------------
#region Internal Properties
///
/// Type identifying this layer for Highlights.GetHighlightValue calls.
///
internal override Type OwnerType
{
get
{
return typeof(HighlightComponent);
}
}
#endregion Internal Properties
//-----------------------------------------------------
//
// Internal Events
//
//------------------------------------------------------
#region Internal Events
// Event raised when a highlight range is inserted, removed, moved, or
// has a local property value change.
internal override event HighlightChangedEventHandler Changed;
#endregion Internal Events
//-----------------------------------------------------
//
// Private Methods
//
//-----------------------------------------------------
#region Private Methods
///
/// Checks if the input range overlaps existing segments and splits them if needed
///
/// range data
/// start pointer of the invalid area
/// end pointer of the invalid area
/// This method requires ordered nonoverlaped input segments. Otherwise it will assert.
private void ProcessOverlapingSegments(IHighlightRange highlightRange, out ITextPointer invalidateStart, out ITextPointer invalidateEnd)
{
Debug.Assert(highlightRange != null, " null highlight range");
ReadOnlyCollection rangeSegments = highlightRange.Range.TextSegments;
Debug.Assert((rangeSegments != null) && (rangeSegments.Count > 0), "invalid rangeSegments");
invalidateStart = null;
invalidateEnd = null;
int ind = 0;
IEnumerator rangeEnumerator = rangeSegments.GetEnumerator();
TextSegment rangeSegment = rangeEnumerator.MoveNext() ? rangeEnumerator.Current : TextSegment.Null;
while ((ind < _segments.Count) && (!rangeSegment.IsNull))
{
HighlightSegment highlightSegment = _segments[ind];
Debug.Assert(highlightSegment != null, "null highlight segment");
if (highlightSegment.Segment.Start.CompareTo(rangeSegment.Start) <= 0)
{
if (highlightSegment.Segment.End.CompareTo(rangeSegment.Start) > 0)
{
//split highlightSegment
//the split method is smart enough to take care of edge cases - point on start/end of the
//segment, points outside the segment etc
IList res = highlightSegment.Split(rangeSegment.Start, rangeSegment.End, highlightRange);
//if the result does not contain the original segment we need to clear the owners
if (!res.Contains(highlightSegment))
highlightSegment.ClearOwners();
_segments.Remove(highlightSegment);
_segments.InsertRange(ind, res);
ind = ind + res.Count - 1;
//check if we need to move to next range segment
if (rangeSegment.End.CompareTo(highlightSegment.Segment.End) <= 0)
{
//get next one
bool next = rangeEnumerator.MoveNext();
Debug.Assert(rangeEnumerator.Current.IsNull || !next ||
(rangeSegment.End.CompareTo(rangeEnumerator.Current.Start) <= 0),
"overlapped range segments");
rangeSegment = next ? rangeEnumerator.Current : TextSegment.Null;
}
else
{
//get the piece that is left
rangeSegment = new TextSegment(highlightSegment.Segment.End, rangeSegment.End);
}
//set invalidateStart if needed
if (invalidateStart == null)
invalidateStart = highlightSegment.Segment.Start;
}
else
{
//move to next highlightsegment
ind++;
}
}
else
{
//set invalidateStart if needed
if (invalidateStart == null)
invalidateStart = rangeSegment.Start;
if (rangeSegment.End.CompareTo(highlightSegment.Segment.Start) > 0)
{
//add the piece before the highlight segment
HighlightSegment temp = new HighlightSegment(rangeSegment.Start, highlightSegment.Segment.Start, highlightRange);
_segments.Insert(ind++, temp);
//now our current segment is the rest of the range segment
rangeSegment = new TextSegment(highlightSegment.Segment.Start, rangeSegment.End);
}
else
{
//just insert this range segment - it does not cover any highlight segnments. Increment ind
//so it points to the same highlight segment
_segments.Insert(ind++, new HighlightSegment(rangeSegment.Start, rangeSegment.End, highlightRange));
//get next range segment
rangeSegment = rangeEnumerator.MoveNext() ? rangeEnumerator.Current : TextSegment.Null;
}
}
}
//
if (!rangeSegment.IsNull)
{
if (invalidateStart == null)
invalidateStart = rangeSegment.Start;
_segments.Insert(ind++, new HighlightSegment(rangeSegment.Start, rangeSegment.End, highlightRange));
}
//check if there are more rangeSegments
while (rangeEnumerator.MoveNext())
{
_segments.Insert(ind++, new HighlightSegment(rangeEnumerator.Current.Start, rangeEnumerator.Current.End, highlightRange));
}
//set invalidateEnd
if (invalidateStart != null)
{
if (ind == _segments.Count) ind--;
invalidateEnd = _segments[ind].Segment.End;
}
}
///
/// Gets next change position in the forward direction
///
/// start position
/// next position if any
private ITextPointer GetNextForwardPosition(StaticTextPointer pos)
{
for (int i = 0; i < _segments.Count; i++)
{
HighlightSegment highlightSegment = _segments[i];
if (pos.CompareTo(highlightSegment.Segment.Start) >= 0)
{
if (pos.CompareTo(highlightSegment.Segment.End) < 0)
return highlightSegment.Segment.End;
}
else
{
return highlightSegment.Segment.Start;
}
}
return null;
}
///
/// Gets next change position in the backward direction
///
/// start position
/// nex position if any
private ITextPointer GetNextBackwardPosition(StaticTextPointer pos)
{
for (int i = _segments.Count - 1; i >= 0; i--)
{
HighlightSegment highlightSegment = _segments[i];
if (pos.CompareTo(highlightSegment.Segment.End) <= 0)
{
if (pos.CompareTo(highlightSegment.Segment.Start) > 0)
return highlightSegment.Segment.Start;
}
else
{
return highlightSegment.Segment.End;
}
}
return null;
}
void GetSpannedSegments(ITextPointer start, ITextPointer end, out int startSeg, out int endSeg)
{
startSeg = -1;
endSeg = -1;
//add it to Highlight segments
for (int i = 0; i < _segments.Count; i++)
{
HighlightSegment highlightSegment = _segments[i];
if (highlightSegment.Segment.Start.CompareTo(start) == 0)
startSeg = i;
if (highlightSegment.Segment.End.CompareTo(end) == 0)
{
endSeg = i;
break;
}
}
if ((startSeg < 0) || (endSeg < 0) || (startSeg > endSeg))
Debug.Assert(false, "Mismatched segment data");
}
#endregion Private Methods
//-----------------------------------------------------
//
// Private Properties
//
//------------------------------------------------------
#region Private Properties
private bool IsFixedContainer
{
get
{
return _isFixedContainer;
}
set
{
_isFixedContainer = value;
}
}
#endregion Private Properties
//-----------------------------------------------------
//
// Private Types
//
//------------------------------------------------------
#region Private Types
// Argument for the Changed event, encapsulates a highlight change.
private class AnnotationHighlightChangedEventArgs : HighlightChangedEventArgs
{
// Constructor.
internal AnnotationHighlightChangedEventArgs(ITextPointer start, ITextPointer end)
{
TextSegment[] rangeArray = new TextSegment[] { new TextSegment(start, end) };
_ranges = new ReadOnlyCollection(rangeArray);
}
// Collection of changed content ranges.
internal override IList Ranges
{
get
{
return _ranges;
}
}
// Type identifying the owner of the changed layer.
internal override Type OwnerType
{
get
{
return typeof(HighlightComponent);
}
}
// Collection of changed content ranges.
private readonly ReadOnlyCollection _ranges;
}
///
/// Represent one segment of the highlight that belongs to a particular set of owners.
/// In case of overlaping highlights one HighlightSegment will be generated for each part
/// that hase same set of owners.
///
internal sealed class HighlightSegment : Shape
{
//------------------------------------------------------
//
// Constructors
//
//-----------------------------------------------------
#region Constructors
///
/// Creates a new HighlightSegment with one owner
///
/// start segment position
/// end segment position
/// Owners data. Used to find the right place of this owner in the list
internal HighlightSegment(ITextPointer start, ITextPointer end, IHighlightRange owner)
: base()
{
List list = new List(1);
list.Add(owner);
Init(start, end, list);
_owners = list;
UpdateOwners();
}
///
/// Creates a new HighlightSegment with a list of owners
///
/// start segment position
/// end segment position
/// owners list
internal HighlightSegment(ITextPointer start, ITextPointer end, IList owners)
{
Init(start, end, owners);
//make a copy of the owners
_owners = new List(owners.Count);
_owners.AddRange(owners);
UpdateOwners();
}
///
/// Creates a new HighlightSegment with a list of owners
///
/// start segment position
/// end segment position
/// owners list
private void Init(ITextPointer start, ITextPointer end, IList owners)
{
Debug.Assert(start != null, "start pointer is null");
Debug.Assert(end != null, "end pointer is null");
Debug.Assert(owners != null, "null owners list");
Debug.Assert(owners.Count > 0, "empty owners list");
for (int i = 0; i < owners.Count; i++)
Debug.Assert(owners[i] != null, "null owner");
_segment = new TextSegment(start, end);
IsHitTestVisible = false;
object textContainer = start.TextContainer;
_isFixedContainer = textContainer is FixedTextContainer || textContainer is DocumentSequenceTextContainer;
//check for tables, figures and floaters and extract the content
GetContent();
}
#endregion Constructors
//------------------------------------------------------
//
// Internal methods
//
//-----------------------------------------------------
#region Internal methods
///
/// Adds one owner to the _owners list
///
/// owner data
///
internal void AddOwner(IHighlightRange owner)
{
//Find the right place in the list according to TimeStamp and the priority
//Note: - currently we care only about priority
for (int i = 0; i < _owners.Count; i++)
{
if (_owners[i].Priority < owner.Priority)
{
_owners.Insert(i, owner);
UpdateOwners();
return;
}
}
//every body has higher priority - add this at the end
_owners.Add(owner);
UpdateOwners();
}
///
/// Removes one owner from the _owners list
///
/// owner data
/// the number of owners left for this HighlightSegment
internal int RemoveOwner(IHighlightRange owner)
{
if (_owners.Contains(owner))
{
if (_activeOwners.Contains(owner))
_activeOwners.Remove(owner);
_owners.Remove(owner);
UpdateOwners();
}
return _owners.Count;
}
///
/// Adds an owner to_activeOwners list
///
/// owner data
/// the number of owners left for this HighlightSegment
internal void AddActiveOwner(IHighlightRange owner)
{
//this must be a valid owner
if (_owners.Contains(owner))
{
_activeOwners.Add(owner);
UpdateOwners();
}
}
///
/// Adds owners range to_activeOwners list
///
/// owner list
/// the number of owners left for this HighlightSegment
private void AddActiveOwners(List owners)
{
_activeOwners.AddRange(owners);
UpdateOwners();
}
///
/// Removes one owner from the _activeOwners list
///
/// owner data
/// the number of owners left for this HighlightSegment
internal void RemoveActiveOwner(IHighlightRange owner)
{
if (_activeOwners.Contains(owner))
{
_activeOwners.Remove(owner);
UpdateOwners();
}
}
///
/// Clear all segment owners - most probably the segment will be removed
///
internal void ClearOwners()
{
_owners.Clear();
_activeOwners.Clear();
UpdateOwners();
}
///
/// Splits this HighlightSegemnt into two highlights
///
/// splitting position
/// On which side of the splitting point to add new owner
/// A list of resulting highlights. They have new TextSegments and the same set of
/// owners as this
internal IList Split(ITextPointer ps, LogicalDirection side)
{
IList res = null;
if ((ps.CompareTo(_segment.Start) == 0) || (ps.CompareTo(_segment.End) == 0))
{
if ( ((ps.CompareTo(_segment.Start) == 0) && (side == LogicalDirection.Forward)) ||
((ps.CompareTo(_segment.End) == 0) && (side == LogicalDirection.Backward)) )
{
res = new List(1);
res.Add(this);
}
}
else if (_segment.Contains(ps))
{
res = new List(2);
res.Add(new HighlightSegment(_segment.Start, ps, _owners));
res.Add(new HighlightSegment(ps, _segment.End, _owners));
res[0].AddActiveOwners(_activeOwners);
res[1].AddActiveOwners(_activeOwners);
}
return res;
}
///
/// Splits HighlightSegment in two positions
///
/// first splitting position
/// second splitting position
/// Guid of a new owner to be added in the middle. May be null
/// A list of resulting HighlightSegments. They have same list of owners as this
internal IList Split(ITextPointer ps1, ITextPointer ps2, IHighlightRange newOwner)
{
Debug.Assert((ps1 != null) && (ps2 != null) && (ps1.CompareTo(ps2) <= 0), "invalid splitting points");
IList res = new List();
if (ps1.CompareTo(ps2) == 0)
{
//special processing for equal splitting points
if ((_segment.Start.CompareTo(ps1) > 0) || (_segment.End.CompareTo(ps1) < 0))
return res;
if (_segment.Start.CompareTo(ps1) < 0)
{
res.Add(new HighlightSegment(_segment.Start, ps1, _owners));
}
//add 0-length segment
res.Add(new HighlightSegment(ps1, ps1, _owners));
if (_segment.End.CompareTo(ps1) > 0)
{
res.Add(new HighlightSegment(ps1, _segment.End, _owners));
}
//add active owners as well
foreach (HighlightSegment seg in res)
{
seg.AddActiveOwners(_activeOwners);
}
}
else if (_segment.Contains(ps1))
{
IList r1 = Split(ps1, LogicalDirection.Forward);
for (int i = 0; i < r1.Count; i++)
{
if (r1[i].Segment.Contains(ps2))
{
IList r2 = r1[i].Split(ps2, LogicalDirection.Backward);
for (int j = 0; j < r2.Count; j++)
res.Add(r2[j]);
//check if r1[i] needs to be discarded (it can be included in the split result
// so we should not discard it in that case)
if (!r2.Contains(r1[i]))
r1[i].Discard();
}
else
{
res.Add(r1[i]);
}
}
}
else
{
res = Split(ps2, LogicalDirection.Backward);
}
if ((res != null) && (res.Count > 0) && (newOwner != null))
{
//add owner
if (res.Count == 3)
{
//if we have 3 segments the new owner will go to the middle one
res[1].AddOwner(newOwner);
}
else if ((res[0].Segment.Start.CompareTo(ps1) == 0) ||
(res[0].Segment.End.CompareTo(ps2) == 0))
{
//if one of the splitting points is on the corresponding end of the first
//segment it will have the new owner
res[0].AddOwner(newOwner);
}
else
{
//if we have 1 segment we should go through the else above, so they must be 2
Debug.Assert(res.Count == 2, "unexpected resulting segment count after split");
res[1].AddOwner(newOwner);
}
}
return res;
}
internal void UpdateOwners()
{
if (_cachedTopOwner != TopOwner)
{
//remove it from the old owner children
if (_cachedTopOwner != null)
_cachedTopOwner.RemoveChild(this);
_cachedTopOwner = TopOwner;
//add it to the new owner children
if (_cachedTopOwner != null)
_cachedTopOwner.AddChild(this);
}
Fill = OwnerColor;
}
///
/// this is called when this HighlightSegment will be discarded
/// It has to remove itself from the TopOwner's children and empty the
/// owners lists
///
internal void Discard()
{
if (TopOwner != null)
TopOwner.RemoveChild(this);
_activeOwners.Clear();
_owners.Clear();
}
#endregion Internal methods
//-----------------------------------------------------
//
// Private methods
//
//-----------------------------------------------------
#region Private methods
///
/// Calculates geometry for ine TextSegment
///
/// GeometryGroup to add the geometry
/// TextSegment
/// TextView to which geometry has to be transformed
private void GetSegmentGeometry(GeometryGroup geometry, TextSegment segment, ITextView parentView)
{
List textViews = TextSelectionHelper.GetDocumentPageTextViews(segment);
Debug.Assert(textViews != null, "geometry text view not found");
foreach (ITextView view in textViews)
{
Geometry viewGeometry = GetPageGeometry(segment, view, parentView);
if (viewGeometry != null)
geometry.Children.Add(viewGeometry);
}
}
///
/// Get a geometry for a particular page and transforms it to the parent page
///
/// the TextSegment for which geometry we are looking
/// the page view
/// the parent page view
///
private Geometry GetPageGeometry(TextSegment segment, ITextView view, ITextView parentView)
{
Debug.Assert((view != null) && (parentView != null), "null text view");
//in the initial layout update the TextViews might be invalid. This is OK
//since there will be a second pass
if (!view.IsValid || !parentView.IsValid)
return null;
//Debug.Assert((view.RenderScope != null) && (parentView.RenderScope != null), "null text view render scope");
if ((view.RenderScope == null) || (parentView.RenderScope == null))
return null;
Geometry pageGeometry = null;
pageGeometry = view.GetTightBoundingGeometryFromTextPositions(segment.Start, segment.End);
if (pageGeometry != null)
{
if (parentView != null)
{
Transform additionalTransform = (Transform)view.RenderScope.TransformToVisual(parentView.RenderScope);
if (pageGeometry.Transform != null)
{
//we need to create geometry group in this case
TransformGroup group = new TransformGroup();
group.Children.Add(pageGeometry.Transform);
group.Children.Add(additionalTransform);
pageGeometry.Transform = group;
}
else
{
//now set the transformation
pageGeometry.Transform = additionalTransform;
}
}
}
return pageGeometry;
}
///
/// Checks the TextSegment for tables, figures and floaters and gets the content if any
///
private void GetContent()
{
Debug.Assert(!_segment.IsNull, "null TextSegment");
_contentSegments.Clear();
ITextPointer cursor = _segment.Start.CreatePointer();
ITextPointer segmentStart = null;
while (cursor.CompareTo(_segment.End) < 0)
{
TextPointerContext nextContext = cursor.GetPointerContext(LogicalDirection.Forward);
if (nextContext == TextPointerContext.ElementStart)
{
Type elementType = cursor.GetElementType(LogicalDirection.Forward);
if (typeof(Run).IsAssignableFrom(elementType) ||
typeof(BlockUIContainer).IsAssignableFrom(elementType))
{
// Open new segment if it was not opened already
OpenSegment(ref segmentStart, cursor);
}
else if (typeof(Table).IsAssignableFrom(elementType) ||
typeof(Floater).IsAssignableFrom(elementType) ||
typeof(Figure).IsAssignableFrom(elementType))
{
// Start of table encountered. Add previous segment to the collection
CloseSegment(ref segmentStart, cursor, _segment.End);
}
cursor.MoveToNextContextPosition(LogicalDirection.Forward);
if (typeof(Run).IsAssignableFrom(elementType) ||
typeof(BlockUIContainer).IsAssignableFrom(elementType))
{// Skip the whole element - it dos not contain Tables, Figures or Floaters
cursor.MoveToElementEdge(ElementEdge.AfterEnd);
}
}
else if (nextContext == TextPointerContext.ElementEnd)
{
Type elementType = cursor.ParentType;
if (typeof(TableCell).IsAssignableFrom(elementType) ||
typeof(Floater).IsAssignableFrom(elementType) ||
typeof(Figure).IsAssignableFrom(elementType))
{
// End of cell encountered. Add the previous segment to the collection
CloseSegment(ref segmentStart, cursor, _segment.End);
}
// Skip the closing tag
cursor.MoveToNextContextPosition(LogicalDirection.Forward);
}
else if (nextContext == TextPointerContext.Text || nextContext == TextPointerContext.EmbeddedElement)
{
// Open new segment if it was not opened already
OpenSegment(ref segmentStart, cursor);
// Skip the text run
cursor.MoveToNextContextPosition(LogicalDirection.Forward);
}
else
{
Invariant.Assert(false, "unexpected TextPointerContext");
}
}
// Close the last segment
CloseSegment(ref segmentStart, cursor, _segment.End);
}
// Opens a segment for the following portion
private void OpenSegment(ref ITextPointer segmentStart, ITextPointer cursor)
{
if (segmentStart == null)
{
// Create normalized position for the segment start
segmentStart = cursor.GetInsertionPosition(LogicalDirection.Forward);
}
}
// Adds individual segment to a collection
private void CloseSegment(ref ITextPointer segmentStart, ITextPointer cursor, ITextPointer end)
{
if (segmentStart != null)
{
// Check for going beyond the end
if (cursor.CompareTo(end) > 0)
{
cursor = end;
}
// Create normalized position for the segment end
ITextPointer segmentEnd = cursor.GetInsertionPosition(LogicalDirection.Backward);
// Add segment to the collection if it is not empty
if (segmentStart.CompareTo(segmentEnd) < 0)
{
_contentSegments.Add(new TextSegment(segmentStart, segmentEnd));
}
// Close the previous segment
segmentStart = null;
}
}
#endregion Private methods
#region Protected Properties
///
/// Get the geometry that defines this shape
///
protected override Geometry DefiningGeometry
{
get
{
//on fixed document the highlights are drawn in a different way
if (_isFixedContainer)
return Geometry.Empty;
Debug.Assert(TopOwner != null, "invalid TopOwner");
ITextView parentView = TextSelectionHelper.GetDocumentPageTextView(TopOwner.Range.Start.CreatePointer(LogicalDirection.Forward));
Debug.Assert(parentView != null, "geometry parent text view not found");
GeometryGroup geometry = new GeometryGroup();
if (TopOwner.HighlightContent)
{
foreach (TextSegment segment in _contentSegments)
{
GetSegmentGeometry(geometry, segment, parentView);
}
}
else
{
GetSegmentGeometry(geometry, _segment, parentView);
}
//reset render transformation of the TopOwner
UIElement uie = TopOwner as UIElement;
if (uie != null)
uie.RenderTransform = Transform.Identity;
return geometry;
}
}
#endregion Protected Properties
//------------------------------------------------------
//
// Internal properties
//
//-----------------------------------------------------
#region Internal Properties
internal TextSegment Segment
{
get
{
return _segment;
}
}
///
/// returns the IHighlightRange that is currently drawn on top of this TextSegment
///
internal IHighlightRange TopOwner
{
get
{
if (_activeOwners.Count != 0)
return _activeOwners[0];
else
//TBD implement Z order
return _owners.Count > 0 ? _owners[0] : null;
}
}
#endregion Internal Properties
//------------------------------------------------------
//
// Private properties
//
//------------------------------------------------------
#region Private Properties
///
/// Creates a background Brush for this segment that reflects the properties
/// and Z order of the owners
///
private Brush OwnerColor
{
get
{
if (_activeOwners.Count != 0)
return new SolidColorBrush(_activeOwners[0].SelectedBackground);
else
//TBD implement Z order
return _owners.Count > 0 ? new SolidColorBrush(_owners[0].Background) : null;
}
}
#endregion Private Properties
//-----------------------------------------------------
//
// Private fields
//
//------------------------------------------------------
#region Private fields
private TextSegment _segment;
private List _contentSegments = new List(1);
private readonly List _owners;
private List _activeOwners = new List();
private IHighlightRange _cachedTopOwner = null;
private bool _isFixedContainer;
#endregion Private fields
}
#endregion Private Types
//-----------------------------------------------------
//
// Private Fields
//
//-----------------------------------------------------
#region Private Fields
///
/// A list of all HiglightSegments ordered by position
///
List _segments;
bool _isFixedContainer = false;
#endregion Private Fields
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
//----------------------------------------------------------------------------
//
//
// Copyright (C) Microsoft Corporation. All rights reserved.
//
//
// Description: HighlightLayer for annotations. It handles highlights
// and StickyNote anchors as well.
//
// History:
// 01/27/2005 ssimova - Created
//
//---------------------------------------------------------------------------
using System;
using MS.Internal;
using System.Diagnostics;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Documents;
using MS.Internal.Documents;
using System.Windows.Media;
using MS.Internal.Text;
using System.Windows.Shapes;
using MS.Internal.Annotations.Anchoring;
using System.Windows.Controls;
namespace MS.Internal.Annotations.Component
{
// Highlight rendering for the Annotation highlight and sticky note anchor.
internal class AnnotationHighlightLayer : HighlightLayer
{
//-----------------------------------------------------
//
// Constructors
//
//-----------------------------------------------------
#region Constructors
///
/// Creates a new instance of the highlight layer
///
internal AnnotationHighlightLayer()
{
_segments = new List();
}
#endregion Constructors
//------------------------------------------------------
//
// Internal Methods
//
//-----------------------------------------------------
#region Internal Methods
///
/// Adds a new text range to the layer
///
///
/// If the range does not overlap any of the existing segments it is added on the appropriate position
/// in the _segments array - the _segments array contains non overlapping HighlightSegments sorted by ITextPointers.
/// If the range overlaps some of the existing segments its IHighlightRange is added to the list of owners
/// for those segments. If the input range overlaps partially some of the existing segments they will be split into
/// new segments by the corresponding range end points. For the overlapping parts the IHighlightRange of the new
/// range will be added. If the new range overlaps parts of existing segments and some nonhighlighted areas, new
/// HighlightSegments will be generated for the nonhighlighted areas. Example:
/// 1. text "1234567" has 2 HighlightSegments covereing "234" and "67"
/// 2. Add a range that covers "3456"
/// 3.The result will be 5 HighlightSegments - "2", "34", "5", "6" and "7". "2" and "7" will keep their original owners array.
/// "5" will have only one owner - the new IHighlightRange, "34" and "6" will have their previous owners + the new one.
///
/// the highlight range owner
internal void AddRange(IHighlightRange highlightRange)
{
Invariant.Assert(highlightRange != null, "the owner is null");
ITextPointer start = highlightRange.Range.Start;
ITextPointer end = highlightRange.Range.End;
//check input data
Debug.Assert(start != null, "start pointer is null");
Debug.Assert(end != null, "end pointer is null");
Debug.Assert(start.CompareTo(end) <= 0, "end pointer before start");
if (start.CompareTo(end) == 0)
{
//it is a 0 length highlight - do not render it
return;
}
if (_segments.Count == 0)
{
//set container type
object textContainer = start.TextContainer;
IsFixedContainer = textContainer is FixedTextContainer || textContainer is DocumentSequenceTextContainer;
}
ITextPointer invalidateStart;
ITextPointer invalidateEnd;
ProcessOverlapingSegments(highlightRange, out invalidateStart, out invalidateEnd);
//fire event - do it only for fixed to avoid disposing of the page in flow. Needs to be changed in V2.
if ((Changed != null) && IsFixedContainer)
{
Changed(this, new AnnotationHighlightChangedEventArgs(invalidateStart, invalidateEnd));
}
}
///
/// RemoveRange from the highlight layer. The corresponding IHighlightRange object will be removed
/// from all HighlightSegments that belong to this range. All ranges with no more owners will also be removed.
/// The visual properties of ranges that have more owners might change.
///
/// The highlight range owner
/// true if the range is successfuly removed
internal void RemoveRange(IHighlightRange highlightRange)
{
Debug.Assert(highlightRange != null, "null range data");
//if range is 0 length do nothing
if (highlightRange.Range.Start.CompareTo(highlightRange.Range.End) == 0)
return;
int startSeg;
int endSeg;
GetSpannedSegments(highlightRange.Range.Start, highlightRange.Range.End, out startSeg, out endSeg);
//get invalidate start and end point
ITextPointer invalidateStart = _segments[startSeg].Segment.Start;
ITextPointer invalidateEnd = _segments[endSeg].Segment.End;
for (int i = startSeg; i <= endSeg; )
{
HighlightSegment highlightSegment = _segments[i];
int count = highlightSegment.RemoveOwner(highlightRange);
if (count == 0)
{
_segments.Remove(highlightSegment);
endSeg--;
}
else
{
i++;
}
}
//TBD:Should do something against fragmentation
//fire event - do it only for fixed to avoid disposing of the page in flow. Needs to be changed in V2.
if ((Changed != null) && IsFixedContainer)
{
Changed(this, new AnnotationHighlightChangedEventArgs(invalidateStart, invalidateEnd));
}
}
///
/// Notifies segments and listeners that a IHighlightRange colors have been modified
///
/// the highlight range to be modified
/// true if successfuly modified
internal void ModifiedRange(IHighlightRange highlightRange)
{
Invariant.Assert(highlightRange != null, "null range data");
//if range is 0 length do nothing
if (highlightRange.Range.Start.CompareTo(highlightRange.Range.End) == 0)
return;
int startSeg;
int endSeg;
GetSpannedSegments(highlightRange.Range.Start, highlightRange.Range.End, out startSeg, out endSeg);
//update colors
for (int seg = startSeg; seg < endSeg; seg++)
{
//the owners have not changed so this will only update the colors
_segments[seg].UpdateOwners();
}
//get invalidate start and end point
ITextPointer invalidateStart = _segments[startSeg].Segment.Start;
ITextPointer invalidateEnd = _segments[endSeg].Segment.End;
//fire event - do it only for fixed to avoid disposing of the page in flow. Needs to be changed in V2.
if ((Changed != null) && IsFixedContainer)
{
Changed(this, new AnnotationHighlightChangedEventArgs(invalidateStart, invalidateEnd));
}
}
///
/// Activate/Deactivate highlight range
///
/// the text range to be modified
/// true - activate, false - deactivate
internal void ActivateRange(IHighlightRange highlightRange, bool activate)
{
Invariant.Assert(highlightRange != null, "null range data");
//if range is 0 length do nothing
if (highlightRange.Range.Start.CompareTo(highlightRange.Range.End) == 0)
return;
int startSeg;
int endSeg;
GetSpannedSegments(highlightRange.Range.Start, highlightRange.Range.End, out startSeg, out endSeg);
//get invalidate start and end point
ITextPointer invalidateStart = _segments[startSeg].Segment.Start;
ITextPointer invalidateEnd = _segments[endSeg].Segment.End;
//set them as active
for (int i = startSeg; i <= endSeg; i++)
{
if (activate)
_segments[i].AddActiveOwner(highlightRange);
else
_segments[i].RemoveActiveOwner(highlightRange);
}
//fire event - do it only for fixed to avoid disposing of the page in flow. Needs to be changed in V2.
if ((Changed != null) && IsFixedContainer)
{
Changed(this, new AnnotationHighlightChangedEventArgs(invalidateStart, invalidateEnd));
}
}
///
/// Returns the value of a property stored on scoping highlight, if any.
/// If no property value is set, returns null.
///
///
/// We scan all the segments in a loop end for each segment make two checks:
/// 1. Is the textPosition before the beginning of the segment, or at the begining with
/// GicalDirection.Backward. If this is true our point is before the current segment.
/// We know that if it belongs to any of the previous segments the loop will stop, so that
/// means the point is outside any segments - break the loop.
/// 2. If the textPosition is not before the current segment, check if it is on the current segment
/// or at the end with LogicalDirection.Backward. If this is true the point belongs to the
/// current segment - save it and break the loop.
///
/// position to check for
/// logical direction
///
internal override object GetHighlightValue(StaticTextPointer textPosition, LogicalDirection direction)
{
object value = DependencyProperty.UnsetValue;
HighlightSegment highlightSegment = null;
for (int i = 0; i < _segments.Count; i++)
{
highlightSegment = _segments[i];
if ((highlightSegment.Segment.Start.CompareTo(textPosition) > 0) ||
((highlightSegment.Segment.Start.CompareTo(textPosition) == 0) && (direction == LogicalDirection.Backward)))
{
// the point is outside highlights
break;
}
if ((highlightSegment.Segment.End.CompareTo(textPosition) > 0) ||
((highlightSegment.Segment.End.CompareTo(textPosition) == 0) && (direction == LogicalDirection.Backward)))
{
value = highlightSegment;
break;
}
}
return value;
}
// Returns true if the indicated content has scoping highlights.
internal override bool IsContentHighlighted(StaticTextPointer staticTextPosition, LogicalDirection direction)
{
return GetHighlightValue(staticTextPosition, direction) != DependencyProperty.UnsetValue;
}
// Returns the position of the next highlight start or end in an
// indicated direction, or null if there is no such position.
internal override StaticTextPointer GetNextChangePosition(StaticTextPointer textPosition, LogicalDirection direction)
{
ITextPointer dynamicPosition;
if (direction == LogicalDirection.Forward)
{
dynamicPosition = GetNextForwardPosition(textPosition);
}
else
{
dynamicPosition = GetNextBackwardPosition(textPosition);
}
return dynamicPosition == null ? StaticTextPointer.Null : dynamicPosition.CreateStaticPointer();
}
#endregion Internal Methods
//------------------------------------------------------
//
// Internal Properties
//
//------------------------------------------------------
#region Internal Properties
///
/// Type identifying this layer for Highlights.GetHighlightValue calls.
///
internal override Type OwnerType
{
get
{
return typeof(HighlightComponent);
}
}
#endregion Internal Properties
//-----------------------------------------------------
//
// Internal Events
//
//------------------------------------------------------
#region Internal Events
// Event raised when a highlight range is inserted, removed, moved, or
// has a local property value change.
internal override event HighlightChangedEventHandler Changed;
#endregion Internal Events
//-----------------------------------------------------
//
// Private Methods
//
//-----------------------------------------------------
#region Private Methods
///
/// Checks if the input range overlaps existing segments and splits them if needed
///
/// range data
/// start pointer of the invalid area
/// end pointer of the invalid area
/// This method requires ordered nonoverlaped input segments. Otherwise it will assert.
private void ProcessOverlapingSegments(IHighlightRange highlightRange, out ITextPointer invalidateStart, out ITextPointer invalidateEnd)
{
Debug.Assert(highlightRange != null, " null highlight range");
ReadOnlyCollection rangeSegments = highlightRange.Range.TextSegments;
Debug.Assert((rangeSegments != null) && (rangeSegments.Count > 0), "invalid rangeSegments");
invalidateStart = null;
invalidateEnd = null;
int ind = 0;
IEnumerator rangeEnumerator = rangeSegments.GetEnumerator();
TextSegment rangeSegment = rangeEnumerator.MoveNext() ? rangeEnumerator.Current : TextSegment.Null;
while ((ind < _segments.Count) && (!rangeSegment.IsNull))
{
HighlightSegment highlightSegment = _segments[ind];
Debug.Assert(highlightSegment != null, "null highlight segment");
if (highlightSegment.Segment.Start.CompareTo(rangeSegment.Start) <= 0)
{
if (highlightSegment.Segment.End.CompareTo(rangeSegment.Start) > 0)
{
//split highlightSegment
//the split method is smart enough to take care of edge cases - point on start/end of the
//segment, points outside the segment etc
IList res = highlightSegment.Split(rangeSegment.Start, rangeSegment.End, highlightRange);
//if the result does not contain the original segment we need to clear the owners
if (!res.Contains(highlightSegment))
highlightSegment.ClearOwners();
_segments.Remove(highlightSegment);
_segments.InsertRange(ind, res);
ind = ind + res.Count - 1;
//check if we need to move to next range segment
if (rangeSegment.End.CompareTo(highlightSegment.Segment.End) <= 0)
{
//get next one
bool next = rangeEnumerator.MoveNext();
Debug.Assert(rangeEnumerator.Current.IsNull || !next ||
(rangeSegment.End.CompareTo(rangeEnumerator.Current.Start) <= 0),
"overlapped range segments");
rangeSegment = next ? rangeEnumerator.Current : TextSegment.Null;
}
else
{
//get the piece that is left
rangeSegment = new TextSegment(highlightSegment.Segment.End, rangeSegment.End);
}
//set invalidateStart if needed
if (invalidateStart == null)
invalidateStart = highlightSegment.Segment.Start;
}
else
{
//move to next highlightsegment
ind++;
}
}
else
{
//set invalidateStart if needed
if (invalidateStart == null)
invalidateStart = rangeSegment.Start;
if (rangeSegment.End.CompareTo(highlightSegment.Segment.Start) > 0)
{
//add the piece before the highlight segment
HighlightSegment temp = new HighlightSegment(rangeSegment.Start, highlightSegment.Segment.Start, highlightRange);
_segments.Insert(ind++, temp);
//now our current segment is the rest of the range segment
rangeSegment = new TextSegment(highlightSegment.Segment.Start, rangeSegment.End);
}
else
{
//just insert this range segment - it does not cover any highlight segnments. Increment ind
//so it points to the same highlight segment
_segments.Insert(ind++, new HighlightSegment(rangeSegment.Start, rangeSegment.End, highlightRange));
//get next range segment
rangeSegment = rangeEnumerator.MoveNext() ? rangeEnumerator.Current : TextSegment.Null;
}
}
}
//
if (!rangeSegment.IsNull)
{
if (invalidateStart == null)
invalidateStart = rangeSegment.Start;
_segments.Insert(ind++, new HighlightSegment(rangeSegment.Start, rangeSegment.End, highlightRange));
}
//check if there are more rangeSegments
while (rangeEnumerator.MoveNext())
{
_segments.Insert(ind++, new HighlightSegment(rangeEnumerator.Current.Start, rangeEnumerator.Current.End, highlightRange));
}
//set invalidateEnd
if (invalidateStart != null)
{
if (ind == _segments.Count) ind--;
invalidateEnd = _segments[ind].Segment.End;
}
}
///
/// Gets next change position in the forward direction
///
/// start position
/// next position if any
private ITextPointer GetNextForwardPosition(StaticTextPointer pos)
{
for (int i = 0; i < _segments.Count; i++)
{
HighlightSegment highlightSegment = _segments[i];
if (pos.CompareTo(highlightSegment.Segment.Start) >= 0)
{
if (pos.CompareTo(highlightSegment.Segment.End) < 0)
return highlightSegment.Segment.End;
}
else
{
return highlightSegment.Segment.Start;
}
}
return null;
}
///
/// Gets next change position in the backward direction
///
/// start position
/// nex position if any
private ITextPointer GetNextBackwardPosition(StaticTextPointer pos)
{
for (int i = _segments.Count - 1; i >= 0; i--)
{
HighlightSegment highlightSegment = _segments[i];
if (pos.CompareTo(highlightSegment.Segment.End) <= 0)
{
if (pos.CompareTo(highlightSegment.Segment.Start) > 0)
return highlightSegment.Segment.Start;
}
else
{
return highlightSegment.Segment.End;
}
}
return null;
}
void GetSpannedSegments(ITextPointer start, ITextPointer end, out int startSeg, out int endSeg)
{
startSeg = -1;
endSeg = -1;
//add it to Highlight segments
for (int i = 0; i < _segments.Count; i++)
{
HighlightSegment highlightSegment = _segments[i];
if (highlightSegment.Segment.Start.CompareTo(start) == 0)
startSeg = i;
if (highlightSegment.Segment.End.CompareTo(end) == 0)
{
endSeg = i;
break;
}
}
if ((startSeg < 0) || (endSeg < 0) || (startSeg > endSeg))
Debug.Assert(false, "Mismatched segment data");
}
#endregion Private Methods
//-----------------------------------------------------
//
// Private Properties
//
//------------------------------------------------------
#region Private Properties
private bool IsFixedContainer
{
get
{
return _isFixedContainer;
}
set
{
_isFixedContainer = value;
}
}
#endregion Private Properties
//-----------------------------------------------------
//
// Private Types
//
//------------------------------------------------------
#region Private Types
// Argument for the Changed event, encapsulates a highlight change.
private class AnnotationHighlightChangedEventArgs : HighlightChangedEventArgs
{
// Constructor.
internal AnnotationHighlightChangedEventArgs(ITextPointer start, ITextPointer end)
{
TextSegment[] rangeArray = new TextSegment[] { new TextSegment(start, end) };
_ranges = new ReadOnlyCollection(rangeArray);
}
// Collection of changed content ranges.
internal override IList Ranges
{
get
{
return _ranges;
}
}
// Type identifying the owner of the changed layer.
internal override Type OwnerType
{
get
{
return typeof(HighlightComponent);
}
}
// Collection of changed content ranges.
private readonly ReadOnlyCollection _ranges;
}
///
/// Represent one segment of the highlight that belongs to a particular set of owners.
/// In case of overlaping highlights one HighlightSegment will be generated for each part
/// that hase same set of owners.
///
internal sealed class HighlightSegment : Shape
{
//------------------------------------------------------
//
// Constructors
//
//-----------------------------------------------------
#region Constructors
///
/// Creates a new HighlightSegment with one owner
///
/// start segment position
/// end segment position
/// Owners data. Used to find the right place of this owner in the list
internal HighlightSegment(ITextPointer start, ITextPointer end, IHighlightRange owner)
: base()
{
List list = new List(1);
list.Add(owner);
Init(start, end, list);
_owners = list;
UpdateOwners();
}
///
/// Creates a new HighlightSegment with a list of owners
///
/// start segment position
/// end segment position
/// owners list
internal HighlightSegment(ITextPointer start, ITextPointer end, IList owners)
{
Init(start, end, owners);
//make a copy of the owners
_owners = new List(owners.Count);
_owners.AddRange(owners);
UpdateOwners();
}
///
/// Creates a new HighlightSegment with a list of owners
///
/// start segment position
/// end segment position
/// owners list
private void Init(ITextPointer start, ITextPointer end, IList owners)
{
Debug.Assert(start != null, "start pointer is null");
Debug.Assert(end != null, "end pointer is null");
Debug.Assert(owners != null, "null owners list");
Debug.Assert(owners.Count > 0, "empty owners list");
for (int i = 0; i < owners.Count; i++)
Debug.Assert(owners[i] != null, "null owner");
_segment = new TextSegment(start, end);
IsHitTestVisible = false;
object textContainer = start.TextContainer;
_isFixedContainer = textContainer is FixedTextContainer || textContainer is DocumentSequenceTextContainer;
//check for tables, figures and floaters and extract the content
GetContent();
}
#endregion Constructors
//------------------------------------------------------
//
// Internal methods
//
//-----------------------------------------------------
#region Internal methods
///
/// Adds one owner to the _owners list
///
/// owner data
///
internal void AddOwner(IHighlightRange owner)
{
//Find the right place in the list according to TimeStamp and the priority
//Note: - currently we care only about priority
for (int i = 0; i < _owners.Count; i++)
{
if (_owners[i].Priority < owner.Priority)
{
_owners.Insert(i, owner);
UpdateOwners();
return;
}
}
//every body has higher priority - add this at the end
_owners.Add(owner);
UpdateOwners();
}
///
/// Removes one owner from the _owners list
///
/// owner data
/// the number of owners left for this HighlightSegment
internal int RemoveOwner(IHighlightRange owner)
{
if (_owners.Contains(owner))
{
if (_activeOwners.Contains(owner))
_activeOwners.Remove(owner);
_owners.Remove(owner);
UpdateOwners();
}
return _owners.Count;
}
///
/// Adds an owner to_activeOwners list
///
/// owner data
/// the number of owners left for this HighlightSegment
internal void AddActiveOwner(IHighlightRange owner)
{
//this must be a valid owner
if (_owners.Contains(owner))
{
_activeOwners.Add(owner);
UpdateOwners();
}
}
///
/// Adds owners range to_activeOwners list
///
/// owner list
/// the number of owners left for this HighlightSegment
private void AddActiveOwners(List owners)
{
_activeOwners.AddRange(owners);
UpdateOwners();
}
///
/// Removes one owner from the _activeOwners list
///
/// owner data
/// the number of owners left for this HighlightSegment
internal void RemoveActiveOwner(IHighlightRange owner)
{
if (_activeOwners.Contains(owner))
{
_activeOwners.Remove(owner);
UpdateOwners();
}
}
///
/// Clear all segment owners - most probably the segment will be removed
///
internal void ClearOwners()
{
_owners.Clear();
_activeOwners.Clear();
UpdateOwners();
}
///
/// Splits this HighlightSegemnt into two highlights
///
/// splitting position
/// On which side of the splitting point to add new owner
/// A list of resulting highlights. They have new TextSegments and the same set of
/// owners as this
internal IList Split(ITextPointer ps, LogicalDirection side)
{
IList res = null;
if ((ps.CompareTo(_segment.Start) == 0) || (ps.CompareTo(_segment.End) == 0))
{
if ( ((ps.CompareTo(_segment.Start) == 0) && (side == LogicalDirection.Forward)) ||
((ps.CompareTo(_segment.End) == 0) && (side == LogicalDirection.Backward)) )
{
res = new List(1);
res.Add(this);
}
}
else if (_segment.Contains(ps))
{
res = new List(2);
res.Add(new HighlightSegment(_segment.Start, ps, _owners));
res.Add(new HighlightSegment(ps, _segment.End, _owners));
res[0].AddActiveOwners(_activeOwners);
res[1].AddActiveOwners(_activeOwners);
}
return res;
}
///
/// Splits HighlightSegment in two positions
///
/// first splitting position
/// second splitting position
/// Guid of a new owner to be added in the middle. May be null
/// A list of resulting HighlightSegments. They have same list of owners as this
internal IList Split(ITextPointer ps1, ITextPointer ps2, IHighlightRange newOwner)
{
Debug.Assert((ps1 != null) && (ps2 != null) && (ps1.CompareTo(ps2) <= 0), "invalid splitting points");
IList res = new List();
if (ps1.CompareTo(ps2) == 0)
{
//special processing for equal splitting points
if ((_segment.Start.CompareTo(ps1) > 0) || (_segment.End.CompareTo(ps1) < 0))
return res;
if (_segment.Start.CompareTo(ps1) < 0)
{
res.Add(new HighlightSegment(_segment.Start, ps1, _owners));
}
//add 0-length segment
res.Add(new HighlightSegment(ps1, ps1, _owners));
if (_segment.End.CompareTo(ps1) > 0)
{
res.Add(new HighlightSegment(ps1, _segment.End, _owners));
}
//add active owners as well
foreach (HighlightSegment seg in res)
{
seg.AddActiveOwners(_activeOwners);
}
}
else if (_segment.Contains(ps1))
{
IList r1 = Split(ps1, LogicalDirection.Forward);
for (int i = 0; i < r1.Count; i++)
{
if (r1[i].Segment.Contains(ps2))
{
IList r2 = r1[i].Split(ps2, LogicalDirection.Backward);
for (int j = 0; j < r2.Count; j++)
res.Add(r2[j]);
//check if r1[i] needs to be discarded (it can be included in the split result
// so we should not discard it in that case)
if (!r2.Contains(r1[i]))
r1[i].Discard();
}
else
{
res.Add(r1[i]);
}
}
}
else
{
res = Split(ps2, LogicalDirection.Backward);
}
if ((res != null) && (res.Count > 0) && (newOwner != null))
{
//add owner
if (res.Count == 3)
{
//if we have 3 segments the new owner will go to the middle one
res[1].AddOwner(newOwner);
}
else if ((res[0].Segment.Start.CompareTo(ps1) == 0) ||
(res[0].Segment.End.CompareTo(ps2) == 0))
{
//if one of the splitting points is on the corresponding end of the first
//segment it will have the new owner
res[0].AddOwner(newOwner);
}
else
{
//if we have 1 segment we should go through the else above, so they must be 2
Debug.Assert(res.Count == 2, "unexpected resulting segment count after split");
res[1].AddOwner(newOwner);
}
}
return res;
}
internal void UpdateOwners()
{
if (_cachedTopOwner != TopOwner)
{
//remove it from the old owner children
if (_cachedTopOwner != null)
_cachedTopOwner.RemoveChild(this);
_cachedTopOwner = TopOwner;
//add it to the new owner children
if (_cachedTopOwner != null)
_cachedTopOwner.AddChild(this);
}
Fill = OwnerColor;
}
///
/// this is called when this HighlightSegment will be discarded
/// It has to remove itself from the TopOwner's children and empty the
/// owners lists
///
internal void Discard()
{
if (TopOwner != null)
TopOwner.RemoveChild(this);
_activeOwners.Clear();
_owners.Clear();
}
#endregion Internal methods
//-----------------------------------------------------
//
// Private methods
//
//-----------------------------------------------------
#region Private methods
///
/// Calculates geometry for ine TextSegment
///
/// GeometryGroup to add the geometry
/// TextSegment
/// TextView to which geometry has to be transformed
private void GetSegmentGeometry(GeometryGroup geometry, TextSegment segment, ITextView parentView)
{
List textViews = TextSelectionHelper.GetDocumentPageTextViews(segment);
Debug.Assert(textViews != null, "geometry text view not found");
foreach (ITextView view in textViews)
{
Geometry viewGeometry = GetPageGeometry(segment, view, parentView);
if (viewGeometry != null)
geometry.Children.Add(viewGeometry);
}
}
///
/// Get a geometry for a particular page and transforms it to the parent page
///
/// the TextSegment for which geometry we are looking
/// the page view
/// the parent page view
///
private Geometry GetPageGeometry(TextSegment segment, ITextView view, ITextView parentView)
{
Debug.Assert((view != null) && (parentView != null), "null text view");
//in the initial layout update the TextViews might be invalid. This is OK
//since there will be a second pass
if (!view.IsValid || !parentView.IsValid)
return null;
//Debug.Assert((view.RenderScope != null) && (parentView.RenderScope != null), "null text view render scope");
if ((view.RenderScope == null) || (parentView.RenderScope == null))
return null;
Geometry pageGeometry = null;
pageGeometry = view.GetTightBoundingGeometryFromTextPositions(segment.Start, segment.End);
if (pageGeometry != null)
{
if (parentView != null)
{
Transform additionalTransform = (Transform)view.RenderScope.TransformToVisual(parentView.RenderScope);
if (pageGeometry.Transform != null)
{
//we need to create geometry group in this case
TransformGroup group = new TransformGroup();
group.Children.Add(pageGeometry.Transform);
group.Children.Add(additionalTransform);
pageGeometry.Transform = group;
}
else
{
//now set the transformation
pageGeometry.Transform = additionalTransform;
}
}
}
return pageGeometry;
}
///
/// Checks the TextSegment for tables, figures and floaters and gets the content if any
///
private void GetContent()
{
Debug.Assert(!_segment.IsNull, "null TextSegment");
_contentSegments.Clear();
ITextPointer cursor = _segment.Start.CreatePointer();
ITextPointer segmentStart = null;
while (cursor.CompareTo(_segment.End) < 0)
{
TextPointerContext nextContext = cursor.GetPointerContext(LogicalDirection.Forward);
if (nextContext == TextPointerContext.ElementStart)
{
Type elementType = cursor.GetElementType(LogicalDirection.Forward);
if (typeof(Run).IsAssignableFrom(elementType) ||
typeof(BlockUIContainer).IsAssignableFrom(elementType))
{
// Open new segment if it was not opened already
OpenSegment(ref segmentStart, cursor);
}
else if (typeof(Table).IsAssignableFrom(elementType) ||
typeof(Floater).IsAssignableFrom(elementType) ||
typeof(Figure).IsAssignableFrom(elementType))
{
// Start of table encountered. Add previous segment to the collection
CloseSegment(ref segmentStart, cursor, _segment.End);
}
cursor.MoveToNextContextPosition(LogicalDirection.Forward);
if (typeof(Run).IsAssignableFrom(elementType) ||
typeof(BlockUIContainer).IsAssignableFrom(elementType))
{// Skip the whole element - it dos not contain Tables, Figures or Floaters
cursor.MoveToElementEdge(ElementEdge.AfterEnd);
}
}
else if (nextContext == TextPointerContext.ElementEnd)
{
Type elementType = cursor.ParentType;
if (typeof(TableCell).IsAssignableFrom(elementType) ||
typeof(Floater).IsAssignableFrom(elementType) ||
typeof(Figure).IsAssignableFrom(elementType))
{
// End of cell encountered. Add the previous segment to the collection
CloseSegment(ref segmentStart, cursor, _segment.End);
}
// Skip the closing tag
cursor.MoveToNextContextPosition(LogicalDirection.Forward);
}
else if (nextContext == TextPointerContext.Text || nextContext == TextPointerContext.EmbeddedElement)
{
// Open new segment if it was not opened already
OpenSegment(ref segmentStart, cursor);
// Skip the text run
cursor.MoveToNextContextPosition(LogicalDirection.Forward);
}
else
{
Invariant.Assert(false, "unexpected TextPointerContext");
}
}
// Close the last segment
CloseSegment(ref segmentStart, cursor, _segment.End);
}
// Opens a segment for the following portion
private void OpenSegment(ref ITextPointer segmentStart, ITextPointer cursor)
{
if (segmentStart == null)
{
// Create normalized position for the segment start
segmentStart = cursor.GetInsertionPosition(LogicalDirection.Forward);
}
}
// Adds individual segment to a collection
private void CloseSegment(ref ITextPointer segmentStart, ITextPointer cursor, ITextPointer end)
{
if (segmentStart != null)
{
// Check for going beyond the end
if (cursor.CompareTo(end) > 0)
{
cursor = end;
}
// Create normalized position for the segment end
ITextPointer segmentEnd = cursor.GetInsertionPosition(LogicalDirection.Backward);
// Add segment to the collection if it is not empty
if (segmentStart.CompareTo(segmentEnd) < 0)
{
_contentSegments.Add(new TextSegment(segmentStart, segmentEnd));
}
// Close the previous segment
segmentStart = null;
}
}
#endregion Private methods
#region Protected Properties
///
/// Get the geometry that defines this shape
///
protected override Geometry DefiningGeometry
{
get
{
//on fixed document the highlights are drawn in a different way
if (_isFixedContainer)
return Geometry.Empty;
Debug.Assert(TopOwner != null, "invalid TopOwner");
ITextView parentView = TextSelectionHelper.GetDocumentPageTextView(TopOwner.Range.Start.CreatePointer(LogicalDirection.Forward));
Debug.Assert(parentView != null, "geometry parent text view not found");
GeometryGroup geometry = new GeometryGroup();
if (TopOwner.HighlightContent)
{
foreach (TextSegment segment in _contentSegments)
{
GetSegmentGeometry(geometry, segment, parentView);
}
}
else
{
GetSegmentGeometry(geometry, _segment, parentView);
}
//reset render transformation of the TopOwner
UIElement uie = TopOwner as UIElement;
if (uie != null)
uie.RenderTransform = Transform.Identity;
return geometry;
}
}
#endregion Protected Properties
//------------------------------------------------------
//
// Internal properties
//
//-----------------------------------------------------
#region Internal Properties
internal TextSegment Segment
{
get
{
return _segment;
}
}
///
/// returns the IHighlightRange that is currently drawn on top of this TextSegment
///
internal IHighlightRange TopOwner
{
get
{
if (_activeOwners.Count != 0)
return _activeOwners[0];
else
//TBD implement Z order
return _owners.Count > 0 ? _owners[0] : null;
}
}
#endregion Internal Properties
//------------------------------------------------------
//
// Private properties
//
//------------------------------------------------------
#region Private Properties
///
/// Creates a background Brush for this segment that reflects the properties
/// and Z order of the owners
///
private Brush OwnerColor
{
get
{
if (_activeOwners.Count != 0)
return new SolidColorBrush(_activeOwners[0].SelectedBackground);
else
//TBD implement Z order
return _owners.Count > 0 ? new SolidColorBrush(_owners[0].Background) : null;
}
}
#endregion Private Properties
//-----------------------------------------------------
//
// Private fields
//
//------------------------------------------------------
#region Private fields
private TextSegment _segment;
private List _contentSegments = new List(1);
private readonly List _owners;
private List _activeOwners = new List();
private IHighlightRange _cachedTopOwner = null;
private bool _isFixedContainer;
#endregion Private fields
}
#endregion Private Types
//-----------------------------------------------------
//
// Private Fields
//
//-----------------------------------------------------
#region Private Fields
///
/// A list of all HiglightSegments ordered by position
///
List _segments;
bool _isFixedContainer = false;
#endregion Private Fields
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- HostedAspNetEnvironment.cs
- TextBox.cs
- XPathDocumentBuilder.cs
- AnnotationAuthorChangedEventArgs.cs
- _ConnectionGroup.cs
- XmlSchemaComplexContent.cs
- HwndSubclass.cs
- PackWebResponse.cs
- EmptyStringExpandableObjectConverter.cs
- XmlArrayAttribute.cs
- ExceptionValidationRule.cs
- DockPattern.cs
- TcpWorkerProcess.cs
- FontDifferentiator.cs
- MimeMultiPart.cs
- ColumnResult.cs
- CatalogPartChrome.cs
- TreeIterator.cs
- KerberosTicketHashIdentifierClause.cs
- XmlSchemaType.cs
- ServiceRoute.cs
- Visual3DCollection.cs
- RewritingPass.cs
- _BaseOverlappedAsyncResult.cs
- GcHandle.cs
- MutexSecurity.cs
- ProcessModelSection.cs
- DataControlPagerLinkButton.cs
- ChtmlTextWriter.cs
- GACIdentityPermission.cs
- MediaEntryAttribute.cs
- EventLogReader.cs
- ReadContentAsBinaryHelper.cs
- PkcsUtils.cs
- Substitution.cs
- DetailsViewInsertedEventArgs.cs
- ExpanderAutomationPeer.cs
- XsdDateTime.cs
- StorageInfo.cs
- MsmqIntegrationBindingCollectionElement.cs
- MouseGestureConverter.cs
- ToolboxItemWrapper.cs
- TextParaLineResult.cs
- WindowsSlider.cs
- ReferenceConverter.cs
- TextRangeEditLists.cs
- Encoding.cs
- ServiceDescriptionImporter.cs
- ManipulationInertiaStartingEventArgs.cs
- DecimalAnimationUsingKeyFrames.cs
- X509PeerCertificateElement.cs
- ResourceAssociationType.cs
- assemblycache.cs
- BuildProviderAppliesToAttribute.cs
- TypeSource.cs
- HttpProfileBase.cs
- DecimalAnimation.cs
- TextStore.cs
- GridViewCancelEditEventArgs.cs
- XmlEventCache.cs
- CompilerResults.cs
- VisualBrush.cs
- SystemFonts.cs
- SafeFileMappingHandle.cs
- PropertyGridCommands.cs
- CounterSample.cs
- FileDialog.cs
- ViewStateModeByIdAttribute.cs
- Error.cs
- dsa.cs
- MarshalDirectiveException.cs
- ScriptingRoleServiceSection.cs
- glyphs.cs
- HelpPage.cs
- DataGridViewRowsAddedEventArgs.cs
- Separator.cs
- ObjectIDGenerator.cs
- MenuCommand.cs
- BitmapEffectInput.cs
- RepeatButton.cs
- TreeNodeStyle.cs
- XmlSchemaValidator.cs
- BamlStream.cs
- CurrencyManager.cs
- ProgressBar.cs
- SQLInt32.cs
- ApplicationContext.cs
- MaterialGroup.cs
- XmlCustomFormatter.cs
- RenderCapability.cs
- Signature.cs
- PtsHelper.cs
- TripleDES.cs
- ScrollProperties.cs
- QilPatternFactory.cs
- XmlValidatingReader.cs
- StringArrayConverter.cs
- BamlTreeNode.cs
- Environment.cs
- BmpBitmapEncoder.cs