// the time to write it all yet.
// Stuff missing (in no particular order):
// - Align text after RecalculateLine
-// - Implement tag types for hotlinks, images, etc.
+// - Implement tag types for hotlinks, etc.
// - Implement CaretPgUp/PgDown
// NOTE:
using System.Drawing;
using System.Drawing.Text;
using System.Text;
+using RTF=System.Windows.Forms.RTF;
namespace System.Windows.Forms {
internal enum LineColor {
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 Document document;
+
// Stuff that matters for our line
internal StringBuilder text; // Characters for the line
internal float[] widths; // Width of each character; always one larger than text.Length
internal int space; // Number of elements in text and widths
internal int line_no; // Line number
internal LineTag tags; // Tags describing the text
- internal int Y; // Baseline
+ internal int offset; // Baseline can be on the X or Y axis depending if we are in multiline mode or not
internal int height; // Height of the line (height of tallest tag)
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 Line right; // Line with higher line number
internal LineColor color; // We're doing a black/red tree. this is the node color
internal int DEFAULT_TEXT_LEN; //
- internal static StringFormat string_format; // For calculating widths/heights
internal bool recalc; // Line changed
#endregion // Local Variables
#region Constructors
- internal Line() {
+ internal Line (Document document, LineEnding ending)
+ {
+ this.document = document;
color = LineColor.Red;
left = null;
right = null;
parent = null;
text = null;
recalc = true;
- soft_break = false;
- alignment = HorizontalAlignment.Left;
+ alignment = document.alignment;
- if (string_format == null) {
- string_format = new StringFormat(StringFormat.GenericTypographic);
- string_format.Trimming = StringTrimming.None;
- string_format.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;
- }
+ this.ending = ending;
}
- internal Line(int LineNo, string Text, Font font, SolidBrush color) : this() {
+ 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, text.Length);
+
+
+ tags = new LineTag(this, 1);
tags.font = font;
- tags.color = color;
+ tags.color = color;
}
- internal Line(int LineNo, string Text, HorizontalAlignment align, Font font, SolidBrush color) : this() {
+ 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, text.Length);
+
+
+ tags = new LineTag(this, 1);
tags.font = font;
tags.color = color;
}
- internal Line(int LineNo, string Text, LineTag tag) : this() {
+ 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];
#endregion // Constructors
#region Internal Properties
+
+ internal int Y {
+ get {
+ if (!document.multiline)
+ return document.top_margin;
+ return document.top_margin + offset;
+ }
+ }
+
+ internal int X {
+ get {
+ if (document.multiline)
+ return align_shift;
+ return offset + align_shift;
+ }
+ }
+
+ internal int Width {
+ get {
+ int res = (int) widths [text.Length];
+ if (!document.multiline) {
+
+ }
+ return res;
+ }
+ }
+
internal int Indent {
get {
return indent;
#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;
current = this.tags;
next = current.next;
+ //
// Catch what the loop below wont; eliminate 0 length
// tags, but only if there are other tags after us
- while ((current.length == 0) && (next != null)) {
+ // We only eliminate text tags if there is another text tag
+ // after it. Otherwise we wind up trying to type on picture tags
+ //
+ while ((current.length == 0) && (next != null) && (next.IsTextTag)) {
tags = next;
tags.previous = null;
current = next;
next = current.next;
}
+
if (next == null) {
return;
while (next != null) {
// Take out 0 length tags unless it's the last tag in the document
- if (next.length == 0) {
+ if (current.IsTextTag && next.length == 0 && next.IsTextTag) {
if ((next.next != null) || (line_no != lines)) {
current.next = next.next;
if (current.next != null) {
tag.shift = 0;
this.recalc = false;
- widths[0] = indent;
- tag.X = indent;
+ widths[0] = document.left_margin + indent;
- w = g.MeasureString(doc.password_char, tags.font, 10000, string_format).Width;
+ w = g.MeasureString(doc.password_char, tags.font, 10000, Document.string_format).Width;
if (this.height != (int)tag.font.Height) {
ret = true;
int len;
SizeF size;
float w;
- int prev_height;
+ int prev_offset;
bool retval;
bool wrapped;
Line line;
int wrap_pos;
- float wrap_width;
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] = hanging_indent;
+ if (ending == LineEnding.Wrap) {
+ widths[0] = document.left_margin + hanging_indent;
} else {
- widths[0] = indent;
+ widths[0] = document.left_margin + indent;
}
this.recalc = false;
wrapped = false;
wrap_pos = 0;
- wrap_width = 0;
while (pos < len) {
- size = g.MeasureString(this.text.ToString(pos, 1), tag.font, 10000, string_format);
while (tag.length == 0) { // We should always have tags after a tag.length==0 unless len==0
tag.ascent = 0;
- if (tag.previous != null) {
- tag.X = tag.previous.X;
- } else {
- tag.X = (int)widths[pos];
- }
- tag = tag.next;
tag.shift = 0;
+ tag = tag.next;
}
+ size = tag.SizeOfPosition (g, pos);
w = size.Width;
if (Char.IsWhiteSpace(text[pos])) {
wrap_pos = pos + 1;
- wrap_width = tag.width + w;
}
if (doc.wrap) {
widths [pos + 1] = widths [pos] + w;
pos = wrap_pos;
- doc.Split(this, tag, pos, this.soft_break);
- this.soft_break = true;
+ len = text.Length;
+ doc.Split(this, tag, pos);
+ ending = LineEnding.Wrap;
len = this.text.Length;
+
retval = true;
wrapped = true;
} else if (pos > 1 && (widths[pos] + w) > (doc.viewport_width - this.right_indent)) {
// 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;
if (pos == (tag.start-1 + tag.length)) {
// We just found the end of our current tag
- tag.height = (int)tag.font.Height;
+ tag.height = tag.MaxHeight ();
// Check if we're the tallest on the line (so far)
if (tag.height > this.height) {
tag.shift = this.ascent - tag.ascent;
}
- // Update our horizontal starting pixel position
- if (tag.previous == null) {
- tag.X = (int)widths[0];
- } else {
- tag.X = tag.previous.X + (int)tag.previous.width;
- }
-
tag = tag.next;
if (tag != null) {
tag.shift = 0;
wrap_pos = pos;
- wrap_width = tag.width;
}
}
}
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();
+ clone = new Line (document, ending);
clone.text = text;
internal object CloneLine() {
Line clone;
- clone = new Line();
+ clone = new Line (document, ending);
clone.text = text;
private bool calc_pass;
private int char_count;
- private bool no_recalc;
+ // For calculating widths/heights
+ public static readonly StringFormat string_format = new StringFormat (StringFormat.GenericTypographic);
+
+ private int recalc_suspended;
private bool recalc_pending;
- private int recalc_start;
+ private int recalc_start = 1; // This starts at one, since lines are 1 based
private int recalc_end;
private bool recalc_optimize;
+ private int update_suspended;
+ private bool update_pending;
+ private int update_start = 1;
+
internal bool multiline;
+ internal HorizontalAlignment alignment;
internal bool wrap;
- internal UndoClass undo;
+ internal UndoManager undo;
internal Marker caret;
internal Marker selection_start;
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;
multiline = true;
password_char = "";
calc_pass = false;
- no_recalc = false;
recalc_pending = false;
// Tree related stuff
- sentinel = new Line();
+ 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 UndoClass(this);
+ undo = new UndoManager (this);
selection_visible = false;
selection_start.line = this.document;
// Default selection is empty
document_id = random.Next();
+
+ string_format.Trimming = StringTrimming.None;
+ string_format.FormatFlags = StringFormatFlags.DisplayFormatControl;
+
+ UpdateMargins ();
}
#endregion
internal Point Caret {
get {
- return new Point((int)caret.tag.line.widths[caret.pos] + caret.line.align_shift, caret.line.Y);
+ return new Point((int)caret.tag.line.widths[caret.pos] + caret.line.X, caret.line.Y);
}
}
set {
password_char = value;
+ PasswordCache.Length = 0;
if ((password_char.Length != 0) && (password_char[0] != '\0')) {
- char ch;
-
calc_pass = true;
- ch = value[0];
- password_cache = new StringBuilder(1024);
- for (int i = 0; i < 1024; i++) {
- password_cache.Append(ch);
- }
} else {
calc_pass = false;
- password_cache = null;
}
}
}
+ private StringBuilder PasswordCache {
+ get {
+ if (password_cache == null)
+ password_cache = new StringBuilder();
+ return password_cache;
+ }
+ }
+
internal int ViewPortX {
get {
return viewport_x;
}
}
- ///<summary>Setting NoRecalc to true will prevent the document from being recalculated.
- ///This ensures that coordinates of added text are predictable after adding the text even with wrapped view</summary>
- internal bool NoRecalc {
- get {
- return no_recalc;
- }
-
- set {
- no_recalc = value;
- if (!no_recalc && recalc_pending) {
- RecalculateDocument(owner.CreateGraphicsInternal(), recalc_start, recalc_end, recalc_optimize);
- recalc_pending = false;
- }
- }
- }
-
internal int ViewPortY {
get {
return viewport_y;
#endregion // Internal Properties
#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++;
+ }
+
+ internal void ResumeRecalc (bool immediate_update)
+ {
+ if (recalc_suspended > 0)
+ recalc_suspended--;
+
+ if (immediate_update && recalc_suspended == 0 && recalc_pending) {
+ RecalculateDocument (owner.CreateGraphicsInternal(), recalc_start, recalc_end, recalc_optimize);
+ recalc_pending = false;
+ }
+ }
+
+ internal void SuspendUpdate ()
+ {
+ update_suspended++;
+ }
+
+ internal void ResumeUpdate (bool immediate_update)
+ {
+ if (update_suspended > 0)
+ update_suspended--;
+
+ if (immediate_update && update_suspended == 0 && update_pending) {
+ UpdateView (GetLine (update_start), 0);
+ update_pending = false;
+ }
+ }
+
// For debugging
internal int DumpTree(Line line, bool with_tags) {
int total;
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) {
length = 0;
Console.Write(" Tags: ");
while (tag != null) {
- Console.Write("{0} <{1}>-<{2}>", count++, tag.start, tag.start + tag.length
+ Console.Write("{0} <{1}>-<{2}>", count++, tag.start, tag.end
/*line.text.ToString (tag.start - 1, tag.length)*/);
length += tag.length;
return;
}
- if (no_recalc) {
- recalc_start = line.line_no;
- recalc_end = line.line_no;
- recalc_optimize = true;
- recalc_pending = true;
+ if (update_suspended > 0) {
+ update_start = Math.Min (update_start, line.line_no);
+ // update_end = Math.Max (update_end, line.line_no);
+ // recalc_optimize = true;
+ update_pending = true;
return;
}
} else {
switch(line.alignment) {
case HorizontalAlignment.Left: {
- owner.Invalidate(new Rectangle((int)line.widths[pos] - viewport_x - 1, line.Y - viewport_y, viewport_width, line.height + 1));
+ owner.Invalidate(new Rectangle(line.X + (int)line.widths[pos] - viewport_x - 1, line.Y - viewport_y, viewport_width, line.height + 1));
break;
}
case HorizontalAlignment.Center: {
- owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, line.height + 1));
+ owner.Invalidate(new Rectangle(line.X, line.Y - viewport_y, viewport_width, line.height + 1));
break;
}
case HorizontalAlignment.Right: {
- owner.Invalidate(new Rectangle(0, line.Y - viewport_y, (int)line.widths[pos + 1] - viewport_x + line.align_shift, line.height + 1));
+ owner.Invalidate(new Rectangle(line.X, line.Y - viewport_y, (int)line.widths[pos + 1] - viewport_x + line.X, line.height + 1));
break;
}
}
return;
}
- if (no_recalc) {
- recalc_start = line.line_no;
- recalc_end = line.line_no + line_count - 1;
+ if (recalc_suspended > 0) {
+ recalc_start = Math.Min (recalc_start, line.line_no);
+ 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;
+ 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);
- end_line = GetLine(line.line_no + line_count -1);
- if (end_line == null) {
- end_line = line;
- }
-
-//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);
}
internal void PositionCaret(Line line, int pos) {
- if (owner.IsHandleCreated) {
- undo.RecordCursor();
- }
+ caret.tag = line.FindTag (pos);
+
+ MoveCaretToTextTag ();
- caret.tag = line.FindTag(pos);
caret.line = line;
caret.pos = pos;
- caret.height = caret.tag.height;
if (owner.IsHandleCreated) {
if (owner.Focused) {
- XplatUI.CreateCaret (owner.Handle, caret_width, caret.height);
- XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.align_shift - viewport_x, caret.line.Y + caret.tag.shift - viewport_y + caret_shift);
+ if (caret.height != caret.tag.height)
+ 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);
}
if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
}
+ // We set this at the end because we use the heights to determine whether or
+ // not we need to recreate the caret
+ caret.height = caret.tag.height;
}
return;
}
- undo.RecordCursor();
-
caret.tag = FindCursor(x, y, out caret.pos);
+
+ MoveCaretToTextTag ();
+
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.align_shift - viewport_x, caret.line.Y + caret.tag.shift - viewport_y + caret_shift);
+ 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);
}
if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
internal void CaretHasFocus() {
if ((caret.tag != null) && owner.IsHandleCreated) {
XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
- XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.align_shift - viewport_x, caret.line.Y + caret.tag.shift - viewport_y + caret_shift);
+ 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 ();
}
}
return;
}
- undo.RecordCursor();
+ caret.tag = LineTag.FindTag (caret.line, caret.pos);
+
+ MoveCaretToTextTag ();
- caret.tag = LineTag.FindTag(caret.line, caret.pos);
caret.height = caret.tag.height;
if (owner.Focused) {
XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
- XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.align_shift - viewport_x, caret.line.Y + caret.tag.shift - viewport_y + caret_shift);
+ 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 ();
}
return;
}
- undo.RecordCursor();
+ MoveCaretToTextTag ();
if (caret.tag.height != caret.height) {
caret.height = caret.tag.height;
}
}
- XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.align_shift - 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);
}
return;
}
- if (owner.Focused && (!selection_visible || owner.show_caret_w_selection)) {
+ if (owner.ShowSelection && (!selection_visible || owner.show_caret_w_selection)) {
XplatUI.CaretVisible(owner.Handle, true);
}
}
}
}
+
+ internal void MoveCaretToTextTag ()
+ {
+ if (caret.tag == null || caret.tag.IsTextTag)
+ return;
+
+
+
+ if (caret.pos < caret.tag.start) {
+ caret.tag = caret.tag.previous;
+ } else {
+ caret.tag = caret.tag.next;
+ }
+ }
+
internal void MoveCaret(CaretDirection direction) {
// FIXME should we use IsWordSeparator to detect whitespace, instead
// of looking for actual spaces in the Word move cases?
goto case CaretDirection.CharForward;
case CaretDirection.CharForward: {
caret.pos++;
- if (caret.pos > caret.line.text.Length) {
- if (multiline && !nowrap) {
+ if (caret.pos > caret.line.TextLengthWithoutEnding ()) {
+ if (!nowrap) {
// Go into next line
if (caret.line.line_no < this.lines) {
caret.line = GetLine(caret.line.line_no+1);
case CaretDirection.CharBack: {
if (caret.pos > 0) {
// caret.pos--; // folded into the if below
+
if (--caret.pos > 0) {
if (caret.tag.start > caret.pos) {
caret.tag = caret.tag.previous;
} 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 lines='{0}'>", lines);
+ for (int i = 1; i <= lines ; i++) {
+ Line line = GetLine (i);
+ 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}' 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;
+ }
+ Console.WriteLine ("</line>");
+ }
+ Console.WriteLine ("</doc>");
+ }
+
internal void Draw (Graphics g, Rectangle clip)
{
Line line; // Current line being drawn
Brush tag_brush;
Brush current_brush;
Brush disabled_brush;
+ Brush readonly_brush;
Brush hilight;
Brush hilight_text;
// First, figure out from what line to what line we need to draw
- start = GetLineByPixel(clip.Top + viewport_y, false).line_no;
- end = GetLineByPixel(clip.Bottom + viewport_y, false).line_no;
+
+ if (multiline) {
+ start = GetLineByPixel(clip.Top + viewport_y, false).line_no;
+ end = GetLineByPixel(clip.Bottom + viewport_y, false).line_no;
+ } else {
+ start = GetLineByPixel(clip.Left + viewport_x, false).line_no;
+ end = GetLineByPixel(clip.Right + viewport_x, false).line_no;
+ }
+
+ ///
+ /// We draw the single border ourself
+ ///
+ if (owner.actual_border_style == BorderStyle.FixedSingle) {
+ ControlPaint.DrawBorder (g, owner.ClientRectangle, Color.Black, ButtonBorderStyle.Solid);
+ }
/// Make sure that we aren't drawing one more line then we need to
line = GetLine (end - 1);
#endif
disabled_brush = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorGrayText);
+ readonly_brush = ThemeEngine.Current.ResPool.GetSolidBrush (ThemeEngine.Current.ColorControlText);
hilight = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorHighlight);
hilight_text = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorHighlightText);
+ // Non multiline selection can be handled outside of the loop
+ if (!multiline && selection_visible && owner.ShowSelection) {
+ g.FillRectangle (hilight,
+ selection_start.line.widths [selection_start.pos] +
+ selection_start.line.X - viewport_x,
+ selection_start.line.Y,
+ (selection_end.line.X + selection_end.line.widths [selection_end.pos]) -
+ (selection_start.line.X + selection_start.line.widths [selection_start.pos]),
+ selection_start.line.height);
+ }
+
while (line_no <= end) {
line = GetLine (line_no);
-
+ float line_y = line.Y - viewport_y;
+
tag = line.tags;
if (!calc_pass) {
text = line.text;
} else {
- // This fails if there's a password > 1024 chars...
- text = this.password_cache;
+ if (PasswordCache.Length < line.text.Length)
+ PasswordCache.Append(Char.Parse(password_char), line.text.Length - PasswordCache.Length);
+ else if (PasswordCache.Length > line.text.Length)
+ PasswordCache.Remove(line.text.Length, PasswordCache.Length - line.text.Length);
+ text = PasswordCache;
}
int line_selection_start = text.Length + 1;
// There isn't really selection
line_selection_start = text.Length + 1;
line_selection_end = line_selection_start;
- } else {
- // lets draw some selection baby!!
-
+ } else if (multiline) {
+ // lets draw some selection baby!! (non multiline selection is drawn outside the loop)
g.FillRectangle (hilight,
- line.widths [line_selection_start - 1] + line.align_shift - viewport_x,
- line.Y - viewport_y,
- line.widths [line_selection_end - 1] - line.widths [line_selection_start - 1],
+ line.widths [line_selection_start - 1] + line.X - viewport_x,
+ line_y, line.widths [line_selection_end - 1] - line.widths [line_selection_start - 1],
line.height);
}
}
current_brush = line.tags.color;
while (tag != null) {
-
// Skip empty tags
if (tag.length == 0) {
continue;
}
- if (((tag.X + tag.width) < (clip.Left - viewport_x)) || (tag.X > (clip.Right - viewport_x))) {
+ if (((tag.X + tag.width) < (clip.Left - viewport_x)) && (tag.X > (clip.Right - viewport_x))) {
tag = tag.next;
continue;
}
if (tag.back_color != null) {
- g.FillRectangle (tag.back_color, tag.X + line.align_shift - viewport_x,
- line.Y + tag.shift - viewport_y, tag.width, line.height);
+ g.FillRectangle (tag.back_color, tag.X + line.X - viewport_x,
+ line_y + tag.shift, tag.width, line.height);
}
tag_brush = tag.color;
current_brush = tag_brush;
-
+
if (!owner.is_enabled) {
Color a = ((SolidBrush) tag.color).Color;
Color b = ThemeEngine.Current.ColorWindowText;
if ((a.R == b.R) && (a.G == b.G) && (a.B == b.B)) {
tag_brush = disabled_brush;
}
+ } else if (owner.read_only && !owner.backcolor_set) {
+ tag_brush = readonly_brush;
}
int tag_pos = tag.start;
if (tag_pos >= line_selection_start && tag_pos < line_selection_end) {
current_brush = hilight_text;
- tag_pos = Math.Min (tag.start + tag.length, line_selection_end);
+ tag_pos = Math.Min (tag.end, line_selection_end);
} else if (tag_pos < line_selection_start) {
- current_brush = tag.color;
- tag_pos = Math.Min (tag.start + tag.length, line_selection_start);
+ current_brush = tag_brush;
+ tag_pos = Math.Min (tag.end, line_selection_start);
} else {
- current_brush = tag.color;
- tag_pos = tag.start + tag.length;
+ current_brush = tag_brush;
+ tag_pos = tag.end;
}
-
- g.DrawString (text.ToString (old_tag_pos - 1,
- Math.Min (tag.length, tag_pos - old_tag_pos)),
- tag.font, current_brush,
- line.widths [old_tag_pos - 1] + line.align_shift - viewport_x,
- line.Y + tag.shift - viewport_y,
- StringFormat.GenericTypographic);
+ tag.Draw (g, current_brush,
+ line.X - viewport_x,
+ line_y + tag.shift,
+ 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)
+ {
+ int res;
+
+ 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;
+ }
+
+ res = line.IndexOf ('\n', start);
+ if (res != -1) {
+ ending = LineEnding.Rich;
+ return res;
+ }
+
+ ending = LineEnding.Wrap;
+ return line.Length;
+ }
+
+ internal int LineEndingLength (LineEnding ending)
{
- bool carriage_return = false;
+ int res = 0;
- if (s.EndsWith ("\r")) {
- s = s.Substring (0, s.Length - 1);
- carriage_return = true;
+ switch (ending) {
+ case LineEnding.Limp:
+ case LineEnding.Rich:
+ res = 1;
+ break;
+ case LineEnding.Hard:
+ res = 2;
+ break;
+ case LineEnding.Soft:
+ res = 3;
+ break;
}
- InsertString (line, pos, s);
+ return res;
+ }
- if (carriage_return) {
- Line l = GetLine (line.line_no);
- l.carriage_return = true;
+ 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);
- NoRecalc = true;
- undo.BeginCompoundAction ();
+ 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;
+ int next_break = GetLineEnding (s, break_index, out ending);
+ string line_text = s.Substring (break_index, next_break - break_index +
+ LineEndingLength (ending));
- if (next_break == -1) {
- next_break = s.Length;
- soft = true;
- }
-
- 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);
}
- NoRecalc = false;
+ ResumeRecalc (true);
UpdateView(line, lines - old_line_count + 1, pos);
PositionCaret(l, l.text.Length);
DisplayCaret ();
}
-
- undo.EndCompoundAction ();
}
// Inserts a character at the given position
line = tag.line;
line.text.Insert(pos, s);
- tag.length += len;
- // TODO: sometimes getting a null tag here when pasting ???
tag = tag.next;
while (tag != null) {
tag.start += len;
// Inserts a string at the caret position
internal void InsertStringAtCaret(string s, bool move_caret) {
- LineTag tag;
- int len;
-
- len = s.Length;
-
- CharCount += len;
- caret.line.text.Insert(caret.pos, s);
- caret.tag.length += len;
-
- if (caret.tag.next != null) {
- tag = caret.tag.next;
- while (tag != null) {
- tag.start += len;
- tag = tag.next;
- }
- }
- caret.line.Grow(len);
- caret.line.recalc = true;
+ InsertString (caret.tag, caret.pos, s);
UpdateView(caret.line, caret.pos);
if (move_caret) {
- caret.pos += len;
+ caret.pos += s.Length;
UpdateCaret();
}
}
line = tag.line;
line.text.Insert(pos, ch);
- tag.length++;
tag = tag.next;
while (tag != null) {
line.Grow(1);
line.recalc = true;
+ undo.RecordTyping (line, pos, ch);
UpdateView(line, pos);
}
// Inserts a character at the current caret position
internal void InsertCharAtCaret(char ch, bool move_caret) {
+ /*
LineTag tag;
CharCount++;
}
caret.line.Grow(1);
caret.line.recalc = true;
+ */
+ InsertChar (caret.tag, caret.pos, ch);
UpdateView(caret.line, caret.pos);
if (move_caret) {
UpdateCaret();
SetSelectionToCaret(true);
}
+
+ }
+
+ internal void InsertPicture (Line line, int pos, RTF.Picture picture)
+ {
+ //LineTag next_tag;
+ LineTag tag;
+ int len;
+
+ len = 1;
+
+ // Just a place holder basically
+ line.text.Insert (pos, "I");
+
+ PictureTag picture_tag = new PictureTag (line, pos + 1, picture);
+
+ tag = LineTag.FindTag (line, pos);
+ picture_tag.CopyFormattingFrom (tag);
+ /*next_tag = */tag.Break (pos + 1);
+ picture_tag.previous = tag;
+ picture_tag.next = tag.next;
+ tag.next = picture_tag;
+
+ //
+ // Picture tags need to be surrounded by text tags
+ //
+ if (picture_tag.next == null) {
+ picture_tag.next = new LineTag (line, pos + 1);
+ picture_tag.next.CopyFormattingFrom (tag);
+ picture_tag.next.previous = picture_tag;
+ }
+
+ tag = picture_tag.next;
+ while (tag != null) {
+ tag.start += len;
+ tag = tag.next;
+ }
+
+ line.Grow (len);
+ line.recalc = true;
+
+ UpdateView (line, pos);
}
internal void DeleteMultiline (Line start_line, int pos, int length)
CharIndexToLineTag (start_index + length, out end.line,
out end.tag, out end.pos);
+ SuspendUpdate ();
+
if (start.line == end.line) {
DeleteChars (start.tag, pos, end.pos - pos);
} else {
// Join start and end
Combine (start.line.line_no, current);
}
+
+ ResumeUpdate (true);
}
line.text.Remove(pos, count);
// Make sure the tag points to the right spot
- while ((tag != null) && (tag.start + tag.length - 1) <= pos) {
+ while ((tag != null) && (tag.end) < pos) {
tag = tag.next;
}
if (tag == null) {
- return;
+ goto Cleanup;
}
// Check if we're crossing tag boundaries
left = count;
left -= tag.start + tag.length - pos - 1;
- tag.length -= tag.start + tag.length - pos - 1;
tag = tag.next;
while ((tag != null) && (left > 0)) {
tag.start -= count - left;
+
if (tag.length > left) {
- tag.length -= left;
left = 0;
} else {
left -= tag.length;
- tag.length = 0;
-
tag = tag.next;
}
+
}
} else {
// We got off easy, same tag
- tag.length -= count;
-
if (tag.length == 0) {
streamline = true;
}
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--;
+ // tag.length--;
if (tag.length == 0) {
streamline = true;
pos--;
line.text.Remove(pos, 1);
if (pos >= (tag.start - 1)) {
- tag.length--;
+ // tag.length--;
if (tag.length == 0) {
streamline = true;
}
} else if (tag.previous != null) {
- tag.previous.length--;
+ // tag.previous.length--;
if (tag.previous.length == 0) {
streamline = true;
}
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;
}
+ // need to get the shift before setting the next tag since that effects length
+ shift = last.start + last.length - 1;
last.next = second.tags;
last.next.previous = last;
- shift = last.start + last.length - 1;
-
// Fix up references within the chain
last = last.next;
while (last != null) {
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;
move_sel_end = false;
// Adjust selection and cursors
- if (soft && (caret.line == line) && (caret.pos >= pos)) {
+ if (caret.line == line && caret.pos >= pos) {
move_caret = true;
}
if (selection_start.line == line && selection_start.pos > pos) {
// 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 = GetLine (line.line_no + 1);
- if (soft) {
- if (move_caret) {
- caret.line = new_line;
- caret.line.soft_break = true;
- caret.tag = new_line.tags;
- caret.pos = 0;
- } else {
- new_line.soft_break = true;
- }
+ if (move_caret) {
+ caret.line = new_line;
+ caret.tag = new_line.tags;
+ caret.pos = 0;
}
if (move_sel_start) {
}
// 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;
-
line.recalc = true;
new_line.recalc = true;
// We can simply break the chain and move the tag into the next line
if (tag == line.tags) {
- new_tag = new LineTag(line, 1, 0);
+ new_tag = new LineTag(line, 1);
new_tag.CopyFormattingFrom (tag);
line.tags = new_tag;
}
} else {
int shift;
- new_tag = new LineTag(new_line, 1, tag.start - 1 + tag.length - pos);
+ new_tag = new LineTag (new_line, 1);
new_tag.next = tag.next;
new_tag.CopyFormattingFrom (tag);
new_line.tags = new_tag;
new_tag.next.previous = new_tag;
}
tag.next = null;
- tag.length = pos - tag.start + 1;
shift = pos;
new_tag = new_tag.next;
}
}
- if (soft) {
- if (move_caret) {
- caret.line = new_line;
- caret.pos = caret.pos - pos;
- caret.tag = caret.line.FindTag(caret.pos);
- }
- new_line.soft_break = true;
+ if (move_caret) {
+ caret.line = new_line;
+ caret.pos = caret.pos - pos;
+ caret.tag = caret.line.FindTag(caret.pos);
}
if (move_sel_start) {
// 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(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;
line1.widths = line3.widths;
- line1.Y = line3.Y;
+ line1.offset = line3.offset;
tag = line1.tags;
while (tag != null) {
p2 = start_pos;
}
+ int endpoint = (int) l1.widths [p2];
+ if (p2 == l1.text.Length + 1) {
+ endpoint = (int) viewport_width;
+ }
+
#if Debug
- Console.WriteLine("Invaliding from {0}:{1} to {2}:{3} {4}",
+ Console.WriteLine("Invaliding backwards from {0}:{1} to {2}:{3} {4}",
l1.line_no, p1, l2.line_no, p2,
new Rectangle(
- (int)l1.widths[p1] + l1.align_shift - viewport_x,
+ (int)l1.widths[p1] + l1.X - viewport_x,
l1.Y - viewport_y,
- (int)l2.widths[p2] - (int)l1.widths[p1] + 1,
+ (int)l1.widths[p2],
l1.height
)
);
#endif
- owner.Invalidate(
- new Rectangle(
- (int)l1.widths[p1] + l1.align_shift - viewport_x,
- l1.Y - viewport_y,
- (int)l2.widths[p2] - (int)l1.widths[p1] + 1,
- l1.height
- )
- );
+ owner.Invalidate(new Rectangle (
+ (int)l1.widths[p1] + l1.X - viewport_x,
+ l1.Y - viewport_y,
+ endpoint - (int)l1.widths[p1] + 1,
+ l1.height));
return;
}
#if Debug
- Console.WriteLine("Invaliding from {0}:{1} to {2}:{3} Start => x={4}, y={5}, {6}x{7}", l1.line_no, p1, l2.line_no, p2, (int)l1.widths[p1] + l1.align_shift - viewport_x, l1.Y - viewport_y, viewport_width, l1.height);
+ Console.WriteLine("Invaliding from {0}:{1} to {2}:{3} Start => x={4}, y={5}, {6}x{7}", l1.line_no, p1, l2.line_no, p2, (int)l1.widths[p1] + l1.X - viewport_x, l1.Y - viewport_y, viewport_width, l1.height);
Console.WriteLine ("invalidate start line: {0} position: {1}", l1.text, p1);
#endif
// Three invalidates:
// First line from start
- owner.Invalidate(new Rectangle((int)l1.widths[p1] + l1.align_shift - viewport_x, l1.Y - viewport_y, viewport_width, l1.height));
+ owner.Invalidate(new Rectangle((int)l1.widths[p1] + l1.X - viewport_x, l1.Y - viewport_y, viewport_width, l1.height));
// lines inbetween
// Last line to end
- owner.Invalidate(new Rectangle((int)l2.widths[0] + l2.align_shift - viewport_x, l2.Y - viewport_y, (int)l2.widths[p2] + 1, l2.height));
+ owner.Invalidate(new Rectangle((int)l2.widths[0] + l2.X - viewport_x, l2.Y - viewport_y, (int)l2.widths[p2] + 1, l2.height));
#if Debug
- Console.WriteLine("Invaliding from {0}:{1} to {2}:{3} End => x={4}, y={5}, {6}x{7}", l1.line_no, p1, l2.line_no, p2, (int)l2.widths[0] + l2.align_shift - viewport_x, l2.Y - viewport_y, (int)l2.widths[p2] + 1, l2.height);
+ Console.WriteLine("Invaliding from {0}:{1} to {2}:{3} End => x={4}, y={5}, {6}x{7}", l1.line_no, p1, l2.line_no, p2, (int)l2.widths[0] + l2.X - viewport_x, l2.Y - viewport_y, (int)l2.widths[p2] + 1, l2.height);
+
#endif
}
}
}
- 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) {
return string.Empty;
}
- if (!multiline || (selection_start.line == selection_end.line)) {
- return selection_start.line.text.ToString(selection_start.pos, selection_end.pos - selection_start.pos);
+ if (selection_start.line == selection_end.line) {
+ return selection_start.line.text.ToString (selection_start.pos, selection_end.pos - selection_start.pos);
} else {
StringBuilder sb;
int i;
internal void ReplaceSelection(string s, bool select_new) {
int i;
- undo.BeginCompoundAction ();
-
- InvalidateSelectionArea ();
-
+ int selection_pos_on_line = selection_start.pos;
int selection_start_pos = LineTagToCharIndex (selection_start.line, selection_start.pos);
+ SuspendRecalc ();
+
// First, delete any selected text
if ((selection_start.pos != selection_end.pos) || (selection_start.line != selection_end.line)) {
- if (!multiline || (selection_start.line == selection_end.line)) {
- undo.RecordDeleteChars(selection_start.line, selection_start.pos + 1, selection_end.pos - selection_start.pos);
+ if (selection_start.line == selection_end.line) {
+ undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
DeleteChars(selection_start.tag, selection_start.pos, selection_end.pos - selection_start.pos);
start = selection_start.line.line_no;
end = selection_end.line.line_no;
- undo.RecordDelete(selection_start.line, selection_start.pos + 1, selection_end.line, selection_end.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);
}
}
- Insert(selection_start.line, selection_start.pos, true, s);
+
+ Insert(selection_start.line, selection_start.pos, false, s);
undo.RecordInsertString (selection_start.line, selection_start.pos, s);
-
+ ResumeRecalc (false);
+
if (!select_new) {
CharIndexToLineTag(selection_start_pos + s.Length, out selection_start.line,
out selection_start.tag, out selection_start.pos);
SetSelectionVisible (true);
}
- undo.EndCompoundAction ();
+ PositionCaret (selection_start.line, selection_start.pos);
+ UpdateView (selection_start.line, selection_pos_on_line);
}
internal void CharIndexToLineTag(int index, out Line line_out, out LineTag tag_out, out int pos) {
line = GetLine(i);
start = chars;
- chars += line.text.Length + 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 + crlf_size;
+ length += GetLine(i).text.Length;
}
length += pos;
return 0;
}
- if (!multiline || (selection_start.line == selection_end.line)) {
+ if (selection_start.line == selection_end.line) {
return selection_end.pos - selection_start.pos;
} else {
int i;
if (start < end) {
for (i = start; i < end; i++) {
- length += GetLine(i).text.Length + crlf_size;
+ Line line = GetLine (i);
+ 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;
return line;
}
- /// <summary>Give it a Y pixel coordinate and it returns the Line covering that Y coordinate</summary>
- internal Line GetLineByPixel(int y, bool exact) {
+ /// <summary>Give it a pixel offset coordinate and it returns the Line covering that are (offset
+ /// is either X or Y depending on if we are multiline
+ /// </summary>
+ internal Line GetLineByPixel (int offset, bool exact)
+ {
Line line = document;
Line last = null;
- while (line != sentinel) {
- last = line;
- if ((y >= line.Y) && (y < (line.Y+line.height))) {
- return line;
- } else if (y < line.Y) {
- line = line.left;
- } else {
- line = line.right;
+ if (multiline) {
+ while (line != sentinel) {
+ last = line;
+ if ((offset >= line.Y) && (offset < (line.Y+line.height))) {
+ return line;
+ } else if (offset < line.Y) {
+ line = line.left;
+ } else {
+ line = line.right;
+ }
+ }
+ } else {
+ while (line != sentinel) {
+ last = line;
+ if ((offset >= line.X) && (offset < (line.X + line.Width)))
+ return line;
+ else if (offset < line.X)
+ line = line.left;
+ else
+ line = line.right;
}
}
tag = line.tags;
// Alignment adjustment
- x += line.align_shift;
+ x += line.X;
while (true) {
if (x >= tag.X && x < (tag.X+tag.width)) {
Line line;
LineTag tag;
- line = GetLineByPixel(y, false);
+ line = GetLineByPixel(multiline ? y : x, false);
tag = line.tags;
- // Adjust for alignment
- x -= line.align_shift;
+ /// 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.start + tag.length - 1;
+ end = tag.TextEnd;
- for (int pos = tag.start-1; pos < end; pos++) {
+ for (int pos = tag.start - 1; pos < end; pos++) {
// When clicking on a character, we position the cursor to whatever edge
// of the character the click was closer
- if (x < (line.widths[pos] + ((line.widths[pos+1]-line.widths[pos])/2))) {
+ if (x < (line.X + line.widths[pos] + ((line.widths[pos+1]-line.widths[pos])/2))) {
index = pos;
- return tag;
+ return LineTag.GetFinalTag (tag);
}
}
index=end;
- return tag;
+ return LineTag.GetFinalTag (tag);
}
if (tag.next != null) {
tag = tag.next;
} else {
- index = line.text.Length;
- return tag;
+ 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() {
+ internal void RecalculateAlignments ()
+ {
Line line;
int line_no;
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;
}
}
internal bool RecalculateDocument(Graphics g, int start, int end, bool optimize) {
Line line;
int line_no;
- int Y;
+ int offset;
int new_width;
bool changed;
int shift;
- if (no_recalc) {
+ if (recalc_suspended > 0) {
recalc_pending = true;
- recalc_start = start;
- recalc_end = end;
+ recalc_start = Math.Min (recalc_start, start);
+ recalc_end = Math.Max (recalc_end, end);
recalc_optimize = optimize;
return false;
}
- Y = GetLine(start).Y;
+ // Fixup the positions, they can go kinda nuts
+ start = Math.Max (start, 1);
+ end = Math.Min (end, lines);
+
+ offset = GetLine(start).offset;
line_no = start;
new_width = 0;
shift = this.lines;
while (line_no <= (end + this.lines - shift)) {
line = GetLine(line_no++);
- line.Y = Y;
+ line.offset = offset;
if (!calc_pass) {
if (!optimize) {
}
}
- Y += line.height;
+ if (multiline)
+ offset += line.height;
+ else
+ 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;
}
#endregion // Administrative
}
+ internal class PictureTag : LineTag {
+
+ internal RTF.Picture picture;
+
+ internal PictureTag (Line line, int start, RTF.Picture picture) : base (line, start)
+ {
+ this.picture = picture;
+ }
+
+ public override bool IsTextTag {
+ get { return false; }
+ }
+
+ internal override SizeF SizeOfPosition (Graphics dc, int pos)
+ {
+ return picture.Size;
+ }
+
+ internal override int MaxHeight ()
+ {
+ return (int) (picture.Height + 0.5F);
+ }
+
+ internal override void Draw (Graphics dc, Brush brush, float xoff, float y, int start, int end)
+ {
+ picture.DrawImage (dc, xoff + line.widths [start], y, false);
+ }
+
+ internal override void Draw (Graphics dc, Brush brush, float xoff, float y, int start, int end, string text)
+ {
+ picture.DrawImage (dc, xoff + + line.widths [start], y, false);
+ }
+
+ public override string Text ()
+ {
+ return "I";
+ }
+ }
+
internal class LineTag {
#region Local Variables;
// Payload; formatting
// Payload; text
internal int start; // start, in chars; index into Line.text
- internal int length; // length, in chars
internal bool r_to_l; // Which way is the font
// Drawing support
internal int height; // Height in pixels of the text this tag describes
- internal int X; // X location of the text this tag describes
- // internal float width; // Width in pixels of the text this tag describes
+
internal int ascent; // Ascent of the font for this tag
internal int shift; // Shift down for this tag, to stay on baseline
#endregion;
#region Constructors
- internal LineTag(Line line, int start, int length) {
+ internal LineTag(Line line, int start) {
this.line = line;
this.start = start;
- this.length = length;
- this.X = 0;
}
#endregion // Constructors
#region Internal Methods
- public int End {
+ public float X {
+ get {
+ if (start == 0)
+ return line.X;
+ return line.X + line.widths [start - 1];
+ }
+ }
+
+ public int end {
get { return start + length; }
}
+ public int TextEnd {
+ get { return start + TextLength; }
+ }
+
public float width {
get {
if (length == 0)
return 0;
- return line.widths [start + length - 1];
+ return line.widths [start + length - 1] - (start != 0 ? line.widths [start - 1] : 0);
+ }
+ }
+
+ public int length {
+ get {
+ int res = 0;
+ if (next != null)
+ res = next.start - start;
+ else
+ res = line.text.Length - (start - 1);
+
+ return res > 0 ? res : 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)
+ {
+ 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 ()
+ {
+ return font.Height;
+ }
+
+ internal virtual void Draw (Graphics dc, Brush brush, float x, float y, int start, int end)
+ {
+ dc.DrawString (line.text.ToString (start, end), font, brush, x, y, StringFormat.GenericTypographic);
+ }
+
+ internal virtual void Draw (Graphics dc, Brush brush, float xoff, float y, int start, int end, string text)
+ {
+ 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>
internal LineTag Break(int pos) {
return null;
}
- new_tag = new LineTag(line, pos, start + length - pos);
+ new_tag = new LineTag(line, pos);
new_tag.CopyFormattingFrom (this);
- this.length -= new_tag.length;
+
new_tag.next = this.next;
this.next = new_tag;
new_tag.previous = this;
+
if (new_tag.next != null) {
new_tag.next.previous = new_tag;
}
return new_tag;
}
+ public virtual string Text ()
+ {
+ return line.text.ToString (start - 1, length);
+ }
+
public void CopyFormattingFrom (LineTag other)
{
height = other.height;
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) {
+ while (tag != null && tag.end <= end) {
SetFormat (tag, font, color, back_color, specified);
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;
+
+ /// 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);
- }
+ if (end_tag != null) {
+ end_tag.Break (end);
+ SetFormat (end_tag, font, color, back_color, specified);
}
return retval;
if ((FormatSpecified.BackColor & specified) == FormatSpecified.BackColor) {
tag.back_color = back_color;
}
+ // 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, 0);
- 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;
}
while (tag != null) {
- if ((tag.start <= pos) && (pos < (tag.start+tag.length))) {
+ if ((tag.start <= pos) && (pos <= tag.end)) {
return GetFinalTag (tag);
}
{
LineTag res = tag;
- while (res.next != null && res.next.length == 0)
+ while (res.length == 0 && res.next != null && res.next.length == 0)
res = res.next;
+
return res;
}
return false;
}
- this.length += other.length;
this.next = other.next;
if (this.next != null) {
this.next.previous = this;
return false;
}
if (this.start != 1) {
- this.previous.length += this.length;
this.previous.next = this.next;
this.next.previous = this.previous;
} else {
this.next.start = 1;
- this.next.length += this.length;
this.line.tags = this.next;
this.next.previous = null;
}
other = (LineTag)obj;
+ if (other.IsTextTag != IsTextTag)
+ return false;
+
if (this.font.Equals(other.font) && this.color.Equals(other.color)) { // FIXME add checking for things like link or type later
return true;
}
public override string ToString() {
if (length > 0)
- return "Tag starts at index " + this.start + " length " + this.length + " text: " + this.line.Text.Substring(this.start-1, this.length) + "Font " + this.font.ToString();
+ return GetType () + " Tag starts at index " + this.start + " length " + this.length + " text: " + Text () + "Font " + this.font.ToString();
return "Zero Lengthed tag at index " + this.start;
}
#endregion // Internal Methods
}
- internal class UndoClass {
+ internal class UndoManager {
+
internal enum ActionType {
- InsertChar,
+
+ Typing,
+
+ // This is basically just cut & paste
InsertString,
- DeleteChar,
- DeleteChars,
- CursorMove,
- Mark,
- CompoundBegin,
- CompoundEnd,
+ DeleteString,
+
+ UserActionBegin,
+ UserActionEnd
}
internal class Action {
private Stack undo_actions;
private Stack redo_actions;
- private int undo_levels;
- private int redo_levels;
- 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;
#endregion // Local Variables
#region Constructors
- internal UndoClass(Document doc) {
- document = doc;
- undo_actions = new Stack(50);
- redo_actions = new Stack(50);
+ internal UndoManager (Document document)
+ {
+ this.document = document;
+ undo_actions = new Stack (50);
+ redo_actions = new Stack (50);
}
#endregion // Constructors
#region Properties
- internal int UndoLevels {
- get {
- return undo_levels;
- }
+ internal bool CanUndo {
+ get { return undo_actions.Count > 0; }
}
- internal int RedoLevels {
- get {
- return redo_levels;
- }
+ internal bool CanRedo {
+ get { return redo_actions.Count > 0; }
}
- internal string UndoName {
+ internal string UndoActionName {
get {
- Action action;
-
- action = (Action)undo_actions.Peek();
-
- if (action.type == ActionType.CompoundEnd)
- return (string) action.data;
-
- switch(action.type) {
- case ActionType.InsertChar: {
- Locale.GetText("Insert character");
- break;
- }
-
- case ActionType.DeleteChar: {
- Locale.GetText("Delete character");
- break;
- }
-
- case ActionType.InsertString: {
- Locale.GetText("Insert string");
- break;
- }
-
- case ActionType.DeleteChars: {
- Locale.GetText("Delete string");
- break;
- }
-
- case ActionType.CursorMove: {
- Locale.GetText("Cursor move");
- break;
- }
+ foreach (Action action in undo_actions) {
+ if (action.type == ActionType.UserActionBegin)
+ return (string) action.data;
+ if (action.type == ActionType.Typing)
+ return Locale.GetText ("Typing");
}
- return null;
+ return String.Empty;
}
}
- internal string RedoName() {
- return null;
+ internal string RedoActionName {
+ get {
+ foreach (Action action in redo_actions) {
+ if (action.type == ActionType.UserActionBegin)
+ return (string) action.data;
+ if (action.type == ActionType.Typing)
+ return Locale.GetText ("Typing");
+ }
+ return String.Empty;
+ }
}
#endregion // Properties
#region Internal Methods
- internal void Clear() {
+ internal void Clear ()
+ {
undo_actions.Clear();
redo_actions.Clear();
- undo_levels = 0;
- redo_levels = 0;
}
- internal void Undo() {
+ internal void Undo ()
+ {
Action action;
- int compound_stack = 0;
+ bool user_action_finished = false;
- if (undo_actions.Count == 0) {
+ if (undo_actions.Count == 0)
return;
- }
-
+ // Nuke the redo queue
+ redo_actions.Clear ();
+ locked = true;
do {
- action = (Action)undo_actions.Pop();
+ Line start;
+ action = (Action) undo_actions.Pop ();
// Put onto redo stack
redo_actions.Push(action);
// Do the thing
switch(action.type) {
- case ActionType.CompoundEnd:
- compound_stack++;
+
+ case ActionType.UserActionBegin:
+ user_action_finished = true;
break;
- case ActionType.CompoundBegin:
- compound_stack--;
- undo_levels--;
- redo_levels++;
+ case ActionType.UserActionEnd:
+ // noop
break;
case ActionType.InsertString:
- document.DeleteMultiline (document.GetLine (action.line_no),
- action.pos, ((string) action.data).Length + 1);
+ start = document.GetLine (action.line_no);
+ document.SuspendUpdate ();
+ document.DeleteMultiline (start, action.pos, ((string) action.data).Length + 1);
+ document.PositionCaret (start, action.pos);
+ document.SetSelectionToCaret (true);
+ document.ResumeUpdate (true);
break;
- case ActionType.InsertChar: {
- // FIXME - implement me
+ case ActionType.Typing:
+ start = document.GetLine (action.line_no);
+ document.SuspendUpdate ();
+ document.DeleteMultiline (start, action.pos, ((StringBuilder) action.data).Length);
+ document.PositionCaret (start, action.pos);
+ document.SetSelectionToCaret (true);
+ document.ResumeUpdate (true);
+
+ // This is an open ended operation, so only a single typing operation can be undone at once
+ user_action_finished = true;
break;
- }
-
- case ActionType.DeleteChars: {
- this.Insert(document.GetLine(action.line_no), action.pos, (Line)action.data);
- Undo(); // Grab the cursor location
+
+ case ActionType.DeleteString:
+ start = document.GetLine (action.line_no);
+ document.SuspendUpdate ();
+ Insert (start, action.pos, (Line) action.data, true);
+ document.ResumeUpdate (true);
break;
}
+ } while (!user_action_finished && undo_actions.Count > 0);
- case ActionType.CursorMove: {
- document.caret.line = document.GetLine(action.line_no);
- if (document.caret.line == null) {
- Undo();
- break;
- }
+ locked = false;
+ }
- document.caret.tag = document.caret.line.FindTag(action.pos);
- document.caret.pos = action.pos;
- document.caret.height = document.caret.tag.height;
+ internal void Redo ()
+ {
+ Action action;
+ bool user_action_finished = false;
- if (document.owner.IsHandleCreated) {
- XplatUI.DestroyCaret(document.owner.Handle);
- XplatUI.CreateCaret(document.owner.Handle, 2, document.caret.height);
- XplatUI.SetCaretPos(document.owner.Handle, (int)document.caret.tag.line.widths[document.caret.pos] + document.caret.line.align_shift - document.viewport_x, document.caret.line.Y + document.caret.tag.shift - document.viewport_y + Document.caret_shift);
+ if (redo_actions.Count == 0)
+ return;
- document.DisplayCaret ();
- }
+ // You can't undo anything after redoing
+ undo_actions.Clear ();
+
+ locked = true;
+ do {
+ Line start;
+ int start_index;
+
+ action = (Action) redo_actions.Pop ();
+
+ switch (action.type) {
+
+ case ActionType.UserActionBegin:
+ // Noop
+ break;
+
+ case ActionType.UserActionEnd:
+ user_action_finished = true;
+ break;
+
+ case ActionType.InsertString:
+ start = document.GetLine (action.line_no);
+ document.SuspendUpdate ();
+ start_index = document.LineTagToCharIndex (start, action.pos);
+ document.InsertString (start, action.pos, (string) action.data);
+ document.CharIndexToLineTag (start_index + ((string) action.data).Length,
+ out document.caret.line, out document.caret.tag,
+ out document.caret.pos);
+ document.UpdateCaret ();
+ document.SetSelectionToCaret (true);
+ document.ResumeUpdate (true);
+ break;
+
+ case ActionType.Typing:
+ start = document.GetLine (action.line_no);
+ document.SuspendUpdate ();
+ start_index = document.LineTagToCharIndex (start, action.pos);
+ document.InsertString (start, action.pos, ((StringBuilder) action.data).ToString ());
+ document.CharIndexToLineTag (start_index + ((StringBuilder) action.data).Length,
+ out document.caret.line, out document.caret.tag,
+ out document.caret.pos);
+ document.UpdateCaret ();
+ document.SetSelectionToCaret (true);
+ document.ResumeUpdate (true);
+
+ // This is an open ended operation, so only a single typing operation can be undone at once
+ user_action_finished = true;
+ break;
+
+ case ActionType.DeleteString:
+ start = document.GetLine (action.line_no);
+ document.SuspendUpdate ();
+ document.DeleteMultiline (start, action.pos, ((Line) action.data).text.Length);
+ document.PositionCaret (start, action.pos);
+ document.SetSelectionToCaret (true);
+ document.ResumeUpdate (true);
- // FIXME - enable call
- //if (document.CaretMoved != null) document.CaretMoved(this, EventArgs.Empty);
break;
}
- }
- } while (compound_stack > 0);
- }
+ } while (!user_action_finished && redo_actions.Count > 0);
- internal void Redo() {
- if (redo_actions.Count == 0) {
- return;
- }
+ locked = false;
}
#endregion // Internal Methods
#region Private Methods
- public void BeginCompoundAction ()
+ public void BeginUserAction (string name)
{
- Action cb = new Action ();
- cb.type = ActionType.CompoundBegin;
+ if (locked)
+ return;
- undo_actions.Push (cb);
+ Action ua = new Action ();
+ ua.type = ActionType.UserActionBegin;
+ ua.data = name;
+
+ undo_actions.Push (ua);
}
- public void EndCompoundAction ()
+ public void EndUserAction ()
{
- Action ce = new Action ();
- ce.type = ActionType.CompoundEnd;
+ if (locked)
+ return;
- undo_actions.Push (ce);
- undo_levels++;
- }
+ Action ua = new Action ();
+ ua.type = ActionType.UserActionEnd;
- // pos = 1-based
- public void RecordDeleteChars(Line line, int pos, int length) {
- RecordDelete(line, pos, line, pos + length - 1);
+ undo_actions.Push (ua);
}
// start_pos, end_pos = 1 based
- public void RecordDelete(Line start_line, int start_pos, Line end_line, int end_pos) {
- Line l;
- Action a;
+ public void RecordDeleteString (Line start_line, int start_pos, Line end_line, int end_pos)
+ {
+ if (locked)
+ return;
- l = Duplicate(start_line, start_pos, end_line, end_pos);
+ Action a = new Action ();
- a = new Action();
- a.type = ActionType.DeleteChars;
- a.data = l;
+ // We cant simply store the string, because then formatting would be lost
+ a.type = ActionType.DeleteString;
a.line_no = start_line.line_no;
- a.pos = start_pos - 1;
+ a.pos = start_pos;
+ a.data = Duplicate (start_line, start_pos, end_line, end_pos);
- // Record the cursor position before, since the actions will occur in reverse order
- RecordCursor();
undo_actions.Push(a);
}
public void RecordInsertString (Line line, int pos, string str)
{
+ if (locked || str.Length == 0)
+ return;
+
Action a = new Action ();
a.type = ActionType.InsertString;
undo_actions.Push (a);
}
- public void RecordCursor() {
- if (document.caret.line == null) {
+ public void RecordTyping (Line line, int pos, char ch)
+ {
+ if (locked)
return;
- }
- RecordCursor(document.caret.line, document.caret.pos);
- }
+ Action a = null;
- public void RecordCursor(Line line, int pos) {
- Action a;
+ if (undo_actions.Count > 0)
+ a = (Action) undo_actions.Peek ();
- if ((line.line_no == caret_line) && (pos == caret_pos)) {
- return;
- }
+ if (a == null || a.type != ActionType.Typing) {
+ a = new Action ();
+ a.type = ActionType.Typing;
+ a.data = new StringBuilder ();
+ a.line_no = line.line_no;
+ a.pos = pos;
- caret_line = line.line_no;
- caret_pos = pos;
-
- a = new Action();
- a.type = ActionType.CursorMove;
- a.line_no = line.line_no;
- a.pos = pos;
+ undo_actions.Push (a);
+ }
- undo_actions.Push(a);
+ StringBuilder data = (StringBuilder) a.data;
+ data.Append (ch);
}
// start_pos = 1-based
// end_pos = 1-based
- public Line Duplicate(Line start_line, int start_pos, Line end_line, int end_pos) {
+ public Line Duplicate(Line start_line, int start_pos, Line end_line, int end_pos)
+ {
Line ret;
Line line;
Line current;
int start;
int end;
int tag_start;
- int tag_length;
- line = new Line();
+ 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) {
end = current.text.Length;
}
+ if (end_pos == 0)
+ continue;
+
// Text for the tag
- line.text = new StringBuilder(current.text.ToString(start - 1, end - start + 1));
+ 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 - 1);
- while ((current_tag != null) && (current_tag.start < end)) {
+ current_tag = current.FindTag (start);
+ 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;
tag_start = current_tag.start;
}
- if (end < (current_tag.start + current_tag.length)) {
- tag_length = end - tag_start + 1;
- } else {
- tag_length = current_tag.start + current_tag.length - tag_start;
- }
- tag = new LineTag(line, tag_start - start + 1, tag_length);
+ tag = new LineTag(line, tag_start - start + 1);
tag.CopyFormattingFrom (current_tag);
current_tag = current_tag.next;
}
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();
+ line.right = new Line (start_line.document, start_line.ending);
line.right.left = line;
line = line.right;
}
}
// Insert multi-line text at the given position; use formatting at insertion point for inserted text
- internal void Insert(Line line, int pos, Line insert) {
+ internal void Insert(Line line, int pos, Line insert, bool select)
+ {
Line current;
LineTag tag;
int offset;
tag.next = insert.tags;
line.text.Insert(offset, insert.text.ToString());
-
+
// Adjust start locations
tag = tag.next;
while (tag != null) {
}
// Put it back together
document.Combine(line.line_no, line.line_no + 1);
+
+ if (select) {
+ document.SetSelectionStart (line, pos, false);
+ document.SetSelectionEnd (line, pos + insert.text.Length, false);
+ }
+
document.UpdateView(line, pos);
return;
}
first = line;
lines = 1;
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;
}