X-Git-Url: http://wien.tomnetworks.com/gitweb/?a=blobdiff_plain;f=mcs%2Fclass%2FManaged.Windows.Forms%2FSystem.Windows.Forms%2FTextControl.cs;h=71e5f4207c888601bb5fc7ed4eb7d8a1cd37104b;hb=fe405951f51ed81b13956a3188460c9c281f605f;hp=407433d0f27511142dfb9e730e5a1b269cf2939a;hpb=64b0a15afcb537338d324f7bab608daf76118566;p=mono.git diff --git a/mcs/class/Managed.Windows.Forms/System.Windows.Forms/TextControl.cs b/mcs/class/Managed.Windows.Forms/System.Windows.Forms/TextControl.cs index 407433d0f27..71e5f4207c8 100644 --- a/mcs/class/Managed.Windows.Forms/System.Windows.Forms/TextControl.cs +++ b/mcs/class/Managed.Windows.Forms/System.Windows.Forms/TextControl.cs @@ -64,15 +64,6 @@ namespace System.Windows.Forms { 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, @@ -104,630 +95,15 @@ namespace System.Windows.Forms { } internal enum LineEnding { - Wrap, // line wraps to the next line - Limp, // \r - Hard, // \r\n - Soft, // \r\r\n - Rich, // \n + Wrap = 1, // line wraps to the next line + Limp = 2, // \r + Hard = 4, // \r\n + Soft = 8, // \r\r\n + Rich = 16, // \n - None + None = 0 } - // 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 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 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 LineEnding ending; - - - // Stuff that's important for the tree - internal Line parent; // Our parent line - internal Line left; // Line with smaller line number - 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 bool recalc; // Line changed - 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 Line (Document document, LineEnding ending) - { - this.document = document; - color = LineColor.Red; - left = null; - right = null; - parent = null; - text = null; - recalc = true; - alignment = document.alignment; - - ending = ending; - } - - 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; - } - - 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, 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]; - tags = tag; - } - - #endregion // Constructors - - #region Internal Properties - - internal int Y { - get { - int tm = document.owner.actual_border_style == BorderStyle.FixedSingle ? top_margin : 0; - if (!document.multiline) - return tm; - return tm + 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; - } - - set { - indent = value; - recalc = true; - } - } - - internal int HangingIndent { - get { - return hanging_indent; - } - - set { - hanging_indent = value; - recalc = true; - } - } - - internal int RightIndent { - get { - return right_indent; - } - - set { - right_indent = value; - recalc = true; - } - } - - - internal int Height { - get { - return height; - } - - set { - height = value; - } - } - - internal int LineNo { - get { - return line_no; - } - - set { - line_no = value; - } - } - - internal string Text { - get { - return text.ToString(); - } - - set { - text = new StringBuilder(value, value.Length > DEFAULT_TEXT_LEN ? value.Length : DEFAULT_TEXT_LEN); - } - } - - internal HorizontalAlignment Alignment { - get { - return alignment; - } - - set { - if (alignment != value) { - alignment = value; - recalc = true; - } - } - } -#if no - internal StringBuilder Text { - get { - return text; - } - - set { - text = value; - } - } -#endif - #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; - float[] new_widths; - - length = text.Length; - - if ((length + minimum) > space) { - // We need to grow; double the size - - if ((length + minimum) > (space * 2)) { - new_widths = new float[length + minimum * 2 + 1]; - space = length + minimum * 2; - } else { - new_widths = new float[space * 2 + 1]; - space *= 2; - } - widths.CopyTo(new_widths, 0); - - widths = new_widths; - } - } - - internal void Streamline(int lines) { - LineTag current; - LineTag next; - - 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 - // 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 (current.IsTextTag && next.length == 0 && next.IsTextTag) { - if ((next.next != null) || (line_no != lines)) { - current.next = next.next; - if (current.next != null) { - current.next.previous = current; - } - next = current.next; - continue; - } - } - if (current.Combine(next)) { - next = current.next; - continue; - } - - current = current.next; - next = current.next; - } - } - - /// Find the tag on a line based on the character position, pos is 0-based - internal LineTag FindTag(int pos) { - LineTag tag; - - if (pos == 0) { - return tags; - } - - tag = this.tags; - - if (pos >= text.Length) { - pos = text.Length - 1; - } - - while (tag != null) { - if (((tag.start - 1) <= pos) && (pos < (tag.start + tag.length - 1))) { - return LineTag.GetFinalTag (tag); - } - tag = tag.next; - } - return null; - } - - /// - /// Recalculate a single line using the same char for every character in the line - /// - - internal bool RecalculatePasswordLine(Graphics g, Document doc) { - LineTag tag; - int pos; - int len; - float w; - bool ret; - int descent; - - pos = 0; - len = this.text.Length; - tag = this.tags; - ascent = 0; - tag.shift = 0; - - this.recalc = false; - widths[0] = left_margin + indent; - - w = g.MeasureString(doc.password_char, tags.font, 10000, Document.string_format).Width; - - if (this.height != (int)tag.font.Height) { - ret = true; - } else { - ret = false; - } - - this.height = (int)tag.font.Height; - tag.height = this.height; - - XplatUI.GetFontMetrics(g, tag.font, out tag.ascent, out descent); - this.ascent = tag.ascent; - - while (pos < len) { - pos++; - widths[pos] = widths[pos-1] + w; - } - - return ret; - } - - /// - /// Go through all tags on a line and recalculate all size-related values; - /// returns true if lineheight changed - /// - internal bool RecalculateLine(Graphics g, Document doc) { - LineTag tag; - int pos; - int len; - SizeF size; - float w; - int prev_offset; - bool retval; - bool wrapped; - Line line; - int wrap_pos; - - pos = 0; - len = this.text.Length; - tag = this.tags; - 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 (ending == LineEnding.Wrap) { - widths[0] = left_margin + hanging_indent; - } else { - widths[0] = left_margin + indent; - } - - this.recalc = false; - retval = false; - wrapped = false; - - wrap_pos = 0; - - while (pos < len) { - - while (tag.length == 0) { // We should always have tags after a tag.length==0 unless len==0 - tag.ascent = 0; - tag.shift = 0; - tag = tag.next; - } - - size = tag.SizeOfPosition (g, pos); - w = size.Width; - - if (Char.IsWhiteSpace(text[pos])) { - wrap_pos = pos + 1; - } - - if (doc.wrap) { - if ((wrap_pos > 0) && (wrap_pos != len) && (widths[pos] + w) + 5 > (doc.viewport_width - this.right_indent)) { - // Make sure to set the last width of the line before wrapping - widths [pos + 1] = widths [pos] + w; - - pos = wrap_pos; - 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)) { - // No suitable wrap position was found so break right in the middle of a word - - // Make sure to set the last width of the line before wrapping - widths [pos + 1] = widths [pos] + w; - - doc.Split(this, tag, pos); - ending = LineEnding.Wrap; - len = this.text.Length; - retval = true; - wrapped = true; - } - } - - // Contract all wrapped lines that follow back into our line - if (!wrapped) { - pos++; - - widths[pos] = widths[pos-1] + w; - - if (pos == len) { - line = doc.GetLine(this.line_no + 1); - 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; - retval = true; - } - } - } - - if (pos == (tag.start-1 + tag.length)) { - // We just found the end of our current tag - tag.height = tag.MaxHeight (); - - // Check if we're the tallest on the line (so far) - if (tag.height > this.height) { - this.height = tag.height; // Yep; make sure the line knows - } - - if (tag.ascent == 0) { - int descent; - - XplatUI.GetFontMetrics(g, tag.font, out tag.ascent, out descent); - } - - if (tag.ascent > this.ascent) { - LineTag t; - - // We have a tag that has a taller ascent than the line; - t = tags; - while (t != null && t != tag) { - t.shift = tag.ascent - t.ascent; - t = t.next; - } - - // Save on our line - this.ascent = tag.ascent; - } else { - tag.shift = this.ascent - tag.ascent; - } - - tag = tag.next; - if (tag != null) { - tag.shift = 0; - wrap_pos = pos; - } - } - } - - if (this.height == 0) { - this.height = tags.font.Height; - tag.height = this.height; - } - - if (prev_offset != offset) { - retval = true; - } - return retval; - } - #endregion // Internal Methods - - #region Administrative - public int CompareTo(object obj) { - if (obj == null) { - return 1; - } - - if (! (obj is Line)) { - throw new ArgumentException("Object is not of type Line", "obj"); - } - - if (line_no < ((Line)obj).line_no) { - return -1; - } else if (line_no > ((Line)obj).line_no) { - return 1; - } else { - return 0; - } - } - - public object Clone() { - Line clone; - - clone = new Line (document, ending); - - clone.text = text; - - if (left != null) { - clone.left = (Line)left.Clone(); - } - - if (left != null) { - clone.left = (Line)left.Clone(); - } - - return clone; - } - - internal object CloneLine() { - Line clone; - - clone = new Line (document, ending); - - clone.text = text; - - return clone; - } - - public override bool Equals(object obj) { - if (obj == null) { - return false; - } - - if (!(obj is Line)) { - return false; - } - - if (obj == this) { - return true; - } - - if (line_no == ((Line)obj).line_no) { - return true; - } - - return false; - } - - public override int GetHashCode() { - return base.GetHashCode (); - } - - public override string ToString() { - return "Line " + line_no; - } - - #endregion // Administrative - } - internal class Document : ICloneable, IEnumerable { #region Structures // FIXME - go through code and check for places where @@ -816,6 +192,7 @@ namespace System.Windows.Forms { private StringBuilder password_cache; private bool calc_pass; private int char_count; + private bool enable_links; // For calculating widths/heights public static readonly StringFormat string_format = new StringFormat (StringFormat.GenericTypographic); @@ -846,19 +223,23 @@ namespace System.Windows.Forms { internal int viewport_x; internal int viewport_y; // The visible area of the document + internal int offset_x; + internal int offset_y; internal int viewport_width; internal int viewport_height; internal int document_x; // Width of the document internal int document_y; // Height of the document - internal Rectangle invalid; - internal int crlf_size; // 1 or 2, depending on whether we use \r\n or just \n 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 @@ -883,7 +264,7 @@ namespace System.Windows.Forms { owner.HandleCreated += new EventHandler(owner_HandleCreated); owner.VisibleChanged += new EventHandler(owner_VisibleChanged); - Add (1, String.Empty, owner.Font, ThemeEngine.Current.ResPool.GetSolidBrush (owner.ForeColor), LineEnding.None); + Add (1, String.Empty, owner.Font, owner.ForeColor, LineEnding.None); undo = new UndoManager (this); @@ -904,6 +285,9 @@ namespace System.Windows.Forms { viewport_x = 0; viewport_y = 0; + offset_x = 0; + offset_y = 0; + crlf_size = 2; // Default selection is empty @@ -912,6 +296,8 @@ namespace System.Windows.Forms { string_format.Trimming = StringTrimming.None; string_format.FormatFlags = StringFormatFlags.DisplayFormatControl; + + UpdateMargins (); } #endregion @@ -926,6 +312,7 @@ namespace System.Windows.Forms { } } + // UIA: Method used via reflection in TextRangeProvider internal int Lines { get { return lines; @@ -946,7 +333,7 @@ namespace System.Windows.Forms { internal Point Caret { get { - return new Point((int)caret.tag.line.widths[caret.pos] + caret.line.X, caret.line.Y); + return new Point((int)caret.tag.Line.widths[caret.pos] + caret.line.X, caret.line.Y); } } @@ -970,6 +357,14 @@ namespace System.Windows.Forms { } } + /// + /// Whether text is scanned for links + /// + internal bool EnableLinks { + get { return enable_links; } + set { enable_links = value; } + } + internal string PasswordChar { get { return password_char; @@ -1034,6 +429,32 @@ namespace System.Windows.Forms { } } + internal int OffsetX + { + get + { + return offset_x; + } + + set + { + offset_x = value; + } + } + + internal int OffsetY + { + get + { + return offset_y; + } + + set + { + offset_y = value; + } + } + internal int ViewPortWidth { get { return viewport_width; @@ -1087,8 +508,34 @@ namespace System.Windows.Forms { #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 () { + if (recalc_suspended == 0) { + recalc_start = int.MaxValue; + recalc_end = int.MinValue; + } + recalc_suspended++; } @@ -1097,8 +544,8 @@ namespace System.Windows.Forms { if (recalc_suspended > 0) recalc_suspended--; - if (immediate_update && recalc_suspended == 0 && recalc_pending) { - RecalculateDocument (owner.CreateGraphicsInternal(), recalc_start, recalc_end, recalc_optimize); + if (recalc_suspended == 0 && (immediate_update || recalc_pending) && !(recalc_start == int.MaxValue && recalc_end == int.MinValue)) { + RecalculateDocument (owner.CreateGraphicsInternal (), recalc_start, recalc_end, recalc_optimize); recalc_pending = false; } } @@ -1153,15 +600,15 @@ namespace System.Windows.Forms { length = 0; Console.Write(" Tags: "); while (tag != null) { - Console.Write("{0} <{1}>-<{2}>", count++, tag.start, tag.end + Console.Write("{0} <{1}>-<{2}>", count++, tag.Start, tag.End /*line.text.ToString (tag.start - 1, tag.length)*/); - length += tag.length; + length += tag.Length; - if (tag.line != line) { + if (tag.Line != line) { Console.Write("BAD line link"); throw new Exception("Bad line link in tree"); } - tag = tag.next; + tag = tag.Next; if (tag != null) { Console.Write(", "); } @@ -1212,11 +659,14 @@ namespace System.Windows.Forms { private void SetSelectionVisible (bool value) { + bool old_selection_visible = selection_visible; selection_visible = value; // cursor and selection are enemies, we can't have both in the same room at the same time if (owner.IsHandleCreated && !owner.show_caret_w_selection) XplatUI.CaretVisible (owner.Handle, !selection_visible); + if (UIASelectionChanged != null && (selection_visible || old_selection_visible)) + UIASelectionChanged (this, EventArgs.Empty); } private void DecrementLines(int line_no) { @@ -1421,7 +871,11 @@ namespace System.Windows.Forms { // 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 - owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, owner.Height - line.Y - viewport_y)); + owner.Invalidate(new Rectangle( + offset_x, + line.Y - viewport_y + offset_y, + viewport_width, + owner.Height - (line.Y - viewport_y))); } else { // The tag was above the visible area, draw everything owner.Invalidate(); @@ -1429,17 +883,29 @@ namespace System.Windows.Forms { } else { switch(line.alignment) { case HorizontalAlignment.Left: { - owner.Invalidate(new Rectangle(line.X + (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) + offset_x, + line.Y - viewport_y + offset_y, + viewport_width, + line.height + 1)); break; } case HorizontalAlignment.Center: { - owner.Invalidate(new Rectangle(line.X, line.Y - viewport_y, viewport_width, line.height + 1)); + owner.Invalidate(new Rectangle( + line.X + offset_x, + line.Y - viewport_y + offset_y, + viewport_width, + line.height + 1)); break; } case HorizontalAlignment.Right: { - owner.Invalidate(new Rectangle(line.X, line.Y - viewport_y, (int)line.widths[pos + 1] - viewport_x + line.X, line.height + 1)); + owner.Invalidate(new Rectangle( + line.X + offset_x, + line.Y - viewport_y + offset_y, + (int)line.widths[pos + 1] - viewport_x + line.X, + line.height + 1)); break; } } @@ -1455,38 +921,289 @@ namespace System.Windows.Forms { 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; + + Line end_line = GetLine (line.line_no + line_count); + if (end_line == null) + end_line = GetLine (lines); + + if (end_line == null) + return; + + int 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)); + owner.Invalidate(new Rectangle( + offset_x, + line.Y - viewport_y + offset_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 + offset_x; + int w = viewport_width; + int y = Math.Min (start_line_top - viewport_y, line.Y - viewport_y) + offset_y; + int h = Math.Max (end_line_bottom - y, end_line.Y + end_line.height - y); + + owner.Invalidate (new Rectangle (x, y, w, h)); + } + } + + /// + /// Scans the next paragraph for http:/ ftp:/ www. https:/ etc and marks the tags + /// as links. + /// + /// The line to start on + /// marks as true if something is changed + private void ScanForLinks (Line start_line, ref bool link_changed) + { + Line current_line = start_line; + StringBuilder line_no_breaks = new StringBuilder (); + StringBuilder line_link_record = new StringBuilder (); + ArrayList cumulative_length_list = new ArrayList (); + bool update_caret_tag = false; + + cumulative_length_list.Add (0); + + while (current_line != null) { + line_no_breaks.Append (current_line.text); + + if (link_changed == false) + current_line.LinkRecord (line_link_record); + + current_line.ClearLinks (); + + cumulative_length_list.Add (line_no_breaks.Length); + + if (current_line.ending == LineEnding.Wrap) + current_line = GetLine (current_line.LineNo + 1); + else + break; + } + + // search for protocols.. make sure www. is first! + string [] search_terms = new string [] { "www.", "http:/", "ftp:/", "https:/" }; + int search_found = 0; + int index_found = 0; + string line_no_breaks_string = line_no_breaks.ToString (); + int line_no_breaks_index = 0; + int link_end = 0; + + while (true) { + if (line_no_breaks_index >= line_no_breaks_string.Length) + break; + + index_found = FirstIndexOfAny (line_no_breaks_string, search_terms, line_no_breaks_index, out search_found); + + //no links found on this line + if (index_found == -1) + break; + + if (search_found == 0) { + // if we are at the end of the line to analyse and the end of the line + // is "www." then there are no links here + if (line_no_breaks_string.Length == index_found + search_terms [0].Length) + break; + + // if after www. we don't have a letter a digit or a @ or - or / + // then it is not a web address, we should continue searching + if (char.IsLetterOrDigit (line_no_breaks_string [index_found + search_terms [0].Length]) == false && + "@/~".IndexOf (line_no_breaks_string [index_found + search_terms [0].Length].ToString ()) == -1) { + line_no_breaks_index = index_found + search_terms [0].Length; + continue; + } + } + + link_end = line_no_breaks_string.Length - 1; + line_no_breaks_index = line_no_breaks_string.Length; + + // we've found a link, we just need to find where it ends now + for (int i = index_found + search_terms [search_found].Length; i < line_no_breaks_string.Length; i++) { + if (line_no_breaks_string [i - 1] == '.') { + if (char.IsLetterOrDigit (line_no_breaks_string [i]) == false && + "@/~".IndexOf (line_no_breaks_string [i].ToString ()) == -1) { + link_end = i - 1; + line_no_breaks_index = i; + break; + } + } else { + if (char.IsLetterOrDigit (line_no_breaks_string [i]) == false && + "@-/:~.?=_&".IndexOf (line_no_breaks_string [i].ToString ()) == -1) { + link_end = i - 1; + line_no_breaks_index = i; + break; + } + } + } + + string link_text = line_no_breaks_string.Substring (index_found, link_end - index_found + 1); + int current_cumulative = 0; + + // we've found a link - index_found -> link_end + // now we just make all the tags as containing link and + // point them to the text for the whole link + + current_line = start_line; + + //find the line we start on + for (current_cumulative = 1; current_cumulative < cumulative_length_list.Count; current_cumulative++) + if ((int)cumulative_length_list [current_cumulative] > index_found) + break; + + current_line = GetLine (start_line.LineNo + current_cumulative - 1); + + // find the tag we start on + LineTag current_tag = current_line.FindTag (index_found - (int)cumulative_length_list [current_cumulative - 1] + 1); + + if (current_tag.Start != (index_found - (int)cumulative_length_list [current_cumulative - 1]) + 1) { + if (current_tag == CaretTag) + update_caret_tag = true; + + current_tag = current_tag.Break ((index_found - (int)cumulative_length_list [current_cumulative - 1]) + 1); + } + + // set the tag + current_tag.IsLink = true; + current_tag.LinkText = link_text; + + //go through each character + // find the tag we are in + // skip the number of characters in the tag + for (int i = 1; i < link_text.Length; i++) { + // on to a new word-wrapped line + if ((int)cumulative_length_list [current_cumulative] <= index_found + i) { + + current_line = GetLine (start_line.LineNo + current_cumulative++); + current_tag = current_line.FindTag (index_found + i - (int)cumulative_length_list [current_cumulative - 1] + 1); + + current_tag.IsLink = true; + current_tag.LinkText = link_text; + + continue; + } + + if (current_tag.End < index_found + 1 + i - (int)cumulative_length_list [current_cumulative - 1]) { + // skip empty tags in the middle of the URL + do { + current_tag = current_tag.Next; + } while (current_tag.Length == 0); + + current_tag.IsLink = true; + current_tag.LinkText = link_text; + } + } + + //if there are characters left in the tag after the link + // split the tag + // make the second part a non link + if (current_tag.End > (index_found + link_text.Length + 1) - (int)cumulative_length_list [current_cumulative - 1]) { + if (current_tag == CaretTag) + update_caret_tag = true; + + current_tag.Break ((index_found + link_text.Length + 1) - (int)cumulative_length_list [current_cumulative - 1]); + } + } + + if (update_caret_tag) { + CaretTag = LineTag.FindTag (CaretLine, CaretPosition); + link_changed = true; + } else { + if (link_changed == false) { + current_line = start_line; + StringBuilder new_link_record = new StringBuilder (); + + while (current_line != null) { + current_line.LinkRecord (new_link_record); + + if (current_line.ending == LineEnding.Wrap) + current_line = GetLine (current_line.LineNo + 1); + else + break; + } + + if (new_link_record.Equals (line_link_record) == false) + link_changed = true; + } + } + } + + private int FirstIndexOfAny (string haystack, string [] needles, int start_index, out int term_found) + { + term_found = -1; + int best_index = -1; - end_line = GetLine(line.line_no + line_count -1); - if (end_line == null) { - end_line = line; + for (int i = 0; i < needles.Length; i++) { + int index = haystack.IndexOf (needles [i], start_index, StringComparison.InvariantCultureIgnoreCase); + + if (index > -1) { + if (term_found > -1) { + if (index < best_index) { + best_index = index; + term_found = i; + } + } else { + best_index = index; + term_found = i; + } } + } + + return best_index; + } + + + + private void InvalidateLinks (Rectangle clip) + { + for (int i = (owner.list_links.Count - 1); i >= 0; i--) { + TextBoxBase.LinkRectangle link = (TextBoxBase.LinkRectangle) owner.list_links [i]; -//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, viewport_width, end_line.Y + end_line.height)); + if (clip.IntersectsWith (link.LinkAreaRectangle)) + owner.list_links.RemoveAt (i); } } #endregion // Private Methods #region Internal Methods + + internal void ScanForLinks (int start, int end, ref bool link_changed) + { + Line line = null; + LineEnding lastending = LineEnding.Rich; + + // make sure we start scanning at the real begining of the line + while (true) { + if (start != 1 && GetLine (start - 1).ending == LineEnding.Wrap) + start--; + else + break; + } + + for (int i = start; i <= end && i <= lines; i++) { + line = GetLine (i); + + if (lastending != LineEnding.Wrap) + ScanForLinks (line, ref link_changed); + + lastending = line.ending; + + if (lastending == LineEnding.Wrap && (i + 1) <= end) + end++; + } + } + // Clear the document and reset state internal void Empty() { @@ -1494,7 +1211,7 @@ namespace System.Windows.Forms { lines = 0; // We always have a blank line - Add (1, String.Empty, owner.Font, ThemeEngine.Current.ResPool.GetSolidBrush (owner.ForeColor), LineEnding.None); + Add (1, String.Empty, owner.Font, owner.ForeColor, LineEnding.None); this.RecalculateDocument(owner.CreateGraphicsInternal()); PositionCaret(0, 0); @@ -1529,9 +1246,11 @@ namespace System.Windows.Forms { if (owner.IsHandleCreated) { if (owner.Focused) { - if (caret.height != caret.tag.height) + 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); + XplatUI.SetCaretPos(owner.Handle, + offset_x + (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x, + offset_y + caret.line.Y + caret.tag.Shift - viewport_y + caret_shift); } if (CaretMoved != null) CaretMoved(this, EventArgs.Empty); @@ -1539,7 +1258,7 @@ namespace System.Windows.Forms { // 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; + caret.height = caret.tag.Height; } @@ -1549,15 +1268,17 @@ namespace System.Windows.Forms { } caret.tag = FindCursor(x, y, out caret.pos); - + MoveCaretToTextTag (); - caret.line = caret.tag.line; - caret.height = caret.tag.height; + caret.line = caret.tag.Line; + caret.height = caret.tag.Height; 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); + XplatUI.SetCaretPos(owner.Handle, + (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x + offset_x, + offset_y + caret.line.Y + caret.tag.Shift - viewport_y + caret_shift); } if (CaretMoved != null) CaretMoved(this, EventArgs.Empty); @@ -1566,7 +1287,9 @@ namespace System.Windows.Forms { 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.X - viewport_x, caret.line.Y + caret.tag.shift - viewport_y + caret_shift); + XplatUI.SetCaretPos(owner.Handle, + offset_x + (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x, + offset_y + caret.line.Y + caret.tag.Shift - viewport_y + caret_shift); DisplayCaret (); } @@ -1583,20 +1306,38 @@ namespace System.Windows.Forms { XplatUI.DestroyCaret(owner.Handle); } - internal void AlignCaret() { + internal void AlignCaret () + { + AlignCaret (true); + } + + internal void AlignCaret(bool changeCaretTag) { if (!owner.IsHandleCreated) { return; } - caret.tag = LineTag.FindTag (caret.line, caret.pos); + if (changeCaretTag) { + caret.tag = LineTag.FindTag (caret.line, caret.pos); - MoveCaretToTextTag (); + MoveCaretToTextTag (); + } - caret.height = caret.tag.height; + // if the caret has had SelectionFont changed to a + // different height, we reflect changes unless the new + // font is larger than the line (line recalculations + // ignore empty tags) in which case we make it equal + // the line height and then when text is entered + if (caret.tag.Height > caret.tag.Line.Height) { + caret.height = caret.line.height; + } else { + 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.X - viewport_x, caret.line.Y + caret.tag.shift - viewport_y + caret_shift); + XplatUI.SetCaretPos (owner.Handle, + offset_x + (int) caret.tag.Line.widths [caret.pos] + caret.line.X - viewport_x, + offset_y + caret.line.Y + viewport_y + caret_shift); DisplayCaret (); } @@ -1610,17 +1351,20 @@ namespace System.Windows.Forms { MoveCaretToTextTag (); - if (caret.tag.height != caret.height) { - caret.height = caret.tag.height; + if (caret.tag.Height != caret.height) { + 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.X - viewport_x, caret.line.Y + caret.tag.shift - viewport_y + caret_shift); - - DisplayCaret (); - + if (owner.Focused) { + XplatUI.SetCaretPos(owner.Handle, + offset_x + (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x, + offset_y + caret.line.Y + caret.tag.Shift - viewport_y + caret_shift); + DisplayCaret (); + } + if (CaretMoved != null) CaretMoved(this, EventArgs.Empty); } @@ -1652,10 +1396,10 @@ namespace System.Windows.Forms { - if (caret.pos < caret.tag.start) { - caret.tag = caret.tag.previous; + if (caret.pos < caret.tag.Start) { + caret.tag = caret.tag.Previous; } else { - caret.tag = caret.tag.next; + caret.tag = caret.tag.Next; } } @@ -1685,8 +1429,8 @@ namespace System.Windows.Forms { caret.pos--; } } else { - if ((caret.tag.start - 1 + caret.tag.length) < caret.pos) { - caret.tag = caret.tag.next; + if ((caret.tag.Start - 1 + caret.tag.Length) < caret.pos) { + caret.tag = caret.tag.Next; } } UpdateCaret(); @@ -1701,8 +1445,8 @@ namespace System.Windows.Forms { // caret.pos--; // folded into the if below if (--caret.pos > 0) { - if (caret.tag.start > caret.pos) { - caret.tag = caret.tag.previous; + if (caret.tag.Start > caret.pos) { + caret.tag = caret.tag.Previous; } } } else { @@ -1808,8 +1552,8 @@ namespace System.Windows.Forms { } 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(); } @@ -1818,56 +1562,38 @@ namespace System.Windows.Forms { 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 (caret.line.line_no == 1 && 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 (caret.line.line_no == lines && owner.richtext) { owner.vscroll.Value = owner.vscroll.Maximum - viewport_height + 1; Line line = GetLine (lines); - PositionCaret (line, line.Text.Length); + PositionCaret (line, line.TextLengthWithoutEnding()); } - 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; } @@ -1883,11 +1609,11 @@ namespace System.Windows.Forms { LineTag tag; int index; - tag = FindTag(0, viewport_y + viewport_height, out index, false); - if (tag.line.line_no > 1) { - line = GetLine(tag.line.line_no - 1); + tag = FindCursor (0, viewport_y + viewport_height, out index); + if (tag.Line.line_no > 1) { + line = GetLine(tag.Line.line_no - 1); } else { - line = tag.line; + line = tag.Line; } PositionCaret(line, line.Text.Length); DisplayCaret (); @@ -1905,7 +1631,7 @@ namespace System.Windows.Forms { 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(); @@ -1942,16 +1668,32 @@ namespace System.Windows.Forms { LineTag tag = line.tags; while (tag != null) { Console.Write ("\t", - tag.GetType (), tag.start, tag.length, tag.font, tag.color.Color); + tag.GetType (), tag.Start, tag.Length, tag.Font, tag.Color); Console.Write (tag.Text ()); Console.WriteLine (""); - tag = tag.next; + tag = tag.Next; } Console.WriteLine (""); } Console.WriteLine (""); } + // UIA: Used via reflection by TextProviderBehavior + internal void GetVisibleLineIndexes (Rectangle clip, out int start, out int end) + { + if (multiline) { + /* Expand the region slightly to be sure to + * paint the full extent of the line of text. + * See bug 464464. + */ + start = GetLineByPixel(clip.Top + viewport_y - offset_y - 1, false).line_no; + end = GetLineByPixel(clip.Bottom + viewport_y - offset_y + 1, false).line_no; + } else { + start = GetLineByPixel(clip.Left + viewport_x - offset_x, false).line_no; + end = GetLineByPixel(clip.Right + viewport_x - offset_x, false).line_no; + } + } + internal void Draw (Graphics g, Rectangle clip) { Line line; // Current line being drawn @@ -1960,34 +1702,26 @@ namespace System.Windows.Forms { int end; // Last line to draw StringBuilder text; // String representing the current line int line_no; - Brush tag_brush; - Brush current_brush; - Brush disabled_brush; - Brush readonly_brush; - Brush hilight; - Brush hilight_text; + Color tag_color; + Color current_color; // First, figure out from what line to what line we need to draw + GetVisibleLineIndexes (clip, out start, out end); - 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; - } + // remove links in the list (used for mouse down events) that are within the clip area. + InvalidateLinks (clip); /// /// 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 line = GetLine (end - 1); - if (line != null && clip.Bottom == line.Y + line.height + viewport_y) - end--; + if (line != null && clip.Bottom == offset_y + line.Y + line.height - viewport_y) + end--; line_no = start; @@ -1999,17 +1733,12 @@ namespace System.Windows.Forms { Console.WriteLine ("E: {0}", GetLine (end).text); #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] + + g.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (ThemeEngine.Current.ColorHighlight), + offset_x + selection_start.line.widths [selection_start.pos] + selection_start.line.X - viewport_x, - selection_start.line.Y, + offset_y + 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); @@ -2017,7 +1746,7 @@ namespace System.Windows.Forms { while (line_no <= end) { line = GetLine (line_no); - float line_y = line.Y - viewport_y; + float line_y = line.Y - viewport_y + offset_y; tag = line.tags; if (!calc_pass) { @@ -2052,69 +1781,77 @@ namespace System.Windows.Forms { line_selection_end = line_selection_start; } 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.X - viewport_x, + g.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (ThemeEngine.Current.ColorHighlight), + offset_x + 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; + current_color = line.tags.ColorToDisplay; while (tag != null) { // Skip empty tags - if (tag.length == 0) { - tag = tag.next; + if (tag.Length == 0) { + tag = tag.Next; continue; } - if (((tag.X + tag.width) < (clip.Left - viewport_x)) && (tag.X > (clip.Right - viewport_x))) { - tag = tag.next; + if (((tag.X + tag.Width) < (clip.Left - viewport_x - offset_x)) && + (tag.X > (clip.Right - viewport_x - offset_x))) { + tag = tag.Next; continue; } - if (tag.back_color != null) { - g.FillRectangle (tag.back_color, tag.X + line.X - viewport_x, - line_y + tag.shift, tag.width, line.height); + if (tag.BackColor != Color.Empty) { + g.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (tag.BackColor), + offset_x + tag.X + line.X - viewport_x, + line_y + tag.Shift, tag.Width, line.height); } - tag_brush = tag.color; - current_brush = tag_brush; + tag_color = tag.ColorToDisplay; + current_color = tag_color; - if (!owner.is_enabled) { - Color a = ((SolidBrush) tag.color).Color; + if (!owner.Enabled) { + Color a = tag.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; - } + if ((a.R == b.R) && (a.G == b.G) && (a.B == b.B)) + tag_color = ThemeEngine.Current.ColorGrayText; + + } - int tag_pos = tag.start; - current_brush = tag_brush; - while (tag_pos < tag.start + tag.length) { + int tag_pos = tag.Start; + current_color = tag_color; + while (tag_pos < tag.Start + tag.Length) { int old_tag_pos = tag_pos; if (tag_pos >= line_selection_start && tag_pos < line_selection_end) { - current_brush = hilight_text; - tag_pos = Math.Min (tag.end, line_selection_end); + current_color = ThemeEngine.Current.ColorHighlightText; + tag_pos = Math.Min (tag.End, line_selection_end); } else if (tag_pos < line_selection_start) { - current_brush = tag_brush; - tag_pos = Math.Min (tag.end, line_selection_start); + current_color = tag_color; + tag_pos = Math.Min (tag.End, line_selection_start); } else { - current_brush = tag_brush; - tag_pos = tag.end; + current_color = tag_color; + tag_pos = tag.End; } - tag.Draw (g, current_brush, - line.widths [Math.Max (0, old_tag_pos - 1)] + line.X - viewport_x, - line_y + tag.shift, - old_tag_pos - 1, Math.Min (tag.length, tag_pos - old_tag_pos), - text.ToString() ); + Rectangle text_size; + + tag.Draw (g, current_color, + offset_x + line.X - viewport_x, + line_y + tag.Shift, + old_tag_pos - 1, Math.Min (tag.Start + tag.Length, tag_pos) - 1, + text.ToString (), out text_size, tag.IsLink); + + if (tag.IsLink) { + TextBoxBase.LinkRectangle link = new TextBoxBase.LinkRectangle (text_size); + link.LinkTag = tag; + owner.list_links.Add (link); + } } - tag = tag.next; + tag = tag.Next; } line.DrawEnding (g, line_y); @@ -2122,11 +1859,26 @@ namespace System.Windows.Forms { } } - internal int GetLineEnding (string line, int start, out LineEnding ending) + private int GetLineEnding (string line, int start, out LineEnding ending) { int res; + int rich_index; + if (start >= line.Length) { + ending = LineEnding.Wrap; + return -1; + } + res = line.IndexOf ('\r', start); + rich_index = line.IndexOf ('\n', start); + + // Handle the case where we find both of them, and the \n is before the \r + if (res != -1 && rich_index != -1) + if (rich_index < res) { + ending = LineEnding.Rich; + return rich_index; + } + if (res != -1) { if (res + 2 < line.Length && line [res + 1] == '\r' && line [res + 2] == '\n') { ending = LineEnding.Soft; @@ -2140,211 +1892,190 @@ namespace System.Windows.Forms { return res; } - res = line.IndexOf ('\n', start); - if (res != -1) { + if (rich_index != -1) { ending = LineEnding.Rich; - return res; + return rich_index; } ending = LineEnding.Wrap; return line.Length; } - internal int LineEndingLength (LineEnding ending) + // Get the line ending, but only of the types specified + private int GetLineEnding (string line, int start, out LineEnding ending, LineEnding type) { - int res = 0; + int index = start; + int last_length = 0; + do { + index = GetLineEnding (line, index + last_length, out ending); + last_length = LineEndingLength (ending); + } while + ((ending & type) != ending && index != -1); + + return index == -1 ? line.Length : index; + } + + internal int LineEndingLength (LineEnding ending) + { switch (ending) { - case LineEnding.Limp: - case LineEnding.Rich: - res = 1; - break; - case LineEnding.Hard: - res = 2; - break; - case LineEnding.Soft: - res = 3; - break; + case LineEnding.Limp: + case LineEnding.Rich: + return 1; + case LineEnding.Hard: + return 2; + case LineEnding.Soft: + return 3; } - return res; + return 0; } 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; + case LineEnding.Limp: + return "\r"; + case LineEnding.Hard: + return "\r\n"; + case LineEnding.Soft: + return "\r\r\n"; + case LineEnding.Rich: + return "\n"; + } + + return string.Empty; } + internal LineEnding StringToLineEnding (string ending) + { + switch (ending) { + case "\r": + return LineEnding.Limp; + case "\r\n": + return LineEnding.Hard; + case "\r\r\n": + return LineEnding.Soft; + case "\n": + return LineEnding.Rich; + default: + return LineEnding.None; + } + } - // 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) { + internal void Insert (Line line, int pos, bool update_caret, string s) + { + Insert (line, pos, update_caret, s, line.FindTag (pos)); + } + + // Insert 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, LineTag tag) + { int break_index; int base_line; int old_line_count; int count = 1; LineEnding ending; - LineTag tag = LineTag.FindTag (line, pos); + Line split_line; + // Don't recalculate while we mess around SuspendRecalc (); base_line = line.line_no; old_line_count = lines; - break_index = GetLineEnding (s, 0, out ending); + // Discard chars after any possible -unlikely- end of file + int eof_index = s.IndexOf ('\0'); + if (eof_index != -1) + s = s.Substring (0, eof_index); + + break_index = GetLineEnding (s, 0, out ending, LineEnding.Hard | LineEnding.Rich); - // Bump the text at insertion point a line down if we're inserting more than one line - if (break_index != s.Length) { - Split (line, pos); + // There are no line feeds in our text to be pasted + if (break_index == s.Length) { + line.InsertString (pos, s, tag); + } else { + // Add up to the first line feed to our current position + line.InsertString (pos, s.Substring (0, break_index + LineEndingLength (ending)), tag); + + // Split the rest of the original line to a new line + Split (line, pos + (break_index + LineEndingLength (ending))); line.ending = ending; - // Remainder of start line is now in base_line + 1 - } + break_index += LineEndingLength (ending); + split_line = GetLine (line.line_no + 1); + + // Insert brand new lines for any more line feeds in the inserted string + while (true) { + int next_break = GetLineEnding (s, break_index, out ending, LineEnding.Hard | LineEnding.Rich); + + if (next_break == s.Length) + break; + + string line_text = s.Substring (break_index, next_break - break_index + + LineEndingLength (ending)); - InsertString (line, pos, s.Substring (0, break_index + LineEndingLength (ending))); - - break_index += LineEndingLength (ending); - while (break_index < s.Length) { - int next_break = GetLineEnding (s, break_index, out ending); - string line_text = s.Substring (break_index, next_break - break_index + - LineEndingLength (ending)); + Add (base_line + count, line_text, line.alignment, tag.Font, tag.Color, ending); - Add (base_line + count, line_text, line.alignment, tag.font, tag.color, ending); + Line last = GetLine (base_line + count); + last.ending = ending; - Line last = GetLine (base_line + count); - last.ending = ending; + count++; + break_index = next_break + LineEndingLength (ending); + } - count++; - break_index = next_break + LineEndingLength (ending); + // Add the remainder of the insert text to the split + // part of the original line + split_line.InsertString (0, s.Substring (break_index)); } + + // Allow the document to recalculate things + ResumeRecalc (false); - ResumeRecalc (true); + // Update our character count + CharCount += s.Length; - UpdateView(line, lines - old_line_count + 1, pos); + UpdateView (line, lines - old_line_count + 1, pos); + // Move the caret to the end of the inserted text if requested if (update_caret) { - // Move caret to the end of the inserted text Line l = GetLine (line.line_no + lines - old_line_count); - PositionCaret(l, l.text.Length); + PositionCaret (l, l.text.Length); DisplayCaret (); } } - // Inserts a character at the given position - internal void InsertString(Line line, int pos, string s) { - InsertString(line.FindTag(pos), pos, s); - } - // Inserts a string at the given position - internal void InsertString(LineTag tag, int pos, string s) { - Line line; - int len; - - len = s.Length; - - CharCount += len; - - line = tag.line; - line.text.Insert(pos, s); - - tag = tag.next; - while (tag != null) { - tag.start += len; - tag = tag.next; - } - line.Grow(len); - line.recalc = true; - - UpdateView(line, pos); - } - - // Inserts a string at the caret position - internal void InsertStringAtCaret(string s, bool move_caret) { - - InsertString (caret.tag, caret.pos, s); - - UpdateView(caret.line, caret.pos); - if (move_caret) { - caret.pos += s.Length; - UpdateCaret(); - } - } - - - - // Inserts a character at the given position - internal void InsertChar(Line line, int pos, char ch) { - InsertChar(line.FindTag(pos), pos, ch); - } - - // Inserts a character at the given position - internal void InsertChar(LineTag tag, int pos, char ch) { - Line line; - - CharCount++; - - line = tag.line; - line.text.Insert(pos, ch); - - tag = tag.next; - while (tag != null) { - tag.start++; - tag = tag.next; - } - line.Grow(1); - line.recalc = true; + internal void InsertString (Line line, int pos, string s) + { + // Update our character count + CharCount += s.Length; - undo.RecordTyping (line, pos, ch); - UpdateView(line, pos); + // Insert the text into the Line + line.InsertString (pos, s); } // Inserts a character at the current caret position - internal void InsertCharAtCaret(char ch, bool move_caret) { - /* - LineTag tag; + internal void InsertCharAtCaret (char ch, bool move_caret) + { + caret.line.InsertString (caret.pos, ch.ToString(), caret.tag); + // Update our character count CharCount++; - - caret.line.text.Insert(caret.pos, ch); - caret.tag.length++; - if (caret.tag.next != null) { - tag = caret.tag.next; - while (tag != null) { - tag.start++; - tag = tag.next; - } - } - caret.line.Grow(1); - caret.line.recalc = true; - */ - InsertChar (caret.tag, caret.pos, ch); + undo.RecordTyping (caret.line, caret.pos, ch); - UpdateView(caret.line, caret.pos); + UpdateView (caret.line, caret.pos); + if (move_caret) { caret.pos++; - UpdateCaret(); - SetSelectionToCaret(true); + UpdateCaret (); + SetSelectionToCaret (true); } - } internal void InsertPicture (Line line, int pos, RTF.Picture picture) { - LineTag next_tag; + //LineTag next_tag; LineTag tag; int len; @@ -2357,24 +2088,24 @@ namespace System.Windows.Forms { 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; + /*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; + 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; + tag = picture_tag.Next; while (tag != null) { - tag.start += len; - tag = tag.next; + tag.Start += len; + tag = tag.Next; } line.Grow (len); @@ -2399,12 +2130,12 @@ namespace System.Windows.Forms { SuspendUpdate (); if (start.line == end.line) { - DeleteChars (start.tag, pos, end.pos - pos); + DeleteChars (start.line, pos, end.pos - pos); } else { // Delete first and last lines - DeleteChars (start.tag, start.pos, start.line.text.Length - start.pos); - DeleteChars (end.line.tags, 0, end.pos); + DeleteChars (start.line, start.pos, start.line.text.Length - start.pos); + DeleteChars (end.line, 0, end.pos); int current = start.line.line_no + 1; if (current < end.line.line_no) { @@ -2426,88 +2157,17 @@ namespace System.Windows.Forms { // Deletes n characters at the given position; it will not delete past line limits // pos is 0-based - internal void DeleteChars(LineTag tag, int pos, int count) { - Line line; - bool streamline; - - streamline = false; - line = tag.line; - + public void DeleteChars (Line line, int pos, int count) + { + // Reduce our character count CharCount -= count; + + line.DeleteCharacters (pos, count); - if (pos == line.text.Length) { - return; - } - - line.text.Remove(pos, count); - - // Make sure the tag points to the right spot - while ((tag != null) && (tag.end) < pos) { - tag = tag.next; - } - - if (tag == null) { - goto Cleanup; - } - - // Check if we're crossing tag boundaries - if ((pos + count) > (tag.start + tag.length - 1)) { - int left; - - // We have to delete cross tag boundaries - streamline = true; - left = count; - - left -= tag.start + tag.length - pos - 1; - - tag = tag.next; - while ((tag != null) && (left > 0)) { - tag.start -= count - left; - - if (tag.length > left) { - left = 0; - } else { - left -= tag.length; - tag = tag.next; - } - - } - } else { - // We got off easy, same tag - - if (tag.length == 0) { - streamline = true; - } - } - - // Delete empty orphaned tags at the end - LineTag walk = tag; - while (walk != null && walk.next != null && walk.next.length == 0) { - LineTag t = walk; - walk.next = walk.next.next; - if (walk.next != null) - walk.next.previous = t; - walk = walk.next; - } - - // Adjust the start point of any tags following - if (tag != null) { - tag = tag.next; - while (tag != null) { - tag.start -= count; - tag = tag.next; - } - } - - line.recalc = true; - if (streamline) { - line.Streamline(lines); - } - - Cleanup: if (pos >= line.TextLengthWithoutEnding ()) { LineEnding ending = line.ending; GetLineEnding (line.text.ToString (), 0, out ending); + if (ending != line.ending) { line.ending = ending; @@ -2522,95 +2182,26 @@ namespace System.Windows.Forms { UpdateView (line, lines, pos); owner.Invalidate (); } else - UpdateView(line, pos); + UpdateView (line, pos); } // Deletes a character at or after the given position (depending on forward); it will not delete past line limits - internal void DeleteChar(LineTag tag, int pos, bool forward) { - Line line; - bool streamline; - - CharCount--; - - streamline = false; - line = tag.line; - - if ((pos == 0 && forward == false) || (pos == line.text.Length && forward == true)) { + public void DeleteChar (Line line, int pos, bool forward) + { + if ((pos == 0 && forward == false) || (pos == line.text.Length && forward == true)) return; - } - + + undo.BeginUserAction ("Delete"); if (forward) { - line.text.Remove(pos, 1); - - while ((tag != null) && (tag.start + tag.length - 1) <= pos) { - tag = tag.next; - } - - if (tag == null) { - goto Cleanup; - } - - // tag.length--; - - if (tag.length == 0) { - streamline = true; - } + undo.RecordDeleteString (line, pos, line, pos + 1); + DeleteChars (line, pos, 1); } else { - pos--; - line.text.Remove(pos, 1); - if (pos >= (tag.start - 1)) { - // tag.length--; - if (tag.length == 0) { - streamline = true; - } - } else if (tag.previous != null) { - // tag.previous.length--; - if (tag.previous.length == 0) { - streamline = true; - } - } - } - - // Delete empty orphaned tags at the end - LineTag walk = tag; - while (walk != null && walk.next != null && walk.next.length == 0) { - LineTag t = walk; - walk.next = walk.next.next; - if (walk.next != null) - walk.next.previous = t; - walk = walk.next; - } - - tag = tag.next; - while (tag != null) { - tag.start--; - tag = tag.next; - } - line.recalc = true; - if (streamline) { - line.Streamline(lines); + undo.RecordDeleteString (line, pos - 1, line, pos); + DeleteChars (line, pos - 1, 1); } - 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); + undo.EndUserAction (); } // Combine two lines @@ -2631,21 +2222,21 @@ namespace System.Windows.Forms { // Maintain the line ending style first.ending = second.ending; - while (last.next != null) { - last = last.next; + 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; + last.Next = second.tags; + last.Next.Previous = last; // Fix up references within the chain - last = last.next; + last = last.Next; while (last != null) { - last.line = first; - last.start += shift; - last = last.next; + last.Line = first; + last.Start += shift; + last = last.Next; } // Combine both lines' strings @@ -2715,7 +2306,7 @@ namespace System.Windows.Forms { } ///Split line at given tag and position into two lines - ///if more space becomes available on previous line + ///if more space becomes available on previous line internal void Split(Line line, LineTag tag, int pos) { LineTag new_tag; Line new_line; @@ -2727,6 +2318,13 @@ namespace System.Windows.Forms { move_sel_start = false; move_sel_end = false; +#if DEBUG + SanityCheck(); + + if (tag.End < pos) + throw new Exception ("Split called with the wrong tag"); +#endif + // Adjust selection and cursors if (caret.line == line && caret.pos >= pos) { move_caret = true; @@ -2741,7 +2339,7 @@ namespace System.Windows.Forms { // cover the easy case first if (pos == line.text.Length) { - Add (line.line_no + 1, String.Empty, line.alignment, tag.font, tag.color, line.ending); + Add (line.line_no + 1, String.Empty, line.alignment, tag.Font, tag.Color, line.ending); new_line = GetLine (line.line_no + 1); @@ -2749,6 +2347,10 @@ namespace System.Windows.Forms { caret.line = new_line; caret.tag = new_line.tags; caret.pos = 0; + + if (selection_visible == false) { + SetSelectionToCaret (true); + } } if (move_sel_start) { @@ -2762,11 +2364,15 @@ namespace System.Windows.Forms { selection_end.pos = 0; selection_end.tag = new_line.tags; } + +#if DEBUG + SanityCheck (); +#endif return; } // 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, line.ending); + 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); @@ -2774,50 +2380,59 @@ namespace System.Windows.Forms { line.recalc = true; new_line.recalc = true; - if ((tag.start - 1) == pos) { + //make sure that if we are at the end of a tag, we start on the begining + //of a new one, if one exists... Stops us creating an empty tag and + //make the operation easier. + if (tag.Next != null && (tag.Next.Start - 1) == pos) + tag = tag.Next; + + if ((tag.Start - 1) == pos) { int shift; // We can simply break the chain and move the tag into the next line + + // if the tag we are moving is the first, create an empty tag + // for the line we are leaving behind if (tag == line.tags) { new_tag = new LineTag(line, 1); new_tag.CopyFormattingFrom (tag); line.tags = new_tag; } - if (tag.previous != null) { - tag.previous.next = null; + if (tag.Previous != null) { + tag.Previous.Next = null; } new_line.tags = tag; - tag.previous = null; - tag.line = new_line; + tag.Previous = null; + tag.Line = new_line; // Walk the list and correct the start location of the tags we just bumped into the next line - shift = tag.start - 1; + shift = tag.Start - 1; new_tag = tag; while (new_tag != null) { - new_tag.start -= shift; - new_tag.line = new_line; - new_tag = new_tag.next; + new_tag.Start -= shift; + new_tag.Line = new_line; + new_tag = new_tag.Next; } } else { int shift; new_tag = new LineTag (new_line, 1); - new_tag.next = tag.next; + new_tag.Next = tag.Next; new_tag.CopyFormattingFrom (tag); new_line.tags = new_tag; - if (new_tag.next != null) { - new_tag.next.previous = new_tag; + if (new_tag.Next != null) { + new_tag.Next.Previous = new_tag; } - tag.next = null; + tag.Next = null; shift = pos; - new_tag = new_tag.next; + new_tag = new_tag.Next; while (new_tag != null) { - new_tag.start -= shift; - new_tag.line = new_line; - new_tag = new_tag.next; + new_tag.Start -= shift; + new_tag.Line = new_line; + new_tag = new_tag.Next; } } @@ -2826,12 +2441,19 @@ namespace System.Windows.Forms { caret.line = new_line; caret.pos = caret.pos - pos; caret.tag = caret.line.FindTag(caret.pos); + + if (selection_visible == false) { + SetSelectionToCaret (true); + } } if (move_sel_start) { selection_start.line = new_line; selection_start.pos = selection_start.pos - pos; - selection_start.tag = new_line.FindTag(selection_start.pos); + if (selection_start.Equals(selection_end)) + selection_start.tag = new_line.FindTag(selection_start.pos); + else + selection_start.tag = new_line.FindTag (selection_start.pos + 1); } if (move_sel_end) { @@ -2842,16 +2464,44 @@ namespace System.Windows.Forms { CharCount -= line.text.Length - pos; line.text.Remove(pos, line.text.Length - pos); +#if DEBUG + SanityCheck (); +#endif + } + +#if DEBUG + private void SanityCheck () { + for (int i = 1; i < lines; i++) { + LineTag tag = GetLine (i).tags; + + if (tag.Start != 1) + throw new Exception ("Line doesn't start at the begining"); + + int start = 1; + tag = tag.Next; + + while (tag != null) { + if (tag.Start == start) + throw new Exception ("Empty tag!"); + + if (tag.Start < start) + throw new Exception ("Insane!!"); + + start = tag.Start; + tag = tag.Next; + } + } } +#endif // 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, LineEnding ending) + internal void Add (int LineNo, string Text, Font font, Color color, LineEnding ending) { Add (LineNo, Text, alignment, font, color, ending); } - internal void Add (int LineNo, string Text, HorizontalAlignment align, Font font, SolidBrush color, LineEnding ending) + internal void Add (int LineNo, string Text, HorizontalAlignment align, Font font, Color color, LineEnding ending) { Line add; Line line; @@ -2921,22 +2571,22 @@ namespace System.Windows.Forms { return clone; } - internal void Delete(int LineNo) { + private void Delete (int LineNo) + { Line line; - if (LineNo>lines) { + if (LineNo > lines) return; - } - line = GetLine(LineNo); + line = GetLine (LineNo); CharCount -= line.text.Length; - DecrementLines(LineNo + 1); - Delete(line); + DecrementLines (LineNo + 1); + Delete (line); } - internal void Delete(Line line1) { + private void Delete(Line line1) { Line line2;// = new Line(); Line line3; @@ -3003,8 +2653,8 @@ namespace System.Windows.Forms { tag = line1.tags; while (tag != null) { - tag.line = line1; - tag = tag.next; + tag.Line = line1; + tag = tag.Next; } } @@ -3014,6 +2664,11 @@ namespace System.Windows.Forms { this.lines--; } + // Invalidates the start line until the end of the viewstate + internal void InvalidateLinesAfter (Line start) { + owner.Invalidate (new Rectangle (0, start.Y - viewport_y, viewport_width, viewport_height - start.Y)); + } + // Invalidate a section of the document to trigger redraw internal void Invalidate(Line start, int start_pos, Line end, int end_pos) { Line l1; @@ -3075,9 +2730,9 @@ namespace System.Windows.Forms { #endif owner.Invalidate(new Rectangle ( - (int)l1.widths[p1] + l1.X - viewport_x, - l1.Y - viewport_y, - endpoint - (int)l1.widths[p1] + 1, + offset_x + (int)l1.widths[p1] + l1.X - viewport_x, + offset_y + l1.Y - viewport_y, + endpoint - (int) l1.widths [p1] + 1, l1.height)); return; } @@ -3089,7 +2744,11 @@ namespace System.Windows.Forms { // Three invalidates: // First line from start - owner.Invalidate(new Rectangle((int)l1.widths[p1] + l1.X - viewport_x, l1.Y - viewport_y, viewport_width, l1.height)); + owner.Invalidate(new Rectangle( + offset_x + (int)l1.widths[p1] + l1.X - viewport_x, + offset_y + l1.Y - viewport_y, + viewport_width, + l1.height)); // lines inbetween @@ -3097,7 +2756,11 @@ namespace System.Windows.Forms { int y; y = GetLine(l1.line_no + 1).Y; - owner.Invalidate(new Rectangle(0, y - viewport_y, viewport_width, l2.Y - y)); + owner.Invalidate(new Rectangle( + offset_x, + offset_y + y - viewport_y, + viewport_width, + l2.Y - y)); #if Debug Console.WriteLine("Invaliding from {0}:{1} to {2}:{3} Middle => x={4}, y={5}, {6}x{7}", l1.line_no, p1, l2.line_no, p2, 0, y - viewport_y, viewport_width, l2.Y - y); @@ -3106,10 +2769,14 @@ namespace System.Windows.Forms { // Last line to end - owner.Invalidate(new Rectangle((int)l2.widths[0] + l2.X - viewport_x, l2.Y - viewport_y, (int)l2.widths[p2] + 1, l2.height)); + owner.Invalidate(new Rectangle( + offset_x + (int)l2.widths[0] + l2.X - viewport_x, + offset_y + 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.X - viewport_x, l2.Y - viewport_y, (int)l2.widths[p2] + 1, l2.height); - #endif } @@ -3139,7 +2806,7 @@ namespace System.Windows.Forms { } else { selection_start.line = selection_anchor.line; selection_start.pos = selection_anchor.height; - selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height); + selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height + 1); selection_end.line = caret.line; selection_end.tag = caret.line.tags; @@ -3170,7 +2837,7 @@ namespace System.Windows.Forms { } if (caret < selection_anchor) { selection_start.line = caret.line; - selection_start.tag = caret.line.FindTag(start_pos); + selection_start.tag = caret.line.FindTag(start_pos + 1); selection_start.pos = start_pos; selection_end.line = selection_anchor.line; @@ -3185,7 +2852,7 @@ namespace System.Windows.Forms { } else { selection_start.line = selection_anchor.line; selection_start.pos = selection_anchor.height; - selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height); + selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height + 1); selection_end.line = caret.line; selection_end.tag = caret.line.FindTag(end_pos); @@ -3243,7 +2910,7 @@ namespace System.Windows.Forms { this.Invalidate(selection_start.line, start_pos, caret.line, end_pos); selection_start.line = caret.line; - selection_start.tag = caret.line.FindTag(start_pos); + selection_start.tag = caret.line.FindTag(start_pos + 1); selection_start.pos = start_pos; selection_end.line = caret.line; @@ -3503,11 +3170,11 @@ namespace System.Windows.Forms { start = selection_start.line.line_no; end = selection_end.line.line_no; - sb.Append(selection_start.line.text.ToString(selection_start.pos, selection_start.line.text.Length - selection_start.pos) + Environment.NewLine); + sb.Append(selection_start.line.text.ToString(selection_start.pos, selection_start.line.text.Length - selection_start.pos)); if ((start + 1) < end) { for (i = start + 1; i < end; i++) { - sb.Append(GetLine(i).text.ToString() + Environment.NewLine); + sb.Append(GetLine(i).text.ToString()); } } @@ -3520,7 +3187,6 @@ namespace System.Windows.Forms { internal void ReplaceSelection(string s, bool select_new) { int i; - int selection_pos_on_line = selection_start.pos; int selection_start_pos = LineTagToCharIndex (selection_start.line, selection_start.pos); SuspendRecalc (); @@ -3529,10 +3195,10 @@ namespace System.Windows.Forms { 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); + DeleteChars (selection_start.line, selection_start.pos, selection_end.pos - selection_start.pos); // The tag might have been removed, we need to recalc it - selection_start.tag = selection_start.line.FindTag(selection_start.pos); + selection_start.tag = selection_start.line.FindTag(selection_start.pos + 1); } else { int start; int end; @@ -3542,14 +3208,14 @@ namespace System.Windows.Forms { undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos); - InvalidateSelectionArea (); + InvalidateLinesAfter(selection_start.line); // Delete first line - DeleteChars(selection_start.tag, selection_start.pos, selection_start.line.text.Length - selection_start.pos); + DeleteChars (selection_start.line, 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); + DeleteChars(selection_end.line, 0, selection_end.pos); start++; if (start < end) { @@ -3571,6 +3237,9 @@ namespace System.Windows.Forms { undo.RecordInsertString (selection_start.line, selection_start.pos, s); ResumeRecalc (false); + Line begin_update_line = selection_start.line; + int begin_update_pos = selection_start.pos; + if (!select_new) { CharIndexToLineTag(selection_start_pos + s.Length, out selection_start.line, out selection_start.tag, out selection_start.pos); @@ -3598,7 +3267,7 @@ namespace System.Windows.Forms { } PositionCaret (selection_start.line, selection_start.pos); - UpdateView (selection_start.line, selection_pos_on_line); + UpdateView (begin_update_line, selection_end.line.line_no - begin_update_line.line_no, begin_update_pos); } internal void CharIndexToLineTag(int index, out Line line_out, out LineTag tag_out, out int pos) { @@ -3614,20 +3283,20 @@ namespace System.Windows.Forms { line = GetLine(i); start = chars; - chars += line.text.Length + LineEndingLength (line.ending); + 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; return; } - if (tag.next == null) { + if (tag.Next == null) { Line next_line; next_line = GetLine(line.line_no + 1); @@ -3644,15 +3313,15 @@ namespace System.Windows.Forms { return; } } - tag = tag.next; + tag = tag.Next; } } } line_out = GetLine(lines); tag = line_out.tags; - while (tag.next != null) { - tag = tag.next; + while (tag.Next != null) { + tag = tag.Next; } tag_out = tag; pos = line_out.text.Length; @@ -3668,7 +3337,7 @@ namespace System.Windows.Forms { // Count the lines in the middle for (i = 1; i < line.line_no; i++) { - length += GetLine(i).text.Length + LineEndingLength (line.ending); + length += GetLine(i).text.Length; } length += pos; @@ -3710,6 +3379,8 @@ namespace System.Windows.Forms { } + // UIA: Method used via reflection in TextRangeProvider + /// Give it a Line number and it returns the Line object at with that line number internal Line GetLine(int LineNo) { Line line = document; @@ -3731,22 +3402,22 @@ namespace System.Windows.Forms { internal LineTag PreviousTag(LineTag tag) { Line l; - if (tag.previous != null) { - return tag.previous; + if (tag.Previous != null) { + return tag.Previous; } // Next line - if (tag.line.line_no == 1) { + if (tag.Line.line_no == 1) { return null; } - l = GetLine(tag.line.line_no - 1); + l = GetLine(tag.Line.line_no - 1); if (l != null) { LineTag t; t = l.tags; - while (t.next != null) { - t = t.next; + while (t.Next != null) { + t = t.Next; } return t; } @@ -3758,12 +3429,12 @@ namespace System.Windows.Forms { internal LineTag NextTag(LineTag tag) { Line l; - if (tag.next != null) { - return tag.next; + if (tag.Next != null) { + return tag.Next; } // Next line - l = GetLine(tag.line.line_no + 1); + l = GetLine(tag.Line.line_no + 1); if (l != null) { return l.tags; } @@ -3772,9 +3443,15 @@ namespace System.Windows.Forms { } internal Line ParagraphStart(Line line) { - while (line.ending == LineEnding.Wrap) { - line = GetLine(line.line_no - 1); - } + Line lastline = line; + do { + if (line.line_no <= 1) + break; + + line = lastline; + lastline = GetLine (line.line_no - 1); + } while (lastline.ending == LineEnding.Wrap); + return line; } @@ -3828,95 +3505,33 @@ namespace System.Windows.Forms { return last; } - // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index - internal LineTag FindTag(int x, int y, out int index, bool exact) { - Line line; - LineTag tag; - - line = GetLineByPixel(y, exact); - if (line == null) { - index = 0; - return null; - } - tag = line.tags; - - // Alignment adjustment - x += line.X; - - while (true) { - if (x >= tag.X && x < (tag.X+tag.width)) { - int end; - - end = tag.start + tag.length - 1; - - for (int pos = tag.start; pos < end; pos++) { - if (x < line.widths[pos]) { - index = pos; - return LineTag.GetFinalTag (tag); - } - } - index=end; - return LineTag.GetFinalTag (tag); - } - if (tag.next != null) { - tag = tag.next; - } else { - if (exact) { - index = 0; - return null; - } - - index = line.text.Length; - return LineTag.GetFinalTag (tag); - } - } - } - - // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index - internal LineTag FindCursor(int x, int y, out int index) { - Line line; - LineTag tag; - - 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; + // UIA: Method used via reflection in TextProviderBehavior - end = tag.TextEnd; - - 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.X + line.widths[pos] + ((line.widths[pos+1]-line.widths[pos])/2))) { - index = pos; - return LineTag.GetFinalTag (tag); - } - } - index=end; - return LineTag.GetFinalTag (tag); - } - if (tag.next != null) { - tag = tag.next; - } else { - index = line.TextLengthWithoutEnding (); - return LineTag.GetFinalTag (tag); - } - } + // Give it x/y pixel coordinates and it returns the Tag at that position + internal LineTag FindCursor (int x, int y, out int index) + { + Line line; + + x -= offset_x; + y -= offset_y; + + line = GetLineByPixel (multiline ? y : x, false); + + LineTag tag = line.GetTag (x); + + if (tag.Length == 0 && tag.Start == 1) + index = 0; + else + index = tag.GetCharIndex (x - line.align_shift); + + return tag; } /// Format area of document in specified font and color /// 1-based start position on start_line /// 1-based end position on end_line internal void FormatText (Line start_line, int start_pos, Line end_line, int end_pos, Font font, - SolidBrush color, SolidBrush back_color, FormatSpecified specified) + Color color, Color back_color, FormatSpecified specified) { Line l; @@ -3936,34 +3551,9 @@ namespace System.Windows.Forms { } else { // Special case, single line LineTag.FormatText(start_line, start_pos, end_pos - start_pos, font, color, back_color, specified); - } - } - - /// Re-format areas of the document in specified font and color - /// 1-based start position on start_line - /// 1-based end position on end_line - /// Font specifying attributes - /// Color (or NULL) to apply - /// Attributes from font and color to apply - 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); + + if ((end_pos - start_pos) == 0 && CaretTag.Length != 0) + CaretTag = CaretTag.Next; } } @@ -3988,7 +3578,7 @@ namespace System.Windows.Forms { 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.right_margin; + line.align_shift = viewport_width - (int)line.widths[line.text.Length] - right_margin; break; } } @@ -4031,6 +3621,7 @@ namespace System.Windows.Forms { } // Fixup the positions, they can go kinda nuts + // (this is suspend and resume recalc - they set them to 1 and max) start = Math.Max (start, 1); end = Math.Min (end, lines); @@ -4048,6 +3639,7 @@ namespace System.Windows.Forms { line = GetLine(line_no++); line.offset = offset; + // if we are not calculating a password if (!calc_pass) { if (!optimize) { line.RecalculateLine(g, this); @@ -4112,6 +3704,12 @@ namespace System.Windows.Forms { HeightChanged(this, null); } } + + // scan for links and tell us if its all + // changed, so we can update everything + if (EnableLinks) + ScanForLinks (start, end, ref changed); + UpdateCaret(); return changed; } @@ -4410,8 +4008,8 @@ namespace System.Windows.Forms { } else { mark.line = GetLine(lines); mark.tag = mark.line.tags; - while (mark.tag.next != null) { - mark.tag = mark.tag.next; + while (mark.tag.Next != null) { + mark.tag = mark.tag.Next; } mark.pos = mark.line.text.Length; } @@ -4423,6 +4021,7 @@ namespace System.Windows.Forms { internal event EventHandler WidthChanged; internal event EventHandler HeightChanged; internal event EventHandler LengthChanged; + internal event EventHandler UIASelectionChanged; #endregion // Events #region Administrative @@ -4474,7 +4073,7 @@ namespace System.Windows.Forms { get { return false; } } - internal override SizeF SizeOfPosition (Graphics dc, int pos) + public override SizeF SizeOfPosition (Graphics dc, int pos) { return picture.Size; } @@ -4484,14 +4083,14 @@ namespace System.Windows.Forms { return (int) (picture.Height + 0.5F); } - internal override void Draw (Graphics dc, Brush brush, float x, float y, int start, int end) + public override void Draw (Graphics dc, Color color, 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) + public override void Draw (Graphics dc, Color color, 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 () @@ -4500,441 +4099,6 @@ namespace System.Windows.Forms { } } - internal class LineTag { - #region Local Variables; - // Payload; formatting - internal Font font; // System.Drawing.Font object for this tag - internal SolidBrush color; // The font color for this tag - - // In 2.0 tags can have background colours. I'm not going to #ifdef - // at this level though since I want to reduce code paths - internal SolidBrush back_color; - - // Payload; text - internal int start; // start, in chars; index into Line.text - 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 ascent; // Ascent of the font for this tag - internal int shift; // Shift down for this tag, to stay on baseline - - // Administrative - internal Line line; // The line we're on - internal LineTag next; // Next tag on the same line - internal LineTag previous; // Previous tag on the same line - #endregion; - - #region Constructors - internal LineTag(Line line, int start) { - this.line = line; - this.start = start; - } - #endregion // Constructors - - #region Internal Methods - - 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] - (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 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 x, float y, int start, int end, string text) - { - dc.DrawString (text.Substring (start, end), font, brush, x, y, StringFormat.GenericTypographic); - } - - ///Break a tag into two with identical attributes; pos is 1-based; returns tag starting at >pos< or null if end-of-line - internal LineTag Break(int pos) { - - LineTag new_tag; - - // Sanity - if (pos == this.start) { - return this; - } else if (pos >= (start + length)) { - return null; - } - - new_tag = new LineTag(line, pos); - new_tag.CopyFormattingFrom (this); - - 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; - font = other.font; - color = other.color; - back_color = other.back_color; - } - - ///Create new font and brush from existing font and given new attributes. Returns true if fontheight changes - 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; - } - - /// 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 - /// 1-based character position on line - internal static bool FormatText(Line line, int start, int length, Font font, SolidBrush color, SolidBrush back_color, FormatSpecified specified) - { - LineTag tag; - LineTag start_tag; - LineTag end_tag; - int end; - bool retval = false; // Assume line-height doesn't change - - // Too simple? - if (((FormatSpecified.Font & specified) == FormatSpecified.Font) && font.Height != line.height) { - retval = true; - } - 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; - end = start + length; - - // Common special case - if ((start == 1) && (length == tag.length)) { - tag.ascent = 0; - SetFormat (tag, font, color, back_color, specified); - return retval; - } - - start_tag = FindTag (line, start); - tag = start_tag.Break (start); - - while (tag != null && tag.end <= end) { - SetFormat (tag, font, color, back_color, specified); - tag = tag.next; - } - - 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); - } - - return retval; - } - - private static void SetFormat (LineTag tag, Font font, SolidBrush color, SolidBrush back_color, FormatSpecified specified) - { - if ((FormatSpecified.Font & specified) == FormatSpecified.Font) - tag.font = font; - if ((FormatSpecified.Color & specified) == FormatSpecified.Color) - tag.color = color; - 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); - } - - /// 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 - /// 1-based character position on line - 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.TextLengthWithoutEnding () - 1); - start_tag.next = new LineTag(line, line.TextLengthWithoutEnding () + 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; - } - - - /// Finds the tag that describes the character at position 'pos' on 'line' - internal static LineTag FindTag(Line line, int pos) { - LineTag tag = line.tags; - - // Beginning of line is a bit special - if (pos == 0) { - // Not sure if we should get the final tag here - return tag; - } - - while (tag != null) { - if ((tag.start <= pos) && (pos <= tag.end)) { - return GetFinalTag (tag); - } - - tag = tag.next; - } - - return null; - } - - // There can be multiple tags at the same position, we want to make - // sure we are using the very last tag at the given position - internal static LineTag GetFinalTag (LineTag tag) - { - LineTag res = tag; - - while (res.length == 0 && res.next != null && res.next.length == 0) - res = res.next; - - return res; - } - - /// Combines 'this' tag with 'other' tag - internal bool Combine(LineTag other) { - if (!this.Equals(other)) { - return false; - } - - this.next = other.next; - if (this.next != null) { - this.next.previous = this; - } - - return true; - } - - - /// Remove 'this' tag ; to be called when formatting is to be removed - internal bool Remove() { - if ((this.start == 1) && (this.next == null)) { - // We cannot remove the only tag - return false; - } - if (this.start != 1) { - this.previous.next = this.next; - this.next.previous = this.previous; - } else { - this.next.start = 1; - this.line.tags = this.next; - this.next.previous = null; - } - return true; - } - - - /// Checks if 'this' tag describes the same formatting options as 'obj' - public override bool Equals(object obj) { - LineTag other; - - if (obj == null) { - return false; - } - - if (!(obj is LineTag)) { - return false; - } - - if (obj == this) { - return true; - } - - 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; - } - - return false; - } - - public override int GetHashCode() { - return base.GetHashCode (); - } - - public override string ToString() { - if (length > 0) - 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 UndoManager { internal enum ActionType { @@ -4961,8 +4125,8 @@ namespace System.Windows.Forms { 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; @@ -5018,16 +4182,13 @@ namespace System.Windows.Forms { redo_actions.Clear(); } - internal void Undo () + internal bool Undo () { Action action; bool user_action_finished = false; if (undo_actions.Count == 0) - return; - - // Nuke the redo queue - redo_actions.Clear (); + return false; locked = true; do { @@ -5079,18 +4240,17 @@ namespace System.Windows.Forms { } while (!user_action_finished && undo_actions.Count > 0); locked = false; + + return true; } - internal void Redo () + internal bool Redo () { Action action; bool user_action_finished = false; if (redo_actions.Count == 0) - return; - - // You can't undo anything after redoing - undo_actions.Clear (); + return false; locked = true; do { @@ -5098,6 +4258,7 @@ namespace System.Windows.Forms { int start_index; action = (Action) redo_actions.Pop (); + undo_actions.Push (action); switch (action.type) { @@ -5151,6 +4312,8 @@ namespace System.Windows.Forms { } while (!user_action_finished && redo_actions.Count > 0); locked = false; + + return true; } #endregion // Internal Methods @@ -5161,6 +4324,9 @@ namespace System.Windows.Forms { if (locked) return; + // Nuke the redo queue + redo_actions.Clear (); + Action ua = new Action (); ua.type = ActionType.UserActionBegin; ua.data = name; @@ -5185,6 +4351,9 @@ namespace System.Windows.Forms { if (locked) return; + // Nuke the redo queue + redo_actions.Clear (); + Action a = new Action (); // We cant simply store the string, because then formatting would be lost @@ -5201,6 +4370,9 @@ namespace System.Windows.Forms { if (locked || str.Length == 0) return; + // Nuke the redo queue + redo_actions.Clear (); + Action a = new Action (); a.type = ActionType.InsertString; @@ -5216,6 +4388,9 @@ namespace System.Windows.Forms { if (locked) return; + // Nuke the redo queue + redo_actions.Clear (); + Action a = null; if (undo_actions.Count > 0) @@ -5257,7 +4432,7 @@ namespace System.Windows.Forms { if (start_line.line_no == i) { start = start_pos; } else { - start = 1; + start = 0; } if (end_line.line_no == i) { @@ -5271,21 +4446,21 @@ namespace System.Windows.Forms { // 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)) { - if ((current_tag.start <= start) && (start < (current_tag.start + current_tag.length))) { + current_tag = current.FindTag (start + 1); + 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; } else { - tag_start = current_tag.start; + tag_start = current_tag.Start; } tag = new LineTag(line, tag_start - start + 1); tag.CopyFormattingFrom (current_tag); - current_tag = current_tag.next; + current_tag = current_tag.Next; // Add the new tag to the line if (line.tags == null) { @@ -5294,11 +4469,11 @@ namespace System.Windows.Forms { LineTag tail; tail = line.tags; - while (tail.next != null) { - tail = tail.next; + while (tail.Next != null) { + tail = tail.Next; } - tail.next = tag; - tag.previous = tail; + tail.Next = tag; + tag.Previous = tail; } } @@ -5337,21 +4512,21 @@ namespace System.Windows.Forms { //Insert our tags at the end tag = line.tags; - while (tag.next != null) { - tag = tag.next; + while (tag.Next != null) { + tag = tag.Next; } - offset = tag.start + tag.length - 1; + offset = tag.Start + tag.Length - 1; - tag.next = insert.tags; + tag.Next = insert.tags; line.text.Insert(offset, insert.text.ToString()); // Adjust start locations - tag = tag.next; + tag = tag.Next; while (tag != null) { - tag.start += offset; - tag.line = line; - tag = tag.next; + tag.Start += offset; + tag.Line = line; + tag = tag.Next; } // Put it back together document.Combine(line.line_no, line.line_no + 1); @@ -5370,40 +4545,46 @@ namespace System.Windows.Forms { 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) { - while (tag.next != null) { - tag = tag.next; + + if (tag != null && tag.Length != 0) { + while (tag.Next != null) { + tag = tag.Next; } - offset = tag.start + tag.length - 1; - tag.next = current.tags; - tag.next.previous = tag; + offset = tag.Start + tag.Length - 1; + tag.Next = current.tags; + tag.Next.Previous = tag; - tag = tag.next; + tag = tag.Next; } else { offset = 0; line.tags = current.tags; - line.tags.previous = null; + 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.tags.Previous = null; + line.ending = current.ending; tag = line.tags; } + // Adjust start locations and line pointers while (tag != null) { - tag.start += offset; - tag.line = line; - tag = tag.next; + tag.Start += offset - 1; + tag.Line = line; + tag = tag.Next; } line.text.Insert(offset, current.text.ToString()); @@ -5413,7 +4594,7 @@ namespace System.Windows.Forms { line = document.GetLine(line.line_no + 1); // FIXME? Test undo of line-boundaries - if ((current.right == null) && (current.tags.length != 0)) { + if ((current.right == null) && (current.tags.Length != 0)) { document.Combine(line.line_no - 1, line.line_no); } current = current.right;