Free cookie consent management tool by TermsFeed Policy Generator

source: branches/2708_ScopedAlgorithms/HeuristicLab.ExtLibs/HeuristicLab.AvalonEdit/5.0.1/AvalonEdit-5.0.1/TextEditor.cs @ 17330

Last change on this file since 17330 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.ComponentModel;
21using System.IO;
22using System.Linq;
23using System.Text;
24using System.Windows;
25using System.Windows.Controls;
26using System.Windows.Controls.Primitives;
27using System.Windows.Data;
28using System.Windows.Input;
29using System.Windows.Markup;
30using System.Windows.Media;
31using System.Windows.Shapes;
32using System.Windows.Threading;
33
34using ICSharpCode.AvalonEdit.Document;
35using ICSharpCode.AvalonEdit.Editing;
36using ICSharpCode.AvalonEdit.Highlighting;
37using ICSharpCode.AvalonEdit.Rendering;
38using ICSharpCode.AvalonEdit.Utils;
39
40namespace ICSharpCode.AvalonEdit
41{
42  /// <summary>
43  /// The text editor control.
44  /// Contains a scrollable TextArea.
45  /// </summary>
46  [Localizability(LocalizationCategory.Text), ContentProperty("Text")]
47  public class TextEditor : Control, ITextEditorComponent, IServiceProvider, IWeakEventListener
48  {
49    #region Constructors
50    static TextEditor()
51    {
52      DefaultStyleKeyProperty.OverrideMetadata(typeof(TextEditor),
53                                               new FrameworkPropertyMetadata(typeof(TextEditor)));
54      FocusableProperty.OverrideMetadata(typeof(TextEditor),
55                                         new FrameworkPropertyMetadata(Boxes.True));
56    }
57   
58    /// <summary>
59    /// Creates a new TextEditor instance.
60    /// </summary>
61    public TextEditor() : this(new TextArea())
62    {
63    }
64   
65    /// <summary>
66    /// Creates a new TextEditor instance.
67    /// </summary>
68    protected TextEditor(TextArea textArea)
69    {
70      if (textArea == null)
71        throw new ArgumentNullException("textArea");
72      this.textArea = textArea;
73     
74      textArea.TextView.Services.AddService(typeof(TextEditor), this);
75     
76      SetCurrentValue(OptionsProperty, textArea.Options);
77      SetCurrentValue(DocumentProperty, new TextDocument());
78    }
79   
80    #if !DOTNET4
81    void SetCurrentValue(DependencyProperty property, object value)
82    {
83      SetValue(property, value);
84    }
85    #endif
86    #endregion
87   
88    /// <inheritdoc/>
89    protected override System.Windows.Automation.Peers.AutomationPeer OnCreateAutomationPeer()
90    {
91      return new TextEditorAutomationPeer(this);
92    }
93   
94    /// Forward focus to TextArea.
95    /// <inheritdoc/>
96    protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e)
97    {
98      base.OnGotKeyboardFocus(e);
99      if (e.NewFocus == this) {
100        Keyboard.Focus(this.TextArea);
101        e.Handled = true;
102      }
103    }
104   
105    #region Document property
106    /// <summary>
107    /// Document property.
108    /// </summary>
109    public static readonly DependencyProperty DocumentProperty
110      = TextView.DocumentProperty.AddOwner(
111        typeof(TextEditor), new FrameworkPropertyMetadata(OnDocumentChanged));
112   
113    /// <summary>
114    /// Gets/Sets the document displayed by the text editor.
115    /// This is a dependency property.
116    /// </summary>
117    public TextDocument Document {
118      get { return (TextDocument)GetValue(DocumentProperty); }
119      set { SetValue(DocumentProperty, value); }
120    }
121   
122    /// <summary>
123    /// Occurs when the document property has changed.
124    /// </summary>
125    public event EventHandler DocumentChanged;
126   
127    /// <summary>
128    /// Raises the <see cref="DocumentChanged"/> event.
129    /// </summary>
130    protected virtual void OnDocumentChanged(EventArgs e)
131    {
132      if (DocumentChanged != null) {
133        DocumentChanged(this, e);
134      }
135    }
136   
137    static void OnDocumentChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
138    {
139      ((TextEditor)dp).OnDocumentChanged((TextDocument)e.OldValue, (TextDocument)e.NewValue);
140    }
141   
142    void OnDocumentChanged(TextDocument oldValue, TextDocument newValue)
143    {
144      if (oldValue != null) {
145        TextDocumentWeakEventManager.TextChanged.RemoveListener(oldValue, this);
146        PropertyChangedEventManager.RemoveListener(oldValue.UndoStack, this, "IsOriginalFile");
147      }
148      textArea.Document = newValue;
149      if (newValue != null) {
150        TextDocumentWeakEventManager.TextChanged.AddListener(newValue, this);
151        PropertyChangedEventManager.AddListener(newValue.UndoStack, this, "IsOriginalFile");
152      }
153      OnDocumentChanged(EventArgs.Empty);
154      OnTextChanged(EventArgs.Empty);
155    }
156    #endregion
157   
158    #region Options property
159    /// <summary>
160    /// Options property.
161    /// </summary>
162    public static readonly DependencyProperty OptionsProperty
163      = TextView.OptionsProperty.AddOwner(typeof(TextEditor), new FrameworkPropertyMetadata(OnOptionsChanged));
164   
165    /// <summary>
166    /// Gets/Sets the options currently used by the text editor.
167    /// </summary>
168    public TextEditorOptions Options {
169      get { return (TextEditorOptions)GetValue(OptionsProperty); }
170      set { SetValue(OptionsProperty, value); }
171    }
172   
173    /// <summary>
174    /// Occurs when a text editor option has changed.
175    /// </summary>
176    public event PropertyChangedEventHandler OptionChanged;
177   
178    /// <summary>
179    /// Raises the <see cref="OptionChanged"/> event.
180    /// </summary>
181    protected virtual void OnOptionChanged(PropertyChangedEventArgs e)
182    {
183      if (OptionChanged != null) {
184        OptionChanged(this, e);
185      }
186    }
187   
188    static void OnOptionsChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
189    {
190      ((TextEditor)dp).OnOptionsChanged((TextEditorOptions)e.OldValue, (TextEditorOptions)e.NewValue);
191    }
192   
193    void OnOptionsChanged(TextEditorOptions oldValue, TextEditorOptions newValue)
194    {
195      if (oldValue != null) {
196        PropertyChangedWeakEventManager.RemoveListener(oldValue, this);
197      }
198      textArea.Options = newValue;
199      if (newValue != null) {
200        PropertyChangedWeakEventManager.AddListener(newValue, this);
201      }
202      OnOptionChanged(new PropertyChangedEventArgs(null));
203    }
204   
205    /// <inheritdoc cref="IWeakEventListener.ReceiveWeakEvent"/>
206    protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
207    {
208      if (managerType == typeof(PropertyChangedWeakEventManager)) {
209        OnOptionChanged((PropertyChangedEventArgs)e);
210        return true;
211      } else if (managerType == typeof(TextDocumentWeakEventManager.TextChanged)) {
212        OnTextChanged(e);
213        return true;
214      } else if (managerType == typeof(PropertyChangedEventManager)) {
215        return HandleIsOriginalChanged((PropertyChangedEventArgs)e);
216      }
217      return false;
218    }
219   
220    bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
221    {
222      return ReceiveWeakEvent(managerType, sender, e);
223    }
224    #endregion
225   
226    #region Text property
227    /// <summary>
228    /// Gets/Sets the text of the current document.
229    /// </summary>
230    [Localizability(LocalizationCategory.Text), DefaultValue("")]
231    public string Text {
232      get {
233        TextDocument document = this.Document;
234        return document != null ? document.Text : string.Empty;
235      }
236      set {
237        TextDocument document = GetDocument();
238        document.Text = value ?? string.Empty;
239        // after replacing the full text, the caret is positioned at the end of the document
240        // - reset it to the beginning.
241        this.CaretOffset = 0;
242        document.UndoStack.ClearAll();
243      }
244    }
245   
246    TextDocument GetDocument()
247    {
248      TextDocument document = this.Document;
249      if (document == null)
250        throw ThrowUtil.NoDocumentAssigned();
251      return document;
252    }
253   
254    /// <summary>
255    /// Occurs when the Text property changes.
256    /// </summary>
257    public event EventHandler TextChanged;
258   
259    /// <summary>
260    /// Raises the <see cref="TextChanged"/> event.
261    /// </summary>
262    protected virtual void OnTextChanged(EventArgs e)
263    {
264      if (TextChanged != null) {
265        TextChanged(this, e);
266      }
267    }
268    #endregion
269   
270    #region TextArea / ScrollViewer properties
271    readonly TextArea textArea;
272    ScrollViewer scrollViewer;
273   
274    /// <summary>
275    /// Is called after the template was applied.
276    /// </summary>
277    public override void OnApplyTemplate()
278    {
279      base.OnApplyTemplate();
280      scrollViewer = (ScrollViewer)Template.FindName("PART_ScrollViewer", this);
281    }
282   
283    /// <summary>
284    /// Gets the text area.
285    /// </summary>
286    public TextArea TextArea {
287      get {
288        return textArea;
289      }
290    }
291   
292    /// <summary>
293    /// Gets the scroll viewer used by the text editor.
294    /// This property can return null if the template has not been applied / does not contain a scroll viewer.
295    /// </summary>
296    internal ScrollViewer ScrollViewer {
297      get { return scrollViewer; }
298    }
299   
300    bool CanExecute(RoutedUICommand command)
301    {
302      TextArea textArea = this.TextArea;
303      if (textArea == null)
304        return false;
305      else
306        return command.CanExecute(null, textArea);
307    }
308   
309    void Execute(RoutedUICommand command)
310    {
311      TextArea textArea = this.TextArea;
312      if (textArea != null)
313        command.Execute(null, textArea);
314    }
315    #endregion
316   
317    #region Syntax highlighting
318    /// <summary>
319    /// The <see cref="SyntaxHighlighting"/> property.
320    /// </summary>
321    public static readonly DependencyProperty SyntaxHighlightingProperty =
322      DependencyProperty.Register("SyntaxHighlighting", typeof(IHighlightingDefinition), typeof(TextEditor),
323                                  new FrameworkPropertyMetadata(OnSyntaxHighlightingChanged));
324   
325   
326    /// <summary>
327    /// Gets/sets the syntax highlighting definition used to colorize the text.
328    /// </summary>
329    public IHighlightingDefinition SyntaxHighlighting {
330      get { return (IHighlightingDefinition)GetValue(SyntaxHighlightingProperty); }
331      set { SetValue(SyntaxHighlightingProperty, value); }
332    }
333   
334    IVisualLineTransformer colorizer;
335   
336    static void OnSyntaxHighlightingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
337    {
338      ((TextEditor)d).OnSyntaxHighlightingChanged(e.NewValue as IHighlightingDefinition);
339    }
340   
341    void OnSyntaxHighlightingChanged(IHighlightingDefinition newValue)
342    {
343      if (colorizer != null) {
344        this.TextArea.TextView.LineTransformers.Remove(colorizer);
345        colorizer = null;
346      }
347      if (newValue != null) {
348        colorizer = CreateColorizer(newValue);
349        if (colorizer != null)
350          this.TextArea.TextView.LineTransformers.Insert(0, colorizer);
351      }
352    }
353   
354    /// <summary>
355    /// Creates the highlighting colorizer for the specified highlighting definition.
356    /// Allows derived classes to provide custom colorizer implementations for special highlighting definitions.
357    /// </summary>
358    /// <returns></returns>
359    protected virtual IVisualLineTransformer CreateColorizer(IHighlightingDefinition highlightingDefinition)
360    {
361      if (highlightingDefinition == null)
362        throw new ArgumentNullException("highlightingDefinition");
363      return new HighlightingColorizer(highlightingDefinition);
364    }
365    #endregion
366   
367    #region WordWrap
368    /// <summary>
369    /// Word wrap dependency property.
370    /// </summary>
371    public static readonly DependencyProperty WordWrapProperty =
372      DependencyProperty.Register("WordWrap", typeof(bool), typeof(TextEditor),
373                                  new FrameworkPropertyMetadata(Boxes.False));
374   
375    /// <summary>
376    /// Specifies whether the text editor uses word wrapping.
377    /// </summary>
378    /// <remarks>
379    /// Setting WordWrap=true has the same effect as setting HorizontalScrollBarVisibility=Disabled and will override the
380    /// HorizontalScrollBarVisibility setting.
381    /// </remarks>
382    public bool WordWrap {
383      get { return (bool)GetValue(WordWrapProperty); }
384      set { SetValue(WordWrapProperty, Boxes.Box(value)); }
385    }
386    #endregion
387   
388    #region IsReadOnly
389    /// <summary>
390    /// IsReadOnly dependency property.
391    /// </summary>
392    public static readonly DependencyProperty IsReadOnlyProperty =
393      DependencyProperty.Register("IsReadOnly", typeof(bool), typeof(TextEditor),
394                                  new FrameworkPropertyMetadata(Boxes.False, OnIsReadOnlyChanged));
395   
396    /// <summary>
397    /// Specifies whether the user can change the text editor content.
398    /// Setting this property will replace the
399    /// <see cref="Editing.TextArea.ReadOnlySectionProvider">TextArea.ReadOnlySectionProvider</see>.
400    /// </summary>
401    public bool IsReadOnly {
402      get { return (bool)GetValue(IsReadOnlyProperty); }
403      set { SetValue(IsReadOnlyProperty, Boxes.Box(value)); }
404    }
405   
406    static void OnIsReadOnlyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
407    {
408      TextEditor editor = d as TextEditor;
409      if (editor != null) {
410        if ((bool)e.NewValue)
411          editor.TextArea.ReadOnlySectionProvider = ReadOnlySectionDocument.Instance;
412        else
413          editor.TextArea.ReadOnlySectionProvider = NoReadOnlySections.Instance;
414       
415        TextEditorAutomationPeer peer = TextEditorAutomationPeer.FromElement(editor) as TextEditorAutomationPeer;
416        if (peer != null) {
417          peer.RaiseIsReadOnlyChanged((bool)e.OldValue, (bool)e.NewValue);
418        }
419      }
420    }
421    #endregion
422   
423    #region IsModified
424    /// <summary>
425    /// Dependency property for <see cref="IsModified"/>
426    /// </summary>
427    public static readonly DependencyProperty IsModifiedProperty =
428      DependencyProperty.Register("IsModified", typeof(bool), typeof(TextEditor),
429                                  new FrameworkPropertyMetadata(Boxes.False, OnIsModifiedChanged));
430   
431    /// <summary>
432    /// Gets/Sets the 'modified' flag.
433    /// </summary>
434    public bool IsModified {
435      get { return (bool)GetValue(IsModifiedProperty); }
436      set { SetValue(IsModifiedProperty, Boxes.Box(value)); }
437    }
438   
439    static void OnIsModifiedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
440    {
441      TextEditor editor = d as TextEditor;
442      if (editor != null) {
443        TextDocument document = editor.Document;
444        if (document != null) {
445          UndoStack undoStack = document.UndoStack;
446          if ((bool)e.NewValue) {
447            if (undoStack.IsOriginalFile)
448              undoStack.DiscardOriginalFileMarker();
449          } else {
450            undoStack.MarkAsOriginalFile();
451          }
452        }
453      }
454    }
455   
456    bool HandleIsOriginalChanged(PropertyChangedEventArgs e)
457    {
458      if (e.PropertyName == "IsOriginalFile") {
459        TextDocument document = this.Document;
460        if (document != null) {
461          SetCurrentValue(IsModifiedProperty, Boxes.Box(!document.UndoStack.IsOriginalFile));
462        }
463        return true;
464      } else {
465        return false;
466      }
467    }
468    #endregion
469   
470    #region ShowLineNumbers
471    /// <summary>
472    /// ShowLineNumbers dependency property.
473    /// </summary>
474    public static readonly DependencyProperty ShowLineNumbersProperty =
475      DependencyProperty.Register("ShowLineNumbers", typeof(bool), typeof(TextEditor),
476                                  new FrameworkPropertyMetadata(Boxes.False, OnShowLineNumbersChanged));
477   
478    /// <summary>
479    /// Specifies whether line numbers are shown on the left to the text view.
480    /// </summary>
481    public bool ShowLineNumbers {
482      get { return (bool)GetValue(ShowLineNumbersProperty); }
483      set { SetValue(ShowLineNumbersProperty, Boxes.Box(value)); }
484    }
485   
486    static void OnShowLineNumbersChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
487    {
488      TextEditor editor = (TextEditor)d;
489      var leftMargins = editor.TextArea.LeftMargins;
490      if ((bool)e.NewValue) {
491        LineNumberMargin lineNumbers = new LineNumberMargin();
492        Line line = (Line)DottedLineMargin.Create();
493        leftMargins.Insert(0, lineNumbers);
494        leftMargins.Insert(1, line);
495        var lineNumbersForeground = new Binding("LineNumbersForeground") { Source = editor };
496        line.SetBinding(Line.StrokeProperty, lineNumbersForeground);
497        lineNumbers.SetBinding(Control.ForegroundProperty, lineNumbersForeground);
498      } else {
499        for (int i = 0; i < leftMargins.Count; i++) {
500          if (leftMargins[i] is LineNumberMargin) {
501            leftMargins.RemoveAt(i);
502            if (i < leftMargins.Count && DottedLineMargin.IsDottedLineMargin(leftMargins[i])) {
503              leftMargins.RemoveAt(i);
504            }
505            break;
506          }
507        }
508      }
509    }
510    #endregion
511   
512    #region LineNumbersForeground
513    /// <summary>
514    /// LineNumbersForeground dependency property.
515    /// </summary>
516    public static readonly DependencyProperty LineNumbersForegroundProperty =
517      DependencyProperty.Register("LineNumbersForeground", typeof(Brush), typeof(TextEditor),
518                                  new FrameworkPropertyMetadata(Brushes.Gray, OnLineNumbersForegroundChanged));
519   
520    /// <summary>
521    /// Gets/sets the Brush used for displaying the foreground color of line numbers.
522    /// </summary>
523    public Brush LineNumbersForeground {
524      get { return (Brush)GetValue(LineNumbersForegroundProperty); }
525      set { SetValue(LineNumbersForegroundProperty, value); }
526    }
527   
528    static void OnLineNumbersForegroundChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
529    {
530      TextEditor editor = (TextEditor)d;
531      var lineNumberMargin = editor.TextArea.LeftMargins.FirstOrDefault(margin => margin is LineNumberMargin) as LineNumberMargin;;
532     
533      if (lineNumberMargin != null) {
534        lineNumberMargin.SetValue(Control.ForegroundProperty, e.NewValue);
535      }
536    }
537    #endregion
538   
539    #region TextBoxBase-like methods
540    /// <summary>
541    /// Appends text to the end of the document.
542    /// </summary>
543    public void AppendText(string textData)
544    {
545      var document = GetDocument();
546      document.Insert(document.TextLength, textData);
547    }
548   
549    /// <summary>
550    /// Begins a group of document changes.
551    /// </summary>
552    public void BeginChange()
553    {
554      GetDocument().BeginUpdate();
555    }
556   
557    /// <summary>
558    /// Copies the current selection to the clipboard.
559    /// </summary>
560    public void Copy()
561    {
562      Execute(ApplicationCommands.Copy);
563    }
564   
565    /// <summary>
566    /// Removes the current selection and copies it to the clipboard.
567    /// </summary>
568    public void Cut()
569    {
570      Execute(ApplicationCommands.Cut);
571    }
572   
573    /// <summary>
574    /// Begins a group of document changes and returns an object that ends the group of document
575    /// changes when it is disposed.
576    /// </summary>
577    public IDisposable DeclareChangeBlock()
578    {
579      return GetDocument().RunUpdate();
580    }
581   
582    /// <summary>
583    /// Ends the current group of document changes.
584    /// </summary>
585    public void EndChange()
586    {
587      GetDocument().EndUpdate();
588    }
589   
590    /// <summary>
591    /// Scrolls one line down.
592    /// </summary>
593    public void LineDown()
594    {
595      if (scrollViewer != null)
596        scrollViewer.LineDown();
597    }
598   
599    /// <summary>
600    /// Scrolls to the left.
601    /// </summary>
602    public void LineLeft()
603    {
604      if (scrollViewer != null)
605        scrollViewer.LineLeft();
606    }
607   
608    /// <summary>
609    /// Scrolls to the right.
610    /// </summary>
611    public void LineRight()
612    {
613      if (scrollViewer != null)
614        scrollViewer.LineRight();
615    }
616   
617    /// <summary>
618    /// Scrolls one line up.
619    /// </summary>
620    public void LineUp()
621    {
622      if (scrollViewer != null)
623        scrollViewer.LineUp();
624    }
625   
626    /// <summary>
627    /// Scrolls one page down.
628    /// </summary>
629    public void PageDown()
630    {
631      if (scrollViewer != null)
632        scrollViewer.PageDown();
633    }
634   
635    /// <summary>
636    /// Scrolls one page up.
637    /// </summary>
638    public void PageUp()
639    {
640      if (scrollViewer != null)
641        scrollViewer.PageUp();
642    }
643   
644    /// <summary>
645    /// Scrolls one page left.
646    /// </summary>
647    public void PageLeft()
648    {
649      if (scrollViewer != null)
650        scrollViewer.PageLeft();
651    }
652   
653    /// <summary>
654    /// Scrolls one page right.
655    /// </summary>
656    public void PageRight()
657    {
658      if (scrollViewer != null)
659        scrollViewer.PageRight();
660    }
661   
662    /// <summary>
663    /// Pastes the clipboard content.
664    /// </summary>
665    public void Paste()
666    {
667      Execute(ApplicationCommands.Paste);
668    }
669   
670    /// <summary>
671    /// Redoes the most recent undone command.
672    /// </summary>
673    /// <returns>True is the redo operation was successful, false is the redo stack is empty.</returns>
674    public bool Redo()
675    {
676      if (CanExecute(ApplicationCommands.Redo)) {
677        Execute(ApplicationCommands.Redo);
678        return true;
679      }
680      return false;
681    }
682   
683    /// <summary>
684    /// Scrolls to the end of the document.
685    /// </summary>
686    public void ScrollToEnd()
687    {
688      ApplyTemplate(); // ensure scrollViewer is created
689      if (scrollViewer != null)
690        scrollViewer.ScrollToEnd();
691    }
692   
693    /// <summary>
694    /// Scrolls to the start of the document.
695    /// </summary>
696    public void ScrollToHome()
697    {
698      ApplyTemplate(); // ensure scrollViewer is created
699      if (scrollViewer != null)
700        scrollViewer.ScrollToHome();
701    }
702   
703    /// <summary>
704    /// Scrolls to the specified position in the document.
705    /// </summary>
706    public void ScrollToHorizontalOffset(double offset)
707    {
708      ApplyTemplate(); // ensure scrollViewer is created
709      if (scrollViewer != null)
710        scrollViewer.ScrollToHorizontalOffset(offset);
711    }
712   
713    /// <summary>
714    /// Scrolls to the specified position in the document.
715    /// </summary>
716    public void ScrollToVerticalOffset(double offset)
717    {
718      ApplyTemplate(); // ensure scrollViewer is created
719      if (scrollViewer != null)
720        scrollViewer.ScrollToVerticalOffset(offset);
721    }
722   
723    /// <summary>
724    /// Selects the entire text.
725    /// </summary>
726    public void SelectAll()
727    {
728      Execute(ApplicationCommands.SelectAll);
729    }
730   
731    /// <summary>
732    /// Undoes the most recent command.
733    /// </summary>
734    /// <returns>True is the undo operation was successful, false is the undo stack is empty.</returns>
735    public bool Undo()
736    {
737      if (CanExecute(ApplicationCommands.Undo)) {
738        Execute(ApplicationCommands.Undo);
739        return true;
740      }
741      return false;
742    }
743   
744    /// <summary>
745    /// Gets if the most recent undone command can be redone.
746    /// </summary>
747    public bool CanRedo {
748      get { return CanExecute(ApplicationCommands.Redo); }
749    }
750   
751    /// <summary>
752    /// Gets if the most recent command can be undone.
753    /// </summary>
754    public bool CanUndo {
755      get { return CanExecute(ApplicationCommands.Undo); }
756    }
757   
758    /// <summary>
759    /// Gets the vertical size of the document.
760    /// </summary>
761    public double ExtentHeight {
762      get {
763        return scrollViewer != null ? scrollViewer.ExtentHeight : 0;
764      }
765    }
766   
767    /// <summary>
768    /// Gets the horizontal size of the current document region.
769    /// </summary>
770    public double ExtentWidth {
771      get {
772        return scrollViewer != null ? scrollViewer.ExtentWidth : 0;
773      }
774    }
775   
776    /// <summary>
777    /// Gets the horizontal size of the viewport.
778    /// </summary>
779    public double ViewportHeight {
780      get {
781        return scrollViewer != null ? scrollViewer.ViewportHeight : 0;
782      }
783    }
784   
785    /// <summary>
786    /// Gets the horizontal size of the viewport.
787    /// </summary>
788    public double ViewportWidth {
789      get {
790        return scrollViewer != null ? scrollViewer.ViewportWidth : 0;
791      }
792    }
793   
794    /// <summary>
795    /// Gets the vertical scroll position.
796    /// </summary>
797    public double VerticalOffset {
798      get {
799        return scrollViewer != null ? scrollViewer.VerticalOffset : 0;
800      }
801    }
802   
803    /// <summary>
804    /// Gets the horizontal scroll position.
805    /// </summary>
806    public double HorizontalOffset {
807      get {
808        return scrollViewer != null ? scrollViewer.HorizontalOffset : 0;
809      }
810    }
811    #endregion
812   
813    #region TextBox methods
814    /// <summary>
815    /// Gets/Sets the selected text.
816    /// </summary>
817    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
818    public string SelectedText {
819      get {
820        TextArea textArea = this.TextArea;
821        // We'll get the text from the whole surrounding segment.
822        // This is done to ensure that SelectedText.Length == SelectionLength.
823        if (textArea != null && textArea.Document != null && !textArea.Selection.IsEmpty)
824          return textArea.Document.GetText(textArea.Selection.SurroundingSegment);
825        else
826          return string.Empty;
827      }
828      set {
829        if (value == null)
830          throw new ArgumentNullException("value");
831        TextArea textArea = this.TextArea;
832        if (textArea != null && textArea.Document != null) {
833          int offset = this.SelectionStart;
834          int length = this.SelectionLength;
835          textArea.Document.Replace(offset, length, value);
836          // keep inserted text selected
837          textArea.Selection = SimpleSelection.Create(textArea, offset, offset + value.Length);
838        }
839      }
840    }
841   
842    /// <summary>
843    /// Gets/sets the caret position.
844    /// </summary>
845    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
846    public int CaretOffset {
847      get {
848        TextArea textArea = this.TextArea;
849        if (textArea != null)
850          return textArea.Caret.Offset;
851        else
852          return 0;
853      }
854      set {
855        TextArea textArea = this.TextArea;
856        if (textArea != null)
857          textArea.Caret.Offset = value;
858      }
859    }
860   
861    /// <summary>
862    /// Gets/sets the start position of the selection.
863    /// </summary>
864    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
865    public int SelectionStart {
866      get {
867        TextArea textArea = this.TextArea;
868        if (textArea != null) {
869          if (textArea.Selection.IsEmpty)
870            return textArea.Caret.Offset;
871          else
872            return textArea.Selection.SurroundingSegment.Offset;
873        } else {
874          return 0;
875        }
876      }
877      set {
878        Select(value, SelectionLength);
879      }
880    }
881   
882    /// <summary>
883    /// Gets/sets the length of the selection.
884    /// </summary>
885    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
886    public int SelectionLength {
887      get {
888        TextArea textArea = this.TextArea;
889        if (textArea != null && !textArea.Selection.IsEmpty)
890          return textArea.Selection.SurroundingSegment.Length;
891        else
892          return 0;
893      }
894      set {
895        Select(SelectionStart, value);
896      }
897    }
898   
899    /// <summary>
900    /// Selects the specified text section.
901    /// </summary>
902    public void Select(int start, int length)
903    {
904      int documentLength = Document != null ? Document.TextLength : 0;
905      if (start < 0 || start > documentLength)
906        throw new ArgumentOutOfRangeException("start", start, "Value must be between 0 and " + documentLength);
907      if (length < 0 || start + length > documentLength)
908        throw new ArgumentOutOfRangeException("length", length, "Value must be between 0 and " + (documentLength - start));
909      textArea.Selection = SimpleSelection.Create(textArea, start, start + length);
910      textArea.Caret.Offset = start + length;
911    }
912   
913    /// <summary>
914    /// Gets the number of lines in the document.
915    /// </summary>
916    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
917    public int LineCount {
918      get {
919        TextDocument document = this.Document;
920        if (document != null)
921          return document.LineCount;
922        else
923          return 1;
924      }
925    }
926   
927    /// <summary>
928    /// Clears the text.
929    /// </summary>
930    public void Clear()
931    {
932      this.Text = string.Empty;
933    }
934    #endregion
935   
936    #region Loading from stream
937    /// <summary>
938    /// Loads the text from the stream, auto-detecting the encoding.
939    /// </summary>
940    /// <remarks>
941    /// This method sets <see cref="IsModified"/> to false.
942    /// </remarks>
943    public void Load(Stream stream)
944    {
945      using (StreamReader reader = FileReader.OpenStream(stream, this.Encoding ?? Encoding.UTF8)) {
946        this.Text = reader.ReadToEnd();
947        SetCurrentValue(EncodingProperty, reader.CurrentEncoding); // assign encoding after ReadToEnd() so that the StreamReader can autodetect the encoding
948      }
949      SetCurrentValue(IsModifiedProperty, Boxes.False);
950    }
951   
952    /// <summary>
953    /// Loads the text from the stream, auto-detecting the encoding.
954    /// </summary>
955    public void Load(string fileName)
956    {
957      if (fileName == null)
958        throw new ArgumentNullException("fileName");
959      using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read)) {
960        Load(fs);
961      }
962    }
963   
964    /// <summary>
965    /// Encoding dependency property.
966    /// </summary>
967    public static readonly DependencyProperty EncodingProperty =
968      DependencyProperty.Register("Encoding", typeof(Encoding), typeof(TextEditor));
969   
970    /// <summary>
971    /// Gets/sets the encoding used when the file is saved.
972    /// </summary>
973    /// <remarks>
974    /// The <see cref="Load(Stream)"/> method autodetects the encoding of the file and sets this property accordingly.
975    /// The <see cref="Save(Stream)"/> method uses the encoding specified in this property.
976    /// </remarks>
977    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
978    public Encoding Encoding {
979      get { return (Encoding)GetValue(EncodingProperty); }
980      set { SetValue(EncodingProperty, value); }
981    }
982   
983    /// <summary>
984    /// Saves the text to the stream.
985    /// </summary>
986    /// <remarks>
987    /// This method sets <see cref="IsModified"/> to false.
988    /// </remarks>
989    public void Save(Stream stream)
990    {
991      if (stream == null)
992        throw new ArgumentNullException("stream");
993      var encoding = this.Encoding;
994      var document = this.Document;
995      StreamWriter writer = encoding != null ? new StreamWriter(stream, encoding) : new StreamWriter(stream);
996      if (document != null)
997        document.WriteTextTo(writer);
998      writer.Flush();
999      // do not close the stream
1000      SetCurrentValue(IsModifiedProperty, Boxes.False);
1001    }
1002   
1003    /// <summary>
1004    /// Saves the text to the file.
1005    /// </summary>
1006    public void Save(string fileName)
1007    {
1008      if (fileName == null)
1009        throw new ArgumentNullException("fileName");
1010      using (FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None)) {
1011        Save(fs);
1012      }
1013    }
1014    #endregion
1015   
1016    #region MouseHover events
1017    /// <summary>
1018    /// The PreviewMouseHover event.
1019    /// </summary>
1020    public static readonly RoutedEvent PreviewMouseHoverEvent =
1021      TextView.PreviewMouseHoverEvent.AddOwner(typeof(TextEditor));
1022   
1023    /// <summary>
1024    /// The MouseHover event.
1025    /// </summary>
1026    public static readonly RoutedEvent MouseHoverEvent =
1027      TextView.MouseHoverEvent.AddOwner(typeof(TextEditor));
1028   
1029   
1030    /// <summary>
1031    /// The PreviewMouseHoverStopped event.
1032    /// </summary>
1033    public static readonly RoutedEvent PreviewMouseHoverStoppedEvent =
1034      TextView.PreviewMouseHoverStoppedEvent.AddOwner(typeof(TextEditor));
1035   
1036    /// <summary>
1037    /// The MouseHoverStopped event.
1038    /// </summary>
1039    public static readonly RoutedEvent MouseHoverStoppedEvent =
1040      TextView.MouseHoverStoppedEvent.AddOwner(typeof(TextEditor));
1041   
1042   
1043    /// <summary>
1044    /// Occurs when the mouse has hovered over a fixed location for some time.
1045    /// </summary>
1046    public event MouseEventHandler PreviewMouseHover {
1047      add { AddHandler(PreviewMouseHoverEvent, value); }
1048      remove { RemoveHandler(PreviewMouseHoverEvent, value); }
1049    }
1050   
1051    /// <summary>
1052    /// Occurs when the mouse has hovered over a fixed location for some time.
1053    /// </summary>
1054    public event MouseEventHandler MouseHover {
1055      add { AddHandler(MouseHoverEvent, value); }
1056      remove { RemoveHandler(MouseHoverEvent, value); }
1057    }
1058   
1059    /// <summary>
1060    /// Occurs when the mouse had previously hovered but now started moving again.
1061    /// </summary>
1062    public event MouseEventHandler PreviewMouseHoverStopped {
1063      add { AddHandler(PreviewMouseHoverStoppedEvent, value); }
1064      remove { RemoveHandler(PreviewMouseHoverStoppedEvent, value); }
1065    }
1066   
1067    /// <summary>
1068    /// Occurs when the mouse had previously hovered but now started moving again.
1069    /// </summary>
1070    public event MouseEventHandler MouseHoverStopped {
1071      add { AddHandler(MouseHoverStoppedEvent, value); }
1072      remove { RemoveHandler(MouseHoverStoppedEvent, value); }
1073    }
1074    #endregion
1075   
1076    #region ScrollBarVisibility
1077    /// <summary>
1078    /// Dependency property for <see cref="HorizontalScrollBarVisibility"/>
1079    /// </summary>
1080    public static readonly DependencyProperty HorizontalScrollBarVisibilityProperty = ScrollViewer.HorizontalScrollBarVisibilityProperty.AddOwner(typeof(TextEditor), new FrameworkPropertyMetadata(ScrollBarVisibility.Visible));
1081   
1082    /// <summary>
1083    /// Gets/Sets the horizontal scroll bar visibility.
1084    /// </summary>
1085    public ScrollBarVisibility HorizontalScrollBarVisibility {
1086      get { return (ScrollBarVisibility)GetValue(HorizontalScrollBarVisibilityProperty); }
1087      set { SetValue(HorizontalScrollBarVisibilityProperty, value); }
1088    }
1089   
1090    /// <summary>
1091    /// Dependency property for <see cref="VerticalScrollBarVisibility"/>
1092    /// </summary>
1093    public static readonly DependencyProperty VerticalScrollBarVisibilityProperty = ScrollViewer.VerticalScrollBarVisibilityProperty.AddOwner(typeof(TextEditor), new FrameworkPropertyMetadata(ScrollBarVisibility.Visible));
1094   
1095    /// <summary>
1096    /// Gets/Sets the vertical scroll bar visibility.
1097    /// </summary>
1098    public ScrollBarVisibility VerticalScrollBarVisibility {
1099      get { return (ScrollBarVisibility)GetValue(VerticalScrollBarVisibilityProperty); }
1100      set { SetValue(VerticalScrollBarVisibilityProperty, value); }
1101    }
1102    #endregion
1103   
1104    object IServiceProvider.GetService(Type serviceType)
1105    {
1106      return textArea.GetService(serviceType);
1107    }
1108   
1109    /// <summary>
1110    /// Gets the text view position from a point inside the editor.
1111    /// </summary>
1112    /// <param name="point">The position, relative to top left
1113    /// corner of TextEditor control</param>
1114    /// <returns>The text view position, or null if the point is outside the document.</returns>
1115    public TextViewPosition? GetPositionFromPoint(Point point)
1116    {
1117      if (this.Document == null)
1118        return null;
1119      TextView textView = this.TextArea.TextView;
1120      return textView.GetPosition(TranslatePoint(point, textView) + textView.ScrollOffset);
1121    }
1122   
1123    /// <summary>
1124    /// Scrolls to the specified line.
1125    /// This method requires that the TextEditor was already assigned a size (WPF layout must have run prior).
1126    /// </summary>
1127    public void ScrollToLine(int line)
1128    {
1129      ScrollTo(line, -1);
1130    }
1131   
1132    /// <summary>
1133    /// Scrolls to the specified line/column.
1134    /// This method requires that the TextEditor was already assigned a size (WPF layout must have run prior).
1135    /// </summary>
1136    public void ScrollTo(int line, int column)
1137    {
1138      const double MinimumScrollPercentage = 0.3;
1139     
1140      TextView textView = textArea.TextView;
1141      TextDocument document = textView.Document;
1142      if (scrollViewer != null && document != null) {
1143        if (line < 1)
1144          line = 1;
1145        if (line > document.LineCount)
1146          line = document.LineCount;
1147       
1148        IScrollInfo scrollInfo = textView;
1149        if (!scrollInfo.CanHorizontallyScroll) {
1150          // Word wrap is enabled. Ensure that we have up-to-date info about line height so that we scroll
1151          // to the correct position.
1152          // This avoids that the user has to repeat the ScrollTo() call several times when there are very long lines.
1153          VisualLine vl = textView.GetOrConstructVisualLine(document.GetLineByNumber(line));
1154          double remainingHeight = scrollViewer.ViewportHeight / 2;
1155          while (remainingHeight > 0) {
1156            DocumentLine prevLine = vl.FirstDocumentLine.PreviousLine;
1157            if (prevLine == null)
1158              break;
1159            vl = textView.GetOrConstructVisualLine(prevLine);
1160            remainingHeight -= vl.Height;
1161          }
1162        }
1163       
1164        Point p = textArea.TextView.GetVisualPosition(new TextViewPosition(line, Math.Max(1, column)), VisualYPosition.LineMiddle);
1165        double verticalPos = p.Y - scrollViewer.ViewportHeight / 2;
1166        if (Math.Abs(verticalPos - scrollViewer.VerticalOffset) > MinimumScrollPercentage * scrollViewer.ViewportHeight) {
1167          scrollViewer.ScrollToVerticalOffset(Math.Max(0, verticalPos));
1168        }
1169        if (column > 0) {
1170          if (p.X > scrollViewer.ViewportWidth - Caret.MinimumDistanceToViewBorder * 2) {
1171            double horizontalPos = Math.Max(0, p.X - scrollViewer.ViewportWidth / 2);
1172            if (Math.Abs(horizontalPos - scrollViewer.HorizontalOffset) > MinimumScrollPercentage * scrollViewer.ViewportWidth) {
1173              scrollViewer.ScrollToHorizontalOffset(horizontalPos);
1174            }
1175          } else {
1176            scrollViewer.ScrollToHorizontalOffset(0);
1177          }
1178        }
1179      }
1180    }
1181  }
1182}
Note: See TracBrowser for help on using the repository browser.