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