3c1f479b9d
Former-commit-id: 806294f5ded97629b74c85c09952f2a74fe182d9
812 lines
21 KiB
C#
812 lines
21 KiB
C#
// Permission is hereby granted, free of charge, to any person obtaining
|
|
// a copy of this software and associated documentation files (the
|
|
// "Software"), to deal in the Software without restriction, including
|
|
// without limitation the rights to use, copy, modify, merge, publish,
|
|
// distribute, sublicense, and/or sell copies of the Software, and to
|
|
// permit persons to whom the Software is furnished to do so, subject to
|
|
// the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be
|
|
// included in all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
//
|
|
// Copyright (c) 2004-2006 Novell, Inc. (http://www.novell.com)
|
|
//
|
|
// Authors:
|
|
// Peter Bartok pbartok@novell.com
|
|
//
|
|
//
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Drawing;
|
|
using System.Drawing.Text;
|
|
using System.Text;
|
|
|
|
namespace System.Windows.Forms
|
|
{
|
|
internal class Line : ICloneable, IComparable
|
|
{
|
|
#region Local Variables
|
|
|
|
internal Document document;
|
|
// Stuff that matters for our line
|
|
internal StringBuilder text; // Characters for the line
|
|
internal float[] widths; // Width of each character; always one larger than text.Length
|
|
internal int space; // Number of elements in text and widths
|
|
internal int line_no; // Line number
|
|
internal LineTag tags; // Tags describing the text
|
|
internal int offset; // Baseline can be on the X or Y axis depending if we are in multiline mode or not
|
|
internal int height; // Height of the line (height of tallest tag)
|
|
internal int ascent; // Ascent of the line (ascent of the tallest tag)
|
|
internal HorizontalAlignment alignment; // Alignment of the line
|
|
internal int align_shift; // Pixel shift caused by the alignment
|
|
internal int indent; // Left indent for the first line
|
|
internal int hanging_indent; // Hanging indent (left indent for all but the first line)
|
|
internal int right_indent; // Right indent for all lines
|
|
internal LineEnding ending;
|
|
|
|
// Stuff that's important for the tree
|
|
internal Line parent; // Our parent line
|
|
internal Line left; // Line with smaller line number
|
|
internal Line right; // Line with higher line number
|
|
internal LineColor color; // We're doing a black/red tree. this is the node color
|
|
static int DEFAULT_TEXT_LEN = 0; //
|
|
internal bool recalc; // Line changed
|
|
|
|
private static Hashtable kerning_fonts = new Hashtable (); // record which fonts use kerning
|
|
#endregion // Local Variables
|
|
|
|
#region Constructors
|
|
internal Line (Document document, LineEnding ending)
|
|
{
|
|
this.document = document;
|
|
color = LineColor.Red;
|
|
left = null;
|
|
right = null;
|
|
parent = null;
|
|
text = null;
|
|
recalc = true;
|
|
alignment = document.alignment;
|
|
|
|
this.ending = ending;
|
|
}
|
|
|
|
internal Line (Document document, int LineNo, string Text, Font font, Color color, LineEnding ending) : this (document, ending)
|
|
{
|
|
space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
|
|
|
|
text = new StringBuilder (Text, space);
|
|
line_no = LineNo;
|
|
this.ending = ending;
|
|
|
|
widths = new float[space + 1];
|
|
|
|
|
|
tags = new LineTag(this, 1);
|
|
tags.Font = font;
|
|
tags.Color = color;
|
|
}
|
|
|
|
internal Line (Document document, int LineNo, string Text, HorizontalAlignment align, Font font, Color color, LineEnding ending) : this(document, ending)
|
|
{
|
|
space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
|
|
|
|
text = new StringBuilder (Text, space);
|
|
line_no = LineNo;
|
|
this.ending = ending;
|
|
alignment = align;
|
|
|
|
widths = new float[space + 1];
|
|
|
|
|
|
tags = new LineTag(this, 1);
|
|
tags.Font = font;
|
|
tags.Color = color;
|
|
}
|
|
|
|
internal Line (Document document, int LineNo, string Text, LineTag tag, LineEnding ending) : this(document, ending)
|
|
{
|
|
space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
|
|
|
|
text = new StringBuilder (Text, space);
|
|
this.ending = ending;
|
|
line_no = LineNo;
|
|
|
|
widths = new float[space + 1];
|
|
tags = tag;
|
|
}
|
|
|
|
#endregion // Constructors
|
|
|
|
#region Internal Properties
|
|
internal HorizontalAlignment Alignment {
|
|
get { return alignment; }
|
|
set {
|
|
if (alignment != value) {
|
|
alignment = value;
|
|
recalc = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
internal int HangingIndent {
|
|
get { return hanging_indent; }
|
|
set {
|
|
hanging_indent = value;
|
|
recalc = true;
|
|
}
|
|
}
|
|
|
|
// UIA: Method used via reflection in TextRangeProvider
|
|
internal int Height {
|
|
get { return height; }
|
|
set { height = value; }
|
|
}
|
|
|
|
internal int Indent {
|
|
get { return indent; }
|
|
set {
|
|
indent = value;
|
|
recalc = true;
|
|
}
|
|
}
|
|
|
|
internal int LineNo {
|
|
get { return line_no; }
|
|
set { line_no = value; }
|
|
}
|
|
|
|
internal int RightIndent {
|
|
get { return right_indent; }
|
|
set {
|
|
right_indent = value;
|
|
recalc = true;
|
|
}
|
|
}
|
|
|
|
// UIA: Method used via reflection in TextRangeProvider
|
|
internal int Width {
|
|
get {
|
|
int res = (int) widths [text.Length];
|
|
return res;
|
|
}
|
|
}
|
|
|
|
internal string Text {
|
|
get { return text.ToString(); }
|
|
set {
|
|
int prev_length = text.Length;
|
|
text = new StringBuilder(value, value.Length > DEFAULT_TEXT_LEN ? value.Length + 1 : DEFAULT_TEXT_LEN);
|
|
|
|
if (text.Length > prev_length)
|
|
Grow (text.Length - prev_length);
|
|
}
|
|
}
|
|
|
|
// UIA: Method used via reflection in TextRangeProvider
|
|
internal int X {
|
|
get {
|
|
if (document.multiline)
|
|
return align_shift;
|
|
return offset + align_shift;
|
|
}
|
|
}
|
|
|
|
// UIA: Method used via reflection in TextRangeProvider
|
|
internal int Y {
|
|
get {
|
|
if (!document.multiline)
|
|
return document.top_margin;
|
|
return document.top_margin + offset;
|
|
}
|
|
}
|
|
#endregion // Internal Properties
|
|
|
|
#region Internal Methods
|
|
|
|
/// <summary>
|
|
/// Builds a simple code to record which tags are links and how many tags
|
|
/// used to compare lines before and after to see if the scan for links
|
|
/// process has changed anything.
|
|
/// </summary>
|
|
internal void LinkRecord (StringBuilder linkRecord)
|
|
{
|
|
LineTag tag = tags;
|
|
|
|
while (tag != null) {
|
|
if (tag.IsLink)
|
|
linkRecord.Append ("L");
|
|
else
|
|
linkRecord.Append ("N");
|
|
|
|
tag = tag.Next;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears all link properties from tags
|
|
/// </summary>
|
|
internal void ClearLinks ()
|
|
{
|
|
LineTag tag = tags;
|
|
|
|
while (tag != null) {
|
|
tag.IsLink = false;
|
|
tag = tag.Next;
|
|
}
|
|
}
|
|
|
|
public void DeleteCharacters(int pos, int count)
|
|
{
|
|
LineTag tag;
|
|
bool streamline = false;
|
|
|
|
// Can't delete more than the line has
|
|
if (pos >= text.Length)
|
|
return;
|
|
|
|
// Find the first tag that we are deleting from
|
|
tag = FindTag (pos + 1);
|
|
|
|
// Remove the characters from the line
|
|
text.Remove (pos, count);
|
|
|
|
if (tag == null)
|
|
return;
|
|
|
|
// Check if we're crossing tag boundaries
|
|
if ((pos + count) > (tag.Start + tag.Length - 1)) {
|
|
int left;
|
|
|
|
// We have to delete cross tag boundaries
|
|
streamline = true;
|
|
left = count;
|
|
|
|
left -= tag.Start + tag.Length - pos - 1;
|
|
tag = tag.Next;
|
|
|
|
// Update the start of each tag
|
|
while ((tag != null) && (left > 0)) {
|
|
// Cache tag.Length as is will be indireclty modified
|
|
// by changes to tag.Start
|
|
int tag_length = tag.Length;
|
|
tag.Start -= count - left;
|
|
|
|
if (tag_length > left) {
|
|
left = 0;
|
|
} else {
|
|
left -= tag_length;
|
|
tag = tag.Next;
|
|
}
|
|
|
|
}
|
|
} else {
|
|
// We got off easy, same tag
|
|
|
|
if (tag.Length == 0)
|
|
streamline = true;
|
|
}
|
|
|
|
// Delete empty orphaned tags at the end
|
|
LineTag walk = tag;
|
|
while (walk != null && walk.Next != null && walk.Next.Length == 0) {
|
|
LineTag t = walk;
|
|
walk.Next = walk.Next.Next;
|
|
if (walk.Next != null)
|
|
walk.Next.Previous = t;
|
|
walk = walk.Next;
|
|
}
|
|
|
|
// Adjust the start point of any tags following
|
|
if (tag != null) {
|
|
tag = tag.Next;
|
|
while (tag != null) {
|
|
tag.Start -= count;
|
|
tag = tag.Next;
|
|
}
|
|
}
|
|
|
|
recalc = true;
|
|
|
|
if (streamline)
|
|
Streamline (document.Lines);
|
|
}
|
|
|
|
// This doesn't do exactly what you would think, it just pulls off the \n part of the ending
|
|
internal void DrawEnding (Graphics dc, float y)
|
|
{
|
|
if (document.multiline)
|
|
return;
|
|
LineTag last = tags;
|
|
while (last.Next != null)
|
|
last = last.Next;
|
|
|
|
string end_str = null;
|
|
switch (document.LineEndingLength (ending)) {
|
|
case 0:
|
|
return;
|
|
case 1:
|
|
end_str = "\u0013";
|
|
break;
|
|
case 2:
|
|
end_str = "\u0013\u0013";
|
|
break;
|
|
case 3:
|
|
end_str = "\u0013\u0013\u0013";
|
|
break;
|
|
}
|
|
|
|
TextBoxTextRenderer.DrawText (dc, end_str, last.Font, last.Color, X + widths [TextLengthWithoutEnding ()] - document.viewport_x + document.OffsetX, y, true);
|
|
}
|
|
|
|
/// <summary> Find the tag on a line based on the character position, pos is 0-based</summary>
|
|
internal LineTag FindTag (int pos)
|
|
{
|
|
LineTag tag;
|
|
|
|
if (pos == 0)
|
|
return tags;
|
|
|
|
tag = this.tags;
|
|
|
|
if (pos >= text.Length)
|
|
pos = text.Length - 1;
|
|
|
|
while (tag != null) {
|
|
if (((tag.Start - 1) <= pos) && (pos <= (tag.Start + tag.Length - 1)))
|
|
return LineTag.GetFinalTag (tag);
|
|
|
|
tag = tag.Next;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public override int GetHashCode ()
|
|
{
|
|
return base.GetHashCode ();
|
|
}
|
|
|
|
// Get the tag that contains this x coordinate
|
|
public LineTag GetTag (int x)
|
|
{
|
|
LineTag tag = tags;
|
|
|
|
// Coord is to the left of the first character
|
|
if (x < tag.X)
|
|
return LineTag.GetFinalTag (tag);
|
|
|
|
// All we have is a linked-list of tags, so we have
|
|
// to do a linear search. But there shouldn't be
|
|
// too many tags per line in general.
|
|
while (true) {
|
|
if (x >= tag.X && x < (tag.X + tag.Width))
|
|
return tag;
|
|
|
|
if (tag.Next != null)
|
|
tag = tag.Next;
|
|
else
|
|
return LineTag.GetFinalTag (tag);
|
|
}
|
|
}
|
|
|
|
// Make sure we always have enoughs space in text and widths
|
|
internal void Grow (int minimum)
|
|
{
|
|
int length;
|
|
float[] new_widths;
|
|
|
|
length = text.Length;
|
|
|
|
if ((length + minimum) > space) {
|
|
// We need to grow; double the size
|
|
|
|
if ((length + minimum) > (space * 2)) {
|
|
new_widths = new float[length + minimum * 2 + 1];
|
|
space = length + minimum * 2;
|
|
} else {
|
|
new_widths = new float[space * 2 + 1];
|
|
space *= 2;
|
|
}
|
|
widths.CopyTo (new_widths, 0);
|
|
|
|
widths = new_widths;
|
|
}
|
|
}
|
|
public void InsertString (int pos, string s)
|
|
{
|
|
InsertString (pos, s, FindTag (pos));
|
|
}
|
|
|
|
// Inserts a string at the given position
|
|
public void InsertString (int pos, string s, LineTag tag)
|
|
{
|
|
int len = s.Length;
|
|
|
|
// Insert the text into the StringBuilder
|
|
text.Insert (pos, s);
|
|
|
|
// Update the start position of every tag after this one
|
|
tag = tag.Next;
|
|
|
|
while (tag != null) {
|
|
tag.Start += len;
|
|
tag = tag.Next;
|
|
}
|
|
|
|
// Make sure we have room in the widths array
|
|
Grow (len);
|
|
|
|
// This line needs to be recalculated
|
|
recalc = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go through all tags on a line and recalculate all size-related values;
|
|
/// returns true if lineheight changed
|
|
/// </summary>
|
|
internal bool RecalculateLine (Graphics g, Document doc)
|
|
{
|
|
return RecalculateLine (g, doc, kerning_fonts.ContainsKey (tags.Font.GetHashCode ()));
|
|
}
|
|
|
|
private bool RecalculateLine (Graphics g, Document doc, bool handleKerning)
|
|
{
|
|
LineTag tag;
|
|
int pos;
|
|
int len;
|
|
SizeF size;
|
|
float w;
|
|
int prev_offset;
|
|
bool retval;
|
|
bool wrapped;
|
|
Line line;
|
|
int wrap_pos;
|
|
int prev_height;
|
|
int prev_ascent;
|
|
|
|
pos = 0;
|
|
len = this.text.Length;
|
|
tag = this.tags;
|
|
prev_offset = this.offset; // For drawing optimization calculations
|
|
prev_height = this.height;
|
|
prev_ascent = this.ascent;
|
|
this.height = 0; // Reset line height
|
|
this.ascent = 0; // Reset the ascent for the line
|
|
tag.Shift = 0; // Reset shift (which should be stored as pixels, not as points)
|
|
|
|
if (ending == LineEnding.Wrap)
|
|
widths[0] = document.left_margin + hanging_indent;
|
|
else
|
|
widths[0] = document.left_margin + indent;
|
|
|
|
this.recalc = false;
|
|
retval = false;
|
|
wrapped = false;
|
|
|
|
wrap_pos = 0;
|
|
|
|
while (pos < len) {
|
|
|
|
while (tag.Length == 0) { // We should always have tags after a tag.length==0 unless len==0
|
|
//tag.Ascent = 0;
|
|
tag.Shift = (tag.Line.ascent - tag.Ascent) / 72;
|
|
tag = tag.Next;
|
|
}
|
|
|
|
// kerning is a problem. The original code in this method assumed that the
|
|
// width of a string equals the sum of the widths of its characters. This is
|
|
// not true when kerning takes place during the display process. Since it's
|
|
// impossible to find out easily whether a font does kerning, and with which
|
|
// characters, we just detect that kerning must have happened and use a slower
|
|
// (but accurate) measurement for those fonts henceforth. Without handling
|
|
// kerning, many fonts for English become unreadable during typing for many
|
|
// input strings, and text in many other languages is even worse trying to
|
|
// type in TextBoxes.
|
|
// See https://bugzilla.xamarin.com/show_bug.cgi?id=26478 for details.
|
|
float newWidth;
|
|
if (handleKerning && !Char.IsWhiteSpace(text[pos]))
|
|
{
|
|
// MeasureText doesn't measure trailing spaces, so we do the best we can for those
|
|
// in the else branch.
|
|
size = TextBoxTextRenderer.MeasureText (g, text.ToString (0, pos + 1), tag.Font);
|
|
newWidth = widths[0] + size.Width;
|
|
}
|
|
else
|
|
{
|
|
size = tag.SizeOfPosition (g, pos);
|
|
w = size.Width;
|
|
newWidth = widths[pos] + w;
|
|
}
|
|
|
|
if (Char.IsWhiteSpace (text[pos]))
|
|
wrap_pos = pos + 1;
|
|
|
|
if (doc.wrap) {
|
|
if ((wrap_pos > 0) && (wrap_pos != len) && (newWidth + 5) > (doc.viewport_width - this.right_indent)) {
|
|
// Make sure to set the last width of the line before wrapping
|
|
widths[pos + 1] = newWidth;
|
|
|
|
pos = wrap_pos;
|
|
len = text.Length;
|
|
doc.Split (this, tag, pos);
|
|
ending = LineEnding.Wrap;
|
|
len = this.text.Length;
|
|
|
|
retval = true;
|
|
wrapped = true;
|
|
} else if (pos > 1 && newWidth > (doc.viewport_width - this.right_indent)) {
|
|
// No suitable wrap position was found so break right in the middle of a word
|
|
|
|
// Make sure to set the last width of the line before wrapping
|
|
widths[pos + 1] = newWidth;
|
|
|
|
doc.Split (this, tag, pos);
|
|
ending = LineEnding.Wrap;
|
|
len = this.text.Length;
|
|
retval = true;
|
|
wrapped = true;
|
|
}
|
|
}
|
|
|
|
// Contract all wrapped lines that follow back into our line
|
|
if (!wrapped) {
|
|
pos++;
|
|
|
|
widths[pos] = newWidth;
|
|
|
|
if (pos == len) {
|
|
line = doc.GetLine (this.line_no + 1);
|
|
if ((line != null) && (ending == LineEnding.Wrap || ending == LineEnding.None)) {
|
|
// Pull the two lines together
|
|
doc.Combine (this.line_no, this.line_no + 1);
|
|
len = this.text.Length;
|
|
retval = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pos == (tag.Start - 1 + tag.Length)) {
|
|
// We just found the end of our current tag
|
|
tag.Height = tag.MaxHeight ();
|
|
|
|
// Check if we're the tallest on the line (so far)
|
|
if (tag.Height > this.height)
|
|
this.height = tag.Height; // Yep; make sure the line knows
|
|
|
|
if (tag.Ascent > this.ascent) {
|
|
LineTag t;
|
|
|
|
// We have a tag that has a taller ascent than the line;
|
|
t = tags;
|
|
while (t != null && t != tag) {
|
|
t.Shift = (tag.Ascent - t.Ascent) / 72;
|
|
t = t.Next;
|
|
}
|
|
|
|
// Save on our line
|
|
this.ascent = tag.Ascent;
|
|
} else {
|
|
tag.Shift = (this.ascent - tag.Ascent) / 72;
|
|
}
|
|
|
|
tag = tag.Next;
|
|
if (tag != null) {
|
|
tag.Shift = 0;
|
|
wrap_pos = pos;
|
|
}
|
|
}
|
|
}
|
|
|
|
var fullText = text.ToString();
|
|
if (!handleKerning && fullText.Length > 1 && !wrapped)
|
|
{
|
|
// Check whether kerning takes place for this string and font.
|
|
var realSize = TextBoxTextRenderer.MeasureText(g, fullText, tags.Font);
|
|
float realWidth = realSize.Width + widths[0];
|
|
// MeasureText ignores trailing whitespace, so we will too at this point.
|
|
int length = fullText.TrimEnd().Length;
|
|
float sumWidth = widths[length];
|
|
if (realWidth != sumWidth)
|
|
{
|
|
kerning_fonts.Add(tags.Font.GetHashCode (), true);
|
|
// Using a slightly incorrect width this time around isn't that bad. All that happens
|
|
// is that the cursor is a pixel or two off until the next character is typed. It's
|
|
// the accumulation of pixel after pixel that causes display problems.
|
|
}
|
|
}
|
|
|
|
while (tag != null) {
|
|
tag.Shift = (tag.Line.ascent - tag.Ascent) / 72;
|
|
tag = tag.Next;
|
|
}
|
|
|
|
if (this.height == 0) {
|
|
this.height = tags.Font.Height;
|
|
tags.Height = this.height;
|
|
tags.Shift = 0;
|
|
}
|
|
|
|
if (prev_offset != offset || prev_height != this.height || prev_ascent != this.ascent)
|
|
retval = true;
|
|
|
|
return retval;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recalculate a single line using the same char for every character in the line
|
|
/// </summary>
|
|
internal bool RecalculatePasswordLine (Graphics g, Document doc)
|
|
{
|
|
LineTag tag;
|
|
int pos;
|
|
int len;
|
|
float w;
|
|
bool ret;
|
|
|
|
pos = 0;
|
|
len = this.text.Length;
|
|
tag = this.tags;
|
|
ascent = 0;
|
|
tag.Shift = 0;
|
|
|
|
this.recalc = false;
|
|
widths[0] = document.left_margin + indent;
|
|
|
|
w = TextBoxTextRenderer.MeasureText (g, doc.password_char, tags.Font).Width;
|
|
|
|
if (this.height != (int)tag.Font.Height)
|
|
ret = true;
|
|
else
|
|
ret = false;
|
|
|
|
this.height = (int)tag.Font.Height;
|
|
tag.Height = this.height;
|
|
|
|
this.ascent = tag.Ascent;
|
|
|
|
while (pos < len) {
|
|
pos++;
|
|
widths[pos] = widths[pos - 1] + w;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
internal void Streamline (int lines)
|
|
{
|
|
LineTag current;
|
|
LineTag next;
|
|
|
|
current = this.tags;
|
|
next = current.Next;
|
|
|
|
//
|
|
// Catch what the loop below wont; eliminate 0 length
|
|
// tags, but only if there are other tags after us
|
|
// We only eliminate text tags if there is another text tag
|
|
// after it. Otherwise we wind up trying to type on picture tags
|
|
//
|
|
while ((current.Length == 0) && (next != null) && (next.IsTextTag)) {
|
|
tags = next;
|
|
tags.Previous = null;
|
|
current = next;
|
|
next = current.Next;
|
|
}
|
|
|
|
|
|
if (next == null)
|
|
return;
|
|
|
|
while (next != null) {
|
|
// Take out 0 length tags unless it's the last tag in the document
|
|
if (current.IsTextTag && next.Length == 0 && next.IsTextTag) {
|
|
if ((next.Next != null) || (line_no != lines)) {
|
|
current.Next = next.Next;
|
|
if (current.Next != null) {
|
|
current.Next.Previous = current;
|
|
}
|
|
next = current.Next;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (current.Combine (next)) {
|
|
next = current.Next;
|
|
continue;
|
|
}
|
|
|
|
current = current.Next;
|
|
next = current.Next;
|
|
}
|
|
}
|
|
|
|
internal int TextLengthWithoutEnding ()
|
|
{
|
|
return text.Length - document.LineEndingLength (ending);
|
|
}
|
|
|
|
internal string TextWithoutEnding ()
|
|
{
|
|
return text.ToString (0, text.Length - document.LineEndingLength (ending));
|
|
}
|
|
#endregion // Internal Methods
|
|
|
|
#region Administrative
|
|
public object Clone ()
|
|
{
|
|
Line clone;
|
|
|
|
clone = new Line (document, ending);
|
|
|
|
clone.text = text;
|
|
|
|
if (left != null)
|
|
clone.left = (Line)left.Clone();
|
|
|
|
if (left != null)
|
|
clone.left = (Line)left.Clone();
|
|
|
|
return clone;
|
|
}
|
|
|
|
internal object CloneLine ()
|
|
{
|
|
Line clone;
|
|
|
|
clone = new Line (document, ending);
|
|
|
|
clone.text = text;
|
|
|
|
return clone;
|
|
}
|
|
|
|
public int CompareTo (object obj)
|
|
{
|
|
if (obj == null)
|
|
return 1;
|
|
|
|
if (! (obj is Line))
|
|
throw new ArgumentException("Object is not of type Line", "obj");
|
|
|
|
if (line_no < ((Line)obj).line_no)
|
|
return -1;
|
|
else if (line_no > ((Line)obj).line_no)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
public override bool Equals (object obj)
|
|
{
|
|
if (obj == null)
|
|
return false;
|
|
|
|
if (!(obj is Line))
|
|
return false;
|
|
|
|
if (obj == this)
|
|
return true;
|
|
|
|
if (line_no == ((Line)obj).line_no)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return string.Format ("Line {0}", line_no);
|
|
}
|
|
#endregion // Administrative
|
|
}
|
|
}
|