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"); ReadOnlyCollectionrangeSegments = 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() { Listlist = 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, IListowners) { 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, IListowners) { 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(Listowners) { _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 IListSplit(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 IListSplit(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) { ListtextViews = 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"); ReadOnlyCollectionrangeSegments = 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() { Listlist = 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, IListowners) { 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, IListowners) { 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(Listowners) { _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 IListSplit(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 IListSplit(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) { ListtextViews = 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
- Command.cs
- WebPartActionVerb.cs
- ListViewItem.cs
- DebugViewWriter.cs
- DynamicDiscoSearcher.cs
- XmlNodeChangedEventArgs.cs
- DataBoundControl.cs
- QilValidationVisitor.cs
- NamespaceQuery.cs
- AuthenticateEventArgs.cs
- DragEventArgs.cs
- ObjectManager.cs
- XmlSchemaComplexContentExtension.cs
- EntitySqlQueryCacheKey.cs
- DateTimeConverter2.cs
- SplitterDesigner.cs
- Base64Encoder.cs
- SafeTimerHandle.cs
- Style.cs
- NegatedCellConstant.cs
- NTAccount.cs
- SmiEventSink_DeferedProcessing.cs
- Tag.cs
- DataExpression.cs
- FontStretches.cs
- CalloutQueueItem.cs
- XhtmlTextWriter.cs
- KeyFrames.cs
- XmlNamespaceDeclarationsAttribute.cs
- ZipFileInfoCollection.cs
- DisplayNameAttribute.cs
- Pointer.cs
- OptimizedTemplateContentHelper.cs
- CodeObjectCreateExpression.cs
- DockPanel.cs
- SHA1CryptoServiceProvider.cs
- ProtocolsConfigurationEntry.cs
- XslException.cs
- MailBnfHelper.cs
- Vector3DIndependentAnimationStorage.cs
- SafeProcessHandle.cs
- TransactionOptions.cs
- EnvironmentPermission.cs
- EdmFunctions.cs
- ExceptionCollection.cs
- TileBrush.cs
- TypeHelper.cs
- FlatButtonAppearance.cs
- DBBindings.cs
- CodeDOMProvider.cs
- BamlWriter.cs
- RootCodeDomSerializer.cs
- ItemList.cs
- LocalizationParserHooks.cs
- CultureTableRecord.cs
- AttributedMetaModel.cs
- DiscoveryOperationContext.cs
- Command.cs
- RenderOptions.cs
- MembershipUser.cs
- PagedControl.cs
- DataView.cs
- TraceUtility.cs
- MetadataUtil.cs
- CustomAttributeBuilder.cs
- SmtpFailedRecipientsException.cs
- ActivationArguments.cs
- QuaternionIndependentAnimationStorage.cs
- Animatable.cs
- PropertyPath.cs
- Propagator.JoinPropagator.cs
- AdRotator.cs
- XmlTextWriter.cs
- ApplicationServiceManager.cs
- PersonalizationProviderHelper.cs
- SchemaConstraints.cs
- EventProvider.cs
- Point3DValueSerializer.cs
- EnumUnknown.cs
- SaveFileDialog.cs
- GatewayIPAddressInformationCollection.cs
- SqlMetaData.cs
- EastAsianLunisolarCalendar.cs
- AssertUtility.cs
- SpecularMaterial.cs
- TextLine.cs
- KnownIds.cs
- Multiply.cs
- LayeredChannelFactory.cs
- SettingsBindableAttribute.cs
- StorageModelBuildProvider.cs
- GridLengthConverter.cs
- PersonalizationProviderCollection.cs
- Ticks.cs
- StrongNameMembershipCondition.cs
- ObjectDesignerDataSourceView.cs
- UrlEncodedParameterWriter.cs
- ControlEvent.cs
- PseudoWebRequest.cs
- HtmlEmptyTagControlBuilder.cs