TextControl.cs :  » 2.6.4-mono-.net-core » System.Windows.Forms » System » Windows » Forms » C# / CSharp Open Source

Home
C# / CSharp Open Source
1.2.6.4 mono .net core
2.2.6.4 mono core
3.Aspect Oriented Frameworks
4.Bloggers
5.Build Systems
6.Business Application
7.Charting Reporting Tools
8.Chat Servers
9.Code Coverage Tools
10.Content Management Systems CMS
11.CRM ERP
12.Database
13.Development
14.Email
15.Forum
16.Game
17.GIS
18.GUI
19.IDEs
20.Installers Generators
21.Inversion of Control Dependency Injection
22.Issue Tracking
23.Logging Tools
24.Message
25.Mobile
26.Network Clients
27.Network Servers
28.Office
29.PDF
30.Persistence Frameworks
31.Portals
32.Profilers
33.Project Management
34.RSS RDF
35.Rule Engines
36.Script
37.Search Engines
38.Sound Audio
39.Source Control
40.SQL Clients
41.Template Engines
42.Testing
43.UML
44.Web Frameworks
45.Web Service
46.Web Testing
47.Wiki Engines
48.Windows Presentation Foundation
49.Workflows
50.XML Parsers
C# / C Sharp
C# / C Sharp by API
C# / CSharp Tutorial
C# / CSharp Open Source » 2.6.4 mono .net core » System.Windows.Forms 
System.Windows.Forms » System » Windows » Forms » TextControl.cs
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// Copyright (c) 2004-2006 Novell, Inc. (http://www.novell.com)
//
// Authors:
//  Peter Bartok  pbartok@novell.com
//
//

// NOT COMPLETE

// There's still plenty of things missing, I've got most of it planned, just hadn't had
// the time to write it all yet.
// Stuff missing (in no particular order):
// - Align text after RecalculateLine
// - Implement tag types for hotlinks, etc.
// - Implement CaretPgUp/PgDown

// NOTE:
// selection_start.pos and selection_end.pos are 0-based
// selection_start.pos = first selected char
// selection_end.pos = first NOT-selected char
//
// FormatText methods are 1-based (as are all tags, LineTag.Start is 1 for 
// the first character on a line; the reason is that 0 is the position 
// *before* the first character on a line


#undef Debug

using System;
using System.Collections;
using System.Drawing;
using System.Drawing.Text;
using System.Text;
using RTFSystem.Windows.Forms.RTF;

namespace System.Windows.Forms{
  internal enum LineColor {
    Red  = 0,
    Black  = 1
  }

  internal enum CaretSelection {
    Position,  // Selection=Caret
    Word,    // Selection=Word under caret
    Line    // Selection=Line under caret
  }

  [Flags]
  internal enum FormatSpecified {
    None,

    BackColor = 2,
    Font = 4,
    Color = 8,
  }

  internal enum CaretDirection {
    CharForward,  // Move a char to the right
    CharBack,  // Move a char to the left
    LineUp,    // Move a line up
    LineDown,  // Move a line down
    Home,    // Move to the beginning of the line
    End,    // Move to the end of the line
    PgUp,    // Move one page up
    PgDn,    // Move one page down
    CtrlPgUp,  // Move caret to the first visible char in the viewport
    CtrlPgDn,  // Move caret to the last visible char in the viewport
    CtrlHome,  // Move to the beginning of the document
    CtrlEnd,  // Move to the end of the document
    WordBack,  // Move to the beginning of the previous word (or beginning of line)
    WordForward,  // Move to the beginning of the next word (or end of line)
    SelectionStart,  // Move to the beginning of the current selection
    SelectionEnd,  // Move to the end of the current selection
    CharForwardNoWrap,   // Move a char forward, but don't wrap onto the next line
    CharBackNoWrap      // Move a char backward, but don't wrap onto the previous line
  }

  internal enum LineEnding {
    Wrap = 1,    // line wraps to the next line
    Limp = 2,    // \r
    Hard = 4,    // \r\n
    Soft = 8,    // \r\r\n
    Rich = 16,    // \n

    None = 0
  }
  
  internal class Document : ICloneable, IEnumerable {
    #region Structures
    // FIXME - go through code and check for places where
    // we do explicit comparisons instead of using the compare overloads
    internal struct Marker {
      internal Line    line;
      internal LineTag  tag;
      internal int    pos;
      internal int    height;

      public static bool operator<(Marker lhs, Marker rhs) {
        if (lhs.line.line_no < rhs.line.line_no) {
          return true;
        }

        if (lhs.line.line_no == rhs.line.line_no) {
          if (lhs.pos < rhs.pos) {
            return true;
          }
        }
        return false;
      }

      public static bool operator>(Marker lhs, Marker rhs) {
        if (lhs.line.line_no > rhs.line.line_no) {
          return true;
        }

        if (lhs.line.line_no == rhs.line.line_no) {
          if (lhs.pos > rhs.pos) {
            return true;
          }
        }
        return false;
      }

      public static bool operator==(Marker lhs, Marker rhs) {
        if ((lhs.line.line_no == rhs.line.line_no) && (lhs.pos == rhs.pos)) {
          return true;
        }
        return false;
      }

      public static bool operator!=(Marker lhs, Marker rhs) {
        if ((lhs.line.line_no != rhs.line.line_no) || (lhs.pos != rhs.pos)) {
          return true;
        }
        return false;
      }

      public void Combine(Line move_to_line, int move_to_line_length) {
        line = move_to_line;
        pos += move_to_line_length;
        tag = LineTag.FindTag(line, pos);
      }

      // This is for future use, right now Document.Split does it by hand, with some added shortcut logic
      public void Split(Line move_to_line, int split_at) {
        line = move_to_line;
        pos -= split_at;
        tag = LineTag.FindTag(line, pos);
      }

      public override bool Equals(object obj) {
           return this==(Marker)obj;
      }

      public override int GetHashCode() {
        return base.GetHashCode ();
      }

      public override string ToString() {
        return "Marker Line " + line + ", Position " + pos;
      }

    }
    #endregion Structures

    #region Local Variables
    private Line    document;
    private int    lines;
    private Line    sentinel;
    private int    document_id;
    private Random    random = new Random();
    internal string    password_char;
    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);

    private int     recalc_suspended;
    private bool    recalc_pending;
    private int    recalc_start = 1;   // This starts at one, since lines are 1 based
    private int    recalc_end;
    private bool    recalc_optimize;

    private int             update_suspended;
    private bool update_pending;
    private int update_start = 1;

    internal bool    multiline;
    internal HorizontalAlignment alignment;
    internal bool    wrap;

    internal UndoManager  undo;

    internal Marker    caret;
    internal Marker    selection_start;
    internal Marker    selection_end;
    internal bool    selection_visible;
    internal Marker    selection_anchor;
    internal Marker    selection_prev;
    internal bool    selection_end_anchor;

    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 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
    internal Document (TextBoxBase owner)
    {
      lines = 0;

      this.owner = owner;

      multiline = true;
      password_char = "";
      calc_pass = false;
      recalc_pending = false;

      // Tree related stuff
      sentinel = new Line (this, LineEnding.None);
      sentinel.color = LineColor.Black;

      document = sentinel;

      // We always have a blank line
      owner.HandleCreated += new EventHandler(owner_HandleCreated);
      owner.VisibleChanged += new EventHandler(owner_VisibleChanged);

      Add (1, String.Empty, owner.Font, owner.ForeColor, LineEnding.None);

      undo = new UndoManager (this);

      selection_visible = false;
      selection_start.line = this.document;
      selection_start.pos = 0;
      selection_start.tag = selection_start.line.tags;
      selection_end.line = this.document;
      selection_end.pos = 0;
      selection_end.tag = selection_end.line.tags;
      selection_anchor.line = this.document;
      selection_anchor.pos = 0;
      selection_anchor.tag = selection_anchor.line.tags;
      caret.line = this.document;
      caret.pos = 0;
      caret.tag = caret.line.tags;

      viewport_x = 0;
      viewport_y = 0;

      offset_x = 0;
      offset_y = 0;

      crlf_size = 2;

      // Default selection is empty

      document_id = random.Next();

      string_format.Trimming = StringTrimming.None;
      string_format.FormatFlags = StringFormatFlags.DisplayFormatControl;

      UpdateMargins ();
    }
    #endregion

    #region Internal Properties
    internal Line Root {
      get {
        return document;
      }

      set {
        document = value;
      }
    }

    // UIA: Method used via reflection in TextRangeProvider
    internal int Lines {
      get {
        return lines;
      }
    }

    internal Line CaretLine {
      get {
        return caret.line;
      }
    }

    internal int CaretPosition {
      get {
        return caret.pos;
      }
    }

    internal Point Caret {
      get {
        return new Point((int)caret.tag.Line.widths[caret.pos] + caret.line.X, caret.line.Y);
      }
    }

    internal LineTag CaretTag {
      get {
        return caret.tag;
      }

      set {
        caret.tag = value;
      }
    }

    internal int CRLFSize {
      get {
        return crlf_size;
      }

      set {
        crlf_size = value;
      }
    }

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

      set {
        password_char = value;
        PasswordCache.Length = 0;
        if ((password_char.Length != 0) && (password_char[0] != '\0')) {
          calc_pass = true;
        } else {
          calc_pass = false;
        }
      }
    }

    private StringBuilder PasswordCache {
      get { 
        if (password_cache == null) 
            password_cache = new StringBuilder(); 
        return password_cache;
      }
    }

    internal int ViewPortX {
      get {
        return viewport_x;
      }

      set {
        viewport_x = value;
      }
    }

    internal int Length {
      get {
        return char_count + lines - 1;  // Add \n for each line but the last
      }
    }

    private int CharCount {
      get {
        return char_count;
      }

      set {
        char_count = value;

        if (LengthChanged != null) {
          LengthChanged(this, EventArgs.Empty);
        }
      }
    }

    internal int ViewPortY {
      get {
        return viewport_y;
      }

      set {
        viewport_y = value;
      }
    }

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

      set {
        viewport_width = value;
      }
    }

    internal int ViewPortHeight {
      get {
        return viewport_height;
      }

      set {
        viewport_height = value;
      }
    }


    internal int Width {
      get {
        return this.document_x;
      }
    }

    internal int Height {
      get {
        return this.document_y;
      }
    }

    internal bool SelectionVisible {
      get {
        return selection_visible;
      }
    }

    internal bool Wrap {
      get {
        return wrap;
      }

      set {
        wrap = value;
      }
    }

    #endregion  // Internal Properties

    #region Private Methods

    internal void UpdateMargins ()
    {
      switch (owner.actual_border_style) {
        case BorderStyle.None:
          left_margin = 0;
          top_margin = 0;
          right_margin = 1;
          break;
        case BorderStyle.FixedSingle:
          left_margin = 2;
          top_margin = 2;
          right_margin = 3;
          break;
        case BorderStyle.Fixed3D:
          left_margin = 1;
          top_margin = 1;
          right_margin = 2;
          break;
      }
    }

    internal void SuspendRecalc ()
    {
      if (recalc_suspended == 0) {
        recalc_start = int.MaxValue;
        recalc_end = int.MinValue;
      }
      
      recalc_suspended++;
    }

    internal void ResumeRecalc (bool immediate_update)
    {
      if (recalc_suspended > 0)
        recalc_suspended--;

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

    internal void SuspendUpdate ()
    {
      update_suspended++;
    }

    internal void ResumeUpdate (bool immediate_update)
    {
      if (update_suspended > 0)
        update_suspended--;

      if (immediate_update && update_suspended == 0 && update_pending) {
        UpdateView (GetLine (update_start), 0);
        update_pending = false;
      }
    }

    // For debugging
    internal int DumpTree(Line line, bool with_tags) {
      int  total;

      total = 1;

      Console.Write("Line {0} [# {1}], Y: {2}, ending style: {3},  Text: '{4}'",
          line.line_no, line.GetHashCode(), line.Y, line.ending,
          line.text != null ? line.text.ToString() : "undefined");

      if (line.left == sentinel) {
        Console.Write(", left = sentinel");
      } else if (line.left == null) {
        Console.Write(", left = NULL");
      }

      if (line.right == sentinel) {
        Console.Write(", right = sentinel");
      } else if (line.right == null) {
        Console.Write(", right = NULL");
      }

      Console.WriteLine("");

      if (with_tags) {
        LineTag  tag;
        int  count;
        int  length;

        tag = line.tags;
        count = 1;
        length = 0;
        Console.Write("   Tags: ");
        while (tag != null) {
          Console.Write("{0} <{1}>-<{2}>", count++, tag.Start, tag.End
              /*line.text.ToString (tag.start - 1, tag.length)*/);
          length += tag.Length;

          if (tag.Line != line) {
            Console.Write("BAD line link");
            throw new Exception("Bad line link in tree");
          }
          tag = tag.Next;
          if (tag != null) {
            Console.Write(", ");
          }
        }
        if (length > line.text.Length) {
          throw new Exception(String.Format("Length of tags more than length of text on line (expected {0} calculated {1})", line.text.Length, length));
        } else if (length < line.text.Length) {
          throw new Exception(String.Format("Length of tags less than length of text on line (expected {0} calculated {1})", line.text.Length, length));
        }
        Console.WriteLine("");
      }
      if (line.left != null) {
        if (line.left != sentinel) {
          total += DumpTree(line.left, with_tags);
        }
      } else {
        if (line != sentinel) {
          throw new Exception("Left should not be NULL");
        }
      }

      if (line.right != null) {
        if (line.right != sentinel) {
          total += DumpTree(line.right, with_tags);
        }
      } else {
        if (line != sentinel) {
          throw new Exception("Right should not be NULL");
        }
      }

      for (int i = 1; i <= this.lines; i++) {
        if (GetLine(i) == null) {
          throw new Exception(String.Format("Hole in line order, missing {0}", i));
        }
      }

      if (line == this.Root) {
        if (total < this.lines) {
          throw new Exception(String.Format("Not enough nodes in tree, found {0}, expected {1}", total, this.lines));
        } else if (total > this.lines) {
          throw new Exception(String.Format("Too many nodes in tree, found {0}, expected {1}", total, this.lines));
        }
      }

      return total;
    }

    private void SetSelectionVisible (bool value)
    {
#if NET_2_0
      bool old_selection_visible = selection_visible;
#endif
      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 NET_2_0
      if (UIASelectionChanged != null && (selection_visible || old_selection_visible))
        UIASelectionChanged (this, EventArgs.Empty);
#endif
    }

    private void DecrementLines(int line_no) {
      int  current;

      current = line_no;
      while (current <= lines) {
        GetLine(current).line_no--;
        current++;
      }
      return;
    }

    private void IncrementLines(int line_no) {
      int  current;

      current = this.lines;
      while (current >= line_no) {
        GetLine(current).line_no++;
        current--;
      }
      return;
    }

    private void RebalanceAfterAdd(Line line1) {
      Line  line2;

      while ((line1 != document) && (line1.parent.color == LineColor.Red)) {
        if (line1.parent == line1.parent.parent.left) {
          line2 = line1.parent.parent.right;

          if ((line2 != null) && (line2.color == LineColor.Red)) {
            line1.parent.color = LineColor.Black;
            line2.color = LineColor.Black;
            line1.parent.parent.color = LineColor.Red;
            line1 = line1.parent.parent;
          } else {
            if (line1 == line1.parent.right) {
              line1 = line1.parent;
              RotateLeft(line1);
            }

            line1.parent.color = LineColor.Black;
            line1.parent.parent.color = LineColor.Red;

            RotateRight(line1.parent.parent);
          }
        } else {
          line2 = line1.parent.parent.left;

          if ((line2 != null) && (line2.color == LineColor.Red)) {
            line1.parent.color = LineColor.Black;
            line2.color = LineColor.Black;
            line1.parent.parent.color = LineColor.Red;
            line1 = line1.parent.parent;
          } else {
            if (line1 == line1.parent.left) {
              line1 = line1.parent;
              RotateRight(line1);
            }

            line1.parent.color = LineColor.Black;
            line1.parent.parent.color = LineColor.Red;
            RotateLeft(line1.parent.parent);
          }
        }
      }
      document.color = LineColor.Black;
    }

    private void RebalanceAfterDelete(Line line1) {
      Line line2;

      while ((line1 != document) && (line1.color == LineColor.Black)) {
        if (line1 == line1.parent.left) {
          line2 = line1.parent.right;
          if (line2.color == LineColor.Red) { 
            line2.color = LineColor.Black;
            line1.parent.color = LineColor.Red;
            RotateLeft(line1.parent);
            line2 = line1.parent.right;
          }
          if ((line2.left.color == LineColor.Black) && (line2.right.color == LineColor.Black)) { 
            line2.color = LineColor.Red;
            line1 = line1.parent;
          } else {
            if (line2.right.color == LineColor.Black) {
              line2.left.color = LineColor.Black;
              line2.color = LineColor.Red;
              RotateRight(line2);
              line2 = line1.parent.right;
            }
            line2.color = line1.parent.color;
            line1.parent.color = LineColor.Black;
            line2.right.color = LineColor.Black;
            RotateLeft(line1.parent);
            line1 = document;
          }
        } else { 
          line2 = line1.parent.left;
          if (line2.color == LineColor.Red) {
            line2.color = LineColor.Black;
            line1.parent.color = LineColor.Red;
            RotateRight(line1.parent);
            line2 = line1.parent.left;
          }
          if ((line2.right.color == LineColor.Black) && (line2.left.color == LineColor.Black)) {
            line2.color = LineColor.Red;
            line1 = line1.parent;
          } else {
            if (line2.left.color == LineColor.Black) {
              line2.right.color = LineColor.Black;
              line2.color = LineColor.Red;
              RotateLeft(line2);
              line2 = line1.parent.left;
            }
            line2.color = line1.parent.color;
            line1.parent.color = LineColor.Black;
            line2.left.color = LineColor.Black;
            RotateRight(line1.parent);
            line1 = document;
          }
        }
      }
      line1.color = LineColor.Black;
    }

    private void RotateLeft(Line line1) {
      Line  line2 = line1.right;

      line1.right = line2.left;

      if (line2.left != sentinel) {
        line2.left.parent = line1;
      }

      if (line2 != sentinel) {
        line2.parent = line1.parent;
      }

      if (line1.parent != null) {
        if (line1 == line1.parent.left) {
          line1.parent.left = line2;
        } else {
          line1.parent.right = line2;
        }
      } else {
        document = line2;
      }

      line2.left = line1;
      if (line1 != sentinel) {
        line1.parent = line2;
      }
    }

    private void RotateRight(Line line1) {
      Line line2 = line1.left;

      line1.left = line2.right;

      if (line2.right != sentinel) {
        line2.right.parent = line1;
      }

      if (line2 != sentinel) {
        line2.parent = line1.parent;
      }

      if (line1.parent != null) {
        if (line1 == line1.parent.right) {
          line1.parent.right = line2;
        } else {
          line1.parent.left = line2;
        }
      } else {
        document = line2;
      }

      line2.right = line1;
      if (line1 != sentinel) {
        line1.parent = line2;
      }
    }        


    internal void UpdateView(Line line, int pos) {
      if (!owner.IsHandleCreated) {
        return;
      }

      if (update_suspended > 0) {
        update_start = Math.Min (update_start, line.line_no);
        // update_end = Math.Max (update_end, line.line_no);
        // recalc_optimize = true;
        update_pending = true;
        return;
      }

      // Optimize invalidation based on Line alignment
      if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no, 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(
            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 {
        switch(line.alignment) {
          case HorizontalAlignment.Left: {
            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 + offset_x, 
              line.Y - viewport_y + offset_y, 
              viewport_width, 
              line.height + 1));
            break;
          }

          case HorizontalAlignment.Right: {
            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;
          }
        }
      }
    }


    // Update display from line, down line_count lines; pos is unused, but required for the signature
    internal void UpdateView(Line line, int line_count, int pos) {
      if (!owner.IsHandleCreated) {
        return;
      }

      if (recalc_suspended > 0) {
        recalc_start = Math.Min (recalc_start, line.line_no);
        recalc_end = Math.Max (recalc_end, line.line_no + line_count);
        recalc_optimize = true;
        recalc_pending = true;
        return;
      }

      int start_line_top = line.Y;      

      int end_line_bottom;
      Line end_line;

      end_line = GetLine (line.line_no + line_count);
      if (end_line == null)
        end_line = GetLine (lines);


      end_line_bottom = end_line.Y + end_line.height;
      
      if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no + line_count, true)) {
        // Lineheight changed, invalidate the rest of the document
        if ((line.Y - viewport_y) >=0 ) {
          // We formatted something that's in view, only draw parts of the screen
          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 + 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));
      }
    }

    /// <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++) {
#if  NET_2_0
        int index = haystack.IndexOf (needles [i], start_index,  StringComparison.InvariantCultureIgnoreCase);
#else
        int index = haystack.ToLower().IndexOf(needles[i], start_index);
#endif

        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() {

      document = sentinel;
      lines = 0;

      // We always have a blank line
      Add (1, String.Empty, owner.Font, owner.ForeColor, LineEnding.None);
      
      this.RecalculateDocument(owner.CreateGraphicsInternal());
      PositionCaret(0, 0);

      SetSelectionVisible (false);

      selection_start.line = this.document;
      selection_start.pos = 0;
      selection_start.tag = selection_start.line.tags;
      selection_end.line = this.document;
      selection_end.pos = 0;
      selection_end.tag = selection_end.line.tags;
      char_count = 0;

      viewport_x = 0;
      viewport_y = 0;

      document_x = 0;
      document_y = 0;

      if (owner.IsHandleCreated)
        owner.Invalidate ();
    }

    internal void PositionCaret(Line line, int pos) {
      caret.tag = line.FindTag (pos);

      MoveCaretToTextTag ();

      caret.line = line;
      caret.pos = pos;

      if (owner.IsHandleCreated) {
        if (owner.Focused) {
          if (caret.height != caret.tag.Height)
            XplatUI.CreateCaret (owner.Handle, caret_width, caret.height);
          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);
      }

      // 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;

    }

    internal void PositionCaret(int x, int y) {
      if (!owner.IsHandleCreated) {
        return;
      }

      caret.tag = FindCursor(x, y, out caret.pos);
      
      MoveCaretToTextTag ();
      
      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 + offset_x, 
          offset_y + caret.line.Y + caret.tag.Shift - viewport_y + caret_shift);
      }

      if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
    }

    internal void CaretHasFocus() {
      if ((caret.tag != null) && owner.IsHandleCreated) {
        XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
        XplatUI.SetCaretPos(owner.Handle, 
          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 (owner.IsHandleCreated && SelectionLength () > 0) {
        InvalidateSelectionArea ();
      }
    }

    internal void CaretLostFocus() {
      if (!owner.IsHandleCreated) {
        return;
      }
      XplatUI.DestroyCaret(owner.Handle);
    }

    internal void AlignCaret ()
    {
      AlignCaret (true);
    }

    internal void AlignCaret(bool changeCaretTag) {
      if (!owner.IsHandleCreated) {
        return;
      }

      if (changeCaretTag) {
        caret.tag = LineTag.FindTag (caret.line, caret.pos);

        MoveCaretToTextTag ();
      }

      // 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, 
          offset_x + (int) caret.tag.Line.widths [caret.pos] + caret.line.X - viewport_x, 
          offset_y + caret.line.Y + viewport_y + caret_shift);
        DisplayCaret ();
      }

      if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
    }

    internal void UpdateCaret() {
      if (!owner.IsHandleCreated || caret.tag == null) {
        return;
      }

      MoveCaretToTextTag ();

      if (caret.tag.Height != caret.height) {
        caret.height = caret.tag.Height;
        if (owner.Focused) {
          XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
        }
      }

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

    internal void DisplayCaret() {
      if (!owner.IsHandleCreated) {
        return;
      }

      if (owner.ShowSelection && (!selection_visible || owner.show_caret_w_selection)) {
        XplatUI.CaretVisible(owner.Handle, true);
      }
    }

    internal void HideCaret() {
      if (!owner.IsHandleCreated) {
        return;
      }

      if (owner.Focused) {
        XplatUI.CaretVisible(owner.Handle, false);
      }
    }

    
    internal void MoveCaretToTextTag ()
    {
      if (caret.tag == null || caret.tag.IsTextTag)
        return;

      

      if (caret.pos < caret.tag.Start) {
        caret.tag = caret.tag.Previous;
      } else {
        caret.tag = caret.tag.Next;
      }
    }

    internal void MoveCaret(CaretDirection direction) {
      // FIXME should we use IsWordSeparator to detect whitespace, instead 
      // of looking for actual spaces in the Word move cases?

      bool nowrap = false;
      switch(direction) {
        case CaretDirection.CharForwardNoWrap:
          nowrap = true;
          goto case CaretDirection.CharForward;
        case CaretDirection.CharForward: {
          caret.pos++;
          if (caret.pos > caret.line.TextLengthWithoutEnding ()) {
            if (!nowrap) {
              // Go into next line
              if (caret.line.line_no < this.lines) {
                caret.line = GetLine(caret.line.line_no+1);
                caret.pos = 0;
                caret.tag = caret.line.tags;
              } else {
                caret.pos--;
              }
            } else {
              // Single line; we stay where we are
              caret.pos--;
            }
          } else {
            if ((caret.tag.Start - 1 + caret.tag.Length) < caret.pos) {
              caret.tag = caret.tag.Next;
            }
          }
          UpdateCaret();
          return;
        }

        case CaretDirection.CharBackNoWrap:
          nowrap = true;
          goto case CaretDirection.CharBack;
        case CaretDirection.CharBack: {
          if (caret.pos > 0) {
            // caret.pos--; // folded into the if below
            
            if (--caret.pos > 0) {
              if (caret.tag.Start > caret.pos) {
                caret.tag = caret.tag.Previous;
              }
            }
          } else {
            if (caret.line.line_no > 1 && !nowrap) {
              caret.line = GetLine(caret.line.line_no - 1);
              caret.pos = caret.line.TextLengthWithoutEnding ();
              caret.tag = LineTag.FindTag(caret.line, caret.pos);
            }
          }
          UpdateCaret();
          return;
        }

        case CaretDirection.WordForward: {
          int len;

          len = caret.line.text.Length;
          if (caret.pos < len) {
            while ((caret.pos < len) && (caret.line.text[caret.pos] != ' ')) {
              caret.pos++;
            }
            if (caret.pos < len) {
              // Skip any whitespace
              while ((caret.pos < len) && (caret.line.text[caret.pos] == ' ')) {
                caret.pos++;
              }
            }
            caret.tag = LineTag.FindTag(caret.line, caret.pos);
          } else {
            if (caret.line.line_no < this.lines) {
              caret.line = GetLine(caret.line.line_no + 1);
              caret.pos = 0;
              caret.tag = caret.line.tags;
            }
          }
          UpdateCaret();
          return;
        }

        case CaretDirection.WordBack: {
          if (caret.pos > 0) {
            caret.pos--;

            while ((caret.pos > 0) && (caret.line.text[caret.pos] == ' ')) {
              caret.pos--;
            }

            while ((caret.pos > 0) && (caret.line.text[caret.pos] != ' ')) {
              caret.pos--;
            }

            if (caret.line.text.ToString(caret.pos, 1) == " ") {
              if (caret.pos != 0) {
                caret.pos++;
              } else {
                caret.line = GetLine(caret.line.line_no - 1);
                caret.pos = caret.line.text.Length;
              }
            }
            caret.tag = LineTag.FindTag(caret.line, caret.pos);
          } else {
            if (caret.line.line_no > 1) {
              caret.line = GetLine(caret.line.line_no - 1);
              caret.pos = caret.line.text.Length;
              caret.tag = LineTag.FindTag(caret.line, caret.pos);
            }
          }
          UpdateCaret();
          return;
        }

        case CaretDirection.LineUp: {
          if (caret.line.line_no > 1) {
            int  pixel;

            pixel = (int)caret.line.widths[caret.pos];
            PositionCaret(pixel, GetLine(caret.line.line_no - 1).Y);

            DisplayCaret ();
          }
          return;
        }

        case CaretDirection.LineDown: {
          if (caret.line.line_no < lines) {
            int  pixel;

            pixel = (int)caret.line.widths[caret.pos];
            PositionCaret(pixel, GetLine(caret.line.line_no + 1).Y);

            DisplayCaret ();
          }
          return;
        }

        case CaretDirection.Home: {
          if (caret.pos > 0) {
            caret.pos = 0;
            caret.tag = caret.line.tags;
            UpdateCaret();
          }
          return;
        }

        case CaretDirection.End: {
          if (caret.pos < caret.line.TextLengthWithoutEnding ()) {
            caret.pos = caret.line.TextLengthWithoutEnding ();
            caret.tag = LineTag.FindTag(caret.line, caret.pos);
            UpdateCaret();
          }
          return;
        }

        case CaretDirection.PgUp: {

          if (caret.line.line_no == 1 && owner.richtext) {
            owner.vscroll.Value = 0;
            Line line = GetLine (1);
            PositionCaret (line, 0);
          }

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

          return;
        }

        case CaretDirection.PgDn: {

          if (caret.line.line_no == lines && owner.richtext) {
            owner.vscroll.Value = owner.vscroll.Maximum - viewport_height + 1;
            Line line = GetLine (lines);
            PositionCaret (line, line.TextLengthWithoutEnding());
          }

          int y_offset = caret.line.Y - viewport_y;
          int index;
          LineTag top = FindCursor ((int) caret.line.widths [caret.pos],
              viewport_y + viewport_height, out index);

          owner.vscroll.Value = Math.Min (top.Line.Y, owner.vscroll.Maximum - viewport_height);
          PositionCaret ((int) caret.line.widths [caret.pos], y_offset + viewport_y);
          
          return;
        }

        case CaretDirection.CtrlPgUp: {
          PositionCaret(0, viewport_y);
          DisplayCaret ();
          return;
        }

        case CaretDirection.CtrlPgDn: {
          Line  line;
          LineTag  tag;
          int  index;

          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;
          }
          PositionCaret(line, line.Text.Length);
          DisplayCaret ();
          return;
        }

        case CaretDirection.CtrlHome: {
          caret.line = GetLine(1);
          caret.pos = 0;
          caret.tag = caret.line.tags;

          UpdateCaret();
          return;
        }

        case CaretDirection.CtrlEnd: {
          caret.line = GetLine(lines);
          caret.pos = caret.line.TextLengthWithoutEnding ();
          caret.tag = LineTag.FindTag(caret.line, caret.pos);

          UpdateCaret();
          return;
        }

        case CaretDirection.SelectionStart: {
          caret.line = selection_start.line;
          caret.pos = selection_start.pos;
          caret.tag = selection_start.tag;

          UpdateCaret();
          return;
        }

        case CaretDirection.SelectionEnd: {
          caret.line = selection_end.line;
          caret.pos = selection_end.pos;
          caret.tag = selection_end.tag;

          UpdateCaret();
          return;
        }
      }
    }

    internal void DumpDoc ()
    {
      Console.WriteLine ("<doc lines='{0}'>", lines);
      for (int i = 1; i <= lines ; i++) {
        Line line = GetLine (i);
        Console.WriteLine ("<line no='{0}' ending='{1}'>", line.line_no, line.ending);

        LineTag tag = line.tags;
        while (tag != null) {
          Console.Write ("\t<tag type='{0}' span='{1}->{2}' font='{3}' color='{4}'>",
              tag.GetType (), tag.Start, tag.Length, tag.Font, tag.Color);
          Console.Write (tag.Text ());
          Console.WriteLine ("</tag>");
          tag = tag.Next;
        }
        Console.WriteLine ("</line>");
      }
      Console.WriteLine ("</doc>");
    }

    // UIA: Used via reflection by TextProviderBehavior
    internal void GetVisibleLineIndexes (Rectangle clip, out int start, out int end)
    {
      if (multiline) {
        start = GetLineByPixel(clip.Top + viewport_y - offset_y, false).line_no;
        end = GetLineByPixel(clip.Bottom + viewport_y - offset_y, 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
      LineTag  tag;    // Current tag being drawn
      int start;    // First line to draw
      int end;    // Last line to draw
      StringBuilder text;  // String representing the current line
      int line_no;
      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);

      // 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.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 == offset_y + line.Y + line.height - viewport_y)
        end--;

      line_no = start;

      #if Debug
        DateTime  n = DateTime.Now;
        Console.WriteLine ("Started drawing: {0}s {1}ms", n.Second, n.Millisecond);
        Console.WriteLine ("CLIP:  {0}", clip);
        Console.WriteLine ("S: {0}", GetLine (start).text);
        Console.WriteLine ("E: {0}", GetLine (end).text);
      #endif

      // 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),
            offset_x + selection_start.line.widths [selection_start.pos] +
            selection_start.line.X - viewport_x, 
            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);
      }

      while (line_no <= end) {
        line = GetLine (line_no);
        float line_y = line.Y - viewport_y + offset_y;
        
        tag = line.tags;
        if (!calc_pass) {
          text = line.text;
        } else {
          if (PasswordCache.Length < line.text.Length)
            PasswordCache.Append(Char.Parse(password_char), line.text.Length - PasswordCache.Length);
          else if (PasswordCache.Length > line.text.Length)
            PasswordCache.Remove(line.text.Length, PasswordCache.Length - line.text.Length);
          text = PasswordCache;
        }

        int line_selection_start = text.Length + 1;
        int line_selection_end = text.Length + 1;
        if (selection_visible && owner.ShowSelection &&
            (line_no >= selection_start.line.line_no) &&
            (line_no <= selection_end.line.line_no)) {

          if (line_no == selection_start.line.line_no)
            line_selection_start = selection_start.pos + 1;
          else
            line_selection_start = 1;

          if (line_no == selection_end.line.line_no)
            line_selection_end = selection_end.pos + 1;
          else
            line_selection_end = text.Length + 1;

          if (line_selection_end == line_selection_start) {
            // There isn't really selection
            line_selection_start = text.Length + 1;
            line_selection_end = line_selection_start;
          } 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),
                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.ColorToDisplay;
        while (tag != null) {

          // Skip empty tags
          if (tag.Length == 0) {
            tag = tag.Next;
            continue;
          }

          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), 
                offset_x + tag.X + line.X - viewport_x,
                line_y + tag.Shift, tag.Width, line.height);
          }

          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))
              tag_color = ThemeEngine.Current.ColorGrayText;

          } 

          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_color = ThemeEngine.Current.ColorHighlightText;
              tag_pos = Math.Min (tag.End, line_selection_end);
            } else if (tag_pos < line_selection_start) {
              current_color = tag_color;
              tag_pos = Math.Min (tag.End, line_selection_start);
            } else {
              current_color = tag_color;
              tag_pos = tag.End;
            }

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

        line.DrawEnding (g, line_y);
        line_no++;
      }
    }

    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;
          return res;
        }
        if (res + 1 < line.Length && line [res + 1] == '\n') {
          ending = LineEnding.Hard;
          return res;
        }
        ending = LineEnding.Limp;
        return res;
      }

      if (rich_index != -1) {
        ending = LineEnding.Rich;
        return rich_index;
      }

      ending = LineEnding.Wrap;
      return line.Length;
    }

    // Get the line ending, but only of the types specified
    private int GetLineEnding (string line, int start, out LineEnding ending, LineEnding type)
    {
      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:
          return 1;
        case LineEnding.Hard:
          return 2;
        case LineEnding.Soft:
          return 3;
      }

      return 0;
    }

    internal string LineEndingToString (LineEnding ending)
    {
      switch (ending) {
        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;
      }
    }
    
    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;
      Line split_line;
      
      // 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, 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;
        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));

          Add (base_line + count, line_text, line.alignment, tag.Font, tag.Color, ending);

          Line last = GetLine (base_line + count);
          last.ending = 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);

      // 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
      if (update_caret) {
        Line l = GetLine (line.line_no + lines - old_line_count);
        PositionCaret (l, l.text.Length);
        DisplayCaret ();
      }
    }

    // Inserts a string at the given position
    internal void InsertString (Line line, int pos, string s)
    {
      // Update our character count
      CharCount += s.Length;

      // 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)
    {
      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);
      
      if (move_caret) {
        caret.pos++;
        UpdateCaret ();
        SetSelectionToCaret (true);
      }
    }
    
    internal void InsertPicture (Line line, int pos, RTF.Picture picture)
    {
      //LineTag next_tag;
      LineTag tag;
      int len;

      len = 1;

      // Just a place holder basically
      line.text.Insert (pos, "I");

      PictureTag picture_tag = new PictureTag (line, pos + 1, picture);

      tag = LineTag.FindTag (line, pos);
      picture_tag.CopyFormattingFrom (tag);
      /*next_tag = */tag.Break (pos + 1);
      picture_tag.Previous = tag;
      picture_tag.Next = tag.Next;
      tag.Next = picture_tag;

      //
      // Picture tags need to be surrounded by text tags
      //
      if (picture_tag.Next == null) {
        picture_tag.Next = new LineTag (line, pos + 1);
        picture_tag.Next.CopyFormattingFrom (tag);
        picture_tag.Next.Previous = picture_tag;
      }

      tag = picture_tag.Next;
      while (tag != null) {
        tag.Start += len;
        tag = tag.Next;
      }

      line.Grow (len);
      line.recalc = true;

      UpdateView (line, pos);
    }

    internal void DeleteMultiline (Line start_line, int pos, int length)
    {
      Marker start = new Marker ();
      Marker end = new Marker ();
      int start_index = LineTagToCharIndex (start_line, pos);

      start.line = start_line;
      start.pos = pos;
      start.tag = LineTag.FindTag (start_line, pos);

      CharIndexToLineTag (start_index + length, out end.line,
          out end.tag, out end.pos);

      SuspendUpdate ();

      if (start.line == end.line) {
        DeleteChars (start.line, pos, end.pos - pos);
      } else {

        // Delete first and last lines
        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) {
          for (int i = end.line.line_no - 1; i >= current; i--) {
            Delete (i);
          }
        }

        // BIG FAT WARNING - selection_end.line might be stale due 
        // to the above Delete() call. DONT USE IT before hitting the end of this method!

        // Join start and end
        Combine (start.line.line_no, current);
      }

      ResumeUpdate (true);
    }

    
    // Deletes n characters at the given position; it will not delete past line limits
    // pos is 0-based
    public void DeleteChars (Line line, int pos, int count)
    {
      // Reduce our character count
      CharCount -= count;
      
      line.DeleteCharacters (pos, count);

      if (pos >= line.TextLengthWithoutEnding ()) {
        LineEnding ending = line.ending;
        GetLineEnding (line.text.ToString (), 0, out ending);
        
        if (ending != line.ending) {
          line.ending = ending;

          if (!multiline) {
            UpdateView (line, lines, pos);
            owner.Invalidate ();
            return;
          }
        }
      }
      if (!multiline) {
        UpdateView (line, lines, pos);
        owner.Invalidate ();
      } else 
        UpdateView (line, pos);
    }

    // Deletes a character at or after the given position (depending on forward); it will not delete past line limits
    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) {
        undo.RecordDeleteString (line, pos, line, pos + 1);
        DeleteChars (line, pos, 1);
      } else {
        undo.RecordDeleteString (line, pos - 1, line, pos);
        DeleteChars (line, pos - 1, 1);
      }

      undo.EndUserAction ();
    }

    // Combine two lines
    internal void Combine(int FirstLine, int SecondLine) {
      Combine(GetLine(FirstLine), GetLine(SecondLine));
    }

    internal void Combine(Line first, Line second) {
      LineTag  last;
      int  shift;

      // strip the ending off of the first lines text
      first.text.Length = first.text.Length - LineEndingLength (first.ending);

      // Combine the two tag chains into one
      last = first.tags;

      // Maintain the line ending style
      first.ending = second.ending;

      while (last.Next != null) {
        last = last.Next;
      }

      // need to get the shift before setting the next tag since that effects length
      shift = last.Start + last.Length - 1;
      last.Next = second.tags;
      last.Next.Previous = last;

      // Fix up references within the chain
      last = last.Next;
      while (last != null) {
        last.Line = first;
        last.Start += shift;
        last = last.Next;
      }

      // Combine both lines' strings
      first.text.Insert(first.text.Length, second.text.ToString());
      first.Grow(first.text.Length);

      // Remove the reference to our (now combined) tags from the doomed line
      second.tags = null;

      // Renumber lines
      DecrementLines(first.line_no + 2);  // first.line_no + 1 will be deleted, so we need to start renumbering one later

      // Mop up
      first.recalc = true;
      first.height = 0;  // This forces RecalcDocument/UpdateView to redraw from this line on
      first.Streamline(lines);

      // Update Caret, Selection, etc
      if (caret.line == second) {
        caret.Combine(first, shift);
      }
      if (selection_anchor.line == second) {
        selection_anchor.Combine(first, shift);
      }
      if (selection_start.line == second) {
        selection_start.Combine(first, shift);
      }
      if (selection_end.line == second) {
        selection_end.Combine(first, shift);
      }

      #if Debug
        Line  check_first;
        Line  check_second;

        check_first = GetLine(first.line_no);
        check_second = GetLine(check_first.line_no + 1);

        Console.WriteLine("Pre-delete: Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
      #endif

      this.Delete(second);

      #if Debug
        check_first = GetLine(first.line_no);
        check_second = GetLine(check_first.line_no + 1);

        Console.WriteLine("Post-delete Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
      #endif
    }

    // Split the line at the position into two
    internal void Split(int LineNo, int pos) {
      Line  line;
      LineTag  tag;

      line = GetLine(LineNo);
      tag = LineTag.FindTag(line, pos);
      Split(line, tag, pos);
    }

    internal void Split(Line line, int pos) {
      LineTag  tag;

      tag = LineTag.FindTag(line, pos);
      Split(line, tag, pos);
    }

    ///<summary>Split line at given tag and position into two lines</summary>
    ///if more space becomes available on previous line
    internal void Split(Line line, LineTag tag, int pos) {
      LineTag  new_tag;
      Line  new_line;
      bool  move_caret;
      bool  move_sel_start;
      bool  move_sel_end;

      move_caret = false;
      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;
      }
      if (selection_start.line == line && selection_start.pos > pos) {
        move_sel_start = true;
      }

      if (selection_end.line == line && selection_end.pos > pos) {
        move_sel_end = true;
      }

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

        new_line = GetLine (line.line_no + 1);
        
        if (move_caret) {
          caret.line = new_line;
          caret.tag = new_line.tags;
          caret.pos = 0;

          if (selection_visible == false) {
            SetSelectionToCaret (true);
          }
        }

        if (move_sel_start) {
          selection_start.line = new_line;
          selection_start.pos = 0;
          selection_start.tag = new_line.tags;
        }

        if (move_sel_end) {
          selection_end.line = new_line;
          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);

      // Now transfer our tags from this line to the next
      new_line = GetLine(line.line_no + 1);

      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);
          line.tags = new_tag;
        }

        if (tag.Previous != null) {
          tag.Previous.Next = null;
        }
        new_line.tags = tag;
        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;

        new_tag = tag;
        while (new_tag != null) {
          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.CopyFormattingFrom (tag);
        new_line.tags = new_tag;
        if (new_tag.Next != null) {
          new_tag.Next.Previous = new_tag;
        }
        tag.Next = null;

        shift = pos;
        new_tag = new_tag.Next;
        while (new_tag != null) {
          new_tag.Start -= shift;
          new_tag.Line = new_line;
          new_tag = new_tag.Next;

        }
      }

      if (move_caret) {
        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;
        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) {
        selection_end.line = new_line;
        selection_end.pos = selection_end.pos - pos;
        selection_end.tag = new_line.FindTag(selection_end.pos);
      }

      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, Color color, LineEnding ending)
    {
      Add (LineNo, Text, alignment, font, color, ending);
    }

    internal void Add (int LineNo, string Text, HorizontalAlignment align, Font font, Color color, LineEnding ending)
    {
      Line  add;
      Line  line;
      int  line_no;

      CharCount += Text.Length;

      if (LineNo<1 || Text == null) {
        if (LineNo<1) {
          throw new ArgumentNullException("LineNo", "Line numbers must be positive");
        } else {
          throw new ArgumentNullException("Text", "Cannot insert NULL line");
        }
      }

      add = new Line (this, LineNo, Text, align, font, color, ending);

      line = document;
      while (line != sentinel) {
        add.parent = line;
        line_no = line.line_no;

        if (LineNo > line_no) {
          line = line.right;
        } else if (LineNo < line_no) {
          line = line.left;
        } else {
          // Bump existing line numbers; walk all nodes to the right of this one and increment line_no
          IncrementLines(line.line_no);
          line = line.left;
        }
      }

      add.left = sentinel;
      add.right = sentinel;

      if (add.parent != null) {
        if (LineNo > add.parent.line_no) {
          add.parent.right = add;
        } else {
          add.parent.left = add;
        }
      } else {
        // Root node
        document = add;
      }

      RebalanceAfterAdd(add);

      lines++;
    }

    internal virtual void Clear() {
      lines = 0;
      CharCount = 0;
      document = sentinel;
    }

    public virtual object Clone() {
      Document clone;

      clone = new Document(null);

      clone.lines = this.lines;
      clone.document = (Line)document.Clone();

      return clone;
    }

    private void Delete (int LineNo)
    {
      Line  line;

      if (LineNo > lines)
        return;

      line = GetLine (LineNo);

      CharCount -= line.text.Length;

      DecrementLines (LineNo + 1);
      Delete (line);
    }

    private void Delete(Line line1) {
      Line  line2;// = new Line();
      Line  line3;

      if ((line1.left == sentinel) || (line1.right == sentinel)) {
        line3 = line1;
      } else {
        line3 = line1.right;
        while (line3.left != sentinel) {
          line3 = line3.left;
        }
      }

      if (line3.left != sentinel) {
        line2 = line3.left;
      } else {
        line2 = line3.right;
      }

      line2.parent = line3.parent;
      if (line3.parent != null) {
        if(line3 == line3.parent.left) {
          line3.parent.left = line2;
        } else {
          line3.parent.right = line2;
        }
      } else {
        document = line2;
      }

      if (line3 != line1) {
        LineTag  tag;

        if (selection_start.line == line3) {
          selection_start.line = line1;
        }

        if (selection_end.line == line3) {
          selection_end.line = line1;
        }

        if (selection_anchor.line == line3) {
          selection_anchor.line = line1;
        }

        if (caret.line == line3) {
          caret.line = line1;
        }


        line1.alignment = line3.alignment;
        line1.ascent = line3.ascent;
        line1.hanging_indent = line3.hanging_indent;
        line1.height = line3.height;
        line1.indent = line3.indent;
        line1.line_no = line3.line_no;
        line1.recalc = line3.recalc;
        line1.right_indent = line3.right_indent;
        line1.ending = line3.ending;
        line1.space = line3.space;
        line1.tags = line3.tags;
        line1.text = line3.text;
        line1.widths = line3.widths;
        line1.offset = line3.offset;

        tag = line1.tags;
        while (tag != null) {
          tag.Line = line1;
          tag = tag.Next;
        }
      }

      if (line3.color == LineColor.Black)
        RebalanceAfterDelete(line2);

      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;
      Line  l2;
      int  p1;
      int  p2;

      if ((start == end) && (start_pos == end_pos)) {
        return;
      }

      if (end_pos == -1) {
        end_pos = end.text.Length;
      }
  
      // figure out what's before what so the logic below is straightforward
      if (start.line_no < end.line_no) {
        l1 = start;
        p1 = start_pos;

        l2 = end;
        p2 = end_pos;
      } else if (start.line_no > end.line_no) {
        l1 = end;
        p1 = end_pos;

        l2 = start;
        p2 = start_pos;
      } else {
        if (start_pos < end_pos) {
          l1 = start;
          p1 = start_pos;

          l2 = end;
          p2 = end_pos;
        } else {
          l1 = end;
          p1 = end_pos;

          l2 = start;
          p2 = start_pos;
        }

        int endpoint = (int) l1.widths [p2];
        if (p2 == l1.text.Length + 1) {
          endpoint = (int) viewport_width;
        }

        #if Debug
          Console.WriteLine("Invaliding backwards from {0}:{1} to {2}:{3}   {4}",
              l1.line_no, p1, l2.line_no, p2,
              new Rectangle(
                (int)l1.widths[p1] + l1.X - viewport_x, 
                l1.Y - viewport_y, 
                (int)l1.widths[p2], 
                l1.height
                )
            );
        #endif

        owner.Invalidate(new Rectangle (
          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;
      }

      #if Debug
        Console.WriteLine("Invaliding from {0}:{1} to {2}:{3} Start  => x={4}, y={5}, {6}x{7}", l1.line_no, p1, l2.line_no, p2, (int)l1.widths[p1] + l1.X - viewport_x, l1.Y - viewport_y, viewport_width, l1.height);
        Console.WriteLine ("invalidate start line:  {0}  position:  {1}", l1.text, p1);
      #endif

      // Three invalidates:
      // First line from start
      owner.Invalidate(new Rectangle(
        offset_x + (int)l1.widths[p1] + l1.X - viewport_x, 
        offset_y + l1.Y - viewport_y, 
        viewport_width, 
        l1.height));

      
      // lines inbetween
      if ((l1.line_no + 1) < l2.line_no) {
        int  y;

        y = GetLine(l1.line_no + 1).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);
        #endif
      }
      

      // Last line to end
      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
    }

    /// <summary>Select text around caret</summary>
    internal void ExpandSelection(CaretSelection mode, bool to_caret) {
      if (to_caret) {
        // We're expanding the selection to the caret position
        switch(mode) {
          case CaretSelection.Line: {
            // Invalidate the selection delta
            if (caret > selection_prev) {
              Invalidate(selection_prev.line, 0, caret.line, caret.line.text.Length);
            } else {
              Invalidate(selection_prev.line, selection_prev.line.text.Length, caret.line, 0);
            }

            if (caret.line.line_no <= selection_anchor.line.line_no) {
              selection_start.line = caret.line;
              selection_start.tag = caret.line.tags;
              selection_start.pos = 0;

              selection_end.line = selection_anchor.line;
              selection_end.tag = selection_anchor.tag;
              selection_end.pos = selection_anchor.pos;

              selection_end_anchor = true;
            } else {
              selection_start.line = selection_anchor.line;
              selection_start.pos = 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;
              selection_end.pos = caret.line.text.Length;

              selection_end_anchor = false;
            }
            selection_prev.line = caret.line;
            selection_prev.tag = caret.tag;
            selection_prev.pos = caret.pos;

            break;
          }

          case CaretSelection.Word: {
            int  start_pos;
            int  end_pos;

            start_pos = FindWordSeparator(caret.line, caret.pos, false);
            end_pos = FindWordSeparator(caret.line, caret.pos, true);

            
            // Invalidate the selection delta
            if (caret > selection_prev) {
              Invalidate(selection_prev.line, selection_prev.pos, caret.line, end_pos);
            } else {
              Invalidate(selection_prev.line, selection_prev.pos, caret.line, start_pos);
            }
            if (caret < selection_anchor) {
              selection_start.line = caret.line;
              selection_start.tag = caret.line.FindTag(start_pos + 1);
              selection_start.pos = start_pos;

              selection_end.line = selection_anchor.line;
              selection_end.tag = selection_anchor.tag;
              selection_end.pos = selection_anchor.pos;

              selection_prev.line = caret.line;
              selection_prev.tag = caret.tag;
              selection_prev.pos = start_pos;

              selection_end_anchor = true;
            } else {
              selection_start.line = selection_anchor.line;
              selection_start.pos = 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);
              selection_end.pos = end_pos;

              selection_prev.line = caret.line;
              selection_prev.tag = caret.tag;
              selection_prev.pos = end_pos;

              selection_end_anchor = false;
            }
            break;
          }

          case CaretSelection.Position: {
            SetSelectionToCaret(false);
            return;
          }
        }
      } else {
        // We're setting the selection 'around' the caret position
        switch(mode) {
          case CaretSelection.Line: {
            this.Invalidate(caret.line, 0, caret.line, caret.line.text.Length);

            selection_start.line = caret.line;
            selection_start.tag = caret.line.tags;
            selection_start.pos = 0;

            selection_end.line = caret.line;
            selection_end.pos = caret.line.text.Length;
            selection_end.tag = caret.line.FindTag(selection_end.pos);

            selection_anchor.line = selection_end.line;
            selection_anchor.tag = selection_end.tag;
            selection_anchor.pos = selection_end.pos;
            selection_anchor.height = 0;

            selection_prev.line = caret.line;
            selection_prev.tag = caret.tag;
            selection_prev.pos = caret.pos;

            this.selection_end_anchor = true;

            break;
          }

          case CaretSelection.Word: {
            int  start_pos;
            int  end_pos;

            start_pos = FindWordSeparator(caret.line, caret.pos, false);
            end_pos = FindWordSeparator(caret.line, caret.pos, true);

            this.Invalidate(selection_start.line, start_pos, caret.line, end_pos);

            selection_start.line = caret.line;
            selection_start.tag = caret.line.FindTag(start_pos + 1);
            selection_start.pos = start_pos;

            selection_end.line = caret.line;
            selection_end.tag = caret.line.FindTag(end_pos);
            selection_end.pos = end_pos;

            selection_anchor.line = selection_end.line;
            selection_anchor.tag = selection_end.tag;
            selection_anchor.pos = selection_end.pos;
            selection_anchor.height = start_pos;

            selection_prev.line = caret.line;
            selection_prev.tag = caret.tag;
            selection_prev.pos = caret.pos;

            this.selection_end_anchor = true;

            break;
          }
        }
      }

      SetSelectionVisible (!(selection_start == selection_end));
    }

    internal void SetSelectionToCaret(bool start) {
      if (start) {
        // Invalidate old selection; selection is being reset to empty
        this.Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);

        selection_start.line = caret.line;
        selection_start.tag = caret.tag;
        selection_start.pos = caret.pos;

        // start always also selects end
        selection_end.line = caret.line;
        selection_end.tag = caret.tag;
        selection_end.pos = caret.pos;

        selection_anchor.line = caret.line;
        selection_anchor.tag = caret.tag;
        selection_anchor.pos = caret.pos;
      } else {
        // Invalidate from previous end to caret (aka new end)
        if (selection_end_anchor) {
          if (selection_start != caret) {
            this.Invalidate(selection_start.line, selection_start.pos, caret.line, caret.pos);
          }
        } else {
          if (selection_end != caret) {
            this.Invalidate(selection_end.line, selection_end.pos, caret.line, caret.pos);
          }
        }

        if (caret < selection_anchor) {
          selection_start.line = caret.line;
          selection_start.tag = caret.tag;
          selection_start.pos = caret.pos;

          selection_end.line = selection_anchor.line;
          selection_end.tag = selection_anchor.tag;
          selection_end.pos = selection_anchor.pos;

          selection_end_anchor = true;
        } else {
          selection_start.line = selection_anchor.line;
          selection_start.tag = selection_anchor.tag;
          selection_start.pos = selection_anchor.pos;

          selection_end.line = caret.line;
          selection_end.tag = caret.tag;
          selection_end.pos = caret.pos;

          selection_end_anchor = false;
        }
      }

      SetSelectionVisible (!(selection_start == selection_end));
    }

    internal void SetSelection(Line start, int start_pos, Line end, int end_pos) {
      if (selection_visible) {
        Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
      }

      if ((end.line_no < start.line_no) || ((end == start) && (end_pos <= start_pos))) {
        selection_start.line = end;
        selection_start.tag = LineTag.FindTag(end, end_pos);
        selection_start.pos = end_pos;

        selection_end.line = start;
        selection_end.tag = LineTag.FindTag(start, start_pos);
        selection_end.pos = start_pos;

        selection_end_anchor = true;
      } else {
        selection_start.line = start;
        selection_start.tag = LineTag.FindTag(start, start_pos);
        selection_start.pos = start_pos;

        selection_end.line = end;
        selection_end.tag = LineTag.FindTag(end, end_pos);
        selection_end.pos = end_pos;

        selection_end_anchor = false;
      }

      selection_anchor.line = start;
      selection_anchor.tag = selection_start.tag;
      selection_anchor.pos = start_pos;

      if (((start == end) && (start_pos == end_pos)) || start == null || end == null) {
        SetSelectionVisible (false);
      } else {
        SetSelectionVisible (true);
        Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
      }
    }

    internal void SetSelectionStart(Line start, int start_pos, bool invalidate) {
      // Invalidate from the previous to the new start pos
      if (invalidate)
        Invalidate(selection_start.line, selection_start.pos, start, start_pos);

      selection_start.line = start;
      selection_start.pos = start_pos;
      selection_start.tag = LineTag.FindTag(start, start_pos);

      selection_anchor.line = start;
      selection_anchor.pos = start_pos;
      selection_anchor.tag = selection_start.tag;

      selection_end_anchor = false;

      
      if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
        SetSelectionVisible (true);
      } else {
        SetSelectionVisible (false);
      }

      if (invalidate)
        Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
    }

    internal void SetSelectionStart(int character_index, bool invalidate) {
      Line  line;
      LineTag  tag;
      int  pos;

      if (character_index < 0) {
        return;
      }

      CharIndexToLineTag(character_index, out line, out tag, out pos);
      SetSelectionStart(line, pos, invalidate);
    }

    internal void SetSelectionEnd(Line end, int end_pos, bool invalidate) {

      if (end == selection_end.line && end_pos == selection_start.pos) {
        selection_anchor.line = selection_start.line;
        selection_anchor.tag = selection_start.tag;
        selection_anchor.pos = selection_start.pos;

        selection_end.line = selection_start.line;
        selection_end.tag = selection_start.tag;
        selection_end.pos = selection_start.pos;

        selection_end_anchor = false;
      } else if ((end.line_no < selection_anchor.line.line_no) || ((end == selection_anchor.line) && (end_pos <= selection_anchor.pos))) {
        selection_start.line = end;
        selection_start.tag = LineTag.FindTag(end, end_pos);
        selection_start.pos = end_pos;

        selection_end.line = selection_anchor.line;
        selection_end.tag = selection_anchor.tag;
        selection_end.pos = selection_anchor.pos;

        selection_end_anchor = true;
      } else {
        selection_start.line = selection_anchor.line;
        selection_start.tag = selection_anchor.tag;
        selection_start.pos = selection_anchor.pos;

        selection_end.line = end;
        selection_end.tag = LineTag.FindTag(end, end_pos);
        selection_end.pos = end_pos;

        selection_end_anchor = false;
      }

      if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
        SetSelectionVisible (true);
        if (invalidate)
          Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
      } else {
        SetSelectionVisible (false);
        // ?? Do I need to invalidate here, tests seem to work without it, but I don't think they should :-s
      }
    }

    internal void SetSelectionEnd(int character_index, bool invalidate) {
      Line  line;
      LineTag  tag;
      int  pos;

      if (character_index < 0) {
        return;
      }

      CharIndexToLineTag(character_index, out line, out tag, out pos);
      SetSelectionEnd(line, pos, invalidate);
    }

    internal void SetSelection(Line start, int start_pos) {
      if (selection_visible) {
        Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
      }

      selection_start.line = start;
      selection_start.pos = start_pos;
      selection_start.tag = LineTag.FindTag(start, start_pos);

      selection_end.line = start;
      selection_end.tag = selection_start.tag;
      selection_end.pos = start_pos;

      selection_anchor.line = start;
      selection_anchor.tag = selection_start.tag;
      selection_anchor.pos = start_pos;

      selection_end_anchor = false;
      SetSelectionVisible (false);
    }

    internal void InvalidateSelectionArea() {
      Invalidate (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
    }

    // Return the current selection, as string
    internal string GetSelection() {
      // We return String.Empty if there is no selection
      if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
        return string.Empty;
      }

      if (selection_start.line == selection_end.line) {
        return selection_start.line.text.ToString (selection_start.pos, selection_end.pos - selection_start.pos);
      } else {
        StringBuilder  sb;
        int    i;
        int    start;
        int    end;

        sb = new StringBuilder();
        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));

        if ((start + 1) < end) {
          for (i = start + 1; i < end; i++) {
            sb.Append(GetLine(i).text.ToString());
          }
        }

        sb.Append(selection_end.line.text.ToString(0, selection_end.pos));

        return sb.ToString();
      }
    }

    internal void ReplaceSelection(string s, bool select_new) {
      int    i;

      int selection_start_pos = LineTagToCharIndex (selection_start.line, selection_start.pos);
      SuspendRecalc ();

      // First, delete any selected text
      if ((selection_start.pos != selection_end.pos) || (selection_start.line != selection_end.line)) {
        if (selection_start.line == selection_end.line) {
          undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.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 + 1);
        } else {
          int    start;
          int    end;

          start = selection_start.line.line_no;
          end = selection_end.line.line_no;

          undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);

          InvalidateLinesAfter(selection_start.line);

          // Delete first line
          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, 0, selection_end.pos);

          start++;
          if (start < end) {
            for (i = end - 1; i >= start; i--) {
              Delete(i);
            }
          }

          // BIG FAT WARNING - selection_end.line might be stale due 
          // to the above Delete() call. DONT USE IT before hitting the end of this method!

          // Join start and end
          Combine(selection_start.line.line_no, start);
        }
      }


      Insert(selection_start.line, selection_start.pos, false, s);
      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);

        selection_end.line = selection_start.line;
        selection_end.pos = selection_start.pos;
        selection_end.tag = selection_start.tag;
        selection_anchor.line = selection_start.line;
        selection_anchor.pos = selection_start.pos;
        selection_anchor.tag = selection_start.tag;

        SetSelectionVisible (false);
      } else {
        CharIndexToLineTag(selection_start_pos, out selection_start.line,
            out selection_start.tag, out selection_start.pos);

        CharIndexToLineTag(selection_start_pos + s.Length, out selection_end.line,
            out selection_end.tag, out selection_end.pos);

        selection_anchor.line = selection_start.line;
        selection_anchor.pos = selection_start.pos;
        selection_anchor.tag = selection_start.tag;

        SetSelectionVisible (true);
      }

      PositionCaret (selection_start.line, selection_start.pos);
      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) {
      Line  line;
      LineTag  tag;
      int  i;
      int  chars;
      int  start;

      chars = 0;

      for (i = 1; i <= lines; i++) {
        line = GetLine(i);

        start = chars;
        chars += line.text.Length;

        if (index <= chars) {
          // we found the line
          tag = line.tags;

          while (tag != null) {
            if (index < (start + tag.Start + tag.Length - 1)) {
              line_out = line;
              tag_out = LineTag.GetFinalTag (tag);
              pos = index - start;
              return;
            }
            if (tag.Next == null) {
              Line  next_line;

              next_line = GetLine(line.line_no + 1);

              if (next_line != null) {
                line_out = next_line;
                tag_out = LineTag.GetFinalTag (next_line.tags);
                pos = 0;
                return;
              } else {
                line_out = line;
                tag_out = LineTag.GetFinalTag (tag);
                pos = line_out.text.Length;
                return;
              }
            }
            tag = tag.Next;
          }
        }
      }

      line_out = GetLine(lines);
      tag = line_out.tags;
      while (tag.Next != null) {
        tag = tag.Next;
      }
      tag_out = tag;
      pos = line_out.text.Length;
    }

    internal int LineTagToCharIndex(Line line, int pos) {
      int  i;
      int  length;

      // Count first and last line
      length = 0;

      // Count the lines in the middle

      for (i = 1; i < line.line_no; i++) {
        length += GetLine(i).text.Length;
      }

      length += pos;

      return length;
    }

    internal int SelectionLength() {
      if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
        return 0;
      }

      if (selection_start.line == selection_end.line) {
        return selection_end.pos - selection_start.pos;
      } else {
        int  i;
        int  start;
        int  end;
        int  length;

        // Count first and last line
        length = selection_start.line.text.Length - selection_start.pos + selection_end.pos + crlf_size;

        // Count the lines in the middle
        start = selection_start.line.line_no + 1;
        end = selection_end.line.line_no;

        if (start < end) {
          for (i = start; i < end; i++) {
            Line line = GetLine (i);
            length += line.text.Length + LineEndingLength (line.ending);
          }
        }

        return length;
      }

      
    }


    // 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;

      while (line != sentinel) {
        if (LineNo == line.line_no) {
          return line;
        } else if (LineNo < line.line_no) {
          line = line.left;
        } else {
          line = line.right;
        }
      }

      return null;
    }

    /// <summary>Retrieve the previous tag; walks line boundaries</summary>
    internal LineTag PreviousTag(LineTag tag) {
      Line l; 

      if (tag.Previous != null) {
        return tag.Previous;
      }

      // Next line 
      if (tag.Line.line_no == 1) {
        return null;
      }

      l = GetLine(tag.Line.line_no - 1);
      if (l != null) {
        LineTag t;

        t = l.tags;
        while (t.Next != null) {
          t = t.Next;
        }
        return t;
      }

      return null;
    }

    /// <summary>Retrieve the next tag; walks line boundaries</summary>
    internal LineTag NextTag(LineTag tag) {
      Line l;

      if (tag.Next != null) {
        return tag.Next;
      }

      // Next line
      l = GetLine(tag.Line.line_no + 1);
      if (l != null) {
        return l.tags;
      }

      return null;
    }

    internal Line ParagraphStart(Line line) {
      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;
    }       

    internal Line ParagraphEnd(Line line) {
      Line    l;
   
      while (line.ending == LineEnding.Wrap) {
        l = GetLine(line.line_no + 1);
        if ((l == null) || (l.ending != LineEnding.Wrap)) {
          break;
        }
        line = l;
      }
      return line;
    }

    /// <summary>Give it a pixel offset coordinate and it returns the Line covering that are (offset
    /// is either X or Y depending on if we are multiline
    /// </summary>
    internal Line GetLineByPixel (int offset, bool exact)
    {
      Line  line = document;
      Line  last = null;

      if (multiline) {
        while (line != sentinel) {
          last = line;
          if ((offset >= line.Y) && (offset < (line.Y+line.height))) {
            return line;
          } else if (offset < line.Y) {
            line = line.left;
          } else {
            line = line.right;
          }
        }
      } else {
        while (line != sentinel) {
          last = line;
          if ((offset >= line.X) && (offset < (line.X + line.Width)))
            return line;
          else if (offset < line.X)
            line = line.left;
          else
            line = line.right;
        }
      }

      if (exact) {
        return null;
      }
      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 && tag.Start == 1)
        index = 0;
      else
        index = tag.GetCharIndex (x - line.align_shift);
      
      return tag;
    }

    /// <summary>Format area of document in specified font and color</summary>
    /// <param name="start_pos">1-based start position on start_line</param>
    /// <param name="end_pos">1-based end position on end_line </param>
    internal void FormatText (Line start_line, int start_pos, Line end_line, int end_pos, Font font,
        Color color, Color back_color, FormatSpecified specified)
    {
      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, font, color, back_color, specified);

        // Format last line
        LineTag.FormatText(end_line, 1, end_pos, font, color, back_color, specified);

        // 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, font, color, back_color, specified);
        }
      } 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;
      }
    }

    internal void RecalculateAlignments ()
    {
      Line  line;
      int  line_no;

      line_no = 1;



      while (line_no <= lines) {
        line = GetLine(line_no);

        if (line != null) {
          switch (line.alignment) {
          case HorizontalAlignment.Left:
            line.align_shift = 0;
            break;
          case HorizontalAlignment.Center:
             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] - right_margin;
            break;
          }
        }

        line_no++;
      }
      return;
    }

    /// <summary>Calculate formatting for the whole document</summary>
    internal bool RecalculateDocument(Graphics g) {
      return RecalculateDocument(g, 1, this.lines, false);
    }

    /// <summary>Calculate formatting starting at a certain line</summary>
    internal bool RecalculateDocument(Graphics g, int start) {
      return RecalculateDocument(g, start, this.lines, false);
    }

    /// <summary>Calculate formatting within two given line numbers</summary>
    internal bool RecalculateDocument(Graphics g, int start, int end) {
      return RecalculateDocument(g, start, end, false);
    }

    /// <summary>With optimize on, returns true if line heights changed</summary>
    internal bool RecalculateDocument(Graphics g, int start, int end, bool optimize) {
      Line  line;
      int  line_no;
      int  offset;
      int  new_width;
      bool  changed;
      int  shift;

      if (recalc_suspended > 0) {
        recalc_pending = true;
        recalc_start = Math.Min (recalc_start, start);
        recalc_end = Math.Max (recalc_end, end);
        recalc_optimize = optimize;
        return false;
      }

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

      offset = GetLine(start).offset;
      line_no = start;
      new_width = 0;
      shift = this.lines;
      if (!optimize) {
        changed = true;    // We always return true if we run non-optimized
      } else {
        changed = false;
      }

      while (line_no <= (end + this.lines - shift)) {
        line = GetLine(line_no++);
        line.offset = offset;

        // if we are not calculating a password
        if (!calc_pass) {
          if (!optimize) {
            line.RecalculateLine(g, this);
          } else {
            if (line.recalc && line.RecalculateLine(g, this)) {
              changed = true;
              // If the height changed, all subsequent lines change
              end = this.lines;
              shift = this.lines;
            }
          }
        } else {
          if (!optimize) {
            line.RecalculatePasswordLine(g, this);
          } else {
            if (line.recalc && line.RecalculatePasswordLine(g, this)) {
              changed = true;
              // If the height changed, all subsequent lines change
              end = this.lines;
              shift = this.lines;
            }
          }
        }

        if (line.widths[line.text.Length] > new_width) {
          new_width = (int)line.widths[line.text.Length];
        }

        // Calculate alignment
        if (line.alignment != HorizontalAlignment.Left) {
          if (line.alignment == HorizontalAlignment.Center) {
            line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
          } else {
            line.align_shift = viewport_width - (int)line.widths[line.text.Length] - 1;
          }
        }

        if (multiline)
          offset += line.height;
        else
          offset += (int) line.widths [line.text.Length];

        if (line_no > lines) {
          break;
        }
      }

      if (document_x != new_width) {
        document_x = new_width;
        if (WidthChanged != null) {
          WidthChanged(this, null);
        }
      }

      RecalculateAlignments();

      line = GetLine(lines);

      if (document_y != line.Y + line.height) {
        document_y = line.Y + line.height;
        if (HeightChanged != null) {
          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;
    }

    internal int Size() {
      return lines;
    }

    private void owner_HandleCreated(object sender, EventArgs e) {
      RecalculateDocument(owner.CreateGraphicsInternal());
      AlignCaret();
    }

    private void owner_VisibleChanged(object sender, EventArgs e) {
      if (owner.Visible) {
        RecalculateDocument(owner.CreateGraphicsInternal());
      }
    }

    internal static bool IsWordSeparator (char ch)
    {
      switch (ch) {
      case ' ':
      case '\t':
      case '(':
      case ')':
      case '\r':
      case '\n':
        return true;
      default:
        return false;
      }
    }

    internal int FindWordSeparator(Line line, int pos, bool forward) {
      int len;

      len = line.text.Length;

      if (forward) {
        for (int i = pos + 1; i < len; i++) {
          if (IsWordSeparator(line.Text[i])) {
            return i + 1;
          }
        }
        return len;
      } else {
        for (int i = pos - 1; i > 0; i--) {
          if (IsWordSeparator(line.Text[i - 1])) {
            return i;
          }
        }
        return 0;
      }
    }

    /* Search document for text */
    internal bool FindChars(char[] chars, Marker start, Marker end, out Marker result) {
      Line  line;
      int  line_no;
      int  pos;
      int  line_len;

      // Search for occurence of any char in the chars array
      result = new Marker();

      line = start.line;
      line_no = start.line.line_no;
      pos = start.pos;
      while (line_no <= end.line.line_no) {
        line_len = line.text.Length;
        while (pos < line_len) {
          for (int i = 0; i < chars.Length; i++) {
            if (line.text[pos] == chars[i]) {
              // Special case
              if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
                return false;
              }

              result.line = line;
              result.pos = pos;
              return true;
            }
          }
          pos++;
        }

        pos = 0;
        line_no++;
        line = GetLine(line_no);
      }

      return false;
    }

    // This version does not build one big string for searching, instead it handles 
    // line-boundaries, which is faster and less memory intensive
    // FIXME - Depending on culture stuff we might have to create a big string and use culturespecific 
    // search stuff and change it to accept and return positions instead of Markers (which would match 
    // RichTextBox behaviour better but would be inconsistent with the rest of TextControl)
    internal bool Find(string search, Marker start, Marker end, out Marker result, RichTextBoxFinds options) {
      Marker  last;
      string  search_string;
      Line  line;
      int  line_no;
      int  pos;
      int  line_len;
      int  current;
      bool  word;
      bool  word_option;
      bool  ignore_case;
      bool  reverse;
      char  c;

      result = new Marker();
      word_option = ((options & RichTextBoxFinds.WholeWord) != 0);
      ignore_case = ((options & RichTextBoxFinds.MatchCase) == 0);
      reverse = ((options & RichTextBoxFinds.Reverse) != 0);

      line = start.line;
      line_no = start.line.line_no;
      pos = start.pos;
      current = 0;

      // Prep our search string, lowercasing it if we do case-independent matching
      if (ignore_case) {
        StringBuilder  sb;
        sb = new StringBuilder(search);
        for (int i = 0; i < sb.Length; i++) {
          sb[i] = Char.ToLower(sb[i]);
        }
        search_string = sb.ToString();
      } else {
        search_string = search;
      }

      // We need to check if the character before our start position is a wordbreak
      if (word_option) {
        if (line_no == 1) {
          if ((pos == 0) || (IsWordSeparator(line.text[pos - 1]))) {
            word = true;
          } else {
            word = false;
          }
        } else {
          if (pos > 0) {
            if (IsWordSeparator(line.text[pos - 1])) {
              word = true;
            } else {
              word = false;
            }
          } else {
            // Need to check the end of the previous line
            Line  prev_line;

            prev_line = GetLine(line_no - 1);
            if (prev_line.ending == LineEnding.Wrap) {
              if (IsWordSeparator(prev_line.text[prev_line.text.Length - 1])) {
                word = true;
              } else {
                word = false;
              }
            } else {
              word = true;
            }
          }
        }
      } else {
        word = false;
      }

      // To avoid duplication of this loop with reverse logic, we search
      // through the document, remembering the last match and when returning
      // report that last remembered match

      last = new Marker();
      last.height = -1;  // Abused - we use it to track change

      while (line_no <= end.line.line_no) {
        if (line_no != end.line.line_no) {
          line_len = line.text.Length;
        } else {
          line_len = end.pos;
        }

        while (pos < line_len) {

          if (word_option && (current == search_string.Length)) {
            if (IsWordSeparator(line.text[pos])) {
              if (!reverse) {
                goto FindFound;
              } else {
                last = result;
                current = 0;
              }
            } else {
              current = 0;
            }
          }

          if (ignore_case) {
            c = Char.ToLower(line.text[pos]);
          } else {
            c = line.text[pos];
          }

          if (c == search_string[current]) {
            
            if (current == 0) {
              result.line = line;
              result.pos = pos;
            }
            if (!word_option || (word_option && (word || (current > 0)))) {
              current++;
            }

            if (!word_option && (current == search_string.Length)) {
              if (!reverse) {
                goto FindFound;
              } else {
                last = result;
                current = 0;
              }
            }
          } else {
            current = 0;
          }
          pos++;

          if (!word_option) {
            continue;
          }

          if (IsWordSeparator(c)) {
            word = true;
          } else {
            word = false;
          }
        }

        if (word_option) {
          // Mark that we just saw a word boundary
          if (line.ending != LineEnding.Wrap || line.line_no == lines - 1) {
            word = true;
          }

          if (current == search_string.Length) {
            if (word) {
              if (!reverse) {
                goto FindFound;
              } else {
                last = result;
                current = 0;
              }
            } else {
              current = 0;
            }
          }
        }

        pos = 0;
        line_no++;
        line = GetLine(line_no);
      }

      if (reverse) {
        if (last.height != -1) {
          result = last;
          return true;
        }
      }

      return false;

      FindFound:
      if (!reverse) {
//        if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
//          return false;
//        }
        return true;
      }

      result = last;
      return true;

    }

    /* Marker stuff */
    internal void GetMarker(out Marker mark, bool start) {
      mark = new Marker();

      if (start) {
        mark.line = GetLine(1);
        mark.tag = mark.line.tags;
        mark.pos = 0;
      } else {
        mark.line = GetLine(lines);
        mark.tag = mark.line.tags;
        while (mark.tag.Next != null) {
          mark.tag = mark.tag.Next;
        }
        mark.pos = mark.line.text.Length;
      }
    }
    #endregion  // Internal Methods

    #region Events
    internal event EventHandler CaretMoved;
    internal event EventHandler WidthChanged;
    internal event EventHandler HeightChanged;
    internal event EventHandler LengthChanged;
#if NET_2_0
    internal event EventHandler UIASelectionChanged;
#endif
    #endregion  // Events

    #region Administrative
    public IEnumerator GetEnumerator() {
      // FIXME
      return null;
    }

    public override bool Equals(object obj) {
      if (obj == null) {
        return false;
      }

      if (!(obj is Document)) {
        return false;
      }

      if (obj == this) {
        return true;
      }

      if (ToString().Equals(((Document)obj).ToString())) {
        return true;
      }

      return false;
    }

    public override int GetHashCode() {
      return document_id;
    }

    public override string ToString() {
      return "document " + this.document_id;
    }
    #endregion  // Administrative
  }

  internal class PictureTag : LineTag {

    internal RTF.Picture picture;

    internal PictureTag (Line line, int start, RTF.Picture picture) : base (line, start)
    {
      this.picture = picture;
    }

    public override bool IsTextTag {
      get { return false; }
    }

    public override SizeF SizeOfPosition (Graphics dc, int pos)
    {
      return picture.Size;
    }

    internal override int MaxHeight ()
    {
      return (int) (picture.Height + 0.5F);
    }

    public override void Draw (Graphics dc, Color color, float xoff, float y, int start, int end)
    {
      picture.DrawImage (dc, xoff + Line.widths [start], y, false);
    }

    public override void Draw (Graphics dc, Color color, float xoff, float y, int start, int end, string text)
    {
      picture.DrawImage (dc, xoff + + Line.widths [start], y, false);
    }

    public override string Text ()
    {
      return "I";
    }
  }

  internal class UndoManager {

    internal enum ActionType {

      Typing,

      // This is basically just cut & paste
      InsertString,
      DeleteString,

      UserActionBegin,
      UserActionEnd
    }

    internal class Action {
      internal ActionType  type;
      internal int    line_no;
      internal int    pos;
      internal object    data;
    }

    #region Local Variables
    private Document  document;
    private Stack    undo_actions;
    private Stack    redo_actions;

    //private int    caret_line;
    //private int    caret_pos;

    // When performing an action, we lock the queue, so that the action can't be undone
    private bool locked;
    #endregion  // Local Variables

    #region Constructors
    internal UndoManager (Document document)
    {
      this.document = document;
      undo_actions = new Stack (50);
      redo_actions = new Stack (50);
    }
    #endregion  // Constructors

    #region Properties
    internal bool CanUndo {
      get { return undo_actions.Count > 0; }
    }

    internal bool CanRedo {
      get { return redo_actions.Count > 0; }
    }

    internal string UndoActionName {
      get {
        foreach (Action action in undo_actions) {
          if (action.type == ActionType.UserActionBegin)
            return (string) action.data;
          if (action.type == ActionType.Typing)
            return Locale.GetText ("Typing");
        }
        return String.Empty;
      }
    }

    internal string RedoActionName {
      get {
        foreach (Action action in redo_actions) {
          if (action.type == ActionType.UserActionBegin)
            return (string) action.data;
          if (action.type == ActionType.Typing)
            return Locale.GetText ("Typing");
        }
        return String.Empty;
      }
    }
    #endregion  // Properties

    #region Internal Methods
    internal void Clear ()
    {
      undo_actions.Clear();
      redo_actions.Clear();
    }

    internal bool Undo ()
    {
      Action action;
      bool user_action_finished = false;

      if (undo_actions.Count == 0)
        return false;

      locked = true;
      do {
        Line start;
        action = (Action) undo_actions.Pop ();

        // Put onto redo stack
        redo_actions.Push(action);

        // Do the thing
        switch(action.type) {

        case ActionType.UserActionBegin:
          user_action_finished = true;
          break;

        case ActionType.UserActionEnd:
          // noop
          break;

        case ActionType.InsertString:
          start = document.GetLine (action.line_no);
          document.SuspendUpdate ();
          document.DeleteMultiline (start, action.pos, ((string) action.data).Length + 1);
          document.PositionCaret (start, action.pos);
          document.SetSelectionToCaret (true);
          document.ResumeUpdate (true);
          break;

        case ActionType.Typing:
          start = document.GetLine (action.line_no);
          document.SuspendUpdate ();
          document.DeleteMultiline (start, action.pos, ((StringBuilder) action.data).Length);
          document.PositionCaret (start, action.pos);
          document.SetSelectionToCaret (true);
          document.ResumeUpdate (true);

          // This is an open ended operation, so only a single typing operation can be undone at once
          user_action_finished = true;
          break;

        case ActionType.DeleteString:
          start = document.GetLine (action.line_no);
          document.SuspendUpdate ();
          Insert (start, action.pos, (Line) action.data, true);
          document.ResumeUpdate (true);
          break;
        }
      } while (!user_action_finished && undo_actions.Count > 0);

      locked = false;

      return true;
    }

    internal bool Redo ()
    {
      Action action;
      bool user_action_finished = false;

      if (redo_actions.Count == 0)
        return false;

      locked = true;
      do {
        Line start;
        int start_index;

        action = (Action) redo_actions.Pop ();
        undo_actions.Push (action);

        switch (action.type) {

        case ActionType.UserActionBegin:
          //  Noop
          break;

        case ActionType.UserActionEnd:
          user_action_finished = true;
          break;

        case ActionType.InsertString:
          start = document.GetLine (action.line_no);
          document.SuspendUpdate ();
          start_index = document.LineTagToCharIndex (start, action.pos);
          document.InsertString (start, action.pos, (string) action.data);
          document.CharIndexToLineTag (start_index + ((string) action.data).Length,
              out document.caret.line, out document.caret.tag,
              out document.caret.pos);
          document.UpdateCaret ();
          document.SetSelectionToCaret (true);
          document.ResumeUpdate (true);
          break;

        case ActionType.Typing:
          start = document.GetLine (action.line_no);
          document.SuspendUpdate ();
          start_index = document.LineTagToCharIndex (start, action.pos);
          document.InsertString (start, action.pos, ((StringBuilder) action.data).ToString ());
          document.CharIndexToLineTag (start_index + ((StringBuilder) action.data).Length,
              out document.caret.line, out document.caret.tag,
              out document.caret.pos);
          document.UpdateCaret ();
          document.SetSelectionToCaret (true);
          document.ResumeUpdate (true);

          // This is an open ended operation, so only a single typing operation can be undone at once
          user_action_finished = true;
          break;

        case ActionType.DeleteString:
          start = document.GetLine (action.line_no);
          document.SuspendUpdate ();
          document.DeleteMultiline (start, action.pos, ((Line) action.data).text.Length);
          document.PositionCaret (start, action.pos);
          document.SetSelectionToCaret (true);
          document.ResumeUpdate (true);

          break;
        }
      } while (!user_action_finished && redo_actions.Count > 0);

      locked = false;

      return true;
    }
    #endregion  // Internal Methods

    #region Private Methods

    public void BeginUserAction (string name)
    {
      if (locked)
        return;

      // Nuke the redo queue
      redo_actions.Clear ();

      Action ua = new Action ();
      ua.type = ActionType.UserActionBegin;
      ua.data = name;

      undo_actions.Push (ua);
    }

    public void EndUserAction ()
    {
      if (locked)
        return;

      Action ua = new Action ();
      ua.type = ActionType.UserActionEnd;

      undo_actions.Push (ua);
    }

    // start_pos, end_pos = 1 based
    public void RecordDeleteString (Line start_line, int start_pos, Line end_line, int end_pos)
    {
      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
      a.type = ActionType.DeleteString;
      a.line_no = start_line.line_no;
      a.pos = start_pos;
      a.data = Duplicate (start_line, start_pos, end_line, end_pos);

      undo_actions.Push(a);
    }

    public void RecordInsertString (Line line, int pos, string str)
    {
      if (locked || str.Length == 0)
        return;

      // Nuke the redo queue
      redo_actions.Clear ();

      Action a = new Action ();

      a.type = ActionType.InsertString;
      a.data = str;
      a.line_no = line.line_no;
      a.pos = pos;

      undo_actions.Push (a);
    }

    public void RecordTyping (Line line, int pos, char ch)
    {
      if (locked)
        return;

      // Nuke the redo queue
      redo_actions.Clear ();

      Action a = null;

      if (undo_actions.Count > 0)
        a = (Action) undo_actions.Peek ();

      if (a == null || a.type != ActionType.Typing) {
        a = new Action ();
        a.type = ActionType.Typing;
        a.data = new StringBuilder ();
        a.line_no = line.line_no;
        a.pos = pos;

        undo_actions.Push (a);
      }

      StringBuilder data = (StringBuilder) a.data;
      data.Append (ch);
    }

    // start_pos = 1-based
    // end_pos = 1-based
    public Line Duplicate(Line start_line, int start_pos, Line end_line, int end_pos)
    {
      Line  ret;
      Line  line;
      Line  current;
      LineTag  tag;
      LineTag  current_tag;
      int  start;
      int  end;
      int  tag_start;

      line = new Line (start_line.document, start_line.ending);
      ret = line;

      for (int i = start_line.line_no; i <= end_line.line_no; i++) {
        current = document.GetLine(i);

        if (start_line.line_no == i) {
          start = start_pos;
        } else {
          start = 0;
        }

        if (end_line.line_no == i) {
          end = end_pos;
        } else {
          end = current.text.Length;
        }

        if (end_pos == 0)
          continue;

        // 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 + 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 = new LineTag(line, tag_start - start + 1);
          tag.CopyFormattingFrom (current_tag);

          current_tag = current_tag.Next;

          // Add the new tag to the line
          if (line.tags == null) {
            line.tags = tag;
          } else {
            LineTag tail;
            tail = line.tags;

            while (tail.Next != null) {
              tail = tail.Next;
            }
            tail.Next = tag;
            tag.Previous = tail;
          }
        }

        if ((i + 1) <= end_line.line_no) {
          line.ending = current.ending;

          // Chain them (we use right/left as next/previous)
          line.right = new Line (start_line.document, start_line.ending);
          line.right.left = line;
          line = line.right;
        }
      }

      return ret;
    }

    // Insert multi-line text at the given position; use formatting at insertion point for inserted text
    internal void Insert(Line line, int pos, Line insert, bool select)
    {
      Line  current;
      LineTag  tag;
      int  offset;
      int  lines;
      Line  first;

      // Handle special case first
      if (insert.right == null) {

        // Single line insert
        document.Split(line, pos);

        if (insert.tags == null) {
          return;  // Blank line
        }

        //Insert our tags at the end
        tag = line.tags;

        while (tag.Next != null) {
          tag = tag.Next;
        }

        offset = tag.Start + tag.Length - 1;

        tag.Next = insert.tags;
        line.text.Insert(offset, insert.text.ToString());

        // Adjust start locations
        tag = tag.Next;
        while (tag != null) {
          tag.Start += offset;
          tag.Line = line;
          tag = tag.Next;
        }
        // Put it back together
        document.Combine(line.line_no, line.line_no + 1);

        if (select) {
          document.SetSelectionStart (line, pos, false);
          document.SetSelectionEnd (line, pos + insert.text.Length, false);
        }

        document.UpdateView(line, pos);
        return;
      }

      first = line;
      lines = 1;
      current = insert;

      while (current != null) {

        if (current == insert) {
          // Inserting the first line we split the line (and make space)
          document.Split(line.line_no, pos);
          //Insert our tags at the end of the line
          tag = line.tags;

          
          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;

            tag = tag.Next;

          } else {
            offset = 0;
            line.tags = current.tags;
            line.tags.Previous = null;
            tag = line.tags;
          }

          line.ending = current.ending;
        } else {
          document.Split(line.line_no, 0);
          offset = 0;
          line.tags = current.tags;
          line.tags.Previous = null;
          line.ending = current.ending;
          tag = line.tags;
        }

        // Adjust start locations and line pointers
        while (tag != null) {
          tag.Start += offset - 1;
          tag.Line = line;
          tag = tag.Next;
        }

        line.text.Insert(offset, current.text.ToString());
        line.Grow(line.text.Length);

        line.recalc = true;
        line = document.GetLine(line.line_no + 1);

        // FIXME? Test undo of line-boundaries
        if ((current.right == null) && (current.tags.Length != 0)) {
          document.Combine(line.line_no - 1, line.line_no);
        }
        current = current.right;
        lines++;

      }

      // Recalculate our document
      document.UpdateView(first, lines, pos);
      return;
    }    
    #endregion  // Private Methods
  }
}
www.java2v.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.