/// <summary>
        ///     Creates a selection object spanning the portion of 'startNode' 
        ///     specified by 'locatorPart'.
        /// </summary>
        /// <param name="locatorPart">locator part specifying data to be spanned</param>
        /// <param name="startNode">the node to be spanned by the created 
        /// selection</param>
        /// <param name="attachmentLevel">set to AttachmentLevel.Full if the entire range of text
        /// was resolved, otherwise set to StartPortion, MiddlePortion, or EndPortion based on
        /// which part of the range was resolved</param>
        /// <returns>a selection spanning the portion of 'startNode' specified by     
        /// 'locatorPart', null if selection described by locator part could not be
        /// recreated</returns>
        /// <exception cref="ArgumentNullException">locatorPart or startNode are 
        /// null</exception>
        /// <exception cref="ArgumentException">locatorPart is of the incorrect type</exception>
        public override Object ResolveLocatorPart(ContentLocatorPart locatorPart, DependencyObject startNode, out AttachmentLevel attachmentLevel)
        {
            if (startNode == null)
                throw new ArgumentNullException("startNode");

            if (locatorPart == null)
                throw new ArgumentNullException("locatorPart");

            if (CharacterRangeElementName != locatorPart.PartType)
                throw new ArgumentException(SR.Get(SRID.IncorrectLocatorPartType, locatorPart.PartType.Namespace + ":" + locatorPart.PartType.Name), "locatorPart");

            // First we extract the offset and length of the 
            // text range from the locator part.
            int startOffset = 0;
            int endOffset = 0;

            string stringCount = locatorPart.NameValuePairs[CountAttribute];
            if (stringCount == null)
                throw new ArgumentException(SR.Get(SRID.InvalidLocatorPart, TextSelectionProcessor.CountAttribute));                
            int count = Int32.Parse(stringCount,NumberFormatInfo.InvariantInfo);

            TextAnchor anchor = new TextAnchor();

            attachmentLevel = AttachmentLevel.Unresolved; 
            
            for (int i = 0; i < count; i++)
            {
                GetLocatorPartSegmentValues(locatorPart, i, out startOffset, out endOffset);

                // Now we grab the TextRange so we can create a selection.  
                // TextBox doesn't expose its internal TextRange so we use
                // its API for creating and getting the selection.
                ITextPointer elementStart;
                ITextPointer elementEnd;
                // If we can't get the start/end of the node then we can't resolve the locator part
                if (!GetNodesStartAndEnd(startNode, out elementStart, out elementEnd))
                    return null;

                // If the offset is not withing the element's text range we return null
                int textRangeLength = elementStart.GetOffsetToPosition(elementEnd);
                if (startOffset > textRangeLength)
                    return null;

                ITextPointer start = elementStart.CreatePointer(startOffset);// new TextPointer((TextPointer)elementStart, startOffset);

                ITextPointer end = (textRangeLength <= endOffset) ?
                    elementEnd.CreatePointer() : //new TextPointer((TextPointer)elementEnd) :
                    elementStart.CreatePointer(endOffset);// new TextPointer((TextPointer)elementStart, endOffset);

                //we do not process 0 length selection
                if (start.CompareTo(end) >= 0)
                    return null;

                anchor.AddTextSegment(start, end);
            }           

            //we do not support 0 or negative length selection
            if (anchor.IsEmpty)
            {
                throw new ArgumentException(SR.Get(SRID.IncorrectAnchorLength), "locatorPart");
            }

            attachmentLevel = AttachmentLevel.Full; 
            
            if (_clamping)
            {
                ITextPointer currentStart = anchor.Start;
                ITextPointer currentEnd = anchor.End;
                IServiceProvider serviceProvider = null;
                ITextView textView = null;

                if (_targetPage != null)
                {
                    serviceProvider = _targetPage as IServiceProvider;
                }
                else
                {
                    FlowDocument content = currentStart.TextContainer.Parent as FlowDocument;
                    serviceProvider = PathNode.GetParent(content as DependencyObject) as IServiceProvider;
                }

                Invariant.Assert(serviceProvider != null, "No ServiceProvider found to get TextView from.");
                textView = serviceProvider.GetService(typeof(ITextView)) as ITextView;
                Invariant.Assert(textView != null, "Null TextView provided by ServiceProvider.");

                anchor = TextAnchor.TrimToIntersectionWith(anchor, textView.TextSegments);

                if (anchor == null)
                {
                    attachmentLevel = AttachmentLevel.Unresolved;
                }
                else
                {
                    if (anchor.Start.CompareTo(currentStart) != 0)
                    {
                        attachmentLevel &= ~AttachmentLevel.StartPortion;
                    }
                    if (anchor.End.CompareTo(currentEnd) != 0)
                    {
                        attachmentLevel &= ~AttachmentLevel.EndPortion;
                    }
                }            
            }

            return anchor;
        }
        /// <summary>
        ///     Creates a TextRange object spanning the portion of 'startNode' 
        ///     specified by 'locatorPart'.
        /// </summary>
        /// <param name="locatorPart">FixedTextRange locator part specifying start and end point of 
        /// the TextRange</param>
        /// <param name="startNode">the FixedPage containing this locator part</param>
        /// <param name="attachmentLevel">set to AttachmentLevel.Full if the FixedPage for the locator 
        /// part was found, AttachmentLevel.Unresolved otherwise</param>
        /// <returns>a TextRange spanning the text between start end end point in the FixedTextRange
        /// locator part
        /// , null if selection described by locator part could not be
        /// recreated</returns>
        /// <exception cref="ArgumentNullException">locatorPart or startNode are 
        /// null</exception>
        /// <exception cref="ArgumentException">locatorPart is of the incorrect type</exception>
        /// <exception cref="ArgumentException">startNode is not a FixedPage</exception>
        /// <exception cref="ArgumentException">startNode does not belong to the DocumentViewer</exception>
        public override Object ResolveLocatorPart(ContentLocatorPart locatorPart, DependencyObject startNode, out AttachmentLevel attachmentLevel)
        {
            if (startNode == null)
                throw new ArgumentNullException("startNode");

            DocumentPage docPage = null;
            FixedPage page = startNode as FixedPage;

            if (page != null)
            {
                docPage = GetDocumentPage(page);
            }
            else 
            {
                // If we were passed a DPV because we are walking the visual tree,
                // extract the DocumentPage from it;  its TextView will be used to
                // turn coordinates into text positions
                DocumentPageView dpv = startNode as DocumentPageView;
                if (dpv != null)
                {
                    docPage = dpv.DocumentPage as FixedDocumentPage;
                    if (docPage == null)
                    {
                        docPage = dpv.DocumentPage as FixedDocumentSequenceDocumentPage;
                    }
                }
            }

            if (docPage == null)
            {
                throw new ArgumentException(SR.Get(SRID.StartNodeMustBeDocumentPageViewOrFixedPage), "startNode");
            }

            if (locatorPart == null)
                throw new ArgumentNullException("locatorPart");

            attachmentLevel = AttachmentLevel.Unresolved;

            ITextView tv = (ITextView)((IServiceProvider)docPage).GetService(typeof(ITextView));
            Debug.Assert(tv != null);

            ReadOnlyCollection<TextSegment> ts = tv.TextSegments;

            //check first if a TextRange can be generated
            if (ts == null || ts.Count <= 0)
                return null;

            TextAnchor resolvedAnchor = new TextAnchor();
            
            if (docPage != null)
            {
                string stringCount = locatorPart.NameValuePairs["Count"];
                if (stringCount == null)
                    throw new ArgumentException(SR.Get(SRID.InvalidLocatorPart, TextSelectionProcessor.CountAttribute));                
                int count = Int32.Parse(stringCount, NumberFormatInfo.InvariantInfo);

                for(int i = 0; i < count; i++)
                {
                    // First we extract the start and end Point from the locator part.
                    Point start;
                    Point end;
                    GetLocatorPartSegmentValues(locatorPart, i, out start, out end);

                    //calulate start ITextPointer
                    ITextPointer segStart;
                    if (double.IsNaN(start.X) || double.IsNaN(start.Y))
                    {
                        //get start of the page
                        segStart = FindStartVisibleTextPointer(docPage);
                    }
                    else
                    {
                        //convert Point to TextPointer
                        segStart = tv.GetTextPositionFromPoint(start, true);
                    }

                    if (segStart == null)
                    {
                        //selStart can be null if there are no insertion points on this page 
                        continue;
                    }

                    //calulate end ITextPointer
                    ITextPointer segEnd;
                    if (double.IsNaN(end.X) || double.IsNaN(end.Y))
                    {
                        segEnd = FindEndVisibleTextPointer(docPage);
                    }
                    else
                    {
                        //convert Point to TextPointer
                        segEnd = tv.GetTextPositionFromPoint(end, true);
                    }

                    //end TP can not be null when start is not
                    Invariant.Assert(segEnd != null, "end TP is null when start TP is not");

                    attachmentLevel = AttachmentLevel.Full;  // Not always true right?
                    resolvedAnchor.AddTextSegment(segStart, segEnd);
                }
            }

            if (resolvedAnchor.TextSegments.Count > 0)
                return resolvedAnchor;
            else
                return null;
        }
예제 #3
0
        /// <summary>
        /// Merges highlights with the same color. Splits highlights with different colors 
        /// </summary>
        /// <param name="service">the AnnotationService</param> 
        /// <param name="textRange">TextRange of the new highlight</param> 
        /// <param name="color">highlight color</param>
        /// <param name="author">highlight author</param> 
        /// <param name="create">if true, this is create highlight operation, otherwise it is Clear</param>
        /// <returns>the annotation created, if create flag was true; null otherwise</returns>
        private static Annotation ProcessHighlights(AnnotationService service, ITextRange textRange, string author, Nullable<Color> color, bool create)
        { 
            Invariant.Assert(textRange != null, "Parameter 'textRange' is null.");
 
            IList<IAttachedAnnotation> spannedAnnots = GetSpannedAnnotations(service); 

            // Step one: trim all the spanned annotations so there is no overlap with the new annotation 
            foreach (IAttachedAnnotation attachedAnnotation in spannedAnnots)
            {
                if (HighlightComponent.TypeName.Equals(attachedAnnotation.Annotation.AnnotationType))
                { 
                    TextAnchor textAnchor = attachedAnnotation.FullyAttachedAnchor as TextAnchor;
                    Invariant.Assert(textAnchor != null, "FullyAttachedAnchor must not be null."); 
                    TextAnchor copy = new TextAnchor(textAnchor); 

                    // This modifies the current fully resolved anchor 
                    copy = TextAnchor.TrimToRelativeComplement(copy, textRange.TextSegments);

                    // If the trimming resulting in no more content being in the anchor,
                    // delete the annotation 
                    if (copy == null || copy.IsEmpty)
                    { 
                        service.Store.DeleteAnnotation(attachedAnnotation.Annotation.Id); 
                        continue;
                    } 

                    // If there was some portion of content still in the anchor,
                    // generate new locators representing the modified anchor
                    SetAnchor(service, attachedAnnotation.Annotation, copy); 
                }
            } 
 
            // Step two: create new annotation
            if (create) 
            {
                //create one annotation and return
                Annotation highlight = CreateHighlight(service, textRange, author, color);
                service.Store.AddAnnotation(highlight); 
                return highlight;
            } 
 
            return null;
        } 
예제 #4
0
        /// <summary> 
        /// Process the case of Empty selection for the different annotation types
        /// </summary> 
        /// <param name="selection">the selection</param>
        /// <param name="anchor">annotation anchor</param>
        /// <param name="type">annotation type</param>
        /// <returns></returns> 
        private static bool CheckCaret(ITextSelection selection, TextAnchor anchor, XmlQualifiedName type)
        { 
            //selection must be empty 
            if (!selection.IsEmpty)
                return false; 

            if (((anchor.Start.CompareTo(selection.Start) == 0) &&
                 (selection.Start.LogicalDirection == LogicalDirection.Forward)) ||
               ((anchor.End.CompareTo(selection.End) == 0) && 
                (selection.End.LogicalDirection == LogicalDirection.Backward)))
                return true; 
 
            return false;
 
        }
예제 #5
0
        /// <summary>
        /// Modifies the text anchor's TextSegments so all of them
        /// overlap with the passed in text segments.  This is used
        /// for instance to clamp a TextAnchor to a set of visible
        /// text segments.  If after trimming the anchor has no more
        /// segments, null is returned instead.  Callers should
        /// assign the result of this method to the anchor they
        /// passed in.
        /// </summary>
        /// <remarks>
        /// Note: This method assumes textSegments is ordered and do not overlap amongs themselves
        ///
        /// The target of the method is to trim this anchor's segments to overlap with the passed in segments.
        /// The loop handles the following three cases -
        /// 1. Current segment is after other segment, the other segment doesn't contribute at all, we move to the next other segment
        /// 2. Current segment is before other segment, no overlap, remove current segment
        /// 3. Current segment starts before other segment, and ends after other segment begins,
        ///    therefore the portion from current's start to other's start should be trimmed
        /// 4. Current segment starts in the middle of other segment, two possibilities
        ///      a. current segment is completely within other segment, the whole segment overlaps
        ///         so we move on to the next current segment
        ///      b. current segment ends after other segment ends, we split current into the
        ///         overlapped portion and the remainder which will be looked at separately
        /// </remarks>
        /// <param name="anchor">the anchor to trim</param>
        /// <param name="textSegments">collection of text segments to intersect with</param>
        internal static TextAnchor TrimToIntersectionWith(TextAnchor anchor, ICollection <TextSegment> textSegments)
        {
            Invariant.Assert(anchor != null, "Anchor must not be null.");
            Invariant.Assert(textSegments != null, "TextSegments must not be null.");

            textSegments = SortTextSegments(textSegments, true);
            TextSegment currentSegment, otherSegment = TextSegment.Null;

            int current = 0;
            IEnumerator <TextSegment> enumerator = textSegments.GetEnumerator();
            bool hasMore = enumerator.MoveNext();

            while (current < anchor._segments.Count && hasMore)
            {
                Invariant.Assert(otherSegment.Equals(TextSegment.Null) || otherSegment.Equals(enumerator.Current) || otherSegment.End.CompareTo(enumerator.Current.Start) <= 0, "TextSegments are overlapping or not ordered.");

                currentSegment = anchor._segments[current];
                otherSegment   = enumerator.Current;

                // Current segment is after other segment, so try the next other segment
                if (currentSegment.Start.CompareTo(otherSegment.End) >= 0)
                {
                    hasMore = enumerator.MoveNext(); // point to the next other
                    continue;                        // Do not increment, we are still on the same current
                }

                // Current segment is before other segment, no overlap so remove it and continue
                if (currentSegment.End.CompareTo(otherSegment.Start) <= 0)
                {
                    anchor._segments.RemoveAt(current);
                    continue; // Do not increment, it happens implicitly because of the remove
                }

                //
                // We know from here down that there is some overlap.
                //

                // Current starts before the other segment and ends after other segment begins, the first portion of current segment doesn't overlap so we remove it
                if (currentSegment.Start.CompareTo(otherSegment.Start) < 0)
                {
                    anchor._segments[current] = CreateNormalizedSegment(otherSegment.Start, currentSegment.End);
                    continue;  // Do not increment, we need to look at this just created segment
                }
                // Current segment begins in the middle of other segment...
                else
                {
                    // and ends after other segment does, we split current into the portion that is overlapping and the remainder
                    if (currentSegment.End.CompareTo(otherSegment.End) > 0)
                    {
                        anchor._segments[current] = CreateNormalizedSegment(currentSegment.Start, otherSegment.End);
                        // This segment will be the first one looked at next
                        anchor._segments.Insert(current + 1, CreateNormalizedSegment(otherSegment.End, currentSegment.End));
                        hasMore = enumerator.MoveNext();
                    }
                    // and ends at the same place as other segment, its completely overlapping, we move on to the next other
                    else if (currentSegment.End.CompareTo(otherSegment.End) == 0)
                    {
                        hasMore = enumerator.MoveNext();
                    }
                    // and ends within other segment, its completely overlapping, but we aren't done with other so we just continue
                }

                current++;
            }

            // If we finished and there are no more other segments, then any remaining segments
            // in our list must not overlap, so we remove them.
            if (!hasMore && current < anchor._segments.Count)
            {
                anchor._segments.RemoveRange(current, anchor._segments.Count - current);
            }

            if (anchor._segments.Count == 0)
            {
                return(null);
            }
            else
            {
                return(anchor);
            }
        }
예제 #6
0
        /// <summary>
        /// Modifies the passed in TextAnchor to contain its relative
        /// complement to the set of text segments passed in.  The resulting
        /// TextAnchor contains those segments or portions of segments that do
        /// not overlap with the passed in segments in anyway.  If after trimming
        /// the anchor has no more segments, null is returned instead.  Callers
        /// should assign the result of this method to the anchor they passed in.
        /// </summary>
        /// <param name="anchor">the anchor to trim</param>
        /// <param name="textSegments">the text segments to calculate relative complement with</param>
        /// <remarks>Note: textSegments is expected to be ordered and contain no overlapping segments</remarks>
        internal static TextAnchor TrimToRelativeComplement(TextAnchor anchor, ICollection <TextSegment> textSegments)
        {
            Invariant.Assert(anchor != null, "Anchor must not be null.");
            Invariant.Assert(textSegments != null, "TextSegments must not be null.");

            textSegments = SortTextSegments(textSegments, true);

            IEnumerator <TextSegment> enumerator = textSegments.GetEnumerator();
            bool        hasMore      = enumerator.MoveNext();
            int         currentIndex = 0;
            TextSegment current;
            TextSegment otherSegment = TextSegment.Null;

            while (currentIndex < anchor._segments.Count && hasMore)
            {
                Invariant.Assert(otherSegment.Equals(TextSegment.Null) || otherSegment.Equals(enumerator.Current) || otherSegment.End.CompareTo(enumerator.Current.Start) <= 0, "TextSegments are overlapping or not ordered.");

                current      = anchor._segments[currentIndex];
                otherSegment = enumerator.Current;

                // Current segment is after other segment, no overlap
                // Also, done with the other segment, move to the next one
                if (current.Start.CompareTo(otherSegment.End) >= 0)
                {
                    hasMore = enumerator.MoveNext();
                    continue;  // No increment, still processing the current segment
                }

                // Current segment starts after other segment starts and ...
                if (current.Start.CompareTo(otherSegment.Start) >= 0)
                {
                    // ends before other segment ends, complete overlap, remove the segment
                    if (current.End.CompareTo(otherSegment.End) <= 0)
                    {
                        anchor._segments.RemoveAt(currentIndex);
                        continue;  // No increment, happens implicitly because of the removal
                    }
                    else
                    {
                        // ends after other segment, first portion of current overlaps,
                        // create new segment from end of other segment to end of current
                        anchor._segments[currentIndex] = CreateNormalizedSegment(otherSegment.End, current.End);
                        // Done with the other segment, move to the next one
                        hasMore = enumerator.MoveNext();
                        continue; // No increment, need to process just created segment
                    }
                }
                // Current segment starts before other segment starts and ...
                else
                {
                    // ends after it starts, first portion of current does not overlap,
                    // create new segment for that portion
                    if (current.End.CompareTo(otherSegment.Start) > 0)
                    {
                        anchor._segments[currentIndex] = CreateNormalizedSegment(current.Start, otherSegment.Start);
                        // If there's any portion of current after other segment, create a new segment for that which
                        // will be the next one processed
                        if (current.End.CompareTo(otherSegment.End) > 0)
                        {
                            // Overlap ends before current segment's end, we create a new segment with the remainder of current segment
                            anchor._segments.Insert(currentIndex + 1, CreateNormalizedSegment(otherSegment.End, current.End));
                            // Done with the other segment, move to the next one
                            hasMore = enumerator.MoveNext();
                        }
                    }
                    // ends before it starts, current is completely before other, no overlap, do nothing
                }

                currentIndex++;
            }

            if (anchor._segments.Count > 0)
            {
                return(anchor);
            }
            else
            {
                return(null);
            }
        }