Line // Selection=Line under caret
}
- internal class FontDefinition {
- internal String face;
- internal int size;
- internal FontStyle add_style;
- internal FontStyle remove_style;
- internal Color color;
- internal Font font_obj;
- }
-
[Flags]
internal enum FormatSpecified {
None,
CharBackNoWrap // Move a char backward, but don't wrap onto the previous line
}
+ internal enum LineEnding {
+ Wrap, // line wraps to the next line
+ Limp, // \r
+ Hard, // \r\n
+ Soft, // \r\r\n
+ Rich, // \n
+
+ None
+ }
+
// Being cloneable should allow for nice line and document copies...
internal class Line : ICloneable, IComparable {
#region Local Variables
internal int ascent; // Ascent of the line (ascent of the tallest tag)
internal HorizontalAlignment alignment; // Alignment of the line
internal int align_shift; // Pixel shift caused by the alignment
- internal bool soft_break; // Tag is 'broken soft' and continuation from previous line
internal int indent; // Left indent for the first line
internal int hanging_indent; // Hanging indent (left indent for all but the first line)
internal int right_indent; // Right indent for all lines
- internal bool carriage_return;
+ internal LineEnding ending;
// Stuff that's important for the tree
internal LineColor color; // We're doing a black/red tree. this is the node color
internal int DEFAULT_TEXT_LEN; //
internal bool recalc; // Line changed
- internal int left_margin = 2; // A left margin for all lines
- internal int top_margin = 2;
#endregion // Local Variables
#region Constructors
- internal Line (Document document)
+ internal Line (Document document, LineEnding ending)
{
this.document = document;
color = LineColor.Red;
parent = null;
text = null;
recalc = true;
- soft_break = false;
- alignment = HorizontalAlignment.Left;
+ alignment = document.alignment;
+
+ this.ending = ending;
}
- internal Line(Document document, int LineNo, string Text, Font font, SolidBrush color) : this (document) {
+ internal Line(Document document, int LineNo, string Text, Font font, SolidBrush color, LineEnding ending) : this (document, ending)
+ {
space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
text = new StringBuilder(Text, space);
line_no = LineNo;
+ this.ending = ending;
widths = new float[space + 1];
+
+
tags = new LineTag(this, 1);
tags.font = font;
- tags.color = color;
+ tags.color = color;
}
- internal Line(Document document, int LineNo, string Text, HorizontalAlignment align, Font font, SolidBrush color) : this(document) {
+ internal Line(Document document, int LineNo, string Text, HorizontalAlignment align, Font font, SolidBrush color, LineEnding ending) : this(document, ending)
+ {
space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
text = new StringBuilder(Text, space);
line_no = LineNo;
+ this.ending = ending;
alignment = align;
widths = new float[space + 1];
+
+
tags = new LineTag(this, 1);
tags.font = font;
tags.color = color;
}
- internal Line(Document document, int LineNo, string Text, LineTag tag) : this(document) {
+ internal Line(Document document, int LineNo, string Text, LineTag tag, LineEnding ending) : this(document, ending)
+ {
space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
text = new StringBuilder(Text, space);
+ this.ending = ending;
line_no = LineNo;
widths = new float[space + 1];
internal int Y {
get {
- int tm = document.owner.actual_border_style == BorderStyle.FixedSingle ? top_margin : 0;
if (!document.multiline)
- return tm;
- return tm + offset;
+ return document.top_margin;
+ return document.top_margin + offset;
}
}
get {
if (document.multiline)
return align_shift;
- return offset;
+ return offset + align_shift;
}
}
#endregion // Internal Properties
#region Internal Methods
+
+ // This doesn't do exactly what you would think, it just pulls of the \n part of the ending
+ internal string TextWithoutEnding ()
+ {
+ return text.ToString (0, text.Length - document.LineEndingLength (ending));
+ }
+
+ internal int TextLengthWithoutEnding ()
+ {
+ return text.Length - document.LineEndingLength (ending);
+ }
+
+ internal void DrawEnding (Graphics dc, float y)
+ {
+ if (document.multiline)
+ return;
+ LineTag last = tags;
+ while (last.next != null)
+ last = last.next;
+
+ string end_str = null;
+ switch (document.LineEndingLength (ending)) {
+ case 0:
+ return;
+ case 1:
+ end_str = "\u0013";
+ break;
+ case 2:
+ end_str = "\u0013\u0013";
+ break;
+ case 3:
+ end_str = "\u0013\u0013\u0013";
+ break;
+ }
+ dc.DrawString (end_str, last.font, last.color, X + widths [TextLengthWithoutEnding ()] - document.viewport_x,
+ y, Document.string_format);
+ }
+
+
// Make sure we always have enoughs space in text and widths
internal void Grow(int minimum) {
int length;
tag.shift = 0;
this.recalc = false;
- widths[0] = left_margin + indent;
+ widths[0] = document.left_margin + indent;
w = g.MeasureString(doc.password_char, tags.font, 10000, Document.string_format).Width;
int len;
SizeF size;
float w;
- int prev_height;
+ int prev_offset;
bool retval;
bool wrapped;
Line line;
pos = 0;
len = this.text.Length;
tag = this.tags;
- prev_height = this.height; // For drawing optimization calculations
+ prev_offset = this.offset; // For drawing optimization calculations
this.height = 0; // Reset line height
this.ascent = 0; // Reset the ascent for the line
tag.shift = 0;
- if (this.soft_break) {
- widths[0] = left_margin + hanging_indent;
+ if (ending == LineEnding.Wrap) {
+ widths[0] = document.left_margin + hanging_indent;
} else {
- widths[0] = left_margin + indent;
+ widths[0] = document.left_margin + indent;
}
this.recalc = false;
pos = wrap_pos;
len = text.Length;
- doc.Split(this, tag, pos, this.soft_break);
- this.soft_break = true;
+ doc.Split(this, tag, pos);
+ ending = LineEnding.Wrap;
len = this.text.Length;
retval = true;
// Make sure to set the last width of the line before wrapping
widths [pos + 1] = widths [pos] + w;
- doc.Split(this, tag, pos, this.soft_break);
- this.soft_break = true;
+ doc.Split(this, tag, pos);
+ ending = LineEnding.Wrap;
len = this.text.Length;
retval = true;
wrapped = true;
}
}
- // Contract all soft lines that follow back into our line
+ // Contract all wrapped lines that follow back into our line
if (!wrapped) {
pos++;
if (pos == len) {
line = doc.GetLine(this.line_no + 1);
- if ((line != null) && soft_break) {
+ if ((line != null) && (ending == LineEnding.Wrap || ending == LineEnding.None)) {
// Pull the two lines together
doc.Combine(this.line_no, this.line_no + 1);
len = this.text.Length;
tag.height = this.height;
}
- if (prev_height != this.height) {
+ if (prev_offset != offset) {
retval = true;
}
return retval;
public object Clone() {
Line clone;
- clone = new Line (document);
+ clone = new Line (document, ending);
clone.text = text;
internal object CloneLine() {
Line clone;
- clone = new Line (document);
+ clone = new Line (document, ending);
clone.text = text;
private int update_start = 1;
internal bool multiline;
+ internal HorizontalAlignment alignment;
internal bool wrap;
internal UndoManager undo;
internal TextBoxBase owner; // Who's owning us?
static internal int caret_width = 1;
static internal int caret_shift = 1;
+
+ internal int left_margin = 2; // A left margin for all lines
+ internal int top_margin = 2;
+ internal int right_margin = 2;
#endregion // Local Variables
#region Constructors
- internal Document(TextBoxBase owner) {
+ internal Document (TextBoxBase owner)
+ {
lines = 0;
this.owner = owner;
recalc_pending = false;
// Tree related stuff
- sentinel = new Line (this);
+ sentinel = new Line (this, LineEnding.None);
sentinel.color = LineColor.Black;
document = sentinel;
owner.HandleCreated += new EventHandler(owner_HandleCreated);
owner.VisibleChanged += new EventHandler(owner_VisibleChanged);
- Add(1, "", owner.Font, ThemeEngine.Current.ResPool.GetSolidBrush(owner.ForeColor));
- Line l = GetLine (1);
- l.soft_break = true;
+ Add (1, String.Empty, owner.Font, ThemeEngine.Current.ResPool.GetSolidBrush (owner.ForeColor), LineEnding.None);
undo = new UndoManager (this);
document_id = random.Next();
string_format.Trimming = StringTrimming.None;
- string_format.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;
+ string_format.FormatFlags = StringFormatFlags.DisplayFormatControl;
+
+ UpdateMargins ();
}
#endregion
#region Private Methods
+ internal void UpdateMargins ()
+ {
+ switch (owner.actual_border_style) {
+ case BorderStyle.None:
+ left_margin = 0;
+ top_margin = 0;
+ right_margin = 1;
+ break;
+ case BorderStyle.FixedSingle:
+ left_margin = 2;
+ top_margin = 2;
+ right_margin = 3;
+ break;
+ case BorderStyle.Fixed3D:
+ left_margin = 1;
+ top_margin = 1;
+ right_margin = 2;
+ break;
+ }
+ }
+
internal void SuspendRecalc ()
{
recalc_suspended++;
total = 1;
- Console.Write("Line {0} [# {1}], Y: {2}, soft: {3}, Text: '{4}'",
- line.line_no, line.GetHashCode(), line.Y, line.soft_break,
+ Console.Write("Line {0} [# {1}], Y: {2}, ending style: {3}, Text: '{4}'",
+ line.line_no, line.GetHashCode(), line.Y, line.ending,
line.text != null ? line.text.ToString() : "undefined");
if (line.left == sentinel) {
if (recalc_suspended > 0) {
recalc_start = Math.Min (recalc_start, line.line_no);
- recalc_end = Math.Max (recalc_end, line.line_no + line_count - 1);
+ recalc_end = Math.Max (recalc_end, line.line_no + line_count);
recalc_optimize = true;
recalc_pending = true;
return;
}
- if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no + line_count - 1, true)) {
+ int start_line_top = line.Y;
+
+ int end_line_bottom;
+ Line end_line;
+
+ end_line = GetLine (line.line_no + line_count);
+ if (end_line == null)
+ end_line = GetLine (lines);
+
+
+ end_line_bottom = end_line.Y + end_line.height;
+
+ if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no + line_count, true)) {
// Lineheight changed, invalidate the rest of the document
if ((line.Y - viewport_y) >=0 ) {
// We formatted something that's in view, only draw parts of the screen
-//blah Console.WriteLine("TextControl.cs(981) Invalidate called in UpdateView(line, line_count, pos)");
owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, owner.Height - line.Y - viewport_y));
} else {
// The tag was above the visible area, draw everything
-//blah Console.WriteLine("TextControl.cs(985) Invalidate called in UpdateView(line, line_count, pos)");
owner.Invalidate();
}
} else {
- Line end_line;
-
- end_line = GetLine(line.line_no + line_count -1);
- if (end_line == null) {
- end_line = line;
- }
+ int x = 0 - viewport_x;
+ int w = viewport_width;
+ int y = Math.Min (start_line_top - viewport_y, line.Y - viewport_y);
+ int h = Math.Max (end_line_bottom - y, end_line.Y + end_line.height - y);
-//blah Console.WriteLine("TextControl.cs(996) Invalidate called in UpdateView(line, line_count, pos)");
- owner.Invalidate(new Rectangle(0 - viewport_x, line.Y - viewport_y, (int)line.widths[line.text.Length], end_line.Y + end_line.height));
+ owner.Invalidate (new Rectangle (x, y, w, h));
}
}
#endregion // Private Methods
lines = 0;
// We always have a blank line
- Add(1, "", owner.Font, ThemeEngine.Current.ResPool.GetSolidBrush(owner.ForeColor));
- Line l = GetLine (1);
- l.soft_break = true;
+ Add (1, String.Empty, owner.Font, ThemeEngine.Current.ResPool.GetSolidBrush (owner.ForeColor), LineEnding.None);
this.RecalculateDocument(owner.CreateGraphicsInternal());
PositionCaret(0, 0);
caret.line = caret.tag.line;
caret.height = caret.tag.height;
- if (owner.Focused) {
+ if (owner.ShowSelection && (!selection_visible || owner.show_caret_w_selection)) {
XplatUI.CreateCaret (owner.Handle, caret_width, caret.height);
XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.X - viewport_x, caret.line.Y + caret.tag.shift - viewport_y + caret_shift);
}
DisplayCaret ();
}
- if (owner.IsHandleCreated && selection_visible) {
+ if (owner.IsHandleCreated && SelectionLength () > 0) {
InvalidateSelectionArea ();
}
}
}
}
- XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.X - viewport_x, caret.line.Y + caret.tag.shift - viewport_y + caret_shift);
-
- DisplayCaret ();
-
+ if (owner.Focused) {
+ XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.X - viewport_x, caret.line.Y + caret.tag.shift - viewport_y + caret_shift);
+ DisplayCaret ();
+ }
+
if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
}
goto case CaretDirection.CharForward;
case CaretDirection.CharForward: {
caret.pos++;
- if (caret.pos > caret.line.text.Length) {
+ if (caret.pos > caret.line.TextLengthWithoutEnding ()) {
if (!nowrap) {
// Go into next line
if (caret.line.line_no < this.lines) {
} else {
if (caret.line.line_no > 1 && !nowrap) {
caret.line = GetLine(caret.line.line_no - 1);
- caret.pos = caret.line.text.Length;
+ caret.pos = caret.line.TextLengthWithoutEnding ();
caret.tag = LineTag.FindTag(caret.line, caret.pos);
}
}
}
case CaretDirection.End: {
- if (caret.pos < caret.line.text.Length) {
- caret.pos = caret.line.text.Length;
+ if (caret.pos < caret.line.TextLengthWithoutEnding ()) {
+ caret.pos = caret.line.TextLengthWithoutEnding ();
caret.tag = LineTag.FindTag(caret.line, caret.pos);
UpdateCaret();
}
case CaretDirection.PgUp: {
- int new_y, y_offset;
-
- if (viewport_y == 0) {
-
- // This should probably be handled elsewhere
- if (!(owner is RichTextBox)) {
- // Page down doesn't do anything in a regular TextBox
- // if the bottom of the document
- // is already visible, the page and the caret stay still
- return;
- }
-
- // We're just placing the caret at the end of the document, no scrolling needed
+ if (viewport_y == 0 && owner.richtext) {
owner.vscroll.Value = 0;
Line line = GetLine (1);
PositionCaret (line, 0);
}
- y_offset = caret.line.Y - viewport_y;
- new_y = caret.line.Y - viewport_height;
+ int y_offset = caret.line.Y + caret.line.height - 1 - viewport_y;
+ int index;
+ LineTag top = FindCursor ((int) caret.line.widths [caret.pos],
+ viewport_y - viewport_height, out index);
+
+ owner.vscroll.Value = Math.Min (top.line.Y, owner.vscroll.Maximum - viewport_height);
+ PositionCaret ((int) caret.line.widths [caret.pos], y_offset + viewport_y);
- owner.vscroll.Value = Math.Max (new_y, 0);
- PositionCaret ((int)caret.line.widths[caret.pos], y_offset + viewport_y);
return;
}
case CaretDirection.PgDn: {
- int new_y, y_offset;
-
- if ((viewport_y + viewport_height) > document_y) {
-
- // This should probably be handled elsewhere
- if (!(owner is RichTextBox)) {
- // Page up doesn't do anything in a regular TextBox
- // if the bottom of the document
- // is already visible, the page and the caret stay still
- return;
- }
- // We're just placing the caret at the end of the document, no scrolling needed
+ if (viewport_y + viewport_height >= document_y && owner.richtext) {
owner.vscroll.Value = owner.vscroll.Maximum - viewport_height + 1;
Line line = GetLine (lines);
PositionCaret (line, line.Text.Length);
}
- y_offset = caret.line.Y - viewport_y;
- new_y = caret.line.Y + viewport_height;
-
- owner.vscroll.Value = Math.Min (new_y, owner.vscroll.Maximum - viewport_height + 1);
- PositionCaret ((int)caret.line.widths[caret.pos], y_offset + viewport_y);
+ int y_offset = caret.line.Y - viewport_y;
+ int index;
+ LineTag top = FindCursor ((int) caret.line.widths [caret.pos],
+ viewport_y + viewport_height, out index);
+
+ owner.vscroll.Value = Math.Min (top.line.Y, owner.vscroll.Maximum - viewport_height);
+ PositionCaret ((int) caret.line.widths [caret.pos], y_offset + viewport_y);
return;
}
case CaretDirection.CtrlEnd: {
caret.line = GetLine(lines);
- caret.pos = caret.line.text.Length;
+ caret.pos = caret.line.TextLengthWithoutEnding ();
caret.tag = LineTag.FindTag(caret.line, caret.pos);
UpdateCaret();
internal void DumpDoc ()
{
- Console.WriteLine ("<doc>");
- for (int i = 1; i < lines; i++) {
+ Console.WriteLine ("<doc lines='{0}'>", lines);
+ for (int i = 1; i <= lines ; i++) {
Line line = GetLine (i);
- Console.WriteLine ("<line no='{0}'>", line.line_no);
+ Console.WriteLine ("<line no='{0}' ending='{1}'>", line.line_no, line.ending);
LineTag tag = line.tags;
while (tag != null) {
- Console.Write ("\t<tag type='{0}' span='{1}->{2}'>", tag.GetType (), tag.start, tag.length);
+ Console.Write ("\t<tag type='{0}' span='{1}->{2}' font='{3}' color='{4}'>",
+ tag.GetType (), tag.start, tag.length, tag.font, tag.color.Color);
Console.Write (tag.Text ());
Console.WriteLine ("</tag>");
tag = tag.next;
/// We draw the single border ourself
///
if (owner.actual_border_style == BorderStyle.FixedSingle) {
- ControlPaint.DrawBorder (g, owner.Bounds, Color.Black, ButtonBorderStyle.Solid);
+ ControlPaint.DrawBorder (g, owner.ClientRectangle, Color.Black, ButtonBorderStyle.Solid);
}
/// Make sure that we aren't drawing one more line then we need to
}
tag.Draw (g, current_brush,
- line.widths [Math.Max (0, old_tag_pos - 1)] + line.X - viewport_x,
+ line.X - viewport_x,
line_y + tag.shift,
- old_tag_pos - 1, Math.Min (tag.length, tag_pos - old_tag_pos),
+ old_tag_pos - 1, Math.Max (tag.length, tag_pos - old_tag_pos),
text.ToString() );
}
tag = tag.next;
}
+
+ line.DrawEnding (g, line_y);
line_no++;
}
}
- private void InsertLineString (Line line, int pos, string s)
+ internal int GetLineEnding (string line, int start, out LineEnding ending)
{
- bool carriage_return = false;
+ int res;
- if (s.EndsWith ("\r")) {
- s = s.Substring (0, s.Length - 1);
- carriage_return = true;
+ res = line.IndexOf ('\r', start);
+ if (res != -1) {
+ if (res + 2 < line.Length && line [res + 1] == '\r' && line [res + 2] == '\n') {
+ ending = LineEnding.Soft;
+ return res;
+ }
+ if (res + 1 < line.Length && line [res + 1] == '\n') {
+ ending = LineEnding.Hard;
+ return res;
+ }
+ ending = LineEnding.Limp;
+ return res;
}
- InsertString (line, pos, s);
+ res = line.IndexOf ('\n', start);
+ if (res != -1) {
+ ending = LineEnding.Rich;
+ return res;
+ }
+
+ ending = LineEnding.Wrap;
+ return line.Length;
+ }
- if (carriage_return) {
- Line l = GetLine (line.line_no);
- l.carriage_return = true;
+ internal int LineEndingLength (LineEnding ending)
+ {
+ int res = 0;
+
+ switch (ending) {
+ case LineEnding.Limp:
+ case LineEnding.Rich:
+ res = 1;
+ break;
+ case LineEnding.Hard:
+ res = 2;
+ break;
+ case LineEnding.Soft:
+ res = 3;
+ break;
}
+
+ return res;
}
+ internal string LineEndingToString (LineEnding ending)
+ {
+ string res = String.Empty;
+ switch (ending) {
+ case LineEnding.Limp:
+ res = "\r";
+ break;
+ case LineEnding.Hard:
+ res = "\r\n";
+ break;
+ case LineEnding.Soft:
+ res = "\r\r\n";
+ break;
+ case LineEnding.Rich:
+ res = "\n";
+ break;
+ }
+ return res;
+ }
+
+
// Insert multi-line text at the given position; use formatting at insertion point for inserted text
internal void Insert(Line line, int pos, bool update_caret, string s) {
int break_index;
int base_line;
int old_line_count;
int count = 1;
+ LineEnding ending;
LineTag tag = LineTag.FindTag (line, pos);
SuspendRecalc ();
base_line = line.line_no;
old_line_count = lines;
- break_index = s.IndexOf ('\n');
+ break_index = GetLineEnding (s, 0, out ending);
// Bump the text at insertion point a line down if we're inserting more than one line
- if (break_index > -1) {
- Split(line, pos);
- line.soft_break = false;
+ if (break_index != s.Length) {
+ Split (line, pos);
+ line.ending = ending;
// Remainder of start line is now in base_line + 1
}
- if (break_index == -1)
- break_index = s.Length;
-
- InsertLineString (line, pos, s.Substring (0, break_index));
- break_index++;
-
+ InsertString (line, pos, s.Substring (0, break_index + LineEndingLength (ending)));
+
+ break_index += LineEndingLength (ending);
while (break_index < s.Length) {
- bool soft = false;
- int next_break = s.IndexOf ('\n', break_index);
- int adjusted_next_break;
- bool carriage_return = false;
-
- if (next_break == -1) {
- next_break = s.Length;
- soft = true;
- }
+ int next_break = GetLineEnding (s, break_index, out ending);
+ string line_text = s.Substring (break_index, next_break - break_index +
+ LineEndingLength (ending));
- adjusted_next_break = next_break;
- if (s [next_break - 1] == '\r') {
- adjusted_next_break--;
- carriage_return = true;
- }
-
- string line_text = s.Substring (break_index, adjusted_next_break - break_index);
- Add (base_line + count, line_text, line.alignment, tag.font, tag.color);
+ Add (base_line + count, line_text, line.alignment, tag.font, tag.color, ending);
- if (carriage_return) {
- Line last = GetLine (base_line + count);
- last.carriage_return = true;
-
- if (soft)
- last.soft_break = true;
- } else if (soft) {
- Line last = GetLine (base_line + count);
- last.soft_break = true;
- }
+ Line last = GetLine (base_line + count);
+ last.ending = ending;
count++;
- break_index = next_break + 1;
+ break_index = next_break + LineEndingLength (ending);
}
ResumeRecalc (true);
internal void InsertPicture (Line line, int pos, RTF.Picture picture)
{
- LineTag next_tag;
+ //LineTag next_tag;
LineTag tag;
int len;
tag = LineTag.FindTag (line, pos);
picture_tag.CopyFormattingFrom (tag);
- next_tag = tag.Break (pos + 1);
+ /*next_tag = */tag.Break (pos + 1);
picture_tag.previous = tag;
picture_tag.next = tag.next;
tag.next = picture_tag;
}
if (tag == null) {
- return;
+ goto Cleanup;
}
// Check if we're crossing tag boundaries
line.Streamline(lines);
}
- UpdateView(line, pos);
+ Cleanup:
+ if (pos >= line.TextLengthWithoutEnding ()) {
+ LineEnding ending = line.ending;
+ GetLineEnding (line.text.ToString (), 0, out ending);
+ if (ending != line.ending) {
+ line.ending = ending;
+
+ if (!multiline) {
+ UpdateView (line, lines, pos);
+ owner.Invalidate ();
+ return;
+ }
+ }
+ }
+ if (!multiline) {
+ UpdateView (line, lines, pos);
+ owner.Invalidate ();
+ } else
+ UpdateView(line, pos);
}
// Deletes a character at or after the given position (depending on forward); it will not delete past line limits
}
if (tag == null) {
- return;
+ goto Cleanup;
}
// tag.length--;
line.Streamline(lines);
}
- UpdateView(line, pos);
+ Cleanup:
+ if (pos >= line.TextLengthWithoutEnding ()) {
+ LineEnding ending = line.ending;
+ GetLineEnding (line.text.ToString (), 0, out ending);
+ if (ending != line.ending) {
+ line.ending = ending;
+
+ if (!multiline) {
+ UpdateView (line, lines, pos);
+ owner.Invalidate ();
+ return;
+ }
+ }
+ }
+ if (!multiline) {
+ UpdateView (line, lines, pos);
+ owner.Invalidate ();
+ } else
+ UpdateView(line, pos);
}
// Combine two lines
LineTag last;
int shift;
+ // strip the ending off of the first lines text
+ first.text.Length = first.text.Length - LineEndingLength (first.ending);
+
// Combine the two tag chains into one
last = first.tags;
// Maintain the line ending style
- first.soft_break = second.soft_break;
+ first.ending = second.ending;
while (last.next != null) {
last = last.next;
line = GetLine(LineNo);
tag = LineTag.FindTag(line, pos);
- Split(line, tag, pos, false);
+ Split(line, tag, pos);
}
internal void Split(Line line, int pos) {
LineTag tag;
tag = LineTag.FindTag(line, pos);
- Split(line, tag, pos, false);
+ Split(line, tag, pos);
}
///<summary>Split line at given tag and position into two lines</summary>
- ///<param name="soft">True if the split should be marked as 'soft', indicating that it can be contracted
///if more space becomes available on previous line</param>
- internal void Split(Line line, LineTag tag, int pos, bool soft) {
+ internal void Split(Line line, LineTag tag, int pos) {
LineTag new_tag;
Line new_line;
bool move_caret;
// cover the easy case first
if (pos == line.text.Length) {
- Add(line.line_no + 1, "", line.alignment, tag.font, tag.color);
+ Add (line.line_no + 1, String.Empty, line.alignment, tag.font, tag.color, line.ending);
- new_line = GetLine(line.line_no + 1);
-
- line.carriage_return = false;
- new_line.carriage_return = line.carriage_return;
- new_line.soft_break = soft;
+ new_line = GetLine (line.line_no + 1);
if (move_caret) {
caret.line = new_line;
}
// We need to move the rest of the text into the new line
- Add (line.line_no + 1, line.text.ToString (pos, line.text.Length - pos), line.alignment, tag.font, tag.color);
+ Add (line.line_no + 1, line.text.ToString (pos, line.text.Length - pos), line.alignment, tag.font, tag.color, line.ending);
// Now transfer our tags from this line to the next
new_line = GetLine(line.line_no + 1);
- line.carriage_return = false;
- new_line.carriage_return = line.carriage_return;
- new_line.soft_break = soft;
-
line.recalc = true;
new_line.recalc = true;
// Adds a line of text, with given font.
// Bumps any line at that line number that already exists down
- internal void Add(int LineNo, string Text, Font font, SolidBrush color) {
- Add(LineNo, Text, HorizontalAlignment.Left, font, color);
+ internal void Add (int LineNo, string Text, Font font, SolidBrush color, LineEnding ending)
+ {
+ Add (LineNo, Text, alignment, font, color, ending);
}
- internal void Add(int LineNo, string Text, HorizontalAlignment align, Font font, SolidBrush color) {
+ internal void Add (int LineNo, string Text, HorizontalAlignment align, Font font, SolidBrush color, LineEnding ending)
+ {
Line add;
Line line;
int line_no;
}
}
- add = new Line (this, LineNo, Text, align, font, color);
+ add = new Line (this, LineNo, Text, align, font, color, ending);
line = document;
while (line != sentinel) {
line1.line_no = line3.line_no;
line1.recalc = line3.recalc;
line1.right_indent = line3.right_indent;
- line1.soft_break = line3.soft_break;
+ line1.ending = line3.ending;
line1.space = line3.space;
line1.tags = line3.tags;
line1.text = line3.text;
}
}
- internal void SetSelectionStart(Line start, int start_pos) {
+ internal void SetSelectionStart(Line start, int start_pos, bool invalidate) {
// Invalidate from the previous to the new start pos
- Invalidate(selection_start.line, selection_start.pos, start, start_pos);
+ if (invalidate)
+ Invalidate(selection_start.line, selection_start.pos, start, start_pos);
selection_start.line = start;
selection_start.pos = start_pos;
SetSelectionVisible (false);
}
- Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
+ if (invalidate)
+ Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
}
- internal void SetSelectionStart(int character_index) {
+ internal void SetSelectionStart(int character_index, bool invalidate) {
Line line;
LineTag tag;
int pos;
}
CharIndexToLineTag(character_index, out line, out tag, out pos);
- SetSelectionStart(line, pos);
+ SetSelectionStart(line, pos, invalidate);
}
- internal void SetSelectionEnd(Line end, int end_pos) {
+ internal void SetSelectionEnd(Line end, int end_pos, bool invalidate) {
if (end == selection_end.line && end_pos == selection_start.pos) {
selection_anchor.line = selection_start.line;
if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
SetSelectionVisible (true);
- Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
+ if (invalidate)
+ Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
} else {
SetSelectionVisible (false);
// ?? Do I need to invalidate here, tests seem to work without it, but I don't think they should :-s
}
}
- internal void SetSelectionEnd(int character_index) {
+ internal void SetSelectionEnd(int character_index, bool invalidate) {
Line line;
LineTag tag;
int pos;
}
CharIndexToLineTag(character_index, out line, out tag, out pos);
- SetSelectionEnd(line, pos);
+ SetSelectionEnd(line, pos, invalidate);
}
internal void SetSelection(Line start, int start_pos) {
undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
+ InvalidateSelectionArea ();
+
// Delete first line
DeleteChars(selection_start.tag, selection_start.pos, selection_start.line.text.Length - selection_start.pos);
+ selection_start.line.recalc = true;
// Delete last line
DeleteChars(selection_end.line.tags, 0, selection_end.pos);
line = GetLine(i);
start = chars;
- chars += line.text.Length + (line.soft_break ? 0 : crlf_size);
+ chars += line.text.Length;
if (index <= chars) {
// we found the line
tag = line.tags;
while (tag != null) {
- if (index < (start + tag.start + tag.length)) {
+ if (index < (start + tag.start + tag.length - 1)) {
line_out = line;
tag_out = LineTag.GetFinalTag (tag);
pos = index - start;
// Count the lines in the middle
for (i = 1; i < line.line_no; i++) {
- length += GetLine(i).text.Length + (line.soft_break ? 0 : crlf_size);
+ length += GetLine(i).text.Length;
}
length += pos;
if (start < end) {
for (i = start; i < end; i++) {
Line line = GetLine (i);
- length += line.text.Length + (line.soft_break ? 0 : crlf_size);
+ length += line.text.Length + LineEndingLength (line.ending);
}
}
}
internal Line ParagraphStart(Line line) {
- while (line.soft_break) {
+ while (line.ending == LineEnding.Wrap) {
line = GetLine(line.line_no - 1);
}
return line;
internal Line ParagraphEnd(Line line) {
Line l;
- while (line.soft_break) {
+ while (line.ending == LineEnding.Wrap) {
l = GetLine(line.line_no + 1);
- if ((l == null) || (!l.soft_break)) {
+ if ((l == null) || (l.ending != LineEnding.Wrap)) {
break;
}
line = l;
line = GetLineByPixel(multiline ? y : x, false);
tag = line.tags;
+ /// Special case going leftwards of the first tag
+ if (x < tag.X) {
+ index = 0;
+ return LineTag.GetFinalTag (tag);
+ }
+
while (true) {
if (x >= tag.X && x < (tag.X+tag.width)) {
int end;
- end = tag.end;
+ end = tag.TextEnd;
for (int pos = tag.start - 1; pos < end; pos++) {
// When clicking on a character, we position the cursor to whatever edge
if (tag.next != null) {
tag = tag.next;
} else {
- index = line.text.Length;
+ index = line.TextLengthWithoutEnding ();
return LineTag.GetFinalTag (tag);
}
}
}
}
- /// <summary>Re-format areas of the document in specified font and color</summary>
- /// <param name="start_pos">1-based start position on start_line</param>
- /// <param name="end_pos">1-based end position on end_line </param>
- /// <param name="font">Font specifying attributes</param>
- /// <param name="color">Color (or NULL) to apply</param>
- /// <param name="apply">Attributes from font and color to apply</param>
- internal void FormatText(Line start_line, int start_pos, Line end_line, int end_pos, FontDefinition attributes) {
- Line l;
-
- // First, format the first line
- if (start_line != end_line) {
- // First line
- LineTag.FormatText(start_line, start_pos, start_line.text.Length - start_pos + 1, attributes);
-
- // Format last line
- LineTag.FormatText(end_line, 1, end_pos - 1, attributes);
-
- // Now all the lines inbetween
- for (int i = start_line.line_no + 1; i < end_line.line_no; i++) {
- l = GetLine(i);
- LineTag.FormatText(l, 1, l.text.Length, attributes);
- }
- } else {
- // Special case, single line
- LineTag.FormatText(start_line, start_pos, end_pos - start_pos, attributes);
- }
- }
-
internal void RecalculateAlignments ()
{
Line line;
line_no = 1;
+
+
while (line_no <= lines) {
line = GetLine(line_no);
line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
break;
case HorizontalAlignment.Right:
- line.align_shift = viewport_width - (int)line.widths[line.text.Length];
+ line.align_shift = viewport_width - (int)line.widths[line.text.Length] - right_margin;
break;
}
}
if (multiline)
offset += line.height;
else
- offset += (int) line.widths [line.text.Length] + 2;
+ offset += (int) line.widths [line.text.Length];
if (line_no > lines) {
break;
}
}
- internal static bool IsWordSeparator(char ch) {
- switch(ch) {
- case ' ':
- case '\t':
- case '(':
- case ')': {
- return true;
- }
-
- default: {
- return false;
- }
+ internal static bool IsWordSeparator (char ch)
+ {
+ switch (ch) {
+ case ' ':
+ case '\t':
+ case '(':
+ case ')':
+ case '\r':
+ case '\n':
+ return true;
+ default:
+ return false;
}
}
+
internal int FindWordSeparator(Line line, int pos, bool forward) {
int len;
Line prev_line;
prev_line = GetLine(line_no - 1);
- if (prev_line.soft_break) {
+ if (prev_line.ending == LineEnding.Wrap) {
if (IsWordSeparator(prev_line.text[prev_line.text.Length - 1])) {
word = true;
} else {
}
while (pos < line_len) {
+
if (word_option && (current == search_string.Length)) {
if (IsWordSeparator(line.text[pos])) {
if (!reverse) {
}
if (c == search_string[current]) {
+
if (current == 0) {
result.line = line;
result.pos = pos;
if (word_option) {
// Mark that we just saw a word boundary
- if (!line.soft_break) {
+ if (line.ending != LineEnding.Wrap || line.line_no == lines - 1) {
word = true;
}
return (int) (picture.Height + 0.5F);
}
- internal override void Draw (Graphics dc, Brush brush, float x, float y, int start, int end)
+ internal override void Draw (Graphics dc, Brush brush, float xoff, float y, int start, int end)
{
- picture.DrawImage (dc, x, y, false);
+ picture.DrawImage (dc, xoff + line.widths [start], y, false);
}
- internal override void Draw (Graphics dc, Brush brush, float x, float y, int start, int end, string text)
+ internal override void Draw (Graphics dc, Brush brush, float xoff, float y, int start, int end, string text)
{
- picture.DrawImage (dc, x, y, false);
+ picture.DrawImage (dc, xoff + + line.widths [start], y, false);
}
public override string Text ()
get { return start + length; }
}
+ public int TextEnd {
+ get { return start + TextLength; }
+ }
+
public float width {
get {
if (length == 0)
}
}
+ public int TextLength {
+ get {
+ int res = 0;
+ if (next != null)
+ res = next.start - start;
+ else
+ res = line.TextLengthWithoutEnding () - (start - 1);
+
+ return res > 0 ? res : 0;
+ }
+ }
+
public virtual bool IsTextTag {
get { return true; }
}
internal virtual SizeF SizeOfPosition (Graphics dc, int pos)
{
- return dc.MeasureString (line.text.ToString (pos, 1), font, 10000, Document.string_format);
+ if (pos >= line.TextLengthWithoutEnding () && line.document.multiline)
+ return SizeF.Empty;
+
+ string text = line.text.ToString (pos, 1);
+ switch ((int) text [0]) {
+ case '\t':
+ if (!line.document.multiline)
+ goto case 10;
+ SizeF res = dc.MeasureString (" ", font, 10000, Document.string_format);
+ res.Width *= 8.0F;
+ return res;
+ case 10:
+ case 13:
+ return dc.MeasureString ("\u0013", font, 10000, Document.string_format);
+ }
+
+ return dc.MeasureString (text, font, 10000, Document.string_format);
}
internal virtual int MaxHeight ()
dc.DrawString (line.text.ToString (start, end), font, brush, x, y, StringFormat.GenericTypographic);
}
- internal virtual void Draw (Graphics dc, Brush brush, float x, float y, int start, int end, string text)
+ internal virtual void Draw (Graphics dc, Brush brush, float xoff, float y, int start, int end, string text)
{
- dc.DrawString (text.Substring (start, end), font, brush, x, y, StringFormat.GenericTypographic);
+ while (start < end) {
+ int tab_index = text.IndexOf ("\t", start);
+ if (tab_index == -1)
+ tab_index = end;
+ dc.DrawString (text.Substring (start, tab_index - start), font, brush, xoff + line.widths [start],
+ y, StringFormat.GenericTypographic);
+
+ // non multilines get the unknown char
+ if (!line.document.multiline && tab_index != end)
+ dc.DrawString ("\u0013", font, brush, xoff + line.widths [tab_index], y, Document.string_format);
+
+ start = tab_index + 1;
+ }
}
///<summary>Break a tag into two with identical attributes; pos is 1-based; returns tag starting at >pos< or null if end-of-line</summary>
back_color = other.back_color;
}
- ///<summary>Create new font and brush from existing font and given new attributes. Returns true if fontheight changes</summary>
- internal static bool GenerateTextFormat(Font font_from, SolidBrush color_from, FontDefinition attributes, out Font new_font, out SolidBrush new_color) {
- float size;
- string face;
- FontStyle style;
- GraphicsUnit unit;
-
- if (attributes.font_obj == null) {
- size = font_from.SizeInPoints;
- unit = font_from.Unit;
- face = font_from.Name;
- style = font_from.Style;
-
- if (attributes.face != null) {
- face = attributes.face;
- }
-
- if (attributes.size != 0) {
- size = attributes.size;
- }
-
- style |= attributes.add_style;
- style &= ~attributes.remove_style;
-
- // Create new font
- new_font = new Font(face, size, style, unit);
- } else {
- new_font = attributes.font_obj;
- }
-
- // Create 'new' color brush
- if (attributes.color != Color.Empty) {
- new_color = new SolidBrush(attributes.color);
- } else {
- new_color = color_from;
- }
-
- if (new_font.Height == font_from.Height) {
- return false;
- }
- return true;
- }
-
/// <summary>Applies 'font' and 'brush' to characters starting at 'start' for 'length' chars;
/// Removes any previous tags overlapping the same area;
/// returns true if lineheight has changed</summary>
}
start_tag = FindTag (line, start);
-
tag = start_tag.Break (start);
while (tag != null && tag.end <= end) {
tag = tag.next;
}
- if (end != line.text.Length) {
- /// Now do the last tag
- end_tag = FindTag (line, end);
+ if (tag != null && tag.end == end)
+ return retval;
- if (end_tag != null) {
- end_tag.Break (end);
- SetFormat (end_tag, font, color, back_color, specified);
- }
+ /// Now do the last tag
+ end_tag = FindTag (line, end);
+
+ if (end_tag != null) {
+ end_tag.Break (end);
+ SetFormat (end_tag, font, color, back_color, specified);
}
return retval;
// Console.WriteLine ("setting format: {0} {1} new color {2}", color.Color, specified, tag.color.Color);
}
- /// <summary>Applies font attributes specified to characters starting at 'start' for 'length' chars;
- /// Breaks tags at start and end point, keeping middle tags with altered attributes.
- /// Returns true if lineheight has changed</summary>
- /// <param name="start">1-based character position on line</param>
- internal static bool FormatText(Line line, int start, int length, FontDefinition attributes) {
- LineTag tag;
- LineTag start_tag;
- LineTag end_tag;
- bool retval = false; // Assume line-height doesn't change
-
- line.recalc = true; // This forces recalculation of the line in RecalculateDocument
-
- // A little sanity, not sure if it's needed, might be able to remove for speed
- if (length > line.text.Length) {
- length = line.text.Length;
- }
-
- tag = line.tags;
-
- // Common special case
- if ((start == 1) && (length == tag.length)) {
- tag.ascent = 0;
- GenerateTextFormat(tag.font, tag.color, attributes, out tag.font, out tag.color);
- return retval;
- }
-
- start_tag = FindTag(line, start);
-
- if (start_tag == null) {
- if (length == 0) {
- // We are 'starting' after all valid tags; create a new tag with the right attributes
- start_tag = FindTag(line, line.text.Length - 1);
- start_tag.next = new LineTag(line, line.text.Length + 1);
- start_tag.next.CopyFormattingFrom (start_tag);
- start_tag.next.previous = start_tag;
- start_tag = start_tag.next;
- } else {
- throw new Exception(String.Format("Could not find start_tag in document at line {0} position {1}", line.line_no, start));
- }
- } else {
- start_tag = start_tag.Break(start);
- }
-
- end_tag = FindTag(line, start + length);
- if (end_tag != null) {
- end_tag = end_tag.Break(start + length);
- }
-
- // start_tag or end_tag might be null; we're cool with that
- // we now walk from start_tag to end_tag, applying new attributes
- tag = start_tag;
- while ((tag != null) && tag != end_tag) {
- if (LineTag.GenerateTextFormat(tag.font, tag.color, attributes, out tag.font, out tag.color)) {
- retval = true;
- }
- tag = tag.next;
- }
- return retval;
- }
-
-
/// <summary>Finds the tag that describes the character at position 'pos' on 'line'</summary>
internal static LineTag FindTag(Line line, int pos) {
LineTag tag = line.tags;
private Stack undo_actions;
private Stack redo_actions;
- private int caret_line;
- private int caret_pos;
+ //private int caret_line;
+ //private int caret_pos;
// When performing an action, we lock the queue, so that the action can't be undone
private bool locked;
int end;
int tag_start;
- line = new Line (start_line.document);
+ line = new Line (start_line.document, start_line.ending);
ret = line;
for (int i = start_line.line_no; i <= end_line.line_no; i++) {
if (start_line.line_no == i) {
start = start_pos;
} else {
- start = 1;
+ start = 0;
}
if (end_line.line_no == i) {
// Text for the tag
line.text = new StringBuilder (current.text.ToString (start, end - start));
-
+
// Copy tags from start to start+length onto new line
current_tag = current.FindTag (start);
- while ((current_tag != null) && (current_tag.start < end)) {
+ while ((current_tag != null) && (current_tag.start <= end)) {
if ((current_tag.start <= start) && (start < (current_tag.start + current_tag.length))) {
// start tag is within this tag
tag_start = start;
}
if ((i + 1) <= end_line.line_no) {
- line.soft_break = current.soft_break;
+ line.ending = current.ending;
// Chain them (we use right/left as next/previous)
- line.right = new Line (start_line.document);
+ line.right = new Line (start_line.document, start_line.ending);
line.right.left = line;
line = line.right;
}
document.Combine(line.line_no, line.line_no + 1);
if (select) {
- document.SetSelectionStart (line, pos);
- document.SetSelectionEnd (line, pos + insert.text.Length);
+ document.SetSelectionStart (line, pos, false);
+ document.SetSelectionEnd (line, pos + insert.text.Length, false);
}
document.UpdateView(line, pos);
current = insert;
while (current != null) {
+
if (current == insert) {
// Inserting the first line we split the line (and make space)
- document.Split(line, pos);
+ document.Split(line.line_no, pos);
//Insert our tags at the end of the line
tag = line.tags;
- if (tag != null) {
+
+ if (tag != null && tag.length != 0) {
while (tag.next != null) {
tag = tag.next;
}
line.tags.previous = null;
tag = line.tags;
}
+
+ line.ending = current.ending;
} else {
document.Split(line.line_no, 0);
offset = 0;
line.tags = current.tags;
line.tags.previous = null;
+ line.ending = current.ending;
tag = line.tags;
}
+
// Adjust start locations and line pointers
while (tag != null) {
- tag.start += offset;
+ tag.start += offset - 1;
tag.line = line;
tag = tag.next;
}