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 viewport_width;
227 internal int viewport_height;
229 internal int document_x; // Width of the document
230 internal int document_y; // Height of the document
232 internal Rectangle invalid;
234 internal int crlf_size; // 1 or 2, depending on whether we use \r\n or just \n
236 internal TextBoxBase owner; // Who's owning us?
237 static internal int caret_width = 1;
238 static internal int caret_shift = 1;
240 internal int left_margin = 2; // A left margin for all lines
241 internal int top_margin = 2;
242 internal int right_margin = 2;
243 #endregion // Local Variables
246 internal Document (TextBoxBase owner)
255 recalc_pending = false;
257 // Tree related stuff
258 sentinel = new Line (this, LineEnding.None);
259 sentinel.color = LineColor.Black;
263 // We always have a blank line
264 owner.HandleCreated += new EventHandler(owner_HandleCreated);
265 owner.VisibleChanged += new EventHandler(owner_VisibleChanged);
267 Add (1, String.Empty, owner.Font, owner.ForeColor, LineEnding.None);
269 undo = new UndoManager (this);
271 selection_visible = false;
272 selection_start.line = this.document;
273 selection_start.pos = 0;
274 selection_start.tag = selection_start.line.tags;
275 selection_end.line = this.document;
276 selection_end.pos = 0;
277 selection_end.tag = selection_end.line.tags;
278 selection_anchor.line = this.document;
279 selection_anchor.pos = 0;
280 selection_anchor.tag = selection_anchor.line.tags;
281 caret.line = this.document;
283 caret.tag = caret.line.tags;
290 // Default selection is empty
292 document_id = random.Next();
294 string_format.Trimming = StringTrimming.None;
295 string_format.FormatFlags = StringFormatFlags.DisplayFormatControl;
301 #region Internal Properties
318 internal Line CaretLine {
324 internal int CaretPosition {
330 internal Point Caret {
332 return new Point((int)caret.tag.Line.widths[caret.pos] + caret.line.X, caret.line.Y);
336 internal LineTag CaretTag {
346 internal int CRLFSize {
357 /// Whether text is scanned for links
359 internal bool EnableLinks {
360 get { return enable_links; }
361 set { enable_links = value; }
364 internal string PasswordChar {
366 return password_char;
370 password_char = value;
371 PasswordCache.Length = 0;
372 if ((password_char.Length != 0) && (password_char[0] != '\0')) {
380 private StringBuilder PasswordCache {
382 if (password_cache == null)
383 password_cache = new StringBuilder();
384 return password_cache;
388 internal int ViewPortX {
398 internal int Length {
400 return char_count + lines - 1; // Add \n for each line but the last
404 private int CharCount {
412 if (LengthChanged != null) {
413 LengthChanged(this, EventArgs.Empty);
418 internal int ViewPortY {
428 internal int ViewPortWidth {
430 return viewport_width;
434 viewport_width = value;
438 internal int ViewPortHeight {
440 return viewport_height;
444 viewport_height = value;
451 return this.document_x;
455 internal int Height {
457 return this.document_y;
461 internal bool SelectionVisible {
463 return selection_visible;
477 #endregion // Internal Properties
479 #region Private Methods
481 internal void UpdateMargins ()
483 switch (owner.actual_border_style) {
484 case BorderStyle.None:
489 case BorderStyle.FixedSingle:
494 case BorderStyle.Fixed3D:
502 internal void SuspendRecalc ()
504 if (recalc_suspended == 0) {
505 recalc_start = int.MaxValue;
506 recalc_end = int.MinValue;
512 internal void ResumeRecalc (bool immediate_update)
514 if (recalc_suspended > 0)
517 if (recalc_suspended == 0 && (immediate_update || recalc_pending) && !(recalc_start == int.MaxValue && recalc_end == int.MinValue)) {
518 RecalculateDocument (owner.CreateGraphicsInternal (), recalc_start, recalc_end, recalc_optimize);
519 recalc_pending = false;
523 internal void SuspendUpdate ()
528 internal void ResumeUpdate (bool immediate_update)
530 if (update_suspended > 0)
533 if (immediate_update && update_suspended == 0 && update_pending) {
534 UpdateView (GetLine (update_start), 0);
535 update_pending = false;
540 internal int DumpTree(Line line, bool with_tags) {
545 Console.Write("Line {0} [# {1}], Y: {2}, ending style: {3}, Text: '{4}'",
546 line.line_no, line.GetHashCode(), line.Y, line.ending,
547 line.text != null ? line.text.ToString() : "undefined");
549 if (line.left == sentinel) {
550 Console.Write(", left = sentinel");
551 } else if (line.left == null) {
552 Console.Write(", left = NULL");
555 if (line.right == sentinel) {
556 Console.Write(", right = sentinel");
557 } else if (line.right == null) {
558 Console.Write(", right = NULL");
561 Console.WriteLine("");
571 Console.Write(" Tags: ");
572 while (tag != null) {
573 Console.Write("{0} <{1}>-<{2}>", count++, tag.Start, tag.End
574 /*line.text.ToString (tag.start - 1, tag.length)*/);
575 length += tag.Length;
577 if (tag.Line != line) {
578 Console.Write("BAD line link");
579 throw new Exception("Bad line link in tree");
586 if (length > line.text.Length) {
587 throw new Exception(String.Format("Length of tags more than length of text on line (expected {0} calculated {1})", line.text.Length, length));
588 } else if (length < line.text.Length) {
589 throw new Exception(String.Format("Length of tags less than length of text on line (expected {0} calculated {1})", line.text.Length, length));
591 Console.WriteLine("");
593 if (line.left != null) {
594 if (line.left != sentinel) {
595 total += DumpTree(line.left, with_tags);
598 if (line != sentinel) {
599 throw new Exception("Left should not be NULL");
603 if (line.right != null) {
604 if (line.right != sentinel) {
605 total += DumpTree(line.right, with_tags);
608 if (line != sentinel) {
609 throw new Exception("Right should not be NULL");
613 for (int i = 1; i <= this.lines; i++) {
614 if (GetLine(i) == null) {
615 throw new Exception(String.Format("Hole in line order, missing {0}", i));
619 if (line == this.Root) {
620 if (total < this.lines) {
621 throw new Exception(String.Format("Not enough nodes in tree, found {0}, expected {1}", total, this.lines));
622 } else if (total > this.lines) {
623 throw new Exception(String.Format("Too many nodes in tree, found {0}, expected {1}", total, this.lines));
630 private void SetSelectionVisible (bool value)
632 selection_visible = value;
634 // cursor and selection are enemies, we can't have both in the same room at the same time
635 if (owner.IsHandleCreated && !owner.show_caret_w_selection)
636 XplatUI.CaretVisible (owner.Handle, !selection_visible);
639 private void DecrementLines(int line_no) {
643 while (current <= lines) {
644 GetLine(current).line_no--;
650 private void IncrementLines(int line_no) {
653 current = this.lines;
654 while (current >= line_no) {
655 GetLine(current).line_no++;
661 private void RebalanceAfterAdd(Line line1) {
664 while ((line1 != document) && (line1.parent.color == LineColor.Red)) {
665 if (line1.parent == line1.parent.parent.left) {
666 line2 = line1.parent.parent.right;
668 if ((line2 != null) && (line2.color == LineColor.Red)) {
669 line1.parent.color = LineColor.Black;
670 line2.color = LineColor.Black;
671 line1.parent.parent.color = LineColor.Red;
672 line1 = line1.parent.parent;
674 if (line1 == line1.parent.right) {
675 line1 = line1.parent;
679 line1.parent.color = LineColor.Black;
680 line1.parent.parent.color = LineColor.Red;
682 RotateRight(line1.parent.parent);
685 line2 = line1.parent.parent.left;
687 if ((line2 != null) && (line2.color == LineColor.Red)) {
688 line1.parent.color = LineColor.Black;
689 line2.color = LineColor.Black;
690 line1.parent.parent.color = LineColor.Red;
691 line1 = line1.parent.parent;
693 if (line1 == line1.parent.left) {
694 line1 = line1.parent;
698 line1.parent.color = LineColor.Black;
699 line1.parent.parent.color = LineColor.Red;
700 RotateLeft(line1.parent.parent);
704 document.color = LineColor.Black;
707 private void RebalanceAfterDelete(Line line1) {
710 while ((line1 != document) && (line1.color == LineColor.Black)) {
711 if (line1 == line1.parent.left) {
712 line2 = line1.parent.right;
713 if (line2.color == LineColor.Red) {
714 line2.color = LineColor.Black;
715 line1.parent.color = LineColor.Red;
716 RotateLeft(line1.parent);
717 line2 = line1.parent.right;
719 if ((line2.left.color == LineColor.Black) && (line2.right.color == LineColor.Black)) {
720 line2.color = LineColor.Red;
721 line1 = line1.parent;
723 if (line2.right.color == LineColor.Black) {
724 line2.left.color = LineColor.Black;
725 line2.color = LineColor.Red;
727 line2 = line1.parent.right;
729 line2.color = line1.parent.color;
730 line1.parent.color = LineColor.Black;
731 line2.right.color = LineColor.Black;
732 RotateLeft(line1.parent);
736 line2 = line1.parent.left;
737 if (line2.color == LineColor.Red) {
738 line2.color = LineColor.Black;
739 line1.parent.color = LineColor.Red;
740 RotateRight(line1.parent);
741 line2 = line1.parent.left;
743 if ((line2.right.color == LineColor.Black) && (line2.left.color == LineColor.Black)) {
744 line2.color = LineColor.Red;
745 line1 = line1.parent;
747 if (line2.left.color == LineColor.Black) {
748 line2.right.color = LineColor.Black;
749 line2.color = LineColor.Red;
751 line2 = line1.parent.left;
753 line2.color = line1.parent.color;
754 line1.parent.color = LineColor.Black;
755 line2.left.color = LineColor.Black;
756 RotateRight(line1.parent);
761 line1.color = LineColor.Black;
764 private void RotateLeft(Line line1) {
765 Line line2 = line1.right;
767 line1.right = line2.left;
769 if (line2.left != sentinel) {
770 line2.left.parent = line1;
773 if (line2 != sentinel) {
774 line2.parent = line1.parent;
777 if (line1.parent != null) {
778 if (line1 == line1.parent.left) {
779 line1.parent.left = line2;
781 line1.parent.right = line2;
788 if (line1 != sentinel) {
789 line1.parent = line2;
793 private void RotateRight(Line line1) {
794 Line line2 = line1.left;
796 line1.left = line2.right;
798 if (line2.right != sentinel) {
799 line2.right.parent = line1;
802 if (line2 != sentinel) {
803 line2.parent = line1.parent;
806 if (line1.parent != null) {
807 if (line1 == line1.parent.right) {
808 line1.parent.right = line2;
810 line1.parent.left = line2;
817 if (line1 != sentinel) {
818 line1.parent = line2;
823 internal void UpdateView(Line line, int pos) {
824 if (!owner.IsHandleCreated) {
828 if (update_suspended > 0) {
829 update_start = Math.Min (update_start, line.line_no);
830 // update_end = Math.Max (update_end, line.line_no);
831 // recalc_optimize = true;
832 update_pending = true;
836 // Optimize invalidation based on Line alignment
837 if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no, true)) {
838 // Lineheight changed, invalidate the rest of the document
839 if ((line.Y - viewport_y) >=0 ) {
840 // We formatted something that's in view, only draw parts of the screen
841 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, owner.Height - line.Y - viewport_y));
843 // The tag was above the visible area, draw everything
847 switch(line.alignment) {
848 case HorizontalAlignment.Left: {
849 owner.Invalidate(new Rectangle(line.X + (int)line.widths[pos] - viewport_x - 1, line.Y - viewport_y, viewport_width, line.height + 1));
853 case HorizontalAlignment.Center: {
854 owner.Invalidate(new Rectangle(line.X, line.Y - viewport_y, viewport_width, line.height + 1));
858 case HorizontalAlignment.Right: {
859 owner.Invalidate(new Rectangle(line.X, line.Y - viewport_y, (int)line.widths[pos + 1] - viewport_x + line.X, line.height + 1));
867 // Update display from line, down line_count lines; pos is unused, but required for the signature
868 internal void UpdateView(Line line, int line_count, int pos) {
869 if (!owner.IsHandleCreated) {
873 if (recalc_suspended > 0) {
874 recalc_start = Math.Min (recalc_start, line.line_no);
875 recalc_end = Math.Max (recalc_end, line.line_no + line_count);
876 recalc_optimize = true;
877 recalc_pending = true;
881 int start_line_top = line.Y;
886 end_line = GetLine (line.line_no + line_count);
887 if (end_line == null)
888 end_line = GetLine (lines);
891 end_line_bottom = end_line.Y + end_line.height;
893 if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no + line_count, true)) {
894 // Lineheight changed, invalidate the rest of the document
895 if ((line.Y - viewport_y) >=0 ) {
896 // We formatted something that's in view, only draw parts of the screen
897 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, owner.Height - line.Y - viewport_y));
899 // The tag was above the visible area, draw everything
903 int x = 0 - viewport_x;
904 int w = viewport_width;
905 int y = Math.Min (start_line_top - viewport_y, line.Y - viewport_y);
906 int h = Math.Max (end_line_bottom - y, end_line.Y + end_line.height - y);
908 owner.Invalidate (new Rectangle (x, y, w, h));
913 /// Scans the next paragraph for http:/ ftp:/ www. https:/ etc and marks the tags
916 /// <param name="start_line">The line to start on</param>
917 /// <param name="link_changed">marks as true if something is changed</param>
918 private void ScanForLinks (Line start_line, ref bool link_changed)
920 Line current_line = start_line;
921 StringBuilder line_no_breaks = new StringBuilder ();
922 StringBuilder line_link_record = new StringBuilder ();
923 ArrayList cumulative_length_list = new ArrayList ();
924 bool update_caret_tag = false;
926 cumulative_length_list.Add (0);
928 while (current_line != null) {
929 line_no_breaks.Append (current_line.text);
931 if (link_changed == false)
932 current_line.LinkRecord (line_link_record);
934 current_line.ClearLinks ();
936 cumulative_length_list.Add (line_no_breaks.Length);
938 if (current_line.ending == LineEnding.Wrap)
939 current_line = GetLine (current_line.LineNo + 1);
944 // search for protocols.. make sure www. is first!
945 string [] search_terms = new string [] { "www.", "http:/", "ftp:/", "https:/" };
946 int search_found = 0;
948 string line_no_breaks_string = line_no_breaks.ToString ();
949 int line_no_breaks_index = 0;
953 if (line_no_breaks_index >= line_no_breaks_string.Length)
956 index_found = FirstIndexOfAny (line_no_breaks_string, search_terms, line_no_breaks_index, out search_found);
958 //no links found on this line
959 if (index_found == -1)
962 if (search_found == 0) {
963 // if we are at the end of the line to analyse and the end of the line
964 // is "www." then there are no links here
965 if (line_no_breaks_string.Length == index_found + search_terms [0].Length)
968 // if after www. we don't have a letter a digit or a @ or - or /
969 // then it is not a web address, we should continue searching
970 if (char.IsLetterOrDigit (line_no_breaks_string [index_found + search_terms [0].Length]) == false &&
971 "@/~".IndexOf (line_no_breaks_string [index_found + search_terms [0].Length].ToString ()) == -1) {
972 line_no_breaks_index = index_found + search_terms [0].Length;
977 link_end = line_no_breaks_string.Length - 1;
978 line_no_breaks_index = line_no_breaks_string.Length;
980 // we've found a link, we just need to find where it ends now
981 for (int i = index_found + search_terms [search_found].Length; i < line_no_breaks_string.Length; i++) {
982 if (line_no_breaks_string [i - 1] == '.') {
983 if (char.IsLetterOrDigit (line_no_breaks_string [i]) == false &&
984 "@/~".IndexOf (line_no_breaks_string [i].ToString ()) == -1) {
986 line_no_breaks_index = i;
990 if (char.IsLetterOrDigit (line_no_breaks_string [i]) == false &&
991 "@-/:~.?=".IndexOf (line_no_breaks_string [i].ToString ()) == -1) {
993 line_no_breaks_index = i;
999 string link_text = line_no_breaks_string.Substring (index_found, link_end - index_found + 1);
1000 int current_cumulative = 0;
1002 // we've found a link - index_found -> link_end
1003 // now we just make all the tags as containing link and
1004 // point them to the text for the whole link
1006 current_line = start_line;
1008 //find the line we start on
1009 for (current_cumulative = 1; current_cumulative < cumulative_length_list.Count; current_cumulative++)
1010 if ((int)cumulative_length_list [current_cumulative] > index_found)
1013 current_line = GetLine (start_line.LineNo + current_cumulative - 1);
1015 // find the tag we start on
1016 LineTag current_tag = current_line.FindTag (index_found - (int)cumulative_length_list [current_cumulative - 1] + 1);
1018 if (current_tag.Start != (index_found - (int)cumulative_length_list [current_cumulative - 1]) + 1) {
1019 if (current_tag == CaretTag)
1020 update_caret_tag = true;
1022 current_tag = current_tag.Break ((index_found - (int)cumulative_length_list [current_cumulative - 1]) + 1);
1026 current_tag.IsLink = true;
1027 current_tag.LinkText = link_text;
1029 //go through each character
1030 // find the tag we are in
1031 // skip the number of characters in the tag
1032 for (int i = 1; i < link_text.Length; i++) {
1033 // on to a new word-wrapped line
1034 if ((int)cumulative_length_list [current_cumulative] <= index_found + i) {
1036 current_line = GetLine (start_line.LineNo + current_cumulative++);
1037 current_tag = current_line.FindTag (index_found + i - (int)cumulative_length_list [current_cumulative - 1] + 1);
1039 current_tag.IsLink = true;
1040 current_tag.LinkText = link_text;
1045 if (current_tag.End < index_found + 1 + i - (int)cumulative_length_list [current_cumulative - 1]) {
1046 // skip empty tags in the middle of the URL
1048 current_tag = current_tag.Next;
1049 } while (current_tag.Length == 0);
1051 current_tag.IsLink = true;
1052 current_tag.LinkText = link_text;
1056 //if there are characters left in the tag after the link
1058 // make the second part a non link
1059 if (current_tag.End > (index_found + link_text.Length + 1) - (int)cumulative_length_list [current_cumulative - 1]) {
1060 if (current_tag == CaretTag)
1061 update_caret_tag = true;
1063 current_tag.Break ((index_found + link_text.Length + 1) - (int)cumulative_length_list [current_cumulative - 1]);
1067 if (update_caret_tag) {
1068 CaretTag = LineTag.FindTag (CaretLine, CaretPosition);
1069 link_changed = true;
1071 if (link_changed == false) {
1072 current_line = start_line;
1073 StringBuilder new_link_record = new StringBuilder ();
1075 while (current_line != null) {
1076 current_line.LinkRecord (new_link_record);
1078 if (current_line.ending == LineEnding.Wrap)
1079 current_line = GetLine (current_line.LineNo + 1);
1084 if (new_link_record.Equals (line_link_record) == false)
1085 link_changed = true;
1090 private int FirstIndexOfAny (string haystack, string [] needles, int start_index, out int term_found)
1093 int best_index = -1;
1095 for (int i = 0; i < needles.Length; i++) {
1097 int index = haystack.IndexOf (needles [i], start_index, StringComparison.InvariantCultureIgnoreCase);
1099 int index = haystack.ToLower().IndexOf(needles[i], start_index);
1103 if (term_found > -1) {
1104 if (index < best_index) {
1120 private void InvalidateLinks (Rectangle clip)
1122 for (int i = (owner.list_links.Count - 1); i >= 0; i--) {
1123 TextBoxBase.LinkRectangle link = (TextBoxBase.LinkRectangle) owner.list_links [i];
1125 if (clip.IntersectsWith (link.LinkAreaRectangle))
1126 owner.list_links.RemoveAt (i);
1129 #endregion // Private Methods
1131 #region Internal Methods
1133 internal void ScanForLinks (int start, int end, ref bool link_changed)
1136 LineEnding lastending = LineEnding.Rich;
1138 // make sure we start scanning at the real begining of the line
1140 if (start != 1 && GetLine (start - 1).ending == LineEnding.Wrap)
1146 for (int i = start; i <= end && i <= lines; i++) {
1149 if (lastending != LineEnding.Wrap)
1150 ScanForLinks (line, ref link_changed);
1152 lastending = line.ending;
1154 if (lastending == LineEnding.Wrap && (i + 1) <= end)
1159 // Clear the document and reset state
1160 internal void Empty() {
1162 document = sentinel;
1165 // We always have a blank line
1166 Add (1, String.Empty, owner.Font, owner.ForeColor, LineEnding.None);
1168 this.RecalculateDocument(owner.CreateGraphicsInternal());
1169 PositionCaret(0, 0);
1171 SetSelectionVisible (false);
1173 selection_start.line = this.document;
1174 selection_start.pos = 0;
1175 selection_start.tag = selection_start.line.tags;
1176 selection_end.line = this.document;
1177 selection_end.pos = 0;
1178 selection_end.tag = selection_end.line.tags;
1187 if (owner.IsHandleCreated)
1188 owner.Invalidate ();
1191 internal void PositionCaret(Line line, int pos) {
1192 caret.tag = line.FindTag (pos);
1194 MoveCaretToTextTag ();
1199 if (owner.IsHandleCreated) {
1200 if (owner.Focused) {
1201 if (caret.height != caret.tag.Height)
1202 XplatUI.CreateCaret (owner.Handle, caret_width, caret.height);
1203 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x, caret.line.Y + caret.tag.Shift - viewport_y + caret_shift);
1206 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1209 // We set this at the end because we use the heights to determine whether or
1210 // not we need to recreate the caret
1211 caret.height = caret.tag.Height;
1215 internal void PositionCaret(int x, int y) {
1216 if (!owner.IsHandleCreated) {
1220 caret.tag = FindCursor(x, y, out caret.pos);
1222 MoveCaretToTextTag ();
1224 caret.line = caret.tag.Line;
1225 caret.height = caret.tag.Height;
1227 if (owner.ShowSelection && (!selection_visible || owner.show_caret_w_selection)) {
1228 XplatUI.CreateCaret (owner.Handle, caret_width, caret.height);
1229 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x, caret.line.Y + caret.tag.Shift - viewport_y + caret_shift);
1232 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1235 internal void CaretHasFocus() {
1236 if ((caret.tag != null) && owner.IsHandleCreated) {
1237 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1238 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x, caret.line.Y + caret.tag.Shift - viewport_y + caret_shift);
1243 if (owner.IsHandleCreated && SelectionLength () > 0) {
1244 InvalidateSelectionArea ();
1248 internal void CaretLostFocus() {
1249 if (!owner.IsHandleCreated) {
1252 XplatUI.DestroyCaret(owner.Handle);
1255 internal void AlignCaret ()
1260 internal void AlignCaret(bool changeCaretTag) {
1261 if (!owner.IsHandleCreated) {
1265 if (changeCaretTag) {
1266 caret.tag = LineTag.FindTag (caret.line, caret.pos);
1268 MoveCaretToTextTag ();
1271 caret.height = caret.tag.Height;
1273 if (owner.Focused) {
1274 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1275 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x, caret.line.Y + caret.tag.Shift - viewport_y + caret_shift);
1279 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1282 internal void UpdateCaret() {
1283 if (!owner.IsHandleCreated || caret.tag == null) {
1287 MoveCaretToTextTag ();
1289 if (caret.tag.Height != caret.height) {
1290 caret.height = caret.tag.Height;
1291 if (owner.Focused) {
1292 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1296 if (owner.Focused) {
1297 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x, caret.line.Y + caret.tag.Shift - viewport_y + caret_shift);
1301 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1304 internal void DisplayCaret() {
1305 if (!owner.IsHandleCreated) {
1309 if (owner.ShowSelection && (!selection_visible || owner.show_caret_w_selection)) {
1310 XplatUI.CaretVisible(owner.Handle, true);
1314 internal void HideCaret() {
1315 if (!owner.IsHandleCreated) {
1319 if (owner.Focused) {
1320 XplatUI.CaretVisible(owner.Handle, false);
1325 internal void MoveCaretToTextTag ()
1327 if (caret.tag == null || caret.tag.IsTextTag)
1332 if (caret.pos < caret.tag.Start) {
1333 caret.tag = caret.tag.Previous;
1335 caret.tag = caret.tag.Next;
1339 internal void MoveCaret(CaretDirection direction) {
1340 // FIXME should we use IsWordSeparator to detect whitespace, instead
1341 // of looking for actual spaces in the Word move cases?
1343 bool nowrap = false;
1345 case CaretDirection.CharForwardNoWrap:
1347 goto case CaretDirection.CharForward;
1348 case CaretDirection.CharForward: {
1350 if (caret.pos > caret.line.TextLengthWithoutEnding ()) {
1352 // Go into next line
1353 if (caret.line.line_no < this.lines) {
1354 caret.line = GetLine(caret.line.line_no+1);
1356 caret.tag = caret.line.tags;
1361 // Single line; we stay where we are
1365 if ((caret.tag.Start - 1 + caret.tag.Length) < caret.pos) {
1366 caret.tag = caret.tag.Next;
1373 case CaretDirection.CharBackNoWrap:
1375 goto case CaretDirection.CharBack;
1376 case CaretDirection.CharBack: {
1377 if (caret.pos > 0) {
1378 // caret.pos--; // folded into the if below
1380 if (--caret.pos > 0) {
1381 if (caret.tag.Start > caret.pos) {
1382 caret.tag = caret.tag.Previous;
1386 if (caret.line.line_no > 1 && !nowrap) {
1387 caret.line = GetLine(caret.line.line_no - 1);
1388 caret.pos = caret.line.TextLengthWithoutEnding ();
1389 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1396 case CaretDirection.WordForward: {
1399 len = caret.line.text.Length;
1400 if (caret.pos < len) {
1401 while ((caret.pos < len) && (caret.line.text[caret.pos] != ' ')) {
1404 if (caret.pos < len) {
1405 // Skip any whitespace
1406 while ((caret.pos < len) && (caret.line.text[caret.pos] == ' ')) {
1410 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1412 if (caret.line.line_no < this.lines) {
1413 caret.line = GetLine(caret.line.line_no + 1);
1415 caret.tag = caret.line.tags;
1422 case CaretDirection.WordBack: {
1423 if (caret.pos > 0) {
1426 while ((caret.pos > 0) && (caret.line.text[caret.pos] == ' ')) {
1430 while ((caret.pos > 0) && (caret.line.text[caret.pos] != ' ')) {
1434 if (caret.line.text.ToString(caret.pos, 1) == " ") {
1435 if (caret.pos != 0) {
1438 caret.line = GetLine(caret.line.line_no - 1);
1439 caret.pos = caret.line.text.Length;
1442 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1444 if (caret.line.line_no > 1) {
1445 caret.line = GetLine(caret.line.line_no - 1);
1446 caret.pos = caret.line.text.Length;
1447 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1454 case CaretDirection.LineUp: {
1455 if (caret.line.line_no > 1) {
1458 pixel = (int)caret.line.widths[caret.pos];
1459 PositionCaret(pixel, GetLine(caret.line.line_no - 1).Y);
1466 case CaretDirection.LineDown: {
1467 if (caret.line.line_no < lines) {
1470 pixel = (int)caret.line.widths[caret.pos];
1471 PositionCaret(pixel, GetLine(caret.line.line_no + 1).Y);
1478 case CaretDirection.Home: {
1479 if (caret.pos > 0) {
1481 caret.tag = caret.line.tags;
1487 case CaretDirection.End: {
1488 if (caret.pos < caret.line.TextLengthWithoutEnding ()) {
1489 caret.pos = caret.line.TextLengthWithoutEnding ();
1490 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1496 case CaretDirection.PgUp: {
1498 if (viewport_y == 0 && owner.richtext) {
1499 owner.vscroll.Value = 0;
1500 Line line = GetLine (1);
1501 PositionCaret (line, 0);
1504 int y_offset = caret.line.Y + caret.line.height - 1 - viewport_y;
1506 LineTag top = FindCursor ((int) caret.line.widths [caret.pos],
1507 viewport_y - viewport_height, out index);
1509 owner.vscroll.Value = Math.Min (top.Line.Y, owner.vscroll.Maximum - viewport_height);
1510 PositionCaret ((int) caret.line.widths [caret.pos], y_offset + viewport_y);
1515 case CaretDirection.PgDn: {
1517 if (viewport_y + viewport_height >= document_y && owner.richtext) {
1518 owner.vscroll.Value = owner.vscroll.Maximum - viewport_height + 1;
1519 Line line = GetLine (lines);
1520 PositionCaret (line, line.Text.Length);
1523 int y_offset = caret.line.Y - viewport_y;
1525 LineTag top = FindCursor ((int) caret.line.widths [caret.pos],
1526 viewport_y + viewport_height, out index);
1528 owner.vscroll.Value = Math.Min (top.Line.Y, owner.vscroll.Maximum - viewport_height);
1529 PositionCaret ((int) caret.line.widths [caret.pos], y_offset + viewport_y);
1534 case CaretDirection.CtrlPgUp: {
1535 PositionCaret(0, viewport_y);
1540 case CaretDirection.CtrlPgDn: {
1545 tag = FindCursor (0, viewport_y + viewport_height, out index);
1546 if (tag.Line.line_no > 1) {
1547 line = GetLine(tag.Line.line_no - 1);
1551 PositionCaret(line, line.Text.Length);
1556 case CaretDirection.CtrlHome: {
1557 caret.line = GetLine(1);
1559 caret.tag = caret.line.tags;
1565 case CaretDirection.CtrlEnd: {
1566 caret.line = GetLine(lines);
1567 caret.pos = caret.line.TextLengthWithoutEnding ();
1568 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1574 case CaretDirection.SelectionStart: {
1575 caret.line = selection_start.line;
1576 caret.pos = selection_start.pos;
1577 caret.tag = selection_start.tag;
1583 case CaretDirection.SelectionEnd: {
1584 caret.line = selection_end.line;
1585 caret.pos = selection_end.pos;
1586 caret.tag = selection_end.tag;
1594 internal void DumpDoc ()
1596 Console.WriteLine ("<doc lines='{0}'>", lines);
1597 for (int i = 1; i <= lines ; i++) {
1598 Line line = GetLine (i);
1599 Console.WriteLine ("<line no='{0}' ending='{1}'>", line.line_no, line.ending);
1601 LineTag tag = line.tags;
1602 while (tag != null) {
1603 Console.Write ("\t<tag type='{0}' span='{1}->{2}' font='{3}' color='{4}'>",
1604 tag.GetType (), tag.Start, tag.Length, tag.Font, tag.Color);
1605 Console.Write (tag.Text ());
1606 Console.WriteLine ("</tag>");
1609 Console.WriteLine ("</line>");
1611 Console.WriteLine ("</doc>");
1614 internal void Draw (Graphics g, Rectangle clip)
1616 Line line; // Current line being drawn
1617 LineTag tag; // Current tag being drawn
1618 int start; // First line to draw
1619 int end; // Last line to draw
1620 StringBuilder text; // String representing the current line
1623 Color current_color;
1625 // First, figure out from what line to what line we need to draw
1628 start = GetLineByPixel(clip.Top + viewport_y, false).line_no;
1629 end = GetLineByPixel(clip.Bottom + viewport_y, false).line_no;
1631 start = GetLineByPixel(clip.Left + viewport_x, false).line_no;
1632 end = GetLineByPixel(clip.Right + viewport_x, false).line_no;
1635 // remove links in the list (used for mouse down events) that are within the clip area.
1636 InvalidateLinks (clip);
1639 /// We draw the single border ourself
1641 if (owner.actual_border_style == BorderStyle.FixedSingle) {
1642 ControlPaint.DrawBorder (g, owner.ClientRectangle, Color.Black, ButtonBorderStyle.Solid);
1645 /// Make sure that we aren't drawing one more line then we need to
1646 line = GetLine (end - 1);
1647 if (line != null && clip.Bottom == line.Y + line.height - viewport_y)
1653 DateTime n = DateTime.Now;
1654 Console.WriteLine ("Started drawing: {0}s {1}ms", n.Second, n.Millisecond);
1655 Console.WriteLine ("CLIP: {0}", clip);
1656 Console.WriteLine ("S: {0}", GetLine (start).text);
1657 Console.WriteLine ("E: {0}", GetLine (end).text);
1660 // Non multiline selection can be handled outside of the loop
1661 if (!multiline && selection_visible && owner.ShowSelection) {
1662 g.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (ThemeEngine.Current.ColorHighlight),
1663 selection_start.line.widths [selection_start.pos] +
1664 selection_start.line.X - viewport_x,
1665 selection_start.line.Y,
1666 (selection_end.line.X + selection_end.line.widths [selection_end.pos]) -
1667 (selection_start.line.X + selection_start.line.widths [selection_start.pos]),
1668 selection_start.line.height);
1671 while (line_no <= end) {
1672 line = GetLine (line_no);
1673 float line_y = line.Y - viewport_y;
1679 if (PasswordCache.Length < line.text.Length)
1680 PasswordCache.Append(Char.Parse(password_char), line.text.Length - PasswordCache.Length);
1681 else if (PasswordCache.Length > line.text.Length)
1682 PasswordCache.Remove(line.text.Length, PasswordCache.Length - line.text.Length);
1683 text = PasswordCache;
1686 int line_selection_start = text.Length + 1;
1687 int line_selection_end = text.Length + 1;
1688 if (selection_visible && owner.ShowSelection &&
1689 (line_no >= selection_start.line.line_no) &&
1690 (line_no <= selection_end.line.line_no)) {
1692 if (line_no == selection_start.line.line_no)
1693 line_selection_start = selection_start.pos + 1;
1695 line_selection_start = 1;
1697 if (line_no == selection_end.line.line_no)
1698 line_selection_end = selection_end.pos + 1;
1700 line_selection_end = text.Length + 1;
1702 if (line_selection_end == line_selection_start) {
1703 // There isn't really selection
1704 line_selection_start = text.Length + 1;
1705 line_selection_end = line_selection_start;
1706 } else if (multiline) {
1707 // lets draw some selection baby!! (non multiline selection is drawn outside the loop)
1708 g.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (ThemeEngine.Current.ColorHighlight),
1709 line.widths [line_selection_start - 1] + line.X - viewport_x,
1710 line_y, line.widths [line_selection_end - 1] - line.widths [line_selection_start - 1],
1715 current_color = line.tags.ColorToDisplay;
1716 while (tag != null) {
1719 if (tag.Length == 0) {
1724 if (((tag.X + tag.Width) < (clip.Left - viewport_x)) && (tag.X > (clip.Right - viewport_x))) {
1729 if (tag.BackColor != Color.Empty) {
1730 g.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (tag.BackColor), tag.X + line.X - viewport_x,
1731 line_y + tag.Shift, tag.Width, line.height);
1734 tag_color = tag.ColorToDisplay;
1735 current_color = tag_color;
1737 if (!owner.Enabled) {
1738 Color a = tag.Color;
1739 Color b = ThemeEngine.Current.ColorWindowText;
1741 if ((a.R == b.R) && (a.G == b.G) && (a.B == b.B))
1742 tag_color = ThemeEngine.Current.ColorGrayText;
1746 int tag_pos = tag.Start;
1747 current_color = tag_color;
1748 while (tag_pos < tag.Start + tag.Length) {
1749 int old_tag_pos = tag_pos;
1751 if (tag_pos >= line_selection_start && tag_pos < line_selection_end) {
1752 current_color = ThemeEngine.Current.ColorHighlightText;
1753 tag_pos = Math.Min (tag.End, line_selection_end);
1754 } else if (tag_pos < line_selection_start) {
1755 current_color = tag_color;
1756 tag_pos = Math.Min (tag.End, line_selection_start);
1758 current_color = tag_color;
1762 Rectangle text_size;
1764 tag.Draw (g, current_color,
1765 line.X - viewport_x,
1767 old_tag_pos - 1, Math.Min (tag.Start + tag.Length, tag_pos) - 1,
1768 text.ToString (), out text_size, tag.IsLink);
1771 TextBoxBase.LinkRectangle link = new TextBoxBase.LinkRectangle (text_size);
1773 owner.list_links.Add (link);
1779 line.DrawEnding (g, line_y);
1784 private int GetLineEnding (string line, int start, out LineEnding ending)
1789 if (start >= line.Length) {
1790 ending = LineEnding.Wrap;
1794 res = line.IndexOf ('\r', start);
1795 rich_index = line.IndexOf ('\n', start);
1797 // Handle the case where we find both of them, and the \n is before the \r
1798 if (res != -1 && rich_index != -1)
1799 if (rich_index < res) {
1800 ending = LineEnding.Rich;
1805 if (res + 2 < line.Length && line [res + 1] == '\r' && line [res + 2] == '\n') {
1806 ending = LineEnding.Soft;
1809 if (res + 1 < line.Length && line [res + 1] == '\n') {
1810 ending = LineEnding.Hard;
1813 ending = LineEnding.Limp;
1817 if (rich_index != -1) {
1818 ending = LineEnding.Rich;
1822 ending = LineEnding.Wrap;
1826 // Get the line ending, but only of the types specified
1827 private int GetLineEnding (string line, int start, out LineEnding ending, LineEnding type)
1830 int last_length = 0;
1833 index = GetLineEnding (line, index + last_length, out ending);
1834 last_length = LineEndingLength (ending);
1836 ((ending & type) != ending && index != -1);
1838 return index == -1 ? line.Length : index;
1841 internal int LineEndingLength (LineEnding ending)
1844 case LineEnding.Limp:
1845 case LineEnding.Rich:
1847 case LineEnding.Hard:
1849 case LineEnding.Soft:
1856 internal string LineEndingToString (LineEnding ending)
1859 case LineEnding.Limp:
1861 case LineEnding.Hard:
1863 case LineEnding.Soft:
1865 case LineEnding.Rich:
1869 return string.Empty;
1872 internal void Insert (Line line, int pos, bool update_caret, string s)
1874 Insert (line, pos, update_caret, s, line.FindTag (pos));
1877 // Insert text at the given position; use formatting at insertion point for inserted text
1878 internal void Insert (Line line, int pos, bool update_caret, string s, LineTag tag)
1887 // Don't recalculate while we mess around
1890 base_line = line.line_no;
1891 old_line_count = lines;
1893 break_index = GetLineEnding (s, 0, out ending, LineEnding.Hard | LineEnding.Rich);
1895 // There are no line feeds in our text to be pasted
1896 if (break_index == s.Length) {
1897 line.InsertString (pos, s, tag);
1899 // Add up to the first line feed to our current position
1900 line.InsertString (pos, s.Substring (0, break_index + LineEndingLength (ending)), tag);
1902 // Split the rest of the original line to a new line
1903 Split (line, pos + (break_index + LineEndingLength (ending)));
1904 line.ending = ending;
1905 break_index += LineEndingLength (ending);
1906 split_line = GetLine (line.line_no + 1);
1908 // Insert brand new lines for any more line feeds in the inserted string
1910 int next_break = GetLineEnding (s, break_index, out ending, LineEnding.Hard | LineEnding.Rich);
1912 if (next_break == s.Length)
1915 string line_text = s.Substring (break_index, next_break - break_index +
1916 LineEndingLength (ending));
1918 Add (base_line + count, line_text, line.alignment, tag.Font, tag.Color, ending);
1920 Line last = GetLine (base_line + count);
1921 last.ending = ending;
1924 break_index = next_break + LineEndingLength (ending);
1927 // Add the remainder of the insert text to the split
1928 // part of the original line
1929 split_line.InsertString (0, s.Substring (break_index));
1932 // Allow the document to recalculate things
1933 ResumeRecalc (false);
1935 UpdateView (line, lines - old_line_count + 1, pos);
1937 // Move the caret to the end of the inserted text if requested
1939 Line l = GetLine (line.line_no + lines - old_line_count);
1940 PositionCaret (l, l.text.Length);
1945 // Inserts a string at the given position
1946 internal void InsertString (Line line, int pos, string s)
1948 // Update our character count
1949 CharCount += s.Length;
1951 // Insert the text into the Line
1952 line.InsertString (pos, s);
1955 // Inserts a character at the current caret position
1956 internal void InsertCharAtCaret (char ch, bool move_caret)
1958 caret.line.InsertString (caret.pos, ch.ToString ());
1960 undo.RecordTyping (caret.line, caret.pos, ch);
1962 UpdateView (caret.line, caret.pos);
1967 SetSelectionToCaret (true);
1971 internal void InsertPicture (Line line, int pos, RTF.Picture picture)
1979 // Just a place holder basically
1980 line.text.Insert (pos, "I");
1982 PictureTag picture_tag = new PictureTag (line, pos + 1, picture);
1984 tag = LineTag.FindTag (line, pos);
1985 picture_tag.CopyFormattingFrom (tag);
1986 /*next_tag = */tag.Break (pos + 1);
1987 picture_tag.Previous = tag;
1988 picture_tag.Next = tag.Next;
1989 tag.Next = picture_tag;
1992 // Picture tags need to be surrounded by text tags
1994 if (picture_tag.Next == null) {
1995 picture_tag.Next = new LineTag (line, pos + 1);
1996 picture_tag.Next.CopyFormattingFrom (tag);
1997 picture_tag.Next.Previous = picture_tag;
2000 tag = picture_tag.Next;
2001 while (tag != null) {
2009 UpdateView (line, pos);
2012 internal void DeleteMultiline (Line start_line, int pos, int length)
2014 Marker start = new Marker ();
2015 Marker end = new Marker ();
2016 int start_index = LineTagToCharIndex (start_line, pos);
2018 start.line = start_line;
2020 start.tag = LineTag.FindTag (start_line, pos);
2022 CharIndexToLineTag (start_index + length, out end.line,
2023 out end.tag, out end.pos);
2027 if (start.line == end.line) {
2028 DeleteChars (start.line, pos, end.pos - pos);
2031 // Delete first and last lines
2032 DeleteChars (start.line, start.pos, start.line.text.Length - start.pos);
2033 DeleteChars (end.line, 0, end.pos);
2035 int current = start.line.line_no + 1;
2036 if (current < end.line.line_no) {
2037 for (int i = end.line.line_no - 1; i >= current; i--) {
2042 // BIG FAT WARNING - selection_end.line might be stale due
2043 // to the above Delete() call. DONT USE IT before hitting the end of this method!
2045 // Join start and end
2046 Combine (start.line.line_no, current);
2049 ResumeUpdate (true);
2053 // Deletes n characters at the given position; it will not delete past line limits
2055 public void DeleteChars (Line line, int pos, int count)
2057 // Reduce our character count
2060 line.DeleteCharacters (pos, count);
2062 if (pos >= line.TextLengthWithoutEnding ()) {
2063 LineEnding ending = line.ending;
2064 GetLineEnding (line.text.ToString (), 0, out ending);
2066 if (ending != line.ending) {
2067 line.ending = ending;
2070 UpdateView (line, lines, pos);
2071 owner.Invalidate ();
2077 UpdateView (line, lines, pos);
2078 owner.Invalidate ();
2080 UpdateView (line, pos);
2083 // Deletes a character at or after the given position (depending on forward); it will not delete past line limits
2084 public void DeleteChar (Line line, int pos, bool forward)
2086 if ((pos == 0 && forward == false) || (pos == line.text.Length && forward == true))
2090 DeleteChars (line, pos, 1);
2092 DeleteChars (line, pos - 1, 1);
2095 // Combine two lines
2096 internal void Combine(int FirstLine, int SecondLine) {
2097 Combine(GetLine(FirstLine), GetLine(SecondLine));
2100 internal void Combine(Line first, Line second) {
2104 // strip the ending off of the first lines text
2105 first.text.Length = first.text.Length - LineEndingLength (first.ending);
2107 // Combine the two tag chains into one
2110 // Maintain the line ending style
2111 first.ending = second.ending;
2113 while (last.Next != null) {
2117 // need to get the shift before setting the next tag since that effects length
2118 shift = last.Start + last.Length - 1;
2119 last.Next = second.tags;
2120 last.Next.Previous = last;
2122 // Fix up references within the chain
2124 while (last != null) {
2126 last.Start += shift;
2130 // Combine both lines' strings
2131 first.text.Insert(first.text.Length, second.text.ToString());
2132 first.Grow(first.text.Length);
2134 // Remove the reference to our (now combined) tags from the doomed line
2138 DecrementLines(first.line_no + 2); // first.line_no + 1 will be deleted, so we need to start renumbering one later
2141 first.recalc = true;
2142 first.height = 0; // This forces RecalcDocument/UpdateView to redraw from this line on
2143 first.Streamline(lines);
2145 // Update Caret, Selection, etc
2146 if (caret.line == second) {
2147 caret.Combine(first, shift);
2149 if (selection_anchor.line == second) {
2150 selection_anchor.Combine(first, shift);
2152 if (selection_start.line == second) {
2153 selection_start.Combine(first, shift);
2155 if (selection_end.line == second) {
2156 selection_end.Combine(first, shift);
2163 check_first = GetLine(first.line_no);
2164 check_second = GetLine(check_first.line_no + 1);
2166 Console.WriteLine("Pre-delete: Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2169 this.Delete(second);
2172 check_first = GetLine(first.line_no);
2173 check_second = GetLine(check_first.line_no + 1);
2175 Console.WriteLine("Post-delete Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2179 // Split the line at the position into two
2180 internal void Split(int LineNo, int pos) {
2184 line = GetLine(LineNo);
2185 tag = LineTag.FindTag(line, pos);
2186 Split(line, tag, pos);
2189 internal void Split(Line line, int pos) {
2192 tag = LineTag.FindTag(line, pos);
2193 Split(line, tag, pos);
2196 ///<summary>Split line at given tag and position into two lines</summary>
2197 ///if more space becomes available on previous line
2198 internal void Split(Line line, LineTag tag, int pos) {
2202 bool move_sel_start;
2206 move_sel_start = false;
2207 move_sel_end = false;
2213 throw new Exception ("Split called with the wrong tag");
2216 // Adjust selection and cursors
2217 if (caret.line == line && caret.pos >= pos) {
2220 if (selection_start.line == line && selection_start.pos > pos) {
2221 move_sel_start = true;
2224 if (selection_end.line == line && selection_end.pos > pos) {
2225 move_sel_end = true;
2228 // cover the easy case first
2229 if (pos == line.text.Length) {
2230 Add (line.line_no + 1, String.Empty, line.alignment, tag.Font, tag.Color, line.ending);
2232 new_line = GetLine (line.line_no + 1);
2235 caret.line = new_line;
2236 caret.tag = new_line.tags;
2239 if (selection_visible == false) {
2240 SetSelectionToCaret (true);
2244 if (move_sel_start) {
2245 selection_start.line = new_line;
2246 selection_start.pos = 0;
2247 selection_start.tag = new_line.tags;
2251 selection_end.line = new_line;
2252 selection_end.pos = 0;
2253 selection_end.tag = new_line.tags;
2262 // We need to move the rest of the text into the new line
2263 Add (line.line_no + 1, line.text.ToString (pos, line.text.Length - pos), line.alignment, tag.Font, tag.Color, line.ending);
2265 // Now transfer our tags from this line to the next
2266 new_line = GetLine(line.line_no + 1);
2269 new_line.recalc = true;
2271 //make sure that if we are at the end of a tag, we start on the begining
2272 //of a new one, if one exists... Stops us creating an empty tag and
2273 //make the operation easier.
2274 if (tag.Next != null && (tag.Next.Start - 1) == pos)
2277 if ((tag.Start - 1) == pos) {
2280 // We can simply break the chain and move the tag into the next line
2282 // if the tag we are moving is the first, create an empty tag
2283 // for the line we are leaving behind
2284 if (tag == line.tags) {
2285 new_tag = new LineTag(line, 1);
2286 new_tag.CopyFormattingFrom (tag);
2287 line.tags = new_tag;
2290 if (tag.Previous != null) {
2291 tag.Previous.Next = null;
2293 new_line.tags = tag;
2294 tag.Previous = null;
2295 tag.Line = new_line;
2297 // Walk the list and correct the start location of the tags we just bumped into the next line
2298 shift = tag.Start - 1;
2301 while (new_tag != null) {
2302 new_tag.Start -= shift;
2303 new_tag.Line = new_line;
2304 new_tag = new_tag.Next;
2309 new_tag = new LineTag (new_line, 1);
2310 new_tag.Next = tag.Next;
2311 new_tag.CopyFormattingFrom (tag);
2312 new_line.tags = new_tag;
2313 if (new_tag.Next != null) {
2314 new_tag.Next.Previous = new_tag;
2319 new_tag = new_tag.Next;
2320 while (new_tag != null) {
2321 new_tag.Start -= shift;
2322 new_tag.Line = new_line;
2323 new_tag = new_tag.Next;
2329 caret.line = new_line;
2330 caret.pos = caret.pos - pos;
2331 caret.tag = caret.line.FindTag(caret.pos);
2333 if (selection_visible == false) {
2334 SetSelectionToCaret (true);
2338 if (move_sel_start) {
2339 selection_start.line = new_line;
2340 selection_start.pos = selection_start.pos - pos;
2341 if (selection_start.Equals(selection_end))
2342 selection_start.tag = new_line.FindTag(selection_start.pos);
2344 selection_start.tag = new_line.FindTag (selection_start.pos + 1);
2348 selection_end.line = new_line;
2349 selection_end.pos = selection_end.pos - pos;
2350 selection_end.tag = new_line.FindTag(selection_end.pos);
2353 CharCount -= line.text.Length - pos;
2354 line.text.Remove(pos, line.text.Length - pos);
2361 private void SanityCheck () {
2362 for (int i = 1; i < lines; i++) {
2363 LineTag tag = GetLine (i).tags;
2366 throw new Exception ("Line doesn't start at the begining");
2371 while (tag != null) {
2372 if (tag.Start == start)
2373 throw new Exception ("Empty tag!");
2375 if (tag.Start < start)
2376 throw new Exception ("Insane!!");
2385 // Adds a line of text, with given font.
2386 // Bumps any line at that line number that already exists down
2387 internal void Add (int LineNo, string Text, Font font, Color color, LineEnding ending)
2389 Add (LineNo, Text, alignment, font, color, ending);
2392 internal void Add (int LineNo, string Text, HorizontalAlignment align, Font font, Color color, LineEnding ending)
2398 CharCount += Text.Length;
2400 if (LineNo<1 || Text == null) {
2402 throw new ArgumentNullException("LineNo", "Line numbers must be positive");
2404 throw new ArgumentNullException("Text", "Cannot insert NULL line");
2408 add = new Line (this, LineNo, Text, align, font, color, ending);
2411 while (line != sentinel) {
2413 line_no = line.line_no;
2415 if (LineNo > line_no) {
2417 } else if (LineNo < line_no) {
2420 // Bump existing line numbers; walk all nodes to the right of this one and increment line_no
2421 IncrementLines(line.line_no);
2426 add.left = sentinel;
2427 add.right = sentinel;
2429 if (add.parent != null) {
2430 if (LineNo > add.parent.line_no) {
2431 add.parent.right = add;
2433 add.parent.left = add;
2440 RebalanceAfterAdd(add);
2445 internal virtual void Clear() {
2448 document = sentinel;
2451 public virtual object Clone() {
2454 clone = new Document(null);
2456 clone.lines = this.lines;
2457 clone.document = (Line)document.Clone();
2462 private void Delete (int LineNo)
2469 line = GetLine (LineNo);
2471 CharCount -= line.text.Length;
2473 DecrementLines (LineNo + 1);
2477 private void Delete(Line line1) {
2478 Line line2;// = new Line();
2481 if ((line1.left == sentinel) || (line1.right == sentinel)) {
2484 line3 = line1.right;
2485 while (line3.left != sentinel) {
2490 if (line3.left != sentinel) {
2493 line2 = line3.right;
2496 line2.parent = line3.parent;
2497 if (line3.parent != null) {
2498 if(line3 == line3.parent.left) {
2499 line3.parent.left = line2;
2501 line3.parent.right = line2;
2507 if (line3 != line1) {
2510 if (selection_start.line == line3) {
2511 selection_start.line = line1;
2514 if (selection_end.line == line3) {
2515 selection_end.line = line1;
2518 if (selection_anchor.line == line3) {
2519 selection_anchor.line = line1;
2522 if (caret.line == line3) {
2527 line1.alignment = line3.alignment;
2528 line1.ascent = line3.ascent;
2529 line1.hanging_indent = line3.hanging_indent;
2530 line1.height = line3.height;
2531 line1.indent = line3.indent;
2532 line1.line_no = line3.line_no;
2533 line1.recalc = line3.recalc;
2534 line1.right_indent = line3.right_indent;
2535 line1.ending = line3.ending;
2536 line1.space = line3.space;
2537 line1.tags = line3.tags;
2538 line1.text = line3.text;
2539 line1.widths = line3.widths;
2540 line1.offset = line3.offset;
2543 while (tag != null) {
2549 if (line3.color == LineColor.Black)
2550 RebalanceAfterDelete(line2);
2555 // Invalidates the start line until the end of the viewstate
2556 internal void InvalidateLinesAfter (Line start) {
2557 owner.Invalidate (new Rectangle (0, start.Y - viewport_y, viewport_width, viewport_height - start.Y));
2560 // Invalidate a section of the document to trigger redraw
2561 internal void Invalidate(Line start, int start_pos, Line end, int end_pos) {
2567 if ((start == end) && (start_pos == end_pos)) {
2571 if (end_pos == -1) {
2572 end_pos = end.text.Length;
2575 // figure out what's before what so the logic below is straightforward
2576 if (start.line_no < end.line_no) {
2582 } else if (start.line_no > end.line_no) {
2589 if (start_pos < end_pos) {
2603 int endpoint = (int) l1.widths [p2];
2604 if (p2 == l1.text.Length + 1) {
2605 endpoint = (int) viewport_width;
2609 Console.WriteLine("Invaliding backwards from {0}:{1} to {2}:{3} {4}",
2610 l1.line_no, p1, l2.line_no, p2,
2612 (int)l1.widths[p1] + l1.X - viewport_x,
2620 owner.Invalidate(new Rectangle (
2621 (int)l1.widths[p1] + l1.X - viewport_x,
2623 endpoint - (int)l1.widths[p1] + 1,
2629 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);
2630 Console.WriteLine ("invalidate start line: {0} position: {1}", l1.text, p1);
2633 // Three invalidates:
2634 // First line from start
2635 owner.Invalidate(new Rectangle((int)l1.widths[p1] + l1.X - viewport_x, l1.Y - viewport_y, viewport_width, l1.height));
2639 if ((l1.line_no + 1) < l2.line_no) {
2642 y = GetLine(l1.line_no + 1).Y;
2643 owner.Invalidate(new Rectangle(0, y - viewport_y, viewport_width, l2.Y - y));
2646 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);
2652 owner.Invalidate(new Rectangle((int)l2.widths[0] + l2.X - viewport_x, l2.Y - viewport_y, (int)l2.widths[p2] + 1, l2.height));
2654 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);
2659 /// <summary>Select text around caret</summary>
2660 internal void ExpandSelection(CaretSelection mode, bool to_caret) {
2662 // We're expanding the selection to the caret position
2664 case CaretSelection.Line: {
2665 // Invalidate the selection delta
2666 if (caret > selection_prev) {
2667 Invalidate(selection_prev.line, 0, caret.line, caret.line.text.Length);
2669 Invalidate(selection_prev.line, selection_prev.line.text.Length, caret.line, 0);
2672 if (caret.line.line_no <= selection_anchor.line.line_no) {
2673 selection_start.line = caret.line;
2674 selection_start.tag = caret.line.tags;
2675 selection_start.pos = 0;
2677 selection_end.line = selection_anchor.line;
2678 selection_end.tag = selection_anchor.tag;
2679 selection_end.pos = selection_anchor.pos;
2681 selection_end_anchor = true;
2683 selection_start.line = selection_anchor.line;
2684 selection_start.pos = selection_anchor.height;
2685 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height + 1);
2687 selection_end.line = caret.line;
2688 selection_end.tag = caret.line.tags;
2689 selection_end.pos = caret.line.text.Length;
2691 selection_end_anchor = false;
2693 selection_prev.line = caret.line;
2694 selection_prev.tag = caret.tag;
2695 selection_prev.pos = caret.pos;
2700 case CaretSelection.Word: {
2704 start_pos = FindWordSeparator(caret.line, caret.pos, false);
2705 end_pos = FindWordSeparator(caret.line, caret.pos, true);
2708 // Invalidate the selection delta
2709 if (caret > selection_prev) {
2710 Invalidate(selection_prev.line, selection_prev.pos, caret.line, end_pos);
2712 Invalidate(selection_prev.line, selection_prev.pos, caret.line, start_pos);
2714 if (caret < selection_anchor) {
2715 selection_start.line = caret.line;
2716 selection_start.tag = caret.line.FindTag(start_pos + 1);
2717 selection_start.pos = start_pos;
2719 selection_end.line = selection_anchor.line;
2720 selection_end.tag = selection_anchor.tag;
2721 selection_end.pos = selection_anchor.pos;
2723 selection_prev.line = caret.line;
2724 selection_prev.tag = caret.tag;
2725 selection_prev.pos = start_pos;
2727 selection_end_anchor = true;
2729 selection_start.line = selection_anchor.line;
2730 selection_start.pos = selection_anchor.height;
2731 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height + 1);
2733 selection_end.line = caret.line;
2734 selection_end.tag = caret.line.FindTag(end_pos);
2735 selection_end.pos = end_pos;
2737 selection_prev.line = caret.line;
2738 selection_prev.tag = caret.tag;
2739 selection_prev.pos = end_pos;
2741 selection_end_anchor = false;
2746 case CaretSelection.Position: {
2747 SetSelectionToCaret(false);
2752 // We're setting the selection 'around' the caret position
2754 case CaretSelection.Line: {
2755 this.Invalidate(caret.line, 0, caret.line, caret.line.text.Length);
2757 selection_start.line = caret.line;
2758 selection_start.tag = caret.line.tags;
2759 selection_start.pos = 0;
2761 selection_end.line = caret.line;
2762 selection_end.pos = caret.line.text.Length;
2763 selection_end.tag = caret.line.FindTag(selection_end.pos);
2765 selection_anchor.line = selection_end.line;
2766 selection_anchor.tag = selection_end.tag;
2767 selection_anchor.pos = selection_end.pos;
2768 selection_anchor.height = 0;
2770 selection_prev.line = caret.line;
2771 selection_prev.tag = caret.tag;
2772 selection_prev.pos = caret.pos;
2774 this.selection_end_anchor = true;
2779 case CaretSelection.Word: {
2783 start_pos = FindWordSeparator(caret.line, caret.pos, false);
2784 end_pos = FindWordSeparator(caret.line, caret.pos, true);
2786 this.Invalidate(selection_start.line, start_pos, caret.line, end_pos);
2788 selection_start.line = caret.line;
2789 selection_start.tag = caret.line.FindTag(start_pos + 1);
2790 selection_start.pos = start_pos;
2792 selection_end.line = caret.line;
2793 selection_end.tag = caret.line.FindTag(end_pos);
2794 selection_end.pos = end_pos;
2796 selection_anchor.line = selection_end.line;
2797 selection_anchor.tag = selection_end.tag;
2798 selection_anchor.pos = selection_end.pos;
2799 selection_anchor.height = start_pos;
2801 selection_prev.line = caret.line;
2802 selection_prev.tag = caret.tag;
2803 selection_prev.pos = caret.pos;
2805 this.selection_end_anchor = true;
2812 SetSelectionVisible (!(selection_start == selection_end));
2815 internal void SetSelectionToCaret(bool start) {
2817 // Invalidate old selection; selection is being reset to empty
2818 this.Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2820 selection_start.line = caret.line;
2821 selection_start.tag = caret.tag;
2822 selection_start.pos = caret.pos;
2824 // start always also selects end
2825 selection_end.line = caret.line;
2826 selection_end.tag = caret.tag;
2827 selection_end.pos = caret.pos;
2829 selection_anchor.line = caret.line;
2830 selection_anchor.tag = caret.tag;
2831 selection_anchor.pos = caret.pos;
2833 // Invalidate from previous end to caret (aka new end)
2834 if (selection_end_anchor) {
2835 if (selection_start != caret) {
2836 this.Invalidate(selection_start.line, selection_start.pos, caret.line, caret.pos);
2839 if (selection_end != caret) {
2840 this.Invalidate(selection_end.line, selection_end.pos, caret.line, caret.pos);
2844 if (caret < selection_anchor) {
2845 selection_start.line = caret.line;
2846 selection_start.tag = caret.tag;
2847 selection_start.pos = caret.pos;
2849 selection_end.line = selection_anchor.line;
2850 selection_end.tag = selection_anchor.tag;
2851 selection_end.pos = selection_anchor.pos;
2853 selection_end_anchor = true;
2855 selection_start.line = selection_anchor.line;
2856 selection_start.tag = selection_anchor.tag;
2857 selection_start.pos = selection_anchor.pos;
2859 selection_end.line = caret.line;
2860 selection_end.tag = caret.tag;
2861 selection_end.pos = caret.pos;
2863 selection_end_anchor = false;
2867 SetSelectionVisible (!(selection_start == selection_end));
2870 internal void SetSelection(Line start, int start_pos, Line end, int end_pos) {
2871 if (selection_visible) {
2872 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2875 if ((end.line_no < start.line_no) || ((end == start) && (end_pos <= start_pos))) {
2876 selection_start.line = end;
2877 selection_start.tag = LineTag.FindTag(end, end_pos);
2878 selection_start.pos = end_pos;
2880 selection_end.line = start;
2881 selection_end.tag = LineTag.FindTag(start, start_pos);
2882 selection_end.pos = start_pos;
2884 selection_end_anchor = true;
2886 selection_start.line = start;
2887 selection_start.tag = LineTag.FindTag(start, start_pos);
2888 selection_start.pos = start_pos;
2890 selection_end.line = end;
2891 selection_end.tag = LineTag.FindTag(end, end_pos);
2892 selection_end.pos = end_pos;
2894 selection_end_anchor = false;
2897 selection_anchor.line = start;
2898 selection_anchor.tag = selection_start.tag;
2899 selection_anchor.pos = start_pos;
2901 if (((start == end) && (start_pos == end_pos)) || start == null || end == null) {
2902 SetSelectionVisible (false);
2904 SetSelectionVisible (true);
2905 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2909 internal void SetSelectionStart(Line start, int start_pos, bool invalidate) {
2910 // Invalidate from the previous to the new start pos
2912 Invalidate(selection_start.line, selection_start.pos, start, start_pos);
2914 selection_start.line = start;
2915 selection_start.pos = start_pos;
2916 selection_start.tag = LineTag.FindTag(start, start_pos);
2918 selection_anchor.line = start;
2919 selection_anchor.pos = start_pos;
2920 selection_anchor.tag = selection_start.tag;
2922 selection_end_anchor = false;
2925 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
2926 SetSelectionVisible (true);
2928 SetSelectionVisible (false);
2932 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2935 internal void SetSelectionStart(int character_index, bool invalidate) {
2940 if (character_index < 0) {
2944 CharIndexToLineTag(character_index, out line, out tag, out pos);
2945 SetSelectionStart(line, pos, invalidate);
2948 internal void SetSelectionEnd(Line end, int end_pos, bool invalidate) {
2950 if (end == selection_end.line && end_pos == selection_start.pos) {
2951 selection_anchor.line = selection_start.line;
2952 selection_anchor.tag = selection_start.tag;
2953 selection_anchor.pos = selection_start.pos;
2955 selection_end.line = selection_start.line;
2956 selection_end.tag = selection_start.tag;
2957 selection_end.pos = selection_start.pos;
2959 selection_end_anchor = false;
2960 } else if ((end.line_no < selection_anchor.line.line_no) || ((end == selection_anchor.line) && (end_pos <= selection_anchor.pos))) {
2961 selection_start.line = end;
2962 selection_start.tag = LineTag.FindTag(end, end_pos);
2963 selection_start.pos = end_pos;
2965 selection_end.line = selection_anchor.line;
2966 selection_end.tag = selection_anchor.tag;
2967 selection_end.pos = selection_anchor.pos;
2969 selection_end_anchor = true;
2971 selection_start.line = selection_anchor.line;
2972 selection_start.tag = selection_anchor.tag;
2973 selection_start.pos = selection_anchor.pos;
2975 selection_end.line = end;
2976 selection_end.tag = LineTag.FindTag(end, end_pos);
2977 selection_end.pos = end_pos;
2979 selection_end_anchor = false;
2982 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
2983 SetSelectionVisible (true);
2985 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2987 SetSelectionVisible (false);
2988 // ?? Do I need to invalidate here, tests seem to work without it, but I don't think they should :-s
2992 internal void SetSelectionEnd(int character_index, bool invalidate) {
2997 if (character_index < 0) {
3001 CharIndexToLineTag(character_index, out line, out tag, out pos);
3002 SetSelectionEnd(line, pos, invalidate);
3005 internal void SetSelection(Line start, int start_pos) {
3006 if (selection_visible) {
3007 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3010 selection_start.line = start;
3011 selection_start.pos = start_pos;
3012 selection_start.tag = LineTag.FindTag(start, start_pos);
3014 selection_end.line = start;
3015 selection_end.tag = selection_start.tag;
3016 selection_end.pos = start_pos;
3018 selection_anchor.line = start;
3019 selection_anchor.tag = selection_start.tag;
3020 selection_anchor.pos = start_pos;
3022 selection_end_anchor = false;
3023 SetSelectionVisible (false);
3026 internal void InvalidateSelectionArea() {
3027 Invalidate (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3030 // Return the current selection, as string
3031 internal string GetSelection() {
3032 // We return String.Empty if there is no selection
3033 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3034 return string.Empty;
3037 if (selection_start.line == selection_end.line) {
3038 return selection_start.line.text.ToString (selection_start.pos, selection_end.pos - selection_start.pos);
3045 sb = new StringBuilder();
3046 start = selection_start.line.line_no;
3047 end = selection_end.line.line_no;
3049 sb.Append(selection_start.line.text.ToString(selection_start.pos, selection_start.line.text.Length - selection_start.pos) + Environment.NewLine);
3051 if ((start + 1) < end) {
3052 for (i = start + 1; i < end; i++) {
3053 sb.Append(GetLine(i).text.ToString() + Environment.NewLine);
3057 sb.Append(selection_end.line.text.ToString(0, selection_end.pos));
3059 return sb.ToString();
3063 internal void ReplaceSelection(string s, bool select_new) {
3066 int selection_pos_on_line = selection_start.pos;
3067 int selection_start_pos = LineTagToCharIndex (selection_start.line, selection_start.pos);
3070 // First, delete any selected text
3071 if ((selection_start.pos != selection_end.pos) || (selection_start.line != selection_end.line)) {
3072 if (selection_start.line == selection_end.line) {
3073 undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3075 DeleteChars (selection_start.line, selection_start.pos, selection_end.pos - selection_start.pos);
3077 // The tag might have been removed, we need to recalc it
3078 selection_start.tag = selection_start.line.FindTag(selection_start.pos + 1);
3083 start = selection_start.line.line_no;
3084 end = selection_end.line.line_no;
3086 undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3088 InvalidateLinesAfter(selection_start.line);
3090 // Delete first line
3091 DeleteChars (selection_start.line, selection_start.pos, selection_start.line.text.Length - selection_start.pos);
3092 selection_start.line.recalc = true;
3095 DeleteChars(selection_end.line, 0, selection_end.pos);
3099 for (i = end - 1; i >= start; i--) {
3104 // BIG FAT WARNING - selection_end.line might be stale due
3105 // to the above Delete() call. DONT USE IT before hitting the end of this method!
3107 // Join start and end
3108 Combine(selection_start.line.line_no, start);
3113 Insert(selection_start.line, selection_start.pos, false, s);
3114 undo.RecordInsertString (selection_start.line, selection_start.pos, s);
3115 ResumeRecalc (false);
3117 Line begin_update_line = selection_start.line;
3118 int begin_update_pos = selection_start.pos;
3121 CharIndexToLineTag(selection_start_pos + s.Length, out selection_start.line,
3122 out selection_start.tag, out selection_start.pos);
3124 selection_end.line = selection_start.line;
3125 selection_end.pos = selection_start.pos;
3126 selection_end.tag = selection_start.tag;
3127 selection_anchor.line = selection_start.line;
3128 selection_anchor.pos = selection_start.pos;
3129 selection_anchor.tag = selection_start.tag;
3131 SetSelectionVisible (false);
3133 CharIndexToLineTag(selection_start_pos, out selection_start.line,
3134 out selection_start.tag, out selection_start.pos);
3136 CharIndexToLineTag(selection_start_pos + s.Length, out selection_end.line,
3137 out selection_end.tag, out selection_end.pos);
3139 selection_anchor.line = selection_start.line;
3140 selection_anchor.pos = selection_start.pos;
3141 selection_anchor.tag = selection_start.tag;
3143 SetSelectionVisible (true);
3146 PositionCaret (selection_start.line, selection_start.pos);
3147 UpdateView (begin_update_line, selection_end.line.line_no - begin_update_line.line_no, begin_update_pos);
3150 internal void CharIndexToLineTag(int index, out Line line_out, out LineTag tag_out, out int pos) {
3159 for (i = 1; i <= lines; i++) {
3163 chars += line.text.Length;
3165 if (index <= chars) {
3166 // we found the line
3169 while (tag != null) {
3170 if (index < (start + tag.Start + tag.Length - 1)) {
3172 tag_out = LineTag.GetFinalTag (tag);
3173 pos = index - start;
3176 if (tag.Next == null) {
3179 next_line = GetLine(line.line_no + 1);
3181 if (next_line != null) {
3182 line_out = next_line;
3183 tag_out = LineTag.GetFinalTag (next_line.tags);
3188 tag_out = LineTag.GetFinalTag (tag);
3189 pos = line_out.text.Length;
3198 line_out = GetLine(lines);
3199 tag = line_out.tags;
3200 while (tag.Next != null) {
3204 pos = line_out.text.Length;
3207 internal int LineTagToCharIndex(Line line, int pos) {
3211 // Count first and last line
3214 // Count the lines in the middle
3216 for (i = 1; i < line.line_no; i++) {
3217 length += GetLine(i).text.Length;
3225 internal int SelectionLength() {
3226 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3230 if (selection_start.line == selection_end.line) {
3231 return selection_end.pos - selection_start.pos;
3238 // Count first and last line
3239 length = selection_start.line.text.Length - selection_start.pos + selection_end.pos + crlf_size;
3241 // Count the lines in the middle
3242 start = selection_start.line.line_no + 1;
3243 end = selection_end.line.line_no;
3246 for (i = start; i < end; i++) {
3247 Line line = GetLine (i);
3248 length += line.text.Length + LineEndingLength (line.ending);
3259 /// <summary>Give it a Line number and it returns the Line object at with that line number</summary>
3260 internal Line GetLine(int LineNo) {
3261 Line line = document;
3263 while (line != sentinel) {
3264 if (LineNo == line.line_no) {
3266 } else if (LineNo < line.line_no) {
3276 /// <summary>Retrieve the previous tag; walks line boundaries</summary>
3277 internal LineTag PreviousTag(LineTag tag) {
3280 if (tag.Previous != null) {
3281 return tag.Previous;
3285 if (tag.Line.line_no == 1) {
3289 l = GetLine(tag.Line.line_no - 1);
3294 while (t.Next != null) {
3303 /// <summary>Retrieve the next tag; walks line boundaries</summary>
3304 internal LineTag NextTag(LineTag tag) {
3307 if (tag.Next != null) {
3312 l = GetLine(tag.Line.line_no + 1);
3320 internal Line ParagraphStart(Line line) {
3321 Line lastline = line;
3323 if (line.line_no <= 1)
3327 lastline = GetLine (line.line_no - 1);
3328 } while (lastline.ending == LineEnding.Wrap);
3333 internal Line ParagraphEnd(Line line) {
3336 while (line.ending == LineEnding.Wrap) {
3337 l = GetLine(line.line_no + 1);
3338 if ((l == null) || (l.ending != LineEnding.Wrap)) {
3346 /// <summary>Give it a pixel offset coordinate and it returns the Line covering that are (offset
3347 /// is either X or Y depending on if we are multiline
3349 internal Line GetLineByPixel (int offset, bool exact)
3351 Line line = document;
3355 while (line != sentinel) {
3357 if ((offset >= line.Y) && (offset < (line.Y+line.height))) {
3359 } else if (offset < line.Y) {
3366 while (line != sentinel) {
3368 if ((offset >= line.X) && (offset < (line.X + line.Width)))
3370 else if (offset < line.X)
3383 // Give it x/y pixel coordinates and it returns the Tag at that position
3384 internal LineTag FindCursor (int x, int y, out int index)
3388 line = GetLineByPixel (multiline ? y : x, false);
3390 LineTag tag = line.GetTag (x);
3392 if (tag.Length == 0 && tag.Start == 1)
3395 index = tag.GetCharIndex (x - line.align_shift);
3400 /// <summary>Format area of document in specified font and color</summary>
3401 /// <param name="start_pos">1-based start position on start_line</param>
3402 /// <param name="end_pos">1-based end position on end_line </param>
3403 internal void FormatText (Line start_line, int start_pos, Line end_line, int end_pos, Font font,
3404 Color color, Color back_color, FormatSpecified specified)
3408 // First, format the first line
3409 if (start_line != end_line) {
3411 LineTag.FormatText(start_line, start_pos, start_line.text.Length - start_pos + 1, font, color, back_color, specified);
3414 LineTag.FormatText(end_line, 1, end_pos, font, color, back_color, specified);
3416 // Now all the lines inbetween
3417 for (int i = start_line.line_no + 1; i < end_line.line_no; i++) {
3419 LineTag.FormatText(l, 1, l.text.Length, font, color, back_color, specified);
3422 // Special case, single line
3423 LineTag.FormatText(start_line, start_pos, end_pos - start_pos, font, color, back_color, specified);
3425 if ((end_pos - start_pos) == 0 && CaretTag.Length != 0)
3426 CaretTag = CaretTag.Next;
3430 internal void RecalculateAlignments ()
3439 while (line_no <= lines) {
3440 line = GetLine(line_no);
3443 switch (line.alignment) {
3444 case HorizontalAlignment.Left:
3445 line.align_shift = 0;
3447 case HorizontalAlignment.Center:
3448 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3450 case HorizontalAlignment.Right:
3451 line.align_shift = viewport_width - (int)line.widths[line.text.Length] - right_margin;
3461 /// <summary>Calculate formatting for the whole document</summary>
3462 internal bool RecalculateDocument(Graphics g) {
3463 return RecalculateDocument(g, 1, this.lines, false);
3466 /// <summary>Calculate formatting starting at a certain line</summary>
3467 internal bool RecalculateDocument(Graphics g, int start) {
3468 return RecalculateDocument(g, start, this.lines, false);
3471 /// <summary>Calculate formatting within two given line numbers</summary>
3472 internal bool RecalculateDocument(Graphics g, int start, int end) {
3473 return RecalculateDocument(g, start, end, false);
3476 /// <summary>With optimize on, returns true if line heights changed</summary>
3477 internal bool RecalculateDocument(Graphics g, int start, int end, bool optimize) {
3485 if (recalc_suspended > 0) {
3486 recalc_pending = true;
3487 recalc_start = Math.Min (recalc_start, start);
3488 recalc_end = Math.Max (recalc_end, end);
3489 recalc_optimize = optimize;
3493 // Fixup the positions, they can go kinda nuts
3494 // (this is suspend and resume recalc - they set them to 1 and max)
3495 start = Math.Max (start, 1);
3496 end = Math.Min (end, lines);
3498 offset = GetLine(start).offset;
3503 changed = true; // We always return true if we run non-optimized
3508 while (line_no <= (end + this.lines - shift)) {
3509 line = GetLine(line_no++);
3510 line.offset = offset;
3512 // if we are not calculating a password
3515 line.RecalculateLine(g, this);
3517 if (line.recalc && line.RecalculateLine(g, this)) {
3519 // If the height changed, all subsequent lines change
3526 line.RecalculatePasswordLine(g, this);
3528 if (line.recalc && line.RecalculatePasswordLine(g, this)) {
3530 // If the height changed, all subsequent lines change
3537 if (line.widths[line.text.Length] > new_width) {
3538 new_width = (int)line.widths[line.text.Length];
3541 // Calculate alignment
3542 if (line.alignment != HorizontalAlignment.Left) {
3543 if (line.alignment == HorizontalAlignment.Center) {
3544 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3546 line.align_shift = viewport_width - (int)line.widths[line.text.Length] - 1;
3551 offset += line.height;
3553 offset += (int) line.widths [line.text.Length];
3555 if (line_no > lines) {
3560 if (document_x != new_width) {
3561 document_x = new_width;
3562 if (WidthChanged != null) {
3563 WidthChanged(this, null);
3567 RecalculateAlignments();
3569 line = GetLine(lines);
3571 if (document_y != line.Y + line.height) {
3572 document_y = line.Y + line.height;
3573 if (HeightChanged != null) {
3574 HeightChanged(this, null);
3578 // scan for links and tell us if its all
3579 // changed, so we can update everything
3581 ScanForLinks (start, end, ref changed);
3587 internal int Size() {
3591 private void owner_HandleCreated(object sender, EventArgs e) {
3592 RecalculateDocument(owner.CreateGraphicsInternal());
3596 private void owner_VisibleChanged(object sender, EventArgs e) {
3597 if (owner.Visible) {
3598 RecalculateDocument(owner.CreateGraphicsInternal());
3602 internal static bool IsWordSeparator (char ch)
3617 internal int FindWordSeparator(Line line, int pos, bool forward) {
3620 len = line.text.Length;
3623 for (int i = pos + 1; i < len; i++) {
3624 if (IsWordSeparator(line.Text[i])) {
3630 for (int i = pos - 1; i > 0; i--) {
3631 if (IsWordSeparator(line.Text[i - 1])) {
3639 /* Search document for text */
3640 internal bool FindChars(char[] chars, Marker start, Marker end, out Marker result) {
3646 // Search for occurence of any char in the chars array
3647 result = new Marker();
3650 line_no = start.line.line_no;
3652 while (line_no <= end.line.line_no) {
3653 line_len = line.text.Length;
3654 while (pos < line_len) {
3655 for (int i = 0; i < chars.Length; i++) {
3656 if (line.text[pos] == chars[i]) {
3658 if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
3672 line = GetLine(line_no);
3678 // This version does not build one big string for searching, instead it handles
3679 // line-boundaries, which is faster and less memory intensive
3680 // FIXME - Depending on culture stuff we might have to create a big string and use culturespecific
3681 // search stuff and change it to accept and return positions instead of Markers (which would match
3682 // RichTextBox behaviour better but would be inconsistent with the rest of TextControl)
3683 internal bool Find(string search, Marker start, Marker end, out Marker result, RichTextBoxFinds options) {
3685 string search_string;
3697 result = new Marker();
3698 word_option = ((options & RichTextBoxFinds.WholeWord) != 0);
3699 ignore_case = ((options & RichTextBoxFinds.MatchCase) == 0);
3700 reverse = ((options & RichTextBoxFinds.Reverse) != 0);
3703 line_no = start.line.line_no;
3707 // Prep our search string, lowercasing it if we do case-independent matching
3710 sb = new StringBuilder(search);
3711 for (int i = 0; i < sb.Length; i++) {
3712 sb[i] = Char.ToLower(sb[i]);
3714 search_string = sb.ToString();
3716 search_string = search;
3719 // We need to check if the character before our start position is a wordbreak
3722 if ((pos == 0) || (IsWordSeparator(line.text[pos - 1]))) {
3729 if (IsWordSeparator(line.text[pos - 1])) {
3735 // Need to check the end of the previous line
3738 prev_line = GetLine(line_no - 1);
3739 if (prev_line.ending == LineEnding.Wrap) {
3740 if (IsWordSeparator(prev_line.text[prev_line.text.Length - 1])) {
3754 // To avoid duplication of this loop with reverse logic, we search
3755 // through the document, remembering the last match and when returning
3756 // report that last remembered match
3758 last = new Marker();
3759 last.height = -1; // Abused - we use it to track change
3761 while (line_no <= end.line.line_no) {
3762 if (line_no != end.line.line_no) {
3763 line_len = line.text.Length;
3768 while (pos < line_len) {
3770 if (word_option && (current == search_string.Length)) {
3771 if (IsWordSeparator(line.text[pos])) {
3784 c = Char.ToLower(line.text[pos]);
3789 if (c == search_string[current]) {
3795 if (!word_option || (word_option && (word || (current > 0)))) {
3799 if (!word_option && (current == search_string.Length)) {
3816 if (IsWordSeparator(c)) {
3824 // Mark that we just saw a word boundary
3825 if (line.ending != LineEnding.Wrap || line.line_no == lines - 1) {
3829 if (current == search_string.Length) {
3845 line = GetLine(line_no);
3849 if (last.height != -1) {
3859 // if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
3871 internal void GetMarker(out Marker mark, bool start) {
3872 mark = new Marker();
3875 mark.line = GetLine(1);
3876 mark.tag = mark.line.tags;
3879 mark.line = GetLine(lines);
3880 mark.tag = mark.line.tags;
3881 while (mark.tag.Next != null) {
3882 mark.tag = mark.tag.Next;
3884 mark.pos = mark.line.text.Length;
3887 #endregion // Internal Methods
3890 internal event EventHandler CaretMoved;
3891 internal event EventHandler WidthChanged;
3892 internal event EventHandler HeightChanged;
3893 internal event EventHandler LengthChanged;
3894 #endregion // Events
3896 #region Administrative
3897 public IEnumerator GetEnumerator() {
3902 public override bool Equals(object obj) {
3907 if (!(obj is Document)) {
3915 if (ToString().Equals(((Document)obj).ToString())) {
3922 public override int GetHashCode() {
3926 public override string ToString() {
3927 return "document " + this.document_id;
3929 #endregion // Administrative
3932 internal class PictureTag : LineTag {
3934 internal RTF.Picture picture;
3936 internal PictureTag (Line line, int start, RTF.Picture picture) : base (line, start)
3938 this.picture = picture;
3941 public override bool IsTextTag {
3942 get { return false; }
3945 public override SizeF SizeOfPosition (Graphics dc, int pos)
3947 return picture.Size;
3950 internal override int MaxHeight ()
3952 return (int) (picture.Height + 0.5F);
3955 public override void Draw (Graphics dc, Color color, float xoff, float y, int start, int end)
3957 picture.DrawImage (dc, xoff + Line.widths [start], y, false);
3960 public override void Draw (Graphics dc, Color color, float xoff, float y, int start, int end, string text)
3962 picture.DrawImage (dc, xoff + + Line.widths [start], y, false);
3965 public override string Text ()
3971 internal class UndoManager {
3973 internal enum ActionType {
3977 // This is basically just cut & paste
3985 internal class Action {
3986 internal ActionType type;
3987 internal int line_no;
3989 internal object data;
3992 #region Local Variables
3993 private Document document;
3994 private Stack undo_actions;
3995 private Stack redo_actions;
3997 //private int caret_line;
3998 //private int caret_pos;
4000 // When performing an action, we lock the queue, so that the action can't be undone
4001 private bool locked;
4002 #endregion // Local Variables
4004 #region Constructors
4005 internal UndoManager (Document document)
4007 this.document = document;
4008 undo_actions = new Stack (50);
4009 redo_actions = new Stack (50);
4011 #endregion // Constructors
4014 internal bool CanUndo {
4015 get { return undo_actions.Count > 0; }
4018 internal bool CanRedo {
4019 get { return redo_actions.Count > 0; }
4022 internal string UndoActionName {
4024 foreach (Action action in undo_actions) {
4025 if (action.type == ActionType.UserActionBegin)
4026 return (string) action.data;
4027 if (action.type == ActionType.Typing)
4028 return Locale.GetText ("Typing");
4030 return String.Empty;
4034 internal string RedoActionName {
4036 foreach (Action action in redo_actions) {
4037 if (action.type == ActionType.UserActionBegin)
4038 return (string) action.data;
4039 if (action.type == ActionType.Typing)
4040 return Locale.GetText ("Typing");
4042 return String.Empty;
4045 #endregion // Properties
4047 #region Internal Methods
4048 internal void Clear ()
4050 undo_actions.Clear();
4051 redo_actions.Clear();
4054 internal void Undo ()
4057 bool user_action_finished = false;
4059 if (undo_actions.Count == 0)
4062 // Nuke the redo queue
4063 redo_actions.Clear ();
4068 action = (Action) undo_actions.Pop ();
4070 // Put onto redo stack
4071 redo_actions.Push(action);
4074 switch(action.type) {
4076 case ActionType.UserActionBegin:
4077 user_action_finished = true;
4080 case ActionType.UserActionEnd:
4084 case ActionType.InsertString:
4085 start = document.GetLine (action.line_no);
4086 document.SuspendUpdate ();
4087 document.DeleteMultiline (start, action.pos, ((string) action.data).Length + 1);
4088 document.PositionCaret (start, action.pos);
4089 document.SetSelectionToCaret (true);
4090 document.ResumeUpdate (true);
4093 case ActionType.Typing:
4094 start = document.GetLine (action.line_no);
4095 document.SuspendUpdate ();
4096 document.DeleteMultiline (start, action.pos, ((StringBuilder) action.data).Length);
4097 document.PositionCaret (start, action.pos);
4098 document.SetSelectionToCaret (true);
4099 document.ResumeUpdate (true);
4101 // This is an open ended operation, so only a single typing operation can be undone at once
4102 user_action_finished = true;
4105 case ActionType.DeleteString:
4106 start = document.GetLine (action.line_no);
4107 document.SuspendUpdate ();
4108 Insert (start, action.pos, (Line) action.data, true);
4109 document.ResumeUpdate (true);
4112 } while (!user_action_finished && undo_actions.Count > 0);
4117 internal void Redo ()
4120 bool user_action_finished = false;
4122 if (redo_actions.Count == 0)
4125 // You can't undo anything after redoing
4126 undo_actions.Clear ();
4133 action = (Action) redo_actions.Pop ();
4135 switch (action.type) {
4137 case ActionType.UserActionBegin:
4141 case ActionType.UserActionEnd:
4142 user_action_finished = true;
4145 case ActionType.InsertString:
4146 start = document.GetLine (action.line_no);
4147 document.SuspendUpdate ();
4148 start_index = document.LineTagToCharIndex (start, action.pos);
4149 document.InsertString (start, action.pos, (string) action.data);
4150 document.CharIndexToLineTag (start_index + ((string) action.data).Length,
4151 out document.caret.line, out document.caret.tag,
4152 out document.caret.pos);
4153 document.UpdateCaret ();
4154 document.SetSelectionToCaret (true);
4155 document.ResumeUpdate (true);
4158 case ActionType.Typing:
4159 start = document.GetLine (action.line_no);
4160 document.SuspendUpdate ();
4161 start_index = document.LineTagToCharIndex (start, action.pos);
4162 document.InsertString (start, action.pos, ((StringBuilder) action.data).ToString ());
4163 document.CharIndexToLineTag (start_index + ((StringBuilder) action.data).Length,
4164 out document.caret.line, out document.caret.tag,
4165 out document.caret.pos);
4166 document.UpdateCaret ();
4167 document.SetSelectionToCaret (true);
4168 document.ResumeUpdate (true);
4170 // This is an open ended operation, so only a single typing operation can be undone at once
4171 user_action_finished = true;
4174 case ActionType.DeleteString:
4175 start = document.GetLine (action.line_no);
4176 document.SuspendUpdate ();
4177 document.DeleteMultiline (start, action.pos, ((Line) action.data).text.Length);
4178 document.PositionCaret (start, action.pos);
4179 document.SetSelectionToCaret (true);
4180 document.ResumeUpdate (true);
4184 } while (!user_action_finished && redo_actions.Count > 0);
4188 #endregion // Internal Methods
4190 #region Private Methods
4192 public void BeginUserAction (string name)
4197 Action ua = new Action ();
4198 ua.type = ActionType.UserActionBegin;
4201 undo_actions.Push (ua);
4204 public void EndUserAction ()
4209 Action ua = new Action ();
4210 ua.type = ActionType.UserActionEnd;
4212 undo_actions.Push (ua);
4215 // start_pos, end_pos = 1 based
4216 public void RecordDeleteString (Line start_line, int start_pos, Line end_line, int end_pos)
4221 Action a = new Action ();
4223 // We cant simply store the string, because then formatting would be lost
4224 a.type = ActionType.DeleteString;
4225 a.line_no = start_line.line_no;
4227 a.data = Duplicate (start_line, start_pos, end_line, end_pos);
4229 undo_actions.Push(a);
4232 public void RecordInsertString (Line line, int pos, string str)
4234 if (locked || str.Length == 0)
4237 Action a = new Action ();
4239 a.type = ActionType.InsertString;
4241 a.line_no = line.line_no;
4244 undo_actions.Push (a);
4247 public void RecordTyping (Line line, int pos, char ch)
4254 if (undo_actions.Count > 0)
4255 a = (Action) undo_actions.Peek ();
4257 if (a == null || a.type != ActionType.Typing) {
4259 a.type = ActionType.Typing;
4260 a.data = new StringBuilder ();
4261 a.line_no = line.line_no;
4264 undo_actions.Push (a);
4267 StringBuilder data = (StringBuilder) a.data;
4271 // start_pos = 1-based
4272 // end_pos = 1-based
4273 public Line Duplicate(Line start_line, int start_pos, Line end_line, int end_pos)
4279 LineTag current_tag;
4284 line = new Line (start_line.document, start_line.ending);
4287 for (int i = start_line.line_no; i <= end_line.line_no; i++) {
4288 current = document.GetLine(i);
4290 if (start_line.line_no == i) {
4296 if (end_line.line_no == i) {
4299 end = current.text.Length;
4306 line.text = new StringBuilder (current.text.ToString (start, end - start));
4308 // Copy tags from start to start+length onto new line
4309 current_tag = current.FindTag (start + 1);
4310 while ((current_tag != null) && (current_tag.Start <= end)) {
4311 if ((current_tag.Start <= start) && (start < (current_tag.Start + current_tag.Length))) {
4312 // start tag is within this tag
4315 tag_start = current_tag.Start;
4318 tag = new LineTag(line, tag_start - start + 1);
4319 tag.CopyFormattingFrom (current_tag);
4321 current_tag = current_tag.Next;
4323 // Add the new tag to the line
4324 if (line.tags == null) {
4330 while (tail.Next != null) {
4334 tag.Previous = tail;
4338 if ((i + 1) <= end_line.line_no) {
4339 line.ending = current.ending;
4341 // Chain them (we use right/left as next/previous)
4342 line.right = new Line (start_line.document, start_line.ending);
4343 line.right.left = line;
4351 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
4352 internal void Insert(Line line, int pos, Line insert, bool select)
4360 // Handle special case first
4361 if (insert.right == null) {
4363 // Single line insert
4364 document.Split(line, pos);
4366 if (insert.tags == null) {
4367 return; // Blank line
4370 //Insert our tags at the end
4373 while (tag.Next != null) {
4377 offset = tag.Start + tag.Length - 1;
4379 tag.Next = insert.tags;
4380 line.text.Insert(offset, insert.text.ToString());
4382 // Adjust start locations
4384 while (tag != null) {
4385 tag.Start += offset;
4389 // Put it back together
4390 document.Combine(line.line_no, line.line_no + 1);
4393 document.SetSelectionStart (line, pos, false);
4394 document.SetSelectionEnd (line, pos + insert.text.Length, false);
4397 document.UpdateView(line, pos);
4405 while (current != null) {
4407 if (current == insert) {
4408 // Inserting the first line we split the line (and make space)
4409 document.Split(line.line_no, pos);
4410 //Insert our tags at the end of the line
4414 if (tag != null && tag.Length != 0) {
4415 while (tag.Next != null) {
4418 offset = tag.Start + tag.Length - 1;
4419 tag.Next = current.tags;
4420 tag.Next.Previous = tag;
4426 line.tags = current.tags;
4427 line.tags.Previous = null;
4431 line.ending = current.ending;
4433 document.Split(line.line_no, 0);
4435 line.tags = current.tags;
4436 line.tags.Previous = null;
4437 line.ending = current.ending;
4441 // Adjust start locations and line pointers
4442 while (tag != null) {
4443 tag.Start += offset - 1;
4448 line.text.Insert(offset, current.text.ToString());
4449 line.Grow(line.text.Length);
4452 line = document.GetLine(line.line_no + 1);
4454 // FIXME? Test undo of line-boundaries
4455 if ((current.right == null) && (current.tags.Length != 0)) {
4456 document.Combine(line.line_no - 1, line.line_no);
4458 current = current.right;
4463 // Recalculate our document
4464 document.UpdateView(first, lines, pos);
4467 #endregion // Private Methods