private StringBuilder password_cache;
private bool calc_pass;
private int char_count;
+ private bool enable_links;
// For calculating widths/heights
public static readonly StringFormat string_format = new StringFormat (StringFormat.GenericTypographic);
internal int viewport_x;
internal int viewport_y; // The visible area of the document
+ internal int offset_x;
+ internal int offset_y;
internal int viewport_width;
internal int viewport_height;
internal int document_x; // Width of the document
internal int document_y; // Height of the document
- internal Rectangle invalid;
-
internal int crlf_size; // 1 or 2, depending on whether we use \r\n or just \n
internal TextBoxBase owner; // Who's owning us?
viewport_x = 0;
viewport_y = 0;
+ offset_x = 0;
+ offset_y = 0;
+
crlf_size = 2;
// Default selection is empty
}
}
+ // UIA: Method used via reflection in TextRangeProvider
internal int Lines {
get {
return lines;
}
}
+ /// <summary>
+ /// Whether text is scanned for links
+ /// </summary>
+ internal bool EnableLinks {
+ get { return enable_links; }
+ set { enable_links = value; }
+ }
+
internal string PasswordChar {
get {
return password_char;
}
}
+ internal int OffsetX
+ {
+ get
+ {
+ return offset_x;
+ }
+
+ set
+ {
+ offset_x = value;
+ }
+ }
+
+ internal int OffsetY
+ {
+ get
+ {
+ return offset_y;
+ }
+
+ set
+ {
+ offset_y = value;
+ }
+ }
+
internal int ViewPortWidth {
get {
return viewport_width;
private void SetSelectionVisible (bool value)
{
+ bool old_selection_visible = selection_visible;
selection_visible = value;
// cursor and selection are enemies, we can't have both in the same room at the same time
if (owner.IsHandleCreated && !owner.show_caret_w_selection)
XplatUI.CaretVisible (owner.Handle, !selection_visible);
+ if (UIASelectionChanged != null && (selection_visible || old_selection_visible))
+ UIASelectionChanged (this, EventArgs.Empty);
}
private void DecrementLines(int line_no) {
// Lineheight changed, invalidate the rest of the document
if ((line.Y - viewport_y) >=0 ) {
// We formatted something that's in view, only draw parts of the screen
- owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, owner.Height - line.Y - viewport_y));
+ owner.Invalidate(new Rectangle(
+ offset_x,
+ line.Y - viewport_y + offset_y,
+ viewport_width,
+ owner.Height - (line.Y - viewport_y)));
} else {
// The tag was above the visible area, draw everything
owner.Invalidate();
} else {
switch(line.alignment) {
case HorizontalAlignment.Left: {
- owner.Invalidate(new Rectangle(line.X + (int)line.widths[pos] - viewport_x - 1, line.Y - viewport_y, viewport_width, line.height + 1));
+ owner.Invalidate(new Rectangle(
+ line.X + ((int)line.widths[pos] - viewport_x - 1) + offset_x,
+ line.Y - viewport_y + offset_y,
+ viewport_width,
+ line.height + 1));
break;
}
case HorizontalAlignment.Center: {
- owner.Invalidate(new Rectangle(line.X, line.Y - viewport_y, viewport_width, line.height + 1));
+ owner.Invalidate(new Rectangle(
+ line.X + offset_x,
+ line.Y - viewport_y + offset_y,
+ viewport_width,
+ line.height + 1));
break;
}
case HorizontalAlignment.Right: {
- owner.Invalidate(new Rectangle(line.X, line.Y - viewport_y, (int)line.widths[pos + 1] - viewport_x + line.X, line.height + 1));
+ owner.Invalidate(new Rectangle(
+ line.X + offset_x,
+ line.Y - viewport_y + offset_y,
+ (int)line.widths[pos + 1] - viewport_x + line.X,
+ line.height + 1));
break;
}
}
int start_line_top = line.Y;
- int end_line_bottom;
- Line end_line;
-
- end_line = GetLine (line.line_no + line_count);
+ Line end_line = GetLine (line.line_no + line_count);
if (end_line == null)
end_line = GetLine (lines);
-
- end_line_bottom = end_line.Y + end_line.height;
+ if (end_line == null)
+ return;
+
+ int end_line_bottom = end_line.Y + end_line.height;
if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no + line_count, true)) {
// Lineheight changed, invalidate the rest of the document
if ((line.Y - viewport_y) >=0 ) {
// We formatted something that's in view, only draw parts of the screen
- owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, owner.Height - line.Y - viewport_y));
+ owner.Invalidate(new Rectangle(
+ offset_x,
+ line.Y - viewport_y + offset_y,
+ viewport_width,
+ owner.Height - (line.Y - viewport_y)));
} else {
// The tag was above the visible area, draw everything
owner.Invalidate();
}
} else {
- int x = 0 - viewport_x;
+ int x = 0 - viewport_x + offset_x;
int w = viewport_width;
- int y = Math.Min (start_line_top - viewport_y, line.Y - viewport_y);
+ int y = Math.Min (start_line_top - viewport_y, line.Y - viewport_y) + offset_y;
int h = Math.Max (end_line_bottom - y, end_line.Y + end_line.height - y);
owner.Invalidate (new Rectangle (x, y, w, h));
}
}
+
+ /// <summary>
+ /// Scans the next paragraph for http:/ ftp:/ www. https:/ etc and marks the tags
+ /// as links.
+ /// </summary>
+ /// <param name="start_line">The line to start on</param>
+ /// <param name="link_changed">marks as true if something is changed</param>
+ private void ScanForLinks (Line start_line, ref bool link_changed)
+ {
+ Line current_line = start_line;
+ StringBuilder line_no_breaks = new StringBuilder ();
+ StringBuilder line_link_record = new StringBuilder ();
+ ArrayList cumulative_length_list = new ArrayList ();
+ bool update_caret_tag = false;
+
+ cumulative_length_list.Add (0);
+
+ while (current_line != null) {
+ line_no_breaks.Append (current_line.text);
+
+ if (link_changed == false)
+ current_line.LinkRecord (line_link_record);
+
+ current_line.ClearLinks ();
+
+ cumulative_length_list.Add (line_no_breaks.Length);
+
+ if (current_line.ending == LineEnding.Wrap)
+ current_line = GetLine (current_line.LineNo + 1);
+ else
+ break;
+ }
+
+ // search for protocols.. make sure www. is first!
+ string [] search_terms = new string [] { "www.", "http:/", "ftp:/", "https:/" };
+ int search_found = 0;
+ int index_found = 0;
+ string line_no_breaks_string = line_no_breaks.ToString ();
+ int line_no_breaks_index = 0;
+ int link_end = 0;
+
+ while (true) {
+ if (line_no_breaks_index >= line_no_breaks_string.Length)
+ break;
+
+ index_found = FirstIndexOfAny (line_no_breaks_string, search_terms, line_no_breaks_index, out search_found);
+
+ //no links found on this line
+ if (index_found == -1)
+ break;
+
+ if (search_found == 0) {
+ // if we are at the end of the line to analyse and the end of the line
+ // is "www." then there are no links here
+ if (line_no_breaks_string.Length == index_found + search_terms [0].Length)
+ break;
+
+ // if after www. we don't have a letter a digit or a @ or - or /
+ // then it is not a web address, we should continue searching
+ if (char.IsLetterOrDigit (line_no_breaks_string [index_found + search_terms [0].Length]) == false &&
+ "@/~".IndexOf (line_no_breaks_string [index_found + search_terms [0].Length].ToString ()) == -1) {
+ line_no_breaks_index = index_found + search_terms [0].Length;
+ continue;
+ }
+ }
+
+ link_end = line_no_breaks_string.Length - 1;
+ line_no_breaks_index = line_no_breaks_string.Length;
+
+ // we've found a link, we just need to find where it ends now
+ for (int i = index_found + search_terms [search_found].Length; i < line_no_breaks_string.Length; i++) {
+ if (line_no_breaks_string [i - 1] == '.') {
+ if (char.IsLetterOrDigit (line_no_breaks_string [i]) == false &&
+ "@/~".IndexOf (line_no_breaks_string [i].ToString ()) == -1) {
+ link_end = i - 1;
+ line_no_breaks_index = i;
+ break;
+ }
+ } else {
+ if (char.IsLetterOrDigit (line_no_breaks_string [i]) == false &&
+ "@-/:~.?=_&".IndexOf (line_no_breaks_string [i].ToString ()) == -1) {
+ link_end = i - 1;
+ line_no_breaks_index = i;
+ break;
+ }
+ }
+ }
+
+ string link_text = line_no_breaks_string.Substring (index_found, link_end - index_found + 1);
+ int current_cumulative = 0;
+
+ // we've found a link - index_found -> link_end
+ // now we just make all the tags as containing link and
+ // point them to the text for the whole link
+
+ current_line = start_line;
+
+ //find the line we start on
+ for (current_cumulative = 1; current_cumulative < cumulative_length_list.Count; current_cumulative++)
+ if ((int)cumulative_length_list [current_cumulative] > index_found)
+ break;
+
+ current_line = GetLine (start_line.LineNo + current_cumulative - 1);
+
+ // find the tag we start on
+ LineTag current_tag = current_line.FindTag (index_found - (int)cumulative_length_list [current_cumulative - 1] + 1);
+
+ if (current_tag.Start != (index_found - (int)cumulative_length_list [current_cumulative - 1]) + 1) {
+ if (current_tag == CaretTag)
+ update_caret_tag = true;
+
+ current_tag = current_tag.Break ((index_found - (int)cumulative_length_list [current_cumulative - 1]) + 1);
+ }
+
+ // set the tag
+ current_tag.IsLink = true;
+ current_tag.LinkText = link_text;
+
+ //go through each character
+ // find the tag we are in
+ // skip the number of characters in the tag
+ for (int i = 1; i < link_text.Length; i++) {
+ // on to a new word-wrapped line
+ if ((int)cumulative_length_list [current_cumulative] <= index_found + i) {
+
+ current_line = GetLine (start_line.LineNo + current_cumulative++);
+ current_tag = current_line.FindTag (index_found + i - (int)cumulative_length_list [current_cumulative - 1] + 1);
+
+ current_tag.IsLink = true;
+ current_tag.LinkText = link_text;
+
+ continue;
+ }
+
+ if (current_tag.End < index_found + 1 + i - (int)cumulative_length_list [current_cumulative - 1]) {
+ // skip empty tags in the middle of the URL
+ do {
+ current_tag = current_tag.Next;
+ } while (current_tag.Length == 0);
+
+ current_tag.IsLink = true;
+ current_tag.LinkText = link_text;
+ }
+ }
+
+ //if there are characters left in the tag after the link
+ // split the tag
+ // make the second part a non link
+ if (current_tag.End > (index_found + link_text.Length + 1) - (int)cumulative_length_list [current_cumulative - 1]) {
+ if (current_tag == CaretTag)
+ update_caret_tag = true;
+
+ current_tag.Break ((index_found + link_text.Length + 1) - (int)cumulative_length_list [current_cumulative - 1]);
+ }
+ }
+
+ if (update_caret_tag) {
+ CaretTag = LineTag.FindTag (CaretLine, CaretPosition);
+ link_changed = true;
+ } else {
+ if (link_changed == false) {
+ current_line = start_line;
+ StringBuilder new_link_record = new StringBuilder ();
+
+ while (current_line != null) {
+ current_line.LinkRecord (new_link_record);
+
+ if (current_line.ending == LineEnding.Wrap)
+ current_line = GetLine (current_line.LineNo + 1);
+ else
+ break;
+ }
+
+ if (new_link_record.Equals (line_link_record) == false)
+ link_changed = true;
+ }
+ }
+ }
+
+ private int FirstIndexOfAny (string haystack, string [] needles, int start_index, out int term_found)
+ {
+ term_found = -1;
+ int best_index = -1;
+
+ for (int i = 0; i < needles.Length; i++) {
+ int index = haystack.IndexOf (needles [i], start_index, StringComparison.InvariantCultureIgnoreCase);
+
+ if (index > -1) {
+ if (term_found > -1) {
+ if (index < best_index) {
+ best_index = index;
+ term_found = i;
+ }
+ } else {
+ best_index = index;
+ term_found = i;
+ }
+ }
+ }
+
+ return best_index;
+ }
+
+
+
+ private void InvalidateLinks (Rectangle clip)
+ {
+ for (int i = (owner.list_links.Count - 1); i >= 0; i--) {
+ TextBoxBase.LinkRectangle link = (TextBoxBase.LinkRectangle) owner.list_links [i];
+
+ if (clip.IntersectsWith (link.LinkAreaRectangle))
+ owner.list_links.RemoveAt (i);
+ }
+ }
#endregion // Private Methods
#region Internal Methods
+
+ internal void ScanForLinks (int start, int end, ref bool link_changed)
+ {
+ Line line = null;
+ LineEnding lastending = LineEnding.Rich;
+
+ // make sure we start scanning at the real begining of the line
+ while (true) {
+ if (start != 1 && GetLine (start - 1).ending == LineEnding.Wrap)
+ start--;
+ else
+ break;
+ }
+
+ for (int i = start; i <= end && i <= lines; i++) {
+ line = GetLine (i);
+
+ if (lastending != LineEnding.Wrap)
+ ScanForLinks (line, ref link_changed);
+
+ lastending = line.ending;
+
+ if (lastending == LineEnding.Wrap && (i + 1) <= end)
+ end++;
+ }
+ }
+
// Clear the document and reset state
internal void Empty() {
if (owner.Focused) {
if (caret.height != caret.tag.Height)
XplatUI.CreateCaret (owner.Handle, caret_width, caret.height);
- XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x, caret.line.Y + caret.tag.Shift - viewport_y + caret_shift);
+ XplatUI.SetCaretPos(owner.Handle,
+ offset_x + (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x,
+ offset_y + caret.line.Y + caret.tag.Shift - viewport_y + caret_shift);
}
if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
if (owner.ShowSelection && (!selection_visible || owner.show_caret_w_selection)) {
XplatUI.CreateCaret (owner.Handle, caret_width, caret.height);
- XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x, caret.line.Y + caret.tag.Shift - viewport_y + caret_shift);
+ XplatUI.SetCaretPos(owner.Handle,
+ (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x + offset_x,
+ offset_y + caret.line.Y + caret.tag.Shift - viewport_y + caret_shift);
}
if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
internal void CaretHasFocus() {
if ((caret.tag != null) && owner.IsHandleCreated) {
XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
- XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x, caret.line.Y + caret.tag.Shift - viewport_y + caret_shift);
+ XplatUI.SetCaretPos(owner.Handle,
+ offset_x + (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x,
+ offset_y + caret.line.Y + caret.tag.Shift - viewport_y + caret_shift);
DisplayCaret ();
}
XplatUI.DestroyCaret(owner.Handle);
}
- internal void AlignCaret() {
+ internal void AlignCaret ()
+ {
+ AlignCaret (true);
+ }
+
+ internal void AlignCaret(bool changeCaretTag) {
if (!owner.IsHandleCreated) {
return;
}
- caret.tag = LineTag.FindTag (caret.line, caret.pos);
+ if (changeCaretTag) {
+ caret.tag = LineTag.FindTag (caret.line, caret.pos);
- MoveCaretToTextTag ();
+ MoveCaretToTextTag ();
+ }
- caret.height = caret.tag.Height;
+ // if the caret has had SelectionFont changed to a
+ // different height, we reflect changes unless the new
+ // font is larger than the line (line recalculations
+ // ignore empty tags) in which case we make it equal
+ // the line height and then when text is entered
+ if (caret.tag.Height > caret.tag.Line.Height) {
+ caret.height = caret.line.height;
+ } else {
+ caret.height = caret.tag.Height;
+ }
if (owner.Focused) {
XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
- XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x, caret.line.Y + caret.tag.Shift - viewport_y + caret_shift);
+ XplatUI.SetCaretPos (owner.Handle,
+ offset_x + (int) caret.tag.Line.widths [caret.pos] + caret.line.X - viewport_x,
+ offset_y + caret.line.Y + viewport_y + caret_shift);
DisplayCaret ();
}
}
if (owner.Focused) {
- XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x, caret.line.Y + caret.tag.Shift - viewport_y + caret_shift);
+ XplatUI.SetCaretPos(owner.Handle,
+ offset_x + (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x,
+ offset_y + caret.line.Y + caret.tag.Shift - viewport_y + caret_shift);
DisplayCaret ();
}
case CaretDirection.PgUp: {
- if (viewport_y == 0 && owner.richtext) {
+ if (caret.line.line_no == 1 && owner.richtext) {
owner.vscroll.Value = 0;
Line line = GetLine (1);
PositionCaret (line, 0);
case CaretDirection.PgDn: {
- if (viewport_y + viewport_height >= document_y && owner.richtext) {
+ if (caret.line.line_no == lines && owner.richtext) {
owner.vscroll.Value = owner.vscroll.Maximum - viewport_height + 1;
Line line = GetLine (lines);
- PositionCaret (line, line.Text.Length);
+ PositionCaret (line, line.TextLengthWithoutEnding());
}
int y_offset = caret.line.Y - viewport_y;
Console.WriteLine ("</doc>");
}
+ // UIA: Used via reflection by TextProviderBehavior
+ internal void GetVisibleLineIndexes (Rectangle clip, out int start, out int end)
+ {
+ if (multiline) {
+ /* Expand the region slightly to be sure to
+ * paint the full extent of the line of text.
+ * See bug 464464.
+ */
+ start = GetLineByPixel(clip.Top + viewport_y - offset_y - 1, false).line_no;
+ end = GetLineByPixel(clip.Bottom + viewport_y - offset_y + 1, false).line_no;
+ } else {
+ start = GetLineByPixel(clip.Left + viewport_x - offset_x, false).line_no;
+ end = GetLineByPixel(clip.Right + viewport_x - offset_x, false).line_no;
+ }
+ }
+
internal void Draw (Graphics g, Rectangle clip)
{
Line line; // Current line being drawn
Color current_color;
// First, figure out from what line to what line we need to draw
+ GetVisibleLineIndexes (clip, out start, out end);
- if (multiline) {
- start = GetLineByPixel(clip.Top + viewport_y, false).line_no;
- end = GetLineByPixel(clip.Bottom + viewport_y, false).line_no;
- } else {
- start = GetLineByPixel(clip.Left + viewport_x, false).line_no;
- end = GetLineByPixel(clip.Right + viewport_x, false).line_no;
- }
+ // remove links in the list (used for mouse down events) that are within the clip area.
+ InvalidateLinks (clip);
///
/// We draw the single border ourself
/// Make sure that we aren't drawing one more line then we need to
line = GetLine (end - 1);
- if (line != null && clip.Bottom == line.Y + line.height + viewport_y)
- end--;
+ if (line != null && clip.Bottom == offset_y + line.Y + line.height - viewport_y)
+ end--;
line_no = start;
// Non multiline selection can be handled outside of the loop
if (!multiline && selection_visible && owner.ShowSelection) {
g.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (ThemeEngine.Current.ColorHighlight),
- selection_start.line.widths [selection_start.pos] +
+ offset_x + selection_start.line.widths [selection_start.pos] +
selection_start.line.X - viewport_x,
- selection_start.line.Y,
+ offset_y + selection_start.line.Y,
(selection_end.line.X + selection_end.line.widths [selection_end.pos]) -
(selection_start.line.X + selection_start.line.widths [selection_start.pos]),
selection_start.line.height);
while (line_no <= end) {
line = GetLine (line_no);
- float line_y = line.Y - viewport_y;
+ float line_y = line.Y - viewport_y + offset_y;
tag = line.tags;
if (!calc_pass) {
} else if (multiline) {
// lets draw some selection baby!! (non multiline selection is drawn outside the loop)
g.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (ThemeEngine.Current.ColorHighlight),
- line.widths [line_selection_start - 1] + line.X - viewport_x,
+ offset_x + line.widths [line_selection_start - 1] + line.X - viewport_x,
line_y, line.widths [line_selection_end - 1] - line.widths [line_selection_start - 1],
line.height);
}
}
- current_color = line.tags.Color;
+ current_color = line.tags.ColorToDisplay;
while (tag != null) {
// Skip empty tags
continue;
}
- if (((tag.X + tag.Width) < (clip.Left - viewport_x)) && (tag.X > (clip.Right - viewport_x))) {
+ if (((tag.X + tag.Width) < (clip.Left - viewport_x - offset_x)) &&
+ (tag.X > (clip.Right - viewport_x - offset_x))) {
tag = tag.Next;
continue;
}
if (tag.BackColor != Color.Empty) {
- g.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (tag.BackColor), tag.X + line.X - viewport_x,
+ g.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (tag.BackColor),
+ offset_x + tag.X + line.X - viewport_x,
line_y + tag.Shift, tag.Width, line.height);
}
- tag_color = tag.Color;
+ tag_color = tag.ColorToDisplay;
current_color = tag_color;
if (!owner.Enabled) {
Color a = tag.Color;
Color b = ThemeEngine.Current.ColorWindowText;
- if ((a.R == b.R) && (a.G == b.G) && (a.B == b.B)) {
+ if ((a.R == b.R) && (a.G == b.G) && (a.B == b.B))
tag_color = ThemeEngine.Current.ColorGrayText;
- }
- } else if (owner.read_only && !owner.backcolor_set) {
- tag_color = ThemeEngine.Current.ColorControlText;
- }
+
+ }
int tag_pos = tag.Start;
current_color = tag_color;
tag_pos = tag.End;
}
+ Rectangle text_size;
+
tag.Draw (g, current_color,
- line.X - viewport_x,
+ offset_x + line.X - viewport_x,
line_y + tag.Shift,
old_tag_pos - 1, Math.Min (tag.Start + tag.Length, tag_pos) - 1,
- text.ToString() );
+ text.ToString (), out text_size, tag.IsLink);
+
+ if (tag.IsLink) {
+ TextBoxBase.LinkRectangle link = new TextBoxBase.LinkRectangle (text_size);
+ link.LinkTag = tag;
+ owner.list_links.Add (link);
+ }
}
tag = tag.Next;
}
return string.Empty;
}
- // Insert text at the given position; use formatting at insertion point for inserted text
+ internal LineEnding StringToLineEnding (string ending)
+ {
+ switch (ending) {
+ case "\r":
+ return LineEnding.Limp;
+ case "\r\n":
+ return LineEnding.Hard;
+ case "\r\r\n":
+ return LineEnding.Soft;
+ case "\n":
+ return LineEnding.Rich;
+ default:
+ return LineEnding.None;
+ }
+ }
+
internal void Insert (Line line, int pos, bool update_caret, string s)
+ {
+ Insert (line, pos, update_caret, s, line.FindTag (pos));
+ }
+
+ // Insert text at the given position; use formatting at insertion point for inserted text
+ internal void Insert (Line line, int pos, bool update_caret, string s, LineTag tag)
{
int break_index;
int base_line;
LineEnding ending;
Line split_line;
- // Find the LineTag to add to
- LineTag tag = line.FindTag (pos);
-
// Don't recalculate while we mess around
SuspendRecalc ();
base_line = line.line_no;
old_line_count = lines;
+ // Discard chars after any possible -unlikely- end of file
+ int eof_index = s.IndexOf ('\0');
+ if (eof_index != -1)
+ s = s.Substring (0, eof_index);
+
break_index = GetLineEnding (s, 0, out ending, LineEnding.Hard | LineEnding.Rich);
// There are no line feeds in our text to be pasted
if (break_index == s.Length) {
- line.InsertString (pos, s);
+ line.InsertString (pos, s, tag);
} else {
// Add up to the first line feed to our current position
- line.InsertString (pos, s.Substring (0, break_index + LineEndingLength (ending)));
+ line.InsertString (pos, s.Substring (0, break_index + LineEndingLength (ending)), tag);
// Split the rest of the original line to a new line
Split (line, pos + (break_index + LineEndingLength (ending)));
// Allow the document to recalculate things
ResumeRecalc (false);
+ // Update our character count
+ CharCount += s.Length;
+
UpdateView (line, lines - old_line_count + 1, pos);
// Move the caret to the end of the inserted text if requested
// Inserts a character at the current caret position
internal void InsertCharAtCaret (char ch, bool move_caret)
{
- caret.line.InsertString (caret.pos, ch.ToString ());
+ caret.line.InsertString (caret.pos, ch.ToString(), caret.tag);
+ // Update our character count
+ CharCount++;
+
undo.RecordTyping (caret.line, caret.pos, ch);
UpdateView (caret.line, caret.pos);
if ((pos == 0 && forward == false) || (pos == line.text.Length && forward == true))
return;
- if (forward)
+ undo.BeginUserAction ("Delete");
+
+ if (forward) {
+ undo.RecordDeleteString (line, pos, line, pos + 1);
DeleteChars (line, pos, 1);
- else
+ } else {
+ undo.RecordDeleteString (line, pos - 1, line, pos);
DeleteChars (line, pos - 1, 1);
+ }
+
+ undo.EndUserAction ();
}
// Combine two lines
}
///<summary>Split line at given tag and position into two lines</summary>
- ///if more space becomes available on previous line</param>
+ ///if more space becomes available on previous line
internal void Split(Line line, LineTag tag, int pos) {
LineTag new_tag;
Line new_line;
move_sel_start = false;
move_sel_end = false;
+#if DEBUG
+ SanityCheck();
+
+ if (tag.End < pos)
+ throw new Exception ("Split called with the wrong tag");
+#endif
+
// Adjust selection and cursors
if (caret.line == line && caret.pos >= pos) {
move_caret = true;
caret.line = new_line;
caret.tag = new_line.tags;
caret.pos = 0;
+
+ if (selection_visible == false) {
+ SetSelectionToCaret (true);
+ }
}
if (move_sel_start) {
selection_end.pos = 0;
selection_end.tag = new_line.tags;
}
+
+#if DEBUG
+ SanityCheck ();
+#endif
return;
}
line.recalc = true;
new_line.recalc = true;
+ //make sure that if we are at the end of a tag, we start on the begining
+ //of a new one, if one exists... Stops us creating an empty tag and
+ //make the operation easier.
+ if (tag.Next != null && (tag.Next.Start - 1) == pos)
+ tag = tag.Next;
+
if ((tag.Start - 1) == pos) {
int shift;
// We can simply break the chain and move the tag into the next line
+
+ // if the tag we are moving is the first, create an empty tag
+ // for the line we are leaving behind
if (tag == line.tags) {
new_tag = new LineTag(line, 1);
new_tag.CopyFormattingFrom (tag);
caret.line = new_line;
caret.pos = caret.pos - pos;
caret.tag = caret.line.FindTag(caret.pos);
+
+ if (selection_visible == false) {
+ SetSelectionToCaret (true);
+ }
}
if (move_sel_start) {
selection_start.line = new_line;
selection_start.pos = selection_start.pos - pos;
- selection_start.tag = new_line.FindTag(selection_start.pos);
+ if (selection_start.Equals(selection_end))
+ selection_start.tag = new_line.FindTag(selection_start.pos);
+ else
+ selection_start.tag = new_line.FindTag (selection_start.pos + 1);
}
if (move_sel_end) {
CharCount -= line.text.Length - pos;
line.text.Remove(pos, line.text.Length - pos);
+#if DEBUG
+ SanityCheck ();
+#endif
+ }
+
+#if DEBUG
+ private void SanityCheck () {
+ for (int i = 1; i < lines; i++) {
+ LineTag tag = GetLine (i).tags;
+
+ if (tag.Start != 1)
+ throw new Exception ("Line doesn't start at the begining");
+
+ int start = 1;
+ tag = tag.Next;
+
+ while (tag != null) {
+ if (tag.Start == start)
+ throw new Exception ("Empty tag!");
+
+ if (tag.Start < start)
+ throw new Exception ("Insane!!");
+
+ start = tag.Start;
+ tag = tag.Next;
+ }
+ }
}
+#endif
// Adds a line of text, with given font.
// Bumps any line at that line number that already exists down
this.lines--;
}
+ // Invalidates the start line until the end of the viewstate
+ internal void InvalidateLinesAfter (Line start) {
+ owner.Invalidate (new Rectangle (0, start.Y - viewport_y, viewport_width, viewport_height - start.Y));
+ }
+
// Invalidate a section of the document to trigger redraw
internal void Invalidate(Line start, int start_pos, Line end, int end_pos) {
Line l1;
#endif
owner.Invalidate(new Rectangle (
- (int)l1.widths[p1] + l1.X - viewport_x,
- l1.Y - viewport_y,
- endpoint - (int)l1.widths[p1] + 1,
+ offset_x + (int)l1.widths[p1] + l1.X - viewport_x,
+ offset_y + l1.Y - viewport_y,
+ endpoint - (int) l1.widths [p1] + 1,
l1.height));
return;
}
// Three invalidates:
// First line from start
- owner.Invalidate(new Rectangle((int)l1.widths[p1] + l1.X - viewport_x, l1.Y - viewport_y, viewport_width, l1.height));
+ owner.Invalidate(new Rectangle(
+ offset_x + (int)l1.widths[p1] + l1.X - viewport_x,
+ offset_y + l1.Y - viewport_y,
+ viewport_width,
+ l1.height));
// lines inbetween
int y;
y = GetLine(l1.line_no + 1).Y;
- owner.Invalidate(new Rectangle(0, y - viewport_y, viewport_width, l2.Y - y));
+ owner.Invalidate(new Rectangle(
+ offset_x,
+ offset_y + y - viewport_y,
+ viewport_width,
+ l2.Y - y));
#if Debug
Console.WriteLine("Invaliding from {0}:{1} to {2}:{3} Middle => x={4}, y={5}, {6}x{7}", l1.line_no, p1, l2.line_no, p2, 0, y - viewport_y, viewport_width, l2.Y - y);
// Last line to end
- owner.Invalidate(new Rectangle((int)l2.widths[0] + l2.X - viewport_x, l2.Y - viewport_y, (int)l2.widths[p2] + 1, l2.height));
+ owner.Invalidate(new Rectangle(
+ offset_x + (int)l2.widths[0] + l2.X - viewport_x,
+ offset_y + l2.Y - viewport_y,
+ (int)l2.widths[p2] + 1,
+ l2.height));
+
#if Debug
Console.WriteLine("Invaliding from {0}:{1} to {2}:{3} End => x={4}, y={5}, {6}x{7}", l1.line_no, p1, l2.line_no, p2, (int)l2.widths[0] + l2.X - viewport_x, l2.Y - viewport_y, (int)l2.widths[p2] + 1, l2.height);
-
#endif
}
} else {
selection_start.line = selection_anchor.line;
selection_start.pos = selection_anchor.height;
- selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height);
+ selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height + 1);
selection_end.line = caret.line;
selection_end.tag = caret.line.tags;
}
if (caret < selection_anchor) {
selection_start.line = caret.line;
- selection_start.tag = caret.line.FindTag(start_pos);
+ selection_start.tag = caret.line.FindTag(start_pos + 1);
selection_start.pos = start_pos;
selection_end.line = selection_anchor.line;
} else {
selection_start.line = selection_anchor.line;
selection_start.pos = selection_anchor.height;
- selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height);
+ selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height + 1);
selection_end.line = caret.line;
selection_end.tag = caret.line.FindTag(end_pos);
this.Invalidate(selection_start.line, start_pos, caret.line, end_pos);
selection_start.line = caret.line;
- selection_start.tag = caret.line.FindTag(start_pos);
+ selection_start.tag = caret.line.FindTag(start_pos + 1);
selection_start.pos = start_pos;
selection_end.line = caret.line;
start = selection_start.line.line_no;
end = selection_end.line.line_no;
- sb.Append(selection_start.line.text.ToString(selection_start.pos, selection_start.line.text.Length - selection_start.pos) + Environment.NewLine);
+ sb.Append(selection_start.line.text.ToString(selection_start.pos, selection_start.line.text.Length - selection_start.pos));
if ((start + 1) < end) {
for (i = start + 1; i < end; i++) {
- sb.Append(GetLine(i).text.ToString() + Environment.NewLine);
+ sb.Append(GetLine(i).text.ToString());
}
}
internal void ReplaceSelection(string s, bool select_new) {
int i;
- int selection_pos_on_line = selection_start.pos;
int selection_start_pos = LineTagToCharIndex (selection_start.line, selection_start.pos);
SuspendRecalc ();
DeleteChars (selection_start.line, selection_start.pos, selection_end.pos - selection_start.pos);
// The tag might have been removed, we need to recalc it
- selection_start.tag = selection_start.line.FindTag(selection_start.pos);
+ selection_start.tag = selection_start.line.FindTag(selection_start.pos + 1);
} else {
int start;
int end;
undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
- InvalidateSelectionArea ();
+ InvalidateLinesAfter(selection_start.line);
// Delete first line
DeleteChars (selection_start.line, selection_start.pos, selection_start.line.text.Length - selection_start.pos);
}
+ // UIA: Method used via reflection in TextRangeProvider
+
/// <summary>Give it a Line number and it returns the Line object at with that line number</summary>
internal Line GetLine(int LineNo) {
Line line = document;
}
internal Line ParagraphStart(Line line) {
- while (line.ending == LineEnding.Wrap) {
- line = GetLine(line.line_no - 1);
- }
+ Line lastline = line;
+ do {
+ if (line.line_no <= 1)
+ break;
+
+ line = lastline;
+ lastline = GetLine (line.line_no - 1);
+ } while (lastline.ending == LineEnding.Wrap);
+
return line;
}
return last;
}
+ // UIA: Method used via reflection in TextProviderBehavior
+
// Give it x/y pixel coordinates and it returns the Tag at that position
internal LineTag FindCursor (int x, int y, out int index)
{
Line line;
+ x -= offset_x;
+ y -= offset_y;
+
line = GetLineByPixel (multiline ? y : x, false);
LineTag tag = line.GetTag (x);
- if (tag.Length == 0)
+ if (tag.Length == 0 && tag.Start == 1)
index = 0;
else
- index = tag.GetCharIndex (x);
+ index = tag.GetCharIndex (x - line.align_shift);
return tag;
}
} else {
// Special case, single line
LineTag.FormatText(start_line, start_pos, end_pos - start_pos, font, color, back_color, specified);
+
+ if ((end_pos - start_pos) == 0 && CaretTag.Length != 0)
+ CaretTag = CaretTag.Next;
}
}
}
// Fixup the positions, they can go kinda nuts
+ // (this is suspend and resume recalc - they set them to 1 and max)
start = Math.Max (start, 1);
end = Math.Min (end, lines);
line = GetLine(line_no++);
line.offset = offset;
+ // if we are not calculating a password
if (!calc_pass) {
if (!optimize) {
line.RecalculateLine(g, this);
HeightChanged(this, null);
}
}
+
+ // scan for links and tell us if its all
+ // changed, so we can update everything
+ if (EnableLinks)
+ ScanForLinks (start, end, ref changed);
+
UpdateCaret();
return changed;
}
internal event EventHandler WidthChanged;
internal event EventHandler HeightChanged;
internal event EventHandler LengthChanged;
+ internal event EventHandler UIASelectionChanged;
#endregion // Events
#region Administrative
redo_actions.Clear();
}
- internal void Undo ()
+ internal bool Undo ()
{
Action action;
bool user_action_finished = false;
if (undo_actions.Count == 0)
- return;
-
- // Nuke the redo queue
- redo_actions.Clear ();
+ return false;
locked = true;
do {
} while (!user_action_finished && undo_actions.Count > 0);
locked = false;
+
+ return true;
}
- internal void Redo ()
+ internal bool Redo ()
{
Action action;
bool user_action_finished = false;
if (redo_actions.Count == 0)
- return;
-
- // You can't undo anything after redoing
- undo_actions.Clear ();
+ return false;
locked = true;
do {
int start_index;
action = (Action) redo_actions.Pop ();
+ undo_actions.Push (action);
switch (action.type) {
} while (!user_action_finished && redo_actions.Count > 0);
locked = false;
+
+ return true;
}
#endregion // Internal Methods
if (locked)
return;
+ // Nuke the redo queue
+ redo_actions.Clear ();
+
Action ua = new Action ();
ua.type = ActionType.UserActionBegin;
ua.data = name;
if (locked)
return;
+ // Nuke the redo queue
+ redo_actions.Clear ();
+
Action a = new Action ();
// We cant simply store the string, because then formatting would be lost
if (locked || str.Length == 0)
return;
+ // Nuke the redo queue
+ redo_actions.Clear ();
+
Action a = new Action ();
a.type = ActionType.InsertString;
if (locked)
return;
+ // Nuke the redo queue
+ redo_actions.Clear ();
+
Action a = null;
if (undo_actions.Count > 0)
line.text = new StringBuilder (current.text.ToString (start, end - start));
// Copy tags from start to start+length onto new line
- current_tag = current.FindTag (start);
+ current_tag = current.FindTag (start + 1);
while ((current_tag != null) && (current_tag.Start <= end)) {
if ((current_tag.Start <= start) && (start < (current_tag.Start + current_tag.Length))) {
// start tag is within this tag