1 // Permission is hereby granted, free of charge, to any person obtaining
2 // a copy of this software and associated documentation files (the
3 // "Software"), to deal in the Software without restriction, including
4 // without limitation the rights to use, copy, modify, merge, publish,
5 // distribute, sublicense, and/or sell copies of the Software, and to
6 // permit persons to whom the Software is furnished to do so, subject to
7 // the following conditions:
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
12 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 // Copyright (c) 2004-2006 Novell, Inc. (http://www.novell.com)
23 // Peter Bartok pbartok@novell.com
29 // There's still plenty of things missing, I've got most of it planned, just hadn't had
30 // the time to write it all yet.
31 // Stuff missing (in no particular order):
32 // - Align text after RecalculateLine
33 // - Implement tag types for hotlinks, etc.
34 // - Implement CaretPgUp/PgDown
37 // selection_start.pos and selection_end.pos are 0-based
38 // selection_start.pos = first selected char
39 // selection_end.pos = first NOT-selected char
41 // FormatText methods are 1-based (as are all tags, LineTag.Start is 1 for
42 // the first character on a line; the reason is that 0 is the position
43 // *before* the first character on a line
49 using System.Collections;
51 using System.Drawing.Text;
53 using RTF=System.Windows.Forms.RTF;
55 namespace System.Windows.Forms {
56 internal enum LineColor {
61 internal enum CaretSelection {
62 Position, // Selection=Caret
63 Word, // Selection=Word under caret
64 Line // Selection=Line under caret
68 internal enum FormatSpecified {
76 internal enum CaretDirection {
77 CharForward, // Move a char to the right
78 CharBack, // Move a char to the left
79 LineUp, // Move a line up
80 LineDown, // Move a line down
81 Home, // Move to the beginning of the line
82 End, // Move to the end of the line
83 PgUp, // Move one page up
84 PgDn, // Move one page down
85 CtrlPgUp, // Move caret to the first visible char in the viewport
86 CtrlPgDn, // Move caret to the last visible char in the viewport
87 CtrlHome, // Move to the beginning of the document
88 CtrlEnd, // Move to the end of the document
89 WordBack, // Move to the beginning of the previous word (or beginning of line)
90 WordForward, // Move to the beginning of the next word (or end of line)
91 SelectionStart, // Move to the beginning of the current selection
92 SelectionEnd, // Move to the end of the current selection
93 CharForwardNoWrap, // Move a char forward, but don't wrap onto the next line
94 CharBackNoWrap // Move a char backward, but don't wrap onto the previous line
97 internal enum LineEnding {
98 Wrap = 1, // line wraps to the next line
107 internal class Document : ICloneable, IEnumerable {
109 // FIXME - go through code and check for places where
110 // we do explicit comparisons instead of using the compare overloads
111 internal struct Marker {
113 internal LineTag tag;
117 public static bool operator<(Marker lhs, Marker rhs) {
118 if (lhs.line.line_no < rhs.line.line_no) {
122 if (lhs.line.line_no == rhs.line.line_no) {
123 if (lhs.pos < rhs.pos) {
130 public static bool operator>(Marker lhs, Marker rhs) {
131 if (lhs.line.line_no > rhs.line.line_no) {
135 if (lhs.line.line_no == rhs.line.line_no) {
136 if (lhs.pos > rhs.pos) {
143 public static bool operator==(Marker lhs, Marker rhs) {
144 if ((lhs.line.line_no == rhs.line.line_no) && (lhs.pos == rhs.pos)) {
150 public static bool operator!=(Marker lhs, Marker rhs) {
151 if ((lhs.line.line_no != rhs.line.line_no) || (lhs.pos != rhs.pos)) {
157 public void Combine(Line move_to_line, int move_to_line_length) {
159 pos += move_to_line_length;
160 tag = LineTag.FindTag(line, pos);
163 // This is for future use, right now Document.Split does it by hand, with some added shortcut logic
164 public void Split(Line move_to_line, int split_at) {
167 tag = LineTag.FindTag(line, pos);
170 public override bool Equals(object obj) {
171 return this==(Marker)obj;
174 public override int GetHashCode() {
175 return base.GetHashCode ();
178 public override string ToString() {
179 return "Marker Line " + line + ", Position " + pos;
183 #endregion Structures
185 #region Local Variables
186 private Line document;
188 private Line sentinel;
189 private int document_id;
190 private Random random = new Random();
191 internal string password_char;
192 private StringBuilder password_cache;
193 private bool calc_pass;
194 private int char_count;
195 private bool enable_links;
197 // For calculating widths/heights
198 public static readonly StringFormat string_format = new StringFormat (StringFormat.GenericTypographic);
200 private int recalc_suspended;
201 private bool recalc_pending;
202 private int recalc_start = 1; // This starts at one, since lines are 1 based
203 private int recalc_end;
204 private bool recalc_optimize;
206 private int update_suspended;
207 private bool update_pending;
208 private int update_start = 1;
210 internal bool multiline;
211 internal HorizontalAlignment alignment;
214 internal UndoManager undo;
216 internal Marker caret;
217 internal Marker selection_start;
218 internal Marker selection_end;
219 internal bool selection_visible;
220 internal Marker selection_anchor;
221 internal Marker selection_prev;
222 internal bool selection_end_anchor;
224 internal int viewport_x;
225 internal int viewport_y; // The visible area of the document
226 internal int offset_x;
227 internal int offset_y;
228 internal int viewport_width;
229 internal int viewport_height;
231 internal int document_x; // Width of the document
232 internal int document_y; // Height of the document
234 internal int crlf_size; // 1 or 2, depending on whether we use \r\n or just \n
236 internal TextBoxBase owner; // Who's owning us?
237 static internal int caret_width = 1;
238 static internal int caret_shift = 1;
240 internal int left_margin = 2; // A left margin for all lines
241 internal int top_margin = 2;
242 internal int right_margin = 2;
243 #endregion // Local Variables
246 internal Document (TextBoxBase owner)
255 recalc_pending = false;
257 // Tree related stuff
258 sentinel = new Line (this, LineEnding.None);
259 sentinel.color = LineColor.Black;
263 // We always have a blank line
264 owner.HandleCreated += new EventHandler(owner_HandleCreated);
265 owner.VisibleChanged += new EventHandler(owner_VisibleChanged);
267 Add (1, String.Empty, owner.Font, owner.ForeColor, LineEnding.None);
269 undo = new UndoManager (this);
271 selection_visible = false;
272 selection_start.line = this.document;
273 selection_start.pos = 0;
274 selection_start.tag = selection_start.line.tags;
275 selection_end.line = this.document;
276 selection_end.pos = 0;
277 selection_end.tag = selection_end.line.tags;
278 selection_anchor.line = this.document;
279 selection_anchor.pos = 0;
280 selection_anchor.tag = selection_anchor.line.tags;
281 caret.line = this.document;
283 caret.tag = caret.line.tags;
293 // Default selection is empty
295 document_id = random.Next();
297 string_format.Trimming = StringTrimming.None;
298 string_format.FormatFlags = StringFormatFlags.DisplayFormatControl;
304 #region Internal Properties
315 // UIA: Method used via reflection in TextRangeProvider
322 internal Line CaretLine {
328 internal int CaretPosition {
334 internal Point Caret {
336 return new Point((int)caret.tag.Line.widths[caret.pos] + caret.line.X, caret.line.Y);
340 internal LineTag CaretTag {
350 internal int CRLFSize {
361 /// Whether text is scanned for links
363 internal bool EnableLinks {
364 get { return enable_links; }
365 set { enable_links = value; }
368 internal string PasswordChar {
370 return password_char;
374 password_char = value;
375 PasswordCache.Length = 0;
376 if ((password_char.Length != 0) && (password_char[0] != '\0')) {
384 private StringBuilder PasswordCache {
386 if (password_cache == null)
387 password_cache = new StringBuilder();
388 return password_cache;
392 internal int ViewPortX {
402 internal int Length {
404 return char_count + lines - 1; // Add \n for each line but the last
408 private int CharCount {
416 if (LengthChanged != null) {
417 LengthChanged(this, EventArgs.Empty);
422 internal int ViewPortY {
458 internal int ViewPortWidth {
460 return viewport_width;
464 viewport_width = value;
468 internal int ViewPortHeight {
470 return viewport_height;
474 viewport_height = value;
481 return this.document_x;
485 internal int Height {
487 return this.document_y;
491 internal bool SelectionVisible {
493 return selection_visible;
507 #endregion // Internal Properties
509 #region Private Methods
511 internal void UpdateMargins ()
513 switch (owner.actual_border_style) {
514 case BorderStyle.None:
519 case BorderStyle.FixedSingle:
524 case BorderStyle.Fixed3D:
532 internal void SuspendRecalc ()
534 if (recalc_suspended == 0) {
535 recalc_start = int.MaxValue;
536 recalc_end = int.MinValue;
542 internal void ResumeRecalc (bool immediate_update)
544 if (recalc_suspended > 0)
547 if (recalc_suspended == 0 && (immediate_update || recalc_pending) && !(recalc_start == int.MaxValue && recalc_end == int.MinValue)) {
548 RecalculateDocument (owner.CreateGraphicsInternal (), recalc_start, recalc_end, recalc_optimize);
549 recalc_pending = false;
553 internal void SuspendUpdate ()
558 internal void ResumeUpdate (bool immediate_update)
560 if (update_suspended > 0)
563 if (immediate_update && update_suspended == 0 && update_pending) {
564 UpdateView (GetLine (update_start), 0);
565 update_pending = false;
570 internal int DumpTree(Line line, bool with_tags) {
575 Console.Write("Line {0} [# {1}], Y: {2}, ending style: {3}, Text: '{4}'",
576 line.line_no, line.GetHashCode(), line.Y, line.ending,
577 line.text != null ? line.text.ToString() : "undefined");
579 if (line.left == sentinel) {
580 Console.Write(", left = sentinel");
581 } else if (line.left == null) {
582 Console.Write(", left = NULL");
585 if (line.right == sentinel) {
586 Console.Write(", right = sentinel");
587 } else if (line.right == null) {
588 Console.Write(", right = NULL");
591 Console.WriteLine("");
601 Console.Write(" Tags: ");
602 while (tag != null) {
603 Console.Write("{0} <{1}>-<{2}>", count++, tag.Start, tag.End
604 /*line.text.ToString (tag.start - 1, tag.length)*/);
605 length += tag.Length;
607 if (tag.Line != line) {
608 Console.Write("BAD line link");
609 throw new Exception("Bad line link in tree");
616 if (length > line.text.Length) {
617 throw new Exception(String.Format("Length of tags more than length of text on line (expected {0} calculated {1})", line.text.Length, length));
618 } else if (length < line.text.Length) {
619 throw new Exception(String.Format("Length of tags less than length of text on line (expected {0} calculated {1})", line.text.Length, length));
621 Console.WriteLine("");
623 if (line.left != null) {
624 if (line.left != sentinel) {
625 total += DumpTree(line.left, with_tags);
628 if (line != sentinel) {
629 throw new Exception("Left should not be NULL");
633 if (line.right != null) {
634 if (line.right != sentinel) {
635 total += DumpTree(line.right, with_tags);
638 if (line != sentinel) {
639 throw new Exception("Right should not be NULL");
643 for (int i = 1; i <= this.lines; i++) {
644 if (GetLine(i) == null) {
645 throw new Exception(String.Format("Hole in line order, missing {0}", i));
649 if (line == this.Root) {
650 if (total < this.lines) {
651 throw new Exception(String.Format("Not enough nodes in tree, found {0}, expected {1}", total, this.lines));
652 } else if (total > this.lines) {
653 throw new Exception(String.Format("Too many nodes in tree, found {0}, expected {1}", total, this.lines));
660 private void SetSelectionVisible (bool value)
662 bool old_selection_visible = selection_visible;
663 selection_visible = value;
665 // cursor and selection are enemies, we can't have both in the same room at the same time
666 if (owner.IsHandleCreated && !owner.show_caret_w_selection)
667 XplatUI.CaretVisible (owner.Handle, !selection_visible);
668 if (UIASelectionChanged != null && (selection_visible || old_selection_visible))
669 UIASelectionChanged (this, EventArgs.Empty);
672 private void DecrementLines(int line_no) {
676 while (current <= lines) {
677 GetLine(current).line_no--;
683 private void IncrementLines(int line_no) {
686 current = this.lines;
687 while (current >= line_no) {
688 GetLine(current).line_no++;
694 private void RebalanceAfterAdd(Line line1) {
697 while ((line1 != document) && (line1.parent.color == LineColor.Red)) {
698 if (line1.parent == line1.parent.parent.left) {
699 line2 = line1.parent.parent.right;
701 if ((line2 != null) && (line2.color == LineColor.Red)) {
702 line1.parent.color = LineColor.Black;
703 line2.color = LineColor.Black;
704 line1.parent.parent.color = LineColor.Red;
705 line1 = line1.parent.parent;
707 if (line1 == line1.parent.right) {
708 line1 = line1.parent;
712 line1.parent.color = LineColor.Black;
713 line1.parent.parent.color = LineColor.Red;
715 RotateRight(line1.parent.parent);
718 line2 = line1.parent.parent.left;
720 if ((line2 != null) && (line2.color == LineColor.Red)) {
721 line1.parent.color = LineColor.Black;
722 line2.color = LineColor.Black;
723 line1.parent.parent.color = LineColor.Red;
724 line1 = line1.parent.parent;
726 if (line1 == line1.parent.left) {
727 line1 = line1.parent;
731 line1.parent.color = LineColor.Black;
732 line1.parent.parent.color = LineColor.Red;
733 RotateLeft(line1.parent.parent);
737 document.color = LineColor.Black;
740 private void RebalanceAfterDelete(Line line1) {
743 while ((line1 != document) && (line1.color == LineColor.Black)) {
744 if (line1 == line1.parent.left) {
745 line2 = line1.parent.right;
746 if (line2.color == LineColor.Red) {
747 line2.color = LineColor.Black;
748 line1.parent.color = LineColor.Red;
749 RotateLeft(line1.parent);
750 line2 = line1.parent.right;
752 if ((line2.left.color == LineColor.Black) && (line2.right.color == LineColor.Black)) {
753 line2.color = LineColor.Red;
754 line1 = line1.parent;
756 if (line2.right.color == LineColor.Black) {
757 line2.left.color = LineColor.Black;
758 line2.color = LineColor.Red;
760 line2 = line1.parent.right;
762 line2.color = line1.parent.color;
763 line1.parent.color = LineColor.Black;
764 line2.right.color = LineColor.Black;
765 RotateLeft(line1.parent);
769 line2 = line1.parent.left;
770 if (line2.color == LineColor.Red) {
771 line2.color = LineColor.Black;
772 line1.parent.color = LineColor.Red;
773 RotateRight(line1.parent);
774 line2 = line1.parent.left;
776 if ((line2.right.color == LineColor.Black) && (line2.left.color == LineColor.Black)) {
777 line2.color = LineColor.Red;
778 line1 = line1.parent;
780 if (line2.left.color == LineColor.Black) {
781 line2.right.color = LineColor.Black;
782 line2.color = LineColor.Red;
784 line2 = line1.parent.left;
786 line2.color = line1.parent.color;
787 line1.parent.color = LineColor.Black;
788 line2.left.color = LineColor.Black;
789 RotateRight(line1.parent);
794 line1.color = LineColor.Black;
797 private void RotateLeft(Line line1) {
798 Line line2 = line1.right;
800 line1.right = line2.left;
802 if (line2.left != sentinel) {
803 line2.left.parent = line1;
806 if (line2 != sentinel) {
807 line2.parent = line1.parent;
810 if (line1.parent != null) {
811 if (line1 == line1.parent.left) {
812 line1.parent.left = line2;
814 line1.parent.right = line2;
821 if (line1 != sentinel) {
822 line1.parent = line2;
826 private void RotateRight(Line line1) {
827 Line line2 = line1.left;
829 line1.left = line2.right;
831 if (line2.right != sentinel) {
832 line2.right.parent = line1;
835 if (line2 != sentinel) {
836 line2.parent = line1.parent;
839 if (line1.parent != null) {
840 if (line1 == line1.parent.right) {
841 line1.parent.right = line2;
843 line1.parent.left = line2;
850 if (line1 != sentinel) {
851 line1.parent = line2;
856 internal void UpdateView(Line line, int pos) {
857 if (!owner.IsHandleCreated) {
861 if (update_suspended > 0) {
862 update_start = Math.Min (update_start, line.line_no);
863 // update_end = Math.Max (update_end, line.line_no);
864 // recalc_optimize = true;
865 update_pending = true;
869 // Optimize invalidation based on Line alignment
870 if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no, true)) {
871 // Lineheight changed, invalidate the rest of the document
872 if ((line.Y - viewport_y) >=0 ) {
873 // We formatted something that's in view, only draw parts of the screen
874 owner.Invalidate(new Rectangle(
876 line.Y - viewport_y + offset_y,
878 owner.Height - (line.Y - viewport_y)));
880 // The tag was above the visible area, draw everything
884 switch(line.alignment) {
885 case HorizontalAlignment.Left: {
886 owner.Invalidate(new Rectangle(
887 line.X + ((int)line.widths[pos] - viewport_x - 1) + offset_x,
888 line.Y - viewport_y + offset_y,
894 case HorizontalAlignment.Center: {
895 owner.Invalidate(new Rectangle(
897 line.Y - viewport_y + offset_y,
903 case HorizontalAlignment.Right: {
904 owner.Invalidate(new Rectangle(
906 line.Y - viewport_y + offset_y,
907 (int)line.widths[pos + 1] - viewport_x + line.X,
916 // Update display from line, down line_count lines; pos is unused, but required for the signature
917 internal void UpdateView(Line line, int line_count, int pos) {
918 if (!owner.IsHandleCreated) {
922 if (recalc_suspended > 0) {
923 recalc_start = Math.Min (recalc_start, line.line_no);
924 recalc_end = Math.Max (recalc_end, line.line_no + line_count);
925 recalc_optimize = true;
926 recalc_pending = true;
930 int start_line_top = line.Y;
932 Line end_line = GetLine (line.line_no + line_count);
933 if (end_line == null)
934 end_line = GetLine (lines);
936 if (end_line == null)
939 int end_line_bottom = end_line.Y + end_line.height;
941 if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no + line_count, true)) {
942 // Lineheight changed, invalidate the rest of the document
943 if ((line.Y - viewport_y) >=0 ) {
944 // We formatted something that's in view, only draw parts of the screen
945 owner.Invalidate(new Rectangle(
947 line.Y - viewport_y + offset_y,
949 owner.Height - (line.Y - viewport_y)));
951 // The tag was above the visible area, draw everything
955 int x = 0 - viewport_x + offset_x;
956 int w = viewport_width;
957 int y = Math.Min (start_line_top - viewport_y, line.Y - viewport_y) + offset_y;
958 int h = Math.Max (end_line_bottom - y, end_line.Y + end_line.height - y);
960 owner.Invalidate (new Rectangle (x, y, w, h));
965 /// Scans the next paragraph for http:/ ftp:/ www. https:/ etc and marks the tags
968 /// <param name="start_line">The line to start on</param>
969 /// <param name="link_changed">marks as true if something is changed</param>
970 private void ScanForLinks (Line start_line, ref bool link_changed)
972 Line current_line = start_line;
973 StringBuilder line_no_breaks = new StringBuilder ();
974 StringBuilder line_link_record = new StringBuilder ();
975 ArrayList cumulative_length_list = new ArrayList ();
976 bool update_caret_tag = false;
978 cumulative_length_list.Add (0);
980 while (current_line != null) {
981 line_no_breaks.Append (current_line.text);
983 if (link_changed == false)
984 current_line.LinkRecord (line_link_record);
986 current_line.ClearLinks ();
988 cumulative_length_list.Add (line_no_breaks.Length);
990 if (current_line.ending == LineEnding.Wrap)
991 current_line = GetLine (current_line.LineNo + 1);
996 // search for protocols.. make sure www. is first!
997 string [] search_terms = new string [] { "www.", "http:/", "ftp:/", "https:/" };
998 int search_found = 0;
1000 string line_no_breaks_string = line_no_breaks.ToString ();
1001 int line_no_breaks_index = 0;
1005 if (line_no_breaks_index >= line_no_breaks_string.Length)
1008 index_found = FirstIndexOfAny (line_no_breaks_string, search_terms, line_no_breaks_index, out search_found);
1010 //no links found on this line
1011 if (index_found == -1)
1014 if (search_found == 0) {
1015 // if we are at the end of the line to analyse and the end of the line
1016 // is "www." then there are no links here
1017 if (line_no_breaks_string.Length == index_found + search_terms [0].Length)
1020 // if after www. we don't have a letter a digit or a @ or - or /
1021 // then it is not a web address, we should continue searching
1022 if (char.IsLetterOrDigit (line_no_breaks_string [index_found + search_terms [0].Length]) == false &&
1023 "@/~".IndexOf (line_no_breaks_string [index_found + search_terms [0].Length].ToString ()) == -1) {
1024 line_no_breaks_index = index_found + search_terms [0].Length;
1029 link_end = line_no_breaks_string.Length - 1;
1030 line_no_breaks_index = line_no_breaks_string.Length;
1032 // we've found a link, we just need to find where it ends now
1033 for (int i = index_found + search_terms [search_found].Length; i < line_no_breaks_string.Length; i++) {
1034 if (line_no_breaks_string [i - 1] == '.') {
1035 if (char.IsLetterOrDigit (line_no_breaks_string [i]) == false &&
1036 "@/~".IndexOf (line_no_breaks_string [i].ToString ()) == -1) {
1038 line_no_breaks_index = i;
1042 if (char.IsLetterOrDigit (line_no_breaks_string [i]) == false &&
1043 "@-/:~.?=_&".IndexOf (line_no_breaks_string [i].ToString ()) == -1) {
1045 line_no_breaks_index = i;
1051 string link_text = line_no_breaks_string.Substring (index_found, link_end - index_found + 1);
1052 int current_cumulative = 0;
1054 // we've found a link - index_found -> link_end
1055 // now we just make all the tags as containing link and
1056 // point them to the text for the whole link
1058 current_line = start_line;
1060 //find the line we start on
1061 for (current_cumulative = 1; current_cumulative < cumulative_length_list.Count; current_cumulative++)
1062 if ((int)cumulative_length_list [current_cumulative] > index_found)
1065 current_line = GetLine (start_line.LineNo + current_cumulative - 1);
1067 // find the tag we start on
1068 LineTag current_tag = current_line.FindTag (index_found - (int)cumulative_length_list [current_cumulative - 1] + 1);
1070 if (current_tag.Start != (index_found - (int)cumulative_length_list [current_cumulative - 1]) + 1) {
1071 if (current_tag == CaretTag)
1072 update_caret_tag = true;
1074 current_tag = current_tag.Break ((index_found - (int)cumulative_length_list [current_cumulative - 1]) + 1);
1078 current_tag.IsLink = true;
1079 current_tag.LinkText = link_text;
1081 //go through each character
1082 // find the tag we are in
1083 // skip the number of characters in the tag
1084 for (int i = 1; i < link_text.Length; i++) {
1085 // on to a new word-wrapped line
1086 if ((int)cumulative_length_list [current_cumulative] <= index_found + i) {
1088 current_line = GetLine (start_line.LineNo + current_cumulative++);
1089 current_tag = current_line.FindTag (index_found + i - (int)cumulative_length_list [current_cumulative - 1] + 1);
1091 current_tag.IsLink = true;
1092 current_tag.LinkText = link_text;
1097 if (current_tag.End < index_found + 1 + i - (int)cumulative_length_list [current_cumulative - 1]) {
1098 // skip empty tags in the middle of the URL
1100 current_tag = current_tag.Next;
1101 } while (current_tag.Length == 0);
1103 current_tag.IsLink = true;
1104 current_tag.LinkText = link_text;
1108 //if there are characters left in the tag after the link
1110 // make the second part a non link
1111 if (current_tag.End > (index_found + link_text.Length + 1) - (int)cumulative_length_list [current_cumulative - 1]) {
1112 if (current_tag == CaretTag)
1113 update_caret_tag = true;
1115 current_tag.Break ((index_found + link_text.Length + 1) - (int)cumulative_length_list [current_cumulative - 1]);
1119 if (update_caret_tag) {
1120 CaretTag = LineTag.FindTag (CaretLine, CaretPosition);
1121 link_changed = true;
1123 if (link_changed == false) {
1124 current_line = start_line;
1125 StringBuilder new_link_record = new StringBuilder ();
1127 while (current_line != null) {
1128 current_line.LinkRecord (new_link_record);
1130 if (current_line.ending == LineEnding.Wrap)
1131 current_line = GetLine (current_line.LineNo + 1);
1136 if (new_link_record.Equals (line_link_record) == false)
1137 link_changed = true;
1142 private int FirstIndexOfAny (string haystack, string [] needles, int start_index, out int term_found)
1145 int best_index = -1;
1147 for (int i = 0; i < needles.Length; i++) {
1148 int index = haystack.IndexOf (needles [i], start_index, StringComparison.InvariantCultureIgnoreCase);
1151 if (term_found > -1) {
1152 if (index < best_index) {
1168 private void InvalidateLinks (Rectangle clip)
1170 for (int i = (owner.list_links.Count - 1); i >= 0; i--) {
1171 TextBoxBase.LinkRectangle link = (TextBoxBase.LinkRectangle) owner.list_links [i];
1173 if (clip.IntersectsWith (link.LinkAreaRectangle))
1174 owner.list_links.RemoveAt (i);
1177 #endregion // Private Methods
1179 #region Internal Methods
1181 internal void ScanForLinks (int start, int end, ref bool link_changed)
1184 LineEnding lastending = LineEnding.Rich;
1186 // make sure we start scanning at the real begining of the line
1188 if (start != 1 && GetLine (start - 1).ending == LineEnding.Wrap)
1194 for (int i = start; i <= end && i <= lines; i++) {
1197 if (lastending != LineEnding.Wrap)
1198 ScanForLinks (line, ref link_changed);
1200 lastending = line.ending;
1202 if (lastending == LineEnding.Wrap && (i + 1) <= end)
1207 // Clear the document and reset state
1208 internal void Empty() {
1210 document = sentinel;
1213 // We always have a blank line
1214 Add (1, String.Empty, owner.Font, owner.ForeColor, LineEnding.None);
1216 this.RecalculateDocument(owner.CreateGraphicsInternal());
1217 PositionCaret(0, 0);
1219 SetSelectionVisible (false);
1221 selection_start.line = this.document;
1222 selection_start.pos = 0;
1223 selection_start.tag = selection_start.line.tags;
1224 selection_end.line = this.document;
1225 selection_end.pos = 0;
1226 selection_end.tag = selection_end.line.tags;
1235 if (owner.IsHandleCreated)
1236 owner.Invalidate ();
1239 internal void PositionCaret(Line line, int pos) {
1240 caret.tag = line.FindTag (pos);
1242 MoveCaretToTextTag ();
1247 if (owner.IsHandleCreated) {
1248 if (owner.Focused) {
1249 if (caret.height != caret.tag.Height)
1250 XplatUI.CreateCaret (owner.Handle, caret_width, caret.height);
1251 XplatUI.SetCaretPos(owner.Handle,
1252 offset_x + (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x,
1253 offset_y + caret.line.Y + caret.tag.Shift - viewport_y + caret_shift);
1256 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1259 // We set this at the end because we use the heights to determine whether or
1260 // not we need to recreate the caret
1261 caret.height = caret.tag.Height;
1265 internal void PositionCaret(int x, int y) {
1266 if (!owner.IsHandleCreated) {
1270 caret.tag = FindCursor(x, y, out caret.pos);
1272 MoveCaretToTextTag ();
1274 caret.line = caret.tag.Line;
1275 caret.height = caret.tag.Height;
1277 if (owner.ShowSelection && (!selection_visible || owner.show_caret_w_selection)) {
1278 XplatUI.CreateCaret (owner.Handle, caret_width, caret.height);
1279 XplatUI.SetCaretPos(owner.Handle,
1280 (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x + offset_x,
1281 offset_y + caret.line.Y + caret.tag.Shift - viewport_y + caret_shift);
1284 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1287 internal void CaretHasFocus() {
1288 if ((caret.tag != null) && owner.IsHandleCreated) {
1289 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1290 XplatUI.SetCaretPos(owner.Handle,
1291 offset_x + (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x,
1292 offset_y + caret.line.Y + caret.tag.Shift - viewport_y + caret_shift);
1297 if (owner.IsHandleCreated && SelectionLength () > 0) {
1298 InvalidateSelectionArea ();
1302 internal void CaretLostFocus() {
1303 if (!owner.IsHandleCreated) {
1306 XplatUI.DestroyCaret(owner.Handle);
1309 internal void AlignCaret ()
1314 internal void AlignCaret(bool changeCaretTag) {
1315 if (!owner.IsHandleCreated) {
1319 if (changeCaretTag) {
1320 caret.tag = LineTag.FindTag (caret.line, caret.pos);
1322 MoveCaretToTextTag ();
1325 // if the caret has had SelectionFont changed to a
1326 // different height, we reflect changes unless the new
1327 // font is larger than the line (line recalculations
1328 // ignore empty tags) in which case we make it equal
1329 // the line height and then when text is entered
1330 if (caret.tag.Height > caret.tag.Line.Height) {
1331 caret.height = caret.line.height;
1333 caret.height = caret.tag.Height;
1336 if (owner.Focused) {
1337 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1338 XplatUI.SetCaretPos (owner.Handle,
1339 offset_x + (int) caret.tag.Line.widths [caret.pos] + caret.line.X - viewport_x,
1340 offset_y + caret.line.Y + viewport_y + caret_shift);
1344 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1347 internal void UpdateCaret() {
1348 if (!owner.IsHandleCreated || caret.tag == null) {
1352 MoveCaretToTextTag ();
1354 if (caret.tag.Height != caret.height) {
1355 caret.height = caret.tag.Height;
1356 if (owner.Focused) {
1357 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1361 if (owner.Focused) {
1362 XplatUI.SetCaretPos(owner.Handle,
1363 offset_x + (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x,
1364 offset_y + caret.line.Y + caret.tag.Shift - viewport_y + caret_shift);
1368 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1371 internal void DisplayCaret() {
1372 if (!owner.IsHandleCreated) {
1376 if (owner.ShowSelection && (!selection_visible || owner.show_caret_w_selection)) {
1377 XplatUI.CaretVisible(owner.Handle, true);
1381 internal void HideCaret() {
1382 if (!owner.IsHandleCreated) {
1386 if (owner.Focused) {
1387 XplatUI.CaretVisible(owner.Handle, false);
1392 internal void MoveCaretToTextTag ()
1394 if (caret.tag == null || caret.tag.IsTextTag)
1399 if (caret.pos < caret.tag.Start) {
1400 caret.tag = caret.tag.Previous;
1402 caret.tag = caret.tag.Next;
1406 internal void MoveCaret(CaretDirection direction) {
1407 // FIXME should we use IsWordSeparator to detect whitespace, instead
1408 // of looking for actual spaces in the Word move cases?
1410 bool nowrap = false;
1412 case CaretDirection.CharForwardNoWrap:
1414 goto case CaretDirection.CharForward;
1415 case CaretDirection.CharForward: {
1417 if (caret.pos > caret.line.TextLengthWithoutEnding ()) {
1419 // Go into next line
1420 if (caret.line.line_no < this.lines) {
1421 caret.line = GetLine(caret.line.line_no+1);
1423 caret.tag = caret.line.tags;
1428 // Single line; we stay where we are
1432 if ((caret.tag.Start - 1 + caret.tag.Length) < caret.pos) {
1433 caret.tag = caret.tag.Next;
1440 case CaretDirection.CharBackNoWrap:
1442 goto case CaretDirection.CharBack;
1443 case CaretDirection.CharBack: {
1444 if (caret.pos > 0) {
1445 // caret.pos--; // folded into the if below
1447 if (--caret.pos > 0) {
1448 if (caret.tag.Start > caret.pos) {
1449 caret.tag = caret.tag.Previous;
1453 if (caret.line.line_no > 1 && !nowrap) {
1454 caret.line = GetLine(caret.line.line_no - 1);
1455 caret.pos = caret.line.TextLengthWithoutEnding ();
1456 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1463 case CaretDirection.WordForward: {
1466 len = caret.line.text.Length;
1467 if (caret.pos < len) {
1468 while ((caret.pos < len) && (caret.line.text[caret.pos] != ' ')) {
1471 if (caret.pos < len) {
1472 // Skip any whitespace
1473 while ((caret.pos < len) && (caret.line.text[caret.pos] == ' ')) {
1477 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1479 if (caret.line.line_no < this.lines) {
1480 caret.line = GetLine(caret.line.line_no + 1);
1482 caret.tag = caret.line.tags;
1489 case CaretDirection.WordBack: {
1490 if (caret.pos > 0) {
1493 while ((caret.pos > 0) && (caret.line.text[caret.pos] == ' ')) {
1497 while ((caret.pos > 0) && (caret.line.text[caret.pos] != ' ')) {
1501 if (caret.line.text.ToString(caret.pos, 1) == " ") {
1502 if (caret.pos != 0) {
1505 caret.line = GetLine(caret.line.line_no - 1);
1506 caret.pos = caret.line.text.Length;
1509 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1511 if (caret.line.line_no > 1) {
1512 caret.line = GetLine(caret.line.line_no - 1);
1513 caret.pos = caret.line.text.Length;
1514 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1521 case CaretDirection.LineUp: {
1522 if (caret.line.line_no > 1) {
1525 pixel = (int)caret.line.widths[caret.pos];
1526 PositionCaret(pixel, GetLine(caret.line.line_no - 1).Y);
1533 case CaretDirection.LineDown: {
1534 if (caret.line.line_no < lines) {
1537 pixel = (int)caret.line.widths[caret.pos];
1538 PositionCaret(pixel, GetLine(caret.line.line_no + 1).Y);
1545 case CaretDirection.Home: {
1546 if (caret.pos > 0) {
1548 caret.tag = caret.line.tags;
1554 case CaretDirection.End: {
1555 if (caret.pos < caret.line.TextLengthWithoutEnding ()) {
1556 caret.pos = caret.line.TextLengthWithoutEnding ();
1557 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1563 case CaretDirection.PgUp: {
1565 if (caret.line.line_no == 1 && owner.richtext) {
1566 owner.vscroll.Value = 0;
1567 Line line = GetLine (1);
1568 PositionCaret (line, 0);
1571 int y_offset = caret.line.Y + caret.line.height - 1 - viewport_y;
1573 LineTag top = FindCursor ((int) caret.line.widths [caret.pos],
1574 viewport_y - viewport_height, out index);
1576 owner.vscroll.Value = Math.Min (top.Line.Y, owner.vscroll.Maximum - viewport_height);
1577 PositionCaret ((int) caret.line.widths [caret.pos], y_offset + viewport_y);
1582 case CaretDirection.PgDn: {
1584 if (caret.line.line_no == lines && owner.richtext) {
1585 owner.vscroll.Value = owner.vscroll.Maximum - viewport_height + 1;
1586 Line line = GetLine (lines);
1587 PositionCaret (line, line.TextLengthWithoutEnding());
1590 int y_offset = caret.line.Y - viewport_y;
1592 LineTag top = FindCursor ((int) caret.line.widths [caret.pos],
1593 viewport_y + viewport_height, out index);
1595 owner.vscroll.Value = Math.Min (top.Line.Y, owner.vscroll.Maximum - viewport_height);
1596 PositionCaret ((int) caret.line.widths [caret.pos], y_offset + viewport_y);
1601 case CaretDirection.CtrlPgUp: {
1602 PositionCaret(0, viewport_y);
1607 case CaretDirection.CtrlPgDn: {
1612 tag = FindCursor (0, viewport_y + viewport_height, out index);
1613 if (tag.Line.line_no > 1) {
1614 line = GetLine(tag.Line.line_no - 1);
1618 PositionCaret(line, line.Text.Length);
1623 case CaretDirection.CtrlHome: {
1624 caret.line = GetLine(1);
1626 caret.tag = caret.line.tags;
1632 case CaretDirection.CtrlEnd: {
1633 caret.line = GetLine(lines);
1634 caret.pos = caret.line.TextLengthWithoutEnding ();
1635 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1641 case CaretDirection.SelectionStart: {
1642 caret.line = selection_start.line;
1643 caret.pos = selection_start.pos;
1644 caret.tag = selection_start.tag;
1650 case CaretDirection.SelectionEnd: {
1651 caret.line = selection_end.line;
1652 caret.pos = selection_end.pos;
1653 caret.tag = selection_end.tag;
1661 internal void DumpDoc ()
1663 Console.WriteLine ("<doc lines='{0}'>", lines);
1664 for (int i = 1; i <= lines ; i++) {
1665 Line line = GetLine (i);
1666 Console.WriteLine ("<line no='{0}' ending='{1}'>", line.line_no, line.ending);
1668 LineTag tag = line.tags;
1669 while (tag != null) {
1670 Console.Write ("\t<tag type='{0}' span='{1}->{2}' font='{3}' color='{4}'>",
1671 tag.GetType (), tag.Start, tag.Length, tag.Font, tag.Color);
1672 Console.Write (tag.Text ());
1673 Console.WriteLine ("</tag>");
1676 Console.WriteLine ("</line>");
1678 Console.WriteLine ("</doc>");
1681 // UIA: Used via reflection by TextProviderBehavior
1682 internal void GetVisibleLineIndexes (Rectangle clip, out int start, out int end)
1685 /* Expand the region slightly to be sure to
1686 * paint the full extent of the line of text.
1689 start = GetLineByPixel(clip.Top + viewport_y - offset_y - 1, false).line_no;
1690 end = GetLineByPixel(clip.Bottom + viewport_y - offset_y + 1, false).line_no;
1692 start = GetLineByPixel(clip.Left + viewport_x - offset_x, false).line_no;
1693 end = GetLineByPixel(clip.Right + viewport_x - offset_x, false).line_no;
1697 internal void Draw (Graphics g, Rectangle clip)
1699 Line line; // Current line being drawn
1700 LineTag tag; // Current tag being drawn
1701 int start; // First line to draw
1702 int end; // Last line to draw
1703 StringBuilder text; // String representing the current line
1706 Color current_color;
1708 // First, figure out from what line to what line we need to draw
1709 GetVisibleLineIndexes (clip, out start, out end);
1711 // remove links in the list (used for mouse down events) that are within the clip area.
1712 InvalidateLinks (clip);
1715 /// We draw the single border ourself
1717 if (owner.actual_border_style == BorderStyle.FixedSingle) {
1718 ControlPaint.DrawBorder (g, owner.ClientRectangle, Color.Black, ButtonBorderStyle.Solid);
1721 /// Make sure that we aren't drawing one more line then we need to
1722 line = GetLine (end - 1);
1723 if (line != null && clip.Bottom == offset_y + line.Y + line.height - viewport_y)
1729 DateTime n = DateTime.Now;
1730 Console.WriteLine ("Started drawing: {0}s {1}ms", n.Second, n.Millisecond);
1731 Console.WriteLine ("CLIP: {0}", clip);
1732 Console.WriteLine ("S: {0}", GetLine (start).text);
1733 Console.WriteLine ("E: {0}", GetLine (end).text);
1736 // Non multiline selection can be handled outside of the loop
1737 if (!multiline && selection_visible && owner.ShowSelection) {
1738 g.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (ThemeEngine.Current.ColorHighlight),
1739 offset_x + selection_start.line.widths [selection_start.pos] +
1740 selection_start.line.X - viewport_x,
1741 offset_y + selection_start.line.Y,
1742 (selection_end.line.X + selection_end.line.widths [selection_end.pos]) -
1743 (selection_start.line.X + selection_start.line.widths [selection_start.pos]),
1744 selection_start.line.height);
1747 while (line_no <= end) {
1748 line = GetLine (line_no);
1749 float line_y = line.Y - viewport_y + offset_y;
1755 if (PasswordCache.Length < line.text.Length)
1756 PasswordCache.Append(Char.Parse(password_char), line.text.Length - PasswordCache.Length);
1757 else if (PasswordCache.Length > line.text.Length)
1758 PasswordCache.Remove(line.text.Length, PasswordCache.Length - line.text.Length);
1759 text = PasswordCache;
1762 int line_selection_start = text.Length + 1;
1763 int line_selection_end = text.Length + 1;
1764 if (selection_visible && owner.ShowSelection &&
1765 (line_no >= selection_start.line.line_no) &&
1766 (line_no <= selection_end.line.line_no)) {
1768 if (line_no == selection_start.line.line_no)
1769 line_selection_start = selection_start.pos + 1;
1771 line_selection_start = 1;
1773 if (line_no == selection_end.line.line_no)
1774 line_selection_end = selection_end.pos + 1;
1776 line_selection_end = text.Length + 1;
1778 if (line_selection_end == line_selection_start) {
1779 // There isn't really selection
1780 line_selection_start = text.Length + 1;
1781 line_selection_end = line_selection_start;
1782 } else if (multiline) {
1783 // lets draw some selection baby!! (non multiline selection is drawn outside the loop)
1784 g.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (ThemeEngine.Current.ColorHighlight),
1785 offset_x + line.widths [line_selection_start - 1] + line.X - viewport_x,
1786 line_y, line.widths [line_selection_end - 1] - line.widths [line_selection_start - 1],
1791 current_color = line.tags.ColorToDisplay;
1792 while (tag != null) {
1795 if (tag.Length == 0) {
1800 if (((tag.X + tag.Width) < (clip.Left - viewport_x - offset_x)) &&
1801 (tag.X > (clip.Right - viewport_x - offset_x))) {
1806 if (tag.BackColor != Color.Empty) {
1807 g.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (tag.BackColor),
1808 offset_x + tag.X + line.X - viewport_x,
1809 line_y + tag.Shift, tag.Width, line.height);
1812 tag_color = tag.ColorToDisplay;
1813 current_color = tag_color;
1815 if (!owner.Enabled) {
1816 Color a = tag.Color;
1817 Color b = ThemeEngine.Current.ColorWindowText;
1819 if ((a.R == b.R) && (a.G == b.G) && (a.B == b.B))
1820 tag_color = ThemeEngine.Current.ColorGrayText;
1824 int tag_pos = tag.Start;
1825 current_color = tag_color;
1826 while (tag_pos < tag.Start + tag.Length) {
1827 int old_tag_pos = tag_pos;
1829 if (tag_pos >= line_selection_start && tag_pos < line_selection_end) {
1830 current_color = ThemeEngine.Current.ColorHighlightText;
1831 tag_pos = Math.Min (tag.End, line_selection_end);
1832 } else if (tag_pos < line_selection_start) {
1833 current_color = tag_color;
1834 tag_pos = Math.Min (tag.End, line_selection_start);
1836 current_color = tag_color;
1840 Rectangle text_size;
1842 tag.Draw (g, current_color,
1843 offset_x + line.X - viewport_x,
1845 old_tag_pos - 1, Math.Min (tag.Start + tag.Length, tag_pos) - 1,
1846 text.ToString (), out text_size, tag.IsLink);
1849 TextBoxBase.LinkRectangle link = new TextBoxBase.LinkRectangle (text_size);
1851 owner.list_links.Add (link);
1857 line.DrawEnding (g, line_y);
1862 private int GetLineEnding (string line, int start, out LineEnding ending)
1867 if (start >= line.Length) {
1868 ending = LineEnding.Wrap;
1872 res = line.IndexOf ('\r', start);
1873 rich_index = line.IndexOf ('\n', start);
1875 // Handle the case where we find both of them, and the \n is before the \r
1876 if (res != -1 && rich_index != -1)
1877 if (rich_index < res) {
1878 ending = LineEnding.Rich;
1883 if (res + 2 < line.Length && line [res + 1] == '\r' && line [res + 2] == '\n') {
1884 ending = LineEnding.Soft;
1887 if (res + 1 < line.Length && line [res + 1] == '\n') {
1888 ending = LineEnding.Hard;
1891 ending = LineEnding.Limp;
1895 if (rich_index != -1) {
1896 ending = LineEnding.Rich;
1900 ending = LineEnding.Wrap;
1904 // Get the line ending, but only of the types specified
1905 private int GetLineEnding (string line, int start, out LineEnding ending, LineEnding type)
1908 int last_length = 0;
1911 index = GetLineEnding (line, index + last_length, out ending);
1912 last_length = LineEndingLength (ending);
1914 ((ending & type) != ending && index != -1);
1916 return index == -1 ? line.Length : index;
1919 internal int LineEndingLength (LineEnding ending)
1922 case LineEnding.Limp:
1923 case LineEnding.Rich:
1925 case LineEnding.Hard:
1927 case LineEnding.Soft:
1934 internal string LineEndingToString (LineEnding ending)
1937 case LineEnding.Limp:
1939 case LineEnding.Hard:
1941 case LineEnding.Soft:
1943 case LineEnding.Rich:
1947 return string.Empty;
1950 internal LineEnding StringToLineEnding (string ending)
1954 return LineEnding.Limp;
1956 return LineEnding.Hard;
1958 return LineEnding.Soft;
1960 return LineEnding.Rich;
1962 return LineEnding.None;
1966 internal void Insert (Line line, int pos, bool update_caret, string s)
1968 Insert (line, pos, update_caret, s, line.FindTag (pos));
1971 // Insert text at the given position; use formatting at insertion point for inserted text
1972 internal void Insert (Line line, int pos, bool update_caret, string s, LineTag tag)
1981 // Don't recalculate while we mess around
1984 base_line = line.line_no;
1985 old_line_count = lines;
1987 // Discard chars after any possible -unlikely- end of file
1988 int eof_index = s.IndexOf ('\0');
1989 if (eof_index != -1)
1990 s = s.Substring (0, eof_index);
1992 break_index = GetLineEnding (s, 0, out ending, LineEnding.Hard | LineEnding.Rich);
1994 // There are no line feeds in our text to be pasted
1995 if (break_index == s.Length) {
1996 line.InsertString (pos, s, tag);
1998 // Add up to the first line feed to our current position
1999 line.InsertString (pos, s.Substring (0, break_index + LineEndingLength (ending)), tag);
2001 // Split the rest of the original line to a new line
2002 Split (line, pos + (break_index + LineEndingLength (ending)));
2003 line.ending = ending;
2004 break_index += LineEndingLength (ending);
2005 split_line = GetLine (line.line_no + 1);
2007 // Insert brand new lines for any more line feeds in the inserted string
2009 int next_break = GetLineEnding (s, break_index, out ending, LineEnding.Hard | LineEnding.Rich);
2011 if (next_break == s.Length)
2014 string line_text = s.Substring (break_index, next_break - break_index +
2015 LineEndingLength (ending));
2017 Add (base_line + count, line_text, line.alignment, tag.Font, tag.Color, ending);
2019 Line last = GetLine (base_line + count);
2020 last.ending = ending;
2023 break_index = next_break + LineEndingLength (ending);
2026 // Add the remainder of the insert text to the split
2027 // part of the original line
2028 split_line.InsertString (0, s.Substring (break_index));
2031 // Allow the document to recalculate things
2032 ResumeRecalc (false);
2034 // Update our character count
2035 CharCount += s.Length;
2037 UpdateView (line, lines - old_line_count + 1, pos);
2039 // Move the caret to the end of the inserted text if requested
2041 Line l = GetLine (line.line_no + lines - old_line_count);
2042 PositionCaret (l, l.text.Length);
2047 // Inserts a string at the given position
2048 internal void InsertString (Line line, int pos, string s)
2050 // Update our character count
2051 CharCount += s.Length;
2053 // Insert the text into the Line
2054 line.InsertString (pos, s);
2057 // Inserts a character at the current caret position
2058 internal void InsertCharAtCaret (char ch, bool move_caret)
2060 caret.line.InsertString (caret.pos, ch.ToString(), caret.tag);
2062 // Update our character count
2065 undo.RecordTyping (caret.line, caret.pos, ch);
2067 UpdateView (caret.line, caret.pos);
2072 SetSelectionToCaret (true);
2076 internal void InsertPicture (Line line, int pos, RTF.Picture picture)
2084 // Just a place holder basically
2085 line.text.Insert (pos, "I");
2087 PictureTag picture_tag = new PictureTag (line, pos + 1, picture);
2089 tag = LineTag.FindTag (line, pos);
2090 picture_tag.CopyFormattingFrom (tag);
2091 /*next_tag = */tag.Break (pos + 1);
2092 picture_tag.Previous = tag;
2093 picture_tag.Next = tag.Next;
2094 tag.Next = picture_tag;
2097 // Picture tags need to be surrounded by text tags
2099 if (picture_tag.Next == null) {
2100 picture_tag.Next = new LineTag (line, pos + 1);
2101 picture_tag.Next.CopyFormattingFrom (tag);
2102 picture_tag.Next.Previous = picture_tag;
2105 tag = picture_tag.Next;
2106 while (tag != null) {
2114 UpdateView (line, pos);
2117 internal void DeleteMultiline (Line start_line, int pos, int length)
2119 Marker start = new Marker ();
2120 Marker end = new Marker ();
2121 int start_index = LineTagToCharIndex (start_line, pos);
2123 start.line = start_line;
2125 start.tag = LineTag.FindTag (start_line, pos);
2127 CharIndexToLineTag (start_index + length, out end.line,
2128 out end.tag, out end.pos);
2132 if (start.line == end.line) {
2133 DeleteChars (start.line, pos, end.pos - pos);
2136 // Delete first and last lines
2137 DeleteChars (start.line, start.pos, start.line.text.Length - start.pos);
2138 DeleteChars (end.line, 0, end.pos);
2140 int current = start.line.line_no + 1;
2141 if (current < end.line.line_no) {
2142 for (int i = end.line.line_no - 1; i >= current; i--) {
2147 // BIG FAT WARNING - selection_end.line might be stale due
2148 // to the above Delete() call. DONT USE IT before hitting the end of this method!
2150 // Join start and end
2151 Combine (start.line.line_no, current);
2154 ResumeUpdate (true);
2158 // Deletes n characters at the given position; it will not delete past line limits
2160 public void DeleteChars (Line line, int pos, int count)
2162 // Reduce our character count
2165 line.DeleteCharacters (pos, count);
2167 if (pos >= line.TextLengthWithoutEnding ()) {
2168 LineEnding ending = line.ending;
2169 GetLineEnding (line.text.ToString (), 0, out ending);
2171 if (ending != line.ending) {
2172 line.ending = ending;
2175 UpdateView (line, lines, pos);
2176 owner.Invalidate ();
2182 UpdateView (line, lines, pos);
2183 owner.Invalidate ();
2185 UpdateView (line, pos);
2188 // Deletes a character at or after the given position (depending on forward); it will not delete past line limits
2189 public void DeleteChar (Line line, int pos, bool forward)
2191 if ((pos == 0 && forward == false) || (pos == line.text.Length && forward == true))
2194 undo.BeginUserAction ("Delete");
2197 undo.RecordDeleteString (line, pos, line, pos + 1);
2198 DeleteChars (line, pos, 1);
2200 undo.RecordDeleteString (line, pos - 1, line, pos);
2201 DeleteChars (line, pos - 1, 1);
2204 undo.EndUserAction ();
2207 // Combine two lines
2208 internal void Combine(int FirstLine, int SecondLine) {
2209 Combine(GetLine(FirstLine), GetLine(SecondLine));
2212 internal void Combine(Line first, Line second) {
2216 // strip the ending off of the first lines text
2217 first.text.Length = first.text.Length - LineEndingLength (first.ending);
2219 // Combine the two tag chains into one
2222 // Maintain the line ending style
2223 first.ending = second.ending;
2225 while (last.Next != null) {
2229 // need to get the shift before setting the next tag since that effects length
2230 shift = last.Start + last.Length - 1;
2231 last.Next = second.tags;
2232 last.Next.Previous = last;
2234 // Fix up references within the chain
2236 while (last != null) {
2238 last.Start += shift;
2242 // Combine both lines' strings
2243 first.text.Insert(first.text.Length, second.text.ToString());
2244 first.Grow(first.text.Length);
2246 // Remove the reference to our (now combined) tags from the doomed line
2250 DecrementLines(first.line_no + 2); // first.line_no + 1 will be deleted, so we need to start renumbering one later
2253 first.recalc = true;
2254 first.height = 0; // This forces RecalcDocument/UpdateView to redraw from this line on
2255 first.Streamline(lines);
2257 // Update Caret, Selection, etc
2258 if (caret.line == second) {
2259 caret.Combine(first, shift);
2261 if (selection_anchor.line == second) {
2262 selection_anchor.Combine(first, shift);
2264 if (selection_start.line == second) {
2265 selection_start.Combine(first, shift);
2267 if (selection_end.line == second) {
2268 selection_end.Combine(first, shift);
2275 check_first = GetLine(first.line_no);
2276 check_second = GetLine(check_first.line_no + 1);
2278 Console.WriteLine("Pre-delete: Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2281 this.Delete(second);
2284 check_first = GetLine(first.line_no);
2285 check_second = GetLine(check_first.line_no + 1);
2287 Console.WriteLine("Post-delete Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2291 // Split the line at the position into two
2292 internal void Split(int LineNo, int pos) {
2296 line = GetLine(LineNo);
2297 tag = LineTag.FindTag(line, pos);
2298 Split(line, tag, pos);
2301 internal void Split(Line line, int pos) {
2304 tag = LineTag.FindTag(line, pos);
2305 Split(line, tag, pos);
2308 ///<summary>Split line at given tag and position into two lines</summary>
2309 ///if more space becomes available on previous line
2310 internal void Split(Line line, LineTag tag, int pos) {
2314 bool move_sel_start;
2318 move_sel_start = false;
2319 move_sel_end = false;
2325 throw new Exception ("Split called with the wrong tag");
2328 // Adjust selection and cursors
2329 if (caret.line == line && caret.pos >= pos) {
2332 if (selection_start.line == line && selection_start.pos > pos) {
2333 move_sel_start = true;
2336 if (selection_end.line == line && selection_end.pos > pos) {
2337 move_sel_end = true;
2340 // cover the easy case first
2341 if (pos == line.text.Length) {
2342 Add (line.line_no + 1, String.Empty, line.alignment, tag.Font, tag.Color, line.ending);
2344 new_line = GetLine (line.line_no + 1);
2347 caret.line = new_line;
2348 caret.tag = new_line.tags;
2351 if (selection_visible == false) {
2352 SetSelectionToCaret (true);
2356 if (move_sel_start) {
2357 selection_start.line = new_line;
2358 selection_start.pos = 0;
2359 selection_start.tag = new_line.tags;
2363 selection_end.line = new_line;
2364 selection_end.pos = 0;
2365 selection_end.tag = new_line.tags;
2374 // We need to move the rest of the text into the new line
2375 Add (line.line_no + 1, line.text.ToString (pos, line.text.Length - pos), line.alignment, tag.Font, tag.Color, line.ending);
2377 // Now transfer our tags from this line to the next
2378 new_line = GetLine(line.line_no + 1);
2381 new_line.recalc = true;
2383 //make sure that if we are at the end of a tag, we start on the begining
2384 //of a new one, if one exists... Stops us creating an empty tag and
2385 //make the operation easier.
2386 if (tag.Next != null && (tag.Next.Start - 1) == pos)
2389 if ((tag.Start - 1) == pos) {
2392 // We can simply break the chain and move the tag into the next line
2394 // if the tag we are moving is the first, create an empty tag
2395 // for the line we are leaving behind
2396 if (tag == line.tags) {
2397 new_tag = new LineTag(line, 1);
2398 new_tag.CopyFormattingFrom (tag);
2399 line.tags = new_tag;
2402 if (tag.Previous != null) {
2403 tag.Previous.Next = null;
2405 new_line.tags = tag;
2406 tag.Previous = null;
2407 tag.Line = new_line;
2409 // Walk the list and correct the start location of the tags we just bumped into the next line
2410 shift = tag.Start - 1;
2413 while (new_tag != null) {
2414 new_tag.Start -= shift;
2415 new_tag.Line = new_line;
2416 new_tag = new_tag.Next;
2421 new_tag = new LineTag (new_line, 1);
2422 new_tag.Next = tag.Next;
2423 new_tag.CopyFormattingFrom (tag);
2424 new_line.tags = new_tag;
2425 if (new_tag.Next != null) {
2426 new_tag.Next.Previous = new_tag;
2431 new_tag = new_tag.Next;
2432 while (new_tag != null) {
2433 new_tag.Start -= shift;
2434 new_tag.Line = new_line;
2435 new_tag = new_tag.Next;
2441 caret.line = new_line;
2442 caret.pos = caret.pos - pos;
2443 caret.tag = caret.line.FindTag(caret.pos);
2445 if (selection_visible == false) {
2446 SetSelectionToCaret (true);
2450 if (move_sel_start) {
2451 selection_start.line = new_line;
2452 selection_start.pos = selection_start.pos - pos;
2453 if (selection_start.Equals(selection_end))
2454 selection_start.tag = new_line.FindTag(selection_start.pos);
2456 selection_start.tag = new_line.FindTag (selection_start.pos + 1);
2460 selection_end.line = new_line;
2461 selection_end.pos = selection_end.pos - pos;
2462 selection_end.tag = new_line.FindTag(selection_end.pos);
2465 CharCount -= line.text.Length - pos;
2466 line.text.Remove(pos, line.text.Length - pos);
2473 private void SanityCheck () {
2474 for (int i = 1; i < lines; i++) {
2475 LineTag tag = GetLine (i).tags;
2478 throw new Exception ("Line doesn't start at the begining");
2483 while (tag != null) {
2484 if (tag.Start == start)
2485 throw new Exception ("Empty tag!");
2487 if (tag.Start < start)
2488 throw new Exception ("Insane!!");
2497 // Adds a line of text, with given font.
2498 // Bumps any line at that line number that already exists down
2499 internal void Add (int LineNo, string Text, Font font, Color color, LineEnding ending)
2501 Add (LineNo, Text, alignment, font, color, ending);
2504 internal void Add (int LineNo, string Text, HorizontalAlignment align, Font font, Color color, LineEnding ending)
2510 CharCount += Text.Length;
2512 if (LineNo<1 || Text == null) {
2514 throw new ArgumentNullException("LineNo", "Line numbers must be positive");
2516 throw new ArgumentNullException("Text", "Cannot insert NULL line");
2520 add = new Line (this, LineNo, Text, align, font, color, ending);
2523 while (line != sentinel) {
2525 line_no = line.line_no;
2527 if (LineNo > line_no) {
2529 } else if (LineNo < line_no) {
2532 // Bump existing line numbers; walk all nodes to the right of this one and increment line_no
2533 IncrementLines(line.line_no);
2538 add.left = sentinel;
2539 add.right = sentinel;
2541 if (add.parent != null) {
2542 if (LineNo > add.parent.line_no) {
2543 add.parent.right = add;
2545 add.parent.left = add;
2552 RebalanceAfterAdd(add);
2557 internal virtual void Clear() {
2560 document = sentinel;
2563 public virtual object Clone() {
2566 clone = new Document(null);
2568 clone.lines = this.lines;
2569 clone.document = (Line)document.Clone();
2574 private void Delete (int LineNo)
2581 line = GetLine (LineNo);
2583 CharCount -= line.text.Length;
2585 DecrementLines (LineNo + 1);
2589 private void Delete(Line line1) {
2590 Line line2;// = new Line();
2593 if ((line1.left == sentinel) || (line1.right == sentinel)) {
2596 line3 = line1.right;
2597 while (line3.left != sentinel) {
2602 if (line3.left != sentinel) {
2605 line2 = line3.right;
2608 line2.parent = line3.parent;
2609 if (line3.parent != null) {
2610 if(line3 == line3.parent.left) {
2611 line3.parent.left = line2;
2613 line3.parent.right = line2;
2619 if (line3 != line1) {
2622 if (selection_start.line == line3) {
2623 selection_start.line = line1;
2626 if (selection_end.line == line3) {
2627 selection_end.line = line1;
2630 if (selection_anchor.line == line3) {
2631 selection_anchor.line = line1;
2634 if (caret.line == line3) {
2639 line1.alignment = line3.alignment;
2640 line1.ascent = line3.ascent;
2641 line1.hanging_indent = line3.hanging_indent;
2642 line1.height = line3.height;
2643 line1.indent = line3.indent;
2644 line1.line_no = line3.line_no;
2645 line1.recalc = line3.recalc;
2646 line1.right_indent = line3.right_indent;
2647 line1.ending = line3.ending;
2648 line1.space = line3.space;
2649 line1.tags = line3.tags;
2650 line1.text = line3.text;
2651 line1.widths = line3.widths;
2652 line1.offset = line3.offset;
2655 while (tag != null) {
2661 if (line3.color == LineColor.Black)
2662 RebalanceAfterDelete(line2);
2667 // Invalidates the start line until the end of the viewstate
2668 internal void InvalidateLinesAfter (Line start) {
2669 owner.Invalidate (new Rectangle (0, start.Y - viewport_y, viewport_width, viewport_height - start.Y));
2672 // Invalidate a section of the document to trigger redraw
2673 internal void Invalidate(Line start, int start_pos, Line end, int end_pos) {
2679 if ((start == end) && (start_pos == end_pos)) {
2683 if (end_pos == -1) {
2684 end_pos = end.text.Length;
2687 // figure out what's before what so the logic below is straightforward
2688 if (start.line_no < end.line_no) {
2694 } else if (start.line_no > end.line_no) {
2701 if (start_pos < end_pos) {
2715 int endpoint = (int) l1.widths [p2];
2716 if (p2 == l1.text.Length + 1) {
2717 endpoint = (int) viewport_width;
2721 Console.WriteLine("Invaliding backwards from {0}:{1} to {2}:{3} {4}",
2722 l1.line_no, p1, l2.line_no, p2,
2724 (int)l1.widths[p1] + l1.X - viewport_x,
2732 owner.Invalidate(new Rectangle (
2733 offset_x + (int)l1.widths[p1] + l1.X - viewport_x,
2734 offset_y + l1.Y - viewport_y,
2735 endpoint - (int) l1.widths [p1] + 1,
2741 Console.WriteLine("Invaliding from {0}:{1} to {2}:{3} Start => x={4}, y={5}, {6}x{7}", l1.line_no, p1, l2.line_no, p2, (int)l1.widths[p1] + l1.X - viewport_x, l1.Y - viewport_y, viewport_width, l1.height);
2742 Console.WriteLine ("invalidate start line: {0} position: {1}", l1.text, p1);
2745 // Three invalidates:
2746 // First line from start
2747 owner.Invalidate(new Rectangle(
2748 offset_x + (int)l1.widths[p1] + l1.X - viewport_x,
2749 offset_y + l1.Y - viewport_y,
2755 if ((l1.line_no + 1) < l2.line_no) {
2758 y = GetLine(l1.line_no + 1).Y;
2759 owner.Invalidate(new Rectangle(
2761 offset_y + y - viewport_y,
2766 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);
2772 owner.Invalidate(new Rectangle(
2773 offset_x + (int)l2.widths[0] + l2.X - viewport_x,
2774 offset_y + l2.Y - viewport_y,
2775 (int)l2.widths[p2] + 1,
2779 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);
2783 /// <summary>Select text around caret</summary>
2784 internal void ExpandSelection(CaretSelection mode, bool to_caret) {
2786 // We're expanding the selection to the caret position
2788 case CaretSelection.Line: {
2789 // Invalidate the selection delta
2790 if (caret > selection_prev) {
2791 Invalidate(selection_prev.line, 0, caret.line, caret.line.text.Length);
2793 Invalidate(selection_prev.line, selection_prev.line.text.Length, caret.line, 0);
2796 if (caret.line.line_no <= selection_anchor.line.line_no) {
2797 selection_start.line = caret.line;
2798 selection_start.tag = caret.line.tags;
2799 selection_start.pos = 0;
2801 selection_end.line = selection_anchor.line;
2802 selection_end.tag = selection_anchor.tag;
2803 selection_end.pos = selection_anchor.pos;
2805 selection_end_anchor = true;
2807 selection_start.line = selection_anchor.line;
2808 selection_start.pos = selection_anchor.height;
2809 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height + 1);
2811 selection_end.line = caret.line;
2812 selection_end.tag = caret.line.tags;
2813 selection_end.pos = caret.line.text.Length;
2815 selection_end_anchor = false;
2817 selection_prev.line = caret.line;
2818 selection_prev.tag = caret.tag;
2819 selection_prev.pos = caret.pos;
2824 case CaretSelection.Word: {
2828 start_pos = FindWordSeparator(caret.line, caret.pos, false);
2829 end_pos = FindWordSeparator(caret.line, caret.pos, true);
2832 // Invalidate the selection delta
2833 if (caret > selection_prev) {
2834 Invalidate(selection_prev.line, selection_prev.pos, caret.line, end_pos);
2836 Invalidate(selection_prev.line, selection_prev.pos, caret.line, start_pos);
2838 if (caret < selection_anchor) {
2839 selection_start.line = caret.line;
2840 selection_start.tag = caret.line.FindTag(start_pos + 1);
2841 selection_start.pos = start_pos;
2843 selection_end.line = selection_anchor.line;
2844 selection_end.tag = selection_anchor.tag;
2845 selection_end.pos = selection_anchor.pos;
2847 selection_prev.line = caret.line;
2848 selection_prev.tag = caret.tag;
2849 selection_prev.pos = start_pos;
2851 selection_end_anchor = true;
2853 selection_start.line = selection_anchor.line;
2854 selection_start.pos = selection_anchor.height;
2855 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height + 1);
2857 selection_end.line = caret.line;
2858 selection_end.tag = caret.line.FindTag(end_pos);
2859 selection_end.pos = end_pos;
2861 selection_prev.line = caret.line;
2862 selection_prev.tag = caret.tag;
2863 selection_prev.pos = end_pos;
2865 selection_end_anchor = false;
2870 case CaretSelection.Position: {
2871 SetSelectionToCaret(false);
2876 // We're setting the selection 'around' the caret position
2878 case CaretSelection.Line: {
2879 this.Invalidate(caret.line, 0, caret.line, caret.line.text.Length);
2881 selection_start.line = caret.line;
2882 selection_start.tag = caret.line.tags;
2883 selection_start.pos = 0;
2885 selection_end.line = caret.line;
2886 selection_end.pos = caret.line.text.Length;
2887 selection_end.tag = caret.line.FindTag(selection_end.pos);
2889 selection_anchor.line = selection_end.line;
2890 selection_anchor.tag = selection_end.tag;
2891 selection_anchor.pos = selection_end.pos;
2892 selection_anchor.height = 0;
2894 selection_prev.line = caret.line;
2895 selection_prev.tag = caret.tag;
2896 selection_prev.pos = caret.pos;
2898 this.selection_end_anchor = true;
2903 case CaretSelection.Word: {
2907 start_pos = FindWordSeparator(caret.line, caret.pos, false);
2908 end_pos = FindWordSeparator(caret.line, caret.pos, true);
2910 this.Invalidate(selection_start.line, start_pos, caret.line, end_pos);
2912 selection_start.line = caret.line;
2913 selection_start.tag = caret.line.FindTag(start_pos + 1);
2914 selection_start.pos = start_pos;
2916 selection_end.line = caret.line;
2917 selection_end.tag = caret.line.FindTag(end_pos);
2918 selection_end.pos = end_pos;
2920 selection_anchor.line = selection_end.line;
2921 selection_anchor.tag = selection_end.tag;
2922 selection_anchor.pos = selection_end.pos;
2923 selection_anchor.height = start_pos;
2925 selection_prev.line = caret.line;
2926 selection_prev.tag = caret.tag;
2927 selection_prev.pos = caret.pos;
2929 this.selection_end_anchor = true;
2936 SetSelectionVisible (!(selection_start == selection_end));
2939 internal void SetSelectionToCaret(bool start) {
2941 // Invalidate old selection; selection is being reset to empty
2942 this.Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2944 selection_start.line = caret.line;
2945 selection_start.tag = caret.tag;
2946 selection_start.pos = caret.pos;
2948 // start always also selects end
2949 selection_end.line = caret.line;
2950 selection_end.tag = caret.tag;
2951 selection_end.pos = caret.pos;
2953 selection_anchor.line = caret.line;
2954 selection_anchor.tag = caret.tag;
2955 selection_anchor.pos = caret.pos;
2957 // Invalidate from previous end to caret (aka new end)
2958 if (selection_end_anchor) {
2959 if (selection_start != caret) {
2960 this.Invalidate(selection_start.line, selection_start.pos, caret.line, caret.pos);
2963 if (selection_end != caret) {
2964 this.Invalidate(selection_end.line, selection_end.pos, caret.line, caret.pos);
2968 if (caret < selection_anchor) {
2969 selection_start.line = caret.line;
2970 selection_start.tag = caret.tag;
2971 selection_start.pos = caret.pos;
2973 selection_end.line = selection_anchor.line;
2974 selection_end.tag = selection_anchor.tag;
2975 selection_end.pos = selection_anchor.pos;
2977 selection_end_anchor = true;
2979 selection_start.line = selection_anchor.line;
2980 selection_start.tag = selection_anchor.tag;
2981 selection_start.pos = selection_anchor.pos;
2983 selection_end.line = caret.line;
2984 selection_end.tag = caret.tag;
2985 selection_end.pos = caret.pos;
2987 selection_end_anchor = false;
2991 SetSelectionVisible (!(selection_start == selection_end));
2994 internal void SetSelection(Line start, int start_pos, Line end, int end_pos) {
2995 if (selection_visible) {
2996 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2999 if ((end.line_no < start.line_no) || ((end == start) && (end_pos <= start_pos))) {
3000 selection_start.line = end;
3001 selection_start.tag = LineTag.FindTag(end, end_pos);
3002 selection_start.pos = end_pos;
3004 selection_end.line = start;
3005 selection_end.tag = LineTag.FindTag(start, start_pos);
3006 selection_end.pos = start_pos;
3008 selection_end_anchor = true;
3010 selection_start.line = start;
3011 selection_start.tag = LineTag.FindTag(start, start_pos);
3012 selection_start.pos = start_pos;
3014 selection_end.line = end;
3015 selection_end.tag = LineTag.FindTag(end, end_pos);
3016 selection_end.pos = end_pos;
3018 selection_end_anchor = false;
3021 selection_anchor.line = start;
3022 selection_anchor.tag = selection_start.tag;
3023 selection_anchor.pos = start_pos;
3025 if (((start == end) && (start_pos == end_pos)) || start == null || end == null) {
3026 SetSelectionVisible (false);
3028 SetSelectionVisible (true);
3029 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3033 internal void SetSelectionStart(Line start, int start_pos, bool invalidate) {
3034 // Invalidate from the previous to the new start pos
3036 Invalidate(selection_start.line, selection_start.pos, start, start_pos);
3038 selection_start.line = start;
3039 selection_start.pos = start_pos;
3040 selection_start.tag = LineTag.FindTag(start, start_pos);
3042 selection_anchor.line = start;
3043 selection_anchor.pos = start_pos;
3044 selection_anchor.tag = selection_start.tag;
3046 selection_end_anchor = false;
3049 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
3050 SetSelectionVisible (true);
3052 SetSelectionVisible (false);
3056 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3059 internal void SetSelectionStart(int character_index, bool invalidate) {
3064 if (character_index < 0) {
3068 CharIndexToLineTag(character_index, out line, out tag, out pos);
3069 SetSelectionStart(line, pos, invalidate);
3072 internal void SetSelectionEnd(Line end, int end_pos, bool invalidate) {
3074 if (end == selection_end.line && end_pos == selection_start.pos) {
3075 selection_anchor.line = selection_start.line;
3076 selection_anchor.tag = selection_start.tag;
3077 selection_anchor.pos = selection_start.pos;
3079 selection_end.line = selection_start.line;
3080 selection_end.tag = selection_start.tag;
3081 selection_end.pos = selection_start.pos;
3083 selection_end_anchor = false;
3084 } else if ((end.line_no < selection_anchor.line.line_no) || ((end == selection_anchor.line) && (end_pos <= selection_anchor.pos))) {
3085 selection_start.line = end;
3086 selection_start.tag = LineTag.FindTag(end, end_pos);
3087 selection_start.pos = end_pos;
3089 selection_end.line = selection_anchor.line;
3090 selection_end.tag = selection_anchor.tag;
3091 selection_end.pos = selection_anchor.pos;
3093 selection_end_anchor = true;
3095 selection_start.line = selection_anchor.line;
3096 selection_start.tag = selection_anchor.tag;
3097 selection_start.pos = selection_anchor.pos;
3099 selection_end.line = end;
3100 selection_end.tag = LineTag.FindTag(end, end_pos);
3101 selection_end.pos = end_pos;
3103 selection_end_anchor = false;
3106 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
3107 SetSelectionVisible (true);
3109 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3111 SetSelectionVisible (false);
3112 // ?? Do I need to invalidate here, tests seem to work without it, but I don't think they should :-s
3116 internal void SetSelectionEnd(int character_index, bool invalidate) {
3121 if (character_index < 0) {
3125 CharIndexToLineTag(character_index, out line, out tag, out pos);
3126 SetSelectionEnd(line, pos, invalidate);
3129 internal void SetSelection(Line start, int start_pos) {
3130 if (selection_visible) {
3131 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3134 selection_start.line = start;
3135 selection_start.pos = start_pos;
3136 selection_start.tag = LineTag.FindTag(start, start_pos);
3138 selection_end.line = start;
3139 selection_end.tag = selection_start.tag;
3140 selection_end.pos = start_pos;
3142 selection_anchor.line = start;
3143 selection_anchor.tag = selection_start.tag;
3144 selection_anchor.pos = start_pos;
3146 selection_end_anchor = false;
3147 SetSelectionVisible (false);
3150 internal void InvalidateSelectionArea() {
3151 Invalidate (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3154 // Return the current selection, as string
3155 internal string GetSelection() {
3156 // We return String.Empty if there is no selection
3157 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3158 return string.Empty;
3161 if (selection_start.line == selection_end.line) {
3162 return selection_start.line.text.ToString (selection_start.pos, selection_end.pos - selection_start.pos);
3169 sb = new StringBuilder();
3170 start = selection_start.line.line_no;
3171 end = selection_end.line.line_no;
3173 sb.Append(selection_start.line.text.ToString(selection_start.pos, selection_start.line.text.Length - selection_start.pos));
3175 if ((start + 1) < end) {
3176 for (i = start + 1; i < end; i++) {
3177 sb.Append(GetLine(i).text.ToString());
3181 sb.Append(selection_end.line.text.ToString(0, selection_end.pos));
3183 return sb.ToString();
3187 internal void ReplaceSelection(string s, bool select_new) {
3190 int selection_start_pos = LineTagToCharIndex (selection_start.line, selection_start.pos);
3193 // First, delete any selected text
3194 if ((selection_start.pos != selection_end.pos) || (selection_start.line != selection_end.line)) {
3195 if (selection_start.line == selection_end.line) {
3196 undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3198 DeleteChars (selection_start.line, selection_start.pos, selection_end.pos - selection_start.pos);
3200 // The tag might have been removed, we need to recalc it
3201 selection_start.tag = selection_start.line.FindTag(selection_start.pos + 1);
3206 start = selection_start.line.line_no;
3207 end = selection_end.line.line_no;
3209 undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3211 InvalidateLinesAfter(selection_start.line);
3213 // Delete first line
3214 DeleteChars (selection_start.line, selection_start.pos, selection_start.line.text.Length - selection_start.pos);
3215 selection_start.line.recalc = true;
3218 DeleteChars(selection_end.line, 0, selection_end.pos);
3222 for (i = end - 1; i >= start; i--) {
3227 // BIG FAT WARNING - selection_end.line might be stale due
3228 // to the above Delete() call. DONT USE IT before hitting the end of this method!
3230 // Join start and end
3231 Combine(selection_start.line.line_no, start);
3236 Insert(selection_start.line, selection_start.pos, false, s);
3237 undo.RecordInsertString (selection_start.line, selection_start.pos, s);
3238 ResumeRecalc (false);
3240 Line begin_update_line = selection_start.line;
3241 int begin_update_pos = selection_start.pos;
3244 CharIndexToLineTag(selection_start_pos + s.Length, out selection_start.line,
3245 out selection_start.tag, out selection_start.pos);
3247 selection_end.line = selection_start.line;
3248 selection_end.pos = selection_start.pos;
3249 selection_end.tag = selection_start.tag;
3250 selection_anchor.line = selection_start.line;
3251 selection_anchor.pos = selection_start.pos;
3252 selection_anchor.tag = selection_start.tag;
3254 SetSelectionVisible (false);
3256 CharIndexToLineTag(selection_start_pos, out selection_start.line,
3257 out selection_start.tag, out selection_start.pos);
3259 CharIndexToLineTag(selection_start_pos + s.Length, out selection_end.line,
3260 out selection_end.tag, out selection_end.pos);
3262 selection_anchor.line = selection_start.line;
3263 selection_anchor.pos = selection_start.pos;
3264 selection_anchor.tag = selection_start.tag;
3266 SetSelectionVisible (true);
3269 PositionCaret (selection_start.line, selection_start.pos);
3270 UpdateView (begin_update_line, selection_end.line.line_no - begin_update_line.line_no, begin_update_pos);
3273 internal void CharIndexToLineTag(int index, out Line line_out, out LineTag tag_out, out int pos) {
3282 for (i = 1; i <= lines; i++) {
3286 chars += line.text.Length;
3288 if (index <= chars) {
3289 // we found the line
3292 while (tag != null) {
3293 if (index < (start + tag.Start + tag.Length - 1)) {
3295 tag_out = LineTag.GetFinalTag (tag);
3296 pos = index - start;
3299 if (tag.Next == null) {
3302 next_line = GetLine(line.line_no + 1);
3304 if (next_line != null) {
3305 line_out = next_line;
3306 tag_out = LineTag.GetFinalTag (next_line.tags);
3311 tag_out = LineTag.GetFinalTag (tag);
3312 pos = line_out.text.Length;
3321 line_out = GetLine(lines);
3322 tag = line_out.tags;
3323 while (tag.Next != null) {
3327 pos = line_out.text.Length;
3330 internal int LineTagToCharIndex(Line line, int pos) {
3334 // Count first and last line
3337 // Count the lines in the middle
3339 for (i = 1; i < line.line_no; i++) {
3340 length += GetLine(i).text.Length;
3348 internal int SelectionLength() {
3349 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3353 if (selection_start.line == selection_end.line) {
3354 return selection_end.pos - selection_start.pos;
3361 // Count first and last line
3362 length = selection_start.line.text.Length - selection_start.pos + selection_end.pos + crlf_size;
3364 // Count the lines in the middle
3365 start = selection_start.line.line_no + 1;
3366 end = selection_end.line.line_no;
3369 for (i = start; i < end; i++) {
3370 Line line = GetLine (i);
3371 length += line.text.Length + LineEndingLength (line.ending);
3382 // UIA: Method used via reflection in TextRangeProvider
3384 /// <summary>Give it a Line number and it returns the Line object at with that line number</summary>
3385 internal Line GetLine(int LineNo) {
3386 Line line = document;
3388 while (line != sentinel) {
3389 if (LineNo == line.line_no) {
3391 } else if (LineNo < line.line_no) {
3401 /// <summary>Retrieve the previous tag; walks line boundaries</summary>
3402 internal LineTag PreviousTag(LineTag tag) {
3405 if (tag.Previous != null) {
3406 return tag.Previous;
3410 if (tag.Line.line_no == 1) {
3414 l = GetLine(tag.Line.line_no - 1);
3419 while (t.Next != null) {
3428 /// <summary>Retrieve the next tag; walks line boundaries</summary>
3429 internal LineTag NextTag(LineTag tag) {
3432 if (tag.Next != null) {
3437 l = GetLine(tag.Line.line_no + 1);
3445 internal Line ParagraphStart(Line line) {
3446 Line lastline = line;
3448 if (line.line_no <= 1)
3452 lastline = GetLine (line.line_no - 1);
3453 } while (lastline.ending == LineEnding.Wrap);
3458 internal Line ParagraphEnd(Line line) {
3461 while (line.ending == LineEnding.Wrap) {
3462 l = GetLine(line.line_no + 1);
3463 if ((l == null) || (l.ending != LineEnding.Wrap)) {
3471 /// <summary>Give it a pixel offset coordinate and it returns the Line covering that are (offset
3472 /// is either X or Y depending on if we are multiline
3474 internal Line GetLineByPixel (int offset, bool exact)
3476 Line line = document;
3480 while (line != sentinel) {
3482 if ((offset >= line.Y) && (offset < (line.Y+line.height))) {
3484 } else if (offset < line.Y) {
3491 while (line != sentinel) {
3493 if ((offset >= line.X) && (offset < (line.X + line.Width)))
3495 else if (offset < line.X)
3508 // UIA: Method used via reflection in TextProviderBehavior
3510 // Give it x/y pixel coordinates and it returns the Tag at that position
3511 internal LineTag FindCursor (int x, int y, out int index)
3518 line = GetLineByPixel (multiline ? y : x, false);
3520 LineTag tag = line.GetTag (x);
3522 if (tag.Length == 0 && tag.Start == 1)
3525 index = tag.GetCharIndex (x - line.align_shift);
3530 /// <summary>Format area of document in specified font and color</summary>
3531 /// <param name="start_pos">1-based start position on start_line</param>
3532 /// <param name="end_pos">1-based end position on end_line </param>
3533 internal void FormatText (Line start_line, int start_pos, Line end_line, int end_pos, Font font,
3534 Color color, Color back_color, FormatSpecified specified)
3538 // First, format the first line
3539 if (start_line != end_line) {
3541 LineTag.FormatText(start_line, start_pos, start_line.text.Length - start_pos + 1, font, color, back_color, specified);
3544 LineTag.FormatText(end_line, 1, end_pos, font, color, back_color, specified);
3546 // Now all the lines inbetween
3547 for (int i = start_line.line_no + 1; i < end_line.line_no; i++) {
3549 LineTag.FormatText(l, 1, l.text.Length, font, color, back_color, specified);
3552 // Special case, single line
3553 LineTag.FormatText(start_line, start_pos, end_pos - start_pos, font, color, back_color, specified);
3555 if ((end_pos - start_pos) == 0 && CaretTag.Length != 0)
3556 CaretTag = CaretTag.Next;
3560 internal void RecalculateAlignments ()
3569 while (line_no <= lines) {
3570 line = GetLine(line_no);
3573 switch (line.alignment) {
3574 case HorizontalAlignment.Left:
3575 line.align_shift = 0;
3577 case HorizontalAlignment.Center:
3578 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3580 case HorizontalAlignment.Right:
3581 line.align_shift = viewport_width - (int)line.widths[line.text.Length] - right_margin;
3591 /// <summary>Calculate formatting for the whole document</summary>
3592 internal bool RecalculateDocument(Graphics g) {
3593 return RecalculateDocument(g, 1, this.lines, false);
3596 /// <summary>Calculate formatting starting at a certain line</summary>
3597 internal bool RecalculateDocument(Graphics g, int start) {
3598 return RecalculateDocument(g, start, this.lines, false);
3601 /// <summary>Calculate formatting within two given line numbers</summary>
3602 internal bool RecalculateDocument(Graphics g, int start, int end) {
3603 return RecalculateDocument(g, start, end, false);
3606 /// <summary>With optimize on, returns true if line heights changed</summary>
3607 internal bool RecalculateDocument(Graphics g, int start, int end, bool optimize) {
3615 if (recalc_suspended > 0) {
3616 recalc_pending = true;
3617 recalc_start = Math.Min (recalc_start, start);
3618 recalc_end = Math.Max (recalc_end, end);
3619 recalc_optimize = optimize;
3623 // Fixup the positions, they can go kinda nuts
3624 // (this is suspend and resume recalc - they set them to 1 and max)
3625 start = Math.Max (start, 1);
3626 end = Math.Min (end, lines);
3628 offset = GetLine(start).offset;
3633 changed = true; // We always return true if we run non-optimized
3638 while (line_no <= (end + this.lines - shift)) {
3639 line = GetLine(line_no++);
3640 line.offset = offset;
3642 // if we are not calculating a password
3645 line.RecalculateLine(g, this);
3647 if (line.recalc && line.RecalculateLine(g, this)) {
3649 // If the height changed, all subsequent lines change
3656 line.RecalculatePasswordLine(g, this);
3658 if (line.recalc && line.RecalculatePasswordLine(g, this)) {
3660 // If the height changed, all subsequent lines change
3667 if (line.widths[line.text.Length] > new_width) {
3668 new_width = (int)line.widths[line.text.Length];
3671 // Calculate alignment
3672 if (line.alignment != HorizontalAlignment.Left) {
3673 if (line.alignment == HorizontalAlignment.Center) {
3674 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3676 line.align_shift = viewport_width - (int)line.widths[line.text.Length] - 1;
3681 offset += line.height;
3683 offset += (int) line.widths [line.text.Length];
3685 if (line_no > lines) {
3690 if (document_x != new_width) {
3691 document_x = new_width;
3692 if (WidthChanged != null) {
3693 WidthChanged(this, null);
3697 RecalculateAlignments();
3699 line = GetLine(lines);
3701 if (document_y != line.Y + line.height) {
3702 document_y = line.Y + line.height;
3703 if (HeightChanged != null) {
3704 HeightChanged(this, null);
3708 // scan for links and tell us if its all
3709 // changed, so we can update everything
3711 ScanForLinks (start, end, ref changed);
3717 internal int Size() {
3721 private void owner_HandleCreated(object sender, EventArgs e) {
3722 RecalculateDocument(owner.CreateGraphicsInternal());
3726 private void owner_VisibleChanged(object sender, EventArgs e) {
3727 if (owner.Visible) {
3728 RecalculateDocument(owner.CreateGraphicsInternal());
3732 internal static bool IsWordSeparator (char ch)
3747 internal int FindWordSeparator(Line line, int pos, bool forward) {
3750 len = line.text.Length;
3753 for (int i = pos + 1; i < len; i++) {
3754 if (IsWordSeparator(line.Text[i])) {
3760 for (int i = pos - 1; i > 0; i--) {
3761 if (IsWordSeparator(line.Text[i - 1])) {
3769 /* Search document for text */
3770 internal bool FindChars(char[] chars, Marker start, Marker end, out Marker result) {
3776 // Search for occurence of any char in the chars array
3777 result = new Marker();
3780 line_no = start.line.line_no;
3782 while (line_no <= end.line.line_no) {
3783 line_len = line.text.Length;
3784 while (pos < line_len) {
3785 for (int i = 0; i < chars.Length; i++) {
3786 if (line.text[pos] == chars[i]) {
3788 if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
3802 line = GetLine(line_no);
3808 // This version does not build one big string for searching, instead it handles
3809 // line-boundaries, which is faster and less memory intensive
3810 // FIXME - Depending on culture stuff we might have to create a big string and use culturespecific
3811 // search stuff and change it to accept and return positions instead of Markers (which would match
3812 // RichTextBox behaviour better but would be inconsistent with the rest of TextControl)
3813 internal bool Find(string search, Marker start, Marker end, out Marker result, RichTextBoxFinds options) {
3815 string search_string;
3827 result = new Marker();
3828 word_option = ((options & RichTextBoxFinds.WholeWord) != 0);
3829 ignore_case = ((options & RichTextBoxFinds.MatchCase) == 0);
3830 reverse = ((options & RichTextBoxFinds.Reverse) != 0);
3833 line_no = start.line.line_no;
3837 // Prep our search string, lowercasing it if we do case-independent matching
3840 sb = new StringBuilder(search);
3841 for (int i = 0; i < sb.Length; i++) {
3842 sb[i] = Char.ToLower(sb[i]);
3844 search_string = sb.ToString();
3846 search_string = search;
3849 // We need to check if the character before our start position is a wordbreak
3852 if ((pos == 0) || (IsWordSeparator(line.text[pos - 1]))) {
3859 if (IsWordSeparator(line.text[pos - 1])) {
3865 // Need to check the end of the previous line
3868 prev_line = GetLine(line_no - 1);
3869 if (prev_line.ending == LineEnding.Wrap) {
3870 if (IsWordSeparator(prev_line.text[prev_line.text.Length - 1])) {
3884 // To avoid duplication of this loop with reverse logic, we search
3885 // through the document, remembering the last match and when returning
3886 // report that last remembered match
3888 last = new Marker();
3889 last.height = -1; // Abused - we use it to track change
3891 while (line_no <= end.line.line_no) {
3892 if (line_no != end.line.line_no) {
3893 line_len = line.text.Length;
3898 while (pos < line_len) {
3900 if (word_option && (current == search_string.Length)) {
3901 if (IsWordSeparator(line.text[pos])) {
3914 c = Char.ToLower(line.text[pos]);
3919 if (c == search_string[current]) {
3925 if (!word_option || (word_option && (word || (current > 0)))) {
3929 if (!word_option && (current == search_string.Length)) {
3946 if (IsWordSeparator(c)) {
3954 // Mark that we just saw a word boundary
3955 if (line.ending != LineEnding.Wrap || line.line_no == lines - 1) {
3959 if (current == search_string.Length) {
3975 line = GetLine(line_no);
3979 if (last.height != -1) {
3989 // if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
4001 internal void GetMarker(out Marker mark, bool start) {
4002 mark = new Marker();
4005 mark.line = GetLine(1);
4006 mark.tag = mark.line.tags;
4009 mark.line = GetLine(lines);
4010 mark.tag = mark.line.tags;
4011 while (mark.tag.Next != null) {
4012 mark.tag = mark.tag.Next;
4014 mark.pos = mark.line.text.Length;
4017 #endregion // Internal Methods
4020 internal event EventHandler CaretMoved;
4021 internal event EventHandler WidthChanged;
4022 internal event EventHandler HeightChanged;
4023 internal event EventHandler LengthChanged;
4024 internal event EventHandler UIASelectionChanged;
4025 #endregion // Events
4027 #region Administrative
4028 public IEnumerator GetEnumerator() {
4033 public override bool Equals(object obj) {
4038 if (!(obj is Document)) {
4046 if (ToString().Equals(((Document)obj).ToString())) {
4053 public override int GetHashCode() {
4057 public override string ToString() {
4058 return "document " + this.document_id;
4060 #endregion // Administrative
4063 internal class PictureTag : LineTag {
4065 internal RTF.Picture picture;
4067 internal PictureTag (Line line, int start, RTF.Picture picture) : base (line, start)
4069 this.picture = picture;
4072 public override bool IsTextTag {
4073 get { return false; }
4076 public override SizeF SizeOfPosition (Graphics dc, int pos)
4078 return picture.Size;
4081 internal override int MaxHeight ()
4083 return (int) (picture.Height + 0.5F);
4086 public override void Draw (Graphics dc, Color color, float xoff, float y, int start, int end)
4088 picture.DrawImage (dc, xoff + Line.widths [start], y, false);
4091 public override void Draw (Graphics dc, Color color, float xoff, float y, int start, int end, string text)
4093 picture.DrawImage (dc, xoff + + Line.widths [start], y, false);
4096 public override string Text ()
4102 internal class UndoManager {
4104 internal enum ActionType {
4108 // This is basically just cut & paste
4116 internal class Action {
4117 internal ActionType type;
4118 internal int line_no;
4120 internal object data;
4123 #region Local Variables
4124 private Document document;
4125 private Stack undo_actions;
4126 private Stack redo_actions;
4128 //private int caret_line;
4129 //private int caret_pos;
4131 // When performing an action, we lock the queue, so that the action can't be undone
4132 private bool locked;
4133 #endregion // Local Variables
4135 #region Constructors
4136 internal UndoManager (Document document)
4138 this.document = document;
4139 undo_actions = new Stack (50);
4140 redo_actions = new Stack (50);
4142 #endregion // Constructors
4145 internal bool CanUndo {
4146 get { return undo_actions.Count > 0; }
4149 internal bool CanRedo {
4150 get { return redo_actions.Count > 0; }
4153 internal string UndoActionName {
4155 foreach (Action action in undo_actions) {
4156 if (action.type == ActionType.UserActionBegin)
4157 return (string) action.data;
4158 if (action.type == ActionType.Typing)
4159 return Locale.GetText ("Typing");
4161 return String.Empty;
4165 internal string RedoActionName {
4167 foreach (Action action in redo_actions) {
4168 if (action.type == ActionType.UserActionBegin)
4169 return (string) action.data;
4170 if (action.type == ActionType.Typing)
4171 return Locale.GetText ("Typing");
4173 return String.Empty;
4176 #endregion // Properties
4178 #region Internal Methods
4179 internal void Clear ()
4181 undo_actions.Clear();
4182 redo_actions.Clear();
4185 internal bool Undo ()
4188 bool user_action_finished = false;
4190 if (undo_actions.Count == 0)
4196 action = (Action) undo_actions.Pop ();
4198 // Put onto redo stack
4199 redo_actions.Push(action);
4202 switch(action.type) {
4204 case ActionType.UserActionBegin:
4205 user_action_finished = true;
4208 case ActionType.UserActionEnd:
4212 case ActionType.InsertString:
4213 start = document.GetLine (action.line_no);
4214 document.SuspendUpdate ();
4215 document.DeleteMultiline (start, action.pos, ((string) action.data).Length + 1);
4216 document.PositionCaret (start, action.pos);
4217 document.SetSelectionToCaret (true);
4218 document.ResumeUpdate (true);
4221 case ActionType.Typing:
4222 start = document.GetLine (action.line_no);
4223 document.SuspendUpdate ();
4224 document.DeleteMultiline (start, action.pos, ((StringBuilder) action.data).Length);
4225 document.PositionCaret (start, action.pos);
4226 document.SetSelectionToCaret (true);
4227 document.ResumeUpdate (true);
4229 // This is an open ended operation, so only a single typing operation can be undone at once
4230 user_action_finished = true;
4233 case ActionType.DeleteString:
4234 start = document.GetLine (action.line_no);
4235 document.SuspendUpdate ();
4236 Insert (start, action.pos, (Line) action.data, true);
4237 document.ResumeUpdate (true);
4240 } while (!user_action_finished && undo_actions.Count > 0);
4247 internal bool Redo ()
4250 bool user_action_finished = false;
4252 if (redo_actions.Count == 0)
4260 action = (Action) redo_actions.Pop ();
4261 undo_actions.Push (action);
4263 switch (action.type) {
4265 case ActionType.UserActionBegin:
4269 case ActionType.UserActionEnd:
4270 user_action_finished = true;
4273 case ActionType.InsertString:
4274 start = document.GetLine (action.line_no);
4275 document.SuspendUpdate ();
4276 start_index = document.LineTagToCharIndex (start, action.pos);
4277 document.InsertString (start, action.pos, (string) action.data);
4278 document.CharIndexToLineTag (start_index + ((string) action.data).Length,
4279 out document.caret.line, out document.caret.tag,
4280 out document.caret.pos);
4281 document.UpdateCaret ();
4282 document.SetSelectionToCaret (true);
4283 document.ResumeUpdate (true);
4286 case ActionType.Typing:
4287 start = document.GetLine (action.line_no);
4288 document.SuspendUpdate ();
4289 start_index = document.LineTagToCharIndex (start, action.pos);
4290 document.InsertString (start, action.pos, ((StringBuilder) action.data).ToString ());
4291 document.CharIndexToLineTag (start_index + ((StringBuilder) action.data).Length,
4292 out document.caret.line, out document.caret.tag,
4293 out document.caret.pos);
4294 document.UpdateCaret ();
4295 document.SetSelectionToCaret (true);
4296 document.ResumeUpdate (true);
4298 // This is an open ended operation, so only a single typing operation can be undone at once
4299 user_action_finished = true;
4302 case ActionType.DeleteString:
4303 start = document.GetLine (action.line_no);
4304 document.SuspendUpdate ();
4305 document.DeleteMultiline (start, action.pos, ((Line) action.data).text.Length);
4306 document.PositionCaret (start, action.pos);
4307 document.SetSelectionToCaret (true);
4308 document.ResumeUpdate (true);
4312 } while (!user_action_finished && redo_actions.Count > 0);
4318 #endregion // Internal Methods
4320 #region Private Methods
4322 public void BeginUserAction (string name)
4327 // Nuke the redo queue
4328 redo_actions.Clear ();
4330 Action ua = new Action ();
4331 ua.type = ActionType.UserActionBegin;
4334 undo_actions.Push (ua);
4337 public void EndUserAction ()
4342 Action ua = new Action ();
4343 ua.type = ActionType.UserActionEnd;
4345 undo_actions.Push (ua);
4348 // start_pos, end_pos = 1 based
4349 public void RecordDeleteString (Line start_line, int start_pos, Line end_line, int end_pos)
4354 // Nuke the redo queue
4355 redo_actions.Clear ();
4357 Action a = new Action ();
4359 // We cant simply store the string, because then formatting would be lost
4360 a.type = ActionType.DeleteString;
4361 a.line_no = start_line.line_no;
4363 a.data = Duplicate (start_line, start_pos, end_line, end_pos);
4365 undo_actions.Push(a);
4368 public void RecordInsertString (Line line, int pos, string str)
4370 if (locked || str.Length == 0)
4373 // Nuke the redo queue
4374 redo_actions.Clear ();
4376 Action a = new Action ();
4378 a.type = ActionType.InsertString;
4380 a.line_no = line.line_no;
4383 undo_actions.Push (a);
4386 public void RecordTyping (Line line, int pos, char ch)
4391 // Nuke the redo queue
4392 redo_actions.Clear ();
4396 if (undo_actions.Count > 0)
4397 a = (Action) undo_actions.Peek ();
4399 if (a == null || a.type != ActionType.Typing) {
4401 a.type = ActionType.Typing;
4402 a.data = new StringBuilder ();
4403 a.line_no = line.line_no;
4406 undo_actions.Push (a);
4409 StringBuilder data = (StringBuilder) a.data;
4413 // start_pos = 1-based
4414 // end_pos = 1-based
4415 public Line Duplicate(Line start_line, int start_pos, Line end_line, int end_pos)
4421 LineTag current_tag;
4426 line = new Line (start_line.document, start_line.ending);
4429 for (int i = start_line.line_no; i <= end_line.line_no; i++) {
4430 current = document.GetLine(i);
4432 if (start_line.line_no == i) {
4438 if (end_line.line_no == i) {
4441 end = current.text.Length;
4448 line.text = new StringBuilder (current.text.ToString (start, end - start));
4450 // Copy tags from start to start+length onto new line
4451 current_tag = current.FindTag (start + 1);
4452 while ((current_tag != null) && (current_tag.Start <= end)) {
4453 if ((current_tag.Start <= start) && (start < (current_tag.Start + current_tag.Length))) {
4454 // start tag is within this tag
4457 tag_start = current_tag.Start;
4460 tag = new LineTag(line, tag_start - start + 1);
4461 tag.CopyFormattingFrom (current_tag);
4463 current_tag = current_tag.Next;
4465 // Add the new tag to the line
4466 if (line.tags == null) {
4472 while (tail.Next != null) {
4476 tag.Previous = tail;
4480 if ((i + 1) <= end_line.line_no) {
4481 line.ending = current.ending;
4483 // Chain them (we use right/left as next/previous)
4484 line.right = new Line (start_line.document, start_line.ending);
4485 line.right.left = line;
4493 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
4494 internal void Insert(Line line, int pos, Line insert, bool select)
4502 // Handle special case first
4503 if (insert.right == null) {
4505 // Single line insert
4506 document.Split(line, pos);
4508 if (insert.tags == null) {
4509 return; // Blank line
4512 //Insert our tags at the end
4515 while (tag.Next != null) {
4519 offset = tag.Start + tag.Length - 1;
4521 tag.Next = insert.tags;
4522 line.text.Insert(offset, insert.text.ToString());
4524 // Adjust start locations
4526 while (tag != null) {
4527 tag.Start += offset;
4531 // Put it back together
4532 document.Combine(line.line_no, line.line_no + 1);
4535 document.SetSelectionStart (line, pos, false);
4536 document.SetSelectionEnd (line, pos + insert.text.Length, false);
4539 document.UpdateView(line, pos);
4547 while (current != null) {
4549 if (current == insert) {
4550 // Inserting the first line we split the line (and make space)
4551 document.Split(line.line_no, pos);
4552 //Insert our tags at the end of the line
4556 if (tag != null && tag.Length != 0) {
4557 while (tag.Next != null) {
4560 offset = tag.Start + tag.Length - 1;
4561 tag.Next = current.tags;
4562 tag.Next.Previous = tag;
4568 line.tags = current.tags;
4569 line.tags.Previous = null;
4573 line.ending = current.ending;
4575 document.Split(line.line_no, 0);
4577 line.tags = current.tags;
4578 line.tags.Previous = null;
4579 line.ending = current.ending;
4583 // Adjust start locations and line pointers
4584 while (tag != null) {
4585 tag.Start += offset - 1;
4590 line.text.Insert(offset, current.text.ToString());
4591 line.Grow(line.text.Length);
4594 line = document.GetLine(line.line_no + 1);
4596 // FIXME? Test undo of line-boundaries
4597 if ((current.right == null) && (current.tags.Length != 0)) {
4598 document.Combine(line.line_no - 1, line.line_no);
4600 current = current.right;
4605 // Recalculate our document
4606 document.UpdateView(first, lines, pos);
4609 #endregion // Private Methods