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
9 // The above copyright notice and this permission notice shall be
\r
10 // included in all copies or substantial portions of the Software.
\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
20 // Copyright (c) 2004-2006 Novell, Inc. (http://www.novell.com)
\r
23 // Peter Bartok pbartok@novell.com
\r
28 using System.Collections;
\r
29 using System.Drawing;
\r
30 using System.Drawing.Text;
\r
33 namespace System.Windows.Forms
\r
35 internal class Line : ICloneable, IComparable
\r
37 #region Local Variables
\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
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
65 #region Constructors
\r
66 internal Line (Document document, LineEnding ending)
\r
68 this.document = document;
\r
69 color = LineColor.Red;
\r
75 alignment = document.alignment;
\r
77 this.ending = ending;
\r
80 internal Line (Document document, int LineNo, string Text, Font font, Color color, LineEnding ending) : this (document, ending)
\r
82 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
\r
84 text = new StringBuilder (Text, space);
\r
86 this.ending = ending;
\r
88 widths = new float[space + 1];
\r
91 tags = new LineTag(this, 1);
\r
96 internal Line (Document document, int LineNo, string Text, HorizontalAlignment align, Font font, Color color, LineEnding ending) : this(document, ending)
\r
98 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
\r
100 text = new StringBuilder (Text, space);
\r
102 this.ending = ending;
\r
105 widths = new float[space + 1];
\r
108 tags = new LineTag(this, 1);
\r
110 tags.Color = color;
\r
113 internal Line (Document document, int LineNo, string Text, LineTag tag, LineEnding ending) : this(document, ending)
\r
115 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
\r
117 text = new StringBuilder (Text, space);
\r
118 this.ending = ending;
\r
121 widths = new float[space + 1];
\r
125 #endregion // Constructors
\r
127 #region Internal Properties
\r
128 internal HorizontalAlignment Alignment {
\r
129 get { return alignment; }
\r
131 if (alignment != value) {
\r
138 internal int HangingIndent {
\r
139 get { return hanging_indent; }
\r
141 hanging_indent = value;
\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
152 internal int Indent {
\r
153 get { return indent; }
\r
160 internal int LineNo {
\r
161 get { return line_no; }
\r
162 set { line_no = value; }
\r
165 internal int RightIndent {
\r
166 get { return right_indent; }
\r
168 right_indent = value;
\r
173 // UIA: Method used via reflection in TextRangeProvider
\r
174 internal int Width {
\r
176 int res = (int) widths [text.Length];
\r
181 internal string Text {
\r
182 get { return text.ToString(); }
\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
187 if (text.Length > prev_length)
\r
188 Grow (text.Length - prev_length);
\r
192 // UIA: Method used via reflection in TextRangeProvider
\r
195 if (document.multiline)
\r
196 return align_shift;
\r
197 return offset + align_shift;
\r
201 // UIA: Method used via reflection in TextRangeProvider
\r
204 if (!document.multiline)
\r
205 return document.top_margin;
\r
206 return document.top_margin + offset;
\r
209 #endregion // Internal Properties
\r
211 #region Internal Methods
\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
218 internal void LinkRecord (StringBuilder linkRecord)
\r
220 LineTag tag = tags;
\r
222 while (tag != null) {
\r
224 linkRecord.Append ("L");
\r
226 linkRecord.Append ("N");
\r
233 /// Clears all link properties from tags
\r
235 internal void ClearLinks ()
\r
237 LineTag tag = tags;
\r
239 while (tag != null) {
\r
240 tag.IsLink = false;
\r
245 public void DeleteCharacters(int pos, int count)
\r
248 bool streamline = false;
\r
250 // Can't delete more than the line has
\r
251 if (pos >= text.Length)
\r
254 // Find the first tag that we are deleting from
\r
255 tag = FindTag (pos + 1);
\r
257 // Remove the characters from the line
\r
258 text.Remove (pos, count);
\r
263 // Check if we're crossing tag boundaries
\r
264 if ((pos + count) > (tag.Start + tag.Length - 1)) {
\r
267 // We have to delete cross tag boundaries
\r
271 left -= tag.Start + tag.Length - pos - 1;
\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
281 if (tag_length > left) {
\r
284 left -= tag_length;
\r
290 // We got off easy, same tag
\r
292 if (tag.Length == 0)
\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
300 walk.Next = walk.Next.Next;
\r
301 if (walk.Next != null)
\r
302 walk.Next.Previous = t;
\r
306 // Adjust the start point of any tags following
\r
309 while (tag != null) {
\r
310 tag.Start -= count;
\r
318 Streamline (document.Lines);
\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
324 if (document.multiline)
\r
326 LineTag last = tags;
\r
327 while (last.Next != null)
\r
330 string end_str = null;
\r
331 switch (document.LineEndingLength (ending)) {
\r
335 end_str = "\u0013";
\r
338 end_str = "\u0013\u0013";
\r
341 end_str = "\u0013\u0013\u0013";
\r
345 TextBoxTextRenderer.DrawText (dc, end_str, last.Font, last.Color, X + widths [TextLengthWithoutEnding ()] - document.viewport_x + document.OffsetX, y, true);
\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
358 if (pos >= text.Length)
\r
359 pos = text.Length - 1;
\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
371 public override int GetHashCode ()
\r
373 return base.GetHashCode ();
\r
376 // Get the tag that contains this x coordinate
\r
377 public LineTag GetTag (int x)
\r
379 LineTag tag = tags;
\r
381 // Coord is to the left of the first character
\r
383 return LineTag.GetFinalTag (tag);
\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
389 if (x >= tag.X && x < (tag.X + tag.Width))
\r
392 if (tag.Next != null)
\r
395 return LineTag.GetFinalTag (tag);
\r
399 // Make sure we always have enoughs space in text and widths
\r
400 internal void Grow (int minimum)
\r
403 float[] new_widths;
\r
405 length = text.Length;
\r
407 if ((length + minimum) > space) {
\r
408 // We need to grow; double the size
\r
410 if ((length + minimum) > (space * 2)) {
\r
411 new_widths = new float[length + minimum * 2 + 1];
\r
412 space = length + minimum * 2;
\r
414 new_widths = new float[space * 2 + 1];
\r
417 widths.CopyTo (new_widths, 0);
\r
419 widths = new_widths;
\r
422 public void InsertString (int pos, string s)
\r
424 InsertString (pos, s, FindTag (pos));
\r
427 // Inserts a string at the given position
\r
428 public void InsertString (int pos, string s, LineTag tag)
\r
430 int len = s.Length;
\r
432 // Insert the text into the StringBuilder
\r
433 text.Insert (pos, s);
\r
435 // Update the start position of every tag after this one
\r
438 while (tag != null) {
\r
443 // Make sure we have room in the widths array
\r
446 // This line needs to be recalculated
\r
451 /// Go through all tags on a line and recalculate all size-related values;
\r
452 /// returns true if lineheight changed
\r
454 internal bool RecalculateLine (Graphics g, Document doc)
\r
470 len = this.text.Length;
\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
479 if (ending == LineEnding.Wrap)
\r
480 widths[0] = document.left_margin + hanging_indent;
\r
482 widths[0] = document.left_margin + indent;
\r
484 this.recalc = false;
\r
490 while (pos < len) {
\r
492 while (tag.Length == 0) { // We should always have tags after a tag.length==0 unless len==0
\r
494 tag.Shift = (tag.Line.ascent - tag.Ascent) / 72;
\r
498 size = tag.SizeOfPosition (g, pos);
\r
501 if (Char.IsWhiteSpace (text[pos]))
\r
502 wrap_pos = pos + 1;
\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
511 doc.Split (this, tag, pos);
\r
512 ending = LineEnding.Wrap;
\r
513 len = this.text.Length;
\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
520 // Make sure to set the last width of the line before wrapping
\r
521 widths[pos + 1] = widths[pos] + w;
\r
523 doc.Split (this, tag, pos);
\r
524 ending = LineEnding.Wrap;
\r
525 len = this.text.Length;
\r
531 // Contract all wrapped lines that follow back into our line
\r
535 widths[pos] = widths[pos - 1] + w;
\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
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
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
556 if (tag.Ascent > this.ascent) {
\r
559 // We have a tag that has a taller ascent than the line;
\r
561 while (t != null && t != tag) {
\r
562 t.Shift = (tag.Ascent - t.Ascent) / 72;
\r
566 // Save on our line
\r
567 this.ascent = tag.Ascent;
\r
569 tag.Shift = (this.ascent - tag.Ascent) / 72;
\r
580 while (tag != null) {
\r
581 tag.Shift = (tag.Line.ascent - tag.Ascent) / 72;
\r
585 if (this.height == 0) {
\r
586 this.height = tags.Font.Height;
\r
587 tags.Height = this.height;
\r
591 if (prev_offset != offset || prev_height != this.height || prev_ascent != this.ascent)
\r
598 /// Recalculate a single line using the same char for every character in the line
\r
600 internal bool RecalculatePasswordLine (Graphics g, Document doc)
\r
609 len = this.text.Length;
\r
614 this.recalc = false;
\r
615 widths[0] = document.left_margin + indent;
\r
617 w = TextBoxTextRenderer.MeasureText (g, doc.password_char, tags.Font).Width;
\r
619 if (this.height != (int)tag.Font.Height)
\r
624 this.height = (int)tag.Font.Height;
\r
625 tag.Height = this.height;
\r
627 this.ascent = tag.Ascent;
\r
629 while (pos < len) {
\r
631 widths[pos] = widths[pos - 1] + w;
\r
637 internal void Streamline (int lines)
\r
642 current = this.tags;
\r
643 next = current.Next;
\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
651 while ((current.Length == 0) && (next != null) && (next.IsTextTag)) {
\r
653 tags.Previous = null;
\r
655 next = current.Next;
\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
670 next = current.Next;
\r
675 if (current.Combine (next)) {
\r
676 next = current.Next;
\r
680 current = current.Next;
\r
681 next = current.Next;
\r
685 internal int TextLengthWithoutEnding ()
\r
687 return text.Length - document.LineEndingLength (ending);
\r
690 internal string TextWithoutEnding ()
\r
692 return text.ToString (0, text.Length - document.LineEndingLength (ending));
\r
694 #endregion // Internal Methods
\r
696 #region Administrative
\r
697 public object Clone ()
\r
701 clone = new Line (document, ending);
\r
706 clone.left = (Line)left.Clone();
\r
709 clone.left = (Line)left.Clone();
\r
714 internal object CloneLine ()
\r
718 clone = new Line (document, ending);
\r
725 public int CompareTo (object obj)
\r
730 if (! (obj is Line))
\r
731 throw new ArgumentException("Object is not of type Line", "obj");
\r
733 if (line_no < ((Line)obj).line_no)
\r
735 else if (line_no > ((Line)obj).line_no)
\r
741 public override bool Equals (object obj)
\r
746 if (!(obj is Line))
\r
752 if (line_no == ((Line)obj).line_no)
\r
758 public override string ToString()
\r
760 return string.Format ("Line {0}", line_no);
\r
762 #endregion // Administrative
\r