Subversion Repositories general

Compare Revisions

Ignore whitespace Rev 1091 → Rev 1092

/TCPproxy/trunk/src/ViewControl.cs
0,0 → 1,1707
using System;
using System.Drawing;
using System.Collections;
using System.Windows.Forms;
using System.Text;
using System.Runtime.InteropServices;
 
namespace TCPproxy
{
// FIXME scroll line-by-line in word-wrap mode
// FIXME implement copy by keyboard commands
// FIXME show bin data
// FIXME abillity to insert text after saved marker
// FIXME don't use double buffering
// FIXME move markers and selection when text is changed
public class ViewControl : Control
{
private struct LineFragment
{
public string text;
public Color color;
public Color backColor;
public bool italic;
public bool bold;
public int indent;
public int wrapIndent;
}
 
private class ViewControlState
{
public bool deep;
public ArrayList lineBackColors;
public ArrayList lineFragments;
public ArrayList validMarkers;
public int curLine;
public int curCol;
public int selBeginLineOrig;
public int selBeginPosOrig;
public int selEndLineOrig;
public int selEndPosOrig;
}
 
private class TextMarker
{
public int beginLine;
public int beginPos;
public int endLine;
public int endPos;
 
public override string ToString()
{
return string.Format("{0},{1} - {2},{3}", beginLine, beginPos, endLine, endPos); // FIXME debug only
}
}
 
private static int NEW_LINE_LENGTH = 20;
 
private ArrayList lineBackColors = new ArrayList();
private ArrayList lineFragments = new ArrayList();
private int lastLineLen = 0;
private int linesCount = 0;
private int maxLineLength = 0;
 
private int curLine = 0;
private int curCol = 0;
private bool updateBlocked = false;
private int linesVisible = 0;
private int colsVisible = 0;
private float fontWidth = -1.0f;
private float fontHeight = -1.0f;
private bool wordWrap = false;
 
private ArrayList validMarkers = new ArrayList();
 
private ScrollBarsControl vScrollBar;
private ScrollBarsControl hScrollBar;
 
private StringFormat fontMeasureFormat = (StringFormat)System.Drawing.StringFormat.GenericTypographic.Clone();
 
private Font normal;
private Font italic;
private Font bold;
private Font boldIt;
 
private int selBeginLine = -1;
private int selBeginPos = -1;
private int selBeginLineOrig = -1;
private int selBeginPosOrig = -1;
private int selEndLine = -1;
private int selEndPos = -1;
private int selEndLineOrig = -1;
private int selEndPosOrig = -1;
 
public ViewControl()
{
this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.DoubleBuffer
| ControlStyles.ResizeRedraw, true);
 
fontMeasureFormat.LineAlignment = StringAlignment.Near;
fontMeasureFormat.FormatFlags = StringFormatFlags.MeasureTrailingSpaces | StringFormatFlags.FitBlackBox
| StringFormatFlags.NoWrap | StringFormatFlags.NoClip;
 
vScrollBar = new ScrollBarsControl(this, true);
vScrollBar.SmallChange = 1;
vScrollBar.Value = 0;
vScrollBar.Minimum = 0;
vScrollBar.Maximum = 0;
vScrollBar.Scroll += new ScrollEventHandler(this.vScrollBarScroll);
 
hScrollBar = new ScrollBarsControl(this, false);
hScrollBar.Scroll += new ScrollEventHandler(this.hScrollBarScroll);
 
RecalcParams();
}
 
const int WS_VSCROLL = 0x200000;
const int WS_HSCROLL = 0x100000;
const int WS_EX_CLIENTEDGE = 0x000200;
 
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
if(!base.DesignMode)
{
cp.Style = cp.Style | WS_HSCROLL | WS_VSCROLL;
cp.ExStyle = cp.ExStyle | WS_EX_CLIENTEDGE;
}
return cp;
}
}
 
public bool WordWrap
{
get { return wordWrap; }
set
{
wordWrap = value;
RecalcParams();
Invalidate();
// FIXME recalc number of lines for the vertical scroll bar
}
}
 
public void Clear()
{
lock(this)
{
lineBackColors = new ArrayList();
lineFragments = new ArrayList();
validMarkers = new ArrayList();
lastLineLen = 0;
linesCount = 0;
maxLineLength = 0;
curLine = 0;
curCol = 0;
selBeginLine = -1;
selBeginPos = -1;
selBeginLineOrig = -1;
selBeginPosOrig = -1;
selEndLine = -1;
selEndPos = -1;
selEndLineOrig = -1;
selEndPosOrig = -1;
 
if(!updateBlocked)
{
vScrollBar.Update(curLine, 0, linesCount, 1, 1);
UpdateHScrollBar();
Invalidate();
}
}
}
 
private void UpdateHScrollBar()
{
if(wordWrap)
hScrollBar.Update(0, 0, 0, 1, 1);
else
hScrollBar.Update(curCol, 0, maxLineLength + 2, 1, colsVisible);
}
 
// FIXME allow to save 'frozen' state
public object SaveState(bool deep)
{
if(deep) throw new NotImplementedException("Deep save is not yet implemented");
 
ViewControlState state = new ViewControlState();
 
lock(this)
{
state.deep = deep;
state.lineBackColors = lineBackColors;
state.lineFragments = lineFragments;
state.validMarkers = validMarkers;
state.curLine = curLine;
state.curCol = curCol;
state.selBeginLineOrig = selBeginLineOrig;
state.selBeginPosOrig = selBeginPosOrig;
state.selEndLineOrig = selEndLineOrig;
state.selEndPosOrig = selEndPosOrig;
}
 
return state;
}
 
public void RestoreState(object state, bool restorePosition)
{
if(!(state is ViewControlState))
throw new ArgumentException("Can restore only from object returned by SaveState()");
 
ViewControlState s = (ViewControlState)state;
 
if(s.deep) throw new NotImplementedException("Deep restore is not yet implemented");
 
if(lineBackColors.Count != lineFragments.Count) throw new ArgumentException("Wrong state");
 
lock(this)
{
lineBackColors = s.lineBackColors;
lineFragments = s.lineFragments;
linesCount = lineFragments.Count;
 
validMarkers = s.validMarkers;
 
lastLineLen = (linesCount > 0) ? ((LineFragment[])lineFragments[linesCount-1]).Length : 0;
 
maxLineLength = 0;
foreach(LineFragment[] line in lineFragments)
{
int lineLength = 0;
foreach(LineFragment frag in line)
{
lineLength += frag.indent + (frag.text == null ? 0 : frag.text.Length);
}
if(lineLength > maxLineLength) maxLineLength = lineLength;
}
 
if(restorePosition)
{
curLine = s.curLine;
curCol = s.curCol;
selBeginLineOrig = s.selBeginLineOrig;
selBeginPosOrig = s.selBeginPosOrig;
selEndLineOrig = s.selEndLineOrig;
selEndPosOrig = s.selEndPosOrig;
}
else
{
curLine = 0;
curCol = 0;
selBeginLineOrig = -1;
selBeginPosOrig = -1;
selEndLineOrig = -1;
selEndPosOrig = -1;
}
 
UpdateSelection();
updateBlocked = false;
 
vScrollBar.Update(curLine, 0, linesCount, 1, linesVisible);
UpdateHScrollBar();
Invalidate();
}
}
 
public object BeginMark()
{
TextMarker marker = new TextMarker();
 
lock(this)
{
marker.beginLine = linesCount-1;
marker.beginPos = lastLineLen;
 
validMarkers.Add(new WeakReference(marker));
}
 
return marker;
}
 
public void EndMark(object marker)
{
lock(this)
{
TextMarker m = GetTextMarker(marker);
 
m.endLine = linesCount-1;
m.endPos = lastLineLen;
}
}
 
public void BeginUpdate()
{
updateBlocked = true;
}
 
public void EndUpdate()
{
lock(this)
{
updateBlocked = false;
 
vScrollBar.Update(curLine, 0, linesCount, 1, linesVisible);
UpdateHScrollBar();
Invalidate();
}
}
 
public void AppendText(string text)
{
lock(this)
{
LineFragment[] line = null;
int lineLen = 0;
LineFragment fragment;
 
for(int i = linesCount - 1; i >= 0; i--)
{
line = (LineFragment[])lineFragments[i];
lineLen = (i == linesCount - 1) ? lastLineLen : line.Length;
if(lineLen > 0) break;
}
 
if(line == null || lineLen == 0)
{
fragment = new LineFragment();
fragment.color = this.ForeColor;
fragment.backColor = this.BackColor;
fragment.italic = false;
fragment.bold = false;
fragment.indent = 0;
fragment.wrapIndent = 0;
}
else
{
fragment = line[lineLen-1];
fragment.indent = 0;
}
 
fragment.text = text;
AppendText(fragment);
}
}
 
public void AppendText(string text, Color color, Color backColor, bool italic, bool bold, int indent, int wrapIndent)
{
LineFragment fragment;
 
fragment.text = text;
fragment.color = color;
fragment.backColor = backColor;
fragment.italic = italic;
fragment.bold = bold;
fragment.indent = indent;
fragment.wrapIndent = wrapIndent;
 
lock(this)
{
AppendText(fragment);
}
}
 
// always called in lock(this)
private void AppendText(LineFragment fragment)
{
LineFragment[] lastLine = (linesCount > 0) ? (LineFragment[])lineFragments[linesCount-1] : null;
 
if(lastLine == null)
{
lastLine = AddEmptyLine(Color.Transparent);
}
else if(lastLine.Length == lastLineLen)
{
LineFragment[] newLine = new LineFragment[lastLineLen * 2];
Array.Copy(lastLine, 0, newLine, 0, lastLineLen);
lastLine = newLine;
lineFragments[linesCount-1] = lastLine;
}
 
lastLine[lastLineLen++] = fragment;
 
int lineLength = 0;
for(int i = 0; i < lastLineLen; i++)
{
lineLength += lastLine[i].indent + (lastLine[i].text == null ? 0 : lastLine[i].text.Length);
}
if(lineLength > maxLineLength)
{
maxLineLength = lineLength;
if(!updateBlocked && !wordWrap) hScrollBar.Maximum = maxLineLength + 2;
}
 
if(!updateBlocked)
{
if(curLine + linesVisible + 1 >= lastLineLen) Invalidate();
}
}
 
public void AppendNewLine()
{
AppendNewLine(Color.Transparent);
}
 
public void AppendNewLine(Color backColor)
{
lock(this)
{
SaveLastLine();
AddEmptyLine(backColor);
}
}
 
private TextMarker GetTextMarker(object marker)
{
if(!(marker is TextMarker))
throw new ArgumentException("Can mark only with object returned by BeginMark()");
 
TextMarker m = (TextMarker)marker;
 
// check if marker is valid
bool isValid = false;
for(int i = validMarkers.Count-1; i >= 0; i--)
{
WeakReference refer = (WeakReference)validMarkers[i];
 
if(!refer.IsAlive || refer.Target == null)
{
validMarkers.RemoveAt(i);
}
else
{
if(refer.Target == m)
{
isValid = true;
}
}
}
if(!isValid) throw new ArgumentException("Invalid marker");
 
if(m.beginLine > linesCount)
throw new ArgumentException("Wrong marker");
 
LineFragment[] line = (linesCount > 0) ? (LineFragment[])lineFragments[m.beginLine] : null;
int lineLen = (linesCount == 0 || m.beginLine == linesCount-1) ? lastLineLen : line.Length;
 
if(m.beginPos > lineLen)
throw new ArgumentException("Wrong marker");
 
return m;
}
 
public void DeleteText(object marker)
{
lock(this)
{
TextMarker m = GetTextMarker(marker);
 
// delete the first line
int lineDeleted = DeleteLinePart(m.beginLine, m.beginPos, (m.beginLine == m.endLine) ? m.endPos : -1);
 
// delete the middle
for(int i = m.beginLine + 1; i < m.endLine; i++)
{
lineFragments.RemoveAt(m.beginLine - lineDeleted + 1);
lineBackColors.RemoveAt(m.beginLine - lineDeleted + 1);
linesCount--;
}
if(m.endLine > m.beginLine) lineDeleted += m.endLine - m.beginLine - 1;
 
// delete the last line
if(m.beginLine != m.endLine && m.endLine < linesCount && m.endPos > 0)
DeleteLinePart(m.endLine - lineDeleted, 0, m.endPos);
 
// update screen
if(!updateBlocked)
{
Invalidate();
}
 
int lastLine = m.endLine;
ShiftTextMarkers(m.beginLine, m.beginPos, -lineDeleted, 0);
ShiftTextMarkers(lastLine, m.endPos, 0, -(m.endPos - (m.beginLine == lastLine ? m.beginPos : 0)));
}
}
 
private int DeleteLinePart(int lineNum, int begin, int end)
{
int deleted = 0;
LineFragment[] line = (LineFragment[])lineFragments[lineNum];
int lineLen = (lineNum == linesCount - 1) ? lastLineLen : line.Length;
 
if(end == 0) return 0;
 
if(end < 0) end = lineLen;
 
if(begin == 0 && end == lineLen)
{
// delete whole line
deleted = 1;
lineFragments.RemoveAt(lineNum);
lineBackColors.RemoveAt(lineNum);
linesCount--;
}
else
{
// delete part of line
LineFragment[] newLine = new LineFragment[(lineNum == linesCount - 1) ? line.Length : lineLen - end + begin];
if(begin > 0) Array.Copy(line, 0, newLine, 0, begin);
if(end < lineLen) Array.Copy(line, end, newLine, begin, lineLen - end);
if(lineNum == linesCount - 1) lastLineLen = lineLen - end + begin;
lineFragments[lineNum] = newLine;
}
 
return deleted;
}
 
public void ChangeText(object marker, string text, Color color, Color backColor,
bool italic, bool bold, int indent, int wrapIndent)
{
lock(this)
{
TextMarker m = GetTextMarker(marker);
 
// FIXME implement for multiple lines and fragments
 
LineFragment[] line = (LineFragment[])lineFragments[m.beginLine];
LineFragment fragment = line[m.beginPos];
 
if(fragment.text == text && fragment.color == color && fragment.backColor == backColor
&& fragment.italic == italic && fragment.bold == bold && fragment.indent == indent
&& fragment.wrapIndent == wrapIndent)
{
return; // the fragment is not changed
}
 
fragment.text = text;
fragment.color = color;
fragment.backColor = backColor;
fragment.italic = italic;
fragment.bold = bold;
fragment.indent = indent;
fragment.wrapIndent = wrapIndent;
 
line[m.beginPos] = fragment;
 
if(!updateBlocked)
{
if(m.beginLine >= curLine && m.beginLine <= curLine + linesVisible + 1) Invalidate();
}
}
}
 
// side effect - the given marker is moved to position after the inserted text
public void InsertText(object marker, string text, Color color, Color backColor,
bool italic, bool bold, int indent, int wrapIndent)
{
lock(this)
{
TextMarker m = GetTextMarker(marker);
 
LineFragment[] line = (linesCount > m.endLine) ? (LineFragment[])lineFragments[m.endLine] : null;
LineFragment fragment = new LineFragment();
 
fragment.text = text;
fragment.color = color;
fragment.backColor = backColor;
fragment.italic = italic;
fragment.bold = bold;
fragment.indent = indent;
fragment.wrapIndent = wrapIndent;
 
if(line == null)
{
line = AddEmptyLine(Color.Transparent);
lastLineLen++;
}
else if(m.endLine == linesCount - 1)
{
if(line.Length <= lastLineLen || m.endPos < lastLineLen)
{
LineFragment[] newLine = new LineFragment[lastLineLen * (line.Length == lastLineLen ? 2 : 1)];
 
if(m.endPos > 0) Array.Copy(line, 0, newLine, 0, m.endPos);
if(lastLineLen > m.endPos) Array.Copy(line, m.endPos, newLine, m.endPos+1, lastLineLen - m.endPos);
line = newLine;
lineFragments[m.endLine] = line;
}
lastLineLen++;
}
else
{
LineFragment[] newLine = new LineFragment[line.Length + 1];
if(m.endPos > 0) Array.Copy(line, 0, newLine, 0, m.endPos);
if(line.Length > m.endPos) Array.Copy(line, m.endPos, newLine, m.endPos+1, line.Length - m.endPos);
line = newLine;
lineFragments[m.endLine] = line;
}
 
line[m.endPos] = fragment;
 
// recalc text width
int lineLength = 0;
int newLineLen = (m.endLine == linesCount - 1) ? lastLineLen : line.Length;
for(int i = 0; i < newLineLen; i++)
{
lineLength += line[i].indent + (line[i].text == null ? 0 : line[i].text.Length);
}
if(lineLength > maxLineLength)
{
maxLineLength = lineLength;
if(!updateBlocked && !wordWrap) hScrollBar.Maximum = maxLineLength + 2;
}
 
// update screen
if(!updateBlocked)
{
if(m.endLine >= curLine && m.endLine <= curLine + linesVisible + 1) Invalidate();
}
 
ShiftTextMarkers(m.endLine, m.endPos, 0, 1);
if(m.beginLine == m.endLine && m.beginPos == m.endPos) m.beginPos--; // return the begin back if empty marker
}
}
 
public void InsertNewLine(object marker)
{
InsertNewLine(marker, Color.Transparent);
}
 
public void InsertNewLine(object marker, Color backColor)
{
lock(this)
{
TextMarker m = GetTextMarker(marker);
 
if(m.endLine == linesCount || (m.endLine == linesCount - 1 && m.endPos == lastLineLen))
{
SaveLastLine();
AddEmptyLine(backColor);
}
else
{
LineFragment[] oldLine = (LineFragment[])lineFragments[m.endLine];
int oldLineLen = (m.endLine == linesCount - 1) ? lastLineLen : oldLine.Length;
LineFragment[] line1 = new LineFragment[m.endPos];
LineFragment[] line2 = new LineFragment[
(m.endLine == linesCount - 1) ? NEW_LINE_LENGTH : oldLine.Length - m.endPos];
 
if(m.endPos > 0) Array.Copy(oldLine, 0, line1, 0, m.endPos);
if(oldLineLen - m.endPos > 0) Array.Copy(oldLine, m.endPos, line2, 0, oldLineLen - m.endPos);
 
lineFragments[m.endLine] = line1;
lineFragments.Insert(m.endLine + 1, line2);
lineBackColors.Insert(m.endLine + 1, backColor);
linesCount++;
 
if(m.endLine == linesCount - 1) lastLineLen = oldLineLen - m.endPos;
}
 
// update screen
if(!updateBlocked)
{
if(m.endLine >= curLine && m.endLine <= curLine + linesVisible + 1) Invalidate();
vScrollBar.Maximum = linesCount;
}
 
ShiftTextMarkers(m.endLine, m.endPos, 1, 0);
if(m.beginLine == m.endLine && m.beginLine == m.endLine) m.beginLine--; // return the begin back if empty marker
}
}
 
private void ShiftTextMarkers(int line, int pos, int lineDelta, int posDelta)
{
for(int i = validMarkers.Count-1; i >= 0; i--)
{
WeakReference refer = (WeakReference)validMarkers[i];
 
if(!refer.IsAlive || refer.Target == null)
{
validMarkers.RemoveAt(i);
}
else
{
TextMarker m = (TextMarker)refer.Target;
 
ShiftTextMarkerBorder(ref m.beginLine, ref m.beginPos, line, pos, lineDelta, posDelta);
ShiftTextMarkerBorder(ref m.endLine, ref m.endPos, line, pos, lineDelta, posDelta);
}
}
}
 
private void ShiftTextMarkerBorder(ref int borderLine, ref int borderPos,
int line, int pos, int lineDelta, int posDelta)
{
if(borderLine > line)
{
borderLine += lineDelta;
if(borderLine < line) borderLine = line;
}
else if(borderLine == line)
{
if(lineDelta != 0)
{
if(borderPos >= pos)
{
borderLine += lineDelta;
if(borderLine < line) borderLine = line;
 
borderPos -= pos - posDelta;
}
}
else
{
if(borderPos >= pos) borderPos += posDelta;
}
}
}
 
private void SaveLastLine()
{
if(linesCount > 0)
{
LineFragment[] lastLine = (LineFragment[])lineFragments[linesCount-1];
LineFragment[] newLine = new LineFragment[lastLineLen];
Array.Copy(lastLine, 0, newLine, 0, lastLineLen);
lineFragments[linesCount-1] = newLine;
}
}
 
private LineFragment[] AddEmptyLine(Color backColor)
{
LineFragment[] lastLine = new LineFragment[NEW_LINE_LENGTH];
lastLineLen = 0;
lineBackColors.Add(backColor);
lineFragments.Add(lastLine);
 
linesCount++;
 
if(!updateBlocked) vScrollBar.Maximum = linesCount;
 
return lastLine;
}
 
public override Font Font
{
get
{
return base.Font;
}
 
set
{
lock(this)
{
base.Font = value;
 
normal = new Font(this.Font.FontFamily, this.Font.Size);
italic = new Font(this.Font.FontFamily, this.Font.Size, FontStyle.Italic);
bold = new Font(this.Font.FontFamily, this.Font.Size, FontStyle.Bold);
boldIt = new Font(this.Font.FontFamily, this.Font.Size, FontStyle.Bold | FontStyle.Italic);
 
fontWidth = -1.0f;
RecalcParams();
Invalidate();
}
}
}
 
private void RecalcParams()
{
fontHeight = this.Font.GetHeight();
linesVisible = (fontHeight > 0) ? (int)Math.Ceiling(this.ClientSize.Height / fontHeight) : 0;
vScrollBar.LargeChange = linesVisible;
 
RecalcColsVisible();
}
 
private void UpdateFontWidth(Graphics g)
{
fontWidth = g.MeasureString("x", this.Font, int.MaxValue, fontMeasureFormat).Width;
RecalcColsVisible();
}
 
private void RecalcColsVisible()
{
colsVisible = (fontWidth > 0) ? (int)Math.Ceiling(this.ClientSize.Width / fontWidth) : 0;
UpdateHScrollBar();
}
 
protected override void OnSystemColorsChanged(EventArgs e)
{
Invalidate();
base.OnSystemColorsChanged(e);
}
 
protected override void OnResize(EventArgs e)
{
lock(this)
{
RecalcParams();
}
base.OnResize(e);
}
 
protected override void OnPaintBackground(PaintEventArgs pevent)
{
}
 
protected override void OnPaint(PaintEventArgs e)
{
lock(this)
{
try
{
Graphics g = e.Graphics;
SolidBrush brush = new SolidBrush(Color.Black);
 
g.FillRectangle(SystemBrushes.Window, g.Clip.GetBounds(g));
 
if(fontWidth <= 0) UpdateFontWidth(g);
 
int drawLine = 0;
for(int lineNumber = curLine; drawLine < linesVisible && lineNumber < linesCount; lineNumber++)
{
Color backColor = (Color)lineBackColors[lineNumber];
LineFragment[] line = (LineFragment[])lineFragments[lineNumber];
int lineLen = (lineNumber == linesCount-1) ? lastLineLen : line.Length;
 
// line background
if(lineLen == 0 && selBeginLine <= lineNumber && lineNumber <= selEndLine)
{
brush.Color = SystemColors.Highlight;
g.FillRectangle(brush, 0, drawLine * fontHeight, this.ClientSize.Width, fontHeight);
}
else if(backColor != Color.Transparent)
{
brush.Color = backColor;
g.FillRectangle(brush, 0, drawLine * fontHeight, this.ClientSize.Width, fontHeight);
}
 
int indent = 0;
int shift = curCol;
for(int j = 0; j < lineLen; j++)
{
LineFragment fragment = line[j];
int curIndent = fragment.indent;
string text = fragment.text;
int textLen = (text == null) ? 0 : text.Length;
 
// calc shift using curCol
if(shift > 0)
{
if(shift < fragment.indent)
{
curIndent -= shift;
shift = 0;
}
else if(shift < textLen + fragment.indent)
{
if(text != null)
{
text = text.Substring(shift - fragment.indent);
textLen = text.Length;
}
shift = 0;
curIndent = 0;
}
else
{
curIndent = 0;
shift -= textLen + fragment.indent;
text = null;
textLen = 0;
}
}
 
// draw the line, m.b. split it to several display lines
while(true)
{
string drawText = text;
int drawLen = textLen;
bool lineWraped = false;
 
// wrap line
// FIXME try to wrap whole words only
// FIXME test if wrap is in fragment indent
if(wordWrap)
{
int visibleLen = colsVisible - 2;
int availableLen = visibleLen - indent - curIndent;
if(textLen > availableLen)
{
if(textLen == 0 || availableLen < 0)
{
drawText = null;
drawLen = 0;
text = null;
textLen = 0;
}
else if(availableLen == 0)
{
drawText = null; // only wrap line, draw nothing on current one
drawLen = 0;
}
else
{
drawText = text.Substring(0, availableLen);
drawLen = drawText.Length;
text = text.Substring(availableLen);
textLen = text.Length;
}
lineWraped = true;
}
}
 
// draw background
if(fragment.backColor != Color.Transparent && (indent >= 0) && (drawLen + curIndent > 0))
{
brush.Color = fragment.backColor;
g.FillRectangle(brush, indent * fontWidth, drawLine * fontHeight,
(float)Math.Ceiling((drawLen + curIndent) * fontWidth), fontHeight);
}
 
// draw selection background
int selBegin = -1;
int selEnd = -1;
if(selBeginLine <= lineNumber && lineNumber <= selEndLine)
{
selBegin = (lineNumber == selBeginLine) ? Math.Max(selBeginPos - curCol, 0) : 0;
selEnd = (lineNumber == selEndLine) ? selEndPos - curCol : colsVisible;
 
int selBeginBack = Math.Max(indent, selBegin);
int selLen = (j == lineLen-1) ? (selEnd - selBeginBack)
: Math.Min(drawLen + curIndent, selEnd - selBeginBack);
 
if(selLen > 0)
{
brush.Color = SystemColors.Highlight;
g.FillRectangle(brush, selBeginBack * fontWidth, drawLine * fontHeight,
(float)Math.Ceiling(selLen* fontWidth), fontHeight);
}
}
 
// draw the text
indent += curIndent;
if(drawText != null)
{
Font font = (fragment.bold) ? (fragment.italic ? boldIt : bold) : (fragment.italic ? italic : normal);
 
// split the fragment text into 3 pieces: before selection, the selection, after selection
// (some may be empty)
string s1 = drawText, s2 = null, s3 = null;
int p2 = 0, p3 = 0;
 
if(selBegin >= 0)
{
int begin = Math.Max(0, selBegin - indent);
if(begin < drawLen)
{
int len = Math.Min(selEnd - indent - begin, drawLen - begin);
if(len > 0)
{
s1 = (begin == 0) ? null : drawText.Substring(0, begin);
s2 = (begin == 0 && len == drawLen) ? drawText : drawText.Substring(begin, len);
s3 = (begin + len >= drawLen) ? null : drawText.Substring(begin + len);
}
}
}
 
if(s1 != null)
{
brush.Color = fragment.color;
g.DrawString(s1, font, brush, indent * fontWidth, drawLine * fontHeight, fontMeasureFormat);
p2 = s1.Length;
}
 
if(s2 != null)
{
brush.Color = SystemColors.HighlightText;
g.DrawString(s2, font, brush, (indent + p2) * fontWidth, drawLine * fontHeight, fontMeasureFormat);
p3 = p2 + s2.Length;
}
 
if(s3 != null)
{
brush.Color = fragment.color;
g.DrawString(s3, font, brush, (indent + p3) * fontWidth, drawLine * fontHeight, fontMeasureFormat);
}
 
indent += drawLen;
}
 
if(lineWraped)
{
indent = fragment.wrapIndent;
curIndent = 0;
drawLine++;
 
if(drawLine >= linesVisible) break;
 
if(backColor != Color.Transparent)
{
brush.Color = backColor;
g.FillRectangle(brush, 0, drawLine * fontHeight, this.ClientSize.Width, fontHeight);
}
}
else
{
break;
}
}
}
 
drawLine++;
}
}
catch(Exception ex)
{
Console.WriteLine(ex.Message + "\n" + ex.StackTrace);
}
}
}
 
public void SelectAll()
{
lock(this)
{
selBeginLineOrig = 0;
selBeginPosOrig = 0;
selEndLineOrig = linesCount-1;
selEndPosOrig = 0;
 
if(linesCount > 0)
{
LineFragment[] line = (LineFragment[])lineFragments[linesCount - 1];
 
for(int i = 0; i < lastLineLen; i++)
{
selEndPosOrig += line[i].indent + ((line[i].text != null) ? line[i].text.Length : 0);
}
}
 
UpdateSelection();
Invalidate();
}
}
 
public string SelectedText
{
get
{
lock(this)
{
if(selBeginLine < 0) return null;
 
StringBuilder b = new StringBuilder((selEndLine - selBeginLine + 1) * maxLineLength);
 
for(int lineNumber = selBeginLine; lineNumber <= selEndLine && lineNumber < linesCount; lineNumber++)
{
int cur = 0;
int begin = (lineNumber == selBeginLine) ? Math.Max(selBeginPos, 0) : 0;
int end = (lineNumber == selEndLine) ? Math.Max(selEndPos, 0) : int.MaxValue;
 
LineFragment[] line = (LineFragment[])lineFragments[lineNumber];
int lineLen = (lineNumber == linesCount-1) ? lastLineLen : line.Length;
for(int i = 0; i < lineLen; i++)
{
LineFragment fragment = line[i];
 
if(cur + fragment.indent <= begin || cur >= end)
{
cur += fragment.indent;
}
else
{
for(int j = fragment.indent - 1; j >= 0; j--)
{
if(cur >= begin && cur < end) b.Append(' ');
cur++;
}
}
 
if(fragment.text != null)
{
int curEnd = cur + fragment.text.Length;
if(begin <= cur && curEnd <= end)
b.Append(fragment.text);
else if(begin <= cur && cur < end || begin < curEnd && curEnd <= end || cur <= begin && end <= curEnd)
{
int copyBegin = Math.Max(begin - cur, 0);
b.Append(fragment.text.Substring(copyBegin, Math.Min(end, curEnd) - cur - copyBegin));
}
 
cur += fragment.text.Length;
}
 
if(cur >= end) break;
if(i == lineLen - 1) b.Append("\r\n");
}
}
 
return b.ToString();
}
}
}
 
private void vScrollBarScroll(Object sender, ScrollEventArgs e)
{
MoveToVertical(e.NewValue, true);
}
 
private void hScrollBarScroll(Object sender, ScrollEventArgs e)
{
MoveToHorizontal(e.NewValue, true);
}
 
private void MoveToVertical(int pos, bool invalidate)
{
curLine = pos;
if(curLine + linesVisible > linesCount) curLine = linesCount - linesVisible + 1;
if(curLine < 0) curLine = 0;
vScrollBar.Value = curLine;
if(invalidate) this.Invalidate();
}
 
private void MoveByVertical(int delta, bool invalidate)
{
MoveToVertical(curLine + delta, invalidate);
}
 
private void MoveToHorizontal(int pos, bool invalidate)
{
if(wordWrap)
{
curCol = 0;
}
else
{
curCol = pos;
if(curCol < 0) curCol = 0;
hScrollBar.Value = curCol;
}
if(invalidate) this.Invalidate();
}
 
private void MoveByHorizontal(int delta, bool invalidate)
{
MoveToHorizontal(curCol + delta, invalidate);
}
 
protected override bool IsInputKey(Keys keyData)
{
switch(keyData)
{
case Keys.PageDown:
case Keys.PageUp:
case Keys.Down:
case Keys.Up:
case Keys.Left:
case Keys.Right:
case Keys.Home:
case Keys.End:
return true;
default:
return base.IsInputKey(keyData);
}
}
 
protected override void OnKeyDown(KeyEventArgs e)
{
switch(e.KeyCode)
{
case Keys.PageDown:
lock(this)
{
MoveByVertical(linesVisible, true);
}
break;
 
case Keys.PageUp:
lock(this)
{
MoveByVertical(-linesVisible, true);
}
break;
case Keys.Down:
lock(this)
{
MoveByVertical(1, true);
}
break;
case Keys.Up:
lock(this)
{
MoveByVertical(-1, true);
}
break;
case Keys.Right:
lock(this)
{
MoveByHorizontal(1, true);
}
break;
case Keys.Left:
lock(this)
{
MoveByHorizontal(-1, true);
}
break;
case Keys.Home:
lock(this)
{
MoveToHorizontal(0, false);
MoveToVertical(0, true);
}
break;
case Keys.End:
lock(this)
{
MoveToHorizontal(0, false);
MoveToVertical(linesCount - linesVisible + 1, true);
}
break;
default:
base.OnKeyDown(e);
break;
}
}
 
private void UpdateSelection()
{
if(selBeginLineOrig < 0)
{
selBeginLine = -1;
selBeginPos = -1;
selEndLine = -1;
selEndPos = -1;
}
else if(selEndLineOrig < selBeginLineOrig || selEndLineOrig == selBeginLineOrig && selEndPosOrig < selBeginPosOrig)
{ // swap if end before begin
selBeginLine = selEndLineOrig;
selBeginPos = selEndPosOrig;
selEndLine = selBeginLineOrig;
selEndPos = selBeginPosOrig;
}
else
{
selBeginLine = selBeginLineOrig;
selBeginPos = selBeginPosOrig;
selEndLine = selEndLineOrig;
selEndPos = selEndPosOrig;
}
}
 
private void UpdateSelectionEnd(int x, int y)
{
if(fontWidth > 0 && fontHeight > 0 && selBeginLineOrig >= 0)
{
selEndLineOrig = (int)(y / fontHeight) + curLine;
selEndPosOrig = (int)Math.Round(x / fontWidth) + curCol;
 
UpdateSelection();
Invalidate();
}
}
 
protected override void OnMouseMove(MouseEventArgs e)
{
if(e.Button == MouseButtons.Left)
{
lock(this)
{
UpdateSelectionEnd(e.X, e.Y);
// FIXME scroll if selection is dragged outside of the control, use timer to scroll
if(e.Y > this.ClientSize.Height && fontWidth > 0)
{
MoveToVertical(selEndLine - linesVisible + 1, true);
}
}
}
 
base.OnMouseMove(e);
}
 
protected override void OnMouseDown(MouseEventArgs e)
{
this.Focus();
 
if(e.Button == MouseButtons.Left)
{
lock(this)
{
if(fontWidth > 0 && fontHeight > 0)
{
selBeginLineOrig = (int)(e.Y / fontHeight) + curLine;
selBeginPosOrig = (int)Math.Round(e.X / fontWidth) + curCol;
selEndLineOrig = selBeginLineOrig;
selEndPosOrig = selBeginPosOrig;
 
UpdateSelection();
Invalidate();
}
}
}
 
base.OnMouseDown(e);
}
 
private const int WHEEL_DELTA = 120;
 
protected override void OnMouseWheel(MouseEventArgs e)
{
lock(this)
{
MoveByVertical(e.Delta / WHEEL_DELTA * SystemInformation.MouseWheelScrollLines * (-1), true);
}
 
base.OnMouseWheel(e);
}
 
private const int WM_ACTIVATE = 0x0006;
private const int WM_ACTIVATEAPP = 0x001C;
private const int WM_HSCROLL = 0x0114;
private const int WM_VSCROLL = 0x0115;
 
protected override void WndProc(ref System.Windows.Forms.Message m)
{
switch(m.Msg)
{
case WM_VSCROLL:
vScrollBar.HandleWindowMessage(ref m);
break;
 
case WM_HSCROLL:
hScrollBar.HandleWindowMessage(ref m);
break;
 
default:
base.WndProc(ref m);
break;
}
}
 
private class ScrollBarsControl
{
private Control owner;
private bool vertical;
private int smallChange;
private int largeChange;
private int minimum;
private int maximum;
private bool showAlways = true;
private int currentValue = 0;
private SCROLLINFO scrollInfo;
 
public event ScrollEventHandler Scroll;
 
public ScrollBarsControl(Control owner, bool vertical)
{
this.owner = owner;
this.vertical = vertical;
 
scrollInfo = new SCROLLINFO();
scrollInfo.cbSize = (uint)Marshal.SizeOf(typeof(SCROLLINFO));
 
// get initial state
scrollInfo.fMask = SIF_RANGE | SIF_PAGE;
GetScrollInfo(owner.Handle, vertical ? SB_VERT : SB_HORZ, ref scrollInfo);
minimum = scrollInfo.nMin;
maximum = scrollInfo.nMax;
smallChange = 1;
largeChange = (int)scrollInfo.nPage;
}
 
public void HandleWindowMessage(ref System.Windows.Forms.Message m)
{
switch(m.Msg)
{
case WM_VSCROLL:
if(!vertical)
throw new ArgumentException("I'm horizontal scroll bar, can not handle vertical scroll");
break;
 
case WM_HSCROLL:
if(vertical)
throw new ArgumentException("I'm vertical scroll bar, can not handle horizontal scroll");
break;
 
default:
throw new ArgumentException("Unknown message");
}
 
short cmd = (short)((uint)m.WParam & 0x0000FFFF);
ScrollEventType type;
bool update = false;
 
switch(cmd)
{
case SB_LINEDOWN:
type = ScrollEventType.SmallIncrement;
currentValue += smallChange;
update = true;
break;
 
case SB_LINEUP:
type = ScrollEventType.SmallDecrement;
currentValue -= smallChange;
update = true;
break;
 
case SB_PAGEDOWN:
type = ScrollEventType.LargeIncrement;
currentValue += largeChange;
update = true;
break;
 
case SB_PAGEUP:
type = ScrollEventType.LargeDecrement;
currentValue -= largeChange;
update = true;
break;
 
case SB_TOP:
type = ScrollEventType.First;
currentValue = minimum;
update = true;
break;
 
case SB_BOTTOM:
type = ScrollEventType.Last;
currentValue = maximum;
update = true;
break;
 
case SB_ENDSCROLL:
type = ScrollEventType.EndScroll;
break;
 
case SB_THUMBTRACK:
type = ScrollEventType.ThumbTrack;
currentValue = GetTrackPos();
break;
 
case SB_THUMBPOSITION:
type = ScrollEventType.ThumbPosition;
currentValue = GetTrackPos();
update = true;
break;
 
default:
throw new ArgumentException("Unknown command " + cmd);
}
 
UpdateCurrentValue(update);
ScrollEventArgs e = new ScrollEventArgs(type, currentValue);
OnScroll(e);
}
 
private int GetTrackPos()
{
lock(this)
{
scrollInfo.fMask = SIF_TRACKPOS;
GetScrollInfo(owner.Handle, vertical ? SB_VERT : SB_HORZ, ref scrollInfo);
return scrollInfo.nTrackPos;
}
}
 
public int Value
{
get
{
return currentValue;
}
 
set
{
currentValue = value;
UpdateCurrentValue(true);
}
}
 
public bool ShowAlways
{
get { return showAlways; }
set
{
showAlways = value;
SetRange(false);
}
}
 
public int Minimum
{
get
{
return minimum;
}
 
set
{
minimum = value;
SetRange(false);
}
}
 
public int Maximum
{
get
{
return maximum;
}
 
set
{
maximum = value;
SetRange(false);
}
}
 
public void Update(int value, int minimum, int maximum, int smallChange, int largeChange)
{
this.currentValue = value;
this.minimum = minimum;
this.maximum = maximum;
this.smallChange = smallChange;
this.largeChange = largeChange;
 
SetRange(true);
}
 
private void SetRange(bool setPos)
{
lock(this)
{
scrollInfo.fMask = SIF_RANGE | SIF_PAGE;
if(showAlways) scrollInfo.fMask |= SIF_DISABLENOSCROLL;
scrollInfo.nMin = minimum;
scrollInfo.nMax = maximum;
scrollInfo.nPage = (uint)largeChange;
 
if(setPos || currentValue < minimum || currentValue > maximum)
{
if(currentValue < minimum) currentValue = minimum;
if(currentValue > maximum) currentValue = maximum;
scrollInfo.fMask |= SIF_POS;
scrollInfo.nPos = currentValue;
}
 
if(setPos)
{
scrollInfo.fMask |= SIF_PAGE;
scrollInfo.nPage = (uint)largeChange;
}
 
SetScrollInfo(owner.Handle, vertical ? SB_VERT : SB_HORZ, ref scrollInfo, true);
}
}
public int SmallChange
{
get
{
return smallChange;
}
 
set
{
if(value < 0) throw new ArgumentException("SmallChange must be non-negative");
smallChange = value;
}
}
 
public int LargeChange
{
get
{
return largeChange;
}
 
set
{
if(value < 0) throw new ArgumentException("LargeChange must be non-negative");
largeChange = value;
 
lock(this)
{
scrollInfo.fMask = SIF_PAGE;
if(showAlways) scrollInfo.fMask |= SIF_DISABLENOSCROLL;
scrollInfo.nPage = (uint)largeChange;
SetScrollInfo(owner.Handle, vertical ? SB_VERT : SB_HORZ, ref scrollInfo, true);
}
}
}
 
private void UpdateCurrentValue(bool force)
{
if(!force && currentValue >= minimum && currentValue <= maximum) return;
 
if(currentValue < minimum) currentValue = minimum;
if(currentValue > maximum) currentValue = maximum;
 
lock(this)
{
scrollInfo.fMask = SIF_POS;
if(showAlways) scrollInfo.fMask |= SIF_DISABLENOSCROLL;
scrollInfo.nPos = currentValue;
SetScrollInfo(owner.Handle, vertical ? SB_VERT : SB_HORZ, ref scrollInfo, true);
}
}
 
protected virtual void OnScroll(ScrollEventArgs e)
{
if(Scroll != null)
{
Scroll(this, e);
}
}
 
private const short SB_HORZ = 0;
private const short SB_VERT = 1;
private const short SB_CTL = 2;
private const short SB_BOTH = 3;
 
private const short SB_LINEUP = 0;
private const short SB_LINELEFT = 0;
private const short SB_LINEDOWN = 1;
private const short SB_LINERIGHT = 1;
private const short SB_PAGEUP = 2;
private const short SB_PAGELEFT = 2;
private const short SB_PAGEDOWN = 3;
private const short SB_PAGERIGHT = 3;
private const short SB_THUMBPOSITION = 4;
private const short SB_THUMBTRACK = 5;
private const short SB_TOP = 6;
private const short SB_LEFT = 6;
private const short SB_BOTTOM = 7;
private const short SB_RIGHT = 7;
private const short SB_ENDSCROLL = 8;
 
private const uint SIF_RANGE = 0x0001;
private const uint SIF_PAGE = 0x0002;
private const uint SIF_POS = 0x0004;
private const uint SIF_DISABLENOSCROLL = 0x0008;
private const uint SIF_TRACKPOS = 0x0010;
private const uint SIF_ALL = (SIF_RANGE | SIF_PAGE | SIF_POS | SIF_TRACKPOS);
 
/*
typedef struct tagSCROLLINFO {
UINT cbSize;
UINT fMask;
int nMin;
int nMax;
UINT nPage;
int nPos;
int nTrackPos;
} SCROLLINFO, *LPSCROLLINFO;
typedef SCROLLINFO CONST *LPCSCROLLINFO;
*/
 
[StructLayout(LayoutKind.Sequential, Pack=4, CharSet=CharSet.Auto)]
private struct SCROLLINFO
{
public uint cbSize;
public uint fMask;
public int nMin;
public int nMax;
public uint nPage;
public int nPos;
public int nTrackPos;
}
 
/*
BOOL GetScrollInfo(
HWND hwnd,
int fnBar,
LPSCROLLINFO lpsi
);
*/
 
[DllImport("User32", CharSet=CharSet.Auto)]
private static extern bool GetScrollInfo(IntPtr hWnd, int fnBar, ref SCROLLINFO lpsi);
 
/*
int SetScrollInfo(
HWND hwnd,
int fnBar,
LPCSCROLLINFO lpsi,
BOOL fRedraw
);
*/
 
[DllImport("User32", CharSet=CharSet.Auto)]
private static extern int SetScrollInfo(IntPtr hWnd, int fnBar, ref SCROLLINFO lpsi, bool fRedraw);
}
}
}