Free cookie consent management tool by TermsFeed Policy Generator

source: stable/HeuristicLab.ExtLibs/HeuristicLab.AvalonEdit/5.0.1/AvalonEdit-5.0.1/Rendering/VisualLine.cs

Last change on this file was 11700, checked in by jkarder, 10 years ago

#2077: created branch and added first version

File size: 27.8 KB
Line 
1// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy of this
4// software and associated documentation files (the "Software"), to deal in the Software
5// without restriction, including without limitation the rights to use, copy, modify, merge,
6// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
7// to whom the Software is furnished to do so, subject to the following conditions:
8//
9// The above copyright notice and this permission notice shall be included in all copies or
10// substantial portions of the Software.
11//
12// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
13// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
15// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
17// DEALINGS IN THE SOFTWARE.
18
19using System.Linq;
20using System.Windows.Controls;
21using System.Windows.Media;
22using ICSharpCode.AvalonEdit.Utils;
23using System;
24using System.Collections.Generic;
25using System.Collections.ObjectModel;
26using System.Diagnostics;
27using System.Windows;
28using System.Windows.Documents;
29using System.Windows.Media.TextFormatting;
30using ICSharpCode.AvalonEdit.Document;
31
32namespace ICSharpCode.AvalonEdit.Rendering
33{
34  /// <summary>
35  /// Represents a visual line in the document.
36  /// A visual line usually corresponds to one DocumentLine, but it can span multiple lines if
37  /// all but the first are collapsed.
38  /// </summary>
39  public sealed class VisualLine
40  {
41    enum LifetimePhase : byte
42    {
43      Generating,
44      Transforming,
45      Live,
46      Disposed
47    }
48   
49    TextView textView;
50    List<VisualLineElement> elements;
51    internal bool hasInlineObjects;
52    LifetimePhase phase;
53   
54    /// <summary>
55    /// Gets the document to which this VisualLine belongs.
56    /// </summary>
57    public TextDocument Document { get; private set; }
58   
59    /// <summary>
60    /// Gets the first document line displayed by this visual line.
61    /// </summary>
62    public DocumentLine FirstDocumentLine { get; private set; }
63   
64    /// <summary>
65    /// Gets the last document line displayed by this visual line.
66    /// </summary>
67    public DocumentLine LastDocumentLine { get; private set; }
68   
69    /// <summary>
70    /// Gets a read-only collection of line elements.
71    /// </summary>
72    public ReadOnlyCollection<VisualLineElement> Elements { get; private set; }
73   
74    ReadOnlyCollection<TextLine> textLines;
75   
76    /// <summary>
77    /// Gets a read-only collection of text lines.
78    /// </summary>
79    public ReadOnlyCollection<TextLine> TextLines {
80      get {
81        if (phase < LifetimePhase.Live)
82          throw new InvalidOperationException();
83        return textLines;
84      }
85    }
86   
87    /// <summary>
88    /// Gets the start offset of the VisualLine inside the document.
89    /// This is equivalent to <c>FirstDocumentLine.Offset</c>.
90    /// </summary>
91    public int StartOffset {
92      get {
93        return FirstDocumentLine.Offset;
94      }
95    }
96   
97    /// <summary>
98    /// Length in visual line coordinates.
99    /// </summary>
100    public int VisualLength { get; private set; }
101   
102    /// <summary>
103    /// Length in visual line coordinates including the end of line marker, if TextEditorOptions.ShowEndOfLine is enabled.
104    /// </summary>
105    public int VisualLengthWithEndOfLineMarker {
106      get {
107        int length = VisualLength;
108        if (textView.Options.ShowEndOfLine && LastDocumentLine.NextLine != null) length++;
109        return length;
110      }
111    }
112   
113    /// <summary>
114    /// Gets the height of the visual line in device-independent pixels.
115    /// </summary>
116    public double Height { get; private set; }
117   
118    /// <summary>
119    /// Gets the Y position of the line. This is measured in device-independent pixels relative to the start of the document.
120    /// </summary>
121    public double VisualTop { get; internal set; }
122   
123    internal VisualLine(TextView textView, DocumentLine firstDocumentLine)
124    {
125      Debug.Assert(textView != null);
126      Debug.Assert(firstDocumentLine != null);
127      this.textView = textView;
128      this.Document = textView.Document;
129      this.FirstDocumentLine = firstDocumentLine;
130    }
131   
132    internal void ConstructVisualElements(ITextRunConstructionContext context, VisualLineElementGenerator[] generators)
133    {
134      Debug.Assert(phase == LifetimePhase.Generating);
135      foreach (VisualLineElementGenerator g in generators) {
136        g.StartGeneration(context);
137      }
138      elements = new List<VisualLineElement>();
139      PerformVisualElementConstruction(generators);
140      foreach (VisualLineElementGenerator g in generators) {
141        g.FinishGeneration();
142      }
143     
144      var globalTextRunProperties = context.GlobalTextRunProperties;
145      foreach (var element in elements) {
146        element.SetTextRunProperties(new VisualLineElementTextRunProperties(globalTextRunProperties));
147      }
148      this.Elements = elements.AsReadOnly();
149      CalculateOffsets();
150      phase = LifetimePhase.Transforming;
151    }
152   
153    void PerformVisualElementConstruction(VisualLineElementGenerator[] generators)
154    {
155      TextDocument document = this.Document;
156      int offset = FirstDocumentLine.Offset;
157      int currentLineEnd = offset + FirstDocumentLine.Length;
158      LastDocumentLine = FirstDocumentLine;
159      int askInterestOffset = 0; // 0 or 1
160      while (offset + askInterestOffset <= currentLineEnd) {
161        int textPieceEndOffset = currentLineEnd;
162        foreach (VisualLineElementGenerator g in generators) {
163          g.cachedInterest = g.GetFirstInterestedOffset(offset + askInterestOffset);
164          if (g.cachedInterest != -1) {
165            if (g.cachedInterest < offset)
166              throw new ArgumentOutOfRangeException(g.GetType().Name + ".GetFirstInterestedOffset",
167                                                    g.cachedInterest,
168                                                    "GetFirstInterestedOffset must not return an offset less than startOffset. Return -1 to signal no interest.");
169            if (g.cachedInterest < textPieceEndOffset)
170              textPieceEndOffset = g.cachedInterest;
171          }
172        }
173        Debug.Assert(textPieceEndOffset >= offset);
174        if (textPieceEndOffset > offset) {
175          int textPieceLength = textPieceEndOffset - offset;
176          elements.Add(new VisualLineText(this, textPieceLength));
177          offset = textPieceEndOffset;
178        }
179        // If no elements constructed / only zero-length elements constructed:
180        // do not asking the generators again for the same location (would cause endless loop)
181        askInterestOffset = 1;
182        foreach (VisualLineElementGenerator g in generators) {
183          if (g.cachedInterest == offset) {
184            VisualLineElement element = g.ConstructElement(offset);
185            if (element != null) {
186              elements.Add(element);
187              if (element.DocumentLength > 0) {
188                // a non-zero-length element was constructed
189                askInterestOffset = 0;
190                offset += element.DocumentLength;
191                if (offset > currentLineEnd) {
192                  DocumentLine newEndLine = document.GetLineByOffset(offset);
193                  currentLineEnd = newEndLine.Offset + newEndLine.Length;
194                  this.LastDocumentLine = newEndLine;
195                  if (currentLineEnd < offset) {
196                    throw new InvalidOperationException(
197                      "The VisualLineElementGenerator " + g.GetType().Name +
198                      " produced an element which ends within the line delimiter");
199                  }
200                }
201                break;
202              }
203            }
204          }
205        }
206      }
207    }
208   
209    void CalculateOffsets()
210    {
211      int visualOffset = 0;
212      int textOffset = 0;
213      foreach (VisualLineElement element in elements) {
214        element.VisualColumn = visualOffset;
215        element.RelativeTextOffset = textOffset;
216        visualOffset += element.VisualLength;
217        textOffset += element.DocumentLength;
218      }
219      VisualLength = visualOffset;
220      Debug.Assert(textOffset == LastDocumentLine.EndOffset - FirstDocumentLine.Offset);
221    }
222   
223    internal void RunTransformers(ITextRunConstructionContext context, IVisualLineTransformer[] transformers)
224    {
225      Debug.Assert(phase == LifetimePhase.Transforming);
226      foreach (IVisualLineTransformer transformer in transformers) {
227        transformer.Transform(context, elements);
228      }
229      // For some strange reason, WPF requires that either all or none of the typography properties are set.
230      if (elements.Any(e => e.TextRunProperties.TypographyProperties != null)) {
231        // Fix typographic properties
232        foreach (VisualLineElement element in elements) {
233          if (element.TextRunProperties.TypographyProperties == null) {
234            element.TextRunProperties.SetTypographyProperties(new DefaultTextRunTypographyProperties());
235          }
236        }
237      }
238      phase = LifetimePhase.Live;
239    }
240   
241    /// <summary>
242    /// Replaces the single element at <paramref name="elementIndex"/> with the specified elements.
243    /// The replacement operation must preserve the document length, but may change the visual length.
244    /// </summary>
245    /// <remarks>
246    /// This method may only be called by line transformers.
247    /// </remarks>
248    public void ReplaceElement(int elementIndex, params VisualLineElement[] newElements)
249    {
250      ReplaceElement(elementIndex, 1, newElements);
251    }
252   
253    /// <summary>
254    /// Replaces <paramref name="count"/> elements starting at <paramref name="elementIndex"/> with the specified elements.
255    /// The replacement operation must preserve the document length, but may change the visual length.
256    /// </summary>
257    /// <remarks>
258    /// This method may only be called by line transformers.
259    /// </remarks>
260    public void ReplaceElement(int elementIndex, int count, params VisualLineElement[] newElements)
261    {
262      if (phase != LifetimePhase.Transforming)
263        throw new InvalidOperationException("This method may only be called by line transformers.");
264      int oldDocumentLength = 0;
265      for (int i = elementIndex; i < elementIndex + count; i++) {
266        oldDocumentLength += elements[i].DocumentLength;
267      }
268      int newDocumentLength = 0;
269      foreach (var newElement in newElements) {
270        newDocumentLength += newElement.DocumentLength;
271      }
272      if (oldDocumentLength != newDocumentLength)
273        throw new InvalidOperationException("Old elements have document length " + oldDocumentLength + ", but new elements have length " + newDocumentLength);
274      elements.RemoveRange(elementIndex, count);
275      elements.InsertRange(elementIndex, newElements);
276      CalculateOffsets();
277    }
278   
279    internal void SetTextLines(List<TextLine> textLines)
280    {
281      this.textLines = textLines.AsReadOnly();
282      Height = 0;
283      foreach (TextLine line in textLines)
284        Height += line.Height;
285    }
286   
287    /// <summary>
288    /// Gets the visual column from a document offset relative to the first line start.
289    /// </summary>
290    public int GetVisualColumn(int relativeTextOffset)
291    {
292      ThrowUtil.CheckNotNegative(relativeTextOffset, "relativeTextOffset");
293      foreach (VisualLineElement element in elements) {
294        if (element.RelativeTextOffset <= relativeTextOffset
295            && element.RelativeTextOffset + element.DocumentLength >= relativeTextOffset)
296        {
297          return element.GetVisualColumn(relativeTextOffset);
298        }
299      }
300      return VisualLength;
301    }
302   
303    /// <summary>
304    /// Gets the document offset (relative to the first line start) from a visual column.
305    /// </summary>
306    public int GetRelativeOffset(int visualColumn)
307    {
308      ThrowUtil.CheckNotNegative(visualColumn, "visualColumn");
309      int documentLength = 0;
310      foreach (VisualLineElement element in elements) {
311        if (element.VisualColumn <= visualColumn
312            && element.VisualColumn + element.VisualLength > visualColumn)
313        {
314          return element.GetRelativeOffset(visualColumn);
315        }
316        documentLength += element.DocumentLength;
317      }
318      return documentLength;
319    }
320   
321    /// <summary>
322    /// Gets the text line containing the specified visual column.
323    /// </summary>
324    public TextLine GetTextLine(int visualColumn)
325    {
326      return GetTextLine(visualColumn, false);
327    }
328   
329    /// <summary>
330    /// Gets the text line containing the specified visual column.
331    /// </summary>
332    public TextLine GetTextLine(int visualColumn, bool isAtEndOfLine)
333    {
334      if (visualColumn < 0)
335        throw new ArgumentOutOfRangeException("visualColumn");
336      if (visualColumn >= VisualLengthWithEndOfLineMarker)
337        return TextLines[TextLines.Count - 1];
338      foreach (TextLine line in TextLines) {
339        if (isAtEndOfLine ? visualColumn <= line.Length : visualColumn < line.Length)
340          return line;
341        else
342          visualColumn -= line.Length;
343      }
344      throw new InvalidOperationException("Shouldn't happen (VisualLength incorrect?)");
345    }
346   
347    /// <summary>
348    /// Gets the visual top from the specified text line.
349    /// </summary>
350    /// <returns>Distance in device-independent pixels
351    /// from the top of the document to the top of the specified text line.</returns>
352    public double GetTextLineVisualYPosition(TextLine textLine, VisualYPosition yPositionMode)
353    {
354      if (textLine == null)
355        throw new ArgumentNullException("textLine");
356      double pos = VisualTop;
357      foreach (TextLine tl in TextLines) {
358        if (tl == textLine) {
359          switch (yPositionMode) {
360            case VisualYPosition.LineTop:
361              return pos;
362            case VisualYPosition.LineMiddle:
363              return pos + tl.Height / 2;
364            case VisualYPosition.LineBottom:
365              return pos + tl.Height;
366            case VisualYPosition.TextTop:
367              return pos + tl.Baseline - textView.DefaultBaseline;
368            case VisualYPosition.TextBottom:
369              return pos + tl.Baseline - textView.DefaultBaseline + textView.DefaultLineHeight;
370            case VisualYPosition.TextMiddle:
371              return pos + tl.Baseline - textView.DefaultBaseline + textView.DefaultLineHeight / 2;
372            case VisualYPosition.Baseline:
373              return pos + tl.Baseline;
374            default:
375              throw new ArgumentException("Invalid yPositionMode:" + yPositionMode);
376          }
377        } else {
378          pos += tl.Height;
379        }
380      }
381      throw new ArgumentException("textLine is not a line in this VisualLine");
382    }
383   
384    /// <summary>
385    /// Gets the start visual column from the specified text line.
386    /// </summary>
387    public int GetTextLineVisualStartColumn(TextLine textLine)
388    {
389      if (!TextLines.Contains(textLine))
390        throw new ArgumentException("textLine is not a line in this VisualLine");
391      int col = 0;
392      foreach (TextLine tl in TextLines) {
393        if (tl == textLine)
394          break;
395        else
396          col += tl.Length;
397      }
398      return col;
399    }
400   
401    /// <summary>
402    /// Gets a TextLine by the visual position.
403    /// </summary>
404    public TextLine GetTextLineByVisualYPosition(double visualTop)
405    {
406      const double epsilon = 0.0001;
407      double pos = this.VisualTop;
408      foreach (TextLine tl in TextLines) {
409        pos += tl.Height;
410        if (visualTop + epsilon < pos)
411          return tl;
412      }
413      return TextLines[TextLines.Count - 1];
414    }
415   
416    /// <summary>
417    /// Gets the visual position from the specified visualColumn.
418    /// </summary>
419    /// <returns>Position in device-independent pixels
420    /// relative to the top left of the document.</returns>
421    public Point GetVisualPosition(int visualColumn, VisualYPosition yPositionMode)
422    {
423      TextLine textLine = GetTextLine(visualColumn);
424      double xPos = GetTextLineVisualXPosition(textLine, visualColumn);
425      double yPos = GetTextLineVisualYPosition(textLine, yPositionMode);
426      return new Point(xPos, yPos);
427    }
428   
429    internal Point GetVisualPosition(int visualColumn, bool isAtEndOfLine, VisualYPosition yPositionMode)
430    {
431      TextLine textLine = GetTextLine(visualColumn, isAtEndOfLine);
432      double xPos = GetTextLineVisualXPosition(textLine, visualColumn);
433      double yPos = GetTextLineVisualYPosition(textLine, yPositionMode);
434      return new Point(xPos, yPos);
435    }
436   
437    /// <summary>
438    /// Gets the distance to the left border of the text area of the specified visual column.
439    /// The visual column must belong to the specified text line.
440    /// </summary>
441    public double GetTextLineVisualXPosition(TextLine textLine, int visualColumn)
442    {
443      if (textLine == null)
444        throw new ArgumentNullException("textLine");
445      double xPos = textLine.GetDistanceFromCharacterHit(
446        new CharacterHit(Math.Min(visualColumn, VisualLengthWithEndOfLineMarker), 0));
447      if (visualColumn > VisualLengthWithEndOfLineMarker) {
448        xPos += (visualColumn - VisualLengthWithEndOfLineMarker) * textView.WideSpaceWidth;
449      }
450      return xPos;
451    }
452   
453    /// <summary>
454    /// Gets the visual column from a document position (relative to top left of the document).
455    /// If the user clicks between two visual columns, rounds to the nearest column.
456    /// </summary>
457    public int GetVisualColumn(Point point)
458    {
459      return GetVisualColumn(point, textView.Options.EnableVirtualSpace);
460    }
461   
462    /// <summary>
463    /// Gets the visual column from a document position (relative to top left of the document).
464    /// If the user clicks between two visual columns, rounds to the nearest column.
465    /// </summary>
466    public int GetVisualColumn(Point point, bool allowVirtualSpace)
467    {
468      return GetVisualColumn(GetTextLineByVisualYPosition(point.Y), point.X, allowVirtualSpace);
469    }
470   
471    internal int GetVisualColumn(Point point, bool allowVirtualSpace, out bool isAtEndOfLine)
472    {
473      var textLine = GetTextLineByVisualYPosition(point.Y);
474      int vc = GetVisualColumn(textLine, point.X, allowVirtualSpace);
475      isAtEndOfLine = (vc >= GetTextLineVisualStartColumn(textLine) + textLine.Length);
476      return vc;
477    }
478   
479    /// <summary>
480    /// Gets the visual column from a document position (relative to top left of the document).
481    /// If the user clicks between two visual columns, rounds to the nearest column.
482    /// </summary>
483    public int GetVisualColumn(TextLine textLine, double xPos, bool allowVirtualSpace)
484    {
485      if (xPos > textLine.WidthIncludingTrailingWhitespace) {
486        if (allowVirtualSpace && textLine == TextLines[TextLines.Count - 1]) {
487          int virtualX = (int)Math.Round((xPos - textLine.WidthIncludingTrailingWhitespace) / textView.WideSpaceWidth);
488          return VisualLengthWithEndOfLineMarker + virtualX;
489        }
490      }
491      CharacterHit ch = textLine.GetCharacterHitFromDistance(xPos);
492      return ch.FirstCharacterIndex + ch.TrailingLength;
493    }
494   
495    /// <summary>
496    /// Validates the visual column and returns the correct one.
497    /// </summary>
498    public int ValidateVisualColumn(TextViewPosition position, bool allowVirtualSpace)
499    {
500      return ValidateVisualColumn(Document.GetOffset(position.Location), position.VisualColumn, allowVirtualSpace);
501    }
502   
503    /// <summary>
504    /// Validates the visual column and returns the correct one.
505    /// </summary>
506    public int ValidateVisualColumn(int offset, int visualColumn, bool allowVirtualSpace)
507    {
508      int firstDocumentLineOffset = this.FirstDocumentLine.Offset;
509      if (visualColumn < 0) {
510        return GetVisualColumn(offset - firstDocumentLineOffset);
511      } else {
512        int offsetFromVisualColumn = GetRelativeOffset(visualColumn);
513        offsetFromVisualColumn += firstDocumentLineOffset;
514        if (offsetFromVisualColumn != offset) {
515          return GetVisualColumn(offset - firstDocumentLineOffset);
516        } else {
517          if (visualColumn > VisualLength && !allowVirtualSpace) {
518            return VisualLength;
519          }
520        }
521      }
522      return visualColumn;
523    }
524   
525    /// <summary>
526    /// Gets the visual column from a document position (relative to top left of the document).
527    /// If the user clicks between two visual columns, returns the first of those columns.
528    /// </summary>
529    public int GetVisualColumnFloor(Point point)
530    {
531      return GetVisualColumnFloor(point, textView.Options.EnableVirtualSpace);
532    }
533   
534    /// <summary>
535    /// Gets the visual column from a document position (relative to top left of the document).
536    /// If the user clicks between two visual columns, returns the first of those columns.
537    /// </summary>
538    public int GetVisualColumnFloor(Point point, bool allowVirtualSpace)
539    {
540      bool tmp;
541      return GetVisualColumnFloor(point, allowVirtualSpace, out tmp);
542    }
543   
544    internal int GetVisualColumnFloor(Point point, bool allowVirtualSpace, out bool isAtEndOfLine)
545    {
546      TextLine textLine = GetTextLineByVisualYPosition(point.Y);
547      if (point.X > textLine.WidthIncludingTrailingWhitespace) {
548        isAtEndOfLine = true;
549        if (allowVirtualSpace && textLine == TextLines[TextLines.Count - 1]) {
550          // clicking virtual space in the last line
551          int virtualX = (int)((point.X - textLine.WidthIncludingTrailingWhitespace) / textView.WideSpaceWidth);
552          return VisualLengthWithEndOfLineMarker + virtualX;
553        } else {
554          // GetCharacterHitFromDistance returns a hit with FirstCharacterIndex=last character in line
555          // and TrailingLength=1 when clicking behind the line, so the floor function needs to handle this case
556          // specially and return the line's end column instead.
557          return GetTextLineVisualStartColumn(textLine) + textLine.Length;
558        }
559      } else {
560        isAtEndOfLine = false;
561      }
562      CharacterHit ch = textLine.GetCharacterHitFromDistance(point.X);
563      return ch.FirstCharacterIndex;
564    }
565   
566    /// <summary>
567    /// Gets the text view position from the specified visual column.
568    /// </summary>
569    public TextViewPosition GetTextViewPosition(int visualColumn)
570    {
571      int documentOffset = GetRelativeOffset(visualColumn) + this.FirstDocumentLine.Offset;
572      return new TextViewPosition(this.Document.GetLocation(documentOffset), visualColumn);
573    }
574   
575    /// <summary>
576    /// Gets the text view position from the specified visual position.
577    /// If the position is within a character, it is rounded to the next character boundary.
578    /// </summary>
579    /// <param name="visualPosition">The position in WPF device-independent pixels relative
580    /// to the top left corner of the document.</param>
581    /// <param name="allowVirtualSpace">Controls whether positions in virtual space may be returned.</param>
582    public TextViewPosition GetTextViewPosition(Point visualPosition, bool allowVirtualSpace)
583    {
584      bool isAtEndOfLine;
585      int visualColumn = GetVisualColumn(visualPosition, allowVirtualSpace, out isAtEndOfLine);
586      int documentOffset = GetRelativeOffset(visualColumn) + this.FirstDocumentLine.Offset;
587      TextViewPosition pos = new TextViewPosition(this.Document.GetLocation(documentOffset), visualColumn);
588      pos.IsAtEndOfLine = isAtEndOfLine;
589      return pos;
590    }
591   
592    /// <summary>
593    /// Gets the text view position from the specified visual position.
594    /// If the position is inside a character, the position in front of the character is returned.
595    /// </summary>
596    /// <param name="visualPosition">The position in WPF device-independent pixels relative
597    /// to the top left corner of the document.</param>
598    /// <param name="allowVirtualSpace">Controls whether positions in virtual space may be returned.</param>
599    public TextViewPosition GetTextViewPositionFloor(Point visualPosition, bool allowVirtualSpace)
600    {
601      bool isAtEndOfLine;
602      int visualColumn = GetVisualColumnFloor(visualPosition, allowVirtualSpace, out isAtEndOfLine);
603      int documentOffset = GetRelativeOffset(visualColumn) + this.FirstDocumentLine.Offset;
604      TextViewPosition pos = new TextViewPosition(this.Document.GetLocation(documentOffset), visualColumn);
605      pos.IsAtEndOfLine = isAtEndOfLine;
606      return pos;
607    }
608   
609    /// <summary>
610    /// Gets whether the visual line was disposed.
611    /// </summary>
612    public bool IsDisposed {
613      get { return phase == LifetimePhase.Disposed; }
614    }
615   
616    internal void Dispose()
617    {
618      if (phase == LifetimePhase.Disposed)
619        return;
620      Debug.Assert(phase == LifetimePhase.Live);
621      phase = LifetimePhase.Disposed;
622      foreach (TextLine textLine in TextLines) {
623        textLine.Dispose();
624      }
625    }
626   
627    /// <summary>
628    /// Gets the next possible caret position after visualColumn, or -1 if there is no caret position.
629    /// </summary>
630    public int GetNextCaretPosition(int visualColumn, LogicalDirection direction, CaretPositioningMode mode, bool allowVirtualSpace)
631    {
632      if (!HasStopsInVirtualSpace(mode))
633        allowVirtualSpace = false;
634     
635      if (elements.Count == 0) {
636        // special handling for empty visual lines:
637        if (allowVirtualSpace) {
638          if (direction == LogicalDirection.Forward)
639            return Math.Max(0, visualColumn + 1);
640          else if (visualColumn > 0)
641            return visualColumn - 1;
642          else
643            return -1;
644        } else {
645          // even though we don't have any elements,
646          // there's a single caret stop at visualColumn 0
647          if (visualColumn < 0 && direction == LogicalDirection.Forward)
648            return 0;
649          else if (visualColumn > 0 && direction == LogicalDirection.Backward)
650            return 0;
651          else
652            return -1;
653        }
654      }
655     
656      int i;
657      if (direction == LogicalDirection.Backward) {
658        // Search Backwards:
659        // If the last element doesn't handle line borders, return the line end as caret stop
660       
661        if (visualColumn > this.VisualLength && !elements[elements.Count-1].HandlesLineBorders && HasImplicitStopAtLineEnd(mode)) {
662          if (allowVirtualSpace)
663            return visualColumn - 1;
664          else
665            return this.VisualLength;
666        }
667        // skip elements that start after or at visualColumn
668        for (i = elements.Count - 1; i >= 0; i--) {
669          if (elements[i].VisualColumn < visualColumn)
670            break;
671        }
672        // search last element that has a caret stop
673        for (; i >= 0; i--) {
674          int pos = elements[i].GetNextCaretPosition(
675            Math.Min(visualColumn, elements[i].VisualColumn + elements[i].VisualLength + 1),
676            direction, mode);
677          if (pos >= 0)
678            return pos;
679        }
680        // If we've found nothing, and the first element doesn't handle line borders,
681        // return the line start as normal caret stop.
682        if (visualColumn > 0 && !elements[0].HandlesLineBorders && HasImplicitStopAtLineStart(mode))
683          return 0;
684      } else {
685        // Search Forwards:
686        // If the first element doesn't handle line borders, return the line start as caret stop
687        if (visualColumn < 0 && !elements[0].HandlesLineBorders && HasImplicitStopAtLineStart(mode))
688          return 0;
689        // skip elements that end before or at visualColumn
690        for (i = 0; i < elements.Count; i++) {
691          if (elements[i].VisualColumn + elements[i].VisualLength > visualColumn)
692            break;
693        }
694        // search first element that has a caret stop
695        for (; i < elements.Count; i++) {
696          int pos = elements[i].GetNextCaretPosition(
697            Math.Max(visualColumn, elements[i].VisualColumn - 1),
698            direction, mode);
699          if (pos >= 0)
700            return pos;
701        }
702        // if we've found nothing, and the last element doesn't handle line borders,
703        // return the line end as caret stop
704        if ((allowVirtualSpace || !elements[elements.Count-1].HandlesLineBorders) && HasImplicitStopAtLineEnd(mode)) {
705          if (visualColumn < this.VisualLength)
706            return this.VisualLength;
707          else if (allowVirtualSpace)
708            return visualColumn + 1;
709        }
710      }
711      // we've found nothing, return -1 and let the caret search continue in the next line
712      return -1;
713    }
714   
715    static bool HasStopsInVirtualSpace(CaretPositioningMode mode)
716    {
717      return mode == CaretPositioningMode.Normal || mode == CaretPositioningMode.EveryCodepoint;
718    }
719   
720    static bool HasImplicitStopAtLineStart(CaretPositioningMode mode)
721    {
722      return mode == CaretPositioningMode.Normal || mode == CaretPositioningMode.EveryCodepoint;
723    }
724   
725    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "mode",
726                                                     Justification = "make method consistent with HasImplicitStopAtLineStart; might depend on mode in the future")]
727    static bool HasImplicitStopAtLineEnd(CaretPositioningMode mode)
728    {
729      return true;
730    }
731   
732    VisualLineDrawingVisual visual;
733   
734    internal VisualLineDrawingVisual Render()
735    {
736      Debug.Assert(phase == LifetimePhase.Live);
737      if (visual == null)
738        visual = new VisualLineDrawingVisual(this);
739      return visual;
740    }
741  }
742 
743  sealed class VisualLineDrawingVisual : DrawingVisual
744  {
745    public readonly VisualLine VisualLine;
746    public readonly double Height;
747    internal bool IsAdded;
748   
749    public VisualLineDrawingVisual(VisualLine visualLine)
750    {
751      this.VisualLine = visualLine;
752      var drawingContext = RenderOpen();
753      double pos = 0;
754      foreach (TextLine textLine in visualLine.TextLines) {
755        textLine.Draw(drawingContext, new Point(0, pos), InvertAxes.None);
756        pos += textLine.Height;
757      }
758      this.Height = pos;
759      drawingContext.Close();
760    }
761   
762    protected override GeometryHitTestResult HitTestCore(GeometryHitTestParameters hitTestParameters)
763    {
764      return null;
765    }
766   
767    protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
768    {
769      return null;
770    }
771  }
772}
Note: See TracBrowser for help on using the repository browser.