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); |
} |
} |
} |