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