// // // // // $Revision$ // using System; using System.Collections.Generic; using System.Drawing; using System.Diagnostics; using System.Runtime.InteropServices; using ICSharpCode.TextEditor.Document; namespace ICSharpCode.TextEditor { /// /// In this enumeration are all caret modes listed. /// public enum CaretMode { /// /// If the caret is in insert mode typed characters will be /// inserted at the caret position /// InsertMode, /// /// If the caret is in overwirte mode typed characters will /// overwrite the character at the caret position /// OverwriteMode } public class Caret : System.IDisposable { int line = 0; int column = 0; int desiredXPos = 0; CaretMode caretMode; static bool caretCreated = false; bool hidden = true; TextArea textArea; Point currentPos = new Point(-1, -1); Ime ime = null; CaretImplementation caretImplementation; /// /// The 'prefered' xPos in which the caret moves, when it is moved /// up/down. Measured in pixels, not in characters! /// public int DesiredColumn { get { return desiredXPos; } set { desiredXPos = value; } } /// /// The current caret mode. /// public CaretMode CaretMode { get { return caretMode; } set { caretMode = value; OnCaretModeChanged(EventArgs.Empty); } } public int Line { get { return line; } set { line = value; ValidateCaretPos(); UpdateCaretPosition(); OnPositionChanged(EventArgs.Empty); } } public int Column { get { return column; } set { column = value; ValidateCaretPos(); UpdateCaretPosition(); OnPositionChanged(EventArgs.Empty); } } public TextLocation Position { get { return new TextLocation(column, line); } set { line = value.Y; column = value.X; ValidateCaretPos(); UpdateCaretPosition(); OnPositionChanged(EventArgs.Empty); } } public int Offset { get { return textArea.Document.PositionToOffset(Position); } } public Caret(TextArea textArea) { this.textArea = textArea; textArea.GotFocus += new EventHandler(GotFocus); textArea.LostFocus += new EventHandler(LostFocus); if (Environment.OSVersion.Platform == PlatformID.Unix) caretImplementation = new ManagedCaret(this); else caretImplementation = new Win32Caret(this); } public void Dispose() { textArea.GotFocus -= new EventHandler(GotFocus); textArea.LostFocus -= new EventHandler(LostFocus); textArea = null; caretImplementation.Dispose(); } public TextLocation ValidatePosition(TextLocation pos) { int line = Math.Max(0, Math.Min(textArea.Document.TotalNumberOfLines - 1, pos.Y)); int column = Math.Max(0, pos.X); if (column == int.MaxValue || !textArea.TextEditorProperties.AllowCaretBeyondEOL) { LineSegment lineSegment = textArea.Document.GetLineSegment(line); column = Math.Min(column, lineSegment.Length); } return new TextLocation(column, line); } /// /// If the caret position is outside the document text bounds /// it is set to the correct position by calling ValidateCaretPos. /// public void ValidateCaretPos() { line = Math.Max(0, Math.Min(textArea.Document.TotalNumberOfLines - 1, line)); column = Math.Max(0, column); if (column == int.MaxValue || !textArea.TextEditorProperties.AllowCaretBeyondEOL) { LineSegment lineSegment = textArea.Document.GetLineSegment(line); column = Math.Min(column, lineSegment.Length); } } void CreateCaret() { while (!caretCreated) { switch (caretMode) { case CaretMode.InsertMode: caretCreated = caretImplementation.Create(2, textArea.TextView.FontHeight); break; case CaretMode.OverwriteMode: caretCreated = caretImplementation.Create((int)textArea.TextView.SpaceWidth, textArea.TextView.FontHeight); break; } } if (currentPos.X < 0) { ValidateCaretPos(); currentPos = ScreenPosition; } caretImplementation.SetPosition(currentPos.X, currentPos.Y); caretImplementation.Show(); } public void RecreateCaret() { Log("RecreateCaret"); DisposeCaret(); if (!hidden) { CreateCaret(); } } void DisposeCaret() { if (caretCreated) { caretCreated = false; caretImplementation.Hide(); caretImplementation.Destroy(); } } void GotFocus(object sender, EventArgs e) { Log("GotFocus, IsInUpdate=" + textArea.MotherTextEditorControl.IsInUpdate); hidden = false; if (!textArea.MotherTextEditorControl.IsInUpdate) { CreateCaret(); UpdateCaretPosition(); } } void LostFocus(object sender, EventArgs e) { Log("LostFocus"); hidden = true; DisposeCaret(); } public Point ScreenPosition { get { int xpos = textArea.TextView.GetDrawingXPos(this.line, this.column); return new Point(textArea.TextView.DrawingPosition.X + xpos, textArea.TextView.DrawingPosition.Y + (textArea.Document.GetVisibleLine(this.line)) * textArea.TextView.FontHeight - textArea.TextView.TextArea.VirtualTop.Y); } } int oldLine = -1; bool outstandingUpdate; internal void OnEndUpdate() { if (outstandingUpdate) UpdateCaretPosition(); } void PaintCaretLine(Graphics g) { if (!textArea.Document.TextEditorProperties.CaretLine) return; HighlightColor caretLineColor = textArea.Document.HighlightingStrategy.GetColorFor("CaretLine"); g.DrawLine(BrushRegistry.GetDotPen(caretLineColor.Color), currentPos.X, 0, currentPos.X, textArea.DisplayRectangle.Height); } public void UpdateCaretPosition() { Log("UpdateCaretPosition"); if (textArea.TextEditorProperties.CaretLine) { textArea.Invalidate(); } else { if (caretImplementation.RequireRedrawOnPositionChange) { textArea.UpdateLine(oldLine); if (line != oldLine) textArea.UpdateLine(line); } else { if (textArea.MotherTextAreaControl.TextEditorProperties.LineViewerStyle == LineViewerStyle.FullRow && oldLine != line) { textArea.UpdateLine(oldLine); textArea.UpdateLine(line); } } } oldLine = line; if (hidden || textArea.MotherTextEditorControl.IsInUpdate) { outstandingUpdate = true; return; } else { outstandingUpdate = false; } ValidateCaretPos(); int lineNr = this.line; int xpos = textArea.TextView.GetDrawingXPos(lineNr, this.column); //LineSegment lineSegment = textArea.Document.GetLineSegment(lineNr); Point pos = ScreenPosition; if (xpos >= 0) { CreateCaret(); bool success = caretImplementation.SetPosition(pos.X, pos.Y); if (!success) { caretImplementation.Destroy(); caretCreated = false; UpdateCaretPosition(); } } else { caretImplementation.Destroy(); } // set the input method editor location if (ime == null) { ime = new Ime(textArea.Handle, textArea.Document.TextEditorProperties.Font); } else { ime.HWnd = textArea.Handle; ime.Font = textArea.Document.TextEditorProperties.Font; } ime.SetIMEWindowLocation(pos.X, pos.Y); currentPos = pos; } [Conditional("DEBUG")] static void Log(string text) { //Console.WriteLine(text); } #region Caret implementation internal void PaintCaret(Graphics g) { caretImplementation.PaintCaret(g); PaintCaretLine(g); } abstract class CaretImplementation : IDisposable { public bool RequireRedrawOnPositionChange; public abstract bool Create(int width, int height); public abstract void Hide(); public abstract void Show(); public abstract bool SetPosition(int x, int y); public abstract void PaintCaret(Graphics g); public abstract void Destroy(); public virtual void Dispose() { Destroy(); } } class ManagedCaret : CaretImplementation { System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer { Interval = 300 }; bool visible; bool blink = true; int x, y, width, height; TextArea textArea; Caret parentCaret; public ManagedCaret(Caret caret) { base.RequireRedrawOnPositionChange = true; this.textArea = caret.textArea; this.parentCaret = caret; timer.Tick += CaretTimerTick; } void CaretTimerTick(object sender, EventArgs e) { blink = !blink; if (visible) textArea.UpdateLine(parentCaret.Line); } public override bool Create(int width, int height) { this.visible = true; this.width = width - 2; this.height = height; timer.Enabled = true; return true; } public override void Hide() { visible = false; } public override void Show() { visible = true; } public override bool SetPosition(int x, int y) { this.x = x - 1; this.y = y; return true; } public override void PaintCaret(Graphics g) { if (visible && blink) g.DrawRectangle(Pens.Gray, x, y, width, height); } public override void Destroy() { visible = false; timer.Enabled = false; } public override void Dispose() { base.Dispose(); timer.Dispose(); } } class Win32Caret : CaretImplementation { [DllImport("User32.dll")] static extern bool CreateCaret(IntPtr hWnd, int hBitmap, int nWidth, int nHeight); [DllImport("User32.dll")] static extern bool SetCaretPos(int x, int y); [DllImport("User32.dll")] static extern bool DestroyCaret(); [DllImport("User32.dll")] static extern bool ShowCaret(IntPtr hWnd); [DllImport("User32.dll")] static extern bool HideCaret(IntPtr hWnd); TextArea textArea; public Win32Caret(Caret caret) { this.textArea = caret.textArea; } public override bool Create(int width, int height) { return CreateCaret(textArea.Handle, 0, width, height); } public override void Hide() { HideCaret(textArea.Handle); } public override void Show() { ShowCaret(textArea.Handle); } public override bool SetPosition(int x, int y) { return SetCaretPos(x, y); } public override void PaintCaret(Graphics g) { } public override void Destroy() { DestroyCaret(); } } #endregion bool firePositionChangedAfterUpdateEnd; void FirePositionChangedAfterUpdateEnd(object sender, EventArgs e) { OnPositionChanged(EventArgs.Empty); } protected virtual void OnPositionChanged(EventArgs e) { if (this.textArea.MotherTextEditorControl.IsInUpdate) { if (firePositionChangedAfterUpdateEnd == false) { firePositionChangedAfterUpdateEnd = true; this.textArea.Document.UpdateCommited += FirePositionChangedAfterUpdateEnd; } return; } else if (firePositionChangedAfterUpdateEnd) { this.textArea.Document.UpdateCommited -= FirePositionChangedAfterUpdateEnd; firePositionChangedAfterUpdateEnd = false; } List foldings = textArea.Document.FoldingManager.GetFoldingsFromPosition(line, column); bool shouldUpdate = false; foreach (FoldMarker foldMarker in foldings) { shouldUpdate |= foldMarker.IsFolded; foldMarker.IsFolded = false; } if (shouldUpdate) { textArea.Document.FoldingManager.NotifyFoldingsChanged(EventArgs.Empty); } if (PositionChanged != null) { PositionChanged(this, e); } textArea.ScrollToCaret(); } protected virtual void OnCaretModeChanged(EventArgs e) { if (CaretModeChanged != null) { CaretModeChanged(this, e); } caretImplementation.Hide(); caretImplementation.Destroy(); caretCreated = false; CreateCaret(); caretImplementation.Show(); } /// /// Is called each time the caret is moved. /// public event EventHandler PositionChanged; /// /// Is called each time the CaretMode has changed. /// public event EventHandler CaretModeChanged; } }