Free cookie consent management tool by TermsFeed Policy Generator

source: branches/PersistenceOverhaul/HeuristicLab.ExtLibs/HeuristicLab.AvalonEdit/5.0.1/AvalonEdit-5.0.1/Rendering/TextView.cs @ 16003

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

#2077: created branch and added first version

File size: 70.6 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;
20using System.Collections.Generic;
21using System.Collections.ObjectModel;
22using System.ComponentModel;
23using System.ComponentModel.Design;
24using System.Diagnostics;
25using System.Globalization;
26using System.Linq;
27using System.Windows;
28using System.Windows.Controls;
29using System.Windows.Controls.Primitives;
30using System.Windows.Input;
31using System.Windows.Media;
32using System.Windows.Media.TextFormatting;
33using System.Windows.Threading;
34using ICSharpCode.NRefactory.Editor;
35using ICSharpCode.AvalonEdit.Document;
36using ICSharpCode.AvalonEdit.Utils;
37
38namespace ICSharpCode.AvalonEdit.Rendering
39{
40  /// <summary>
41  /// A virtualizing panel producing+showing <see cref="VisualLine"/>s for a <see cref="TextDocument"/>.
42  ///
43  /// This is the heart of the text editor, this class controls the text rendering process.
44  ///
45  /// Taken as a standalone control, it's a text viewer without any editing capability.
46  /// </summary>
47  [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable",
48                                                   Justification = "The user usually doesn't work with TextView but with TextEditor; and nulling the Document property is sufficient to dispose everything.")]
49  public class TextView : FrameworkElement, IScrollInfo, IWeakEventListener, ITextEditorComponent, IServiceProvider
50  {
51    #region Constructor
52    static TextView()
53    {
54      ClipToBoundsProperty.OverrideMetadata(typeof(TextView), new FrameworkPropertyMetadata(Boxes.True));
55      FocusableProperty.OverrideMetadata(typeof(TextView), new FrameworkPropertyMetadata(Boxes.False));
56    }
57   
58    ColumnRulerRenderer columnRulerRenderer;
59    CurrentLineHighlightRenderer currentLineHighlighRenderer;
60   
61    /// <summary>
62    /// Creates a new TextView instance.
63    /// </summary>
64    public TextView()
65    {
66      services.AddService(typeof(TextView), this);
67      textLayer = new TextLayer(this);
68      elementGenerators = new ObserveAddRemoveCollection<VisualLineElementGenerator>(ElementGenerator_Added, ElementGenerator_Removed);
69      lineTransformers = new ObserveAddRemoveCollection<IVisualLineTransformer>(LineTransformer_Added, LineTransformer_Removed);
70      backgroundRenderers = new ObserveAddRemoveCollection<IBackgroundRenderer>(BackgroundRenderer_Added, BackgroundRenderer_Removed);
71      columnRulerRenderer = new ColumnRulerRenderer(this);
72      currentLineHighlighRenderer = new CurrentLineHighlightRenderer(this);
73      this.Options = new TextEditorOptions();
74     
75      Debug.Assert(singleCharacterElementGenerator != null); // assert that the option change created the builtin element generators
76     
77      layers = new LayerCollection(this);
78      InsertLayer(textLayer, KnownLayer.Text, LayerInsertionPosition.Replace);
79     
80      this.hoverLogic = new MouseHoverLogic(this);
81      this.hoverLogic.MouseHover += (sender, e) => RaiseHoverEventPair(e, PreviewMouseHoverEvent, MouseHoverEvent);
82      this.hoverLogic.MouseHoverStopped += (sender, e) => RaiseHoverEventPair(e, PreviewMouseHoverStoppedEvent, MouseHoverStoppedEvent);
83    }
84
85    #endregion
86   
87    #region Document Property
88    /// <summary>
89    /// Document property.
90    /// </summary>
91    public static readonly DependencyProperty DocumentProperty =
92      DependencyProperty.Register("Document", typeof(TextDocument), typeof(TextView),
93                                  new FrameworkPropertyMetadata(OnDocumentChanged));
94   
95    TextDocument document;
96    HeightTree heightTree;
97   
98    /// <summary>
99    /// Gets/Sets the document displayed by the text editor.
100    /// </summary>
101    public TextDocument Document {
102      get { return (TextDocument)GetValue(DocumentProperty); }
103      set { SetValue(DocumentProperty, value); }
104    }
105   
106    static void OnDocumentChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
107    {
108      ((TextView)dp).OnDocumentChanged((TextDocument)e.OldValue, (TextDocument)e.NewValue);
109    }
110   
111    internal double FontSize {
112      get {
113        return (double)GetValue(TextBlock.FontSizeProperty);
114      }
115    }
116   
117    /// <summary>
118    /// Occurs when the document property has changed.
119    /// </summary>
120    public event EventHandler DocumentChanged;
121   
122    void OnDocumentChanged(TextDocument oldValue, TextDocument newValue)
123    {
124      if (oldValue != null) {
125        heightTree.Dispose();
126        heightTree = null;
127        formatter.Dispose();
128        formatter = null;
129        cachedElements.Dispose();
130        cachedElements = null;
131        TextDocumentWeakEventManager.Changing.RemoveListener(oldValue, this);
132      }
133      this.document = newValue;
134      ClearScrollData();
135      ClearVisualLines();
136      if (newValue != null) {
137        TextDocumentWeakEventManager.Changing.AddListener(newValue, this);
138        formatter = TextFormatterFactory.Create(this);
139        InvalidateDefaultTextMetrics(); // measuring DefaultLineHeight depends on formatter
140        heightTree = new HeightTree(newValue, DefaultLineHeight);
141        cachedElements = new TextViewCachedElements();
142      }
143      InvalidateMeasure(DispatcherPriority.Normal);
144      if (DocumentChanged != null)
145        DocumentChanged(this, EventArgs.Empty);
146    }
147   
148    /// <summary>
149    /// Recreates the text formatter that is used internally
150    /// by calling <see cref="TextFormatterFactory.Create"/>.
151    /// </summary>
152    void RecreateTextFormatter()
153    {
154      if (formatter != null) {
155        formatter.Dispose();
156        formatter = TextFormatterFactory.Create(this);
157        Redraw();
158      }
159    }
160   
161    void RecreateCachedElements()
162    {
163      if (cachedElements != null) {
164        cachedElements.Dispose();
165        cachedElements = new TextViewCachedElements();
166      }
167    }
168   
169    /// <inheritdoc cref="IWeakEventListener.ReceiveWeakEvent"/>
170    protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
171    {
172      if (managerType == typeof(TextDocumentWeakEventManager.Changing)) {
173        // TODO: put redraw into background so that other input events can be handled before the redraw.
174        // Unfortunately the "easy" approach (just use DispatcherPriority.Background) here makes the editor twice as slow because
175        // the caret position change forces an immediate redraw, and the text input then forces a background redraw.
176        // When fixing this, make sure performance on the SharpDevelop "type text in C# comment" stress test doesn't get significantly worse.
177        DocumentChangeEventArgs change = (DocumentChangeEventArgs)e;
178        Redraw(change.Offset, change.RemovalLength, DispatcherPriority.Normal);
179        return true;
180      } else if (managerType == typeof(PropertyChangedWeakEventManager)) {
181        OnOptionChanged((PropertyChangedEventArgs)e);
182        return true;
183      }
184      return false;
185    }
186   
187    bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
188    {
189      return ReceiveWeakEvent(managerType, sender, e);
190    }
191    #endregion
192   
193    #region Options property
194    /// <summary>
195    /// Options property.
196    /// </summary>
197    public static readonly DependencyProperty OptionsProperty =
198      DependencyProperty.Register("Options", typeof(TextEditorOptions), typeof(TextView),
199                                  new FrameworkPropertyMetadata(OnOptionsChanged));
200   
201    /// <summary>
202    /// Gets/Sets the options used by the text editor.
203    /// </summary>
204    public TextEditorOptions Options {
205      get { return (TextEditorOptions)GetValue(OptionsProperty); }
206      set { SetValue(OptionsProperty, value); }
207    }
208   
209    /// <summary>
210    /// Occurs when a text editor option has changed.
211    /// </summary>
212    public event PropertyChangedEventHandler OptionChanged;
213   
214    /// <summary>
215    /// Raises the <see cref="OptionChanged"/> event.
216    /// </summary>
217    protected virtual void OnOptionChanged(PropertyChangedEventArgs e)
218    {
219      if (OptionChanged != null) {
220        OptionChanged(this, e);
221      }
222     
223      if (Options.ShowColumnRuler)
224        columnRulerRenderer.SetRuler(Options.ColumnRulerPosition, ColumnRulerPen);
225      else
226        columnRulerRenderer.SetRuler(-1, ColumnRulerPen);
227     
228      UpdateBuiltinElementGeneratorsFromOptions();
229      Redraw();
230    }
231   
232    static void OnOptionsChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
233    {
234      ((TextView)dp).OnOptionsChanged((TextEditorOptions)e.OldValue, (TextEditorOptions)e.NewValue);
235    }
236   
237    void OnOptionsChanged(TextEditorOptions oldValue, TextEditorOptions newValue)
238    {
239      if (oldValue != null) {
240        PropertyChangedWeakEventManager.RemoveListener(oldValue, this);
241      }
242      if (newValue != null) {
243        PropertyChangedWeakEventManager.AddListener(newValue, this);
244      }
245      OnOptionChanged(new PropertyChangedEventArgs(null));
246    }
247    #endregion
248   
249    #region ElementGenerators+LineTransformers Properties
250    readonly ObserveAddRemoveCollection<VisualLineElementGenerator> elementGenerators;
251   
252    /// <summary>
253    /// Gets a collection where element generators can be registered.
254    /// </summary>
255    public IList<VisualLineElementGenerator> ElementGenerators {
256      get { return elementGenerators; }
257    }
258   
259    void ElementGenerator_Added(VisualLineElementGenerator generator)
260    {
261      ConnectToTextView(generator);
262      Redraw();
263    }
264   
265    void ElementGenerator_Removed(VisualLineElementGenerator generator)
266    {
267      DisconnectFromTextView(generator);
268      Redraw();
269    }
270   
271    readonly ObserveAddRemoveCollection<IVisualLineTransformer> lineTransformers;
272   
273    /// <summary>
274    /// Gets a collection where line transformers can be registered.
275    /// </summary>
276    public IList<IVisualLineTransformer> LineTransformers {
277      get { return lineTransformers; }
278    }
279   
280    void LineTransformer_Added(IVisualLineTransformer lineTransformer)
281    {
282      ConnectToTextView(lineTransformer);
283      Redraw();
284    }
285   
286    void LineTransformer_Removed(IVisualLineTransformer lineTransformer)
287    {
288      DisconnectFromTextView(lineTransformer);
289      Redraw();
290    }
291    #endregion
292   
293    #region Builtin ElementGenerators
294//    NewLineElementGenerator newLineElementGenerator;
295    SingleCharacterElementGenerator singleCharacterElementGenerator;
296    LinkElementGenerator linkElementGenerator;
297    MailLinkElementGenerator mailLinkElementGenerator;
298   
299    void UpdateBuiltinElementGeneratorsFromOptions()
300    {
301      TextEditorOptions options = this.Options;
302     
303//      AddRemoveDefaultElementGeneratorOnDemand(ref newLineElementGenerator, options.ShowEndOfLine);
304      AddRemoveDefaultElementGeneratorOnDemand(ref singleCharacterElementGenerator, options.ShowBoxForControlCharacters || options.ShowSpaces || options.ShowTabs);
305      AddRemoveDefaultElementGeneratorOnDemand(ref linkElementGenerator, options.EnableHyperlinks);
306      AddRemoveDefaultElementGeneratorOnDemand(ref mailLinkElementGenerator, options.EnableEmailHyperlinks);
307    }
308   
309    void AddRemoveDefaultElementGeneratorOnDemand<T>(ref T generator, bool demand)
310      where T : VisualLineElementGenerator, IBuiltinElementGenerator, new()
311    {
312      bool hasGenerator = generator != null;
313      if (hasGenerator != demand) {
314        if (demand) {
315          generator = new T();
316          this.ElementGenerators.Add(generator);
317        } else {
318          this.ElementGenerators.Remove(generator);
319          generator = null;
320        }
321      }
322      if (generator != null)
323        generator.FetchOptions(this.Options);
324    }
325    #endregion
326   
327    #region Layers
328    internal readonly TextLayer textLayer;
329    readonly LayerCollection layers;
330   
331    /// <summary>
332    /// Gets the list of layers displayed in the text view.
333    /// </summary>
334    public UIElementCollection Layers {
335      get { return layers; }
336    }
337   
338    sealed class LayerCollection : UIElementCollection
339    {
340      readonly TextView textView;
341     
342      public LayerCollection(TextView textView)
343        : base(textView, textView)
344      {
345        this.textView = textView;
346      }
347     
348      public override void Clear()
349      {
350        base.Clear();
351        textView.LayersChanged();
352      }
353     
354      public override int Add(UIElement element)
355      {
356        int r = base.Add(element);
357        textView.LayersChanged();
358        return r;
359      }
360     
361      public override void RemoveAt(int index)
362      {
363        base.RemoveAt(index);
364        textView.LayersChanged();
365      }
366     
367      public override void RemoveRange(int index, int count)
368      {
369        base.RemoveRange(index, count);
370        textView.LayersChanged();
371      }
372    }
373   
374    void LayersChanged()
375    {
376      textLayer.index = layers.IndexOf(textLayer);
377    }
378   
379    /// <summary>
380    /// Inserts a new layer at a position specified relative to an existing layer.
381    /// </summary>
382    /// <param name="layer">The new layer to insert.</param>
383    /// <param name="referencedLayer">The existing layer</param>
384    /// <param name="position">Specifies whether the layer is inserted above,below, or replaces the referenced layer</param>
385    public void InsertLayer(UIElement layer, KnownLayer referencedLayer, LayerInsertionPosition position)
386    {
387      if (layer == null)
388        throw new ArgumentNullException("layer");
389      if (!Enum.IsDefined(typeof(KnownLayer), referencedLayer))
390        throw new InvalidEnumArgumentException("referencedLayer", (int)referencedLayer, typeof(KnownLayer));
391      if (!Enum.IsDefined(typeof(LayerInsertionPosition), position))
392        throw new InvalidEnumArgumentException("position", (int)position, typeof(LayerInsertionPosition));
393      if (referencedLayer == KnownLayer.Background && position != LayerInsertionPosition.Above)
394        throw new InvalidOperationException("Cannot replace or insert below the background layer.");
395     
396      LayerPosition newPosition = new LayerPosition(referencedLayer, position);
397      LayerPosition.SetLayerPosition(layer, newPosition);
398      for (int i = 0; i < layers.Count; i++) {
399        LayerPosition p = LayerPosition.GetLayerPosition(layers[i]);
400        if (p != null) {
401          if (p.KnownLayer == referencedLayer && p.Position == LayerInsertionPosition.Replace) {
402            // found the referenced layer
403            switch (position) {
404              case LayerInsertionPosition.Below:
405                layers.Insert(i, layer);
406                return;
407              case LayerInsertionPosition.Above:
408                layers.Insert(i + 1, layer);
409                return;
410              case LayerInsertionPosition.Replace:
411                layers[i] = layer;
412                return;
413            }
414          } else if (p.KnownLayer == referencedLayer && p.Position == LayerInsertionPosition.Above
415                     || p.KnownLayer > referencedLayer) {
416            // we skipped the insertion position (referenced layer does not exist?)
417            layers.Insert(i, layer);
418            return;
419          }
420        }
421      }
422      // inserting after all existing layers:
423      layers.Add(layer);
424    }
425   
426    /// <inheritdoc/>
427    protected override int VisualChildrenCount {
428      get { return layers.Count + inlineObjects.Count; }
429    }
430   
431    /// <inheritdoc/>
432    protected override Visual GetVisualChild(int index)
433    {
434      int cut = textLayer.index + 1;
435      if (index < cut)
436        return layers[index];
437      else if (index < cut + inlineObjects.Count)
438        return inlineObjects[index - cut].Element;
439      else
440        return layers[index - inlineObjects.Count];
441    }
442   
443    /// <inheritdoc/>
444    protected override System.Collections.IEnumerator LogicalChildren {
445      get {
446        return inlineObjects.Select(io => io.Element).Concat(layers.Cast<UIElement>()).GetEnumerator();
447      }
448    }
449    #endregion
450   
451    #region Inline object handling
452    List<InlineObjectRun> inlineObjects = new List<InlineObjectRun>();
453   
454    /// <summary>
455    /// Adds a new inline object.
456    /// </summary>
457    internal void AddInlineObject(InlineObjectRun inlineObject)
458    {
459      Debug.Assert(inlineObject.VisualLine != null);
460     
461      // Remove inline object if its already added, can happen e.g. when recreating textrun for word-wrapping
462      bool alreadyAdded = false;
463      for (int i = 0; i < inlineObjects.Count; i++) {
464        if (inlineObjects[i].Element == inlineObject.Element) {
465          RemoveInlineObjectRun(inlineObjects[i], true);
466          inlineObjects.RemoveAt(i);
467          alreadyAdded = true;
468          break;
469        }
470      }
471     
472      inlineObjects.Add(inlineObject);
473      if (!alreadyAdded) {
474        AddVisualChild(inlineObject.Element);
475      }
476      inlineObject.Element.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
477      inlineObject.desiredSize = inlineObject.Element.DesiredSize;
478    }
479   
480    void MeasureInlineObjects()
481    {
482      // As part of MeasureOverride(), re-measure the inline objects
483      foreach (InlineObjectRun inlineObject in inlineObjects) {
484        if (inlineObject.VisualLine.IsDisposed) {
485          // Don't re-measure inline objects that are going to be removed anyways.
486          // If the inline object will be reused in a different VisualLine, we'll measure it in the AddInlineObject() call.
487          continue;
488        }
489        inlineObject.Element.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
490        if (!inlineObject.Element.DesiredSize.IsClose(inlineObject.desiredSize)) {
491          // the element changed size -> recreate its parent visual line
492          inlineObject.desiredSize = inlineObject.Element.DesiredSize;
493          if (allVisualLines.Remove(inlineObject.VisualLine)) {
494            DisposeVisualLine(inlineObject.VisualLine);
495          }
496        }
497      }
498    }
499   
500    List<VisualLine> visualLinesWithOutstandingInlineObjects = new List<VisualLine>();
501   
502    void RemoveInlineObjects(VisualLine visualLine)
503    {
504      // Delay removing inline objects:
505      // A document change immediately invalidates affected visual lines, but it does not
506      // cause an immediate redraw.
507      // To prevent inline objects from flickering when they are recreated, we delay removing
508      // inline objects until the next redraw.
509      if (visualLine.hasInlineObjects) {
510        visualLinesWithOutstandingInlineObjects.Add(visualLine);
511      }
512    }
513   
514    /// <summary>
515    /// Remove the inline objects that were marked for removal.
516    /// </summary>
517    void RemoveInlineObjectsNow()
518    {
519      if (visualLinesWithOutstandingInlineObjects.Count == 0)
520        return;
521      inlineObjects.RemoveAll(
522        ior => {
523          if (visualLinesWithOutstandingInlineObjects.Contains(ior.VisualLine)) {
524            RemoveInlineObjectRun(ior, false);
525            return true;
526          }
527          return false;
528        });
529      visualLinesWithOutstandingInlineObjects.Clear();
530    }
531
532    // Remove InlineObjectRun.Element from TextLayer.
533    // Caller of RemoveInlineObjectRun will remove it from inlineObjects collection.
534    void RemoveInlineObjectRun(InlineObjectRun ior, bool keepElement)
535    {
536      if (!keepElement && ior.Element.IsKeyboardFocusWithin) {
537        // When the inline element that has the focus is removed, WPF will reset the
538        // focus to the main window without raising appropriate LostKeyboardFocus events.
539        // To work around this, we manually set focus to the next focusable parent.
540        UIElement element = this;
541        while (element != null && !element.Focusable) {
542          element = VisualTreeHelper.GetParent(element) as UIElement;
543        }
544        if (element != null)
545          Keyboard.Focus(element);
546      }
547      ior.VisualLine = null;
548      if (!keepElement)
549        RemoveVisualChild(ior.Element);
550    }
551    #endregion
552   
553    #region Brushes
554    /// <summary>
555    /// NonPrintableCharacterBrush dependency property.
556    /// </summary>
557    public static readonly DependencyProperty NonPrintableCharacterBrushProperty =
558      DependencyProperty.Register("NonPrintableCharacterBrush", typeof(Brush), typeof(TextView),
559                                  new FrameworkPropertyMetadata(Brushes.LightGray));
560   
561    /// <summary>
562    /// Gets/sets the Brush used for displaying non-printable characters.
563    /// </summary>
564    public Brush NonPrintableCharacterBrush {
565      get { return (Brush)GetValue(NonPrintableCharacterBrushProperty); }
566      set { SetValue(NonPrintableCharacterBrushProperty, value); }
567    }
568   
569    /// <summary>
570    /// LinkTextForegroundBrush dependency property.
571    /// </summary>
572    public static readonly DependencyProperty LinkTextForegroundBrushProperty =
573      DependencyProperty.Register("LinkTextForegroundBrush", typeof(Brush), typeof(TextView),
574                                  new FrameworkPropertyMetadata(Brushes.Blue));
575   
576    /// <summary>
577    /// Gets/sets the Brush used for displaying link texts.
578    /// </summary>
579    public Brush LinkTextForegroundBrush {
580      get { return (Brush)GetValue(LinkTextForegroundBrushProperty); }
581      set { SetValue(LinkTextForegroundBrushProperty, value); }
582    }
583   
584    /// <summary>
585    /// LinkTextBackgroundBrush dependency property.
586    /// </summary>
587    public static readonly DependencyProperty LinkTextBackgroundBrushProperty =
588      DependencyProperty.Register("LinkTextBackgroundBrush", typeof(Brush), typeof(TextView),
589                                  new FrameworkPropertyMetadata(Brushes.Transparent));
590   
591    /// <summary>
592    /// Gets/sets the Brush used for the background of link texts.
593    /// </summary>
594    public Brush LinkTextBackgroundBrush {
595      get { return (Brush)GetValue(LinkTextBackgroundBrushProperty); }
596      set { SetValue(LinkTextBackgroundBrushProperty, value); }
597    }
598    #endregion
599   
600    #region Redraw methods / VisualLine invalidation
601    /// <summary>
602    /// Causes the text editor to regenerate all visual lines.
603    /// </summary>
604    public void Redraw()
605    {
606      Redraw(DispatcherPriority.Normal);
607    }
608   
609    /// <summary>
610    /// Causes the text editor to regenerate all visual lines.
611    /// </summary>
612    public void Redraw(DispatcherPriority redrawPriority)
613    {
614      VerifyAccess();
615      ClearVisualLines();
616      InvalidateMeasure(redrawPriority);
617    }
618   
619    /// <summary>
620    /// Causes the text editor to regenerate the specified visual line.
621    /// </summary>
622    public void Redraw(VisualLine visualLine, DispatcherPriority redrawPriority = DispatcherPriority.Normal)
623    {
624      VerifyAccess();
625      if (allVisualLines.Remove(visualLine)) {
626        DisposeVisualLine(visualLine);
627        InvalidateMeasure(redrawPriority);
628      }
629    }
630   
631    /// <summary>
632    /// Causes the text editor to redraw all lines overlapping with the specified segment.
633    /// </summary>
634    public void Redraw(int offset, int length, DispatcherPriority redrawPriority = DispatcherPriority.Normal)
635    {
636      VerifyAccess();
637      bool changedSomethingBeforeOrInLine = false;
638      for (int i = 0; i < allVisualLines.Count; i++) {
639        VisualLine visualLine = allVisualLines[i];
640        int lineStart = visualLine.FirstDocumentLine.Offset;
641        int lineEnd = visualLine.LastDocumentLine.Offset + visualLine.LastDocumentLine.TotalLength;
642        if (offset <= lineEnd) {
643          changedSomethingBeforeOrInLine = true;
644          if (offset + length >= lineStart) {
645            allVisualLines.RemoveAt(i--);
646            DisposeVisualLine(visualLine);
647          }
648        }
649      }
650      if (changedSomethingBeforeOrInLine) {
651        // Repaint not only when something in visible area was changed, but also when anything in front of it
652        // was changed. We might have to redraw the line number margin. Or the highlighting changed.
653        // However, we'll try to reuse the existing VisualLines.
654        InvalidateMeasure(redrawPriority);
655      }
656    }
657   
658    /// <summary>
659    /// Causes a known layer to redraw.
660    /// This method does not invalidate visual lines;
661    /// use the <see cref="Redraw()"/> method to do that.
662    /// </summary>
663    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "knownLayer",
664                                                     Justification="This method is meant to invalidate only a specific layer - I just haven't figured out how to do that, yet.")]
665    public void InvalidateLayer(KnownLayer knownLayer)
666    {
667      InvalidateMeasure(DispatcherPriority.Normal);
668    }
669   
670    /// <summary>
671    /// Causes a known layer to redraw.
672    /// This method does not invalidate visual lines;
673    /// use the <see cref="Redraw()"/> method to do that.
674    /// </summary>
675    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "knownLayer",
676                                                     Justification="This method is meant to invalidate only a specific layer - I just haven't figured out how to do that, yet.")]
677    public void InvalidateLayer(KnownLayer knownLayer, DispatcherPriority priority)
678    {
679      InvalidateMeasure(priority);
680    }
681   
682    /// <summary>
683    /// Causes the text editor to redraw all lines overlapping with the specified segment.
684    /// Does nothing if segment is null.
685    /// </summary>
686    public void Redraw(ISegment segment, DispatcherPriority redrawPriority = DispatcherPriority.Normal)
687    {
688      if (segment != null) {
689        Redraw(segment.Offset, segment.Length, redrawPriority);
690      }
691    }
692   
693    /// <summary>
694    /// Invalidates all visual lines.
695    /// The caller of ClearVisualLines() must also call InvalidateMeasure() to ensure
696    /// that the visual lines will be recreated.
697    /// </summary>
698    void ClearVisualLines()
699    {
700      visibleVisualLines = null;
701      if (allVisualLines.Count != 0) {
702        foreach (VisualLine visualLine in allVisualLines) {
703          DisposeVisualLine(visualLine);
704        }
705        allVisualLines.Clear();
706      }
707    }
708   
709    void DisposeVisualLine(VisualLine visualLine)
710    {
711      if (newVisualLines != null && newVisualLines.Contains(visualLine)) {
712        throw new ArgumentException("Cannot dispose visual line because it is in construction!");
713      }
714      visibleVisualLines = null;
715      visualLine.Dispose();
716      RemoveInlineObjects(visualLine);
717    }
718    #endregion
719   
720    #region InvalidateMeasure(DispatcherPriority)
721    DispatcherOperation invalidateMeasureOperation;
722   
723    void InvalidateMeasure(DispatcherPriority priority)
724    {
725      if (priority >= DispatcherPriority.Render) {
726        if (invalidateMeasureOperation != null) {
727          invalidateMeasureOperation.Abort();
728          invalidateMeasureOperation = null;
729        }
730        base.InvalidateMeasure();
731      } else {
732        if (invalidateMeasureOperation != null) {
733          invalidateMeasureOperation.Priority = priority;
734        } else {
735          invalidateMeasureOperation = Dispatcher.BeginInvoke(
736            priority,
737            new Action(
738              delegate {
739                invalidateMeasureOperation = null;
740                base.InvalidateMeasure();
741              }
742            )
743          );
744        }
745      }
746    }
747    #endregion
748   
749    #region Get(OrConstruct)VisualLine
750    /// <summary>
751    /// Gets the visual line that contains the document line with the specified number.
752    /// Returns null if the document line is outside the visible range.
753    /// </summary>
754    public VisualLine GetVisualLine(int documentLineNumber)
755    {
756      // TODO: EnsureVisualLines() ?
757      foreach (VisualLine visualLine in allVisualLines) {
758        Debug.Assert(visualLine.IsDisposed == false);
759        int start = visualLine.FirstDocumentLine.LineNumber;
760        int end = visualLine.LastDocumentLine.LineNumber;
761        if (documentLineNumber >= start && documentLineNumber <= end)
762          return visualLine;
763      }
764      return null;
765    }
766   
767    /// <summary>
768    /// Gets the visual line that contains the document line with the specified number.
769    /// If that line is outside the visible range, a new VisualLine for that document line is constructed.
770    /// </summary>
771    public VisualLine GetOrConstructVisualLine(DocumentLine documentLine)
772    {
773      if (documentLine == null)
774        throw new ArgumentNullException("documentLine");
775      if (!this.Document.Lines.Contains(documentLine))
776        throw new InvalidOperationException("Line belongs to wrong document");
777      VerifyAccess();
778     
779      VisualLine l = GetVisualLine(documentLine.LineNumber);
780      if (l == null) {
781        TextRunProperties globalTextRunProperties = CreateGlobalTextRunProperties();
782        VisualLineTextParagraphProperties paragraphProperties = CreateParagraphProperties(globalTextRunProperties);
783       
784        while (heightTree.GetIsCollapsed(documentLine.LineNumber)) {
785          documentLine = documentLine.PreviousLine;
786        }
787       
788        l = BuildVisualLine(documentLine,
789                            globalTextRunProperties, paragraphProperties,
790                            elementGenerators.ToArray(), lineTransformers.ToArray(),
791                            lastAvailableSize);
792        allVisualLines.Add(l);
793        // update all visual top values (building the line might have changed visual top of other lines due to word wrapping)
794        foreach (var line in allVisualLines) {
795          line.VisualTop = heightTree.GetVisualPosition(line.FirstDocumentLine);
796        }
797      }
798      return l;
799    }
800    #endregion
801   
802    #region Visual Lines (fields and properties)
803    List<VisualLine> allVisualLines = new List<VisualLine>();
804    ReadOnlyCollection<VisualLine> visibleVisualLines;
805    double clippedPixelsOnTop;
806    List<VisualLine> newVisualLines;
807   
808    /// <summary>
809    /// Gets the currently visible visual lines.
810    /// </summary>
811    /// <exception cref="VisualLinesInvalidException">
812    /// Gets thrown if there are invalid visual lines when this property is accessed.
813    /// You can use the <see cref="VisualLinesValid"/> property to check for this case,
814    /// or use the <see cref="EnsureVisualLines()"/> method to force creating the visual lines
815    /// when they are invalid.
816    /// </exception>
817    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")]
818    public ReadOnlyCollection<VisualLine> VisualLines {
819      get {
820        if (visibleVisualLines == null)
821          throw new VisualLinesInvalidException();
822        return visibleVisualLines;
823      }
824    }
825   
826    /// <summary>
827    /// Gets whether the visual lines are valid.
828    /// Will return false after a call to Redraw().
829    /// Accessing the visual lines property will cause a <see cref="VisualLinesInvalidException"/>
830    /// if this property is <c>false</c>.
831    /// </summary>
832    public bool VisualLinesValid {
833      get { return visibleVisualLines != null; }
834    }
835   
836    /// <summary>
837    /// Occurs when the TextView is about to be measured and will regenerate its visual lines.
838    /// This event may be used to mark visual lines as invalid that would otherwise be reused.
839    /// </summary>
840    public event EventHandler<VisualLineConstructionStartEventArgs> VisualLineConstructionStarting;
841   
842    /// <summary>
843    /// Occurs when the TextView was measured and changed its visual lines.
844    /// </summary>
845    public event EventHandler VisualLinesChanged;
846   
847    /// <summary>
848    /// If the visual lines are invalid, creates new visual lines for the visible part
849    /// of the document.
850    /// If all visual lines are valid, this method does nothing.
851    /// </summary>
852    /// <exception cref="InvalidOperationException">The visual line build process is already running.
853    /// It is not allowed to call this method during the construction of a visual line.</exception>
854    public void EnsureVisualLines()
855    {
856      Dispatcher.VerifyAccess();
857      if (inMeasure)
858        throw new InvalidOperationException("The visual line build process is already running! Cannot EnsureVisualLines() during Measure!");
859      if (!VisualLinesValid) {
860        // increase priority for re-measure
861        InvalidateMeasure(DispatcherPriority.Normal);
862        // force immediate re-measure
863        UpdateLayout();
864      }
865      // Sometimes we still have invalid lines after UpdateLayout - work around the problem
866      // by calling MeasureOverride directly.
867      if (!VisualLinesValid) {
868        Debug.WriteLine("UpdateLayout() failed in EnsureVisualLines");
869        MeasureOverride(lastAvailableSize);
870      }
871      if (!VisualLinesValid)
872        throw new VisualLinesInvalidException("Internal error: visual lines invalid after EnsureVisualLines call");
873    }
874    #endregion
875   
876    #region Measure
877    /// <summary>
878    /// Additonal amount that allows horizontal scrolling past the end of the longest line.
879    /// This is necessary to ensure the caret always is visible, even when it is at the end of the longest line.
880    /// </summary>
881    const double AdditionalHorizontalScrollAmount = 3;
882   
883    Size lastAvailableSize;
884    bool inMeasure;
885   
886    /// <inheritdoc/>
887    protected override Size MeasureOverride(Size availableSize)
888    {
889      // We don't support infinite available width, so we'll limit it to 32000 pixels.
890      if (availableSize.Width > 32000)
891        availableSize.Width = 32000;
892     
893      if (!canHorizontallyScroll && !availableSize.Width.IsClose(lastAvailableSize.Width))
894        ClearVisualLines();
895      lastAvailableSize = availableSize;
896     
897      foreach (UIElement layer in layers) {
898        layer.Measure(availableSize);
899      }
900      MeasureInlineObjects();
901     
902      InvalidateVisual(); // = InvalidateArrange+InvalidateRender
903     
904      double maxWidth;
905      if (document == null) {
906        // no document -> create empty list of lines
907        allVisualLines = new List<VisualLine>();
908        visibleVisualLines = allVisualLines.AsReadOnly();
909        maxWidth = 0;
910      } else {
911        inMeasure = true;
912        try {
913          maxWidth = CreateAndMeasureVisualLines(availableSize);
914        } finally {
915          inMeasure = false;
916        }
917      }
918     
919      // remove inline objects only at the end, so that inline objects that were re-used are not removed from the editor
920      RemoveInlineObjectsNow();
921     
922      maxWidth += AdditionalHorizontalScrollAmount;
923      double heightTreeHeight = this.DocumentHeight;
924      TextEditorOptions options = this.Options;
925      if (options.AllowScrollBelowDocument) {
926        if (!double.IsInfinity(scrollViewport.Height)) {
927          heightTreeHeight = Math.Max(heightTreeHeight, Math.Min(heightTreeHeight - 50, scrollOffset.Y) + scrollViewport.Height);
928        }
929      }
930     
931      textLayer.SetVisualLines(visibleVisualLines);
932     
933      SetScrollData(availableSize,
934                    new Size(maxWidth, heightTreeHeight),
935                    scrollOffset);
936      if (VisualLinesChanged != null)
937        VisualLinesChanged(this, EventArgs.Empty);
938     
939      return new Size(Math.Min(availableSize.Width, maxWidth), Math.Min(availableSize.Height, heightTreeHeight));
940    }
941   
942    /// <summary>
943    /// Build all VisualLines in the visible range.
944    /// </summary>
945    /// <returns>Width the longest line</returns>
946    double CreateAndMeasureVisualLines(Size availableSize)
947    {
948      TextRunProperties globalTextRunProperties = CreateGlobalTextRunProperties();
949      VisualLineTextParagraphProperties paragraphProperties = CreateParagraphProperties(globalTextRunProperties);
950     
951      Debug.WriteLine("Measure availableSize=" + availableSize + ", scrollOffset=" + scrollOffset);
952      var firstLineInView = heightTree.GetLineByVisualPosition(scrollOffset.Y);
953     
954      // number of pixels clipped from the first visual line(s)
955      clippedPixelsOnTop = scrollOffset.Y - heightTree.GetVisualPosition(firstLineInView);
956      // clippedPixelsOnTop should be >= 0, except for floating point inaccurracy.
957      Debug.Assert(clippedPixelsOnTop >= -ExtensionMethods.Epsilon);
958     
959      newVisualLines = new List<VisualLine>();
960     
961      if (VisualLineConstructionStarting != null)
962        VisualLineConstructionStarting(this, new VisualLineConstructionStartEventArgs(firstLineInView));
963     
964      var elementGeneratorsArray = elementGenerators.ToArray();
965      var lineTransformersArray = lineTransformers.ToArray();
966      var nextLine = firstLineInView;
967      double maxWidth = 0;
968      double yPos = -clippedPixelsOnTop;
969      while (yPos < availableSize.Height && nextLine != null) {
970        VisualLine visualLine = GetVisualLine(nextLine.LineNumber);
971        if (visualLine == null) {
972          visualLine = BuildVisualLine(nextLine,
973                                       globalTextRunProperties, paragraphProperties,
974                                       elementGeneratorsArray, lineTransformersArray,
975                                       availableSize);
976        }
977       
978        visualLine.VisualTop = scrollOffset.Y + yPos;
979       
980        nextLine = visualLine.LastDocumentLine.NextLine;
981       
982        yPos += visualLine.Height;
983       
984        foreach (TextLine textLine in visualLine.TextLines) {
985          if (textLine.WidthIncludingTrailingWhitespace > maxWidth)
986            maxWidth = textLine.WidthIncludingTrailingWhitespace;
987        }
988       
989        newVisualLines.Add(visualLine);
990      }
991     
992      foreach (VisualLine line in allVisualLines) {
993        Debug.Assert(line.IsDisposed == false);
994        if (!newVisualLines.Contains(line))
995          DisposeVisualLine(line);
996      }
997     
998      allVisualLines = newVisualLines;
999      // visibleVisualLines = readonly copy of visual lines
1000      visibleVisualLines = new ReadOnlyCollection<VisualLine>(newVisualLines.ToArray());
1001      newVisualLines = null;
1002     
1003      if (allVisualLines.Any(line => line.IsDisposed)) {
1004        throw new InvalidOperationException("A visual line was disposed even though it is still in use.\n" +
1005                                            "This can happen when Redraw() is called during measure for lines " +
1006                                            "that are already constructed.");
1007      }
1008      return maxWidth;
1009    }
1010    #endregion
1011   
1012    #region BuildVisualLine
1013    TextFormatter formatter;
1014    internal TextViewCachedElements cachedElements;
1015   
1016    TextRunProperties CreateGlobalTextRunProperties()
1017    {
1018      var p = new GlobalTextRunProperties();
1019      p.typeface = this.CreateTypeface();
1020      p.fontRenderingEmSize = FontSize;
1021      p.foregroundBrush = (Brush)GetValue(Control.ForegroundProperty);
1022      ExtensionMethods.CheckIsFrozen(p.foregroundBrush);
1023      p.cultureInfo = CultureInfo.CurrentCulture;
1024      return p;
1025    }
1026   
1027    VisualLineTextParagraphProperties CreateParagraphProperties(TextRunProperties defaultTextRunProperties)
1028    {
1029      return new VisualLineTextParagraphProperties {
1030        defaultTextRunProperties = defaultTextRunProperties,
1031        textWrapping = canHorizontallyScroll ? TextWrapping.NoWrap : TextWrapping.Wrap,
1032        tabSize = Options.IndentationSize * WideSpaceWidth
1033      };
1034    }
1035   
1036    VisualLine BuildVisualLine(DocumentLine documentLine,
1037                               TextRunProperties globalTextRunProperties,
1038                               VisualLineTextParagraphProperties paragraphProperties,
1039                               VisualLineElementGenerator[] elementGeneratorsArray,
1040                               IVisualLineTransformer[] lineTransformersArray,
1041                               Size availableSize)
1042    {
1043      if (heightTree.GetIsCollapsed(documentLine.LineNumber))
1044        throw new InvalidOperationException("Trying to build visual line from collapsed line");
1045     
1046      //Debug.WriteLine("Building line " + documentLine.LineNumber);
1047     
1048      VisualLine visualLine = new VisualLine(this, documentLine);
1049      VisualLineTextSource textSource = new VisualLineTextSource(visualLine) {
1050        Document = document,
1051        GlobalTextRunProperties = globalTextRunProperties,
1052        TextView = this
1053      };
1054     
1055      visualLine.ConstructVisualElements(textSource, elementGeneratorsArray);
1056     
1057      if (visualLine.FirstDocumentLine != visualLine.LastDocumentLine) {
1058        // Check whether the lines are collapsed correctly:
1059        double firstLinePos = heightTree.GetVisualPosition(visualLine.FirstDocumentLine.NextLine);
1060        double lastLinePos = heightTree.GetVisualPosition(visualLine.LastDocumentLine.NextLine ?? visualLine.LastDocumentLine);
1061        if (!firstLinePos.IsClose(lastLinePos)) {
1062          for (int i = visualLine.FirstDocumentLine.LineNumber + 1; i <= visualLine.LastDocumentLine.LineNumber; i++) {
1063            if (!heightTree.GetIsCollapsed(i))
1064              throw new InvalidOperationException("Line " + i + " was skipped by a VisualLineElementGenerator, but it is not collapsed.");
1065          }
1066          throw new InvalidOperationException("All lines collapsed but visual pos different - height tree inconsistency?");
1067        }
1068      }
1069     
1070      visualLine.RunTransformers(textSource, lineTransformersArray);
1071     
1072      // now construct textLines:
1073      int textOffset = 0;
1074      TextLineBreak lastLineBreak = null;
1075      var textLines = new List<TextLine>();
1076      paragraphProperties.indent = 0;
1077      paragraphProperties.firstLineInParagraph = true;
1078      while (textOffset <= visualLine.VisualLengthWithEndOfLineMarker) {
1079        TextLine textLine = formatter.FormatLine(
1080          textSource,
1081          textOffset,
1082          availableSize.Width,
1083          paragraphProperties,
1084          lastLineBreak
1085        );
1086        textLines.Add(textLine);
1087        textOffset += textLine.Length;
1088       
1089        // exit loop so that we don't do the indentation calculation if there's only a single line
1090        if (textOffset >= visualLine.VisualLengthWithEndOfLineMarker)
1091          break;
1092       
1093        if (paragraphProperties.firstLineInParagraph) {
1094          paragraphProperties.firstLineInParagraph = false;
1095         
1096          TextEditorOptions options = this.Options;
1097          double indentation = 0;
1098          if (options.InheritWordWrapIndentation) {
1099            // determine indentation for next line:
1100            int indentVisualColumn = GetIndentationVisualColumn(visualLine);
1101            if (indentVisualColumn > 0 && indentVisualColumn < textOffset) {
1102              indentation = textLine.GetDistanceFromCharacterHit(new CharacterHit(indentVisualColumn, 0));
1103            }
1104          }
1105          indentation += options.WordWrapIndentation;
1106          // apply the calculated indentation unless it's more than half of the text editor size:
1107          if (indentation > 0 && indentation * 2 < availableSize.Width)
1108            paragraphProperties.indent = indentation;
1109        }
1110        lastLineBreak = textLine.GetTextLineBreak();
1111      }
1112      visualLine.SetTextLines(textLines);
1113      heightTree.SetHeight(visualLine.FirstDocumentLine, visualLine.Height);
1114      return visualLine;
1115    }
1116   
1117    static int GetIndentationVisualColumn(VisualLine visualLine)
1118    {
1119      if (visualLine.Elements.Count == 0)
1120        return 0;
1121      int column = 0;
1122      int elementIndex = 0;
1123      VisualLineElement element = visualLine.Elements[elementIndex];
1124      while (element.IsWhitespace(column)) {
1125        column++;
1126        if (column == element.VisualColumn + element.VisualLength) {
1127          elementIndex++;
1128          if (elementIndex == visualLine.Elements.Count)
1129            break;
1130          element = visualLine.Elements[elementIndex];
1131        }
1132      }
1133      return column;
1134    }
1135    #endregion
1136   
1137    #region Arrange
1138    /// <summary>
1139    /// Arrange implementation.
1140    /// </summary>
1141    protected override Size ArrangeOverride(Size finalSize)
1142    {
1143      EnsureVisualLines();
1144     
1145      foreach (UIElement layer in layers) {
1146        layer.Arrange(new Rect(new Point(0, 0), finalSize));
1147      }
1148     
1149      if (document == null || allVisualLines.Count == 0)
1150        return finalSize;
1151     
1152      // validate scroll position
1153      Vector newScrollOffset = scrollOffset;
1154      if (scrollOffset.X + finalSize.Width > scrollExtent.Width) {
1155        newScrollOffset.X = Math.Max(0, scrollExtent.Width - finalSize.Width);
1156      }
1157      if (scrollOffset.Y + finalSize.Height > scrollExtent.Height) {
1158        newScrollOffset.Y = Math.Max(0, scrollExtent.Height - finalSize.Height);
1159      }
1160      if (SetScrollData(scrollViewport, scrollExtent, newScrollOffset))
1161        InvalidateMeasure(DispatcherPriority.Normal);
1162     
1163      //Debug.WriteLine("Arrange finalSize=" + finalSize + ", scrollOffset=" + scrollOffset);
1164     
1165//      double maxWidth = 0;
1166     
1167      if (visibleVisualLines != null) {
1168        Point pos = new Point(-scrollOffset.X, -clippedPixelsOnTop);
1169        foreach (VisualLine visualLine in visibleVisualLines) {
1170          int offset = 0;
1171          foreach (TextLine textLine in visualLine.TextLines) {
1172            foreach (var span in textLine.GetTextRunSpans()) {
1173              InlineObjectRun inline = span.Value as InlineObjectRun;
1174              if (inline != null && inline.VisualLine != null) {
1175                Debug.Assert(inlineObjects.Contains(inline));
1176                double distance = textLine.GetDistanceFromCharacterHit(new CharacterHit(offset, 0));
1177                inline.Element.Arrange(new Rect(new Point(pos.X + distance, pos.Y), inline.Element.DesiredSize));
1178              }
1179              offset += span.Length;
1180            }
1181            pos.Y += textLine.Height;
1182          }
1183        }
1184      }
1185      InvalidateCursorIfMouseWithinTextView();
1186     
1187      return finalSize;
1188    }
1189    #endregion
1190   
1191    #region Render
1192    readonly ObserveAddRemoveCollection<IBackgroundRenderer> backgroundRenderers;
1193   
1194    /// <summary>
1195    /// Gets the list of background renderers.
1196    /// </summary>
1197    public IList<IBackgroundRenderer> BackgroundRenderers {
1198      get { return backgroundRenderers; }
1199    }
1200   
1201    void BackgroundRenderer_Added(IBackgroundRenderer renderer)
1202    {
1203      ConnectToTextView(renderer);
1204      InvalidateLayer(renderer.Layer);
1205    }
1206   
1207    void BackgroundRenderer_Removed(IBackgroundRenderer renderer)
1208    {
1209      DisconnectFromTextView(renderer);
1210      InvalidateLayer(renderer.Layer);
1211    }
1212   
1213    /// <inheritdoc/>
1214    protected override void OnRender(DrawingContext drawingContext)
1215    {
1216      RenderBackground(drawingContext, KnownLayer.Background);
1217      foreach (var line in visibleVisualLines) {
1218        Brush currentBrush = null;
1219        int startVC = 0;
1220        int length = 0;
1221        foreach (var element in line.Elements) {
1222          if (currentBrush == null || !currentBrush.Equals(element.BackgroundBrush)) {
1223            if (currentBrush != null) {
1224              BackgroundGeometryBuilder builder = new BackgroundGeometryBuilder();
1225              builder.AlignToWholePixels = true;
1226              builder.CornerRadius = 3;
1227              foreach (var rect in BackgroundGeometryBuilder.GetRectsFromVisualSegment(this, line, startVC, startVC + length))
1228                builder.AddRectangle(this, rect);
1229              Geometry geometry = builder.CreateGeometry();
1230              if (geometry != null) {
1231                drawingContext.DrawGeometry(currentBrush, null, geometry);
1232              }
1233            }
1234            startVC = element.VisualColumn;
1235            length = element.DocumentLength;
1236            currentBrush = element.BackgroundBrush;
1237          } else {
1238            length += element.VisualLength;
1239          }
1240        }
1241        if (currentBrush != null) {
1242          BackgroundGeometryBuilder builder = new BackgroundGeometryBuilder();
1243          builder.AlignToWholePixels = true;
1244          builder.CornerRadius = 3;
1245          foreach (var rect in BackgroundGeometryBuilder.GetRectsFromVisualSegment(this, line, startVC, startVC + length))
1246            builder.AddRectangle(this, rect);
1247          Geometry geometry = builder.CreateGeometry();
1248          if (geometry != null) {
1249            drawingContext.DrawGeometry(currentBrush, null, geometry);
1250          }
1251        }
1252      }
1253    }
1254   
1255    internal void RenderBackground(DrawingContext drawingContext, KnownLayer layer)
1256    {
1257      foreach (IBackgroundRenderer bg in backgroundRenderers) {
1258        if (bg.Layer == layer) {
1259          bg.Draw(this, drawingContext);
1260        }
1261      }
1262    }
1263   
1264    internal void ArrangeTextLayer(IList<VisualLineDrawingVisual> visuals)
1265    {
1266      Point pos = new Point(-scrollOffset.X, -clippedPixelsOnTop);
1267      foreach (VisualLineDrawingVisual visual in visuals) {
1268        TranslateTransform t = visual.Transform as TranslateTransform;
1269        if (t == null || t.X != pos.X || t.Y != pos.Y) {
1270          visual.Transform = new TranslateTransform(pos.X, pos.Y);
1271          visual.Transform.Freeze();
1272        }
1273        pos.Y += visual.Height;
1274      }
1275    }
1276    #endregion
1277   
1278    #region IScrollInfo implementation
1279    /// <summary>
1280    /// Size of the document, in pixels.
1281    /// </summary>
1282    Size scrollExtent;
1283   
1284    /// <summary>
1285    /// Offset of the scroll position.
1286    /// </summary>
1287    Vector scrollOffset;
1288   
1289    /// <summary>
1290    /// Size of the viewport.
1291    /// </summary>
1292    Size scrollViewport;
1293   
1294    void ClearScrollData()
1295    {
1296      SetScrollData(new Size(), new Size(), new Vector());
1297    }
1298   
1299    bool SetScrollData(Size viewport, Size extent, Vector offset)
1300    {
1301      if (!(viewport.IsClose(this.scrollViewport)
1302            && extent.IsClose(this.scrollExtent)
1303            && offset.IsClose(this.scrollOffset)))
1304      {
1305        this.scrollViewport = viewport;
1306        this.scrollExtent = extent;
1307        SetScrollOffset(offset);
1308        this.OnScrollChange();
1309        return true;
1310      }
1311      return false;
1312    }
1313   
1314    void OnScrollChange()
1315    {
1316      ScrollViewer scrollOwner = ((IScrollInfo)this).ScrollOwner;
1317      if (scrollOwner != null) {
1318        scrollOwner.InvalidateScrollInfo();
1319      }
1320    }
1321   
1322    bool canVerticallyScroll;
1323    bool IScrollInfo.CanVerticallyScroll {
1324      get { return canVerticallyScroll; }
1325      set {
1326        if (canVerticallyScroll != value) {
1327          canVerticallyScroll = value;
1328          InvalidateMeasure(DispatcherPriority.Normal);
1329        }
1330      }
1331    }
1332    bool canHorizontallyScroll;
1333    bool IScrollInfo.CanHorizontallyScroll {
1334      get { return canHorizontallyScroll; }
1335      set {
1336        if (canHorizontallyScroll != value) {
1337          canHorizontallyScroll = value;
1338          ClearVisualLines();
1339          InvalidateMeasure(DispatcherPriority.Normal);
1340        }
1341      }
1342    }
1343   
1344    double IScrollInfo.ExtentWidth {
1345      get { return scrollExtent.Width; }
1346    }
1347   
1348    double IScrollInfo.ExtentHeight {
1349      get { return scrollExtent.Height; }
1350    }
1351   
1352    double IScrollInfo.ViewportWidth {
1353      get { return scrollViewport.Width; }
1354    }
1355   
1356    double IScrollInfo.ViewportHeight {
1357      get { return scrollViewport.Height; }
1358    }
1359   
1360    /// <summary>
1361    /// Gets the horizontal scroll offset.
1362    /// </summary>
1363    public double HorizontalOffset {
1364      get { return scrollOffset.X; }
1365    }
1366   
1367    /// <summary>
1368    /// Gets the vertical scroll offset.
1369    /// </summary>
1370    public double VerticalOffset {
1371      get { return scrollOffset.Y; }
1372    }
1373   
1374    /// <summary>
1375    /// Gets the scroll offset;
1376    /// </summary>
1377    public Vector ScrollOffset {
1378      get { return scrollOffset; }
1379    }
1380   
1381    /// <summary>
1382    /// Occurs when the scroll offset has changed.
1383    /// </summary>
1384    public event EventHandler ScrollOffsetChanged;
1385   
1386    void SetScrollOffset(Vector vector)
1387    {
1388      if (!canHorizontallyScroll)
1389        vector.X = 0;
1390      if (!canVerticallyScroll)
1391        vector.Y = 0;
1392     
1393      if (!scrollOffset.IsClose(vector)) {
1394        scrollOffset = vector;
1395        if (ScrollOffsetChanged != null)
1396          ScrollOffsetChanged(this, EventArgs.Empty);
1397      }
1398    }
1399   
1400    ScrollViewer IScrollInfo.ScrollOwner { get; set; }
1401   
1402    void IScrollInfo.LineUp()
1403    {
1404      ((IScrollInfo)this).SetVerticalOffset(scrollOffset.Y - DefaultLineHeight);
1405    }
1406   
1407    void IScrollInfo.LineDown()
1408    {
1409      ((IScrollInfo)this).SetVerticalOffset(scrollOffset.Y + DefaultLineHeight);
1410    }
1411   
1412    void IScrollInfo.LineLeft()
1413    {
1414      ((IScrollInfo)this).SetHorizontalOffset(scrollOffset.X - WideSpaceWidth);
1415    }
1416   
1417    void IScrollInfo.LineRight()
1418    {
1419      ((IScrollInfo)this).SetHorizontalOffset(scrollOffset.X + WideSpaceWidth);
1420    }
1421   
1422    void IScrollInfo.PageUp()
1423    {
1424      ((IScrollInfo)this).SetVerticalOffset(scrollOffset.Y - scrollViewport.Height);
1425    }
1426   
1427    void IScrollInfo.PageDown()
1428    {
1429      ((IScrollInfo)this).SetVerticalOffset(scrollOffset.Y + scrollViewport.Height);
1430    }
1431   
1432    void IScrollInfo.PageLeft()
1433    {
1434      ((IScrollInfo)this).SetHorizontalOffset(scrollOffset.X - scrollViewport.Width);
1435    }
1436   
1437    void IScrollInfo.PageRight()
1438    {
1439      ((IScrollInfo)this).SetHorizontalOffset(scrollOffset.X + scrollViewport.Width);
1440    }
1441   
1442    void IScrollInfo.MouseWheelUp()
1443    {
1444      ((IScrollInfo)this).SetVerticalOffset(
1445        scrollOffset.Y - (SystemParameters.WheelScrollLines * DefaultLineHeight));
1446      OnScrollChange();
1447    }
1448   
1449    void IScrollInfo.MouseWheelDown()
1450    {
1451      ((IScrollInfo)this).SetVerticalOffset(
1452        scrollOffset.Y + (SystemParameters.WheelScrollLines * DefaultLineHeight));
1453      OnScrollChange();
1454    }
1455   
1456    void IScrollInfo.MouseWheelLeft()
1457    {
1458      ((IScrollInfo)this).SetHorizontalOffset(
1459        scrollOffset.X - (SystemParameters.WheelScrollLines * WideSpaceWidth));
1460      OnScrollChange();
1461    }
1462   
1463    void IScrollInfo.MouseWheelRight()
1464    {
1465      ((IScrollInfo)this).SetHorizontalOffset(
1466        scrollOffset.X + (SystemParameters.WheelScrollLines * WideSpaceWidth));
1467      OnScrollChange();
1468    }
1469   
1470    bool defaultTextMetricsValid;
1471    double wideSpaceWidth; // Width of an 'x'. Used as basis for the tab width, and for scrolling.
1472    double defaultLineHeight; // Height of a line containing 'x'. Used for scrolling.
1473    double defaultBaseline; // Baseline of a line containing 'x'. Used for TextTop/TextBottom calculation.
1474   
1475    /// <summary>
1476    /// Gets the width of a 'wide space' (the space width used for calculating the tab size).
1477    /// </summary>
1478    /// <remarks>
1479    /// This is the width of an 'x' in the current font.
1480    /// We do not measure the width of an actual space as that would lead to tiny tabs in
1481    /// some proportional fonts.
1482    /// For monospaced fonts, this property will return the expected value, as 'x' and ' ' have the same width.
1483    /// </remarks>
1484    public double WideSpaceWidth {
1485      get {
1486        CalculateDefaultTextMetrics();
1487        return wideSpaceWidth;
1488      }
1489    }
1490   
1491    /// <summary>
1492    /// Gets the default line height. This is the height of an empty line or a line containing regular text.
1493    /// Lines that include formatted text or custom UI elements may have a different line height.
1494    /// </summary>
1495    public double DefaultLineHeight {
1496      get {
1497        CalculateDefaultTextMetrics();
1498        return defaultLineHeight;
1499      }
1500    }
1501   
1502    /// <summary>
1503    /// Gets the default baseline position. This is the difference between <see cref="VisualYPosition.TextTop"/>
1504    /// and <see cref="VisualYPosition.Baseline"/> for a line containing regular text.
1505    /// Lines that include formatted text or custom UI elements may have a different baseline.
1506    /// </summary>
1507    public double DefaultBaseline {
1508      get {
1509        CalculateDefaultTextMetrics();
1510        return defaultBaseline;
1511      }
1512    }
1513   
1514    void InvalidateDefaultTextMetrics()
1515    {
1516      defaultTextMetricsValid = false;
1517      if (heightTree != null) {
1518        // calculate immediately so that height tree gets updated
1519        CalculateDefaultTextMetrics();
1520      }
1521    }
1522   
1523    void CalculateDefaultTextMetrics()
1524    {
1525      if (defaultTextMetricsValid)
1526        return;
1527      defaultTextMetricsValid = true;
1528      if (formatter != null) {
1529        var textRunProperties = CreateGlobalTextRunProperties();
1530        using (var line = formatter.FormatLine(
1531          new SimpleTextSource("x", textRunProperties),
1532          0, 32000,
1533          new VisualLineTextParagraphProperties { defaultTextRunProperties = textRunProperties },
1534          null))
1535        {
1536          wideSpaceWidth = Math.Max(1, line.WidthIncludingTrailingWhitespace);
1537          defaultBaseline = Math.Max(1, line.Baseline);
1538          defaultLineHeight = Math.Max(1, line.Height);
1539        }
1540      } else {
1541        wideSpaceWidth = FontSize / 2;
1542        defaultBaseline = FontSize;
1543        defaultLineHeight = FontSize + 3;
1544      }
1545      // Update heightTree.DefaultLineHeight, if a document is loaded.
1546      if (heightTree != null)
1547        heightTree.DefaultLineHeight = defaultLineHeight;
1548    }
1549   
1550    static double ValidateVisualOffset(double offset)
1551    {
1552      if (double.IsNaN(offset))
1553        throw new ArgumentException("offset must not be NaN");
1554      if (offset < 0)
1555        return 0;
1556      else
1557        return offset;
1558    }
1559   
1560    void IScrollInfo.SetHorizontalOffset(double offset)
1561    {
1562      offset = ValidateVisualOffset(offset);
1563      if (!scrollOffset.X.IsClose(offset)) {
1564        SetScrollOffset(new Vector(offset, scrollOffset.Y));
1565        InvalidateVisual();
1566        textLayer.InvalidateVisual();
1567      }
1568    }
1569   
1570    void IScrollInfo.SetVerticalOffset(double offset)
1571    {
1572      offset = ValidateVisualOffset(offset);
1573      if (!scrollOffset.Y.IsClose(offset)) {
1574        SetScrollOffset(new Vector(scrollOffset.X, offset));
1575        InvalidateMeasure(DispatcherPriority.Normal);
1576      }
1577    }
1578   
1579    Rect IScrollInfo.MakeVisible(Visual visual, Rect rectangle)
1580    {
1581      if (rectangle.IsEmpty || visual == null || visual == this || !this.IsAncestorOf(visual)) {
1582        return Rect.Empty;
1583      }
1584      // Convert rectangle into our coordinate space.
1585      GeneralTransform childTransform = visual.TransformToAncestor(this);
1586      rectangle = childTransform.TransformBounds(rectangle);
1587     
1588      MakeVisible(Rect.Offset(rectangle, scrollOffset));
1589     
1590      return rectangle;
1591    }
1592   
1593    /// <summary>
1594    /// Scrolls the text view so that the specified rectangle gets visible.
1595    /// </summary>
1596    public void MakeVisible(Rect rectangle)
1597    {
1598      Rect visibleRectangle = new Rect(scrollOffset.X, scrollOffset.Y,
1599                                       scrollViewport.Width, scrollViewport.Height);
1600      Vector newScrollOffset = scrollOffset;
1601      if (rectangle.Left < visibleRectangle.Left) {
1602        if (rectangle.Right > visibleRectangle.Right) {
1603          newScrollOffset.X = rectangle.Left + rectangle.Width / 2;
1604        } else {
1605          newScrollOffset.X = rectangle.Left;
1606        }
1607      } else if (rectangle.Right > visibleRectangle.Right) {
1608        newScrollOffset.X = rectangle.Right - scrollViewport.Width;
1609      }
1610      if (rectangle.Top < visibleRectangle.Top) {
1611        if (rectangle.Bottom > visibleRectangle.Bottom) {
1612          newScrollOffset.Y = rectangle.Top + rectangle.Height / 2;
1613        } else {
1614          newScrollOffset.Y = rectangle.Top;
1615        }
1616      } else if (rectangle.Bottom > visibleRectangle.Bottom) {
1617        newScrollOffset.Y = rectangle.Bottom - scrollViewport.Height;
1618      }
1619      newScrollOffset.X = ValidateVisualOffset(newScrollOffset.X);
1620      newScrollOffset.Y = ValidateVisualOffset(newScrollOffset.Y);
1621      if (!scrollOffset.IsClose(newScrollOffset)) {
1622        SetScrollOffset(newScrollOffset);
1623        this.OnScrollChange();
1624        InvalidateMeasure(DispatcherPriority.Normal);
1625      }
1626    }
1627    #endregion
1628   
1629    #region Visual element mouse handling
1630    /// <inheritdoc/>
1631    protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
1632    {
1633      // accept clicks even where the text area draws no background
1634      return new PointHitTestResult(this, hitTestParameters.HitPoint);
1635    }
1636   
1637    [ThreadStatic] static bool invalidCursor;
1638   
1639    /// <summary>
1640    /// Updates the mouse cursor by calling <see cref="Mouse.UpdateCursor"/>, but with background priority.
1641    /// </summary>
1642    public static void InvalidateCursor()
1643    {
1644      if (!invalidCursor) {
1645        invalidCursor = true;
1646        Dispatcher.CurrentDispatcher.BeginInvoke(
1647          DispatcherPriority.Background, // fixes issue #288
1648          new Action(
1649            delegate {
1650              invalidCursor = false;
1651              Mouse.UpdateCursor();
1652            }));
1653      }
1654    }
1655   
1656    internal void InvalidateCursorIfMouseWithinTextView()
1657    {
1658      // Don't unnecessarily call Mouse.UpdateCursor() if the mouse is outside the text view.
1659      // Unnecessary updates may cause the mouse pointer to flicker
1660      // (e.g. if it is over a window border, it blinks between Resize and Normal)
1661      if (this.IsMouseOver)
1662        InvalidateCursor();
1663    }
1664   
1665    /// <inheritdoc/>
1666    protected override void OnQueryCursor(QueryCursorEventArgs e)
1667    {
1668      VisualLineElement element = GetVisualLineElementFromPosition(e.GetPosition(this) + scrollOffset);
1669      if (element != null) {
1670        element.OnQueryCursor(e);
1671      }
1672    }
1673   
1674    /// <inheritdoc/>
1675    protected override void OnMouseDown(MouseButtonEventArgs e)
1676    {
1677      base.OnMouseDown(e);
1678      if (!e.Handled) {
1679        EnsureVisualLines();
1680        VisualLineElement element = GetVisualLineElementFromPosition(e.GetPosition(this) + scrollOffset);
1681        if (element != null) {
1682          element.OnMouseDown(e);
1683        }
1684      }
1685    }
1686   
1687    /// <inheritdoc/>
1688    protected override void OnMouseUp(MouseButtonEventArgs e)
1689    {
1690      base.OnMouseUp(e);
1691      if (!e.Handled) {
1692        EnsureVisualLines();
1693        VisualLineElement element = GetVisualLineElementFromPosition(e.GetPosition(this) + scrollOffset);
1694        if (element != null) {
1695          element.OnMouseUp(e);
1696        }
1697      }
1698    }
1699    #endregion
1700   
1701    #region Getting elements from Visual Position
1702    /// <summary>
1703    /// Gets the visual line at the specified document position (relative to start of document).
1704    /// Returns null if there is no visual line for the position (e.g. the position is outside the visible
1705    /// text area).
1706    /// </summary>
1707    public VisualLine GetVisualLineFromVisualTop(double visualTop)
1708    {
1709      // TODO: change this method to also work outside the visible range -
1710      // required to make GetPosition work as expected!
1711      EnsureVisualLines();
1712      foreach (VisualLine vl in this.VisualLines) {
1713        if (visualTop < vl.VisualTop)
1714          continue;
1715        if (visualTop < vl.VisualTop + vl.Height)
1716          return vl;
1717      }
1718      return null;
1719    }
1720   
1721    /// <summary>
1722    /// Gets the visual top position (relative to start of document) from a document line number.
1723    /// </summary>
1724    public double GetVisualTopByDocumentLine(int line)
1725    {
1726      VerifyAccess();
1727      if (heightTree == null)
1728        throw ThrowUtil.NoDocumentAssigned();
1729      return heightTree.GetVisualPosition(heightTree.GetLineByNumber(line));
1730    }
1731   
1732    VisualLineElement GetVisualLineElementFromPosition(Point visualPosition)
1733    {
1734      VisualLine vl = GetVisualLineFromVisualTop(visualPosition.Y);
1735      if (vl != null) {
1736        int column = vl.GetVisualColumnFloor(visualPosition);
1737//        Debug.WriteLine(vl.FirstDocumentLine.LineNumber + " vc " + column);
1738        foreach (VisualLineElement element in vl.Elements) {
1739          if (element.VisualColumn + element.VisualLength <= column)
1740            continue;
1741          return element;
1742        }
1743      }
1744      return null;
1745    }
1746    #endregion
1747   
1748    #region Visual Position <-> TextViewPosition
1749    /// <summary>
1750    /// Gets the visual position from a text view position.
1751    /// </summary>
1752    /// <param name="position">The text view position.</param>
1753    /// <param name="yPositionMode">The mode how to retrieve the Y position.</param>
1754    /// <returns>The position in WPF device-independent pixels relative
1755    /// to the top left corner of the document.</returns>
1756    public Point GetVisualPosition(TextViewPosition position, VisualYPosition yPositionMode)
1757    {
1758      VerifyAccess();
1759      if (this.Document == null)
1760        throw ThrowUtil.NoDocumentAssigned();
1761      DocumentLine documentLine = this.Document.GetLineByNumber(position.Line);
1762      VisualLine visualLine = GetOrConstructVisualLine(documentLine);
1763      int visualColumn = position.VisualColumn;
1764      if (visualColumn < 0) {
1765        int offset = documentLine.Offset + position.Column - 1;
1766        visualColumn = visualLine.GetVisualColumn(offset - visualLine.FirstDocumentLine.Offset);
1767      }
1768      return visualLine.GetVisualPosition(visualColumn, position.IsAtEndOfLine, yPositionMode);
1769    }
1770   
1771    /// <summary>
1772    /// Gets the text view position from the specified visual position.
1773    /// If the position is within a character, it is rounded to the next character boundary.
1774    /// </summary>
1775    /// <param name="visualPosition">The position in WPF device-independent pixels relative
1776    /// to the top left corner of the document.</param>
1777    /// <returns>The logical position, or null if the position is outside the document.</returns>
1778    public TextViewPosition? GetPosition(Point visualPosition)
1779    {
1780      VerifyAccess();
1781      if (this.Document == null)
1782        throw ThrowUtil.NoDocumentAssigned();
1783      VisualLine line = GetVisualLineFromVisualTop(visualPosition.Y);
1784      if (line == null)
1785        return null;
1786      return line.GetTextViewPosition(visualPosition, Options.EnableVirtualSpace);
1787    }
1788   
1789    /// <summary>
1790    /// Gets the text view position from the specified visual position.
1791    /// If the position is inside a character, the position in front of the character is returned.
1792    /// </summary>
1793    /// <param name="visualPosition">The position in WPF device-independent pixels relative
1794    /// to the top left corner of the document.</param>
1795    /// <returns>The logical position, or null if the position is outside the document.</returns>
1796    public TextViewPosition? GetPositionFloor(Point visualPosition)
1797    {
1798      VerifyAccess();
1799      if (this.Document == null)
1800        throw ThrowUtil.NoDocumentAssigned();
1801      VisualLine line = GetVisualLineFromVisualTop(visualPosition.Y);
1802      if (line == null)
1803        return null;
1804      return line.GetTextViewPositionFloor(visualPosition, Options.EnableVirtualSpace);
1805    }
1806    #endregion
1807   
1808    #region Service Provider
1809    readonly ServiceContainer services = new ServiceContainer();
1810   
1811    /// <summary>
1812    /// Gets a service container used to associate services with the text view.
1813    /// </summary>
1814    /// <remarks>
1815    /// This container does not provide document services -
1816    /// use <c>TextView.GetService()</c> instead of <c>TextView.Services.GetService()</c> to ensure
1817    /// that document services can be found as well.
1818    /// </remarks>
1819    public ServiceContainer Services {
1820      get { return services; }
1821    }
1822   
1823    /// <summary>
1824    /// Retrieves a service from the text view.
1825    /// If the service is not found in the <see cref="Services"/> container,
1826    /// this method will also look for it in the current document's service provider.
1827    /// </summary>
1828    public virtual object GetService(Type serviceType)
1829    {
1830      object instance = services.GetService(serviceType);
1831      if (instance == null && document != null) {
1832        instance = document.ServiceProvider.GetService(serviceType);
1833      }
1834      return instance;
1835    }
1836   
1837    void ConnectToTextView(object obj)
1838    {
1839      ITextViewConnect c = obj as ITextViewConnect;
1840      if (c != null)
1841        c.AddToTextView(this);
1842    }
1843   
1844    void DisconnectFromTextView(object obj)
1845    {
1846      ITextViewConnect c = obj as ITextViewConnect;
1847      if (c != null)
1848        c.RemoveFromTextView(this);
1849    }
1850    #endregion
1851   
1852    #region MouseHover
1853    /// <summary>
1854    /// The PreviewMouseHover event.
1855    /// </summary>
1856    public static readonly RoutedEvent PreviewMouseHoverEvent =
1857      EventManager.RegisterRoutedEvent("PreviewMouseHover", RoutingStrategy.Tunnel,
1858                                       typeof(MouseEventHandler), typeof(TextView));
1859    /// <summary>
1860    /// The MouseHover event.
1861    /// </summary>
1862    public static readonly RoutedEvent MouseHoverEvent =
1863      EventManager.RegisterRoutedEvent("MouseHover", RoutingStrategy.Bubble,
1864                                       typeof(MouseEventHandler), typeof(TextView));
1865   
1866    /// <summary>
1867    /// The PreviewMouseHoverStopped event.
1868    /// </summary>
1869    public static readonly RoutedEvent PreviewMouseHoverStoppedEvent =
1870      EventManager.RegisterRoutedEvent("PreviewMouseHoverStopped", RoutingStrategy.Tunnel,
1871                                       typeof(MouseEventHandler), typeof(TextView));
1872    /// <summary>
1873    /// The MouseHoverStopped event.
1874    /// </summary>
1875    public static readonly RoutedEvent MouseHoverStoppedEvent =
1876      EventManager.RegisterRoutedEvent("MouseHoverStopped", RoutingStrategy.Bubble,
1877                                       typeof(MouseEventHandler), typeof(TextView));
1878   
1879   
1880    /// <summary>
1881    /// Occurs when the mouse has hovered over a fixed location for some time.
1882    /// </summary>
1883    public event MouseEventHandler PreviewMouseHover {
1884      add { AddHandler(PreviewMouseHoverEvent, value); }
1885      remove { RemoveHandler(PreviewMouseHoverEvent, value); }
1886    }
1887   
1888    /// <summary>
1889    /// Occurs when the mouse has hovered over a fixed location for some time.
1890    /// </summary>
1891    public event MouseEventHandler MouseHover {
1892      add { AddHandler(MouseHoverEvent, value); }
1893      remove { RemoveHandler(MouseHoverEvent, value); }
1894    }
1895   
1896    /// <summary>
1897    /// Occurs when the mouse had previously hovered but now started moving again.
1898    /// </summary>
1899    public event MouseEventHandler PreviewMouseHoverStopped {
1900      add { AddHandler(PreviewMouseHoverStoppedEvent, value); }
1901      remove { RemoveHandler(PreviewMouseHoverStoppedEvent, value); }
1902    }
1903   
1904    /// <summary>
1905    /// Occurs when the mouse had previously hovered but now started moving again.
1906    /// </summary>
1907    public event MouseEventHandler MouseHoverStopped {
1908      add { AddHandler(MouseHoverStoppedEvent, value); }
1909      remove { RemoveHandler(MouseHoverStoppedEvent, value); }
1910    }
1911   
1912    MouseHoverLogic hoverLogic;
1913   
1914    void RaiseHoverEventPair(MouseEventArgs e, RoutedEvent tunnelingEvent, RoutedEvent bubblingEvent)
1915    {
1916      var mouseDevice = e.MouseDevice;
1917      var stylusDevice = e.StylusDevice;
1918      int inputTime = Environment.TickCount;
1919      var args1 = new MouseEventArgs(mouseDevice, inputTime, stylusDevice) {
1920        RoutedEvent = tunnelingEvent,
1921        Source = this
1922      };
1923      RaiseEvent(args1);
1924      var args2 = new MouseEventArgs(mouseDevice, inputTime, stylusDevice) {
1925        RoutedEvent = bubblingEvent,
1926        Source = this,
1927        Handled = args1.Handled
1928      };
1929      RaiseEvent(args2);
1930    }
1931    #endregion
1932   
1933    /// <summary>
1934    /// Collapses lines for the purpose of scrolling. <see cref="DocumentLine"/>s marked as collapsed will be hidden
1935    /// and not used to start the generation of a <see cref="VisualLine"/>.
1936    /// </summary>
1937    /// <remarks>
1938    /// This method is meant for <see cref="VisualLineElementGenerator"/>s that cause <see cref="VisualLine"/>s to span
1939    /// multiple <see cref="DocumentLine"/>s. Do not call it without providing a corresponding
1940    /// <see cref="VisualLineElementGenerator"/>.
1941    /// If you want to create collapsible text sections, see <see cref="Folding.FoldingManager"/>.
1942    ///
1943    /// Note that if you want a VisualLineElement to span from line N to line M, then you need to collapse only the lines
1944    /// N+1 to M. Do not collapse line N itself.
1945    ///
1946    /// When you no longer need the section to be collapsed, call <see cref="CollapsedLineSection.Uncollapse()"/> on the
1947    /// <see cref="CollapsedLineSection"/> returned from this method.
1948    /// </remarks>
1949    public CollapsedLineSection CollapseLines(DocumentLine start, DocumentLine end)
1950    {
1951      VerifyAccess();
1952      if (heightTree == null)
1953        throw ThrowUtil.NoDocumentAssigned();
1954      return heightTree.CollapseText(start, end);
1955    }
1956   
1957    /// <summary>
1958    /// Gets the height of the document.
1959    /// </summary>
1960    public double DocumentHeight {
1961      get {
1962        // return 0 if there is no document = no heightTree
1963        return heightTree != null ? heightTree.TotalHeight : 0;
1964      }
1965    }
1966   
1967    /// <summary>
1968    /// Gets the document line at the specified visual position.
1969    /// </summary>
1970    public DocumentLine GetDocumentLineByVisualTop(double visualTop)
1971    {
1972      VerifyAccess();
1973      if (heightTree == null)
1974        throw ThrowUtil.NoDocumentAssigned();
1975      return heightTree.GetLineByVisualPosition(visualTop);
1976    }
1977   
1978    /// <inheritdoc/>
1979    protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
1980    {
1981      base.OnPropertyChanged(e);
1982      if (TextFormatterFactory.PropertyChangeAffectsTextFormatter(e.Property)) {
1983        // first, create the new text formatter:
1984        RecreateTextFormatter();
1985        // changing text formatter requires recreating the cached elements
1986        RecreateCachedElements();
1987        // and we need to re-measure the font metrics:
1988        InvalidateDefaultTextMetrics();
1989      } else if (e.Property == Control.ForegroundProperty
1990                 || e.Property == TextView.NonPrintableCharacterBrushProperty
1991                 || e.Property == TextView.LinkTextBackgroundBrushProperty
1992                 || e.Property == TextView.LinkTextForegroundBrushProperty)
1993      {
1994        // changing brushes requires recreating the cached elements
1995        RecreateCachedElements();
1996        Redraw();
1997      }
1998      if (e.Property == Control.FontFamilyProperty
1999          || e.Property == Control.FontSizeProperty
2000          || e.Property == Control.FontStretchProperty
2001          || e.Property == Control.FontStyleProperty
2002          || e.Property == Control.FontWeightProperty)
2003      {
2004        // changing font properties requires recreating cached elements
2005        RecreateCachedElements();
2006        // and we need to re-measure the font metrics:
2007        InvalidateDefaultTextMetrics();
2008        Redraw();
2009      }
2010      if (e.Property == ColumnRulerPenProperty) {
2011        columnRulerRenderer.SetRuler(this.Options.ColumnRulerPosition, this.ColumnRulerPen);
2012      }
2013      if (e.Property == CurrentLineBorderProperty) {
2014        currentLineHighlighRenderer.BorderPen = this.CurrentLineBorder;
2015      }
2016      if (e.Property == CurrentLineBackgroundProperty) {
2017        currentLineHighlighRenderer.BackgroundBrush = this.CurrentLineBackground;
2018      }
2019    }
2020   
2021    /// <summary>
2022    /// The pen used to draw the column ruler.
2023    /// <seealso cref="TextEditorOptions.ShowColumnRuler"/>
2024    /// </summary>
2025    public static readonly DependencyProperty ColumnRulerPenProperty =
2026      DependencyProperty.Register("ColumnRulerBrush", typeof(Pen), typeof(TextView),
2027                                  new FrameworkPropertyMetadata(CreateFrozenPen(Brushes.LightGray)));
2028   
2029    static Pen CreateFrozenPen(SolidColorBrush brush)
2030    {
2031      Pen pen = new Pen(brush, 1);
2032      pen.Freeze();
2033      return pen;
2034    }
2035   
2036    /// <summary>
2037    /// Gets/Sets the pen used to draw the column ruler.
2038    /// <seealso cref="TextEditorOptions.ShowColumnRuler"/>
2039    /// </summary>
2040    public Pen ColumnRulerPen {
2041      get { return (Pen)GetValue(ColumnRulerPenProperty); }
2042      set { SetValue(ColumnRulerPenProperty, value); }
2043    }
2044   
2045    /// <summary>
2046    /// The <see cref="CurrentLineBackground"/> property.
2047    /// </summary>
2048    public static readonly DependencyProperty CurrentLineBackgroundProperty =
2049      DependencyProperty.Register("CurrentLineBackground", typeof(Brush), typeof(TextView));
2050   
2051    /// <summary>
2052    /// Gets/Sets the background brush used by current line highlighter.
2053    /// </summary>
2054    public Brush CurrentLineBackground {
2055      get { return (Brush)GetValue(CurrentLineBackgroundProperty); }
2056      set { SetValue(CurrentLineBackgroundProperty, value); }
2057    }
2058   
2059    /// <summary>
2060    /// The <see cref="CurrentLineBorder"/> property.
2061    /// </summary>
2062    public static readonly DependencyProperty CurrentLineBorderProperty =
2063      DependencyProperty.Register("CurrentLineBorder", typeof(Pen), typeof(TextView));
2064   
2065    /// <summary>
2066    /// Gets/Sets the background brush used for the current line.
2067    /// </summary>
2068    public Pen CurrentLineBorder {
2069      get { return (Pen)GetValue(CurrentLineBorderProperty); }
2070      set { SetValue(CurrentLineBorderProperty, value); }
2071    }
2072   
2073    /// <summary>
2074    /// Gets/Sets highlighted line number.
2075    /// </summary>
2076    public int HighlightedLine {
2077      get { return this.currentLineHighlighRenderer.Line; }
2078      set { this.currentLineHighlighRenderer.Line = value; }
2079    }
2080  }
2081}
Note: See TracBrowser for help on using the repository browser.