// // 2002-2005 AlphaSierraPapa // GNU General Public License // // $Revision$ // using System; using System.Collections; using System.IO; using System.ComponentModel; using System.Drawing; using System.Threading; using System.Drawing.Text; using System.Drawing.Drawing2D; using System.Drawing.Printing; using System.Diagnostics; using System.Windows.Forms; using System.Runtime.Remoting; using System.Runtime.InteropServices; using System.Text; using System.Xml; using ICSharpCode.TextEditor.Actions; using ICSharpCode.TextEditor.Document; using ICSharpCode.TextEditor.Gui.CompletionWindow; namespace ICSharpCode.TextEditor { public delegate bool KeyEventHandler(char ch); public delegate bool DialogKeyProcessor(Keys keyData); /// /// This class paints the textarea. /// [ToolboxItem(false)] public class TextArea : UserControl { public static bool HiddenMouseCursor = false; Point virtualTop = new Point(0, 0); TextAreaControl motherTextAreaControl; TextEditorControl motherTextEditorControl; ArrayList bracketshemes = new ArrayList(); TextAreaClipboardHandler textAreaClipboardHandler; bool autoClearSelection = false; ArrayList leftMargins = new ArrayList(); ArrayList topMargins = new ArrayList(); TextView textView; GutterMargin gutterMargin; FoldMargin foldMargin; IconBarMargin iconBarMargin; SelectionManager selectionManager; Caret caret; public TextEditorControl MotherTextEditorControl { get { return motherTextEditorControl; } } public TextAreaControl MotherTextAreaControl { get { return motherTextAreaControl; } } public SelectionManager SelectionManager { get { return selectionManager; } } public Caret Caret { get { return caret; } } public TextView TextView { get { return textView; } } public GutterMargin GutterMargin { get { return gutterMargin; } } public FoldMargin FoldMargin { get { return foldMargin; } } public IconBarMargin IconBarMargin { get { return iconBarMargin; } } public Encoding Encoding { get { return motherTextEditorControl.Encoding; } } public int MaxVScrollValue { get { return (Document.GetVisibleLine(Document.TotalNumberOfLines - 1) + 1 + TextView.VisibleLineCount * 2 / 3) * TextView.FontHeight; } } public Point VirtualTop { get { return virtualTop; } set { Point newVirtualTop = new Point(value.X, Math.Min(MaxVScrollValue, Math.Max(0, value.Y))); if (virtualTop != newVirtualTop) { virtualTop = newVirtualTop; motherTextAreaControl.VScrollBar.Value = virtualTop.Y; Invalidate(); } } } public bool AutoClearSelection { get { return autoClearSelection; } set { autoClearSelection = value; } } [Browsable(false)] public IDocument Document { get { return motherTextEditorControl.Document; } } public TextAreaClipboardHandler ClipboardHandler { get { return textAreaClipboardHandler; } } public ITextEditorProperties TextEditorProperties { get { return motherTextEditorControl.TextEditorProperties; } } public TextArea(TextEditorControl motherTextEditorControl, TextAreaControl motherTextAreaControl) { this.motherTextAreaControl = motherTextAreaControl; this.motherTextEditorControl = motherTextEditorControl; caret = new Caret(this); selectionManager = new SelectionManager(Document); this.textAreaClipboardHandler = new TextAreaClipboardHandler(this); ResizeRedraw = true; SetStyle(ControlStyles.OptimizedDoubleBuffer, true); // SetStyle(ControlStyles.AllPaintingInWmPaint, true); // SetStyle(ControlStyles.UserPaint, true); SetStyle(ControlStyles.Opaque, false); SetStyle(ControlStyles.ResizeRedraw, true); SetStyle(ControlStyles.Selectable, true); textView = new TextView(this); gutterMargin = new GutterMargin(this); foldMargin = new FoldMargin(this); iconBarMargin = new IconBarMargin(this); leftMargins.AddRange(new AbstractMargin[] { iconBarMargin, gutterMargin, foldMargin }); OptionsChanged(); new TextAreaMouseHandler(this).Attach(); new TextAreaDragDropHandler().Attach(this); bracketshemes.Add(new BracketHighlightingSheme('{', '}')); bracketshemes.Add(new BracketHighlightingSheme('(', ')')); bracketshemes.Add(new BracketHighlightingSheme('[', ']')); caret.PositionChanged += new EventHandler(SearchMatchingBracket); Document.TextContentChanged += new EventHandler(TextContentChanged); Document.FoldingManager.FoldingsChanged += new EventHandler(DocumentFoldingsChanged); } public void UpdateMatchingBracket() { SearchMatchingBracket(null, null); } void TextContentChanged(object sender, EventArgs e) { Caret.Position = new Point(0, 0); SelectionManager.SelectionCollection.Clear(); } void SearchMatchingBracket(object sender, EventArgs e) { if (!TextEditorProperties.ShowMatchingBracket) { textView.Highlight = null; return; } bool changed = false; if (caret.Offset == 0) { if (textView.Highlight != null) { int line = textView.Highlight.OpenBrace.Y; int line2 = textView.Highlight.CloseBrace.Y; textView.Highlight = null; UpdateLine(line); UpdateLine(line2); } return; } foreach (BracketHighlightingSheme bracketsheme in bracketshemes) { // if (bracketsheme.IsInside(textareapainter.Document, textareapainter.Document.Caret.Offset)) { Highlight highlight = bracketsheme.GetHighlight(Document, Caret.Offset - 1); if (textView.Highlight != null && textView.Highlight.OpenBrace.Y >=0 && textView.Highlight.OpenBrace.Y < Document.TotalNumberOfLines) { UpdateLine(textView.Highlight.OpenBrace.Y); } if (textView.Highlight != null && textView.Highlight.CloseBrace.Y >=0 && textView.Highlight.CloseBrace.Y < Document.TotalNumberOfLines) { UpdateLine(textView.Highlight.CloseBrace.Y); } textView.Highlight = highlight; if (highlight != null) { changed = true; break; } // } } if (changed || textView.Highlight != null) { int line = textView.Highlight.OpenBrace.Y; int line2 = textView.Highlight.CloseBrace.Y; if (!changed) { textView.Highlight = null; } UpdateLine(line); UpdateLine(line2); } } public void SetDesiredColumn() { Caret.DesiredColumn = TextView.GetDrawingXPos(Caret.Line, Caret.Column) + (int)(VirtualTop.X * textView.WideSpaceWidth); } public void SetCaretToDesiredColumn(int caretLine) { Caret.Position = textView.GetLogicalColumn(Caret.Line, Caret.DesiredColumn + (int)(VirtualTop.X * textView.WideSpaceWidth)); } public void OptionsChanged() { UpdateMatchingBracket(); textView.OptionsChanged(); caret.RecreateCaret(); caret.UpdateCaretPosition(); Refresh(); } AbstractMargin lastMouseInMargin; protected override void OnMouseLeave(System.EventArgs e) { base.OnMouseLeave(e); this.Cursor = Cursors.Default; if (lastMouseInMargin != null) { lastMouseInMargin.HandleMouseLeave(EventArgs.Empty); lastMouseInMargin = null; } } protected override void OnMouseDown(System.Windows.Forms.MouseEventArgs e) { base.OnMouseDown(e); foreach (AbstractMargin margin in leftMargins) { if (margin.DrawingPosition.Contains(e.X, e.Y)) { margin.HandleMouseDown(new Point(e.X, e.Y), e.Button); } } } // static because the mouse can only be in one text area and we don't want to have // tooltips of text areas from inactive tabs floating around. static DeclarationViewWindow toolTip; static string oldToolTip; bool toolTipSet; public bool ToolTipVisible { get { return toolTipSet; } } public void SetToolTip(string text) { if (toolTip == null) toolTip = new DeclarationViewWindow(this.FindForm()); toolTipSet = (text != null); if (oldToolTip == text) return; if (text == null) { toolTip.Hide(); } else { Point p = Control.MousePosition; p.Offset(3, 3); toolTip.Location = p; toolTip.Description = text; toolTip.Show(); } oldToolTip = text; } protected override void OnMouseMove(System.Windows.Forms.MouseEventArgs e) { toolTipSet = false; base.OnMouseMove(e); if (!toolTipSet) SetToolTip(null); foreach (AbstractMargin margin in leftMargins) { if (margin.DrawingPosition.Contains(e.X, e.Y)) { this.Cursor = margin.Cursor; margin.HandleMouseMove(new Point(e.X, e.Y), e.Button); if (lastMouseInMargin != margin) { if (lastMouseInMargin != null) { lastMouseInMargin.HandleMouseLeave(EventArgs.Empty); } lastMouseInMargin = margin; } return; } } if (lastMouseInMargin != null) { lastMouseInMargin.HandleMouseLeave(EventArgs.Empty); lastMouseInMargin = null; } if (textView.DrawingPosition.Contains(e.X, e.Y)) { this.Cursor = textView.Cursor; return; } this.Cursor = Cursors.Default; } AbstractMargin updateMargin = null; public void Refresh(AbstractMargin margin) { updateMargin = margin; Invalidate(updateMargin.DrawingPosition); Update(); updateMargin = null; } protected override void OnPaintBackground(System.Windows.Forms.PaintEventArgs pevent) { } protected override void OnPaint(System.Windows.Forms.PaintEventArgs e) { int currentXPos = 0; int currentYPos = 0; bool adjustScrollBars = false; Graphics g = e.Graphics; Rectangle clipRectangle = e.ClipRectangle; if (updateMargin != null) { updateMargin.Paint(g, updateMargin.DrawingPosition); // clipRectangle.Intersect(updateMargin.DrawingPosition); } if (clipRectangle.Width <= 0 || clipRectangle.Height <= 0) { return; } if (this.TextEditorProperties.UseAntiAliasedFont) { g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit; } else { g.TextRenderingHint = TextRenderingHint.SystemDefault; } foreach (AbstractMargin margin in leftMargins) { if (margin.IsVisible) { Rectangle marginRectangle = new Rectangle(currentXPos , currentYPos, margin.Size.Width, Height - currentYPos); if (marginRectangle != margin.DrawingPosition) { adjustScrollBars = true; margin.DrawingPosition = marginRectangle; } currentXPos += margin.DrawingPosition.Width; if (clipRectangle.IntersectsWith(marginRectangle)) { marginRectangle.Intersect(clipRectangle); if (!marginRectangle.IsEmpty) { margin.Paint(g, marginRectangle); } } } } Rectangle textViewArea = new Rectangle(currentXPos, currentYPos, Width - currentXPos, Height - currentYPos); if (textViewArea != textView.DrawingPosition) { adjustScrollBars = true; textView.DrawingPosition = textViewArea; } if (clipRectangle.IntersectsWith(textViewArea)) { textViewArea.Intersect(clipRectangle); if (!textViewArea.IsEmpty) { textView.Paint(g, textViewArea); } } if (adjustScrollBars) { this.motherTextAreaControl.AdjustScrollBars(null, null); } Caret.UpdateCaretPosition(); base.OnPaint(e); } void DocumentFoldingsChanged(object sender, EventArgs e) { this.motherTextAreaControl.AdjustScrollBars(null, null); } #region keyboard handling methods /// /// This method is called on each Keypress /// /// /// True, if the key is handled by this method and should NOT be /// inserted in the textarea. /// protected internal virtual bool HandleKeyPress(char ch) { if (KeyEventHandler != null) { return KeyEventHandler(ch); } return false; } public void SimulateKeyPress(char ch) { if (Document.ReadOnly) { return; } if (TextEditorProperties.UseCustomLine == true) { if (SelectionManager.HasSomethingSelected) { if (Document.CustomLineManager.IsReadOnly(SelectionManager.SelectionCollection[0], false)) return; } else if (Document.CustomLineManager.IsReadOnly(Caret.Line, false) == true) return; } if (ch < ' ') { return; } if (!HiddenMouseCursor && TextEditorProperties.HideMouseCursor) { HiddenMouseCursor = true; Cursor.Hide(); } motherTextEditorControl.BeginUpdate(); // INSERT char if (!HandleKeyPress(ch)) { switch (Caret.CaretMode) { case CaretMode.InsertMode: InsertChar(ch); break; case CaretMode.OverwriteMode: ReplaceChar(ch); break; default: Debug.Assert(false, "Unknown caret mode " + Caret.CaretMode); break; } } int currentLineNr = Caret.Line; int delta = Document.FormattingStrategy.FormatLine(this, currentLineNr, Document.PositionToOffset(Caret.Position), ch); motherTextEditorControl.EndUpdate(); if (delta != 0) { // this.motherTextEditorControl.UpdateLines(currentLineNr, currentLineNr); } } protected override void OnKeyPress(System.Windows.Forms.KeyPressEventArgs e) { base.OnKeyPress(e); SimulateKeyPress(e.KeyChar); } /// /// This method executes a dialog key /// public bool ExecuteDialogKey(Keys keyData) { // try, if a dialog key processor was set to use this if (DoProcessDialogKey != null && DoProcessDialogKey(keyData)) { return true; } if (keyData == Keys.Back || keyData == Keys.Delete || keyData == Keys.Enter) { if (TextEditorProperties.UseCustomLine == true) { if (SelectionManager.HasSomethingSelected) { if (Document.CustomLineManager.IsReadOnly(SelectionManager.SelectionCollection[0], false)) return true; } else { int curLineNr = Document.GetLineNumberForOffset(Caret.Offset); if (Document.CustomLineManager.IsReadOnly(curLineNr, false) == true) return true; if ((Caret.Column == 0) && (curLineNr - 1 >= 0) && keyData == Keys.Back && Document.CustomLineManager.IsReadOnly(curLineNr - 1, false) == true) return true; if (keyData == Keys.Delete) { LineSegment curLine = Document.GetLineSegment(curLineNr); if (curLine.Offset + curLine.Length == Caret.Offset && Document.CustomLineManager.IsReadOnly(curLineNr + 1, false) == true) { return true; } } } } } // if not (or the process was 'silent', use the standard edit actions IEditAction action = motherTextEditorControl.GetEditAction(keyData); AutoClearSelection = true; if (action != null) { motherTextEditorControl.BeginUpdate(); try { lock (Document) { action.Execute(this); if (SelectionManager.HasSomethingSelected && AutoClearSelection /*&& caretchanged*/) { if (Document.TextEditorProperties.DocumentSelectionMode == DocumentSelectionMode.Normal) { SelectionManager.ClearSelection(); } } } } finally { motherTextEditorControl.EndUpdate(); Caret.UpdateCaretPosition(); } return true; } return false; } protected override bool ProcessDialogKey(Keys keyData) { return ExecuteDialogKey(keyData) || base.ProcessDialogKey(keyData); } #endregion public void ScrollToCaret() { motherTextAreaControl.ScrollToCaret(); } public void ScrollTo(int line) { motherTextAreaControl.ScrollTo(line); } public void BeginUpdate() { motherTextEditorControl.BeginUpdate(); } public void EndUpdate() { motherTextEditorControl.EndUpdate(); } public bool EnableCutOrPaste { get { if (TextEditorProperties.UseCustomLine == true) { if (SelectionManager.HasSomethingSelected == true) { if (Document.CustomLineManager.IsReadOnly(SelectionManager.SelectionCollection[0], false)) return false; } if (Document.CustomLineManager.IsReadOnly(Caret.Line, false) == true) return false; } return true; } } string GenerateWhitespaceString(int length) { return new String(' ', length); } /// /// Inserts a single character at the caret position /// public void InsertChar(char ch) { bool updating = motherTextEditorControl.IsUpdating; if (!updating) { BeginUpdate(); } // filter out forgein whitespace chars and replace them with standard space (ASCII 32) if (Char.IsWhiteSpace(ch) && ch != '\t' && ch != '\n') { ch = ' '; } bool removedText = false; if (Document.TextEditorProperties.DocumentSelectionMode == DocumentSelectionMode.Normal && SelectionManager.SelectionCollection.Count > 0) { Caret.Position = SelectionManager.SelectionCollection[0].StartPosition; SelectionManager.RemoveSelectedText(); removedText = true; } LineSegment caretLine = Document.GetLineSegment(Caret.Line); int offset = Caret.Offset; // use desired column for generated whitespaces int dc=Math.Min(Caret.Column,Caret.DesiredColumn); if (caretLine.Length < dc && ch != '\n') { Document.Insert(offset, GenerateWhitespaceString(dc - caretLine.Length) + ch); } else { Document.Insert(offset, ch.ToString()); } ++Caret.Column; if (removedText) { Document.UndoStack.UndoLast(2); } if (!updating) { EndUpdate(); UpdateLineToEnd(Caret.Line, Caret.Column); } // I prefer to set NOT the standard column, if you type something // ++Caret.DesiredColumn; } /// /// Inserts a whole string at the caret position /// public void InsertString(string str) { bool updating = motherTextEditorControl.IsUpdating; if (!updating) { BeginUpdate(); } try { bool removedText = false; if (Document.TextEditorProperties.DocumentSelectionMode == DocumentSelectionMode.Normal && SelectionManager.SelectionCollection.Count > 0) { Caret.Position = SelectionManager.SelectionCollection[0].StartPosition; SelectionManager.RemoveSelectedText(); removedText = true; } int oldOffset = Document.PositionToOffset(Caret.Position); int oldLine = Caret.Line; LineSegment caretLine = Document.GetLineSegment(Caret.Line); if (caretLine.Length < Caret.Column) { int whiteSpaceLength = Caret.Column - caretLine.Length; Document.Insert(oldOffset, GenerateWhitespaceString(whiteSpaceLength) + str); Caret.Position = Document.OffsetToPosition(oldOffset + str.Length + whiteSpaceLength); } else { Document.Insert(oldOffset, str); Caret.Position = Document.OffsetToPosition(oldOffset + str.Length); } if (removedText) { Document.UndoStack.UndoLast(2); } if (oldLine != Caret.Line) { UpdateToEnd(oldLine); } else { UpdateLineToEnd(Caret.Line, Caret.Column); } } finally { if (!updating) { EndUpdate(); } } } /// /// Replaces a char at the caret position /// public void ReplaceChar(char ch) { bool updating = motherTextEditorControl.IsUpdating; if (!updating) { BeginUpdate(); } if (Document.TextEditorProperties.DocumentSelectionMode == DocumentSelectionMode.Normal && SelectionManager.SelectionCollection.Count > 0) { Caret.Position = SelectionManager.SelectionCollection[0].StartPosition; SelectionManager.RemoveSelectedText(); } int lineNr = Caret.Line; LineSegment line = Document.GetLineSegment(lineNr); int offset = Document.PositionToOffset(Caret.Position); if (offset < line.Offset + line.Length) { Document.Replace(offset, 1, ch.ToString()); } else { Document.Insert(offset, ch.ToString()); } if (!updating) { EndUpdate(); UpdateLineToEnd(lineNr, Caret.Column); } ++Caret.Column; // ++Caret.DesiredColumn; } protected override void Dispose(bool disposing) { base.Dispose(disposing); if (disposing) { Caret.Dispose(); } } #region UPDATE Commands internal void UpdateLine(int line) { UpdateLines(0, line, line); } internal void UpdateLines(int lineBegin, int lineEnd) { UpdateLines(0, lineBegin, lineEnd); } internal void UpdateToEnd(int lineBegin) { // if (lineBegin > FirstPhysicalLine + textView.VisibleLineCount) { // return; // } lineBegin = Math.Min(lineBegin, FirstPhysicalLine); int y = Math.Max( 0, (int)(lineBegin * textView.FontHeight)); y = Math.Max(0, y - this.virtualTop.Y); Rectangle r = new Rectangle(0, y, Width, Height - y); Invalidate(r); } internal void UpdateLineToEnd(int lineNr, int xStart) { UpdateLines(xStart, lineNr, lineNr); } internal void UpdateLine(int line, int begin, int end) { UpdateLines(line, line); } int FirstPhysicalLine { get { return VirtualTop.Y / textView.FontHeight; } } internal void UpdateLines(int xPos, int lineBegin, int lineEnd) { // if (lineEnd < FirstPhysicalLine || lineBegin > FirstPhysicalLine + textView.VisibleLineCount) { // return; // } InvalidateLines((int)(xPos * this.TextView.WideSpaceWidth), lineBegin, lineEnd); } void InvalidateLines(int xPos, int lineBegin, int lineEnd) { lineBegin = Math.Max(Document.GetVisibleLine(lineBegin), FirstPhysicalLine); lineEnd = Math.Min(Document.GetVisibleLine(lineEnd), FirstPhysicalLine + textView.VisibleLineCount); int y = Math.Max( 0, (int)(lineBegin * textView.FontHeight)); int height = Math.Min(textView.DrawingPosition.Height, (int)((1 + lineEnd - lineBegin) * (textView.FontHeight + 1))); Rectangle r = new Rectangle(0, y - 1 - this.virtualTop.Y, Width, height + 3); Invalidate(r); } #endregion public event KeyEventHandler KeyEventHandler; public event DialogKeyProcessor DoProcessDialogKey; //internal void } }