first commit

This commit is contained in:
2026-01-07 11:33:05 +08:00
commit fc54ffd43b
215 changed files with 31856 additions and 0 deletions

View File

@@ -0,0 +1,115 @@
// <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.Drawing;
using System.Windows.Forms;
using ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor
{
public delegate void MarginMouseEventHandler(AbstractMargin sender, Point mousepos, MouseButtons mouseButtons);
public delegate void MarginPaintEventHandler(AbstractMargin sender, Graphics g, Rectangle rect);
/// <summary>
/// This class views the line numbers and folding markers.
/// </summary>
public abstract class AbstractMargin
{
Cursor cursor = Cursors.Default;
[CLSCompliant(false)]
protected Rectangle drawingPosition = new Rectangle(0, 0, 0, 0);
[CLSCompliant(false)]
protected TextArea textArea;
public Rectangle DrawingPosition {
get {
return drawingPosition;
}
set {
drawingPosition = value;
}
}
public TextArea TextArea {
get {
return textArea;
}
}
public IDocument Document {
get {
return textArea.Document;
}
}
public ITextEditorProperties TextEditorProperties {
get {
return textArea.Document.TextEditorProperties;
}
}
public virtual Cursor Cursor {
get {
return cursor;
}
set {
cursor = value;
}
}
public virtual Size Size {
get {
return new Size(-1, -1);
}
}
public virtual bool IsVisible {
get {
return true;
}
}
protected AbstractMargin(TextArea textArea)
{
this.textArea = textArea;
}
public virtual void HandleMouseDown(Point mousepos, MouseButtons mouseButtons)
{
if (MouseDown != null) {
MouseDown(this, mousepos, mouseButtons);
}
}
public virtual void HandleMouseMove(Point mousepos, MouseButtons mouseButtons)
{
if (MouseMove != null) {
MouseMove(this, mousepos, mouseButtons);
}
}
public virtual void HandleMouseLeave(EventArgs e)
{
if (MouseLeave != null) {
MouseLeave(this, e);
}
}
public virtual void Paint(Graphics g, Rectangle rect)
{
if (Painted != null) {
Painted(this, g, rect);
}
}
public event MarginPaintEventHandler Painted;
public event MarginMouseEventHandler MouseDown;
public event MarginMouseEventHandler MouseMove;
public event EventHandler MouseLeave;
}
}

View File

@@ -0,0 +1,86 @@
// <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.Drawing;
using ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor
{
public class Highlight
{
public TextLocation OpenBrace { get; set; }
public TextLocation CloseBrace { get; set; }
public Highlight(TextLocation openBrace, TextLocation closeBrace)
{
this.OpenBrace = openBrace;
this.CloseBrace = closeBrace;
}
}
public class BracketHighlightingSheme
{
char opentag;
char closingtag;
public char OpenTag {
get {
return opentag;
}
set {
opentag = value;
}
}
public char ClosingTag {
get {
return closingtag;
}
set {
closingtag = value;
}
}
public BracketHighlightingSheme(char opentag, char closingtag)
{
this.opentag = opentag;
this.closingtag = closingtag;
}
public Highlight GetHighlight(IDocument document, int offset)
{
int searchOffset;
if (document.TextEditorProperties.BracketMatchingStyle == BracketMatchingStyle.After) {
searchOffset = offset;
} else {
searchOffset = offset + 1;
}
char word = document.GetCharAt(Math.Max(0, Math.Min(document.TextLength - 1, searchOffset)));
TextLocation endP = document.OffsetToPosition(searchOffset);
if (word == opentag) {
if (searchOffset < document.TextLength) {
int bracketOffset = TextUtilities.SearchBracketForward(document, searchOffset + 1, opentag, closingtag);
if (bracketOffset >= 0) {
TextLocation p = document.OffsetToPosition(bracketOffset);
return new Highlight(p, endP);
}
}
} else if (word == closingtag) {
if (searchOffset > 0) {
int bracketOffset = TextUtilities.SearchBracketBackward(document, searchOffset - 1, opentag, closingtag);
if (bracketOffset >= 0) {
TextLocation p = document.OffsetToPosition(bracketOffset);
return new Highlight(p, endP);
}
}
}
return null;
}
}
}

View File

@@ -0,0 +1,65 @@
// <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;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
namespace ICSharpCode.TextEditor
{
/// <summary>
/// Contains brushes/pens for the text editor to speed up drawing. Re-Creation of brushes and pens
/// seems too costly.
/// </summary>
public class BrushRegistry
{
static Dictionary<Color, Brush> brushes = new Dictionary<Color, Brush>();
static Dictionary<Color, Pen> pens = new Dictionary<Color, Pen>();
static Dictionary<Color, Pen> dotPens = new Dictionary<Color, Pen>();
public static Brush GetBrush(Color color)
{
lock (brushes) {
Brush brush;
if (!brushes.TryGetValue(color, out brush)) {
brush = new SolidBrush(color);
brushes.Add(color, brush);
}
return brush;
}
}
public static Pen GetPen(Color color)
{
lock (pens) {
Pen pen;
if (!pens.TryGetValue(color, out pen)) {
pen = new Pen(color);
pens.Add(color, pen);
}
return pen;
}
}
static readonly float[] dotPattern = { 1, 1, 1, 1 };
public static Pen GetDotPen(Color color)
{
lock (dotPens) {
Pen pen;
if (!dotPens.TryGetValue(color, out pen)) {
pen = new Pen(color);
pen.DashPattern = dotPattern;
dotPens.Add(color, pen);
}
return pen;
}
}
}
}

View File

@@ -0,0 +1,510 @@
// <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;
}
}

View File

@@ -0,0 +1,214 @@
// <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.Drawing;
using System.Windows.Forms;
namespace ICSharpCode.TextEditor.Gui.CompletionWindow
{
/// <summary>
/// Description of AbstractCompletionWindow.
/// </summary>
public abstract class AbstractCompletionWindow : System.Windows.Forms.Form
{
protected TextEditorControl control;
protected Size drawingSize;
Rectangle workingScreen;
Form parentForm;
protected AbstractCompletionWindow(Form parentForm, TextEditorControl control)
{
workingScreen = Screen.GetWorkingArea(parentForm);
// SetStyle(ControlStyles.Selectable, false);
this.parentForm = parentForm;
this.control = control;
SetLocation();
StartPosition = FormStartPosition.Manual;
FormBorderStyle = FormBorderStyle.None;
ShowInTaskbar = false;
MinimumSize = new Size(1, 1);
Size = new Size(1, 1);
}
protected virtual void SetLocation()
{
TextArea textArea = control.ActiveTextAreaControl.TextArea;
TextLocation caretPos = textArea.Caret.Position;
int xpos = textArea.TextView.GetDrawingXPos(caretPos.Y, caretPos.X);
int rulerHeight = textArea.TextEditorProperties.ShowHorizontalRuler ? textArea.TextView.FontHeight : 0;
Point pos = new Point(textArea.TextView.DrawingPosition.X + xpos,
textArea.TextView.DrawingPosition.Y + (textArea.Document.GetVisibleLine(caretPos.Y)) * textArea.TextView.FontHeight
- textArea.TextView.TextArea.VirtualTop.Y + textArea.TextView.FontHeight + rulerHeight);
Point location = control.ActiveTextAreaControl.PointToScreen(pos);
// set bounds
Rectangle bounds = new Rectangle(location, drawingSize);
if (!workingScreen.Contains(bounds)) {
if (bounds.Right > workingScreen.Right) {
bounds.X = workingScreen.Right - bounds.Width;
}
if (bounds.Left < workingScreen.Left) {
bounds.X = workingScreen.Left;
}
if (bounds.Top < workingScreen.Top) {
bounds.Y = workingScreen.Top;
}
if (bounds.Bottom > workingScreen.Bottom) {
bounds.Y = bounds.Y - bounds.Height - control.ActiveTextAreaControl.TextArea.TextView.FontHeight;
if (bounds.Bottom > workingScreen.Bottom) {
bounds.Y = workingScreen.Bottom - bounds.Height;
}
}
}
Bounds = bounds;
}
protected override CreateParams CreateParams {
get {
CreateParams p = base.CreateParams;
AddShadowToWindow(p);
return p;
}
}
static int shadowStatus;
/// <summary>
/// Adds a shadow to the create params if it is supported by the operating system.
/// </summary>
public static void AddShadowToWindow(CreateParams createParams)
{
if (shadowStatus == 0) {
// Test OS version
shadowStatus = -1; // shadow not supported
if (Environment.OSVersion.Platform == PlatformID.Win32NT) {
Version ver = Environment.OSVersion.Version;
if (ver.Major > 5 || ver.Major == 5 && ver.Minor >= 1) {
shadowStatus = 1;
}
}
}
if (shadowStatus == 1) {
createParams.ClassStyle |= 0x00020000; // set CS_DROPSHADOW
}
}
protected override bool ShowWithoutActivation {
get {
return true;
}
}
protected void ShowCompletionWindow()
{
Owner = parentForm;
Enabled = true;
this.Show();
control.Focus();
if (parentForm != null) {
parentForm.LocationChanged += new EventHandler(this.ParentFormLocationChanged);
}
control.ActiveTextAreaControl.VScrollBar.ValueChanged += new EventHandler(ParentFormLocationChanged);
control.ActiveTextAreaControl.HScrollBar.ValueChanged += new EventHandler(ParentFormLocationChanged);
control.ActiveTextAreaControl.TextArea.DoProcessDialogKey += new DialogKeyProcessor(ProcessTextAreaKey);
control.ActiveTextAreaControl.Caret.PositionChanged += new EventHandler(CaretOffsetChanged);
control.ActiveTextAreaControl.TextArea.LostFocus += new EventHandler(this.TextEditorLostFocus);
control.Resize += new EventHandler(ParentFormLocationChanged);
foreach (Control c in Controls) {
c.MouseMove += ControlMouseMove;
}
}
void ParentFormLocationChanged(object sender, EventArgs e)
{
SetLocation();
}
public virtual bool ProcessKeyEvent(char ch)
{
return false;
}
protected virtual bool ProcessTextAreaKey(Keys keyData)
{
if (!Visible) {
return false;
}
switch (keyData) {
case Keys.Escape:
Close();
return true;
}
return false;
}
protected virtual void CaretOffsetChanged(object sender, EventArgs e)
{
}
protected void TextEditorLostFocus(object sender, EventArgs e)
{
if (!control.ActiveTextAreaControl.TextArea.Focused && !this.ContainsFocus) {
Close();
}
}
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
// take out the inserted methods
parentForm.LocationChanged -= new EventHandler(ParentFormLocationChanged);
foreach (Control c in Controls) {
c.MouseMove -= ControlMouseMove;
}
if (control.ActiveTextAreaControl.VScrollBar != null) {
control.ActiveTextAreaControl.VScrollBar.ValueChanged -= new EventHandler(ParentFormLocationChanged);
}
if (control.ActiveTextAreaControl.HScrollBar != null) {
control.ActiveTextAreaControl.HScrollBar.ValueChanged -= new EventHandler(ParentFormLocationChanged);
}
control.ActiveTextAreaControl.TextArea.LostFocus -= new EventHandler(this.TextEditorLostFocus);
control.ActiveTextAreaControl.Caret.PositionChanged -= new EventHandler(CaretOffsetChanged);
control.ActiveTextAreaControl.TextArea.DoProcessDialogKey -= new DialogKeyProcessor(ProcessTextAreaKey);
control.Resize -= new EventHandler(ParentFormLocationChanged);
Dispose();
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
ControlMouseMove(this, e);
}
/// <summary>
/// Invoked when the mouse moves over this form or any child control.
/// Shows the mouse cursor on the text area if it has been hidden.
/// </summary>
/// <remarks>
/// Derived classes should attach this handler to the MouseMove event
/// of all created controls which are not added to the Controls
/// collection.
/// </remarks>
protected void ControlMouseMove(object sender, MouseEventArgs e)
{
control.ActiveTextAreaControl.TextArea.ShowHiddenCursor(false);
}
}
}

View File

@@ -0,0 +1,293 @@
// <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.Drawing;
using System.Windows.Forms;
namespace ICSharpCode.TextEditor.Gui.CompletionWindow
{
/// <summary>
/// Description of CodeCompletionListView.
/// </summary>
public class CodeCompletionListView : System.Windows.Forms.UserControl
{
ICompletionData[] completionData;
int firstItem = 0;
int selectedItem = -1;
ImageList imageList;
public ImageList ImageList {
get {
return imageList;
}
set {
imageList = value;
}
}
public int FirstItem {
get {
return firstItem;
}
set {
if (firstItem != value) {
firstItem = value;
OnFirstItemChanged(EventArgs.Empty);
}
}
}
public ICompletionData SelectedCompletionData {
get {
if (selectedItem < 0) {
return null;
}
return completionData[selectedItem];
}
}
public int ItemHeight {
get {
return Math.Max(imageList.ImageSize.Height, (int)(Font.Height * 1.25));
}
}
public int MaxVisibleItem {
get {
return Height / ItemHeight;
}
}
public CodeCompletionListView(ICompletionData[] completionData)
{
Array.Sort(completionData, DefaultCompletionData.Compare);
this.completionData = completionData;
// this.KeyDown += new System.Windows.Forms.KeyEventHandler(OnKey);
// SetStyle(ControlStyles.Selectable, false);
// SetStyle(ControlStyles.UserPaint, true);
// SetStyle(ControlStyles.DoubleBuffer, false);
}
public void Close()
{
if (completionData != null) {
Array.Clear(completionData, 0, completionData.Length);
}
base.Dispose();
}
public void SelectIndex(int index)
{
int oldSelectedItem = selectedItem;
int oldFirstItem = firstItem;
index = Math.Max(0, index);
selectedItem = Math.Max(0, Math.Min(completionData.Length - 1, index));
if (selectedItem < firstItem) {
FirstItem = selectedItem;
}
if (firstItem + MaxVisibleItem <= selectedItem) {
FirstItem = selectedItem - MaxVisibleItem + 1;
}
if (oldSelectedItem != selectedItem) {
if (firstItem != oldFirstItem) {
Invalidate();
} else {
int min = Math.Min(selectedItem, oldSelectedItem) - firstItem;
int max = Math.Max(selectedItem, oldSelectedItem) - firstItem;
Invalidate(new Rectangle(0, 1 + min * ItemHeight, Width, (max - min + 1) * ItemHeight));
}
OnSelectedItemChanged(EventArgs.Empty);
}
}
public void CenterViewOn(int index)
{
int oldFirstItem = this.FirstItem;
int firstItem = index - MaxVisibleItem / 2;
if (firstItem < 0)
this.FirstItem = 0;
else if (firstItem >= completionData.Length - MaxVisibleItem)
this.FirstItem = completionData.Length - MaxVisibleItem;
else
this.FirstItem = firstItem;
if (this.FirstItem != oldFirstItem) {
Invalidate();
}
}
public void ClearSelection()
{
if (selectedItem < 0)
return;
int itemNum = selectedItem - firstItem;
selectedItem = -1;
Invalidate(new Rectangle(0, itemNum * ItemHeight, Width, (itemNum + 1) * ItemHeight + 1));
Update();
OnSelectedItemChanged(EventArgs.Empty);
}
public void PageDown()
{
SelectIndex(selectedItem + MaxVisibleItem);
}
public void PageUp()
{
SelectIndex(selectedItem - MaxVisibleItem);
}
public void SelectNextItem()
{
SelectIndex(selectedItem + 1);
}
public void SelectPrevItem()
{
SelectIndex(selectedItem - 1);
}
public void SelectItemWithStart(string startText)
{
if (startText == null || startText.Length == 0) return;
string originalStartText = startText;
startText = startText.ToLower();
int bestIndex = -1;
int bestQuality = -1;
// Qualities: 0 = match start
// 1 = match start case sensitive
// 2 = full match
// 3 = full match case sensitive
double bestPriority = 0;
for (int i = 0; i < completionData.Length; ++i) {
string itemText = completionData[i].Text;
string lowerText = itemText.ToLower();
if (lowerText.StartsWith(startText)) {
double priority = completionData[i].Priority;
int quality;
if (lowerText == startText) {
if (itemText == originalStartText)
quality = 3;
else
quality = 2;
} else if (itemText.StartsWith(originalStartText)) {
quality = 1;
} else {
quality = 0;
}
bool useThisItem;
if (bestQuality < quality) {
useThisItem = true;
} else {
if (bestIndex == selectedItem) {
useThisItem = false;
} else if (i == selectedItem) {
useThisItem = bestQuality == quality;
} else {
useThisItem = bestQuality == quality && bestPriority < priority;
}
}
if (useThisItem) {
bestIndex = i;
bestPriority = priority;
bestQuality = quality;
}
}
}
if (bestIndex < 0) {
ClearSelection();
} else {
if (bestIndex < firstItem || firstItem + MaxVisibleItem <= bestIndex) {
SelectIndex(bestIndex);
CenterViewOn(bestIndex);
} else {
SelectIndex(bestIndex);
}
}
}
protected override void OnPaint(PaintEventArgs pe)
{
float yPos = 1;
float itemHeight = ItemHeight;
// Maintain aspect ratio
int imageWidth = (int)(itemHeight * imageList.ImageSize.Width / imageList.ImageSize.Height);
int curItem = firstItem;
Graphics g = pe.Graphics;
while (curItem < completionData.Length && yPos < Height) {
RectangleF drawingBackground = new RectangleF(1, yPos, Width - 2, itemHeight);
if (drawingBackground.IntersectsWith(pe.ClipRectangle)) {
// draw Background
if (curItem == selectedItem) {
g.FillRectangle(SystemBrushes.Highlight, drawingBackground);
} else {
g.FillRectangle(SystemBrushes.Window, drawingBackground);
}
// draw Icon
int xPos = 0;
if (imageList != null && completionData[curItem].ImageIndex < imageList.Images.Count) {
g.DrawImage(imageList.Images[completionData[curItem].ImageIndex], new RectangleF(1, yPos, imageWidth, itemHeight));
xPos = imageWidth;
}
// draw text
if (curItem == selectedItem) {
g.DrawString(completionData[curItem].Text, Font, SystemBrushes.HighlightText, xPos, yPos);
} else {
g.DrawString(completionData[curItem].Text, Font, SystemBrushes.WindowText, xPos, yPos);
}
}
yPos += itemHeight;
++curItem;
}
g.DrawRectangle(SystemPens.Control, new Rectangle(0, 0, Width - 1, Height - 1));
}
protected override void OnMouseDown(System.Windows.Forms.MouseEventArgs e)
{
float yPos = 1;
int curItem = firstItem;
float itemHeight = ItemHeight;
while (curItem < completionData.Length && yPos < Height) {
RectangleF drawingBackground = new RectangleF(1, yPos, Width - 2, itemHeight);
if (drawingBackground.Contains(e.X, e.Y)) {
SelectIndex(curItem);
break;
}
yPos += itemHeight;
++curItem;
}
}
protected override void OnPaintBackground(PaintEventArgs pe)
{
}
protected virtual void OnSelectedItemChanged(EventArgs e)
{
if (SelectedItemChanged != null) {
SelectedItemChanged(this, e);
}
}
protected virtual void OnFirstItemChanged(EventArgs e)
{
if (FirstItemChanged != null) {
FirstItemChanged(this, e);
}
}
public event EventHandler SelectedItemChanged;
public event EventHandler FirstItemChanged;
}
}

View File

@@ -0,0 +1,364 @@
// <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.Drawing;
using System.Diagnostics;
using System.Windows.Forms;
using ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor.Gui.CompletionWindow
{
public class CodeCompletionWindow : AbstractCompletionWindow
{
ICompletionData[] completionData;
CodeCompletionListView codeCompletionListView;
VScrollBar vScrollBar = new VScrollBar();
ICompletionDataProvider dataProvider;
IDocument document;
bool showDeclarationWindow = true;
bool fixedListViewWidth = true;
const int ScrollbarWidth = 16;
const int MaxListLength = 10;
int startOffset;
int endOffset;
DeclarationViewWindow declarationViewWindow = null;
Rectangle workingScreen;
public static CodeCompletionWindow ShowCompletionWindow(Form parent, TextEditorControl control, string fileName, ICompletionDataProvider completionDataProvider, char firstChar)
{
return ShowCompletionWindow(parent, control, fileName, completionDataProvider, firstChar, true, true);
}
public static CodeCompletionWindow ShowCompletionWindow(Form parent, TextEditorControl control, string fileName, ICompletionDataProvider completionDataProvider, char firstChar, bool showDeclarationWindow, bool fixedListViewWidth)
{
ICompletionData[] completionData = completionDataProvider.GenerateCompletionData(fileName, control.ActiveTextAreaControl.TextArea, firstChar);
if (completionData == null || completionData.Length == 0) {
return null;
}
CodeCompletionWindow codeCompletionWindow = new CodeCompletionWindow(completionDataProvider, completionData, parent, control, showDeclarationWindow, fixedListViewWidth);
codeCompletionWindow.CloseWhenCaretAtBeginning = firstChar == '\0';
codeCompletionWindow.ShowCompletionWindow();
return codeCompletionWindow;
}
CodeCompletionWindow(ICompletionDataProvider completionDataProvider, ICompletionData[] completionData, Form parentForm, TextEditorControl control, bool showDeclarationWindow, bool fixedListViewWidth) : base(parentForm, control)
{
this.dataProvider = completionDataProvider;
this.completionData = completionData;
this.document = control.Document;
this.showDeclarationWindow = showDeclarationWindow;
this.fixedListViewWidth = fixedListViewWidth;
workingScreen = Screen.GetWorkingArea(Location);
startOffset = control.ActiveTextAreaControl.Caret.Offset + 1;
endOffset = startOffset;
if (completionDataProvider.PreSelection != null) {
startOffset -= completionDataProvider.PreSelection.Length + 1;
endOffset--;
}
codeCompletionListView = new CodeCompletionListView(completionData);
codeCompletionListView.ImageList = completionDataProvider.ImageList;
codeCompletionListView.Dock = DockStyle.Fill;
codeCompletionListView.SelectedItemChanged += new EventHandler(CodeCompletionListViewSelectedItemChanged);
codeCompletionListView.DoubleClick += new EventHandler(CodeCompletionListViewDoubleClick);
codeCompletionListView.Click += new EventHandler(CodeCompletionListViewClick);
Controls.Add(codeCompletionListView);
if (completionData.Length > MaxListLength) {
vScrollBar.Dock = DockStyle.Right;
vScrollBar.Minimum = 0;
vScrollBar.Maximum = completionData.Length - 1;
vScrollBar.SmallChange = 1;
vScrollBar.LargeChange = MaxListLength;
codeCompletionListView.FirstItemChanged += new EventHandler(CodeCompletionListViewFirstItemChanged);
Controls.Add(vScrollBar);
}
this.drawingSize = GetListViewSize();
SetLocation();
if (declarationViewWindow == null) {
declarationViewWindow = new DeclarationViewWindow(parentForm);
}
SetDeclarationViewLocation();
declarationViewWindow.ShowDeclarationViewWindow();
declarationViewWindow.MouseMove += ControlMouseMove;
control.Focus();
CodeCompletionListViewSelectedItemChanged(this, EventArgs.Empty);
if (completionDataProvider.DefaultIndex >= 0) {
codeCompletionListView.SelectIndex(completionDataProvider.DefaultIndex);
}
if (completionDataProvider.PreSelection != null) {
CaretOffsetChanged(this, EventArgs.Empty);
}
vScrollBar.ValueChanged += VScrollBarValueChanged;
document.DocumentAboutToBeChanged += DocumentAboutToBeChanged;
}
bool inScrollUpdate;
void CodeCompletionListViewFirstItemChanged(object sender, EventArgs e)
{
if (inScrollUpdate) return;
inScrollUpdate = true;
vScrollBar.Value = Math.Min(vScrollBar.Maximum, codeCompletionListView.FirstItem);
inScrollUpdate = false;
}
void VScrollBarValueChanged(object sender, EventArgs e)
{
if (inScrollUpdate) return;
inScrollUpdate = true;
codeCompletionListView.FirstItem = vScrollBar.Value;
codeCompletionListView.Refresh();
control.ActiveTextAreaControl.TextArea.Focus();
inScrollUpdate = false;
}
void SetDeclarationViewLocation()
{
// This method uses the side with more free space
int leftSpace = Bounds.Left - workingScreen.Left;
int rightSpace = workingScreen.Right - Bounds.Right;
Point pos;
// The declaration view window has better line break when used on
// the right side, so prefer the right side to the left.
if (rightSpace * 2 > leftSpace) {
declarationViewWindow.FixedWidth = false;
pos = new Point(Bounds.Right, Bounds.Top);
if (declarationViewWindow.Location != pos) {
declarationViewWindow.Location = pos;
}
} else {
declarationViewWindow.Width = declarationViewWindow.GetRequiredLeftHandSideWidth(new Point(Bounds.Left, Bounds.Top));
declarationViewWindow.FixedWidth = true;
if (Bounds.Left < declarationViewWindow.Width) {
pos = new Point(0, Bounds.Top);
} else {
pos = new Point(Bounds.Left - declarationViewWindow.Width, Bounds.Top);
}
if (declarationViewWindow.Location != pos) {
declarationViewWindow.Location = pos;
}
declarationViewWindow.Refresh();
}
}
protected override void SetLocation()
{
base.SetLocation();
if (declarationViewWindow != null) {
SetDeclarationViewLocation();
}
}
Util.MouseWheelHandler mouseWheelHandler = new Util.MouseWheelHandler();
public void HandleMouseWheel(MouseEventArgs e)
{
int scrollDistance = mouseWheelHandler.GetScrollAmount(e);
if (scrollDistance == 0)
return;
if (control.TextEditorProperties.MouseWheelScrollDown)
scrollDistance = -scrollDistance;
int newValue = vScrollBar.Value + vScrollBar.SmallChange * scrollDistance;
vScrollBar.Value = Math.Max(vScrollBar.Minimum, Math.Min(vScrollBar.Maximum - vScrollBar.LargeChange + 1, newValue));
}
void CodeCompletionListViewSelectedItemChanged(object sender, EventArgs e)
{
ICompletionData data = codeCompletionListView.SelectedCompletionData;
if (showDeclarationWindow && data != null && data.Description != null && data.Description.Length > 0) {
declarationViewWindow.Description = data.Description;
SetDeclarationViewLocation();
} else {
declarationViewWindow.Description = null;
}
}
public override bool ProcessKeyEvent(char ch)
{
switch (dataProvider.ProcessKey(ch)) {
case CompletionDataProviderKeyResult.BeforeStartKey:
// increment start+end, then process as normal char
++startOffset;
++endOffset;
return base.ProcessKeyEvent(ch);
case CompletionDataProviderKeyResult.NormalKey:
// just process normally
return base.ProcessKeyEvent(ch);
case CompletionDataProviderKeyResult.InsertionKey:
return InsertSelectedItem(ch);
default:
throw new InvalidOperationException("Invalid return value of dataProvider.ProcessKey");
}
}
void DocumentAboutToBeChanged(object sender, DocumentEventArgs e)
{
// => startOffset test required so that this startOffset/endOffset are not incremented again
// for BeforeStartKey characters
if (e.Offset >= startOffset && e.Offset <= endOffset) {
if (e.Length > 0) { // length of removed region
endOffset -= e.Length;
}
if (!string.IsNullOrEmpty(e.Text)) {
endOffset += e.Text.Length;
}
}
}
/// <summary>
/// When this flag is set, code completion closes if the caret moves to the
/// beginning of the allowed range. This is useful in Ctrl+Space and "complete when typing",
/// but not in dot-completion.
/// </summary>
public bool CloseWhenCaretAtBeginning { get; set; }
protected override void CaretOffsetChanged(object sender, EventArgs e)
{
int offset = control.ActiveTextAreaControl.Caret.Offset;
if (offset == startOffset) {
if (CloseWhenCaretAtBeginning)
Close();
return;
}
if (offset < startOffset || offset > endOffset) {
Close();
} else {
codeCompletionListView.SelectItemWithStart(control.Document.GetText(startOffset, offset - startOffset));
}
}
protected override bool ProcessTextAreaKey(Keys keyData)
{
if (!Visible) {
return false;
}
switch (keyData) {
case Keys.Home:
codeCompletionListView.SelectIndex(0);
return true;
case Keys.End:
codeCompletionListView.SelectIndex(completionData.Length-1);
return true;
case Keys.PageDown:
codeCompletionListView.PageDown();
return true;
case Keys.PageUp:
codeCompletionListView.PageUp();
return true;
case Keys.Down:
codeCompletionListView.SelectNextItem();
return true;
case Keys.Up:
codeCompletionListView.SelectPrevItem();
return true;
case Keys.Tab:
InsertSelectedItem('\t');
return true;
case Keys.Return:
InsertSelectedItem('\n');
return true;
}
return base.ProcessTextAreaKey(keyData);
}
void CodeCompletionListViewDoubleClick(object sender, EventArgs e)
{
InsertSelectedItem('\0');
}
void CodeCompletionListViewClick(object sender, EventArgs e)
{
control.ActiveTextAreaControl.TextArea.Focus();
}
protected override void Dispose(bool disposing)
{
if (disposing) {
document.DocumentAboutToBeChanged -= DocumentAboutToBeChanged;
if (codeCompletionListView != null) {
codeCompletionListView.Dispose();
codeCompletionListView = null;
}
if (declarationViewWindow != null) {
declarationViewWindow.Dispose();
declarationViewWindow = null;
}
}
base.Dispose(disposing);
}
bool InsertSelectedItem(char ch)
{
document.DocumentAboutToBeChanged -= DocumentAboutToBeChanged;
ICompletionData data = codeCompletionListView.SelectedCompletionData;
bool result = false;
if (data != null) {
control.BeginUpdate();
try {
if (endOffset - startOffset > 0) {
control.Document.Remove(startOffset, endOffset - startOffset);
}
Debug.Assert(startOffset <= document.TextLength);
result = dataProvider.InsertAction(data, control.ActiveTextAreaControl.TextArea, startOffset, ch);
} finally {
control.EndUpdate();
}
}
Close();
return result;
}
Size GetListViewSize()
{
int height = codeCompletionListView.ItemHeight * Math.Min(MaxListLength, completionData.Length);
int width = codeCompletionListView.ItemHeight * 10;
if (!fixedListViewWidth) {
width = GetListViewWidth(width, height);
}
return new Size(width, height);
}
/// <summary>
/// Gets the list view width large enough to handle the longest completion data
/// text string.
/// </summary>
/// <param name="defaultWidth">The default width of the list view.</param>
/// <param name="height">The height of the list view. This is
/// used to determine if the scrollbar is visible.</param>
/// <returns>The list view width to accommodate the longest completion
/// data text string; otherwise the default width.</returns>
int GetListViewWidth(int defaultWidth, int height)
{
float width = defaultWidth;
using (Graphics graphics = codeCompletionListView.CreateGraphics()) {
for (int i = 0; i < completionData.Length; ++i) {
float itemWidth = graphics.MeasureString(completionData[i].Text.ToString(), codeCompletionListView.Font).Width;
if(itemWidth > width) {
width = itemWidth;
}
}
}
float totalItemsHeight = codeCompletionListView.ItemHeight * completionData.Length;
if (totalItemsHeight > height) {
width += ScrollbarWidth; // Compensate for scroll bar.
}
return (int)width;
}
}
}

View File

@@ -0,0 +1,125 @@
// <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.Drawing;
using System.Windows.Forms;
using ICSharpCode.TextEditor.Util;
namespace ICSharpCode.TextEditor.Gui.CompletionWindow
{
public interface IDeclarationViewWindow
{
string Description {
get;
set;
}
void ShowDeclarationViewWindow();
void CloseDeclarationViewWindow();
}
public class DeclarationViewWindow : Form, IDeclarationViewWindow
{
string description = string.Empty;
bool fixedWidth;
public string Description {
get {
return description;
}
set {
description = value;
if (value == null && Visible) {
Visible = false;
} else if (value != null) {
if (!Visible) ShowDeclarationViewWindow();
Refresh();
}
}
}
public bool FixedWidth {
get {
return fixedWidth;
}
set {
fixedWidth = value;
}
}
public int GetRequiredLeftHandSideWidth(Point p) {
if (description != null && description.Length > 0) {
using (Graphics g = CreateGraphics()) {
Size s = TipPainterTools.GetLeftHandSideDrawingSizeHelpTipFromCombinedDescription(this, g, Font, null, description, p);
return s.Width;
}
}
return 0;
}
public bool HideOnClick;
public DeclarationViewWindow(Form parent)
{
SetStyle(ControlStyles.Selectable, false);
StartPosition = FormStartPosition.Manual;
FormBorderStyle = FormBorderStyle.None;
Owner = parent;
ShowInTaskbar = false;
Size = new Size(0, 0);
base.CreateHandle();
}
protected override CreateParams CreateParams {
get {
CreateParams p = base.CreateParams;
AbstractCompletionWindow.AddShadowToWindow(p);
return p;
}
}
protected override bool ShowWithoutActivation {
get {
return true;
}
}
protected override void OnClick(EventArgs e)
{
base.OnClick(e);
if (HideOnClick) Hide();
}
public void ShowDeclarationViewWindow()
{
Show();
}
public void CloseDeclarationViewWindow()
{
Close();
Dispose();
}
protected override void OnPaint(PaintEventArgs pe)
{
if (description != null && description.Length > 0) {
if (fixedWidth) {
TipPainterTools.DrawFixedWidthHelpTipFromCombinedDescription(this, pe.Graphics, Font, null, description);
} else {
TipPainterTools.DrawHelpTipFromCombinedDescription(this, pe.Graphics, Font, null, description);
}
}
}
protected override void OnPaintBackground(PaintEventArgs pe)
{
pe.Graphics.FillRectangle(SystemBrushes.Info, pe.ClipRectangle);
}
}
}

View File

@@ -0,0 +1,114 @@
// <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;
namespace ICSharpCode.TextEditor.Gui.CompletionWindow
{
public interface ICompletionData
{
int ImageIndex {
get;
}
string Text {
get;
set;
}
string Description {
get;
}
/// <summary>
/// Gets a priority value for the completion data item.
/// When selecting items by their start characters, the item with the highest
/// priority is selected first.
/// </summary>
double Priority {
get;
}
/// <summary>
/// Insert the element represented by the completion data into the text
/// editor.
/// </summary>
/// <param name="textArea">TextArea to insert the completion data in.</param>
/// <param name="ch">Character that should be inserted after the completion data.
/// \0 when no character should be inserted.</param>
/// <returns>Returns true when the insert action has processed the character
/// <paramref name="ch"/>; false when the character was not processed.</returns>
bool InsertAction(TextArea textArea, char ch);
}
public class DefaultCompletionData : ICompletionData
{
string text;
string description;
int imageIndex;
public int ImageIndex {
get {
return imageIndex;
}
}
public string Text {
get {
return text;
}
set {
text = value;
}
}
public virtual string Description {
get {
return description;
}
}
double priority;
public double Priority {
get {
return priority;
}
set {
priority = value;
}
}
public virtual bool InsertAction(TextArea textArea, char ch)
{
textArea.InsertString(text);
return false;
}
public DefaultCompletionData(string text, int imageIndex)
{
this.text = text;
this.imageIndex = imageIndex;
}
public DefaultCompletionData(string text, string description, int imageIndex)
{
this.text = text;
this.description = description;
this.imageIndex = imageIndex;
}
public static int Compare(ICompletionData a, ICompletionData b)
{
if (a == null)
throw new ArgumentNullException("a");
if (b == null)
throw new ArgumentNullException("b");
return string.Compare(a.Text, b.Text, StringComparison.InvariantCultureIgnoreCase);
}
}
}

View File

@@ -0,0 +1,62 @@
// <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.Windows.Forms;
namespace ICSharpCode.TextEditor.Gui.CompletionWindow
{
public interface ICompletionDataProvider
{
ImageList ImageList {
get;
}
string PreSelection {
get;
}
/// <summary>
/// Gets the index of the element in the list that is chosen by default.
/// </summary>
int DefaultIndex {
get;
}
/// <summary>
/// Processes a keypress. Returns the action to be run with the key.
/// </summary>
CompletionDataProviderKeyResult ProcessKey(char key);
/// <summary>
/// Executes the insertion. The provider should set the caret position and then
/// call data.InsertAction.
/// </summary>
bool InsertAction(ICompletionData data, TextArea textArea, int insertionOffset, char key);
/// <summary>
/// Generates the completion data. This method is called by the text editor control.
/// </summary>
ICompletionData[] GenerateCompletionData(string fileName, TextArea textArea, char charTyped);
}
public enum CompletionDataProviderKeyResult
{
/// <summary>
/// Normal key, used to choose an entry from the completion list
/// </summary>
NormalKey,
/// <summary>
/// This key triggers insertion of the completed expression
/// </summary>
InsertionKey,
/// <summary>
/// Increment both start and end offset of completion region when inserting this
/// key. Can be used to insert whitespace (or other characters) in front of the expression
/// while the completion window is open.
/// </summary>
BeforeStartKey
}
}

View File

@@ -0,0 +1,190 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Daniel Grunwald" email="daniel@danielgrunwald.de"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
using System.Drawing;
using ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor
{
/// <summary>
/// A class that is able to draw a line on any control (outside the text editor)
/// </summary>
public class DrawableLine
{
static StringFormat sf = (StringFormat)System.Drawing.StringFormat.GenericTypographic.Clone();
List<SimpleTextWord> words = new List<SimpleTextWord>();
SizeF spaceSize;
Font monospacedFont;
Font boldMonospacedFont;
private class SimpleTextWord {
internal TextWordType Type;
internal string Word;
internal bool Bold;
internal Color Color;
public SimpleTextWord(TextWordType Type, string Word, bool Bold, Color Color)
{
this.Type = Type;
this.Word = Word;
this.Bold = Bold;
this.Color = Color;
}
internal readonly static SimpleTextWord Space = new SimpleTextWord(TextWordType.Space, " ", false, Color.Black);
internal readonly static SimpleTextWord Tab = new SimpleTextWord(TextWordType.Tab, "\t", false, Color.Black);
}
public DrawableLine(IDocument document, LineSegment line, Font monospacedFont, Font boldMonospacedFont)
{
this.monospacedFont = monospacedFont;
this.boldMonospacedFont = boldMonospacedFont;
if (line.Words != null) {
foreach (TextWord word in line.Words) {
if (word.Type == TextWordType.Space) {
words.Add(SimpleTextWord.Space);
} else if (word.Type == TextWordType.Tab) {
words.Add(SimpleTextWord.Tab);
} else {
words.Add(new SimpleTextWord(TextWordType.Word, word.Word, word.Bold, word.Color));
}
}
} else {
words.Add(new SimpleTextWord(TextWordType.Word, document.GetText(line), false, Color.Black));
}
}
public int LineLength {
get {
int length = 0;
foreach (SimpleTextWord word in words) {
length += word.Word.Length;
}
return length;
}
}
public void SetBold(int startIndex, int endIndex, bool bold)
{
if (startIndex < 0)
throw new ArgumentException("startIndex must be >= 0");
if (startIndex > endIndex)
throw new ArgumentException("startIndex must be <= endIndex");
if (startIndex == endIndex) return;
int pos = 0;
for (int i = 0; i < words.Count; i++) {
SimpleTextWord word = words[i];
if (pos >= endIndex)
break;
int wordEnd = pos + word.Word.Length;
// 3 possibilities:
if (startIndex <= pos && endIndex >= wordEnd) {
// word is fully in region:
word.Bold = bold;
} else if (startIndex <= pos) {
// beginning of word is in region
int inRegionLength = endIndex - pos;
SimpleTextWord newWord = new SimpleTextWord(word.Type, word.Word.Substring(inRegionLength), word.Bold, word.Color);
words.Insert(i + 1, newWord);
word.Bold = bold;
word.Word = word.Word.Substring(0, inRegionLength);
} else if (startIndex < wordEnd) {
// end of word is in region (or middle of word is in region)
int notInRegionLength = startIndex - pos;
SimpleTextWord newWord = new SimpleTextWord(word.Type, word.Word.Substring(notInRegionLength), word.Bold, word.Color);
// newWord.Bold will be set in the next iteration
words.Insert(i + 1, newWord);
word.Word = word.Word.Substring(0, notInRegionLength);
}
pos = wordEnd;
}
}
public static float DrawDocumentWord(Graphics g, string word, PointF position, Font font, Color foreColor)
{
if (word == null || word.Length == 0) {
return 0f;
}
SizeF wordSize = g.MeasureString(word, font, 32768, sf);
g.DrawString(word,
font,
BrushRegistry.GetBrush(foreColor),
position,
sf);
return wordSize.Width;
}
public SizeF GetSpaceSize(Graphics g)
{
if (spaceSize.IsEmpty) {
spaceSize = g.MeasureString("-", boldMonospacedFont, new PointF(0, 0), sf);
}
return spaceSize;
}
public void DrawLine(Graphics g, ref float xPos, float xOffset, float yPos, Color c)
{
SizeF spaceSize = GetSpaceSize(g);
foreach (SimpleTextWord word in words) {
switch (word.Type) {
case TextWordType.Space:
xPos += spaceSize.Width;
break;
case TextWordType.Tab:
float tabWidth = spaceSize.Width * 4;
xPos += tabWidth;
xPos = (int)((xPos + 2) / tabWidth) * tabWidth;
break;
case TextWordType.Word:
xPos += DrawDocumentWord(g,
word.Word,
new PointF(xPos + xOffset, yPos),
word.Bold ? boldMonospacedFont : monospacedFont,
c == Color.Empty ? word.Color : c
);
break;
}
}
}
public void DrawLine(Graphics g, ref float xPos, float xOffset, float yPos)
{
DrawLine(g, ref xPos, xOffset, yPos, Color.Empty);
}
public float MeasureWidth(Graphics g, float xPos)
{
SizeF spaceSize = GetSpaceSize(g);
foreach (SimpleTextWord word in words) {
switch (word.Type) {
case TextWordType.Space:
xPos += spaceSize.Width;
break;
case TextWordType.Tab:
float tabWidth = spaceSize.Width * 4;
xPos += tabWidth;
xPos = (int)((xPos + 2) / tabWidth) * tabWidth;
break;
case TextWordType.Word:
if (word.Word != null && word.Word.Length > 0) {
xPos += g.MeasureString(word.Word, word.Bold ? boldMonospacedFont : monospacedFont, 32768, sf).Width;
}
break;
}
}
return xPos;
}
}
}

View File

@@ -0,0 +1,276 @@
// <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.Windows.Forms;
using ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor
{
/// <summary>
/// This class views the line numbers and folding markers.
/// </summary>
public class FoldMargin : AbstractMargin
{
int selectedFoldLine = -1;
public override Size Size {
get {
return new Size((int)(textArea.TextView.FontHeight),
-1);
}
}
public override bool IsVisible {
get {
return textArea.TextEditorProperties.EnableFolding;
}
}
public FoldMargin(TextArea textArea) : base(textArea)
{
}
public override void Paint(Graphics g, Rectangle rect)
{
if (rect.Width <= 0 || rect.Height <= 0) {
return;
}
HighlightColor lineNumberPainterColor = textArea.Document.HighlightingStrategy.GetColorFor("LineNumbers");
for (int y = 0; y < (DrawingPosition.Height + textArea.TextView.VisibleLineDrawingRemainder) / textArea.TextView.FontHeight + 1; ++y) {
Rectangle markerRectangle = new Rectangle(DrawingPosition.X,
DrawingPosition.Top + y * textArea.TextView.FontHeight - textArea.TextView.VisibleLineDrawingRemainder,
DrawingPosition.Width,
textArea.TextView.FontHeight);
if (rect.IntersectsWith(markerRectangle)) {
// draw dotted separator line
if (textArea.Document.TextEditorProperties.ShowLineNumbers) {
g.FillRectangle(BrushRegistry.GetBrush(textArea.Enabled ? lineNumberPainterColor.BackgroundColor : SystemColors.InactiveBorder),
markerRectangle);
g.DrawLine(BrushRegistry.GetDotPen(lineNumberPainterColor.Color),
base.drawingPosition.X,
markerRectangle.Y,
base.drawingPosition.X,
markerRectangle.Bottom);
} else {
g.FillRectangle(BrushRegistry.GetBrush(textArea.Enabled ? lineNumberPainterColor.BackgroundColor : SystemColors.InactiveBorder), markerRectangle);
}
int currentLine = textArea.Document.GetFirstLogicalLine(textArea.TextView.FirstPhysicalLine + y);
if (currentLine < textArea.Document.TotalNumberOfLines) {
PaintFoldMarker(g, currentLine, markerRectangle);
}
}
}
}
bool SelectedFoldingFrom(List<FoldMarker> list)
{
if (list != null) {
for (int i = 0; i < list.Count; ++i) {
if (this.selectedFoldLine == list[i].StartLine) {
return true;
}
}
}
return false;
}
void PaintFoldMarker(Graphics g, int lineNumber, Rectangle drawingRectangle)
{
HighlightColor foldLineColor = textArea.Document.HighlightingStrategy.GetColorFor("FoldLine");
HighlightColor selectedFoldLine = textArea.Document.HighlightingStrategy.GetColorFor("SelectedFoldLine");
List<FoldMarker> foldingsWithStart = textArea.Document.FoldingManager.GetFoldingsWithStart(lineNumber);
List<FoldMarker> foldingsBetween = textArea.Document.FoldingManager.GetFoldingsContainsLineNumber(lineNumber);
List<FoldMarker> foldingsWithEnd = textArea.Document.FoldingManager.GetFoldingsWithEnd(lineNumber);
bool isFoldStart = foldingsWithStart.Count > 0;
bool isBetween = foldingsBetween.Count > 0;
bool isFoldEnd = foldingsWithEnd.Count > 0;
bool isStartSelected = SelectedFoldingFrom(foldingsWithStart);
bool isBetweenSelected = SelectedFoldingFrom(foldingsBetween);
bool isEndSelected = SelectedFoldingFrom(foldingsWithEnd);
int foldMarkerSize = (int)Math.Round(textArea.TextView.FontHeight * 0.57f);
foldMarkerSize -= (foldMarkerSize) % 2;
int foldMarkerYPos = drawingRectangle.Y + (int)((drawingRectangle.Height - foldMarkerSize) / 2);
int xPos = drawingRectangle.X + (drawingRectangle.Width - foldMarkerSize) / 2 + foldMarkerSize / 2;
if (isFoldStart) {
bool isVisible = true;
bool moreLinedOpenFold = false;
foreach (FoldMarker foldMarker in foldingsWithStart) {
if (foldMarker.IsFolded) {
isVisible = false;
} else {
moreLinedOpenFold = foldMarker.EndLine > foldMarker.StartLine;
}
}
bool isFoldEndFromUpperFold = false;
foreach (FoldMarker foldMarker in foldingsWithEnd) {
if (foldMarker.EndLine > foldMarker.StartLine && !foldMarker.IsFolded) {
isFoldEndFromUpperFold = true;
}
}
DrawFoldMarker(g, new RectangleF(drawingRectangle.X + (drawingRectangle.Width - foldMarkerSize) / 2,
foldMarkerYPos,
foldMarkerSize,
foldMarkerSize),
isVisible,
isStartSelected
);
// draw line above fold marker
if (isBetween || isFoldEndFromUpperFold) {
g.DrawLine(BrushRegistry.GetPen(isBetweenSelected ? selectedFoldLine.Color : foldLineColor.Color),
xPos,
drawingRectangle.Top,
xPos,
foldMarkerYPos - 1);
}
// draw line below fold marker
if (isBetween || moreLinedOpenFold) {
g.DrawLine(BrushRegistry.GetPen(isEndSelected || (isStartSelected && isVisible) || isBetweenSelected ? selectedFoldLine.Color : foldLineColor.Color),
xPos,
foldMarkerYPos + foldMarkerSize + 1,
xPos,
drawingRectangle.Bottom);
}
} else {
if (isFoldEnd) {
int midy = drawingRectangle.Top + drawingRectangle.Height / 2;
// draw fold end marker
g.DrawLine(BrushRegistry.GetPen(isEndSelected ? selectedFoldLine.Color : foldLineColor.Color),
xPos,
midy,
xPos + foldMarkerSize / 2,
midy);
// draw line above fold end marker
// must be drawn after fold marker because it might have a different color than the fold marker
g.DrawLine(BrushRegistry.GetPen(isBetweenSelected || isEndSelected ? selectedFoldLine.Color : foldLineColor.Color),
xPos,
drawingRectangle.Top,
xPos,
midy);
// draw line below fold end marker
if (isBetween) {
g.DrawLine(BrushRegistry.GetPen(isBetweenSelected ? selectedFoldLine.Color : foldLineColor.Color),
xPos,
midy + 1,
xPos,
drawingRectangle.Bottom);
}
} else if (isBetween) {
// just draw the line :)
g.DrawLine(BrushRegistry.GetPen(isBetweenSelected ? selectedFoldLine.Color : foldLineColor.Color),
xPos,
drawingRectangle.Top,
xPos,
drawingRectangle.Bottom);
}
}
}
public override void HandleMouseMove(Point mousepos, MouseButtons mouseButtons)
{
bool showFolding = textArea.Document.TextEditorProperties.EnableFolding;
int physicalLine = + (int)((mousepos.Y + textArea.VirtualTop.Y) / textArea.TextView.FontHeight);
int realline = textArea.Document.GetFirstLogicalLine(physicalLine);
if (!showFolding || realline < 0 || realline + 1 >= textArea.Document.TotalNumberOfLines) {
return;
}
List<FoldMarker> foldMarkers = textArea.Document.FoldingManager.GetFoldingsWithStart(realline);
int oldSelection = selectedFoldLine;
if (foldMarkers.Count > 0) {
selectedFoldLine = realline;
} else {
selectedFoldLine = -1;
}
if (oldSelection != selectedFoldLine) {
textArea.Refresh(this);
}
}
public override void HandleMouseDown(Point mousepos, MouseButtons mouseButtons)
{
bool showFolding = textArea.Document.TextEditorProperties.EnableFolding;
int physicalLine = + (int)((mousepos.Y + textArea.VirtualTop.Y) / textArea.TextView.FontHeight);
int realline = textArea.Document.GetFirstLogicalLine(physicalLine);
// focus the textarea if the user clicks on the line number view
textArea.Focus();
if (!showFolding || realline < 0 || realline + 1 >= textArea.Document.TotalNumberOfLines) {
return;
}
List<FoldMarker> foldMarkers = textArea.Document.FoldingManager.GetFoldingsWithStart(realline);
foreach (FoldMarker fm in foldMarkers) {
fm.IsFolded = !fm.IsFolded;
}
textArea.Document.FoldingManager.NotifyFoldingsChanged(EventArgs.Empty);
}
public override void HandleMouseLeave(EventArgs e)
{
if (selectedFoldLine != -1) {
selectedFoldLine = -1;
textArea.Refresh(this);
}
}
#region Drawing functions
void DrawFoldMarker(Graphics g, RectangleF rectangle, bool isOpened, bool isSelected)
{
HighlightColor foldMarkerColor = textArea.Document.HighlightingStrategy.GetColorFor("FoldMarker");
HighlightColor foldLineColor = textArea.Document.HighlightingStrategy.GetColorFor("FoldLine");
HighlightColor selectedFoldLine = textArea.Document.HighlightingStrategy.GetColorFor("SelectedFoldLine");
Rectangle intRect = new Rectangle((int)rectangle.X, (int)rectangle.Y, (int)rectangle.Width, (int)rectangle.Height);
g.FillRectangle(BrushRegistry.GetBrush(foldMarkerColor.BackgroundColor), intRect);
g.DrawRectangle(BrushRegistry.GetPen(isSelected ? selectedFoldLine.Color : foldLineColor.Color), intRect);
int space = (int)Math.Round(((double)rectangle.Height) / 8d) + 1;
int mid = intRect.Height / 2 + intRect.Height % 2;
// draw minus
g.DrawLine(BrushRegistry.GetPen(foldMarkerColor.Color),
rectangle.X + space,
rectangle.Y + mid,
rectangle.X + rectangle.Width - space,
rectangle.Y + mid);
// draw plus
if (!isOpened) {
g.DrawLine(BrushRegistry.GetPen(foldMarkerColor.Color),
rectangle.X + mid,
rectangle.Y + space,
rectangle.X + mid,
rectangle.Y + rectangle.Height - space);
}
}
#endregion
}
}

View File

@@ -0,0 +1,159 @@
// <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.Drawing;
using System.IO;
using System.Reflection;
using System.Windows.Forms;
using ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor
{
/// <summary>
/// This class views the line numbers and folding markers.
/// </summary>
public class GutterMargin : AbstractMargin, IDisposable
{
StringFormat numberStringFormat = (StringFormat)StringFormat.GenericTypographic.Clone();
public static Cursor RightLeftCursor;
static GutterMargin()
{
using (var ms = new MemoryStream(Properties.Resources.RightArrow))
{
RightLeftCursor = new Cursor(ms);
}
}
public void Dispose()
{
numberStringFormat.Dispose();
}
public override Cursor Cursor {
get {
return RightLeftCursor;
}
}
public override Size Size {
get {
return new Size((int)(textArea.TextView.WideSpaceWidth
* Math.Max(3, (int)Math.Log10(textArea.Document.TotalNumberOfLines) + 1)),
-1);
}
}
public override bool IsVisible {
get {
return textArea.TextEditorProperties.ShowLineNumbers;
}
}
public GutterMargin(TextArea textArea) : base(textArea)
{
numberStringFormat.LineAlignment = StringAlignment.Far;
numberStringFormat.FormatFlags = StringFormatFlags.MeasureTrailingSpaces | StringFormatFlags.FitBlackBox |
StringFormatFlags.NoWrap | StringFormatFlags.NoClip;
}
public override void Paint(Graphics g, Rectangle rect)
{
if (rect.Width <= 0 || rect.Height <= 0) {
return;
}
HighlightColor lineNumberPainterColor = textArea.Document.HighlightingStrategy.GetColorFor("LineNumbers");
int fontHeight = textArea.TextView.FontHeight;
Brush fillBrush = textArea.Enabled ? BrushRegistry.GetBrush(lineNumberPainterColor.BackgroundColor) : SystemBrushes.InactiveBorder;
Brush drawBrush = BrushRegistry.GetBrush(lineNumberPainterColor.Color);
for (int y = 0; y < (DrawingPosition.Height + textArea.TextView.VisibleLineDrawingRemainder) / fontHeight + 1; ++y) {
int ypos = drawingPosition.Y + fontHeight * y - textArea.TextView.VisibleLineDrawingRemainder;
Rectangle backgroundRectangle = new Rectangle(drawingPosition.X, ypos, drawingPosition.Width, fontHeight);
if (rect.IntersectsWith(backgroundRectangle)) {
g.FillRectangle(fillBrush, backgroundRectangle);
int curLine = textArea.Document.GetFirstLogicalLine(textArea.Document.GetVisibleLine(textArea.TextView.FirstVisibleLine) + y);
if (curLine < textArea.Document.TotalNumberOfLines) {
g.DrawString((curLine + 1).ToString(),
lineNumberPainterColor.GetFont(TextEditorProperties.FontContainer),
drawBrush,
backgroundRectangle,
numberStringFormat);
}
}
}
}
public override void HandleMouseDown(Point mousepos, MouseButtons mouseButtons)
{
TextLocation selectionStartPos;
textArea.SelectionManager.selectFrom.where = WhereFrom.Gutter;
int realline = textArea.TextView.GetLogicalLine(mousepos.Y);
if (realline >= 0 && realline < textArea.Document.TotalNumberOfLines) {
// shift-select
if((Control.ModifierKeys & Keys.Shift) != 0) {
if(!textArea.SelectionManager.HasSomethingSelected && realline != textArea.Caret.Position.Y) {
if (realline >= textArea.Caret.Position.Y)
{ // at or below starting selection, place the cursor on the next line
// nothing is selected so make a new selection from cursor
selectionStartPos = textArea.Caret.Position;
// whole line selection - start of line to start of next line
if (realline < textArea.Document.TotalNumberOfLines - 1)
{
textArea.SelectionManager.SetSelection(new DefaultSelection(textArea.Document, selectionStartPos, new TextLocation(0, realline + 1)));
textArea.Caret.Position = new TextLocation(0, realline + 1);
}
else
{
textArea.SelectionManager.SetSelection(new DefaultSelection(textArea.Document, selectionStartPos, new TextLocation(textArea.Document.GetLineSegment(realline).Length + 1, realline)));
textArea.Caret.Position = new TextLocation(textArea.Document.GetLineSegment(realline).Length + 1, realline);
}
}
else
{ // prior lines to starting selection, place the cursor on the same line as the new selection
// nothing is selected so make a new selection from cursor
selectionStartPos = textArea.Caret.Position;
// whole line selection - start of line to start of next line
textArea.SelectionManager.SetSelection(new DefaultSelection(textArea.Document, selectionStartPos, new TextLocation(selectionStartPos.X, selectionStartPos.Y)));
textArea.SelectionManager.ExtendSelection(new TextLocation(selectionStartPos.X, selectionStartPos.Y), new TextLocation(0, realline));
textArea.Caret.Position = new TextLocation(0, realline);
}
}
else
{
// let MouseMove handle a shift-click in a gutter
MouseEventArgs e = new MouseEventArgs(mouseButtons, 1, mousepos.X, mousepos.Y, 0);
textArea.RaiseMouseMove(e);
}
} else { // this is a new selection with no shift-key
// sync the textareamousehandler mouse location
// (fixes problem with clicking out into a menu then back to the gutter whilst
// there is a selection)
textArea.mousepos = mousepos;
selectionStartPos = new TextLocation(0, realline);
textArea.SelectionManager.ClearSelection();
// whole line selection - start of line to start of next line
if (realline < textArea.Document.TotalNumberOfLines - 1)
{
textArea.SelectionManager.SetSelection(new DefaultSelection(textArea.Document, selectionStartPos, new TextLocation(selectionStartPos.X, selectionStartPos.Y + 1)));
textArea.Caret.Position = new TextLocation(selectionStartPos.X, selectionStartPos.Y + 1);
}
else
{
textArea.SelectionManager.SetSelection(new DefaultSelection(textArea.Document, new TextLocation(0, realline), new TextLocation(textArea.Document.GetLineSegment(realline).Length + 1, selectionStartPos.Y)));
textArea.Caret.Position = new TextLocation(textArea.Document.GetLineSegment(realline).Length + 1, selectionStartPos.Y);
}
}
}
}
}
}

View File

@@ -0,0 +1,54 @@
// <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.Drawing;
using System.Windows.Forms;
namespace ICSharpCode.TextEditor
{
/// <summary>
/// Horizontal ruler - text column measuring ruler at the top of the text area.
/// </summary>
public class HRuler : Control
{
TextArea textArea;
public HRuler(TextArea textArea)
{
this.textArea = textArea;
}
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
Graphics g = e.Graphics;
int num = 0;
for (float x = textArea.TextView.DrawingPosition.Left; x < textArea.TextView.DrawingPosition.Right; x += textArea.TextView.WideSpaceWidth) {
int offset = (Height * 2) / 3;
if (num % 5 == 0) {
offset = (Height * 4) / 5;
}
if (num % 10 == 0) {
offset = 1;
}
++num;
g.DrawLine(Pens.Black,
(int)x, offset, (int)x, Height - offset);
}
}
protected override void OnPaintBackground(System.Windows.Forms.PaintEventArgs e)
{
e.Graphics.FillRectangle(Brushes.White,
new Rectangle(0,
0,
Width,
Height));
}
}
}

View File

@@ -0,0 +1,254 @@
// <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.Drawing.Drawing2D;
using System.Windows.Forms;
using ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor
{
/// <summary>
/// This class views the line numbers and folding markers.
/// </summary>
public class IconBarMargin : AbstractMargin
{
const int iconBarWidth = 18;
static readonly Size iconBarSize = new Size(iconBarWidth, -1);
public override Size Size {
get {
return iconBarSize;
}
}
public override bool IsVisible {
get {
return textArea.TextEditorProperties.IsIconBarVisible;
}
}
public IconBarMargin(TextArea textArea) : base(textArea)
{
}
public override void Paint(Graphics g, Rectangle rect)
{
if (rect.Width <= 0 || rect.Height <= 0) {
return;
}
// paint background
g.FillRectangle(SystemBrushes.Control, new Rectangle(drawingPosition.X, rect.Top, drawingPosition.Width - 1, rect.Height));
g.DrawLine(SystemPens.ControlDark, base.drawingPosition.Right - 1, rect.Top, base.drawingPosition.Right - 1, rect.Bottom);
// paint icons
foreach (Bookmark mark in textArea.Document.BookmarkManager.Marks) {
int lineNumber = textArea.Document.GetVisibleLine(mark.LineNumber);
int lineHeight = textArea.TextView.FontHeight;
int yPos = (int)(lineNumber * lineHeight) - textArea.VirtualTop.Y;
if (IsLineInsideRegion(yPos, yPos + lineHeight, rect.Y, rect.Bottom)) {
if (lineNumber == textArea.Document.GetVisibleLine(mark.LineNumber - 1)) {
// marker is inside folded region, do not draw it
continue;
}
mark.Draw(this, g, new Point(0, yPos));
}
}
base.Paint(g, rect);
}
public override void HandleMouseDown(Point mousePos, MouseButtons mouseButtons)
{
int clickedVisibleLine = (mousePos.Y + textArea.VirtualTop.Y) / textArea.TextView.FontHeight;
int lineNumber = textArea.Document.GetFirstLogicalLine(clickedVisibleLine);
if ((mouseButtons & MouseButtons.Right) == MouseButtons.Right) {
if (textArea.Caret.Line != lineNumber) {
textArea.Caret.Line = lineNumber;
}
}
IList<Bookmark> marks = textArea.Document.BookmarkManager.Marks;
List<Bookmark> marksInLine = new List<Bookmark>();
int oldCount = marks.Count;
foreach (Bookmark mark in marks) {
if (mark.LineNumber == lineNumber) {
marksInLine.Add(mark);
}
}
for (int i = marksInLine.Count - 1; i >= 0; i--) {
Bookmark mark = marksInLine[i];
if (mark.Click(textArea, new MouseEventArgs(mouseButtons, 1, mousePos.X, mousePos.Y, 0))) {
if (oldCount != marks.Count) {
textArea.UpdateLine(lineNumber);
}
return;
}
}
base.HandleMouseDown(mousePos, mouseButtons);
}
#region Drawing functions
public void DrawBreakpoint(Graphics g, int y, bool isEnabled, bool isHealthy)
{
int diameter = Math.Min(iconBarWidth - 2, textArea.TextView.FontHeight);
Rectangle rect = new Rectangle(1,
y + (textArea.TextView.FontHeight - diameter) / 2,
diameter,
diameter);
using (GraphicsPath path = new GraphicsPath()) {
path.AddEllipse(rect);
using (PathGradientBrush pthGrBrush = new PathGradientBrush(path)) {
pthGrBrush.CenterPoint = new PointF(rect.Left + rect.Width / 3 , rect.Top + rect.Height / 3);
pthGrBrush.CenterColor = Color.MistyRose;
Color[] colors = {isHealthy ? Color.Firebrick : Color.Olive};
pthGrBrush.SurroundColors = colors;
if (isEnabled) {
g.FillEllipse(pthGrBrush, rect);
} else {
g.FillEllipse(SystemBrushes.Control, rect);
using (Pen pen = new Pen(pthGrBrush)) {
g.DrawEllipse(pen, new Rectangle(rect.X + 1, rect.Y + 1, rect.Width - 2, rect.Height - 2));
}
}
}
}
}
public void DrawBookmark(Graphics g, int y, bool isEnabled)
{
int delta = textArea.TextView.FontHeight / 8;
Rectangle rect = new Rectangle(1, y + delta, base.drawingPosition.Width - 4, textArea.TextView.FontHeight - delta * 2);
if (isEnabled) {
using (Brush brush = new LinearGradientBrush(new Point(rect.Left, rect.Top),
new Point(rect.Right, rect.Bottom),
Color.SkyBlue,
Color.White)) {
FillRoundRect(g, brush, rect);
}
} else {
FillRoundRect(g, Brushes.White, rect);
}
using (Brush brush = new LinearGradientBrush(new Point(rect.Left, rect.Top),
new Point(rect.Right, rect.Bottom),
Color.SkyBlue,
Color.Blue)) {
using (Pen pen = new Pen(brush)) {
DrawRoundRect(g, pen, rect);
}
}
}
public void DrawArrow(Graphics g, int y)
{
int delta = textArea.TextView.FontHeight / 8;
Rectangle rect = new Rectangle(1, y + delta, base.drawingPosition.Width - 4, textArea.TextView.FontHeight - delta * 2);
using (Brush brush = new LinearGradientBrush(new Point(rect.Left, rect.Top),
new Point(rect.Right, rect.Bottom),
Color.LightYellow,
Color.Yellow)) {
FillArrow(g, brush, rect);
}
using (Brush brush = new LinearGradientBrush(new Point(rect.Left, rect.Top),
new Point(rect.Right, rect.Bottom),
Color.Yellow,
Color.Brown)) {
using (Pen pen = new Pen(brush)) {
DrawArrow(g, pen, rect);
}
}
}
GraphicsPath CreateArrowGraphicsPath(Rectangle r)
{
GraphicsPath gp = new GraphicsPath();
int halfX = r.Width / 2;
int halfY = r.Height/ 2;
gp.AddLine(r.X, r.Y + halfY/2, r.X + halfX, r.Y + halfY/2);
gp.AddLine(r.X + halfX, r.Y + halfY/2, r.X + halfX, r.Y);
gp.AddLine(r.X + halfX, r.Y, r.Right, r.Y + halfY);
gp.AddLine(r.Right, r.Y + halfY, r.X + halfX, r.Bottom);
gp.AddLine(r.X + halfX, r.Bottom, r.X + halfX, r.Bottom - halfY/2);
gp.AddLine(r.X + halfX, r.Bottom - halfY/2, r.X, r.Bottom - halfY/2);
gp.AddLine(r.X, r.Bottom - halfY/2, r.X, r.Y + halfY/2);
gp.CloseFigure();
return gp;
}
GraphicsPath CreateRoundRectGraphicsPath(Rectangle r)
{
GraphicsPath gp = new GraphicsPath();
int radius = r.Width / 2;
gp.AddLine(r.X + radius, r.Y, r.Right - radius, r.Y);
gp.AddArc(r.Right - radius, r.Y, radius, radius, 270, 90);
gp.AddLine(r.Right, r.Y + radius, r.Right, r.Bottom - radius);
gp.AddArc(r.Right - radius, r.Bottom - radius, radius, radius, 0, 90);
gp.AddLine(r.Right - radius, r.Bottom, r.X + radius, r.Bottom);
gp.AddArc(r.X, r.Bottom - radius, radius, radius, 90, 90);
gp.AddLine(r.X, r.Bottom - radius, r.X, r.Y + radius);
gp.AddArc(r.X, r.Y, radius, radius, 180, 90);
gp.CloseFigure();
return gp;
}
void DrawRoundRect(Graphics g, Pen p , Rectangle r)
{
using (GraphicsPath gp = CreateRoundRectGraphicsPath(r)) {
g.DrawPath(p, gp);
}
}
void FillRoundRect(Graphics g, Brush b , Rectangle r)
{
using (GraphicsPath gp = CreateRoundRectGraphicsPath(r)) {
g.FillPath(b, gp);
}
}
void DrawArrow(Graphics g, Pen p , Rectangle r)
{
using (GraphicsPath gp = CreateArrowGraphicsPath(r)) {
g.DrawPath(p, gp);
}
}
void FillArrow(Graphics g, Brush b , Rectangle r)
{
using (GraphicsPath gp = CreateArrowGraphicsPath(r)) {
g.FillPath(b, gp);
}
}
#endregion
static bool IsLineInsideRegion(int top, int bottom, int regionTop, int regionBottom)
{
if (top >= regionTop && top <= regionBottom) {
// Region overlaps the line's top edge.
return true;
} else if(regionTop > top && regionTop < bottom) {
// Region's top edge inside line.
return true;
}
return false;
}
}
}

View File

@@ -0,0 +1,179 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Shinsaku Nakagawa" email="shinsaku@users.sourceforge.jp"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace ICSharpCode.TextEditor
{
/// <summary>
/// Used internally, not for own use.
/// </summary>
internal class Ime
{
public Ime(IntPtr hWnd, Font font)
{
// For unknown reasons, the IME support is causing crashes when used in a WOW64 process
// or when used in .NET 4.0. We'll disable IME support in those cases.
string PROCESSOR_ARCHITEW6432 = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITEW6432");
if (PROCESSOR_ARCHITEW6432 == "IA64" || PROCESSOR_ARCHITEW6432 == "AMD64" || Environment.OSVersion.Platform == PlatformID.Unix || Environment.Version >= new Version(4,0)) {
disableIME = true;
} else {
this.hIMEWnd = ImmGetDefaultIMEWnd(hWnd);
}
this.hWnd = hWnd;
this.font = font;
SetIMEWindowFont(font);
}
private Font font = null;
public Font Font
{
get {
return font;
}
set {
if (!value.Equals(font)) {
font = value;
lf = null;
SetIMEWindowFont(value);
}
}
}
public IntPtr HWnd
{
set {
if (this.hWnd != value) {
this.hWnd = value;
if (!disableIME)
this.hIMEWnd = ImmGetDefaultIMEWnd(value);
SetIMEWindowFont(font);
}
}
}
[ DllImport("imm32.dll") ]
private static extern IntPtr ImmGetDefaultIMEWnd(IntPtr hWnd);
[ DllImport("user32.dll") ]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, COMPOSITIONFORM lParam);
[ DllImport("user32.dll") ]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, [In, MarshalAs(UnmanagedType.LPStruct)] LOGFONT lParam);
[ StructLayout(LayoutKind.Sequential) ]
private class COMPOSITIONFORM
{
public int dwStyle = 0;
public POINT ptCurrentPos = null;
public RECT rcArea = null;
}
[ StructLayout(LayoutKind.Sequential) ]
private class POINT
{
public int x = 0;
public int y = 0;
}
[ StructLayout(LayoutKind.Sequential) ]
private class RECT
{
public int left = 0;
public int top = 0;
public int right = 0;
public int bottom = 0;
}
private const int WM_IME_CONTROL = 0x0283;
private const int IMC_SETCOMPOSITIONWINDOW = 0x000c;
private IntPtr hIMEWnd;
private IntPtr hWnd;
private const int CFS_POINT = 0x0002;
[ StructLayout(LayoutKind.Sequential) ]
private class LOGFONT
{
public int lfHeight = 0;
public int lfWidth = 0;
public int lfEscapement = 0;
public int lfOrientation = 0;
public int lfWeight = 0;
public byte lfItalic = 0;
public byte lfUnderline = 0;
public byte lfStrikeOut = 0;
public byte lfCharSet = 0;
public byte lfOutPrecision = 0;
public byte lfClipPrecision = 0;
public byte lfQuality = 0;
public byte lfPitchAndFamily = 0;
[ MarshalAs(UnmanagedType.ByValTStr, SizeConst=32) ] public string lfFaceName = null;
}
private const int IMC_SETCOMPOSITIONFONT = 0x000a;
LOGFONT lf = null;
static bool disableIME;
private void SetIMEWindowFont(Font f)
{
if (disableIME || hIMEWnd == IntPtr.Zero) return;
if (lf == null) {
lf = new LOGFONT();
f.ToLogFont(lf);
lf.lfFaceName = f.Name; // This is very important! "Font.ToLogFont" Method sets invalid value to LOGFONT.lfFaceName
}
try {
SendMessage(
hIMEWnd,
WM_IME_CONTROL,
new IntPtr(IMC_SETCOMPOSITIONFONT),
lf
);
} catch (AccessViolationException ex) {
Handle(ex);
}
}
public void SetIMEWindowLocation(int x, int y)
{
if (disableIME || hIMEWnd == IntPtr.Zero) return;
POINT p = new POINT();
p.x = x;
p.y = y;
COMPOSITIONFORM lParam = new COMPOSITIONFORM();
lParam.dwStyle = CFS_POINT;
lParam.ptCurrentPos = p;
lParam.rcArea = new RECT();
try {
SendMessage(
hIMEWnd,
WM_IME_CONTROL,
new IntPtr(IMC_SETCOMPOSITIONWINDOW),
lParam
);
} catch (AccessViolationException ex) {
Handle(ex);
}
}
void Handle(Exception ex)
{
Console.WriteLine(ex);
if (!disableIME) {
disableIME = true;
MessageBox.Show("Error calling IME: " + ex.Message + "\nIME is disabled.", "IME error");
}
}
}
}

View File

@@ -0,0 +1,53 @@
// <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;
namespace ICSharpCode.TextEditor.Gui.InsightWindow
{
public interface IInsightDataProvider
{
/// <summary>
/// Tells the insight provider to prepare its data.
/// </summary>
/// <param name="fileName">The name of the edited file</param>
/// <param name="textArea">The text area in which the file is being edited</param>
void SetupDataProvider(string fileName, TextArea textArea);
/// <summary>
/// Notifies the insight provider that the caret offset has changed.
/// </summary>
/// <returns>Return true to close the insight window (e.g. when the
/// caret was moved outside the region where insight is displayed for).
/// Return false to keep the window open.</returns>
bool CaretOffsetChanged();
/// <summary>
/// Gets the text to display in the insight window.
/// </summary>
/// <param name="number">The number of the active insight entry.
/// Multiple insight entries might be multiple overloads of the same method.</param>
/// <returns>The text to display, e.g. a multi-line string where
/// the first line is the method definition, followed by a description.</returns>
string GetInsightData(int number);
/// <summary>
/// Gets the number of available insight entries, e.g. the number of available
/// overloads to call.
/// </summary>
int InsightDataCount {
get;
}
/// <summary>
/// Gets the index of the entry to initially select.
/// </summary>
int DefaultIndex {
get;
}
}
}

View File

@@ -0,0 +1,199 @@
// <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.Windows.Forms;
using ICSharpCode.TextEditor.Gui.CompletionWindow;
using ICSharpCode.TextEditor.Util;
namespace ICSharpCode.TextEditor.Gui.InsightWindow
{
public class InsightWindow : AbstractCompletionWindow
{
public InsightWindow(Form parentForm, TextEditorControl control) : base(parentForm, control)
{
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
}
public void ShowInsightWindow()
{
if (!Visible) {
if (insightDataProviderStack.Count > 0) {
ShowCompletionWindow();
}
} else {
Refresh();
}
}
#region Event handling routines
protected override bool ProcessTextAreaKey(Keys keyData)
{
if (!Visible) {
return false;
}
switch (keyData) {
case Keys.Down:
if (DataProvider != null && DataProvider.InsightDataCount > 0) {
CurrentData = (CurrentData + 1) % DataProvider.InsightDataCount;
Refresh();
}
return true;
case Keys.Up:
if (DataProvider != null && DataProvider.InsightDataCount > 0) {
CurrentData = (CurrentData + DataProvider.InsightDataCount - 1) % DataProvider.InsightDataCount;
Refresh();
}
return true;
}
return base.ProcessTextAreaKey(keyData);
}
protected override void CaretOffsetChanged(object sender, EventArgs e)
{
// move the window under the caret (don't change the x position)
TextLocation caretPos = control.ActiveTextAreaControl.Caret.Position;
int y = (int)((1 + caretPos.Y) * control.ActiveTextAreaControl.TextArea.TextView.FontHeight)
- control.ActiveTextAreaControl.TextArea.VirtualTop.Y - 1
+ control.ActiveTextAreaControl.TextArea.TextView.DrawingPosition.Y;
int xpos = control.ActiveTextAreaControl.TextArea.TextView.GetDrawingXPos(caretPos.Y, caretPos.X);
int ypos = (control.ActiveTextAreaControl.Document.GetVisibleLine(caretPos.Y) + 1) * control.ActiveTextAreaControl.TextArea.TextView.FontHeight
- control.ActiveTextAreaControl.TextArea.VirtualTop.Y;
int rulerHeight = control.TextEditorProperties.ShowHorizontalRuler ? control.ActiveTextAreaControl.TextArea.TextView.FontHeight : 0;
Point p = control.ActiveTextAreaControl.PointToScreen(new Point(xpos, ypos + rulerHeight));
if (p.Y != Location.Y) {
Location = p;
}
while (DataProvider != null && DataProvider.CaretOffsetChanged()) {
CloseCurrentDataProvider();
}
}
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
control.ActiveTextAreaControl.TextArea.Focus();
if (TipPainterTools.DrawingRectangle1.Contains(e.X, e.Y)) {
CurrentData = (CurrentData + DataProvider.InsightDataCount - 1) % DataProvider.InsightDataCount;
Refresh();
}
if (TipPainterTools.DrawingRectangle2.Contains(e.X, e.Y)) {
CurrentData = (CurrentData + 1) % DataProvider.InsightDataCount;
Refresh();
}
}
#endregion
MouseWheelHandler mouseWheelHandler = new MouseWheelHandler();
public void HandleMouseWheel(MouseEventArgs e)
{
if (DataProvider != null && DataProvider.InsightDataCount > 0) {
int distance = mouseWheelHandler.GetScrollAmount(e);
if (control.TextEditorProperties.MouseWheelScrollDown)
distance = -distance;
if (distance > 0) {
CurrentData = (CurrentData + 1) % DataProvider.InsightDataCount;
} else if (distance < 0) {
CurrentData = (CurrentData + DataProvider.InsightDataCount - 1) % DataProvider.InsightDataCount;
}
Refresh();
}
}
#region Insight Window Drawing routines
protected override void OnPaint(PaintEventArgs pe)
{
string methodCountMessage = null, description;
if (DataProvider == null || DataProvider.InsightDataCount < 1) {
description = "Unknown Method";
} else {
if (DataProvider.InsightDataCount > 1) {
methodCountMessage = control.GetRangeDescription(CurrentData + 1, DataProvider.InsightDataCount);
}
description = DataProvider.GetInsightData(CurrentData);
}
drawingSize = TipPainterTools.GetDrawingSizeHelpTipFromCombinedDescription(this,
pe.Graphics,
Font,
methodCountMessage,
description);
if (drawingSize != Size) {
SetLocation();
} else {
TipPainterTools.DrawHelpTipFromCombinedDescription(this, pe.Graphics, Font, methodCountMessage, description);
}
}
protected override void OnPaintBackground(PaintEventArgs pe)
{
pe.Graphics.FillRectangle(SystemBrushes.Info, pe.ClipRectangle);
}
#endregion
#region InsightDataProvider handling
Stack<InsightDataProviderStackElement> insightDataProviderStack = new Stack<InsightDataProviderStackElement>();
int CurrentData {
get {
return insightDataProviderStack.Peek().currentData;
}
set {
insightDataProviderStack.Peek().currentData = value;
}
}
IInsightDataProvider DataProvider {
get {
if (insightDataProviderStack.Count == 0) {
return null;
}
return insightDataProviderStack.Peek().dataProvider;
}
}
public void AddInsightDataProvider(IInsightDataProvider provider, string fileName)
{
provider.SetupDataProvider(fileName, control.ActiveTextAreaControl.TextArea);
if (provider.InsightDataCount > 0) {
insightDataProviderStack.Push(new InsightDataProviderStackElement(provider));
}
}
void CloseCurrentDataProvider()
{
insightDataProviderStack.Pop();
if (insightDataProviderStack.Count == 0) {
Close();
} else {
Refresh();
}
}
class InsightDataProviderStackElement
{
public int currentData;
public IInsightDataProvider dataProvider;
public InsightDataProviderStackElement(IInsightDataProvider dataProvider)
{
this.currentData = Math.Max(dataProvider.DefaultIndex, 0);
this.dataProvider = dataProvider;
}
}
#endregion
}
}

View File

@@ -0,0 +1,947 @@
// <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.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Text;
using System.Text;
using System.Windows.Forms;
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);
/// <summary>
/// This class paints the textarea.
/// </summary>
[ToolboxItem(false)]
public class TextArea : Control
{
bool hiddenMouseCursor = false;
/// <summary>
/// The position where the mouse cursor was when it was hidden. Sometimes the text editor gets MouseMove
/// events when typing text even if the mouse is not moved.
/// </summary>
Point mouseCursorHidePosition;
Point virtualTop = new Point(0, 0);
TextAreaControl motherTextAreaControl;
TextEditorControl motherTextEditorControl;
List<BracketHighlightingSheme> bracketshemes = new List<BracketHighlightingSheme>();
TextAreaClipboardHandler textAreaClipboardHandler;
bool autoClearSelection = false;
List<AbstractMargin> leftMargins = new List<AbstractMargin>();
TextView textView;
GutterMargin gutterMargin;
FoldMargin foldMargin;
IconBarMargin iconBarMargin;
SelectionManager selectionManager;
Caret caret;
internal Point mousepos = new Point(0, 0);
//public Point selectionStartPos = new Point(0,0);
bool disposed;
[Browsable(false)]
public IList<AbstractMargin> LeftMargins {
get {
return leftMargins.AsReadOnly();
}
}
public void InsertLeftMargin(int index, AbstractMargin margin)
{
leftMargins.Insert(index, margin);
Refresh();
}
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();
}
caret.UpdateCaretPosition();
}
}
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);
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 TextLocation(0, 0);
SelectionManager.SelectionCollection.Clear();
}
void SearchMatchingBracket(object sender, EventArgs e)
{
if (!TextEditorProperties.ShowMatchingBracket) {
textView.Highlight = null;
return;
}
int oldLine1 = -1, oldLine2 = -1;
if (textView.Highlight != null && textView.Highlight.OpenBrace.Y >=0 && textView.Highlight.OpenBrace.Y < Document.TotalNumberOfLines) {
oldLine1 = textView.Highlight.OpenBrace.Y;
}
if (textView.Highlight != null && textView.Highlight.CloseBrace.Y >=0 && textView.Highlight.CloseBrace.Y < Document.TotalNumberOfLines) {
oldLine2 = textView.Highlight.CloseBrace.Y;
}
textView.Highlight = FindMatchingBracketHighlight();
if (oldLine1 >= 0)
UpdateLine(oldLine1);
if (oldLine2 >= 0 && oldLine2 != oldLine1)
UpdateLine(oldLine2);
if (textView.Highlight != null) {
int newLine1 = textView.Highlight.OpenBrace.Y;
int newLine2 = textView.Highlight.CloseBrace.Y;
if (newLine1 != oldLine1 && newLine1 != oldLine2)
UpdateLine(newLine1);
if (newLine2 != oldLine1 && newLine2 != oldLine2 && newLine2 != newLine1)
UpdateLine(newLine2);
}
}
public Highlight FindMatchingBracketHighlight()
{
if (Caret.Offset == 0)
return null;
foreach (BracketHighlightingSheme bracketsheme in bracketshemes) {
Highlight highlight = bracketsheme.GetHighlight(Document, Caret.Offset - 1);
if (highlight != null) {
return highlight;
}
}
return null;
}
public void SetDesiredColumn()
{
Caret.DesiredColumn = TextView.GetDrawingXPos(Caret.Line, Caret.Column) + VirtualTop.X;
}
public void SetCaretToDesiredColumn()
{
FoldMarker dummy;
Caret.Position = textView.GetLogicalColumn(Caret.Line, Caret.DesiredColumn + VirtualTop.X, out dummy);
}
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;
}
CloseToolTip();
}
protected override void OnMouseDown(System.Windows.Forms.MouseEventArgs e)
{
// this corrects weird problems when text is selected,
// then a menu item is selected, then the text is
// clicked again - it correctly synchronises the
// click position
mousepos = new Point(e.X, e.Y);
base.OnMouseDown(e);
CloseToolTip();
foreach (AbstractMargin margin in leftMargins) {
if (margin.DrawingPosition.Contains(e.X, e.Y)) {
margin.HandleMouseDown(new Point(e.X, e.Y), e.Button);
}
}
}
/// <summary>
/// Shows the mouse cursor if it has been hidden.
/// </summary>
/// <param name="forceShow"><c>true</c> to always show the cursor or <c>false</c> to show it only if it has been moved since it was hidden.</param>
internal void ShowHiddenCursor(bool forceShow)
{
if (hiddenMouseCursor) {
if (mouseCursorHidePosition != Cursor.Position || forceShow) {
Cursor.Show();
hiddenMouseCursor = false;
}
}
}
// 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;
void SetToolTip(string text, int lineNumber)
{
if (toolTip == null || toolTip.IsDisposed)
toolTip = new DeclarationViewWindow(this.FindForm());
if (oldToolTip == text)
return;
if (text == null) {
toolTip.Hide();
} else {
Point p = Control.MousePosition;
Point cp = PointToClient(p);
if (lineNumber >= 0) {
lineNumber = this.Document.GetVisibleLine(lineNumber);
p.Y = (p.Y - cp.Y) + (lineNumber * this.TextView.FontHeight) - this.virtualTop.Y;
}
p.Offset(3, 3);
toolTip.Owner = this.FindForm();
toolTip.Location = p;
toolTip.Description = text;
toolTip.HideOnClick = true;
toolTip.Show();
}
oldToolTip = text;
}
public event ToolTipRequestEventHandler ToolTipRequest;
protected virtual void OnToolTipRequest(ToolTipRequestEventArgs e)
{
if (ToolTipRequest != null) {
ToolTipRequest(this, e);
}
}
bool toolTipActive;
/// <summary>
/// Rectangle in text area that caused the current tool tip.
/// Prevents tooltip from re-showing when it was closed because of a click or keyboard
/// input and the mouse was not used.
/// </summary>
Rectangle toolTipRectangle;
void CloseToolTip()
{
if (toolTipActive) {
//Console.WriteLine("Closing tooltip");
toolTipActive = false;
SetToolTip(null, -1);
}
ResetMouseEventArgs();
}
protected override void OnMouseHover(EventArgs e)
{
base.OnMouseHover(e);
//Console.WriteLine("Hover raised at " + PointToClient(Control.MousePosition));
if (MouseButtons == MouseButtons.None) {
RequestToolTip(PointToClient(Control.MousePosition));
} else {
CloseToolTip();
}
}
protected void RequestToolTip(Point mousePos)
{
if (toolTipRectangle.Contains(mousePos)) {
if (!toolTipActive)
ResetMouseEventArgs();
return;
}
//Console.WriteLine("Request tooltip for " + mousePos);
toolTipRectangle = new Rectangle(mousePos.X - 4, mousePos.Y - 4, 8, 8);
TextLocation logicPos = textView.GetLogicalPosition(mousePos.X - textView.DrawingPosition.Left,
mousePos.Y - textView.DrawingPosition.Top);
bool inDocument = textView.DrawingPosition.Contains(mousePos)
&& logicPos.Y >= 0 && logicPos.Y < Document.TotalNumberOfLines;
ToolTipRequestEventArgs args = new ToolTipRequestEventArgs(mousePos, logicPos, inDocument);
OnToolTipRequest(args);
if (args.ToolTipShown) {
//Console.WriteLine("Set tooltip to " + args.toolTipText);
toolTipActive = true;
SetToolTip(args.toolTipText, inDocument ? logicPos.Y + 1 : -1);
} else {
CloseToolTip();
}
}
// external interface to the attached event
internal void RaiseMouseMove(MouseEventArgs e)
{
OnMouseMove(e);
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (!toolTipRectangle.Contains(e.Location)) {
toolTipRectangle = Rectangle.Empty;
if (toolTipActive)
RequestToolTip(e.Location);
}
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)) {
TextLocation realmousepos = TextView.GetLogicalPosition(e.X - TextView.DrawingPosition.X, e.Y - TextView.DrawingPosition.Y);
if(SelectionManager.IsSelected(Document.PositionToOffset(realmousepos)) && MouseButtons == MouseButtons.None) {
// mouse is hovering over a selection, so show default mouse
this.Cursor = Cursors.Default;
} else {
// mouse is hovering over text area, not a selection, so show the textView cursor
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;
bool isFullRepaint = clipRectangle.X == 0 && clipRectangle.Y == 0
&& clipRectangle.Width == this.Width && clipRectangle.Height == this.Height;
g.TextRenderingHint = this.TextEditorProperties.TextRenderingHint;
if (updateMargin != null) {
updateMargin.Paint(g, updateMargin.DrawingPosition);
// clipRectangle.Intersect(updateMargin.DrawingPosition);
}
if (clipRectangle.Width <= 0 || clipRectangle.Height <= 0) {
return;
}
foreach (AbstractMargin margin in leftMargins) {
if (margin.IsVisible) {
Rectangle marginRectangle = new Rectangle(currentXPos , currentYPos, margin.Size.Width, Height - currentYPos);
if (marginRectangle != margin.DrawingPosition) {
// margin changed size
if (!isFullRepaint && !clipRectangle.Contains(marginRectangle)) {
Invalidate(); // do a full repaint
}
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;
// update caret position (but outside of WM_PAINT!)
BeginInvoke((MethodInvoker)caret.UpdateCaretPosition);
}
if (clipRectangle.IntersectsWith(textViewArea)) {
textViewArea.Intersect(clipRectangle);
if (!textViewArea.IsEmpty) {
textView.Paint(g, textViewArea);
}
}
if (adjustScrollBars) {
this.motherTextAreaControl.AdjustScrollBars();
}
// we cannot update the caret position here, it's not allowed to call the caret API inside WM_PAINT
//Caret.UpdateCaretPosition();
base.OnPaint(e);
}
void DocumentFoldingsChanged(object sender, EventArgs e)
{
Caret.UpdateCaretPosition();
Invalidate();
this.motherTextAreaControl.AdjustScrollBars();
}
#region keyboard handling methods
/// <summary>
/// This method is called on each Keypress
/// </summary>
/// <returns>
/// True, if the key is handled by this method and should NOT be
/// inserted in the textarea.
/// </returns>
protected internal virtual bool HandleKeyPress(char ch)
{
if (KeyEventHandler != null) {
return KeyEventHandler(ch);
}
return false;
}
// Fixes SD2-747: Form containing the text editor and a button with a shortcut
protected override bool IsInputChar(char charCode)
{
return true;
}
internal bool IsReadOnly(int offset)
{
if (Document.ReadOnly) {
return true;
}
if (TextEditorProperties.SupportReadOnlySegments) {
return Document.MarkerStrategy.GetMarkers(offset).Exists(m=>m.IsReadOnly);
} else {
return false;
}
}
internal bool IsReadOnly(int offset, int length)
{
if (Document.ReadOnly) {
return true;
}
if (TextEditorProperties.SupportReadOnlySegments) {
return Document.MarkerStrategy.GetMarkers(offset, length).Exists(m=>m.IsReadOnly);
} else {
return false;
}
}
public void SimulateKeyPress(char ch)
{
if (SelectionManager.HasSomethingSelected) {
if (SelectionManager.SelectionIsReadonly)
return;
} else if (IsReadOnly(Caret.Offset)) {
return;
}
if (ch < ' ') {
return;
}
if (!hiddenMouseCursor && TextEditorProperties.HideMouseCursor) {
if (this.ClientRectangle.Contains(PointToClient(Cursor.Position))) {
mouseCursorHidePosition = Cursor.Position;
hiddenMouseCursor = true;
Cursor.Hide();
}
}
CloseToolTip();
BeginUpdate();
Document.UndoStack.StartUndoGroup();
try {
// 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;
Document.FormattingStrategy.FormatLine(this, currentLineNr, Document.PositionToOffset(Caret.Position), ch);
EndUpdate();
} finally {
Document.UndoStack.EndUndoGroup();
}
}
protected override void OnKeyPress(KeyPressEventArgs e)
{
base.OnKeyPress(e);
SimulateKeyPress(e.KeyChar);
e.Handled = true;
}
/// <summary>
/// This method executes a dialog key
/// </summary>
public bool ExecuteDialogKey(Keys keyData)
{
// try, if a dialog key processor was set to use this
if (DoProcessDialogKey != null && DoProcessDialogKey(keyData)) {
return true;
}
// if not (or the process was 'silent', use the standard edit actions
IEditAction action = motherTextEditorControl.GetEditAction(keyData);
AutoClearSelection = true;
if (action != null) {
BeginUpdate();
try {
lock (Document) {
action.Execute(this);
if (SelectionManager.HasSomethingSelected && AutoClearSelection /*&& caretchanged*/) {
if (Document.TextEditorProperties.DocumentSelectionMode == DocumentSelectionMode.Normal) {
SelectionManager.ClearSelection();
}
}
}
} finally {
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 (motherTextAreaControl == null)
return false;
if (SelectionManager.HasSomethingSelected)
return !SelectionManager.SelectionIsReadonly;
else
return !IsReadOnly(Caret.Offset);
}
}
string GenerateWhitespaceString(int length)
{
return new string(' ', length);
}
/// <remarks>
/// Inserts a single character at the caret position
/// </remarks>
public void InsertChar(char ch)
{
bool updating = motherTextEditorControl.IsInUpdate;
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 = ' ';
}
Document.UndoStack.StartUndoGroup();
if (Document.TextEditorProperties.DocumentSelectionMode == DocumentSelectionMode.Normal &&
SelectionManager.SelectionCollection.Count > 0) {
Caret.Position = SelectionManager.SelectionCollection[0].StartPosition;
SelectionManager.RemoveSelectedText();
}
LineSegment caretLine = Document.GetLineSegment(Caret.Line);
int offset = Caret.Offset;
// use desired column for generated whitespaces
int dc = Caret.Column;
if (caretLine.Length < dc && ch != '\n') {
Document.Insert(offset, GenerateWhitespaceString(dc - caretLine.Length) + ch);
} else {
Document.Insert(offset, ch.ToString());
}
Document.UndoStack.EndUndoGroup();
++Caret.Column;
if (!updating) {
EndUpdate();
UpdateLineToEnd(Caret.Line, Caret.Column);
}
// I prefer to set NOT the standard column, if you type something
// ++Caret.DesiredColumn;
}
/// <remarks>
/// Inserts a whole string at the caret position
/// </remarks>
public void InsertString(string str)
{
bool updating = motherTextEditorControl.IsInUpdate;
if (!updating) {
BeginUpdate();
}
try {
Document.UndoStack.StartUndoGroup();
if (Document.TextEditorProperties.DocumentSelectionMode == DocumentSelectionMode.Normal &&
SelectionManager.SelectionCollection.Count > 0) {
Caret.Position = SelectionManager.SelectionCollection[0].StartPosition;
SelectionManager.RemoveSelectedText();
}
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);
}
Document.UndoStack.EndUndoGroup();
if (oldLine != Caret.Line) {
UpdateToEnd(oldLine);
} else {
UpdateLineToEnd(Caret.Line, Caret.Column);
}
} finally {
if (!updating) {
EndUpdate();
}
}
}
/// <remarks>
/// Replaces a char at the caret position
/// </remarks>
public void ReplaceChar(char ch)
{
bool updating = motherTextEditorControl.IsInUpdate;
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) {
if (!disposed) {
disposed = true;
if (caret != null) {
caret.PositionChanged -= new EventHandler(SearchMatchingBracket);
caret.Dispose();
}
if (selectionManager != null) {
selectionManager.Dispose();
}
Document.TextContentChanged -= new EventHandler(TextContentChanged);
Document.FoldingManager.FoldingsChanged -= new EventHandler(DocumentFoldingsChanged);
motherTextAreaControl = null;
motherTextEditorControl = null;
foreach (AbstractMargin margin in leftMargins) {
if (margin is IDisposable)
(margin as IDisposable).Dispose();
}
textView.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 = Document.GetVisibleLine(lineBegin);
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
}
}

View File

@@ -0,0 +1,268 @@
// <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.Drawing;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using ICSharpCode.TextEditor.Document;
using ICSharpCode.TextEditor.Util;
namespace ICSharpCode.TextEditor
{
public class TextAreaClipboardHandler
{
TextArea textArea;
public bool EnableCut {
get {
return textArea.EnableCutOrPaste; //textArea.SelectionManager.HasSomethingSelected;
}
}
public bool EnableCopy {
get {
return true; //textArea.SelectionManager.HasSomethingSelected;
}
}
public delegate bool ClipboardContainsTextDelegate();
/// <summary>
/// Is called when CachedClipboardContainsText should be updated.
/// If this property is null (the default value), the text editor uses
/// System.Windows.Forms.Clipboard.ContainsText.
/// </summary>
/// <remarks>
/// This property is useful if you want to prevent the default Clipboard.ContainsText
/// behaviour that waits for the clipboard to be available - the clipboard might
/// never become available if it is owned by a process that is paused by the debugger.
/// </remarks>
public static ClipboardContainsTextDelegate GetClipboardContainsText;
public bool EnablePaste {
get {
if (!textArea.EnableCutOrPaste)
return false;
ClipboardContainsTextDelegate d = GetClipboardContainsText;
if (d != null) {
return d();
} else {
try {
return Clipboard.ContainsText();
} catch (ExternalException) {
return false;
}
}
}
}
public bool EnableDelete {
get {
return textArea.SelectionManager.HasSomethingSelected && !textArea.SelectionManager.SelectionIsReadonly;
}
}
public bool EnableSelectAll {
get {
return true;
}
}
public TextAreaClipboardHandler(TextArea textArea)
{
this.textArea = textArea;
textArea.SelectionManager.SelectionChanged += new EventHandler(DocumentSelectionChanged);
}
void DocumentSelectionChanged(object sender, EventArgs e)
{
// ((DefaultWorkbench)WorkbenchSingleton.Workbench).UpdateToolbars();
}
const string LineSelectedType = "MSDEVLineSelect"; // This is the type VS 2003 and 2005 use for flagging a whole line copy
bool CopyTextToClipboard(string stringToCopy, bool asLine)
{
if (stringToCopy.Length > 0) {
DataObject dataObject = new DataObject();
dataObject.SetData(DataFormats.UnicodeText, true, stringToCopy);
if (asLine) {
MemoryStream lineSelected = new MemoryStream(1);
lineSelected.WriteByte(1);
dataObject.SetData(LineSelectedType, false, lineSelected);
}
// Default has no highlighting, therefore we don't need RTF output
if (textArea.Document.HighlightingStrategy.Name != "Default") {
dataObject.SetData(DataFormats.Rtf, RtfWriter.GenerateRtf(textArea));
}
OnCopyText(new CopyTextEventArgs(stringToCopy));
SafeSetClipboard(dataObject);
return true;
} else {
return false;
}
}
// Code duplication: TextAreaClipboardHandler.cs also has SafeSetClipboard
[ThreadStatic] static int SafeSetClipboardDataVersion;
static void SafeSetClipboard(object dataObject)
{
// Work around ExternalException bug. (SD2-426)
// Best reproducable inside Virtual PC.
int version = unchecked(++SafeSetClipboardDataVersion);
try {
Clipboard.SetDataObject(dataObject, true);
} catch (ExternalException) {
Timer timer = new Timer();
timer.Interval = 100;
timer.Tick += delegate {
timer.Stop();
timer.Dispose();
if (SafeSetClipboardDataVersion == version) {
try {
Clipboard.SetDataObject(dataObject, true, 10, 50);
} catch (ExternalException) { }
}
};
timer.Start();
}
}
bool CopyTextToClipboard(string stringToCopy)
{
return CopyTextToClipboard(stringToCopy, false);
}
public void Cut(object sender, EventArgs e)
{
if (textArea.SelectionManager.HasSomethingSelected) {
if (CopyTextToClipboard(textArea.SelectionManager.SelectedText)) {
if (textArea.SelectionManager.SelectionIsReadonly)
return;
// Remove text
textArea.BeginUpdate();
textArea.Caret.Position = textArea.SelectionManager.SelectionCollection[0].StartPosition;
textArea.SelectionManager.RemoveSelectedText();
textArea.EndUpdate();
}
} else if (textArea.Document.TextEditorProperties.CutCopyWholeLine) {
// No text was selected, select and cut the entire line
int curLineNr = textArea.Document.GetLineNumberForOffset(textArea.Caret.Offset);
LineSegment lineWhereCaretIs = textArea.Document.GetLineSegment(curLineNr);
string caretLineText = textArea.Document.GetText(lineWhereCaretIs.Offset, lineWhereCaretIs.TotalLength);
textArea.SelectionManager.SetSelection(textArea.Document.OffsetToPosition(lineWhereCaretIs.Offset), textArea.Document.OffsetToPosition(lineWhereCaretIs.Offset + lineWhereCaretIs.TotalLength));
if (CopyTextToClipboard(caretLineText, true)) {
if (textArea.SelectionManager.SelectionIsReadonly)
return;
// remove line
textArea.BeginUpdate();
textArea.Caret.Position = textArea.Document.OffsetToPosition(lineWhereCaretIs.Offset);
textArea.SelectionManager.RemoveSelectedText();
textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.PositionToEnd, new TextLocation(0, curLineNr)));
textArea.EndUpdate();
}
}
}
public void Copy(object sender, EventArgs e)
{
if (!CopyTextToClipboard(textArea.SelectionManager.SelectedText) && textArea.Document.TextEditorProperties.CutCopyWholeLine) {
// No text was selected, select the entire line, copy it, and then deselect
int curLineNr = textArea.Document.GetLineNumberForOffset(textArea.Caret.Offset);
LineSegment lineWhereCaretIs = textArea.Document.GetLineSegment(curLineNr);
string caretLineText = textArea.Document.GetText(lineWhereCaretIs.Offset, lineWhereCaretIs.TotalLength);
CopyTextToClipboard(caretLineText, true);
}
}
public void Paste(object sender, EventArgs e)
{
if (!textArea.EnableCutOrPaste) {
return;
}
// Clipboard.GetDataObject may throw an exception...
for (int i = 0;; i++) {
try {
IDataObject data = Clipboard.GetDataObject();
if (data == null)
return;
bool fullLine = data.GetDataPresent(LineSelectedType);
if (data.GetDataPresent(DataFormats.UnicodeText)) {
string text = (string)data.GetData(DataFormats.UnicodeText);
// we got NullReferenceExceptions here, apparently the clipboard can contain null strings
if (!string.IsNullOrEmpty(text)) {
textArea.Document.UndoStack.StartUndoGroup();
try {
if (textArea.SelectionManager.HasSomethingSelected) {
textArea.Caret.Position = textArea.SelectionManager.SelectionCollection[0].StartPosition;
textArea.SelectionManager.RemoveSelectedText();
}
if (fullLine) {
int col = textArea.Caret.Column;
textArea.Caret.Column = 0;
if (!textArea.IsReadOnly(textArea.Caret.Offset))
textArea.InsertString(text);
textArea.Caret.Column = col;
} else {
// textArea.EnableCutOrPaste already checked readonly for this case
textArea.InsertString(text);
}
} finally {
textArea.Document.UndoStack.EndUndoGroup();
}
}
}
return;
} catch (ExternalException) {
// GetDataObject does not provide RetryTimes parameter
if (i > 5) throw;
}
}
}
public void Delete(object sender, EventArgs e)
{
new ICSharpCode.TextEditor.Actions.Delete().Execute(textArea);
}
public void SelectAll(object sender, EventArgs e)
{
new ICSharpCode.TextEditor.Actions.SelectWholeDocument().Execute(textArea);
}
protected virtual void OnCopyText(CopyTextEventArgs e)
{
if (CopyText != null) {
CopyText(this, e);
}
}
public event CopyTextEventHandler CopyText;
}
public delegate void CopyTextEventHandler(object sender, CopyTextEventArgs e);
public class CopyTextEventArgs : EventArgs
{
string text;
public string Text {
get {
return text;
}
}
public CopyTextEventArgs(string text)
{
this.text = text;
}
}
}

View File

@@ -0,0 +1,463 @@
// <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.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor
{
/// <summary>
/// This class paints the textarea.
/// </summary>
[ToolboxItem(false)]
public class TextAreaControl : Panel
{
TextEditorControl motherTextEditorControl;
HRuler hRuler = null;
VScrollBar vScrollBar = new VScrollBar();
HScrollBar hScrollBar = new HScrollBar();
TextArea textArea;
bool doHandleMousewheel = true;
bool disposed;
public TextArea TextArea {
get {
return textArea;
}
}
public SelectionManager SelectionManager {
get {
return textArea.SelectionManager;
}
}
public Caret Caret {
get {
return textArea.Caret;
}
}
[Browsable(false)]
public IDocument Document {
get {
if (motherTextEditorControl != null)
return motherTextEditorControl.Document;
return null;
}
}
public ITextEditorProperties TextEditorProperties {
get {
if (motherTextEditorControl != null)
return motherTextEditorControl.TextEditorProperties;
return null;
}
}
public VScrollBar VScrollBar {
get {
return vScrollBar;
}
}
public HScrollBar HScrollBar {
get {
return hScrollBar;
}
}
public bool DoHandleMousewheel {
get {
return doHandleMousewheel;
}
set {
doHandleMousewheel = value;
}
}
public TextAreaControl(TextEditorControl motherTextEditorControl)
{
this.motherTextEditorControl = motherTextEditorControl;
this.textArea = new TextArea(motherTextEditorControl, this);
Controls.Add(textArea);
vScrollBar.ValueChanged += new EventHandler(VScrollBarValueChanged);
Controls.Add(this.vScrollBar);
hScrollBar.ValueChanged += new EventHandler(HScrollBarValueChanged);
Controls.Add(this.hScrollBar);
ResizeRedraw = true;
Document.TextContentChanged += DocumentTextContentChanged;
Document.DocumentChanged += AdjustScrollBarsOnDocumentChange;
Document.UpdateCommited += DocumentUpdateCommitted;
}
protected override void Dispose(bool disposing)
{
if (disposing) {
if (!disposed) {
disposed = true;
Document.TextContentChanged -= DocumentTextContentChanged;
Document.DocumentChanged -= AdjustScrollBarsOnDocumentChange;
Document.UpdateCommited -= DocumentUpdateCommitted;
motherTextEditorControl = null;
if (vScrollBar != null) {
vScrollBar.Dispose();
vScrollBar = null;
}
if (hScrollBar != null) {
hScrollBar.Dispose();
hScrollBar = null;
}
if (hRuler != null) {
hRuler.Dispose();
hRuler = null;
}
}
}
base.Dispose(disposing);
}
void DocumentTextContentChanged(object sender, EventArgs e)
{
// after the text content is changed abruptly, we need to validate the
// caret position - otherwise the caret position is invalid for a short amount
// of time, which can break client code that expects that the caret position is always valid
Caret.ValidateCaretPos();
}
protected override void OnResize(System.EventArgs e)
{
base.OnResize(e);
ResizeTextArea();
}
public void ResizeTextArea()
{
int y = 0;
int h = 0;
if (hRuler != null) {
hRuler.Bounds = new Rectangle(0,
0,
Width - SystemInformation.HorizontalScrollBarArrowWidth,
textArea.TextView.FontHeight);
y = hRuler.Bounds.Bottom;
h = hRuler.Bounds.Height;
}
textArea.Bounds = new Rectangle(0, y,
Width - SystemInformation.HorizontalScrollBarArrowWidth,
Height - SystemInformation.VerticalScrollBarArrowHeight - h);
SetScrollBarBounds();
}
public void SetScrollBarBounds()
{
vScrollBar.Bounds = new Rectangle(textArea.Bounds.Right, 0, SystemInformation.HorizontalScrollBarArrowWidth, Height - SystemInformation.VerticalScrollBarArrowHeight);
hScrollBar.Bounds = new Rectangle(0,
textArea.Bounds.Bottom,
Width - SystemInformation.HorizontalScrollBarArrowWidth,
SystemInformation.VerticalScrollBarArrowHeight);
}
bool adjustScrollBarsOnNextUpdate;
Point scrollToPosOnNextUpdate;
void AdjustScrollBarsOnDocumentChange(object sender, DocumentEventArgs e)
{
if (motherTextEditorControl.IsInUpdate == false) {
AdjustScrollBarsClearCache();
AdjustScrollBars();
} else {
adjustScrollBarsOnNextUpdate = true;
}
}
void DocumentUpdateCommitted(object sender, EventArgs e)
{
if (motherTextEditorControl.IsInUpdate == false) {
Caret.ValidateCaretPos();
// AdjustScrollBarsOnCommittedUpdate
if (!scrollToPosOnNextUpdate.IsEmpty) {
ScrollTo(scrollToPosOnNextUpdate.Y, scrollToPosOnNextUpdate.X);
}
if (adjustScrollBarsOnNextUpdate) {
AdjustScrollBarsClearCache();
AdjustScrollBars();
}
}
}
int[] lineLengthCache;
const int LineLengthCacheAdditionalSize = 100;
void AdjustScrollBarsClearCache()
{
if (lineLengthCache != null) {
if (lineLengthCache.Length < this.Document.TotalNumberOfLines + 2 * LineLengthCacheAdditionalSize) {
lineLengthCache = null;
} else {
Array.Clear(lineLengthCache, 0, lineLengthCache.Length);
}
}
}
public void AdjustScrollBars()
{
adjustScrollBarsOnNextUpdate = false;
vScrollBar.Minimum = 0;
// number of visible lines in document (folding!)
vScrollBar.Maximum = textArea.MaxVScrollValue;
int max = 0;
int firstLine = textArea.TextView.FirstVisibleLine;
int lastLine = this.Document.GetFirstLogicalLine(textArea.TextView.FirstPhysicalLine + textArea.TextView.VisibleLineCount);
if (lastLine >= this.Document.TotalNumberOfLines)
lastLine = this.Document.TotalNumberOfLines - 1;
if (lineLengthCache == null || lineLengthCache.Length <= lastLine) {
lineLengthCache = new int[lastLine + LineLengthCacheAdditionalSize];
}
for (int lineNumber = firstLine; lineNumber <= lastLine; lineNumber++) {
LineSegment lineSegment = this.Document.GetLineSegment(lineNumber);
if (Document.FoldingManager.IsLineVisible(lineNumber)) {
if (lineLengthCache[lineNumber] > 0) {
max = Math.Max(max, lineLengthCache[lineNumber]);
} else {
int visualLength = textArea.TextView.GetVisualColumnFast(lineSegment, lineSegment.Length);
lineLengthCache[lineNumber] = Math.Max(1, visualLength);
max = Math.Max(max, visualLength);
}
}
}
hScrollBar.Minimum = 0;
hScrollBar.Maximum = (Math.Max(max + 20, textArea.TextView.VisibleColumnCount - 1));
vScrollBar.LargeChange = Math.Max(0, textArea.TextView.DrawingPosition.Height);
vScrollBar.SmallChange = Math.Max(0, textArea.TextView.FontHeight);
hScrollBar.LargeChange = Math.Max(0, textArea.TextView.VisibleColumnCount - 1);
hScrollBar.SmallChange = Math.Max(0, (int)textArea.TextView.SpaceWidth);
}
public void OptionsChanged()
{
textArea.OptionsChanged();
if (textArea.TextEditorProperties.ShowHorizontalRuler) {
if (hRuler == null) {
hRuler = new HRuler(textArea);
Controls.Add(hRuler);
ResizeTextArea();
} else {
hRuler.Invalidate();
}
} else {
if (hRuler != null) {
Controls.Remove(hRuler);
hRuler.Dispose();
hRuler = null;
ResizeTextArea();
}
}
AdjustScrollBars();
}
void VScrollBarValueChanged(object sender, EventArgs e)
{
textArea.VirtualTop = new Point(textArea.VirtualTop.X, vScrollBar.Value);
textArea.Invalidate();
AdjustScrollBars();
}
void HScrollBarValueChanged(object sender, EventArgs e)
{
textArea.VirtualTop = new Point(hScrollBar.Value * textArea.TextView.WideSpaceWidth, textArea.VirtualTop.Y);
textArea.Invalidate();
}
Util.MouseWheelHandler mouseWheelHandler = new Util.MouseWheelHandler();
public void HandleMouseWheel(MouseEventArgs e)
{
int scrollDistance = mouseWheelHandler.GetScrollAmount(e);
if (scrollDistance == 0)
return;
if ((Control.ModifierKeys & Keys.Control) != 0 && TextEditorProperties.MouseWheelTextZoom) {
if (scrollDistance > 0) {
motherTextEditorControl.Font = new Font(motherTextEditorControl.Font.Name,
motherTextEditorControl.Font.Size + 1);
} else {
motherTextEditorControl.Font = new Font(motherTextEditorControl.Font.Name,
Math.Max(6, motherTextEditorControl.Font.Size - 1));
}
} else {
if (TextEditorProperties.MouseWheelScrollDown)
scrollDistance = -scrollDistance;
int newValue = vScrollBar.Value + vScrollBar.SmallChange * scrollDistance;
vScrollBar.Value = Math.Max(vScrollBar.Minimum, Math.Min(vScrollBar.Maximum - vScrollBar.LargeChange + 1, newValue));
}
}
protected override void OnMouseWheel(MouseEventArgs e)
{
base.OnMouseWheel(e);
if (DoHandleMousewheel) {
HandleMouseWheel(e);
}
}
public void ScrollToCaret()
{
ScrollTo(textArea.Caret.Line, textArea.Caret.Column);
}
public void ScrollTo(int line, int column)
{
if (motherTextEditorControl.IsInUpdate) {
scrollToPosOnNextUpdate = new Point(column, line);
return;
} else {
scrollToPosOnNextUpdate = Point.Empty;
}
ScrollTo(line);
int curCharMin = (int)(this.hScrollBar.Value - this.hScrollBar.Minimum);
int curCharMax = curCharMin + textArea.TextView.VisibleColumnCount;
int pos = textArea.TextView.GetVisualColumn(line, column);
if (textArea.TextView.VisibleColumnCount < 0) {
hScrollBar.Value = 0;
} else {
if (pos < curCharMin) {
hScrollBar.Value = (int)(Math.Max(0, pos - scrollMarginHeight));
} else {
if (pos > curCharMax) {
hScrollBar.Value = (int)Math.Max(0, Math.Min(hScrollBar.Maximum, (pos - textArea.TextView.VisibleColumnCount + scrollMarginHeight)));
}
}
}
}
int scrollMarginHeight = 3;
/// <summary>
/// Ensure that <paramref name="line"/> is visible.
/// </summary>
public void ScrollTo(int line)
{
line = Math.Max(0, Math.Min(Document.TotalNumberOfLines - 1, line));
line = Document.GetVisibleLine(line);
int curLineMin = textArea.TextView.FirstPhysicalLine;
if (textArea.TextView.LineHeightRemainder > 0) {
curLineMin ++;
}
if (line - scrollMarginHeight + 3 < curLineMin) {
this.vScrollBar.Value = Math.Max(0, Math.Min(this.vScrollBar.Maximum, (line - scrollMarginHeight + 3) * textArea.TextView.FontHeight)) ;
VScrollBarValueChanged(this, EventArgs.Empty);
} else {
int curLineMax = curLineMin + this.textArea.TextView.VisibleLineCount;
if (line + scrollMarginHeight - 1 > curLineMax) {
if (this.textArea.TextView.VisibleLineCount == 1) {
this.vScrollBar.Value = Math.Max(0, Math.Min(this.vScrollBar.Maximum, (line - scrollMarginHeight - 1) * textArea.TextView.FontHeight)) ;
} else {
this.vScrollBar.Value = Math.Min(this.vScrollBar.Maximum,
(line - this.textArea.TextView.VisibleLineCount + scrollMarginHeight - 1)* textArea.TextView.FontHeight) ;
}
VScrollBarValueChanged(this, EventArgs.Empty);
}
}
}
/// <summary>
/// Scroll so that the specified line is centered.
/// </summary>
/// <param name="line">Line to center view on</param>
/// <param name="treshold">If this action would cause scrolling by less than or equal to
/// <paramref name="treshold"/> lines in any direction, don't scroll.
/// Use -1 to always center the view.</param>
public void CenterViewOn(int line, int treshold)
{
line = Math.Max(0, Math.Min(Document.TotalNumberOfLines - 1, line));
// convert line to visible line:
line = Document.GetVisibleLine(line);
// subtract half the visible line count
line -= textArea.TextView.VisibleLineCount / 2;
int curLineMin = textArea.TextView.FirstPhysicalLine;
if (textArea.TextView.LineHeightRemainder > 0) {
curLineMin ++;
}
if (Math.Abs(curLineMin - line) > treshold) {
// scroll:
this.vScrollBar.Value = Math.Max(0, Math.Min(this.vScrollBar.Maximum, (line - scrollMarginHeight + 3) * textArea.TextView.FontHeight)) ;
VScrollBarValueChanged(this, EventArgs.Empty);
}
}
public void JumpTo(int line)
{
line = Math.Max(0, Math.Min(line, Document.TotalNumberOfLines - 1));
string text = Document.GetText(Document.GetLineSegment(line));
JumpTo(line, text.Length - text.TrimStart().Length);
}
public void JumpTo(int line, int column)
{
textArea.Focus();
textArea.SelectionManager.ClearSelection();
textArea.Caret.Position = new TextLocation(column, line);
textArea.SetDesiredColumn();
ScrollToCaret();
}
public event MouseEventHandler ShowContextMenu;
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x007B) { // handle WM_CONTEXTMENU
if (ShowContextMenu != null) {
long lParam = m.LParam.ToInt64();
int x = unchecked((short)(lParam & 0xffff));
int y = unchecked((short)((lParam & 0xffff0000) >> 16));
if (x == -1 && y == -1) {
Point pos = Caret.ScreenPosition;
ShowContextMenu(this, new MouseEventArgs(MouseButtons.None, 0, pos.X, pos.Y + textArea.TextView.FontHeight, 0));
} else {
Point pos = PointToClient(new Point(x, y));
ShowContextMenu(this, new MouseEventArgs(MouseButtons.Right, 1, pos.X, pos.Y, 0));
}
}
}
base.WndProc(ref m);
}
protected override void OnEnter(EventArgs e)
{
// SD2-1072 - Make sure the caret line is valid if anyone
// has handlers for the Enter event.
Caret.ValidateCaretPos();
base.OnEnter(e);
}
}
}

View File

@@ -0,0 +1,144 @@
// <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.Drawing;
using System.Windows.Forms;
using ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor
{
public class TextAreaDragDropHandler
{
public static Action<Exception> OnDragDropException = ex => MessageBox.Show(ex.ToString());
TextArea textArea;
public void Attach(TextArea textArea)
{
this.textArea = textArea;
textArea.AllowDrop = true;
textArea.DragEnter += MakeDragEventHandler(OnDragEnter);
textArea.DragDrop += MakeDragEventHandler(OnDragDrop);
textArea.DragOver += MakeDragEventHandler(OnDragOver);
}
/// <summary>
/// Create a drag'n'drop event handler.
/// Windows Forms swallows unhandled exceptions during drag'n'drop, so we report them here.
/// </summary>
static DragEventHandler MakeDragEventHandler(DragEventHandler h)
{
return (sender, e) => {
try {
h(sender, e);
} catch (Exception ex) {
OnDragDropException(ex);
}
};
}
static DragDropEffects GetDragDropEffect(DragEventArgs e)
{
if ((e.AllowedEffect & DragDropEffects.Move) > 0 &&
(e.AllowedEffect & DragDropEffects.Copy) > 0) {
return (e.KeyState & 8) > 0 ? DragDropEffects.Copy : DragDropEffects.Move;
} else if ((e.AllowedEffect & DragDropEffects.Move) > 0) {
return DragDropEffects.Move;
} else if ((e.AllowedEffect & DragDropEffects.Copy) > 0) {
return DragDropEffects.Copy;
}
return DragDropEffects.None;
}
protected void OnDragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(typeof(string))) {
e.Effect = GetDragDropEffect(e);
}
}
void InsertString(int offset, string str)
{
textArea.Document.Insert(offset, str);
textArea.SelectionManager.SetSelection(new DefaultSelection(textArea.Document,
textArea.Document.OffsetToPosition(offset),
textArea.Document.OffsetToPosition(offset + str.Length)));
textArea.Caret.Position = textArea.Document.OffsetToPosition(offset + str.Length);
textArea.Refresh();
}
protected void OnDragDrop(object sender, DragEventArgs e)
{
Point p = textArea.PointToClient(new Point(e.X, e.Y));
if (e.Data.GetDataPresent(typeof(string))) {
textArea.BeginUpdate();
textArea.Document.UndoStack.StartUndoGroup();
try {
int offset = textArea.Caret.Offset;
if (textArea.IsReadOnly(offset)) {
// prevent dragging text into readonly section
return;
}
if (e.Data.GetDataPresent(typeof(DefaultSelection))) {
ISelection sel = (ISelection)e.Data.GetData(typeof(DefaultSelection));
if (sel.ContainsPosition(textArea.Caret.Position)) {
return;
}
if (GetDragDropEffect(e) == DragDropEffects.Move) {
if (SelectionManager.SelectionIsReadOnly(textArea.Document, sel)) {
// prevent dragging text out of readonly section
return;
}
int len = sel.Length;
textArea.Document.Remove(sel.Offset, len);
if (sel.Offset < offset) {
offset -= len;
}
}
}
textArea.SelectionManager.ClearSelection();
InsertString(offset, (string)e.Data.GetData(typeof(string)));
textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea));
} finally {
textArea.Document.UndoStack.EndUndoGroup();
textArea.EndUpdate();
}
}
}
protected void OnDragOver(object sender, DragEventArgs e)
{
if (!textArea.Focused) {
textArea.Focus();
}
Point p = textArea.PointToClient(new Point(e.X, e.Y));
if (textArea.TextView.DrawingPosition.Contains(p.X, p.Y)) {
TextLocation realmousepos= textArea.TextView.GetLogicalPosition(p.X - textArea.TextView.DrawingPosition.X,
p.Y - textArea.TextView.DrawingPosition.Y);
int lineNr = Math.Min(textArea.Document.TotalNumberOfLines - 1, Math.Max(0, realmousepos.Y));
textArea.Caret.Position = new TextLocation(realmousepos.X, lineNr);
textArea.SetDesiredColumn();
if (e.Data.GetDataPresent(typeof(string)) && !textArea.IsReadOnly(textArea.Caret.Offset)) {
e.Effect = GetDragDropEffect(e);
} else {
e.Effect = DragDropEffects.None;
}
} else {
e.Effect = DragDropEffects.None;
}
}
}
}

View File

@@ -0,0 +1,492 @@
// <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.Text;
using System.Windows.Forms;
using ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor
{
/// <summary>
/// This class handles all mouse stuff for a textArea.
/// </summary>
public class TextAreaMouseHandler
{
TextArea textArea;
bool doubleclick = false;
bool clickedOnSelectedText = false;
MouseButtons button;
static readonly Point nilPoint = new Point(-1, -1);
Point mousedownpos = nilPoint;
Point lastmousedownpos = nilPoint;
bool gotmousedown = false;
bool dodragdrop = false;
public TextAreaMouseHandler(TextArea ttextArea)
{
textArea = ttextArea;
}
public void Attach()
{
textArea.Click += new EventHandler(TextAreaClick);
textArea.MouseMove += new MouseEventHandler(TextAreaMouseMove);
textArea.MouseDown += new MouseEventHandler(OnMouseDown);
textArea.DoubleClick += new EventHandler(OnDoubleClick);
textArea.MouseLeave += new EventHandler(OnMouseLeave);
textArea.MouseUp += new MouseEventHandler(OnMouseUp);
textArea.LostFocus += new EventHandler(TextAreaLostFocus);
textArea.ToolTipRequest += new ToolTipRequestEventHandler(OnToolTipRequest);
}
void OnToolTipRequest(object sender, ToolTipRequestEventArgs e)
{
if (e.ToolTipShown)
return;
Point mousepos = e.MousePosition;
FoldMarker marker = textArea.TextView.GetFoldMarkerFromPosition(mousepos.X - textArea.TextView.DrawingPosition.X,
mousepos.Y - textArea.TextView.DrawingPosition.Y);
if (marker != null && marker.IsFolded) {
StringBuilder sb = new StringBuilder(marker.InnerText);
// max 10 lines
int endLines = 0;
for (int i = 0; i < sb.Length; ++i) {
if (sb[i] == '\n') {
++endLines;
if (endLines >= 10) {
sb.Remove(i + 1, sb.Length - i - 1);
sb.Append(Environment.NewLine);
sb.Append("...");
break;
}
}
}
sb.Replace("\t", " ");
e.ShowToolTip(sb.ToString());
return;
}
List<TextMarker> markers = textArea.Document.MarkerStrategy.GetMarkers(e.LogicalPosition);
foreach (TextMarker tm in markers) {
if (tm.ToolTip != null) {
e.ShowToolTip(tm.ToolTip.Replace("\t", " "));
return;
}
}
}
void ShowHiddenCursorIfMovedOrLeft()
{
textArea.ShowHiddenCursor(!textArea.Focused ||
!textArea.ClientRectangle.Contains(textArea.PointToClient(Cursor.Position)));
}
void TextAreaLostFocus(object sender, EventArgs e)
{
// The call to ShowHiddenCursorIfMovedOrLeft is delayed
// until pending messages have been processed
// so that it can properly detect whether the TextArea
// has really lost focus.
// For example, the CodeCompletionWindow gets focus when it is shown,
// but immediately gives back focus to the TextArea.
textArea.BeginInvoke(new MethodInvoker(ShowHiddenCursorIfMovedOrLeft));
}
void OnMouseLeave(object sender, EventArgs e)
{
ShowHiddenCursorIfMovedOrLeft();
gotmousedown = false;
mousedownpos = nilPoint;
}
void OnMouseUp(object sender, MouseEventArgs e)
{
textArea.SelectionManager.selectFrom.where = WhereFrom.None;
gotmousedown = false;
mousedownpos = nilPoint;
}
void TextAreaClick(object sender, EventArgs e)
{
Point mousepos;
mousepos = textArea.mousepos;
if (dodragdrop)
{
return;
}
if (clickedOnSelectedText && textArea.TextView.DrawingPosition.Contains(mousepos.X, mousepos.Y))
{
textArea.SelectionManager.ClearSelection();
TextLocation clickPosition = textArea.TextView.GetLogicalPosition(
mousepos.X - textArea.TextView.DrawingPosition.X,
mousepos.Y - textArea.TextView.DrawingPosition.Y);
textArea.Caret.Position = clickPosition;
textArea.SetDesiredColumn();
}
}
void TextAreaMouseMove(object sender, MouseEventArgs e)
{
textArea.mousepos = e.Location;
// honour the starting selection strategy
switch (textArea.SelectionManager.selectFrom.where)
{
case WhereFrom.Gutter:
ExtendSelectionToMouse();
return;
case WhereFrom.TArea:
break;
}
textArea.ShowHiddenCursor(false);
if (dodragdrop) {
dodragdrop = false;
return;
}
doubleclick = false;
textArea.mousepos = new Point(e.X, e.Y);
if (clickedOnSelectedText) {
if (Math.Abs(mousedownpos.X - e.X) >= SystemInformation.DragSize.Width / 2 ||
Math.Abs(mousedownpos.Y - e.Y) >= SystemInformation.DragSize.Height / 2)
{
clickedOnSelectedText = false;
ISelection selection = textArea.SelectionManager.GetSelectionAt(textArea.Caret.Offset);
if (selection != null) {
string text = selection.SelectedText;
bool isReadOnly = SelectionManager.SelectionIsReadOnly(textArea.Document, selection);
if (text != null && text.Length > 0) {
DataObject dataObject = new DataObject ();
dataObject.SetData(DataFormats.UnicodeText, true, text);
dataObject.SetData(selection);
dodragdrop = true;
textArea.DoDragDrop(dataObject, isReadOnly ? DragDropEffects.All & ~DragDropEffects.Move : DragDropEffects.All);
}
}
}
return;
}
if (e.Button == MouseButtons.Left) {
if (gotmousedown && textArea.SelectionManager.selectFrom.where == WhereFrom.TArea)
{
ExtendSelectionToMouse();
}
}
}
void ExtendSelectionToMouse()
{
Point mousepos;
mousepos = textArea.mousepos;
TextLocation realmousepos = textArea.TextView.GetLogicalPosition(
Math.Max(0, mousepos.X - textArea.TextView.DrawingPosition.X),
mousepos.Y - textArea.TextView.DrawingPosition.Y);
int y = realmousepos.Y;
realmousepos = textArea.Caret.ValidatePosition(realmousepos);
TextLocation oldPos = textArea.Caret.Position;
if (oldPos == realmousepos && textArea.SelectionManager.selectFrom.where != WhereFrom.Gutter)
{
return;
}
// the selection is from the gutter
if (textArea.SelectionManager.selectFrom.where == WhereFrom.Gutter) {
if(realmousepos.Y < textArea.SelectionManager.SelectionStart.Y) {
// the selection has moved above the startpoint
textArea.Caret.Position = new TextLocation(0, realmousepos.Y);
} else {
// the selection has moved below the startpoint
textArea.Caret.Position = textArea.SelectionManager.NextValidPosition(realmousepos.Y);
}
} else {
textArea.Caret.Position = realmousepos;
}
// moves selection across whole words for double-click initiated selection
if (!minSelection.IsEmpty && textArea.SelectionManager.SelectionCollection.Count > 0 && textArea.SelectionManager.selectFrom.where == WhereFrom.TArea) {
// Extend selection when selection was started with double-click
ISelection selection = textArea.SelectionManager.SelectionCollection[0];
TextLocation min = textArea.SelectionManager.GreaterEqPos(minSelection, maxSelection) ? maxSelection : minSelection;
TextLocation max = textArea.SelectionManager.GreaterEqPos(minSelection, maxSelection) ? minSelection : maxSelection;
if (textArea.SelectionManager.GreaterEqPos(max, realmousepos) && textArea.SelectionManager.GreaterEqPos(realmousepos, min)) {
textArea.SelectionManager.SetSelection(min, max);
} else if (textArea.SelectionManager.GreaterEqPos(max, realmousepos)) {
int moff = textArea.Document.PositionToOffset(realmousepos);
min = textArea.Document.OffsetToPosition(FindWordStart(textArea.Document, moff));
textArea.SelectionManager.SetSelection(min, max);
} else {
int moff = textArea.Document.PositionToOffset(realmousepos);
max = textArea.Document.OffsetToPosition(FindWordEnd(textArea.Document, moff));
textArea.SelectionManager.SetSelection(min, max);
}
} else {
textArea.SelectionManager.ExtendSelection(oldPos, textArea.Caret.Position);
}
textArea.SetDesiredColumn();
}
void DoubleClickSelectionExtend()
{
Point mousepos;
mousepos = textArea.mousepos;
textArea.SelectionManager.ClearSelection();
if (textArea.TextView.DrawingPosition.Contains(mousepos.X, mousepos.Y))
{
FoldMarker marker = textArea.TextView.GetFoldMarkerFromPosition(mousepos.X - textArea.TextView.DrawingPosition.X,
mousepos.Y - textArea.TextView.DrawingPosition.Y);
if (marker != null && marker.IsFolded) {
marker.IsFolded = false;
textArea.MotherTextAreaControl.AdjustScrollBars();
}
if (textArea.Caret.Offset < textArea.Document.TextLength) {
switch (textArea.Document.GetCharAt(textArea.Caret.Offset)) {
case '"':
if (textArea.Caret.Offset < textArea.Document.TextLength) {
int next = FindNext(textArea.Document, textArea.Caret.Offset + 1, '"');
minSelection = textArea.Caret.Position;
if (next > textArea.Caret.Offset && next < textArea.Document.TextLength)
next += 1;
maxSelection = textArea.Document.OffsetToPosition(next);
}
break;
default:
minSelection = textArea.Document.OffsetToPosition(FindWordStart(textArea.Document, textArea.Caret.Offset));
maxSelection = textArea.Document.OffsetToPosition(FindWordEnd(textArea.Document, textArea.Caret.Offset));
break;
}
textArea.Caret.Position = maxSelection;
textArea.SelectionManager.ExtendSelection(minSelection, maxSelection);
}
if (textArea.SelectionManager.selectionCollection.Count > 0) {
ISelection selection = textArea.SelectionManager.selectionCollection[0];
selection.StartPosition = minSelection;
selection.EndPosition = maxSelection;
textArea.SelectionManager.SelectionStart = minSelection;
}
// after a double-click selection, the caret is placed correctly,
// but it is not positioned internally. The effect is when the cursor
// is moved up or down a line, the caret will take on the column first
// clicked on for the double-click
textArea.SetDesiredColumn();
// HACK WARNING !!!
// must refresh here, because when a error tooltip is showed and the underlined
// code is double clicked the textArea don't update corrctly, updateline doesn't
// work ... but the refresh does.
// Mike
textArea.Refresh();
}
}
void OnMouseDown(object sender, MouseEventArgs e)
{
Point mousepos;
textArea.mousepos = e.Location;
mousepos = e.Location;
if (dodragdrop)
{
return;
}
if (doubleclick) {
doubleclick = false;
return;
}
if (textArea.TextView.DrawingPosition.Contains(mousepos.X, mousepos.Y)) {
gotmousedown = true;
textArea.SelectionManager.selectFrom.where = WhereFrom.TArea;
button = e.Button;
// double-click
if (button == MouseButtons.Left && e.Clicks == 2) {
int deltaX = Math.Abs(lastmousedownpos.X - e.X);
int deltaY = Math.Abs(lastmousedownpos.Y - e.Y);
if (deltaX <= SystemInformation.DoubleClickSize.Width &&
deltaY <= SystemInformation.DoubleClickSize.Height) {
DoubleClickSelectionExtend();
lastmousedownpos = new Point(e.X, e.Y);
if (textArea.SelectionManager.selectFrom.where == WhereFrom.Gutter) {
if (!minSelection.IsEmpty && !maxSelection.IsEmpty && textArea.SelectionManager.SelectionCollection.Count > 0) {
textArea.SelectionManager.SelectionCollection[0].StartPosition = minSelection;
textArea.SelectionManager.SelectionCollection[0].EndPosition = maxSelection;
textArea.SelectionManager.SelectionStart = minSelection;
minSelection = TextLocation.Empty;
maxSelection = TextLocation.Empty;
}
}
return;
}
}
minSelection = TextLocation.Empty;
maxSelection = TextLocation.Empty;
lastmousedownpos = mousedownpos = new Point(e.X, e.Y);
if (button == MouseButtons.Left) {
FoldMarker marker = textArea.TextView.GetFoldMarkerFromPosition(mousepos.X - textArea.TextView.DrawingPosition.X,
mousepos.Y - textArea.TextView.DrawingPosition.Y);
if (marker != null && marker.IsFolded) {
if (textArea.SelectionManager.HasSomethingSelected) {
clickedOnSelectedText = true;
}
TextLocation startLocation = new TextLocation(marker.StartColumn, marker.StartLine);
TextLocation endLocation = new TextLocation(marker.EndColumn, marker.EndLine);
textArea.SelectionManager.SetSelection(new DefaultSelection(textArea.TextView.Document, startLocation, endLocation));
textArea.Caret.Position = startLocation;
textArea.SetDesiredColumn();
textArea.Focus();
return;
}
if ((Control.ModifierKeys & Keys.Shift) == Keys.Shift) {
ExtendSelectionToMouse();
} else {
TextLocation realmousepos = textArea.TextView.GetLogicalPosition(mousepos.X - textArea.TextView.DrawingPosition.X, mousepos.Y - textArea.TextView.DrawingPosition.Y);
clickedOnSelectedText = false;
int offset = textArea.Document.PositionToOffset(realmousepos);
if (textArea.SelectionManager.HasSomethingSelected &&
textArea.SelectionManager.IsSelected(offset)) {
clickedOnSelectedText = true;
} else {
textArea.SelectionManager.ClearSelection();
if (mousepos.Y > 0 && mousepos.Y < textArea.TextView.DrawingPosition.Height) {
TextLocation pos = new TextLocation();
pos.Y = Math.Min(textArea.Document.TotalNumberOfLines - 1, realmousepos.Y);
pos.X = realmousepos.X;
textArea.Caret.Position = pos;
textArea.SetDesiredColumn();
}
}
}
} else if (button == MouseButtons.Right) {
// Rightclick sets the cursor to the click position unless
// the previous selection was clicked
TextLocation realmousepos = textArea.TextView.GetLogicalPosition(mousepos.X - textArea.TextView.DrawingPosition.X, mousepos.Y - textArea.TextView.DrawingPosition.Y);
int offset = textArea.Document.PositionToOffset(realmousepos);
if (!textArea.SelectionManager.HasSomethingSelected ||
!textArea.SelectionManager.IsSelected(offset))
{
textArea.SelectionManager.ClearSelection();
if (mousepos.Y > 0 && mousepos.Y < textArea.TextView.DrawingPosition.Height) {
TextLocation pos = new TextLocation();
pos.Y = Math.Min(textArea.Document.TotalNumberOfLines - 1, realmousepos.Y);
pos.X = realmousepos.X;
textArea.Caret.Position = pos;
textArea.SetDesiredColumn();
}
}
}
}
textArea.Focus();
}
int FindNext(IDocument document, int offset, char ch)
{
LineSegment line = document.GetLineSegmentForOffset(offset);
int endPos = line.Offset + line.Length;
while (offset < endPos && document.GetCharAt(offset) != ch) {
++offset;
}
return offset;
}
bool IsSelectableChar(char ch)
{
return char.IsLetterOrDigit(ch) || ch=='_';
}
int FindWordStart(IDocument document, int offset)
{
LineSegment line = document.GetLineSegmentForOffset(offset);
if (offset > 0 && char.IsWhiteSpace(document.GetCharAt(offset - 1)) && char.IsWhiteSpace(document.GetCharAt(offset))) {
while (offset > line.Offset && char.IsWhiteSpace(document.GetCharAt(offset - 1))) {
--offset;
}
} else if (IsSelectableChar(document.GetCharAt(offset)) || (offset > 0 && char.IsWhiteSpace(document.GetCharAt(offset)) && IsSelectableChar(document.GetCharAt(offset - 1)))) {
while (offset > line.Offset && IsSelectableChar(document.GetCharAt(offset - 1))) {
--offset;
}
} else {
if (offset > 0 && !char.IsWhiteSpace(document.GetCharAt(offset - 1)) && !IsSelectableChar(document.GetCharAt(offset - 1)) ) {
return Math.Max(0, offset - 1);
}
}
return offset;
}
int FindWordEnd(IDocument document, int offset)
{
LineSegment line = document.GetLineSegmentForOffset(offset);
if (line.Length == 0)
return offset;
int endPos = line.Offset + line.Length;
offset = Math.Min(offset, endPos - 1);
if (IsSelectableChar(document.GetCharAt(offset))) {
while (offset < endPos && IsSelectableChar(document.GetCharAt(offset))) {
++offset;
}
} else if (char.IsWhiteSpace(document.GetCharAt(offset))) {
if (offset > 0 && char.IsWhiteSpace(document.GetCharAt(offset - 1))) {
while (offset < endPos && char.IsWhiteSpace(document.GetCharAt(offset))) {
++offset;
}
}
} else {
return Math.Max(0, offset + 1);
}
return offset;
}
TextLocation minSelection = TextLocation.Empty;
TextLocation maxSelection = TextLocation.Empty;
void OnDoubleClick(object sender, System.EventArgs e)
{
if (dodragdrop) {
return;
}
textArea.SelectionManager.selectFrom.where = WhereFrom.TArea;
doubleclick = true;
}
}
}

View File

@@ -0,0 +1,85 @@
// <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.Drawing;
namespace ICSharpCode.TextEditor
{
/// <summary>
/// This enum describes all implemented request types
/// </summary>
public enum TextAreaUpdateType {
WholeTextArea,
SingleLine,
SinglePosition,
PositionToLineEnd,
PositionToEnd,
LinesBetween
}
/// <summary>
/// This class is used to request an update of the textarea
/// </summary>
public class TextAreaUpdate
{
TextLocation position;
TextAreaUpdateType type;
public TextAreaUpdateType TextAreaUpdateType {
get {
return type;
}
}
public TextLocation Position {
get {
return position;
}
}
/// <summary>
/// Creates a new instance of <see cref="TextAreaUpdate"/>
/// </summary>
public TextAreaUpdate(TextAreaUpdateType type)
{
this.type = type;
}
/// <summary>
/// Creates a new instance of <see cref="TextAreaUpdate"/>
/// </summary>
public TextAreaUpdate(TextAreaUpdateType type, TextLocation position)
{
this.type = type;
this.position = position;
}
/// <summary>
/// Creates a new instance of <see cref="TextAreaUpdate"/>
/// </summary>
public TextAreaUpdate(TextAreaUpdateType type, int startLine, int endLine)
{
this.type = type;
this.position = new TextLocation(startLine, endLine);
}
/// <summary>
/// Creates a new instance of <see cref="TextAreaUpdate"/>
/// </summary>
public TextAreaUpdate(TextAreaUpdateType type, int singleLine)
{
this.type = type;
this.position = new TextLocation(0, singleLine);
}
public override string ToString()
{
return string.Format("[TextAreaUpdate: Type={0}, Position={1}]", type, position);
}
}
}

View File

@@ -0,0 +1,396 @@
// <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.ComponentModel;
using System.Drawing;
using System.Drawing.Printing;
using System.Windows.Forms;
using ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor
{
/// <summary>
/// This class is used for a basic text area control
/// </summary>
[ToolboxBitmap("ICSharpCode.TextEditor.Resources.TextEditorControl.bmp")]
[ToolboxItem(true)]
public class TextEditorControl : TextEditorControlBase
{
protected Panel textAreaPanel = new Panel();
TextAreaControl primaryTextArea;
Splitter textAreaSplitter = null;
TextAreaControl secondaryTextArea = null;
PrintDocument printDocument = null;
[Browsable(false)]
public PrintDocument PrintDocument {
get {
if (printDocument == null) {
printDocument = new PrintDocument();
printDocument.BeginPrint += new PrintEventHandler(this.BeginPrint);
printDocument.PrintPage += new PrintPageEventHandler(this.PrintPage);
}
return printDocument;
}
}
TextAreaControl activeTextAreaControl;
public override TextAreaControl ActiveTextAreaControl {
get {
return activeTextAreaControl;
}
}
protected void SetActiveTextAreaControl(TextAreaControl value)
{
if (activeTextAreaControl != value) {
activeTextAreaControl = value;
if (ActiveTextAreaControlChanged != null) {
ActiveTextAreaControlChanged(this, EventArgs.Empty);
}
}
}
public event EventHandler ActiveTextAreaControlChanged;
public TextEditorControl()
{
SetStyle(ControlStyles.ContainerControl, true);
textAreaPanel.Dock = DockStyle.Fill;
Document = (new DocumentFactory()).CreateDocument();
Document.HighlightingStrategy = HighlightingStrategyFactory.CreateHighlightingStrategy();
primaryTextArea = new TextAreaControl(this);
activeTextAreaControl = primaryTextArea;
primaryTextArea.TextArea.GotFocus += delegate {
SetActiveTextAreaControl(primaryTextArea);
};
primaryTextArea.Dock = DockStyle.Fill;
textAreaPanel.Controls.Add(primaryTextArea);
InitializeTextAreaControl(primaryTextArea);
Controls.Add(textAreaPanel);
ResizeRedraw = true;
Document.UpdateCommited += new EventHandler(CommitUpdateRequested);
OptionsChanged();
}
protected virtual void InitializeTextAreaControl(TextAreaControl newControl)
{
}
public override void OptionsChanged()
{
primaryTextArea.OptionsChanged();
if (secondaryTextArea != null) {
secondaryTextArea.OptionsChanged();
}
}
public void Split()
{
if (secondaryTextArea == null) {
secondaryTextArea = new TextAreaControl(this);
secondaryTextArea.Dock = DockStyle.Bottom;
secondaryTextArea.Height = Height / 2;
secondaryTextArea.TextArea.GotFocus += delegate {
SetActiveTextAreaControl(secondaryTextArea);
};
textAreaSplitter = new Splitter();
textAreaSplitter.BorderStyle = BorderStyle.FixedSingle ;
textAreaSplitter.Height = 8;
textAreaSplitter.Dock = DockStyle.Bottom;
textAreaPanel.Controls.Add(textAreaSplitter);
textAreaPanel.Controls.Add(secondaryTextArea);
InitializeTextAreaControl(secondaryTextArea);
secondaryTextArea.OptionsChanged();
} else {
SetActiveTextAreaControl(primaryTextArea);
textAreaPanel.Controls.Remove(secondaryTextArea);
textAreaPanel.Controls.Remove(textAreaSplitter);
secondaryTextArea.Dispose();
textAreaSplitter.Dispose();
secondaryTextArea = null;
textAreaSplitter = null;
}
}
[Browsable(false)]
public bool EnableUndo {
get {
return Document.UndoStack.CanUndo;
}
}
[Browsable(false)]
public bool EnableRedo {
get {
return Document.UndoStack.CanRedo;
}
}
public void Undo()
{
if (Document.ReadOnly) {
return;
}
if (Document.UndoStack.CanUndo) {
BeginUpdate();
Document.UndoStack.Undo();
Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea));
this.primaryTextArea.TextArea.UpdateMatchingBracket();
if (secondaryTextArea != null) {
this.secondaryTextArea.TextArea.UpdateMatchingBracket();
}
EndUpdate();
}
}
public void Redo()
{
if (Document.ReadOnly) {
return;
}
if (Document.UndoStack.CanRedo) {
BeginUpdate();
Document.UndoStack.Redo();
Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea));
this.primaryTextArea.TextArea.UpdateMatchingBracket();
if (secondaryTextArea != null) {
this.secondaryTextArea.TextArea.UpdateMatchingBracket();
}
EndUpdate();
}
}
public virtual void SetHighlighting(string name)
{
Document.HighlightingStrategy = HighlightingStrategyFactory.CreateHighlightingStrategy(name);
}
protected override void Dispose(bool disposing)
{
if (disposing) {
if (printDocument != null) {
printDocument.BeginPrint -= new PrintEventHandler(this.BeginPrint);
printDocument.PrintPage -= new PrintPageEventHandler(this.PrintPage);
printDocument = null;
}
Document.UndoStack.ClearAll();
Document.UpdateCommited -= new EventHandler(CommitUpdateRequested);
if (textAreaPanel != null) {
if (secondaryTextArea != null) {
secondaryTextArea.Dispose();
textAreaSplitter.Dispose();
secondaryTextArea = null;
textAreaSplitter = null;
}
if (primaryTextArea != null) {
primaryTextArea.Dispose();
}
textAreaPanel.Dispose();
textAreaPanel = null;
}
}
base.Dispose(disposing);
}
#region Update Methods
public override void EndUpdate()
{
base.EndUpdate();
Document.CommitUpdate();
if (!IsInUpdate) {
ActiveTextAreaControl.Caret.OnEndUpdate();
}
}
void CommitUpdateRequested(object sender, EventArgs e)
{
if (IsInUpdate) {
return;
}
foreach (TextAreaUpdate update in Document.UpdateQueue) {
switch (update.TextAreaUpdateType) {
case TextAreaUpdateType.PositionToEnd:
this.primaryTextArea.TextArea.UpdateToEnd(update.Position.Y);
if (this.secondaryTextArea != null) {
this.secondaryTextArea.TextArea.UpdateToEnd(update.Position.Y);
}
break;
case TextAreaUpdateType.PositionToLineEnd:
case TextAreaUpdateType.SingleLine:
this.primaryTextArea.TextArea.UpdateLine(update.Position.Y);
if (this.secondaryTextArea != null) {
this.secondaryTextArea.TextArea.UpdateLine(update.Position.Y);
}
break;
case TextAreaUpdateType.SinglePosition:
this.primaryTextArea.TextArea.UpdateLine(update.Position.Y, update.Position.X, update.Position.X);
if (this.secondaryTextArea != null) {
this.secondaryTextArea.TextArea.UpdateLine(update.Position.Y, update.Position.X, update.Position.X);
}
break;
case TextAreaUpdateType.LinesBetween:
this.primaryTextArea.TextArea.UpdateLines(update.Position.X, update.Position.Y);
if (this.secondaryTextArea != null) {
this.secondaryTextArea.TextArea.UpdateLines(update.Position.X, update.Position.Y);
}
break;
case TextAreaUpdateType.WholeTextArea:
this.primaryTextArea.TextArea.Invalidate();
if (this.secondaryTextArea != null) {
this.secondaryTextArea.TextArea.Invalidate();
}
break;
}
}
Document.UpdateQueue.Clear();
// this.primaryTextArea.TextArea.Update();
// if (this.secondaryTextArea != null) {
// this.secondaryTextArea.TextArea.Update();
// }
}
#endregion
#region Printing routines
int curLineNr = 0;
float curTabIndent = 0;
StringFormat printingStringFormat;
void BeginPrint(object sender, PrintEventArgs ev)
{
curLineNr = 0;
printingStringFormat = (StringFormat)System.Drawing.StringFormat.GenericTypographic.Clone();
// 100 should be enough for everyone ...err ?
float[] tabStops = new float[100];
for (int i = 0; i < tabStops.Length; ++i) {
tabStops[i] = TabIndent * primaryTextArea.TextArea.TextView.WideSpaceWidth;
}
printingStringFormat.SetTabStops(0, tabStops);
}
void Advance(ref float x, ref float y, float maxWidth, float size, float fontHeight)
{
if (x + size < maxWidth) {
x += size;
} else {
x = curTabIndent;
y += fontHeight;
}
}
// btw. I hate source code duplication ... but this time I don't care !!!!
float MeasurePrintingHeight(Graphics g, LineSegment line, float maxWidth)
{
float xPos = 0;
float yPos = 0;
float fontHeight = Font.GetHeight(g);
// bool gotNonWhitespace = false;
curTabIndent = 0;
FontContainer fontContainer = TextEditorProperties.FontContainer;
foreach (TextWord word in line.Words) {
switch (word.Type) {
case TextWordType.Space:
Advance(ref xPos, ref yPos, maxWidth, primaryTextArea.TextArea.TextView.SpaceWidth, fontHeight);
// if (!gotNonWhitespace) {
// curTabIndent = xPos;
// }
break;
case TextWordType.Tab:
Advance(ref xPos, ref yPos, maxWidth, TabIndent * primaryTextArea.TextArea.TextView.WideSpaceWidth, fontHeight);
// if (!gotNonWhitespace) {
// curTabIndent = xPos;
// }
break;
case TextWordType.Word:
// if (!gotNonWhitespace) {
// gotNonWhitespace = true;
// curTabIndent += TabIndent * primaryTextArea.TextArea.TextView.GetWidth(' ');
// }
SizeF drawingSize = g.MeasureString(word.Word, word.GetFont(fontContainer), new SizeF(maxWidth, fontHeight * 100), printingStringFormat);
Advance(ref xPos, ref yPos, maxWidth, drawingSize.Width, fontHeight);
break;
}
}
return yPos + fontHeight;
}
void DrawLine(Graphics g, LineSegment line, float yPos, RectangleF margin)
{
float xPos = 0;
float fontHeight = Font.GetHeight(g);
// bool gotNonWhitespace = false;
curTabIndent = 0 ;
FontContainer fontContainer = TextEditorProperties.FontContainer;
foreach (TextWord word in line.Words) {
switch (word.Type) {
case TextWordType.Space:
Advance(ref xPos, ref yPos, margin.Width, primaryTextArea.TextArea.TextView.SpaceWidth, fontHeight);
// if (!gotNonWhitespace) {
// curTabIndent = xPos;
// }
break;
case TextWordType.Tab:
Advance(ref xPos, ref yPos, margin.Width, TabIndent * primaryTextArea.TextArea.TextView.WideSpaceWidth, fontHeight);
// if (!gotNonWhitespace) {
// curTabIndent = xPos;
// }
break;
case TextWordType.Word:
// if (!gotNonWhitespace) {
// gotNonWhitespace = true;
// curTabIndent += TabIndent * primaryTextArea.TextArea.TextView.GetWidth(' ');
// }
g.DrawString(word.Word, word.GetFont(fontContainer), BrushRegistry.GetBrush(word.Color), xPos + margin.X, yPos);
SizeF drawingSize = g.MeasureString(word.Word, word.GetFont(fontContainer), new SizeF(margin.Width, fontHeight * 100), printingStringFormat);
Advance(ref xPos, ref yPos, margin.Width, drawingSize.Width, fontHeight);
break;
}
}
}
void PrintPage(object sender, PrintPageEventArgs ev)
{
Graphics g = ev.Graphics;
float yPos = ev.MarginBounds.Top;
while (curLineNr < Document.TotalNumberOfLines) {
LineSegment curLine = Document.GetLineSegment(curLineNr);
if (curLine.Words != null) {
float drawingHeight = MeasurePrintingHeight(g, curLine, ev.MarginBounds.Width);
if (drawingHeight + yPos > ev.MarginBounds.Bottom) {
break;
}
DrawLine(g, curLine, yPos, ev.MarginBounds);
yPos += drawingHeight;
}
++curLineNr;
}
// If more lines exist, print another page.
ev.HasMorePages = curLineNr < Document.TotalNumberOfLines;
}
#endregion
}
}

View File

@@ -0,0 +1,759 @@
// <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.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Text;
using System.IO;
using System.Text;
using System.Windows.Forms;
using ICSharpCode.TextEditor.Actions;
using ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor
{
/// <summary>
/// This class is used for a basic text area control
/// </summary>
[ToolboxItem(false)]
public abstract class TextEditorControlBase : UserControl
{
string currentFileName = null;
int updateLevel = 0;
protected IDocument document; // Stef Heyenrath : Changed to protected
/// <summary>
/// This hashtable contains all editor keys, where
/// the key is the key combination and the value the
/// action.
/// </summary>
protected Dictionary<Keys, IEditAction> editactions = new Dictionary<Keys, IEditAction>();
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public ITextEditorProperties TextEditorProperties {
get {
return document.TextEditorProperties;
}
set {
document.TextEditorProperties = value;
OptionsChanged();
}
}
Encoding encoding;
/// <value>
/// Current file's character encoding
/// </value>
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Encoding Encoding {
get {
if (encoding == null)
return TextEditorProperties.Encoding;
return encoding;
}
set {
encoding = value;
}
}
/// <value>
/// The current file name
/// </value>
[Browsable(false)]
[ReadOnly(true)]
public string FileName {
get {
return currentFileName;
}
set {
if (currentFileName != value) {
currentFileName = value;
OnFileNameChanged(EventArgs.Empty);
}
}
}
/// <value>
/// The current document
/// </value>
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public IDocument Document {
get {
return document;
}
set {
if (value == null)
throw new ArgumentNullException("value");
if (document != null) {
document.DocumentChanged -= OnDocumentChanged;
}
document = value;
document.UndoStack.TextEditorControl = this;
document.DocumentChanged += OnDocumentChanged;
}
}
protected void OnDocumentChanged(object sender, EventArgs e) // Stef Heyenrath : changed to protected
{
OnTextChanged(e);
}
[EditorBrowsable(EditorBrowsableState.Always), Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[Editor("System.ComponentModel.Design.MultilineStringEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(System.Drawing.Design.UITypeEditor))]
public override string Text {
get {
return Document.TextContent;
}
set {
Document.TextContent = value;
}
}
[EditorBrowsable(EditorBrowsableState.Always), Browsable(true)]
public new event EventHandler TextChanged
{
add { base.TextChanged += value; }
remove { base.TextChanged -= value; }
}
static Font ParseFont(string font)
{
string[] descr = font.Split(new char[]{',', '='});
return new Font(descr[1], float.Parse(descr[3]));
}
/// <value>
/// If set to true the contents can't be altered.
/// </value>
[Browsable(false)]
public bool IsReadOnly {
get {
return Document.ReadOnly;
}
set {
Document.ReadOnly = value;
}
}
/// <value>
/// true, if the textarea is updating it's status, while
/// it updates it status no redraw operation occurs.
/// </value>
[Browsable(false)]
public bool IsInUpdate {
get {
return updateLevel > 0;
}
}
/// <value>
/// supposedly this is the way to do it according to .NET docs,
/// as opposed to setting the size in the constructor
/// </value>
protected override Size DefaultSize {
get {
return new Size(100, 100);
}
}
#region Document Properties
/// <value>
/// If true spaces are shown in the textarea
/// </value>
[Category("Appearance")]
[DefaultValue(false)]
[Description("If true spaces are shown in the textarea")]
public bool ShowSpaces {
get {
return document.TextEditorProperties.ShowSpaces;
}
set {
document.TextEditorProperties.ShowSpaces = value;
OptionsChanged();
}
}
/// <value>
/// Specifies the quality of text rendering (whether to use hinting and/or anti-aliasing).
/// </value>
[Category("Appearance")]
[DefaultValue(TextRenderingHint.SystemDefault)]
[Description("Specifies the quality of text rendering (whether to use hinting and/or anti-aliasing).")]
public TextRenderingHint TextRenderingHint {
get {
return document.TextEditorProperties.TextRenderingHint;
}
set {
document.TextEditorProperties.TextRenderingHint = value;
OptionsChanged();
}
}
/// <value>
/// If true tabs are shown in the textarea
/// </value>
[Category("Appearance")]
[DefaultValue(false)]
[Description("If true tabs are shown in the textarea")]
public bool ShowTabs {
get {
return document.TextEditorProperties.ShowTabs;
}
set {
document.TextEditorProperties.ShowTabs = value;
OptionsChanged();
}
}
/// <value>
/// If true EOL markers are shown in the textarea
/// </value>
[Category("Appearance")]
[DefaultValue(false)]
[Description("If true EOL markers are shown in the textarea")]
public bool ShowEOLMarkers {
get {
return document.TextEditorProperties.ShowEOLMarker;
}
set {
document.TextEditorProperties.ShowEOLMarker = value;
OptionsChanged();
}
}
/// <value>
/// If true the horizontal ruler is shown in the textarea
/// </value>
[Category("Appearance")]
[DefaultValue(false)]
[Description("If true the horizontal ruler is shown in the textarea")]
public bool ShowHRuler {
get {
return document.TextEditorProperties.ShowHorizontalRuler;
}
set {
document.TextEditorProperties.ShowHorizontalRuler = value;
OptionsChanged();
}
}
/// <value>
/// If true the vertical ruler is shown in the textarea
/// </value>
[Category("Appearance")]
[DefaultValue(true)]
[Description("If true the vertical ruler is shown in the textarea")]
public bool ShowVRuler {
get {
return document.TextEditorProperties.ShowVerticalRuler;
}
set {
document.TextEditorProperties.ShowVerticalRuler = value;
OptionsChanged();
}
}
/// <value>
/// The row in which the vertical ruler is displayed
/// </value>
[Category("Appearance")]
[DefaultValue(80)]
[Description("The row in which the vertical ruler is displayed")]
public int VRulerRow {
get {
return document.TextEditorProperties.VerticalRulerRow;
}
set {
document.TextEditorProperties.VerticalRulerRow = value;
OptionsChanged();
}
}
/// <value>
/// If true line numbers are shown in the textarea
/// </value>
[Category("Appearance")]
[DefaultValue(true)]
[Description("If true line numbers are shown in the textarea")]
public bool ShowLineNumbers {
get {
return document.TextEditorProperties.ShowLineNumbers;
}
set {
document.TextEditorProperties.ShowLineNumbers = value;
OptionsChanged();
}
}
/// <value>
/// If true invalid lines are marked in the textarea
/// </value>
[Category("Appearance")]
[DefaultValue(false)]
[Description("If true invalid lines are marked in the textarea")]
public bool ShowInvalidLines {
get {
return document.TextEditorProperties.ShowInvalidLines;
}
set {
document.TextEditorProperties.ShowInvalidLines = value;
OptionsChanged();
}
}
/// <value>
/// If true folding is enabled in the textarea
/// </value>
[Category("Appearance")]
[DefaultValue(true)]
[Description("If true folding is enabled in the textarea")]
public bool EnableFolding {
get {
return document.TextEditorProperties.EnableFolding;
}
set {
document.TextEditorProperties.EnableFolding = value;
OptionsChanged();
}
}
[Category("Appearance")]
[DefaultValue(true)]
[Description("If true matching brackets are highlighted")]
public bool ShowMatchingBracket {
get {
return document.TextEditorProperties.ShowMatchingBracket;
}
set {
document.TextEditorProperties.ShowMatchingBracket = value;
OptionsChanged();
}
}
[Category("Appearance")]
[DefaultValue(false)]
[Description("If true the icon bar is displayed")]
public bool IsIconBarVisible {
get {
return document.TextEditorProperties.IsIconBarVisible;
}
set {
document.TextEditorProperties.IsIconBarVisible = value;
OptionsChanged();
}
}
/// <value>
/// The width in spaces of a tab character
/// </value>
[Category("Appearance")]
[DefaultValue(4)]
[Description("The width in spaces of a tab character")]
public int TabIndent {
get {
return document.TextEditorProperties.TabIndent;
}
set {
document.TextEditorProperties.TabIndent = value;
OptionsChanged();
}
}
/// <value>
/// The line viewer style
/// </value>
[Category("Appearance")]
[DefaultValue(LineViewerStyle.None)]
[Description("The line viewer style")]
public LineViewerStyle LineViewerStyle {
get {
return document.TextEditorProperties.LineViewerStyle;
}
set {
document.TextEditorProperties.LineViewerStyle = value;
OptionsChanged();
}
}
/// <value>
/// The indent style
/// </value>
[Category("Behavior")]
[DefaultValue(IndentStyle.Smart)]
[Description("The indent style")]
public IndentStyle IndentStyle {
get {
return document.TextEditorProperties.IndentStyle;
}
set {
document.TextEditorProperties.IndentStyle = value;
OptionsChanged();
}
}
/// <value>
/// if true spaces are converted to tabs
/// </value>
[Category("Behavior")]
[DefaultValue(false)]
[Description("Converts tabs to spaces while typing")]
public bool ConvertTabsToSpaces {
get {
return document.TextEditorProperties.ConvertTabsToSpaces;
}
set {
document.TextEditorProperties.ConvertTabsToSpaces = value;
OptionsChanged();
}
}
/// <value>
/// if true spaces are converted to tabs
/// </value>
[Category("Behavior")]
[DefaultValue(false)]
[Description("Hide the mouse cursor while typing")]
public bool HideMouseCursor {
get {
return document.TextEditorProperties.HideMouseCursor;
}
set {
document.TextEditorProperties.HideMouseCursor = value;
OptionsChanged();
}
}
/// <value>
/// if true spaces are converted to tabs
/// </value>
[Category("Behavior")]
[DefaultValue(false)]
[Description("Allows the caret to be placed beyond the end of line")]
public bool AllowCaretBeyondEOL {
get {
return document.TextEditorProperties.AllowCaretBeyondEOL;
}
set {
document.TextEditorProperties.AllowCaretBeyondEOL = value;
OptionsChanged();
}
}
/// <value>
/// if true spaces are converted to tabs
/// </value>
[Category("Behavior")]
[DefaultValue(BracketMatchingStyle.After)]
[Description("Specifies if the bracket matching should match the bracket before or after the caret.")]
public BracketMatchingStyle BracketMatchingStyle {
get {
return document.TextEditorProperties.BracketMatchingStyle;
}
set {
document.TextEditorProperties.BracketMatchingStyle = value;
OptionsChanged();
}
}
/// <value>
/// The base font of the text area. No bold or italic fonts
/// can be used because bold/italic is reserved for highlighting
/// purposes.
/// </value>
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[DefaultValue(typeof(Font), null)] // Stef_H
[Description("The base font of the text area. No bold or italic fonts can be used because bold/italic is reserved for highlighting purposes.")]
public override Font Font {
get {
return document.TextEditorProperties.Font;
}
set {
document.TextEditorProperties.Font = value;
OptionsChanged();
}
}
#endregion
public abstract TextAreaControl ActiveTextAreaControl {
get;
}
protected TextEditorControlBase()
{
GenerateDefaultActions();
HighlightingManager.Manager.ReloadSyntaxHighlighting += new EventHandler(OnReloadHighlighting);
}
protected virtual void OnReloadHighlighting(object sender, EventArgs e)
{
if (Document.HighlightingStrategy != null) {
try {
Document.HighlightingStrategy = HighlightingStrategyFactory.CreateHighlightingStrategy(Document.HighlightingStrategy.Name);
} catch (HighlightingDefinitionInvalidException ex) {
MessageBox.Show(ex.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
OptionsChanged();
}
}
public bool IsEditAction(Keys keyData)
{
return editactions.ContainsKey(keyData);
}
internal IEditAction GetEditAction(Keys keyData)
{
if (!IsEditAction(keyData)) {
return null;
}
return (IEditAction)editactions[keyData];
}
void GenerateDefaultActions()
{
editactions[Keys.Left] = new CaretLeft();
editactions[Keys.Left | Keys.Shift] = new ShiftCaretLeft();
editactions[Keys.Left | Keys.Control] = new WordLeft();
editactions[Keys.Left | Keys.Control | Keys.Shift] = new ShiftWordLeft();
editactions[Keys.Right] = new CaretRight();
editactions[Keys.Right | Keys.Shift] = new ShiftCaretRight();
editactions[Keys.Right | Keys.Control] = new WordRight();
editactions[Keys.Right | Keys.Control | Keys.Shift] = new ShiftWordRight();
editactions[Keys.Up] = new CaretUp();
editactions[Keys.Up | Keys.Shift] = new ShiftCaretUp();
editactions[Keys.Up | Keys.Control] = new ScrollLineUp();
editactions[Keys.Down] = new CaretDown();
editactions[Keys.Down | Keys.Shift] = new ShiftCaretDown();
editactions[Keys.Down | Keys.Control] = new ScrollLineDown();
editactions[Keys.Insert] = new ToggleEditMode();
editactions[Keys.Insert | Keys.Control] = new Copy();
editactions[Keys.Insert | Keys.Shift] = new Paste();
editactions[Keys.Delete] = new Delete();
editactions[Keys.Delete | Keys.Shift] = new Cut();
editactions[Keys.Home] = new Home();
editactions[Keys.Home | Keys.Shift] = new ShiftHome();
editactions[Keys.Home | Keys.Control] = new MoveToStart();
editactions[Keys.Home | Keys.Control | Keys.Shift] = new ShiftMoveToStart();
editactions[Keys.End] = new End();
editactions[Keys.End | Keys.Shift] = new ShiftEnd();
editactions[Keys.End | Keys.Control] = new MoveToEnd();
editactions[Keys.End | Keys.Control | Keys.Shift] = new ShiftMoveToEnd();
editactions[Keys.PageUp] = new MovePageUp();
editactions[Keys.PageUp | Keys.Shift] = new ShiftMovePageUp();
editactions[Keys.PageDown] = new MovePageDown();
editactions[Keys.PageDown | Keys.Shift] = new ShiftMovePageDown();
editactions[Keys.Return] = new Return();
editactions[Keys.Tab] = new Tab();
editactions[Keys.Tab | Keys.Shift] = new ShiftTab();
editactions[Keys.Back] = new Backspace();
editactions[Keys.Back | Keys.Shift] = new Backspace();
editactions[Keys.X | Keys.Control] = new Cut();
editactions[Keys.C | Keys.Control] = new Copy();
editactions[Keys.V | Keys.Control] = new Paste();
editactions[Keys.A | Keys.Control] = new SelectWholeDocument();
editactions[Keys.Escape] = new ClearAllSelections();
editactions[Keys.Divide | Keys.Control] = new ToggleComment();
editactions[Keys.OemQuestion | Keys.Control] = new ToggleComment();
editactions[Keys.Back | Keys.Alt] = new Actions.Undo();
editactions[Keys.Z | Keys.Control] = new Actions.Undo();
editactions[Keys.Y | Keys.Control] = new Redo();
editactions[Keys.Delete | Keys.Control] = new DeleteWord();
editactions[Keys.Back | Keys.Control] = new WordBackspace();
editactions[Keys.D | Keys.Control] = new DeleteLine();
editactions[Keys.D | Keys.Shift | Keys.Control] = new DeleteToLineEnd();
editactions[Keys.B | Keys.Control] = new GotoMatchingBrace();
}
/// <remarks>
/// Call this method before a long update operation this
/// 'locks' the text area so that no screen update occurs.
/// </remarks>
public virtual void BeginUpdate()
{
++updateLevel;
}
/// <remarks>
/// Call this method to 'unlock' the text area. After this call
/// screen update can occur. But no automatical refresh occurs you
/// have to commit the updates in the queue.
/// </remarks>
public virtual void EndUpdate()
{
Debug.Assert(updateLevel > 0);
updateLevel = Math.Max(0, updateLevel - 1);
}
public void LoadFile(string fileName)
{
LoadFile(fileName, true, true);
}
/// <remarks>
/// Loads a file given by fileName
/// </remarks>
/// <param name="fileName">The name of the file to open</param>
/// <param name="autoLoadHighlighting">Automatically load the highlighting for the file</param>
/// <param name="autodetectEncoding">Automatically detect file encoding and set Encoding property to the detected encoding.</param>
public void LoadFile(string fileName, bool autoLoadHighlighting, bool autodetectEncoding)
{
using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read)) {
LoadFile(fileName, fs, autoLoadHighlighting, autodetectEncoding);
}
}
/// <remarks>
/// Loads a file from the specified stream.
/// </remarks>
/// <param name="fileName">The name of the file to open. Used to find the correct highlighting strategy
/// if autoLoadHighlighting is active, and sets the filename property to this value.</param>
/// <param name="stream">The stream to actually load the file content from.</param>
/// <param name="autoLoadHighlighting">Automatically load the highlighting for the file</param>
/// <param name="autodetectEncoding">Automatically detect file encoding and set Encoding property to the detected encoding.</param>
public void LoadFile(string fileName, Stream stream, bool autoLoadHighlighting, bool autodetectEncoding)
{
if (stream == null)
throw new ArgumentNullException("stream");
BeginUpdate();
document.TextContent = string.Empty;
document.UndoStack.ClearAll();
document.BookmarkManager.Clear();
if (autoLoadHighlighting) {
try {
document.HighlightingStrategy = HighlightingStrategyFactory.CreateHighlightingStrategyForFile(fileName);
} catch (HighlightingDefinitionInvalidException ex) {
MessageBox.Show(ex.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
if (autodetectEncoding) {
Encoding encoding = this.Encoding;
Document.TextContent = Util.FileReader.ReadFileContent(stream, ref encoding);
this.Encoding = encoding;
} else {
using (StreamReader reader = new StreamReader(fileName, this.Encoding)) {
Document.TextContent = reader.ReadToEnd();
}
}
this.FileName = fileName;
Document.UpdateQueue.Clear();
EndUpdate();
OptionsChanged();
Refresh();
}
/// <summary>
/// Gets if the document can be saved with the current encoding without losing data.
/// </summary>
public bool CanSaveWithCurrentEncoding()
{
if (encoding == null || Util.FileReader.IsUnicode(encoding))
return true;
// not a unicode codepage
string text = document.TextContent;
return encoding.GetString(encoding.GetBytes(text)) == text;
}
/// <remarks>
/// Saves the text editor content into the file.
/// </remarks>
public void SaveFile(string fileName)
{
using (FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.Write)) {
SaveFile(fs);
}
this.FileName = fileName;
}
/// <remarks>
/// Saves the text editor content into the specified stream.
/// Does not close the stream.
/// </remarks>
public void SaveFile(Stream stream)
{
StreamWriter streamWriter = new StreamWriter(stream, this.Encoding ?? Encoding.UTF8);
// save line per line to apply the LineTerminator to all lines
// (otherwise we might save files with mixed-up line endings)
foreach (LineSegment line in Document.LineSegmentCollection) {
streamWriter.Write(Document.GetText(line.Offset, line.Length));
if (line.DelimiterLength > 0) {
char charAfterLine = Document.GetCharAt(line.Offset + line.Length);
if (charAfterLine != '\n' && charAfterLine != '\r')
throw new InvalidOperationException("The document cannot be saved because it is corrupted.");
// only save line terminator if the line has one
streamWriter.Write(document.TextEditorProperties.LineTerminator);
}
}
streamWriter.Flush();
}
public abstract void OptionsChanged();
// Localization ISSUES
// used in insight window
public virtual string GetRangeDescription(int selectedItem, int itemCount)
{
StringBuilder sb=new StringBuilder(selectedItem.ToString());
sb.Append(" from ");
sb.Append(itemCount.ToString());
return sb.ToString();
}
/// <remarks>
/// Overwritten refresh method that does nothing if the control is in
/// an update cycle.
/// </remarks>
public override void Refresh()
{
if (IsInUpdate) {
return;
}
base.Refresh();
}
protected override void Dispose(bool disposing)
{
if (disposing) {
HighlightingManager.Manager.ReloadSyntaxHighlighting -= new EventHandler(OnReloadHighlighting);
document.HighlightingStrategy = null;
document.UndoStack.TextEditorControl = null;
}
base.Dispose(disposing);
}
protected virtual void OnFileNameChanged(EventArgs e)
{
if (FileNameChanged != null) {
FileNameChanged(this, e);
}
}
public event EventHandler FileNameChanged;
}
}

View File

@@ -0,0 +1,541 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using ICSharpCode.TextEditor.Actions;
using ICSharpCode.TextEditor.Document;
using ICSharpCode.TextEditor.Properties;
using ICSharpCode.TextEditor.Src.Actions;
using ICSharpCode.TextEditor.Src.Document.FoldingStrategy;
using ICSharpCode.TextEditor.Src.Document.HighlightingStrategy.SyntaxModes;
using ICSharpCode.TextEditor.UserControls;
// ReSharper disable once CheckNamespace
namespace ICSharpCode.TextEditor
{
[ToolboxBitmap("ICSharpCode.TextEditor.Resources.TextEditorControl.bmp")]
[ToolboxItem(true)]
public class TextEditorControlEx : TextEditorControl
{
private bool _contextMenuEnabled;
private bool _contextMenuShowDefaultIcons;
private bool _contextMenuShowShortCutKeys;
private readonly FindAndReplaceForm _findForm = new FindAndReplaceForm();
public TextEditorControlEx()
{
editactions[Keys.Control | Keys.F] = new EditFindAction(_findForm, this);
editactions[Keys.Control | Keys.H] = new EditReplaceAction(_findForm, this);
editactions[Keys.F3] = new FindAgainAction(_findForm, this);
editactions[Keys.F3 | Keys.Shift] = new FindAgainReverseAction(_findForm, this);
editactions[Keys.Control | Keys.G] = new GoToLineNumberAction();
// Add additional Syntax highlighting providers
HighlightingManager.Manager.AddSyntaxModeFileProvider(new ResourceSyntaxModeProviderEx());
TextChanged += TextChangedEventHandler;
}
protected override void OnLoad(EventArgs e)
{
if (ContextMenuEnabled)
{
AssignContextMenu(CreateNewContextMenu(ContextMenuShowDefaultIcons, ContextMenuShowShortCutKeys));
}
base.OnLoad(e);
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
TextChanged -= TextChangedEventHandler;
}
private void TextChangedEventHandler(object sender, EventArgs e)
{
var editor = sender as TextEditorControlEx;
if (editor != null)
{
bool vScrollBarIsNeeded = editor.Document.TotalNumberOfLines > ActiveTextAreaControl.TextArea.TextView.VisibleLineCount;
if (ActiveTextAreaControl.VScrollBar.Visible && HideVScrollBarIfPossible && !vScrollBarIsNeeded)
{
ActiveTextAreaControl.ShowScrollBars(Orientation.Vertical, false);
}
}
}
#region Extended properties
public string SelectedText
{
get
{
return ActiveTextAreaControl.SelectionManager.SelectedText;
}
}
public string[] Lines
{
get
{
return base.Text.Split(new[] { "\r\n" }, StringSplitOptions.None);
}
}
#endregion
#region Designer Properties
/// <value>
/// The current document
/// </value>
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public new IDocument Document
{
get
{
return document;
}
set
{
if (value == null)
throw new ArgumentNullException("value");
if (document != null)
{
document.DocumentChanged -= OnDocumentChanged;
document.DocumentChanged -= OnDocumentChangedDoUpdateContextMenu;
}
document = value;
document.UndoStack.TextEditorControl = this;
document.DocumentChanged += OnDocumentChanged;
document.DocumentChanged += OnDocumentChangedDoUpdateContextMenu;
}
}
/// <value>
/// The base font of the text area. No bold or italic fonts
/// can be used because bold/italic is reserved for highlighting
/// purposes.
/// </value>
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[DefaultValue(typeof(Font), null)]
[Description("The base font of the text area. No bold or italic fonts can be used because bold/italic is reserved for highlighting purposes.")]
public override Font Font
{
get
{
return Document.TextEditorProperties.Font;
}
set
{
Document.TextEditorProperties.Font = value;
OptionsChanged();
}
}
[Category("Appearance")]
[DefaultValue(false)]
[Description("Hide the vertical ScrollBar if it's not needed. ")]
public bool HideVScrollBarIfPossible { get; set; }
private string _foldingStrategy;
[Category("Appearance")]
[Description("Set the Folding Strategy. Supported : XML and CSharp.")]
public string FoldingStrategy
{
get
{
return _foldingStrategy;
}
set
{
SetFoldingStrategy(value);
OptionsChanged();
}
}
private string _syntaxHighlighting;
[Category("Appearance")]
[Description("Sets the Syntax Highlighting.")]
public string SyntaxHighlighting
{
get
{
return _syntaxHighlighting;
}
set
{
_syntaxHighlighting = value;
SetHighlighting(_syntaxHighlighting);
OptionsChanged();
}
}
[Category("Behavior")]
[DefaultValue(false)]
[Description("If true document is readonly.")]
[Browsable(true)]
public new bool IsReadOnly
{
get
{
return Document.ReadOnly;
}
set
{
Document.ReadOnly = value;
OptionsChanged();
}
}
[DefaultValue(false)]
[Category("Appearance")]
[Description("Show default Icons in ContextMenu")]
[Browsable(true)]
public bool ContextMenuShowDefaultIcons
{
get
{
return _contextMenuShowDefaultIcons & _contextMenuEnabled;
}
set
{
_contextMenuShowDefaultIcons = _contextMenuEnabled & value;
}
}
[DefaultValue(false)]
[Category("Appearance")]
[Description("Show shortcut keys in ContextMenu")]
[Browsable(true)]
public bool ContextMenuShowShortCutKeys
{
get
{
return _contextMenuShowShortCutKeys & _contextMenuEnabled;
}
set
{
_contextMenuShowShortCutKeys = _contextMenuEnabled & value;
}
}
[DefaultValue(false)]
[Category("Appearance")]
[Description("Enable a ContextMenu")]
[Browsable(true)]
public bool ContextMenuEnabled
{
get
{
return _contextMenuEnabled;
}
set
{
_contextMenuEnabled = value;
}
}
#endregion
/// <summary>
/// Sets the text and refreshes the control.
/// </summary>
/// <param name="text">The text.</param>
/// <param name="updateFoldings">if set to <c>true</c> [update foldings].</param>
public void SetTextAndRefresh(string text, bool updateFoldings = false)
{
ResetText();
Text = text;
if (updateFoldings && Document.TextEditorProperties.EnableFolding)
{
Document.FoldingManager.UpdateFoldings(null, null);
}
Refresh();
}
/// <summary>
/// Sets the folding strategy. Currently only XML is supported.
/// </summary>
/// <param name="foldingStrategy">The foldingStrategy.</param>
public void SetFoldingStrategy(string foldingStrategy)
{
if (foldingStrategy == null)
{
throw new ArgumentNullException("foldingStrategy");
}
if (!Document.TextEditorProperties.EnableFolding)
{
return;
}
switch (foldingStrategy)
{
case "XML":
_foldingStrategy = foldingStrategy;
Document.FoldingManager.FoldingStrategy = new XmlFoldingStrategy();
break;
case "C#":
_foldingStrategy = foldingStrategy;
Document.FoldingManager.FoldingStrategy = new CSharpFoldingStrategy();
break;
case "JSON":
_foldingStrategy = foldingStrategy;
Document.FoldingManager.FoldingStrategy = new JSONFoldingStrategy();
break;
default:
Document.FoldingManager.FoldingStrategy = null;
_foldingStrategy = null;
break;
}
Document.FoldingManager.UpdateFoldings(null, null);
}
/// <summary>
/// Gets the folding errors. Currently only XML is supported.
/// </summary>
/// <returns>List of errors, else empty list</returns>
public List<string> GetFoldingErrors()
{
if (_foldingStrategy == "XML")
{
var foldingStrategy = Document.FoldingManager.FoldingStrategy as IFoldingStrategyEx;
if (foldingStrategy != null)
{
return foldingStrategy.GetFoldingErrors();
}
}
return new List<string>();
}
#region ContextMenu Commands implementations
private bool CanUndo()
{
return Document.UndoStack.CanUndo;
}
private bool CanRedo()
{
return Document.UndoStack.CanRedo;
}
private bool CanCopy()
{
return ActiveTextAreaControl.SelectionManager.HasSomethingSelected;
}
private bool CanCut()
{
return ActiveTextAreaControl.SelectionManager.HasSomethingSelected;
}
private bool CanDelete()
{
return ActiveTextAreaControl.SelectionManager.HasSomethingSelected;
}
private bool CanPaste()
{
return ActiveTextAreaControl.TextArea.ClipboardHandler.EnablePaste;
}
private bool CanSelectAll()
{
if (Document.TextContent == null)
return false;
return !Document.TextContent.Trim().Equals(string.Empty);
}
private bool CanFind()
{
if (Document.TextContent == null)
return false;
return Document.TextContent.Trim().Any();
}
private void DoCut()
{
new Cut().Execute(ActiveTextAreaControl.TextArea);
ActiveTextAreaControl.Focus();
}
private void DoDelete()
{
new Delete().Execute(ActiveTextAreaControl.TextArea);
ActiveTextAreaControl.Focus();
}
private void DoCopy()
{
new Copy().Execute(ActiveTextAreaControl.TextArea);
ActiveTextAreaControl.Focus();
}
private void DoPaste()
{
new Paste().Execute(ActiveTextAreaControl.TextArea);
ActiveTextAreaControl.Focus();
}
private void DoSelectAll()
{
new SelectWholeDocument().Execute(ActiveTextAreaControl.TextArea);
ActiveTextAreaControl.Focus();
}
public void DoToggleFoldings()
{
new ToggleAllFoldings().Execute(ActiveTextAreaControl.TextArea);
}
private void DoFind()
{
new EditFindAction(_findForm, this).Execute(ActiveTextAreaControl.TextArea);
}
#endregion
#region ContextMenu Initialization
private ContextMenuStrip CreateNewContextMenu(bool showImages, bool showKeys)
{
var mnu = new ContextMenuStripEx();
mnu.AddToolStripMenuItem("&Undo",
showImages ? Resources.sc_undo : null,
(sender, e) => Undo(),
showKeys ? Keys.Control | Keys.Z : Keys.None,
CanUndo
);
mnu.AddToolStripMenuItem("&Redo",
showImages ? Resources.sc_redo : null,
(sender, e) => Redo(),
showKeys ? Keys.Control | Keys.Y : Keys.None,
CanRedo
);
mnu.AddToolStripSeparator();
mnu.AddToolStripMenuItem("&Cut",
showImages ? Resources.cut : null,
(sender, e) => DoCut(),
showKeys ? Keys.Control | Keys.X : Keys.None,
CanCut
);
mnu.AddToolStripMenuItem("Cop&y",
showImages ? Resources.sc_copy : null,
(sender, e) => DoCopy(),
showKeys ? Keys.Control | Keys.C : Keys.None,
CanCopy
);
mnu.AddToolStripMenuItem("&Paste",
showImages ? Resources.sc_paste : null,
(sender, e) => DoPaste(),
showKeys ? Keys.Control | Keys.V : Keys.None,
CanPaste
);
mnu.AddToolStripSeparator();
mnu.AddToolStripMenuItem("&Delete",
showImages ? Resources.sc_cancel : null,
(sender, e) => DoDelete(),
showKeys ? Keys.Delete : Keys.None,
CanDelete
);
mnu.AddToolStripMenuItem("&Select All",
showImages ? Resources.sc_selectall : null,
(sender, e) => DoSelectAll(),
showKeys ? Keys.Control | Keys.A : Keys.None,
CanSelectAll
);
mnu.AddToolStripMenuItem("&Find",
showImages ? Resources.sc_searchdialog : null,
(sender, e) => DoFind(),
showKeys ? Keys.Control | Keys.F : Keys.None,
CanFind
);
return mnu;
}
private void AssignContextMenu(ContextMenuStrip mnu)
{
if (ActiveTextAreaControl.ContextMenuStrip != null)
{
ActiveTextAreaControl.ContextMenuStrip.Dispose();
ActiveTextAreaControl.ContextMenuStrip = null;
}
ActiveTextAreaControl.ContextMenuStrip = mnu;
}
#endregion
#region ContextMenu Methods
public void SelectText(int start, int length)
{
var textLength = Document.TextLength;
if (textLength < (start + length))
{
length = (textLength - 1) - start;
}
ActiveTextAreaControl.Caret.Position = Document.OffsetToPosition(start + length);
ActiveTextAreaControl.SelectionManager.ClearSelection();
ActiveTextAreaControl.SelectionManager.SetSelection(new DefaultSelection(Document, Document.OffsetToPosition(start), Document.OffsetToPosition(start + length)));
Refresh();
}
private void OnDocumentChangedDoUpdateContextMenu(object sender, DocumentEventArgs e)
{
bool isVisible = (Document.TotalNumberOfLines > ActiveTextAreaControl.TextArea.TextView.VisibleLineCount);
ActiveTextAreaControl.VScrollBar.Visible = isVisible;
}
#endregion
}
public static class TextAreaControlExtensions
{
/// <summary>
/// Extension method to show a scrollbar.
/// </summary>
/// <param name="textAreaControl">The text area control.</param>
/// <param name="orientation">The orientation.</param>
/// <param name="isVisible">if set to <c>true</c> [is visible].</param>
public static void ShowScrollBars(this TextAreaControl textAreaControl, Orientation orientation, bool isVisible)
{
if (orientation == Orientation.Vertical)
{
textAreaControl.VScrollBar.Visible = isVisible;
}
else
{
textAreaControl.HScrollBar.Visible = isVisible;
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,62 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Daniel Grunwald" email="daniel@danielgrunwald.de"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Drawing;
namespace ICSharpCode.TextEditor
{
public delegate void ToolTipRequestEventHandler(object sender, ToolTipRequestEventArgs e);
public class ToolTipRequestEventArgs
{
Point mousePosition;
TextLocation logicalPosition;
bool inDocument;
public Point MousePosition {
get {
return mousePosition;
}
}
public TextLocation LogicalPosition {
get {
return logicalPosition;
}
}
public bool InDocument {
get {
return inDocument;
}
}
/// <summary>
/// Gets if some client handling the event has already shown a tool tip.
/// </summary>
public bool ToolTipShown {
get {
return toolTipText != null;
}
}
internal string toolTipText;
public void ShowToolTip(string text)
{
toolTipText = text;
}
public ToolTipRequestEventArgs(Point mousePosition, TextLocation logicalPosition, bool inDocument)
{
this.mousePosition = mousePosition;
this.logicalPosition = logicalPosition;
this.inDocument = inDocument;
}
}
}