Wed Feb 24 15:47:16 CET 2010 Paolo Molaro <lupus@ximian.com>
[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                 // UIA: Method used via reflection in TextRangeProvider\r
147                 internal int Height {\r
148                         get { return height; }\r
149                         set { height = value; }\r
150                 }\r
151 \r
152                 internal int Indent {\r
153                         get { return indent; }\r
154                         set { \r
155                                 indent = value;\r
156                                 recalc = true;\r
157                         }\r
158                 }\r
159 \r
160                 internal int LineNo {\r
161                         get { return line_no; }\r
162                         set { line_no = value; }\r
163                 }\r
164 \r
165                 internal int RightIndent {\r
166                         get { return right_indent; }\r
167                         set { \r
168                                 right_indent = value;\r
169                                 recalc = true;\r
170                         }\r
171                 }\r
172                         \r
173                 // UIA: Method used via reflection in TextRangeProvider\r
174                 internal int Width {\r
175                         get {\r
176                                 int res = (int) widths [text.Length];\r
177                                 return res;\r
178                         }\r
179                 }\r
180 \r
181                 internal string Text {\r
182                         get { return text.ToString(); }\r
183                         set {\r
184                                 int prev_length = text.Length;\r
185                                 text = new StringBuilder(value, value.Length > DEFAULT_TEXT_LEN ? value.Length + 1 : DEFAULT_TEXT_LEN);\r
186 \r
187                                 if (text.Length > prev_length)\r
188                                         Grow (text.Length - prev_length);\r
189                         }\r
190                 }\r
191                 \r
192                 // UIA: Method used via reflection in TextRangeProvider\r
193                 internal int X {\r
194                         get {\r
195                                 if (document.multiline)\r
196                                         return align_shift;\r
197                                 return offset + align_shift;\r
198                         }\r
199                 }\r
200 \r
201                 // UIA: Method used via reflection in TextRangeProvider\r
202                 internal int Y {\r
203                         get {\r
204                                 if (!document.multiline)\r
205                                         return document.top_margin;\r
206                                 return document.top_margin + offset;\r
207                         }\r
208                 }\r
209                 #endregion      // Internal Properties\r
210 \r
211                 #region Internal Methods\r
212 \r
213                 /// <summary>\r
214                 ///  Builds a simple code to record which tags are links and how many tags\r
215                 ///  used to compare lines before and after to see if the scan for links\r
216                 ///  process has changed anything.\r
217                 /// </summary>\r
218                 internal void LinkRecord (StringBuilder linkRecord)\r
219                 {\r
220                         LineTag tag = tags;\r
221 \r
222                         while (tag != null) {\r
223                                 if (tag.IsLink)\r
224                                         linkRecord.Append ("L");\r
225                                 else\r
226                                         linkRecord.Append ("N");\r
227 \r
228                                 tag = tag.Next;\r
229                         }\r
230                 }\r
231 \r
232                 /// <summary>\r
233                 ///  Clears all link properties from tags\r
234                 /// </summary>\r
235                 internal void ClearLinks ()\r
236                 {\r
237                         LineTag tag = tags;\r
238 \r
239                         while (tag != null) {\r
240                                 tag.IsLink = false;\r
241                                 tag = tag.Next;\r
242                         }\r
243                 }\r
244 \r
245                 public void DeleteCharacters(int pos, int count)\r
246                 {\r
247                         LineTag tag;\r
248                         bool streamline = false;\r
249                         \r
250                         // Can't delete more than the line has\r
251                         if (pos >= text.Length)\r
252                                 return;\r
253 \r
254                         // Find the first tag that we are deleting from\r
255                         tag = FindTag (pos + 1);\r
256 \r
257                         // Remove the characters from the line\r
258                         text.Remove (pos, count);\r
259 \r
260                         if (tag == null)\r
261                                 return;\r
262 \r
263                         // Check if we're crossing tag boundaries\r
264                         if ((pos + count) > (tag.Start + tag.Length - 1)) {\r
265                                 int left;\r
266 \r
267                                 // We have to delete cross tag boundaries\r
268                                 streamline = true;\r
269                                 left = count;\r
270 \r
271                                 left -= tag.Start + tag.Length - pos - 1;\r
272                                 tag = tag.Next;\r
273                                 \r
274                                 // Update the start of each tag\r
275                                 while ((tag != null) && (left > 0)) {\r
276                                         // Cache tag.Length as is will be indireclty modified\r
277                                         // by changes to tag.Start\r
278                                         int tag_length = tag.Length;\r
279                                         tag.Start -= count - left;\r
280 \r
281                                         if (tag_length > left) {\r
282                                                 left = 0;\r
283                                         } else {\r
284                                                 left -= tag_length;\r
285                                                 tag = tag.Next;\r
286                                         }\r
287 \r
288                                 }\r
289                         } else {\r
290                                 // We got off easy, same tag\r
291 \r
292                                 if (tag.Length == 0)\r
293                                         streamline = true;\r
294                         }\r
295 \r
296                         // Delete empty orphaned tags at the end\r
297                         LineTag walk = tag;\r
298                         while (walk != null && walk.Next != null && walk.Next.Length == 0) {\r
299                                 LineTag t = walk;\r
300                                 walk.Next = walk.Next.Next;\r
301                                 if (walk.Next != null)\r
302                                         walk.Next.Previous = t;\r
303                                 walk = walk.Next;\r
304                         }\r
305 \r
306                         // Adjust the start point of any tags following\r
307                         if (tag != null) {\r
308                                 tag = tag.Next;\r
309                                 while (tag != null) {\r
310                                         tag.Start -= count;\r
311                                         tag = tag.Next;\r
312                                 }\r
313                         }\r
314 \r
315                         recalc = true;\r
316 \r
317                         if (streamline)\r
318                                 Streamline (document.Lines);\r
319                 }\r
320                 \r
321                 // This doesn't do exactly what you would think, it just pulls off the \n part of the ending\r
322                 internal void DrawEnding (Graphics dc, float y)\r
323                 {\r
324                         if (document.multiline)\r
325                                 return;\r
326                         LineTag last = tags;\r
327                         while (last.Next != null)\r
328                                 last = last.Next;\r
329 \r
330                         string end_str = null;\r
331                         switch (document.LineEndingLength (ending)) {\r
332                         case 0:\r
333                                 return;\r
334                         case 1:\r
335                                 end_str = "\u0013";\r
336                                 break;\r
337                         case 2:\r
338                                 end_str = "\u0013\u0013";\r
339                                 break;\r
340                         case 3:\r
341                                 end_str = "\u0013\u0013\u0013";\r
342                                 break;\r
343                         }\r
344 \r
345                         TextBoxTextRenderer.DrawText (dc, end_str, last.Font, last.Color, X + widths [TextLengthWithoutEnding ()] - document.viewport_x + document.OffsetX, y, true);\r
346                 }\r
347 \r
348                 /// <summary> Find the tag on a line based on the character position, pos is 0-based</summary>\r
349                 internal LineTag FindTag (int pos)\r
350                 {\r
351                         LineTag tag;\r
352 \r
353                         if (pos == 0)\r
354                                 return tags;\r
355 \r
356                         tag = this.tags;\r
357 \r
358                         if (pos >= text.Length)\r
359                                 pos = text.Length - 1;\r
360 \r
361                         while (tag != null) {\r
362                                 if (((tag.Start - 1) <= pos) && (pos <= (tag.Start + tag.Length - 1)))\r
363                                         return LineTag.GetFinalTag (tag);\r
364 \r
365                                 tag = tag.Next;\r
366                         }\r
367                         \r
368                         return null;\r
369                 }\r
370 \r
371                 public override int GetHashCode ()\r
372                 {\r
373                         return base.GetHashCode ();\r
374                 }\r
375                 \r
376                 // Get the tag that contains this x coordinate\r
377                 public LineTag GetTag (int x)\r
378                 {\r
379                         LineTag tag = tags;\r
380                         \r
381                         // Coord is to the left of the first character\r
382                         if (x < tag.X)\r
383                                 return LineTag.GetFinalTag (tag);\r
384                         \r
385                         // All we have is a linked-list of tags, so we have\r
386                         // to do a linear search.  But there shouldn't be\r
387                         // too many tags per line in general.\r
388                         while (true) {\r
389                                 if (x >= tag.X && x < (tag.X + tag.Width))\r
390                                         return tag;\r
391                                         \r
392                                 if (tag.Next != null)\r
393                                         tag = tag.Next;\r
394                                 else\r
395                                         return LineTag.GetFinalTag (tag);                       \r
396                         }\r
397                 }\r
398                                         \r
399                 // Make sure we always have enoughs space in text and widths\r
400                 internal void Grow (int minimum)\r
401                 {\r
402                         int     length;\r
403                         float[] new_widths;\r
404 \r
405                         length = text.Length;\r
406 \r
407                         if ((length + minimum) > space) {\r
408                                 // We need to grow; double the size\r
409 \r
410                                 if ((length + minimum) > (space * 2)) {\r
411                                         new_widths = new float[length + minimum * 2 + 1];\r
412                                         space = length + minimum * 2;\r
413                                 } else {                                \r
414                                         new_widths = new float[space * 2 + 1];\r
415                                         space *= 2;\r
416                                 }\r
417                                 widths.CopyTo (new_widths, 0);\r
418 \r
419                                 widths = new_widths;\r
420                         }\r
421                 }\r
422                 public void InsertString (int pos, string s)\r
423                 {\r
424                         InsertString (pos, s, FindTag (pos));\r
425                 }\r
426 \r
427                 // Inserts a string at the given position\r
428                 public void InsertString (int pos, string s, LineTag tag)\r
429                 {\r
430                         int len = s.Length;\r
431 \r
432                         // Insert the text into the StringBuilder\r
433                         text.Insert (pos, s);\r
434 \r
435                         // Update the start position of every tag after this one\r
436                         tag = tag.Next;\r
437 \r
438                         while (tag != null) {\r
439                                 tag.Start += len;\r
440                                 tag = tag.Next;\r
441                         }\r
442 \r
443                         // Make sure we have room in the widths array\r
444                         Grow (len);\r
445 \r
446                         // This line needs to be recalculated\r
447                         recalc = true;\r
448                 }\r
449 \r
450                 /// <summary>\r
451                 /// Go through all tags on a line and recalculate all size-related values;\r
452                 /// returns true if lineheight changed\r
453                 /// </summary>\r
454                 internal bool RecalculateLine (Graphics g, Document doc)\r
455                 {\r
456                         LineTag tag;\r
457                         int pos;\r
458                         int len;\r
459                         SizeF size;\r
460                         float w;\r
461                         int prev_offset;\r
462                         bool retval;\r
463                         bool wrapped;\r
464                         Line line;\r
465                         int wrap_pos;\r
466                         int prev_height;\r
467                         int prev_ascent;\r
468 \r
469                         pos = 0;\r
470                         len = this.text.Length;\r
471                         tag = this.tags;\r
472                         prev_offset = this.offset;      // For drawing optimization calculations\r
473                         prev_height = this.height;\r
474                         prev_ascent = this.ascent;\r
475                         this.height = 0;                // Reset line height\r
476                         this.ascent = 0;                // Reset the ascent for the line\r
477                         tag.Shift = 0;                  // Reset shift (which should be stored as pixels, not as points)\r
478 \r
479                         if (ending == LineEnding.Wrap)\r
480                                 widths[0] = document.left_margin + hanging_indent;\r
481                         else\r
482                                 widths[0] = document.left_margin + indent;\r
483 \r
484                         this.recalc = false;\r
485                         retval = false;\r
486                         wrapped = false;\r
487 \r
488                         wrap_pos = 0;\r
489 \r
490                         while (pos < len) {\r
491 \r
492                                 while (tag.Length == 0) {       // We should always have tags after a tag.length==0 unless len==0\r
493                                         //tag.Ascent = 0;\r
494                                         tag.Shift = (tag.Line.ascent - tag.Ascent) / 72;\r
495                                         tag = tag.Next;\r
496                                 }\r
497 \r
498                                 size = tag.SizeOfPosition (g, pos);\r
499                                 w = size.Width;\r
500 \r
501                                 if (Char.IsWhiteSpace (text[pos]))\r
502                                         wrap_pos = pos + 1;\r
503 \r
504                                 if (doc.wrap) {\r
505                                         if ((wrap_pos > 0) && (wrap_pos != len) && (widths[pos] + w) + 5 > (doc.viewport_width - this.right_indent)) {\r
506                                                 // Make sure to set the last width of the line before wrapping\r
507                                                 widths[pos + 1] = widths[pos] + w;\r
508 \r
509                                                 pos = wrap_pos;\r
510                                                 len = text.Length;\r
511                                                 doc.Split (this, tag, pos);\r
512                                                 ending = LineEnding.Wrap;\r
513                                                 len = this.text.Length;\r
514 \r
515                                                 retval = true;\r
516                                                 wrapped = true;\r
517                                         } else if (pos > 1 && (widths[pos] + w) > (doc.viewport_width - this.right_indent)) {\r
518                                                 // No suitable wrap position was found so break right in the middle of a word\r
519 \r
520                                                 // Make sure to set the last width of the line before wrapping\r
521                                                 widths[pos + 1] = widths[pos] + w;\r
522 \r
523                                                 doc.Split (this, tag, pos);\r
524                                                 ending = LineEnding.Wrap;\r
525                                                 len = this.text.Length;\r
526                                                 retval = true;\r
527                                                 wrapped = true;\r
528                                         }\r
529                                 }\r
530 \r
531                                 // Contract all wrapped lines that follow back into our line\r
532                                 if (!wrapped) {\r
533                                         pos++;\r
534 \r
535                                         widths[pos] = widths[pos - 1] + w;\r
536 \r
537                                         if (pos == len) {\r
538                                                 line = doc.GetLine (this.line_no + 1);\r
539                                                 if ((line != null) && (ending == LineEnding.Wrap || ending == LineEnding.None)) {\r
540                                                         // Pull the two lines together\r
541                                                         doc.Combine (this.line_no, this.line_no + 1);\r
542                                                         len = this.text.Length;\r
543                                                         retval = true;\r
544                                                 }\r
545                                         }\r
546                                 }\r
547 \r
548                                 if (pos == (tag.Start - 1 + tag.Length)) {\r
549                                         // We just found the end of our current tag\r
550                                         tag.Height = tag.MaxHeight ();\r
551 \r
552                                         // Check if we're the tallest on the line (so far)\r
553                                         if (tag.Height > this.height)\r
554                                                 this.height = tag.Height;       // Yep; make sure the line knows\r
555 \r
556                                         if (tag.Ascent > this.ascent) {\r
557                                                 LineTag t;\r
558 \r
559                                                 // We have a tag that has a taller ascent than the line;\r
560                                                 t = tags;\r
561                                                 while (t != null && t != tag) {\r
562                                                         t.Shift = (tag.Ascent - t.Ascent) / 72;\r
563                                                         t = t.Next;\r
564                                                 }\r
565 \r
566                                                 // Save on our line\r
567                                                 this.ascent = tag.Ascent;\r
568                                         } else {\r
569                                                 tag.Shift = (this.ascent - tag.Ascent) / 72;\r
570                                         }\r
571 \r
572                                         tag = tag.Next;\r
573                                         if (tag != null) {\r
574                                                 tag.Shift = 0;\r
575                                                 wrap_pos = pos;\r
576                                         }\r
577                                 }\r
578                         }\r
579 \r
580                         while (tag != null) {   \r
581                                 tag.Shift = (tag.Line.ascent - tag.Ascent) / 72;\r
582                                 tag = tag.Next;\r
583                         }\r
584 \r
585                         if (this.height == 0) {\r
586                                 this.height = tags.Font.Height;\r
587                                 tags.Height = this.height;\r
588                                 tags.Shift = 0;\r
589                         }\r
590 \r
591                         if (prev_offset != offset || prev_height != this.height || prev_ascent != this.ascent)\r
592                                 retval = true;\r
593 \r
594                         return retval;\r
595                 }\r
596 \r
597                 /// <summary>\r
598                 /// Recalculate a single line using the same char for every character in the line\r
599                 /// </summary>\r
600                 internal bool RecalculatePasswordLine (Graphics g, Document doc)\r
601                 {\r
602                         LineTag tag;\r
603                         int pos;\r
604                         int len;\r
605                         float w;\r
606                         bool ret;\r
607 \r
608                         pos = 0;\r
609                         len = this.text.Length;\r
610                         tag = this.tags;\r
611                         ascent = 0;\r
612                         tag.Shift = 0;\r
613 \r
614                         this.recalc = false;\r
615                         widths[0] = document.left_margin + indent;\r
616 \r
617                         w = TextBoxTextRenderer.MeasureText (g, doc.password_char, tags.Font).Width;\r
618 \r
619                         if (this.height != (int)tag.Font.Height)\r
620                                 ret = true;\r
621                         else\r
622                                 ret = false;\r
623 \r
624                         this.height = (int)tag.Font.Height;\r
625                         tag.Height = this.height;\r
626 \r
627                         this.ascent = tag.Ascent;\r
628 \r
629                         while (pos < len) {\r
630                                 pos++;\r
631                                 widths[pos] = widths[pos - 1] + w;\r
632                         }\r
633 \r
634                         return ret;\r
635                 }\r
636                 \r
637                 internal void Streamline (int lines)\r
638                 {\r
639                         LineTag current;\r
640                         LineTag next;\r
641 \r
642                         current = this.tags;\r
643                         next = current.Next;\r
644 \r
645                         //\r
646                         // Catch what the loop below wont; eliminate 0 length \r
647                         // tags, but only if there are other tags after us\r
648                         // We only eliminate text tags if there is another text tag\r
649                         // after it.  Otherwise we wind up trying to type on picture tags\r
650                         //\r
651                         while ((current.Length == 0) && (next != null) && (next.IsTextTag)) {\r
652                                 tags = next;\r
653                                 tags.Previous = null;\r
654                                 current = next;\r
655                                 next = current.Next;\r
656                         }\r
657 \r
658 \r
659                         if (next == null)\r
660                                 return;\r
661 \r
662                         while (next != null) {\r
663                                 // Take out 0 length tags unless it's the last tag in the document\r
664                                 if (current.IsTextTag && next.Length == 0 && next.IsTextTag) {\r
665                                         if ((next.Next != null) || (line_no != lines)) {\r
666                                                 current.Next = next.Next;\r
667                                                 if (current.Next != null) {\r
668                                                         current.Next.Previous = current;\r
669                                                 }\r
670                                                 next = current.Next;\r
671                                                 continue;\r
672                                         }\r
673                                 }\r
674                                 \r
675                                 if (current.Combine (next)) {\r
676                                         next = current.Next;\r
677                                         continue;\r
678                                 }\r
679 \r
680                                 current = current.Next;\r
681                                 next = current.Next;\r
682                         }\r
683                 }\r
684 \r
685                 internal int TextLengthWithoutEnding ()\r
686                 {\r
687                         return text.Length - document.LineEndingLength (ending);\r
688                 }\r
689 \r
690                 internal string TextWithoutEnding ()\r
691                 {\r
692                         return text.ToString (0, text.Length - document.LineEndingLength (ending));\r
693                 }\r
694                 #endregion      // Internal Methods\r
695 \r
696                 #region Administrative\r
697                 public object Clone ()\r
698                 {\r
699                         Line    clone;\r
700 \r
701                         clone = new Line (document, ending);\r
702 \r
703                         clone.text = text;\r
704 \r
705                         if (left != null)\r
706                                 clone.left = (Line)left.Clone();\r
707 \r
708                         if (left != null)\r
709                                 clone.left = (Line)left.Clone();\r
710 \r
711                         return clone;\r
712                 }\r
713 \r
714                 internal object CloneLine ()\r
715                 {\r
716                         Line    clone;\r
717 \r
718                         clone = new Line (document, ending);\r
719 \r
720                         clone.text = text;\r
721 \r
722                         return clone;\r
723                 }\r
724 \r
725                 public int CompareTo (object obj)\r
726                 {\r
727                         if (obj == null)\r
728                                 return 1;\r
729 \r
730                         if (! (obj is Line))\r
731                                 throw new ArgumentException("Object is not of type Line", "obj");\r
732 \r
733                         if (line_no < ((Line)obj).line_no)\r
734                                 return -1;\r
735                         else if (line_no > ((Line)obj).line_no)\r
736                                 return 1;\r
737                         else\r
738                                 return 0;\r
739                 }\r
740 \r
741                 public override bool Equals (object obj)\r
742                 {\r
743                         if (obj == null)\r
744                                 return false;\r
745 \r
746                         if (!(obj is Line))\r
747                                 return false;\r
748 \r
749                         if (obj == this)\r
750                                 return true;\r
751 \r
752                         if (line_no == ((Line)obj).line_no)\r
753                                 return true;\r
754 \r
755                         return false;\r
756                 }\r
757 \r
758                 public override string ToString()\r
759                 {\r
760                         return string.Format ("Line {0}", line_no);\r
761                 }\r
762                 #endregion      // Administrative\r
763         }\r
764 }\r