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