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 internal int DEFAULT_TEXT_LEN; //
\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
93 tags.Color = color;
\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 internal int Height {
\r
147 get { return height; }
\r
148 set { height = value; }
\r
151 internal int Indent {
\r
152 get { return indent; }
\r
159 internal int LineNo {
\r
160 get { return line_no; }
\r
161 set { line_no = value; }
\r
164 internal int RightIndent {
\r
165 get { return right_indent; }
\r
167 right_indent = value;
\r
172 internal int Width {
\r
174 int res = (int) widths [text.Length];
\r
179 internal string Text {
\r
180 get { return text.ToString(); }
\r
182 text = new StringBuilder(value, value.Length > DEFAULT_TEXT_LEN ? value.Length : DEFAULT_TEXT_LEN);
\r
188 if (document.multiline)
\r
189 return align_shift;
\r
190 return offset + align_shift;
\r
196 if (!document.multiline)
\r
197 return document.top_margin;
\r
198 return document.top_margin + offset;
\r
201 #endregion // Internal Properties
\r
203 #region Internal Methods
\r
204 public void DeleteCharacters (int pos, int count)
\r
207 bool streamline = false;
\r
209 // Can't delete more than the line has
\r
210 if (pos >= text.Length)
\r
213 // Find the first tag that we are deleting from
\r
214 tag = FindTag (pos);
\r
216 // Remove the characters from the line
\r
217 text.Remove (pos, count);
\r
222 // Check if we're crossing tag boundaries
\r
223 if ((pos + count) > (tag.Start + tag.Length - 1)) {
\r
226 // We have to delete cross tag boundaries
\r
230 left -= tag.Start + tag.Length - pos - 1;
\r
233 // Update the start of each tag
\r
234 while ((tag != null) && (left > 0)) {
\r
235 tag.Start -= count - left;
\r
237 if (tag.Length > left) {
\r
240 left -= tag.Length;
\r
246 // We got off easy, same tag
\r
248 if (tag.Length == 0)
\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
256 walk.Next = walk.Next.Next;
\r
257 if (walk.Next != null)
\r
258 walk.Next.Previous = t;
\r
262 // Adjust the start point of any tags following
\r
265 while (tag != null) {
\r
266 tag.Start -= count;
\r
274 Streamline (document.Lines);
\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
280 if (document.multiline)
\r
282 LineTag last = tags;
\r
283 while (last.Next != null)
\r
286 string end_str = null;
\r
287 switch (document.LineEndingLength (ending)) {
\r
291 end_str = "\u0013";
\r
294 end_str = "\u0013\u0013";
\r
297 end_str = "\u0013\u0013\u0013";
\r
301 TextBoxTextRenderer.DrawText (dc, end_str, last.Font, last.Color, X + widths [TextLengthWithoutEnding ()] - document.viewport_x, y, true);
\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
314 if (pos >= text.Length)
\r
315 pos = text.Length - 1;
\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
327 public override int GetHashCode ()
\r
329 return base.GetHashCode ();
\r
332 // Get the tag that contains this x coordinate
\r
333 public LineTag GetTag (int x)
\r
335 LineTag tag = tags;
\r
337 // Coord is to the left of the first character
\r
339 return LineTag.GetFinalTag (tag);
\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
345 if (x >= tag.X && x < (tag.X + tag.Width))
\r
348 if (tag.Next != null)
\r
351 return LineTag.GetFinalTag (tag);
\r
355 // Make sure we always have enoughs space in text and widths
\r
356 internal void Grow (int minimum)
\r
359 float[] new_widths;
\r
361 length = text.Length;
\r
363 if ((length + minimum) > space) {
\r
364 // We need to grow; double the size
\r
366 if ((length + minimum) > (space * 2)) {
\r
367 new_widths = new float[length + minimum * 2 + 1];
\r
368 space = length + minimum * 2;
\r
370 new_widths = new float[space * 2 + 1];
\r
373 widths.CopyTo (new_widths, 0);
\r
375 widths = new_widths;
\r
379 // Inserts a string at the given position
\r
380 public void InsertString (int pos, string s)
\r
382 LineTag tag = FindTag (pos);
\r
383 int len = s.Length;
\r
385 // Insert the text into the StringBuilder
\r
386 text.Insert (pos, s);
\r
388 // Update the start position of every tag after this one
\r
391 while (tag != null) {
\r
396 // Make sure we have room in the widths array
\r
399 // This line needs to be recalculated
\r
404 /// Go through all tags on a line and recalculate all size-related values;
\r
405 /// returns true if lineheight changed
\r
407 internal bool RecalculateLine (Graphics g, Document doc)
\r
421 len = this.text.Length;
\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
428 if (ending == LineEnding.Wrap)
\r
429 widths[0] = document.left_margin + hanging_indent;
\r
431 widths[0] = document.left_margin + indent;
\r
433 this.recalc = false;
\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
446 size = tag.SizeOfPosition (g, pos);
\r
449 if (Char.IsWhiteSpace (text[pos]))
\r
450 wrap_pos = pos + 1;
\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
459 doc.Split (this, tag, pos);
\r
460 ending = LineEnding.Wrap;
\r
461 len = this.text.Length;
\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
468 // Make sure to set the last width of the line before wrapping
\r
469 widths[pos + 1] = widths[pos] + w;
\r
471 doc.Split (this, tag, pos);
\r
472 ending = LineEnding.Wrap;
\r
473 len = this.text.Length;
\r
479 // Contract all wrapped lines that follow back into our line
\r
483 widths[pos] = widths[pos - 1] + w;
\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
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
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
504 if (tag.Ascent > this.ascent) {
\r
507 // We have a tag that has a taller ascent than the line;
\r
509 while (t != null && t != tag) {
\r
510 t.Shift = tag.Ascent - t.Ascent;
\r
514 // Save on our line
\r
515 this.ascent = tag.Ascent;
\r
517 tag.Shift = this.ascent - tag.Ascent;
\r
528 if (this.height == 0) {
\r
529 this.height = tags.Font.Height;
\r
530 tag.Height = this.height;
\r
533 if (prev_offset != offset)
\r
540 /// Recalculate a single line using the same char for every character in the line
\r
542 internal bool RecalculatePasswordLine (Graphics g, Document doc)
\r
551 len = this.text.Length;
\r
556 this.recalc = false;
\r
557 widths[0] = document.left_margin + indent;
\r
559 w = TextBoxTextRenderer.MeasureText (g, doc.password_char, tags.Font).Width;
\r
561 if (this.height != (int)tag.Font.Height)
\r
566 this.height = (int)tag.Font.Height;
\r
567 tag.Height = this.height;
\r
569 this.ascent = tag.Ascent;
\r
571 while (pos < len) {
\r
573 widths[pos] = widths[pos - 1] + w;
\r
579 internal void Streamline (int lines)
\r
584 current = this.tags;
\r
585 next = current.Next;
\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
593 while ((current.Length == 0) && (next != null) && (next.IsTextTag)) {
\r
595 tags.Previous = null;
\r
597 next = current.Next;
\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
612 next = current.Next;
\r
617 if (current.Combine (next)) {
\r
618 next = current.Next;
\r
622 current = current.Next;
\r
623 next = current.Next;
\r
627 internal int TextLengthWithoutEnding ()
\r
629 return text.Length - document.LineEndingLength (ending);
\r
632 internal string TextWithoutEnding ()
\r
634 return text.ToString (0, text.Length - document.LineEndingLength (ending));
\r
636 #endregion // Internal Methods
\r
638 #region Administrative
\r
639 public object Clone ()
\r
643 clone = new Line (document, ending);
\r
648 clone.left = (Line)left.Clone();
\r
651 clone.left = (Line)left.Clone();
\r
656 internal object CloneLine ()
\r
660 clone = new Line (document, ending);
\r
667 public int CompareTo (object obj)
\r
672 if (! (obj is Line))
\r
673 throw new ArgumentException("Object is not of type Line", "obj");
\r
675 if (line_no < ((Line)obj).line_no)
\r
677 else if (line_no > ((Line)obj).line_no)
\r
683 public override bool Equals (object obj)
\r
688 if (!(obj is Line))
\r
694 if (line_no == ((Line)obj).line_no)
\r
700 public override string ToString()
\r
702 return string.Format ("Line {0}", line_no);
\r
704 #endregion // Administrative
\r