Fixed bug where using ResXResourceWriter filename ctor caused corrupted output
[mono.git] / mcs / class / Managed.Windows.Forms / System.Windows.Forms / TextControl.cs
index 97c9a29636bc45669b025b0e62f8cb8085706365..71e5f4207c888601bb5fc7ed4eb7d8a1cd37104b 100644 (file)
@@ -192,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);
@@ -222,14 +223,14 @@ 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?
@@ -284,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
@@ -308,6 +312,7 @@ namespace System.Windows.Forms {
                        }
                }
 
+               // UIA: Method used via reflection in TextRangeProvider
                internal int Lines {
                        get {
                                return lines;
@@ -352,6 +357,14 @@ namespace System.Windows.Forms {
                        }
                }
 
+               /// <summary>
+               ///  Whether text is scanned for links
+               /// </summary>
+               internal bool EnableLinks {
+                       get { return enable_links; }
+                       set { enable_links = value; }
+               }
+
                internal string PasswordChar {
                        get {
                                return password_char;
@@ -416,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;
@@ -620,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) {
@@ -829,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();
@@ -837,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;
                                        }
                                }
@@ -871,37 +929,281 @@ namespace System.Windows.Forms {
 
                        int start_line_top = line.Y;                    
 
-                       int end_line_bottom;
-                       Line end_line;
-
-                       end_line = GetLine (line.line_no + line_count);
+                       Line end_line = GetLine (line.line_no + line_count);
                        if (end_line == null)
                                end_line = GetLine (lines);
 
-
-                       end_line_bottom = end_line.Y + end_line.height;
+                       if (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
-                                       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();
                                }
                        } else {
-                               int x = 0 - viewport_x;
+                               int x = 0 - viewport_x + offset_x;
                                int w = viewport_width;
-                               int y = Math.Min (start_line_top - viewport_y, line.Y - viewport_y);
+                               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));
                        }
                }
+
+               /// <summary>
+               ///  Scans the next paragraph for http:/ ftp:/ www. https:/ etc and marks the tags
+               ///  as links.
+               /// </summary>
+               /// <param name="start_line">The line to start on</param>
+               /// <param name="link_changed">marks as true if something is changed</param>
+               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;
+
+                       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];
+
+                               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() {
 
@@ -946,7 +1248,9 @@ namespace System.Windows.Forms {
                                if (owner.Focused) {
                                        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);
@@ -972,7 +1276,9 @@ namespace System.Windows.Forms {
 
                        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);
@@ -981,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 ();
                        }
@@ -998,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 ();
                        }
 
@@ -1033,7 +1359,9 @@ namespace System.Windows.Forms {
                        }
 
                        if (owner.Focused) {
-                               XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x, caret.line.Y + caret.tag.Shift - viewport_y + caret_shift);
+                               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 ();
                        }
                        
@@ -1234,7 +1562,7 @@ namespace System.Windows.Forms {
 
                                case CaretDirection.PgUp: {
 
-                                       if (viewport_y == 0 && owner.richtext) {
+                                       if (caret.line.line_no == 1 && owner.richtext) {
                                                owner.vscroll.Value = 0;
                                                Line line = GetLine (1);
                                                PositionCaret (line, 0);
@@ -1253,10 +1581,10 @@ namespace System.Windows.Forms {
 
                                case CaretDirection.PgDn: {
 
-                                       if (viewport_y + viewport_height >= document_y && owner.richtext) {
+                                       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());
                                        }
 
                                        int y_offset = caret.line.Y - viewport_y;
@@ -1350,6 +1678,22 @@ namespace System.Windows.Forms {
                        Console.WriteLine ("</doc>");
                }
 
+               // 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
@@ -1362,14 +1706,10 @@ namespace System.Windows.Forms {
                        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
@@ -1380,8 +1720,8 @@ namespace System.Windows.Forms {
 
                        /// 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;
 
@@ -1396,9 +1736,9 @@ namespace System.Windows.Forms {
                        // Non multiline selection can be handled outside of the loop
                        if (!multiline && selection_visible && owner.ShowSelection) {
                                g.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (ThemeEngine.Current.ColorHighlight),
-                                               selection_start.line.widths [selection_start.pos] +
+                                               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);
@@ -1406,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) {
@@ -1442,13 +1782,13 @@ namespace System.Windows.Forms {
                                        } else if (multiline) {
                                                // lets draw some selection baby!!  (non multiline selection is drawn outside the loop)
                                                g.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (ThemeEngine.Current.ColorHighlight),
-                                                               line.widths [line_selection_start - 1] + line.X - viewport_x, 
+                                                               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_color = line.tags.Color;
+                               current_color = line.tags.ColorToDisplay;
                                while (tag != null) {
 
                                        // Skip empty tags
@@ -1457,29 +1797,29 @@ namespace System.Windows.Forms {
                                                continue;
                                        }
 
-                                       if (((tag.X + tag.Width) < (clip.Left - viewport_x)) && (tag.X > (clip.Right - viewport_x))) {
+                                       if (((tag.X + tag.Width) < (clip.Left - viewport_x - offset_x)) && 
+                                            (tag.X > (clip.Right - viewport_x - offset_x))) {
                                                tag = tag.Next;
                                                continue;
                                        }
 
                                        if (tag.BackColor != Color.Empty) {
-                                               g.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (tag.BackColor), tag.X + line.X - viewport_x,
+                                               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_color = tag.Color;
+                                       tag_color = tag.ColorToDisplay;
                                        current_color = tag_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)) {
+                                               if ((a.R == b.R) && (a.G == b.G) && (a.B == b.B))
                                                        tag_color = ThemeEngine.Current.ColorGrayText;
-                                               }
-                                       } else if (owner.read_only && !owner.backcolor_set) {
-                                               tag_color = ThemeEngine.Current.ColorControlText;
-                                       }
+
+                                       } 
 
                                        int tag_pos = tag.Start;
                                        current_color = tag_color;
@@ -1497,11 +1837,19 @@ namespace System.Windows.Forms {
                                                        tag_pos = tag.End;
                                                }
 
+                                               Rectangle text_size;
+
                                                tag.Draw (g, current_color,
-                                                               line.X - viewport_x,
+                                                               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() );
+                                                               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;
                                }
@@ -1599,8 +1947,29 @@ namespace System.Windows.Forms {
                        return string.Empty;
                }
 
-               // Insert text at the given position; use formatting at insertion point for inserted text
+               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;
+                       }
+               }
+               
                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;
@@ -1609,23 +1978,25 @@ namespace System.Windows.Forms {
                        LineEnding ending;
                        Line split_line;
                        
-                       // Find the LineTag to add to
-                       LineTag tag = line.FindTag (pos);
-                       
                        // Don't recalculate while we mess around
                        SuspendRecalc ();
                        
                        base_line = line.line_no;
                        old_line_count = lines;
 
+                       // 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);
 
                        // There are no line feeds in our text to be pasted
                        if (break_index == s.Length) {
-                               line.InsertString (pos, s);
+                               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)));
+                               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)));
@@ -1660,6 +2031,9 @@ namespace System.Windows.Forms {
                        // Allow the document to recalculate things
                        ResumeRecalc (false);
 
+                       // Update our character count
+                       CharCount += s.Length;
+
                        UpdateView (line, lines - old_line_count + 1, pos);
 
                        // Move the caret to the end of the inserted text if requested
@@ -1683,8 +2057,11 @@ namespace System.Windows.Forms {
                // Inserts a character at the current caret position
                internal void InsertCharAtCaret (char ch, bool move_caret)
                {
-                       caret.line.InsertString (caret.pos, ch.ToString ());
+                       caret.line.InsertString (caret.pos, ch.ToString(), caret.tag);
 
+                       // Update our character count
+                       CharCount++;
+                       
                        undo.RecordTyping (caret.line, caret.pos, ch);
 
                        UpdateView (caret.line, caret.pos);
@@ -1814,10 +2191,17 @@ namespace System.Windows.Forms {
                        if ((pos == 0 && forward == false) || (pos == line.text.Length && forward == true))
                                return;
                        
-                       if (forward)
+                       undo.BeginUserAction ("Delete");
+
+                       if (forward) {
+                               undo.RecordDeleteString (line, pos, line, pos + 1);
                                DeleteChars (line, pos, 1);
-                       else
+                       } else {
+                               undo.RecordDeleteString (line, pos - 1, line, pos);
                                DeleteChars (line, pos - 1, 1);
+                       }
+
+                       undo.EndUserAction ();
                }
 
                // Combine two lines
@@ -1922,7 +2306,7 @@ namespace System.Windows.Forms {
                }
 
                ///<summary>Split line at given tag and position into two lines</summary>
-               ///if more space becomes available on previous line</param>
+               ///if more space becomes available on previous line
                internal void Split(Line line, LineTag tag, int pos) {
                        LineTag new_tag;
                        Line    new_line;
@@ -1934,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;
@@ -1956,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) {
@@ -1969,6 +2364,10 @@ namespace System.Windows.Forms {
                                        selection_end.pos = 0;
                                        selection_end.tag = new_line.tags;
                                }
+
+#if DEBUG
+                               SanityCheck ();
+#endif
                                return;
                        }
 
@@ -1981,10 +2380,19 @@ namespace System.Windows.Forms {
                        line.recalc = true;
                        new_line.recalc = true;
 
+                       //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);
@@ -2033,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) {
@@ -2049,7 +2464,35 @@ 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
@@ -2221,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;
@@ -2282,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;
                        }
@@ -2296,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
@@ -2304,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);
@@ -2313,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
                }
 
@@ -2346,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;
@@ -2377,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;
@@ -2392,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);
@@ -2450,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;
@@ -2710,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());
                                        }
                                }
 
@@ -2727,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 ();
 
@@ -2739,7 +3198,7 @@ namespace System.Windows.Forms {
                                        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;
@@ -2749,7 +3208,7 @@ 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.line, selection_start.pos, selection_start.line.text.Length - selection_start.pos);
@@ -2920,6 +3379,8 @@ namespace System.Windows.Forms {
                }
 
 
+               // UIA: Method used via reflection in TextRangeProvider
+
                /// <summary>Give it a Line number and it returns the Line object at with that line number</summary>
                internal Line GetLine(int LineNo) {
                        Line    line = document;
@@ -2982,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;
                }       
 
@@ -3038,19 +3505,24 @@ namespace System.Windows.Forms {
                        return last;
                }
 
+               // UIA: Method used via reflection in TextProviderBehavior
+
                // 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)
+                       if (tag.Length == 0 && tag.Start == 1)
                                index = 0;
                        else
-                               index = tag.GetCharIndex (x);
+                               index = tag.GetCharIndex (x - line.align_shift);
                        
                        return tag;
                }
@@ -3079,6 +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);
+                               
+                               if ((end_pos - start_pos) == 0 && CaretTag.Length != 0)
+                                       CaretTag = CaretTag.Next;
                        }
                }
 
@@ -3146,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);
 
@@ -3163,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);
@@ -3227,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;
                }
@@ -3538,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
@@ -3698,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 {
@@ -3759,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 {
@@ -3778,6 +4258,7 @@ namespace System.Windows.Forms {
                                int start_index;
 
                                action = (Action) redo_actions.Pop ();
+                               undo_actions.Push (action);
 
                                switch (action.type) {
 
@@ -3831,6 +4312,8 @@ namespace System.Windows.Forms {
                        } while (!user_action_finished && redo_actions.Count > 0);
 
                        locked = false;
+
+                       return true;
                }
                #endregion      // Internal Methods
 
@@ -3841,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;
@@ -3865,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
@@ -3881,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;
@@ -3896,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)
@@ -3953,7 +4448,7 @@ namespace System.Windows.Forms {
                                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);
+                               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