464 lines
14 KiB
C#
464 lines
14 KiB
C#
// <file>
|
|
// <copyright see="prj:///doc/copyright.txt"/>
|
|
// <license see="prj:///doc/license.txt"/>
|
|
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
|
// <version>$Revision$</version>
|
|
// </file>
|
|
|
|
using System;
|
|
using System.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);
|
|
}
|
|
}
|
|
}
|