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