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 selection_visible = value;
664 // cursor and selection are enemies, we can't have both in the same room at the same time
665 if (owner.IsHandleCreated && !owner.show_caret_w_selection)
666 XplatUI.CaretVisible (owner.Handle, !selection_visible);
669 private void DecrementLines(int line_no) {
673 while (current <= lines) {
674 GetLine(current).line_no--;
680 private void IncrementLines(int line_no) {
683 current = this.lines;
684 while (current >= line_no) {
685 GetLine(current).line_no++;
691 private void RebalanceAfterAdd(Line line1) {
694 while ((line1 != document) && (line1.parent.color == LineColor.Red)) {
695 if (line1.parent == line1.parent.parent.left) {
696 line2 = line1.parent.parent.right;
698 if ((line2 != null) && (line2.color == LineColor.Red)) {
699 line1.parent.color = LineColor.Black;
700 line2.color = LineColor.Black;
701 line1.parent.parent.color = LineColor.Red;
702 line1 = line1.parent.parent;
704 if (line1 == line1.parent.right) {
705 line1 = line1.parent;
709 line1.parent.color = LineColor.Black;
710 line1.parent.parent.color = LineColor.Red;
712 RotateRight(line1.parent.parent);
715 line2 = line1.parent.parent.left;
717 if ((line2 != null) && (line2.color == LineColor.Red)) {
718 line1.parent.color = LineColor.Black;
719 line2.color = LineColor.Black;
720 line1.parent.parent.color = LineColor.Red;
721 line1 = line1.parent.parent;
723 if (line1 == line1.parent.left) {
724 line1 = line1.parent;
728 line1.parent.color = LineColor.Black;
729 line1.parent.parent.color = LineColor.Red;
730 RotateLeft(line1.parent.parent);
734 document.color = LineColor.Black;
737 private void RebalanceAfterDelete(Line line1) {
740 while ((line1 != document) && (line1.color == LineColor.Black)) {
741 if (line1 == line1.parent.left) {
742 line2 = line1.parent.right;
743 if (line2.color == LineColor.Red) {
744 line2.color = LineColor.Black;
745 line1.parent.color = LineColor.Red;
746 RotateLeft(line1.parent);
747 line2 = line1.parent.right;
749 if ((line2.left.color == LineColor.Black) && (line2.right.color == LineColor.Black)) {
750 line2.color = LineColor.Red;
751 line1 = line1.parent;
753 if (line2.right.color == LineColor.Black) {
754 line2.left.color = LineColor.Black;
755 line2.color = LineColor.Red;
757 line2 = line1.parent.right;
759 line2.color = line1.parent.color;
760 line1.parent.color = LineColor.Black;
761 line2.right.color = LineColor.Black;
762 RotateLeft(line1.parent);
766 line2 = line1.parent.left;
767 if (line2.color == LineColor.Red) {
768 line2.color = LineColor.Black;
769 line1.parent.color = LineColor.Red;
770 RotateRight(line1.parent);
771 line2 = line1.parent.left;
773 if ((line2.right.color == LineColor.Black) && (line2.left.color == LineColor.Black)) {
774 line2.color = LineColor.Red;
775 line1 = line1.parent;
777 if (line2.left.color == LineColor.Black) {
778 line2.right.color = LineColor.Black;
779 line2.color = LineColor.Red;
781 line2 = line1.parent.left;
783 line2.color = line1.parent.color;
784 line1.parent.color = LineColor.Black;
785 line2.left.color = LineColor.Black;
786 RotateRight(line1.parent);
791 line1.color = LineColor.Black;
794 private void RotateLeft(Line line1) {
795 Line line2 = line1.right;
797 line1.right = line2.left;
799 if (line2.left != sentinel) {
800 line2.left.parent = line1;
803 if (line2 != sentinel) {
804 line2.parent = line1.parent;
807 if (line1.parent != null) {
808 if (line1 == line1.parent.left) {
809 line1.parent.left = line2;
811 line1.parent.right = line2;
818 if (line1 != sentinel) {
819 line1.parent = line2;
823 private void RotateRight(Line line1) {
824 Line line2 = line1.left;
826 line1.left = line2.right;
828 if (line2.right != sentinel) {
829 line2.right.parent = line1;
832 if (line2 != sentinel) {
833 line2.parent = line1.parent;
836 if (line1.parent != null) {
837 if (line1 == line1.parent.right) {
838 line1.parent.right = line2;
840 line1.parent.left = line2;
847 if (line1 != sentinel) {
848 line1.parent = line2;
853 internal void UpdateView(Line line, int pos) {
854 if (!owner.IsHandleCreated) {
858 if (update_suspended > 0) {
859 update_start = Math.Min (update_start, line.line_no);
860 // update_end = Math.Max (update_end, line.line_no);
861 // recalc_optimize = true;
862 update_pending = true;
866 // Optimize invalidation based on Line alignment
867 if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no, true)) {
868 // Lineheight changed, invalidate the rest of the document
869 if ((line.Y - viewport_y) >=0 ) {
870 // We formatted something that's in view, only draw parts of the screen
871 owner.Invalidate(new Rectangle(
873 line.Y - viewport_y + offset_y,
875 owner.Height - (line.Y - viewport_y)));
877 // The tag was above the visible area, draw everything
881 switch(line.alignment) {
882 case HorizontalAlignment.Left: {
883 owner.Invalidate(new Rectangle(
884 line.X + ((int)line.widths[pos] - viewport_x - 1) + offset_x,
885 line.Y - viewport_y + offset_y,
891 case HorizontalAlignment.Center: {
892 owner.Invalidate(new Rectangle(
894 line.Y - viewport_y + offset_y,
900 case HorizontalAlignment.Right: {
901 owner.Invalidate(new Rectangle(
903 line.Y - viewport_y + offset_y,
904 (int)line.widths[pos + 1] - viewport_x + line.X,
913 // Update display from line, down line_count lines; pos is unused, but required for the signature
914 internal void UpdateView(Line line, int line_count, int pos) {
915 if (!owner.IsHandleCreated) {
919 if (recalc_suspended > 0) {
920 recalc_start = Math.Min (recalc_start, line.line_no);
921 recalc_end = Math.Max (recalc_end, line.line_no + line_count);
922 recalc_optimize = true;
923 recalc_pending = true;
927 int start_line_top = line.Y;
932 end_line = GetLine (line.line_no + line_count);
933 if (end_line == null)
934 end_line = GetLine (lines);
937 end_line_bottom = end_line.Y + end_line.height;
939 if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no + line_count, true)) {
940 // Lineheight changed, invalidate the rest of the document
941 if ((line.Y - viewport_y) >=0 ) {
942 // We formatted something that's in view, only draw parts of the screen
943 owner.Invalidate(new Rectangle(
945 line.Y - viewport_y + offset_y,
947 owner.Height - (line.Y - viewport_y)));
949 // The tag was above the visible area, draw everything
953 int x = 0 - viewport_x + offset_x;
954 int w = viewport_width;
955 int y = Math.Min (start_line_top - viewport_y, line.Y - viewport_y) + offset_y;
956 int h = Math.Max (end_line_bottom - y, end_line.Y + end_line.height - y);
958 owner.Invalidate (new Rectangle (x, y, w, h));
963 /// Scans the next paragraph for http:/ ftp:/ www. https:/ etc and marks the tags
966 /// <param name="start_line">The line to start on</param>
967 /// <param name="link_changed">marks as true if something is changed</param>
968 private void ScanForLinks (Line start_line, ref bool link_changed)
970 Line current_line = start_line;
971 StringBuilder line_no_breaks = new StringBuilder ();
972 StringBuilder line_link_record = new StringBuilder ();
973 ArrayList cumulative_length_list = new ArrayList ();
974 bool update_caret_tag = false;
976 cumulative_length_list.Add (0);
978 while (current_line != null) {
979 line_no_breaks.Append (current_line.text);
981 if (link_changed == false)
982 current_line.LinkRecord (line_link_record);
984 current_line.ClearLinks ();
986 cumulative_length_list.Add (line_no_breaks.Length);
988 if (current_line.ending == LineEnding.Wrap)
989 current_line = GetLine (current_line.LineNo + 1);
994 // search for protocols.. make sure www. is first!
995 string [] search_terms = new string [] { "www.", "http:/", "ftp:/", "https:/" };
996 int search_found = 0;
998 string line_no_breaks_string = line_no_breaks.ToString ();
999 int line_no_breaks_index = 0;
1003 if (line_no_breaks_index >= line_no_breaks_string.Length)
1006 index_found = FirstIndexOfAny (line_no_breaks_string, search_terms, line_no_breaks_index, out search_found);
1008 //no links found on this line
1009 if (index_found == -1)
1012 if (search_found == 0) {
1013 // if we are at the end of the line to analyse and the end of the line
1014 // is "www." then there are no links here
1015 if (line_no_breaks_string.Length == index_found + search_terms [0].Length)
1018 // if after www. we don't have a letter a digit or a @ or - or /
1019 // then it is not a web address, we should continue searching
1020 if (char.IsLetterOrDigit (line_no_breaks_string [index_found + search_terms [0].Length]) == false &&
1021 "@/~".IndexOf (line_no_breaks_string [index_found + search_terms [0].Length].ToString ()) == -1) {
1022 line_no_breaks_index = index_found + search_terms [0].Length;
1027 link_end = line_no_breaks_string.Length - 1;
1028 line_no_breaks_index = line_no_breaks_string.Length;
1030 // we've found a link, we just need to find where it ends now
1031 for (int i = index_found + search_terms [search_found].Length; i < line_no_breaks_string.Length; i++) {
1032 if (line_no_breaks_string [i - 1] == '.') {
1033 if (char.IsLetterOrDigit (line_no_breaks_string [i]) == false &&
1034 "@/~".IndexOf (line_no_breaks_string [i].ToString ()) == -1) {
1036 line_no_breaks_index = i;
1040 if (char.IsLetterOrDigit (line_no_breaks_string [i]) == false &&
1041 "@-/:~.?=_&".IndexOf (line_no_breaks_string [i].ToString ()) == -1) {
1043 line_no_breaks_index = i;
1049 string link_text = line_no_breaks_string.Substring (index_found, link_end - index_found + 1);
1050 int current_cumulative = 0;
1052 // we've found a link - index_found -> link_end
1053 // now we just make all the tags as containing link and
1054 // point them to the text for the whole link
1056 current_line = start_line;
1058 //find the line we start on
1059 for (current_cumulative = 1; current_cumulative < cumulative_length_list.Count; current_cumulative++)
1060 if ((int)cumulative_length_list [current_cumulative] > index_found)
1063 current_line = GetLine (start_line.LineNo + current_cumulative - 1);
1065 // find the tag we start on
1066 LineTag current_tag = current_line.FindTag (index_found - (int)cumulative_length_list [current_cumulative - 1] + 1);
1068 if (current_tag.Start != (index_found - (int)cumulative_length_list [current_cumulative - 1]) + 1) {
1069 if (current_tag == CaretTag)
1070 update_caret_tag = true;
1072 current_tag = current_tag.Break ((index_found - (int)cumulative_length_list [current_cumulative - 1]) + 1);
1076 current_tag.IsLink = true;
1077 current_tag.LinkText = link_text;
1079 //go through each character
1080 // find the tag we are in
1081 // skip the number of characters in the tag
1082 for (int i = 1; i < link_text.Length; i++) {
1083 // on to a new word-wrapped line
1084 if ((int)cumulative_length_list [current_cumulative] <= index_found + i) {
1086 current_line = GetLine (start_line.LineNo + current_cumulative++);
1087 current_tag = current_line.FindTag (index_found + i - (int)cumulative_length_list [current_cumulative - 1] + 1);
1089 current_tag.IsLink = true;
1090 current_tag.LinkText = link_text;
1095 if (current_tag.End < index_found + 1 + i - (int)cumulative_length_list [current_cumulative - 1]) {
1096 // skip empty tags in the middle of the URL
1098 current_tag = current_tag.Next;
1099 } while (current_tag.Length == 0);
1101 current_tag.IsLink = true;
1102 current_tag.LinkText = link_text;
1106 //if there are characters left in the tag after the link
1108 // make the second part a non link
1109 if (current_tag.End > (index_found + link_text.Length + 1) - (int)cumulative_length_list [current_cumulative - 1]) {
1110 if (current_tag == CaretTag)
1111 update_caret_tag = true;
1113 current_tag.Break ((index_found + link_text.Length + 1) - (int)cumulative_length_list [current_cumulative - 1]);
1117 if (update_caret_tag) {
1118 CaretTag = LineTag.FindTag (CaretLine, CaretPosition);
1119 link_changed = true;
1121 if (link_changed == false) {
1122 current_line = start_line;
1123 StringBuilder new_link_record = new StringBuilder ();
1125 while (current_line != null) {
1126 current_line.LinkRecord (new_link_record);
1128 if (current_line.ending == LineEnding.Wrap)
1129 current_line = GetLine (current_line.LineNo + 1);
1134 if (new_link_record.Equals (line_link_record) == false)
1135 link_changed = true;
1140 private int FirstIndexOfAny (string haystack, string [] needles, int start_index, out int term_found)
1143 int best_index = -1;
1145 for (int i = 0; i < needles.Length; i++) {
1147 int index = haystack.IndexOf (needles [i], start_index, StringComparison.InvariantCultureIgnoreCase);
1149 int index = haystack.ToLower().IndexOf(needles[i], start_index);
1153 if (term_found > -1) {
1154 if (index < best_index) {
1170 private void InvalidateLinks (Rectangle clip)
1172 for (int i = (owner.list_links.Count - 1); i >= 0; i--) {
1173 TextBoxBase.LinkRectangle link = (TextBoxBase.LinkRectangle) owner.list_links [i];
1175 if (clip.IntersectsWith (link.LinkAreaRectangle))
1176 owner.list_links.RemoveAt (i);
1179 #endregion // Private Methods
1181 #region Internal Methods
1183 internal void ScanForLinks (int start, int end, ref bool link_changed)
1186 LineEnding lastending = LineEnding.Rich;
1188 // make sure we start scanning at the real begining of the line
1190 if (start != 1 && GetLine (start - 1).ending == LineEnding.Wrap)
1196 for (int i = start; i <= end && i <= lines; i++) {
1199 if (lastending != LineEnding.Wrap)
1200 ScanForLinks (line, ref link_changed);
1202 lastending = line.ending;
1204 if (lastending == LineEnding.Wrap && (i + 1) <= end)
1209 // Clear the document and reset state
1210 internal void Empty() {
1212 document = sentinel;
1215 // We always have a blank line
1216 Add (1, String.Empty, owner.Font, owner.ForeColor, LineEnding.None);
1218 this.RecalculateDocument(owner.CreateGraphicsInternal());
1219 PositionCaret(0, 0);
1221 SetSelectionVisible (false);
1223 selection_start.line = this.document;
1224 selection_start.pos = 0;
1225 selection_start.tag = selection_start.line.tags;
1226 selection_end.line = this.document;
1227 selection_end.pos = 0;
1228 selection_end.tag = selection_end.line.tags;
1237 if (owner.IsHandleCreated)
1238 owner.Invalidate ();
1241 internal void PositionCaret(Line line, int pos) {
1242 caret.tag = line.FindTag (pos);
1244 MoveCaretToTextTag ();
1249 if (owner.IsHandleCreated) {
1250 if (owner.Focused) {
1251 if (caret.height != caret.tag.Height)
1252 XplatUI.CreateCaret (owner.Handle, caret_width, caret.height);
1253 XplatUI.SetCaretPos(owner.Handle,
1254 offset_x + (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x,
1255 offset_y + caret.line.Y + caret.tag.Shift - viewport_y + caret_shift);
1258 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1261 // We set this at the end because we use the heights to determine whether or
1262 // not we need to recreate the caret
1263 caret.height = caret.tag.Height;
1267 internal void PositionCaret(int x, int y) {
1268 if (!owner.IsHandleCreated) {
1272 caret.tag = FindCursor(x, y, out caret.pos);
1274 MoveCaretToTextTag ();
1276 caret.line = caret.tag.Line;
1277 caret.height = caret.tag.Height;
1279 if (owner.ShowSelection && (!selection_visible || owner.show_caret_w_selection)) {
1280 XplatUI.CreateCaret (owner.Handle, caret_width, caret.height);
1281 XplatUI.SetCaretPos(owner.Handle,
1282 (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x + offset_x,
1283 offset_y + caret.line.Y + caret.tag.Shift - viewport_y + caret_shift);
1286 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1289 internal void CaretHasFocus() {
1290 if ((caret.tag != null) && owner.IsHandleCreated) {
1291 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1292 XplatUI.SetCaretPos(owner.Handle,
1293 offset_x + (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x,
1294 offset_y + caret.line.Y + caret.tag.Shift - viewport_y + caret_shift);
1299 if (owner.IsHandleCreated && SelectionLength () > 0) {
1300 InvalidateSelectionArea ();
1304 internal void CaretLostFocus() {
1305 if (!owner.IsHandleCreated) {
1308 XplatUI.DestroyCaret(owner.Handle);
1311 internal void AlignCaret ()
1316 internal void AlignCaret(bool changeCaretTag) {
1317 if (!owner.IsHandleCreated) {
1321 if (changeCaretTag) {
1322 caret.tag = LineTag.FindTag (caret.line, caret.pos);
1324 MoveCaretToTextTag ();
1327 // if the caret has had SelectionFont changed to a
1328 // different height, we reflect changes unless the new
1329 // font is larger than the line (line recalculations
1330 // ignore empty tags) in which case we make it equal
1331 // the line height and then when text is entered
1332 if (caret.tag.Height > caret.tag.Line.Height) {
1333 caret.height = caret.line.height;
1335 caret.height = caret.tag.Height;
1338 if (owner.Focused) {
1339 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1340 XplatUI.SetCaretPos (owner.Handle,
1341 offset_x + (int) caret.tag.Line.widths [caret.pos] + caret.line.X - viewport_x,
1342 offset_y + caret.line.Y + viewport_y + caret_shift);
1346 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1349 internal void UpdateCaret() {
1350 if (!owner.IsHandleCreated || caret.tag == null) {
1354 MoveCaretToTextTag ();
1356 if (caret.tag.Height != caret.height) {
1357 caret.height = caret.tag.Height;
1358 if (owner.Focused) {
1359 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1363 if (owner.Focused) {
1364 XplatUI.SetCaretPos(owner.Handle,
1365 offset_x + (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x,
1366 offset_y + caret.line.Y + caret.tag.Shift - viewport_y + caret_shift);
1370 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1373 internal void DisplayCaret() {
1374 if (!owner.IsHandleCreated) {
1378 if (owner.ShowSelection && (!selection_visible || owner.show_caret_w_selection)) {
1379 XplatUI.CaretVisible(owner.Handle, true);
1383 internal void HideCaret() {
1384 if (!owner.IsHandleCreated) {
1388 if (owner.Focused) {
1389 XplatUI.CaretVisible(owner.Handle, false);
1394 internal void MoveCaretToTextTag ()
1396 if (caret.tag == null || caret.tag.IsTextTag)
1401 if (caret.pos < caret.tag.Start) {
1402 caret.tag = caret.tag.Previous;
1404 caret.tag = caret.tag.Next;
1408 internal void MoveCaret(CaretDirection direction) {
1409 // FIXME should we use IsWordSeparator to detect whitespace, instead
1410 // of looking for actual spaces in the Word move cases?
1412 bool nowrap = false;
1414 case CaretDirection.CharForwardNoWrap:
1416 goto case CaretDirection.CharForward;
1417 case CaretDirection.CharForward: {
1419 if (caret.pos > caret.line.TextLengthWithoutEnding ()) {
1421 // Go into next line
1422 if (caret.line.line_no < this.lines) {
1423 caret.line = GetLine(caret.line.line_no+1);
1425 caret.tag = caret.line.tags;
1430 // Single line; we stay where we are
1434 if ((caret.tag.Start - 1 + caret.tag.Length) < caret.pos) {
1435 caret.tag = caret.tag.Next;
1442 case CaretDirection.CharBackNoWrap:
1444 goto case CaretDirection.CharBack;
1445 case CaretDirection.CharBack: {
1446 if (caret.pos > 0) {
1447 // caret.pos--; // folded into the if below
1449 if (--caret.pos > 0) {
1450 if (caret.tag.Start > caret.pos) {
1451 caret.tag = caret.tag.Previous;
1455 if (caret.line.line_no > 1 && !nowrap) {
1456 caret.line = GetLine(caret.line.line_no - 1);
1457 caret.pos = caret.line.TextLengthWithoutEnding ();
1458 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1465 case CaretDirection.WordForward: {
1468 len = caret.line.text.Length;
1469 if (caret.pos < len) {
1470 while ((caret.pos < len) && (caret.line.text[caret.pos] != ' ')) {
1473 if (caret.pos < len) {
1474 // Skip any whitespace
1475 while ((caret.pos < len) && (caret.line.text[caret.pos] == ' ')) {
1479 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1481 if (caret.line.line_no < this.lines) {
1482 caret.line = GetLine(caret.line.line_no + 1);
1484 caret.tag = caret.line.tags;
1491 case CaretDirection.WordBack: {
1492 if (caret.pos > 0) {
1495 while ((caret.pos > 0) && (caret.line.text[caret.pos] == ' ')) {
1499 while ((caret.pos > 0) && (caret.line.text[caret.pos] != ' ')) {
1503 if (caret.line.text.ToString(caret.pos, 1) == " ") {
1504 if (caret.pos != 0) {
1507 caret.line = GetLine(caret.line.line_no - 1);
1508 caret.pos = caret.line.text.Length;
1511 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1513 if (caret.line.line_no > 1) {
1514 caret.line = GetLine(caret.line.line_no - 1);
1515 caret.pos = caret.line.text.Length;
1516 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1523 case CaretDirection.LineUp: {
1524 if (caret.line.line_no > 1) {
1527 pixel = (int)caret.line.widths[caret.pos];
1528 PositionCaret(pixel, GetLine(caret.line.line_no - 1).Y);
1535 case CaretDirection.LineDown: {
1536 if (caret.line.line_no < lines) {
1539 pixel = (int)caret.line.widths[caret.pos];
1540 PositionCaret(pixel, GetLine(caret.line.line_no + 1).Y);
1547 case CaretDirection.Home: {
1548 if (caret.pos > 0) {
1550 caret.tag = caret.line.tags;
1556 case CaretDirection.End: {
1557 if (caret.pos < caret.line.TextLengthWithoutEnding ()) {
1558 caret.pos = caret.line.TextLengthWithoutEnding ();
1559 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1565 case CaretDirection.PgUp: {
1567 if (caret.line.line_no == 1 && owner.richtext) {
1568 owner.vscroll.Value = 0;
1569 Line line = GetLine (1);
1570 PositionCaret (line, 0);
1573 int y_offset = caret.line.Y + caret.line.height - 1 - viewport_y;
1575 LineTag top = FindCursor ((int) caret.line.widths [caret.pos],
1576 viewport_y - viewport_height, out index);
1578 owner.vscroll.Value = Math.Min (top.Line.Y, owner.vscroll.Maximum - viewport_height);
1579 PositionCaret ((int) caret.line.widths [caret.pos], y_offset + viewport_y);
1584 case CaretDirection.PgDn: {
1586 if (caret.line.line_no == lines && owner.richtext) {
1587 owner.vscroll.Value = owner.vscroll.Maximum - viewport_height + 1;
1588 Line line = GetLine (lines);
1589 PositionCaret (line, line.TextLengthWithoutEnding());
1592 int y_offset = caret.line.Y - viewport_y;
1594 LineTag top = FindCursor ((int) caret.line.widths [caret.pos],
1595 viewport_y + viewport_height, out index);
1597 owner.vscroll.Value = Math.Min (top.Line.Y, owner.vscroll.Maximum - viewport_height);
1598 PositionCaret ((int) caret.line.widths [caret.pos], y_offset + viewport_y);
1603 case CaretDirection.CtrlPgUp: {
1604 PositionCaret(0, viewport_y);
1609 case CaretDirection.CtrlPgDn: {
1614 tag = FindCursor (0, viewport_y + viewport_height, out index);
1615 if (tag.Line.line_no > 1) {
1616 line = GetLine(tag.Line.line_no - 1);
1620 PositionCaret(line, line.Text.Length);
1625 case CaretDirection.CtrlHome: {
1626 caret.line = GetLine(1);
1628 caret.tag = caret.line.tags;
1634 case CaretDirection.CtrlEnd: {
1635 caret.line = GetLine(lines);
1636 caret.pos = caret.line.TextLengthWithoutEnding ();
1637 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1643 case CaretDirection.SelectionStart: {
1644 caret.line = selection_start.line;
1645 caret.pos = selection_start.pos;
1646 caret.tag = selection_start.tag;
1652 case CaretDirection.SelectionEnd: {
1653 caret.line = selection_end.line;
1654 caret.pos = selection_end.pos;
1655 caret.tag = selection_end.tag;
1663 internal void DumpDoc ()
1665 Console.WriteLine ("<doc lines='{0}'>", lines);
1666 for (int i = 1; i <= lines ; i++) {
1667 Line line = GetLine (i);
1668 Console.WriteLine ("<line no='{0}' ending='{1}'>", line.line_no, line.ending);
1670 LineTag tag = line.tags;
1671 while (tag != null) {
1672 Console.Write ("\t<tag type='{0}' span='{1}->{2}' font='{3}' color='{4}'>",
1673 tag.GetType (), tag.Start, tag.Length, tag.Font, tag.Color);
1674 Console.Write (tag.Text ());
1675 Console.WriteLine ("</tag>");
1678 Console.WriteLine ("</line>");
1680 Console.WriteLine ("</doc>");
1683 // UIA: Used via reflection by TextProviderBehavior
1684 internal void GetVisibleLineIndexes (Rectangle clip, out int start, out int end)
1687 start = GetLineByPixel(clip.Top + viewport_y - offset_y, false).line_no;
1688 end = GetLineByPixel(clip.Bottom + viewport_y - offset_y, false).line_no;
1690 start = GetLineByPixel(clip.Left + viewport_x - offset_x, false).line_no;
1691 end = GetLineByPixel(clip.Right + viewport_x - offset_x, false).line_no;
1695 internal void Draw (Graphics g, Rectangle clip)
1697 Line line; // Current line being drawn
1698 LineTag tag; // Current tag being drawn
1699 int start; // First line to draw
1700 int end; // Last line to draw
1701 StringBuilder text; // String representing the current line
1704 Color current_color;
1706 // First, figure out from what line to what line we need to draw
1707 GetVisibleLineIndexes (clip, out start, out end);
1709 // remove links in the list (used for mouse down events) that are within the clip area.
1710 InvalidateLinks (clip);
1713 /// We draw the single border ourself
1715 if (owner.actual_border_style == BorderStyle.FixedSingle) {
1716 ControlPaint.DrawBorder (g, owner.ClientRectangle, Color.Black, ButtonBorderStyle.Solid);
1719 /// Make sure that we aren't drawing one more line then we need to
1720 line = GetLine (end - 1);
1721 if (line != null && clip.Bottom == offset_y + line.Y + line.height - viewport_y)
1727 DateTime n = DateTime.Now;
1728 Console.WriteLine ("Started drawing: {0}s {1}ms", n.Second, n.Millisecond);
1729 Console.WriteLine ("CLIP: {0}", clip);
1730 Console.WriteLine ("S: {0}", GetLine (start).text);
1731 Console.WriteLine ("E: {0}", GetLine (end).text);
1734 // Non multiline selection can be handled outside of the loop
1735 if (!multiline && selection_visible && owner.ShowSelection) {
1736 g.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (ThemeEngine.Current.ColorHighlight),
1737 offset_x + selection_start.line.widths [selection_start.pos] +
1738 selection_start.line.X - viewport_x,
1739 offset_y + selection_start.line.Y,
1740 (selection_end.line.X + selection_end.line.widths [selection_end.pos]) -
1741 (selection_start.line.X + selection_start.line.widths [selection_start.pos]),
1742 selection_start.line.height);
1745 while (line_no <= end) {
1746 line = GetLine (line_no);
1747 float line_y = line.Y - viewport_y + offset_y;
1753 if (PasswordCache.Length < line.text.Length)
1754 PasswordCache.Append(Char.Parse(password_char), line.text.Length - PasswordCache.Length);
1755 else if (PasswordCache.Length > line.text.Length)
1756 PasswordCache.Remove(line.text.Length, PasswordCache.Length - line.text.Length);
1757 text = PasswordCache;
1760 int line_selection_start = text.Length + 1;
1761 int line_selection_end = text.Length + 1;
1762 if (selection_visible && owner.ShowSelection &&
1763 (line_no >= selection_start.line.line_no) &&
1764 (line_no <= selection_end.line.line_no)) {
1766 if (line_no == selection_start.line.line_no)
1767 line_selection_start = selection_start.pos + 1;
1769 line_selection_start = 1;
1771 if (line_no == selection_end.line.line_no)
1772 line_selection_end = selection_end.pos + 1;
1774 line_selection_end = text.Length + 1;
1776 if (line_selection_end == line_selection_start) {
1777 // There isn't really selection
1778 line_selection_start = text.Length + 1;
1779 line_selection_end = line_selection_start;
1780 } else if (multiline) {
1781 // lets draw some selection baby!! (non multiline selection is drawn outside the loop)
1782 g.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (ThemeEngine.Current.ColorHighlight),
1783 offset_x + line.widths [line_selection_start - 1] + line.X - viewport_x,
1784 line_y, line.widths [line_selection_end - 1] - line.widths [line_selection_start - 1],
1789 current_color = line.tags.ColorToDisplay;
1790 while (tag != null) {
1793 if (tag.Length == 0) {
1798 if (((tag.X + tag.Width) < (clip.Left - viewport_x - offset_x)) &&
1799 (tag.X > (clip.Right - viewport_x - offset_x))) {
1804 if (tag.BackColor != Color.Empty) {
1805 g.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (tag.BackColor),
1806 offset_x + tag.X + line.X - viewport_x,
1807 line_y + tag.Shift, tag.Width, line.height);
1810 tag_color = tag.ColorToDisplay;
1811 current_color = tag_color;
1813 if (!owner.Enabled) {
1814 Color a = tag.Color;
1815 Color b = ThemeEngine.Current.ColorWindowText;
1817 if ((a.R == b.R) && (a.G == b.G) && (a.B == b.B))
1818 tag_color = ThemeEngine.Current.ColorGrayText;
1822 int tag_pos = tag.Start;
1823 current_color = tag_color;
1824 while (tag_pos < tag.Start + tag.Length) {
1825 int old_tag_pos = tag_pos;
1827 if (tag_pos >= line_selection_start && tag_pos < line_selection_end) {
1828 current_color = ThemeEngine.Current.ColorHighlightText;
1829 tag_pos = Math.Min (tag.End, line_selection_end);
1830 } else if (tag_pos < line_selection_start) {
1831 current_color = tag_color;
1832 tag_pos = Math.Min (tag.End, line_selection_start);
1834 current_color = tag_color;
1838 Rectangle text_size;
1840 tag.Draw (g, current_color,
1841 offset_x + line.X - viewport_x,
1843 old_tag_pos - 1, Math.Min (tag.Start + tag.Length, tag_pos) - 1,
1844 text.ToString (), out text_size, tag.IsLink);
1847 TextBoxBase.LinkRectangle link = new TextBoxBase.LinkRectangle (text_size);
1849 owner.list_links.Add (link);
1855 line.DrawEnding (g, line_y);
1860 private int GetLineEnding (string line, int start, out LineEnding ending)
1865 if (start >= line.Length) {
1866 ending = LineEnding.Wrap;
1870 res = line.IndexOf ('\r', start);
1871 rich_index = line.IndexOf ('\n', start);
1873 // Handle the case where we find both of them, and the \n is before the \r
1874 if (res != -1 && rich_index != -1)
1875 if (rich_index < res) {
1876 ending = LineEnding.Rich;
1881 if (res + 2 < line.Length && line [res + 1] == '\r' && line [res + 2] == '\n') {
1882 ending = LineEnding.Soft;
1885 if (res + 1 < line.Length && line [res + 1] == '\n') {
1886 ending = LineEnding.Hard;
1889 ending = LineEnding.Limp;
1893 if (rich_index != -1) {
1894 ending = LineEnding.Rich;
1898 ending = LineEnding.Wrap;
1902 // Get the line ending, but only of the types specified
1903 private int GetLineEnding (string line, int start, out LineEnding ending, LineEnding type)
1906 int last_length = 0;
1909 index = GetLineEnding (line, index + last_length, out ending);
1910 last_length = LineEndingLength (ending);
1912 ((ending & type) != ending && index != -1);
1914 return index == -1 ? line.Length : index;
1917 internal int LineEndingLength (LineEnding ending)
1920 case LineEnding.Limp:
1921 case LineEnding.Rich:
1923 case LineEnding.Hard:
1925 case LineEnding.Soft:
1932 internal string LineEndingToString (LineEnding ending)
1935 case LineEnding.Limp:
1937 case LineEnding.Hard:
1939 case LineEnding.Soft:
1941 case LineEnding.Rich:
1945 return string.Empty;
1948 internal LineEnding StringToLineEnding (string ending)
1952 return LineEnding.Limp;
1954 return LineEnding.Hard;
1956 return LineEnding.Soft;
1958 return LineEnding.Rich;
1960 return LineEnding.None;
1964 internal void Insert (Line line, int pos, bool update_caret, string s)
1966 Insert (line, pos, update_caret, s, line.FindTag (pos));
1969 // Insert text at the given position; use formatting at insertion point for inserted text
1970 internal void Insert (Line line, int pos, bool update_caret, string s, LineTag tag)
1979 // Don't recalculate while we mess around
1982 base_line = line.line_no;
1983 old_line_count = lines;
1985 break_index = GetLineEnding (s, 0, out ending, LineEnding.Hard | LineEnding.Rich);
1987 // There are no line feeds in our text to be pasted
1988 if (break_index == s.Length) {
1989 line.InsertString (pos, s, tag);
1991 // Add up to the first line feed to our current position
1992 line.InsertString (pos, s.Substring (0, break_index + LineEndingLength (ending)), tag);
1994 // Split the rest of the original line to a new line
1995 Split (line, pos + (break_index + LineEndingLength (ending)));
1996 line.ending = ending;
1997 break_index += LineEndingLength (ending);
1998 split_line = GetLine (line.line_no + 1);
2000 // Insert brand new lines for any more line feeds in the inserted string
2002 int next_break = GetLineEnding (s, break_index, out ending, LineEnding.Hard | LineEnding.Rich);
2004 if (next_break == s.Length)
2007 string line_text = s.Substring (break_index, next_break - break_index +
2008 LineEndingLength (ending));
2010 Add (base_line + count, line_text, line.alignment, tag.Font, tag.Color, ending);
2012 Line last = GetLine (base_line + count);
2013 last.ending = ending;
2016 break_index = next_break + LineEndingLength (ending);
2019 // Add the remainder of the insert text to the split
2020 // part of the original line
2021 split_line.InsertString (0, s.Substring (break_index));
2024 // Allow the document to recalculate things
2025 ResumeRecalc (false);
2027 // Update our character count
2028 CharCount += s.Length;
2030 UpdateView (line, lines - old_line_count + 1, pos);
2032 // Move the caret to the end of the inserted text if requested
2034 Line l = GetLine (line.line_no + lines - old_line_count);
2035 PositionCaret (l, l.text.Length);
2040 // Inserts a string at the given position
2041 internal void InsertString (Line line, int pos, string s)
2043 // Update our character count
2044 CharCount += s.Length;
2046 // Insert the text into the Line
2047 line.InsertString (pos, s);
2050 // Inserts a character at the current caret position
2051 internal void InsertCharAtCaret (char ch, bool move_caret)
2053 caret.line.InsertString (caret.pos, ch.ToString(), caret.tag);
2055 // Update our character count
2058 undo.RecordTyping (caret.line, caret.pos, ch);
2060 UpdateView (caret.line, caret.pos);
2065 SetSelectionToCaret (true);
2069 internal void InsertPicture (Line line, int pos, RTF.Picture picture)
2077 // Just a place holder basically
2078 line.text.Insert (pos, "I");
2080 PictureTag picture_tag = new PictureTag (line, pos + 1, picture);
2082 tag = LineTag.FindTag (line, pos);
2083 picture_tag.CopyFormattingFrom (tag);
2084 /*next_tag = */tag.Break (pos + 1);
2085 picture_tag.Previous = tag;
2086 picture_tag.Next = tag.Next;
2087 tag.Next = picture_tag;
2090 // Picture tags need to be surrounded by text tags
2092 if (picture_tag.Next == null) {
2093 picture_tag.Next = new LineTag (line, pos + 1);
2094 picture_tag.Next.CopyFormattingFrom (tag);
2095 picture_tag.Next.Previous = picture_tag;
2098 tag = picture_tag.Next;
2099 while (tag != null) {
2107 UpdateView (line, pos);
2110 internal void DeleteMultiline (Line start_line, int pos, int length)
2112 Marker start = new Marker ();
2113 Marker end = new Marker ();
2114 int start_index = LineTagToCharIndex (start_line, pos);
2116 start.line = start_line;
2118 start.tag = LineTag.FindTag (start_line, pos);
2120 CharIndexToLineTag (start_index + length, out end.line,
2121 out end.tag, out end.pos);
2125 if (start.line == end.line) {
2126 DeleteChars (start.line, pos, end.pos - pos);
2129 // Delete first and last lines
2130 DeleteChars (start.line, start.pos, start.line.text.Length - start.pos);
2131 DeleteChars (end.line, 0, end.pos);
2133 int current = start.line.line_no + 1;
2134 if (current < end.line.line_no) {
2135 for (int i = end.line.line_no - 1; i >= current; i--) {
2140 // BIG FAT WARNING - selection_end.line might be stale due
2141 // to the above Delete() call. DONT USE IT before hitting the end of this method!
2143 // Join start and end
2144 Combine (start.line.line_no, current);
2147 ResumeUpdate (true);
2151 // Deletes n characters at the given position; it will not delete past line limits
2153 public void DeleteChars (Line line, int pos, int count)
2155 // Reduce our character count
2158 line.DeleteCharacters (pos, count);
2160 if (pos >= line.TextLengthWithoutEnding ()) {
2161 LineEnding ending = line.ending;
2162 GetLineEnding (line.text.ToString (), 0, out ending);
2164 if (ending != line.ending) {
2165 line.ending = ending;
2168 UpdateView (line, lines, pos);
2169 owner.Invalidate ();
2175 UpdateView (line, lines, pos);
2176 owner.Invalidate ();
2178 UpdateView (line, pos);
2181 // Deletes a character at or after the given position (depending on forward); it will not delete past line limits
2182 public void DeleteChar (Line line, int pos, bool forward)
2184 if ((pos == 0 && forward == false) || (pos == line.text.Length && forward == true))
2188 DeleteChars (line, pos, 1);
2190 DeleteChars (line, pos - 1, 1);
2193 // Combine two lines
2194 internal void Combine(int FirstLine, int SecondLine) {
2195 Combine(GetLine(FirstLine), GetLine(SecondLine));
2198 internal void Combine(Line first, Line second) {
2202 // strip the ending off of the first lines text
2203 first.text.Length = first.text.Length - LineEndingLength (first.ending);
2205 // Combine the two tag chains into one
2208 // Maintain the line ending style
2209 first.ending = second.ending;
2211 while (last.Next != null) {
2215 // need to get the shift before setting the next tag since that effects length
2216 shift = last.Start + last.Length - 1;
2217 last.Next = second.tags;
2218 last.Next.Previous = last;
2220 // Fix up references within the chain
2222 while (last != null) {
2224 last.Start += shift;
2228 // Combine both lines' strings
2229 first.text.Insert(first.text.Length, second.text.ToString());
2230 first.Grow(first.text.Length);
2232 // Remove the reference to our (now combined) tags from the doomed line
2236 DecrementLines(first.line_no + 2); // first.line_no + 1 will be deleted, so we need to start renumbering one later
2239 first.recalc = true;
2240 first.height = 0; // This forces RecalcDocument/UpdateView to redraw from this line on
2241 first.Streamline(lines);
2243 // Update Caret, Selection, etc
2244 if (caret.line == second) {
2245 caret.Combine(first, shift);
2247 if (selection_anchor.line == second) {
2248 selection_anchor.Combine(first, shift);
2250 if (selection_start.line == second) {
2251 selection_start.Combine(first, shift);
2253 if (selection_end.line == second) {
2254 selection_end.Combine(first, shift);
2261 check_first = GetLine(first.line_no);
2262 check_second = GetLine(check_first.line_no + 1);
2264 Console.WriteLine("Pre-delete: Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2267 this.Delete(second);
2270 check_first = GetLine(first.line_no);
2271 check_second = GetLine(check_first.line_no + 1);
2273 Console.WriteLine("Post-delete Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2277 // Split the line at the position into two
2278 internal void Split(int LineNo, int pos) {
2282 line = GetLine(LineNo);
2283 tag = LineTag.FindTag(line, pos);
2284 Split(line, tag, pos);
2287 internal void Split(Line line, int pos) {
2290 tag = LineTag.FindTag(line, pos);
2291 Split(line, tag, pos);
2294 ///<summary>Split line at given tag and position into two lines</summary>
2295 ///if more space becomes available on previous line
2296 internal void Split(Line line, LineTag tag, int pos) {
2300 bool move_sel_start;
2304 move_sel_start = false;
2305 move_sel_end = false;
2311 throw new Exception ("Split called with the wrong tag");
2314 // Adjust selection and cursors
2315 if (caret.line == line && caret.pos >= pos) {
2318 if (selection_start.line == line && selection_start.pos > pos) {
2319 move_sel_start = true;
2322 if (selection_end.line == line && selection_end.pos > pos) {
2323 move_sel_end = true;
2326 // cover the easy case first
2327 if (pos == line.text.Length) {
2328 Add (line.line_no + 1, String.Empty, line.alignment, tag.Font, tag.Color, line.ending);
2330 new_line = GetLine (line.line_no + 1);
2333 caret.line = new_line;
2334 caret.tag = new_line.tags;
2337 if (selection_visible == false) {
2338 SetSelectionToCaret (true);
2342 if (move_sel_start) {
2343 selection_start.line = new_line;
2344 selection_start.pos = 0;
2345 selection_start.tag = new_line.tags;
2349 selection_end.line = new_line;
2350 selection_end.pos = 0;
2351 selection_end.tag = new_line.tags;
2360 // We need to move the rest of the text into the new line
2361 Add (line.line_no + 1, line.text.ToString (pos, line.text.Length - pos), line.alignment, tag.Font, tag.Color, line.ending);
2363 // Now transfer our tags from this line to the next
2364 new_line = GetLine(line.line_no + 1);
2367 new_line.recalc = true;
2369 //make sure that if we are at the end of a tag, we start on the begining
2370 //of a new one, if one exists... Stops us creating an empty tag and
2371 //make the operation easier.
2372 if (tag.Next != null && (tag.Next.Start - 1) == pos)
2375 if ((tag.Start - 1) == pos) {
2378 // We can simply break the chain and move the tag into the next line
2380 // if the tag we are moving is the first, create an empty tag
2381 // for the line we are leaving behind
2382 if (tag == line.tags) {
2383 new_tag = new LineTag(line, 1);
2384 new_tag.CopyFormattingFrom (tag);
2385 line.tags = new_tag;
2388 if (tag.Previous != null) {
2389 tag.Previous.Next = null;
2391 new_line.tags = tag;
2392 tag.Previous = null;
2393 tag.Line = new_line;
2395 // Walk the list and correct the start location of the tags we just bumped into the next line
2396 shift = tag.Start - 1;
2399 while (new_tag != null) {
2400 new_tag.Start -= shift;
2401 new_tag.Line = new_line;
2402 new_tag = new_tag.Next;
2407 new_tag = new LineTag (new_line, 1);
2408 new_tag.Next = tag.Next;
2409 new_tag.CopyFormattingFrom (tag);
2410 new_line.tags = new_tag;
2411 if (new_tag.Next != null) {
2412 new_tag.Next.Previous = new_tag;
2417 new_tag = new_tag.Next;
2418 while (new_tag != null) {
2419 new_tag.Start -= shift;
2420 new_tag.Line = new_line;
2421 new_tag = new_tag.Next;
2427 caret.line = new_line;
2428 caret.pos = caret.pos - pos;
2429 caret.tag = caret.line.FindTag(caret.pos);
2431 if (selection_visible == false) {
2432 SetSelectionToCaret (true);
2436 if (move_sel_start) {
2437 selection_start.line = new_line;
2438 selection_start.pos = selection_start.pos - pos;
2439 if (selection_start.Equals(selection_end))
2440 selection_start.tag = new_line.FindTag(selection_start.pos);
2442 selection_start.tag = new_line.FindTag (selection_start.pos + 1);
2446 selection_end.line = new_line;
2447 selection_end.pos = selection_end.pos - pos;
2448 selection_end.tag = new_line.FindTag(selection_end.pos);
2451 CharCount -= line.text.Length - pos;
2452 line.text.Remove(pos, line.text.Length - pos);
2459 private void SanityCheck () {
2460 for (int i = 1; i < lines; i++) {
2461 LineTag tag = GetLine (i).tags;
2464 throw new Exception ("Line doesn't start at the begining");
2469 while (tag != null) {
2470 if (tag.Start == start)
2471 throw new Exception ("Empty tag!");
2473 if (tag.Start < start)
2474 throw new Exception ("Insane!!");
2483 // Adds a line of text, with given font.
2484 // Bumps any line at that line number that already exists down
2485 internal void Add (int LineNo, string Text, Font font, Color color, LineEnding ending)
2487 Add (LineNo, Text, alignment, font, color, ending);
2490 internal void Add (int LineNo, string Text, HorizontalAlignment align, Font font, Color color, LineEnding ending)
2496 CharCount += Text.Length;
2498 if (LineNo<1 || Text == null) {
2500 throw new ArgumentNullException("LineNo", "Line numbers must be positive");
2502 throw new ArgumentNullException("Text", "Cannot insert NULL line");
2506 add = new Line (this, LineNo, Text, align, font, color, ending);
2509 while (line != sentinel) {
2511 line_no = line.line_no;
2513 if (LineNo > line_no) {
2515 } else if (LineNo < line_no) {
2518 // Bump existing line numbers; walk all nodes to the right of this one and increment line_no
2519 IncrementLines(line.line_no);
2524 add.left = sentinel;
2525 add.right = sentinel;
2527 if (add.parent != null) {
2528 if (LineNo > add.parent.line_no) {
2529 add.parent.right = add;
2531 add.parent.left = add;
2538 RebalanceAfterAdd(add);
2543 internal virtual void Clear() {
2546 document = sentinel;
2549 public virtual object Clone() {
2552 clone = new Document(null);
2554 clone.lines = this.lines;
2555 clone.document = (Line)document.Clone();
2560 private void Delete (int LineNo)
2567 line = GetLine (LineNo);
2569 CharCount -= line.text.Length;
2571 DecrementLines (LineNo + 1);
2575 private void Delete(Line line1) {
2576 Line line2;// = new Line();
2579 if ((line1.left == sentinel) || (line1.right == sentinel)) {
2582 line3 = line1.right;
2583 while (line3.left != sentinel) {
2588 if (line3.left != sentinel) {
2591 line2 = line3.right;
2594 line2.parent = line3.parent;
2595 if (line3.parent != null) {
2596 if(line3 == line3.parent.left) {
2597 line3.parent.left = line2;
2599 line3.parent.right = line2;
2605 if (line3 != line1) {
2608 if (selection_start.line == line3) {
2609 selection_start.line = line1;
2612 if (selection_end.line == line3) {
2613 selection_end.line = line1;
2616 if (selection_anchor.line == line3) {
2617 selection_anchor.line = line1;
2620 if (caret.line == line3) {
2625 line1.alignment = line3.alignment;
2626 line1.ascent = line3.ascent;
2627 line1.hanging_indent = line3.hanging_indent;
2628 line1.height = line3.height;
2629 line1.indent = line3.indent;
2630 line1.line_no = line3.line_no;
2631 line1.recalc = line3.recalc;
2632 line1.right_indent = line3.right_indent;
2633 line1.ending = line3.ending;
2634 line1.space = line3.space;
2635 line1.tags = line3.tags;
2636 line1.text = line3.text;
2637 line1.widths = line3.widths;
2638 line1.offset = line3.offset;
2641 while (tag != null) {
2647 if (line3.color == LineColor.Black)
2648 RebalanceAfterDelete(line2);
2653 // Invalidates the start line until the end of the viewstate
2654 internal void InvalidateLinesAfter (Line start) {
2655 owner.Invalidate (new Rectangle (0, start.Y - viewport_y, viewport_width, viewport_height - start.Y));
2658 // Invalidate a section of the document to trigger redraw
2659 internal void Invalidate(Line start, int start_pos, Line end, int end_pos) {
2665 if ((start == end) && (start_pos == end_pos)) {
2669 if (end_pos == -1) {
2670 end_pos = end.text.Length;
2673 // figure out what's before what so the logic below is straightforward
2674 if (start.line_no < end.line_no) {
2680 } else if (start.line_no > end.line_no) {
2687 if (start_pos < end_pos) {
2701 int endpoint = (int) l1.widths [p2];
2702 if (p2 == l1.text.Length + 1) {
2703 endpoint = (int) viewport_width;
2707 Console.WriteLine("Invaliding backwards from {0}:{1} to {2}:{3} {4}",
2708 l1.line_no, p1, l2.line_no, p2,
2710 (int)l1.widths[p1] + l1.X - viewport_x,
2718 owner.Invalidate(new Rectangle (
2719 offset_x + (int)l1.widths[p1] + l1.X - viewport_x,
2720 offset_y + l1.Y - viewport_y,
2721 endpoint - (int) l1.widths [p1] + 1,
2727 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);
2728 Console.WriteLine ("invalidate start line: {0} position: {1}", l1.text, p1);
2731 // Three invalidates:
2732 // First line from start
2733 owner.Invalidate(new Rectangle(
2734 offset_x + (int)l1.widths[p1] + l1.X - viewport_x,
2735 offset_y + l1.Y - viewport_y,
2741 if ((l1.line_no + 1) < l2.line_no) {
2744 y = GetLine(l1.line_no + 1).Y;
2745 owner.Invalidate(new Rectangle(
2747 offset_y + y - viewport_y,
2752 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);
2758 owner.Invalidate(new Rectangle(
2759 offset_x + (int)l2.widths[0] + l2.X - viewport_x,
2760 offset_y + l2.Y - viewport_y,
2761 (int)l2.widths[p2] + 1,
2765 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);
2769 /// <summary>Select text around caret</summary>
2770 internal void ExpandSelection(CaretSelection mode, bool to_caret) {
2772 // We're expanding the selection to the caret position
2774 case CaretSelection.Line: {
2775 // Invalidate the selection delta
2776 if (caret > selection_prev) {
2777 Invalidate(selection_prev.line, 0, caret.line, caret.line.text.Length);
2779 Invalidate(selection_prev.line, selection_prev.line.text.Length, caret.line, 0);
2782 if (caret.line.line_no <= selection_anchor.line.line_no) {
2783 selection_start.line = caret.line;
2784 selection_start.tag = caret.line.tags;
2785 selection_start.pos = 0;
2787 selection_end.line = selection_anchor.line;
2788 selection_end.tag = selection_anchor.tag;
2789 selection_end.pos = selection_anchor.pos;
2791 selection_end_anchor = true;
2793 selection_start.line = selection_anchor.line;
2794 selection_start.pos = selection_anchor.height;
2795 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height + 1);
2797 selection_end.line = caret.line;
2798 selection_end.tag = caret.line.tags;
2799 selection_end.pos = caret.line.text.Length;
2801 selection_end_anchor = false;
2803 selection_prev.line = caret.line;
2804 selection_prev.tag = caret.tag;
2805 selection_prev.pos = caret.pos;
2810 case CaretSelection.Word: {
2814 start_pos = FindWordSeparator(caret.line, caret.pos, false);
2815 end_pos = FindWordSeparator(caret.line, caret.pos, true);
2818 // Invalidate the selection delta
2819 if (caret > selection_prev) {
2820 Invalidate(selection_prev.line, selection_prev.pos, caret.line, end_pos);
2822 Invalidate(selection_prev.line, selection_prev.pos, caret.line, start_pos);
2824 if (caret < selection_anchor) {
2825 selection_start.line = caret.line;
2826 selection_start.tag = caret.line.FindTag(start_pos + 1);
2827 selection_start.pos = start_pos;
2829 selection_end.line = selection_anchor.line;
2830 selection_end.tag = selection_anchor.tag;
2831 selection_end.pos = selection_anchor.pos;
2833 selection_prev.line = caret.line;
2834 selection_prev.tag = caret.tag;
2835 selection_prev.pos = start_pos;
2837 selection_end_anchor = true;
2839 selection_start.line = selection_anchor.line;
2840 selection_start.pos = selection_anchor.height;
2841 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height + 1);
2843 selection_end.line = caret.line;
2844 selection_end.tag = caret.line.FindTag(end_pos);
2845 selection_end.pos = end_pos;
2847 selection_prev.line = caret.line;
2848 selection_prev.tag = caret.tag;
2849 selection_prev.pos = end_pos;
2851 selection_end_anchor = false;
2856 case CaretSelection.Position: {
2857 SetSelectionToCaret(false);
2862 // We're setting the selection 'around' the caret position
2864 case CaretSelection.Line: {
2865 this.Invalidate(caret.line, 0, caret.line, caret.line.text.Length);
2867 selection_start.line = caret.line;
2868 selection_start.tag = caret.line.tags;
2869 selection_start.pos = 0;
2871 selection_end.line = caret.line;
2872 selection_end.pos = caret.line.text.Length;
2873 selection_end.tag = caret.line.FindTag(selection_end.pos);
2875 selection_anchor.line = selection_end.line;
2876 selection_anchor.tag = selection_end.tag;
2877 selection_anchor.pos = selection_end.pos;
2878 selection_anchor.height = 0;
2880 selection_prev.line = caret.line;
2881 selection_prev.tag = caret.tag;
2882 selection_prev.pos = caret.pos;
2884 this.selection_end_anchor = true;
2889 case CaretSelection.Word: {
2893 start_pos = FindWordSeparator(caret.line, caret.pos, false);
2894 end_pos = FindWordSeparator(caret.line, caret.pos, true);
2896 this.Invalidate(selection_start.line, start_pos, caret.line, end_pos);
2898 selection_start.line = caret.line;
2899 selection_start.tag = caret.line.FindTag(start_pos + 1);
2900 selection_start.pos = start_pos;
2902 selection_end.line = caret.line;
2903 selection_end.tag = caret.line.FindTag(end_pos);
2904 selection_end.pos = end_pos;
2906 selection_anchor.line = selection_end.line;
2907 selection_anchor.tag = selection_end.tag;
2908 selection_anchor.pos = selection_end.pos;
2909 selection_anchor.height = start_pos;
2911 selection_prev.line = caret.line;
2912 selection_prev.tag = caret.tag;
2913 selection_prev.pos = caret.pos;
2915 this.selection_end_anchor = true;
2922 SetSelectionVisible (!(selection_start == selection_end));
2925 internal void SetSelectionToCaret(bool start) {
2927 // Invalidate old selection; selection is being reset to empty
2928 this.Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2930 selection_start.line = caret.line;
2931 selection_start.tag = caret.tag;
2932 selection_start.pos = caret.pos;
2934 // start always also selects end
2935 selection_end.line = caret.line;
2936 selection_end.tag = caret.tag;
2937 selection_end.pos = caret.pos;
2939 selection_anchor.line = caret.line;
2940 selection_anchor.tag = caret.tag;
2941 selection_anchor.pos = caret.pos;
2943 // Invalidate from previous end to caret (aka new end)
2944 if (selection_end_anchor) {
2945 if (selection_start != caret) {
2946 this.Invalidate(selection_start.line, selection_start.pos, caret.line, caret.pos);
2949 if (selection_end != caret) {
2950 this.Invalidate(selection_end.line, selection_end.pos, caret.line, caret.pos);
2954 if (caret < selection_anchor) {
2955 selection_start.line = caret.line;
2956 selection_start.tag = caret.tag;
2957 selection_start.pos = caret.pos;
2959 selection_end.line = selection_anchor.line;
2960 selection_end.tag = selection_anchor.tag;
2961 selection_end.pos = selection_anchor.pos;
2963 selection_end_anchor = true;
2965 selection_start.line = selection_anchor.line;
2966 selection_start.tag = selection_anchor.tag;
2967 selection_start.pos = selection_anchor.pos;
2969 selection_end.line = caret.line;
2970 selection_end.tag = caret.tag;
2971 selection_end.pos = caret.pos;
2973 selection_end_anchor = false;
2977 SetSelectionVisible (!(selection_start == selection_end));
2980 internal void SetSelection(Line start, int start_pos, Line end, int end_pos) {
2981 if (selection_visible) {
2982 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2985 if ((end.line_no < start.line_no) || ((end == start) && (end_pos <= start_pos))) {
2986 selection_start.line = end;
2987 selection_start.tag = LineTag.FindTag(end, end_pos);
2988 selection_start.pos = end_pos;
2990 selection_end.line = start;
2991 selection_end.tag = LineTag.FindTag(start, start_pos);
2992 selection_end.pos = start_pos;
2994 selection_end_anchor = true;
2996 selection_start.line = start;
2997 selection_start.tag = LineTag.FindTag(start, start_pos);
2998 selection_start.pos = start_pos;
3000 selection_end.line = end;
3001 selection_end.tag = LineTag.FindTag(end, end_pos);
3002 selection_end.pos = end_pos;
3004 selection_end_anchor = false;
3007 selection_anchor.line = start;
3008 selection_anchor.tag = selection_start.tag;
3009 selection_anchor.pos = start_pos;
3011 if (((start == end) && (start_pos == end_pos)) || start == null || end == null) {
3012 SetSelectionVisible (false);
3014 SetSelectionVisible (true);
3015 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3019 internal void SetSelectionStart(Line start, int start_pos, bool invalidate) {
3020 // Invalidate from the previous to the new start pos
3022 Invalidate(selection_start.line, selection_start.pos, start, start_pos);
3024 selection_start.line = start;
3025 selection_start.pos = start_pos;
3026 selection_start.tag = LineTag.FindTag(start, start_pos);
3028 selection_anchor.line = start;
3029 selection_anchor.pos = start_pos;
3030 selection_anchor.tag = selection_start.tag;
3032 selection_end_anchor = false;
3035 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
3036 SetSelectionVisible (true);
3038 SetSelectionVisible (false);
3042 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3045 internal void SetSelectionStart(int character_index, bool invalidate) {
3050 if (character_index < 0) {
3054 CharIndexToLineTag(character_index, out line, out tag, out pos);
3055 SetSelectionStart(line, pos, invalidate);
3058 internal void SetSelectionEnd(Line end, int end_pos, bool invalidate) {
3060 if (end == selection_end.line && end_pos == selection_start.pos) {
3061 selection_anchor.line = selection_start.line;
3062 selection_anchor.tag = selection_start.tag;
3063 selection_anchor.pos = selection_start.pos;
3065 selection_end.line = selection_start.line;
3066 selection_end.tag = selection_start.tag;
3067 selection_end.pos = selection_start.pos;
3069 selection_end_anchor = false;
3070 } else if ((end.line_no < selection_anchor.line.line_no) || ((end == selection_anchor.line) && (end_pos <= selection_anchor.pos))) {
3071 selection_start.line = end;
3072 selection_start.tag = LineTag.FindTag(end, end_pos);
3073 selection_start.pos = end_pos;
3075 selection_end.line = selection_anchor.line;
3076 selection_end.tag = selection_anchor.tag;
3077 selection_end.pos = selection_anchor.pos;
3079 selection_end_anchor = true;
3081 selection_start.line = selection_anchor.line;
3082 selection_start.tag = selection_anchor.tag;
3083 selection_start.pos = selection_anchor.pos;
3085 selection_end.line = end;
3086 selection_end.tag = LineTag.FindTag(end, end_pos);
3087 selection_end.pos = end_pos;
3089 selection_end_anchor = false;
3092 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
3093 SetSelectionVisible (true);
3095 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3097 SetSelectionVisible (false);
3098 // ?? Do I need to invalidate here, tests seem to work without it, but I don't think they should :-s
3102 internal void SetSelectionEnd(int character_index, bool invalidate) {
3107 if (character_index < 0) {
3111 CharIndexToLineTag(character_index, out line, out tag, out pos);
3112 SetSelectionEnd(line, pos, invalidate);
3115 internal void SetSelection(Line start, int start_pos) {
3116 if (selection_visible) {
3117 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3120 selection_start.line = start;
3121 selection_start.pos = start_pos;
3122 selection_start.tag = LineTag.FindTag(start, start_pos);
3124 selection_end.line = start;
3125 selection_end.tag = selection_start.tag;
3126 selection_end.pos = start_pos;
3128 selection_anchor.line = start;
3129 selection_anchor.tag = selection_start.tag;
3130 selection_anchor.pos = start_pos;
3132 selection_end_anchor = false;
3133 SetSelectionVisible (false);
3136 internal void InvalidateSelectionArea() {
3137 Invalidate (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3140 // Return the current selection, as string
3141 internal string GetSelection() {
3142 // We return String.Empty if there is no selection
3143 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3144 return string.Empty;
3147 if (selection_start.line == selection_end.line) {
3148 return selection_start.line.text.ToString (selection_start.pos, selection_end.pos - selection_start.pos);
3155 sb = new StringBuilder();
3156 start = selection_start.line.line_no;
3157 end = selection_end.line.line_no;
3159 sb.Append(selection_start.line.text.ToString(selection_start.pos, selection_start.line.text.Length - selection_start.pos));
3161 if ((start + 1) < end) {
3162 for (i = start + 1; i < end; i++) {
3163 sb.Append(GetLine(i).text.ToString());
3167 sb.Append(selection_end.line.text.ToString(0, selection_end.pos));
3169 return sb.ToString();
3173 internal void ReplaceSelection(string s, bool select_new) {
3176 int selection_start_pos = LineTagToCharIndex (selection_start.line, selection_start.pos);
3179 // First, delete any selected text
3180 if ((selection_start.pos != selection_end.pos) || (selection_start.line != selection_end.line)) {
3181 if (selection_start.line == selection_end.line) {
3182 undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3184 DeleteChars (selection_start.line, selection_start.pos, selection_end.pos - selection_start.pos);
3186 // The tag might have been removed, we need to recalc it
3187 selection_start.tag = selection_start.line.FindTag(selection_start.pos + 1);
3192 start = selection_start.line.line_no;
3193 end = selection_end.line.line_no;
3195 undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3197 InvalidateLinesAfter(selection_start.line);
3199 // Delete first line
3200 DeleteChars (selection_start.line, selection_start.pos, selection_start.line.text.Length - selection_start.pos);
3201 selection_start.line.recalc = true;
3204 DeleteChars(selection_end.line, 0, selection_end.pos);
3208 for (i = end - 1; i >= start; i--) {
3213 // BIG FAT WARNING - selection_end.line might be stale due
3214 // to the above Delete() call. DONT USE IT before hitting the end of this method!
3216 // Join start and end
3217 Combine(selection_start.line.line_no, start);
3222 Insert(selection_start.line, selection_start.pos, false, s);
3223 undo.RecordInsertString (selection_start.line, selection_start.pos, s);
3224 ResumeRecalc (false);
3226 Line begin_update_line = selection_start.line;
3227 int begin_update_pos = selection_start.pos;
3230 CharIndexToLineTag(selection_start_pos + s.Length, out selection_start.line,
3231 out selection_start.tag, out selection_start.pos);
3233 selection_end.line = selection_start.line;
3234 selection_end.pos = selection_start.pos;
3235 selection_end.tag = selection_start.tag;
3236 selection_anchor.line = selection_start.line;
3237 selection_anchor.pos = selection_start.pos;
3238 selection_anchor.tag = selection_start.tag;
3240 SetSelectionVisible (false);
3242 CharIndexToLineTag(selection_start_pos, out selection_start.line,
3243 out selection_start.tag, out selection_start.pos);
3245 CharIndexToLineTag(selection_start_pos + s.Length, out selection_end.line,
3246 out selection_end.tag, out selection_end.pos);
3248 selection_anchor.line = selection_start.line;
3249 selection_anchor.pos = selection_start.pos;
3250 selection_anchor.tag = selection_start.tag;
3252 SetSelectionVisible (true);
3255 PositionCaret (selection_start.line, selection_start.pos);
3256 UpdateView (begin_update_line, selection_end.line.line_no - begin_update_line.line_no, begin_update_pos);
3259 internal void CharIndexToLineTag(int index, out Line line_out, out LineTag tag_out, out int pos) {
3268 for (i = 1; i <= lines; i++) {
3272 chars += line.text.Length;
3274 if (index <= chars) {
3275 // we found the line
3278 while (tag != null) {
3279 if (index < (start + tag.Start + tag.Length - 1)) {
3281 tag_out = LineTag.GetFinalTag (tag);
3282 pos = index - start;
3285 if (tag.Next == null) {
3288 next_line = GetLine(line.line_no + 1);
3290 if (next_line != null) {
3291 line_out = next_line;
3292 tag_out = LineTag.GetFinalTag (next_line.tags);
3297 tag_out = LineTag.GetFinalTag (tag);
3298 pos = line_out.text.Length;
3307 line_out = GetLine(lines);
3308 tag = line_out.tags;
3309 while (tag.Next != null) {
3313 pos = line_out.text.Length;
3316 internal int LineTagToCharIndex(Line line, int pos) {
3320 // Count first and last line
3323 // Count the lines in the middle
3325 for (i = 1; i < line.line_no; i++) {
3326 length += GetLine(i).text.Length;
3334 internal int SelectionLength() {
3335 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3339 if (selection_start.line == selection_end.line) {
3340 return selection_end.pos - selection_start.pos;
3347 // Count first and last line
3348 length = selection_start.line.text.Length - selection_start.pos + selection_end.pos + crlf_size;
3350 // Count the lines in the middle
3351 start = selection_start.line.line_no + 1;
3352 end = selection_end.line.line_no;
3355 for (i = start; i < end; i++) {
3356 Line line = GetLine (i);
3357 length += line.text.Length + LineEndingLength (line.ending);
3368 // UIA: Method used via reflection in TextRangeProvider
3370 /// <summary>Give it a Line number and it returns the Line object at with that line number</summary>
3371 internal Line GetLine(int LineNo) {
3372 Line line = document;
3374 while (line != sentinel) {
3375 if (LineNo == line.line_no) {
3377 } else if (LineNo < line.line_no) {
3387 /// <summary>Retrieve the previous tag; walks line boundaries</summary>
3388 internal LineTag PreviousTag(LineTag tag) {
3391 if (tag.Previous != null) {
3392 return tag.Previous;
3396 if (tag.Line.line_no == 1) {
3400 l = GetLine(tag.Line.line_no - 1);
3405 while (t.Next != null) {
3414 /// <summary>Retrieve the next tag; walks line boundaries</summary>
3415 internal LineTag NextTag(LineTag tag) {
3418 if (tag.Next != null) {
3423 l = GetLine(tag.Line.line_no + 1);
3431 internal Line ParagraphStart(Line line) {
3432 Line lastline = line;
3434 if (line.line_no <= 1)
3438 lastline = GetLine (line.line_no - 1);
3439 } while (lastline.ending == LineEnding.Wrap);
3444 internal Line ParagraphEnd(Line line) {
3447 while (line.ending == LineEnding.Wrap) {
3448 l = GetLine(line.line_no + 1);
3449 if ((l == null) || (l.ending != LineEnding.Wrap)) {
3457 /// <summary>Give it a pixel offset coordinate and it returns the Line covering that are (offset
3458 /// is either X or Y depending on if we are multiline
3460 internal Line GetLineByPixel (int offset, bool exact)
3462 Line line = document;
3466 while (line != sentinel) {
3468 if ((offset >= line.Y) && (offset < (line.Y+line.height))) {
3470 } else if (offset < line.Y) {
3477 while (line != sentinel) {
3479 if ((offset >= line.X) && (offset < (line.X + line.Width)))
3481 else if (offset < line.X)
3494 // UIA: Method used via reflection in TextProviderBehavior
3496 // Give it x/y pixel coordinates and it returns the Tag at that position
3497 internal LineTag FindCursor (int x, int y, out int index)
3504 line = GetLineByPixel (multiline ? y : x, false);
3506 LineTag tag = line.GetTag (x);
3508 if (tag.Length == 0 && tag.Start == 1)
3511 index = tag.GetCharIndex (x - line.align_shift);
3516 /// <summary>Format area of document in specified font and color</summary>
3517 /// <param name="start_pos">1-based start position on start_line</param>
3518 /// <param name="end_pos">1-based end position on end_line </param>
3519 internal void FormatText (Line start_line, int start_pos, Line end_line, int end_pos, Font font,
3520 Color color, Color back_color, FormatSpecified specified)
3524 // First, format the first line
3525 if (start_line != end_line) {
3527 LineTag.FormatText(start_line, start_pos, start_line.text.Length - start_pos + 1, font, color, back_color, specified);
3530 LineTag.FormatText(end_line, 1, end_pos, font, color, back_color, specified);
3532 // Now all the lines inbetween
3533 for (int i = start_line.line_no + 1; i < end_line.line_no; i++) {
3535 LineTag.FormatText(l, 1, l.text.Length, font, color, back_color, specified);
3538 // Special case, single line
3539 LineTag.FormatText(start_line, start_pos, end_pos - start_pos, font, color, back_color, specified);
3541 if ((end_pos - start_pos) == 0 && CaretTag.Length != 0)
3542 CaretTag = CaretTag.Next;
3546 internal void RecalculateAlignments ()
3555 while (line_no <= lines) {
3556 line = GetLine(line_no);
3559 switch (line.alignment) {
3560 case HorizontalAlignment.Left:
3561 line.align_shift = 0;
3563 case HorizontalAlignment.Center:
3564 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3566 case HorizontalAlignment.Right:
3567 line.align_shift = viewport_width - (int)line.widths[line.text.Length] - right_margin;
3577 /// <summary>Calculate formatting for the whole document</summary>
3578 internal bool RecalculateDocument(Graphics g) {
3579 return RecalculateDocument(g, 1, this.lines, false);
3582 /// <summary>Calculate formatting starting at a certain line</summary>
3583 internal bool RecalculateDocument(Graphics g, int start) {
3584 return RecalculateDocument(g, start, this.lines, false);
3587 /// <summary>Calculate formatting within two given line numbers</summary>
3588 internal bool RecalculateDocument(Graphics g, int start, int end) {
3589 return RecalculateDocument(g, start, end, false);
3592 /// <summary>With optimize on, returns true if line heights changed</summary>
3593 internal bool RecalculateDocument(Graphics g, int start, int end, bool optimize) {
3601 if (recalc_suspended > 0) {
3602 recalc_pending = true;
3603 recalc_start = Math.Min (recalc_start, start);
3604 recalc_end = Math.Max (recalc_end, end);
3605 recalc_optimize = optimize;
3609 // Fixup the positions, they can go kinda nuts
3610 // (this is suspend and resume recalc - they set them to 1 and max)
3611 start = Math.Max (start, 1);
3612 end = Math.Min (end, lines);
3614 offset = GetLine(start).offset;
3619 changed = true; // We always return true if we run non-optimized
3624 while (line_no <= (end + this.lines - shift)) {
3625 line = GetLine(line_no++);
3626 line.offset = offset;
3628 // if we are not calculating a password
3631 line.RecalculateLine(g, this);
3633 if (line.recalc && line.RecalculateLine(g, this)) {
3635 // If the height changed, all subsequent lines change
3642 line.RecalculatePasswordLine(g, this);
3644 if (line.recalc && line.RecalculatePasswordLine(g, this)) {
3646 // If the height changed, all subsequent lines change
3653 if (line.widths[line.text.Length] > new_width) {
3654 new_width = (int)line.widths[line.text.Length];
3657 // Calculate alignment
3658 if (line.alignment != HorizontalAlignment.Left) {
3659 if (line.alignment == HorizontalAlignment.Center) {
3660 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3662 line.align_shift = viewport_width - (int)line.widths[line.text.Length] - 1;
3667 offset += line.height;
3669 offset += (int) line.widths [line.text.Length];
3671 if (line_no > lines) {
3676 if (document_x != new_width) {
3677 document_x = new_width;
3678 if (WidthChanged != null) {
3679 WidthChanged(this, null);
3683 RecalculateAlignments();
3685 line = GetLine(lines);
3687 if (document_y != line.Y + line.height) {
3688 document_y = line.Y + line.height;
3689 if (HeightChanged != null) {
3690 HeightChanged(this, null);
3694 // scan for links and tell us if its all
3695 // changed, so we can update everything
3697 ScanForLinks (start, end, ref changed);
3703 internal int Size() {
3707 private void owner_HandleCreated(object sender, EventArgs e) {
3708 RecalculateDocument(owner.CreateGraphicsInternal());
3712 private void owner_VisibleChanged(object sender, EventArgs e) {
3713 if (owner.Visible) {
3714 RecalculateDocument(owner.CreateGraphicsInternal());
3718 internal static bool IsWordSeparator (char ch)
3733 internal int FindWordSeparator(Line line, int pos, bool forward) {
3736 len = line.text.Length;
3739 for (int i = pos + 1; i < len; i++) {
3740 if (IsWordSeparator(line.Text[i])) {
3746 for (int i = pos - 1; i > 0; i--) {
3747 if (IsWordSeparator(line.Text[i - 1])) {
3755 /* Search document for text */
3756 internal bool FindChars(char[] chars, Marker start, Marker end, out Marker result) {
3762 // Search for occurence of any char in the chars array
3763 result = new Marker();
3766 line_no = start.line.line_no;
3768 while (line_no <= end.line.line_no) {
3769 line_len = line.text.Length;
3770 while (pos < line_len) {
3771 for (int i = 0; i < chars.Length; i++) {
3772 if (line.text[pos] == chars[i]) {
3774 if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
3788 line = GetLine(line_no);
3794 // This version does not build one big string for searching, instead it handles
3795 // line-boundaries, which is faster and less memory intensive
3796 // FIXME - Depending on culture stuff we might have to create a big string and use culturespecific
3797 // search stuff and change it to accept and return positions instead of Markers (which would match
3798 // RichTextBox behaviour better but would be inconsistent with the rest of TextControl)
3799 internal bool Find(string search, Marker start, Marker end, out Marker result, RichTextBoxFinds options) {
3801 string search_string;
3813 result = new Marker();
3814 word_option = ((options & RichTextBoxFinds.WholeWord) != 0);
3815 ignore_case = ((options & RichTextBoxFinds.MatchCase) == 0);
3816 reverse = ((options & RichTextBoxFinds.Reverse) != 0);
3819 line_no = start.line.line_no;
3823 // Prep our search string, lowercasing it if we do case-independent matching
3826 sb = new StringBuilder(search);
3827 for (int i = 0; i < sb.Length; i++) {
3828 sb[i] = Char.ToLower(sb[i]);
3830 search_string = sb.ToString();
3832 search_string = search;
3835 // We need to check if the character before our start position is a wordbreak
3838 if ((pos == 0) || (IsWordSeparator(line.text[pos - 1]))) {
3845 if (IsWordSeparator(line.text[pos - 1])) {
3851 // Need to check the end of the previous line
3854 prev_line = GetLine(line_no - 1);
3855 if (prev_line.ending == LineEnding.Wrap) {
3856 if (IsWordSeparator(prev_line.text[prev_line.text.Length - 1])) {
3870 // To avoid duplication of this loop with reverse logic, we search
3871 // through the document, remembering the last match and when returning
3872 // report that last remembered match
3874 last = new Marker();
3875 last.height = -1; // Abused - we use it to track change
3877 while (line_no <= end.line.line_no) {
3878 if (line_no != end.line.line_no) {
3879 line_len = line.text.Length;
3884 while (pos < line_len) {
3886 if (word_option && (current == search_string.Length)) {
3887 if (IsWordSeparator(line.text[pos])) {
3900 c = Char.ToLower(line.text[pos]);
3905 if (c == search_string[current]) {
3911 if (!word_option || (word_option && (word || (current > 0)))) {
3915 if (!word_option && (current == search_string.Length)) {
3932 if (IsWordSeparator(c)) {
3940 // Mark that we just saw a word boundary
3941 if (line.ending != LineEnding.Wrap || line.line_no == lines - 1) {
3945 if (current == search_string.Length) {
3961 line = GetLine(line_no);
3965 if (last.height != -1) {
3975 // if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
3987 internal void GetMarker(out Marker mark, bool start) {
3988 mark = new Marker();
3991 mark.line = GetLine(1);
3992 mark.tag = mark.line.tags;
3995 mark.line = GetLine(lines);
3996 mark.tag = mark.line.tags;
3997 while (mark.tag.Next != null) {
3998 mark.tag = mark.tag.Next;
4000 mark.pos = mark.line.text.Length;
4003 #endregion // Internal Methods
4006 internal event EventHandler CaretMoved;
4007 internal event EventHandler WidthChanged;
4008 internal event EventHandler HeightChanged;
4009 internal event EventHandler LengthChanged;
4010 #endregion // Events
4012 #region Administrative
4013 public IEnumerator GetEnumerator() {
4018 public override bool Equals(object obj) {
4023 if (!(obj is Document)) {
4031 if (ToString().Equals(((Document)obj).ToString())) {
4038 public override int GetHashCode() {
4042 public override string ToString() {
4043 return "document " + this.document_id;
4045 #endregion // Administrative
4048 internal class PictureTag : LineTag {
4050 internal RTF.Picture picture;
4052 internal PictureTag (Line line, int start, RTF.Picture picture) : base (line, start)
4054 this.picture = picture;
4057 public override bool IsTextTag {
4058 get { return false; }
4061 public override SizeF SizeOfPosition (Graphics dc, int pos)
4063 return picture.Size;
4066 internal override int MaxHeight ()
4068 return (int) (picture.Height + 0.5F);
4071 public override void Draw (Graphics dc, Color color, float xoff, float y, int start, int end)
4073 picture.DrawImage (dc, xoff + Line.widths [start], y, false);
4076 public override void Draw (Graphics dc, Color color, float xoff, float y, int start, int end, string text)
4078 picture.DrawImage (dc, xoff + + Line.widths [start], y, false);
4081 public override string Text ()
4087 internal class UndoManager {
4089 internal enum ActionType {
4093 // This is basically just cut & paste
4101 internal class Action {
4102 internal ActionType type;
4103 internal int line_no;
4105 internal object data;
4108 #region Local Variables
4109 private Document document;
4110 private Stack undo_actions;
4111 private Stack redo_actions;
4113 //private int caret_line;
4114 //private int caret_pos;
4116 // When performing an action, we lock the queue, so that the action can't be undone
4117 private bool locked;
4118 #endregion // Local Variables
4120 #region Constructors
4121 internal UndoManager (Document document)
4123 this.document = document;
4124 undo_actions = new Stack (50);
4125 redo_actions = new Stack (50);
4127 #endregion // Constructors
4130 internal bool CanUndo {
4131 get { return undo_actions.Count > 0; }
4134 internal bool CanRedo {
4135 get { return redo_actions.Count > 0; }
4138 internal string UndoActionName {
4140 foreach (Action action in undo_actions) {
4141 if (action.type == ActionType.UserActionBegin)
4142 return (string) action.data;
4143 if (action.type == ActionType.Typing)
4144 return Locale.GetText ("Typing");
4146 return String.Empty;
4150 internal string RedoActionName {
4152 foreach (Action action in redo_actions) {
4153 if (action.type == ActionType.UserActionBegin)
4154 return (string) action.data;
4155 if (action.type == ActionType.Typing)
4156 return Locale.GetText ("Typing");
4158 return String.Empty;
4161 #endregion // Properties
4163 #region Internal Methods
4164 internal void Clear ()
4166 undo_actions.Clear();
4167 redo_actions.Clear();
4170 internal bool Undo ()
4173 bool user_action_finished = false;
4175 if (undo_actions.Count == 0)
4181 action = (Action) undo_actions.Pop ();
4183 // Put onto redo stack
4184 redo_actions.Push(action);
4187 switch(action.type) {
4189 case ActionType.UserActionBegin:
4190 user_action_finished = true;
4193 case ActionType.UserActionEnd:
4197 case ActionType.InsertString:
4198 start = document.GetLine (action.line_no);
4199 document.SuspendUpdate ();
4200 document.DeleteMultiline (start, action.pos, ((string) action.data).Length + 1);
4201 document.PositionCaret (start, action.pos);
4202 document.SetSelectionToCaret (true);
4203 document.ResumeUpdate (true);
4206 case ActionType.Typing:
4207 start = document.GetLine (action.line_no);
4208 document.SuspendUpdate ();
4209 document.DeleteMultiline (start, action.pos, ((StringBuilder) action.data).Length);
4210 document.PositionCaret (start, action.pos);
4211 document.SetSelectionToCaret (true);
4212 document.ResumeUpdate (true);
4214 // This is an open ended operation, so only a single typing operation can be undone at once
4215 user_action_finished = true;
4218 case ActionType.DeleteString:
4219 start = document.GetLine (action.line_no);
4220 document.SuspendUpdate ();
4221 Insert (start, action.pos, (Line) action.data, true);
4222 document.ResumeUpdate (true);
4225 } while (!user_action_finished && undo_actions.Count > 0);
4232 internal bool Redo ()
4235 bool user_action_finished = false;
4237 if (redo_actions.Count == 0)
4245 action = (Action) redo_actions.Pop ();
4246 undo_actions.Push (action);
4248 switch (action.type) {
4250 case ActionType.UserActionBegin:
4254 case ActionType.UserActionEnd:
4255 user_action_finished = true;
4258 case ActionType.InsertString:
4259 start = document.GetLine (action.line_no);
4260 document.SuspendUpdate ();
4261 start_index = document.LineTagToCharIndex (start, action.pos);
4262 document.InsertString (start, action.pos, (string) action.data);
4263 document.CharIndexToLineTag (start_index + ((string) action.data).Length,
4264 out document.caret.line, out document.caret.tag,
4265 out document.caret.pos);
4266 document.UpdateCaret ();
4267 document.SetSelectionToCaret (true);
4268 document.ResumeUpdate (true);
4271 case ActionType.Typing:
4272 start = document.GetLine (action.line_no);
4273 document.SuspendUpdate ();
4274 start_index = document.LineTagToCharIndex (start, action.pos);
4275 document.InsertString (start, action.pos, ((StringBuilder) action.data).ToString ());
4276 document.CharIndexToLineTag (start_index + ((StringBuilder) action.data).Length,
4277 out document.caret.line, out document.caret.tag,
4278 out document.caret.pos);
4279 document.UpdateCaret ();
4280 document.SetSelectionToCaret (true);
4281 document.ResumeUpdate (true);
4283 // This is an open ended operation, so only a single typing operation can be undone at once
4284 user_action_finished = true;
4287 case ActionType.DeleteString:
4288 start = document.GetLine (action.line_no);
4289 document.SuspendUpdate ();
4290 document.DeleteMultiline (start, action.pos, ((Line) action.data).text.Length);
4291 document.PositionCaret (start, action.pos);
4292 document.SetSelectionToCaret (true);
4293 document.ResumeUpdate (true);
4297 } while (!user_action_finished && redo_actions.Count > 0);
4303 #endregion // Internal Methods
4305 #region Private Methods
4307 public void BeginUserAction (string name)
4312 // Nuke the redo queue
4313 redo_actions.Clear ();
4315 Action ua = new Action ();
4316 ua.type = ActionType.UserActionBegin;
4319 undo_actions.Push (ua);
4322 public void EndUserAction ()
4327 Action ua = new Action ();
4328 ua.type = ActionType.UserActionEnd;
4330 undo_actions.Push (ua);
4333 // start_pos, end_pos = 1 based
4334 public void RecordDeleteString (Line start_line, int start_pos, Line end_line, int end_pos)
4339 // Nuke the redo queue
4340 redo_actions.Clear ();
4342 Action a = new Action ();
4344 // We cant simply store the string, because then formatting would be lost
4345 a.type = ActionType.DeleteString;
4346 a.line_no = start_line.line_no;
4348 a.data = Duplicate (start_line, start_pos, end_line, end_pos);
4350 undo_actions.Push(a);
4353 public void RecordInsertString (Line line, int pos, string str)
4355 if (locked || str.Length == 0)
4358 // Nuke the redo queue
4359 redo_actions.Clear ();
4361 Action a = new Action ();
4363 a.type = ActionType.InsertString;
4365 a.line_no = line.line_no;
4368 undo_actions.Push (a);
4371 public void RecordTyping (Line line, int pos, char ch)
4376 // Nuke the redo queue
4377 redo_actions.Clear ();
4381 if (undo_actions.Count > 0)
4382 a = (Action) undo_actions.Peek ();
4384 if (a == null || a.type != ActionType.Typing) {
4386 a.type = ActionType.Typing;
4387 a.data = new StringBuilder ();
4388 a.line_no = line.line_no;
4391 undo_actions.Push (a);
4394 StringBuilder data = (StringBuilder) a.data;
4398 // start_pos = 1-based
4399 // end_pos = 1-based
4400 public Line Duplicate(Line start_line, int start_pos, Line end_line, int end_pos)
4406 LineTag current_tag;
4411 line = new Line (start_line.document, start_line.ending);
4414 for (int i = start_line.line_no; i <= end_line.line_no; i++) {
4415 current = document.GetLine(i);
4417 if (start_line.line_no == i) {
4423 if (end_line.line_no == i) {
4426 end = current.text.Length;
4433 line.text = new StringBuilder (current.text.ToString (start, end - start));
4435 // Copy tags from start to start+length onto new line
4436 current_tag = current.FindTag (start + 1);
4437 while ((current_tag != null) && (current_tag.Start <= end)) {
4438 if ((current_tag.Start <= start) && (start < (current_tag.Start + current_tag.Length))) {
4439 // start tag is within this tag
4442 tag_start = current_tag.Start;
4445 tag = new LineTag(line, tag_start - start + 1);
4446 tag.CopyFormattingFrom (current_tag);
4448 current_tag = current_tag.Next;
4450 // Add the new tag to the line
4451 if (line.tags == null) {
4457 while (tail.Next != null) {
4461 tag.Previous = tail;
4465 if ((i + 1) <= end_line.line_no) {
4466 line.ending = current.ending;
4468 // Chain them (we use right/left as next/previous)
4469 line.right = new Line (start_line.document, start_line.ending);
4470 line.right.left = line;
4478 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
4479 internal void Insert(Line line, int pos, Line insert, bool select)
4487 // Handle special case first
4488 if (insert.right == null) {
4490 // Single line insert
4491 document.Split(line, pos);
4493 if (insert.tags == null) {
4494 return; // Blank line
4497 //Insert our tags at the end
4500 while (tag.Next != null) {
4504 offset = tag.Start + tag.Length - 1;
4506 tag.Next = insert.tags;
4507 line.text.Insert(offset, insert.text.ToString());
4509 // Adjust start locations
4511 while (tag != null) {
4512 tag.Start += offset;
4516 // Put it back together
4517 document.Combine(line.line_no, line.line_no + 1);
4520 document.SetSelectionStart (line, pos, false);
4521 document.SetSelectionEnd (line, pos + insert.text.Length, false);
4524 document.UpdateView(line, pos);
4532 while (current != null) {
4534 if (current == insert) {
4535 // Inserting the first line we split the line (and make space)
4536 document.Split(line.line_no, pos);
4537 //Insert our tags at the end of the line
4541 if (tag != null && tag.Length != 0) {
4542 while (tag.Next != null) {
4545 offset = tag.Start + tag.Length - 1;
4546 tag.Next = current.tags;
4547 tag.Next.Previous = tag;
4553 line.tags = current.tags;
4554 line.tags.Previous = null;
4558 line.ending = current.ending;
4560 document.Split(line.line_no, 0);
4562 line.tags = current.tags;
4563 line.tags.Previous = null;
4564 line.ending = current.ending;
4568 // Adjust start locations and line pointers
4569 while (tag != null) {
4570 tag.Start += offset - 1;
4575 line.text.Insert(offset, current.text.ToString());
4576 line.Grow(line.text.Length);
4579 line = document.GetLine(line.line_no + 1);
4581 // FIXME? Test undo of line-boundaries
4582 if ((current.right == null) && (current.tags.Length != 0)) {
4583 document.Combine(line.line_no - 1, line.line_no);
4585 current = current.right;
4590 // Recalculate our document
4591 document.UpdateView(first, lines, pos);
4594 #endregion // Private Methods