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 Rectangle invalid;
236 internal int crlf_size; // 1 or 2, depending on whether we use \r\n or just \n
238 internal TextBoxBase owner; // Who's owning us?
239 static internal int caret_width = 1;
240 static internal int caret_shift = 1;
242 internal int left_margin = 2; // A left margin for all lines
243 internal int top_margin = 2;
244 internal int right_margin = 2;
245 #endregion // Local Variables
248 internal Document (TextBoxBase owner)
257 recalc_pending = false;
259 // Tree related stuff
260 sentinel = new Line (this, LineEnding.None);
261 sentinel.color = LineColor.Black;
265 // We always have a blank line
266 owner.HandleCreated += new EventHandler(owner_HandleCreated);
267 owner.VisibleChanged += new EventHandler(owner_VisibleChanged);
269 Add (1, String.Empty, owner.Font, owner.ForeColor, LineEnding.None);
271 undo = new UndoManager (this);
273 selection_visible = false;
274 selection_start.line = this.document;
275 selection_start.pos = 0;
276 selection_start.tag = selection_start.line.tags;
277 selection_end.line = this.document;
278 selection_end.pos = 0;
279 selection_end.tag = selection_end.line.tags;
280 selection_anchor.line = this.document;
281 selection_anchor.pos = 0;
282 selection_anchor.tag = selection_anchor.line.tags;
283 caret.line = this.document;
285 caret.tag = caret.line.tags;
295 // Default selection is empty
297 document_id = random.Next();
299 string_format.Trimming = StringTrimming.None;
300 string_format.FormatFlags = StringFormatFlags.DisplayFormatControl;
306 #region Internal Properties
323 internal Line CaretLine {
329 internal int CaretPosition {
335 internal Point Caret {
337 return new Point((int)caret.tag.Line.widths[caret.pos] + caret.line.X, caret.line.Y);
341 internal LineTag CaretTag {
351 internal int CRLFSize {
362 /// Whether text is scanned for links
364 internal bool EnableLinks {
365 get { return enable_links; }
366 set { enable_links = value; }
369 internal string PasswordChar {
371 return password_char;
375 password_char = value;
376 PasswordCache.Length = 0;
377 if ((password_char.Length != 0) && (password_char[0] != '\0')) {
385 private StringBuilder PasswordCache {
387 if (password_cache == null)
388 password_cache = new StringBuilder();
389 return password_cache;
393 internal int ViewPortX {
403 internal int Length {
405 return char_count + lines - 1; // Add \n for each line but the last
409 private int CharCount {
417 if (LengthChanged != null) {
418 LengthChanged(this, EventArgs.Empty);
423 internal int ViewPortY {
459 internal int ViewPortWidth {
461 return viewport_width;
465 viewport_width = value;
469 internal int ViewPortHeight {
471 return viewport_height;
475 viewport_height = value;
482 return this.document_x;
486 internal int Height {
488 return this.document_y;
492 internal bool SelectionVisible {
494 return selection_visible;
508 #endregion // Internal Properties
510 #region Private Methods
512 internal void UpdateMargins ()
514 switch (owner.actual_border_style) {
515 case BorderStyle.None:
520 case BorderStyle.FixedSingle:
525 case BorderStyle.Fixed3D:
533 internal void SuspendRecalc ()
535 if (recalc_suspended == 0) {
536 recalc_start = int.MaxValue;
537 recalc_end = int.MinValue;
543 internal void ResumeRecalc (bool immediate_update)
545 if (recalc_suspended > 0)
548 if (recalc_suspended == 0 && (immediate_update || recalc_pending) && !(recalc_start == int.MaxValue && recalc_end == int.MinValue)) {
549 RecalculateDocument (owner.CreateGraphicsInternal (), recalc_start, recalc_end, recalc_optimize);
550 recalc_pending = false;
554 internal void SuspendUpdate ()
559 internal void ResumeUpdate (bool immediate_update)
561 if (update_suspended > 0)
564 if (immediate_update && update_suspended == 0 && update_pending) {
565 UpdateView (GetLine (update_start), 0);
566 update_pending = false;
571 internal int DumpTree(Line line, bool with_tags) {
576 Console.Write("Line {0} [# {1}], Y: {2}, ending style: {3}, Text: '{4}'",
577 line.line_no, line.GetHashCode(), line.Y, line.ending,
578 line.text != null ? line.text.ToString() : "undefined");
580 if (line.left == sentinel) {
581 Console.Write(", left = sentinel");
582 } else if (line.left == null) {
583 Console.Write(", left = NULL");
586 if (line.right == sentinel) {
587 Console.Write(", right = sentinel");
588 } else if (line.right == null) {
589 Console.Write(", right = NULL");
592 Console.WriteLine("");
602 Console.Write(" Tags: ");
603 while (tag != null) {
604 Console.Write("{0} <{1}>-<{2}>", count++, tag.Start, tag.End
605 /*line.text.ToString (tag.start - 1, tag.length)*/);
606 length += tag.Length;
608 if (tag.Line != line) {
609 Console.Write("BAD line link");
610 throw new Exception("Bad line link in tree");
617 if (length > line.text.Length) {
618 throw new Exception(String.Format("Length of tags more than length of text on line (expected {0} calculated {1})", line.text.Length, length));
619 } else if (length < line.text.Length) {
620 throw new Exception(String.Format("Length of tags less than length of text on line (expected {0} calculated {1})", line.text.Length, length));
622 Console.WriteLine("");
624 if (line.left != null) {
625 if (line.left != sentinel) {
626 total += DumpTree(line.left, with_tags);
629 if (line != sentinel) {
630 throw new Exception("Left should not be NULL");
634 if (line.right != null) {
635 if (line.right != sentinel) {
636 total += DumpTree(line.right, with_tags);
639 if (line != sentinel) {
640 throw new Exception("Right should not be NULL");
644 for (int i = 1; i <= this.lines; i++) {
645 if (GetLine(i) == null) {
646 throw new Exception(String.Format("Hole in line order, missing {0}", i));
650 if (line == this.Root) {
651 if (total < this.lines) {
652 throw new Exception(String.Format("Not enough nodes in tree, found {0}, expected {1}", total, this.lines));
653 } else if (total > this.lines) {
654 throw new Exception(String.Format("Too many nodes in tree, found {0}, expected {1}", total, this.lines));
661 private void SetSelectionVisible (bool value)
663 selection_visible = value;
665 // cursor and selection are enemies, we can't have both in the same room at the same time
666 if (owner.IsHandleCreated && !owner.show_caret_w_selection)
667 XplatUI.CaretVisible (owner.Handle, !selection_visible);
670 private void DecrementLines(int line_no) {
674 while (current <= lines) {
675 GetLine(current).line_no--;
681 private void IncrementLines(int line_no) {
684 current = this.lines;
685 while (current >= line_no) {
686 GetLine(current).line_no++;
692 private void RebalanceAfterAdd(Line line1) {
695 while ((line1 != document) && (line1.parent.color == LineColor.Red)) {
696 if (line1.parent == line1.parent.parent.left) {
697 line2 = line1.parent.parent.right;
699 if ((line2 != null) && (line2.color == LineColor.Red)) {
700 line1.parent.color = LineColor.Black;
701 line2.color = LineColor.Black;
702 line1.parent.parent.color = LineColor.Red;
703 line1 = line1.parent.parent;
705 if (line1 == line1.parent.right) {
706 line1 = line1.parent;
710 line1.parent.color = LineColor.Black;
711 line1.parent.parent.color = LineColor.Red;
713 RotateRight(line1.parent.parent);
716 line2 = line1.parent.parent.left;
718 if ((line2 != null) && (line2.color == LineColor.Red)) {
719 line1.parent.color = LineColor.Black;
720 line2.color = LineColor.Black;
721 line1.parent.parent.color = LineColor.Red;
722 line1 = line1.parent.parent;
724 if (line1 == line1.parent.left) {
725 line1 = line1.parent;
729 line1.parent.color = LineColor.Black;
730 line1.parent.parent.color = LineColor.Red;
731 RotateLeft(line1.parent.parent);
735 document.color = LineColor.Black;
738 private void RebalanceAfterDelete(Line line1) {
741 while ((line1 != document) && (line1.color == LineColor.Black)) {
742 if (line1 == line1.parent.left) {
743 line2 = line1.parent.right;
744 if (line2.color == LineColor.Red) {
745 line2.color = LineColor.Black;
746 line1.parent.color = LineColor.Red;
747 RotateLeft(line1.parent);
748 line2 = line1.parent.right;
750 if ((line2.left.color == LineColor.Black) && (line2.right.color == LineColor.Black)) {
751 line2.color = LineColor.Red;
752 line1 = line1.parent;
754 if (line2.right.color == LineColor.Black) {
755 line2.left.color = LineColor.Black;
756 line2.color = LineColor.Red;
758 line2 = line1.parent.right;
760 line2.color = line1.parent.color;
761 line1.parent.color = LineColor.Black;
762 line2.right.color = LineColor.Black;
763 RotateLeft(line1.parent);
767 line2 = line1.parent.left;
768 if (line2.color == LineColor.Red) {
769 line2.color = LineColor.Black;
770 line1.parent.color = LineColor.Red;
771 RotateRight(line1.parent);
772 line2 = line1.parent.left;
774 if ((line2.right.color == LineColor.Black) && (line2.left.color == LineColor.Black)) {
775 line2.color = LineColor.Red;
776 line1 = line1.parent;
778 if (line2.left.color == LineColor.Black) {
779 line2.right.color = LineColor.Black;
780 line2.color = LineColor.Red;
782 line2 = line1.parent.left;
784 line2.color = line1.parent.color;
785 line1.parent.color = LineColor.Black;
786 line2.left.color = LineColor.Black;
787 RotateRight(line1.parent);
792 line1.color = LineColor.Black;
795 private void RotateLeft(Line line1) {
796 Line line2 = line1.right;
798 line1.right = line2.left;
800 if (line2.left != sentinel) {
801 line2.left.parent = line1;
804 if (line2 != sentinel) {
805 line2.parent = line1.parent;
808 if (line1.parent != null) {
809 if (line1 == line1.parent.left) {
810 line1.parent.left = line2;
812 line1.parent.right = line2;
819 if (line1 != sentinel) {
820 line1.parent = line2;
824 private void RotateRight(Line line1) {
825 Line line2 = line1.left;
827 line1.left = line2.right;
829 if (line2.right != sentinel) {
830 line2.right.parent = line1;
833 if (line2 != sentinel) {
834 line2.parent = line1.parent;
837 if (line1.parent != null) {
838 if (line1 == line1.parent.right) {
839 line1.parent.right = line2;
841 line1.parent.left = line2;
848 if (line1 != sentinel) {
849 line1.parent = line2;
854 internal void UpdateView(Line line, int pos) {
855 if (!owner.IsHandleCreated) {
859 if (update_suspended > 0) {
860 update_start = Math.Min (update_start, line.line_no);
861 // update_end = Math.Max (update_end, line.line_no);
862 // recalc_optimize = true;
863 update_pending = true;
867 // Optimize invalidation based on Line alignment
868 if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no, true)) {
869 // Lineheight changed, invalidate the rest of the document
870 if ((line.Y - viewport_y) >=0 ) {
871 // We formatted something that's in view, only draw parts of the screen
872 owner.Invalidate(new Rectangle(
874 line.Y - viewport_y + offset_y,
876 owner.Height - line.Y - viewport_y));
878 // The tag was above the visible area, draw everything
882 switch(line.alignment) {
883 case HorizontalAlignment.Left: {
884 owner.Invalidate(new Rectangle(
885 line.X + ((int)line.widths[pos] - viewport_x - 1) + offset_x,
886 line.Y - viewport_y + offset_y,
892 case HorizontalAlignment.Center: {
893 owner.Invalidate(new Rectangle(
895 line.Y - viewport_y + offset_y,
901 case HorizontalAlignment.Right: {
902 owner.Invalidate(new Rectangle(
904 line.Y - viewport_y + offset_y,
905 (int)line.widths[pos + 1] - viewport_x + line.X,
914 // Update display from line, down line_count lines; pos is unused, but required for the signature
915 internal void UpdateView(Line line, int line_count, int pos) {
916 if (!owner.IsHandleCreated) {
920 if (recalc_suspended > 0) {
921 recalc_start = Math.Min (recalc_start, line.line_no);
922 recalc_end = Math.Max (recalc_end, line.line_no + line_count);
923 recalc_optimize = true;
924 recalc_pending = true;
928 int start_line_top = line.Y;
933 end_line = GetLine (line.line_no + line_count);
934 if (end_line == null)
935 end_line = GetLine (lines);
938 end_line_bottom = end_line.Y + end_line.height;
940 if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no + line_count, true)) {
941 // Lineheight changed, invalidate the rest of the document
942 if ((line.Y - viewport_y) >=0 ) {
943 // We formatted something that's in view, only draw parts of the screen
944 owner.Invalidate(new Rectangle(
946 line.Y - viewport_y + offset_y,
948 owner.Height - line.Y - viewport_y));
950 // The tag was above the visible area, draw everything
954 int x = 0 - viewport_x + offset_x;
955 int w = viewport_width;
956 int y = Math.Min (start_line_top - viewport_y, line.Y - viewport_y) + offset_y;
957 int h = Math.Max (end_line_bottom - y, end_line.Y + end_line.height - y);
959 owner.Invalidate (new Rectangle (x, y, w, h));
964 /// Scans the next paragraph for http:/ ftp:/ www. https:/ etc and marks the tags
967 /// <param name="start_line">The line to start on</param>
968 /// <param name="link_changed">marks as true if something is changed</param>
969 private void ScanForLinks (Line start_line, ref bool link_changed)
971 Line current_line = start_line;
972 StringBuilder line_no_breaks = new StringBuilder ();
973 StringBuilder line_link_record = new StringBuilder ();
974 ArrayList cumulative_length_list = new ArrayList ();
975 bool update_caret_tag = false;
977 cumulative_length_list.Add (0);
979 while (current_line != null) {
980 line_no_breaks.Append (current_line.text);
982 if (link_changed == false)
983 current_line.LinkRecord (line_link_record);
985 current_line.ClearLinks ();
987 cumulative_length_list.Add (line_no_breaks.Length);
989 if (current_line.ending == LineEnding.Wrap)
990 current_line = GetLine (current_line.LineNo + 1);
995 // search for protocols.. make sure www. is first!
996 string [] search_terms = new string [] { "www.", "http:/", "ftp:/", "https:/" };
997 int search_found = 0;
999 string line_no_breaks_string = line_no_breaks.ToString ();
1000 int line_no_breaks_index = 0;
1004 if (line_no_breaks_index >= line_no_breaks_string.Length)
1007 index_found = FirstIndexOfAny (line_no_breaks_string, search_terms, line_no_breaks_index, out search_found);
1009 //no links found on this line
1010 if (index_found == -1)
1013 if (search_found == 0) {
1014 // if we are at the end of the line to analyse and the end of the line
1015 // is "www." then there are no links here
1016 if (line_no_breaks_string.Length == index_found + search_terms [0].Length)
1019 // if after www. we don't have a letter a digit or a @ or - or /
1020 // then it is not a web address, we should continue searching
1021 if (char.IsLetterOrDigit (line_no_breaks_string [index_found + search_terms [0].Length]) == false &&
1022 "@/~".IndexOf (line_no_breaks_string [index_found + search_terms [0].Length].ToString ()) == -1) {
1023 line_no_breaks_index = index_found + search_terms [0].Length;
1028 link_end = line_no_breaks_string.Length - 1;
1029 line_no_breaks_index = line_no_breaks_string.Length;
1031 // we've found a link, we just need to find where it ends now
1032 for (int i = index_found + search_terms [search_found].Length; i < line_no_breaks_string.Length; i++) {
1033 if (line_no_breaks_string [i - 1] == '.') {
1034 if (char.IsLetterOrDigit (line_no_breaks_string [i]) == false &&
1035 "@/~".IndexOf (line_no_breaks_string [i].ToString ()) == -1) {
1037 line_no_breaks_index = i;
1041 if (char.IsLetterOrDigit (line_no_breaks_string [i]) == false &&
1042 "@-/:~.?=_&".IndexOf (line_no_breaks_string [i].ToString ()) == -1) {
1044 line_no_breaks_index = i;
1050 string link_text = line_no_breaks_string.Substring (index_found, link_end - index_found + 1);
1051 int current_cumulative = 0;
1053 // we've found a link - index_found -> link_end
1054 // now we just make all the tags as containing link and
1055 // point them to the text for the whole link
1057 current_line = start_line;
1059 //find the line we start on
1060 for (current_cumulative = 1; current_cumulative < cumulative_length_list.Count; current_cumulative++)
1061 if ((int)cumulative_length_list [current_cumulative] > index_found)
1064 current_line = GetLine (start_line.LineNo + current_cumulative - 1);
1066 // find the tag we start on
1067 LineTag current_tag = current_line.FindTag (index_found - (int)cumulative_length_list [current_cumulative - 1] + 1);
1069 if (current_tag.Start != (index_found - (int)cumulative_length_list [current_cumulative - 1]) + 1) {
1070 if (current_tag == CaretTag)
1071 update_caret_tag = true;
1073 current_tag = current_tag.Break ((index_found - (int)cumulative_length_list [current_cumulative - 1]) + 1);
1077 current_tag.IsLink = true;
1078 current_tag.LinkText = link_text;
1080 //go through each character
1081 // find the tag we are in
1082 // skip the number of characters in the tag
1083 for (int i = 1; i < link_text.Length; i++) {
1084 // on to a new word-wrapped line
1085 if ((int)cumulative_length_list [current_cumulative] <= index_found + i) {
1087 current_line = GetLine (start_line.LineNo + current_cumulative++);
1088 current_tag = current_line.FindTag (index_found + i - (int)cumulative_length_list [current_cumulative - 1] + 1);
1090 current_tag.IsLink = true;
1091 current_tag.LinkText = link_text;
1096 if (current_tag.End < index_found + 1 + i - (int)cumulative_length_list [current_cumulative - 1]) {
1097 // skip empty tags in the middle of the URL
1099 current_tag = current_tag.Next;
1100 } while (current_tag.Length == 0);
1102 current_tag.IsLink = true;
1103 current_tag.LinkText = link_text;
1107 //if there are characters left in the tag after the link
1109 // make the second part a non link
1110 if (current_tag.End > (index_found + link_text.Length + 1) - (int)cumulative_length_list [current_cumulative - 1]) {
1111 if (current_tag == CaretTag)
1112 update_caret_tag = true;
1114 current_tag.Break ((index_found + link_text.Length + 1) - (int)cumulative_length_list [current_cumulative - 1]);
1118 if (update_caret_tag) {
1119 CaretTag = LineTag.FindTag (CaretLine, CaretPosition);
1120 link_changed = true;
1122 if (link_changed == false) {
1123 current_line = start_line;
1124 StringBuilder new_link_record = new StringBuilder ();
1126 while (current_line != null) {
1127 current_line.LinkRecord (new_link_record);
1129 if (current_line.ending == LineEnding.Wrap)
1130 current_line = GetLine (current_line.LineNo + 1);
1135 if (new_link_record.Equals (line_link_record) == false)
1136 link_changed = true;
1141 private int FirstIndexOfAny (string haystack, string [] needles, int start_index, out int term_found)
1144 int best_index = -1;
1146 for (int i = 0; i < needles.Length; i++) {
1148 int index = haystack.IndexOf (needles [i], start_index, StringComparison.InvariantCultureIgnoreCase);
1150 int index = haystack.ToLower().IndexOf(needles[i], start_index);
1154 if (term_found > -1) {
1155 if (index < best_index) {
1171 private void InvalidateLinks (Rectangle clip)
1173 for (int i = (owner.list_links.Count - 1); i >= 0; i--) {
1174 TextBoxBase.LinkRectangle link = (TextBoxBase.LinkRectangle) owner.list_links [i];
1176 if (clip.IntersectsWith (link.LinkAreaRectangle))
1177 owner.list_links.RemoveAt (i);
1180 #endregion // Private Methods
1182 #region Internal Methods
1184 internal void ScanForLinks (int start, int end, ref bool link_changed)
1187 LineEnding lastending = LineEnding.Rich;
1189 // make sure we start scanning at the real begining of the line
1191 if (start != 1 && GetLine (start - 1).ending == LineEnding.Wrap)
1197 for (int i = start; i <= end && i <= lines; i++) {
1200 if (lastending != LineEnding.Wrap)
1201 ScanForLinks (line, ref link_changed);
1203 lastending = line.ending;
1205 if (lastending == LineEnding.Wrap && (i + 1) <= end)
1210 // Clear the document and reset state
1211 internal void Empty() {
1213 document = sentinel;
1216 // We always have a blank line
1217 Add (1, String.Empty, owner.Font, owner.ForeColor, LineEnding.None);
1219 this.RecalculateDocument(owner.CreateGraphicsInternal());
1220 PositionCaret(0, 0);
1222 SetSelectionVisible (false);
1224 selection_start.line = this.document;
1225 selection_start.pos = 0;
1226 selection_start.tag = selection_start.line.tags;
1227 selection_end.line = this.document;
1228 selection_end.pos = 0;
1229 selection_end.tag = selection_end.line.tags;
1238 if (owner.IsHandleCreated)
1239 owner.Invalidate ();
1242 internal void PositionCaret(Line line, int pos) {
1243 caret.tag = line.FindTag (pos);
1245 MoveCaretToTextTag ();
1250 if (owner.IsHandleCreated) {
1251 if (owner.Focused) {
1252 if (caret.height != caret.tag.Height)
1253 XplatUI.CreateCaret (owner.Handle, caret_width, caret.height);
1254 XplatUI.SetCaretPos(owner.Handle,
1255 offset_x + (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x,
1256 offset_y + caret.line.Y + caret.tag.Shift - viewport_y + caret_shift);
1259 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1262 // We set this at the end because we use the heights to determine whether or
1263 // not we need to recreate the caret
1264 caret.height = caret.tag.Height;
1268 internal void PositionCaret(int x, int y) {
1269 if (!owner.IsHandleCreated) {
1273 caret.tag = FindCursor(x, y, out caret.pos);
1275 MoveCaretToTextTag ();
1277 caret.line = caret.tag.Line;
1278 caret.height = caret.tag.Height;
1280 if (owner.ShowSelection && (!selection_visible || owner.show_caret_w_selection)) {
1281 XplatUI.CreateCaret (owner.Handle, caret_width, caret.height);
1282 XplatUI.SetCaretPos(owner.Handle,
1283 (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x + offset_x,
1284 offset_y + caret.line.Y + caret.tag.Shift - viewport_y + caret_shift);
1287 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1290 internal void CaretHasFocus() {
1291 if ((caret.tag != null) && owner.IsHandleCreated) {
1292 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1293 XplatUI.SetCaretPos(owner.Handle,
1294 offset_x + (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x,
1295 offset_y + caret.line.Y + caret.tag.Shift - viewport_y + caret_shift);
1300 if (owner.IsHandleCreated && SelectionLength () > 0) {
1301 InvalidateSelectionArea ();
1305 internal void CaretLostFocus() {
1306 if (!owner.IsHandleCreated) {
1309 XplatUI.DestroyCaret(owner.Handle);
1312 internal void AlignCaret ()
1317 internal void AlignCaret(bool changeCaretTag) {
1318 if (!owner.IsHandleCreated) {
1322 if (changeCaretTag) {
1323 caret.tag = LineTag.FindTag (caret.line, caret.pos);
1325 MoveCaretToTextTag ();
1328 // if the caret has had SelectionFont changed to a
1329 // different height, we reflect changes unless the new
1330 // font is larger than the line (line recalculations
1331 // ignore empty tags) in which case we make it equal
1332 // the line height and then when text is entered
1333 if (caret.tag.Height > caret.tag.Line.Height) {
1334 caret.height = caret.line.height;
1336 caret.height = caret.tag.Height;
1339 if (owner.Focused) {
1340 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1341 XplatUI.SetCaretPos (owner.Handle,
1342 offset_x + (int) caret.tag.Line.widths [caret.pos] + caret.line.X - viewport_x,
1343 offset_y + caret.line.Y + viewport_y + caret_shift);
1347 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1350 internal void UpdateCaret() {
1351 if (!owner.IsHandleCreated || caret.tag == null) {
1355 MoveCaretToTextTag ();
1357 if (caret.tag.Height != caret.height) {
1358 caret.height = caret.tag.Height;
1359 if (owner.Focused) {
1360 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1364 if (owner.Focused) {
1365 XplatUI.SetCaretPos(owner.Handle,
1366 offset_x + (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x,
1367 offset_y + caret.line.Y + caret.tag.Shift - viewport_y + caret_shift);
1371 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1374 internal void DisplayCaret() {
1375 if (!owner.IsHandleCreated) {
1379 if (owner.ShowSelection && (!selection_visible || owner.show_caret_w_selection)) {
1380 XplatUI.CaretVisible(owner.Handle, true);
1384 internal void HideCaret() {
1385 if (!owner.IsHandleCreated) {
1389 if (owner.Focused) {
1390 XplatUI.CaretVisible(owner.Handle, false);
1395 internal void MoveCaretToTextTag ()
1397 if (caret.tag == null || caret.tag.IsTextTag)
1402 if (caret.pos < caret.tag.Start) {
1403 caret.tag = caret.tag.Previous;
1405 caret.tag = caret.tag.Next;
1409 internal void MoveCaret(CaretDirection direction) {
1410 // FIXME should we use IsWordSeparator to detect whitespace, instead
1411 // of looking for actual spaces in the Word move cases?
1413 bool nowrap = false;
1415 case CaretDirection.CharForwardNoWrap:
1417 goto case CaretDirection.CharForward;
1418 case CaretDirection.CharForward: {
1420 if (caret.pos > caret.line.TextLengthWithoutEnding ()) {
1422 // Go into next line
1423 if (caret.line.line_no < this.lines) {
1424 caret.line = GetLine(caret.line.line_no+1);
1426 caret.tag = caret.line.tags;
1431 // Single line; we stay where we are
1435 if ((caret.tag.Start - 1 + caret.tag.Length) < caret.pos) {
1436 caret.tag = caret.tag.Next;
1443 case CaretDirection.CharBackNoWrap:
1445 goto case CaretDirection.CharBack;
1446 case CaretDirection.CharBack: {
1447 if (caret.pos > 0) {
1448 // caret.pos--; // folded into the if below
1450 if (--caret.pos > 0) {
1451 if (caret.tag.Start > caret.pos) {
1452 caret.tag = caret.tag.Previous;
1456 if (caret.line.line_no > 1 && !nowrap) {
1457 caret.line = GetLine(caret.line.line_no - 1);
1458 caret.pos = caret.line.TextLengthWithoutEnding ();
1459 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1466 case CaretDirection.WordForward: {
1469 len = caret.line.text.Length;
1470 if (caret.pos < len) {
1471 while ((caret.pos < len) && (caret.line.text[caret.pos] != ' ')) {
1474 if (caret.pos < len) {
1475 // Skip any whitespace
1476 while ((caret.pos < len) && (caret.line.text[caret.pos] == ' ')) {
1480 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1482 if (caret.line.line_no < this.lines) {
1483 caret.line = GetLine(caret.line.line_no + 1);
1485 caret.tag = caret.line.tags;
1492 case CaretDirection.WordBack: {
1493 if (caret.pos > 0) {
1496 while ((caret.pos > 0) && (caret.line.text[caret.pos] == ' ')) {
1500 while ((caret.pos > 0) && (caret.line.text[caret.pos] != ' ')) {
1504 if (caret.line.text.ToString(caret.pos, 1) == " ") {
1505 if (caret.pos != 0) {
1508 caret.line = GetLine(caret.line.line_no - 1);
1509 caret.pos = caret.line.text.Length;
1512 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1514 if (caret.line.line_no > 1) {
1515 caret.line = GetLine(caret.line.line_no - 1);
1516 caret.pos = caret.line.text.Length;
1517 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1524 case CaretDirection.LineUp: {
1525 if (caret.line.line_no > 1) {
1528 pixel = (int)caret.line.widths[caret.pos];
1529 PositionCaret(pixel, GetLine(caret.line.line_no - 1).Y);
1536 case CaretDirection.LineDown: {
1537 if (caret.line.line_no < lines) {
1540 pixel = (int)caret.line.widths[caret.pos];
1541 PositionCaret(pixel, GetLine(caret.line.line_no + 1).Y);
1548 case CaretDirection.Home: {
1549 if (caret.pos > 0) {
1551 caret.tag = caret.line.tags;
1557 case CaretDirection.End: {
1558 if (caret.pos < caret.line.TextLengthWithoutEnding ()) {
1559 caret.pos = caret.line.TextLengthWithoutEnding ();
1560 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1566 case CaretDirection.PgUp: {
1568 if (caret.line.line_no == 1 && owner.richtext) {
1569 owner.vscroll.Value = 0;
1570 Line line = GetLine (1);
1571 PositionCaret (line, 0);
1574 int y_offset = caret.line.Y + caret.line.height - 1 - viewport_y;
1576 LineTag top = FindCursor ((int) caret.line.widths [caret.pos],
1577 viewport_y - viewport_height, out index);
1579 owner.vscroll.Value = Math.Min (top.Line.Y, owner.vscroll.Maximum - viewport_height);
1580 PositionCaret ((int) caret.line.widths [caret.pos], y_offset + viewport_y);
1585 case CaretDirection.PgDn: {
1587 if (caret.line.line_no == lines && owner.richtext) {
1588 owner.vscroll.Value = owner.vscroll.Maximum - viewport_height + 1;
1589 Line line = GetLine (lines);
1590 PositionCaret (line, line.TextLengthWithoutEnding());
1593 int y_offset = caret.line.Y - viewport_y;
1595 LineTag top = FindCursor ((int) caret.line.widths [caret.pos],
1596 viewport_y + viewport_height, out index);
1598 owner.vscroll.Value = Math.Min (top.Line.Y, owner.vscroll.Maximum - viewport_height);
1599 PositionCaret ((int) caret.line.widths [caret.pos], y_offset + viewport_y);
1604 case CaretDirection.CtrlPgUp: {
1605 PositionCaret(0, viewport_y);
1610 case CaretDirection.CtrlPgDn: {
1615 tag = FindCursor (0, viewport_y + viewport_height, out index);
1616 if (tag.Line.line_no > 1) {
1617 line = GetLine(tag.Line.line_no - 1);
1621 PositionCaret(line, line.Text.Length);
1626 case CaretDirection.CtrlHome: {
1627 caret.line = GetLine(1);
1629 caret.tag = caret.line.tags;
1635 case CaretDirection.CtrlEnd: {
1636 caret.line = GetLine(lines);
1637 caret.pos = caret.line.TextLengthWithoutEnding ();
1638 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1644 case CaretDirection.SelectionStart: {
1645 caret.line = selection_start.line;
1646 caret.pos = selection_start.pos;
1647 caret.tag = selection_start.tag;
1653 case CaretDirection.SelectionEnd: {
1654 caret.line = selection_end.line;
1655 caret.pos = selection_end.pos;
1656 caret.tag = selection_end.tag;
1664 internal void DumpDoc ()
1666 Console.WriteLine ("<doc lines='{0}'>", lines);
1667 for (int i = 1; i <= lines ; i++) {
1668 Line line = GetLine (i);
1669 Console.WriteLine ("<line no='{0}' ending='{1}'>", line.line_no, line.ending);
1671 LineTag tag = line.tags;
1672 while (tag != null) {
1673 Console.Write ("\t<tag type='{0}' span='{1}->{2}' font='{3}' color='{4}'>",
1674 tag.GetType (), tag.Start, tag.Length, tag.Font, tag.Color);
1675 Console.Write (tag.Text ());
1676 Console.WriteLine ("</tag>");
1679 Console.WriteLine ("</line>");
1681 Console.WriteLine ("</doc>");
1684 internal void Draw (Graphics g, Rectangle clip)
1686 Line line; // Current line being drawn
1687 LineTag tag; // Current tag being drawn
1688 int start; // First line to draw
1689 int end; // Last line to draw
1690 StringBuilder text; // String representing the current line
1693 Color current_color;
1695 // First, figure out from what line to what line we need to draw
1698 start = GetLineByPixel(clip.Top + viewport_y - offset_y, false).line_no;
1699 end = GetLineByPixel(clip.Bottom + viewport_y - offset_y, false).line_no;
1701 start = GetLineByPixel(clip.Left + viewport_x - offset_x, false).line_no;
1702 end = GetLineByPixel(clip.Right + viewport_x - offset_x, false).line_no;
1705 // remove links in the list (used for mouse down events) that are within the clip area.
1706 InvalidateLinks (clip);
1709 /// We draw the single border ourself
1711 if (owner.actual_border_style == BorderStyle.FixedSingle) {
1712 ControlPaint.DrawBorder (g, owner.ClientRectangle, Color.Black, ButtonBorderStyle.Solid);
1715 /// Make sure that we aren't drawing one more line then we need to
1716 line = GetLine (end - 1);
1717 if (line != null && clip.Bottom == offset_y + line.Y + line.height - viewport_y)
1723 DateTime n = DateTime.Now;
1724 Console.WriteLine ("Started drawing: {0}s {1}ms", n.Second, n.Millisecond);
1725 Console.WriteLine ("CLIP: {0}", clip);
1726 Console.WriteLine ("S: {0}", GetLine (start).text);
1727 Console.WriteLine ("E: {0}", GetLine (end).text);
1730 // Non multiline selection can be handled outside of the loop
1731 if (!multiline && selection_visible && owner.ShowSelection) {
1732 g.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (ThemeEngine.Current.ColorHighlight),
1733 offset_x + selection_start.line.widths [selection_start.pos] +
1734 selection_start.line.X - viewport_x,
1735 offset_y + selection_start.line.Y,
1736 (selection_end.line.X + selection_end.line.widths [selection_end.pos]) -
1737 (selection_start.line.X + selection_start.line.widths [selection_start.pos]),
1738 selection_start.line.height);
1741 while (line_no <= end) {
1742 line = GetLine (line_no);
1743 float line_y = line.Y - viewport_y + offset_y;
1749 if (PasswordCache.Length < line.text.Length)
1750 PasswordCache.Append(Char.Parse(password_char), line.text.Length - PasswordCache.Length);
1751 else if (PasswordCache.Length > line.text.Length)
1752 PasswordCache.Remove(line.text.Length, PasswordCache.Length - line.text.Length);
1753 text = PasswordCache;
1756 int line_selection_start = text.Length + 1;
1757 int line_selection_end = text.Length + 1;
1758 if (selection_visible && owner.ShowSelection &&
1759 (line_no >= selection_start.line.line_no) &&
1760 (line_no <= selection_end.line.line_no)) {
1762 if (line_no == selection_start.line.line_no)
1763 line_selection_start = selection_start.pos + 1;
1765 line_selection_start = 1;
1767 if (line_no == selection_end.line.line_no)
1768 line_selection_end = selection_end.pos + 1;
1770 line_selection_end = text.Length + 1;
1772 if (line_selection_end == line_selection_start) {
1773 // There isn't really selection
1774 line_selection_start = text.Length + 1;
1775 line_selection_end = line_selection_start;
1776 } else if (multiline) {
1777 // lets draw some selection baby!! (non multiline selection is drawn outside the loop)
1778 g.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (ThemeEngine.Current.ColorHighlight),
1779 offset_x + line.widths [line_selection_start - 1] + line.X - viewport_x,
1780 line_y, line.widths [line_selection_end - 1] - line.widths [line_selection_start - 1],
1785 current_color = line.tags.ColorToDisplay;
1786 while (tag != null) {
1789 if (tag.Length == 0) {
1794 if (((tag.X + tag.Width) < (clip.Left - viewport_x - offset_x)) &&
1795 (tag.X > (clip.Right - viewport_x - offset_x))) {
1800 if (tag.BackColor != Color.Empty) {
1801 g.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (tag.BackColor),
1802 offset_x + tag.X + line.X - viewport_x,
1803 line_y + tag.Shift, tag.Width, line.height);
1806 tag_color = tag.ColorToDisplay;
1807 current_color = tag_color;
1809 if (!owner.Enabled) {
1810 Color a = tag.Color;
1811 Color b = ThemeEngine.Current.ColorWindowText;
1813 if ((a.R == b.R) && (a.G == b.G) && (a.B == b.B))
1814 tag_color = ThemeEngine.Current.ColorGrayText;
1818 int tag_pos = tag.Start;
1819 current_color = tag_color;
1820 while (tag_pos < tag.Start + tag.Length) {
1821 int old_tag_pos = tag_pos;
1823 if (tag_pos >= line_selection_start && tag_pos < line_selection_end) {
1824 current_color = ThemeEngine.Current.ColorHighlightText;
1825 tag_pos = Math.Min (tag.End, line_selection_end);
1826 } else if (tag_pos < line_selection_start) {
1827 current_color = tag_color;
1828 tag_pos = Math.Min (tag.End, line_selection_start);
1830 current_color = tag_color;
1834 Rectangle text_size;
1836 tag.Draw (g, current_color,
1837 offset_x + line.X - viewport_x,
1839 old_tag_pos - 1, Math.Min (tag.Start + tag.Length, tag_pos) - 1,
1840 text.ToString (), out text_size, tag.IsLink);
1843 TextBoxBase.LinkRectangle link = new TextBoxBase.LinkRectangle (text_size);
1845 owner.list_links.Add (link);
1851 line.DrawEnding (g, line_y);
1856 private int GetLineEnding (string line, int start, out LineEnding ending)
1861 if (start >= line.Length) {
1862 ending = LineEnding.Wrap;
1866 res = line.IndexOf ('\r', start);
1867 rich_index = line.IndexOf ('\n', start);
1869 // Handle the case where we find both of them, and the \n is before the \r
1870 if (res != -1 && rich_index != -1)
1871 if (rich_index < res) {
1872 ending = LineEnding.Rich;
1877 if (res + 2 < line.Length && line [res + 1] == '\r' && line [res + 2] == '\n') {
1878 ending = LineEnding.Soft;
1881 if (res + 1 < line.Length && line [res + 1] == '\n') {
1882 ending = LineEnding.Hard;
1885 ending = LineEnding.Limp;
1889 if (rich_index != -1) {
1890 ending = LineEnding.Rich;
1894 ending = LineEnding.Wrap;
1898 // Get the line ending, but only of the types specified
1899 private int GetLineEnding (string line, int start, out LineEnding ending, LineEnding type)
1902 int last_length = 0;
1905 index = GetLineEnding (line, index + last_length, out ending);
1906 last_length = LineEndingLength (ending);
1908 ((ending & type) != ending && index != -1);
1910 return index == -1 ? line.Length : index;
1913 internal int LineEndingLength (LineEnding ending)
1916 case LineEnding.Limp:
1917 case LineEnding.Rich:
1919 case LineEnding.Hard:
1921 case LineEnding.Soft:
1928 internal string LineEndingToString (LineEnding ending)
1931 case LineEnding.Limp:
1933 case LineEnding.Hard:
1935 case LineEnding.Soft:
1937 case LineEnding.Rich:
1941 return string.Empty;
1944 internal void Insert (Line line, int pos, bool update_caret, string s)
1946 Insert (line, pos, update_caret, s, line.FindTag (pos));
1949 // Insert text at the given position; use formatting at insertion point for inserted text
1950 internal void Insert (Line line, int pos, bool update_caret, string s, LineTag tag)
1959 // Don't recalculate while we mess around
1962 base_line = line.line_no;
1963 old_line_count = lines;
1965 break_index = GetLineEnding (s, 0, out ending, LineEnding.Hard | LineEnding.Rich);
1967 // There are no line feeds in our text to be pasted
1968 if (break_index == s.Length) {
1969 line.InsertString (pos, s, tag);
1971 // Add up to the first line feed to our current position
1972 line.InsertString (pos, s.Substring (0, break_index + LineEndingLength (ending)), tag);
1974 // Split the rest of the original line to a new line
1975 Split (line, pos + (break_index + LineEndingLength (ending)));
1976 line.ending = ending;
1977 break_index += LineEndingLength (ending);
1978 split_line = GetLine (line.line_no + 1);
1980 // Insert brand new lines for any more line feeds in the inserted string
1982 int next_break = GetLineEnding (s, break_index, out ending, LineEnding.Hard | LineEnding.Rich);
1984 if (next_break == s.Length)
1987 string line_text = s.Substring (break_index, next_break - break_index +
1988 LineEndingLength (ending));
1990 Add (base_line + count, line_text, line.alignment, tag.Font, tag.Color, ending);
1992 Line last = GetLine (base_line + count);
1993 last.ending = ending;
1996 break_index = next_break + LineEndingLength (ending);
1999 // Add the remainder of the insert text to the split
2000 // part of the original line
2001 split_line.InsertString (0, s.Substring (break_index));
2004 // Allow the document to recalculate things
2005 ResumeRecalc (false);
2007 // Update our character count
2008 CharCount += s.Length;
2010 UpdateView (line, lines - old_line_count + 1, pos);
2012 // Move the caret to the end of the inserted text if requested
2014 Line l = GetLine (line.line_no + lines - old_line_count);
2015 PositionCaret (l, l.text.Length);
2020 // Inserts a string at the given position
2021 internal void InsertString (Line line, int pos, string s)
2023 // Update our character count
2024 CharCount += s.Length;
2026 // Insert the text into the Line
2027 line.InsertString (pos, s);
2030 // Inserts a character at the current caret position
2031 internal void InsertCharAtCaret (char ch, bool move_caret)
2033 caret.line.InsertString (caret.pos, ch.ToString(), caret.tag);
2035 // Update our character count
2038 undo.RecordTyping (caret.line, caret.pos, ch);
2040 UpdateView (caret.line, caret.pos);
2045 SetSelectionToCaret (true);
2049 internal void InsertPicture (Line line, int pos, RTF.Picture picture)
2057 // Just a place holder basically
2058 line.text.Insert (pos, "I");
2060 PictureTag picture_tag = new PictureTag (line, pos + 1, picture);
2062 tag = LineTag.FindTag (line, pos);
2063 picture_tag.CopyFormattingFrom (tag);
2064 /*next_tag = */tag.Break (pos + 1);
2065 picture_tag.Previous = tag;
2066 picture_tag.Next = tag.Next;
2067 tag.Next = picture_tag;
2070 // Picture tags need to be surrounded by text tags
2072 if (picture_tag.Next == null) {
2073 picture_tag.Next = new LineTag (line, pos + 1);
2074 picture_tag.Next.CopyFormattingFrom (tag);
2075 picture_tag.Next.Previous = picture_tag;
2078 tag = picture_tag.Next;
2079 while (tag != null) {
2087 UpdateView (line, pos);
2090 internal void DeleteMultiline (Line start_line, int pos, int length)
2092 Marker start = new Marker ();
2093 Marker end = new Marker ();
2094 int start_index = LineTagToCharIndex (start_line, pos);
2096 start.line = start_line;
2098 start.tag = LineTag.FindTag (start_line, pos);
2100 CharIndexToLineTag (start_index + length, out end.line,
2101 out end.tag, out end.pos);
2105 if (start.line == end.line) {
2106 DeleteChars (start.line, pos, end.pos - pos);
2109 // Delete first and last lines
2110 DeleteChars (start.line, start.pos, start.line.text.Length - start.pos);
2111 DeleteChars (end.line, 0, end.pos);
2113 int current = start.line.line_no + 1;
2114 if (current < end.line.line_no) {
2115 for (int i = end.line.line_no - 1; i >= current; i--) {
2120 // BIG FAT WARNING - selection_end.line might be stale due
2121 // to the above Delete() call. DONT USE IT before hitting the end of this method!
2123 // Join start and end
2124 Combine (start.line.line_no, current);
2127 ResumeUpdate (true);
2131 // Deletes n characters at the given position; it will not delete past line limits
2133 public void DeleteChars (Line line, int pos, int count)
2135 // Reduce our character count
2138 line.DeleteCharacters (pos, count);
2140 if (pos >= line.TextLengthWithoutEnding ()) {
2141 LineEnding ending = line.ending;
2142 GetLineEnding (line.text.ToString (), 0, out ending);
2144 if (ending != line.ending) {
2145 line.ending = ending;
2148 UpdateView (line, lines, pos);
2149 owner.Invalidate ();
2155 UpdateView (line, lines, pos);
2156 owner.Invalidate ();
2158 UpdateView (line, pos);
2161 // Deletes a character at or after the given position (depending on forward); it will not delete past line limits
2162 public void DeleteChar (Line line, int pos, bool forward)
2164 if ((pos == 0 && forward == false) || (pos == line.text.Length && forward == true))
2168 DeleteChars (line, pos, 1);
2170 DeleteChars (line, pos - 1, 1);
2173 // Combine two lines
2174 internal void Combine(int FirstLine, int SecondLine) {
2175 Combine(GetLine(FirstLine), GetLine(SecondLine));
2178 internal void Combine(Line first, Line second) {
2182 // strip the ending off of the first lines text
2183 first.text.Length = first.text.Length - LineEndingLength (first.ending);
2185 // Combine the two tag chains into one
2188 // Maintain the line ending style
2189 first.ending = second.ending;
2191 while (last.Next != null) {
2195 // need to get the shift before setting the next tag since that effects length
2196 shift = last.Start + last.Length - 1;
2197 last.Next = second.tags;
2198 last.Next.Previous = last;
2200 // Fix up references within the chain
2202 while (last != null) {
2204 last.Start += shift;
2208 // Combine both lines' strings
2209 first.text.Insert(first.text.Length, second.text.ToString());
2210 first.Grow(first.text.Length);
2212 // Remove the reference to our (now combined) tags from the doomed line
2216 DecrementLines(first.line_no + 2); // first.line_no + 1 will be deleted, so we need to start renumbering one later
2219 first.recalc = true;
2220 first.height = 0; // This forces RecalcDocument/UpdateView to redraw from this line on
2221 first.Streamline(lines);
2223 // Update Caret, Selection, etc
2224 if (caret.line == second) {
2225 caret.Combine(first, shift);
2227 if (selection_anchor.line == second) {
2228 selection_anchor.Combine(first, shift);
2230 if (selection_start.line == second) {
2231 selection_start.Combine(first, shift);
2233 if (selection_end.line == second) {
2234 selection_end.Combine(first, shift);
2241 check_first = GetLine(first.line_no);
2242 check_second = GetLine(check_first.line_no + 1);
2244 Console.WriteLine("Pre-delete: Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2247 this.Delete(second);
2250 check_first = GetLine(first.line_no);
2251 check_second = GetLine(check_first.line_no + 1);
2253 Console.WriteLine("Post-delete Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2257 // Split the line at the position into two
2258 internal void Split(int LineNo, int pos) {
2262 line = GetLine(LineNo);
2263 tag = LineTag.FindTag(line, pos);
2264 Split(line, tag, pos);
2267 internal void Split(Line line, int pos) {
2270 tag = LineTag.FindTag(line, pos);
2271 Split(line, tag, pos);
2274 ///<summary>Split line at given tag and position into two lines</summary>
2275 ///if more space becomes available on previous line
2276 internal void Split(Line line, LineTag tag, int pos) {
2280 bool move_sel_start;
2284 move_sel_start = false;
2285 move_sel_end = false;
2291 throw new Exception ("Split called with the wrong tag");
2294 // Adjust selection and cursors
2295 if (caret.line == line && caret.pos >= pos) {
2298 if (selection_start.line == line && selection_start.pos > pos) {
2299 move_sel_start = true;
2302 if (selection_end.line == line && selection_end.pos > pos) {
2303 move_sel_end = true;
2306 // cover the easy case first
2307 if (pos == line.text.Length) {
2308 Add (line.line_no + 1, String.Empty, line.alignment, tag.Font, tag.Color, line.ending);
2310 new_line = GetLine (line.line_no + 1);
2313 caret.line = new_line;
2314 caret.tag = new_line.tags;
2317 if (selection_visible == false) {
2318 SetSelectionToCaret (true);
2322 if (move_sel_start) {
2323 selection_start.line = new_line;
2324 selection_start.pos = 0;
2325 selection_start.tag = new_line.tags;
2329 selection_end.line = new_line;
2330 selection_end.pos = 0;
2331 selection_end.tag = new_line.tags;
2340 // We need to move the rest of the text into the new line
2341 Add (line.line_no + 1, line.text.ToString (pos, line.text.Length - pos), line.alignment, tag.Font, tag.Color, line.ending);
2343 // Now transfer our tags from this line to the next
2344 new_line = GetLine(line.line_no + 1);
2347 new_line.recalc = true;
2349 //make sure that if we are at the end of a tag, we start on the begining
2350 //of a new one, if one exists... Stops us creating an empty tag and
2351 //make the operation easier.
2352 if (tag.Next != null && (tag.Next.Start - 1) == pos)
2355 if ((tag.Start - 1) == pos) {
2358 // We can simply break the chain and move the tag into the next line
2360 // if the tag we are moving is the first, create an empty tag
2361 // for the line we are leaving behind
2362 if (tag == line.tags) {
2363 new_tag = new LineTag(line, 1);
2364 new_tag.CopyFormattingFrom (tag);
2365 line.tags = new_tag;
2368 if (tag.Previous != null) {
2369 tag.Previous.Next = null;
2371 new_line.tags = tag;
2372 tag.Previous = null;
2373 tag.Line = new_line;
2375 // Walk the list and correct the start location of the tags we just bumped into the next line
2376 shift = tag.Start - 1;
2379 while (new_tag != null) {
2380 new_tag.Start -= shift;
2381 new_tag.Line = new_line;
2382 new_tag = new_tag.Next;
2387 new_tag = new LineTag (new_line, 1);
2388 new_tag.Next = tag.Next;
2389 new_tag.CopyFormattingFrom (tag);
2390 new_line.tags = new_tag;
2391 if (new_tag.Next != null) {
2392 new_tag.Next.Previous = new_tag;
2397 new_tag = new_tag.Next;
2398 while (new_tag != null) {
2399 new_tag.Start -= shift;
2400 new_tag.Line = new_line;
2401 new_tag = new_tag.Next;
2407 caret.line = new_line;
2408 caret.pos = caret.pos - pos;
2409 caret.tag = caret.line.FindTag(caret.pos);
2411 if (selection_visible == false) {
2412 SetSelectionToCaret (true);
2416 if (move_sel_start) {
2417 selection_start.line = new_line;
2418 selection_start.pos = selection_start.pos - pos;
2419 if (selection_start.Equals(selection_end))
2420 selection_start.tag = new_line.FindTag(selection_start.pos);
2422 selection_start.tag = new_line.FindTag (selection_start.pos + 1);
2426 selection_end.line = new_line;
2427 selection_end.pos = selection_end.pos - pos;
2428 selection_end.tag = new_line.FindTag(selection_end.pos);
2431 CharCount -= line.text.Length - pos;
2432 line.text.Remove(pos, line.text.Length - pos);
2439 private void SanityCheck () {
2440 for (int i = 1; i < lines; i++) {
2441 LineTag tag = GetLine (i).tags;
2444 throw new Exception ("Line doesn't start at the begining");
2449 while (tag != null) {
2450 if (tag.Start == start)
2451 throw new Exception ("Empty tag!");
2453 if (tag.Start < start)
2454 throw new Exception ("Insane!!");
2463 // Adds a line of text, with given font.
2464 // Bumps any line at that line number that already exists down
2465 internal void Add (int LineNo, string Text, Font font, Color color, LineEnding ending)
2467 Add (LineNo, Text, alignment, font, color, ending);
2470 internal void Add (int LineNo, string Text, HorizontalAlignment align, Font font, Color color, LineEnding ending)
2476 CharCount += Text.Length;
2478 if (LineNo<1 || Text == null) {
2480 throw new ArgumentNullException("LineNo", "Line numbers must be positive");
2482 throw new ArgumentNullException("Text", "Cannot insert NULL line");
2486 add = new Line (this, LineNo, Text, align, font, color, ending);
2489 while (line != sentinel) {
2491 line_no = line.line_no;
2493 if (LineNo > line_no) {
2495 } else if (LineNo < line_no) {
2498 // Bump existing line numbers; walk all nodes to the right of this one and increment line_no
2499 IncrementLines(line.line_no);
2504 add.left = sentinel;
2505 add.right = sentinel;
2507 if (add.parent != null) {
2508 if (LineNo > add.parent.line_no) {
2509 add.parent.right = add;
2511 add.parent.left = add;
2518 RebalanceAfterAdd(add);
2523 internal virtual void Clear() {
2526 document = sentinel;
2529 public virtual object Clone() {
2532 clone = new Document(null);
2534 clone.lines = this.lines;
2535 clone.document = (Line)document.Clone();
2540 private void Delete (int LineNo)
2547 line = GetLine (LineNo);
2549 CharCount -= line.text.Length;
2551 DecrementLines (LineNo + 1);
2555 private void Delete(Line line1) {
2556 Line line2;// = new Line();
2559 if ((line1.left == sentinel) || (line1.right == sentinel)) {
2562 line3 = line1.right;
2563 while (line3.left != sentinel) {
2568 if (line3.left != sentinel) {
2571 line2 = line3.right;
2574 line2.parent = line3.parent;
2575 if (line3.parent != null) {
2576 if(line3 == line3.parent.left) {
2577 line3.parent.left = line2;
2579 line3.parent.right = line2;
2585 if (line3 != line1) {
2588 if (selection_start.line == line3) {
2589 selection_start.line = line1;
2592 if (selection_end.line == line3) {
2593 selection_end.line = line1;
2596 if (selection_anchor.line == line3) {
2597 selection_anchor.line = line1;
2600 if (caret.line == line3) {
2605 line1.alignment = line3.alignment;
2606 line1.ascent = line3.ascent;
2607 line1.hanging_indent = line3.hanging_indent;
2608 line1.height = line3.height;
2609 line1.indent = line3.indent;
2610 line1.line_no = line3.line_no;
2611 line1.recalc = line3.recalc;
2612 line1.right_indent = line3.right_indent;
2613 line1.ending = line3.ending;
2614 line1.space = line3.space;
2615 line1.tags = line3.tags;
2616 line1.text = line3.text;
2617 line1.widths = line3.widths;
2618 line1.offset = line3.offset;
2621 while (tag != null) {
2627 if (line3.color == LineColor.Black)
2628 RebalanceAfterDelete(line2);
2633 // Invalidates the start line until the end of the viewstate
2634 internal void InvalidateLinesAfter (Line start) {
2635 owner.Invalidate (new Rectangle (0, start.Y - viewport_y, viewport_width, viewport_height - start.Y));
2638 // Invalidate a section of the document to trigger redraw
2639 internal void Invalidate(Line start, int start_pos, Line end, int end_pos) {
2645 if ((start == end) && (start_pos == end_pos)) {
2649 if (end_pos == -1) {
2650 end_pos = end.text.Length;
2653 // figure out what's before what so the logic below is straightforward
2654 if (start.line_no < end.line_no) {
2660 } else if (start.line_no > end.line_no) {
2667 if (start_pos < end_pos) {
2681 int endpoint = (int) l1.widths [p2];
2682 if (p2 == l1.text.Length + 1) {
2683 endpoint = (int) viewport_width;
2687 Console.WriteLine("Invaliding backwards from {0}:{1} to {2}:{3} {4}",
2688 l1.line_no, p1, l2.line_no, p2,
2690 (int)l1.widths[p1] + l1.X - viewport_x,
2698 owner.Invalidate(new Rectangle (
2699 offset_x + (int)l1.widths[p1] + l1.X - viewport_x,
2700 offset_y + l1.Y - viewport_y,
2701 endpoint - (int) l1.widths [p1] + 1,
2707 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);
2708 Console.WriteLine ("invalidate start line: {0} position: {1}", l1.text, p1);
2711 // Three invalidates:
2712 // First line from start
2713 owner.Invalidate(new Rectangle(
2714 offset_x + (int)l1.widths[p1] + l1.X - viewport_x,
2715 offset_y + l1.Y - viewport_y,
2721 if ((l1.line_no + 1) < l2.line_no) {
2724 y = GetLine(l1.line_no + 1).Y;
2725 owner.Invalidate(new Rectangle(
2727 offset_y + y - viewport_y,
2732 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);
2738 owner.Invalidate(new Rectangle(
2739 offset_x + (int)l2.widths[0] + l2.X - viewport_x,
2740 offset_y + l2.Y - viewport_y,
2741 (int)l2.widths[p2] + 1,
2745 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);
2749 /// <summary>Select text around caret</summary>
2750 internal void ExpandSelection(CaretSelection mode, bool to_caret) {
2752 // We're expanding the selection to the caret position
2754 case CaretSelection.Line: {
2755 // Invalidate the selection delta
2756 if (caret > selection_prev) {
2757 Invalidate(selection_prev.line, 0, caret.line, caret.line.text.Length);
2759 Invalidate(selection_prev.line, selection_prev.line.text.Length, caret.line, 0);
2762 if (caret.line.line_no <= selection_anchor.line.line_no) {
2763 selection_start.line = caret.line;
2764 selection_start.tag = caret.line.tags;
2765 selection_start.pos = 0;
2767 selection_end.line = selection_anchor.line;
2768 selection_end.tag = selection_anchor.tag;
2769 selection_end.pos = selection_anchor.pos;
2771 selection_end_anchor = true;
2773 selection_start.line = selection_anchor.line;
2774 selection_start.pos = selection_anchor.height;
2775 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height + 1);
2777 selection_end.line = caret.line;
2778 selection_end.tag = caret.line.tags;
2779 selection_end.pos = caret.line.text.Length;
2781 selection_end_anchor = false;
2783 selection_prev.line = caret.line;
2784 selection_prev.tag = caret.tag;
2785 selection_prev.pos = caret.pos;
2790 case CaretSelection.Word: {
2794 start_pos = FindWordSeparator(caret.line, caret.pos, false);
2795 end_pos = FindWordSeparator(caret.line, caret.pos, true);
2798 // Invalidate the selection delta
2799 if (caret > selection_prev) {
2800 Invalidate(selection_prev.line, selection_prev.pos, caret.line, end_pos);
2802 Invalidate(selection_prev.line, selection_prev.pos, caret.line, start_pos);
2804 if (caret < selection_anchor) {
2805 selection_start.line = caret.line;
2806 selection_start.tag = caret.line.FindTag(start_pos + 1);
2807 selection_start.pos = start_pos;
2809 selection_end.line = selection_anchor.line;
2810 selection_end.tag = selection_anchor.tag;
2811 selection_end.pos = selection_anchor.pos;
2813 selection_prev.line = caret.line;
2814 selection_prev.tag = caret.tag;
2815 selection_prev.pos = start_pos;
2817 selection_end_anchor = true;
2819 selection_start.line = selection_anchor.line;
2820 selection_start.pos = selection_anchor.height;
2821 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height + 1);
2823 selection_end.line = caret.line;
2824 selection_end.tag = caret.line.FindTag(end_pos);
2825 selection_end.pos = end_pos;
2827 selection_prev.line = caret.line;
2828 selection_prev.tag = caret.tag;
2829 selection_prev.pos = end_pos;
2831 selection_end_anchor = false;
2836 case CaretSelection.Position: {
2837 SetSelectionToCaret(false);
2842 // We're setting the selection 'around' the caret position
2844 case CaretSelection.Line: {
2845 this.Invalidate(caret.line, 0, caret.line, caret.line.text.Length);
2847 selection_start.line = caret.line;
2848 selection_start.tag = caret.line.tags;
2849 selection_start.pos = 0;
2851 selection_end.line = caret.line;
2852 selection_end.pos = caret.line.text.Length;
2853 selection_end.tag = caret.line.FindTag(selection_end.pos);
2855 selection_anchor.line = selection_end.line;
2856 selection_anchor.tag = selection_end.tag;
2857 selection_anchor.pos = selection_end.pos;
2858 selection_anchor.height = 0;
2860 selection_prev.line = caret.line;
2861 selection_prev.tag = caret.tag;
2862 selection_prev.pos = caret.pos;
2864 this.selection_end_anchor = true;
2869 case CaretSelection.Word: {
2873 start_pos = FindWordSeparator(caret.line, caret.pos, false);
2874 end_pos = FindWordSeparator(caret.line, caret.pos, true);
2876 this.Invalidate(selection_start.line, start_pos, caret.line, end_pos);
2878 selection_start.line = caret.line;
2879 selection_start.tag = caret.line.FindTag(start_pos + 1);
2880 selection_start.pos = start_pos;
2882 selection_end.line = caret.line;
2883 selection_end.tag = caret.line.FindTag(end_pos);
2884 selection_end.pos = end_pos;
2886 selection_anchor.line = selection_end.line;
2887 selection_anchor.tag = selection_end.tag;
2888 selection_anchor.pos = selection_end.pos;
2889 selection_anchor.height = start_pos;
2891 selection_prev.line = caret.line;
2892 selection_prev.tag = caret.tag;
2893 selection_prev.pos = caret.pos;
2895 this.selection_end_anchor = true;
2902 SetSelectionVisible (!(selection_start == selection_end));
2905 internal void SetSelectionToCaret(bool start) {
2907 // Invalidate old selection; selection is being reset to empty
2908 this.Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2910 selection_start.line = caret.line;
2911 selection_start.tag = caret.tag;
2912 selection_start.pos = caret.pos;
2914 // start always also selects end
2915 selection_end.line = caret.line;
2916 selection_end.tag = caret.tag;
2917 selection_end.pos = caret.pos;
2919 selection_anchor.line = caret.line;
2920 selection_anchor.tag = caret.tag;
2921 selection_anchor.pos = caret.pos;
2923 // Invalidate from previous end to caret (aka new end)
2924 if (selection_end_anchor) {
2925 if (selection_start != caret) {
2926 this.Invalidate(selection_start.line, selection_start.pos, caret.line, caret.pos);
2929 if (selection_end != caret) {
2930 this.Invalidate(selection_end.line, selection_end.pos, caret.line, caret.pos);
2934 if (caret < selection_anchor) {
2935 selection_start.line = caret.line;
2936 selection_start.tag = caret.tag;
2937 selection_start.pos = caret.pos;
2939 selection_end.line = selection_anchor.line;
2940 selection_end.tag = selection_anchor.tag;
2941 selection_end.pos = selection_anchor.pos;
2943 selection_end_anchor = true;
2945 selection_start.line = selection_anchor.line;
2946 selection_start.tag = selection_anchor.tag;
2947 selection_start.pos = selection_anchor.pos;
2949 selection_end.line = caret.line;
2950 selection_end.tag = caret.tag;
2951 selection_end.pos = caret.pos;
2953 selection_end_anchor = false;
2957 SetSelectionVisible (!(selection_start == selection_end));
2960 internal void SetSelection(Line start, int start_pos, Line end, int end_pos) {
2961 if (selection_visible) {
2962 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2965 if ((end.line_no < start.line_no) || ((end == start) && (end_pos <= start_pos))) {
2966 selection_start.line = end;
2967 selection_start.tag = LineTag.FindTag(end, end_pos);
2968 selection_start.pos = end_pos;
2970 selection_end.line = start;
2971 selection_end.tag = LineTag.FindTag(start, start_pos);
2972 selection_end.pos = start_pos;
2974 selection_end_anchor = true;
2976 selection_start.line = start;
2977 selection_start.tag = LineTag.FindTag(start, start_pos);
2978 selection_start.pos = start_pos;
2980 selection_end.line = end;
2981 selection_end.tag = LineTag.FindTag(end, end_pos);
2982 selection_end.pos = end_pos;
2984 selection_end_anchor = false;
2987 selection_anchor.line = start;
2988 selection_anchor.tag = selection_start.tag;
2989 selection_anchor.pos = start_pos;
2991 if (((start == end) && (start_pos == end_pos)) || start == null || end == null) {
2992 SetSelectionVisible (false);
2994 SetSelectionVisible (true);
2995 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2999 internal void SetSelectionStart(Line start, int start_pos, bool invalidate) {
3000 // Invalidate from the previous to the new start pos
3002 Invalidate(selection_start.line, selection_start.pos, start, start_pos);
3004 selection_start.line = start;
3005 selection_start.pos = start_pos;
3006 selection_start.tag = LineTag.FindTag(start, start_pos);
3008 selection_anchor.line = start;
3009 selection_anchor.pos = start_pos;
3010 selection_anchor.tag = selection_start.tag;
3012 selection_end_anchor = false;
3015 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
3016 SetSelectionVisible (true);
3018 SetSelectionVisible (false);
3022 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3025 internal void SetSelectionStart(int character_index, bool invalidate) {
3030 if (character_index < 0) {
3034 CharIndexToLineTag(character_index, out line, out tag, out pos);
3035 SetSelectionStart(line, pos, invalidate);
3038 internal void SetSelectionEnd(Line end, int end_pos, bool invalidate) {
3040 if (end == selection_end.line && end_pos == selection_start.pos) {
3041 selection_anchor.line = selection_start.line;
3042 selection_anchor.tag = selection_start.tag;
3043 selection_anchor.pos = selection_start.pos;
3045 selection_end.line = selection_start.line;
3046 selection_end.tag = selection_start.tag;
3047 selection_end.pos = selection_start.pos;
3049 selection_end_anchor = false;
3050 } else if ((end.line_no < selection_anchor.line.line_no) || ((end == selection_anchor.line) && (end_pos <= selection_anchor.pos))) {
3051 selection_start.line = end;
3052 selection_start.tag = LineTag.FindTag(end, end_pos);
3053 selection_start.pos = end_pos;
3055 selection_end.line = selection_anchor.line;
3056 selection_end.tag = selection_anchor.tag;
3057 selection_end.pos = selection_anchor.pos;
3059 selection_end_anchor = true;
3061 selection_start.line = selection_anchor.line;
3062 selection_start.tag = selection_anchor.tag;
3063 selection_start.pos = selection_anchor.pos;
3065 selection_end.line = end;
3066 selection_end.tag = LineTag.FindTag(end, end_pos);
3067 selection_end.pos = end_pos;
3069 selection_end_anchor = false;
3072 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
3073 SetSelectionVisible (true);
3075 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3077 SetSelectionVisible (false);
3078 // ?? Do I need to invalidate here, tests seem to work without it, but I don't think they should :-s
3082 internal void SetSelectionEnd(int character_index, bool invalidate) {
3087 if (character_index < 0) {
3091 CharIndexToLineTag(character_index, out line, out tag, out pos);
3092 SetSelectionEnd(line, pos, invalidate);
3095 internal void SetSelection(Line start, int start_pos) {
3096 if (selection_visible) {
3097 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3100 selection_start.line = start;
3101 selection_start.pos = start_pos;
3102 selection_start.tag = LineTag.FindTag(start, start_pos);
3104 selection_end.line = start;
3105 selection_end.tag = selection_start.tag;
3106 selection_end.pos = start_pos;
3108 selection_anchor.line = start;
3109 selection_anchor.tag = selection_start.tag;
3110 selection_anchor.pos = start_pos;
3112 selection_end_anchor = false;
3113 SetSelectionVisible (false);
3116 internal void InvalidateSelectionArea() {
3117 Invalidate (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3120 // Return the current selection, as string
3121 internal string GetSelection() {
3122 // We return String.Empty if there is no selection
3123 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3124 return string.Empty;
3127 if (selection_start.line == selection_end.line) {
3128 return selection_start.line.text.ToString (selection_start.pos, selection_end.pos - selection_start.pos);
3135 sb = new StringBuilder();
3136 start = selection_start.line.line_no;
3137 end = selection_end.line.line_no;
3139 sb.Append(selection_start.line.text.ToString(selection_start.pos, selection_start.line.text.Length - selection_start.pos) + Environment.NewLine);
3141 if ((start + 1) < end) {
3142 for (i = start + 1; i < end; i++) {
3143 sb.Append(GetLine(i).text.ToString() + Environment.NewLine);
3147 sb.Append(selection_end.line.text.ToString(0, selection_end.pos));
3149 return sb.ToString();
3153 internal void ReplaceSelection(string s, bool select_new) {
3156 int selection_start_pos = LineTagToCharIndex (selection_start.line, selection_start.pos);
3159 // First, delete any selected text
3160 if ((selection_start.pos != selection_end.pos) || (selection_start.line != selection_end.line)) {
3161 if (selection_start.line == selection_end.line) {
3162 undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3164 DeleteChars (selection_start.line, selection_start.pos, selection_end.pos - selection_start.pos);
3166 // The tag might have been removed, we need to recalc it
3167 selection_start.tag = selection_start.line.FindTag(selection_start.pos + 1);
3172 start = selection_start.line.line_no;
3173 end = selection_end.line.line_no;
3175 undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3177 InvalidateLinesAfter(selection_start.line);
3179 // Delete first line
3180 DeleteChars (selection_start.line, selection_start.pos, selection_start.line.text.Length - selection_start.pos);
3181 selection_start.line.recalc = true;
3184 DeleteChars(selection_end.line, 0, selection_end.pos);
3188 for (i = end - 1; i >= start; i--) {
3193 // BIG FAT WARNING - selection_end.line might be stale due
3194 // to the above Delete() call. DONT USE IT before hitting the end of this method!
3196 // Join start and end
3197 Combine(selection_start.line.line_no, start);
3202 Insert(selection_start.line, selection_start.pos, false, s);
3203 undo.RecordInsertString (selection_start.line, selection_start.pos, s);
3204 ResumeRecalc (false);
3206 Line begin_update_line = selection_start.line;
3207 int begin_update_pos = selection_start.pos;
3210 CharIndexToLineTag(selection_start_pos + s.Length, out selection_start.line,
3211 out selection_start.tag, out selection_start.pos);
3213 selection_end.line = selection_start.line;
3214 selection_end.pos = selection_start.pos;
3215 selection_end.tag = selection_start.tag;
3216 selection_anchor.line = selection_start.line;
3217 selection_anchor.pos = selection_start.pos;
3218 selection_anchor.tag = selection_start.tag;
3220 SetSelectionVisible (false);
3222 CharIndexToLineTag(selection_start_pos, out selection_start.line,
3223 out selection_start.tag, out selection_start.pos);
3225 CharIndexToLineTag(selection_start_pos + s.Length, out selection_end.line,
3226 out selection_end.tag, out selection_end.pos);
3228 selection_anchor.line = selection_start.line;
3229 selection_anchor.pos = selection_start.pos;
3230 selection_anchor.tag = selection_start.tag;
3232 SetSelectionVisible (true);
3235 PositionCaret (selection_start.line, selection_start.pos);
3236 UpdateView (begin_update_line, selection_end.line.line_no - begin_update_line.line_no, begin_update_pos);
3239 internal void CharIndexToLineTag(int index, out Line line_out, out LineTag tag_out, out int pos) {
3248 for (i = 1; i <= lines; i++) {
3252 chars += line.text.Length;
3254 if (index <= chars) {
3255 // we found the line
3258 while (tag != null) {
3259 if (index < (start + tag.Start + tag.Length - 1)) {
3261 tag_out = LineTag.GetFinalTag (tag);
3262 pos = index - start;
3265 if (tag.Next == null) {
3268 next_line = GetLine(line.line_no + 1);
3270 if (next_line != null) {
3271 line_out = next_line;
3272 tag_out = LineTag.GetFinalTag (next_line.tags);
3277 tag_out = LineTag.GetFinalTag (tag);
3278 pos = line_out.text.Length;
3287 line_out = GetLine(lines);
3288 tag = line_out.tags;
3289 while (tag.Next != null) {
3293 pos = line_out.text.Length;
3296 internal int LineTagToCharIndex(Line line, int pos) {
3300 // Count first and last line
3303 // Count the lines in the middle
3305 for (i = 1; i < line.line_no; i++) {
3306 length += GetLine(i).text.Length;
3314 internal int SelectionLength() {
3315 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3319 if (selection_start.line == selection_end.line) {
3320 return selection_end.pos - selection_start.pos;
3327 // Count first and last line
3328 length = selection_start.line.text.Length - selection_start.pos + selection_end.pos + crlf_size;
3330 // Count the lines in the middle
3331 start = selection_start.line.line_no + 1;
3332 end = selection_end.line.line_no;
3335 for (i = start; i < end; i++) {
3336 Line line = GetLine (i);
3337 length += line.text.Length + LineEndingLength (line.ending);
3348 /// <summary>Give it a Line number and it returns the Line object at with that line number</summary>
3349 internal Line GetLine(int LineNo) {
3350 Line line = document;
3352 while (line != sentinel) {
3353 if (LineNo == line.line_no) {
3355 } else if (LineNo < line.line_no) {
3365 /// <summary>Retrieve the previous tag; walks line boundaries</summary>
3366 internal LineTag PreviousTag(LineTag tag) {
3369 if (tag.Previous != null) {
3370 return tag.Previous;
3374 if (tag.Line.line_no == 1) {
3378 l = GetLine(tag.Line.line_no - 1);
3383 while (t.Next != null) {
3392 /// <summary>Retrieve the next tag; walks line boundaries</summary>
3393 internal LineTag NextTag(LineTag tag) {
3396 if (tag.Next != null) {
3401 l = GetLine(tag.Line.line_no + 1);
3409 internal Line ParagraphStart(Line line) {
3410 Line lastline = line;
3412 if (line.line_no <= 1)
3416 lastline = GetLine (line.line_no - 1);
3417 } while (lastline.ending == LineEnding.Wrap);
3422 internal Line ParagraphEnd(Line line) {
3425 while (line.ending == LineEnding.Wrap) {
3426 l = GetLine(line.line_no + 1);
3427 if ((l == null) || (l.ending != LineEnding.Wrap)) {
3435 /// <summary>Give it a pixel offset coordinate and it returns the Line covering that are (offset
3436 /// is either X or Y depending on if we are multiline
3438 internal Line GetLineByPixel (int offset, bool exact)
3440 Line line = document;
3444 while (line != sentinel) {
3446 if ((offset >= line.Y) && (offset < (line.Y+line.height))) {
3448 } else if (offset < line.Y) {
3455 while (line != sentinel) {
3457 if ((offset >= line.X) && (offset < (line.X + line.Width)))
3459 else if (offset < line.X)
3472 // Give it x/y pixel coordinates and it returns the Tag at that position
3473 internal LineTag FindCursor (int x, int y, out int index)
3480 line = GetLineByPixel (multiline ? y : x, false);
3482 LineTag tag = line.GetTag (x);
3484 if (tag.Length == 0 && tag.Start == 1)
3487 index = tag.GetCharIndex (x - line.align_shift);
3492 /// <summary>Format area of document in specified font and color</summary>
3493 /// <param name="start_pos">1-based start position on start_line</param>
3494 /// <param name="end_pos">1-based end position on end_line </param>
3495 internal void FormatText (Line start_line, int start_pos, Line end_line, int end_pos, Font font,
3496 Color color, Color back_color, FormatSpecified specified)
3500 // First, format the first line
3501 if (start_line != end_line) {
3503 LineTag.FormatText(start_line, start_pos, start_line.text.Length - start_pos + 1, font, color, back_color, specified);
3506 LineTag.FormatText(end_line, 1, end_pos, font, color, back_color, specified);
3508 // Now all the lines inbetween
3509 for (int i = start_line.line_no + 1; i < end_line.line_no; i++) {
3511 LineTag.FormatText(l, 1, l.text.Length, font, color, back_color, specified);
3514 // Special case, single line
3515 LineTag.FormatText(start_line, start_pos, end_pos - start_pos, font, color, back_color, specified);
3517 if ((end_pos - start_pos) == 0 && CaretTag.Length != 0)
3518 CaretTag = CaretTag.Next;
3522 internal void RecalculateAlignments ()
3531 while (line_no <= lines) {
3532 line = GetLine(line_no);
3535 switch (line.alignment) {
3536 case HorizontalAlignment.Left:
3537 line.align_shift = 0;
3539 case HorizontalAlignment.Center:
3540 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3542 case HorizontalAlignment.Right:
3543 line.align_shift = viewport_width - (int)line.widths[line.text.Length] - right_margin;
3553 /// <summary>Calculate formatting for the whole document</summary>
3554 internal bool RecalculateDocument(Graphics g) {
3555 return RecalculateDocument(g, 1, this.lines, false);
3558 /// <summary>Calculate formatting starting at a certain line</summary>
3559 internal bool RecalculateDocument(Graphics g, int start) {
3560 return RecalculateDocument(g, start, this.lines, false);
3563 /// <summary>Calculate formatting within two given line numbers</summary>
3564 internal bool RecalculateDocument(Graphics g, int start, int end) {
3565 return RecalculateDocument(g, start, end, false);
3568 /// <summary>With optimize on, returns true if line heights changed</summary>
3569 internal bool RecalculateDocument(Graphics g, int start, int end, bool optimize) {
3577 if (recalc_suspended > 0) {
3578 recalc_pending = true;
3579 recalc_start = Math.Min (recalc_start, start);
3580 recalc_end = Math.Max (recalc_end, end);
3581 recalc_optimize = optimize;
3585 // Fixup the positions, they can go kinda nuts
3586 // (this is suspend and resume recalc - they set them to 1 and max)
3587 start = Math.Max (start, 1);
3588 end = Math.Min (end, lines);
3590 offset = GetLine(start).offset;
3595 changed = true; // We always return true if we run non-optimized
3600 while (line_no <= (end + this.lines - shift)) {
3601 line = GetLine(line_no++);
3602 line.offset = offset;
3604 // if we are not calculating a password
3607 line.RecalculateLine(g, this);
3609 if (line.recalc && line.RecalculateLine(g, this)) {
3611 // If the height changed, all subsequent lines change
3618 line.RecalculatePasswordLine(g, this);
3620 if (line.recalc && line.RecalculatePasswordLine(g, this)) {
3622 // If the height changed, all subsequent lines change
3629 if (line.widths[line.text.Length] > new_width) {
3630 new_width = (int)line.widths[line.text.Length];
3633 // Calculate alignment
3634 if (line.alignment != HorizontalAlignment.Left) {
3635 if (line.alignment == HorizontalAlignment.Center) {
3636 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3638 line.align_shift = viewport_width - (int)line.widths[line.text.Length] - 1;
3643 offset += line.height;
3645 offset += (int) line.widths [line.text.Length];
3647 if (line_no > lines) {
3652 if (document_x != new_width) {
3653 document_x = new_width;
3654 if (WidthChanged != null) {
3655 WidthChanged(this, null);
3659 RecalculateAlignments();
3661 line = GetLine(lines);
3663 if (document_y != line.Y + line.height) {
3664 document_y = line.Y + line.height;
3665 if (HeightChanged != null) {
3666 HeightChanged(this, null);
3670 // scan for links and tell us if its all
3671 // changed, so we can update everything
3673 ScanForLinks (start, end, ref changed);
3679 internal int Size() {
3683 private void owner_HandleCreated(object sender, EventArgs e) {
3684 RecalculateDocument(owner.CreateGraphicsInternal());
3688 private void owner_VisibleChanged(object sender, EventArgs e) {
3689 if (owner.Visible) {
3690 RecalculateDocument(owner.CreateGraphicsInternal());
3694 internal static bool IsWordSeparator (char ch)
3709 internal int FindWordSeparator(Line line, int pos, bool forward) {
3712 len = line.text.Length;
3715 for (int i = pos + 1; i < len; i++) {
3716 if (IsWordSeparator(line.Text[i])) {
3722 for (int i = pos - 1; i > 0; i--) {
3723 if (IsWordSeparator(line.Text[i - 1])) {
3731 /* Search document for text */
3732 internal bool FindChars(char[] chars, Marker start, Marker end, out Marker result) {
3738 // Search for occurence of any char in the chars array
3739 result = new Marker();
3742 line_no = start.line.line_no;
3744 while (line_no <= end.line.line_no) {
3745 line_len = line.text.Length;
3746 while (pos < line_len) {
3747 for (int i = 0; i < chars.Length; i++) {
3748 if (line.text[pos] == chars[i]) {
3750 if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
3764 line = GetLine(line_no);
3770 // This version does not build one big string for searching, instead it handles
3771 // line-boundaries, which is faster and less memory intensive
3772 // FIXME - Depending on culture stuff we might have to create a big string and use culturespecific
3773 // search stuff and change it to accept and return positions instead of Markers (which would match
3774 // RichTextBox behaviour better but would be inconsistent with the rest of TextControl)
3775 internal bool Find(string search, Marker start, Marker end, out Marker result, RichTextBoxFinds options) {
3777 string search_string;
3789 result = new Marker();
3790 word_option = ((options & RichTextBoxFinds.WholeWord) != 0);
3791 ignore_case = ((options & RichTextBoxFinds.MatchCase) == 0);
3792 reverse = ((options & RichTextBoxFinds.Reverse) != 0);
3795 line_no = start.line.line_no;
3799 // Prep our search string, lowercasing it if we do case-independent matching
3802 sb = new StringBuilder(search);
3803 for (int i = 0; i < sb.Length; i++) {
3804 sb[i] = Char.ToLower(sb[i]);
3806 search_string = sb.ToString();
3808 search_string = search;
3811 // We need to check if the character before our start position is a wordbreak
3814 if ((pos == 0) || (IsWordSeparator(line.text[pos - 1]))) {
3821 if (IsWordSeparator(line.text[pos - 1])) {
3827 // Need to check the end of the previous line
3830 prev_line = GetLine(line_no - 1);
3831 if (prev_line.ending == LineEnding.Wrap) {
3832 if (IsWordSeparator(prev_line.text[prev_line.text.Length - 1])) {
3846 // To avoid duplication of this loop with reverse logic, we search
3847 // through the document, remembering the last match and when returning
3848 // report that last remembered match
3850 last = new Marker();
3851 last.height = -1; // Abused - we use it to track change
3853 while (line_no <= end.line.line_no) {
3854 if (line_no != end.line.line_no) {
3855 line_len = line.text.Length;
3860 while (pos < line_len) {
3862 if (word_option && (current == search_string.Length)) {
3863 if (IsWordSeparator(line.text[pos])) {
3876 c = Char.ToLower(line.text[pos]);
3881 if (c == search_string[current]) {
3887 if (!word_option || (word_option && (word || (current > 0)))) {
3891 if (!word_option && (current == search_string.Length)) {
3908 if (IsWordSeparator(c)) {
3916 // Mark that we just saw a word boundary
3917 if (line.ending != LineEnding.Wrap || line.line_no == lines - 1) {
3921 if (current == search_string.Length) {
3937 line = GetLine(line_no);
3941 if (last.height != -1) {
3951 // if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
3963 internal void GetMarker(out Marker mark, bool start) {
3964 mark = new Marker();
3967 mark.line = GetLine(1);
3968 mark.tag = mark.line.tags;
3971 mark.line = GetLine(lines);
3972 mark.tag = mark.line.tags;
3973 while (mark.tag.Next != null) {
3974 mark.tag = mark.tag.Next;
3976 mark.pos = mark.line.text.Length;
3979 #endregion // Internal Methods
3982 internal event EventHandler CaretMoved;
3983 internal event EventHandler WidthChanged;
3984 internal event EventHandler HeightChanged;
3985 internal event EventHandler LengthChanged;
3986 #endregion // Events
3988 #region Administrative
3989 public IEnumerator GetEnumerator() {
3994 public override bool Equals(object obj) {
3999 if (!(obj is Document)) {
4007 if (ToString().Equals(((Document)obj).ToString())) {
4014 public override int GetHashCode() {
4018 public override string ToString() {
4019 return "document " + this.document_id;
4021 #endregion // Administrative
4024 internal class PictureTag : LineTag {
4026 internal RTF.Picture picture;
4028 internal PictureTag (Line line, int start, RTF.Picture picture) : base (line, start)
4030 this.picture = picture;
4033 public override bool IsTextTag {
4034 get { return false; }
4037 public override SizeF SizeOfPosition (Graphics dc, int pos)
4039 return picture.Size;
4042 internal override int MaxHeight ()
4044 return (int) (picture.Height + 0.5F);
4047 public override void Draw (Graphics dc, Color color, float xoff, float y, int start, int end)
4049 picture.DrawImage (dc, xoff + Line.widths [start], y, false);
4052 public override void Draw (Graphics dc, Color color, float xoff, float y, int start, int end, string text)
4054 picture.DrawImage (dc, xoff + + Line.widths [start], y, false);
4057 public override string Text ()
4063 internal class UndoManager {
4065 internal enum ActionType {
4069 // This is basically just cut & paste
4077 internal class Action {
4078 internal ActionType type;
4079 internal int line_no;
4081 internal object data;
4084 #region Local Variables
4085 private Document document;
4086 private Stack undo_actions;
4087 private Stack redo_actions;
4089 //private int caret_line;
4090 //private int caret_pos;
4092 // When performing an action, we lock the queue, so that the action can't be undone
4093 private bool locked;
4094 #endregion // Local Variables
4096 #region Constructors
4097 internal UndoManager (Document document)
4099 this.document = document;
4100 undo_actions = new Stack (50);
4101 redo_actions = new Stack (50);
4103 #endregion // Constructors
4106 internal bool CanUndo {
4107 get { return undo_actions.Count > 0; }
4110 internal bool CanRedo {
4111 get { return redo_actions.Count > 0; }
4114 internal string UndoActionName {
4116 foreach (Action action in undo_actions) {
4117 if (action.type == ActionType.UserActionBegin)
4118 return (string) action.data;
4119 if (action.type == ActionType.Typing)
4120 return Locale.GetText ("Typing");
4122 return String.Empty;
4126 internal string RedoActionName {
4128 foreach (Action action in redo_actions) {
4129 if (action.type == ActionType.UserActionBegin)
4130 return (string) action.data;
4131 if (action.type == ActionType.Typing)
4132 return Locale.GetText ("Typing");
4134 return String.Empty;
4137 #endregion // Properties
4139 #region Internal Methods
4140 internal void Clear ()
4142 undo_actions.Clear();
4143 redo_actions.Clear();
4146 internal bool Undo ()
4149 bool user_action_finished = false;
4151 if (undo_actions.Count == 0)
4157 action = (Action) undo_actions.Pop ();
4159 // Put onto redo stack
4160 redo_actions.Push(action);
4163 switch(action.type) {
4165 case ActionType.UserActionBegin:
4166 user_action_finished = true;
4169 case ActionType.UserActionEnd:
4173 case ActionType.InsertString:
4174 start = document.GetLine (action.line_no);
4175 document.SuspendUpdate ();
4176 document.DeleteMultiline (start, action.pos, ((string) action.data).Length + 1);
4177 document.PositionCaret (start, action.pos);
4178 document.SetSelectionToCaret (true);
4179 document.ResumeUpdate (true);
4182 case ActionType.Typing:
4183 start = document.GetLine (action.line_no);
4184 document.SuspendUpdate ();
4185 document.DeleteMultiline (start, action.pos, ((StringBuilder) action.data).Length);
4186 document.PositionCaret (start, action.pos);
4187 document.SetSelectionToCaret (true);
4188 document.ResumeUpdate (true);
4190 // This is an open ended operation, so only a single typing operation can be undone at once
4191 user_action_finished = true;
4194 case ActionType.DeleteString:
4195 start = document.GetLine (action.line_no);
4196 document.SuspendUpdate ();
4197 Insert (start, action.pos, (Line) action.data, true);
4198 document.ResumeUpdate (true);
4201 } while (!user_action_finished && undo_actions.Count > 0);
4208 internal bool Redo ()
4211 bool user_action_finished = false;
4213 if (redo_actions.Count == 0)
4221 action = (Action) redo_actions.Pop ();
4222 undo_actions.Push (action);
4224 switch (action.type) {
4226 case ActionType.UserActionBegin:
4230 case ActionType.UserActionEnd:
4231 user_action_finished = true;
4234 case ActionType.InsertString:
4235 start = document.GetLine (action.line_no);
4236 document.SuspendUpdate ();
4237 start_index = document.LineTagToCharIndex (start, action.pos);
4238 document.InsertString (start, action.pos, (string) action.data);
4239 document.CharIndexToLineTag (start_index + ((string) action.data).Length,
4240 out document.caret.line, out document.caret.tag,
4241 out document.caret.pos);
4242 document.UpdateCaret ();
4243 document.SetSelectionToCaret (true);
4244 document.ResumeUpdate (true);
4247 case ActionType.Typing:
4248 start = document.GetLine (action.line_no);
4249 document.SuspendUpdate ();
4250 start_index = document.LineTagToCharIndex (start, action.pos);
4251 document.InsertString (start, action.pos, ((StringBuilder) action.data).ToString ());
4252 document.CharIndexToLineTag (start_index + ((StringBuilder) action.data).Length,
4253 out document.caret.line, out document.caret.tag,
4254 out document.caret.pos);
4255 document.UpdateCaret ();
4256 document.SetSelectionToCaret (true);
4257 document.ResumeUpdate (true);
4259 // This is an open ended operation, so only a single typing operation can be undone at once
4260 user_action_finished = true;
4263 case ActionType.DeleteString:
4264 start = document.GetLine (action.line_no);
4265 document.SuspendUpdate ();
4266 document.DeleteMultiline (start, action.pos, ((Line) action.data).text.Length);
4267 document.PositionCaret (start, action.pos);
4268 document.SetSelectionToCaret (true);
4269 document.ResumeUpdate (true);
4273 } while (!user_action_finished && redo_actions.Count > 0);
4279 #endregion // Internal Methods
4281 #region Private Methods
4283 public void BeginUserAction (string name)
4288 // Nuke the redo queue
4289 redo_actions.Clear ();
4291 Action ua = new Action ();
4292 ua.type = ActionType.UserActionBegin;
4295 undo_actions.Push (ua);
4298 public void EndUserAction ()
4303 Action ua = new Action ();
4304 ua.type = ActionType.UserActionEnd;
4306 undo_actions.Push (ua);
4309 // start_pos, end_pos = 1 based
4310 public void RecordDeleteString (Line start_line, int start_pos, Line end_line, int end_pos)
4315 // Nuke the redo queue
4316 redo_actions.Clear ();
4318 Action a = new Action ();
4320 // We cant simply store the string, because then formatting would be lost
4321 a.type = ActionType.DeleteString;
4322 a.line_no = start_line.line_no;
4324 a.data = Duplicate (start_line, start_pos, end_line, end_pos);
4326 undo_actions.Push(a);
4329 public void RecordInsertString (Line line, int pos, string str)
4331 if (locked || str.Length == 0)
4334 // Nuke the redo queue
4335 redo_actions.Clear ();
4337 Action a = new Action ();
4339 a.type = ActionType.InsertString;
4341 a.line_no = line.line_no;
4344 undo_actions.Push (a);
4347 public void RecordTyping (Line line, int pos, char ch)
4352 // Nuke the redo queue
4353 redo_actions.Clear ();
4357 if (undo_actions.Count > 0)
4358 a = (Action) undo_actions.Peek ();
4360 if (a == null || a.type != ActionType.Typing) {
4362 a.type = ActionType.Typing;
4363 a.data = new StringBuilder ();
4364 a.line_no = line.line_no;
4367 undo_actions.Push (a);
4370 StringBuilder data = (StringBuilder) a.data;
4374 // start_pos = 1-based
4375 // end_pos = 1-based
4376 public Line Duplicate(Line start_line, int start_pos, Line end_line, int end_pos)
4382 LineTag current_tag;
4387 line = new Line (start_line.document, start_line.ending);
4390 for (int i = start_line.line_no; i <= end_line.line_no; i++) {
4391 current = document.GetLine(i);
4393 if (start_line.line_no == i) {
4399 if (end_line.line_no == i) {
4402 end = current.text.Length;
4409 line.text = new StringBuilder (current.text.ToString (start, end - start));
4411 // Copy tags from start to start+length onto new line
4412 current_tag = current.FindTag (start + 1);
4413 while ((current_tag != null) && (current_tag.Start <= end)) {
4414 if ((current_tag.Start <= start) && (start < (current_tag.Start + current_tag.Length))) {
4415 // start tag is within this tag
4418 tag_start = current_tag.Start;
4421 tag = new LineTag(line, tag_start - start + 1);
4422 tag.CopyFormattingFrom (current_tag);
4424 current_tag = current_tag.Next;
4426 // Add the new tag to the line
4427 if (line.tags == null) {
4433 while (tail.Next != null) {
4437 tag.Previous = tail;
4441 if ((i + 1) <= end_line.line_no) {
4442 line.ending = current.ending;
4444 // Chain them (we use right/left as next/previous)
4445 line.right = new Line (start_line.document, start_line.ending);
4446 line.right.left = line;
4454 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
4455 internal void Insert(Line line, int pos, Line insert, bool select)
4463 // Handle special case first
4464 if (insert.right == null) {
4466 // Single line insert
4467 document.Split(line, pos);
4469 if (insert.tags == null) {
4470 return; // Blank line
4473 //Insert our tags at the end
4476 while (tag.Next != null) {
4480 offset = tag.Start + tag.Length - 1;
4482 tag.Next = insert.tags;
4483 line.text.Insert(offset, insert.text.ToString());
4485 // Adjust start locations
4487 while (tag != null) {
4488 tag.Start += offset;
4492 // Put it back together
4493 document.Combine(line.line_no, line.line_no + 1);
4496 document.SetSelectionStart (line, pos, false);
4497 document.SetSelectionEnd (line, pos + insert.text.Length, false);
4500 document.UpdateView(line, pos);
4508 while (current != null) {
4510 if (current == insert) {
4511 // Inserting the first line we split the line (and make space)
4512 document.Split(line.line_no, pos);
4513 //Insert our tags at the end of the line
4517 if (tag != null && tag.Length != 0) {
4518 while (tag.Next != null) {
4521 offset = tag.Start + tag.Length - 1;
4522 tag.Next = current.tags;
4523 tag.Next.Previous = tag;
4529 line.tags = current.tags;
4530 line.tags.Previous = null;
4534 line.ending = current.ending;
4536 document.Split(line.line_no, 0);
4538 line.tags = current.tags;
4539 line.tags.Previous = null;
4540 line.ending = current.ending;
4544 // Adjust start locations and line pointers
4545 while (tag != null) {
4546 tag.Start += offset - 1;
4551 line.text.Insert(offset, current.text.ToString());
4552 line.Grow(line.text.Length);
4555 line = document.GetLine(line.line_no + 1);
4557 // FIXME? Test undo of line-boundaries
4558 if ((current.right == null) && (current.tags.Length != 0)) {
4559 document.Combine(line.line_no - 1, line.line_no);
4561 current = current.right;
4566 // Recalculate our document
4567 document.UpdateView(first, lines, pos);
4570 #endregion // Private Methods