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