// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team // // Permission is hereby granted, free of charge, to any person obtaining a copy of this // software and associated documentation files (the "Software"), to deal in the Software // without restriction, including without limitation the rights to use, copy, modify, merge, // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons // to whom the Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all copies or // substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; using System.ComponentModel; using System.IO; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Data; using System.Windows.Input; using System.Windows.Markup; using System.Windows.Media; using System.Windows.Shapes; using System.Windows.Threading; using ICSharpCode.AvalonEdit.Document; using ICSharpCode.AvalonEdit.Editing; using ICSharpCode.AvalonEdit.Highlighting; using ICSharpCode.AvalonEdit.Rendering; using ICSharpCode.AvalonEdit.Utils; namespace ICSharpCode.AvalonEdit { /// /// The text editor control. /// Contains a scrollable TextArea. /// [Localizability(LocalizationCategory.Text), ContentProperty("Text")] public class TextEditor : Control, ITextEditorComponent, IServiceProvider, IWeakEventListener { #region Constructors static TextEditor() { DefaultStyleKeyProperty.OverrideMetadata(typeof(TextEditor), new FrameworkPropertyMetadata(typeof(TextEditor))); FocusableProperty.OverrideMetadata(typeof(TextEditor), new FrameworkPropertyMetadata(Boxes.True)); } /// /// Creates a new TextEditor instance. /// public TextEditor() : this(new TextArea()) { } /// /// Creates a new TextEditor instance. /// protected TextEditor(TextArea textArea) { if (textArea == null) throw new ArgumentNullException("textArea"); this.textArea = textArea; textArea.TextView.Services.AddService(typeof(TextEditor), this); SetCurrentValue(OptionsProperty, textArea.Options); SetCurrentValue(DocumentProperty, new TextDocument()); } #if !DOTNET4 void SetCurrentValue(DependencyProperty property, object value) { SetValue(property, value); } #endif #endregion /// protected override System.Windows.Automation.Peers.AutomationPeer OnCreateAutomationPeer() { return new TextEditorAutomationPeer(this); } /// Forward focus to TextArea. /// protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e) { base.OnGotKeyboardFocus(e); if (e.NewFocus == this) { Keyboard.Focus(this.TextArea); e.Handled = true; } } #region Document property /// /// Document property. /// public static readonly DependencyProperty DocumentProperty = TextView.DocumentProperty.AddOwner( typeof(TextEditor), new FrameworkPropertyMetadata(OnDocumentChanged)); /// /// Gets/Sets the document displayed by the text editor. /// This is a dependency property. /// public TextDocument Document { get { return (TextDocument)GetValue(DocumentProperty); } set { SetValue(DocumentProperty, value); } } /// /// Occurs when the document property has changed. /// public event EventHandler DocumentChanged; /// /// Raises the event. /// protected virtual void OnDocumentChanged(EventArgs e) { if (DocumentChanged != null) { DocumentChanged(this, e); } } static void OnDocumentChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e) { ((TextEditor)dp).OnDocumentChanged((TextDocument)e.OldValue, (TextDocument)e.NewValue); } void OnDocumentChanged(TextDocument oldValue, TextDocument newValue) { if (oldValue != null) { TextDocumentWeakEventManager.TextChanged.RemoveListener(oldValue, this); PropertyChangedEventManager.RemoveListener(oldValue.UndoStack, this, "IsOriginalFile"); } textArea.Document = newValue; if (newValue != null) { TextDocumentWeakEventManager.TextChanged.AddListener(newValue, this); PropertyChangedEventManager.AddListener(newValue.UndoStack, this, "IsOriginalFile"); } OnDocumentChanged(EventArgs.Empty); OnTextChanged(EventArgs.Empty); } #endregion #region Options property /// /// Options property. /// public static readonly DependencyProperty OptionsProperty = TextView.OptionsProperty.AddOwner(typeof(TextEditor), new FrameworkPropertyMetadata(OnOptionsChanged)); /// /// Gets/Sets the options currently used by the text editor. /// public TextEditorOptions Options { get { return (TextEditorOptions)GetValue(OptionsProperty); } set { SetValue(OptionsProperty, value); } } /// /// Occurs when a text editor option has changed. /// public event PropertyChangedEventHandler OptionChanged; /// /// Raises the event. /// protected virtual void OnOptionChanged(PropertyChangedEventArgs e) { if (OptionChanged != null) { OptionChanged(this, e); } } static void OnOptionsChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e) { ((TextEditor)dp).OnOptionsChanged((TextEditorOptions)e.OldValue, (TextEditorOptions)e.NewValue); } void OnOptionsChanged(TextEditorOptions oldValue, TextEditorOptions newValue) { if (oldValue != null) { PropertyChangedWeakEventManager.RemoveListener(oldValue, this); } textArea.Options = newValue; if (newValue != null) { PropertyChangedWeakEventManager.AddListener(newValue, this); } OnOptionChanged(new PropertyChangedEventArgs(null)); } /// protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) { if (managerType == typeof(PropertyChangedWeakEventManager)) { OnOptionChanged((PropertyChangedEventArgs)e); return true; } else if (managerType == typeof(TextDocumentWeakEventManager.TextChanged)) { OnTextChanged(e); return true; } else if (managerType == typeof(PropertyChangedEventManager)) { return HandleIsOriginalChanged((PropertyChangedEventArgs)e); } return false; } bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e) { return ReceiveWeakEvent(managerType, sender, e); } #endregion #region Text property /// /// Gets/Sets the text of the current document. /// [Localizability(LocalizationCategory.Text), DefaultValue("")] public string Text { get { TextDocument document = this.Document; return document != null ? document.Text : string.Empty; } set { TextDocument document = GetDocument(); document.Text = value ?? string.Empty; // after replacing the full text, the caret is positioned at the end of the document // - reset it to the beginning. this.CaretOffset = 0; document.UndoStack.ClearAll(); } } TextDocument GetDocument() { TextDocument document = this.Document; if (document == null) throw ThrowUtil.NoDocumentAssigned(); return document; } /// /// Occurs when the Text property changes. /// public event EventHandler TextChanged; /// /// Raises the event. /// protected virtual void OnTextChanged(EventArgs e) { if (TextChanged != null) { TextChanged(this, e); } } #endregion #region TextArea / ScrollViewer properties readonly TextArea textArea; ScrollViewer scrollViewer; /// /// Is called after the template was applied. /// public override void OnApplyTemplate() { base.OnApplyTemplate(); scrollViewer = (ScrollViewer)Template.FindName("PART_ScrollViewer", this); } /// /// Gets the text area. /// public TextArea TextArea { get { return textArea; } } /// /// Gets the scroll viewer used by the text editor. /// This property can return null if the template has not been applied / does not contain a scroll viewer. /// internal ScrollViewer ScrollViewer { get { return scrollViewer; } } bool CanExecute(RoutedUICommand command) { TextArea textArea = this.TextArea; if (textArea == null) return false; else return command.CanExecute(null, textArea); } void Execute(RoutedUICommand command) { TextArea textArea = this.TextArea; if (textArea != null) command.Execute(null, textArea); } #endregion #region Syntax highlighting /// /// The property. /// public static readonly DependencyProperty SyntaxHighlightingProperty = DependencyProperty.Register("SyntaxHighlighting", typeof(IHighlightingDefinition), typeof(TextEditor), new FrameworkPropertyMetadata(OnSyntaxHighlightingChanged)); /// /// Gets/sets the syntax highlighting definition used to colorize the text. /// public IHighlightingDefinition SyntaxHighlighting { get { return (IHighlightingDefinition)GetValue(SyntaxHighlightingProperty); } set { SetValue(SyntaxHighlightingProperty, value); } } IVisualLineTransformer colorizer; static void OnSyntaxHighlightingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ((TextEditor)d).OnSyntaxHighlightingChanged(e.NewValue as IHighlightingDefinition); } void OnSyntaxHighlightingChanged(IHighlightingDefinition newValue) { if (colorizer != null) { this.TextArea.TextView.LineTransformers.Remove(colorizer); colorizer = null; } if (newValue != null) { colorizer = CreateColorizer(newValue); if (colorizer != null) this.TextArea.TextView.LineTransformers.Insert(0, colorizer); } } /// /// Creates the highlighting colorizer for the specified highlighting definition. /// Allows derived classes to provide custom colorizer implementations for special highlighting definitions. /// /// protected virtual IVisualLineTransformer CreateColorizer(IHighlightingDefinition highlightingDefinition) { if (highlightingDefinition == null) throw new ArgumentNullException("highlightingDefinition"); return new HighlightingColorizer(highlightingDefinition); } #endregion #region WordWrap /// /// Word wrap dependency property. /// public static readonly DependencyProperty WordWrapProperty = DependencyProperty.Register("WordWrap", typeof(bool), typeof(TextEditor), new FrameworkPropertyMetadata(Boxes.False)); /// /// Specifies whether the text editor uses word wrapping. /// /// /// Setting WordWrap=true has the same effect as setting HorizontalScrollBarVisibility=Disabled and will override the /// HorizontalScrollBarVisibility setting. /// public bool WordWrap { get { return (bool)GetValue(WordWrapProperty); } set { SetValue(WordWrapProperty, Boxes.Box(value)); } } #endregion #region IsReadOnly /// /// IsReadOnly dependency property. /// public static readonly DependencyProperty IsReadOnlyProperty = DependencyProperty.Register("IsReadOnly", typeof(bool), typeof(TextEditor), new FrameworkPropertyMetadata(Boxes.False, OnIsReadOnlyChanged)); /// /// Specifies whether the user can change the text editor content. /// Setting this property will replace the /// TextArea.ReadOnlySectionProvider. /// public bool IsReadOnly { get { return (bool)GetValue(IsReadOnlyProperty); } set { SetValue(IsReadOnlyProperty, Boxes.Box(value)); } } static void OnIsReadOnlyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { TextEditor editor = d as TextEditor; if (editor != null) { if ((bool)e.NewValue) editor.TextArea.ReadOnlySectionProvider = ReadOnlySectionDocument.Instance; else editor.TextArea.ReadOnlySectionProvider = NoReadOnlySections.Instance; TextEditorAutomationPeer peer = TextEditorAutomationPeer.FromElement(editor) as TextEditorAutomationPeer; if (peer != null) { peer.RaiseIsReadOnlyChanged((bool)e.OldValue, (bool)e.NewValue); } } } #endregion #region IsModified /// /// Dependency property for /// public static readonly DependencyProperty IsModifiedProperty = DependencyProperty.Register("IsModified", typeof(bool), typeof(TextEditor), new FrameworkPropertyMetadata(Boxes.False, OnIsModifiedChanged)); /// /// Gets/Sets the 'modified' flag. /// public bool IsModified { get { return (bool)GetValue(IsModifiedProperty); } set { SetValue(IsModifiedProperty, Boxes.Box(value)); } } static void OnIsModifiedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { TextEditor editor = d as TextEditor; if (editor != null) { TextDocument document = editor.Document; if (document != null) { UndoStack undoStack = document.UndoStack; if ((bool)e.NewValue) { if (undoStack.IsOriginalFile) undoStack.DiscardOriginalFileMarker(); } else { undoStack.MarkAsOriginalFile(); } } } } bool HandleIsOriginalChanged(PropertyChangedEventArgs e) { if (e.PropertyName == "IsOriginalFile") { TextDocument document = this.Document; if (document != null) { SetCurrentValue(IsModifiedProperty, Boxes.Box(!document.UndoStack.IsOriginalFile)); } return true; } else { return false; } } #endregion #region ShowLineNumbers /// /// ShowLineNumbers dependency property. /// public static readonly DependencyProperty ShowLineNumbersProperty = DependencyProperty.Register("ShowLineNumbers", typeof(bool), typeof(TextEditor), new FrameworkPropertyMetadata(Boxes.False, OnShowLineNumbersChanged)); /// /// Specifies whether line numbers are shown on the left to the text view. /// public bool ShowLineNumbers { get { return (bool)GetValue(ShowLineNumbersProperty); } set { SetValue(ShowLineNumbersProperty, Boxes.Box(value)); } } static void OnShowLineNumbersChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { TextEditor editor = (TextEditor)d; var leftMargins = editor.TextArea.LeftMargins; if ((bool)e.NewValue) { LineNumberMargin lineNumbers = new LineNumberMargin(); Line line = (Line)DottedLineMargin.Create(); leftMargins.Insert(0, lineNumbers); leftMargins.Insert(1, line); var lineNumbersForeground = new Binding("LineNumbersForeground") { Source = editor }; line.SetBinding(Line.StrokeProperty, lineNumbersForeground); lineNumbers.SetBinding(Control.ForegroundProperty, lineNumbersForeground); } else { for (int i = 0; i < leftMargins.Count; i++) { if (leftMargins[i] is LineNumberMargin) { leftMargins.RemoveAt(i); if (i < leftMargins.Count && DottedLineMargin.IsDottedLineMargin(leftMargins[i])) { leftMargins.RemoveAt(i); } break; } } } } #endregion #region LineNumbersForeground /// /// LineNumbersForeground dependency property. /// public static readonly DependencyProperty LineNumbersForegroundProperty = DependencyProperty.Register("LineNumbersForeground", typeof(Brush), typeof(TextEditor), new FrameworkPropertyMetadata(Brushes.Gray, OnLineNumbersForegroundChanged)); /// /// Gets/sets the Brush used for displaying the foreground color of line numbers. /// public Brush LineNumbersForeground { get { return (Brush)GetValue(LineNumbersForegroundProperty); } set { SetValue(LineNumbersForegroundProperty, value); } } static void OnLineNumbersForegroundChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { TextEditor editor = (TextEditor)d; var lineNumberMargin = editor.TextArea.LeftMargins.FirstOrDefault(margin => margin is LineNumberMargin) as LineNumberMargin;; if (lineNumberMargin != null) { lineNumberMargin.SetValue(Control.ForegroundProperty, e.NewValue); } } #endregion #region TextBoxBase-like methods /// /// Appends text to the end of the document. /// public void AppendText(string textData) { var document = GetDocument(); document.Insert(document.TextLength, textData); } /// /// Begins a group of document changes. /// public void BeginChange() { GetDocument().BeginUpdate(); } /// /// Copies the current selection to the clipboard. /// public void Copy() { Execute(ApplicationCommands.Copy); } /// /// Removes the current selection and copies it to the clipboard. /// public void Cut() { Execute(ApplicationCommands.Cut); } /// /// Begins a group of document changes and returns an object that ends the group of document /// changes when it is disposed. /// public IDisposable DeclareChangeBlock() { return GetDocument().RunUpdate(); } /// /// Ends the current group of document changes. /// public void EndChange() { GetDocument().EndUpdate(); } /// /// Scrolls one line down. /// public void LineDown() { if (scrollViewer != null) scrollViewer.LineDown(); } /// /// Scrolls to the left. /// public void LineLeft() { if (scrollViewer != null) scrollViewer.LineLeft(); } /// /// Scrolls to the right. /// public void LineRight() { if (scrollViewer != null) scrollViewer.LineRight(); } /// /// Scrolls one line up. /// public void LineUp() { if (scrollViewer != null) scrollViewer.LineUp(); } /// /// Scrolls one page down. /// public void PageDown() { if (scrollViewer != null) scrollViewer.PageDown(); } /// /// Scrolls one page up. /// public void PageUp() { if (scrollViewer != null) scrollViewer.PageUp(); } /// /// Scrolls one page left. /// public void PageLeft() { if (scrollViewer != null) scrollViewer.PageLeft(); } /// /// Scrolls one page right. /// public void PageRight() { if (scrollViewer != null) scrollViewer.PageRight(); } /// /// Pastes the clipboard content. /// public void Paste() { Execute(ApplicationCommands.Paste); } /// /// Redoes the most recent undone command. /// /// True is the redo operation was successful, false is the redo stack is empty. public bool Redo() { if (CanExecute(ApplicationCommands.Redo)) { Execute(ApplicationCommands.Redo); return true; } return false; } /// /// Scrolls to the end of the document. /// public void ScrollToEnd() { ApplyTemplate(); // ensure scrollViewer is created if (scrollViewer != null) scrollViewer.ScrollToEnd(); } /// /// Scrolls to the start of the document. /// public void ScrollToHome() { ApplyTemplate(); // ensure scrollViewer is created if (scrollViewer != null) scrollViewer.ScrollToHome(); } /// /// Scrolls to the specified position in the document. /// public void ScrollToHorizontalOffset(double offset) { ApplyTemplate(); // ensure scrollViewer is created if (scrollViewer != null) scrollViewer.ScrollToHorizontalOffset(offset); } /// /// Scrolls to the specified position in the document. /// public void ScrollToVerticalOffset(double offset) { ApplyTemplate(); // ensure scrollViewer is created if (scrollViewer != null) scrollViewer.ScrollToVerticalOffset(offset); } /// /// Selects the entire text. /// public void SelectAll() { Execute(ApplicationCommands.SelectAll); } /// /// Undoes the most recent command. /// /// True is the undo operation was successful, false is the undo stack is empty. public bool Undo() { if (CanExecute(ApplicationCommands.Undo)) { Execute(ApplicationCommands.Undo); return true; } return false; } /// /// Gets if the most recent undone command can be redone. /// public bool CanRedo { get { return CanExecute(ApplicationCommands.Redo); } } /// /// Gets if the most recent command can be undone. /// public bool CanUndo { get { return CanExecute(ApplicationCommands.Undo); } } /// /// Gets the vertical size of the document. /// public double ExtentHeight { get { return scrollViewer != null ? scrollViewer.ExtentHeight : 0; } } /// /// Gets the horizontal size of the current document region. /// public double ExtentWidth { get { return scrollViewer != null ? scrollViewer.ExtentWidth : 0; } } /// /// Gets the horizontal size of the viewport. /// public double ViewportHeight { get { return scrollViewer != null ? scrollViewer.ViewportHeight : 0; } } /// /// Gets the horizontal size of the viewport. /// public double ViewportWidth { get { return scrollViewer != null ? scrollViewer.ViewportWidth : 0; } } /// /// Gets the vertical scroll position. /// public double VerticalOffset { get { return scrollViewer != null ? scrollViewer.VerticalOffset : 0; } } /// /// Gets the horizontal scroll position. /// public double HorizontalOffset { get { return scrollViewer != null ? scrollViewer.HorizontalOffset : 0; } } #endregion #region TextBox methods /// /// Gets/Sets the selected text. /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public string SelectedText { get { TextArea textArea = this.TextArea; // We'll get the text from the whole surrounding segment. // This is done to ensure that SelectedText.Length == SelectionLength. if (textArea != null && textArea.Document != null && !textArea.Selection.IsEmpty) return textArea.Document.GetText(textArea.Selection.SurroundingSegment); else return string.Empty; } set { if (value == null) throw new ArgumentNullException("value"); TextArea textArea = this.TextArea; if (textArea != null && textArea.Document != null) { int offset = this.SelectionStart; int length = this.SelectionLength; textArea.Document.Replace(offset, length, value); // keep inserted text selected textArea.Selection = SimpleSelection.Create(textArea, offset, offset + value.Length); } } } /// /// Gets/sets the caret position. /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public int CaretOffset { get { TextArea textArea = this.TextArea; if (textArea != null) return textArea.Caret.Offset; else return 0; } set { TextArea textArea = this.TextArea; if (textArea != null) textArea.Caret.Offset = value; } } /// /// Gets/sets the start position of the selection. /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public int SelectionStart { get { TextArea textArea = this.TextArea; if (textArea != null) { if (textArea.Selection.IsEmpty) return textArea.Caret.Offset; else return textArea.Selection.SurroundingSegment.Offset; } else { return 0; } } set { Select(value, SelectionLength); } } /// /// Gets/sets the length of the selection. /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public int SelectionLength { get { TextArea textArea = this.TextArea; if (textArea != null && !textArea.Selection.IsEmpty) return textArea.Selection.SurroundingSegment.Length; else return 0; } set { Select(SelectionStart, value); } } /// /// Selects the specified text section. /// public void Select(int start, int length) { int documentLength = Document != null ? Document.TextLength : 0; if (start < 0 || start > documentLength) throw new ArgumentOutOfRangeException("start", start, "Value must be between 0 and " + documentLength); if (length < 0 || start + length > documentLength) throw new ArgumentOutOfRangeException("length", length, "Value must be between 0 and " + (documentLength - start)); textArea.Selection = SimpleSelection.Create(textArea, start, start + length); textArea.Caret.Offset = start + length; } /// /// Gets the number of lines in the document. /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public int LineCount { get { TextDocument document = this.Document; if (document != null) return document.LineCount; else return 1; } } /// /// Clears the text. /// public void Clear() { this.Text = string.Empty; } #endregion #region Loading from stream /// /// Loads the text from the stream, auto-detecting the encoding. /// /// /// This method sets to false. /// public void Load(Stream stream) { using (StreamReader reader = FileReader.OpenStream(stream, this.Encoding ?? Encoding.UTF8)) { this.Text = reader.ReadToEnd(); SetCurrentValue(EncodingProperty, reader.CurrentEncoding); // assign encoding after ReadToEnd() so that the StreamReader can autodetect the encoding } SetCurrentValue(IsModifiedProperty, Boxes.False); } /// /// Loads the text from the stream, auto-detecting the encoding. /// public void Load(string fileName) { if (fileName == null) throw new ArgumentNullException("fileName"); using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read)) { Load(fs); } } /// /// Encoding dependency property. /// public static readonly DependencyProperty EncodingProperty = DependencyProperty.Register("Encoding", typeof(Encoding), typeof(TextEditor)); /// /// Gets/sets the encoding used when the file is saved. /// /// /// The method autodetects the encoding of the file and sets this property accordingly. /// The method uses the encoding specified in this property. /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public Encoding Encoding { get { return (Encoding)GetValue(EncodingProperty); } set { SetValue(EncodingProperty, value); } } /// /// Saves the text to the stream. /// /// /// This method sets to false. /// public void Save(Stream stream) { if (stream == null) throw new ArgumentNullException("stream"); var encoding = this.Encoding; var document = this.Document; StreamWriter writer = encoding != null ? new StreamWriter(stream, encoding) : new StreamWriter(stream); if (document != null) document.WriteTextTo(writer); writer.Flush(); // do not close the stream SetCurrentValue(IsModifiedProperty, Boxes.False); } /// /// Saves the text to the file. /// public void Save(string fileName) { if (fileName == null) throw new ArgumentNullException("fileName"); using (FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None)) { Save(fs); } } #endregion #region MouseHover events /// /// The PreviewMouseHover event. /// public static readonly RoutedEvent PreviewMouseHoverEvent = TextView.PreviewMouseHoverEvent.AddOwner(typeof(TextEditor)); /// /// The MouseHover event. /// public static readonly RoutedEvent MouseHoverEvent = TextView.MouseHoverEvent.AddOwner(typeof(TextEditor)); /// /// The PreviewMouseHoverStopped event. /// public static readonly RoutedEvent PreviewMouseHoverStoppedEvent = TextView.PreviewMouseHoverStoppedEvent.AddOwner(typeof(TextEditor)); /// /// The MouseHoverStopped event. /// public static readonly RoutedEvent MouseHoverStoppedEvent = TextView.MouseHoverStoppedEvent.AddOwner(typeof(TextEditor)); /// /// Occurs when the mouse has hovered over a fixed location for some time. /// public event MouseEventHandler PreviewMouseHover { add { AddHandler(PreviewMouseHoverEvent, value); } remove { RemoveHandler(PreviewMouseHoverEvent, value); } } /// /// Occurs when the mouse has hovered over a fixed location for some time. /// public event MouseEventHandler MouseHover { add { AddHandler(MouseHoverEvent, value); } remove { RemoveHandler(MouseHoverEvent, value); } } /// /// Occurs when the mouse had previously hovered but now started moving again. /// public event MouseEventHandler PreviewMouseHoverStopped { add { AddHandler(PreviewMouseHoverStoppedEvent, value); } remove { RemoveHandler(PreviewMouseHoverStoppedEvent, value); } } /// /// Occurs when the mouse had previously hovered but now started moving again. /// public event MouseEventHandler MouseHoverStopped { add { AddHandler(MouseHoverStoppedEvent, value); } remove { RemoveHandler(MouseHoverStoppedEvent, value); } } #endregion #region ScrollBarVisibility /// /// Dependency property for /// public static readonly DependencyProperty HorizontalScrollBarVisibilityProperty = ScrollViewer.HorizontalScrollBarVisibilityProperty.AddOwner(typeof(TextEditor), new FrameworkPropertyMetadata(ScrollBarVisibility.Visible)); /// /// Gets/Sets the horizontal scroll bar visibility. /// public ScrollBarVisibility HorizontalScrollBarVisibility { get { return (ScrollBarVisibility)GetValue(HorizontalScrollBarVisibilityProperty); } set { SetValue(HorizontalScrollBarVisibilityProperty, value); } } /// /// Dependency property for /// public static readonly DependencyProperty VerticalScrollBarVisibilityProperty = ScrollViewer.VerticalScrollBarVisibilityProperty.AddOwner(typeof(TextEditor), new FrameworkPropertyMetadata(ScrollBarVisibility.Visible)); /// /// Gets/Sets the vertical scroll bar visibility. /// public ScrollBarVisibility VerticalScrollBarVisibility { get { return (ScrollBarVisibility)GetValue(VerticalScrollBarVisibilityProperty); } set { SetValue(VerticalScrollBarVisibilityProperty, value); } } #endregion object IServiceProvider.GetService(Type serviceType) { return textArea.GetService(serviceType); } /// /// Gets the text view position from a point inside the editor. /// /// The position, relative to top left /// corner of TextEditor control /// The text view position, or null if the point is outside the document. public TextViewPosition? GetPositionFromPoint(Point point) { if (this.Document == null) return null; TextView textView = this.TextArea.TextView; return textView.GetPosition(TranslatePoint(point, textView) + textView.ScrollOffset); } /// /// Scrolls to the specified line. /// This method requires that the TextEditor was already assigned a size (WPF layout must have run prior). /// public void ScrollToLine(int line) { ScrollTo(line, -1); } /// /// Scrolls to the specified line/column. /// This method requires that the TextEditor was already assigned a size (WPF layout must have run prior). /// public void ScrollTo(int line, int column) { const double MinimumScrollPercentage = 0.3; TextView textView = textArea.TextView; TextDocument document = textView.Document; if (scrollViewer != null && document != null) { if (line < 1) line = 1; if (line > document.LineCount) line = document.LineCount; IScrollInfo scrollInfo = textView; if (!scrollInfo.CanHorizontallyScroll) { // Word wrap is enabled. Ensure that we have up-to-date info about line height so that we scroll // to the correct position. // This avoids that the user has to repeat the ScrollTo() call several times when there are very long lines. VisualLine vl = textView.GetOrConstructVisualLine(document.GetLineByNumber(line)); double remainingHeight = scrollViewer.ViewportHeight / 2; while (remainingHeight > 0) { DocumentLine prevLine = vl.FirstDocumentLine.PreviousLine; if (prevLine == null) break; vl = textView.GetOrConstructVisualLine(prevLine); remainingHeight -= vl.Height; } } Point p = textArea.TextView.GetVisualPosition(new TextViewPosition(line, Math.Max(1, column)), VisualYPosition.LineMiddle); double verticalPos = p.Y - scrollViewer.ViewportHeight / 2; if (Math.Abs(verticalPos - scrollViewer.VerticalOffset) > MinimumScrollPercentage * scrollViewer.ViewportHeight) { scrollViewer.ScrollToVerticalOffset(Math.Max(0, verticalPos)); } if (column > 0) { if (p.X > scrollViewer.ViewportWidth - Caret.MinimumDistanceToViewBorder * 2) { double horizontalPos = Math.Max(0, p.X - scrollViewer.ViewportWidth / 2); if (Math.Abs(horizontalPos - scrollViewer.HorizontalOffset) > MinimumScrollPercentage * scrollViewer.ViewportWidth) { scrollViewer.ScrollToHorizontalOffset(horizontalPos); } } else { scrollViewer.ScrollToHorizontalOffset(0); } } } } } }