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