511 lines
12 KiB
C#
511 lines
12 KiB
C#
// <file>
|
|
// <copyright see="prj:///doc/copyright.txt"/>
|
|
// <license see="prj:///doc/license.txt"/>
|
|
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
|
// <version>$Revision$</version>
|
|
// </file>
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Drawing;
|
|
using System.Diagnostics;
|
|
using System.Runtime.InteropServices;
|
|
|
|
using ICSharpCode.TextEditor.Document;
|
|
|
|
namespace ICSharpCode.TextEditor
|
|
{
|
|
/// <summary>
|
|
/// In this enumeration are all caret modes listed.
|
|
/// </summary>
|
|
public enum CaretMode {
|
|
/// <summary>
|
|
/// If the caret is in insert mode typed characters will be
|
|
/// inserted at the caret position
|
|
/// </summary>
|
|
InsertMode,
|
|
|
|
/// <summary>
|
|
/// If the caret is in overwirte mode typed characters will
|
|
/// overwrite the character at the caret position
|
|
/// </summary>
|
|
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;
|
|
|
|
/// <value>
|
|
/// The 'prefered' xPos in which the caret moves, when it is moved
|
|
/// up/down. Measured in pixels, not in characters!
|
|
/// </value>
|
|
public int DesiredColumn {
|
|
get {
|
|
return desiredXPos;
|
|
}
|
|
set {
|
|
desiredXPos = value;
|
|
}
|
|
}
|
|
|
|
/// <value>
|
|
/// The current caret mode.
|
|
/// </value>
|
|
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);
|
|
}
|
|
|
|
/// <remarks>
|
|
/// If the caret position is outside the document text bounds
|
|
/// it is set to the correct position by calling ValidateCaretPos.
|
|
/// </remarks>
|
|
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<FoldMarker> 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();
|
|
}
|
|
|
|
/// <remarks>
|
|
/// Is called each time the caret is moved.
|
|
/// </remarks>
|
|
public event EventHandler PositionChanged;
|
|
|
|
/// <remarks>
|
|
/// Is called each time the CaretMode has changed.
|
|
/// </remarks>
|
|
public event EventHandler CaretModeChanged;
|
|
}
|
|
}
|