Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/HeuristicLab.ExtLibs/HeuristicLab.AvalonEdit/5.0.1/AvalonEdit-5.0.1/Editing/TextArea.cs @ 17495

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

#2077: created branch and added first version

File size: 35.2 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.ObjectModel;
21using System.Collections.Specialized;
22using System.ComponentModel;
23using System.Diagnostics;
24using System.Linq;
25using System.Windows;
26using System.Windows.Controls;
27using System.Windows.Controls.Primitives;
28using System.Windows.Documents;
29using System.Windows.Input;
30using System.Windows.Media;
31using System.Windows.Threading;
32using ICSharpCode.AvalonEdit.Document;
33using ICSharpCode.AvalonEdit.Indentation;
34using ICSharpCode.AvalonEdit.Rendering;
35using ICSharpCode.AvalonEdit.Utils;
36using ICSharpCode.NRefactory;
37using ICSharpCode.NRefactory.Editor;
38
39namespace ICSharpCode.AvalonEdit.Editing
40{
41  /// <summary>
42  /// Control that wraps a TextView and adds support for user input and the caret.
43  /// </summary>
44  public class TextArea : Control, IScrollInfo, IWeakEventListener, ITextEditorComponent, IServiceProvider
45  {
46    internal readonly ImeSupport ime;
47   
48    #region Constructor
49    static TextArea()
50    {
51      DefaultStyleKeyProperty.OverrideMetadata(typeof(TextArea),
52                                               new FrameworkPropertyMetadata(typeof(TextArea)));
53      KeyboardNavigation.IsTabStopProperty.OverrideMetadata(
54        typeof(TextArea), new FrameworkPropertyMetadata(Boxes.True));
55      KeyboardNavigation.TabNavigationProperty.OverrideMetadata(
56        typeof(TextArea), new FrameworkPropertyMetadata(KeyboardNavigationMode.None));
57      FocusableProperty.OverrideMetadata(
58        typeof(TextArea), new FrameworkPropertyMetadata(Boxes.True));
59    }
60   
61    /// <summary>
62    /// Creates a new TextArea instance.
63    /// </summary>
64    public TextArea() : this(new TextView())
65    {
66    }
67   
68    /// <summary>
69    /// Creates a new TextArea instance.
70    /// </summary>
71    protected TextArea(TextView textView)
72    {
73      if (textView == null)
74        throw new ArgumentNullException("textView");
75      this.textView = textView;
76      this.Options = textView.Options;
77     
78      selection = emptySelection = new EmptySelection(this);
79     
80      textView.Services.AddService(typeof(TextArea), this);
81     
82      textView.LineTransformers.Add(new SelectionColorizer(this));
83      textView.InsertLayer(new SelectionLayer(this), KnownLayer.Selection, LayerInsertionPosition.Replace);
84     
85      caret = new Caret(this);
86      caret.PositionChanged += (sender, e) => RequestSelectionValidation();
87      caret.PositionChanged += CaretPositionChanged;
88      AttachTypingEvents();
89      ime = new ImeSupport(this);
90     
91      leftMargins.CollectionChanged += leftMargins_CollectionChanged;
92     
93      this.DefaultInputHandler = new TextAreaDefaultInputHandler(this);
94      this.ActiveInputHandler = this.DefaultInputHandler;
95    }
96    #endregion
97   
98    #region InputHandler management
99    /// <summary>
100    /// Gets the default input handler.
101    /// </summary>
102    /// <remarks><inheritdoc cref="ITextAreaInputHandler"/></remarks>
103    public TextAreaDefaultInputHandler DefaultInputHandler { get; private set; }
104   
105    ITextAreaInputHandler activeInputHandler;
106    bool isChangingInputHandler;
107   
108    /// <summary>
109    /// Gets/Sets the active input handler.
110    /// This property does not return currently active stacked input handlers. Setting this property detached all stacked input handlers.
111    /// </summary>
112    /// <remarks><inheritdoc cref="ITextAreaInputHandler"/></remarks>
113    public ITextAreaInputHandler ActiveInputHandler {
114      get { return activeInputHandler; }
115      set {
116        if (value != null && value.TextArea != this)
117          throw new ArgumentException("The input handler was created for a different text area than this one.");
118        if (isChangingInputHandler)
119          throw new InvalidOperationException("Cannot set ActiveInputHandler recursively");
120        if (activeInputHandler != value) {
121          isChangingInputHandler = true;
122          try {
123            // pop the whole stack
124            PopStackedInputHandler(stackedInputHandlers.LastOrDefault());
125            Debug.Assert(stackedInputHandlers.IsEmpty);
126           
127            if (activeInputHandler != null)
128              activeInputHandler.Detach();
129            activeInputHandler = value;
130            if (value != null)
131              value.Attach();
132          } finally {
133            isChangingInputHandler = false;
134          }
135          if (ActiveInputHandlerChanged != null)
136            ActiveInputHandlerChanged(this, EventArgs.Empty);
137        }
138      }
139    }
140   
141    /// <summary>
142    /// Occurs when the ActiveInputHandler property changes.
143    /// </summary>
144    public event EventHandler ActiveInputHandlerChanged;
145   
146    ImmutableStack<TextAreaStackedInputHandler> stackedInputHandlers = ImmutableStack<TextAreaStackedInputHandler>.Empty;
147   
148    /// <summary>
149    /// Gets the list of currently active stacked input handlers.
150    /// </summary>
151    /// <remarks><inheritdoc cref="ITextAreaInputHandler"/></remarks>
152    public ImmutableStack<TextAreaStackedInputHandler> StackedInputHandlers {
153      get { return stackedInputHandlers; }
154    }
155   
156    /// <summary>
157    /// Pushes an input handler onto the list of stacked input handlers.
158    /// </summary>
159    /// <remarks><inheritdoc cref="ITextAreaInputHandler"/></remarks>
160    public void PushStackedInputHandler(TextAreaStackedInputHandler inputHandler)
161    {
162      if (inputHandler == null)
163        throw new ArgumentNullException("inputHandler");
164      stackedInputHandlers = stackedInputHandlers.Push(inputHandler);
165      inputHandler.Attach();
166    }
167   
168    /// <summary>
169    /// Pops the stacked input handler (and all input handlers above it).
170    /// If <paramref name="inputHandler"/> is not found in the currently stacked input handlers, or is null, this method
171    /// does nothing.
172    /// </summary>
173    /// <remarks><inheritdoc cref="ITextAreaInputHandler"/></remarks>
174    public void PopStackedInputHandler(TextAreaStackedInputHandler inputHandler)
175    {
176      if (stackedInputHandlers.Any(i => i == inputHandler)) {
177        ITextAreaInputHandler oldHandler;
178        do {
179          oldHandler = stackedInputHandlers.Peek();
180          stackedInputHandlers = stackedInputHandlers.Pop();
181          oldHandler.Detach();
182        } while (oldHandler != inputHandler);
183      }
184    }
185    #endregion
186   
187    #region Document property
188    /// <summary>
189    /// Document property.
190    /// </summary>
191    public static readonly DependencyProperty DocumentProperty
192      = TextView.DocumentProperty.AddOwner(typeof(TextArea), new FrameworkPropertyMetadata(OnDocumentChanged));
193   
194    /// <summary>
195    /// Gets/Sets the document displayed by the text editor.
196    /// </summary>
197    public TextDocument Document {
198      get { return (TextDocument)GetValue(DocumentProperty); }
199      set { SetValue(DocumentProperty, value); }
200    }
201   
202    /// <inheritdoc/>
203    public event EventHandler DocumentChanged;
204   
205    static void OnDocumentChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
206    {
207      ((TextArea)dp).OnDocumentChanged((TextDocument)e.OldValue, (TextDocument)e.NewValue);
208    }
209   
210    void OnDocumentChanged(TextDocument oldValue, TextDocument newValue)
211    {
212      if (oldValue != null) {
213        TextDocumentWeakEventManager.Changing.RemoveListener(oldValue, this);
214        TextDocumentWeakEventManager.Changed.RemoveListener(oldValue, this);
215        TextDocumentWeakEventManager.UpdateStarted.RemoveListener(oldValue, this);
216        TextDocumentWeakEventManager.UpdateFinished.RemoveListener(oldValue, this);
217      }
218      textView.Document = newValue;
219      if (newValue != null) {
220        TextDocumentWeakEventManager.Changing.AddListener(newValue, this);
221        TextDocumentWeakEventManager.Changed.AddListener(newValue, this);
222        TextDocumentWeakEventManager.UpdateStarted.AddListener(newValue, this);
223        TextDocumentWeakEventManager.UpdateFinished.AddListener(newValue, this);
224      }
225      // Reset caret location and selection: this is necessary because the caret/selection might be invalid
226      // in the new document (e.g. if new document is shorter than the old document).
227      caret.Location = new TextLocation(1, 1);
228      this.ClearSelection();
229      if (DocumentChanged != null)
230        DocumentChanged(this, EventArgs.Empty);
231      CommandManager.InvalidateRequerySuggested();
232    }
233    #endregion
234   
235    #region Options property
236    /// <summary>
237    /// Options property.
238    /// </summary>
239    public static readonly DependencyProperty OptionsProperty
240      = TextView.OptionsProperty.AddOwner(typeof(TextArea), new FrameworkPropertyMetadata(OnOptionsChanged));
241   
242    /// <summary>
243    /// Gets/Sets the document displayed by the text editor.
244    /// </summary>
245    public TextEditorOptions Options {
246      get { return (TextEditorOptions)GetValue(OptionsProperty); }
247      set { SetValue(OptionsProperty, value); }
248    }
249   
250    /// <summary>
251    /// Occurs when a text editor option has changed.
252    /// </summary>
253    public event PropertyChangedEventHandler OptionChanged;
254   
255    /// <summary>
256    /// Raises the <see cref="OptionChanged"/> event.
257    /// </summary>
258    protected virtual void OnOptionChanged(PropertyChangedEventArgs e)
259    {
260      if (OptionChanged != null) {
261        OptionChanged(this, e);
262      }
263    }
264   
265    static void OnOptionsChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
266    {
267      ((TextArea)dp).OnOptionsChanged((TextEditorOptions)e.OldValue, (TextEditorOptions)e.NewValue);
268    }
269   
270    void OnOptionsChanged(TextEditorOptions oldValue, TextEditorOptions newValue)
271    {
272      if (oldValue != null) {
273        PropertyChangedWeakEventManager.RemoveListener(oldValue, this);
274      }
275      textView.Options = newValue;
276      if (newValue != null) {
277        PropertyChangedWeakEventManager.AddListener(newValue, this);
278      }
279      OnOptionChanged(new PropertyChangedEventArgs(null));
280    }
281    #endregion
282   
283    #region ReceiveWeakEvent
284    /// <inheritdoc cref="IWeakEventListener.ReceiveWeakEvent"/>
285    protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
286    {
287      if (managerType == typeof(TextDocumentWeakEventManager.Changing)) {
288        OnDocumentChanging();
289        return true;
290      } else if (managerType == typeof(TextDocumentWeakEventManager.Changed)) {
291        OnDocumentChanged((DocumentChangeEventArgs)e);
292        return true;
293      } else if (managerType == typeof(TextDocumentWeakEventManager.UpdateStarted)) {
294        OnUpdateStarted();
295        return true;
296      } else if (managerType == typeof(TextDocumentWeakEventManager.UpdateFinished)) {
297        OnUpdateFinished();
298        return true;
299      } else if (managerType == typeof(PropertyChangedWeakEventManager)) {
300        OnOptionChanged((PropertyChangedEventArgs)e);
301        return true;
302      }
303      return false;
304    }
305   
306    bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
307    {
308      return ReceiveWeakEvent(managerType, sender, e);
309    }
310    #endregion
311   
312    #region Caret handling on document changes
313    void OnDocumentChanging()
314    {
315      caret.OnDocumentChanging();
316    }
317   
318    void OnDocumentChanged(DocumentChangeEventArgs e)
319    {
320      caret.OnDocumentChanged(e);
321      this.Selection = selection.UpdateOnDocumentChange(e);
322    }
323   
324    void OnUpdateStarted()
325    {
326      Document.UndoStack.PushOptional(new RestoreCaretAndSelectionUndoAction(this));
327    }
328   
329    void OnUpdateFinished()
330    {
331      caret.OnDocumentUpdateFinished();
332    }
333   
334    sealed class RestoreCaretAndSelectionUndoAction : IUndoableOperation
335    {
336      // keep textarea in weak reference because the IUndoableOperation is stored with the document
337      WeakReference textAreaReference;
338      TextViewPosition caretPosition;
339      Selection selection;
340     
341      public RestoreCaretAndSelectionUndoAction(TextArea textArea)
342      {
343        this.textAreaReference = new WeakReference(textArea);
344        // Just save the old caret position, no need to validate here.
345        // If we restore it, we'll validate it anyways.
346        this.caretPosition = textArea.Caret.NonValidatedPosition;
347        this.selection = textArea.Selection;
348      }
349     
350      public void Undo()
351      {
352        TextArea textArea = (TextArea)textAreaReference.Target;
353        if (textArea != null) {
354          textArea.Caret.Position = caretPosition;
355          textArea.Selection = selection;
356        }
357      }
358     
359      public void Redo()
360      {
361        // redo=undo: we just restore the caret/selection state
362        Undo();
363      }
364    }
365    #endregion
366   
367    #region TextView property
368    readonly TextView textView;
369    IScrollInfo scrollInfo;
370
371    /// <summary>
372    /// Gets the text view used to display text in this text area.
373    /// </summary>
374    public TextView TextView {
375      get {
376        return textView;
377      }
378    }
379    /// <inheritdoc/>
380    public override void OnApplyTemplate()
381    {
382      base.OnApplyTemplate();
383      scrollInfo = textView;
384      ApplyScrollInfo();
385    }
386    #endregion
387   
388    #region Selection property
389    internal readonly Selection emptySelection;
390    Selection selection;
391   
392    /// <summary>
393    /// Occurs when the selection has changed.
394    /// </summary>
395    public event EventHandler SelectionChanged;
396   
397    /// <summary>
398    /// Gets/Sets the selection in this text area.
399    /// </summary>
400   
401    public Selection Selection {
402      get { return selection; }
403      set {
404        if (value == null)
405          throw new ArgumentNullException("value");
406        if (value.textArea != this)
407          throw new ArgumentException("Cannot use a Selection instance that belongs to another text area.");
408        if (!object.Equals(selection, value)) {
409//          Debug.WriteLine("Selection change from " + selection + " to " + value);
410          if (textView != null) {
411            ISegment oldSegment = selection.SurroundingSegment;
412            ISegment newSegment = value.SurroundingSegment;
413            if (!Selection.EnableVirtualSpace && (selection is SimpleSelection && value is SimpleSelection && oldSegment != null && newSegment != null)) {
414              // perf optimization:
415              // When a simple selection changes, don't redraw the whole selection, but only the changed parts.
416              int oldSegmentOffset = oldSegment.Offset;
417              int newSegmentOffset = newSegment.Offset;
418              if (oldSegmentOffset != newSegmentOffset) {
419                textView.Redraw(Math.Min(oldSegmentOffset, newSegmentOffset),
420                                Math.Abs(oldSegmentOffset - newSegmentOffset),
421                                DispatcherPriority.Background);
422              }
423              int oldSegmentEndOffset = oldSegment.EndOffset;
424              int newSegmentEndOffset = newSegment.EndOffset;
425              if (oldSegmentEndOffset != newSegmentEndOffset) {
426                textView.Redraw(Math.Min(oldSegmentEndOffset, newSegmentEndOffset),
427                                Math.Abs(oldSegmentEndOffset - newSegmentEndOffset),
428                                DispatcherPriority.Background);
429              }
430            } else {
431              textView.Redraw(oldSegment, DispatcherPriority.Background);
432              textView.Redraw(newSegment, DispatcherPriority.Background);
433            }
434          }
435          selection = value;
436          if (SelectionChanged != null)
437            SelectionChanged(this, EventArgs.Empty);
438          // a selection change causes commands like copy/paste/etc. to change status
439          CommandManager.InvalidateRequerySuggested();
440        }
441      }
442    }
443   
444    /// <summary>
445    /// Clears the current selection.
446    /// </summary>
447    public void ClearSelection()
448    {
449      this.Selection = emptySelection;
450    }
451   
452    /// <summary>
453    /// The <see cref="SelectionBrush"/> property.
454    /// </summary>
455    public static readonly DependencyProperty SelectionBrushProperty =
456      DependencyProperty.Register("SelectionBrush", typeof(Brush), typeof(TextArea));
457   
458    /// <summary>
459    /// Gets/Sets the background brush used for the selection.
460    /// </summary>
461    public Brush SelectionBrush {
462      get { return (Brush)GetValue(SelectionBrushProperty); }
463      set { SetValue(SelectionBrushProperty, value); }
464    }
465   
466    /// <summary>
467    /// The <see cref="SelectionForeground"/> property.
468    /// </summary>
469    public static readonly DependencyProperty SelectionForegroundProperty =
470      DependencyProperty.Register("SelectionForeground", typeof(Brush), typeof(TextArea));
471   
472    /// <summary>
473    /// Gets/Sets the foreground brush used selected text.
474    /// </summary>
475    public Brush SelectionForeground {
476      get { return (Brush)GetValue(SelectionForegroundProperty); }
477      set { SetValue(SelectionForegroundProperty, value); }
478    }
479   
480    /// <summary>
481    /// The <see cref="SelectionBorder"/> property.
482    /// </summary>
483    public static readonly DependencyProperty SelectionBorderProperty =
484      DependencyProperty.Register("SelectionBorder", typeof(Pen), typeof(TextArea));
485   
486    /// <summary>
487    /// Gets/Sets the background brush used for the selection.
488    /// </summary>
489    public Pen SelectionBorder {
490      get { return (Pen)GetValue(SelectionBorderProperty); }
491      set { SetValue(SelectionBorderProperty, value); }
492    }
493   
494    /// <summary>
495    /// The <see cref="SelectionCornerRadius"/> property.
496    /// </summary>
497    public static readonly DependencyProperty SelectionCornerRadiusProperty =
498      DependencyProperty.Register("SelectionCornerRadius", typeof(double), typeof(TextArea),
499                                  new FrameworkPropertyMetadata(3.0));
500   
501    /// <summary>
502    /// Gets/Sets the corner radius of the selection.
503    /// </summary>
504    public double SelectionCornerRadius {
505      get { return (double)GetValue(SelectionCornerRadiusProperty); }
506      set { SetValue(SelectionCornerRadiusProperty, value); }
507    }
508    #endregion
509   
510    #region Force caret to stay inside selection
511    bool ensureSelectionValidRequested;
512    int allowCaretOutsideSelection;
513   
514    void RequestSelectionValidation()
515    {
516      if (!ensureSelectionValidRequested && allowCaretOutsideSelection == 0) {
517        ensureSelectionValidRequested = true;
518        Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(EnsureSelectionValid));
519      }
520    }
521   
522    /// <summary>
523    /// Code that updates only the caret but not the selection can cause confusion when
524    /// keys like 'Delete' delete the (possibly invisible) selected text and not the
525    /// text around the caret.
526    ///
527    /// So we'll ensure that the caret is inside the selection.
528    /// (when the caret is not in the selection, we'll clear the selection)
529    ///
530    /// This method is invoked using the Dispatcher so that code may temporarily violate this rule
531    /// (e.g. most 'extend selection' methods work by first setting the caret, then the selection),
532    /// it's sufficient to fix it after any event handlers have run.
533    /// </summary>
534    void EnsureSelectionValid()
535    {
536      ensureSelectionValidRequested = false;
537      if (allowCaretOutsideSelection == 0) {
538        if (!selection.IsEmpty && !selection.Contains(caret.Offset)) {
539          Debug.WriteLine("Resetting selection because caret is outside");
540          this.ClearSelection();
541        }
542      }
543    }
544   
545    /// <summary>
546    /// Temporarily allows positioning the caret outside the selection.
547    /// Dispose the returned IDisposable to revert the allowance.
548    /// </summary>
549    /// <remarks>
550    /// The text area only forces the caret to be inside the selection when other events
551    /// have finished running (using the dispatcher), so you don't have to use this method
552    /// for temporarily positioning the caret in event handlers.
553    /// This method is only necessary if you want to run the WPF dispatcher, e.g. if you
554    /// perform a drag'n'drop operation.
555    /// </remarks>
556    public IDisposable AllowCaretOutsideSelection()
557    {
558      VerifyAccess();
559      allowCaretOutsideSelection++;
560      return new CallbackOnDispose(
561        delegate {
562          VerifyAccess();
563          allowCaretOutsideSelection--;
564          RequestSelectionValidation();
565        });
566    }
567    #endregion
568   
569    #region Properties
570    readonly Caret caret;
571   
572    /// <summary>
573    /// Gets the Caret used for this text area.
574    /// </summary>
575    public Caret Caret {
576      get { return caret; }
577    }
578   
579    void CaretPositionChanged(object sender, EventArgs e)
580    {
581      if (textView == null)
582        return;
583     
584      this.textView.HighlightedLine = this.Caret.Line;
585    }
586   
587    ObservableCollection<UIElement> leftMargins = new ObservableCollection<UIElement>();
588   
589    /// <summary>
590    /// Gets the collection of margins displayed to the left of the text view.
591    /// </summary>
592    public ObservableCollection<UIElement> LeftMargins {
593      get {
594        return leftMargins;
595      }
596    }
597   
598    void leftMargins_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
599    {
600      if (e.OldItems != null) {
601        foreach (ITextViewConnect c in e.OldItems.OfType<ITextViewConnect>()) {
602          c.RemoveFromTextView(textView);
603        }
604      }
605      if (e.NewItems != null) {
606        foreach (ITextViewConnect c in e.NewItems.OfType<ITextViewConnect>()) {
607          c.AddToTextView(textView);
608        }
609      }
610    }
611   
612    IReadOnlySectionProvider readOnlySectionProvider = NoReadOnlySections.Instance;
613   
614    /// <summary>
615    /// Gets/Sets an object that provides read-only sections for the text area.
616    /// </summary>
617    public IReadOnlySectionProvider ReadOnlySectionProvider {
618      get { return readOnlySectionProvider; }
619      set {
620        if (value == null)
621          throw new ArgumentNullException("value");
622        readOnlySectionProvider = value;
623        CommandManager.InvalidateRequerySuggested(); // the read-only status effects Paste.CanExecute and the IME
624      }
625    }
626    #endregion
627   
628    #region IScrollInfo implementation
629    ScrollViewer scrollOwner;
630    bool canVerticallyScroll, canHorizontallyScroll;
631   
632    void ApplyScrollInfo()
633    {
634      if (scrollInfo != null) {
635        scrollInfo.ScrollOwner = scrollOwner;
636        scrollInfo.CanVerticallyScroll = canVerticallyScroll;
637        scrollInfo.CanHorizontallyScroll = canHorizontallyScroll;
638        scrollOwner = null;
639      }
640    }
641   
642    bool IScrollInfo.CanVerticallyScroll {
643      get { return scrollInfo != null ? scrollInfo.CanVerticallyScroll : false; }
644      set {
645        canVerticallyScroll = value;
646        if (scrollInfo != null)
647          scrollInfo.CanVerticallyScroll = value;
648      }
649    }
650   
651    bool IScrollInfo.CanHorizontallyScroll {
652      get { return scrollInfo != null ? scrollInfo.CanHorizontallyScroll : false; }
653      set {
654        canHorizontallyScroll = value;
655        if (scrollInfo != null)
656          scrollInfo.CanHorizontallyScroll = value;
657      }
658    }
659   
660    double IScrollInfo.ExtentWidth {
661      get { return scrollInfo != null ? scrollInfo.ExtentWidth : 0; }
662    }
663   
664    double IScrollInfo.ExtentHeight {
665      get { return scrollInfo != null ? scrollInfo.ExtentHeight : 0; }
666    }
667   
668    double IScrollInfo.ViewportWidth {
669      get { return scrollInfo != null ? scrollInfo.ViewportWidth : 0; }
670    }
671   
672    double IScrollInfo.ViewportHeight {
673      get { return scrollInfo != null ? scrollInfo.ViewportHeight : 0; }
674    }
675   
676    double IScrollInfo.HorizontalOffset {
677      get { return scrollInfo != null ? scrollInfo.HorizontalOffset : 0; }
678    }
679   
680    double IScrollInfo.VerticalOffset {
681      get { return scrollInfo != null ? scrollInfo.VerticalOffset : 0; }
682    }
683   
684    ScrollViewer IScrollInfo.ScrollOwner {
685      get { return scrollInfo != null ? scrollInfo.ScrollOwner : null; }
686      set {
687        if (scrollInfo != null)
688          scrollInfo.ScrollOwner = value;
689        else
690          scrollOwner = value;
691      }
692    }
693   
694    void IScrollInfo.LineUp()
695    {
696      if (scrollInfo != null) scrollInfo.LineUp();
697    }
698   
699    void IScrollInfo.LineDown()
700    {
701      if (scrollInfo != null) scrollInfo.LineDown();
702    }
703   
704    void IScrollInfo.LineLeft()
705    {
706      if (scrollInfo != null) scrollInfo.LineLeft();
707    }
708   
709    void IScrollInfo.LineRight()
710    {
711      if (scrollInfo != null) scrollInfo.LineRight();
712    }
713   
714    void IScrollInfo.PageUp()
715    {
716      if (scrollInfo != null) scrollInfo.PageUp();
717    }
718   
719    void IScrollInfo.PageDown()
720    {
721      if (scrollInfo != null) scrollInfo.PageDown();
722    }
723   
724    void IScrollInfo.PageLeft()
725    {
726      if (scrollInfo != null) scrollInfo.PageLeft();
727    }
728   
729    void IScrollInfo.PageRight()
730    {
731      if (scrollInfo != null) scrollInfo.PageRight();
732    }
733   
734    void IScrollInfo.MouseWheelUp()
735    {
736      if (scrollInfo != null) scrollInfo.MouseWheelUp();
737    }
738   
739    void IScrollInfo.MouseWheelDown()
740    {
741      if (scrollInfo != null) scrollInfo.MouseWheelDown();
742    }
743   
744    void IScrollInfo.MouseWheelLeft()
745    {
746      if (scrollInfo != null) scrollInfo.MouseWheelLeft();
747    }
748   
749    void IScrollInfo.MouseWheelRight()
750    {
751      if (scrollInfo != null) scrollInfo.MouseWheelRight();
752    }
753   
754    void IScrollInfo.SetHorizontalOffset(double offset)
755    {
756      if (scrollInfo != null) scrollInfo.SetHorizontalOffset(offset);
757    }
758   
759    void IScrollInfo.SetVerticalOffset(double offset)
760    {
761      if (scrollInfo != null) scrollInfo.SetVerticalOffset(offset);
762    }
763   
764    Rect IScrollInfo.MakeVisible(System.Windows.Media.Visual visual, Rect rectangle)
765    {
766      if (scrollInfo != null)
767        return scrollInfo.MakeVisible(visual, rectangle);
768      else
769        return Rect.Empty;
770    }
771    #endregion
772   
773    #region Focus Handling (Show/Hide Caret)
774    /// <inheritdoc/>
775    protected override void OnMouseDown(MouseButtonEventArgs e)
776    {
777      base.OnMouseDown(e);
778      Focus();
779    }
780   
781    /// <inheritdoc/>
782    protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e)
783    {
784      base.OnGotKeyboardFocus(e);
785      // First activate IME, then show caret
786      ime.OnGotKeyboardFocus(e);
787      caret.Show();
788    }
789   
790    /// <inheritdoc/>
791    protected override void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
792    {
793      base.OnLostKeyboardFocus(e);
794      caret.Hide();
795      ime.OnLostKeyboardFocus(e);
796    }
797    #endregion
798   
799    #region OnTextInput / RemoveSelectedText / ReplaceSelectionWithText
800    /// <summary>
801    /// Occurs when the TextArea receives text input.
802    /// This is like the <see cref="UIElement.TextInput"/> event,
803    /// but occurs immediately before the TextArea handles the TextInput event.
804    /// </summary>
805    public event TextCompositionEventHandler TextEntering;
806   
807    /// <summary>
808    /// Occurs when the TextArea receives text input.
809    /// This is like the <see cref="UIElement.TextInput"/> event,
810    /// but occurs immediately after the TextArea handles the TextInput event.
811    /// </summary>
812    public event TextCompositionEventHandler TextEntered;
813   
814    /// <summary>
815    /// Raises the TextEntering event.
816    /// </summary>
817    protected virtual void OnTextEntering(TextCompositionEventArgs e)
818    {
819      if (TextEntering != null) {
820        TextEntering(this, e);
821      }
822    }
823   
824    /// <summary>
825    /// Raises the TextEntered event.
826    /// </summary>
827    protected virtual void OnTextEntered(TextCompositionEventArgs e)
828    {
829      if (TextEntered != null) {
830        TextEntered(this, e);
831      }
832    }
833   
834    /// <inheritdoc/>
835    protected override void OnTextInput(TextCompositionEventArgs e)
836    {
837      //Debug.WriteLine("TextInput: Text='" + e.Text + "' SystemText='" + e.SystemText + "' ControlText='" + e.ControlText + "'");
838      base.OnTextInput(e);
839      if (!e.Handled && this.Document != null) {
840        if (string.IsNullOrEmpty(e.Text) || e.Text == "\x1b" || e.Text == "\b") {
841          // ASCII 0x1b = ESC.
842          // WPF produces a TextInput event with that old ASCII control char
843          // when Escape is pressed. We'll just ignore it.
844         
845          // A deadkey followed by backspace causes a textinput event for the BS character.
846         
847          // Similarly, some shortcuts like Alt+Space produce an empty TextInput event.
848          // We have to ignore those (not handle them) to keep the shortcut working.
849          return;
850        }
851        HideMouseCursor();
852        PerformTextInput(e);
853        e.Handled = true;
854      }
855    }
856   
857    /// <summary>
858    /// Performs text input.
859    /// This raises the <see cref="TextEntering"/> event, replaces the selection with the text,
860    /// and then raises the <see cref="TextEntered"/> event.
861    /// </summary>
862    public void PerformTextInput(string text)
863    {
864      TextComposition textComposition = new TextComposition(InputManager.Current, this, text);
865      TextCompositionEventArgs e = new TextCompositionEventArgs(Keyboard.PrimaryDevice, textComposition);
866      e.RoutedEvent = TextInputEvent;
867      PerformTextInput(e);
868    }
869   
870    /// <summary>
871    /// Performs text input.
872    /// This raises the <see cref="TextEntering"/> event, replaces the selection with the text,
873    /// and then raises the <see cref="TextEntered"/> event.
874    /// </summary>
875    public void PerformTextInput(TextCompositionEventArgs e)
876    {
877      if (e == null)
878        throw new ArgumentNullException("e");
879      if (this.Document == null)
880        throw ThrowUtil.NoDocumentAssigned();
881      OnTextEntering(e);
882      if (!e.Handled) {
883        if (e.Text == "\n" || e.Text == "\r" || e.Text == "\r\n")
884          ReplaceSelectionWithNewLine();
885        else {
886          if (OverstrikeMode && Selection.IsEmpty && Document.GetLineByNumber(Caret.Line).EndOffset > Caret.Offset)
887            EditingCommands.SelectRightByCharacter.Execute(null, this);
888          ReplaceSelectionWithText(e.Text);
889        }
890        OnTextEntered(e);
891        caret.BringCaretToView();
892      }
893    }
894   
895    void ReplaceSelectionWithNewLine()
896    {
897      string newLine = TextUtilities.GetNewLineFromDocument(this.Document, this.Caret.Line);
898      using (this.Document.RunUpdate()) {
899        ReplaceSelectionWithText(newLine);
900        if (this.IndentationStrategy != null) {
901          DocumentLine line = this.Document.GetLineByNumber(this.Caret.Line);
902          ISegment[] deletable = GetDeletableSegments(line);
903          if (deletable.Length == 1 && deletable[0].Offset == line.Offset && deletable[0].Length == line.Length) {
904            // use indentation strategy only if the line is not read-only
905            this.IndentationStrategy.IndentLine(this.Document, line);
906          }
907        }
908      }
909    }
910   
911    internal void RemoveSelectedText()
912    {
913      if (this.Document == null)
914        throw ThrowUtil.NoDocumentAssigned();
915      selection.ReplaceSelectionWithText(string.Empty);
916      #if DEBUG
917      if (!selection.IsEmpty) {
918        foreach (ISegment s in selection.Segments) {
919          Debug.Assert(this.ReadOnlySectionProvider.GetDeletableSegments(s).Count() == 0);
920        }
921      }
922      #endif
923    }
924   
925    internal void ReplaceSelectionWithText(string newText)
926    {
927      if (newText == null)
928        throw new ArgumentNullException("newText");
929      if (this.Document == null)
930        throw ThrowUtil.NoDocumentAssigned();
931      selection.ReplaceSelectionWithText(newText);
932    }
933   
934    internal ISegment[] GetDeletableSegments(ISegment segment)
935    {
936      var deletableSegments = this.ReadOnlySectionProvider.GetDeletableSegments(segment);
937      if (deletableSegments == null)
938        throw new InvalidOperationException("ReadOnlySectionProvider.GetDeletableSegments returned null");
939      var array = deletableSegments.ToArray();
940      int lastIndex = segment.Offset;
941      for (int i = 0; i < array.Length; i++) {
942        if (array[i].Offset < lastIndex)
943          throw new InvalidOperationException("ReadOnlySectionProvider returned incorrect segments (outside of input segment / wrong order)");
944        lastIndex = array[i].EndOffset;
945      }
946      if (lastIndex > segment.EndOffset)
947        throw new InvalidOperationException("ReadOnlySectionProvider returned incorrect segments (outside of input segment / wrong order)");
948      return array;
949    }
950    #endregion
951   
952    #region IndentationStrategy property
953    /// <summary>
954    /// IndentationStrategy property.
955    /// </summary>
956    public static readonly DependencyProperty IndentationStrategyProperty =
957      DependencyProperty.Register("IndentationStrategy", typeof(IIndentationStrategy), typeof(TextArea),
958                                  new FrameworkPropertyMetadata(new DefaultIndentationStrategy()));
959   
960    /// <summary>
961    /// Gets/Sets the indentation strategy used when inserting new lines.
962    /// </summary>
963    public IIndentationStrategy IndentationStrategy {
964      get { return (IIndentationStrategy)GetValue(IndentationStrategyProperty); }
965      set { SetValue(IndentationStrategyProperty, value); }
966    }
967    #endregion
968   
969    #region OnKeyDown/OnKeyUp
970    /// <inheritdoc/>
971    protected override void OnPreviewKeyDown(KeyEventArgs e)
972    {
973      base.OnPreviewKeyDown(e);
974     
975      if (!e.Handled && e.Key == Key.Insert && this.Options.AllowToggleOverstrikeMode) {
976        this.OverstrikeMode = !this.OverstrikeMode;
977        e.Handled = true;
978        return;
979      }
980     
981      foreach (TextAreaStackedInputHandler h in stackedInputHandlers) {
982        if (e.Handled)
983          break;
984        h.OnPreviewKeyDown(e);
985      }
986    }
987   
988    /// <inheritdoc/>
989    protected override void OnPreviewKeyUp(KeyEventArgs e)
990    {
991      base.OnPreviewKeyUp(e);
992      foreach (TextAreaStackedInputHandler h in stackedInputHandlers) {
993        if (e.Handled)
994          break;
995        h.OnPreviewKeyUp(e);
996      }
997    }
998   
999    // Make life easier for text editor extensions that use a different cursor based on the pressed modifier keys.
1000    /// <inheritdoc/>
1001    protected override void OnKeyDown(KeyEventArgs e)
1002    {
1003      base.OnKeyDown(e);
1004      TextView.InvalidateCursorIfMouseWithinTextView();
1005    }
1006   
1007    /// <inheritdoc/>
1008    protected override void OnKeyUp(KeyEventArgs e)
1009    {
1010      base.OnKeyUp(e);
1011      TextView.InvalidateCursorIfMouseWithinTextView();
1012    }
1013    #endregion
1014   
1015    #region Hide Mouse Cursor While Typing
1016   
1017    bool isMouseCursorHidden;
1018   
1019    void AttachTypingEvents()
1020    {
1021      // Use the PreviewMouseMove event in case some other editor layer consumes the MouseMove event (e.g. SD's InsertionCursorLayer)
1022      this.MouseEnter += delegate { ShowMouseCursor(); };
1023      this.MouseLeave += delegate { ShowMouseCursor(); };
1024      this.PreviewMouseMove += delegate { ShowMouseCursor(); };
1025      #if DOTNET4
1026      this.TouchEnter += delegate { ShowMouseCursor(); };
1027      this.TouchLeave += delegate { ShowMouseCursor(); };
1028      this.PreviewTouchMove += delegate { ShowMouseCursor(); };
1029      #endif
1030    }
1031   
1032    void ShowMouseCursor()
1033    {
1034      if (this.isMouseCursorHidden) {
1035        System.Windows.Forms.Cursor.Show();
1036        this.isMouseCursorHidden = false;
1037      }
1038    }
1039   
1040    void HideMouseCursor() {
1041      if (Options.HideCursorWhileTyping && !this.isMouseCursorHidden && this.IsMouseOver) {
1042        this.isMouseCursorHidden = true;
1043        System.Windows.Forms.Cursor.Hide();
1044      }
1045    }
1046   
1047    #endregion
1048   
1049    #region Overstrike mode
1050   
1051    /// <summary>
1052    /// The <see cref="OverstrikeMode"/> dependency property.
1053    /// </summary>
1054    public static readonly DependencyProperty OverstrikeModeProperty =
1055      DependencyProperty.Register("OverstrikeMode", typeof(bool), typeof(TextArea),
1056                                  new FrameworkPropertyMetadata(Boxes.False));
1057   
1058    /// <summary>
1059    /// Gets/Sets whether overstrike mode is active.
1060    /// </summary>
1061    public bool OverstrikeMode {
1062      get { return (bool)GetValue(OverstrikeModeProperty); }
1063      set { SetValue(OverstrikeModeProperty, value); }
1064    }
1065   
1066    #endregion
1067   
1068    /// <inheritdoc/>
1069    protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
1070    {
1071      // accept clicks even where the text area draws no background
1072      return new PointHitTestResult(this, hitTestParameters.HitPoint);
1073    }
1074   
1075    /// <inheritdoc/>
1076    protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
1077    {
1078      base.OnPropertyChanged(e);
1079      if (e.Property == SelectionBrushProperty
1080          || e.Property == SelectionBorderProperty
1081          || e.Property == SelectionForegroundProperty
1082          || e.Property == SelectionCornerRadiusProperty)
1083      {
1084        textView.Redraw();
1085      } else if (e.Property == OverstrikeModeProperty) {
1086        caret.UpdateIfVisible();
1087      }
1088    }
1089   
1090    /// <summary>
1091    /// Gets the requested service.
1092    /// </summary>
1093    /// <returns>Returns the requested service instance, or null if the service cannot be found.</returns>
1094    public virtual object GetService(Type serviceType)
1095    {
1096      return textView.GetService(serviceType);
1097    }
1098   
1099    /// <summary>
1100    /// Occurs when text inside the TextArea was copied.
1101    /// </summary>
1102    public event EventHandler<TextEventArgs> TextCopied;
1103   
1104    internal void OnTextCopied(TextEventArgs e)
1105    {
1106      if (TextCopied != null)
1107        TextCopied(this, e);
1108    }
1109  }
1110 
1111  /// <summary>
1112  /// EventArgs with text.
1113  /// </summary>
1114  [Serializable]
1115  public class TextEventArgs : EventArgs
1116  {
1117    string text;
1118   
1119    /// <summary>
1120    /// Gets the text.
1121    /// </summary>
1122    public string Text {
1123      get {
1124        return text;
1125      }
1126    }
1127   
1128    /// <summary>
1129    /// Creates a new TextEventArgs instance.
1130    /// </summary>
1131    public TextEventArgs(string text)
1132    {
1133      if (text == null)
1134        throw new ArgumentNullException("text");
1135      this.text = text;
1136    }
1137  }
1138}
Note: See TracBrowser for help on using the repository browser.