- Now properly aligns text on a baseline, using the new
[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 using System;
30 using System.Collections;
31 using System.Drawing;
32 using System.Drawing.Text;
33 using System.Text;
34
35 namespace System.Windows.Forms {
36         public enum LineColor {
37                 Red     = 0,
38                 Black   = 1
39         }
40
41         public enum CaretDirection {
42                 CharForward,    // Move a char to the right
43                 CharBack,       // Move a char to the left
44                 LineUp,         // Move a line up
45                 LineDown,       // Move a line down
46                 Home,           // Move to the beginning of the line
47                 End,            // Move to the end of the line
48                 PgUp,           // Move one page up
49                 PgDn,           // Move one page down
50                 CtrlHome,       // Move to the beginning of the document
51                 CtrlEnd,        // Move to the end of the document
52                 WordBack,       // Move to the beginning of the previous word (or beginning of line)
53                 WordForward     // Move to the beginning of the next word (or end of line)
54         }
55
56         // Being cloneable should allow for nice line and document copies...
57         public class Line : ICloneable, IComparable {
58                 #region Local Variables
59                 // Stuff that matters for our line
60                 internal StringBuilder          text;                   // Characters for the line
61                 internal float[]                widths;                 // Width of each character; always one larger than text.Length
62                 internal int                    space;                  // Number of elements in text and widths
63                 internal int                    line_no;                // Line number
64                 internal LineTag                tags;                   // Tags describing the text
65                 internal int                    Y;                      // Baseline
66                 internal int                    height;                 // Height of the line (height of tallest tag)
67                 internal int                    ascent;                 // Ascent of the line (ascent of the tallest tag)
68
69                 // Stuff that's important for the tree
70                 internal Line                   parent;                 // Our parent line
71                 public Line                     left;                   // Line with smaller line number
72                 public Line                     right;                  // Line with higher line number
73                 internal LineColor              color;                  // We're doing a black/red tree. this is the node color
74                 internal int                    DEFAULT_TEXT_LEN;       // 
75                 internal static StringFormat    string_format;          // For calculating widths/heights
76                 internal bool                   recalc;                 // Line changed
77                 #endregion      // Local Variables
78
79                 #region Constructors
80                 public Line() {
81                         color = LineColor.Red;
82                         left = null;
83                         right = null;
84                         parent = null;
85                         text = null;
86                         recalc = true;
87
88                         if (string_format == null) {
89                                 string_format = new StringFormat(StringFormat.GenericTypographic);
90                                 string_format.Trimming = StringTrimming.None;
91                                 string_format.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;
92                         }
93                 }
94
95                 public Line(int LineNo, string Text, Font font) : this() {
96                         space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
97
98                         text = new StringBuilder(Text, space);
99                         line_no = LineNo;
100
101                         widths = new float[space + 1];
102                         tags = new LineTag(this, 1, text.Length);
103                         tags.font = font;
104                 }
105
106                 public Line(int LineNo, string Text, LineTag tag) : this() {
107                         space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
108
109                         text = new StringBuilder(Text, space);
110                         line_no = LineNo;
111
112                         widths = new float[space + 1];
113                         tags = tag;
114                 }
115
116                 #endregion      // Constructors
117
118                 #region Public Properties
119                 public int Height {
120                         get {
121                                 return height;
122                         }
123
124                         set {
125                                 height = value;
126                         }
127                 }
128
129                 public int LineNo {
130                         get {
131                                 return line_no;
132                         }
133
134                         set {
135                                 line_no = value;
136                         }
137                 }
138
139                 public string Text {
140                         get {
141                                 return text.ToString();
142                         }
143
144                         set {
145                                 text = new StringBuilder(value, value.Length > DEFAULT_TEXT_LEN ? value.Length : DEFAULT_TEXT_LEN);
146                         }
147                 }
148 #if no
149                 public StringBuilder Text {
150                         get {
151                                 return text;
152                         }
153
154                         set {
155                                 text = value;
156                         }
157                 }
158 #endif
159                 #endregion      // Public Properties
160
161                 #region Public Methods
162                 // Make sure we always have enoughs space in text and widths
163                 public void Grow(int minimum) {
164                         int     length;
165                         float[] new_widths;
166
167                         length = text.Length;
168
169                         if ((length + minimum) > space) {
170                                 // We need to grow; double the size
171
172                                 if ((length + minimum) > (space * 2)) {
173                                         new_widths = new float[length + minimum * 2 + 1];
174                                         space = length + minimum * 2;
175                                 } else {                                
176                                         new_widths = new float[space * 2 + 1];
177                                         space *= 2;
178                                 }
179                                 widths.CopyTo(new_widths, 0);
180
181                                 widths = new_widths;
182                         }
183                 }
184
185                 public void Streamline() {
186                         LineTag current;
187                         LineTag next;
188
189                         current = this.tags;
190                         next = current.next;
191
192                         if (next == null) {
193                                 return;
194                         }
195
196                         while (next != null) {
197                                 // Take out 0 length tags
198                                 if (next.length == 0) {
199                                         current.next = next.next;
200                                         if (current.next != null) {
201                                                 current.next.previous = current;
202                                         }
203                                         next = current.next;
204                                         continue;
205                                 }
206
207                                 if (current.Combine(next)) {
208                                         next = current.next;
209                                         continue;
210                                 }
211
212                                 current = current.next;
213                                 next = current.next;
214                         }
215                 }
216
217                 // Find the tag on a line based on the character position
218                 public LineTag FindTag(int pos) {
219                         LineTag tag;
220
221                         if (pos == 0) {
222                                 return tags;
223                         }
224
225                         tag = this.tags;
226
227                         if (pos > text.Length) {
228                                 pos = text.Length;
229                         }
230
231                         while (tag != null) {
232                                 if ((tag.start <= pos) && (pos < (tag.start + tag.length))) {
233                                         return tag;
234                                 }
235                                 tag = tag.next;
236                         }
237                         return null;
238                 }
239
240
241                 //
242                 // Go through all tags on a line and recalculate all size-related values
243                 // returns true if lineheight changed
244                 //
245                 public bool RecalculateLine(Graphics g) {
246                         LineTag tag;
247                         int     pos;
248                         int     len;
249                         SizeF   size;
250                         float   w;
251                         int     prev_height;
252
253                         pos = 0;
254                         len = this.text.Length;
255                         tag = this.tags;
256                         prev_height = this.height;      // For drawing optimization calculations
257                         this.height = 0;                // Reset line height
258                         this.ascent = 0;                // Reset the ascent for the line
259                         tag.width = 0;
260                         widths[0] = 0;
261                         this.recalc = false;
262
263                         while (pos < len) {
264                                 size = g.MeasureString(this.text.ToString(pos, 1), tag.font, 10000, string_format);
265
266                                 w = size.Width;
267
268                                 tag.width += w;
269
270                                 pos++;
271
272                                 widths[pos] = widths[pos-1] + w;
273
274                                 if (pos == (tag.start-1 + tag.length)) {
275                                         // We just found the end of our current tag
276                                         tag.height = (int)tag.font.Height;
277                                         // Check if we're the tallest on the line (so far)
278                                         if (tag.height > this.height) {
279                                                 this.height = tag.height;               // Yep; make sure the line knows
280                                         }
281
282                                         if (tag.ascent == 0) {
283                                                 int     descent;
284
285                                                 XplatUI.GetFontMetrics(g, tag.font, out tag.ascent, out descent);
286                                         }
287
288                                         if (tag.ascent > this.ascent) {
289                                                 LineTag         t;
290
291                                                 // We have a tag that has a taller ascent than the line;
292                                                 t = tags;
293                                                 while (t != tag) {
294                                                         t.shift = tag.ascent - this.ascent;
295                                                         t = t.next;
296                                                 }
297
298                                                 // Save on our line
299                                                 this.ascent = tag.ascent;
300                                         } else {
301                                                 tag.shift = this.ascent - tag.ascent;
302                                         }
303
304                                         // Update our horizontal starting pixel position
305                                         if (tag.previous == null) {
306                                                 tag.X = 0;
307                                         } else {
308                                                 tag.X = tag.previous.X + (int)tag.previous.width;
309                                         }
310
311                                         tag = tag.next;
312                                         if (tag != null) {
313                                                 tag.width = 0;
314                                         }
315                                 }
316                         }
317
318                         if (this.height == 0) {
319                                 this.height = tags.font.Height;
320                                 tag.height = this.height;
321                         }
322
323                         if (prev_height != this.height) {
324                                 return true;
325                         }
326                         return false;
327                 }
328                 #endregion      // Public Methods
329
330                 #region Administrative
331                 public int CompareTo(object obj) {
332                         if (obj == null) {
333                                 return 1;
334                         }
335
336                         if (! (obj is Line)) {
337                                 throw new ArgumentException("Object is not of type Line", "obj");
338                         }
339
340                         if (line_no < ((Line)obj).line_no) {
341                                 return -1;
342                         } else if (line_no > ((Line)obj).line_no) {
343                                 return 1;
344                         } else {
345                                 return 0;
346                         }
347                 }
348
349                 public object Clone() {
350                         Line    clone;
351
352                         clone = new Line();
353
354                         clone.text = text;
355
356                         if (left != null) {
357                                 clone.left = (Line)left.Clone();
358                         }
359
360                         if (left != null) {
361                                 clone.left = (Line)left.Clone();
362                         }
363
364                         return clone;
365                 }
366
367                 public object CloneLine() {
368                         Line    clone;
369
370                         clone = new Line();
371
372                         clone.text = text;
373
374                         return clone;
375                 }
376
377                 public override bool Equals(object obj) {
378                         if (obj == null) {
379                                 return false;
380                         }
381
382                         if (!(obj is Line)) {
383                                 return false;
384                         }
385
386                         if (obj == this) {
387                                 return true;
388                         }
389
390                         if (line_no == ((Line)obj).line_no) {
391                                 return true;
392                         }
393
394                         return false;
395                 }
396
397
398                 public override string ToString() {
399                         return "Line " + line_no;
400                 }
401
402                 #endregion      // Administrative
403         }
404
405         public class Document : ICloneable, IEnumerable {
406                 #region Structures
407                 internal struct Marker {
408                         internal Line           line;
409                         internal LineTag        tag;
410                         internal int            pos;
411                         internal int            height;
412                 }
413                 #endregion Structures
414
415                 #region Local Variables
416                 private Line            document;
417                 private int             lines;
418                 private static Line     sentinel;
419                 private Line            last_found;
420                 private int             document_id;
421                 private Random          random = new Random();
422
423                 internal bool           multiline;
424
425                 private Line            selection_start_line;
426                 private int             selection_start_pos;
427                 private Line            selection_end_line;
428                 private int             selection_end_pos;
429
430                 internal Marker         caret;
431                 internal Marker         selection_start;
432                 internal Marker         selection_end;
433
434                 internal int            viewport_x;
435                 internal int            viewport_y;             // The visible area of the document
436
437                 internal int            document_x;             // Width of the document
438                 internal int            document_y;             // Height of the document
439
440                 internal Control        owner;                  // Who's owning us?
441                 #endregion      // Local Variables
442
443                 #region Constructors
444                 public Document(Control owner) {
445                         lines = 0;
446
447                         this.owner = owner;
448
449                         multiline = true;
450
451                         // Tree related stuff
452                         sentinel = new Line();
453                         sentinel.color = LineColor.Black;
454
455                         document = sentinel;
456                         last_found = sentinel;
457
458                         // We always have a blank line
459                         Add(1, "", owner.Font);
460                         this.RecalculateDocument(owner.CreateGraphics());
461                         PositionCaret(0, 0);
462                         lines=1;
463
464                         // Default selection is empty
465
466                         document_id = random.Next();
467                 }
468                 #endregion
469
470                 #region Public Properties
471                 public Line Root {
472                         get {
473                                 return document;
474                         }
475
476                         set {
477                                 document = value;
478                         }
479                 }
480
481                 public int Lines {
482                         get {
483                                 return lines;
484                         }
485                 }
486
487                 public Line CaretLine {
488                         get {
489                                 return caret.tag.line;
490                         }
491                 }
492
493                 public int CaretPosition {
494                         get {
495                                 return caret.pos;
496                         }
497                 }
498
499                 public LineTag CaretTag {
500                         get {
501                                 return caret.tag;
502                         }
503                 }
504
505                 public int ViewPortX {
506                         get {
507                                 return viewport_x;
508                         }
509
510                         set {
511                                 viewport_x = value;
512                         }
513                 }
514
515                 public int ViewPortY {
516                         get {
517                                 return viewport_y;
518                         }
519
520                         set {
521                                 viewport_y = value;
522                         }
523                 }
524
525                 #endregion      // Public Properties
526
527                 #region Private Methods
528                 // For debugging
529                 internal void DumpTree(Line line, bool with_tags) {
530                         Console.Write("Line {0}, Y: {1} Text {2}", line.line_no, line.Y, line.text.ToString());
531
532                         if (line.left == sentinel) {
533                                 Console.Write(", left = sentinel");
534                         } else if (line.left == null) {
535                                 Console.Write(", left = NULL");
536                         }
537
538                         if (line.right == sentinel) {
539                                 Console.Write(", right = sentinel");
540                         } else if (line.right == null) {
541                                 Console.Write(", right = NULL");
542                         }
543
544                         Console.WriteLine("");
545
546                         if (with_tags) {
547                                 LineTag tag;
548                                 int     count;
549
550                                 tag = line.tags;
551                                 count = 1;
552                                 Console.Write("   Tags: ");
553                                 while (tag != null) {
554                                         Console.Write("{0} <{1}>-<{2}> ", count++, tag.start, tag.length);
555                                         if (tag.line != line) {
556                                                 Console.Write("BAD line link");
557                                                 throw new Exception("Bad line link in tree");
558                                         }
559                                         tag = tag.next;
560                                         if (tag != null) {
561                                                 Console.Write(", ");
562                                         }
563                                 }
564                                 Console.WriteLine("");
565                         }
566                         if (line.left != null) {
567                                 if (line.left != sentinel) {
568                                         DumpTree(line.left, with_tags);
569                                 }
570                         }
571
572                         if (line.right != null) {
573                                 if (line.right != sentinel) {
574                                         DumpTree(line.right, with_tags);
575                                 }
576                         }
577                 }
578
579                 private void DecrementLines(int line_no) {
580                         int     current;
581
582                         current = line_no;
583                         while (current <= lines) {
584                                 GetLine(current).line_no--;
585                                 current++;
586                         }
587                         return;
588                 }
589
590                 private void IncrementLines(int line_no) {
591                         int     current;
592
593                         current = this.lines;
594                         while (current >= line_no) {
595                                 GetLine(current).line_no++;
596                                 current--;
597                         }
598                         return;
599                 }
600
601                 private void RebalanceAfterAdd(Line line1) {
602                         Line    line2;
603
604                         while ((line1 != document) && (line1.parent.color == LineColor.Red)) {
605                                 if (line1.parent == line1.parent.parent.left) {
606                                         line2 = line1.parent.parent.right;
607
608                                         if ((line2 != null) && (line1.color == LineColor.Red)) {
609                                                 line1.parent.color = LineColor.Black;
610                                                 line2.color = LineColor.Black;
611                                                 line1.parent.parent.color = LineColor.Red;
612                                                 line1 = line1.parent.parent;
613                                         } else {
614                                                 if (line1 == line1.parent.right) {
615                                                         line1 = line1.parent;
616                                                         RotateLeft(line1);
617                                                 }
618
619                                                 line1.parent.color = LineColor.Black;
620                                                 line1.parent.parent.color = LineColor.Red;
621
622                                                 RotateRight(line1.parent.parent);
623                                         }
624                                 } else {
625                                         line2 = line1.parent.parent.left;
626
627                                         if ((line2 != null) && (line2.color == LineColor.Red)) {
628                                                 line1.parent.color = LineColor.Black;
629                                                 line2.color = LineColor.Black;
630                                                 line1.parent.parent.color = LineColor.Red;
631                                                 line1 = line1.parent.parent;
632                                         } else {
633                                                 if (line1 == line1.parent.left) {
634                                                         line1 = line1.parent;
635                                                         RotateRight(line1);
636                                                 }
637
638                                                 line1.parent.color = LineColor.Black;
639                                                 line1.parent.parent.color = LineColor.Red;
640                                                 RotateLeft(line1.parent.parent);
641                                         }
642                                 }
643                         }
644                         document.color = LineColor.Black;
645                 }
646
647                 private void RebalanceAfterDelete(Line line1) {
648                         Line line2;
649
650                         while ((line1 != document) && (line1.color == LineColor.Black)) {
651                                 if (line1 == line1.parent.left) {
652                                         line2 = line1.parent.right;
653                                         if (line2.color == LineColor.Red) { 
654                                                 line2.color = LineColor.Black;
655                                                 line1.parent.color = LineColor.Red;
656                                                 RotateLeft(line1.parent);
657                                                 line2 = line1.parent.right;
658                                         }
659                                         if ((line2.left.color == LineColor.Black) && (line2.right.color == LineColor.Black)) { 
660                                                 line2.color = LineColor.Red;
661                                                 line1 = line1.parent;
662                                         } else {
663                                                 if (line2.right.color == LineColor.Red) {
664                                                         line2.left.color = LineColor.Black;
665                                                         line2.color = LineColor.Red;
666                                                         RotateRight(line2);
667                                                         line2 = line1.parent.right;
668                                                 }
669                                                 line2.color = line1.parent.color;
670                                                 line1.parent.color = LineColor.Black;
671                                                 line2.right.color = LineColor.Black;
672                                                 RotateLeft(line1.parent);
673                                                 line1 = document;
674                                         }
675                                 } else { 
676                                         line2 = line1.parent.left;
677                                         if (line2.color == LineColor.Red) {
678                                                 line2.color = LineColor.Black;
679                                                 line1.parent.color = LineColor.Red;
680                                                 RotateRight(line1.parent);
681                                                 line2 = line1.parent.left;
682                                         }
683                                         if ((line2.right.color == LineColor.Black) && (line2.left.color == LineColor.Black)) {
684                                                 line2.color = LineColor.Red;
685                                                 line1 = line1.parent;
686                                         } else {
687                                                 if (line2.left.color == LineColor.Black) {
688                                                         line2.right.color = LineColor.Black;
689                                                         line2.color = LineColor.Red;
690                                                         RotateLeft(line2);
691                                                         line2 = line1.parent.left;
692                                                 }
693                                                 line2.color = line1.parent.color;
694                                                 line1.parent.color = LineColor.Black;
695                                                 line2.left.color = LineColor.Black;
696                                                 RotateRight(line1.parent);
697                                                 line1 = document;
698                                         }
699                                 }
700                         }
701                         line1.color = LineColor.Black;
702                 }
703
704                 private void RotateLeft(Line line1) {
705                         Line    line2 = line1.right;
706
707                         line1.right = line2.left;
708
709                         if (line2.left != sentinel) {
710                                 line2.left.parent = line1;
711                         }
712
713                         if (line2 != sentinel) {
714                                 line2.parent = line1.parent;
715                         }
716
717                         if (line1.parent != null) {
718                                 if (line1 == line1.parent.left) {
719                                         line1.parent.left = line2;
720                                 } else {
721                                         line1.parent.right = line2;
722                                 }
723                         } else {
724                                 document = line2;
725                         }
726
727                         line2.left = line1;
728                         if (line1 != sentinel) {
729                                 line1.parent = line2;
730                         }
731                 }
732
733                 private void RotateRight(Line line1) {
734                         Line line2 = line1.left;
735
736                         line1.left = line2.right;
737
738                         if (line2.right != sentinel) {
739                                 line2.right.parent = line1;
740                         }
741
742                         if (line2 != sentinel) {
743                                 line2.parent = line1.parent;
744                         }
745
746                         if (line1.parent != null) {
747                                 if (line1 == line1.parent.right) {
748                                         line1.parent.right = line2;
749                                 } else {
750                                         line1.parent.left = line2;
751                                 }
752                         } else {
753                                 document = line2;
754                         }
755
756                         line2.right = line1;
757                         if (line1 != sentinel) {
758                                 line1.parent = line2;
759                         }
760                 }        
761
762
763                 public void UpdateView(Line line, int pos) {
764                         int     prev_width;
765
766                         // This is an optimization; we need to invalidate 
767                         prev_width = (int)line.widths[line.text.Length];
768
769                         if (RecalculateDocument(owner.CreateGraphics(), line.line_no, line.line_no, true)) {
770                                 // Lineheight changed, invalidate the rest of the document
771                                 if ((line.Y - viewport_y) >=0 ) {
772                                         // We formatted something that's in view, only draw parts of the screen
773                                         owner.Invalidate(new Rectangle(0, line.Y - viewport_y, owner.Width, owner.Height - line.Y - viewport_y));
774                                 } else {
775                                         // The tag was above the visible area, draw everything
776                                         owner.Invalidate();
777                                 }
778                         } else {
779                                 owner.Invalidate(new Rectangle((int)line.widths[pos] - viewport_x, line.Y - viewport_y, (int)owner.Width, line.height));
780                         }
781                 }
782
783
784                 // Update display from line, down line_count lines; pos is unused, but required for the signature
785                 public void UpdateView(Line line, int line_count, int pos) {
786                         int     prev_width;
787
788                         // This is an optimization; we need to invalidate 
789                         prev_width = (int)line.widths[line.text.Length];
790
791                         if (RecalculateDocument(owner.CreateGraphics(), line.line_no, line.line_no + line_count - 1, true)) {
792                                 // Lineheight changed, invalidate the rest of the document
793                                 if ((line.Y - viewport_y) >=0 ) {
794                                         // We formatted something that's in view, only draw parts of the screen
795                                         owner.Invalidate(new Rectangle(0, line.Y - viewport_y, owner.Width, owner.Height - line.Y - viewport_y));
796                                 } else {
797                                         // The tag was above the visible area, draw everything
798                                         owner.Invalidate();
799                                 }
800                         } else {
801                                 Line    end_line;
802
803                                 end_line = GetLine(line.line_no + line_count -1);
804                                 if (end_line == null) {
805                                         end_line = line;
806                                 }
807
808                                 owner.Invalidate(new Rectangle(0 - viewport_x, line.Y - viewport_y, (int)line.widths[line.text.Length], end_line.Y + end_line.height));
809                         }
810                 }
811                 #endregion      // Private Methods
812
813                 #region Public Methods
814                 public void PositionCaret(Line line, int pos) {
815                         caret.tag = line.FindTag(pos);
816                         caret.line = line;
817                         caret.pos = pos;
818                         caret.height = caret.tag.height;
819
820                         XplatUI.DestroyCaret(owner.Handle);
821                         XplatUI.CreateCaret(owner.Handle, 2, caret.height);
822                         XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos], caret.tag.line.Y + caret.tag.shift);
823                 }
824
825                 public void PositionCaret(int x, int y) {
826                         caret.tag = FindCursor(x, y, out caret.pos);
827                         caret.line = caret.tag.line;
828                         caret.height = caret.tag.height;
829
830                         XplatUI.DestroyCaret(owner.Handle);
831                         XplatUI.CreateCaret(owner.Handle, 2, caret.height);
832                         XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos], caret.tag.line.Y + caret.tag.shift);
833                 }
834
835                 public void CaretHasFocus() {
836                         if (caret.tag != null) {
837                                 XplatUI.CreateCaret(owner.Handle, 2, caret.height);
838                                 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos], caret.tag.line.Y + caret.tag.shift);
839                                 XplatUI.CaretVisible(owner.Handle, true);
840                         }
841                 }
842
843                 public void CaretLostFocus() {
844                         XplatUI.DestroyCaret(owner.Handle);
845                 }
846
847                 public void AlignCaret() {
848                         caret.tag = LineTag.FindTag(caret.line, caret.pos);
849                         caret.height = caret.tag.height;
850
851                         XplatUI.CreateCaret(owner.Handle, 2, caret.height);
852                         XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos], caret.tag.line.Y + caret.tag.shift);
853                         XplatUI.CaretVisible(owner.Handle, true);
854                 }
855
856                 public void UpdateCaret() {
857                         if (caret.tag.height != caret.height) {
858                                 caret.height = caret.tag.height;
859                                 XplatUI.CreateCaret(owner.Handle, 2, caret.height);
860                         }
861                         XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos], caret.tag.line.Y + caret.tag.shift);
862                         XplatUI.CaretVisible(owner.Handle, true);
863                 }
864
865                 public void DisplayCaret() {
866                         XplatUI.CaretVisible(owner.Handle, true);
867                 }
868
869                 public void HideCaret() {
870                         XplatUI.CaretVisible(owner.Handle, false);
871                 }
872
873                 public void MoveCaret(CaretDirection direction) {
874                         switch(direction) {
875                                 case CaretDirection.CharForward: {
876                                         caret.pos++;
877                                         if (caret.pos > caret.line.text.Length) {
878                                                 if (multiline) {
879                                                         // Go into next line
880                                                         if (caret.line.line_no < this.lines) {
881                                                                 caret.line = GetLine(caret.line.line_no+1);
882                                                                 caret.pos = 0;
883                                                                 caret.tag = caret.line.tags;
884                                                         } else {
885                                                                 caret.pos--;
886                                                         }
887                                                 } else {
888                                                         // Single line; we stay where we are
889                                                         caret.pos--;
890                                                 }
891                                         } else {
892                                                 if ((caret.tag.start - 1 + caret.tag.length) < caret.pos) {
893                                                         caret.tag = caret.tag.next;
894                                                 }
895                                         }
896                                         UpdateCaret();
897                                         return;
898                                 }
899
900                                 case CaretDirection.CharBack: {
901                                         if (caret.pos > 0) {
902                                                 // caret.pos--; // folded into the if below
903                                                 if (--caret.pos > 0) {
904                                                         if (caret.tag.start > caret.pos) {
905                                                                 caret.tag = caret.tag.previous;
906                                                         }
907                                                 }
908                                         } else {
909                                                 if (caret.line.line_no > 1) {
910                                                         caret.line = GetLine(caret.line.line_no - 1);
911                                                         caret.pos = caret.line.text.Length;
912                                                         caret.tag = LineTag.FindTag(caret.line, caret.pos);
913                                                 }
914                                         }
915                                         UpdateCaret();
916                                         return;
917                                 }
918
919                                 case CaretDirection.WordForward: {
920                                         int len;
921
922                                         len = caret.line.text.Length;
923                                         if (caret.pos < len) {
924                                                 while ((caret.pos < len) && (caret.line.text.ToString(caret.pos, 1) != " ")) {
925                                                         caret.pos++;
926                                                 }
927                                                 if (caret.pos < len) {
928                                                         // Skip any whitespace
929                                                         while ((caret.pos < len) && (caret.line.text.ToString(caret.pos, 1) == " ")) {
930                                                                 caret.pos++;
931                                                         }
932                                                 }
933                                         } else {
934                                                 if (caret.line.line_no < this.lines) {
935                                                         caret.line = GetLine(caret.line.line_no+1);
936                                                         caret.pos = 0;
937                                                         caret.tag = caret.line.tags;
938                                                 }
939                                         }
940                                         UpdateCaret();
941                                         return;
942                                 }
943
944                                 case CaretDirection.WordBack: {
945                                         if (caret.pos > 0) {
946                                                 int     len;
947
948                                                 len = caret.line.text.Length;
949
950                                                 caret.pos--;
951
952                                                 while ((caret.pos > 0) && (caret.line.text.ToString(caret.pos, 1) == " ")) {
953                                                         caret.pos--;
954                                                 }
955
956                                                 while ((caret.pos > 0) && (caret.line.text.ToString(caret.pos, 1) != " ")) {
957                                                         caret.pos--;
958                                                 }
959
960                                                 if (caret.line.text.ToString(caret.pos, 1) == " ") {
961                                                         if (caret.pos != 0) {
962                                                                 caret.pos++;
963                                                         } else {
964                                                                 caret.line = GetLine(caret.line.line_no - 1);
965                                                                 caret.pos = caret.line.text.Length;
966                                                                 caret.tag = LineTag.FindTag(caret.line, caret.pos);
967                                                         }
968                                                 }
969                                         } else {
970                                                 if (caret.line.line_no > 1) {
971                                                         caret.line = GetLine(caret.line.line_no - 1);
972                                                         caret.pos = caret.line.text.Length;
973                                                         caret.tag = LineTag.FindTag(caret.line, caret.pos);
974                                                 }
975                                         }
976                                         UpdateCaret();
977                                         return;
978                                 }
979
980                                 case CaretDirection.LineUp: {
981                                         if (caret.line.line_no > 1) {
982                                                 int     pixel;
983
984                                                 pixel = (int)caret.line.widths[caret.pos];
985                                                 PositionCaret(pixel, GetLine(caret.line.line_no - 1).Y);
986                                                 XplatUI.CaretVisible(owner.Handle, true);
987                                         }
988                                         return;
989                                 }
990
991                                 case CaretDirection.LineDown: {
992                                         if (caret.line.line_no < lines) {
993                                                 int     pixel;
994
995                                                 pixel = (int)caret.line.widths[caret.pos];
996                                                 PositionCaret(pixel, GetLine(caret.line.line_no + 1).Y);
997                                                 XplatUI.CaretVisible(owner.Handle, true);
998                                         }
999                                         return;
1000                                 }
1001
1002                                 case CaretDirection.Home: {
1003                                         if (caret.pos > 0) {
1004                                                 caret.pos = 0;
1005                                                 caret.tag = caret.line.tags;
1006                                                 UpdateCaret();
1007                                         }
1008                                         return;
1009                                 }
1010
1011                                 case CaretDirection.End: {
1012                                         if (caret.pos < caret.line.text.Length) {
1013                                                 caret.pos = caret.line.text.Length;
1014                                                 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1015                                                 UpdateCaret();
1016                                         }
1017                                         return;
1018                                 }
1019
1020                                 case CaretDirection.PgUp: {
1021                                         return;
1022                                 }
1023
1024                                 case CaretDirection.PgDn: {
1025                                         return;
1026                                 }
1027
1028                                 case CaretDirection.CtrlHome: {
1029                                         caret.line = GetLine(1);
1030                                         caret.pos = 0;
1031                                         caret.tag = caret.line.tags;
1032
1033                                         UpdateCaret();
1034                                         return;
1035                                 }
1036
1037                                 case CaretDirection.CtrlEnd: {
1038                                         caret.line = GetLine(lines);
1039                                         caret.pos = 0;
1040                                         caret.tag = caret.line.tags;
1041
1042                                         UpdateCaret();
1043                                         return;
1044                                 }
1045                         }
1046                 }
1047
1048                 // Draw the document
1049                 public void Draw(Graphics g, Rectangle clip, Brush brush) {
1050                         Line    line;           // Current line being drawn
1051                         LineTag tag;            // Current tag being drawn
1052                         int     start;          // First line to draw
1053                         int     end;            // Last line to draw
1054                         string  s;              // String representing the current line
1055                         int     line_no;        //
1056
1057                         // First, figure out from what line to what line we need to draw
1058                         start = GetLineByPixel(clip.Top - viewport_y, false).line_no;
1059                         end = GetLineByPixel(clip.Bottom - viewport_y, false).line_no;
1060
1061                         // Now draw our elements; try to only draw those that are visible
1062                         line_no = start;
1063
1064                         #if Debug
1065                                 DateTime        n = DateTime.Now;
1066                                 Console.WriteLine("Started drawing: {0}s {1}ms", n.Second, n.Millisecond);
1067                         #endif
1068
1069                         while (line_no <= end) {
1070                                 line = GetLine(line_no);
1071                                 tag = line.tags;
1072                                 s = line.text.ToString();
1073                                 while (tag != null) {
1074                                         if (((tag.X + tag.width) > (clip.Left - viewport_x)) || (tag.X < (clip.Right - viewport_x))) {
1075                                                 g.DrawString(s.Substring(tag.start-1, tag.length), tag.font, brush, tag.X - viewport_x, line.Y + tag.shift  - viewport_y, StringFormat.GenericTypographic);
1076                                         }
1077
1078                                         tag = tag.next;
1079                                 }
1080
1081                                 line_no++;
1082                         }
1083                         #if Debug
1084                                 n = DateTime.Now;
1085                                 Console.WriteLine("Finished drawing: {0}s {1}ms", n.Second, n.Millisecond);
1086                         #endif
1087
1088                 }
1089
1090
1091                 // Inserts a character at the given position
1092                 public void InsertChar(Line line, int pos, char ch) {
1093                         InsertChar(line.FindTag(pos), pos, ch);
1094                 }
1095
1096                 // Inserts a character at the given position
1097                 public void InsertChar(LineTag tag, int pos, char ch) {
1098                         Line    line;
1099
1100                         line = tag.line;
1101                         line.text.Insert(pos, ch);
1102                         tag.length++;
1103
1104                         tag = tag.next;
1105                         while (tag != null) {
1106                                 tag.start++;
1107                                 tag = tag.next;
1108                         }
1109                         line.Grow(1);
1110                         line.recalc = true;
1111
1112                         UpdateView(line, pos);
1113                 }
1114
1115                 // Inserts a character at the given position
1116                 public void InsertCharAtCaret(char ch, bool move_caret) {
1117                         caret.line.text.Insert(caret.pos, ch);
1118                         caret.tag.length++;
1119                         if (caret.tag.next != null) {
1120                                 caret.tag.next.start++;
1121                         }
1122                         caret.line.Grow(1);
1123                         caret.line.recalc = true;
1124
1125                         UpdateView(caret.line, caret.pos);
1126                         if (move_caret) {
1127                                 caret.pos++;
1128                                 UpdateCaret();
1129                         }
1130                 }
1131
1132
1133                 // Inserts a character at the given position; it will not delete past line limits
1134                 public void DeleteChar(LineTag tag, int pos, bool forward) {
1135                         Line    line;
1136                         bool    streamline;
1137
1138
1139                         streamline = false;
1140                         line = tag.line;
1141
1142                         if ((pos == 0 && forward == false) || (pos == line.text.Length && forward == true)) {
1143                                 return;
1144                         }
1145
1146                         if (forward) {
1147                                 line.text.Remove(pos, 1);
1148                                 tag.length--;
1149
1150                                 if (tag.length == 0) {
1151                                         streamline = true;
1152                                 }
1153                         } else {
1154                                 pos--;
1155                                 line.text.Remove(pos, 1);
1156                                 if (pos >= (tag.start - 1)) {
1157                                         tag.length--;
1158                                         if (tag.length == 0) {
1159                                                 streamline = true;
1160                                         }
1161                                 } else if (tag.previous != null) {
1162                                         tag.previous.length--;
1163                                         if (tag.previous.length == 0) {
1164                                                 streamline = true;
1165                                         }
1166                                 }
1167                         }
1168
1169                         tag = tag.next;
1170                         while (tag != null) {
1171                                 tag.start--;
1172                                 tag = tag.next;
1173                         }
1174                         line.recalc = true;
1175                         line.Streamline();
1176
1177                         UpdateView(line, pos);
1178                 }
1179
1180                 // Combine two lines
1181                 public void Combine(int FirstLine, int SecondLine) {
1182                         Combine(GetLine(FirstLine), GetLine(SecondLine));
1183                 }
1184
1185                 public void Combine(Line first, Line second) {
1186                         LineTag last;
1187                         int     shift;
1188
1189                         // Combine the two tag chains into one
1190                         last = first.tags;
1191
1192                         while (last.next != null) {
1193                                 last = last.next;
1194                         }
1195
1196                         last.next = second.tags;
1197                         last.next.previous = last;
1198
1199                         shift = last.start + last.length - 1;
1200
1201                         // Fix up references within the chain
1202                         last = last.next;
1203                         while (last != null) {
1204                                 last.line = first;
1205                                 last.start += shift;
1206                                 last = last.next;
1207                         }
1208
1209                         // Combine both lines' strings
1210                         first.text.Insert(first.text.Length, second.text.ToString());
1211                         first.Grow(first.text.Length);
1212
1213                         // Remove the reference to our (now combined) tags from the doomed line
1214                         second.tags = null;
1215
1216                         // Renumber lines
1217                         DecrementLines(first.line_no + 2);      // first.line_no + 1 will be deleted, so we need to start renumbering one later
1218
1219                         // Mop up
1220                         first.recalc = true;
1221                         first.height = 0;       // This forces RecalcDocument/UpdateView to redraw from this line on
1222                         first.Streamline();
1223
1224                         this.Delete(second);
1225
1226                 }
1227
1228                 // Split the line at the position into two
1229                 public void Split(int LineNo, int pos) {
1230                         Line    line;
1231                         LineTag tag;
1232
1233                         line = GetLine(LineNo);
1234                         tag = LineTag.FindTag(line, pos);
1235                         Split(line, tag, pos);
1236                 }
1237
1238                 public void Split(Line line, int pos) {
1239                         LineTag tag;
1240
1241                         tag = LineTag.FindTag(line, pos);
1242                         Split(line, tag, pos);
1243                 }
1244
1245                 public void Split(Line line, LineTag tag, int pos) {
1246                         LineTag new_tag;
1247                         Line    new_line;
1248
1249                         // cover the easy case first
1250                         if (pos == line.text.Length) {
1251                                 Add(line.line_no + 1, "", tag.font);
1252                                 return;
1253                         }
1254
1255                         // We need to move the rest of the text into the new line
1256                         Add(line.line_no + 1, line.text.ToString(pos, line.text.Length - pos), tag.font);
1257
1258                         // Now transfer our tags from this line to the next
1259                         new_line = GetLine(line.line_no + 1);
1260                         line.recalc = true;
1261
1262                         if ((tag.start - 1) == pos) {
1263                                 int     shift;
1264
1265                                 // We can simply break the chain and move the tag into the next line
1266                                 if (tag == line.tags) {
1267                                         new_tag = new LineTag(line, 1, 0);
1268                                         new_tag.font = tag.font;
1269                                         line.tags = new_tag;
1270                                 }
1271
1272                                 if (tag.previous != null) {
1273                                         tag.previous.next = null;
1274                                 }
1275                                 new_line.tags = tag;
1276                                 tag.previous = null;
1277                                 tag.line = new_line;
1278
1279                                 // Walk the list and correct the start location of the tags we just bumped into the next line
1280                                 shift = tag.start - 1;
1281
1282                                 new_tag = tag;
1283                                 while (new_tag != null) {
1284                                         new_tag.start -= shift;
1285                                         new_tag.line = new_line;
1286                                         new_tag = new_tag.next;
1287                                 }
1288                         } else {
1289                                 int     shift;
1290
1291                                 new_tag = new LineTag(new_line, 1, tag.start - 1 + tag.length - pos);
1292                                 new_tag.next = tag.next;
1293                                 new_tag.font = tag.font;
1294                                 new_line.tags = new_tag;
1295                                 if (new_tag.next != null) {
1296                                         new_tag.next.previous = new_tag;
1297                                 }
1298                                 tag.next = null;
1299                                 tag.length = pos - tag.start + 1;
1300
1301                                 shift = pos;
1302                                 new_tag = new_tag.next;
1303                                 while (new_tag != null) {
1304                                         new_tag.start -= shift;
1305                                         new_tag.line = new_line;
1306                                         new_tag = new_tag.next;
1307
1308                                 }
1309                         }
1310                         line.text.Remove(pos, line.text.Length - pos);
1311                 }
1312
1313                 // Adds a line of text, with given font.
1314                 // Bumps any line at that line number that already exists down
1315                 public void Add(int LineNo, string Text, Font font) {
1316                         Line    add;
1317                         Line    line;
1318                         int     line_no;
1319
1320                         if (LineNo<1 || Text == null) {
1321                                 if (LineNo<1) {
1322                                         throw new ArgumentNullException("LineNo", "Line numbers must be positive");
1323                                 } else {
1324                                         throw new ArgumentNullException("Text", "Cannot insert NULL line");
1325                                 }
1326                         }
1327
1328                         add = new Line(LineNo, Text, font);
1329
1330                         line = document;
1331                         while (line != sentinel) {
1332                                 add.parent = line;
1333                                 line_no = line.line_no;
1334
1335                                 if (LineNo > line_no) {
1336                                         line = line.right;
1337                                 } else if (LineNo < line_no) {
1338                                         line = line.left;
1339                                 } else {
1340                                         // Bump existing line numbers; walk all nodes to the right of this one and increment line_no
1341                                         IncrementLines(line.line_no);
1342                                         line = line.left;
1343                                 }
1344                         }
1345
1346                         add.left = sentinel;
1347                         add.right = sentinel;
1348
1349                         if (add.parent != null) {
1350                                 if (LineNo > add.parent.line_no) {
1351                                         add.parent.right = add;
1352                                 } else {
1353                                         add.parent.left = add;
1354                                 }
1355                         } else {
1356                                 // Root node
1357                                 document = add;
1358                         }
1359
1360                         RebalanceAfterAdd(add);
1361
1362                         lines++;
1363                 }
1364
1365                 public virtual void Clear() {
1366                         lines = 0;
1367                         document = sentinel;
1368                 }
1369
1370                 public virtual object Clone() {
1371                         Document clone;
1372
1373                         clone = new Document(null);
1374
1375                         clone.lines = this.lines;
1376                         clone.document = (Line)document.Clone();
1377
1378                         return clone;
1379                 }
1380
1381                 public void Delete(int LineNo) {
1382                         if (LineNo>lines) {
1383                                 return;
1384                         }
1385
1386                         Delete(GetLine(LineNo));
1387                 }
1388
1389                 public void Delete(Line line1) {
1390                         Line    line2;// = new Line();
1391                         Line    line3;
1392
1393                         if ((line1.left == sentinel) || (line1.right == sentinel)) {
1394                                 line3 = line1;
1395                         } else {
1396                                 line3 = line1.right;
1397                                 while (line3.left != sentinel) {
1398                                         line3 = line3.left;
1399                                 }
1400                         }
1401
1402                         if (line3.left != sentinel) {
1403                                 line2 = line3.left;
1404                         } else {
1405                                 line2 = line3.right;
1406                         }
1407
1408                         line2.parent = line3.parent;
1409                         if (line3.parent != null) {
1410                                 if(line3 == line3.parent.left) {
1411                                         line3.parent.left = line2;
1412                                 } else {
1413                                         line3.parent.right = line2;
1414                                 }
1415                         } else {
1416                                 document = line2;
1417                         }
1418
1419                         if (line3 != line1) {
1420                                 LineTag tag;
1421
1422                                 line1.line_no = line3.line_no;
1423                                 line1.text = line3.text;
1424                                 line1.tags = line3.tags;
1425                                 line1.height = line3.height;
1426                                 line1.recalc = line3.recalc;
1427                                 line1.space = line3.space;
1428                                 line1.widths = line3.widths;
1429                                 line1.Y = line3.Y;
1430
1431                                 tag = line1.tags;
1432                                 while (tag != null) {
1433                                         tag.line = line1;
1434                                         tag = tag.next;
1435                                 }
1436                         }
1437
1438                         if (line3.color == LineColor.Black)
1439                                 RebalanceAfterDelete(line2);
1440
1441                         this.lines--;
1442
1443                         last_found = sentinel;
1444                 }
1445
1446                 // Set our selection markers
1447                 public void SetSelection(Line start, int start_pos, Line end, int end_pos) {
1448                         selection_start_line = start;
1449                         selection_start_pos = start_pos;
1450                         selection_end_line = end;
1451                         selection_end_pos = end_pos;
1452                 }
1453
1454                 public void SetSelection(Line start, int start_pos) {
1455                         selection_start_line = start;
1456                         selection_start_pos = start_pos;
1457                         selection_end_line = start;
1458                         selection_end_pos = start_pos;
1459                 }
1460
1461
1462                 // Give it a Line number and it returns the Line object at with that line number
1463                 public Line GetLine(int LineNo) {
1464                         Line    line = document;
1465
1466                         while (line != sentinel) {
1467                                 if (LineNo == line.line_no) {
1468                                         return line;
1469                                 } else if (LineNo < line.line_no) {
1470                                         line = line.left;
1471                                 } else {
1472                                         line = line.right;
1473                                 }
1474                         }
1475
1476                         return null;
1477                 }
1478
1479                 // Give it a Y pixel coordinate and it returns the Line covering that Y coordinate
1480                 ///
1481                 public Line GetLineByPixel(int y, bool exact) {
1482                         Line    line = document;
1483                         Line    last = null;
1484
1485                         while (line != sentinel) {
1486                                 last = line;
1487                                 if ((y >= line.Y) && (y < (line.Y+line.height))) {
1488                                         return line;
1489                                 } else if (y < line.Y) {
1490                                         line = line.left;
1491                                 } else {
1492                                         line = line.right;
1493                                 }
1494                         }
1495
1496                         if (exact) {
1497                                 return null;
1498                         }
1499                         return last;
1500                 }
1501
1502                 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
1503                 public LineTag FindTag(int x, int y, out int index, bool exact) {
1504                         Line    line;
1505                         LineTag tag;
1506
1507                         line = GetLineByPixel(y, exact);
1508                         if (line == null) {
1509                                 index = 0;
1510                                 return null;
1511                         }
1512                         tag = line.tags;
1513
1514                         while (true) {
1515                                 if (x >= tag.X && x < (tag.X+tag.width)) {
1516                                         int     end;
1517
1518                                         end = tag.start + tag.length - 1;
1519
1520                                         for (int pos = tag.start; pos < end; pos++) {
1521                                                 if (x < line.widths[pos]) {
1522                                                         index = pos;
1523                                                         return tag;
1524                                                 }
1525                                         }
1526                                         index=end;
1527                                         return tag;
1528                                 }
1529                                 if (tag.next != null) {
1530                                         tag = tag.next;
1531                                 } else {
1532                                         if (exact) {
1533                                                 index = 0;
1534                                                 return null;
1535                                         }
1536
1537                                         index = line.text.Length;
1538                                         return tag;
1539                                 }
1540                         }
1541                 }
1542
1543                 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
1544                 public LineTag FindCursor(int x, int y, out int index) {
1545                         Line    line;
1546                         LineTag tag;
1547
1548                         line = GetLineByPixel(y, false);
1549                         tag = line.tags;
1550
1551                         while (true) {
1552                                 if (x >= tag.X && x < (tag.X+tag.width)) {
1553                                         int     end;
1554
1555                                         end = tag.start + tag.length - 1;
1556
1557                                         for (int pos = tag.start-1; pos < end; pos++) {
1558                                                 // When clicking on a character, we position the cursor to whatever edge
1559                                                 // of the character the click was closer
1560                                                 if (x < (line.widths[pos] + ((line.widths[pos+1]-line.widths[pos])/2))) {
1561                                                         index = pos;
1562                                                         return tag;
1563                                                 }
1564                                         }
1565                                         index=end;
1566                                         return tag;
1567                                 }
1568                                 if (tag.next != null) {
1569                                         tag = tag.next;
1570                                 } else {
1571                                         index = line.text.Length;
1572                                         return tag;
1573                                 }
1574                         }
1575                 }
1576
1577                 // Calculate formatting for the whole document
1578                 public bool RecalculateDocument(Graphics g) {
1579                         return RecalculateDocument(g, 1, this.lines, false);
1580                 }
1581
1582                 // Calculate formatting starting at a certain line
1583                 public bool RecalculateDocument(Graphics g, int start) {
1584                         return RecalculateDocument(g, start, this.lines, false);
1585                 }
1586
1587                 // Calculate formatting within two given line numbers
1588                 public bool RecalculateDocument(Graphics g, int start, int end) {
1589                         return RecalculateDocument(g, start, end, false);
1590                 }
1591
1592                 // With optimize on, returns true if line heights changed
1593                 public bool RecalculateDocument(Graphics g, int start, int end, bool optimize) {
1594                         Line    line;
1595                         int     line_no;
1596                         int     Y;
1597
1598                         Y = GetLine(start).Y;
1599                         line_no = start;
1600                         if (optimize) {
1601                                 bool    changed;
1602
1603                                 changed = false;
1604
1605                                 while (line_no <= end) {
1606                                         line = GetLine(line_no++);
1607                                         line.Y = Y;
1608                                         if (line.recalc) {
1609                                                 if (line.RecalculateLine(g)) {
1610                                                         changed = true;
1611                                                         // If the height changed, all subsequent lines change
1612                                                         end = this.lines;
1613                                                 }
1614                                         }
1615
1616                                         Y += line.height;
1617                                 }
1618
1619                                 return changed;
1620                         } else {
1621                                 while (line_no <= end) {
1622                                         line = GetLine(line_no++);
1623                                         line.Y = Y;
1624                                         line.RecalculateLine(g);
1625                                         Y += line.height;
1626                                 }
1627                                 return true;
1628                         }
1629                 }
1630
1631                 public bool SetCursor(int x, int y) {
1632                         return true;
1633                 }
1634
1635                 public int Size() {
1636                         return lines;
1637                 }
1638                 #endregion      // Public Methods
1639
1640                 #region Administrative
1641                 public IEnumerator GetEnumerator() {
1642                         // FIXME
1643                         return null;
1644                 }
1645
1646                 public override bool Equals(object obj) {
1647                         if (obj == null) {
1648                                 return false;
1649                         }
1650
1651                         if (!(obj is Document)) {
1652                                 return false;
1653                         }
1654
1655                         if (obj == this) {
1656                                 return true;
1657                         }
1658
1659                         if (ToString().Equals(((Document)obj).ToString())) {
1660                                 return true;
1661                         }
1662
1663                         return false;
1664                 }
1665
1666                 public override int GetHashCode() {
1667                         return document_id;
1668                 }
1669
1670                 public override string ToString() {
1671                         return "document " + this.document_id;
1672                 }
1673
1674                 #endregion      // Administrative
1675         }
1676
1677         public class LineTag {
1678                 #region Local Variables;
1679                 // Payload; formatting
1680                 internal Font           font;           // System.Drawing.Font object for this tag
1681
1682                 // Payload; text
1683                 internal int            start;          // start, in chars; index into Line.text
1684                 internal int            length;         // length, in chars
1685                 internal bool           r_to_l;         // Which way is the font
1686
1687                 // Drawing support
1688                 internal int            height;         // Height in pixels of the text this tag describes
1689                 internal int            X;              // X location of the text this tag describes
1690                 internal float          width;          // Width in pixels of the text this tag describes
1691                 internal int            ascent;         // Ascent of the font for this tag
1692                 internal int            shift;          // Shift down for this tag, to stay on baseline
1693
1694                 // Administrative
1695                 internal Line           line;           // The line we're on
1696                 internal LineTag        next;           // Next tag on the same line
1697                 internal LineTag        previous;       // Previous tag on the same line
1698                 #endregion;
1699
1700                 #region Constructors
1701                 public LineTag(Line line, int start, int length) {
1702                         this.line = line;
1703                         this.start = start;
1704                         this.length = length;
1705                         this.X = 0;
1706                         this.width = 0;
1707                 }
1708                 #endregion      // Constructors
1709
1710                 #region Public Methods
1711                 //
1712                 // Applies 'font' to characters starting at 'start' for 'length' chars
1713                 // Removes any previous tags overlapping the same area
1714                 // returns true if lineheight has changed
1715                 //
1716                 public static bool FormatText(Line line, int start, int length, Font font) {
1717                         LineTag tag;
1718                         LineTag start_tag;
1719                         LineTag end_tag;
1720                         int     end;
1721                         int     state;
1722                         int     left;
1723                         bool    retval = false;         // Assume line-height doesn't change
1724
1725                         // Too simple?
1726                         if (font.Height != line.height) {
1727                                 retval = true;
1728                         }
1729                         line.recalc = true;             // This forces recalculation of the line in RecalculateDocument
1730
1731                         // A little sanity, not sure if it's needed, might be able to remove for speed
1732                         if (length > line.text.Length) {
1733                                 length = line.text.Length;
1734                         }
1735
1736                         tag = line.tags;
1737                         end = start + length;
1738                         state = 0;
1739
1740                         // Common special case
1741                         if ((start == 1) && (length == tag.length)) {
1742                                 tag.font = font;
1743                                 return retval;
1744                         }
1745
1746                         start_tag = FindTag(line, start);
1747                         end_tag = FindTag(line, end);
1748
1749                         tag = new LineTag(line, start, length);
1750                         tag.font = font;
1751
1752                         if (start == 1) {
1753                                 line.tags = tag;
1754                         }
1755
1756                         if (start_tag.start == start) {
1757                                 tag.next = start_tag;
1758                                 tag.previous = start_tag.previous;
1759                                 if (start_tag.previous != null) {
1760                                         start_tag.previous.next = tag;
1761                                 }
1762                                 start_tag.previous = tag;
1763                         } else {
1764                                 // Insert ourselves 'in the middle'
1765                                 if ((start_tag.next != null) && (start_tag.next.start < end)) {
1766                                         tag.next = start_tag.next;
1767                                 } else {
1768                                         tag.next = new LineTag(line, start_tag.start, start_tag.length);
1769                                         tag.next.font = start_tag.font;
1770
1771                                         if (start_tag.next != null) {
1772                                                 tag.next.next = start_tag.next;
1773                                                 tag.next.next.previous = tag.next;
1774                                         }
1775                                 }
1776                                 tag.next.previous = tag;
1777
1778                                 start_tag.length = start - start_tag.start;
1779
1780                                 tag.previous = start_tag;
1781                                 start_tag.next = tag;
1782 #if crap
1783                                 if (tag.next.start > (tag.start + tag.length)) {
1784                                         tag.next.length  += tag.next.start - (tag.start + tag.length);
1785                                         tag.next.start = tag.start + tag.length;
1786                                 }
1787 #endif
1788                         }
1789
1790                         // Elimination loop
1791                         tag = tag.next;
1792                         while ((tag != null) && (tag.start < end)) {
1793                                 if ((tag.start + tag.length) <= end) {
1794                                         // remove the tag
1795                                         tag.previous.next = tag.next;
1796                                         if (tag.next != null) {
1797                                                 tag.next.previous = tag.previous;
1798                                         }
1799                                         tag = tag.previous;
1800                                 } else {
1801                                         // Adjust the length of the tag
1802                                         tag.length = (tag.start + tag.length) - end;
1803                                         tag.start = end;
1804                                 }
1805                                 tag = tag.next;
1806                         }
1807
1808                         return retval;
1809                 }
1810
1811
1812                 //
1813                 // Finds the tag that describes the character at position 'pos' on 'line'
1814                 //
1815                 public static LineTag FindTag(Line line, int pos) {
1816                         LineTag tag = line.tags;
1817
1818                         // Beginning of line is a bit special
1819                         if (pos == 0) {
1820                                 return tag;
1821                         }
1822
1823                         while (tag != null) {
1824                                 if ((tag.start <= pos) && (pos < (tag.start+tag.length))) {
1825                                         return tag;
1826                                 }
1827
1828                                 tag = tag.next;
1829                         }
1830
1831                         return null;
1832                 }
1833
1834                 //
1835                 // Combines 'this' tag with 'other' tag.
1836                 //
1837                 public bool Combine(LineTag other) {
1838                         if (!this.Equals(other)) {
1839                                 return false;
1840                         }
1841
1842                         this.width += other.width;
1843                         this.length += other.length;
1844                         this.next = other.next;
1845                         if (this.next != null) {
1846                                 this.next.previous = this;
1847                         }
1848
1849                         return true;
1850                 }
1851
1852
1853                 //
1854                 // Remove 'this' tag ; to be called when formatting is to be removed
1855                 //
1856                 public bool Remove() {
1857                         if ((this.start == 1) && (this.next == null)) {
1858                                 // We cannot remove the only tag
1859                                 return false;
1860                         }
1861                         if (this.start != 1) {
1862                                 this.previous.length += this.length;
1863                                 this.previous.width = -1;
1864                                 this.previous.next = this.next;
1865                                 this.next.previous = this.previous;
1866                         } else {
1867                                 this.next.start = 1;
1868                                 this.next.length += this.length;
1869                                 this.next.width = -1;
1870                                 this.line.tags = this.next;
1871                                 this.next.previous = null;
1872                         }
1873                         return true;
1874                 }
1875
1876
1877                 //
1878                 // Checks if 'this' tag describes the same formatting options as 'obj'
1879                 //
1880                 public override bool Equals(object obj) {
1881                         LineTag other;
1882
1883                         if (obj == null) {
1884                                 return false;
1885                         }
1886
1887                         if (!(obj is LineTag)) {
1888                                 return false;
1889                         }
1890
1891                         if (obj == this) {
1892                                 return true;
1893                         }
1894
1895                         other = (LineTag)obj;
1896
1897                         if (this.font.Equals(other.font)) {     // FIXME add checking for things like link or type later
1898                                 return true;
1899                         }
1900
1901                         return false;
1902                 }
1903
1904                 public override string ToString() {
1905                         return "Tag starts at index " + this.start + "length " + this.length + " text: " + this.line.Text.Substring(this.start-1, this.length) + "Font " + this.font.ToString();
1906                 }
1907
1908                 #endregion      // Public Methods
1909         }
1910 }