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