//
//
//
//
// $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;
}
}