a94ef12fbb818503e036843a5769cface8c5b8d5
[mono.git] / mcs / class / Managed.Windows.Forms / System.Windows.Forms / Line.cs
1 // Permission is hereby granted, free of charge, to any person obtaining\r
2 // a copy of this software and associated documentation files (the\r
3 // "Software"), to deal in the Software without restriction, including\r
4 // without limitation the rights to use, copy, modify, merge, publish,\r
5 // distribute, sublicense, and/or sell copies of the Software, and to\r
6 // permit persons to whom the Software is furnished to do so, subject to\r
7 // the following conditions:\r
8 // \r
9 // The above copyright notice and this permission notice shall be\r
10 // included in all copies or substantial portions of the Software.\r
11 // \r
12 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,\r
13 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\r
14 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r
15 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\r
16 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\r
17 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\r
18 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r
19 //\r
20 // Copyright (c) 2004-2006 Novell, Inc. (http://www.novell.com)\r
21 //\r
22 // Authors:\r
23 //      Peter Bartok    pbartok@novell.com\r
24 //\r
25 //\r
26 \r
27 using System;\r
28 using System.Collections;\r
29 using System.Drawing;\r
30 using System.Drawing.Text;\r
31 using System.Text;\r
32 \r
33 namespace System.Windows.Forms\r
34 {\r
35         internal class Line : ICloneable, IComparable\r
36         {\r
37                 #region Local Variables\r
38 \r
39                 internal Document document;\r
40                 // Stuff that matters for our line\r
41                 internal StringBuilder          text;                   // Characters for the line\r
42                 internal float[]                widths;                 // Width of each character; always one larger than text.Length\r
43                 internal int                    space;                  // Number of elements in text and widths\r
44                 internal int                    line_no;                // Line number\r
45                 internal LineTag                tags;                   // Tags describing the text\r
46                 internal int                    offset;                 // Baseline can be on the X or Y axis depending if we are in multiline mode or not\r
47                 internal int                    height;                 // Height of the line (height of tallest tag)\r
48                 internal int                    ascent;                 // Ascent of the line (ascent of the tallest tag)\r
49                 internal HorizontalAlignment    alignment;              // Alignment of the line\r
50                 internal int                    align_shift;            // Pixel shift caused by the alignment\r
51                 internal int                    indent;                 // Left indent for the first line\r
52                 internal int                    hanging_indent;         // Hanging indent (left indent for all but the first line)\r
53                 internal int                    right_indent;           // Right indent for all lines\r
54                 internal LineEnding             ending;\r
55 \r
56                 // Stuff that's important for the tree\r
57                 internal Line                   parent;                 // Our parent line\r
58                 internal Line                   left;                   // Line with smaller line number\r
59                 internal Line                   right;                  // Line with higher line number\r
60                 internal LineColor              color;                  // We're doing a black/red tree. this is the node color\r
61                 static int                      DEFAULT_TEXT_LEN = 0;   // \r
62                 internal bool                   recalc;                 // Line changed\r
63                 #endregion      // Local Variables\r
64 \r
65                 #region Constructors\r
66                 internal Line (Document document, LineEnding ending)\r
67                 {\r
68                         this.document = document; \r
69                         color = LineColor.Red;\r
70                         left = null;\r
71                         right = null;\r
72                         parent = null;\r
73                         text = null;\r
74                         recalc = true;\r
75                         alignment = document.alignment;\r
76 \r
77                         this.ending = ending;\r
78                 }\r
79 \r
80                 internal Line (Document document, int LineNo, string Text, Font font, Color color, LineEnding ending) : this (document, ending)\r
81                 {\r
82                         space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;\r
83 \r
84                         text = new StringBuilder (Text, space);\r
85                         line_no = LineNo;\r
86                         this.ending = ending;\r
87 \r
88                         widths = new float[space + 1];\r
89 \r
90                         \r
91                         tags = new LineTag(this, 1);\r
92                         tags.Font = font;\r
93                         tags.Color = color;\r
94                 }\r
95 \r
96                 internal Line (Document document, int LineNo, string Text, HorizontalAlignment align, Font font, Color color, LineEnding ending) : this(document, ending)\r
97                 {\r
98                         space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;\r
99 \r
100                         text = new StringBuilder (Text, space);\r
101                         line_no = LineNo;\r
102                         this.ending = ending;\r
103                         alignment = align;\r
104 \r
105                         widths = new float[space + 1];\r
106 \r
107                         \r
108                         tags = new LineTag(this, 1);\r
109                         tags.Font = font;\r
110                         tags.Color = color;\r
111                 }\r
112 \r
113                 internal Line (Document document, int LineNo, string Text, LineTag tag, LineEnding ending) : this(document, ending)\r
114                 {\r
115                         space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;\r
116 \r
117                         text = new StringBuilder (Text, space);\r
118                         this.ending = ending;\r
119                         line_no = LineNo;\r
120 \r
121                         widths = new float[space + 1];\r
122                         tags = tag;\r
123                 }\r
124 \r
125                 #endregion      // Constructors\r
126 \r
127                 #region Internal Properties\r
128                 internal HorizontalAlignment Alignment {\r
129                         get { return alignment; }\r
130                         set {\r
131                                 if (alignment != value) {\r
132                                         alignment = value;\r
133                                         recalc = true;\r
134                                 }\r
135                         }\r
136                 }\r
137 \r
138                 internal int HangingIndent {\r
139                         get { return hanging_indent; }\r
140                         set {\r
141                                 hanging_indent = value;\r
142                                 recalc = true;\r
143                         }\r
144                 }\r
145 \r
146                 internal int Height {\r
147                         get { return height; }\r
148                         set { height = value; }\r
149                 }\r
150 \r
151                 internal int Indent {\r
152                         get { return indent; }\r
153                         set { \r
154                                 indent = value;\r
155                                 recalc = true;\r
156                         }\r
157                 }\r
158 \r
159                 internal int LineNo {\r
160                         get { return line_no; }\r
161                         set { line_no = value; }\r
162                 }\r
163 \r
164                 internal int RightIndent {\r
165                         get { return right_indent; }\r
166                         set { \r
167                                 right_indent = value;\r
168                                 recalc = true;\r
169                         }\r
170                 }\r
171                         \r
172                 internal int Width {\r
173                         get {\r
174                                 int res = (int) widths [text.Length];\r
175                                 return res;\r
176                         }\r
177                 }\r
178 \r
179                 internal string Text {\r
180                         get { return text.ToString(); }\r
181                         set { \r
182                                 text = new StringBuilder(value, value.Length > DEFAULT_TEXT_LEN ? value.Length : DEFAULT_TEXT_LEN);\r
183                         }\r
184                 }\r
185                 \r
186                 internal int X {\r
187                         get {\r
188                                 if (document.multiline)\r
189                                         return align_shift;\r
190                                 return offset + align_shift;\r
191                         }\r
192                 }\r
193 \r
194                 internal int Y {\r
195                         get {\r
196                                 if (!document.multiline)\r
197                                         return document.top_margin;\r
198                                 return document.top_margin + offset;\r
199                         }\r
200                 }\r
201                 #endregion      // Internal Properties\r
202 \r
203                 #region Internal Methods\r
204 \r
205                 /// <summary>\r
206                 ///  Builds a simple code to record which tags are links and how many tags\r
207                 ///  used to compare lines before and after to see if the scan for links\r
208                 ///  process has changed anything.\r
209                 /// </summary>\r
210                 internal void LinkRecord (StringBuilder linkRecord)\r
211                 {\r
212                         LineTag tag = tags;\r
213 \r
214                         while (tag != null) {\r
215                                 if (tag.IsLink)\r
216                                         linkRecord.Append ("L");\r
217                                 else\r
218                                         linkRecord.Append ("N");\r
219 \r
220                                 tag = tag.Next;\r
221                         }\r
222                 }\r
223 \r
224                 /// <summary>\r
225                 ///  Clears all link properties from tags\r
226                 /// </summary>\r
227                 internal void ClearLinks ()\r
228                 {\r
229                         LineTag tag = tags;\r
230 \r
231                         while (tag != null) {\r
232                                 tag.IsLink = false;\r
233                                 tag = tag.Next;\r
234                         }\r
235                 }\r
236 \r
237                 public void DeleteCharacters(int pos, int count)\r
238                 {\r
239                         LineTag tag;\r
240                         bool streamline = false;\r
241                         \r
242                         // Can't delete more than the line has\r
243                         if (pos >= text.Length)\r
244                                 return;\r
245 \r
246                         // Find the first tag that we are deleting from\r
247                         tag = FindTag (pos + 1);\r
248 \r
249                         // Remove the characters from the line\r
250                         text.Remove (pos, count);\r
251 \r
252                         if (tag == null)\r
253                                 return;\r
254 \r
255                         // Check if we're crossing tag boundaries\r
256                         if ((pos + count) > (tag.Start + tag.Length - 1)) {\r
257                                 int left;\r
258 \r
259                                 // We have to delete cross tag boundaries\r
260                                 streamline = true;\r
261                                 left = count;\r
262 \r
263                                 left -= tag.Start + tag.Length - pos - 1;\r
264                                 tag = tag.Next;\r
265                                 \r
266                                 // Update the start of each tag\r
267                                 while ((tag != null) && (left > 0)) {\r
268                                         tag.Start -= count - left;\r
269 \r
270                                         if (tag.Length > left) {\r
271                                                 left = 0;\r
272                                         } else {\r
273                                                 left -= tag.Length;\r
274                                                 tag = tag.Next;\r
275                                         }\r
276 \r
277                                 }\r
278                         } else {\r
279                                 // We got off easy, same tag\r
280 \r
281                                 if (tag.Length == 0)\r
282                                         streamline = true;\r
283                         }\r
284 \r
285                         // Delete empty orphaned tags at the end\r
286                         LineTag walk = tag;\r
287                         while (walk != null && walk.Next != null && walk.Next.Length == 0) {\r
288                                 LineTag t = walk;\r
289                                 walk.Next = walk.Next.Next;\r
290                                 if (walk.Next != null)\r
291                                         walk.Next.Previous = t;\r
292                                 walk = walk.Next;\r
293                         }\r
294 \r
295                         // Adjust the start point of any tags following\r
296                         if (tag != null) {\r
297                                 tag = tag.Next;\r
298                                 while (tag != null) {\r
299                                         tag.Start -= count;\r
300                                         tag = tag.Next;\r
301                                 }\r
302                         }\r
303 \r
304                         recalc = true;\r
305 \r
306                         if (streamline)\r
307                                 Streamline (document.Lines);\r
308                 }\r
309                 \r
310                 // This doesn't do exactly what you would think, it just pulls off the \n part of the ending\r
311                 internal void DrawEnding (Graphics dc, float y)\r
312                 {\r
313                         if (document.multiline)\r
314                                 return;\r
315                         LineTag last = tags;\r
316                         while (last.Next != null)\r
317                                 last = last.Next;\r
318 \r
319                         string end_str = null;\r
320                         switch (document.LineEndingLength (ending)) {\r
321                         case 0:\r
322                                 return;\r
323                         case 1:\r
324                                 end_str = "\u0013";\r
325                                 break;\r
326                         case 2:\r
327                                 end_str = "\u0013\u0013";\r
328                                 break;\r
329                         case 3:\r
330                                 end_str = "\u0013\u0013\u0013";\r
331                                 break;\r
332                         }\r
333 \r
334                         TextBoxTextRenderer.DrawText (dc, end_str, last.Font, last.Color, X + widths [TextLengthWithoutEnding ()] - document.viewport_x + document.OffsetX, y, true);\r
335                 }\r
336 \r
337                 /// <summary> Find the tag on a line based on the character position, pos is 0-based</summary>\r
338                 internal LineTag FindTag (int pos)\r
339                 {\r
340                         LineTag tag;\r
341 \r
342                         if (pos == 0)\r
343                                 return tags;\r
344 \r
345                         tag = this.tags;\r
346 \r
347                         if (pos >= text.Length)\r
348                                 pos = text.Length - 1;\r
349 \r
350                         while (tag != null) {\r
351                                 if (((tag.Start - 1) <= pos) && (pos <= (tag.Start + tag.Length - 1)))\r
352                                         return LineTag.GetFinalTag (tag);\r
353 \r
354                                 tag = tag.Next;\r
355                         }\r
356                         \r
357                         return null;\r
358                 }\r
359 \r
360                 public override int GetHashCode ()\r
361                 {\r
362                         return base.GetHashCode ();\r
363                 }\r
364                 \r
365                 // Get the tag that contains this x coordinate\r
366                 public LineTag GetTag (int x)\r
367                 {\r
368                         LineTag tag = tags;\r
369                         \r
370                         // Coord is to the left of the first character\r
371                         if (x < tag.X)\r
372                                 return LineTag.GetFinalTag (tag);\r
373                         \r
374                         // All we have is a linked-list of tags, so we have\r
375                         // to do a linear search.  But there shouldn't be\r
376                         // too many tags per line in general.\r
377                         while (true) {\r
378                                 if (x >= tag.X && x < (tag.X + tag.Width))\r
379                                         return tag;\r
380                                         \r
381                                 if (tag.Next != null)\r
382                                         tag = tag.Next;\r
383                                 else\r
384                                         return LineTag.GetFinalTag (tag);                       \r
385                         }\r
386                 }\r
387                                         \r
388                 // Make sure we always have enoughs space in text and widths\r
389                 internal void Grow (int minimum)\r
390                 {\r
391                         int     length;\r
392                         float[] new_widths;\r
393 \r
394                         length = text.Length;\r
395 \r
396                         if ((length + minimum) > space) {\r
397                                 // We need to grow; double the size\r
398 \r
399                                 if ((length + minimum) > (space * 2)) {\r
400                                         new_widths = new float[length + minimum * 2 + 1];\r
401                                         space = length + minimum * 2;\r
402                                 } else {                                \r
403                                         new_widths = new float[space * 2 + 1];\r
404                                         space *= 2;\r
405                                 }\r
406                                 widths.CopyTo (new_widths, 0);\r
407 \r
408                                 widths = new_widths;\r
409                         }\r
410                 }\r
411                 public void InsertString (int pos, string s)\r
412                 {\r
413                         InsertString (pos, s, FindTag (pos));\r
414                 }\r
415 \r
416                 // Inserts a string at the given position\r
417                 public void InsertString (int pos, string s, LineTag tag)\r
418                 {\r
419                         int len = s.Length;\r
420 \r
421                         // Insert the text into the StringBuilder\r
422                         text.Insert (pos, s);\r
423 \r
424                         // Update the start position of every tag after this one\r
425                         tag = tag.Next;\r
426 \r
427                         while (tag != null) {\r
428                                 tag.Start += len;\r
429                                 tag = tag.Next;\r
430                         }\r
431 \r
432                         // Make sure we have room in the widths array\r
433                         Grow (len);\r
434 \r
435                         // This line needs to be recalculated\r
436                         recalc = true;\r
437                 }\r
438 \r
439                 /// <summary>\r
440                 /// Go through all tags on a line and recalculate all size-related values;\r
441                 /// returns true if lineheight changed\r
442                 /// </summary>\r
443                 internal bool RecalculateLine (Graphics g, Document doc)\r
444                 {\r
445                         LineTag tag;\r
446                         int pos;\r
447                         int len;\r
448                         SizeF size;\r
449                         float w;\r
450                         int prev_offset;\r
451                         bool retval;\r
452                         bool wrapped;\r
453                         Line line;\r
454                         int wrap_pos;\r
455                         int prev_height;\r
456                         int prev_ascent;\r
457 \r
458                         pos = 0;\r
459                         len = this.text.Length;\r
460                         tag = this.tags;\r
461                         prev_offset = this.offset;      // For drawing optimization calculations\r
462                         prev_height = this.height;\r
463                         prev_ascent = this.ascent;\r
464                         this.height = 0;                // Reset line height\r
465                         this.ascent = 0;                // Reset the ascent for the line\r
466                         tag.Shift = 0;\r
467 \r
468                         if (ending == LineEnding.Wrap)\r
469                                 widths[0] = document.left_margin + hanging_indent;\r
470                         else\r
471                                 widths[0] = document.left_margin + indent;\r
472 \r
473                         this.recalc = false;\r
474                         retval = false;\r
475                         wrapped = false;\r
476 \r
477                         wrap_pos = 0;\r
478 \r
479                         while (pos < len) {\r
480 \r
481                                 while (tag.Length == 0) {       // We should always have tags after a tag.length==0 unless len==0\r
482                                         //tag.Ascent = 0;\r
483                                         tag.Shift = tag.Line.ascent - tag.Ascent;\r
484                                         tag = tag.Next;\r
485                                 }\r
486 \r
487                                 size = tag.SizeOfPosition (g, pos);\r
488                                 w = size.Width;\r
489 \r
490                                 if (Char.IsWhiteSpace (text[pos]))\r
491                                         wrap_pos = pos + 1;\r
492 \r
493                                 if (doc.wrap) {\r
494                                         if ((wrap_pos > 0) && (wrap_pos != len) && (widths[pos] + w) + 5 > (doc.viewport_width - this.right_indent)) {\r
495                                                 // Make sure to set the last width of the line before wrapping\r
496                                                 widths[pos + 1] = widths[pos] + w;\r
497 \r
498                                                 pos = wrap_pos;\r
499                                                 len = text.Length;\r
500                                                 doc.Split (this, tag, pos);\r
501                                                 ending = LineEnding.Wrap;\r
502                                                 len = this.text.Length;\r
503 \r
504                                                 retval = true;\r
505                                                 wrapped = true;\r
506                                         } else if (pos > 1 && (widths[pos] + w) > (doc.viewport_width - this.right_indent)) {\r
507                                                 // No suitable wrap position was found so break right in the middle of a word\r
508 \r
509                                                 // Make sure to set the last width of the line before wrapping\r
510                                                 widths[pos + 1] = widths[pos] + w;\r
511 \r
512                                                 doc.Split (this, tag, pos);\r
513                                                 ending = LineEnding.Wrap;\r
514                                                 len = this.text.Length;\r
515                                                 retval = true;\r
516                                                 wrapped = true;\r
517                                         }\r
518                                 }\r
519 \r
520                                 // Contract all wrapped lines that follow back into our line\r
521                                 if (!wrapped) {\r
522                                         pos++;\r
523 \r
524                                         widths[pos] = widths[pos - 1] + w;\r
525 \r
526                                         if (pos == len) {\r
527                                                 line = doc.GetLine (this.line_no + 1);\r
528                                                 if ((line != null) && (ending == LineEnding.Wrap || ending == LineEnding.None)) {\r
529                                                         // Pull the two lines together\r
530                                                         doc.Combine (this.line_no, this.line_no + 1);\r
531                                                         len = this.text.Length;\r
532                                                         retval = true;\r
533                                                 }\r
534                                         }\r
535                                 }\r
536 \r
537                                 if (pos == (tag.Start - 1 + tag.Length)) {\r
538                                         // We just found the end of our current tag\r
539                                         tag.Height = tag.MaxHeight ();\r
540 \r
541                                         // Check if we're the tallest on the line (so far)\r
542                                         if (tag.Height > this.height)\r
543                                                 this.height = tag.Height;       // Yep; make sure the line knows\r
544 \r
545                                         if (tag.Ascent > this.ascent) {\r
546                                                 LineTag t;\r
547 \r
548                                                 // We have a tag that has a taller ascent than the line;\r
549                                                 t = tags;\r
550                                                 while (t != null && t != tag) {\r
551                                                         t.Shift = tag.Ascent - t.Ascent;\r
552                                                         t = t.Next;\r
553                                                 }\r
554 \r
555                                                 // Save on our line\r
556                                                 this.ascent = tag.Ascent;\r
557                                         } else {\r
558                                                 tag.Shift = this.ascent - tag.Ascent;\r
559                                         }\r
560 \r
561                                         tag = tag.Next;\r
562                                         if (tag != null) {\r
563                                                 tag.Shift = 0;\r
564                                                 wrap_pos = pos;\r
565                                         }\r
566                                 }\r
567                         }\r
568 \r
569                         while (tag != null) {   \r
570                                 tag.Shift = tag.Line.ascent - tag.Ascent;\r
571                                 tag = tag.Next;\r
572                         }\r
573 \r
574                         if (this.height == 0) {\r
575                                 this.height = tags.Font.Height;\r
576                                 tags.Height = this.height;\r
577                                 tags.Shift = 0;\r
578                         }\r
579 \r
580                         if (prev_offset != offset || prev_height != this.height || prev_ascent != this.ascent)\r
581                                 retval = true;\r
582 \r
583                         return retval;\r
584                 }\r
585 \r
586                 /// <summary>\r
587                 /// Recalculate a single line using the same char for every character in the line\r
588                 /// </summary>\r
589                 internal bool RecalculatePasswordLine (Graphics g, Document doc)\r
590                 {\r
591                         LineTag tag;\r
592                         int pos;\r
593                         int len;\r
594                         float w;\r
595                         bool ret;\r
596 \r
597                         pos = 0;\r
598                         len = this.text.Length;\r
599                         tag = this.tags;\r
600                         ascent = 0;\r
601                         tag.Shift = 0;\r
602 \r
603                         this.recalc = false;\r
604                         widths[0] = document.left_margin + indent;\r
605 \r
606                         w = TextBoxTextRenderer.MeasureText (g, doc.password_char, tags.Font).Width;\r
607 \r
608                         if (this.height != (int)tag.Font.Height)\r
609                                 ret = true;\r
610                         else\r
611                                 ret = false;\r
612 \r
613                         this.height = (int)tag.Font.Height;\r
614                         tag.Height = this.height;\r
615 \r
616                         this.ascent = tag.Ascent;\r
617 \r
618                         while (pos < len) {\r
619                                 pos++;\r
620                                 widths[pos] = widths[pos - 1] + w;\r
621                         }\r
622 \r
623                         return ret;\r
624                 }\r
625                 \r
626                 internal void Streamline (int lines)\r
627                 {\r
628                         LineTag current;\r
629                         LineTag next;\r
630 \r
631                         current = this.tags;\r
632                         next = current.Next;\r
633 \r
634                         //\r
635                         // Catch what the loop below wont; eliminate 0 length \r
636                         // tags, but only if there are other tags after us\r
637                         // We only eliminate text tags if there is another text tag\r
638                         // after it.  Otherwise we wind up trying to type on picture tags\r
639                         //\r
640                         while ((current.Length == 0) && (next != null) && (next.IsTextTag)) {\r
641                                 tags = next;\r
642                                 tags.Previous = null;\r
643                                 current = next;\r
644                                 next = current.Next;\r
645                         }\r
646 \r
647 \r
648                         if (next == null)\r
649                                 return;\r
650 \r
651                         while (next != null) {\r
652                                 // Take out 0 length tags unless it's the last tag in the document\r
653                                 if (current.IsTextTag && next.Length == 0 && next.IsTextTag) {\r
654                                         if ((next.Next != null) || (line_no != lines)) {\r
655                                                 current.Next = next.Next;\r
656                                                 if (current.Next != null) {\r
657                                                         current.Next.Previous = current;\r
658                                                 }\r
659                                                 next = current.Next;\r
660                                                 continue;\r
661                                         }\r
662                                 }\r
663                                 \r
664                                 if (current.Combine (next)) {\r
665                                         next = current.Next;\r
666                                         continue;\r
667                                 }\r
668 \r
669                                 current = current.Next;\r
670                                 next = current.Next;\r
671                         }\r
672                 }\r
673 \r
674                 internal int TextLengthWithoutEnding ()\r
675                 {\r
676                         return text.Length - document.LineEndingLength (ending);\r
677                 }\r
678 \r
679                 internal string TextWithoutEnding ()\r
680                 {\r
681                         return text.ToString (0, text.Length - document.LineEndingLength (ending));\r
682                 }\r
683                 #endregion      // Internal Methods\r
684 \r
685                 #region Administrative\r
686                 public object Clone ()\r
687                 {\r
688                         Line    clone;\r
689 \r
690                         clone = new Line (document, ending);\r
691 \r
692                         clone.text = text;\r
693 \r
694                         if (left != null)\r
695                                 clone.left = (Line)left.Clone();\r
696 \r
697                         if (left != null)\r
698                                 clone.left = (Line)left.Clone();\r
699 \r
700                         return clone;\r
701                 }\r
702 \r
703                 internal object CloneLine ()\r
704                 {\r
705                         Line    clone;\r
706 \r
707                         clone = new Line (document, ending);\r
708 \r
709                         clone.text = text;\r
710 \r
711                         return clone;\r
712                 }\r
713 \r
714                 public int CompareTo (object obj)\r
715                 {\r
716                         if (obj == null)\r
717                                 return 1;\r
718 \r
719                         if (! (obj is Line))\r
720                                 throw new ArgumentException("Object is not of type Line", "obj");\r
721 \r
722                         if (line_no < ((Line)obj).line_no)\r
723                                 return -1;\r
724                         else if (line_no > ((Line)obj).line_no)\r
725                                 return 1;\r
726                         else\r
727                                 return 0;\r
728                 }\r
729 \r
730                 public override bool Equals (object obj)\r
731                 {\r
732                         if (obj == null)\r
733                                 return false;\r
734 \r
735                         if (!(obj is Line))\r
736                                 return false;\r
737 \r
738                         if (obj == this)\r
739                                 return true;\r
740 \r
741                         if (line_no == ((Line)obj).line_no)\r
742                                 return true;\r
743 \r
744                         return false;\r
745                 }\r
746 \r
747                 public override string ToString()\r
748                 {\r
749                         return string.Format ("Line {0}", line_no);\r
750                 }\r
751                 #endregion      // Administrative\r
752         }\r
753 }\r