2007-06-04 Jonathan Pobst <monkey@jpobst.com>
[mono.git] / mcs / class / Managed.Windows.Forms / System.Windows.Forms / TextControl.cs
1 // Permission is hereby granted, free of charge, to any person obtaining
2 // a copy of this software and associated documentation files (the
3 // "Software"), to deal in the Software without restriction, including
4 // without limitation the rights to use, copy, modify, merge, publish,
5 // distribute, sublicense, and/or sell copies of the Software, and to
6 // permit persons to whom the Software is furnished to do so, subject to
7 // the following conditions:
8 // 
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
11 // 
12 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19 //
20 // Copyright (c) 2004-2006 Novell, Inc. (http://www.novell.com)
21 //
22 // Authors:
23 //      Peter Bartok    pbartok@novell.com
24 //
25 //
26
27 // NOT COMPLETE
28
29 // There's still plenty of things missing, I've got most of it planned, just hadn't had
30 // the time to write it all yet.
31 // Stuff missing (in no particular order):
32 // - Align text after RecalculateLine
33 // - Implement tag types for hotlinks, etc.
34 // - Implement CaretPgUp/PgDown
35
36 // NOTE:
37 // selection_start.pos and selection_end.pos are 0-based
38 // selection_start.pos = first selected char
39 // selection_end.pos = first NOT-selected char
40 //
41 // FormatText methods are 1-based (as are all tags, LineTag.Start is 1 for 
42 // the first character on a line; the reason is that 0 is the position 
43 // *before* the first character on a line
44
45
46 #undef Debug
47
48 using System;
49 using System.Collections;
50 using System.Drawing;
51 using System.Drawing.Text;
52 using System.Text;
53 using RTF=System.Windows.Forms.RTF;
54
55 namespace System.Windows.Forms {
56         internal enum LineColor {
57                 Red     = 0,
58                 Black   = 1
59         }
60
61         internal enum CaretSelection {
62                 Position,       // Selection=Caret
63                 Word,           // Selection=Word under caret
64                 Line            // Selection=Line under caret
65         }
66
67         internal class FontDefinition {
68                 internal String         face;
69                 internal int            size;
70                 internal FontStyle      add_style;
71                 internal FontStyle      remove_style;
72                 internal Color          color;
73                 internal Font           font_obj;
74         }
75
76         [Flags]
77         internal enum FormatSpecified {
78                 None,
79
80                 BackColor = 2,
81                 Font = 4,
82                 Color = 8,
83         }
84
85         internal enum CaretDirection {
86                 CharForward,    // Move a char to the right
87                 CharBack,       // Move a char to the left
88                 LineUp,         // Move a line up
89                 LineDown,       // Move a line down
90                 Home,           // Move to the beginning of the line
91                 End,            // Move to the end of the line
92                 PgUp,           // Move one page up
93                 PgDn,           // Move one page down
94                 CtrlPgUp,       // Move caret to the first visible char in the viewport
95                 CtrlPgDn,       // Move caret to the last visible char in the viewport
96                 CtrlHome,       // Move to the beginning of the document
97                 CtrlEnd,        // Move to the end of the document
98                 WordBack,       // Move to the beginning of the previous word (or beginning of line)
99                 WordForward,    // Move to the beginning of the next word (or end of line)
100                 SelectionStart, // Move to the beginning of the current selection
101                 SelectionEnd,   // Move to the end of the current selection
102                 CharForwardNoWrap,   // Move a char forward, but don't wrap onto the next line
103                 CharBackNoWrap      // Move a char backward, but don't wrap onto the previous line
104         }
105
106         internal enum LineEnding {
107                 Wrap,    // line wraps to the next line
108                 Limp,    // \r
109                 Hard,    // \r\n
110                 Soft,    // \r\r\n
111                 Rich,    // \n
112
113                 None
114         }
115         
116         // Being cloneable should allow for nice line and document copies...
117         internal class Line : ICloneable, IComparable {
118                 #region Local Variables
119
120                 internal Document document;
121
122                 // Stuff that matters for our line
123                 internal StringBuilder          text;                   // Characters for the line
124                 internal float[]                widths;                 // Width of each character; always one larger than text.Length
125                 internal int                    space;                  // Number of elements in text and widths
126                 internal int                    line_no;                // Line number
127                 internal LineTag                tags;                   // Tags describing the text
128                 internal int                    offset;                 // Baseline can be on the X or Y axis depending if we are in multiline mode or not
129                 internal int                    height;                 // Height of the line (height of tallest tag)
130                 internal int                    ascent;                 // Ascent of the line (ascent of the tallest tag)
131                 internal HorizontalAlignment    alignment;              // Alignment of the line
132                 internal int                    align_shift;            // Pixel shift caused by the alignment
133                 internal int                    indent;                 // Left indent for the first line
134                 internal int                    hanging_indent;         // Hanging indent (left indent for all but the first line)
135                 internal int                    right_indent;           // Right indent for all lines
136                 internal LineEnding ending;
137
138
139                 // Stuff that's important for the tree
140                 internal Line                   parent;                 // Our parent line
141                 internal Line                   left;                   // Line with smaller line number
142                 internal Line                   right;                  // Line with higher line number
143                 internal LineColor              color;                  // We're doing a black/red tree. this is the node color
144                 internal int                    DEFAULT_TEXT_LEN;       // 
145                 internal bool                   recalc;                 // Line changed
146                 #endregion      // Local Variables
147
148                 #region Constructors
149                 internal Line (Document document, LineEnding ending)
150                 {
151                         this.document = document; 
152                         color = LineColor.Red;
153                         left = null;
154                         right = null;
155                         parent = null;
156                         text = null;
157                         recalc = true;
158                         alignment = document.alignment;
159
160                         this.ending = ending;
161                 }
162
163                 internal Line(Document document, int LineNo, string Text, Font font, SolidBrush color, LineEnding ending) : this (document, ending)
164                 {
165                         space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
166
167                         text = new StringBuilder(Text, space);
168                         line_no = LineNo;
169                         this.ending = ending;
170
171                         widths = new float[space + 1];
172
173                         
174                         tags = new LineTag(this, 1);
175                         tags.font = font;
176                         tags.color = color;                             
177                 }
178
179                 internal Line(Document document, int LineNo, string Text, HorizontalAlignment align, Font font, SolidBrush color, LineEnding ending) : this(document, ending)
180                 {
181                         space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
182
183                         text = new StringBuilder(Text, space);
184                         line_no = LineNo;
185                         this.ending = ending;
186                         alignment = align;
187
188                         widths = new float[space + 1];
189
190                         
191                         tags = new LineTag(this, 1);
192                         tags.font = font;
193                         tags.color = color;
194                 }
195
196                 internal Line(Document document, int LineNo, string Text, LineTag tag, LineEnding ending) : this(document, ending)
197                 {
198                         space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
199
200                         text = new StringBuilder(Text, space);
201                         this.ending = ending;
202                         line_no = LineNo;
203
204                         widths = new float[space + 1];
205                         tags = tag;
206                 }
207
208                 #endregion      // Constructors
209
210                 #region Internal Properties
211
212                 internal int Y {
213                         get {
214                                 if (!document.multiline)
215                                         return document.top_margin;
216                                 return document.top_margin + offset;
217                         }
218                 }
219
220                 internal int X {
221                         get {
222                                 if (document.multiline)
223                                         return align_shift;
224                                 return offset + align_shift;
225                         }
226                 }
227
228                 internal int Width {
229                         get {
230                                 int res = (int) widths [text.Length];
231                                 if (!document.multiline) {
232
233                                 }
234                                 return res;
235                         }
236                 }
237
238                 internal int Indent {
239                         get {
240                                 return indent;
241                         }
242
243                         set {
244                                 indent = value;
245                                 recalc = true;
246                         }
247                 }
248
249                 internal int HangingIndent {
250                         get {
251                                 return hanging_indent;
252                         }
253
254                         set {
255                                 hanging_indent = value;
256                                 recalc = true;
257                         }
258                 }
259
260                 internal int RightIndent {
261                         get {
262                                 return right_indent;
263                         }
264
265                         set {
266                                 right_indent = value;
267                                 recalc = true;
268                         }
269                 }
270                         
271
272                 internal int Height {
273                         get {
274                                 return height;
275                         }
276
277                         set {
278                                 height = value;
279                         }
280                 }
281
282                 internal int LineNo {
283                         get {
284                                 return line_no;
285                         }
286
287                         set {
288                                 line_no = value;
289                         }
290                 }
291
292                 internal string Text {
293                         get {
294                                 return text.ToString();
295                         }
296
297                         set {
298                                 text = new StringBuilder(value, value.Length > DEFAULT_TEXT_LEN ? value.Length : DEFAULT_TEXT_LEN);
299                         }
300                 }
301
302                 internal HorizontalAlignment Alignment {
303                         get {
304                                 return alignment;
305                         }
306
307                         set {
308                                 if (alignment != value) {
309                                         alignment = value;
310                                         recalc = true;
311                                 }
312                         }
313                 }
314 #if no
315                 internal StringBuilder Text {
316                         get {
317                                 return text;
318                         }
319
320                         set {
321                                 text = value;
322                         }
323                 }
324 #endif
325                 #endregion      // Internal Properties
326
327                 #region Internal Methods
328
329                 // This doesn't do exactly what you would think, it just pulls of the \n part of the ending
330                 internal string TextWithoutEnding ()
331                 {
332                         return text.ToString (0, text.Length - document.LineEndingLength (ending));
333                 }
334
335                 internal int TextLengthWithoutEnding ()
336                 {
337                         return text.Length - document.LineEndingLength (ending);
338                 }
339
340                 internal void DrawEnding (Graphics dc, float y)
341                 {
342                         if (document.multiline)
343                                 return;
344                         LineTag last = tags;
345                         while (last.next != null)
346                                 last = last.next;
347
348                         string end_str = null;
349                         switch (document.LineEndingLength (ending)) {
350                         case 0:
351                                 return;
352                         case 1:
353                                 end_str = "\u0013";
354                                 break;
355                         case 2:
356                                 end_str = "\u0013\u0013";
357                                 break;
358                         case 3:
359                                 end_str = "\u0013\u0013\u0013";
360                                 break;
361                         }
362                         dc.DrawString (end_str, last.font, last.color,  X + widths [TextLengthWithoutEnding ()] - document.viewport_x,
363                                         y, Document.string_format);
364                 }
365
366                 
367                 // Make sure we always have enoughs space in text and widths
368                 internal void Grow(int minimum) {
369                         int     length;
370                         float[] new_widths;
371
372                         length = text.Length;
373
374                         if ((length + minimum) > space) {
375                                 // We need to grow; double the size
376
377                                 if ((length + minimum) > (space * 2)) {
378                                         new_widths = new float[length + minimum * 2 + 1];
379                                         space = length + minimum * 2;
380                                 } else {                                
381                                         new_widths = new float[space * 2 + 1];
382                                         space *= 2;
383                                 }
384                                 widths.CopyTo(new_widths, 0);
385
386                                 widths = new_widths;
387                         }
388                 }
389
390                 internal void Streamline(int lines) {
391                         LineTag current;
392                         LineTag next;
393
394                         current = this.tags;
395                         next = current.next;
396
397                         //
398                         // Catch what the loop below wont; eliminate 0 length 
399                         // tags, but only if there are other tags after us
400                         // We only eliminate text tags if there is another text tag
401                         // after it.  Otherwise we wind up trying to type on picture tags
402                         //
403                         while ((current.length == 0) && (next != null) && (next.IsTextTag)) {
404                                 tags = next;
405                                 tags.previous = null;
406                                 current = next;
407                                 next = current.next;
408                         }
409                         
410
411                         if (next == null) {
412                                 return;
413                         }
414
415                         while (next != null) {
416                                 // Take out 0 length tags unless it's the last tag in the document
417                                 if (current.IsTextTag && next.length == 0 && next.IsTextTag) {
418                                         if ((next.next != null) || (line_no != lines)) {
419                                                 current.next = next.next;
420                                                 if (current.next != null) {
421                                                         current.next.previous = current;
422                                                 }
423                                                 next = current.next;
424                                                 continue;
425                                         }
426                                 }
427                                 if (current.Combine(next)) {
428                                         next = current.next;
429                                         continue;
430                                 }
431
432                                 current = current.next;
433                                 next = current.next;
434                         }
435                 }
436
437                 /// <summary> Find the tag on a line based on the character position, pos is 0-based</summary>
438                 internal LineTag FindTag(int pos) {
439                         LineTag tag;
440
441                         if (pos == 0) {
442                                 return tags;
443                         }
444
445                         tag = this.tags;
446
447                         if (pos >= text.Length) {
448                                 pos = text.Length - 1;
449                         }
450
451                         while (tag != null) {
452                                 if (((tag.start - 1) <= pos) && (pos < (tag.start + tag.length - 1))) {
453                                         return LineTag.GetFinalTag (tag);
454                                 }
455                                 tag = tag.next;
456                         }
457                         return null;
458                 }
459
460                 /// <summary>
461                 /// Recalculate a single line using the same char for every character in the line
462                 /// </summary>
463                 
464                 internal bool RecalculatePasswordLine(Graphics g, Document doc) {
465                         LineTag tag;
466                         int     pos;
467                         int     len;
468                         float   w;
469                         bool    ret;
470                         int     descent;
471
472                         pos = 0;
473                         len = this.text.Length;
474                         tag = this.tags;
475                         ascent = 0;
476                         tag.shift = 0;
477
478                         this.recalc = false;
479                         widths[0] = document.left_margin + indent;
480
481                         w = g.MeasureString(doc.password_char, tags.font, 10000, Document.string_format).Width;
482
483                         if (this.height != (int)tag.font.Height) {
484                                 ret = true;
485                         } else {
486                                 ret = false;
487                         }
488
489                         this.height = (int)tag.font.Height;
490                         tag.height = this.height;
491
492                         XplatUI.GetFontMetrics(g, tag.font, out tag.ascent, out descent);
493                         this.ascent = tag.ascent;
494
495                         while (pos < len) {
496                                 pos++;
497                                 widths[pos] = widths[pos-1] + w;
498                         }
499
500                         return ret;
501                 }
502
503                 /// <summary>
504                 /// Go through all tags on a line and recalculate all size-related values;
505                 /// returns true if lineheight changed
506                 /// </summary>
507                 internal bool RecalculateLine(Graphics g, Document doc) {
508                         LineTag tag;
509                         int     pos;
510                         int     len;
511                         SizeF   size;
512                         float   w;
513                         int     prev_offset;
514                         bool    retval;
515                         bool    wrapped;
516                         Line    line;
517                         int     wrap_pos;
518
519                         pos = 0;
520                         len = this.text.Length;
521                         tag = this.tags;
522                         prev_offset = this.offset;      // For drawing optimization calculations
523                         this.height = 0;                // Reset line height
524                         this.ascent = 0;                // Reset the ascent for the line
525                         tag.shift = 0;
526
527                         if (ending == LineEnding.Wrap) {
528                                 widths[0] = document.left_margin + hanging_indent;
529                         } else {
530                                 widths[0] = document.left_margin + indent;
531                         }
532
533                         this.recalc = false;
534                         retval = false;
535                         wrapped = false;
536
537                         wrap_pos = 0;
538
539                         while (pos < len) {
540
541                                 while (tag.length == 0) {       // We should always have tags after a tag.length==0 unless len==0
542                                         tag.ascent = 0;
543                                         tag.shift = 0;
544                                         tag = tag.next;
545                                 }
546
547                                 size = tag.SizeOfPosition (g, pos);
548                                 w = size.Width;
549
550                                 if (Char.IsWhiteSpace(text[pos])) {
551                                         wrap_pos = pos + 1;
552                                 }
553
554                                 if (doc.wrap) {
555                                         if ((wrap_pos > 0) && (wrap_pos != len) && (widths[pos] + w) + 5 > (doc.viewport_width - this.right_indent)) {
556                                                 // Make sure to set the last width of the line before wrapping
557                                                 widths [pos + 1] = widths [pos] + w;
558
559                                                 pos = wrap_pos;
560                                                 len = text.Length;
561                                                 doc.Split(this, tag, pos);
562                                                 ending = LineEnding.Wrap;
563                                                 len = this.text.Length;
564                                                 
565                                                 retval = true;
566                                                 wrapped = true;
567                                         }  else if (pos > 1 && (widths[pos] + w) > (doc.viewport_width - this.right_indent)) {
568                                                 // No suitable wrap position was found so break right in the middle of a word
569
570                                                 // Make sure to set the last width of the line before wrapping
571                                                 widths [pos + 1] = widths [pos] + w;
572
573                                                 doc.Split(this, tag, pos);
574                                                 ending = LineEnding.Wrap;
575                                                 len = this.text.Length;
576                                                 retval = true;
577                                                 wrapped = true;
578                                         }
579                                 }
580
581                                 // Contract all wrapped lines that follow back into our line
582                                 if (!wrapped) {
583                                         pos++;
584
585                                         widths[pos] = widths[pos-1] + w;
586
587                                         if (pos == len) {
588                                                 line = doc.GetLine(this.line_no + 1);
589                                                 if ((line != null) && (ending == LineEnding.Wrap || ending == LineEnding.None)) {
590                                                         // Pull the two lines together
591                                                         doc.Combine(this.line_no, this.line_no + 1);
592                                                         len = this.text.Length;
593                                                         retval = true;
594                                                 }
595                                         }
596                                 }
597
598                                 if (pos == (tag.start-1 + tag.length)) {
599                                         // We just found the end of our current tag
600                                         tag.height = tag.MaxHeight ();
601
602                                         // Check if we're the tallest on the line (so far)
603                                         if (tag.height > this.height) {
604                                                 this.height = tag.height;               // Yep; make sure the line knows
605                                         }
606
607                                         if (tag.ascent == 0) {
608                                                 int     descent;
609
610                                                 XplatUI.GetFontMetrics(g, tag.font, out tag.ascent, out descent);
611                                         }
612
613                                         if (tag.ascent > this.ascent) {
614                                                 LineTag         t;
615
616                                                 // We have a tag that has a taller ascent than the line;
617                                                 t = tags;
618                                                 while (t != null && t != tag) {
619                                                         t.shift = tag.ascent - t.ascent;
620                                                         t = t.next;
621                                                 }
622
623                                                 // Save on our line
624                                                 this.ascent = tag.ascent;
625                                         } else {
626                                                 tag.shift = this.ascent - tag.ascent;
627                                         }
628
629                                         tag = tag.next;
630                                         if (tag != null) {
631                                                 tag.shift = 0;
632                                                 wrap_pos = pos;
633                                         }
634                                 }
635                         }
636
637                         if (this.height == 0) {
638                                 this.height = tags.font.Height;
639                                 tag.height = this.height;
640                         }
641
642                         if (prev_offset != offset) {
643                                 retval = true;
644                         }
645                         return retval;
646                 }
647                 #endregion      // Internal Methods
648
649                 #region Administrative
650                 public int CompareTo(object obj) {
651                         if (obj == null) {
652                                 return 1;
653                         }
654
655                         if (! (obj is Line)) {
656                                 throw new ArgumentException("Object is not of type Line", "obj");
657                         }
658
659                         if (line_no < ((Line)obj).line_no) {
660                                 return -1;
661                         } else if (line_no > ((Line)obj).line_no) {
662                                 return 1;
663                         } else {
664                                 return 0;
665                         }
666                 }
667
668                 public object Clone() {
669                         Line    clone;
670
671                         clone = new Line (document, ending);
672
673                         clone.text = text;
674
675                         if (left != null) {
676                                 clone.left = (Line)left.Clone();
677                         }
678
679                         if (left != null) {
680                                 clone.left = (Line)left.Clone();
681                         }
682
683                         return clone;
684                 }
685
686                 internal object CloneLine() {
687                         Line    clone;
688
689                         clone = new Line (document, ending);
690
691                         clone.text = text;
692
693                         return clone;
694                 }
695
696                 public override bool Equals(object obj) {
697                         if (obj == null) {
698                                 return false;
699                         }
700
701                         if (!(obj is Line)) {
702                                 return false;
703                         }
704
705                         if (obj == this) {
706                                 return true;
707                         }
708
709                         if (line_no == ((Line)obj).line_no) {
710                                 return true;
711                         }
712
713                         return false;
714                 }
715
716                 public override int GetHashCode() {
717                         return base.GetHashCode ();
718                 }
719
720                 public override string ToString() {
721                         return "Line " + line_no;
722                 }
723
724                 #endregion      // Administrative
725         }
726
727         internal class Document : ICloneable, IEnumerable {
728                 #region Structures
729                 // FIXME - go through code and check for places where
730                 // we do explicit comparisons instead of using the compare overloads
731                 internal struct Marker {
732                         internal Line           line;
733                         internal LineTag        tag;
734                         internal int            pos;
735                         internal int            height;
736
737                         public static bool operator<(Marker lhs, Marker rhs) {
738                                 if (lhs.line.line_no < rhs.line.line_no) {
739                                         return true;
740                                 }
741
742                                 if (lhs.line.line_no == rhs.line.line_no) {
743                                         if (lhs.pos < rhs.pos) {
744                                                 return true;
745                                         }
746                                 }
747                                 return false;
748                         }
749
750                         public static bool operator>(Marker lhs, Marker rhs) {
751                                 if (lhs.line.line_no > rhs.line.line_no) {
752                                         return true;
753                                 }
754
755                                 if (lhs.line.line_no == rhs.line.line_no) {
756                                         if (lhs.pos > rhs.pos) {
757                                                 return true;
758                                         }
759                                 }
760                                 return false;
761                         }
762
763                         public static bool operator==(Marker lhs, Marker rhs) {
764                                 if ((lhs.line.line_no == rhs.line.line_no) && (lhs.pos == rhs.pos)) {
765                                         return true;
766                                 }
767                                 return false;
768                         }
769
770                         public static bool operator!=(Marker lhs, Marker rhs) {
771                                 if ((lhs.line.line_no != rhs.line.line_no) || (lhs.pos != rhs.pos)) {
772                                         return true;
773                                 }
774                                 return false;
775                         }
776
777                         public void Combine(Line move_to_line, int move_to_line_length) {
778                                 line = move_to_line;
779                                 pos += move_to_line_length;
780                                 tag = LineTag.FindTag(line, pos);
781                         }
782
783                         // This is for future use, right now Document.Split does it by hand, with some added shortcut logic
784                         public void Split(Line move_to_line, int split_at) {
785                                 line = move_to_line;
786                                 pos -= split_at;
787                                 tag = LineTag.FindTag(line, pos);
788                         }
789
790                         public override bool Equals(object obj) {
791                                    return this==(Marker)obj;
792                         }
793
794                         public override int GetHashCode() {
795                                 return base.GetHashCode ();
796                         }
797
798                         public override string ToString() {
799                                 return "Marker Line " + line + ", Position " + pos;
800                         }
801
802                 }
803                 #endregion Structures
804
805                 #region Local Variables
806                 private Line            document;
807                 private int             lines;
808                 private Line            sentinel;
809                 private int             document_id;
810                 private Random          random = new Random();
811                 internal string         password_char;
812                 private StringBuilder   password_cache;
813                 private bool            calc_pass;
814                 private int             char_count;
815
816                 // For calculating widths/heights
817                 public static readonly StringFormat string_format = new StringFormat (StringFormat.GenericTypographic);
818
819                 private int             recalc_suspended;
820                 private bool            recalc_pending;
821                 private int             recalc_start = 1;   // This starts at one, since lines are 1 based
822                 private int             recalc_end;
823                 private bool            recalc_optimize;
824
825                 private int             update_suspended;
826                 private bool update_pending;
827                 private int update_start = 1;
828
829                 internal bool           multiline;
830                 internal HorizontalAlignment alignment;
831                 internal bool           wrap;
832
833                 internal UndoManager    undo;
834
835                 internal Marker         caret;
836                 internal Marker         selection_start;
837                 internal Marker         selection_end;
838                 internal bool           selection_visible;
839                 internal Marker         selection_anchor;
840                 internal Marker         selection_prev;
841                 internal bool           selection_end_anchor;
842
843                 internal int            viewport_x;
844                 internal int            viewport_y;             // The visible area of the document
845                 internal int            viewport_width;
846                 internal int            viewport_height;
847
848                 internal int            document_x;             // Width of the document
849                 internal int            document_y;             // Height of the document
850
851                 internal Rectangle      invalid;
852
853                 internal int            crlf_size;              // 1 or 2, depending on whether we use \r\n or just \n
854
855                 internal TextBoxBase    owner;                  // Who's owning us?
856                 static internal int     caret_width = 1;
857                 static internal int     caret_shift = 1;
858
859                 internal int left_margin = 2;  // A left margin for all lines
860                 internal int top_margin = 2;
861                 internal int right_margin = 2;
862                 #endregion      // Local Variables
863
864                 #region Constructors
865                 internal Document (TextBoxBase owner)
866                 {
867                         lines = 0;
868
869                         this.owner = owner;
870
871                         multiline = true;
872                         password_char = "";
873                         calc_pass = false;
874                         recalc_pending = false;
875
876                         // Tree related stuff
877                         sentinel = new Line (this, LineEnding.None);
878                         sentinel.color = LineColor.Black;
879
880                         document = sentinel;
881
882                         // We always have a blank line
883                         owner.HandleCreated += new EventHandler(owner_HandleCreated);
884                         owner.VisibleChanged += new EventHandler(owner_VisibleChanged);
885
886                         Add (1, String.Empty, owner.Font, ThemeEngine.Current.ResPool.GetSolidBrush (owner.ForeColor), LineEnding.None);
887
888                         undo = new UndoManager (this);
889
890                         selection_visible = false;
891                         selection_start.line = this.document;
892                         selection_start.pos = 0;
893                         selection_start.tag = selection_start.line.tags;
894                         selection_end.line = this.document;
895                         selection_end.pos = 0;
896                         selection_end.tag = selection_end.line.tags;
897                         selection_anchor.line = this.document;
898                         selection_anchor.pos = 0;
899                         selection_anchor.tag = selection_anchor.line.tags;
900                         caret.line = this.document;
901                         caret.pos = 0;
902                         caret.tag = caret.line.tags;
903
904                         viewport_x = 0;
905                         viewport_y = 0;
906
907                         crlf_size = 2;
908
909                         // Default selection is empty
910
911                         document_id = random.Next();
912
913                         string_format.Trimming = StringTrimming.None;
914                         string_format.FormatFlags = StringFormatFlags.DisplayFormatControl;
915
916                         UpdateMargins ();
917                 }
918                 #endregion
919
920                 #region Internal Properties
921                 internal Line Root {
922                         get {
923                                 return document;
924                         }
925
926                         set {
927                                 document = value;
928                         }
929                 }
930
931                 internal int Lines {
932                         get {
933                                 return lines;
934                         }
935                 }
936
937                 internal Line CaretLine {
938                         get {
939                                 return caret.line;
940                         }
941                 }
942
943                 internal int CaretPosition {
944                         get {
945                                 return caret.pos;
946                         }
947                 }
948
949                 internal Point Caret {
950                         get {
951                                 return new Point((int)caret.tag.line.widths[caret.pos] + caret.line.X, caret.line.Y);
952                         }
953                 }
954
955                 internal LineTag CaretTag {
956                         get {
957                                 return caret.tag;
958                         }
959
960                         set {
961                                 caret.tag = value;
962                         }
963                 }
964
965                 internal int CRLFSize {
966                         get {
967                                 return crlf_size;
968                         }
969
970                         set {
971                                 crlf_size = value;
972                         }
973                 }
974
975                 internal string PasswordChar {
976                         get {
977                                 return password_char;
978                         }
979
980                         set {
981                                 password_char = value;
982                                 PasswordCache.Length = 0;
983                                 if ((password_char.Length != 0) && (password_char[0] != '\0')) {
984                                         calc_pass = true;
985                                 } else {
986                                         calc_pass = false;
987                                 }
988                         }
989                 }
990
991                 private StringBuilder PasswordCache {
992                         get { 
993                                 if (password_cache == null) 
994                                           password_cache = new StringBuilder(); 
995                                 return password_cache;
996                         }
997                 }
998
999                 internal int ViewPortX {
1000                         get {
1001                                 return viewport_x;
1002                         }
1003
1004                         set {
1005                                 viewport_x = value;
1006                         }
1007                 }
1008
1009                 internal int Length {
1010                         get {
1011                                 return char_count + lines - 1;  // Add \n for each line but the last
1012                         }
1013                 }
1014
1015                 private int CharCount {
1016                         get {
1017                                 return char_count;
1018                         }
1019
1020                         set {
1021                                 char_count = value;
1022
1023                                 if (LengthChanged != null) {
1024                                         LengthChanged(this, EventArgs.Empty);
1025                                 }
1026                         }
1027                 }
1028
1029                 internal int ViewPortY {
1030                         get {
1031                                 return viewport_y;
1032                         }
1033
1034                         set {
1035                                 viewport_y = value;
1036                         }
1037                 }
1038
1039                 internal int ViewPortWidth {
1040                         get {
1041                                 return viewport_width;
1042                         }
1043
1044                         set {
1045                                 viewport_width = value;
1046                         }
1047                 }
1048
1049                 internal int ViewPortHeight {
1050                         get {
1051                                 return viewport_height;
1052                         }
1053
1054                         set {
1055                                 viewport_height = value;
1056                         }
1057                 }
1058
1059
1060                 internal int Width {
1061                         get {
1062                                 return this.document_x;
1063                         }
1064                 }
1065
1066                 internal int Height {
1067                         get {
1068                                 return this.document_y;
1069                         }
1070                 }
1071
1072                 internal bool SelectionVisible {
1073                         get {
1074                                 return selection_visible;
1075                         }
1076                 }
1077
1078                 internal bool Wrap {
1079                         get {
1080                                 return wrap;
1081                         }
1082
1083                         set {
1084                                 wrap = value;
1085                         }
1086                 }
1087
1088                 #endregion      // Internal Properties
1089
1090                 #region Private Methods
1091
1092                 internal void UpdateMargins ()
1093                 {
1094                         if (owner.actual_border_style == BorderStyle.FixedSingle) {
1095                                 left_margin = 0;
1096                                 top_margin = 0;
1097                                 right_margin = 0;
1098                         } else {
1099                                 left_margin = 2;
1100                                 top_margin = 2;
1101                                 right_margin = 2;
1102                         }
1103                 }
1104
1105                 internal void SuspendRecalc ()
1106                 {
1107                         recalc_suspended++;
1108                 }
1109
1110                 internal void ResumeRecalc (bool immediate_update)
1111                 {
1112                         if (recalc_suspended > 0)
1113                                 recalc_suspended--;
1114
1115                         if (immediate_update && recalc_suspended == 0 && recalc_pending) {
1116                                 RecalculateDocument (owner.CreateGraphicsInternal(), recalc_start, recalc_end, recalc_optimize);
1117                                 recalc_pending = false;
1118                         }
1119                 }
1120
1121                 internal void SuspendUpdate ()
1122                 {
1123                         update_suspended++;
1124                 }
1125
1126                 internal void ResumeUpdate (bool immediate_update)
1127                 {
1128                         if (update_suspended > 0)
1129                                 update_suspended--;
1130
1131                         if (immediate_update && update_suspended == 0 && update_pending) {
1132                                 UpdateView (GetLine (update_start), 0);
1133                                 update_pending = false;
1134                         }
1135                 }
1136
1137                 // For debugging
1138                 internal int DumpTree(Line line, bool with_tags) {
1139                         int     total;
1140
1141                         total = 1;
1142
1143                         Console.Write("Line {0} [# {1}], Y: {2}, ending style: {3},  Text: '{4}'",
1144                                         line.line_no, line.GetHashCode(), line.Y, line.ending,
1145                                         line.text != null ? line.text.ToString() : "undefined");
1146
1147                         if (line.left == sentinel) {
1148                                 Console.Write(", left = sentinel");
1149                         } else if (line.left == null) {
1150                                 Console.Write(", left = NULL");
1151                         }
1152
1153                         if (line.right == sentinel) {
1154                                 Console.Write(", right = sentinel");
1155                         } else if (line.right == null) {
1156                                 Console.Write(", right = NULL");
1157                         }
1158
1159                         Console.WriteLine("");
1160
1161                         if (with_tags) {
1162                                 LineTag tag;
1163                                 int     count;
1164                                 int     length;
1165
1166                                 tag = line.tags;
1167                                 count = 1;
1168                                 length = 0;
1169                                 Console.Write("   Tags: ");
1170                                 while (tag != null) {
1171                                         Console.Write("{0} <{1}>-<{2}>", count++, tag.start, tag.end
1172                                                         /*line.text.ToString (tag.start - 1, tag.length)*/);
1173                                         length += tag.length;
1174
1175                                         if (tag.line != line) {
1176                                                 Console.Write("BAD line link");
1177                                                 throw new Exception("Bad line link in tree");
1178                                         }
1179                                         tag = tag.next;
1180                                         if (tag != null) {
1181                                                 Console.Write(", ");
1182                                         }
1183                                 }
1184                                 if (length > line.text.Length) {
1185                                         throw new Exception(String.Format("Length of tags more than length of text on line (expected {0} calculated {1})", line.text.Length, length));
1186                                 } else if (length < line.text.Length) {
1187                                         throw new Exception(String.Format("Length of tags less than length of text on line (expected {0} calculated {1})", line.text.Length, length));
1188                                 }
1189                                 Console.WriteLine("");
1190                         }
1191                         if (line.left != null) {
1192                                 if (line.left != sentinel) {
1193                                         total += DumpTree(line.left, with_tags);
1194                                 }
1195                         } else {
1196                                 if (line != sentinel) {
1197                                         throw new Exception("Left should not be NULL");
1198                                 }
1199                         }
1200
1201                         if (line.right != null) {
1202                                 if (line.right != sentinel) {
1203                                         total += DumpTree(line.right, with_tags);
1204                                 }
1205                         } else {
1206                                 if (line != sentinel) {
1207                                         throw new Exception("Right should not be NULL");
1208                                 }
1209                         }
1210
1211                         for (int i = 1; i <= this.lines; i++) {
1212                                 if (GetLine(i) == null) {
1213                                         throw new Exception(String.Format("Hole in line order, missing {0}", i));
1214                                 }
1215                         }
1216
1217                         if (line == this.Root) {
1218                                 if (total < this.lines) {
1219                                         throw new Exception(String.Format("Not enough nodes in tree, found {0}, expected {1}", total, this.lines));
1220                                 } else if (total > this.lines) {
1221                                         throw new Exception(String.Format("Too many nodes in tree, found {0}, expected {1}", total, this.lines));
1222                                 }
1223                         }
1224
1225                         return total;
1226                 }
1227
1228                 private void SetSelectionVisible (bool value)
1229                 {
1230                         selection_visible = value;
1231
1232                         // cursor and selection are enemies, we can't have both in the same room at the same time
1233                         if (owner.IsHandleCreated && !owner.show_caret_w_selection)
1234                                 XplatUI.CaretVisible (owner.Handle, !selection_visible);
1235                 }
1236
1237                 private void DecrementLines(int line_no) {
1238                         int     current;
1239
1240                         current = line_no;
1241                         while (current <= lines) {
1242                                 GetLine(current).line_no--;
1243                                 current++;
1244                         }
1245                         return;
1246                 }
1247
1248                 private void IncrementLines(int line_no) {
1249                         int     current;
1250
1251                         current = this.lines;
1252                         while (current >= line_no) {
1253                                 GetLine(current).line_no++;
1254                                 current--;
1255                         }
1256                         return;
1257                 }
1258
1259                 private void RebalanceAfterAdd(Line line1) {
1260                         Line    line2;
1261
1262                         while ((line1 != document) && (line1.parent.color == LineColor.Red)) {
1263                                 if (line1.parent == line1.parent.parent.left) {
1264                                         line2 = line1.parent.parent.right;
1265
1266                                         if ((line2 != null) && (line2.color == LineColor.Red)) {
1267                                                 line1.parent.color = LineColor.Black;
1268                                                 line2.color = LineColor.Black;
1269                                                 line1.parent.parent.color = LineColor.Red;
1270                                                 line1 = line1.parent.parent;
1271                                         } else {
1272                                                 if (line1 == line1.parent.right) {
1273                                                         line1 = line1.parent;
1274                                                         RotateLeft(line1);
1275                                                 }
1276
1277                                                 line1.parent.color = LineColor.Black;
1278                                                 line1.parent.parent.color = LineColor.Red;
1279
1280                                                 RotateRight(line1.parent.parent);
1281                                         }
1282                                 } else {
1283                                         line2 = line1.parent.parent.left;
1284
1285                                         if ((line2 != null) && (line2.color == LineColor.Red)) {
1286                                                 line1.parent.color = LineColor.Black;
1287                                                 line2.color = LineColor.Black;
1288                                                 line1.parent.parent.color = LineColor.Red;
1289                                                 line1 = line1.parent.parent;
1290                                         } else {
1291                                                 if (line1 == line1.parent.left) {
1292                                                         line1 = line1.parent;
1293                                                         RotateRight(line1);
1294                                                 }
1295
1296                                                 line1.parent.color = LineColor.Black;
1297                                                 line1.parent.parent.color = LineColor.Red;
1298                                                 RotateLeft(line1.parent.parent);
1299                                         }
1300                                 }
1301                         }
1302                         document.color = LineColor.Black;
1303                 }
1304
1305                 private void RebalanceAfterDelete(Line line1) {
1306                         Line line2;
1307
1308                         while ((line1 != document) && (line1.color == LineColor.Black)) {
1309                                 if (line1 == line1.parent.left) {
1310                                         line2 = line1.parent.right;
1311                                         if (line2.color == LineColor.Red) { 
1312                                                 line2.color = LineColor.Black;
1313                                                 line1.parent.color = LineColor.Red;
1314                                                 RotateLeft(line1.parent);
1315                                                 line2 = line1.parent.right;
1316                                         }
1317                                         if ((line2.left.color == LineColor.Black) && (line2.right.color == LineColor.Black)) { 
1318                                                 line2.color = LineColor.Red;
1319                                                 line1 = line1.parent;
1320                                         } else {
1321                                                 if (line2.right.color == LineColor.Black) {
1322                                                         line2.left.color = LineColor.Black;
1323                                                         line2.color = LineColor.Red;
1324                                                         RotateRight(line2);
1325                                                         line2 = line1.parent.right;
1326                                                 }
1327                                                 line2.color = line1.parent.color;
1328                                                 line1.parent.color = LineColor.Black;
1329                                                 line2.right.color = LineColor.Black;
1330                                                 RotateLeft(line1.parent);
1331                                                 line1 = document;
1332                                         }
1333                                 } else { 
1334                                         line2 = line1.parent.left;
1335                                         if (line2.color == LineColor.Red) {
1336                                                 line2.color = LineColor.Black;
1337                                                 line1.parent.color = LineColor.Red;
1338                                                 RotateRight(line1.parent);
1339                                                 line2 = line1.parent.left;
1340                                         }
1341                                         if ((line2.right.color == LineColor.Black) && (line2.left.color == LineColor.Black)) {
1342                                                 line2.color = LineColor.Red;
1343                                                 line1 = line1.parent;
1344                                         } else {
1345                                                 if (line2.left.color == LineColor.Black) {
1346                                                         line2.right.color = LineColor.Black;
1347                                                         line2.color = LineColor.Red;
1348                                                         RotateLeft(line2);
1349                                                         line2 = line1.parent.left;
1350                                                 }
1351                                                 line2.color = line1.parent.color;
1352                                                 line1.parent.color = LineColor.Black;
1353                                                 line2.left.color = LineColor.Black;
1354                                                 RotateRight(line1.parent);
1355                                                 line1 = document;
1356                                         }
1357                                 }
1358                         }
1359                         line1.color = LineColor.Black;
1360                 }
1361
1362                 private void RotateLeft(Line line1) {
1363                         Line    line2 = line1.right;
1364
1365                         line1.right = line2.left;
1366
1367                         if (line2.left != sentinel) {
1368                                 line2.left.parent = line1;
1369                         }
1370
1371                         if (line2 != sentinel) {
1372                                 line2.parent = line1.parent;
1373                         }
1374
1375                         if (line1.parent != null) {
1376                                 if (line1 == line1.parent.left) {
1377                                         line1.parent.left = line2;
1378                                 } else {
1379                                         line1.parent.right = line2;
1380                                 }
1381                         } else {
1382                                 document = line2;
1383                         }
1384
1385                         line2.left = line1;
1386                         if (line1 != sentinel) {
1387                                 line1.parent = line2;
1388                         }
1389                 }
1390
1391                 private void RotateRight(Line line1) {
1392                         Line line2 = line1.left;
1393
1394                         line1.left = line2.right;
1395
1396                         if (line2.right != sentinel) {
1397                                 line2.right.parent = line1;
1398                         }
1399
1400                         if (line2 != sentinel) {
1401                                 line2.parent = line1.parent;
1402                         }
1403
1404                         if (line1.parent != null) {
1405                                 if (line1 == line1.parent.right) {
1406                                         line1.parent.right = line2;
1407                                 } else {
1408                                         line1.parent.left = line2;
1409                                 }
1410                         } else {
1411                                 document = line2;
1412                         }
1413
1414                         line2.right = line1;
1415                         if (line1 != sentinel) {
1416                                 line1.parent = line2;
1417                         }
1418                 }        
1419
1420
1421                 internal void UpdateView(Line line, int pos) {
1422                         if (!owner.IsHandleCreated) {
1423                                 return;
1424                         }
1425
1426                         if (update_suspended > 0) {
1427                                 update_start = Math.Min (update_start, line.line_no);
1428                                 // update_end = Math.Max (update_end, line.line_no);
1429                                 // recalc_optimize = true;
1430                                 update_pending = true;
1431                                 return;
1432                         }
1433
1434                         // Optimize invalidation based on Line alignment
1435                         if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no, true)) {
1436                                 // Lineheight changed, invalidate the rest of the document
1437                                 if ((line.Y - viewport_y) >=0 ) {
1438                                         // We formatted something that's in view, only draw parts of the screen
1439                                         owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, owner.Height - line.Y - viewport_y));
1440                                 } else {
1441                                         // The tag was above the visible area, draw everything
1442                                         owner.Invalidate();
1443                                 }
1444                         } else {
1445                                 switch(line.alignment) {
1446                                         case HorizontalAlignment.Left: {
1447                                                 owner.Invalidate(new Rectangle(line.X + (int)line.widths[pos] - viewport_x - 1, line.Y - viewport_y, viewport_width, line.height + 1));
1448                                                 break;
1449                                         }
1450
1451                                         case HorizontalAlignment.Center: {
1452                                                 owner.Invalidate(new Rectangle(line.X, line.Y - viewport_y, viewport_width, line.height + 1));
1453                                                 break;
1454                                         }
1455
1456                                         case HorizontalAlignment.Right: {
1457                                                 owner.Invalidate(new Rectangle(line.X, line.Y - viewport_y, (int)line.widths[pos + 1] - viewport_x + line.X, line.height + 1));
1458                                                 break;
1459                                         }
1460                                 }
1461                         }
1462                 }
1463
1464
1465                 // Update display from line, down line_count lines; pos is unused, but required for the signature
1466                 internal void UpdateView(Line line, int line_count, int pos) {
1467                         if (!owner.IsHandleCreated) {
1468                                 return;
1469                         }
1470
1471                         if (recalc_suspended > 0) {
1472                                 recalc_start = Math.Min (recalc_start, line.line_no);
1473                                 recalc_end = Math.Max (recalc_end, line.line_no + line_count);
1474                                 recalc_optimize = true;
1475                                 recalc_pending = true;
1476                                 return;
1477                         }
1478
1479                         int start_line_top = line.Y;                    
1480
1481                         int end_line_bottom;
1482                         Line end_line;
1483
1484                         end_line = GetLine (line.line_no + line_count);
1485                         if (end_line == null)
1486                                 end_line = GetLine (lines);
1487
1488
1489                         end_line_bottom = end_line.Y + end_line.height;
1490                         
1491                         if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no + line_count, true)) {
1492                                 // Lineheight changed, invalidate the rest of the document
1493                                 if ((line.Y - viewport_y) >=0 ) {
1494                                         // We formatted something that's in view, only draw parts of the screen
1495                                         owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, owner.Height - line.Y - viewport_y));
1496                                 } else {
1497                                         // The tag was above the visible area, draw everything
1498                                         owner.Invalidate();
1499                                 }
1500                         } else {
1501                                 int x = 0 - viewport_x;
1502                                 int w = viewport_width;
1503                                 int y = Math.Min (start_line_top - viewport_y, line.Y - viewport_y);
1504                                 int h = Math.Max (end_line_bottom - y, end_line.Y + end_line.height - y);
1505
1506                                 owner.Invalidate (new Rectangle (x, y, w, h));
1507                         }
1508                 }
1509                 #endregion      // Private Methods
1510
1511                 #region Internal Methods
1512                 // Clear the document and reset state
1513                 internal void Empty() {
1514
1515                         document = sentinel;
1516                         lines = 0;
1517
1518                         // We always have a blank line
1519                         Add (1, String.Empty, owner.Font, ThemeEngine.Current.ResPool.GetSolidBrush (owner.ForeColor), LineEnding.None);
1520                         
1521                         this.RecalculateDocument(owner.CreateGraphicsInternal());
1522                         PositionCaret(0, 0);
1523
1524                         SetSelectionVisible (false);
1525
1526                         selection_start.line = this.document;
1527                         selection_start.pos = 0;
1528                         selection_start.tag = selection_start.line.tags;
1529                         selection_end.line = this.document;
1530                         selection_end.pos = 0;
1531                         selection_end.tag = selection_end.line.tags;
1532                         char_count = 0;
1533
1534                         viewport_x = 0;
1535                         viewport_y = 0;
1536
1537                         document_x = 0;
1538                         document_y = 0;
1539
1540                         if (owner.IsHandleCreated)
1541                                 owner.Invalidate ();
1542                 }
1543
1544                 internal void PositionCaret(Line line, int pos) {
1545                         caret.tag = line.FindTag (pos);
1546
1547                         MoveCaretToTextTag ();
1548
1549                         caret.line = line;
1550                         caret.pos = pos;
1551
1552                         if (owner.IsHandleCreated) {
1553                                 if (owner.Focused) {
1554                                         if (caret.height != caret.tag.height)
1555                                                 XplatUI.CreateCaret (owner.Handle, caret_width, caret.height);
1556                                         XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.X - viewport_x, caret.line.Y + caret.tag.shift - viewport_y + caret_shift);
1557                                 }
1558
1559                                 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1560                         }
1561
1562                         // We set this at the end because we use the heights to determine whether or
1563                         // not we need to recreate the caret
1564                         caret.height = caret.tag.height;
1565
1566                 }
1567
1568                 internal void PositionCaret(int x, int y) {
1569                         if (!owner.IsHandleCreated) {
1570                                 return;
1571                         }
1572
1573                         caret.tag = FindCursor(x, y, out caret.pos);
1574
1575                         MoveCaretToTextTag ();
1576                         
1577                         caret.line = caret.tag.line;
1578                         caret.height = caret.tag.height;
1579
1580                         if (owner.ShowSelection && (!selection_visible || owner.show_caret_w_selection)) {
1581                                 XplatUI.CreateCaret (owner.Handle, caret_width, caret.height);
1582                                 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.X - viewport_x, caret.line.Y + caret.tag.shift - viewport_y + caret_shift);
1583                         }
1584
1585                         if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1586                 }
1587
1588                 internal void CaretHasFocus() {
1589                         if ((caret.tag != null) && owner.IsHandleCreated) {
1590                                 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1591                                 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.X - viewport_x, caret.line.Y + caret.tag.shift - viewport_y + caret_shift);
1592
1593                                 DisplayCaret ();
1594                         }
1595
1596                         if (owner.IsHandleCreated && SelectionLength () > 0) {
1597                                 InvalidateSelectionArea ();
1598                         }
1599                 }
1600
1601                 internal void CaretLostFocus() {
1602                         if (!owner.IsHandleCreated) {
1603                                 return;
1604                         }
1605                         XplatUI.DestroyCaret(owner.Handle);
1606                 }
1607
1608                 internal void AlignCaret() {
1609                         if (!owner.IsHandleCreated) {
1610                                 return;
1611                         }
1612
1613                         caret.tag = LineTag.FindTag (caret.line, caret.pos);
1614
1615                         MoveCaretToTextTag ();
1616
1617                         caret.height = caret.tag.height;
1618
1619                         if (owner.Focused) {
1620                                 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1621                                 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.X - viewport_x, caret.line.Y + caret.tag.shift - viewport_y + caret_shift);
1622                                 DisplayCaret ();
1623                         }
1624
1625                         if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1626                 }
1627
1628                 internal void UpdateCaret() {
1629                         if (!owner.IsHandleCreated || caret.tag == null) {
1630                                 return;
1631                         }
1632
1633                         MoveCaretToTextTag ();
1634
1635                         if (caret.tag.height != caret.height) {
1636                                 caret.height = caret.tag.height;
1637                                 if (owner.Focused) {
1638                                         XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1639                                 }
1640                         }
1641
1642                         XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.X - viewport_x, caret.line.Y + caret.tag.shift - viewport_y + caret_shift);
1643
1644                         DisplayCaret ();
1645
1646                         if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1647                 }
1648
1649                 internal void DisplayCaret() {
1650                         if (!owner.IsHandleCreated) {
1651                                 return;
1652                         }
1653
1654                         if (owner.ShowSelection && (!selection_visible || owner.show_caret_w_selection)) {
1655                                 XplatUI.CaretVisible(owner.Handle, true);
1656                         }
1657                 }
1658
1659                 internal void HideCaret() {
1660                         if (!owner.IsHandleCreated) {
1661                                 return;
1662                         }
1663
1664                         if (owner.Focused) {
1665                                 XplatUI.CaretVisible(owner.Handle, false);
1666                         }
1667                 }
1668
1669                 
1670                 internal void MoveCaretToTextTag ()
1671                 {
1672                         if (caret.tag == null || caret.tag.IsTextTag)
1673                                 return;
1674
1675                         
1676
1677                         if (caret.pos < caret.tag.start) {
1678                                 caret.tag = caret.tag.previous;
1679                         } else {
1680                                 caret.tag = caret.tag.next;
1681                         }
1682                 }
1683
1684                 internal void MoveCaret(CaretDirection direction) {
1685                         // FIXME should we use IsWordSeparator to detect whitespace, instead 
1686                         // of looking for actual spaces in the Word move cases?
1687
1688                         bool nowrap = false;
1689                         switch(direction) {
1690                                 case CaretDirection.CharForwardNoWrap:
1691                                         nowrap = true;
1692                                         goto case CaretDirection.CharForward;
1693                                 case CaretDirection.CharForward: {
1694                                         caret.pos++;
1695                                         if (caret.pos > caret.line.TextLengthWithoutEnding ()) {
1696                                                 if (!nowrap) {
1697                                                         // Go into next line
1698                                                         if (caret.line.line_no < this.lines) {
1699                                                                 caret.line = GetLine(caret.line.line_no+1);
1700                                                                 caret.pos = 0;
1701                                                                 caret.tag = caret.line.tags;
1702                                                         } else {
1703                                                                 caret.pos--;
1704                                                         }
1705                                                 } else {
1706                                                         // Single line; we stay where we are
1707                                                         caret.pos--;
1708                                                 }
1709                                         } else {
1710                                                 if ((caret.tag.start - 1 + caret.tag.length) < caret.pos) {
1711                                                         caret.tag = caret.tag.next;
1712                                                 }
1713                                         }
1714                                         UpdateCaret();
1715                                         return;
1716                                 }
1717
1718                                 case CaretDirection.CharBackNoWrap:
1719                                         nowrap = true;
1720                                         goto case CaretDirection.CharBack;
1721                                 case CaretDirection.CharBack: {
1722                                         if (caret.pos > 0) {
1723                                                 // caret.pos--; // folded into the if below
1724                                                 
1725                                                 if (--caret.pos > 0) {
1726                                                         if (caret.tag.start > caret.pos) {
1727                                                                 caret.tag = caret.tag.previous;
1728                                                         }
1729                                                 }
1730                                         } else {
1731                                                 if (caret.line.line_no > 1 && !nowrap) {
1732                                                         caret.line = GetLine(caret.line.line_no - 1);
1733                                                         caret.pos = caret.line.TextLengthWithoutEnding ();
1734                                                         caret.tag = LineTag.FindTag(caret.line, caret.pos);
1735                                                 }
1736                                         }
1737                                         UpdateCaret();
1738                                         return;
1739                                 }
1740
1741                                 case CaretDirection.WordForward: {
1742                                         int len;
1743
1744                                         len = caret.line.text.Length;
1745                                         if (caret.pos < len) {
1746                                                 while ((caret.pos < len) && (caret.line.text[caret.pos] != ' ')) {
1747                                                         caret.pos++;
1748                                                 }
1749                                                 if (caret.pos < len) {
1750                                                         // Skip any whitespace
1751                                                         while ((caret.pos < len) && (caret.line.text[caret.pos] == ' ')) {
1752                                                                 caret.pos++;
1753                                                         }
1754                                                 }
1755                                                 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1756                                         } else {
1757                                                 if (caret.line.line_no < this.lines) {
1758                                                         caret.line = GetLine(caret.line.line_no + 1);
1759                                                         caret.pos = 0;
1760                                                         caret.tag = caret.line.tags;
1761                                                 }
1762                                         }
1763                                         UpdateCaret();
1764                                         return;
1765                                 }
1766
1767                                 case CaretDirection.WordBack: {
1768                                         if (caret.pos > 0) {
1769                                                 caret.pos--;
1770
1771                                                 while ((caret.pos > 0) && (caret.line.text[caret.pos] == ' ')) {
1772                                                         caret.pos--;
1773                                                 }
1774
1775                                                 while ((caret.pos > 0) && (caret.line.text[caret.pos] != ' ')) {
1776                                                         caret.pos--;
1777                                                 }
1778
1779                                                 if (caret.line.text.ToString(caret.pos, 1) == " ") {
1780                                                         if (caret.pos != 0) {
1781                                                                 caret.pos++;
1782                                                         } else {
1783                                                                 caret.line = GetLine(caret.line.line_no - 1);
1784                                                                 caret.pos = caret.line.text.Length;
1785                                                         }
1786                                                 }
1787                                                 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1788                                         } else {
1789                                                 if (caret.line.line_no > 1) {
1790                                                         caret.line = GetLine(caret.line.line_no - 1);
1791                                                         caret.pos = caret.line.text.Length;
1792                                                         caret.tag = LineTag.FindTag(caret.line, caret.pos);
1793                                                 }
1794                                         }
1795                                         UpdateCaret();
1796                                         return;
1797                                 }
1798
1799                                 case CaretDirection.LineUp: {
1800                                         if (caret.line.line_no > 1) {
1801                                                 int     pixel;
1802
1803                                                 pixel = (int)caret.line.widths[caret.pos];
1804                                                 PositionCaret(pixel, GetLine(caret.line.line_no - 1).Y);
1805
1806                                                 DisplayCaret ();
1807                                         }
1808                                         return;
1809                                 }
1810
1811                                 case CaretDirection.LineDown: {
1812                                         if (caret.line.line_no < lines) {
1813                                                 int     pixel;
1814
1815                                                 pixel = (int)caret.line.widths[caret.pos];
1816                                                 PositionCaret(pixel, GetLine(caret.line.line_no + 1).Y);
1817
1818                                                 DisplayCaret ();
1819                                         }
1820                                         return;
1821                                 }
1822
1823                                 case CaretDirection.Home: {
1824                                         if (caret.pos > 0) {
1825                                                 caret.pos = 0;
1826                                                 caret.tag = caret.line.tags;
1827                                                 UpdateCaret();
1828                                         }
1829                                         return;
1830                                 }
1831
1832                                 case CaretDirection.End: {
1833                                         if (caret.pos < caret.line.TextLengthWithoutEnding ()) {
1834                                                 caret.pos = caret.line.TextLengthWithoutEnding ();
1835                                                 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1836                                                 UpdateCaret();
1837                                         }
1838                                         return;
1839                                 }
1840
1841                                 case CaretDirection.PgUp: {
1842
1843                                         if (viewport_y == 0 && owner.richtext) {
1844                                                 owner.vscroll.Value = 0;
1845                                                 Line line = GetLine (1);
1846                                                 PositionCaret (line, 0);
1847                                         }
1848
1849                                         int y_offset = caret.line.Y + caret.line.height - 1 - viewport_y;
1850                                         int index;
1851                                         LineTag top = FindCursor ((int) caret.line.widths [caret.pos],
1852                                                         viewport_y - viewport_height, out index);
1853
1854                                         owner.vscroll.Value = Math.Min (top.line.Y, owner.vscroll.Maximum - viewport_height);
1855                                         PositionCaret ((int) caret.line.widths [caret.pos], y_offset + viewport_y);
1856
1857                                         return;
1858                                 }
1859
1860                                 case CaretDirection.PgDn: {
1861
1862                                         if (viewport_y + viewport_height >= document_y && owner.richtext) {
1863                                                 owner.vscroll.Value = owner.vscroll.Maximum - viewport_height + 1;
1864                                                 Line line = GetLine (lines);
1865                                                 PositionCaret (line, line.Text.Length);
1866                                         }
1867
1868                                         int y_offset = caret.line.Y - viewport_y;
1869                                         int index;
1870                                         LineTag top = FindCursor ((int) caret.line.widths [caret.pos],
1871                                                         viewport_y + viewport_height, out index);
1872
1873                                         owner.vscroll.Value = Math.Min (top.line.Y, owner.vscroll.Maximum - viewport_height);
1874                                         PositionCaret ((int) caret.line.widths [caret.pos], y_offset + viewport_y);
1875                                         
1876                                         return;
1877                                 }
1878
1879                                 case CaretDirection.CtrlPgUp: {
1880                                         PositionCaret(0, viewport_y);
1881                                         DisplayCaret ();
1882                                         return;
1883                                 }
1884
1885                                 case CaretDirection.CtrlPgDn: {
1886                                         Line    line;
1887                                         LineTag tag;
1888                                         int     index;
1889
1890                                         tag = FindTag(0, viewport_y + viewport_height, out index, false);
1891                                         if (tag.line.line_no > 1) {
1892                                                 line = GetLine(tag.line.line_no - 1);
1893                                         } else {
1894                                                 line = tag.line;
1895                                         }
1896                                         PositionCaret(line, line.Text.Length);
1897                                         DisplayCaret ();
1898                                         return;
1899                                 }
1900
1901                                 case CaretDirection.CtrlHome: {
1902                                         caret.line = GetLine(1);
1903                                         caret.pos = 0;
1904                                         caret.tag = caret.line.tags;
1905
1906                                         UpdateCaret();
1907                                         return;
1908                                 }
1909
1910                                 case CaretDirection.CtrlEnd: {
1911                                         caret.line = GetLine(lines);
1912                                         caret.pos = caret.line.TextLengthWithoutEnding ();
1913                                         caret.tag = LineTag.FindTag(caret.line, caret.pos);
1914
1915                                         UpdateCaret();
1916                                         return;
1917                                 }
1918
1919                                 case CaretDirection.SelectionStart: {
1920                                         caret.line = selection_start.line;
1921                                         caret.pos = selection_start.pos;
1922                                         caret.tag = selection_start.tag;
1923
1924                                         UpdateCaret();
1925                                         return;
1926                                 }
1927
1928                                 case CaretDirection.SelectionEnd: {
1929                                         caret.line = selection_end.line;
1930                                         caret.pos = selection_end.pos;
1931                                         caret.tag = selection_end.tag;
1932
1933                                         UpdateCaret();
1934                                         return;
1935                                 }
1936                         }
1937                 }
1938
1939                 internal void DumpDoc ()
1940                 {
1941                         Console.WriteLine ("<doc lines='{0}'>", lines);
1942                         for (int i = 1; i <= lines ; i++) {
1943                                 Line line = GetLine (i);
1944                                 Console.WriteLine ("<line no='{0}' ending='{1}'>", line.line_no, line.ending);
1945
1946                                 LineTag tag = line.tags;
1947                                 while (tag != null) {
1948                                         Console.Write ("\t<tag type='{0}' span='{1}->{2}' font='{3}' color='{4}'>",
1949                                                         tag.GetType (), tag.start, tag.length, tag.font, tag.color.Color);
1950                                         Console.Write (tag.Text ());
1951                                         Console.WriteLine ("</tag>");
1952                                         tag = tag.next;
1953                                 }
1954                                 Console.WriteLine ("</line>");
1955                         }
1956                         Console.WriteLine ("</doc>");
1957                 }
1958
1959                 internal void Draw (Graphics g, Rectangle clip)
1960                 {
1961                         Line line;              // Current line being drawn
1962                         LineTag tag;            // Current tag being drawn
1963                         int start;              // First line to draw
1964                         int end;                // Last line to draw
1965                         StringBuilder text;     // String representing the current line
1966                         int line_no;
1967                         Brush tag_brush;
1968                         Brush current_brush;
1969                         Brush disabled_brush;
1970                         Brush readonly_brush;
1971                         Brush hilight;
1972                         Brush hilight_text;
1973
1974                         // First, figure out from what line to what line we need to draw
1975
1976                         if (multiline) {
1977                                 start = GetLineByPixel(clip.Top + viewport_y, false).line_no;
1978                                 end = GetLineByPixel(clip.Bottom + viewport_y, false).line_no;
1979                         } else {
1980                                 start = GetLineByPixel(clip.Left + viewport_x, false).line_no;
1981                                 end = GetLineByPixel(clip.Right + viewport_x, false).line_no;
1982                         }
1983
1984                         ///
1985                         /// We draw the single border ourself
1986                         ///
1987                         if (owner.actual_border_style == BorderStyle.FixedSingle) {
1988                                 ControlPaint.DrawBorder (g, owner.ClientRectangle, Color.Black, ButtonBorderStyle.Solid);
1989                         }
1990
1991                         /// Make sure that we aren't drawing one more line then we need to
1992                         line = GetLine (end - 1);
1993                         if (line != null && clip.Bottom == line.Y + line.height + viewport_y)
1994                                 end--;                  
1995
1996                         line_no = start;
1997
1998                         #if Debug
1999                                 DateTime        n = DateTime.Now;
2000                                 Console.WriteLine ("Started drawing: {0}s {1}ms", n.Second, n.Millisecond);
2001                                 Console.WriteLine ("CLIP:  {0}", clip);
2002                                 Console.WriteLine ("S: {0}", GetLine (start).text);
2003                                 Console.WriteLine ("E: {0}", GetLine (end).text);
2004                         #endif
2005
2006                         disabled_brush = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorGrayText);
2007                         readonly_brush = ThemeEngine.Current.ResPool.GetSolidBrush (ThemeEngine.Current.ColorControlText);
2008                         hilight = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorHighlight);
2009                         hilight_text = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorHighlightText);
2010
2011                         // Non multiline selection can be handled outside of the loop
2012                         if (!multiline && selection_visible && owner.ShowSelection) {
2013                                 g.FillRectangle (hilight,
2014                                                 selection_start.line.widths [selection_start.pos] +
2015                                                 selection_start.line.X - viewport_x, 
2016                                                 selection_start.line.Y,
2017                                                 (selection_end.line.X + selection_end.line.widths [selection_end.pos]) -
2018                                                 (selection_start.line.X + selection_start.line.widths [selection_start.pos]), 
2019                                                 selection_start.line.height);
2020                         }
2021
2022                         while (line_no <= end) {
2023                                 line = GetLine (line_no);
2024                                 float line_y = line.Y - viewport_y;
2025                                 
2026                                 tag = line.tags;
2027                                 if (!calc_pass) {
2028                                         text = line.text;
2029                                 } else {
2030                                         if (PasswordCache.Length < line.text.Length)
2031                                                 PasswordCache.Append(Char.Parse(password_char), line.text.Length - PasswordCache.Length);
2032                                         else if (PasswordCache.Length > line.text.Length)
2033                                                 PasswordCache.Remove(line.text.Length, PasswordCache.Length - line.text.Length);
2034                                         text = PasswordCache;
2035                                 }
2036
2037                                 int line_selection_start = text.Length + 1;
2038                                 int line_selection_end = text.Length + 1;
2039                                 if (selection_visible && owner.ShowSelection &&
2040                                                 (line_no >= selection_start.line.line_no) &&
2041                                                 (line_no <= selection_end.line.line_no)) {
2042
2043                                         if (line_no == selection_start.line.line_no)
2044                                                 line_selection_start = selection_start.pos + 1;
2045                                         else
2046                                                 line_selection_start = 1;
2047
2048                                         if (line_no == selection_end.line.line_no)
2049                                                 line_selection_end = selection_end.pos + 1;
2050                                         else
2051                                                 line_selection_end = text.Length + 1;
2052
2053                                         if (line_selection_end == line_selection_start) {
2054                                                 // There isn't really selection
2055                                                 line_selection_start = text.Length + 1;
2056                                                 line_selection_end = line_selection_start;
2057                                         } else if (multiline) {
2058                                                 // lets draw some selection baby!!  (non multiline selection is drawn outside the loop)
2059                                                 g.FillRectangle (hilight,
2060                                                                 line.widths [line_selection_start - 1] + line.X - viewport_x, 
2061                                                                 line_y, line.widths [line_selection_end - 1] - line.widths [line_selection_start - 1], 
2062                                                                 line.height);
2063                                         }
2064                                 }
2065
2066                                 current_brush = line.tags.color;
2067                                 while (tag != null) {
2068
2069                                         // Skip empty tags
2070                                         if (tag.length == 0) {
2071                                                 tag = tag.next;
2072                                                 continue;
2073                                         }
2074
2075                                         if (((tag.X + tag.width) < (clip.Left - viewport_x)) && (tag.X > (clip.Right - viewport_x))) {
2076                                                 tag = tag.next;
2077                                                 continue;
2078                                         }
2079
2080                                         if (tag.back_color != null) {
2081                                                 g.FillRectangle (tag.back_color, tag.X + line.X - viewport_x,
2082                                                                 line_y + tag.shift, tag.width, line.height);
2083                                         }
2084
2085                                         tag_brush = tag.color;
2086                                         current_brush = tag_brush;
2087
2088                                         if (!owner.is_enabled) {
2089                                                 Color a = ((SolidBrush) tag.color).Color;
2090                                                 Color b = ThemeEngine.Current.ColorWindowText;
2091
2092                                                 if ((a.R == b.R) && (a.G == b.G) && (a.B == b.B)) {
2093                                                         tag_brush = disabled_brush;
2094                                                 }
2095                                         } else if (owner.read_only && !owner.backcolor_set) {
2096                                                 tag_brush = readonly_brush;
2097                                         }
2098
2099                                         int tag_pos = tag.start;
2100                                         current_brush = tag_brush;
2101                                         while (tag_pos < tag.start + tag.length) {
2102                                                 int old_tag_pos = tag_pos;
2103
2104                                                 if (tag_pos >= line_selection_start && tag_pos < line_selection_end) {
2105                                                         current_brush = hilight_text;
2106                                                         tag_pos = Math.Min (tag.end, line_selection_end);
2107                                                 } else if (tag_pos < line_selection_start) {
2108                                                         current_brush = tag_brush;
2109                                                         tag_pos = Math.Min (tag.end, line_selection_start);
2110                                                 } else {
2111                                                         current_brush = tag_brush;
2112                                                         tag_pos = tag.end;
2113                                                 }
2114
2115                                                 tag.Draw (g, current_brush,
2116                                                                 line.X - viewport_x,
2117                                                                 line_y + tag.shift,
2118                                                                 old_tag_pos - 1, Math.Min (tag.length, tag_pos - old_tag_pos),
2119                                                                 text.ToString() );
2120                                         }
2121                                         tag = tag.next;
2122                                 }
2123
2124                                 line.DrawEnding (g, line_y);
2125                                 line_no++;
2126                         }
2127                 }
2128
2129                 internal int GetLineEnding (string line, int start, out LineEnding ending)
2130                 {
2131                         int res;
2132
2133                         res = line.IndexOf ('\r', start);
2134                         if (res != -1) {
2135                                 if (res + 2 < line.Length && line [res + 1] == '\r' && line [res + 2] == '\n') {
2136                                         ending = LineEnding.Soft;
2137                                         return res;
2138                                 }
2139                                 if (res + 1 < line.Length && line [res + 1] == '\n') {
2140                                         ending = LineEnding.Hard;
2141                                         return res;
2142                                 }
2143                                 ending = LineEnding.Limp;
2144                                 return res;
2145                         }
2146
2147                         res = line.IndexOf ('\n', start);
2148                         if (res != -1) {
2149                                 ending = LineEnding.Rich;
2150                                 return res;
2151                         }
2152
2153                         ending = LineEnding.Wrap;
2154                         return line.Length;
2155                 }
2156
2157                 internal int LineEndingLength (LineEnding ending)
2158                 {
2159                         int res = 0;
2160
2161                         switch (ending) {
2162                         case LineEnding.Limp:
2163                         case LineEnding.Rich:
2164                                 res = 1;
2165                                 break;
2166                         case LineEnding.Hard:
2167                                 res = 2;
2168                                 break;
2169                         case LineEnding.Soft:
2170                                 res = 3;
2171                                 break;
2172                         }
2173
2174                         return res;
2175                 }
2176
2177                 internal string LineEndingToString (LineEnding ending)
2178                 {
2179                         string res = String.Empty;
2180                         switch (ending) {
2181                         case LineEnding.Limp:
2182                                 res = "\r";
2183                                 break;
2184                         case LineEnding.Hard:
2185                                 res = "\r\n";
2186                                 break;
2187                         case LineEnding.Soft:
2188                                 res = "\r\r\n";
2189                                 break;
2190                         case LineEnding.Rich:
2191                                 res = "\n";
2192                                 break;
2193                         }
2194                         return res;
2195                 }
2196
2197                 
2198                 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
2199                 internal void Insert(Line line, int pos, bool update_caret, string s) {
2200                         int break_index;
2201                         int base_line;
2202                         int old_line_count;
2203                         int count = 1;
2204                         LineEnding ending;
2205                         LineTag tag = LineTag.FindTag (line, pos);
2206                         
2207                         SuspendRecalc ();
2208                         
2209                         base_line = line.line_no;
2210                         old_line_count = lines;
2211
2212                         break_index = GetLineEnding (s, 0, out ending);
2213
2214                         // Bump the text at insertion point a line down if we're inserting more than one line
2215                         if (break_index != s.Length) {
2216                                 Split (line, pos);
2217                                 line.ending = ending;
2218                                 // Remainder of start line is now in base_line + 1
2219                         }
2220
2221                         InsertString (line, pos, s.Substring (0, break_index + LineEndingLength (ending)));
2222                         
2223                         break_index += LineEndingLength (ending);
2224                         while (break_index < s.Length) {
2225                                 int next_break = GetLineEnding (s, break_index, out ending);
2226                                 string line_text = s.Substring (break_index, next_break - break_index +
2227                                                 LineEndingLength (ending));
2228
2229                                 Add (base_line + count, line_text, line.alignment, tag.font, tag.color, ending);
2230
2231                                 Line last = GetLine (base_line + count);
2232                                 last.ending = ending;
2233
2234                                 count++;
2235                                 break_index = next_break + LineEndingLength (ending);
2236                         }
2237
2238                         ResumeRecalc (true);
2239
2240                         UpdateView(line, lines - old_line_count + 1, pos);
2241
2242                         if (update_caret) {
2243                                 // Move caret to the end of the inserted text
2244                                 Line l = GetLine (line.line_no + lines - old_line_count);
2245                                 PositionCaret(l, l.text.Length);
2246                                 DisplayCaret ();
2247                         }
2248                 }
2249
2250                 // Inserts a character at the given position
2251                 internal void InsertString(Line line, int pos, string s) {
2252                         InsertString(line.FindTag(pos), pos, s);
2253                 }
2254
2255                 // Inserts a string at the given position
2256                 internal void InsertString(LineTag tag, int pos, string s) {
2257                         Line    line;
2258                         int     len;
2259
2260                         len = s.Length;
2261
2262                         CharCount += len;
2263
2264                         line = tag.line;
2265                         line.text.Insert(pos, s);
2266
2267                         tag = tag.next;
2268                         while (tag != null) {
2269                                 tag.start += len;
2270                                 tag = tag.next;
2271                         }
2272                         line.Grow(len);
2273                         line.recalc = true;
2274
2275                         UpdateView(line, pos);
2276                 }
2277
2278                 // Inserts a string at the caret position
2279                 internal void InsertStringAtCaret(string s, bool move_caret) {
2280
2281                         InsertString (caret.tag, caret.pos, s);
2282
2283                         UpdateView(caret.line, caret.pos);
2284                         if (move_caret) {
2285                                 caret.pos += s.Length;
2286                                 UpdateCaret();
2287                         }
2288                 }
2289
2290
2291
2292                 // Inserts a character at the given position
2293                 internal void InsertChar(Line line, int pos, char ch) {
2294                         InsertChar(line.FindTag(pos), pos, ch);
2295                 }
2296
2297                 // Inserts a character at the given position
2298                 internal void InsertChar(LineTag tag, int pos, char ch) {
2299                         Line    line;
2300
2301                         CharCount++;
2302
2303                         line = tag.line;
2304                         line.text.Insert(pos, ch);
2305
2306                         tag = tag.next;
2307                         while (tag != null) {
2308                                 tag.start++;
2309                                 tag = tag.next;
2310                         }
2311                         line.Grow(1);
2312                         line.recalc = true;
2313
2314                         undo.RecordTyping (line, pos, ch);
2315                         UpdateView(line, pos);
2316                 }
2317
2318                 // Inserts a character at the current caret position
2319                 internal void InsertCharAtCaret(char ch, bool move_caret) {
2320                         /*
2321                         LineTag tag;
2322
2323                         CharCount++;
2324
2325                         caret.line.text.Insert(caret.pos, ch);
2326                         caret.tag.length++;
2327                         
2328                         if (caret.tag.next != null) {
2329                                 tag = caret.tag.next;
2330                                 while (tag != null) {
2331                                         tag.start++;
2332                                         tag = tag.next;
2333                                 }
2334                         }
2335                         caret.line.Grow(1);
2336                         caret.line.recalc = true;
2337                         */
2338                         InsertChar (caret.tag, caret.pos, ch);
2339
2340                         UpdateView(caret.line, caret.pos);
2341                         if (move_caret) {
2342                                 caret.pos++;
2343                                 UpdateCaret();
2344                                 SetSelectionToCaret(true);
2345                         }
2346
2347                 }
2348                 
2349                 internal void InsertPicture (Line line, int pos, RTF.Picture picture)
2350                 {
2351                         LineTag next_tag;
2352                         LineTag tag;
2353                         int len;
2354
2355                         len = 1;
2356
2357                         // Just a place holder basically
2358                         line.text.Insert (pos, "I");
2359
2360                         PictureTag picture_tag = new PictureTag (line, pos + 1, picture);
2361
2362                         tag = LineTag.FindTag (line, pos);
2363                         picture_tag.CopyFormattingFrom (tag);
2364                         next_tag = tag.Break (pos + 1);
2365                         picture_tag.previous = tag;
2366                         picture_tag.next = tag.next;
2367                         tag.next = picture_tag;
2368
2369                         //
2370                         // Picture tags need to be surrounded by text tags
2371                         //
2372                         if (picture_tag.next == null) {
2373                                 picture_tag.next = new LineTag (line, pos + 1);
2374                                 picture_tag.next.CopyFormattingFrom (tag);
2375                                 picture_tag.next.previous = picture_tag;
2376                         }
2377
2378                         tag = picture_tag.next;
2379                         while (tag != null) {
2380                                 tag.start += len;
2381                                 tag = tag.next;
2382                         }
2383
2384                         line.Grow (len);
2385                         line.recalc = true;
2386
2387                         UpdateView (line, pos);
2388                 }
2389
2390                 internal void DeleteMultiline (Line start_line, int pos, int length)
2391                 {
2392                         Marker start = new Marker ();
2393                         Marker end = new Marker ();
2394                         int start_index = LineTagToCharIndex (start_line, pos);
2395
2396                         start.line = start_line;
2397                         start.pos = pos;
2398                         start.tag = LineTag.FindTag (start_line, pos);
2399
2400                         CharIndexToLineTag (start_index + length, out end.line,
2401                                         out end.tag, out end.pos);
2402
2403                         SuspendUpdate ();
2404
2405                         if (start.line == end.line) {
2406                                 DeleteChars (start.tag, pos, end.pos - pos);
2407                         } else {
2408
2409                                 // Delete first and last lines
2410                                 DeleteChars (start.tag, start.pos, start.line.text.Length - start.pos);
2411                                 DeleteChars (end.line.tags, 0, end.pos);
2412
2413                                 int current = start.line.line_no + 1;
2414                                 if (current < end.line.line_no) {
2415                                         for (int i = end.line.line_no - 1; i >= current; i--) {
2416                                                 Delete (i);
2417                                         }
2418                                 }
2419
2420                                 // BIG FAT WARNING - selection_end.line might be stale due 
2421                                 // to the above Delete() call. DONT USE IT before hitting the end of this method!
2422
2423                                 // Join start and end
2424                                 Combine (start.line.line_no, current);
2425                         }
2426
2427                         ResumeUpdate (true);
2428                 }
2429
2430                 
2431                 // Deletes n characters at the given position; it will not delete past line limits
2432                 // pos is 0-based
2433                 internal void DeleteChars(LineTag tag, int pos, int count) {
2434                         Line    line;
2435                         bool    streamline;
2436
2437                         streamline = false;
2438                         line = tag.line;
2439
2440                         CharCount -= count;
2441
2442                         if (pos == line.text.Length) {
2443                                 return;
2444                         }
2445
2446                         line.text.Remove(pos, count);
2447
2448                         // Make sure the tag points to the right spot
2449                         while ((tag != null) && (tag.end) < pos) {
2450                                 tag = tag.next;
2451                         }
2452
2453                         if (tag == null) {
2454                                 goto Cleanup;
2455                         }
2456
2457                         // Check if we're crossing tag boundaries
2458                         if ((pos + count) > (tag.start + tag.length - 1)) {
2459                                 int     left;
2460
2461                                 // We have to delete cross tag boundaries
2462                                 streamline = true;
2463                                 left = count;
2464
2465                                 left -= tag.start + tag.length - pos - 1;
2466
2467                                 tag = tag.next;
2468                                 while ((tag != null) && (left > 0)) {
2469                                         tag.start -= count - left;
2470
2471                                         if (tag.length > left) {
2472                                                 left = 0;
2473                                         } else {
2474                                                 left -= tag.length;
2475                                                 tag = tag.next;
2476                                         }
2477
2478                                 }
2479                         } else {
2480                                 // We got off easy, same tag
2481
2482                                 if (tag.length == 0) {
2483                                         streamline = true;
2484                                 }
2485                         }
2486
2487                         // Delete empty orphaned tags at the end
2488                         LineTag walk = tag;
2489                         while (walk != null && walk.next != null && walk.next.length == 0) {
2490                                 LineTag t = walk;
2491                                 walk.next = walk.next.next;
2492                                 if (walk.next != null)
2493                                         walk.next.previous = t;
2494                                 walk = walk.next;
2495                         }
2496
2497                         // Adjust the start point of any tags following
2498                         if (tag != null) {
2499                                 tag = tag.next;
2500                                 while (tag != null) {
2501                                         tag.start -= count;
2502                                         tag = tag.next;
2503                                 }
2504                         }
2505
2506                         line.recalc = true;
2507                         if (streamline) {
2508                                 line.Streamline(lines);
2509                         }
2510
2511                 Cleanup:
2512                         if (pos >= line.TextLengthWithoutEnding ()) {
2513                                 LineEnding ending = line.ending;
2514                                 GetLineEnding (line.text.ToString (), 0, out ending);
2515                                 if (ending != line.ending) {
2516                                         line.ending = ending;
2517
2518                                         if (!multiline) {
2519                                                 UpdateView (line, lines, pos);
2520                                                 owner.Invalidate ();
2521                                                 return;
2522                                         }
2523                                 }
2524                         }
2525                         if (!multiline) {
2526                                 UpdateView (line, lines, pos);
2527                                 owner.Invalidate ();
2528                         } else 
2529                                 UpdateView(line, pos);
2530                 }
2531
2532                 // Deletes a character at or after the given position (depending on forward); it will not delete past line limits
2533                 internal void DeleteChar(LineTag tag, int pos, bool forward) {
2534                         Line    line;
2535                         bool    streamline;
2536
2537                         CharCount--;
2538
2539                         streamline = false;
2540                         line = tag.line;
2541
2542                         if ((pos == 0 && forward == false) || (pos == line.text.Length && forward == true)) {
2543                                 return;
2544                         }
2545
2546
2547                         if (forward) {
2548                                 line.text.Remove(pos, 1);
2549
2550                                 while ((tag != null) && (tag.start + tag.length - 1) <= pos) {
2551                                         tag = tag.next;
2552                                 }
2553
2554                                 if (tag == null) {
2555                                         goto Cleanup;
2556                                 }
2557
2558                                 //      tag.length--;
2559
2560                                 if (tag.length == 0) {
2561                                         streamline = true;
2562                                 }
2563                         } else {
2564                                 pos--;
2565                                 line.text.Remove(pos, 1);
2566                                 if (pos >= (tag.start - 1)) {
2567                                         //              tag.length--;
2568                                         if (tag.length == 0) {
2569                                                 streamline = true;
2570                                         }
2571                                 } else if (tag.previous != null) {
2572                                         //              tag.previous.length--;
2573                                         if (tag.previous.length == 0) {
2574                                                 streamline = true;
2575                                         }
2576                                 }
2577                         }
2578
2579                         // Delete empty orphaned tags at the end
2580                         LineTag walk = tag;
2581                         while (walk != null && walk.next != null && walk.next.length == 0) {
2582                                 LineTag t = walk;
2583                                 walk.next = walk.next.next;
2584                                 if (walk.next != null)
2585                                         walk.next.previous = t;
2586                                 walk = walk.next;
2587                         }
2588
2589                         tag = tag.next;
2590                         while (tag != null) {
2591                                 tag.start--;
2592                                 tag = tag.next;
2593                         }
2594                         line.recalc = true;
2595                         if (streamline) {
2596                                 line.Streamline(lines);
2597                         }
2598
2599                 Cleanup:
2600                         if (pos >= line.TextLengthWithoutEnding ()) {
2601                                 LineEnding ending = line.ending;
2602                                 GetLineEnding (line.text.ToString (), 0, out ending);
2603                                 if (ending != line.ending) {
2604                                         line.ending = ending;
2605
2606                                         if (!multiline) {
2607                                                 UpdateView (line, lines, pos);
2608                                                 owner.Invalidate ();
2609                                                 return;
2610                                         }
2611                                 }
2612                         }
2613                         if (!multiline) {
2614                                 UpdateView (line, lines, pos);
2615                                 owner.Invalidate ();
2616                         } else 
2617                                 UpdateView(line, pos);
2618                 }
2619
2620                 // Combine two lines
2621                 internal void Combine(int FirstLine, int SecondLine) {
2622                         Combine(GetLine(FirstLine), GetLine(SecondLine));
2623                 }
2624
2625                 internal void Combine(Line first, Line second) {
2626                         LineTag last;
2627                         int     shift;
2628
2629                         // strip the ending off of the first lines text
2630                         first.text.Length = first.text.Length - LineEndingLength (first.ending);
2631
2632                         // Combine the two tag chains into one
2633                         last = first.tags;
2634
2635                         // Maintain the line ending style
2636                         first.ending = second.ending;
2637
2638                         while (last.next != null) {
2639                                 last = last.next;
2640                         }
2641
2642                         // need to get the shift before setting the next tag since that effects length
2643                         shift = last.start + last.length - 1;
2644                         last.next = second.tags;
2645                         last.next.previous = last;
2646
2647                         // Fix up references within the chain
2648                         last = last.next;
2649                         while (last != null) {
2650                                 last.line = first;
2651                                 last.start += shift;
2652                                 last = last.next;
2653                         }
2654
2655                         // Combine both lines' strings
2656                         first.text.Insert(first.text.Length, second.text.ToString());
2657                         first.Grow(first.text.Length);
2658
2659                         // Remove the reference to our (now combined) tags from the doomed line
2660                         second.tags = null;
2661
2662                         // Renumber lines
2663                         DecrementLines(first.line_no + 2);      // first.line_no + 1 will be deleted, so we need to start renumbering one later
2664
2665                         // Mop up
2666                         first.recalc = true;
2667                         first.height = 0;       // This forces RecalcDocument/UpdateView to redraw from this line on
2668                         first.Streamline(lines);
2669
2670                         // Update Caret, Selection, etc
2671                         if (caret.line == second) {
2672                                 caret.Combine(first, shift);
2673                         }
2674                         if (selection_anchor.line == second) {
2675                                 selection_anchor.Combine(first, shift);
2676                         }
2677                         if (selection_start.line == second) {
2678                                 selection_start.Combine(first, shift);
2679                         }
2680                         if (selection_end.line == second) {
2681                                 selection_end.Combine(first, shift);
2682                         }
2683
2684                         #if Debug
2685                                 Line    check_first;
2686                                 Line    check_second;
2687
2688                                 check_first = GetLine(first.line_no);
2689                                 check_second = GetLine(check_first.line_no + 1);
2690
2691                                 Console.WriteLine("Pre-delete: Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2692                         #endif
2693
2694                         this.Delete(second);
2695
2696                         #if Debug
2697                                 check_first = GetLine(first.line_no);
2698                                 check_second = GetLine(check_first.line_no + 1);
2699
2700                                 Console.WriteLine("Post-delete Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2701                         #endif
2702                 }
2703
2704                 // Split the line at the position into two
2705                 internal void Split(int LineNo, int pos) {
2706                         Line    line;
2707                         LineTag tag;
2708
2709                         line = GetLine(LineNo);
2710                         tag = LineTag.FindTag(line, pos);
2711                         Split(line, tag, pos);
2712                 }
2713
2714                 internal void Split(Line line, int pos) {
2715                         LineTag tag;
2716
2717                         tag = LineTag.FindTag(line, pos);
2718                         Split(line, tag, pos);
2719                 }
2720
2721                 ///<summary>Split line at given tag and position into two lines</summary>
2722                 ///if more space becomes available on previous line</param>
2723                 internal void Split(Line line, LineTag tag, int pos) {
2724                         LineTag new_tag;
2725                         Line    new_line;
2726                         bool    move_caret;
2727                         bool    move_sel_start;
2728                         bool    move_sel_end;
2729
2730                         move_caret = false;
2731                         move_sel_start = false;
2732                         move_sel_end = false;
2733
2734                         // Adjust selection and cursors
2735                         if (caret.line == line && caret.pos >= pos) {
2736                                 move_caret = true;
2737                         }
2738                         if (selection_start.line == line && selection_start.pos > pos) {
2739                                 move_sel_start = true;
2740                         }
2741
2742                         if (selection_end.line == line && selection_end.pos > pos) {
2743                                 move_sel_end = true;
2744                         }
2745
2746                         // cover the easy case first
2747                         if (pos == line.text.Length) {
2748                                 Add (line.line_no + 1, String.Empty, line.alignment, tag.font, tag.color, line.ending);
2749
2750                                 new_line = GetLine (line.line_no + 1);
2751                                 
2752                                 if (move_caret) {
2753                                         caret.line = new_line;
2754                                         caret.tag = new_line.tags;
2755                                         caret.pos = 0;
2756                                 }
2757
2758                                 if (move_sel_start) {
2759                                         selection_start.line = new_line;
2760                                         selection_start.pos = 0;
2761                                         selection_start.tag = new_line.tags;
2762                                 }
2763
2764                                 if (move_sel_end) {
2765                                         selection_end.line = new_line;
2766                                         selection_end.pos = 0;
2767                                         selection_end.tag = new_line.tags;
2768                                 }
2769                                 return;
2770                         }
2771
2772                         // We need to move the rest of the text into the new line
2773                         Add (line.line_no + 1, line.text.ToString (pos, line.text.Length - pos), line.alignment, tag.font, tag.color, line.ending);
2774
2775                         // Now transfer our tags from this line to the next
2776                         new_line = GetLine(line.line_no + 1);
2777
2778                         line.recalc = true;
2779                         new_line.recalc = true;
2780
2781                         if ((tag.start - 1) == pos) {
2782                                 int     shift;
2783
2784                                 // We can simply break the chain and move the tag into the next line
2785                                 if (tag == line.tags) {
2786                                         new_tag = new LineTag(line, 1);
2787                                         new_tag.CopyFormattingFrom (tag);
2788                                         line.tags = new_tag;
2789                                 }
2790
2791                                 if (tag.previous != null) {
2792                                         tag.previous.next = null;
2793                                 }
2794                                 new_line.tags = tag;
2795                                 tag.previous = null;
2796                                 tag.line = new_line;
2797
2798                                 // Walk the list and correct the start location of the tags we just bumped into the next line
2799                                 shift = tag.start - 1;
2800
2801                                 new_tag = tag;
2802                                 while (new_tag != null) {
2803                                         new_tag.start -= shift;
2804                                         new_tag.line = new_line;
2805                                         new_tag = new_tag.next;
2806                                 }
2807                         } else {
2808                                 int     shift;
2809
2810                                 new_tag = new LineTag (new_line, 1);                    
2811                                 new_tag.next = tag.next;
2812                                 new_tag.CopyFormattingFrom (tag);
2813                                 new_line.tags = new_tag;
2814                                 if (new_tag.next != null) {
2815                                         new_tag.next.previous = new_tag;
2816                                 }
2817                                 tag.next = null;
2818
2819                                 shift = pos;
2820                                 new_tag = new_tag.next;
2821                                 while (new_tag != null) {
2822                                         new_tag.start -= shift;
2823                                         new_tag.line = new_line;
2824                                         new_tag = new_tag.next;
2825
2826                                 }
2827                         }
2828
2829                         if (move_caret) {
2830                                 caret.line = new_line;
2831                                 caret.pos = caret.pos - pos;
2832                                 caret.tag = caret.line.FindTag(caret.pos);
2833                         }
2834
2835                         if (move_sel_start) {
2836                                 selection_start.line = new_line;
2837                                 selection_start.pos = selection_start.pos - pos;
2838                                 selection_start.tag = new_line.FindTag(selection_start.pos);
2839                         }
2840
2841                         if (move_sel_end) {
2842                                 selection_end.line = new_line;
2843                                 selection_end.pos = selection_end.pos - pos;
2844                                 selection_end.tag = new_line.FindTag(selection_end.pos);
2845                         }
2846
2847                         CharCount -= line.text.Length - pos;
2848                         line.text.Remove(pos, line.text.Length - pos);
2849                 }
2850
2851                 // Adds a line of text, with given font.
2852                 // Bumps any line at that line number that already exists down
2853                 internal void Add (int LineNo, string Text, Font font, SolidBrush color, LineEnding ending)
2854                 {
2855                         Add (LineNo, Text, alignment, font, color, ending);
2856                 }
2857
2858                 internal void Add (int LineNo, string Text, HorizontalAlignment align, Font font, SolidBrush color, LineEnding ending)
2859                 {
2860                         Line    add;
2861                         Line    line;
2862                         int     line_no;
2863
2864                         CharCount += Text.Length;
2865
2866                         if (LineNo<1 || Text == null) {
2867                                 if (LineNo<1) {
2868                                         throw new ArgumentNullException("LineNo", "Line numbers must be positive");
2869                                 } else {
2870                                         throw new ArgumentNullException("Text", "Cannot insert NULL line");
2871                                 }
2872                         }
2873
2874                         add = new Line (this, LineNo, Text, align, font, color, ending);
2875
2876                         line = document;
2877                         while (line != sentinel) {
2878                                 add.parent = line;
2879                                 line_no = line.line_no;
2880
2881                                 if (LineNo > line_no) {
2882                                         line = line.right;
2883                                 } else if (LineNo < line_no) {
2884                                         line = line.left;
2885                                 } else {
2886                                         // Bump existing line numbers; walk all nodes to the right of this one and increment line_no
2887                                         IncrementLines(line.line_no);
2888                                         line = line.left;
2889                                 }
2890                         }
2891
2892                         add.left = sentinel;
2893                         add.right = sentinel;
2894
2895                         if (add.parent != null) {
2896                                 if (LineNo > add.parent.line_no) {
2897                                         add.parent.right = add;
2898                                 } else {
2899                                         add.parent.left = add;
2900                                 }
2901                         } else {
2902                                 // Root node
2903                                 document = add;
2904                         }
2905
2906                         RebalanceAfterAdd(add);
2907
2908                         lines++;
2909                 }
2910
2911                 internal virtual void Clear() {
2912                         lines = 0;
2913                         CharCount = 0;
2914                         document = sentinel;
2915                 }
2916
2917                 public virtual object Clone() {
2918                         Document clone;
2919
2920                         clone = new Document(null);
2921
2922                         clone.lines = this.lines;
2923                         clone.document = (Line)document.Clone();
2924
2925                         return clone;
2926                 }
2927
2928                 internal void Delete(int LineNo) {
2929                         Line    line;
2930
2931                         if (LineNo>lines) {
2932                                 return;
2933                         }
2934
2935                         line = GetLine(LineNo);
2936
2937                         CharCount -= line.text.Length;
2938
2939                         DecrementLines(LineNo + 1);
2940                         Delete(line);
2941                 }
2942
2943                 internal void Delete(Line line1) {
2944                         Line    line2;// = new Line();
2945                         Line    line3;
2946
2947                         if ((line1.left == sentinel) || (line1.right == sentinel)) {
2948                                 line3 = line1;
2949                         } else {
2950                                 line3 = line1.right;
2951                                 while (line3.left != sentinel) {
2952                                         line3 = line3.left;
2953                                 }
2954                         }
2955
2956                         if (line3.left != sentinel) {
2957                                 line2 = line3.left;
2958                         } else {
2959                                 line2 = line3.right;
2960                         }
2961
2962                         line2.parent = line3.parent;
2963                         if (line3.parent != null) {
2964                                 if(line3 == line3.parent.left) {
2965                                         line3.parent.left = line2;
2966                                 } else {
2967                                         line3.parent.right = line2;
2968                                 }
2969                         } else {
2970                                 document = line2;
2971                         }
2972
2973                         if (line3 != line1) {
2974                                 LineTag tag;
2975
2976                                 if (selection_start.line == line3) {
2977                                         selection_start.line = line1;
2978                                 }
2979
2980                                 if (selection_end.line == line3) {
2981                                         selection_end.line = line1;
2982                                 }
2983
2984                                 if (selection_anchor.line == line3) {
2985                                         selection_anchor.line = line1;
2986                                 }
2987
2988                                 if (caret.line == line3) {
2989                                         caret.line = line1;
2990                                 }
2991
2992
2993                                 line1.alignment = line3.alignment;
2994                                 line1.ascent = line3.ascent;
2995                                 line1.hanging_indent = line3.hanging_indent;
2996                                 line1.height = line3.height;
2997                                 line1.indent = line3.indent;
2998                                 line1.line_no = line3.line_no;
2999                                 line1.recalc = line3.recalc;
3000                                 line1.right_indent = line3.right_indent;
3001                                 line1.ending = line3.ending;
3002                                 line1.space = line3.space;
3003                                 line1.tags = line3.tags;
3004                                 line1.text = line3.text;
3005                                 line1.widths = line3.widths;
3006                                 line1.offset = line3.offset;
3007
3008                                 tag = line1.tags;
3009                                 while (tag != null) {
3010                                         tag.line = line1;
3011                                         tag = tag.next;
3012                                 }
3013                         }
3014
3015                         if (line3.color == LineColor.Black)
3016                                 RebalanceAfterDelete(line2);
3017
3018                         this.lines--;
3019                 }
3020
3021                 // Invalidate a section of the document to trigger redraw
3022                 internal void Invalidate(Line start, int start_pos, Line end, int end_pos) {
3023                         Line    l1;
3024                         Line    l2;
3025                         int     p1;
3026                         int     p2;
3027
3028                         if ((start == end) && (start_pos == end_pos)) {
3029                                 return;
3030                         }
3031
3032                         if (end_pos == -1) {
3033                                 end_pos = end.text.Length;
3034                         }
3035         
3036                         // figure out what's before what so the logic below is straightforward
3037                         if (start.line_no < end.line_no) {
3038                                 l1 = start;
3039                                 p1 = start_pos;
3040
3041                                 l2 = end;
3042                                 p2 = end_pos;
3043                         } else if (start.line_no > end.line_no) {
3044                                 l1 = end;
3045                                 p1 = end_pos;
3046
3047                                 l2 = start;
3048                                 p2 = start_pos;
3049                         } else {
3050                                 if (start_pos < end_pos) {
3051                                         l1 = start;
3052                                         p1 = start_pos;
3053
3054                                         l2 = end;
3055                                         p2 = end_pos;
3056                                 } else {
3057                                         l1 = end;
3058                                         p1 = end_pos;
3059
3060                                         l2 = start;
3061                                         p2 = start_pos;
3062                                 }
3063
3064                                 int endpoint = (int) l1.widths [p2];
3065                                 if (p2 == l1.text.Length + 1) {
3066                                         endpoint = (int) viewport_width;
3067                                 }
3068
3069                                 #if Debug
3070                                         Console.WriteLine("Invaliding backwards from {0}:{1} to {2}:{3}   {4}",
3071                                                         l1.line_no, p1, l2.line_no, p2,
3072                                                         new Rectangle(
3073                                                                 (int)l1.widths[p1] + l1.X - viewport_x, 
3074                                                                 l1.Y - viewport_y, 
3075                                                                 (int)l1.widths[p2], 
3076                                                                 l1.height
3077                                                                 )
3078                                                 );
3079                                 #endif
3080
3081                                 owner.Invalidate(new Rectangle (
3082                                         (int)l1.widths[p1] + l1.X - viewport_x, 
3083                                         l1.Y - viewport_y, 
3084                                         endpoint - (int)l1.widths[p1] + 1, 
3085                                         l1.height));
3086                                 return;
3087                         }
3088
3089                         #if Debug
3090                                 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);
3091                                 Console.WriteLine ("invalidate start line:  {0}  position:  {1}", l1.text, p1);
3092                         #endif
3093
3094                         // Three invalidates:
3095                         // First line from start
3096                         owner.Invalidate(new Rectangle((int)l1.widths[p1] + l1.X - viewport_x, l1.Y - viewport_y, viewport_width, l1.height));
3097
3098                         
3099                         // lines inbetween
3100                         if ((l1.line_no + 1) < l2.line_no) {
3101                                 int     y;
3102
3103                                 y = GetLine(l1.line_no + 1).Y;
3104                                 owner.Invalidate(new Rectangle(0, y - viewport_y, viewport_width, l2.Y - y));
3105
3106                                 #if Debug
3107                                         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);
3108                                 #endif
3109                         }
3110                         
3111
3112                         // Last line to end
3113                         owner.Invalidate(new Rectangle((int)l2.widths[0] + l2.X - viewport_x, l2.Y - viewport_y, (int)l2.widths[p2] + 1, l2.height));
3114                         #if Debug
3115                                 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);
3116
3117                         #endif
3118                 }
3119
3120                 /// <summary>Select text around caret</summary>
3121                 internal void ExpandSelection(CaretSelection mode, bool to_caret) {
3122                         if (to_caret) {
3123                                 // We're expanding the selection to the caret position
3124                                 switch(mode) {
3125                                         case CaretSelection.Line: {
3126                                                 // Invalidate the selection delta
3127                                                 if (caret > selection_prev) {
3128                                                         Invalidate(selection_prev.line, 0, caret.line, caret.line.text.Length);
3129                                                 } else {
3130                                                         Invalidate(selection_prev.line, selection_prev.line.text.Length, caret.line, 0);
3131                                                 }
3132
3133                                                 if (caret.line.line_no <= selection_anchor.line.line_no) {
3134                                                         selection_start.line = caret.line;
3135                                                         selection_start.tag = caret.line.tags;
3136                                                         selection_start.pos = 0;
3137
3138                                                         selection_end.line = selection_anchor.line;
3139                                                         selection_end.tag = selection_anchor.tag;
3140                                                         selection_end.pos = selection_anchor.pos;
3141
3142                                                         selection_end_anchor = true;
3143                                                 } else {
3144                                                         selection_start.line = selection_anchor.line;
3145                                                         selection_start.pos = selection_anchor.height;
3146                                                         selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height);
3147
3148                                                         selection_end.line = caret.line;
3149                                                         selection_end.tag = caret.line.tags;
3150                                                         selection_end.pos = caret.line.text.Length;
3151
3152                                                         selection_end_anchor = false;
3153                                                 }
3154                                                 selection_prev.line = caret.line;
3155                                                 selection_prev.tag = caret.tag;
3156                                                 selection_prev.pos = caret.pos;
3157
3158                                                 break;
3159                                         }
3160
3161                                         case CaretSelection.Word: {
3162                                                 int     start_pos;
3163                                                 int     end_pos;
3164
3165                                                 start_pos = FindWordSeparator(caret.line, caret.pos, false);
3166                                                 end_pos = FindWordSeparator(caret.line, caret.pos, true);
3167
3168                                                 
3169                                                 // Invalidate the selection delta
3170                                                 if (caret > selection_prev) {
3171                                                         Invalidate(selection_prev.line, selection_prev.pos, caret.line, end_pos);
3172                                                 } else {
3173                                                         Invalidate(selection_prev.line, selection_prev.pos, caret.line, start_pos);
3174                                                 }
3175                                                 if (caret < selection_anchor) {
3176                                                         selection_start.line = caret.line;
3177                                                         selection_start.tag = caret.line.FindTag(start_pos);
3178                                                         selection_start.pos = start_pos;
3179
3180                                                         selection_end.line = selection_anchor.line;
3181                                                         selection_end.tag = selection_anchor.tag;
3182                                                         selection_end.pos = selection_anchor.pos;
3183
3184                                                         selection_prev.line = caret.line;
3185                                                         selection_prev.tag = caret.tag;
3186                                                         selection_prev.pos = start_pos;
3187
3188                                                         selection_end_anchor = true;
3189                                                 } else {
3190                                                         selection_start.line = selection_anchor.line;
3191                                                         selection_start.pos = selection_anchor.height;
3192                                                         selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height);
3193
3194                                                         selection_end.line = caret.line;
3195                                                         selection_end.tag = caret.line.FindTag(end_pos);
3196                                                         selection_end.pos = end_pos;
3197
3198                                                         selection_prev.line = caret.line;
3199                                                         selection_prev.tag = caret.tag;
3200                                                         selection_prev.pos = end_pos;
3201
3202                                                         selection_end_anchor = false;
3203                                                 }
3204                                                 break;
3205                                         }
3206
3207                                         case CaretSelection.Position: {
3208                                                 SetSelectionToCaret(false);
3209                                                 return;
3210                                         }
3211                                 }
3212                         } else {
3213                                 // We're setting the selection 'around' the caret position
3214                                 switch(mode) {
3215                                         case CaretSelection.Line: {
3216                                                 this.Invalidate(caret.line, 0, caret.line, caret.line.text.Length);
3217
3218                                                 selection_start.line = caret.line;
3219                                                 selection_start.tag = caret.line.tags;
3220                                                 selection_start.pos = 0;
3221
3222                                                 selection_end.line = caret.line;
3223                                                 selection_end.pos = caret.line.text.Length;
3224                                                 selection_end.tag = caret.line.FindTag(selection_end.pos);
3225
3226                                                 selection_anchor.line = selection_end.line;
3227                                                 selection_anchor.tag = selection_end.tag;
3228                                                 selection_anchor.pos = selection_end.pos;
3229                                                 selection_anchor.height = 0;
3230
3231                                                 selection_prev.line = caret.line;
3232                                                 selection_prev.tag = caret.tag;
3233                                                 selection_prev.pos = caret.pos;
3234
3235                                                 this.selection_end_anchor = true;
3236
3237                                                 break;
3238                                         }
3239
3240                                         case CaretSelection.Word: {
3241                                                 int     start_pos;
3242                                                 int     end_pos;
3243
3244                                                 start_pos = FindWordSeparator(caret.line, caret.pos, false);
3245                                                 end_pos = FindWordSeparator(caret.line, caret.pos, true);
3246
3247                                                 this.Invalidate(selection_start.line, start_pos, caret.line, end_pos);
3248
3249                                                 selection_start.line = caret.line;
3250                                                 selection_start.tag = caret.line.FindTag(start_pos);
3251                                                 selection_start.pos = start_pos;
3252
3253                                                 selection_end.line = caret.line;
3254                                                 selection_end.tag = caret.line.FindTag(end_pos);
3255                                                 selection_end.pos = end_pos;
3256
3257                                                 selection_anchor.line = selection_end.line;
3258                                                 selection_anchor.tag = selection_end.tag;
3259                                                 selection_anchor.pos = selection_end.pos;
3260                                                 selection_anchor.height = start_pos;
3261
3262                                                 selection_prev.line = caret.line;
3263                                                 selection_prev.tag = caret.tag;
3264                                                 selection_prev.pos = caret.pos;
3265
3266                                                 this.selection_end_anchor = true;
3267
3268                                                 break;
3269                                         }
3270                                 }
3271                         }
3272
3273                         SetSelectionVisible (!(selection_start == selection_end));
3274                 }
3275
3276                 internal void SetSelectionToCaret(bool start) {
3277                         if (start) {
3278                                 // Invalidate old selection; selection is being reset to empty
3279                                 this.Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3280
3281                                 selection_start.line = caret.line;
3282                                 selection_start.tag = caret.tag;
3283                                 selection_start.pos = caret.pos;
3284
3285                                 // start always also selects end
3286                                 selection_end.line = caret.line;
3287                                 selection_end.tag = caret.tag;
3288                                 selection_end.pos = caret.pos;
3289
3290                                 selection_anchor.line = caret.line;
3291                                 selection_anchor.tag = caret.tag;
3292                                 selection_anchor.pos = caret.pos;
3293                         } else {
3294                                 // Invalidate from previous end to caret (aka new end)
3295                                 if (selection_end_anchor) {
3296                                         if (selection_start != caret) {
3297                                                 this.Invalidate(selection_start.line, selection_start.pos, caret.line, caret.pos);
3298                                         }
3299                                 } else {
3300                                         if (selection_end != caret) {
3301                                                 this.Invalidate(selection_end.line, selection_end.pos, caret.line, caret.pos);
3302                                         }
3303                                 }
3304
3305                                 if (caret < selection_anchor) {
3306                                         selection_start.line = caret.line;
3307                                         selection_start.tag = caret.tag;
3308                                         selection_start.pos = caret.pos;
3309
3310                                         selection_end.line = selection_anchor.line;
3311                                         selection_end.tag = selection_anchor.tag;
3312                                         selection_end.pos = selection_anchor.pos;
3313
3314                                         selection_end_anchor = true;
3315                                 } else {
3316                                         selection_start.line = selection_anchor.line;
3317                                         selection_start.tag = selection_anchor.tag;
3318                                         selection_start.pos = selection_anchor.pos;
3319
3320                                         selection_end.line = caret.line;
3321                                         selection_end.tag = caret.tag;
3322                                         selection_end.pos = caret.pos;
3323
3324                                         selection_end_anchor = false;
3325                                 }
3326                         }
3327
3328                         SetSelectionVisible (!(selection_start == selection_end));
3329                 }
3330
3331                 internal void SetSelection(Line start, int start_pos, Line end, int end_pos) {
3332                         if (selection_visible) {
3333                                 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3334                         }
3335
3336                         if ((end.line_no < start.line_no) || ((end == start) && (end_pos <= start_pos))) {
3337                                 selection_start.line = end;
3338                                 selection_start.tag = LineTag.FindTag(end, end_pos);
3339                                 selection_start.pos = end_pos;
3340
3341                                 selection_end.line = start;
3342                                 selection_end.tag = LineTag.FindTag(start, start_pos);
3343                                 selection_end.pos = start_pos;
3344
3345                                 selection_end_anchor = true;
3346                         } else {
3347                                 selection_start.line = start;
3348                                 selection_start.tag = LineTag.FindTag(start, start_pos);
3349                                 selection_start.pos = start_pos;
3350
3351                                 selection_end.line = end;
3352                                 selection_end.tag = LineTag.FindTag(end, end_pos);
3353                                 selection_end.pos = end_pos;
3354
3355                                 selection_end_anchor = false;
3356                         }
3357
3358                         selection_anchor.line = start;
3359                         selection_anchor.tag = selection_start.tag;
3360                         selection_anchor.pos = start_pos;
3361
3362                         if (((start == end) && (start_pos == end_pos)) || start == null || end == null) {
3363                                 SetSelectionVisible (false);
3364                         } else {
3365                                 SetSelectionVisible (true);
3366                                 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3367                         }
3368                 }
3369
3370                 internal void SetSelectionStart(Line start, int start_pos, bool invalidate) {
3371                         // Invalidate from the previous to the new start pos
3372                         if (invalidate)
3373                                 Invalidate(selection_start.line, selection_start.pos, start, start_pos);
3374
3375                         selection_start.line = start;
3376                         selection_start.pos = start_pos;
3377                         selection_start.tag = LineTag.FindTag(start, start_pos);
3378
3379                         selection_anchor.line = start;
3380                         selection_anchor.pos = start_pos;
3381                         selection_anchor.tag = selection_start.tag;
3382
3383                         selection_end_anchor = false;
3384
3385                         
3386                         if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
3387                                 SetSelectionVisible (true);
3388                         } else {
3389                                 SetSelectionVisible (false);
3390                         }
3391
3392                         if (invalidate)
3393                                 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3394                 }
3395
3396                 internal void SetSelectionStart(int character_index, bool invalidate) {
3397                         Line    line;
3398                         LineTag tag;
3399                         int     pos;
3400
3401                         if (character_index < 0) {
3402                                 return;
3403                         }
3404
3405                         CharIndexToLineTag(character_index, out line, out tag, out pos);
3406                         SetSelectionStart(line, pos, invalidate);
3407                 }
3408
3409                 internal void SetSelectionEnd(Line end, int end_pos, bool invalidate) {
3410
3411                         if (end == selection_end.line && end_pos == selection_start.pos) {
3412                                 selection_anchor.line = selection_start.line;
3413                                 selection_anchor.tag = selection_start.tag;
3414                                 selection_anchor.pos = selection_start.pos;
3415
3416                                 selection_end.line = selection_start.line;
3417                                 selection_end.tag = selection_start.tag;
3418                                 selection_end.pos = selection_start.pos;
3419
3420                                 selection_end_anchor = false;
3421                         } else if ((end.line_no < selection_anchor.line.line_no) || ((end == selection_anchor.line) && (end_pos <= selection_anchor.pos))) {
3422                                 selection_start.line = end;
3423                                 selection_start.tag = LineTag.FindTag(end, end_pos);
3424                                 selection_start.pos = end_pos;
3425
3426                                 selection_end.line = selection_anchor.line;
3427                                 selection_end.tag = selection_anchor.tag;
3428                                 selection_end.pos = selection_anchor.pos;
3429
3430                                 selection_end_anchor = true;
3431                         } else {
3432                                 selection_start.line = selection_anchor.line;
3433                                 selection_start.tag = selection_anchor.tag;
3434                                 selection_start.pos = selection_anchor.pos;
3435
3436                                 selection_end.line = end;
3437                                 selection_end.tag = LineTag.FindTag(end, end_pos);
3438                                 selection_end.pos = end_pos;
3439
3440                                 selection_end_anchor = false;
3441                         }
3442
3443                         if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
3444                                 SetSelectionVisible (true);
3445                                 if (invalidate)
3446                                         Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3447                         } else {
3448                                 SetSelectionVisible (false);
3449                                 // ?? Do I need to invalidate here, tests seem to work without it, but I don't think they should :-s
3450                         }
3451                 }
3452
3453                 internal void SetSelectionEnd(int character_index, bool invalidate) {
3454                         Line    line;
3455                         LineTag tag;
3456                         int     pos;
3457
3458                         if (character_index < 0) {
3459                                 return;
3460                         }
3461
3462                         CharIndexToLineTag(character_index, out line, out tag, out pos);
3463                         SetSelectionEnd(line, pos, invalidate);
3464                 }
3465
3466                 internal void SetSelection(Line start, int start_pos) {
3467                         if (selection_visible) {
3468                                 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3469                         }
3470
3471                         selection_start.line = start;
3472                         selection_start.pos = start_pos;
3473                         selection_start.tag = LineTag.FindTag(start, start_pos);
3474
3475                         selection_end.line = start;
3476                         selection_end.tag = selection_start.tag;
3477                         selection_end.pos = start_pos;
3478
3479                         selection_anchor.line = start;
3480                         selection_anchor.tag = selection_start.tag;
3481                         selection_anchor.pos = start_pos;
3482
3483                         selection_end_anchor = false;
3484                         SetSelectionVisible (false);
3485                 }
3486
3487                 internal void InvalidateSelectionArea() {
3488                         Invalidate (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3489                 }
3490
3491                 // Return the current selection, as string
3492                 internal string GetSelection() {
3493                         // We return String.Empty if there is no selection
3494                         if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3495                                 return string.Empty;
3496                         }
3497
3498                         if (selection_start.line == selection_end.line) {
3499                                 return selection_start.line.text.ToString (selection_start.pos, selection_end.pos - selection_start.pos);
3500                         } else {
3501                                 StringBuilder   sb;
3502                                 int             i;
3503                                 int             start;
3504                                 int             end;
3505
3506                                 sb = new StringBuilder();
3507                                 start = selection_start.line.line_no;
3508                                 end = selection_end.line.line_no;
3509
3510                                 sb.Append(selection_start.line.text.ToString(selection_start.pos, selection_start.line.text.Length - selection_start.pos) + Environment.NewLine);
3511
3512                                 if ((start + 1) < end) {
3513                                         for (i = start + 1; i < end; i++) {
3514                                                 sb.Append(GetLine(i).text.ToString() + Environment.NewLine);
3515                                         }
3516                                 }
3517
3518                                 sb.Append(selection_end.line.text.ToString(0, selection_end.pos));
3519
3520                                 return sb.ToString();
3521                         }
3522                 }
3523
3524                 internal void ReplaceSelection(string s, bool select_new) {
3525                         int             i;
3526
3527                         int selection_pos_on_line = selection_start.pos;
3528                         int selection_start_pos = LineTagToCharIndex (selection_start.line, selection_start.pos);
3529                         SuspendRecalc ();
3530
3531                         // First, delete any selected text
3532                         if ((selection_start.pos != selection_end.pos) || (selection_start.line != selection_end.line)) {
3533                                 if (selection_start.line == selection_end.line) {
3534                                         undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3535
3536                                         DeleteChars(selection_start.tag, selection_start.pos, selection_end.pos - selection_start.pos);
3537
3538                                         // The tag might have been removed, we need to recalc it
3539                                         selection_start.tag = selection_start.line.FindTag(selection_start.pos);
3540                                 } else {
3541                                         int             start;
3542                                         int             end;
3543
3544                                         start = selection_start.line.line_no;
3545                                         end = selection_end.line.line_no;
3546
3547                                         undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3548
3549                                         InvalidateSelectionArea ();
3550
3551                                         // Delete first line
3552                                         DeleteChars(selection_start.tag, selection_start.pos, selection_start.line.text.Length - selection_start.pos);
3553                                         selection_start.line.recalc = true;
3554
3555                                         // Delete last line
3556                                         DeleteChars(selection_end.line.tags, 0, selection_end.pos);
3557
3558                                         start++;
3559                                         if (start < end) {
3560                                                 for (i = end - 1; i >= start; i--) {
3561                                                         Delete(i);
3562                                                 }
3563                                         }
3564
3565                                         // BIG FAT WARNING - selection_end.line might be stale due 
3566                                         // to the above Delete() call. DONT USE IT before hitting the end of this method!
3567
3568                                         // Join start and end
3569                                         Combine(selection_start.line.line_no, start);
3570                                 }
3571                         }
3572
3573
3574                         Insert(selection_start.line, selection_start.pos, false, s);
3575                         undo.RecordInsertString (selection_start.line, selection_start.pos, s);
3576                         ResumeRecalc (false);
3577
3578                         if (!select_new) {
3579                                 CharIndexToLineTag(selection_start_pos + s.Length, out selection_start.line,
3580                                                 out selection_start.tag, out selection_start.pos);
3581
3582                                 selection_end.line = selection_start.line;
3583                                 selection_end.pos = selection_start.pos;
3584                                 selection_end.tag = selection_start.tag;
3585                                 selection_anchor.line = selection_start.line;
3586                                 selection_anchor.pos = selection_start.pos;
3587                                 selection_anchor.tag = selection_start.tag;
3588
3589                                 SetSelectionVisible (false);
3590                         } else {
3591                                 CharIndexToLineTag(selection_start_pos, out selection_start.line,
3592                                                 out selection_start.tag, out selection_start.pos);
3593
3594                                 CharIndexToLineTag(selection_start_pos + s.Length, out selection_end.line,
3595                                                 out selection_end.tag, out selection_end.pos);
3596
3597                                 selection_anchor.line = selection_start.line;
3598                                 selection_anchor.pos = selection_start.pos;
3599                                 selection_anchor.tag = selection_start.tag;
3600
3601                                 SetSelectionVisible (true);
3602                         }
3603
3604                         PositionCaret (selection_start.line, selection_start.pos);
3605                         UpdateView (selection_start.line, selection_pos_on_line);
3606                 }
3607
3608                 internal void CharIndexToLineTag(int index, out Line line_out, out LineTag tag_out, out int pos) {
3609                         Line    line;
3610                         LineTag tag;
3611                         int     i;
3612                         int     chars;
3613                         int     start;
3614
3615                         chars = 0;
3616
3617                         for (i = 1; i <= lines; i++) {
3618                                 line = GetLine(i);
3619
3620                                 start = chars;
3621                                 chars += line.text.Length;
3622
3623                                 if (index <= chars) {
3624                                         // we found the line
3625                                         tag = line.tags;
3626
3627                                         while (tag != null) {
3628                                                 if (index < (start + tag.start + tag.length - 1)) {
3629                                                         line_out = line;
3630                                                         tag_out = LineTag.GetFinalTag (tag);
3631                                                         pos = index - start;
3632                                                         return;
3633                                                 }
3634                                                 if (tag.next == null) {
3635                                                         Line    next_line;
3636
3637                                                         next_line = GetLine(line.line_no + 1);
3638
3639                                                         if (next_line != null) {
3640                                                                 line_out = next_line;
3641                                                                 tag_out = LineTag.GetFinalTag (next_line.tags);
3642                                                                 pos = 0;
3643                                                                 return;
3644                                                         } else {
3645                                                                 line_out = line;
3646                                                                 tag_out = LineTag.GetFinalTag (tag);
3647                                                                 pos = line_out.text.Length;
3648                                                                 return;
3649                                                         }
3650                                                 }
3651                                                 tag = tag.next;
3652                                         }
3653                                 }
3654                         }
3655
3656                         line_out = GetLine(lines);
3657                         tag = line_out.tags;
3658                         while (tag.next != null) {
3659                                 tag = tag.next;
3660                         }
3661                         tag_out = tag;
3662                         pos = line_out.text.Length;
3663                 }
3664
3665                 internal int LineTagToCharIndex(Line line, int pos) {
3666                         int     i;
3667                         int     length;
3668
3669                         // Count first and last line
3670                         length = 0;
3671
3672                         // Count the lines in the middle
3673
3674                         for (i = 1; i < line.line_no; i++) {
3675                                 length += GetLine(i).text.Length;
3676                         }
3677
3678                         length += pos;
3679
3680                         return length;
3681                 }
3682
3683                 internal int SelectionLength() {
3684                         if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3685                                 return 0;
3686                         }
3687
3688                         if (selection_start.line == selection_end.line) {
3689                                 return selection_end.pos - selection_start.pos;
3690                         } else {
3691                                 int     i;
3692                                 int     start;
3693                                 int     end;
3694                                 int     length;
3695
3696                                 // Count first and last line
3697                                 length = selection_start.line.text.Length - selection_start.pos + selection_end.pos + crlf_size;
3698
3699                                 // Count the lines in the middle
3700                                 start = selection_start.line.line_no + 1;
3701                                 end = selection_end.line.line_no;
3702
3703                                 if (start < end) {
3704                                         for (i = start; i < end; i++) {
3705                                                 Line line = GetLine (i);
3706                                                 length += line.text.Length + LineEndingLength (line.ending);
3707                                         }
3708                                 }
3709
3710                                 return length;
3711                         }
3712
3713                         
3714                 }
3715
3716
3717                 /// <summary>Give it a Line number and it returns the Line object at with that line number</summary>
3718                 internal Line GetLine(int LineNo) {
3719                         Line    line = document;
3720
3721                         while (line != sentinel) {
3722                                 if (LineNo == line.line_no) {
3723                                         return line;
3724                                 } else if (LineNo < line.line_no) {
3725                                         line = line.left;
3726                                 } else {
3727                                         line = line.right;
3728                                 }
3729                         }
3730
3731                         return null;
3732                 }
3733
3734                 /// <summary>Retrieve the previous tag; walks line boundaries</summary>
3735                 internal LineTag PreviousTag(LineTag tag) {
3736                         Line l; 
3737
3738                         if (tag.previous != null) {
3739                                 return tag.previous;
3740                         }
3741
3742                         // Next line 
3743                         if (tag.line.line_no == 1) {
3744                                 return null;
3745                         }
3746
3747                         l = GetLine(tag.line.line_no - 1);
3748                         if (l != null) {
3749                                 LineTag t;
3750
3751                                 t = l.tags;
3752                                 while (t.next != null) {
3753                                         t = t.next;
3754                                 }
3755                                 return t;
3756                         }
3757
3758                         return null;
3759                 }
3760
3761                 /// <summary>Retrieve the next tag; walks line boundaries</summary>
3762                 internal LineTag NextTag(LineTag tag) {
3763                         Line l;
3764
3765                         if (tag.next != null) {
3766                                 return tag.next;
3767                         }
3768
3769                         // Next line
3770                         l = GetLine(tag.line.line_no + 1);
3771                         if (l != null) {
3772                                 return l.tags;
3773                         }
3774
3775                         return null;
3776                 }
3777
3778                 internal Line ParagraphStart(Line line) {
3779                         while (line.ending == LineEnding.Wrap) {
3780                                 line = GetLine(line.line_no - 1);
3781                         }
3782                         return line;
3783                 }       
3784
3785                 internal Line ParagraphEnd(Line line) {
3786                         Line    l;
3787    
3788                         while (line.ending == LineEnding.Wrap) {
3789                                 l = GetLine(line.line_no + 1);
3790                                 if ((l == null) || (l.ending != LineEnding.Wrap)) {
3791                                         break;
3792                                 }
3793                                 line = l;
3794                         }
3795                         return line;
3796                 }
3797
3798                 /// <summary>Give it a pixel offset coordinate and it returns the Line covering that are (offset
3799                 /// is either X or Y depending on if we are multiline
3800                 /// </summary>
3801                 internal Line GetLineByPixel (int offset, bool exact)
3802                 {
3803                         Line    line = document;
3804                         Line    last = null;
3805
3806                         if (multiline) {
3807                                 while (line != sentinel) {
3808                                         last = line;
3809                                         if ((offset >= line.Y) && (offset < (line.Y+line.height))) {
3810                                                 return line;
3811                                         } else if (offset < line.Y) {
3812                                                 line = line.left;
3813                                         } else {
3814                                                 line = line.right;
3815                                         }
3816                                 }
3817                         } else {
3818                                 while (line != sentinel) {
3819                                         last = line;
3820                                         if ((offset >= line.X) && (offset < (line.X + line.Width)))
3821                                                 return line;
3822                                         else if (offset < line.X)
3823                                                 line = line.left;
3824                                         else
3825                                                 line = line.right;
3826                                 }
3827                         }
3828
3829                         if (exact) {
3830                                 return null;
3831                         }
3832                         return last;
3833                 }
3834
3835                 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3836                 internal LineTag FindTag(int x, int y, out int index, bool exact) {
3837                         Line    line;
3838                         LineTag tag;
3839
3840                         line = GetLineByPixel(y, exact);
3841                         if (line == null) {
3842                                 index = 0;
3843                                 return null;
3844                         }
3845                         tag = line.tags;
3846
3847                         // Alignment adjustment
3848                         x += line.X;
3849
3850                         while (true) {
3851                                 if (x >= tag.X && x < (tag.X+tag.width)) {
3852                                         int     end;
3853
3854                                         end = tag.start + tag.length - 1;
3855
3856                                         for (int pos = tag.start; pos < end; pos++) {
3857                                                 if (x < line.widths[pos]) {
3858                                                         index = pos;
3859                                                         return LineTag.GetFinalTag (tag);
3860                                                 }
3861                                         }
3862                                         index=end;
3863                                         return LineTag.GetFinalTag (tag);
3864                                 }
3865                                 if (tag.next != null) {
3866                                         tag = tag.next;
3867                                 } else {
3868                                         if (exact) {
3869                                                 index = 0;
3870                                                 return null;
3871                                         }
3872
3873                                         index = line.text.Length;
3874                                         return LineTag.GetFinalTag (tag);
3875                                 }
3876                         }
3877                 }
3878
3879                 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3880                 internal LineTag FindCursor(int x, int y, out int index) {
3881                         Line    line;
3882                         LineTag tag;
3883
3884                         line = GetLineByPixel(multiline ? y : x, false);
3885                         tag = line.tags;
3886
3887                         /// Special case going leftwards of the first tag
3888                         if (x < tag.X) {
3889                                 index = 0;
3890                                 return LineTag.GetFinalTag (tag);
3891                         }
3892
3893                         while (true) {
3894                                 if (x >= tag.X && x < (tag.X+tag.width)) {
3895                                         int     end;
3896
3897                                         end = tag.TextEnd;
3898
3899                                         for (int pos = tag.start - 1; pos < end; pos++) {
3900                                                 // When clicking on a character, we position the cursor to whatever edge
3901                                                 // of the character the click was closer
3902                                                 if (x < (line.X + line.widths[pos] + ((line.widths[pos+1]-line.widths[pos])/2))) {
3903                                                         index = pos;
3904                                                         return LineTag.GetFinalTag (tag);
3905                                                 }
3906                                         }
3907                                         index=end;
3908                                         return LineTag.GetFinalTag (tag);
3909                                 }
3910                                 if (tag.next != null) {
3911                                         tag = tag.next;
3912                                 } else {
3913                                         index = line.TextLengthWithoutEnding ();
3914                                         return LineTag.GetFinalTag (tag);
3915                                 }
3916                         }
3917                 }
3918
3919                 /// <summary>Format area of document in specified font and color</summary>
3920                 /// <param name="start_pos">1-based start position on start_line</param>
3921                 /// <param name="end_pos">1-based end position on end_line </param>
3922                 internal void FormatText (Line start_line, int start_pos, Line end_line, int end_pos, Font font,
3923                                 SolidBrush color, SolidBrush back_color, FormatSpecified specified)
3924                 {
3925                         Line    l;
3926
3927                         // First, format the first line
3928                         if (start_line != end_line) {
3929                                 // First line
3930                                 LineTag.FormatText(start_line, start_pos, start_line.text.Length - start_pos + 1, font, color, back_color, specified);
3931
3932                                 // Format last line
3933                                 LineTag.FormatText(end_line, 1, end_pos, font, color, back_color, specified);
3934
3935                                 // Now all the lines inbetween
3936                                 for (int i = start_line.line_no + 1; i < end_line.line_no; i++) {
3937                                         l = GetLine(i);
3938                                         LineTag.FormatText(l, 1, l.text.Length, font, color, back_color, specified);
3939                                 }
3940                         } else {
3941                                 // Special case, single line
3942                                 LineTag.FormatText(start_line, start_pos, end_pos - start_pos, font, color, back_color, specified);
3943                         }
3944                 }
3945
3946                 /// <summary>Re-format areas of the document in specified font and color</summary>
3947                 /// <param name="start_pos">1-based start position on start_line</param>
3948                 /// <param name="end_pos">1-based end position on end_line </param>
3949                 /// <param name="font">Font specifying attributes</param>
3950                 /// <param name="color">Color (or NULL) to apply</param>
3951                 /// <param name="apply">Attributes from font and color to apply</param>
3952                 internal void FormatText(Line start_line, int start_pos, Line end_line, int end_pos, FontDefinition attributes) {
3953                         Line    l;
3954
3955                         // First, format the first line
3956                         if (start_line != end_line) {
3957                                 // First line
3958                                 LineTag.FormatText(start_line, start_pos, start_line.text.Length - start_pos + 1, attributes);
3959
3960                                 // Format last line
3961                                 LineTag.FormatText(end_line, 1, end_pos - 1, attributes);
3962
3963                                 // Now all the lines inbetween
3964                                 for (int i = start_line.line_no + 1; i < end_line.line_no; i++) {
3965                                         l = GetLine(i);
3966                                         LineTag.FormatText(l, 1, l.text.Length, attributes);
3967                                 }
3968                         } else {
3969                                 // Special case, single line
3970                                 LineTag.FormatText(start_line, start_pos, end_pos - start_pos, attributes);
3971                         }
3972                 }
3973
3974                 internal void RecalculateAlignments ()
3975                 {
3976                         Line    line;
3977                         int     line_no;
3978
3979                         line_no = 1;
3980
3981
3982
3983                         while (line_no <= lines) {
3984                                 line = GetLine(line_no);
3985
3986                                 if (line != null) {
3987                                         switch (line.alignment) {
3988                                         case HorizontalAlignment.Left:
3989                                                 line.align_shift = 0;
3990                                                 break;
3991                                         case HorizontalAlignment.Center:
3992                                                 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3993                                                 break;
3994                                         case HorizontalAlignment.Right:
3995                                                 line.align_shift = viewport_width - (int)line.widths[line.text.Length] - right_margin;
3996                                                 break;
3997                                         }
3998                                 }
3999
4000                                 line_no++;
4001                         }
4002                         return;
4003                 }
4004
4005                 /// <summary>Calculate formatting for the whole document</summary>
4006                 internal bool RecalculateDocument(Graphics g) {
4007                         return RecalculateDocument(g, 1, this.lines, false);
4008                 }
4009
4010                 /// <summary>Calculate formatting starting at a certain line</summary>
4011                 internal bool RecalculateDocument(Graphics g, int start) {
4012                         return RecalculateDocument(g, start, this.lines, false);
4013                 }
4014
4015                 /// <summary>Calculate formatting within two given line numbers</summary>
4016                 internal bool RecalculateDocument(Graphics g, int start, int end) {
4017                         return RecalculateDocument(g, start, end, false);
4018                 }
4019
4020                 /// <summary>With optimize on, returns true if line heights changed</summary>
4021                 internal bool RecalculateDocument(Graphics g, int start, int end, bool optimize) {
4022                         Line    line;
4023                         int     line_no;
4024                         int     offset;
4025                         int     new_width;
4026                         bool    changed;
4027                         int     shift;
4028
4029                         if (recalc_suspended > 0) {
4030                                 recalc_pending = true;
4031                                 recalc_start = Math.Min (recalc_start, start);
4032                                 recalc_end = Math.Max (recalc_end, end);
4033                                 recalc_optimize = optimize;
4034                                 return false;
4035                         }
4036
4037                         // Fixup the positions, they can go kinda nuts
4038                         start = Math.Max (start, 1);
4039                         end = Math.Min (end, lines);
4040
4041                         offset = GetLine(start).offset;
4042                         line_no = start;
4043                         new_width = 0;
4044                         shift = this.lines;
4045                         if (!optimize) {
4046                                 changed = true;         // We always return true if we run non-optimized
4047                         } else {
4048                                 changed = false;
4049                         }
4050
4051                         while (line_no <= (end + this.lines - shift)) {
4052                                 line = GetLine(line_no++);
4053                                 line.offset = offset;
4054
4055                                 if (!calc_pass) {
4056                                         if (!optimize) {
4057                                                 line.RecalculateLine(g, this);
4058                                         } else {
4059                                                 if (line.recalc && line.RecalculateLine(g, this)) {
4060                                                         changed = true;
4061                                                         // If the height changed, all subsequent lines change
4062                                                         end = this.lines;
4063                                                         shift = this.lines;
4064                                                 }
4065                                         }
4066                                 } else {
4067                                         if (!optimize) {
4068                                                 line.RecalculatePasswordLine(g, this);
4069                                         } else {
4070                                                 if (line.recalc && line.RecalculatePasswordLine(g, this)) {
4071                                                         changed = true;
4072                                                         // If the height changed, all subsequent lines change
4073                                                         end = this.lines;
4074                                                         shift = this.lines;
4075                                                 }
4076                                         }
4077                                 }
4078
4079                                 if (line.widths[line.text.Length] > new_width) {
4080                                         new_width = (int)line.widths[line.text.Length];
4081                                 }
4082
4083                                 // Calculate alignment
4084                                 if (line.alignment != HorizontalAlignment.Left) {
4085                                         if (line.alignment == HorizontalAlignment.Center) {
4086                                                 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
4087                                         } else {
4088                                                 line.align_shift = viewport_width - (int)line.widths[line.text.Length] - 1;
4089                                         }
4090                                 }
4091
4092                                 if (multiline)
4093                                         offset += line.height;
4094                                 else
4095                                         offset += (int) line.widths [line.text.Length];
4096
4097                                 if (line_no > lines) {
4098                                         break;
4099                                 }
4100                         }
4101
4102                         if (document_x != new_width) {
4103                                 document_x = new_width;
4104                                 if (WidthChanged != null) {
4105                                         WidthChanged(this, null);
4106                                 }
4107                         }
4108
4109                         RecalculateAlignments();
4110
4111                         line = GetLine(lines);
4112
4113                         if (document_y != line.Y + line.height) {
4114                                 document_y = line.Y + line.height;
4115                                 if (HeightChanged != null) {
4116                                         HeightChanged(this, null);
4117                                 }
4118                         }
4119                         UpdateCaret();
4120                         return changed;
4121                 }
4122
4123                 internal int Size() {
4124                         return lines;
4125                 }
4126
4127                 private void owner_HandleCreated(object sender, EventArgs e) {
4128                         RecalculateDocument(owner.CreateGraphicsInternal());
4129                         AlignCaret();
4130                 }
4131
4132                 private void owner_VisibleChanged(object sender, EventArgs e) {
4133                         if (owner.Visible) {
4134                                 RecalculateDocument(owner.CreateGraphicsInternal());
4135                         }
4136                 }
4137
4138                 internal static bool IsWordSeparator (char ch)
4139                 {
4140                         switch (ch) {
4141                         case ' ':
4142                         case '\t':
4143                         case '(':
4144                         case ')':
4145                         case '\r':
4146                         case '\n':
4147                                 return true;
4148                         default:
4149                                 return false;
4150                         }
4151                 }
4152
4153                 internal int FindWordSeparator(Line line, int pos, bool forward) {
4154                         int len;
4155
4156                         len = line.text.Length;
4157
4158                         if (forward) {
4159                                 for (int i = pos + 1; i < len; i++) {
4160                                         if (IsWordSeparator(line.Text[i])) {
4161                                                 return i + 1;
4162                                         }
4163                                 }
4164                                 return len;
4165                         } else {
4166                                 for (int i = pos - 1; i > 0; i--) {
4167                                         if (IsWordSeparator(line.Text[i - 1])) {
4168                                                 return i;
4169                                         }
4170                                 }
4171                                 return 0;
4172                         }
4173                 }
4174
4175                 /* Search document for text */
4176                 internal bool FindChars(char[] chars, Marker start, Marker end, out Marker result) {
4177                         Line    line;
4178                         int     line_no;
4179                         int     pos;
4180                         int     line_len;
4181
4182                         // Search for occurence of any char in the chars array
4183                         result = new Marker();
4184
4185                         line = start.line;
4186                         line_no = start.line.line_no;
4187                         pos = start.pos;
4188                         while (line_no <= end.line.line_no) {
4189                                 line_len = line.text.Length;
4190                                 while (pos < line_len) {
4191                                         for (int i = 0; i < chars.Length; i++) {
4192                                                 if (line.text[pos] == chars[i]) {
4193                                                         // Special case
4194                                                         if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
4195                                                                 return false;
4196                                                         }
4197
4198                                                         result.line = line;
4199                                                         result.pos = pos;
4200                                                         return true;
4201                                                 }
4202                                         }
4203                                         pos++;
4204                                 }
4205
4206                                 pos = 0;
4207                                 line_no++;
4208                                 line = GetLine(line_no);
4209                         }
4210
4211                         return false;
4212                 }
4213
4214                 // This version does not build one big string for searching, instead it handles 
4215                 // line-boundaries, which is faster and less memory intensive
4216                 // FIXME - Depending on culture stuff we might have to create a big string and use culturespecific 
4217                 // search stuff and change it to accept and return positions instead of Markers (which would match 
4218                 // RichTextBox behaviour better but would be inconsistent with the rest of TextControl)
4219                 internal bool Find(string search, Marker start, Marker end, out Marker result, RichTextBoxFinds options) {
4220                         Marker  last;
4221                         string  search_string;
4222                         Line    line;
4223                         int     line_no;
4224                         int     pos;
4225                         int     line_len;
4226                         int     current;
4227                         bool    word;
4228                         bool    word_option;
4229                         bool    ignore_case;
4230                         bool    reverse;
4231                         char    c;
4232
4233                         result = new Marker();
4234                         word_option = ((options & RichTextBoxFinds.WholeWord) != 0);
4235                         ignore_case = ((options & RichTextBoxFinds.MatchCase) == 0);
4236                         reverse = ((options & RichTextBoxFinds.Reverse) != 0);
4237
4238                         line = start.line;
4239                         line_no = start.line.line_no;
4240                         pos = start.pos;
4241                         current = 0;
4242
4243                         // Prep our search string, lowercasing it if we do case-independent matching
4244                         if (ignore_case) {
4245                                 StringBuilder   sb;
4246                                 sb = new StringBuilder(search);
4247                                 for (int i = 0; i < sb.Length; i++) {
4248                                         sb[i] = Char.ToLower(sb[i]);
4249                                 }
4250                                 search_string = sb.ToString();
4251                         } else {
4252                                 search_string = search;
4253                         }
4254
4255                         // We need to check if the character before our start position is a wordbreak
4256                         if (word_option) {
4257                                 if (line_no == 1) {
4258                                         if ((pos == 0) || (IsWordSeparator(line.text[pos - 1]))) {
4259                                                 word = true;
4260                                         } else {
4261                                                 word = false;
4262                                         }
4263                                 } else {
4264                                         if (pos > 0) {
4265                                                 if (IsWordSeparator(line.text[pos - 1])) {
4266                                                         word = true;
4267                                                 } else {
4268                                                         word = false;
4269                                                 }
4270                                         } else {
4271                                                 // Need to check the end of the previous line
4272                                                 Line    prev_line;
4273
4274                                                 prev_line = GetLine(line_no - 1);
4275                                                 if (prev_line.ending == LineEnding.Wrap) {
4276                                                         if (IsWordSeparator(prev_line.text[prev_line.text.Length - 1])) {
4277                                                                 word = true;
4278                                                         } else {
4279                                                                 word = false;
4280                                                         }
4281                                                 } else {
4282                                                         word = true;
4283                                                 }
4284                                         }
4285                                 }
4286                         } else {
4287                                 word = false;
4288                         }
4289
4290                         // To avoid duplication of this loop with reverse logic, we search
4291                         // through the document, remembering the last match and when returning
4292                         // report that last remembered match
4293
4294                         last = new Marker();
4295                         last.height = -1;       // Abused - we use it to track change
4296
4297                         while (line_no <= end.line.line_no) {
4298                                 if (line_no != end.line.line_no) {
4299                                         line_len = line.text.Length;
4300                                 } else {
4301                                         line_len = end.pos;
4302                                 }
4303
4304                                 while (pos < line_len) {
4305
4306                                         if (word_option && (current == search_string.Length)) {
4307                                                 if (IsWordSeparator(line.text[pos])) {
4308                                                         if (!reverse) {
4309                                                                 goto FindFound;
4310                                                         } else {
4311                                                                 last = result;
4312                                                                 current = 0;
4313                                                         }
4314                                                 } else {
4315                                                         current = 0;
4316                                                 }
4317                                         }
4318
4319                                         if (ignore_case) {
4320                                                 c = Char.ToLower(line.text[pos]);
4321                                         } else {
4322                                                 c = line.text[pos];
4323                                         }
4324
4325                                         if (c == search_string[current]) {
4326                                                 
4327                                                 if (current == 0) {
4328                                                         result.line = line;
4329                                                         result.pos = pos;
4330                                                 }
4331                                                 if (!word_option || (word_option && (word || (current > 0)))) {
4332                                                         current++;
4333                                                 }
4334
4335                                                 if (!word_option && (current == search_string.Length)) {
4336                                                         if (!reverse) {
4337                                                                 goto FindFound;
4338                                                         } else {
4339                                                                 last = result;
4340                                                                 current = 0;
4341                                                         }
4342                                                 }
4343                                         } else {
4344                                                 current = 0;
4345                                         }
4346                                         pos++;
4347
4348                                         if (!word_option) {
4349                                                 continue;
4350                                         }
4351
4352                                         if (IsWordSeparator(c)) {
4353                                                 word = true;
4354                                         } else {
4355                                                 word = false;
4356                                         }
4357                                 }
4358
4359                                 if (word_option) {
4360                                         // Mark that we just saw a word boundary
4361                                         if (line.ending != LineEnding.Wrap || line.line_no == lines - 1) {
4362                                                 word = true;
4363                                         }
4364
4365                                         if (current == search_string.Length) {
4366                                                 if (word) {
4367                                                         if (!reverse) {
4368                                                                 goto FindFound;
4369                                                         } else {
4370                                                                 last = result;
4371                                                                 current = 0;
4372                                                         }
4373                                                 } else {
4374                                                         current = 0;
4375                                                 }
4376                                         }
4377                                 }
4378
4379                                 pos = 0;
4380                                 line_no++;
4381                                 line = GetLine(line_no);
4382                         }
4383
4384                         if (reverse) {
4385                                 if (last.height != -1) {
4386                                         result = last;
4387                                         return true;
4388                                 }
4389                         }
4390
4391                         return false;
4392
4393                         FindFound:
4394                         if (!reverse) {
4395 //                              if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
4396 //                                      return false;
4397 //                              }
4398                                 return true;
4399                         }
4400
4401                         result = last;
4402                         return true;
4403
4404                 }
4405
4406                 /* Marker stuff */
4407                 internal void GetMarker(out Marker mark, bool start) {
4408                         mark = new Marker();
4409
4410                         if (start) {
4411                                 mark.line = GetLine(1);
4412                                 mark.tag = mark.line.tags;
4413                                 mark.pos = 0;
4414                         } else {
4415                                 mark.line = GetLine(lines);
4416                                 mark.tag = mark.line.tags;
4417                                 while (mark.tag.next != null) {
4418                                         mark.tag = mark.tag.next;
4419                                 }
4420                                 mark.pos = mark.line.text.Length;
4421                         }
4422                 }
4423                 #endregion      // Internal Methods
4424
4425                 #region Events
4426                 internal event EventHandler CaretMoved;
4427                 internal event EventHandler WidthChanged;
4428                 internal event EventHandler HeightChanged;
4429                 internal event EventHandler LengthChanged;
4430                 #endregion      // Events
4431
4432                 #region Administrative
4433                 public IEnumerator GetEnumerator() {
4434                         // FIXME
4435                         return null;
4436                 }
4437
4438                 public override bool Equals(object obj) {
4439                         if (obj == null) {
4440                                 return false;
4441                         }
4442
4443                         if (!(obj is Document)) {
4444                                 return false;
4445                         }
4446
4447                         if (obj == this) {
4448                                 return true;
4449                         }
4450
4451                         if (ToString().Equals(((Document)obj).ToString())) {
4452                                 return true;
4453                         }
4454
4455                         return false;
4456                 }
4457
4458                 public override int GetHashCode() {
4459                         return document_id;
4460                 }
4461
4462                 public override string ToString() {
4463                         return "document " + this.document_id;
4464                 }
4465                 #endregion      // Administrative
4466         }
4467
4468         internal class PictureTag : LineTag {
4469
4470                 internal RTF.Picture picture;
4471
4472                 internal PictureTag (Line line, int start, RTF.Picture picture) : base (line, start)
4473                 {
4474                         this.picture = picture;
4475                 }
4476
4477                 public override bool IsTextTag {
4478                         get { return false; }
4479                 }
4480
4481                 internal override SizeF SizeOfPosition (Graphics dc, int pos)
4482                 {
4483                         return picture.Size;
4484                 }
4485
4486                 internal override int MaxHeight ()
4487                 {
4488                         return (int) (picture.Height + 0.5F);
4489                 }
4490
4491                 internal override void Draw (Graphics dc, Brush brush, float xoff, float y, int start, int end)
4492                 {
4493                         picture.DrawImage (dc, xoff + line.widths [start], y, false);
4494                 }
4495
4496                 internal override void Draw (Graphics dc, Brush brush, float xoff, float y, int start, int end, string text)
4497                 {
4498                         picture.DrawImage (dc, xoff + + line.widths [start], y, false);
4499                 }
4500
4501                 public override string Text ()
4502                 {
4503                         return "I";
4504                 }
4505         }
4506
4507         internal class LineTag {
4508                 #region Local Variables;
4509                 // Payload; formatting
4510                 internal Font           font;           // System.Drawing.Font object for this tag
4511                 internal SolidBrush     color;          // The font color for this tag
4512
4513                 // In 2.0 tags can have background colours.  I'm not going to #ifdef
4514                 // at this level though since I want to reduce code paths
4515                 internal SolidBrush back_color;  
4516
4517                 // Payload; text
4518                 internal int            start;          // start, in chars; index into Line.text
4519                 internal bool           r_to_l;         // Which way is the font
4520
4521                 // Drawing support
4522                 internal int            height;         // Height in pixels of the text this tag describes
4523
4524                 internal int            ascent;         // Ascent of the font for this tag
4525                 internal int            shift;          // Shift down for this tag, to stay on baseline
4526
4527                 // Administrative
4528                 internal Line           line;           // The line we're on
4529                 internal LineTag        next;           // Next tag on the same line
4530                 internal LineTag        previous;       // Previous tag on the same line
4531                 #endregion;
4532
4533                 #region Constructors
4534                 internal LineTag(Line line, int start) {
4535                         this.line = line;
4536                         this.start = start;
4537                 }
4538                 #endregion      // Constructors
4539
4540                 #region Internal Methods
4541
4542                 public float X {
4543                         get {
4544                                 if (start == 0)
4545                                         return line.X;
4546                                 return line.X + line.widths [start - 1];
4547                         }
4548                 }
4549
4550                 public int end {
4551                         get { return start + length; }
4552                 }
4553
4554                 public int TextEnd {
4555                         get { return start + TextLength; }
4556                 }
4557
4558                 public float width {
4559                         get {
4560                                 if (length == 0)
4561                                         return 0;
4562                                 return line.widths [start + length - 1] - (start != 0 ? line.widths [start - 1] : 0);
4563                         }
4564                 }
4565
4566                 public int length {
4567                         get {
4568                                 int res = 0;
4569                                 if (next != null)
4570                                         res = next.start - start;
4571                                 else
4572                                         res = line.text.Length - (start - 1);
4573
4574                                 return res > 0 ? res : 0;
4575                         }
4576                 }
4577
4578                 public int TextLength {
4579                         get {
4580                                 int res = 0;
4581                                 if (next != null)
4582                                         res = next.start - start;
4583                                 else
4584                                         res = line.TextLengthWithoutEnding () - (start - 1);
4585
4586                                 return res > 0 ? res : 0;
4587                         }
4588                 }
4589
4590                 public virtual bool IsTextTag {
4591                         get { return true; }
4592                 }
4593
4594                 internal virtual SizeF SizeOfPosition (Graphics dc, int pos)
4595                 {
4596                         if (pos >= line.TextLengthWithoutEnding () && line.document.multiline)
4597                                 return SizeF.Empty;
4598
4599                         string text = line.text.ToString (pos, 1);
4600                         switch ((int) text [0]) {
4601                         case '\t':
4602                                 if (!line.document.multiline)
4603                                         goto case 10;
4604                                 SizeF res = dc.MeasureString (" ", font, 10000, Document.string_format);
4605                                 res.Width *= 8.0F;
4606                                 return res;
4607                         case 10:
4608                         case 13:
4609                                 return dc.MeasureString ("\u0013", font, 10000, Document.string_format);
4610                         }
4611                         
4612                         return dc.MeasureString (text, font, 10000, Document.string_format);
4613                 }
4614
4615                 internal virtual int MaxHeight ()
4616                 {
4617                         return font.Height;
4618                 }
4619
4620                 internal virtual void Draw (Graphics dc, Brush brush, float x, float y, int start, int end)
4621                 {
4622                         dc.DrawString (line.text.ToString (start, end), font, brush, x, y, StringFormat.GenericTypographic);
4623                 }
4624
4625                 internal virtual void Draw (Graphics dc, Brush brush, float xoff, float y, int start, int end, string text)
4626                 {
4627                         while (start < end) {
4628                                 int tab_index = text.IndexOf ("\t", start);
4629                                 if (tab_index == -1)
4630                                         tab_index = end;
4631                                 dc.DrawString (text.Substring (start, tab_index - start), font, brush, xoff + line.widths [start],
4632                                                 y, StringFormat.GenericTypographic);
4633
4634                                 // non multilines get the unknown char 
4635                                 if (!line.document.multiline && tab_index != end)
4636                                         dc.DrawString ("\u0013", font, brush,  xoff + line.widths [tab_index], y, Document.string_format);
4637                                         
4638                                 start = tab_index + 1;
4639                         }
4640                 }
4641
4642                 ///<summary>Break a tag into two with identical attributes; pos is 1-based; returns tag starting at &gt;pos&lt; or null if end-of-line</summary>
4643                 internal LineTag Break(int pos) {
4644
4645                         LineTag new_tag;
4646
4647                         // Sanity
4648                         if (pos == this.start) {
4649                                 return this;
4650                         } else if (pos >= (start + length)) {
4651                                 return null;
4652                         }
4653
4654                         new_tag = new LineTag(line, pos);
4655                         new_tag.CopyFormattingFrom (this);
4656
4657                         new_tag.next = this.next;
4658                         this.next = new_tag;
4659                         new_tag.previous = this;
4660
4661                         if (new_tag.next != null) {
4662                                 new_tag.next.previous = new_tag;
4663                         }
4664
4665                         return new_tag;
4666                 }
4667
4668                 public virtual string Text ()
4669                 {
4670                         return line.text.ToString (start - 1, length);
4671                 }
4672
4673                 public void CopyFormattingFrom (LineTag other)
4674                 {
4675                         height = other.height;
4676                         font = other.font;
4677                         color = other.color;
4678                         back_color = other.back_color;
4679                 }
4680
4681                 ///<summary>Create new font and brush from existing font and given new attributes. Returns true if fontheight changes</summary>
4682                 internal static bool GenerateTextFormat(Font font_from, SolidBrush color_from, FontDefinition attributes, out Font new_font, out SolidBrush new_color) {
4683                         float           size;
4684                         string          face;
4685                         FontStyle       style;
4686                         GraphicsUnit    unit;
4687
4688                         if (attributes.font_obj == null) {
4689                                 size = font_from.SizeInPoints;
4690                                 unit = font_from.Unit;
4691                                 face = font_from.Name;
4692                                 style = font_from.Style;
4693
4694                                 if (attributes.face != null) {
4695                                         face = attributes.face;
4696                                 }
4697                                 
4698                                 if (attributes.size != 0) {
4699                                         size = attributes.size;
4700                                 }
4701
4702                                 style |= attributes.add_style;
4703                                 style &= ~attributes.remove_style;
4704
4705                                 // Create new font
4706                                 new_font = new Font(face, size, style, unit);
4707                         } else {
4708                                 new_font = attributes.font_obj;
4709                         }
4710
4711                         // Create 'new' color brush
4712                         if (attributes.color != Color.Empty) {
4713                                 new_color = new SolidBrush(attributes.color);
4714                         } else {
4715                                 new_color = color_from;
4716                         }
4717
4718                         if (new_font.Height == font_from.Height) {
4719                                 return false;
4720                         }
4721                         return true;
4722                 }
4723
4724                 /// <summary>Applies 'font' and 'brush' to characters starting at 'start' for 'length' chars; 
4725                 /// Removes any previous tags overlapping the same area; 
4726                 /// returns true if lineheight has changed</summary>
4727                 /// <param name="start">1-based character position on line</param>
4728                 internal static bool FormatText(Line line, int start, int length, Font font, SolidBrush color, SolidBrush back_color, FormatSpecified specified)
4729                 {
4730                         LineTag tag;
4731                         LineTag start_tag;
4732                         LineTag end_tag;
4733                         int     end;
4734                         bool    retval = false;         // Assume line-height doesn't change
4735
4736                         // Too simple?
4737                         if (((FormatSpecified.Font & specified) == FormatSpecified.Font) && font.Height != line.height) {
4738                                 retval = true;
4739                         }
4740                         line.recalc = true;             // This forces recalculation of the line in RecalculateDocument
4741
4742                         // A little sanity, not sure if it's needed, might be able to remove for speed
4743                         if (length > line.text.Length) {
4744                                 length = line.text.Length;
4745                         }
4746
4747                         tag = line.tags;
4748                         end = start + length;
4749
4750                         // Common special case
4751                         if ((start == 1) && (length == tag.length)) {
4752                                 tag.ascent = 0;
4753                                 SetFormat (tag, font, color, back_color, specified);
4754                                 return retval;
4755                         }
4756
4757                         start_tag = FindTag (line, start);
4758                         tag = start_tag.Break (start);
4759
4760                         while (tag != null && tag.end <= end) {
4761                                 SetFormat (tag, font, color, back_color, specified);
4762                                 tag = tag.next;
4763                         }
4764
4765                         if (tag != null && tag.end == end)
4766                                 return retval;
4767
4768                         /// Now do the last tag
4769                         end_tag = FindTag (line, end);
4770
4771                         if (end_tag != null) {
4772                                 end_tag.Break (end);
4773                                 SetFormat (end_tag, font, color, back_color, specified);
4774                         }
4775
4776                         return retval;
4777                 }
4778
4779                 private static void SetFormat (LineTag tag, Font font, SolidBrush color, SolidBrush back_color, FormatSpecified specified)
4780                 {
4781                         if ((FormatSpecified.Font & specified) == FormatSpecified.Font)
4782                                 tag.font = font;
4783                         if ((FormatSpecified.Color & specified) == FormatSpecified.Color)
4784                                 tag.color = color;
4785                         if ((FormatSpecified.BackColor & specified) == FormatSpecified.BackColor) {
4786                                 tag.back_color = back_color;
4787                         }
4788                         // Console.WriteLine ("setting format:   {0}  {1}   new color {2}", color.Color, specified, tag.color.Color);
4789                 }
4790
4791                 /// <summary>Applies font attributes specified to characters starting at 'start' for 'length' chars; 
4792                 /// Breaks tags at start and end point, keeping middle tags with altered attributes.
4793                 /// Returns true if lineheight has changed</summary>
4794                 /// <param name="start">1-based character position on line</param>
4795                 internal static bool FormatText(Line line, int start, int length, FontDefinition attributes) {
4796                         LineTag tag;
4797                         LineTag start_tag;
4798                         LineTag end_tag;
4799                         bool    retval = false;         // Assume line-height doesn't change
4800
4801                         line.recalc = true;             // This forces recalculation of the line in RecalculateDocument
4802
4803                         // A little sanity, not sure if it's needed, might be able to remove for speed
4804                         if (length > line.text.Length) {
4805                                 length = line.text.Length;
4806                         }
4807
4808                         tag = line.tags;
4809
4810                         // Common special case
4811                         if ((start == 1) && (length == tag.length)) {
4812                                 tag.ascent = 0;
4813                                 GenerateTextFormat(tag.font, tag.color, attributes, out tag.font, out tag.color);
4814                                 return retval;
4815                         }
4816
4817                         start_tag = FindTag(line, start);
4818                         
4819                         if (start_tag == null) {
4820                                 if (length == 0) {
4821                                         // We are 'starting' after all valid tags; create a new tag with the right attributes
4822                                         start_tag = FindTag(line, line.TextLengthWithoutEnding () - 1);
4823                                         start_tag.next = new LineTag(line, line.TextLengthWithoutEnding () + 1);
4824                                         start_tag.next.CopyFormattingFrom (start_tag);
4825                                         start_tag.next.previous = start_tag;
4826                                         start_tag = start_tag.next;
4827                                 } else {
4828                                         throw new Exception(String.Format("Could not find start_tag in document at line {0} position {1}", line.line_no, start));
4829                                 }
4830                         } else {
4831                                 start_tag = start_tag.Break(start);
4832                         }
4833
4834                         end_tag = FindTag(line, start + length);
4835                         if (end_tag != null) {
4836                                 end_tag = end_tag.Break(start + length);
4837                         }
4838
4839                         // start_tag or end_tag might be null; we're cool with that
4840                         // we now walk from start_tag to end_tag, applying new attributes
4841                         tag = start_tag;
4842                         while ((tag != null) && tag != end_tag) {
4843                                 if (LineTag.GenerateTextFormat(tag.font, tag.color, attributes, out tag.font, out tag.color)) {
4844                                         retval = true;
4845                                 }
4846                                 tag = tag.next;
4847                         }
4848                         return retval;
4849                 }
4850
4851
4852                 /// <summary>Finds the tag that describes the character at position 'pos' on 'line'</summary>
4853                 internal static LineTag FindTag(Line line, int pos) {
4854                         LineTag tag = line.tags;
4855
4856                         // Beginning of line is a bit special
4857                         if (pos == 0) {
4858                                 // Not sure if we should get the final tag here
4859                                 return tag;
4860                         }
4861
4862                         while (tag != null) {
4863                                 if ((tag.start <= pos) && (pos <= tag.end)) {
4864                                         return GetFinalTag (tag);
4865                                 }
4866
4867                                 tag = tag.next;
4868                         }
4869
4870                         return null;
4871                 }
4872
4873                 // There can be multiple tags at the same position, we want to make
4874                 // sure we are using the very last tag at the given position
4875                 internal static LineTag GetFinalTag (LineTag tag)
4876                 {
4877                         LineTag res = tag;
4878
4879                         while (res.length == 0 && res.next != null && res.next.length == 0)
4880                                 res = res.next;
4881
4882                         return res;
4883                 }
4884
4885                 /// <summary>Combines 'this' tag with 'other' tag</summary>
4886                 internal bool Combine(LineTag other) {
4887                         if (!this.Equals(other)) {
4888                                 return false;
4889                         }
4890
4891                         this.next = other.next;
4892                         if (this.next != null) {
4893                                 this.next.previous = this;
4894                         }
4895
4896                         return true;
4897                 }
4898
4899
4900                 /// <summary>Remove 'this' tag ; to be called when formatting is to be removed</summary>
4901                 internal bool Remove() {
4902                         if ((this.start == 1) && (this.next == null)) {
4903                                 // We cannot remove the only tag
4904                                 return false;
4905                         }
4906                         if (this.start != 1) {
4907                                 this.previous.next = this.next;
4908                                 this.next.previous = this.previous;
4909                         } else {
4910                                 this.next.start = 1;
4911                                 this.line.tags = this.next;
4912                                 this.next.previous = null;
4913                         }
4914                         return true;
4915                 }
4916
4917
4918                 /// <summary>Checks if 'this' tag describes the same formatting options as 'obj'</summary>
4919                 public override bool Equals(object obj) {
4920                         LineTag other;
4921
4922                         if (obj == null) {
4923                                 return false;
4924                         }
4925
4926                         if (!(obj is LineTag)) {
4927                                 return false;
4928                         }
4929
4930                         if (obj == this) {
4931                                 return true;
4932                         }
4933
4934                         other = (LineTag)obj;
4935
4936                         if (other.IsTextTag != IsTextTag)
4937                                 return false;
4938
4939                         if (this.font.Equals(other.font) && this.color.Equals(other.color)) {   // FIXME add checking for things like link or type later
4940                                 return true;
4941                         }
4942
4943                         return false;
4944                 }
4945
4946                 public override int GetHashCode() {
4947                         return base.GetHashCode ();
4948                 }
4949
4950                 public override string ToString() {
4951                         if (length > 0)
4952                                 return GetType () + " Tag starts at index " + this.start + " length " + this.length + " text: " + Text () + "Font " + this.font.ToString();
4953                         return "Zero Lengthed tag at index " + this.start;
4954                 }
4955
4956                 #endregion      // Internal Methods
4957         }
4958
4959         internal class UndoManager {
4960
4961                 internal enum ActionType {
4962
4963                         Typing,
4964
4965                         // This is basically just cut & paste
4966                         InsertString,
4967                         DeleteString,
4968
4969                         UserActionBegin,
4970                         UserActionEnd
4971                 }
4972
4973                 internal class Action {
4974                         internal ActionType     type;
4975                         internal int            line_no;
4976                         internal int            pos;
4977                         internal object         data;
4978                 }
4979
4980                 #region Local Variables
4981                 private Document        document;
4982                 private Stack           undo_actions;
4983                 private Stack           redo_actions;
4984
4985                 private int             caret_line;
4986                 private int             caret_pos;
4987
4988                 // When performing an action, we lock the queue, so that the action can't be undone
4989                 private bool locked;
4990                 #endregion      // Local Variables
4991
4992                 #region Constructors
4993                 internal UndoManager (Document document)
4994                 {
4995                         this.document = document;
4996                         undo_actions = new Stack (50);
4997                         redo_actions = new Stack (50);
4998                 }
4999                 #endregion      // Constructors
5000
5001                 #region Properties
5002                 internal bool CanUndo {
5003                         get { return undo_actions.Count > 0; }
5004                 }
5005
5006                 internal bool CanRedo {
5007                         get { return redo_actions.Count > 0; }
5008                 }
5009
5010                 internal string UndoActionName {
5011                         get {
5012                                 foreach (Action action in undo_actions) {
5013                                         if (action.type == ActionType.UserActionBegin)
5014                                                 return (string) action.data;
5015                                         if (action.type == ActionType.Typing)
5016                                                 return Locale.GetText ("Typing");
5017                                 }
5018                                 return String.Empty;
5019                         }
5020                 }
5021
5022                 internal string RedoActionName {
5023                         get {
5024                                 foreach (Action action in redo_actions) {
5025                                         if (action.type == ActionType.UserActionBegin)
5026                                                 return (string) action.data;
5027                                         if (action.type == ActionType.Typing)
5028                                                 return Locale.GetText ("Typing");
5029                                 }
5030                                 return String.Empty;
5031                         }
5032                 }
5033                 #endregion      // Properties
5034
5035                 #region Internal Methods
5036                 internal void Clear ()
5037                 {
5038                         undo_actions.Clear();
5039                         redo_actions.Clear();
5040                 }
5041
5042                 internal void Undo ()
5043                 {
5044                         Action action;
5045                         bool user_action_finished = false;
5046
5047                         if (undo_actions.Count == 0)
5048                                 return;
5049
5050                         // Nuke the redo queue
5051                         redo_actions.Clear ();
5052
5053                         locked = true;
5054                         do {
5055                                 Line start;
5056                                 action = (Action) undo_actions.Pop ();
5057
5058                                 // Put onto redo stack
5059                                 redo_actions.Push(action);
5060
5061                                 // Do the thing
5062                                 switch(action.type) {
5063
5064                                 case ActionType.UserActionBegin:
5065                                         user_action_finished = true;
5066                                         break;
5067
5068                                 case ActionType.UserActionEnd:
5069                                         // noop
5070                                         break;
5071
5072                                 case ActionType.InsertString:
5073                                         start = document.GetLine (action.line_no);
5074                                         document.SuspendUpdate ();
5075                                         document.DeleteMultiline (start, action.pos, ((string) action.data).Length + 1);
5076                                         document.PositionCaret (start, action.pos);
5077                                         document.SetSelectionToCaret (true);
5078                                         document.ResumeUpdate (true);
5079                                         break;
5080
5081                                 case ActionType.Typing:
5082                                         start = document.GetLine (action.line_no);
5083                                         document.SuspendUpdate ();
5084                                         document.DeleteMultiline (start, action.pos, ((StringBuilder) action.data).Length);
5085                                         document.PositionCaret (start, action.pos);
5086                                         document.SetSelectionToCaret (true);
5087                                         document.ResumeUpdate (true);
5088
5089                                         // This is an open ended operation, so only a single typing operation can be undone at once
5090                                         user_action_finished = true;
5091                                         break;
5092
5093                                 case ActionType.DeleteString:
5094                                         start = document.GetLine (action.line_no);
5095                                         document.SuspendUpdate ();
5096                                         Insert (start, action.pos, (Line) action.data, true);
5097                                         document.ResumeUpdate (true);
5098                                         break;
5099                                 }
5100                         } while (!user_action_finished && undo_actions.Count > 0);
5101
5102                         locked = false;
5103                 }
5104
5105                 internal void Redo ()
5106                 {
5107                         Action action;
5108                         bool user_action_finished = false;
5109
5110                         if (redo_actions.Count == 0)
5111                                 return;
5112
5113                         // You can't undo anything after redoing
5114                         undo_actions.Clear ();
5115
5116                         locked = true;
5117                         do {
5118                                 Line start;
5119                                 int start_index;
5120
5121                                 action = (Action) redo_actions.Pop ();
5122
5123                                 switch (action.type) {
5124
5125                                 case ActionType.UserActionBegin:
5126                                         //  Noop
5127                                         break;
5128
5129                                 case ActionType.UserActionEnd:
5130                                         user_action_finished = true;
5131                                         break;
5132
5133                                 case ActionType.InsertString:
5134                                         start = document.GetLine (action.line_no);
5135                                         document.SuspendUpdate ();
5136                                         start_index = document.LineTagToCharIndex (start, action.pos);
5137                                         document.InsertString (start, action.pos, (string) action.data);
5138                                         document.CharIndexToLineTag (start_index + ((string) action.data).Length,
5139                                                         out document.caret.line, out document.caret.tag,
5140                                                         out document.caret.pos);
5141                                         document.UpdateCaret ();
5142                                         document.SetSelectionToCaret (true);
5143                                         document.ResumeUpdate (true);
5144                                         break;
5145
5146                                 case ActionType.Typing:
5147                                         start = document.GetLine (action.line_no);
5148                                         document.SuspendUpdate ();
5149                                         start_index = document.LineTagToCharIndex (start, action.pos);
5150                                         document.InsertString (start, action.pos, ((StringBuilder) action.data).ToString ());
5151                                         document.CharIndexToLineTag (start_index + ((StringBuilder) action.data).Length,
5152                                                         out document.caret.line, out document.caret.tag,
5153                                                         out document.caret.pos);
5154                                         document.UpdateCaret ();
5155                                         document.SetSelectionToCaret (true);
5156                                         document.ResumeUpdate (true);
5157
5158                                         // This is an open ended operation, so only a single typing operation can be undone at once
5159                                         user_action_finished = true;
5160                                         break;
5161
5162                                 case ActionType.DeleteString:
5163                                         start = document.GetLine (action.line_no);
5164                                         document.SuspendUpdate ();
5165                                         document.DeleteMultiline (start, action.pos, ((Line) action.data).text.Length);
5166                                         document.PositionCaret (start, action.pos);
5167                                         document.SetSelectionToCaret (true);
5168                                         document.ResumeUpdate (true);
5169
5170                                         break;
5171                                 }
5172                         } while (!user_action_finished && redo_actions.Count > 0);
5173
5174                         locked = false;
5175                 }
5176                 #endregion      // Internal Methods
5177
5178                 #region Private Methods
5179
5180                 public void BeginUserAction (string name)
5181                 {
5182                         if (locked)
5183                                 return;
5184
5185                         Action ua = new Action ();
5186                         ua.type = ActionType.UserActionBegin;
5187                         ua.data = name;
5188
5189                         undo_actions.Push (ua);
5190                 }
5191
5192                 public void EndUserAction ()
5193                 {
5194                         if (locked)
5195                                 return;
5196
5197                         Action ua = new Action ();
5198                         ua.type = ActionType.UserActionEnd;
5199
5200                         undo_actions.Push (ua);
5201                 }
5202
5203                 // start_pos, end_pos = 1 based
5204                 public void RecordDeleteString (Line start_line, int start_pos, Line end_line, int end_pos)
5205                 {
5206                         if (locked)
5207                                 return;
5208
5209                         Action  a = new Action ();
5210
5211                         // We cant simply store the string, because then formatting would be lost
5212                         a.type = ActionType.DeleteString;
5213                         a.line_no = start_line.line_no;
5214                         a.pos = start_pos;
5215                         a.data = Duplicate (start_line, start_pos, end_line, end_pos);
5216
5217                         undo_actions.Push(a);
5218                 }
5219
5220                 public void RecordInsertString (Line line, int pos, string str)
5221                 {
5222                         if (locked || str.Length == 0)
5223                                 return;
5224
5225                         Action a = new Action ();
5226
5227                         a.type = ActionType.InsertString;
5228                         a.data = str;
5229                         a.line_no = line.line_no;
5230                         a.pos = pos;
5231
5232                         undo_actions.Push (a);
5233                 }
5234
5235                 public void RecordTyping (Line line, int pos, char ch)
5236                 {
5237                         if (locked)
5238                                 return;
5239
5240                         Action a = null;
5241
5242                         if (undo_actions.Count > 0)
5243                                 a = (Action) undo_actions.Peek ();
5244
5245                         if (a == null || a.type != ActionType.Typing) {
5246                                 a = new Action ();
5247                                 a.type = ActionType.Typing;
5248                                 a.data = new StringBuilder ();
5249                                 a.line_no = line.line_no;
5250                                 a.pos = pos;
5251
5252                                 undo_actions.Push (a);
5253                         }
5254
5255                         StringBuilder data = (StringBuilder) a.data;
5256                         data.Append (ch);
5257                 }
5258
5259                 // start_pos = 1-based
5260                 // end_pos = 1-based
5261                 public Line Duplicate(Line start_line, int start_pos, Line end_line, int end_pos)
5262                 {
5263                         Line    ret;
5264                         Line    line;
5265                         Line    current;
5266                         LineTag tag;
5267                         LineTag current_tag;
5268                         int     start;
5269                         int     end;
5270                         int     tag_start;
5271
5272                         line = new Line (start_line.document, start_line.ending);
5273                         ret = line;
5274
5275                         for (int i = start_line.line_no; i <= end_line.line_no; i++) {
5276                                 current = document.GetLine(i);
5277
5278                                 if (start_line.line_no == i) {
5279                                         start = start_pos;
5280                                 } else {
5281                                         start = 0;
5282                                 }
5283
5284                                 if (end_line.line_no == i) {
5285                                         end = end_pos;
5286                                 } else {
5287                                         end = current.text.Length;
5288                                 }
5289
5290                                 if (end_pos == 0)
5291                                         continue;
5292
5293                                 // Text for the tag
5294                                 line.text = new StringBuilder (current.text.ToString (start, end - start));
5295
5296                                 // Copy tags from start to start+length onto new line
5297                                 current_tag = current.FindTag (start);
5298                                 while ((current_tag != null) && (current_tag.start <= end)) {
5299                                         if ((current_tag.start <= start) && (start < (current_tag.start + current_tag.length))) {
5300                                                 // start tag is within this tag
5301                                                 tag_start = start;
5302                                         } else {
5303                                                 tag_start = current_tag.start;
5304                                         }
5305
5306                                         tag = new LineTag(line, tag_start - start + 1);
5307                                         tag.CopyFormattingFrom (current_tag);
5308
5309                                         current_tag = current_tag.next;
5310
5311                                         // Add the new tag to the line
5312                                         if (line.tags == null) {
5313                                                 line.tags = tag;
5314                                         } else {
5315                                                 LineTag tail;
5316                                                 tail = line.tags;
5317
5318                                                 while (tail.next != null) {
5319                                                         tail = tail.next;
5320                                                 }
5321                                                 tail.next = tag;
5322                                                 tag.previous = tail;
5323                                         }
5324                                 }
5325
5326                                 if ((i + 1) <= end_line.line_no) {
5327                                         line.ending = current.ending;
5328
5329                                         // Chain them (we use right/left as next/previous)
5330                                         line.right = new Line (start_line.document, start_line.ending);
5331                                         line.right.left = line;
5332                                         line = line.right;
5333                                 }
5334                         }
5335
5336                         return ret;
5337                 }
5338
5339                 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
5340                 internal void Insert(Line line, int pos, Line insert, bool select)
5341                 {
5342                         Line    current;
5343                         LineTag tag;
5344                         int     offset;
5345                         int     lines;
5346                         Line    first;
5347
5348                         // Handle special case first
5349                         if (insert.right == null) {
5350
5351                                 // Single line insert
5352                                 document.Split(line, pos);
5353
5354                                 if (insert.tags == null) {
5355                                         return; // Blank line
5356                                 }
5357
5358                                 //Insert our tags at the end
5359                                 tag = line.tags;
5360
5361                                 while (tag.next != null) {
5362                                         tag = tag.next;
5363                                 }
5364
5365                                 offset = tag.start + tag.length - 1;
5366
5367                                 tag.next = insert.tags;
5368                                 line.text.Insert(offset, insert.text.ToString());
5369
5370                                 // Adjust start locations
5371                                 tag = tag.next;
5372                                 while (tag != null) {
5373                                         tag.start += offset;
5374                                         tag.line = line;
5375                                         tag = tag.next;
5376                                 }
5377                                 // Put it back together
5378                                 document.Combine(line.line_no, line.line_no + 1);
5379
5380                                 if (select) {
5381                                         document.SetSelectionStart (line, pos, false);
5382                                         document.SetSelectionEnd (line, pos + insert.text.Length, false);
5383                                 }
5384
5385                                 document.UpdateView(line, pos);
5386                                 return;
5387                         }
5388
5389                         first = line;
5390                         lines = 1;
5391                         current = insert;
5392
5393                         while (current != null) {
5394
5395                                 if (current == insert) {
5396                                         // Inserting the first line we split the line (and make space)
5397                                         document.Split(line.line_no, pos);
5398                                         //Insert our tags at the end of the line
5399                                         tag = line.tags;
5400
5401                                         
5402                                         if (tag != null && tag.length != 0) {
5403                                                 while (tag.next != null) {
5404                                                         tag = tag.next;
5405                                                 }
5406                                                 offset = tag.start + tag.length - 1;
5407                                                 tag.next = current.tags;
5408                                                 tag.next.previous = tag;
5409
5410                                                 tag = tag.next;
5411
5412                                         } else {
5413                                                 offset = 0;
5414                                                 line.tags = current.tags;
5415                                                 line.tags.previous = null;
5416                                                 tag = line.tags;
5417                                         }
5418
5419                                         line.ending = current.ending;
5420                                 } else {
5421                                         document.Split(line.line_no, 0);
5422                                         offset = 0;
5423                                         line.tags = current.tags;
5424                                         line.tags.previous = null;
5425                                         line.ending = current.ending;
5426                                         tag = line.tags;
5427                                 }
5428
5429                                 // Adjust start locations and line pointers
5430                                 while (tag != null) {
5431                                         tag.start += offset - 1;
5432                                         tag.line = line;
5433                                         tag = tag.next;
5434                                 }
5435
5436                                 line.text.Insert(offset, current.text.ToString());
5437                                 line.Grow(line.text.Length);
5438
5439                                 line.recalc = true;
5440                                 line = document.GetLine(line.line_no + 1);
5441
5442                                 // FIXME? Test undo of line-boundaries
5443                                 if ((current.right == null) && (current.tags.length != 0)) {
5444                                         document.Combine(line.line_no - 1, line.line_no);
5445                                 }
5446                                 current = current.right;
5447                                 lines++;
5448
5449                         }
5450
5451                         // Recalculate our document
5452                         document.UpdateView(first, lines, pos);
5453                         return;
5454                 }               
5455                 #endregion      // Private Methods
5456         }
5457 }