2004-12-27 Ben Maurer <bmaurer@ximian.com>
[mono.git] / mcs / class / Managed.Windows.Forms / System.Windows.Forms / TextControl.cs
1 // Permission is hereby granted, free of charge, to any person obtaining
2 // a copy of this software and associated documentation files (the
3 // "Software"), to deal in the Software without restriction, including
4 // without limitation the rights to use, copy, modify, merge, publish,
5 // distribute, sublicense, and/or sell copies of the Software, and to
6 // permit persons to whom the Software is furnished to do so, subject to
7 // the following conditions:
8 // 
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
11 // 
12 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19 //
20 // Copyright (c) 2004 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 to support wrapping (ie have a 'newline' tag), for images, etc.
34 // - Wrap and recalculate lines
35 // - Implement CaretPgUp/PgDown
36 // - Finish selection calculations (invalidate only changed, more ways to select)
37 // - Implement C&P
38
39
40 #undef Debug
41
42 using System;
43 using System.Collections;
44 using System.Drawing;
45 using System.Drawing.Text;
46 using System.Text;
47
48 namespace System.Windows.Forms {
49         public enum LineColor {
50                 Red     = 0,
51                 Black   = 1
52         }
53
54         public enum CaretDirection {
55                 CharForward,    // Move a char to the right
56                 CharBack,       // Move a char to the left
57                 LineUp,         // Move a line up
58                 LineDown,       // Move a line down
59                 Home,           // Move to the beginning of the line
60                 End,            // Move to the end of the line
61                 PgUp,           // Move one page up
62                 PgDn,           // Move one page down
63                 CtrlHome,       // Move to the beginning of the document
64                 CtrlEnd,        // Move to the end of the document
65                 WordBack,       // Move to the beginning of the previous word (or beginning of line)
66                 WordForward     // Move to the beginning of the next word (or end of line)
67         }
68
69         // Being cloneable should allow for nice line and document copies...
70         public class Line : ICloneable, IComparable {
71                 #region Local Variables
72                 // Stuff that matters for our line
73                 internal StringBuilder          text;                   // Characters for the line
74                 internal float[]                widths;                 // Width of each character; always one larger than text.Length
75                 internal int                    space;                  // Number of elements in text and widths
76                 internal int                    line_no;                // Line number
77                 internal LineTag                tags;                   // Tags describing the text
78                 internal int                    Y;                      // Baseline
79                 internal int                    height;                 // Height of the line (height of tallest tag)
80                 internal int                    ascent;                 // Ascent of the line (ascent of the tallest tag)
81
82                 // Stuff that's important for the tree
83                 internal Line                   parent;                 // Our parent line
84                 public Line                     left;                   // Line with smaller line number
85                 public Line                     right;                  // Line with higher line number
86                 internal LineColor              color;                  // We're doing a black/red tree. this is the node color
87                 internal int                    DEFAULT_TEXT_LEN;       // 
88                 internal static StringFormat    string_format;          // For calculating widths/heights
89                 internal bool                   recalc;                 // Line changed
90                 #endregion      // Local Variables
91
92                 #region Constructors
93                 public Line() {
94                         color = LineColor.Red;
95                         left = null;
96                         right = null;
97                         parent = null;
98                         text = null;
99                         recalc = true;
100
101                         if (string_format == null) {
102                                 string_format = new StringFormat(StringFormat.GenericTypographic);
103                                 string_format.Trimming = StringTrimming.None;
104                                 string_format.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;
105                         }
106                 }
107
108                 public Line(int LineNo, string Text, Font font, Brush color) : this() {
109                         space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
110
111                         text = new StringBuilder(Text, space);
112                         line_no = LineNo;
113
114                         widths = new float[space + 1];
115                         tags = new LineTag(this, 1, text.Length);
116                         tags.font = font;
117                         tags.color = color;
118                 }
119
120                 public Line(int LineNo, string Text, LineTag tag) : this() {
121                         space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
122
123                         text = new StringBuilder(Text, space);
124                         line_no = LineNo;
125
126                         widths = new float[space + 1];
127                         tags = tag;
128                 }
129
130                 #endregion      // Constructors
131
132                 #region Public Properties
133                 public int Height {
134                         get {
135                                 return height;
136                         }
137
138                         set {
139                                 height = value;
140                         }
141                 }
142
143                 public int LineNo {
144                         get {
145                                 return line_no;
146                         }
147
148                         set {
149                                 line_no = value;
150                         }
151                 }
152
153                 public string Text {
154                         get {
155                                 return text.ToString();
156                         }
157
158                         set {
159                                 text = new StringBuilder(value, value.Length > DEFAULT_TEXT_LEN ? value.Length : DEFAULT_TEXT_LEN);
160                         }
161                 }
162 #if no
163                 public StringBuilder Text {
164                         get {
165                                 return text;
166                         }
167
168                         set {
169                                 text = value;
170                         }
171                 }
172 #endif
173                 #endregion      // Public Properties
174
175                 #region Public Methods
176                 // Make sure we always have enoughs space in text and widths
177                 public void Grow(int minimum) {
178                         int     length;
179                         float[] new_widths;
180
181                         length = text.Length;
182
183                         if ((length + minimum) > space) {
184                                 // We need to grow; double the size
185
186                                 if ((length + minimum) > (space * 2)) {
187                                         new_widths = new float[length + minimum * 2 + 1];
188                                         space = length + minimum * 2;
189                                 } else {                                
190                                         new_widths = new float[space * 2 + 1];
191                                         space *= 2;
192                                 }
193                                 widths.CopyTo(new_widths, 0);
194
195                                 widths = new_widths;
196                         }
197                 }
198
199                 public void Streamline() {
200                         LineTag current;
201                         LineTag next;
202
203                         current = this.tags;
204                         next = current.next;
205
206                         // Catch what the loop below wont; eliminate 0 length 
207                         // tags, but only if there are other tags after us
208                         while ((current.length == 0) && (next != null)) {
209                                 tags = next;
210                                 current = next;
211                                 next = current.next;
212                         }
213                         
214                         if (next == null) {
215                                 return;
216                         }
217
218                         while (next != null) {
219                                 // Take out 0 length tags
220                                 if (next.length == 0) {
221                                         current.next = next.next;
222                                         if (current.next != null) {
223                                                 current.next.previous = current;
224                                         }
225                                         next = current.next;
226                                         continue;
227                                 }
228
229                                 if (current.Combine(next)) {
230                                         next = current.next;
231                                         continue;
232                                 }
233
234                                 current = current.next;
235                                 next = current.next;
236                         }
237                 }
238
239                 // Find the tag on a line based on the character position
240                 public LineTag FindTag(int pos) {
241                         LineTag tag;
242
243                         if (pos == 0) {
244                                 return tags;
245                         }
246
247                         tag = this.tags;
248
249                         if (pos > text.Length) {
250                                 pos = text.Length;
251                         }
252
253                         while (tag != null) {
254                                 if ((tag.start <= pos) && (pos < (tag.start + tag.length))) {
255                                         return tag;
256                                 }
257                                 tag = tag.next;
258                         }
259                         return null;
260                 }
261
262
263                 //
264                 // Go through all tags on a line and recalculate all size-related values
265                 // returns true if lineheight changed
266                 //
267                 public bool RecalculateLine(Graphics g) {
268                         LineTag tag;
269                         int     pos;
270                         int     len;
271                         SizeF   size;
272                         float   w;
273                         int     prev_height;
274
275                         pos = 0;
276                         len = this.text.Length;
277                         tag = this.tags;
278                         prev_height = this.height;      // For drawing optimization calculations
279                         this.height = 0;                // Reset line height
280                         this.ascent = 0;                // Reset the ascent for the line
281                         tag.shift = 0;
282                         tag.width = 0;
283                         widths[0] = 0;
284                         this.recalc = false;
285
286                         while (pos < len) {
287                                 size = g.MeasureString(this.text.ToString(pos, 1), tag.font, 10000, string_format);
288
289                                 w = size.Width;
290
291                                 tag.width += w;
292
293                                 pos++;
294
295                                 widths[pos] = widths[pos-1] + w;
296
297                                 if (pos == (tag.start-1 + tag.length)) {
298                                         // We just found the end of our current tag
299                                         tag.height = (int)tag.font.Height;
300
301                                         // Check if we're the tallest on the line (so far)
302                                         if (tag.height > this.height) {
303                                                 this.height = tag.height;               // Yep; make sure the line knows
304                                         }
305
306                                         if (tag.ascent == 0) {
307                                                 int     descent;
308
309                                                 XplatUI.GetFontMetrics(g, tag.font, out tag.ascent, out descent);
310                                         }
311
312                                         if (tag.ascent > this.ascent) {
313                                                 LineTag         t;
314
315                                                 // We have a tag that has a taller ascent than the line;
316
317                                                 t = tags;
318                                                 while (t != tag) {
319                                                         t.shift = tag.ascent - t.ascent;
320                                                         t = t.next;
321                                                 }
322
323                                                 // Save on our line
324                                                 this.ascent = tag.ascent;
325                                         } else {
326                                                 tag.shift = this.ascent - tag.ascent;
327                                         }
328
329                                         // Update our horizontal starting pixel position
330                                         if (tag.previous == null) {
331                                                 tag.X = 0;
332                                         } else {
333                                                 tag.X = tag.previous.X + (int)tag.previous.width;
334                                         }
335
336                                         tag = tag.next;
337                                         if (tag != null) {
338                                                 tag.width = 0;
339                                                 tag.shift = 0;
340                                         }
341                                 }
342                         }
343
344                         if (this.height == 0) {
345                                 this.height = tags.font.Height;
346                                 tag.height = this.height;
347                         }
348
349                         if (prev_height != this.height) {
350                                 return true;
351                         }
352                         return false;
353                 }
354                 #endregion      // Public Methods
355
356                 #region Administrative
357                 public int CompareTo(object obj) {
358                         if (obj == null) {
359                                 return 1;
360                         }
361
362                         if (! (obj is Line)) {
363                                 throw new ArgumentException("Object is not of type Line", "obj");
364                         }
365
366                         if (line_no < ((Line)obj).line_no) {
367                                 return -1;
368                         } else if (line_no > ((Line)obj).line_no) {
369                                 return 1;
370                         } else {
371                                 return 0;
372                         }
373                 }
374
375                 public object Clone() {
376                         Line    clone;
377
378                         clone = new Line();
379
380                         clone.text = text;
381
382                         if (left != null) {
383                                 clone.left = (Line)left.Clone();
384                         }
385
386                         if (left != null) {
387                                 clone.left = (Line)left.Clone();
388                         }
389
390                         return clone;
391                 }
392
393                 public object CloneLine() {
394                         Line    clone;
395
396                         clone = new Line();
397
398                         clone.text = text;
399
400                         return clone;
401                 }
402
403                 public override bool Equals(object obj) {
404                         if (obj == null) {
405                                 return false;
406                         }
407
408                         if (!(obj is Line)) {
409                                 return false;
410                         }
411
412                         if (obj == this) {
413                                 return true;
414                         }
415
416                         if (line_no == ((Line)obj).line_no) {
417                                 return true;
418                         }
419
420                         return false;
421                 }
422
423
424                 public override string ToString() {
425                         return "Line " + line_no;
426                 }
427
428                 #endregion      // Administrative
429         }
430
431         public class Document : ICloneable, IEnumerable {
432                 #region Structures
433                 internal struct Marker {
434                         internal Line           line;
435                         internal LineTag        tag;
436                         internal int            pos;
437                         internal int            height;
438                 }
439                 #endregion Structures
440
441                 #region Local Variables
442                 private Line            document;
443                 private int             lines;
444                 private static Line     sentinel;
445                 private Line            last_found;
446                 private int             document_id;
447                 private Random          random = new Random();
448
449                 internal bool           multiline;
450                 internal bool           wrap;
451
452                 internal Marker         caret;
453                 internal Marker         selection_start;
454                 internal Marker         selection_end;
455                 internal bool           selection_visible;
456
457                 internal int            viewport_x;
458                 internal int            viewport_y;             // The visible area of the document
459
460                 internal int            document_x;             // Width of the document
461                 internal int            document_y;             // Height of the document
462
463                 internal Control        owner;                  // Who's owning us?
464                 #endregion      // Local Variables
465
466                 #region Constructors
467                 public Document(Control owner) {
468                         lines = 0;
469
470                         this.owner = owner;
471
472                         multiline = true;
473
474                         // Tree related stuff
475                         sentinel = new Line();
476                         sentinel.color = LineColor.Black;
477
478                         document = sentinel;
479                         last_found = sentinel;
480
481                         // We always have a blank line
482                         Add(1, "", owner.Font, new SolidBrush(owner.ForeColor));
483                         this.RecalculateDocument(owner.CreateGraphics());
484                         PositionCaret(0, 0);
485                         lines=1;
486
487                         selection_visible = false;
488                         selection_start.line = this.document;
489                         selection_start.pos = 0;
490                         selection_end.line = this.document;
491                         selection_end.pos = 0;
492
493
494
495                         // Default selection is empty
496
497                         document_id = random.Next();
498                 }
499                 #endregion
500
501                 #region Public Properties
502                 public Line Root {
503                         get {
504                                 return document;
505                         }
506
507                         set {
508                                 document = value;
509                         }
510                 }
511
512                 public int Lines {
513                         get {
514                                 return lines;
515                         }
516                 }
517
518                 public Line CaretLine {
519                         get {
520                                 return caret.line;
521                         }
522                 }
523
524                 public int CaretPosition {
525                         get {
526                                 return caret.pos;
527                         }
528                 }
529
530                 public LineTag CaretTag {
531                         get {
532                                 return caret.tag;
533                         }
534                 }
535
536                 public int ViewPortX {
537                         get {
538                                 return viewport_x;
539                         }
540
541                         set {
542                                 viewport_x = value;
543                         }
544                 }
545
546                 public int ViewPortY {
547                         get {
548                                 return viewport_y;
549                         }
550
551                         set {
552                                 viewport_y = value;
553                         }
554                 }
555
556                 public int Width {
557                         get {
558                                 return this.document_x;
559                         }
560                 }
561
562                 public int Height {
563                         get {
564                                 return this.document_y;
565                         }
566                 }
567
568                 #endregion      // Public Properties
569
570                 #region Private Methods
571                 // For debugging
572                 internal void DumpTree(Line line, bool with_tags) {
573                         Console.Write("Line {0}, Y: {1} Text {2}", line.line_no, line.Y, line.text != null ? line.text.ToString() : "undefined");
574
575                         if (line.left == sentinel) {
576                                 Console.Write(", left = sentinel");
577                         } else if (line.left == null) {
578                                 Console.Write(", left = NULL");
579                         }
580
581                         if (line.right == sentinel) {
582                                 Console.Write(", right = sentinel");
583                         } else if (line.right == null) {
584                                 Console.Write(", right = NULL");
585                         }
586
587                         Console.WriteLine("");
588
589                         if (with_tags) {
590                                 LineTag tag;
591                                 int     count;
592
593                                 tag = line.tags;
594                                 count = 1;
595                                 Console.Write("   Tags: ");
596                                 while (tag != null) {
597                                         Console.Write("{0} <{1}>-<{2}> ", count++, tag.start, tag.length);
598                                         if (tag.line != line) {
599                                                 Console.Write("BAD line link");
600                                                 throw new Exception("Bad line link in tree");
601                                         }
602                                         tag = tag.next;
603                                         if (tag != null) {
604                                                 Console.Write(", ");
605                                         }
606                                 }
607                                 Console.WriteLine("");
608                         }
609                         if (line.left != null) {
610                                 if (line.left != sentinel) {
611                                         DumpTree(line.left, with_tags);
612                                 }
613                         } else {
614                                 if (line != sentinel) {
615                                         throw new Exception("Left should not be NULL");
616                                 }
617                         }
618
619                         if (line.right != null) {
620                                 if (line.right != sentinel) {
621                                         DumpTree(line.right, with_tags);
622                                 }
623                         } else {
624                                 if (line != sentinel) {
625                                         throw new Exception("Right should not be NULL");
626                                 }
627                         }
628                 }
629
630                 private void DecrementLines(int line_no) {
631                         int     current;
632
633                         current = line_no;
634                         while (current <= lines) {
635                                 GetLine(current).line_no--;
636                                 current++;
637                         }
638                         return;
639                 }
640
641                 private void IncrementLines(int line_no) {
642                         int     current;
643
644                         current = this.lines;
645                         while (current >= line_no) {
646                                 GetLine(current).line_no++;
647                                 current--;
648                         }
649                         return;
650                 }
651
652                 private void RebalanceAfterAdd(Line line1) {
653                         Line    line2;
654
655                         while ((line1 != document) && (line1.parent.color == LineColor.Red)) {
656                                 if (line1.parent == line1.parent.parent.left) {
657                                         line2 = line1.parent.parent.right;
658
659                                         if ((line2 != null) && (line2.color == LineColor.Red)) {
660                                                 line1.parent.color = LineColor.Black;
661                                                 line2.color = LineColor.Black;
662                                                 line1.parent.parent.color = LineColor.Red;
663                                                 line1 = line1.parent.parent;
664                                         } else {
665                                                 if (line1 == line1.parent.right) {
666                                                         line1 = line1.parent;
667                                                         RotateLeft(line1);
668                                                 }
669
670                                                 line1.parent.color = LineColor.Black;
671                                                 line1.parent.parent.color = LineColor.Red;
672
673                                                 RotateRight(line1.parent.parent);
674                                         }
675                                 } else {
676                                         line2 = line1.parent.parent.left;
677
678                                         if ((line2 != null) && (line2.color == LineColor.Red)) {
679                                                 line1.parent.color = LineColor.Black;
680                                                 line2.color = LineColor.Black;
681                                                 line1.parent.parent.color = LineColor.Red;
682                                                 line1 = line1.parent.parent;
683                                         } else {
684                                                 if (line1 == line1.parent.left) {
685                                                         line1 = line1.parent;
686                                                         RotateRight(line1);
687                                                 }
688
689                                                 line1.parent.color = LineColor.Black;
690                                                 line1.parent.parent.color = LineColor.Red;
691                                                 RotateLeft(line1.parent.parent);
692                                         }
693                                 }
694                         }
695                         document.color = LineColor.Black;
696                 }
697
698                 private void RebalanceAfterDelete(Line line1) {
699                         Line line2;
700
701                         while ((line1 != document) && (line1.color == LineColor.Black)) {
702                                 if (line1 == line1.parent.left) {
703                                         line2 = line1.parent.right;
704                                         if (line2.color == LineColor.Red) { 
705                                                 line2.color = LineColor.Black;
706                                                 line1.parent.color = LineColor.Red;
707                                                 RotateLeft(line1.parent);
708                                                 line2 = line1.parent.right;
709                                         }
710                                         if ((line2.left.color == LineColor.Black) && (line2.right.color == LineColor.Black)) { 
711                                                 line2.color = LineColor.Red;
712                                                 line1 = line1.parent;
713                                         } else {
714                                                 if (line2.right.color == LineColor.Black) {
715                                                         line2.left.color = LineColor.Black;
716                                                         line2.color = LineColor.Red;
717                                                         RotateRight(line2);
718                                                         line2 = line1.parent.right;
719                                                 }
720                                                 line2.color = line1.parent.color;
721                                                 line1.parent.color = LineColor.Black;
722                                                 line2.right.color = LineColor.Black;
723                                                 RotateLeft(line1.parent);
724                                                 line1 = document;
725                                         }
726                                 } else { 
727                                         line2 = line1.parent.left;
728                                         if (line2.color == LineColor.Red) {
729                                                 line2.color = LineColor.Black;
730                                                 line1.parent.color = LineColor.Red;
731                                                 RotateRight(line1.parent);
732                                                 line2 = line1.parent.left;
733                                         }
734                                         if ((line2.right.color == LineColor.Black) && (line2.left.color == LineColor.Black)) {
735                                                 line2.color = LineColor.Red;
736                                                 line1 = line1.parent;
737                                         } else {
738                                                 if (line2.left.color == LineColor.Black) {
739                                                         line2.right.color = LineColor.Black;
740                                                         line2.color = LineColor.Red;
741                                                         RotateLeft(line2);
742                                                         line2 = line1.parent.left;
743                                                 }
744                                                 line2.color = line1.parent.color;
745                                                 line1.parent.color = LineColor.Black;
746                                                 line2.left.color = LineColor.Black;
747                                                 RotateRight(line1.parent);
748                                                 line1 = document;
749                                         }
750                                 }
751                         }
752                         line1.color = LineColor.Black;
753                 }
754
755                 private void RotateLeft(Line line1) {
756                         Line    line2 = line1.right;
757
758                         line1.right = line2.left;
759
760                         if (line2.left != sentinel) {
761                                 line2.left.parent = line1;
762                         }
763
764                         if (line2 != sentinel) {
765                                 line2.parent = line1.parent;
766                         }
767
768                         if (line1.parent != null) {
769                                 if (line1 == line1.parent.left) {
770                                         line1.parent.left = line2;
771                                 } else {
772                                         line1.parent.right = line2;
773                                 }
774                         } else {
775                                 document = line2;
776                         }
777
778                         line2.left = line1;
779                         if (line1 != sentinel) {
780                                 line1.parent = line2;
781                         }
782                 }
783
784                 private void RotateRight(Line line1) {
785                         Line line2 = line1.left;
786
787                         line1.left = line2.right;
788
789                         if (line2.right != sentinel) {
790                                 line2.right.parent = line1;
791                         }
792
793                         if (line2 != sentinel) {
794                                 line2.parent = line1.parent;
795                         }
796
797                         if (line1.parent != null) {
798                                 if (line1 == line1.parent.right) {
799                                         line1.parent.right = line2;
800                                 } else {
801                                         line1.parent.left = line2;
802                                 }
803                         } else {
804                                 document = line2;
805                         }
806
807                         line2.right = line1;
808                         if (line1 != sentinel) {
809                                 line1.parent = line2;
810                         }
811                 }        
812
813
814                 public void UpdateView(Line line, int pos) {
815                         int     prev_width;
816
817                         // This is an optimization; we need to invalidate 
818                         prev_width = (int)line.widths[line.text.Length];
819
820                         if (RecalculateDocument(owner.CreateGraphics(), line.line_no, line.line_no, true)) {
821                                 // Lineheight changed, invalidate the rest of the document
822                                 if ((line.Y - viewport_y) >=0 ) {
823                                         // We formatted something that's in view, only draw parts of the screen
824                                         owner.Invalidate(new Rectangle(0, line.Y - viewport_y, owner.Width, owner.Height - line.Y - viewport_y));
825                                 } else {
826                                         // The tag was above the visible area, draw everything
827                                         owner.Invalidate();
828                                 }
829                         } else {
830                                 owner.Invalidate(new Rectangle((int)line.widths[pos] - viewport_x - 1, line.Y - viewport_y, (int)owner.Width, line.height));
831                         }
832                 }
833
834
835                 // Update display from line, down line_count lines; pos is unused, but required for the signature
836                 public void UpdateView(Line line, int line_count, int pos) {
837                         int     prev_width;
838
839                         // This is an optimization; we need to invalidate 
840                         prev_width = (int)line.widths[line.text.Length];
841
842                         if (RecalculateDocument(owner.CreateGraphics(), line.line_no, line.line_no + line_count - 1, true)) {
843                                 // Lineheight changed, invalidate the rest of the document
844                                 if ((line.Y - viewport_y) >=0 ) {
845                                         // We formatted something that's in view, only draw parts of the screen
846                                         owner.Invalidate(new Rectangle(0, line.Y - viewport_y, owner.Width, owner.Height - line.Y - viewport_y));
847                                 } else {
848                                         // The tag was above the visible area, draw everything
849                                         owner.Invalidate();
850                                 }
851                         } else {
852                                 Line    end_line;
853
854                                 end_line = GetLine(line.line_no + line_count -1);
855                                 if (end_line == null) {
856                                         end_line = line;
857                                 }
858
859                                 owner.Invalidate(new Rectangle(0 - viewport_x, line.Y - viewport_y, (int)line.widths[line.text.Length], end_line.Y + end_line.height));
860                         }
861                 }
862                 #endregion      // Private Methods
863
864                 #region Public Methods
865                 // Clear the document and reset state
866                 public void Empty() {
867
868                         document = sentinel;
869                         last_found = sentinel;
870                         lines = 0;
871
872                         // We always have a blank line
873                         Add(1, "", owner.Font, new SolidBrush(owner.ForeColor));
874                         this.RecalculateDocument(owner.CreateGraphics());
875                         PositionCaret(0, 0);
876
877                         selection_visible = false;
878                         selection_start.line = this.document;
879                         selection_start.pos = 0;
880                         selection_end.line = this.document;
881                         selection_end.pos = 0;
882
883                         viewport_x = 0;
884                         viewport_y = 0;
885
886                         document_x = 0;
887                         document_y = 0;
888                 }
889
890                 public void PositionCaret(Line line, int pos) {
891                         caret.tag = line.FindTag(pos);
892                         caret.line = line;
893                         caret.pos = pos;
894                         caret.height = caret.tag.height;
895
896                         XplatUI.DestroyCaret(owner.Handle);
897                         XplatUI.CreateCaret(owner.Handle, 2, caret.height);
898                         XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] - viewport_x, caret.tag.line.Y + caret.tag.shift - viewport_y);
899                 }
900
901                 public void PositionCaret(int x, int y) {
902                         caret.tag = FindCursor(x + viewport_x, y + viewport_y, out caret.pos);
903                         caret.line = caret.tag.line;
904                         caret.height = caret.tag.height;
905
906                         XplatUI.DestroyCaret(owner.Handle);
907                         XplatUI.CreateCaret(owner.Handle, 2, caret.height);
908                         XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] - viewport_x, caret.tag.line.Y + caret.tag.shift - viewport_y);
909                 }
910
911                 public void CaretHasFocus() {
912                         if (caret.tag != null) {
913                                 XplatUI.CreateCaret(owner.Handle, 2, caret.height);
914                                 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] - viewport_x, caret.tag.line.Y + caret.tag.shift - viewport_y);
915                                 XplatUI.CaretVisible(owner.Handle, true);
916                         }
917                 }
918
919                 public void CaretLostFocus() {
920                         XplatUI.DestroyCaret(owner.Handle);
921                 }
922
923                 public void AlignCaret() {
924                         caret.tag = LineTag.FindTag(caret.line, caret.pos);
925                         caret.height = caret.tag.height;
926
927                         XplatUI.CreateCaret(owner.Handle, 2, caret.height);
928                         XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] - viewport_x, caret.tag.line.Y + caret.tag.shift - viewport_y);
929                         XplatUI.CaretVisible(owner.Handle, true);
930                 }
931
932                 public void UpdateCaret() {
933                         if (caret.tag.height != caret.height) {
934                                 caret.height = caret.tag.height;
935                                 XplatUI.CreateCaret(owner.Handle, 2, caret.height);
936                         }
937                         XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] - viewport_x, caret.tag.line.Y + caret.tag.shift - viewport_y);
938                         XplatUI.CaretVisible(owner.Handle, true);
939                 }
940
941                 public void DisplayCaret() {
942                         XplatUI.CaretVisible(owner.Handle, true);
943                 }
944
945                 public void HideCaret() {
946                         XplatUI.CaretVisible(owner.Handle, false);
947                 }
948
949                 public void MoveCaret(CaretDirection direction) {
950                         switch(direction) {
951                                 case CaretDirection.CharForward: {
952                                         caret.pos++;
953                                         if (caret.pos > caret.line.text.Length) {
954                                                 if (multiline) {
955                                                         // Go into next line
956                                                         if (caret.line.line_no < this.lines) {
957                                                                 caret.line = GetLine(caret.line.line_no+1);
958                                                                 caret.pos = 0;
959                                                                 caret.tag = caret.line.tags;
960                                                         } else {
961                                                                 caret.pos--;
962                                                         }
963                                                 } else {
964                                                         // Single line; we stay where we are
965                                                         caret.pos--;
966                                                 }
967                                         } else {
968                                                 if ((caret.tag.start - 1 + caret.tag.length) < caret.pos) {
969                                                         caret.tag = caret.tag.next;
970                                                 }
971                                         }
972                                         UpdateCaret();
973                                         return;
974                                 }
975
976                                 case CaretDirection.CharBack: {
977                                         if (caret.pos > 0) {
978                                                 // caret.pos--; // folded into the if below
979                                                 if (--caret.pos > 0) {
980                                                         if (caret.tag.start > caret.pos) {
981                                                                 caret.tag = caret.tag.previous;
982                                                         }
983                                                 }
984                                         } else {
985                                                 if (caret.line.line_no > 1) {
986                                                         caret.line = GetLine(caret.line.line_no - 1);
987                                                         caret.pos = caret.line.text.Length;
988                                                         caret.tag = LineTag.FindTag(caret.line, caret.pos);
989                                                 }
990                                         }
991                                         UpdateCaret();
992                                         return;
993                                 }
994
995                                 case CaretDirection.WordForward: {
996                                         int len;
997
998                                         len = caret.line.text.Length;
999                                         if (caret.pos < len) {
1000                                                 while ((caret.pos < len) && (caret.line.text.ToString(caret.pos, 1) != " ")) {
1001                                                         caret.pos++;
1002                                                 }
1003                                                 if (caret.pos < len) {
1004                                                         // Skip any whitespace
1005                                                         while ((caret.pos < len) && (caret.line.text.ToString(caret.pos, 1) == " ")) {
1006                                                                 caret.pos++;
1007                                                         }
1008                                                 }
1009                                         } else {
1010                                                 if (caret.line.line_no < this.lines) {
1011                                                         caret.line = GetLine(caret.line.line_no+1);
1012                                                         caret.pos = 0;
1013                                                         caret.tag = caret.line.tags;
1014                                                 }
1015                                         }
1016                                         UpdateCaret();
1017                                         return;
1018                                 }
1019
1020                                 case CaretDirection.WordBack: {
1021                                         if (caret.pos > 0) {
1022                                                 int     len;
1023
1024                                                 len = caret.line.text.Length;
1025
1026                                                 caret.pos--;
1027
1028                                                 while ((caret.pos > 0) && (caret.line.text.ToString(caret.pos, 1) == " ")) {
1029                                                         caret.pos--;
1030                                                 }
1031
1032                                                 while ((caret.pos > 0) && (caret.line.text.ToString(caret.pos, 1) != " ")) {
1033                                                         caret.pos--;
1034                                                 }
1035
1036                                                 if (caret.line.text.ToString(caret.pos, 1) == " ") {
1037                                                         if (caret.pos != 0) {
1038                                                                 caret.pos++;
1039                                                         } else {
1040                                                                 caret.line = GetLine(caret.line.line_no - 1);
1041                                                                 caret.pos = caret.line.text.Length;
1042                                                                 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1043                                                         }
1044                                                 }
1045                                         } else {
1046                                                 if (caret.line.line_no > 1) {
1047                                                         caret.line = GetLine(caret.line.line_no - 1);
1048                                                         caret.pos = caret.line.text.Length;
1049                                                         caret.tag = LineTag.FindTag(caret.line, caret.pos);
1050                                                 }
1051                                         }
1052                                         UpdateCaret();
1053                                         return;
1054                                 }
1055
1056                                 case CaretDirection.LineUp: {
1057                                         if (caret.line.line_no > 1) {
1058                                                 int     pixel;
1059
1060                                                 pixel = (int)caret.line.widths[caret.pos];
1061                                                 PositionCaret(pixel, GetLine(caret.line.line_no - 1).Y);
1062                                                 XplatUI.CaretVisible(owner.Handle, true);
1063                                         }
1064                                         return;
1065                                 }
1066
1067                                 case CaretDirection.LineDown: {
1068                                         if (caret.line.line_no < lines) {
1069                                                 int     pixel;
1070
1071                                                 pixel = (int)caret.line.widths[caret.pos];
1072                                                 PositionCaret(pixel, GetLine(caret.line.line_no + 1).Y);
1073                                                 XplatUI.CaretVisible(owner.Handle, true);
1074                                         }
1075                                         return;
1076                                 }
1077
1078                                 case CaretDirection.Home: {
1079                                         if (caret.pos > 0) {
1080                                                 caret.pos = 0;
1081                                                 caret.tag = caret.line.tags;
1082                                                 UpdateCaret();
1083                                         }
1084                                         return;
1085                                 }
1086
1087                                 case CaretDirection.End: {
1088                                         if (caret.pos < caret.line.text.Length) {
1089                                                 caret.pos = caret.line.text.Length;
1090                                                 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1091                                                 UpdateCaret();
1092                                         }
1093                                         return;
1094                                 }
1095
1096                                 case CaretDirection.PgUp: {
1097                                         return;
1098                                 }
1099
1100                                 case CaretDirection.PgDn: {
1101                                         return;
1102                                 }
1103
1104                                 case CaretDirection.CtrlHome: {
1105                                         caret.line = GetLine(1);
1106                                         caret.pos = 0;
1107                                         caret.tag = caret.line.tags;
1108
1109                                         UpdateCaret();
1110                                         return;
1111                                 }
1112
1113                                 case CaretDirection.CtrlEnd: {
1114                                         caret.line = GetLine(lines);
1115                                         caret.pos = 0;
1116                                         caret.tag = caret.line.tags;
1117
1118                                         UpdateCaret();
1119                                         return;
1120                                 }
1121                         }
1122                 }
1123
1124                 // Draw the document
1125                 public void Draw(Graphics g, Rectangle clip) {
1126                         Line    line;           // Current line being drawn
1127                         LineTag tag;            // Current tag being drawn
1128                         int     start;          // First line to draw
1129                         int     end;            // Last line to draw
1130                         string  s;              // String representing the current line
1131                         int     line_no;        //
1132
1133                         // First, figure out from what line to what line we need to draw
1134                         start = GetLineByPixel(clip.Top - viewport_y, false).line_no;
1135                         end = GetLineByPixel(clip.Bottom - viewport_y, false).line_no;
1136
1137                         // Now draw our elements; try to only draw those that are visible
1138                         line_no = start;
1139
1140                         #if Debug
1141                                 DateTime        n = DateTime.Now;
1142                                 Console.WriteLine("Started drawing: {0}s {1}ms", n.Second, n.Millisecond);
1143                         #endif
1144
1145                         while (line_no <= end) {
1146                                 line = GetLine(line_no);
1147                                 tag = line.tags;
1148                                 s = line.text.ToString();
1149                                 while (tag != null) {
1150                                         if (((tag.X + tag.width) > (clip.Left - viewport_x)) || (tag.X < (clip.Right - viewport_x))) {
1151                                                 // Check for selection
1152                                                 if ((!selection_visible) || (line_no < selection_start.line.line_no) || (line_no > selection_end.line.line_no)) {
1153                                                         // regular drawing
1154                                                         g.DrawString(s.Substring(tag.start-1, tag.length), tag.font, tag.color, tag.X - viewport_x, line.Y + tag.shift  - viewport_y, StringFormat.GenericTypographic);
1155                                                 } else {
1156                                                         // we might have to draw our selection
1157                                                         if ((line_no != selection_start.line.line_no) && (line_no != selection_end.line.line_no)) {
1158                                                                 g.FillRectangle(tag.color, tag.X - viewport_x, line.Y + tag.shift  - viewport_y, line.widths[tag.start + tag.length - 1], tag.height);
1159                                                                 g.DrawString(s.Substring(tag.start-1, tag.length), tag.font, ThemeEngine.Current.ResPool.GetSolidBrush(owner.BackColor), tag.X - viewport_x, line.Y + tag.shift  - viewport_y, StringFormat.GenericTypographic);
1160                                                         } else {
1161                                                                 int     middle;
1162                                                                 bool    highlight;
1163                                                                 bool    partial;
1164
1165                                                                 highlight = false;
1166                                                                 partial = false;
1167
1168                                                                 // Check the partial drawings first
1169                                                                 if ((selection_start.tag == tag) && (selection_end.tag == tag)) {
1170                                                                         partial = true;
1171
1172                                                                         // First, the regular part
1173                                                                         g.DrawString(s.Substring(tag.start - 1, selection_start.pos - tag.start + 1), tag.font, tag.color, tag.X - viewport_x, line.Y + tag.shift  - viewport_y, StringFormat.GenericTypographic);
1174
1175                                                                         // Now the highlight
1176                                                                         g.FillRectangle(tag.color, line.widths[selection_start.pos], line.Y + tag.shift - viewport_y, line.widths[selection_end.pos] - line.widths[selection_start.pos], tag.height);
1177                                                                         g.DrawString(s.Substring(selection_start.pos, selection_end.pos - selection_start.pos), tag.font, ThemeEngine.Current.ResPool.GetSolidBrush(owner.BackColor), line.widths[selection_start.pos], line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
1178
1179                                                                         // And back to the regular
1180                                                                         g.DrawString(s.Substring(selection_end.pos, tag.length - selection_end.pos), tag.font, tag.color, line.widths[selection_end.pos], line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
1181                                                                 } else if (selection_start.tag == tag) {
1182                                                                         partial = true;
1183
1184                                                                         // The highlighted part
1185                                                                         g.FillRectangle(tag.color, line.widths[selection_start.pos], line.Y + tag.shift - viewport_y, line.widths[tag.start + tag.length - 1], tag.height);
1186                                                                         g.DrawString(s.Substring(selection_start.pos, tag.length - selection_start.pos), tag.font, ThemeEngine.Current.ResPool.GetSolidBrush(owner.BackColor), line.widths[selection_start.pos], line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
1187
1188                                                                         // The regular part
1189                                                                         g.DrawString(s.Substring(tag.start - 1, selection_start.pos - tag.start + 1), tag.font, tag.color, tag.X - viewport_x, line.Y + tag.shift  - viewport_y, StringFormat.GenericTypographic);
1190                                                                 } else if (selection_end.tag == tag) {
1191                                                                         partial = true;
1192
1193                                                                         // The highlighted part
1194                                                                         g.FillRectangle(tag.color, tag.X - viewport_x, line.Y + tag.shift - viewport_y, line.widths[selection_end.pos], tag.height);
1195                                                                         g.DrawString(s.Substring(tag.start - 1, selection_end.pos - tag.start + 1), tag.font, ThemeEngine.Current.ResPool.GetSolidBrush(owner.BackColor), tag.X - viewport_x, line.Y + tag.shift  - viewport_y, StringFormat.GenericTypographic);
1196
1197                                                                         // The regular part
1198                                                                         g.DrawString(s.Substring(selection_end.pos, tag.length - selection_end.pos), tag.font, tag.color, line.widths[selection_end.pos], line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
1199                                                                 } else {
1200                                                                         // no partially selected tags here, simple checks...
1201                                                                         if (selection_start.line == line) {
1202                                                                                 if ((tag.start + tag.length - 1) > selection_start.pos) {
1203                                                                                         highlight = true;
1204                                                                                 }
1205                                                                         }
1206                                                                         if (selection_end.line == line) {
1207                                                                                 if ((tag.start + tag.length - 1) < selection_start.pos) {
1208                                                                                         highlight = true;
1209                                                                                 }
1210                                                                         }
1211                                                                 }
1212
1213                                                                 if (!partial) {
1214                                                                         if (highlight) {
1215                                                                                 g.FillRectangle(tag.color, tag.X - viewport_x, line.Y + tag.shift  - viewport_y, line.widths[tag.start + tag.length - 1], tag.height);
1216                                                                                 g.DrawString(s.Substring(tag.start-1, tag.length), tag.font, ThemeEngine.Current.ResPool.GetSolidBrush(owner.BackColor), tag.X - viewport_x, line.Y + tag.shift  - viewport_y, StringFormat.GenericTypographic);
1217                                                                         } else {
1218                                                                                 g.DrawString(s.Substring(tag.start-1, tag.length), tag.font, tag.color, tag.X - viewport_x, line.Y + tag.shift  - viewport_y, StringFormat.GenericTypographic);
1219                                                                         }
1220                                                                 }
1221                                                         }
1222
1223                                                 }
1224                                         }
1225
1226                                         tag = tag.next;
1227                                 }
1228
1229                                 line_no++;
1230                         }
1231                         #if Debug
1232                                 n = DateTime.Now;
1233                                 Console.WriteLine("Finished drawing: {0}s {1}ms", n.Second, n.Millisecond);
1234                         #endif
1235
1236                 }
1237
1238
1239                 // Inserts a character at the given position
1240                 public void InsertString(Line line, int pos, string s) {
1241                         InsertString(line.FindTag(pos), pos, s);
1242                 }
1243
1244                 // Inserts a string at the given position
1245                 public void InsertString(LineTag tag, int pos, string s) {
1246                         Line    line;
1247                         int     len;
1248
1249                         len = s.Length;
1250
1251                         line = tag.line;
1252                         line.text.Insert(pos, s);
1253                         tag.length += len;
1254
1255                         tag = tag.next;
1256                         while (tag != null) {
1257                                 tag.start += len;
1258                                 tag = tag.next;
1259                         }
1260                         line.Grow(len);
1261                         line.recalc = true;
1262
1263                         UpdateView(line, pos);
1264                 }
1265
1266                 // Inserts a string at the caret position
1267                 public void InsertStringAtCaret(string s, bool move_caret) {
1268                         LineTag tag;
1269                         int     len;
1270
1271                         len = s.Length;
1272
1273                         caret.line.text.Insert(caret.pos, s);
1274                         caret.tag.length += len;
1275                         
1276                         if (caret.tag.next != null) {
1277                                 tag = caret.tag.next;
1278                                 while (tag != null) {
1279                                         tag.start += len;
1280                                         tag = tag.next;
1281                                 }
1282                         }
1283                         caret.line.Grow(len);
1284                         caret.line.recalc = true;
1285
1286                         UpdateView(caret.line, caret.pos);
1287                         if (move_caret) {
1288                                 caret.pos += len;
1289                                 UpdateCaret();
1290                         }
1291                 }
1292
1293
1294
1295                 // Inserts a character at the given position
1296                 public void InsertChar(Line line, int pos, char ch) {
1297                         InsertChar(line.FindTag(pos), pos, ch);
1298                 }
1299
1300                 // Inserts a character at the given position
1301                 public void InsertChar(LineTag tag, int pos, char ch) {
1302                         Line    line;
1303
1304                         line = tag.line;
1305                         line.text.Insert(pos, ch);
1306                         tag.length++;
1307
1308                         tag = tag.next;
1309                         while (tag != null) {
1310                                 tag.start++;
1311                                 tag = tag.next;
1312                         }
1313                         line.Grow(1);
1314                         line.recalc = true;
1315
1316                         UpdateView(line, pos);
1317                 }
1318
1319                 // Inserts a character at the current caret position
1320                 public void InsertCharAtCaret(char ch, bool move_caret) {
1321                         LineTag tag;
1322
1323                         caret.line.text.Insert(caret.pos, ch);
1324                         caret.tag.length++;
1325                         
1326                         if (caret.tag.next != null) {
1327                                 tag = caret.tag.next;
1328                                 while (tag != null) {
1329                                         tag.start++;
1330                                         tag = tag.next;
1331                                 }
1332                         }
1333                         caret.line.Grow(1);
1334                         caret.line.recalc = true;
1335
1336                         UpdateView(caret.line, caret.pos);
1337                         if (move_caret) {
1338                                 caret.pos++;
1339                                 UpdateCaret();
1340                         }
1341                 }
1342
1343                 // Inserts n characters at the given position; it will not delete past line limits
1344                 public void DeleteChars(LineTag tag, int pos, int count) {
1345                         Line    line;
1346                         bool    streamline;
1347
1348
1349                         streamline = false;
1350                         line = tag.line;
1351
1352                         if (pos == line.text.Length) {
1353                                 return;
1354                         }
1355
1356                         line.text.Remove(pos, count);
1357
1358                         // Check if we're crossing tag boundaries
1359                         if ((pos + count) > (tag.start + tag.length)) {
1360                                 int     left;
1361
1362                                 // We have to delete cross tag boundaries
1363                                 streamline = true;
1364                                 left = count;
1365
1366                                 left -= pos - tag.start;
1367                                 tag.length -= pos - tag.start;
1368
1369                                 tag = tag.next;
1370                                 while ((tag != null) && (left > 0)) {
1371                                         if (tag.length > left) {
1372                                                 tag.length -= left;
1373                                                 left = 0;
1374                                         } else {
1375                                                 left -= tag.length;
1376                                                 tag.length = 0;
1377         
1378                                                 tag = tag.next;
1379                                         }
1380                                 }
1381                         } else {
1382                                 // We got off easy, same tag
1383
1384                                 tag.length -= count;
1385                         }
1386
1387                         tag = tag.next;
1388                         while (tag != null) {
1389                                 tag.start -= count;
1390                                 tag = tag.next;
1391                         }
1392
1393                         line.recalc = true;
1394                         if (streamline) {
1395                                 line.Streamline();
1396                         }
1397
1398                         UpdateView(line, pos);
1399                 }
1400
1401
1402                 // Deletes a character at or after the given position (depending on forward); it will not delete past line limits
1403                 public void DeleteChar(LineTag tag, int pos, bool forward) {
1404                         Line    line;
1405                         bool    streamline;
1406
1407                         streamline = false;
1408                         line = tag.line;
1409
1410                         if ((pos == 0 && forward == false) || (pos == line.text.Length && forward == true)) {
1411                                 return;
1412                         }
1413
1414                         if (forward) {
1415                                 line.text.Remove(pos, 1);
1416                                 tag.length--;
1417
1418                                 if (tag.length == 0) {
1419                                         streamline = true;
1420                                 }
1421                         } else {
1422                                 pos--;
1423                                 line.text.Remove(pos, 1);
1424                                 if (pos >= (tag.start - 1)) {
1425                                         tag.length--;
1426                                         if (tag.length == 0) {
1427                                                 streamline = true;
1428                                         }
1429                                 } else if (tag.previous != null) {
1430                                         tag.previous.length--;
1431                                         if (tag.previous.length == 0) {
1432                                                 streamline = true;
1433                                         }
1434                                 }
1435                         }
1436
1437                         tag = tag.next;
1438                         while (tag != null) {
1439                                 tag.start--;
1440                                 tag = tag.next;
1441                         }
1442                         line.recalc = true;
1443                         if (streamline) {
1444                                 line.Streamline();
1445                         }
1446
1447                         UpdateView(line, pos);
1448                 }
1449
1450                 // Combine two lines
1451                 public void Combine(int FirstLine, int SecondLine) {
1452                         Combine(GetLine(FirstLine), GetLine(SecondLine));
1453                 }
1454
1455                 public void Combine(Line first, Line second) {
1456                         LineTag last;
1457                         int     shift;
1458
1459                         // Combine the two tag chains into one
1460                         last = first.tags;
1461
1462                         while (last.next != null) {
1463                                 last = last.next;
1464                         }
1465
1466                         last.next = second.tags;
1467                         last.next.previous = last;
1468
1469                         shift = last.start + last.length - 1;
1470
1471                         // Fix up references within the chain
1472                         last = last.next;
1473                         while (last != null) {
1474                                 last.line = first;
1475                                 last.start += shift;
1476                                 last = last.next;
1477                         }
1478
1479                         // Combine both lines' strings
1480                         first.text.Insert(first.text.Length, second.text.ToString());
1481                         first.Grow(first.text.Length);
1482
1483                         // Remove the reference to our (now combined) tags from the doomed line
1484                         second.tags = null;
1485
1486                         // Renumber lines
1487                         DecrementLines(first.line_no + 2);      // first.line_no + 1 will be deleted, so we need to start renumbering one later
1488
1489                         // Mop up
1490                         first.recalc = true;
1491                         first.height = 0;       // This forces RecalcDocument/UpdateView to redraw from this line on
1492                         first.Streamline();
1493
1494                         #if Debug
1495                                 Line    check_first;
1496                                 Line    check_second;
1497
1498                                 check_first = GetLine(first.line_no);
1499                                 check_second = GetLine(check_first.line_no + 1);
1500
1501                                 Console.WriteLine("Pre-delete: Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
1502                         #endif
1503
1504                         this.Delete(second);
1505
1506                         #if Debug
1507                                 check_first = GetLine(first.line_no);
1508                                 check_second = GetLine(check_first.line_no + 1);
1509
1510                                 Console.WriteLine("Post-delete Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
1511                         #endif
1512
1513                 }
1514
1515                 // Split the line at the position into two
1516                 public void Split(int LineNo, int pos) {
1517                         Line    line;
1518                         LineTag tag;
1519
1520                         line = GetLine(LineNo);
1521                         tag = LineTag.FindTag(line, pos);
1522                         Split(line, tag, pos);
1523                 }
1524
1525                 public void Split(Line line, int pos) {
1526                         LineTag tag;
1527
1528                         tag = LineTag.FindTag(line, pos);
1529                         Split(line, tag, pos);
1530                 }
1531
1532                 public void Split(Line line, LineTag tag, int pos) {
1533                         LineTag new_tag;
1534                         Line    new_line;
1535
1536                         // cover the easy case first
1537                         if (pos == line.text.Length) {
1538                                 Add(line.line_no + 1, "", tag.font, tag.color);
1539                                 return;
1540                         }
1541
1542                         // We need to move the rest of the text into the new line
1543                         Add(line.line_no + 1, line.text.ToString(pos, line.text.Length - pos), tag.font, tag.color);
1544
1545                         // Now transfer our tags from this line to the next
1546                         new_line = GetLine(line.line_no + 1);
1547                         line.recalc = true;
1548
1549                         if ((tag.start - 1) == pos) {
1550                                 int     shift;
1551
1552                                 // We can simply break the chain and move the tag into the next line
1553                                 if (tag == line.tags) {
1554                                         new_tag = new LineTag(line, 1, 0);
1555                                         new_tag.font = tag.font;
1556                                         new_tag.color = tag.color;
1557                                         line.tags = new_tag;
1558                                 }
1559
1560                                 if (tag.previous != null) {
1561                                         tag.previous.next = null;
1562                                 }
1563                                 new_line.tags = tag;
1564                                 tag.previous = null;
1565                                 tag.line = new_line;
1566
1567                                 // Walk the list and correct the start location of the tags we just bumped into the next line
1568                                 shift = tag.start - 1;
1569
1570                                 new_tag = tag;
1571                                 while (new_tag != null) {
1572                                         new_tag.start -= shift;
1573                                         new_tag.line = new_line;
1574                                         new_tag = new_tag.next;
1575                                 }
1576                         } else {
1577                                 int     shift;
1578
1579                                 new_tag = new LineTag(new_line, 1, tag.start - 1 + tag.length - pos);
1580                                 new_tag.next = tag.next;
1581                                 new_tag.font = tag.font;
1582                                 new_tag.color = tag.color;
1583                                 new_line.tags = new_tag;
1584                                 if (new_tag.next != null) {
1585                                         new_tag.next.previous = new_tag;
1586                                 }
1587                                 tag.next = null;
1588                                 tag.length = pos - tag.start + 1;
1589
1590                                 shift = pos;
1591                                 new_tag = new_tag.next;
1592                                 while (new_tag != null) {
1593                                         new_tag.start -= shift;
1594                                         new_tag.line = new_line;
1595                                         new_tag = new_tag.next;
1596
1597                                 }
1598                         }
1599                         line.text.Remove(pos, line.text.Length - pos);
1600                 }
1601
1602                 // Adds a line of text, with given font.
1603                 // Bumps any line at that line number that already exists down
1604                 public void Add(int LineNo, string Text, Font font, Brush color) {
1605                         Line    add;
1606                         Line    line;
1607                         int     line_no;
1608
1609                         if (LineNo<1 || Text == null) {
1610                                 if (LineNo<1) {
1611                                         throw new ArgumentNullException("LineNo", "Line numbers must be positive");
1612                                 } else {
1613                                         throw new ArgumentNullException("Text", "Cannot insert NULL line");
1614                                 }
1615                         }
1616
1617                         add = new Line(LineNo, Text, font, color);
1618
1619                         line = document;
1620                         while (line != sentinel) {
1621                                 add.parent = line;
1622                                 line_no = line.line_no;
1623
1624                                 if (LineNo > line_no) {
1625                                         line = line.right;
1626                                 } else if (LineNo < line_no) {
1627                                         line = line.left;
1628                                 } else {
1629                                         // Bump existing line numbers; walk all nodes to the right of this one and increment line_no
1630                                         IncrementLines(line.line_no);
1631                                         line = line.left;
1632                                 }
1633                         }
1634
1635                         add.left = sentinel;
1636                         add.right = sentinel;
1637
1638                         if (add.parent != null) {
1639                                 if (LineNo > add.parent.line_no) {
1640                                         add.parent.right = add;
1641                                 } else {
1642                                         add.parent.left = add;
1643                                 }
1644                         } else {
1645                                 // Root node
1646                                 document = add;
1647                         }
1648
1649                         RebalanceAfterAdd(add);
1650
1651                         lines++;
1652                 }
1653
1654                 public virtual void Clear() {
1655                         lines = 0;
1656                         document = sentinel;
1657                 }
1658
1659                 public virtual object Clone() {
1660                         Document clone;
1661
1662                         clone = new Document(null);
1663
1664                         clone.lines = this.lines;
1665                         clone.document = (Line)document.Clone();
1666
1667                         return clone;
1668                 }
1669
1670                 public void Delete(int LineNo) {
1671                         if (LineNo>lines) {
1672                                 return;
1673                         }
1674
1675                         Delete(GetLine(LineNo));
1676                 }
1677
1678                 public void Delete(Line line1) {
1679                         Line    line2;// = new Line();
1680                         Line    line3;
1681
1682                         if ((line1.left == sentinel) || (line1.right == sentinel)) {
1683                                 line3 = line1;
1684                         } else {
1685                                 line3 = line1.right;
1686                                 while (line3.left != sentinel) {
1687                                         line3 = line3.left;
1688                                 }
1689                         }
1690
1691                         if (line3.left != sentinel) {
1692                                 line2 = line3.left;
1693                         } else {
1694                                 line2 = line3.right;
1695                         }
1696
1697                         line2.parent = line3.parent;
1698                         if (line3.parent != null) {
1699                                 if(line3 == line3.parent.left) {
1700                                         line3.parent.left = line2;
1701                                 } else {
1702                                         line3.parent.right = line2;
1703                                 }
1704                         } else {
1705                                 document = line2;
1706                         }
1707
1708                         if (line3 != line1) {
1709                                 LineTag tag;
1710
1711                                 line1.ascent = line3.ascent;
1712                                 line1.height = line3.height;
1713                                 line1.line_no = line3.line_no;
1714                                 line1.recalc = line3.recalc;
1715                                 line1.space = line3.space;
1716                                 line1.tags = line3.tags;
1717                                 line1.text = line3.text;
1718                                 line1.widths = line3.widths;
1719                                 line1.Y = line3.Y;
1720
1721                                 tag = line1.tags;
1722                                 while (tag != null) {
1723                                         tag.line = line1;
1724                                         tag = tag.next;
1725                                 }
1726                         }
1727
1728                         if (line3.color == LineColor.Black)
1729                                 RebalanceAfterDelete(line2);
1730
1731                         this.lines--;
1732
1733                         last_found = sentinel;
1734                 }
1735
1736                 // Set our selection markers
1737                 public void Invalidate(Line start, int start_pos, Line end, int end_pos) {
1738                         Line    l1;
1739                         Line    l2;
1740                         int     p1;
1741                         int     p2;
1742         
1743                         // figure out what's before what so the logic below is straightforward
1744                         if (start.line_no < end.line_no) {
1745                                 l1 = start;
1746                                 p1 = start_pos;
1747
1748                                 l2 = end;
1749                                 p2 = end_pos;
1750                         } else if (start.line_no > end.line_no) {
1751                                 l1 = end;
1752                                 p1 = end_pos;
1753
1754                                 l2 = start;
1755                                 p2 = start_pos;
1756                         } else {
1757                                 if (start_pos < end_pos) {
1758                                         l1 = start;
1759                                         p1 = start_pos;
1760
1761                                         l2 = end;
1762                                         p2 = end_pos;
1763                                 } else {
1764                                         l1 = end;
1765                                         p1 = end_pos;
1766
1767                                         l2 = start;
1768                                         p2 = start_pos;
1769                                 }
1770
1771                                 owner.Invalidate(new Rectangle((int)l1.widths[p1] - viewport_x, l1.Y - viewport_y, (int)l2.widths[p2] - (int)l1.widths[p1], l1.height));
1772                                 return;
1773                         }
1774
1775                         // Three invalidates:
1776                         // First line from start
1777                         owner.Invalidate(new Rectangle((int)l1.widths[p1] - viewport_x, l1.Y - viewport_y, owner.Width, l1.height));
1778
1779                         // lines inbetween
1780                         if ((l1.line_no + 1) < l2.line_no) {
1781                                 int     y;
1782
1783                                 y = GetLine(l1.line_no + 1).Y;
1784                                 owner.Invalidate(new Rectangle(0 - viewport_x, y - viewport_y, owner.Width, GetLine(l2.line_no - 1).Y - y - viewport_y));
1785                         }
1786
1787                         // Last line to end
1788                         owner.Invalidate(new Rectangle((int)l1.widths[p1] - viewport_x, l1.Y - viewport_y, owner.Width, l1.height));
1789
1790
1791                 }
1792
1793                 // It's nothing short of pathetic to always invalidate the whole control
1794                 // I will find time to finish the optimization and make it invalidate deltas only
1795                 public void SetSelectionToCaret(bool start) {
1796                         if (start) {
1797                                 selection_start.line = caret.line;
1798                                 selection_start.tag = caret.tag;
1799                                 selection_start.pos = caret.pos;
1800
1801                                 // start always also selects end
1802                                 selection_end.line = caret.line;
1803                                 selection_end.tag = caret.tag;
1804                                 selection_end.pos = caret.pos;
1805                         } else {
1806                                 selection_end.line = caret.line;
1807                                 selection_end.tag = caret.tag;
1808                                 selection_end.pos = caret.pos;
1809                         }
1810
1811
1812                         if ((selection_start.line == selection_end.line) && (selection_start.pos == selection_end.pos)) {
1813                                 selection_visible = false;
1814                         } else {
1815                                 selection_visible = true;
1816                         }
1817                         owner.Invalidate();
1818                 }
1819
1820 #if buggy
1821                 public void SetSelection(Line start, int start_pos, Line end, int end_pos) {
1822                         // Ok, this is ugly, bad and slow, but I don't have time right now to optimize it.
1823                         if (selection_visible) {
1824                                 // Try to only invalidate what's changed so we don't redraw the whole thing
1825                                 if ((start != selection_start.line) || (start_pos != selection_start.pos) && ((end == selection_end.line) && (end_pos == selection_end.pos))) {
1826                                         Invalidate(start, start_pos, selection_start.line, selection_start.pos);
1827                                 } else if ((end != selection_end.line) || (end_pos != selection_end.pos) && ((start == selection_start.line) && (start_pos == selection_start.pos))) {
1828                                         Invalidate(end, end_pos, selection_end.line, selection_end.pos);
1829                                 } else {
1830                                         // both start and end changed
1831                                         Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
1832                                 }
1833                         }
1834                         selection_start.line = start;
1835                         selection_start.pos = start_pos;
1836 if (start != null) {
1837                         selection_start.tag = LineTag.FindTag(start, start_pos);
1838 }
1839
1840                         selection_end.line = end;
1841                         selection_end.pos = end_pos;
1842 if (end != null) {
1843                         selection_end.tag = LineTag.FindTag(end, end_pos);
1844 }
1845
1846                         if (((start == end) && (start_pos == end_pos)) || start == null || end == null) {
1847                                 selection_visible = false;
1848                         } else {
1849                                 selection_visible = true;
1850                                 
1851                         }
1852                 }
1853 #endif
1854                 // Make sure that start is always before end
1855                 private void FixupSelection() {
1856                         if (selection_start.line.line_no > selection_end.line.line_no) {
1857                                 Line    line;
1858                                 int     pos;
1859                                 LineTag tag;
1860
1861                                 line = selection_start.line;
1862                                 tag = selection_start.tag;
1863                                 pos = selection_start.pos;
1864
1865                                 selection_start.line = selection_end.line;
1866                                 selection_start.tag =  selection_end.tag;
1867                                 selection_start.pos = selection_end.pos;
1868                                 
1869                                 selection_end.line = line;
1870                                 selection_end.tag =  tag;
1871                                 selection_end.pos = pos;
1872
1873                                 return;
1874                         }
1875
1876                         if ((selection_start.line == selection_end.line) && (selection_start.pos > selection_end.pos)) {
1877                                 int     pos;
1878
1879                                 pos = selection_start.pos;
1880                                 selection_start.pos = selection_end.pos;
1881                                 selection_end.pos = pos;
1882                                 return;
1883                         }
1884
1885                         return;
1886                 }
1887
1888                 public void SetSelectionStart(Line start, int start_pos) {
1889                         selection_start.line = start;
1890                         selection_start.pos = start_pos;
1891                         selection_start.tag = LineTag.FindTag(start, start_pos);
1892
1893                         FixupSelection();
1894
1895                         if ((selection_end.line != selection_start.line) && (selection_end.pos != selection_start.pos)) {
1896                                 selection_visible = true;
1897                         }
1898
1899                         if (selection_visible) {
1900                                 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
1901                         }
1902
1903                 }
1904
1905                 public void SetSelectionEnd(Line end, int end_pos) {
1906                         selection_end.line = end;
1907                         selection_end.pos = end_pos;
1908                         selection_end.tag = LineTag.FindTag(end, end_pos);
1909
1910                         FixupSelection();
1911
1912                         if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
1913                                 selection_visible = true;
1914                         }
1915
1916                         if (selection_visible) {
1917                                 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
1918                         }
1919                 }
1920
1921                 public void SetSelection(Line start, int start_pos) {
1922                         if (selection_visible) {
1923                                 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
1924                         }
1925
1926
1927                         selection_start.line = start;
1928                         selection_start.pos = start_pos;
1929                         selection_start.tag = LineTag.FindTag(start, start_pos);
1930
1931                         selection_end.line = start;
1932                         selection_end.tag = selection_start.tag;
1933                         selection_end.pos = start_pos;
1934
1935                         selection_visible = false;
1936                 }
1937
1938                 public void InvalidateSelectionArea() {
1939                         // implement me
1940                 }
1941
1942                 // Return the current selection, as string
1943                 public string GetSelection() {
1944                         // We return String.Empty if there is no selection
1945                         if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
1946                                 return string.Empty;
1947                         }
1948
1949                         if (!multiline || (selection_start.line == selection_end.line)) {
1950                                 return selection_start.line.text.ToString(selection_start.pos, selection_end.pos - selection_start.pos);
1951                         } else {
1952                                 StringBuilder   sb;
1953                                 int             i;
1954                                 int             start;
1955                                 int             end;
1956
1957                                 sb = new StringBuilder();
1958                                 start = selection_start.line.line_no;
1959                                 end = selection_end.line.line_no;
1960
1961                                 sb.Append(selection_start.line.text.ToString(selection_start.pos, selection_start.line.text.Length - selection_start.pos) + "\n");
1962
1963                                 if ((start + 1) < end) {
1964                                         for (i = start + 1; i < end; i++) {
1965                                                 sb.Append(GetLine(i).text.ToString() + "\n");
1966                                         }
1967                                 }
1968
1969                                 sb.Append(selection_end.line.text.ToString(0, selection_end.pos));
1970
1971                                 return sb.ToString();
1972                         }
1973                 }
1974
1975                 public void ReplaceSelection(string s) {
1976                         // The easiest is to break the lines where the selection tags are and delete those lines
1977                         if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
1978                                 // Nothing to delete, simply insert
1979                                 InsertString(selection_start.tag, selection_start.pos, s);
1980                         }
1981
1982                         if (!multiline || (selection_start.line == selection_end.line)) {
1983                                 DeleteChars(selection_start.tag, selection_start.pos, selection_end.pos - selection_start.pos);
1984
1985                                 // The tag might have been removed, we need to recalc it
1986                                 selection_start.tag = selection_start.line.FindTag(selection_start.pos);
1987
1988                                 InsertString(selection_start.tag, selection_start.pos, s);
1989                         } else {
1990                                 int             i;
1991                                 int             start;
1992                                 int             end;
1993                                 int             base_line;
1994                                 string[]        ins;
1995                                 int             insert_lines;
1996
1997                                 start = selection_start.line.line_no;
1998                                 end = selection_end.line.line_no;
1999
2000                                 // Delete first line
2001                                 DeleteChars(selection_start.tag, selection_start.pos, selection_end.pos - selection_start.pos);
2002
2003                                 start++;
2004                                 if (start < end) {
2005                                         for (i = end - 1; i >= start; i++) {
2006                                                 Delete(i);
2007                                         }
2008                                 }
2009
2010                                 // Delete last line
2011                                 DeleteChars(selection_end.line.tags, 0, selection_end.pos);
2012
2013                                 ins = s.Split(new char[] {'\n'});
2014
2015                                 insert_lines = ins.Length;
2016
2017                                 // Bump the text at insertion point a line down if we're inserting more than one line
2018                                 if (insert_lines > 1) {
2019                                         Split(selection_start.line, selection_start.pos);
2020
2021                                         // Reminder of start line is now in startline+1
2022
2023                                         // if the last line does not end with a \n we will insert the last line in front of the just moved text
2024                                         if (s.EndsWith("\n")) {
2025                                                 insert_lines--; // We don't want to insert the last line as part of the loop anymore
2026
2027                                                 InsertString(GetLine(selection_start.line.line_no + 1), 0, ins[insert_lines - 1]);
2028                                         }
2029                                 }
2030
2031                                 // Insert the first line
2032                                 InsertString(selection_start.line, selection_start.pos, ins[0]);
2033
2034
2035                                 if (insert_lines > 1) {
2036                                         base_line = selection_start.line.line_no + 1;
2037
2038                                         for (i = 1; i < insert_lines; i++) {
2039                                                 Add(base_line + i, ins[i], selection_start.tag.font, selection_start.tag.color);
2040                                         }
2041                                 }
2042                         }
2043                         selection_end.line = selection_start.line;
2044                         selection_end.pos = selection_start.pos;
2045                         selection_end.tag = selection_start.tag;
2046                         selection_visible = false;
2047                         InvalidateSelectionArea();
2048                 }
2049
2050                 public int SelectionLength() {
2051                         if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
2052                                 return 0;
2053                         }
2054
2055                         if (!multiline || (selection_start.line == selection_end.line)) {
2056                                 return selection_end.pos - selection_start.pos;
2057                         } else {
2058                                 int     i;
2059                                 int     start;
2060                                 int     end;
2061                                 int     length;
2062                                 Line    line;
2063
2064                                 // Count first and last line
2065                                 length = selection_start.line.text.Length - selection_start.pos + selection_end.pos;
2066
2067                                 // Count the lines in the middle
2068                                 start = selection_start.line.line_no + 1;
2069                                 end = selection_end.line.line_no;
2070
2071                                 if (start < end) {
2072                                         for (i = start; i < end; i++) {
2073                                                 length += GetLine(i).text.Length;
2074                                         }
2075                                 }
2076
2077                                 return length;
2078                         }
2079
2080                         
2081                 }
2082
2083
2084                 // Give it a Line number and it returns the Line object at with that line number
2085                 public Line GetLine(int LineNo) {
2086                         Line    line = document;
2087
2088                         while (line != sentinel) {
2089                                 if (LineNo == line.line_no) {
2090                                         return line;
2091                                 } else if (LineNo < line.line_no) {
2092                                         line = line.left;
2093                                 } else {
2094                                         line = line.right;
2095                                 }
2096                         }
2097
2098                         return null;
2099                 }
2100
2101                 // Give it a Y pixel coordinate and it returns the Line covering that Y coordinate
2102                 ///
2103                 public Line GetLineByPixel(int y, bool exact) {
2104                         Line    line = document;
2105                         Line    last = null;
2106
2107                         while (line != sentinel) {
2108                                 last = line;
2109                                 if ((y >= line.Y) && (y < (line.Y+line.height))) {
2110                                         return line;
2111                                 } else if (y < line.Y) {
2112                                         line = line.left;
2113                                 } else {
2114                                         line = line.right;
2115                                 }
2116                         }
2117
2118                         if (exact) {
2119                                 return null;
2120                         }
2121                         return last;
2122                 }
2123
2124                 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
2125                 public LineTag FindTag(int x, int y, out int index, bool exact) {
2126                         Line    line;
2127                         LineTag tag;
2128
2129                         line = GetLineByPixel(y, exact);
2130                         if (line == null) {
2131                                 index = 0;
2132                                 return null;
2133                         }
2134                         tag = line.tags;
2135
2136                         while (true) {
2137                                 if (x >= tag.X && x < (tag.X+tag.width)) {
2138                                         int     end;
2139
2140                                         end = tag.start + tag.length - 1;
2141
2142                                         for (int pos = tag.start; pos < end; pos++) {
2143                                                 if (x < line.widths[pos]) {
2144                                                         index = pos;
2145                                                         return tag;
2146                                                 }
2147                                         }
2148                                         index=end;
2149                                         return tag;
2150                                 }
2151                                 if (tag.next != null) {
2152                                         tag = tag.next;
2153                                 } else {
2154                                         if (exact) {
2155                                                 index = 0;
2156                                                 return null;
2157                                         }
2158
2159                                         index = line.text.Length;
2160                                         return tag;
2161                                 }
2162                         }
2163                 }
2164
2165                 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
2166                 public LineTag FindCursor(int x, int y, out int index) {
2167                         Line    line;
2168                         LineTag tag;
2169
2170                         line = GetLineByPixel(y, false);
2171                         tag = line.tags;
2172
2173                         while (true) {
2174                                 if (x >= tag.X && x < (tag.X+tag.width)) {
2175                                         int     end;
2176
2177                                         end = tag.start + tag.length - 1;
2178
2179                                         for (int pos = tag.start-1; pos < end; pos++) {
2180                                                 // When clicking on a character, we position the cursor to whatever edge
2181                                                 // of the character the click was closer
2182                                                 if (x < (line.widths[pos] + ((line.widths[pos+1]-line.widths[pos])/2))) {
2183                                                         index = pos;
2184                                                         return tag;
2185                                                 }
2186                                         }
2187                                         index=end;
2188                                         return tag;
2189                                 }
2190                                 if (tag.next != null) {
2191                                         tag = tag.next;
2192                                 } else {
2193                                         index = line.text.Length;
2194                                         return tag;
2195                                 }
2196                         }
2197                 }
2198
2199                 // Calculate formatting for the whole document
2200                 public bool RecalculateDocument(Graphics g) {
2201                         return RecalculateDocument(g, 1, this.lines, false);
2202                 }
2203
2204                 // Calculate formatting starting at a certain line
2205                 public bool RecalculateDocument(Graphics g, int start) {
2206                         return RecalculateDocument(g, start, this.lines, false);
2207                 }
2208
2209                 // Calculate formatting within two given line numbers
2210                 public bool RecalculateDocument(Graphics g, int start, int end) {
2211                         return RecalculateDocument(g, start, end, false);
2212                 }
2213
2214                 // With optimize on, returns true if line heights changed
2215                 public bool RecalculateDocument(Graphics g, int start, int end, bool optimize) {
2216                         Line    line;
2217                         int     line_no;
2218                         int     Y;
2219
2220                         Y = GetLine(start).Y;
2221                         line_no = start;
2222                         if (optimize) {
2223                                 bool    changed;
2224
2225                                 changed = false;
2226
2227                                 while (line_no <= end) {
2228                                         line = GetLine(line_no++);
2229                                         line.Y = Y;
2230                                         if (line.recalc) {
2231                                                 if (line.RecalculateLine(g)) {
2232                                                         changed = true;
2233                                                         // If the height changed, all subsequent lines change
2234                                                         end = this.lines;
2235                                                 }
2236                                                 if (line.widths[line.text.Length] > this.document_x) {
2237                                                         this.document_x = (int)line.widths[line.text.Length];
2238                                                 }
2239                                         }
2240
2241                                         Y += line.height;
2242                                 }
2243
2244                                 return changed;
2245                         } else {
2246                                 while (line_no <= end) {
2247                                         line = GetLine(line_no++);
2248                                         line.Y = Y;
2249                                         line.RecalculateLine(g);
2250                                         if (line.widths[line.text.Length] > this.document_x) {
2251                                                 this.document_x = (int)line.widths[line.text.Length];
2252                                         }
2253                                         Y += line.height;
2254                                 }
2255                                 return true;
2256                         }
2257                 }
2258
2259                 public bool SetCursor(int x, int y) {
2260                         return true;
2261                 }
2262
2263                 public int Size() {
2264                         return lines;
2265                 }
2266                 #endregion      // Public Methods
2267
2268                 #region Administrative
2269                 public IEnumerator GetEnumerator() {
2270                         // FIXME
2271                         return null;
2272                 }
2273
2274                 public override bool Equals(object obj) {
2275                         if (obj == null) {
2276                                 return false;
2277                         }
2278
2279                         if (!(obj is Document)) {
2280                                 return false;
2281                         }
2282
2283                         if (obj == this) {
2284                                 return true;
2285                         }
2286
2287                         if (ToString().Equals(((Document)obj).ToString())) {
2288                                 return true;
2289                         }
2290
2291                         return false;
2292                 }
2293
2294                 public override int GetHashCode() {
2295                         return document_id;
2296                 }
2297
2298                 public override string ToString() {
2299                         return "document " + this.document_id;
2300                 }
2301
2302                 #endregion      // Administrative
2303         }
2304
2305         public class LineTag {
2306                 #region Local Variables;
2307                 // Payload; formatting
2308                 internal Font           font;           // System.Drawing.Font object for this tag
2309                 internal Brush          color;          // System.Drawing.Brush object
2310
2311                 // Payload; text
2312                 internal int            start;          // start, in chars; index into Line.text
2313                 internal int            length;         // length, in chars
2314                 internal bool           r_to_l;         // Which way is the font
2315
2316                 // Drawing support
2317                 internal int            height;         // Height in pixels of the text this tag describes
2318                 internal int            X;              // X location of the text this tag describes
2319                 internal float          width;          // Width in pixels of the text this tag describes
2320                 internal int            ascent;         // Ascent of the font for this tag
2321                 internal int            shift;          // Shift down for this tag, to stay on baseline
2322
2323                 internal int            soft_break;     // Tag is 'broken soft' and continues in the next line
2324
2325                 // Administrative
2326                 internal Line           line;           // The line we're on
2327                 internal LineTag        next;           // Next tag on the same line
2328                 internal LineTag        previous;       // Previous tag on the same line
2329                 #endregion;
2330
2331                 #region Constructors
2332                 public LineTag(Line line, int start, int length) {
2333                         this.line = line;
2334                         this.start = start;
2335                         this.length = length;
2336                         this.X = 0;
2337                         this.width = 0;
2338                 }
2339                 #endregion      // Constructors
2340
2341                 #region Public Methods
2342                 //
2343                 // Applies 'font' to characters starting at 'start' for 'length' chars
2344                 // Removes any previous tags overlapping the same area
2345                 // returns true if lineheight has changed
2346                 //
2347                 public static bool FormatText(Line line, int start, int length, Font font, Brush color) {
2348                         LineTag tag;
2349                         LineTag start_tag;
2350                         LineTag end_tag;
2351                         int     end;
2352                         int     state;
2353                         int     left;
2354                         bool    retval = false;         // Assume line-height doesn't change
2355
2356                         // Too simple?
2357                         if (font.Height != line.height) {
2358                                 retval = true;
2359                         }
2360                         line.recalc = true;             // This forces recalculation of the line in RecalculateDocument
2361
2362                         // A little sanity, not sure if it's needed, might be able to remove for speed
2363                         if (length > line.text.Length) {
2364                                 length = line.text.Length;
2365                         }
2366
2367                         tag = line.tags;
2368                         end = start + length;
2369                         state = 0;
2370
2371                         // Common special case
2372                         if ((start == 1) && (length == tag.length)) {
2373                                 tag.ascent = 0;
2374                                 tag.font = font;
2375                                 tag.color = color;
2376                                 return retval;
2377                         }
2378
2379                         start_tag = FindTag(line, start);
2380                         end_tag = FindTag(line, end);
2381
2382                         tag = new LineTag(line, start, length);
2383                         tag.font = font;
2384                         tag.color = color;
2385
2386                         if (start == 1) {
2387                                 line.tags = tag;
2388                         }
2389
2390                         if (start_tag.start == start) {
2391                                 tag.next = start_tag;
2392                                 tag.previous = start_tag.previous;
2393                                 if (start_tag.previous != null) {
2394                                         start_tag.previous.next = tag;
2395                                 }
2396                                 start_tag.previous = tag;
2397                         } else {
2398                                 // Insert ourselves 'in the middle'
2399                                 if ((start_tag.next != null) && (start_tag.next.start < end)) {
2400                                         tag.next = start_tag.next;
2401                                 } else {
2402                                         tag.next = new LineTag(line, start_tag.start, start_tag.length);
2403                                         tag.next.font = start_tag.font;
2404                                         tag.next.color = start_tag.color;
2405
2406                                         if (start_tag.next != null) {
2407                                                 tag.next.next = start_tag.next;
2408                                                 tag.next.next.previous = tag.next;
2409                                         }
2410                                 }
2411                                 tag.next.previous = tag;
2412
2413                                 start_tag.length = start - start_tag.start;
2414
2415                                 tag.previous = start_tag;
2416                                 start_tag.next = tag;
2417 #if nope
2418                                 if (tag.next.start > (tag.start + tag.length)) {
2419                                         tag.next.length  += tag.next.start - (tag.start + tag.length);
2420                                         tag.next.start = tag.start + tag.length;
2421                                 }
2422 #endif
2423                         }
2424
2425                         // Elimination loop
2426                         tag = tag.next;
2427                         while ((tag != null) && (tag.start < end)) {
2428                                 if ((tag.start + tag.length) <= end) {
2429                                         // remove the tag
2430                                         tag.previous.next = tag.next;
2431                                         if (tag.next != null) {
2432                                                 tag.next.previous = tag.previous;
2433                                         }
2434                                         tag = tag.previous;
2435                                 } else {
2436                                         // Adjust the length of the tag
2437                                         tag.length = (tag.start + tag.length) - end;
2438                                         tag.start = end;
2439                                 }
2440                                 tag = tag.next;
2441                         }
2442
2443                         return retval;
2444                 }
2445
2446
2447                 //
2448                 // Finds the tag that describes the character at position 'pos' on 'line'
2449                 //
2450                 public static LineTag FindTag(Line line, int pos) {
2451                         LineTag tag = line.tags;
2452
2453                         // Beginning of line is a bit special
2454                         if (pos == 0) {
2455                                 return tag;
2456                         }
2457
2458                         while (tag != null) {
2459                                 if ((tag.start <= pos) && (pos < (tag.start+tag.length))) {
2460                                         return tag;
2461                                 }
2462
2463                                 tag = tag.next;
2464                         }
2465
2466                         return null;
2467                 }
2468
2469                 //
2470                 // Combines 'this' tag with 'other' tag.
2471                 //
2472                 public bool Combine(LineTag other) {
2473                         if (!this.Equals(other)) {
2474                                 return false;
2475                         }
2476
2477                         this.width += other.width;
2478                         this.length += other.length;
2479                         this.next = other.next;
2480                         if (this.next != null) {
2481                                 this.next.previous = this;
2482                         }
2483
2484                         return true;
2485                 }
2486
2487
2488                 //
2489                 // Remove 'this' tag ; to be called when formatting is to be removed
2490                 //
2491                 public bool Remove() {
2492                         if ((this.start == 1) && (this.next == null)) {
2493                                 // We cannot remove the only tag
2494                                 return false;
2495                         }
2496                         if (this.start != 1) {
2497                                 this.previous.length += this.length;
2498                                 this.previous.width = -1;
2499                                 this.previous.next = this.next;
2500                                 this.next.previous = this.previous;
2501                         } else {
2502                                 this.next.start = 1;
2503                                 this.next.length += this.length;
2504                                 this.next.width = -1;
2505                                 this.line.tags = this.next;
2506                                 this.next.previous = null;
2507                         }
2508                         return true;
2509                 }
2510
2511
2512                 //
2513                 // Checks if 'this' tag describes the same formatting options as 'obj'
2514                 //
2515                 public override bool Equals(object obj) {
2516                         LineTag other;
2517
2518                         if (obj == null) {
2519                                 return false;
2520                         }
2521
2522                         if (!(obj is LineTag)) {
2523                                 return false;
2524                         }
2525
2526                         if (obj == this) {
2527                                 return true;
2528                         }
2529
2530                         other = (LineTag)obj;
2531
2532                         if (this.font.Equals(other.font) && this.color.Equals(other.color)) {   // FIXME add checking for things like link or type later
2533                                 return true;
2534                         }
2535
2536                         return false;
2537                 }
2538
2539                 public override string ToString() {
2540                         return "Tag starts at index " + this.start + "length " + this.length + " text: " + this.line.Text.Substring(this.start-1, this.length) + "Font " + this.font.ToString();
2541                 }
2542
2543                 #endregion      // Public Methods
2544         }
2545 }