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);
2447 move_sel_start = false;
2448 move_sel_end = false;
2452 if (move_sel_start) {
2453 selection_start.line = new_line;
2454 selection_start.pos = selection_start.pos - pos;
2455 if (selection_start.Equals(selection_end))
2456 selection_start.tag = new_line.FindTag(selection_start.pos);
2458 selection_start.tag = new_line.FindTag (selection_start.pos + 1);
2462 selection_end.line = new_line;
2463 selection_end.pos = selection_end.pos - pos;
2464 selection_end.tag = new_line.FindTag(selection_end.pos);
2467 CharCount -= line.text.Length - pos;
2468 line.text.Remove(pos, line.text.Length - pos);
2475 private void SanityCheck () {
2476 for (int i = 1; i < lines; i++) {
2477 LineTag tag = GetLine (i).tags;
2480 throw new Exception ("Line doesn't start at the begining");
2485 while (tag != null) {
2486 if (tag.Start == start)
2487 throw new Exception ("Empty tag!");
2489 if (tag.Start < start)
2490 throw new Exception ("Insane!!");
2499 // Adds a line of text, with given font.
2500 // Bumps any line at that line number that already exists down
2501 internal void Add (int LineNo, string Text, Font font, Color color, LineEnding ending)
2503 Add (LineNo, Text, alignment, font, color, ending);
2506 internal void Add (int LineNo, string Text, HorizontalAlignment align, Font font, Color color, LineEnding ending)
2512 CharCount += Text.Length;
2514 if (LineNo<1 || Text == null) {
2516 throw new ArgumentNullException("LineNo", "Line numbers must be positive");
2518 throw new ArgumentNullException("Text", "Cannot insert NULL line");
2522 add = new Line (this, LineNo, Text, align, font, color, ending);
2525 while (line != sentinel) {
2527 line_no = line.line_no;
2529 if (LineNo > line_no) {
2531 } else if (LineNo < line_no) {
2534 // Bump existing line numbers; walk all nodes to the right of this one and increment line_no
2535 IncrementLines(line.line_no);
2540 add.left = sentinel;
2541 add.right = sentinel;
2543 if (add.parent != null) {
2544 if (LineNo > add.parent.line_no) {
2545 add.parent.right = add;
2547 add.parent.left = add;
2554 RebalanceAfterAdd(add);
2559 internal virtual void Clear() {
2562 document = sentinel;
2565 public virtual object Clone() {
2568 clone = new Document(null);
2570 clone.lines = this.lines;
2571 clone.document = (Line)document.Clone();
2576 private void Delete (int LineNo)
2583 line = GetLine (LineNo);
2585 CharCount -= line.text.Length;
2587 DecrementLines (LineNo + 1);
2591 private void Delete(Line line1) {
2592 Line line2;// = new Line();
2595 if ((line1.left == sentinel) || (line1.right == sentinel)) {
2598 line3 = line1.right;
2599 while (line3.left != sentinel) {
2604 if (line3.left != sentinel) {
2607 line2 = line3.right;
2610 line2.parent = line3.parent;
2611 if (line3.parent != null) {
2612 if(line3 == line3.parent.left) {
2613 line3.parent.left = line2;
2615 line3.parent.right = line2;
2621 if (line3 != line1) {
2624 if (selection_start.line == line3) {
2625 selection_start.line = line1;
2628 if (selection_end.line == line3) {
2629 selection_end.line = line1;
2632 if (selection_anchor.line == line3) {
2633 selection_anchor.line = line1;
2636 if (caret.line == line3) {
2641 line1.alignment = line3.alignment;
2642 line1.ascent = line3.ascent;
2643 line1.hanging_indent = line3.hanging_indent;
2644 line1.height = line3.height;
2645 line1.indent = line3.indent;
2646 line1.line_no = line3.line_no;
2647 line1.recalc = line3.recalc;
2648 line1.right_indent = line3.right_indent;
2649 line1.ending = line3.ending;
2650 line1.space = line3.space;
2651 line1.tags = line3.tags;
2652 line1.text = line3.text;
2653 line1.widths = line3.widths;
2654 line1.offset = line3.offset;
2657 while (tag != null) {
2663 if (line3.color == LineColor.Black)
2664 RebalanceAfterDelete(line2);
2669 // Invalidates the start line until the end of the viewstate
2670 internal void InvalidateLinesAfter (Line start) {
2671 owner.Invalidate (new Rectangle (0, start.Y - viewport_y, viewport_width, viewport_height - start.Y));
2674 // Invalidate a section of the document to trigger redraw
2675 internal void Invalidate(Line start, int start_pos, Line end, int end_pos) {
2681 if ((start == end) && (start_pos == end_pos)) {
2685 if (end_pos == -1) {
2686 end_pos = end.text.Length;
2689 // figure out what's before what so the logic below is straightforward
2690 if (start.line_no < end.line_no) {
2696 } else if (start.line_no > end.line_no) {
2703 if (start_pos < end_pos) {
2717 int endpoint = (int) l1.widths [p2];
2718 if (p2 == l1.text.Length + 1) {
2719 endpoint = (int) viewport_width;
2723 Console.WriteLine("Invaliding backwards from {0}:{1} to {2}:{3} {4}",
2724 l1.line_no, p1, l2.line_no, p2,
2726 (int)l1.widths[p1] + l1.X - viewport_x,
2734 owner.Invalidate(new Rectangle (
2735 offset_x + (int)l1.widths[p1] + l1.X - viewport_x,
2736 offset_y + l1.Y - viewport_y,
2737 endpoint - (int) l1.widths [p1] + 1,
2743 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);
2744 Console.WriteLine ("invalidate start line: {0} position: {1}", l1.text, p1);
2747 // Three invalidates:
2748 // First line from start
2749 owner.Invalidate(new Rectangle(
2750 offset_x + (int)l1.widths[p1] + l1.X - viewport_x,
2751 offset_y + l1.Y - viewport_y,
2757 if ((l1.line_no + 1) < l2.line_no) {
2760 y = GetLine(l1.line_no + 1).Y;
2761 owner.Invalidate(new Rectangle(
2763 offset_y + y - viewport_y,
2768 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);
2774 owner.Invalidate(new Rectangle(
2775 offset_x + (int)l2.widths[0] + l2.X - viewport_x,
2776 offset_y + l2.Y - viewport_y,
2777 (int)l2.widths[p2] + 1,
2781 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);
2785 /// <summary>Select text around caret</summary>
2786 internal void ExpandSelection(CaretSelection mode, bool to_caret) {
2788 // We're expanding the selection to the caret position
2790 case CaretSelection.Line: {
2791 // Invalidate the selection delta
2792 if (caret > selection_prev) {
2793 Invalidate(selection_prev.line, 0, caret.line, caret.line.text.Length);
2795 Invalidate(selection_prev.line, selection_prev.line.text.Length, caret.line, 0);
2798 if (caret.line.line_no <= selection_anchor.line.line_no) {
2799 selection_start.line = caret.line;
2800 selection_start.tag = caret.line.tags;
2801 selection_start.pos = 0;
2803 selection_end.line = selection_anchor.line;
2804 selection_end.tag = selection_anchor.tag;
2805 selection_end.pos = selection_anchor.pos;
2807 selection_end_anchor = true;
2809 selection_start.line = selection_anchor.line;
2810 selection_start.pos = selection_anchor.height;
2811 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height + 1);
2813 selection_end.line = caret.line;
2814 selection_end.tag = caret.line.tags;
2815 selection_end.pos = caret.line.text.Length;
2817 selection_end_anchor = false;
2819 selection_prev.line = caret.line;
2820 selection_prev.tag = caret.tag;
2821 selection_prev.pos = caret.pos;
2826 case CaretSelection.Word: {
2830 start_pos = FindWordSeparator(caret.line, caret.pos, false);
2831 end_pos = FindWordSeparator(caret.line, caret.pos, true);
2834 // Invalidate the selection delta
2835 if (caret > selection_prev) {
2836 Invalidate(selection_prev.line, selection_prev.pos, caret.line, end_pos);
2838 Invalidate(selection_prev.line, selection_prev.pos, caret.line, start_pos);
2840 if (caret < selection_anchor) {
2841 selection_start.line = caret.line;
2842 selection_start.tag = caret.line.FindTag(start_pos + 1);
2843 selection_start.pos = start_pos;
2845 selection_end.line = selection_anchor.line;
2846 selection_end.tag = selection_anchor.tag;
2847 selection_end.pos = selection_anchor.pos;
2849 selection_prev.line = caret.line;
2850 selection_prev.tag = caret.tag;
2851 selection_prev.pos = start_pos;
2853 selection_end_anchor = true;
2855 selection_start.line = selection_anchor.line;
2856 selection_start.pos = selection_anchor.height;
2857 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height + 1);
2859 selection_end.line = caret.line;
2860 selection_end.tag = caret.line.FindTag(end_pos);
2861 selection_end.pos = end_pos;
2863 selection_prev.line = caret.line;
2864 selection_prev.tag = caret.tag;
2865 selection_prev.pos = end_pos;
2867 selection_end_anchor = false;
2872 case CaretSelection.Position: {
2873 SetSelectionToCaret(false);
2878 // We're setting the selection 'around' the caret position
2880 case CaretSelection.Line: {
2881 this.Invalidate(caret.line, 0, caret.line, caret.line.text.Length);
2883 selection_start.line = caret.line;
2884 selection_start.tag = caret.line.tags;
2885 selection_start.pos = 0;
2887 selection_end.line = caret.line;
2888 selection_end.pos = caret.line.text.Length;
2889 selection_end.tag = caret.line.FindTag(selection_end.pos);
2891 selection_anchor.line = selection_end.line;
2892 selection_anchor.tag = selection_end.tag;
2893 selection_anchor.pos = selection_end.pos;
2894 selection_anchor.height = 0;
2896 selection_prev.line = caret.line;
2897 selection_prev.tag = caret.tag;
2898 selection_prev.pos = caret.pos;
2900 this.selection_end_anchor = true;
2905 case CaretSelection.Word: {
2909 start_pos = FindWordSeparator(caret.line, caret.pos, false);
2910 end_pos = FindWordSeparator(caret.line, caret.pos, true);
2912 this.Invalidate(selection_start.line, start_pos, caret.line, end_pos);
2914 selection_start.line = caret.line;
2915 selection_start.tag = caret.line.FindTag(start_pos + 1);
2916 selection_start.pos = start_pos;
2918 selection_end.line = caret.line;
2919 selection_end.tag = caret.line.FindTag(end_pos);
2920 selection_end.pos = end_pos;
2922 selection_anchor.line = selection_end.line;
2923 selection_anchor.tag = selection_end.tag;
2924 selection_anchor.pos = selection_end.pos;
2925 selection_anchor.height = start_pos;
2927 selection_prev.line = caret.line;
2928 selection_prev.tag = caret.tag;
2929 selection_prev.pos = caret.pos;
2931 this.selection_end_anchor = true;
2938 SetSelectionVisible (!(selection_start == selection_end));
2941 internal void SetSelectionToCaret(bool start) {
2943 // Invalidate old selection; selection is being reset to empty
2944 this.Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2946 selection_start.line = caret.line;
2947 selection_start.tag = caret.tag;
2948 selection_start.pos = caret.pos;
2950 // start always also selects end
2951 selection_end.line = caret.line;
2952 selection_end.tag = caret.tag;
2953 selection_end.pos = caret.pos;
2955 selection_anchor.line = caret.line;
2956 selection_anchor.tag = caret.tag;
2957 selection_anchor.pos = caret.pos;
2959 // Invalidate from previous end to caret (aka new end)
2960 if (selection_end_anchor) {
2961 if (selection_start != caret) {
2962 this.Invalidate(selection_start.line, selection_start.pos, caret.line, caret.pos);
2965 if (selection_end != caret) {
2966 this.Invalidate(selection_end.line, selection_end.pos, caret.line, caret.pos);
2970 if (caret < selection_anchor) {
2971 selection_start.line = caret.line;
2972 selection_start.tag = caret.tag;
2973 selection_start.pos = caret.pos;
2975 selection_end.line = selection_anchor.line;
2976 selection_end.tag = selection_anchor.tag;
2977 selection_end.pos = selection_anchor.pos;
2979 selection_end_anchor = true;
2981 selection_start.line = selection_anchor.line;
2982 selection_start.tag = selection_anchor.tag;
2983 selection_start.pos = selection_anchor.pos;
2985 selection_end.line = caret.line;
2986 selection_end.tag = caret.tag;
2987 selection_end.pos = caret.pos;
2989 selection_end_anchor = false;
2993 SetSelectionVisible (!(selection_start == selection_end));
2996 internal void SetSelection(Line start, int start_pos, Line end, int end_pos) {
2997 if (selection_visible) {
2998 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3001 if ((end.line_no < start.line_no) || ((end == start) && (end_pos <= start_pos))) {
3002 selection_start.line = end;
3003 selection_start.tag = LineTag.FindTag(end, end_pos);
3004 selection_start.pos = end_pos;
3006 selection_end.line = start;
3007 selection_end.tag = LineTag.FindTag(start, start_pos);
3008 selection_end.pos = start_pos;
3010 selection_end_anchor = true;
3012 selection_start.line = start;
3013 selection_start.tag = LineTag.FindTag(start, start_pos);
3014 selection_start.pos = start_pos;
3016 selection_end.line = end;
3017 selection_end.tag = LineTag.FindTag(end, end_pos);
3018 selection_end.pos = end_pos;
3020 selection_end_anchor = false;
3023 selection_anchor.line = start;
3024 selection_anchor.tag = selection_start.tag;
3025 selection_anchor.pos = start_pos;
3027 if (((start == end) && (start_pos == end_pos)) || start == null || end == null) {
3028 SetSelectionVisible (false);
3030 SetSelectionVisible (true);
3031 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3035 internal void SetSelectionStart(Line start, int start_pos, bool invalidate) {
3036 // Invalidate from the previous to the new start pos
3038 Invalidate(selection_start.line, selection_start.pos, start, start_pos);
3040 selection_start.line = start;
3041 selection_start.pos = start_pos;
3042 selection_start.tag = LineTag.FindTag(start, start_pos);
3044 selection_anchor.line = start;
3045 selection_anchor.pos = start_pos;
3046 selection_anchor.tag = selection_start.tag;
3048 selection_end_anchor = false;
3051 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
3052 SetSelectionVisible (true);
3054 SetSelectionVisible (false);
3058 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3061 internal void SetSelectionStart(int character_index, bool invalidate) {
3066 if (character_index < 0) {
3070 CharIndexToLineTag(character_index, out line, out tag, out pos);
3071 SetSelectionStart(line, pos, invalidate);
3074 internal void SetSelectionEnd(Line end, int end_pos, bool invalidate) {
3076 if (end == selection_end.line && end_pos == selection_start.pos) {
3077 selection_anchor.line = selection_start.line;
3078 selection_anchor.tag = selection_start.tag;
3079 selection_anchor.pos = selection_start.pos;
3081 selection_end.line = selection_start.line;
3082 selection_end.tag = selection_start.tag;
3083 selection_end.pos = selection_start.pos;
3085 selection_end_anchor = false;
3086 } else if ((end.line_no < selection_anchor.line.line_no) || ((end == selection_anchor.line) && (end_pos <= selection_anchor.pos))) {
3087 selection_start.line = end;
3088 selection_start.tag = LineTag.FindTag(end, end_pos);
3089 selection_start.pos = end_pos;
3091 selection_end.line = selection_anchor.line;
3092 selection_end.tag = selection_anchor.tag;
3093 selection_end.pos = selection_anchor.pos;
3095 selection_end_anchor = true;
3097 selection_start.line = selection_anchor.line;
3098 selection_start.tag = selection_anchor.tag;
3099 selection_start.pos = selection_anchor.pos;
3101 selection_end.line = end;
3102 selection_end.tag = LineTag.FindTag(end, end_pos);
3103 selection_end.pos = end_pos;
3105 selection_end_anchor = false;
3108 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
3109 SetSelectionVisible (true);
3111 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3113 SetSelectionVisible (false);
3114 // ?? Do I need to invalidate here, tests seem to work without it, but I don't think they should :-s
3118 internal void SetSelectionEnd(int character_index, bool invalidate) {
3123 if (character_index < 0) {
3127 CharIndexToLineTag(character_index, out line, out tag, out pos);
3128 SetSelectionEnd(line, pos, invalidate);
3131 internal void SetSelection(Line start, int start_pos) {
3132 if (selection_visible) {
3133 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3136 selection_start.line = start;
3137 selection_start.pos = start_pos;
3138 selection_start.tag = LineTag.FindTag(start, start_pos);
3140 selection_end.line = start;
3141 selection_end.tag = selection_start.tag;
3142 selection_end.pos = start_pos;
3144 selection_anchor.line = start;
3145 selection_anchor.tag = selection_start.tag;
3146 selection_anchor.pos = start_pos;
3148 selection_end_anchor = false;
3149 SetSelectionVisible (false);
3152 internal void InvalidateSelectionArea() {
3153 Invalidate (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3156 // Return the current selection, as string
3157 internal string GetSelection() {
3158 // We return String.Empty if there is no selection
3159 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3160 return string.Empty;
3163 if (selection_start.line == selection_end.line) {
3164 return selection_start.line.text.ToString (selection_start.pos, selection_end.pos - selection_start.pos);
3171 sb = new StringBuilder();
3172 start = selection_start.line.line_no;
3173 end = selection_end.line.line_no;
3175 sb.Append(selection_start.line.text.ToString(selection_start.pos, selection_start.line.text.Length - selection_start.pos));
3177 if ((start + 1) < end) {
3178 for (i = start + 1; i < end; i++) {
3179 sb.Append(GetLine(i).text.ToString());
3183 sb.Append(selection_end.line.text.ToString(0, selection_end.pos));
3185 return sb.ToString();
3189 internal void ReplaceSelection(string s, bool select_new) {
3192 int selection_start_pos = LineTagToCharIndex (selection_start.line, selection_start.pos);
3195 // First, delete any selected text
3196 if ((selection_start.pos != selection_end.pos) || (selection_start.line != selection_end.line)) {
3197 if (selection_start.line == selection_end.line) {
3198 undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3200 DeleteChars (selection_start.line, selection_start.pos, selection_end.pos - selection_start.pos);
3202 // The tag might have been removed, we need to recalc it
3203 selection_start.tag = selection_start.line.FindTag(selection_start.pos + 1);
3208 start = selection_start.line.line_no;
3209 end = selection_end.line.line_no;
3211 undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3213 InvalidateLinesAfter(selection_start.line);
3215 // Delete first line
3216 DeleteChars (selection_start.line, selection_start.pos, selection_start.line.text.Length - selection_start.pos);
3217 selection_start.line.recalc = true;
3220 DeleteChars(selection_end.line, 0, selection_end.pos);
3224 for (i = end - 1; i >= start; i--) {
3229 // BIG FAT WARNING - selection_end.line might be stale due
3230 // to the above Delete() call. DONT USE IT before hitting the end of this method!
3232 // Join start and end
3233 Combine(selection_start.line.line_no, start);
3238 Insert(selection_start.line, selection_start.pos, false, s);
3239 undo.RecordInsertString (selection_start.line, selection_start.pos, s);
3240 ResumeRecalc (false);
3242 Line begin_update_line = selection_start.line;
3243 int begin_update_pos = selection_start.pos;
3246 CharIndexToLineTag(selection_start_pos + s.Length, out selection_start.line,
3247 out selection_start.tag, out selection_start.pos);
3249 selection_end.line = selection_start.line;
3250 selection_end.pos = selection_start.pos;
3251 selection_end.tag = selection_start.tag;
3252 selection_anchor.line = selection_start.line;
3253 selection_anchor.pos = selection_start.pos;
3254 selection_anchor.tag = selection_start.tag;
3256 SetSelectionVisible (false);
3258 CharIndexToLineTag(selection_start_pos, out selection_start.line,
3259 out selection_start.tag, out selection_start.pos);
3261 CharIndexToLineTag(selection_start_pos + s.Length, out selection_end.line,
3262 out selection_end.tag, out selection_end.pos);
3264 selection_anchor.line = selection_start.line;
3265 selection_anchor.pos = selection_start.pos;
3266 selection_anchor.tag = selection_start.tag;
3268 SetSelectionVisible (true);
3271 PositionCaret (selection_start.line, selection_start.pos);
3272 UpdateView (begin_update_line, selection_end.line.line_no - begin_update_line.line_no, begin_update_pos);
3275 internal void CharIndexToLineTag(int index, out Line line_out, out LineTag tag_out, out int pos) {
3284 for (i = 1; i <= lines; i++) {
3288 chars += line.text.Length;
3290 if (index <= chars) {
3291 // we found the line
3294 while (tag != null) {
3295 if (index < (start + tag.Start + tag.Length - 1)) {
3297 tag_out = LineTag.GetFinalTag (tag);
3298 pos = index - start;
3301 if (tag.Next == null) {
3304 next_line = GetLine(line.line_no + 1);
3306 if (next_line != null) {
3307 line_out = next_line;
3308 tag_out = LineTag.GetFinalTag (next_line.tags);
3313 tag_out = LineTag.GetFinalTag (tag);
3314 pos = line_out.text.Length;
3323 line_out = GetLine(lines);
3324 tag = line_out.tags;
3325 while (tag.Next != null) {
3329 pos = line_out.text.Length;
3332 internal int LineTagToCharIndex(Line line, int pos) {
3336 // Count first and last line
3339 // Count the lines in the middle
3341 for (i = 1; i < line.line_no; i++) {
3342 length += GetLine(i).text.Length;
3350 internal int SelectionLength() {
3351 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3355 if (selection_start.line == selection_end.line) {
3356 return selection_end.pos - selection_start.pos;
3363 // Count first and last line
3364 length = selection_start.line.text.Length - selection_start.pos + selection_end.pos + crlf_size;
3366 // Count the lines in the middle
3367 start = selection_start.line.line_no + 1;
3368 end = selection_end.line.line_no;
3371 for (i = start; i < end; i++) {
3372 Line line = GetLine (i);
3373 length += line.text.Length + LineEndingLength (line.ending);
3384 // UIA: Method used via reflection in TextRangeProvider
3386 /// <summary>Give it a Line number and it returns the Line object at with that line number</summary>
3387 internal Line GetLine(int LineNo) {
3388 Line line = document;
3390 while (line != sentinel) {
3391 if (LineNo == line.line_no) {
3393 } else if (LineNo < line.line_no) {
3403 /// <summary>Retrieve the previous tag; walks line boundaries</summary>
3404 internal LineTag PreviousTag(LineTag tag) {
3407 if (tag.Previous != null) {
3408 return tag.Previous;
3412 if (tag.Line.line_no == 1) {
3416 l = GetLine(tag.Line.line_no - 1);
3421 while (t.Next != null) {
3430 /// <summary>Retrieve the next tag; walks line boundaries</summary>
3431 internal LineTag NextTag(LineTag tag) {
3434 if (tag.Next != null) {
3439 l = GetLine(tag.Line.line_no + 1);
3447 internal Line ParagraphStart(Line line) {
3448 Line lastline = line;
3450 if (line.line_no <= 1)
3454 lastline = GetLine (line.line_no - 1);
3455 } while (lastline.ending == LineEnding.Wrap);
3460 internal Line ParagraphEnd(Line line) {
3463 while (line.ending == LineEnding.Wrap) {
3464 l = GetLine(line.line_no + 1);
3465 if ((l == null) || (l.ending != LineEnding.Wrap)) {
3473 /// <summary>Give it a pixel offset coordinate and it returns the Line covering that are (offset
3474 /// is either X or Y depending on if we are multiline
3476 internal Line GetLineByPixel (int offset, bool exact)
3478 Line line = document;
3482 while (line != sentinel) {
3484 if ((offset >= line.Y) && (offset < (line.Y+line.height))) {
3486 } else if (offset < line.Y) {
3493 while (line != sentinel) {
3495 if ((offset >= line.X) && (offset < (line.X + line.Width)))
3497 else if (offset < line.X)
3510 // UIA: Method used via reflection in TextProviderBehavior
3512 // Give it x/y pixel coordinates and it returns the Tag at that position
3513 internal LineTag FindCursor (int x, int y, out int index)
3520 line = GetLineByPixel (multiline ? y : x, false);
3522 LineTag tag = line.GetTag (x);
3524 if (tag.Length == 0 && tag.Start == 1)
3527 index = tag.GetCharIndex (x - line.align_shift);
3532 /// <summary>Format area of document in specified font and color</summary>
3533 /// <param name="start_pos">1-based start position on start_line</param>
3534 /// <param name="end_pos">1-based end position on end_line </param>
3535 internal void FormatText (Line start_line, int start_pos, Line end_line, int end_pos, Font font,
3536 Color color, Color back_color, FormatSpecified specified)
3540 // First, format the first line
3541 if (start_line != end_line) {
3543 LineTag.FormatText(start_line, start_pos, start_line.text.Length - start_pos + 1, font, color, back_color, specified);
3546 LineTag.FormatText(end_line, 1, end_pos, font, color, back_color, specified);
3548 // Now all the lines inbetween
3549 for (int i = start_line.line_no + 1; i < end_line.line_no; i++) {
3551 LineTag.FormatText(l, 1, l.text.Length, font, color, back_color, specified);
3554 // Special case, single line
3555 LineTag.FormatText(start_line, start_pos, end_pos - start_pos, font, color, back_color, specified);
3557 if ((end_pos - start_pos) == 0 && CaretTag.Length != 0)
3558 CaretTag = CaretTag.Next;
3562 internal void RecalculateAlignments ()
3571 while (line_no <= lines) {
3572 line = GetLine(line_no);
3575 switch (line.alignment) {
3576 case HorizontalAlignment.Left:
3577 line.align_shift = 0;
3579 case HorizontalAlignment.Center:
3580 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3582 case HorizontalAlignment.Right:
3583 line.align_shift = viewport_width - (int)line.widths[line.text.Length] - right_margin;
3593 /// <summary>Calculate formatting for the whole document</summary>
3594 internal bool RecalculateDocument(Graphics g) {
3595 return RecalculateDocument(g, 1, this.lines, false);
3598 /// <summary>Calculate formatting starting at a certain line</summary>
3599 internal bool RecalculateDocument(Graphics g, int start) {
3600 return RecalculateDocument(g, start, this.lines, false);
3603 /// <summary>Calculate formatting within two given line numbers</summary>
3604 internal bool RecalculateDocument(Graphics g, int start, int end) {
3605 return RecalculateDocument(g, start, end, false);
3608 /// <summary>With optimize on, returns true if line heights changed</summary>
3609 internal bool RecalculateDocument(Graphics g, int start, int end, bool optimize) {
3617 if (recalc_suspended > 0) {
3618 recalc_pending = true;
3619 recalc_start = Math.Min (recalc_start, start);
3620 recalc_end = Math.Max (recalc_end, end);
3621 recalc_optimize = optimize;
3625 // Fixup the positions, they can go kinda nuts
3626 // (this is suspend and resume recalc - they set them to 1 and max)
3627 start = Math.Max (start, 1);
3628 end = Math.Min (end, lines);
3630 offset = GetLine(start).offset;
3635 changed = true; // We always return true if we run non-optimized
3640 while (line_no <= (end + this.lines - shift)) {
3641 line = GetLine(line_no++);
3642 line.offset = offset;
3644 // if we are not calculating a password
3647 line.RecalculateLine(g, this);
3649 if (line.recalc && line.RecalculateLine(g, this)) {
3651 // If the height changed, all subsequent lines change
3658 line.RecalculatePasswordLine(g, this);
3660 if (line.recalc && line.RecalculatePasswordLine(g, this)) {
3662 // If the height changed, all subsequent lines change
3669 if (line.widths[line.text.Length] > new_width) {
3670 new_width = (int)line.widths[line.text.Length];
3673 // Calculate alignment
3674 if (line.alignment != HorizontalAlignment.Left) {
3675 if (line.alignment == HorizontalAlignment.Center) {
3676 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3678 line.align_shift = viewport_width - (int)line.widths[line.text.Length] - 1;
3683 offset += line.height;
3685 offset += (int) line.widths [line.text.Length];
3687 if (line_no > lines) {
3692 if (document_x != new_width) {
3693 document_x = new_width;
3694 if (WidthChanged != null) {
3695 WidthChanged(this, null);
3699 RecalculateAlignments();
3701 line = GetLine(lines);
3703 if (document_y != line.Y + line.height) {
3704 document_y = line.Y + line.height;
3705 if (HeightChanged != null) {
3706 HeightChanged(this, null);
3710 // scan for links and tell us if its all
3711 // changed, so we can update everything
3713 ScanForLinks (start, end, ref changed);
3719 internal int Size() {
3723 private void owner_HandleCreated(object sender, EventArgs e) {
3724 RecalculateDocument(owner.CreateGraphicsInternal());
3728 private void owner_VisibleChanged(object sender, EventArgs e) {
3729 if (owner.Visible) {
3730 RecalculateDocument(owner.CreateGraphicsInternal());
3734 internal static bool IsWordSeparator (char ch)
3749 internal int FindWordSeparator(Line line, int pos, bool forward) {
3752 len = line.text.Length;
3755 for (int i = pos + 1; i < len; i++) {
3756 if (IsWordSeparator(line.Text[i])) {
3762 for (int i = pos - 1; i > 0; i--) {
3763 if (IsWordSeparator(line.Text[i - 1])) {
3771 /* Search document for text */
3772 internal bool FindChars(char[] chars, Marker start, Marker end, out Marker result) {
3778 // Search for occurence of any char in the chars array
3779 result = new Marker();
3782 line_no = start.line.line_no;
3784 while (line_no <= end.line.line_no) {
3785 line_len = line.text.Length;
3786 while (pos < line_len) {
3787 for (int i = 0; i < chars.Length; i++) {
3788 if (line.text[pos] == chars[i]) {
3790 if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
3804 line = GetLine(line_no);
3810 // This version does not build one big string for searching, instead it handles
3811 // line-boundaries, which is faster and less memory intensive
3812 // FIXME - Depending on culture stuff we might have to create a big string and use culturespecific
3813 // search stuff and change it to accept and return positions instead of Markers (which would match
3814 // RichTextBox behaviour better but would be inconsistent with the rest of TextControl)
3815 internal bool Find(string search, Marker start, Marker end, out Marker result, RichTextBoxFinds options) {
3817 string search_string;
3829 result = new Marker();
3830 word_option = ((options & RichTextBoxFinds.WholeWord) != 0);
3831 ignore_case = ((options & RichTextBoxFinds.MatchCase) == 0);
3832 reverse = ((options & RichTextBoxFinds.Reverse) != 0);
3835 line_no = start.line.line_no;
3839 // Prep our search string, lowercasing it if we do case-independent matching
3842 sb = new StringBuilder(search);
3843 for (int i = 0; i < sb.Length; i++) {
3844 sb[i] = Char.ToLower(sb[i]);
3846 search_string = sb.ToString();
3848 search_string = search;
3851 // We need to check if the character before our start position is a wordbreak
3854 if ((pos == 0) || (IsWordSeparator(line.text[pos - 1]))) {
3861 if (IsWordSeparator(line.text[pos - 1])) {
3867 // Need to check the end of the previous line
3870 prev_line = GetLine(line_no - 1);
3871 if (prev_line.ending == LineEnding.Wrap) {
3872 if (IsWordSeparator(prev_line.text[prev_line.text.Length - 1])) {
3886 // To avoid duplication of this loop with reverse logic, we search
3887 // through the document, remembering the last match and when returning
3888 // report that last remembered match
3890 last = new Marker();
3891 last.height = -1; // Abused - we use it to track change
3893 while (line_no <= end.line.line_no) {
3894 if (line_no != end.line.line_no) {
3895 line_len = line.text.Length;
3900 while (pos < line_len) {
3902 if (word_option && (current == search_string.Length)) {
3903 if (IsWordSeparator(line.text[pos])) {
3916 c = Char.ToLower(line.text[pos]);
3921 if (c == search_string[current]) {
3927 if (!word_option || (word_option && (word || (current > 0)))) {
3931 if (!word_option && (current == search_string.Length)) {
3948 if (IsWordSeparator(c)) {
3956 // Mark that we just saw a word boundary
3957 if (line.ending != LineEnding.Wrap || line.line_no == lines - 1) {
3961 if (current == search_string.Length) {
3977 line = GetLine(line_no);
3981 if (last.height != -1) {
3991 // if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
4003 internal void GetMarker(out Marker mark, bool start) {
4004 mark = new Marker();
4007 mark.line = GetLine(1);
4008 mark.tag = mark.line.tags;
4011 mark.line = GetLine(lines);
4012 mark.tag = mark.line.tags;
4013 while (mark.tag.Next != null) {
4014 mark.tag = mark.tag.Next;
4016 mark.pos = mark.line.text.Length;
4019 #endregion // Internal Methods
4022 internal event EventHandler CaretMoved;
4023 internal event EventHandler WidthChanged;
4024 internal event EventHandler HeightChanged;
4025 internal event EventHandler LengthChanged;
4026 internal event EventHandler UIASelectionChanged;
4027 #endregion // Events
4029 #region Administrative
4030 public IEnumerator GetEnumerator() {
4035 public override bool Equals(object obj) {
4040 if (!(obj is Document)) {
4048 if (ToString().Equals(((Document)obj).ToString())) {
4055 public override int GetHashCode() {
4059 public override string ToString() {
4060 return "document " + this.document_id;
4062 #endregion // Administrative
4065 internal class PictureTag : LineTag {
4067 internal RTF.Picture picture;
4069 internal PictureTag (Line line, int start, RTF.Picture picture) : base (line, start)
4071 this.picture = picture;
4074 public override bool IsTextTag {
4075 get { return false; }
4078 public override SizeF SizeOfPosition (Graphics dc, int pos)
4080 return picture.Size;
4083 internal override int MaxHeight ()
4085 return (int) (picture.Height + 0.5F);
4088 public override void Draw (Graphics dc, Color color, float xoff, float y, int start, int end)
4090 picture.DrawImage (dc, xoff + Line.widths [start], y, false);
4093 public override void Draw (Graphics dc, Color color, float xoff, float y, int start, int end, string text)
4095 picture.DrawImage (dc, xoff + + Line.widths [start], y, false);
4098 public override string Text ()
4104 internal class UndoManager {
4106 internal enum ActionType {
4110 // This is basically just cut & paste
4118 internal class Action {
4119 internal ActionType type;
4120 internal int line_no;
4122 internal object data;
4125 #region Local Variables
4126 private Document document;
4127 private Stack undo_actions;
4128 private Stack redo_actions;
4130 //private int caret_line;
4131 //private int caret_pos;
4133 // When performing an action, we lock the queue, so that the action can't be undone
4134 private bool locked;
4135 #endregion // Local Variables
4137 #region Constructors
4138 internal UndoManager (Document document)
4140 this.document = document;
4141 undo_actions = new Stack (50);
4142 redo_actions = new Stack (50);
4144 #endregion // Constructors
4147 internal bool CanUndo {
4148 get { return undo_actions.Count > 0; }
4151 internal bool CanRedo {
4152 get { return redo_actions.Count > 0; }
4155 internal string UndoActionName {
4157 foreach (Action action in undo_actions) {
4158 if (action.type == ActionType.UserActionBegin)
4159 return (string) action.data;
4160 if (action.type == ActionType.Typing)
4161 return Locale.GetText ("Typing");
4163 return String.Empty;
4167 internal string RedoActionName {
4169 foreach (Action action in redo_actions) {
4170 if (action.type == ActionType.UserActionBegin)
4171 return (string) action.data;
4172 if (action.type == ActionType.Typing)
4173 return Locale.GetText ("Typing");
4175 return String.Empty;
4178 #endregion // Properties
4180 #region Internal Methods
4181 internal void Clear ()
4183 undo_actions.Clear();
4184 redo_actions.Clear();
4187 internal bool Undo ()
4190 bool user_action_finished = false;
4192 if (undo_actions.Count == 0)
4198 action = (Action) undo_actions.Pop ();
4200 // Put onto redo stack
4201 redo_actions.Push(action);
4204 switch(action.type) {
4206 case ActionType.UserActionBegin:
4207 user_action_finished = true;
4210 case ActionType.UserActionEnd:
4214 case ActionType.InsertString:
4215 start = document.GetLine (action.line_no);
4216 document.SuspendUpdate ();
4217 document.DeleteMultiline (start, action.pos, ((string) action.data).Length + 1);
4218 document.PositionCaret (start, action.pos);
4219 document.SetSelectionToCaret (true);
4220 document.ResumeUpdate (true);
4223 case ActionType.Typing:
4224 start = document.GetLine (action.line_no);
4225 document.SuspendUpdate ();
4226 document.DeleteMultiline (start, action.pos, ((StringBuilder) action.data).Length);
4227 document.PositionCaret (start, action.pos);
4228 document.SetSelectionToCaret (true);
4229 document.ResumeUpdate (true);
4231 // This is an open ended operation, so only a single typing operation can be undone at once
4232 user_action_finished = true;
4235 case ActionType.DeleteString:
4236 start = document.GetLine (action.line_no);
4237 document.SuspendUpdate ();
4238 Insert (start, action.pos, (Line) action.data, true);
4239 document.ResumeUpdate (true);
4242 } while (!user_action_finished && undo_actions.Count > 0);
4249 internal bool Redo ()
4252 bool user_action_finished = false;
4254 if (redo_actions.Count == 0)
4262 action = (Action) redo_actions.Pop ();
4263 undo_actions.Push (action);
4265 switch (action.type) {
4267 case ActionType.UserActionBegin:
4271 case ActionType.UserActionEnd:
4272 user_action_finished = true;
4275 case ActionType.InsertString:
4276 start = document.GetLine (action.line_no);
4277 document.SuspendUpdate ();
4278 start_index = document.LineTagToCharIndex (start, action.pos);
4279 document.InsertString (start, action.pos, (string) action.data);
4280 document.CharIndexToLineTag (start_index + ((string) action.data).Length,
4281 out document.caret.line, out document.caret.tag,
4282 out document.caret.pos);
4283 document.UpdateCaret ();
4284 document.SetSelectionToCaret (true);
4285 document.ResumeUpdate (true);
4288 case ActionType.Typing:
4289 start = document.GetLine (action.line_no);
4290 document.SuspendUpdate ();
4291 start_index = document.LineTagToCharIndex (start, action.pos);
4292 document.InsertString (start, action.pos, ((StringBuilder) action.data).ToString ());
4293 document.CharIndexToLineTag (start_index + ((StringBuilder) action.data).Length,
4294 out document.caret.line, out document.caret.tag,
4295 out document.caret.pos);
4296 document.UpdateCaret ();
4297 document.SetSelectionToCaret (true);
4298 document.ResumeUpdate (true);
4300 // This is an open ended operation, so only a single typing operation can be undone at once
4301 user_action_finished = true;
4304 case ActionType.DeleteString:
4305 start = document.GetLine (action.line_no);
4306 document.SuspendUpdate ();
4307 document.DeleteMultiline (start, action.pos, ((Line) action.data).text.Length);
4308 document.PositionCaret (start, action.pos);
4309 document.SetSelectionToCaret (true);
4310 document.ResumeUpdate (true);
4314 } while (!user_action_finished && redo_actions.Count > 0);
4320 #endregion // Internal Methods
4322 #region Private Methods
4324 public void BeginUserAction (string name)
4329 // Nuke the redo queue
4330 redo_actions.Clear ();
4332 Action ua = new Action ();
4333 ua.type = ActionType.UserActionBegin;
4336 undo_actions.Push (ua);
4339 public void EndUserAction ()
4344 Action ua = new Action ();
4345 ua.type = ActionType.UserActionEnd;
4347 undo_actions.Push (ua);
4350 // start_pos, end_pos = 1 based
4351 public void RecordDeleteString (Line start_line, int start_pos, Line end_line, int end_pos)
4356 // Nuke the redo queue
4357 redo_actions.Clear ();
4359 Action a = new Action ();
4361 // We cant simply store the string, because then formatting would be lost
4362 a.type = ActionType.DeleteString;
4363 a.line_no = start_line.line_no;
4365 a.data = Duplicate (start_line, start_pos, end_line, end_pos);
4367 undo_actions.Push(a);
4370 public void RecordInsertString (Line line, int pos, string str)
4372 if (locked || str.Length == 0)
4375 // Nuke the redo queue
4376 redo_actions.Clear ();
4378 Action a = new Action ();
4380 a.type = ActionType.InsertString;
4382 a.line_no = line.line_no;
4385 undo_actions.Push (a);
4388 public void RecordTyping (Line line, int pos, char ch)
4393 // Nuke the redo queue
4394 redo_actions.Clear ();
4398 if (undo_actions.Count > 0)
4399 a = (Action) undo_actions.Peek ();
4401 if (a == null || a.type != ActionType.Typing) {
4403 a.type = ActionType.Typing;
4404 a.data = new StringBuilder ();
4405 a.line_no = line.line_no;
4408 undo_actions.Push (a);
4411 StringBuilder data = (StringBuilder) a.data;
4415 // start_pos = 1-based
4416 // end_pos = 1-based
4417 public Line Duplicate(Line start_line, int start_pos, Line end_line, int end_pos)
4423 LineTag current_tag;
4428 line = new Line (start_line.document, start_line.ending);
4431 for (int i = start_line.line_no; i <= end_line.line_no; i++) {
4432 current = document.GetLine(i);
4434 if (start_line.line_no == i) {
4440 if (end_line.line_no == i) {
4443 end = current.text.Length;
4450 line.text = new StringBuilder (current.text.ToString (start, end - start));
4452 // Copy tags from start to start+length onto new line
4453 current_tag = current.FindTag (start + 1);
4454 while ((current_tag != null) && (current_tag.Start <= end)) {
4455 if ((current_tag.Start <= start) && (start < (current_tag.Start + current_tag.Length))) {
4456 // start tag is within this tag
4459 tag_start = current_tag.Start;
4462 tag = new LineTag(line, tag_start - start + 1);
4463 tag.CopyFormattingFrom (current_tag);
4465 current_tag = current_tag.Next;
4467 // Add the new tag to the line
4468 if (line.tags == null) {
4474 while (tail.Next != null) {
4478 tag.Previous = tail;
4482 if ((i + 1) <= end_line.line_no) {
4483 line.ending = current.ending;
4485 // Chain them (we use right/left as next/previous)
4486 line.right = new Line (start_line.document, start_line.ending);
4487 line.right.left = line;
4495 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
4496 internal void Insert(Line line, int pos, Line insert, bool select)
4504 // Handle special case first
4505 if (insert.right == null) {
4507 // Single line insert
4508 document.Split(line, pos);
4510 if (insert.tags == null) {
4511 return; // Blank line
4514 //Insert our tags at the end
4517 while (tag.Next != null) {
4521 offset = tag.Start + tag.Length - 1;
4523 tag.Next = insert.tags;
4524 line.text.Insert(offset, insert.text.ToString());
4526 // Adjust start locations
4528 while (tag != null) {
4529 tag.Start += offset;
4533 // Put it back together
4534 document.Combine(line.line_no, line.line_no + 1);
4537 document.SetSelectionStart (line, pos, false);
4538 document.SetSelectionEnd (line, pos + insert.text.Length, false);
4541 document.UpdateView(line, pos);
4549 while (current != null) {
4551 if (current == insert) {
4552 // Inserting the first line we split the line (and make space)
4553 document.Split(line.line_no, pos);
4554 //Insert our tags at the end of the line
4558 if (tag != null && tag.Length != 0) {
4559 while (tag.Next != null) {
4562 offset = tag.Start + tag.Length - 1;
4563 tag.Next = current.tags;
4564 tag.Next.Previous = tag;
4570 line.tags = current.tags;
4571 line.tags.Previous = null;
4575 line.ending = current.ending;
4577 document.Split(line.line_no, 0);
4579 line.tags = current.tags;
4580 line.tags.Previous = null;
4581 line.ending = current.ending;
4585 // Adjust start locations and line pointers
4586 while (tag != null) {
4587 tag.Start += offset - 1;
4592 line.text.Insert(offset, current.text.ToString());
4593 line.Grow(line.text.Length);
4596 line = document.GetLine(line.line_no + 1);
4598 // FIXME? Test undo of line-boundaries
4599 if ((current.right == null) && (current.tags.Length != 0)) {
4600 document.Combine(line.line_no - 1, line.line_no);
4602 current = current.right;
4607 // Recalculate our document
4608 document.UpdateView(first, lines, pos);
4611 #endregion // Private Methods