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