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, // 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;
196 // For calculating widths/heights
197 public static readonly StringFormat string_format = new StringFormat (StringFormat.GenericTypographic);
199 private int recalc_suspended;
200 private bool recalc_pending;
201 private int recalc_start = 1; // This starts at one, since lines are 1 based
202 private int recalc_end;
203 private bool recalc_optimize;
205 private int update_suspended;
206 private bool update_pending;
207 private int update_start = 1;
209 internal bool multiline;
210 internal HorizontalAlignment alignment;
213 internal UndoManager undo;
215 internal Marker caret;
216 internal Marker selection_start;
217 internal Marker selection_end;
218 internal bool selection_visible;
219 internal Marker selection_anchor;
220 internal Marker selection_prev;
221 internal bool selection_end_anchor;
223 internal int viewport_x;
224 internal int viewport_y; // The visible area of the document
225 internal int viewport_width;
226 internal int viewport_height;
228 internal int document_x; // Width of the document
229 internal int document_y; // Height of the document
231 internal Rectangle invalid;
233 internal int crlf_size; // 1 or 2, depending on whether we use \r\n or just \n
235 internal TextBoxBase owner; // Who's owning us?
236 static internal int caret_width = 1;
237 static internal int caret_shift = 1;
239 internal int left_margin = 2; // A left margin for all lines
240 internal int top_margin = 2;
241 internal int right_margin = 2;
242 #endregion // Local Variables
245 internal Document (TextBoxBase owner)
254 recalc_pending = false;
256 // Tree related stuff
257 sentinel = new Line (this, LineEnding.None);
258 sentinel.color = LineColor.Black;
262 // We always have a blank line
263 owner.HandleCreated += new EventHandler(owner_HandleCreated);
264 owner.VisibleChanged += new EventHandler(owner_VisibleChanged);
266 Add (1, String.Empty, owner.Font, owner.ForeColor, LineEnding.None);
268 undo = new UndoManager (this);
270 selection_visible = false;
271 selection_start.line = this.document;
272 selection_start.pos = 0;
273 selection_start.tag = selection_start.line.tags;
274 selection_end.line = this.document;
275 selection_end.pos = 0;
276 selection_end.tag = selection_end.line.tags;
277 selection_anchor.line = this.document;
278 selection_anchor.pos = 0;
279 selection_anchor.tag = selection_anchor.line.tags;
280 caret.line = this.document;
282 caret.tag = caret.line.tags;
289 // Default selection is empty
291 document_id = random.Next();
293 string_format.Trimming = StringTrimming.None;
294 string_format.FormatFlags = StringFormatFlags.DisplayFormatControl;
300 #region Internal Properties
317 internal Line CaretLine {
323 internal int CaretPosition {
329 internal Point Caret {
331 return new Point((int)caret.tag.line.widths[caret.pos] + caret.line.X, caret.line.Y);
335 internal LineTag CaretTag {
345 internal int CRLFSize {
355 internal string PasswordChar {
357 return password_char;
361 password_char = value;
362 PasswordCache.Length = 0;
363 if ((password_char.Length != 0) && (password_char[0] != '\0')) {
371 private StringBuilder PasswordCache {
373 if (password_cache == null)
374 password_cache = new StringBuilder();
375 return password_cache;
379 internal int ViewPortX {
389 internal int Length {
391 return char_count + lines - 1; // Add \n for each line but the last
395 private int CharCount {
403 if (LengthChanged != null) {
404 LengthChanged(this, EventArgs.Empty);
409 internal int ViewPortY {
419 internal int ViewPortWidth {
421 return viewport_width;
425 viewport_width = value;
429 internal int ViewPortHeight {
431 return viewport_height;
435 viewport_height = value;
442 return this.document_x;
446 internal int Height {
448 return this.document_y;
452 internal bool SelectionVisible {
454 return selection_visible;
468 #endregion // Internal Properties
470 #region Private Methods
472 internal void UpdateMargins ()
474 switch (owner.actual_border_style) {
475 case BorderStyle.None:
480 case BorderStyle.FixedSingle:
485 case BorderStyle.Fixed3D:
493 internal void SuspendRecalc ()
498 internal void ResumeRecalc (bool immediate_update)
500 if (recalc_suspended > 0)
503 if (immediate_update && recalc_suspended == 0 && recalc_pending) {
504 RecalculateDocument (owner.CreateGraphicsInternal(), recalc_start, recalc_end, recalc_optimize);
505 recalc_pending = false;
509 internal void SuspendUpdate ()
514 internal void ResumeUpdate (bool immediate_update)
516 if (update_suspended > 0)
519 if (immediate_update && update_suspended == 0 && update_pending) {
520 UpdateView (GetLine (update_start), 0);
521 update_pending = false;
526 internal int DumpTree(Line line, bool with_tags) {
531 Console.Write("Line {0} [# {1}], Y: {2}, ending style: {3}, Text: '{4}'",
532 line.line_no, line.GetHashCode(), line.Y, line.ending,
533 line.text != null ? line.text.ToString() : "undefined");
535 if (line.left == sentinel) {
536 Console.Write(", left = sentinel");
537 } else if (line.left == null) {
538 Console.Write(", left = NULL");
541 if (line.right == sentinel) {
542 Console.Write(", right = sentinel");
543 } else if (line.right == null) {
544 Console.Write(", right = NULL");
547 Console.WriteLine("");
557 Console.Write(" Tags: ");
558 while (tag != null) {
559 Console.Write("{0} <{1}>-<{2}>", count++, tag.start, tag.End
560 /*line.text.ToString (tag.start - 1, tag.length)*/);
561 length += tag.Length;
563 if (tag.line != line) {
564 Console.Write("BAD line link");
565 throw new Exception("Bad line link in tree");
572 if (length > line.text.Length) {
573 throw new Exception(String.Format("Length of tags more than length of text on line (expected {0} calculated {1})", line.text.Length, length));
574 } else if (length < line.text.Length) {
575 throw new Exception(String.Format("Length of tags less than length of text on line (expected {0} calculated {1})", line.text.Length, length));
577 Console.WriteLine("");
579 if (line.left != null) {
580 if (line.left != sentinel) {
581 total += DumpTree(line.left, with_tags);
584 if (line != sentinel) {
585 throw new Exception("Left should not be NULL");
589 if (line.right != null) {
590 if (line.right != sentinel) {
591 total += DumpTree(line.right, with_tags);
594 if (line != sentinel) {
595 throw new Exception("Right should not be NULL");
599 for (int i = 1; i <= this.lines; i++) {
600 if (GetLine(i) == null) {
601 throw new Exception(String.Format("Hole in line order, missing {0}", i));
605 if (line == this.Root) {
606 if (total < this.lines) {
607 throw new Exception(String.Format("Not enough nodes in tree, found {0}, expected {1}", total, this.lines));
608 } else if (total > this.lines) {
609 throw new Exception(String.Format("Too many nodes in tree, found {0}, expected {1}", total, this.lines));
616 private void SetSelectionVisible (bool value)
618 selection_visible = value;
620 // cursor and selection are enemies, we can't have both in the same room at the same time
621 if (owner.IsHandleCreated && !owner.show_caret_w_selection)
622 XplatUI.CaretVisible (owner.Handle, !selection_visible);
625 private void DecrementLines(int line_no) {
629 while (current <= lines) {
630 GetLine(current).line_no--;
636 private void IncrementLines(int line_no) {
639 current = this.lines;
640 while (current >= line_no) {
641 GetLine(current).line_no++;
647 private void RebalanceAfterAdd(Line line1) {
650 while ((line1 != document) && (line1.parent.color == LineColor.Red)) {
651 if (line1.parent == line1.parent.parent.left) {
652 line2 = line1.parent.parent.right;
654 if ((line2 != null) && (line2.color == LineColor.Red)) {
655 line1.parent.color = LineColor.Black;
656 line2.color = LineColor.Black;
657 line1.parent.parent.color = LineColor.Red;
658 line1 = line1.parent.parent;
660 if (line1 == line1.parent.right) {
661 line1 = line1.parent;
665 line1.parent.color = LineColor.Black;
666 line1.parent.parent.color = LineColor.Red;
668 RotateRight(line1.parent.parent);
671 line2 = line1.parent.parent.left;
673 if ((line2 != null) && (line2.color == LineColor.Red)) {
674 line1.parent.color = LineColor.Black;
675 line2.color = LineColor.Black;
676 line1.parent.parent.color = LineColor.Red;
677 line1 = line1.parent.parent;
679 if (line1 == line1.parent.left) {
680 line1 = line1.parent;
684 line1.parent.color = LineColor.Black;
685 line1.parent.parent.color = LineColor.Red;
686 RotateLeft(line1.parent.parent);
690 document.color = LineColor.Black;
693 private void RebalanceAfterDelete(Line line1) {
696 while ((line1 != document) && (line1.color == LineColor.Black)) {
697 if (line1 == line1.parent.left) {
698 line2 = line1.parent.right;
699 if (line2.color == LineColor.Red) {
700 line2.color = LineColor.Black;
701 line1.parent.color = LineColor.Red;
702 RotateLeft(line1.parent);
703 line2 = line1.parent.right;
705 if ((line2.left.color == LineColor.Black) && (line2.right.color == LineColor.Black)) {
706 line2.color = LineColor.Red;
707 line1 = line1.parent;
709 if (line2.right.color == LineColor.Black) {
710 line2.left.color = LineColor.Black;
711 line2.color = LineColor.Red;
713 line2 = line1.parent.right;
715 line2.color = line1.parent.color;
716 line1.parent.color = LineColor.Black;
717 line2.right.color = LineColor.Black;
718 RotateLeft(line1.parent);
722 line2 = line1.parent.left;
723 if (line2.color == LineColor.Red) {
724 line2.color = LineColor.Black;
725 line1.parent.color = LineColor.Red;
726 RotateRight(line1.parent);
727 line2 = line1.parent.left;
729 if ((line2.right.color == LineColor.Black) && (line2.left.color == LineColor.Black)) {
730 line2.color = LineColor.Red;
731 line1 = line1.parent;
733 if (line2.left.color == LineColor.Black) {
734 line2.right.color = LineColor.Black;
735 line2.color = LineColor.Red;
737 line2 = line1.parent.left;
739 line2.color = line1.parent.color;
740 line1.parent.color = LineColor.Black;
741 line2.left.color = LineColor.Black;
742 RotateRight(line1.parent);
747 line1.color = LineColor.Black;
750 private void RotateLeft(Line line1) {
751 Line line2 = line1.right;
753 line1.right = line2.left;
755 if (line2.left != sentinel) {
756 line2.left.parent = line1;
759 if (line2 != sentinel) {
760 line2.parent = line1.parent;
763 if (line1.parent != null) {
764 if (line1 == line1.parent.left) {
765 line1.parent.left = line2;
767 line1.parent.right = line2;
774 if (line1 != sentinel) {
775 line1.parent = line2;
779 private void RotateRight(Line line1) {
780 Line line2 = line1.left;
782 line1.left = line2.right;
784 if (line2.right != sentinel) {
785 line2.right.parent = line1;
788 if (line2 != sentinel) {
789 line2.parent = line1.parent;
792 if (line1.parent != null) {
793 if (line1 == line1.parent.right) {
794 line1.parent.right = line2;
796 line1.parent.left = line2;
803 if (line1 != sentinel) {
804 line1.parent = line2;
809 internal void UpdateView(Line line, int pos) {
810 if (!owner.IsHandleCreated) {
814 if (update_suspended > 0) {
815 update_start = Math.Min (update_start, line.line_no);
816 // update_end = Math.Max (update_end, line.line_no);
817 // recalc_optimize = true;
818 update_pending = true;
822 // Optimize invalidation based on Line alignment
823 if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no, true)) {
824 // Lineheight changed, invalidate the rest of the document
825 if ((line.Y - viewport_y) >=0 ) {
826 // We formatted something that's in view, only draw parts of the screen
827 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, owner.Height - line.Y - viewport_y));
829 // The tag was above the visible area, draw everything
833 switch(line.alignment) {
834 case HorizontalAlignment.Left: {
835 owner.Invalidate(new Rectangle(line.X + (int)line.widths[pos] - viewport_x - 1, line.Y - viewport_y, viewport_width, line.height + 1));
839 case HorizontalAlignment.Center: {
840 owner.Invalidate(new Rectangle(line.X, line.Y - viewport_y, viewport_width, line.height + 1));
844 case HorizontalAlignment.Right: {
845 owner.Invalidate(new Rectangle(line.X, line.Y - viewport_y, (int)line.widths[pos + 1] - viewport_x + line.X, line.height + 1));
853 // Update display from line, down line_count lines; pos is unused, but required for the signature
854 internal void UpdateView(Line line, int line_count, int pos) {
855 if (!owner.IsHandleCreated) {
859 if (recalc_suspended > 0) {
860 recalc_start = Math.Min (recalc_start, line.line_no);
861 recalc_end = Math.Max (recalc_end, line.line_no + line_count);
862 recalc_optimize = true;
863 recalc_pending = true;
867 int start_line_top = line.Y;
872 end_line = GetLine (line.line_no + line_count);
873 if (end_line == null)
874 end_line = GetLine (lines);
877 end_line_bottom = end_line.Y + end_line.height;
879 if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no + line_count, true)) {
880 // Lineheight changed, invalidate the rest of the document
881 if ((line.Y - viewport_y) >=0 ) {
882 // We formatted something that's in view, only draw parts of the screen
883 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, owner.Height - line.Y - viewport_y));
885 // The tag was above the visible area, draw everything
889 int x = 0 - viewport_x;
890 int w = viewport_width;
891 int y = Math.Min (start_line_top - viewport_y, line.Y - viewport_y);
892 int h = Math.Max (end_line_bottom - y, end_line.Y + end_line.height - y);
894 owner.Invalidate (new Rectangle (x, y, w, h));
897 #endregion // Private Methods
899 #region Internal Methods
900 // Clear the document and reset state
901 internal void Empty() {
906 // We always have a blank line
907 Add (1, String.Empty, owner.Font, owner.ForeColor, LineEnding.None);
909 this.RecalculateDocument(owner.CreateGraphicsInternal());
912 SetSelectionVisible (false);
914 selection_start.line = this.document;
915 selection_start.pos = 0;
916 selection_start.tag = selection_start.line.tags;
917 selection_end.line = this.document;
918 selection_end.pos = 0;
919 selection_end.tag = selection_end.line.tags;
928 if (owner.IsHandleCreated)
932 internal void PositionCaret(Line line, int pos) {
933 caret.tag = line.FindTag (pos);
935 MoveCaretToTextTag ();
940 if (owner.IsHandleCreated) {
942 if (caret.height != caret.tag.height)
943 XplatUI.CreateCaret (owner.Handle, caret_width, caret.height);
944 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);
947 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
950 // We set this at the end because we use the heights to determine whether or
951 // not we need to recreate the caret
952 caret.height = caret.tag.height;
956 internal void PositionCaret(int x, int y) {
957 if (!owner.IsHandleCreated) {
961 caret.tag = FindCursor(x, y, out caret.pos);
963 MoveCaretToTextTag ();
965 caret.line = caret.tag.line;
966 caret.height = caret.tag.height;
968 if (owner.ShowSelection && (!selection_visible || owner.show_caret_w_selection)) {
969 XplatUI.CreateCaret (owner.Handle, caret_width, caret.height);
970 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);
973 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
976 internal void CaretHasFocus() {
977 if ((caret.tag != null) && owner.IsHandleCreated) {
978 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
979 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);
984 if (owner.IsHandleCreated && SelectionLength () > 0) {
985 InvalidateSelectionArea ();
989 internal void CaretLostFocus() {
990 if (!owner.IsHandleCreated) {
993 XplatUI.DestroyCaret(owner.Handle);
996 internal void AlignCaret() {
997 if (!owner.IsHandleCreated) {
1001 caret.tag = LineTag.FindTag (caret.line, caret.pos);
1003 MoveCaretToTextTag ();
1005 caret.height = caret.tag.height;
1007 if (owner.Focused) {
1008 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1009 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);
1013 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1016 internal void UpdateCaret() {
1017 if (!owner.IsHandleCreated || caret.tag == null) {
1021 MoveCaretToTextTag ();
1023 if (caret.tag.height != caret.height) {
1024 caret.height = caret.tag.height;
1025 if (owner.Focused) {
1026 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1030 if (owner.Focused) {
1031 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);
1035 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1038 internal void DisplayCaret() {
1039 if (!owner.IsHandleCreated) {
1043 if (owner.ShowSelection && (!selection_visible || owner.show_caret_w_selection)) {
1044 XplatUI.CaretVisible(owner.Handle, true);
1048 internal void HideCaret() {
1049 if (!owner.IsHandleCreated) {
1053 if (owner.Focused) {
1054 XplatUI.CaretVisible(owner.Handle, false);
1059 internal void MoveCaretToTextTag ()
1061 if (caret.tag == null || caret.tag.IsTextTag)
1066 if (caret.pos < caret.tag.start) {
1067 caret.tag = caret.tag.previous;
1069 caret.tag = caret.tag.next;
1073 internal void MoveCaret(CaretDirection direction) {
1074 // FIXME should we use IsWordSeparator to detect whitespace, instead
1075 // of looking for actual spaces in the Word move cases?
1077 bool nowrap = false;
1079 case CaretDirection.CharForwardNoWrap:
1081 goto case CaretDirection.CharForward;
1082 case CaretDirection.CharForward: {
1084 if (caret.pos > caret.line.TextLengthWithoutEnding ()) {
1086 // Go into next line
1087 if (caret.line.line_no < this.lines) {
1088 caret.line = GetLine(caret.line.line_no+1);
1090 caret.tag = caret.line.tags;
1095 // Single line; we stay where we are
1099 if ((caret.tag.start - 1 + caret.tag.Length) < caret.pos) {
1100 caret.tag = caret.tag.next;
1107 case CaretDirection.CharBackNoWrap:
1109 goto case CaretDirection.CharBack;
1110 case CaretDirection.CharBack: {
1111 if (caret.pos > 0) {
1112 // caret.pos--; // folded into the if below
1114 if (--caret.pos > 0) {
1115 if (caret.tag.start > caret.pos) {
1116 caret.tag = caret.tag.previous;
1120 if (caret.line.line_no > 1 && !nowrap) {
1121 caret.line = GetLine(caret.line.line_no - 1);
1122 caret.pos = caret.line.TextLengthWithoutEnding ();
1123 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1130 case CaretDirection.WordForward: {
1133 len = caret.line.text.Length;
1134 if (caret.pos < len) {
1135 while ((caret.pos < len) && (caret.line.text[caret.pos] != ' ')) {
1138 if (caret.pos < len) {
1139 // Skip any whitespace
1140 while ((caret.pos < len) && (caret.line.text[caret.pos] == ' ')) {
1144 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1146 if (caret.line.line_no < this.lines) {
1147 caret.line = GetLine(caret.line.line_no + 1);
1149 caret.tag = caret.line.tags;
1156 case CaretDirection.WordBack: {
1157 if (caret.pos > 0) {
1160 while ((caret.pos > 0) && (caret.line.text[caret.pos] == ' ')) {
1164 while ((caret.pos > 0) && (caret.line.text[caret.pos] != ' ')) {
1168 if (caret.line.text.ToString(caret.pos, 1) == " ") {
1169 if (caret.pos != 0) {
1172 caret.line = GetLine(caret.line.line_no - 1);
1173 caret.pos = caret.line.text.Length;
1176 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1178 if (caret.line.line_no > 1) {
1179 caret.line = GetLine(caret.line.line_no - 1);
1180 caret.pos = caret.line.text.Length;
1181 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1188 case CaretDirection.LineUp: {
1189 if (caret.line.line_no > 1) {
1192 pixel = (int)caret.line.widths[caret.pos];
1193 PositionCaret(pixel, GetLine(caret.line.line_no - 1).Y);
1200 case CaretDirection.LineDown: {
1201 if (caret.line.line_no < lines) {
1204 pixel = (int)caret.line.widths[caret.pos];
1205 PositionCaret(pixel, GetLine(caret.line.line_no + 1).Y);
1212 case CaretDirection.Home: {
1213 if (caret.pos > 0) {
1215 caret.tag = caret.line.tags;
1221 case CaretDirection.End: {
1222 if (caret.pos < caret.line.TextLengthWithoutEnding ()) {
1223 caret.pos = caret.line.TextLengthWithoutEnding ();
1224 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1230 case CaretDirection.PgUp: {
1232 if (viewport_y == 0 && owner.richtext) {
1233 owner.vscroll.Value = 0;
1234 Line line = GetLine (1);
1235 PositionCaret (line, 0);
1238 int y_offset = caret.line.Y + caret.line.height - 1 - viewport_y;
1240 LineTag top = FindCursor ((int) caret.line.widths [caret.pos],
1241 viewport_y - viewport_height, out index);
1243 owner.vscroll.Value = Math.Min (top.line.Y, owner.vscroll.Maximum - viewport_height);
1244 PositionCaret ((int) caret.line.widths [caret.pos], y_offset + viewport_y);
1249 case CaretDirection.PgDn: {
1251 if (viewport_y + viewport_height >= document_y && owner.richtext) {
1252 owner.vscroll.Value = owner.vscroll.Maximum - viewport_height + 1;
1253 Line line = GetLine (lines);
1254 PositionCaret (line, line.Text.Length);
1257 int y_offset = caret.line.Y - viewport_y;
1259 LineTag top = FindCursor ((int) caret.line.widths [caret.pos],
1260 viewport_y + viewport_height, out index);
1262 owner.vscroll.Value = Math.Min (top.line.Y, owner.vscroll.Maximum - viewport_height);
1263 PositionCaret ((int) caret.line.widths [caret.pos], y_offset + viewport_y);
1268 case CaretDirection.CtrlPgUp: {
1269 PositionCaret(0, viewport_y);
1274 case CaretDirection.CtrlPgDn: {
1279 tag = FindTag(0, viewport_y + viewport_height, out index, false);
1280 if (tag.line.line_no > 1) {
1281 line = GetLine(tag.line.line_no - 1);
1285 PositionCaret(line, line.Text.Length);
1290 case CaretDirection.CtrlHome: {
1291 caret.line = GetLine(1);
1293 caret.tag = caret.line.tags;
1299 case CaretDirection.CtrlEnd: {
1300 caret.line = GetLine(lines);
1301 caret.pos = caret.line.TextLengthWithoutEnding ();
1302 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1308 case CaretDirection.SelectionStart: {
1309 caret.line = selection_start.line;
1310 caret.pos = selection_start.pos;
1311 caret.tag = selection_start.tag;
1317 case CaretDirection.SelectionEnd: {
1318 caret.line = selection_end.line;
1319 caret.pos = selection_end.pos;
1320 caret.tag = selection_end.tag;
1328 internal void DumpDoc ()
1330 Console.WriteLine ("<doc lines='{0}'>", lines);
1331 for (int i = 1; i <= lines ; i++) {
1332 Line line = GetLine (i);
1333 Console.WriteLine ("<line no='{0}' ending='{1}'>", line.line_no, line.ending);
1335 LineTag tag = line.tags;
1336 while (tag != null) {
1337 Console.Write ("\t<tag type='{0}' span='{1}->{2}' font='{3}' color='{4}'>",
1338 tag.GetType (), tag.start, tag.Length, tag.font, tag.color);
1339 Console.Write (tag.Text ());
1340 Console.WriteLine ("</tag>");
1343 Console.WriteLine ("</line>");
1345 Console.WriteLine ("</doc>");
1348 internal void Draw (Graphics g, Rectangle clip)
1350 Line line; // Current line being drawn
1351 LineTag tag; // Current tag being drawn
1352 int start; // First line to draw
1353 int end; // Last line to draw
1354 StringBuilder text; // String representing the current line
1357 Color current_color;
1359 // First, figure out from what line to what line we need to draw
1362 start = GetLineByPixel(clip.Top + viewport_y, false).line_no;
1363 end = GetLineByPixel(clip.Bottom + viewport_y, false).line_no;
1365 start = GetLineByPixel(clip.Left + viewport_x, false).line_no;
1366 end = GetLineByPixel(clip.Right + viewport_x, false).line_no;
1370 /// We draw the single border ourself
1372 if (owner.actual_border_style == BorderStyle.FixedSingle) {
1373 ControlPaint.DrawBorder (g, owner.ClientRectangle, Color.Black, ButtonBorderStyle.Solid);
1376 /// Make sure that we aren't drawing one more line then we need to
1377 line = GetLine (end - 1);
1378 if (line != null && clip.Bottom == line.Y + line.height + viewport_y)
1384 DateTime n = DateTime.Now;
1385 Console.WriteLine ("Started drawing: {0}s {1}ms", n.Second, n.Millisecond);
1386 Console.WriteLine ("CLIP: {0}", clip);
1387 Console.WriteLine ("S: {0}", GetLine (start).text);
1388 Console.WriteLine ("E: {0}", GetLine (end).text);
1391 // Non multiline selection can be handled outside of the loop
1392 if (!multiline && selection_visible && owner.ShowSelection) {
1393 g.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (ThemeEngine.Current.ColorHighlight),
1394 selection_start.line.widths [selection_start.pos] +
1395 selection_start.line.X - viewport_x,
1396 selection_start.line.Y,
1397 (selection_end.line.X + selection_end.line.widths [selection_end.pos]) -
1398 (selection_start.line.X + selection_start.line.widths [selection_start.pos]),
1399 selection_start.line.height);
1402 while (line_no <= end) {
1403 line = GetLine (line_no);
1404 float line_y = line.Y - viewport_y;
1410 if (PasswordCache.Length < line.text.Length)
1411 PasswordCache.Append(Char.Parse(password_char), line.text.Length - PasswordCache.Length);
1412 else if (PasswordCache.Length > line.text.Length)
1413 PasswordCache.Remove(line.text.Length, PasswordCache.Length - line.text.Length);
1414 text = PasswordCache;
1417 int line_selection_start = text.Length + 1;
1418 int line_selection_end = text.Length + 1;
1419 if (selection_visible && owner.ShowSelection &&
1420 (line_no >= selection_start.line.line_no) &&
1421 (line_no <= selection_end.line.line_no)) {
1423 if (line_no == selection_start.line.line_no)
1424 line_selection_start = selection_start.pos + 1;
1426 line_selection_start = 1;
1428 if (line_no == selection_end.line.line_no)
1429 line_selection_end = selection_end.pos + 1;
1431 line_selection_end = text.Length + 1;
1433 if (line_selection_end == line_selection_start) {
1434 // There isn't really selection
1435 line_selection_start = text.Length + 1;
1436 line_selection_end = line_selection_start;
1437 } else if (multiline) {
1438 // lets draw some selection baby!! (non multiline selection is drawn outside the loop)
1439 g.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (ThemeEngine.Current.ColorHighlight),
1440 line.widths [line_selection_start - 1] + line.X - viewport_x,
1441 line_y, line.widths [line_selection_end - 1] - line.widths [line_selection_start - 1],
1446 current_color = line.tags.color;
1447 while (tag != null) {
1450 if (tag.Length == 0) {
1455 if (((tag.X + tag.Width) < (clip.Left - viewport_x)) && (tag.X > (clip.Right - viewport_x))) {
1460 if (tag.back_color != Color.Empty) {
1461 g.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (tag.back_color), tag.X + line.X - viewport_x,
1462 line_y + tag.shift, tag.Width, line.height);
1465 tag_color = tag.color;
1466 current_color = tag_color;
1468 if (!owner.Enabled) {
1469 Color a = tag.color;
1470 Color b = ThemeEngine.Current.ColorWindowText;
1472 if ((a.R == b.R) && (a.G == b.G) && (a.B == b.B)) {
1473 tag_color = ThemeEngine.Current.ColorGrayText;
1475 } else if (owner.read_only && !owner.backcolor_set) {
1476 tag_color = ThemeEngine.Current.ColorControlText;
1479 int tag_pos = tag.start;
1480 current_color = tag_color;
1481 while (tag_pos < tag.start + tag.Length) {
1482 int old_tag_pos = tag_pos;
1484 if (tag_pos >= line_selection_start && tag_pos < line_selection_end) {
1485 current_color = ThemeEngine.Current.ColorHighlightText;
1486 tag_pos = Math.Min (tag.End, line_selection_end);
1487 } else if (tag_pos < line_selection_start) {
1488 current_color = tag_color;
1489 tag_pos = Math.Min (tag.End, line_selection_start);
1491 current_color = tag_color;
1495 tag.Draw (g, current_color,
1496 line.X - viewport_x,
1498 old_tag_pos - 1, Math.Min (tag.start + tag.Length, tag_pos) - 1,
1504 line.DrawEnding (g, line_y);
1509 internal int GetLineEnding (string line, int start, out LineEnding ending)
1513 res = line.IndexOf ('\r', start);
1515 if (res + 2 < line.Length && line [res + 1] == '\r' && line [res + 2] == '\n') {
1516 ending = LineEnding.Soft;
1519 if (res + 1 < line.Length && line [res + 1] == '\n') {
1520 ending = LineEnding.Hard;
1523 ending = LineEnding.Limp;
1527 res = line.IndexOf ('\n', start);
1529 ending = LineEnding.Rich;
1533 ending = LineEnding.Wrap;
1537 internal int LineEndingLength (LineEnding ending)
1542 case LineEnding.Limp:
1543 case LineEnding.Rich:
1546 case LineEnding.Hard:
1549 case LineEnding.Soft:
1557 internal string LineEndingToString (LineEnding ending)
1559 string res = String.Empty;
1561 case LineEnding.Limp:
1564 case LineEnding.Hard:
1567 case LineEnding.Soft:
1570 case LineEnding.Rich:
1578 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
1579 internal void Insert(Line line, int pos, bool update_caret, string s) {
1585 LineTag tag = LineTag.FindTag (line, pos);
1589 base_line = line.line_no;
1590 old_line_count = lines;
1592 break_index = GetLineEnding (s, 0, out ending);
1594 // Bump the text at insertion point a line down if we're inserting more than one line
1595 if (break_index != s.Length) {
1597 line.ending = ending;
1598 // Remainder of start line is now in base_line + 1
1601 InsertString (line, pos, s.Substring (0, break_index + LineEndingLength (ending)));
1603 break_index += LineEndingLength (ending);
1604 while (break_index < s.Length) {
1605 int next_break = GetLineEnding (s, break_index, out ending);
1606 string line_text = s.Substring (break_index, next_break - break_index +
1607 LineEndingLength (ending));
1609 Add (base_line + count, line_text, line.alignment, tag.font, tag.color, ending);
1611 Line last = GetLine (base_line + count);
1612 last.ending = ending;
1615 break_index = next_break + LineEndingLength (ending);
1618 ResumeRecalc (true);
1620 UpdateView(line, lines - old_line_count + 1, pos);
1623 // Move caret to the end of the inserted text
1624 Line l = GetLine (line.line_no + lines - old_line_count);
1625 PositionCaret(l, l.text.Length);
1630 // Inserts a character at the given position
1631 internal void InsertString(Line line, int pos, string s) {
1632 InsertString(line.FindTag(pos), pos, s);
1635 // Inserts a string at the given position
1636 internal void InsertString(LineTag tag, int pos, string s) {
1645 line.text.Insert(pos, s);
1648 while (tag != null) {
1655 UpdateView(line, pos);
1658 // Inserts a string at the caret position
1659 internal void InsertStringAtCaret(string s, bool move_caret) {
1661 InsertString (caret.tag, caret.pos, s);
1663 UpdateView(caret.line, caret.pos);
1665 caret.pos += s.Length;
1672 // Inserts a character at the given position
1673 internal void InsertChar(Line line, int pos, char ch) {
1674 InsertChar(line.FindTag(pos), pos, ch);
1677 // Inserts a character at the given position
1678 internal void InsertChar(LineTag tag, int pos, char ch) {
1684 line.text.Insert(pos, ch);
1687 while (tag != null) {
1694 undo.RecordTyping (line, pos, ch);
1695 UpdateView(line, pos);
1698 // Inserts a character at the current caret position
1699 internal void InsertCharAtCaret(char ch, bool move_caret) {
1705 caret.line.text.Insert(caret.pos, ch);
1708 if (caret.tag.next != null) {
1709 tag = caret.tag.next;
1710 while (tag != null) {
1716 caret.line.recalc = true;
1718 InsertChar (caret.tag, caret.pos, ch);
1720 UpdateView(caret.line, caret.pos);
1724 SetSelectionToCaret(true);
1729 internal void InsertPicture (Line line, int pos, RTF.Picture picture)
1737 // Just a place holder basically
1738 line.text.Insert (pos, "I");
1740 PictureTag picture_tag = new PictureTag (line, pos + 1, picture);
1742 tag = LineTag.FindTag (line, pos);
1743 picture_tag.CopyFormattingFrom (tag);
1744 /*next_tag = */tag.Break (pos + 1);
1745 picture_tag.previous = tag;
1746 picture_tag.next = tag.next;
1747 tag.next = picture_tag;
1750 // Picture tags need to be surrounded by text tags
1752 if (picture_tag.next == null) {
1753 picture_tag.next = new LineTag (line, pos + 1);
1754 picture_tag.next.CopyFormattingFrom (tag);
1755 picture_tag.next.previous = picture_tag;
1758 tag = picture_tag.next;
1759 while (tag != null) {
1767 UpdateView (line, pos);
1770 internal void DeleteMultiline (Line start_line, int pos, int length)
1772 Marker start = new Marker ();
1773 Marker end = new Marker ();
1774 int start_index = LineTagToCharIndex (start_line, pos);
1776 start.line = start_line;
1778 start.tag = LineTag.FindTag (start_line, pos);
1780 CharIndexToLineTag (start_index + length, out end.line,
1781 out end.tag, out end.pos);
1785 if (start.line == end.line) {
1786 DeleteChars (start.tag, pos, end.pos - pos);
1789 // Delete first and last lines
1790 DeleteChars (start.tag, start.pos, start.line.text.Length - start.pos);
1791 DeleteChars (end.line.tags, 0, end.pos);
1793 int current = start.line.line_no + 1;
1794 if (current < end.line.line_no) {
1795 for (int i = end.line.line_no - 1; i >= current; i--) {
1800 // BIG FAT WARNING - selection_end.line might be stale due
1801 // to the above Delete() call. DONT USE IT before hitting the end of this method!
1803 // Join start and end
1804 Combine (start.line.line_no, current);
1807 ResumeUpdate (true);
1811 // Deletes n characters at the given position; it will not delete past line limits
1813 internal void DeleteChars(LineTag tag, int pos, int count) {
1822 if (pos == line.text.Length) {
1826 line.text.Remove(pos, count);
1828 // Make sure the tag points to the right spot
1829 while ((tag != null) && (tag.End) < pos) {
1837 // Check if we're crossing tag boundaries
1838 if ((pos + count) > (tag.start + tag.Length - 1)) {
1841 // We have to delete cross tag boundaries
1845 left -= tag.start + tag.Length - pos - 1;
1848 while ((tag != null) && (left > 0)) {
1849 tag.start -= count - left;
1851 if (tag.Length > left) {
1860 // We got off easy, same tag
1862 if (tag.Length == 0) {
1867 // Delete empty orphaned tags at the end
1869 while (walk != null && walk.next != null && walk.next.Length == 0) {
1871 walk.next = walk.next.next;
1872 if (walk.next != null)
1873 walk.next.previous = t;
1877 // Adjust the start point of any tags following
1880 while (tag != null) {
1888 line.Streamline(lines);
1892 if (pos >= line.TextLengthWithoutEnding ()) {
1893 LineEnding ending = line.ending;
1894 GetLineEnding (line.text.ToString (), 0, out ending);
1895 if (ending != line.ending) {
1896 line.ending = ending;
1899 UpdateView (line, lines, pos);
1900 owner.Invalidate ();
1906 UpdateView (line, lines, pos);
1907 owner.Invalidate ();
1909 UpdateView(line, pos);
1912 // Deletes a character at or after the given position (depending on forward); it will not delete past line limits
1913 internal void DeleteChar(LineTag tag, int pos, bool forward) {
1922 if ((pos == 0 && forward == false) || (pos == line.text.Length && forward == true)) {
1928 line.text.Remove(pos, 1);
1930 while ((tag != null) && (tag.start + tag.Length - 1) <= pos) {
1940 if (tag.Length == 0) {
1945 line.text.Remove(pos, 1);
1946 if (pos >= (tag.start - 1)) {
1948 if (tag.Length == 0) {
1951 } else if (tag.previous != null) {
1952 // tag.previous.length--;
1953 if (tag.previous.Length == 0) {
1959 // Delete empty orphaned tags at the end
1961 while (walk != null && walk.next != null && walk.next.Length == 0) {
1963 walk.next = walk.next.next;
1964 if (walk.next != null)
1965 walk.next.previous = t;
1970 while (tag != null) {
1976 line.Streamline(lines);
1980 if (pos >= line.TextLengthWithoutEnding ()) {
1981 LineEnding ending = line.ending;
1982 GetLineEnding (line.text.ToString (), 0, out ending);
1983 if (ending != line.ending) {
1984 line.ending = ending;
1987 UpdateView (line, lines, pos);
1988 owner.Invalidate ();
1994 UpdateView (line, lines, pos);
1995 owner.Invalidate ();
1997 UpdateView(line, pos);
2000 // Combine two lines
2001 internal void Combine(int FirstLine, int SecondLine) {
2002 Combine(GetLine(FirstLine), GetLine(SecondLine));
2005 internal void Combine(Line first, Line second) {
2009 // strip the ending off of the first lines text
2010 first.text.Length = first.text.Length - LineEndingLength (first.ending);
2012 // Combine the two tag chains into one
2015 // Maintain the line ending style
2016 first.ending = second.ending;
2018 while (last.next != null) {
2022 // need to get the shift before setting the next tag since that effects length
2023 shift = last.start + last.Length - 1;
2024 last.next = second.tags;
2025 last.next.previous = last;
2027 // Fix up references within the chain
2029 while (last != null) {
2031 last.start += shift;
2035 // Combine both lines' strings
2036 first.text.Insert(first.text.Length, second.text.ToString());
2037 first.Grow(first.text.Length);
2039 // Remove the reference to our (now combined) tags from the doomed line
2043 DecrementLines(first.line_no + 2); // first.line_no + 1 will be deleted, so we need to start renumbering one later
2046 first.recalc = true;
2047 first.height = 0; // This forces RecalcDocument/UpdateView to redraw from this line on
2048 first.Streamline(lines);
2050 // Update Caret, Selection, etc
2051 if (caret.line == second) {
2052 caret.Combine(first, shift);
2054 if (selection_anchor.line == second) {
2055 selection_anchor.Combine(first, shift);
2057 if (selection_start.line == second) {
2058 selection_start.Combine(first, shift);
2060 if (selection_end.line == second) {
2061 selection_end.Combine(first, shift);
2068 check_first = GetLine(first.line_no);
2069 check_second = GetLine(check_first.line_no + 1);
2071 Console.WriteLine("Pre-delete: Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2074 this.Delete(second);
2077 check_first = GetLine(first.line_no);
2078 check_second = GetLine(check_first.line_no + 1);
2080 Console.WriteLine("Post-delete Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2084 // Split the line at the position into two
2085 internal void Split(int LineNo, int pos) {
2089 line = GetLine(LineNo);
2090 tag = LineTag.FindTag(line, pos);
2091 Split(line, tag, pos);
2094 internal void Split(Line line, int pos) {
2097 tag = LineTag.FindTag(line, pos);
2098 Split(line, tag, pos);
2101 ///<summary>Split line at given tag and position into two lines</summary>
2102 ///if more space becomes available on previous line</param>
2103 internal void Split(Line line, LineTag tag, int pos) {
2107 bool move_sel_start;
2111 move_sel_start = false;
2112 move_sel_end = false;
2114 // Adjust selection and cursors
2115 if (caret.line == line && caret.pos >= pos) {
2118 if (selection_start.line == line && selection_start.pos > pos) {
2119 move_sel_start = true;
2122 if (selection_end.line == line && selection_end.pos > pos) {
2123 move_sel_end = true;
2126 // cover the easy case first
2127 if (pos == line.text.Length) {
2128 Add (line.line_no + 1, String.Empty, line.alignment, tag.font, tag.color, line.ending);
2130 new_line = GetLine (line.line_no + 1);
2133 caret.line = new_line;
2134 caret.tag = new_line.tags;
2138 if (move_sel_start) {
2139 selection_start.line = new_line;
2140 selection_start.pos = 0;
2141 selection_start.tag = new_line.tags;
2145 selection_end.line = new_line;
2146 selection_end.pos = 0;
2147 selection_end.tag = new_line.tags;
2152 // We need to move the rest of the text into the new line
2153 Add (line.line_no + 1, line.text.ToString (pos, line.text.Length - pos), line.alignment, tag.font, tag.color, line.ending);
2155 // Now transfer our tags from this line to the next
2156 new_line = GetLine(line.line_no + 1);
2159 new_line.recalc = true;
2161 if ((tag.start - 1) == pos) {
2164 // We can simply break the chain and move the tag into the next line
2165 if (tag == line.tags) {
2166 new_tag = new LineTag(line, 1);
2167 new_tag.CopyFormattingFrom (tag);
2168 line.tags = new_tag;
2171 if (tag.previous != null) {
2172 tag.previous.next = null;
2174 new_line.tags = tag;
2175 tag.previous = null;
2176 tag.line = new_line;
2178 // Walk the list and correct the start location of the tags we just bumped into the next line
2179 shift = tag.start - 1;
2182 while (new_tag != null) {
2183 new_tag.start -= shift;
2184 new_tag.line = new_line;
2185 new_tag = new_tag.next;
2190 new_tag = new LineTag (new_line, 1);
2191 new_tag.next = tag.next;
2192 new_tag.CopyFormattingFrom (tag);
2193 new_line.tags = new_tag;
2194 if (new_tag.next != null) {
2195 new_tag.next.previous = new_tag;
2200 new_tag = new_tag.next;
2201 while (new_tag != null) {
2202 new_tag.start -= shift;
2203 new_tag.line = new_line;
2204 new_tag = new_tag.next;
2210 caret.line = new_line;
2211 caret.pos = caret.pos - pos;
2212 caret.tag = caret.line.FindTag(caret.pos);
2215 if (move_sel_start) {
2216 selection_start.line = new_line;
2217 selection_start.pos = selection_start.pos - pos;
2218 selection_start.tag = new_line.FindTag(selection_start.pos);
2222 selection_end.line = new_line;
2223 selection_end.pos = selection_end.pos - pos;
2224 selection_end.tag = new_line.FindTag(selection_end.pos);
2227 CharCount -= line.text.Length - pos;
2228 line.text.Remove(pos, line.text.Length - pos);
2231 // Adds a line of text, with given font.
2232 // Bumps any line at that line number that already exists down
2233 internal void Add (int LineNo, string Text, Font font, Color color, LineEnding ending)
2235 Add (LineNo, Text, alignment, font, color, ending);
2238 internal void Add (int LineNo, string Text, HorizontalAlignment align, Font font, Color color, LineEnding ending)
2244 CharCount += Text.Length;
2246 if (LineNo<1 || Text == null) {
2248 throw new ArgumentNullException("LineNo", "Line numbers must be positive");
2250 throw new ArgumentNullException("Text", "Cannot insert NULL line");
2254 add = new Line (this, LineNo, Text, align, font, color, ending);
2257 while (line != sentinel) {
2259 line_no = line.line_no;
2261 if (LineNo > line_no) {
2263 } else if (LineNo < line_no) {
2266 // Bump existing line numbers; walk all nodes to the right of this one and increment line_no
2267 IncrementLines(line.line_no);
2272 add.left = sentinel;
2273 add.right = sentinel;
2275 if (add.parent != null) {
2276 if (LineNo > add.parent.line_no) {
2277 add.parent.right = add;
2279 add.parent.left = add;
2286 RebalanceAfterAdd(add);
2291 internal virtual void Clear() {
2294 document = sentinel;
2297 public virtual object Clone() {
2300 clone = new Document(null);
2302 clone.lines = this.lines;
2303 clone.document = (Line)document.Clone();
2308 internal void Delete(int LineNo) {
2315 line = GetLine(LineNo);
2317 CharCount -= line.text.Length;
2319 DecrementLines(LineNo + 1);
2323 internal void Delete(Line line1) {
2324 Line line2;// = new Line();
2327 if ((line1.left == sentinel) || (line1.right == sentinel)) {
2330 line3 = line1.right;
2331 while (line3.left != sentinel) {
2336 if (line3.left != sentinel) {
2339 line2 = line3.right;
2342 line2.parent = line3.parent;
2343 if (line3.parent != null) {
2344 if(line3 == line3.parent.left) {
2345 line3.parent.left = line2;
2347 line3.parent.right = line2;
2353 if (line3 != line1) {
2356 if (selection_start.line == line3) {
2357 selection_start.line = line1;
2360 if (selection_end.line == line3) {
2361 selection_end.line = line1;
2364 if (selection_anchor.line == line3) {
2365 selection_anchor.line = line1;
2368 if (caret.line == line3) {
2373 line1.alignment = line3.alignment;
2374 line1.ascent = line3.ascent;
2375 line1.hanging_indent = line3.hanging_indent;
2376 line1.height = line3.height;
2377 line1.indent = line3.indent;
2378 line1.line_no = line3.line_no;
2379 line1.recalc = line3.recalc;
2380 line1.right_indent = line3.right_indent;
2381 line1.ending = line3.ending;
2382 line1.space = line3.space;
2383 line1.tags = line3.tags;
2384 line1.text = line3.text;
2385 line1.widths = line3.widths;
2386 line1.offset = line3.offset;
2389 while (tag != null) {
2395 if (line3.color == LineColor.Black)
2396 RebalanceAfterDelete(line2);
2401 // Invalidate a section of the document to trigger redraw
2402 internal void Invalidate(Line start, int start_pos, Line end, int end_pos) {
2408 if ((start == end) && (start_pos == end_pos)) {
2412 if (end_pos == -1) {
2413 end_pos = end.text.Length;
2416 // figure out what's before what so the logic below is straightforward
2417 if (start.line_no < end.line_no) {
2423 } else if (start.line_no > end.line_no) {
2430 if (start_pos < end_pos) {
2444 int endpoint = (int) l1.widths [p2];
2445 if (p2 == l1.text.Length + 1) {
2446 endpoint = (int) viewport_width;
2450 Console.WriteLine("Invaliding backwards from {0}:{1} to {2}:{3} {4}",
2451 l1.line_no, p1, l2.line_no, p2,
2453 (int)l1.widths[p1] + l1.X - viewport_x,
2461 owner.Invalidate(new Rectangle (
2462 (int)l1.widths[p1] + l1.X - viewport_x,
2464 endpoint - (int)l1.widths[p1] + 1,
2470 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);
2471 Console.WriteLine ("invalidate start line: {0} position: {1}", l1.text, p1);
2474 // Three invalidates:
2475 // First line from start
2476 owner.Invalidate(new Rectangle((int)l1.widths[p1] + l1.X - viewport_x, l1.Y - viewport_y, viewport_width, l1.height));
2480 if ((l1.line_no + 1) < l2.line_no) {
2483 y = GetLine(l1.line_no + 1).Y;
2484 owner.Invalidate(new Rectangle(0, y - viewport_y, viewport_width, l2.Y - y));
2487 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);
2493 owner.Invalidate(new Rectangle((int)l2.widths[0] + l2.X - viewport_x, l2.Y - viewport_y, (int)l2.widths[p2] + 1, l2.height));
2495 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);
2500 /// <summary>Select text around caret</summary>
2501 internal void ExpandSelection(CaretSelection mode, bool to_caret) {
2503 // We're expanding the selection to the caret position
2505 case CaretSelection.Line: {
2506 // Invalidate the selection delta
2507 if (caret > selection_prev) {
2508 Invalidate(selection_prev.line, 0, caret.line, caret.line.text.Length);
2510 Invalidate(selection_prev.line, selection_prev.line.text.Length, caret.line, 0);
2513 if (caret.line.line_no <= selection_anchor.line.line_no) {
2514 selection_start.line = caret.line;
2515 selection_start.tag = caret.line.tags;
2516 selection_start.pos = 0;
2518 selection_end.line = selection_anchor.line;
2519 selection_end.tag = selection_anchor.tag;
2520 selection_end.pos = selection_anchor.pos;
2522 selection_end_anchor = true;
2524 selection_start.line = selection_anchor.line;
2525 selection_start.pos = selection_anchor.height;
2526 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height);
2528 selection_end.line = caret.line;
2529 selection_end.tag = caret.line.tags;
2530 selection_end.pos = caret.line.text.Length;
2532 selection_end_anchor = false;
2534 selection_prev.line = caret.line;
2535 selection_prev.tag = caret.tag;
2536 selection_prev.pos = caret.pos;
2541 case CaretSelection.Word: {
2545 start_pos = FindWordSeparator(caret.line, caret.pos, false);
2546 end_pos = FindWordSeparator(caret.line, caret.pos, true);
2549 // Invalidate the selection delta
2550 if (caret > selection_prev) {
2551 Invalidate(selection_prev.line, selection_prev.pos, caret.line, end_pos);
2553 Invalidate(selection_prev.line, selection_prev.pos, caret.line, start_pos);
2555 if (caret < selection_anchor) {
2556 selection_start.line = caret.line;
2557 selection_start.tag = caret.line.FindTag(start_pos);
2558 selection_start.pos = start_pos;
2560 selection_end.line = selection_anchor.line;
2561 selection_end.tag = selection_anchor.tag;
2562 selection_end.pos = selection_anchor.pos;
2564 selection_prev.line = caret.line;
2565 selection_prev.tag = caret.tag;
2566 selection_prev.pos = start_pos;
2568 selection_end_anchor = true;
2570 selection_start.line = selection_anchor.line;
2571 selection_start.pos = selection_anchor.height;
2572 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height);
2574 selection_end.line = caret.line;
2575 selection_end.tag = caret.line.FindTag(end_pos);
2576 selection_end.pos = end_pos;
2578 selection_prev.line = caret.line;
2579 selection_prev.tag = caret.tag;
2580 selection_prev.pos = end_pos;
2582 selection_end_anchor = false;
2587 case CaretSelection.Position: {
2588 SetSelectionToCaret(false);
2593 // We're setting the selection 'around' the caret position
2595 case CaretSelection.Line: {
2596 this.Invalidate(caret.line, 0, caret.line, caret.line.text.Length);
2598 selection_start.line = caret.line;
2599 selection_start.tag = caret.line.tags;
2600 selection_start.pos = 0;
2602 selection_end.line = caret.line;
2603 selection_end.pos = caret.line.text.Length;
2604 selection_end.tag = caret.line.FindTag(selection_end.pos);
2606 selection_anchor.line = selection_end.line;
2607 selection_anchor.tag = selection_end.tag;
2608 selection_anchor.pos = selection_end.pos;
2609 selection_anchor.height = 0;
2611 selection_prev.line = caret.line;
2612 selection_prev.tag = caret.tag;
2613 selection_prev.pos = caret.pos;
2615 this.selection_end_anchor = true;
2620 case CaretSelection.Word: {
2624 start_pos = FindWordSeparator(caret.line, caret.pos, false);
2625 end_pos = FindWordSeparator(caret.line, caret.pos, true);
2627 this.Invalidate(selection_start.line, start_pos, caret.line, end_pos);
2629 selection_start.line = caret.line;
2630 selection_start.tag = caret.line.FindTag(start_pos);
2631 selection_start.pos = start_pos;
2633 selection_end.line = caret.line;
2634 selection_end.tag = caret.line.FindTag(end_pos);
2635 selection_end.pos = end_pos;
2637 selection_anchor.line = selection_end.line;
2638 selection_anchor.tag = selection_end.tag;
2639 selection_anchor.pos = selection_end.pos;
2640 selection_anchor.height = start_pos;
2642 selection_prev.line = caret.line;
2643 selection_prev.tag = caret.tag;
2644 selection_prev.pos = caret.pos;
2646 this.selection_end_anchor = true;
2653 SetSelectionVisible (!(selection_start == selection_end));
2656 internal void SetSelectionToCaret(bool start) {
2658 // Invalidate old selection; selection is being reset to empty
2659 this.Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2661 selection_start.line = caret.line;
2662 selection_start.tag = caret.tag;
2663 selection_start.pos = caret.pos;
2665 // start always also selects end
2666 selection_end.line = caret.line;
2667 selection_end.tag = caret.tag;
2668 selection_end.pos = caret.pos;
2670 selection_anchor.line = caret.line;
2671 selection_anchor.tag = caret.tag;
2672 selection_anchor.pos = caret.pos;
2674 // Invalidate from previous end to caret (aka new end)
2675 if (selection_end_anchor) {
2676 if (selection_start != caret) {
2677 this.Invalidate(selection_start.line, selection_start.pos, caret.line, caret.pos);
2680 if (selection_end != caret) {
2681 this.Invalidate(selection_end.line, selection_end.pos, caret.line, caret.pos);
2685 if (caret < selection_anchor) {
2686 selection_start.line = caret.line;
2687 selection_start.tag = caret.tag;
2688 selection_start.pos = caret.pos;
2690 selection_end.line = selection_anchor.line;
2691 selection_end.tag = selection_anchor.tag;
2692 selection_end.pos = selection_anchor.pos;
2694 selection_end_anchor = true;
2696 selection_start.line = selection_anchor.line;
2697 selection_start.tag = selection_anchor.tag;
2698 selection_start.pos = selection_anchor.pos;
2700 selection_end.line = caret.line;
2701 selection_end.tag = caret.tag;
2702 selection_end.pos = caret.pos;
2704 selection_end_anchor = false;
2708 SetSelectionVisible (!(selection_start == selection_end));
2711 internal void SetSelection(Line start, int start_pos, Line end, int end_pos) {
2712 if (selection_visible) {
2713 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2716 if ((end.line_no < start.line_no) || ((end == start) && (end_pos <= start_pos))) {
2717 selection_start.line = end;
2718 selection_start.tag = LineTag.FindTag(end, end_pos);
2719 selection_start.pos = end_pos;
2721 selection_end.line = start;
2722 selection_end.tag = LineTag.FindTag(start, start_pos);
2723 selection_end.pos = start_pos;
2725 selection_end_anchor = true;
2727 selection_start.line = start;
2728 selection_start.tag = LineTag.FindTag(start, start_pos);
2729 selection_start.pos = start_pos;
2731 selection_end.line = end;
2732 selection_end.tag = LineTag.FindTag(end, end_pos);
2733 selection_end.pos = end_pos;
2735 selection_end_anchor = false;
2738 selection_anchor.line = start;
2739 selection_anchor.tag = selection_start.tag;
2740 selection_anchor.pos = start_pos;
2742 if (((start == end) && (start_pos == end_pos)) || start == null || end == null) {
2743 SetSelectionVisible (false);
2745 SetSelectionVisible (true);
2746 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2750 internal void SetSelectionStart(Line start, int start_pos, bool invalidate) {
2751 // Invalidate from the previous to the new start pos
2753 Invalidate(selection_start.line, selection_start.pos, start, start_pos);
2755 selection_start.line = start;
2756 selection_start.pos = start_pos;
2757 selection_start.tag = LineTag.FindTag(start, start_pos);
2759 selection_anchor.line = start;
2760 selection_anchor.pos = start_pos;
2761 selection_anchor.tag = selection_start.tag;
2763 selection_end_anchor = false;
2766 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
2767 SetSelectionVisible (true);
2769 SetSelectionVisible (false);
2773 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2776 internal void SetSelectionStart(int character_index, bool invalidate) {
2781 if (character_index < 0) {
2785 CharIndexToLineTag(character_index, out line, out tag, out pos);
2786 SetSelectionStart(line, pos, invalidate);
2789 internal void SetSelectionEnd(Line end, int end_pos, bool invalidate) {
2791 if (end == selection_end.line && end_pos == selection_start.pos) {
2792 selection_anchor.line = selection_start.line;
2793 selection_anchor.tag = selection_start.tag;
2794 selection_anchor.pos = selection_start.pos;
2796 selection_end.line = selection_start.line;
2797 selection_end.tag = selection_start.tag;
2798 selection_end.pos = selection_start.pos;
2800 selection_end_anchor = false;
2801 } else if ((end.line_no < selection_anchor.line.line_no) || ((end == selection_anchor.line) && (end_pos <= selection_anchor.pos))) {
2802 selection_start.line = end;
2803 selection_start.tag = LineTag.FindTag(end, end_pos);
2804 selection_start.pos = end_pos;
2806 selection_end.line = selection_anchor.line;
2807 selection_end.tag = selection_anchor.tag;
2808 selection_end.pos = selection_anchor.pos;
2810 selection_end_anchor = true;
2812 selection_start.line = selection_anchor.line;
2813 selection_start.tag = selection_anchor.tag;
2814 selection_start.pos = selection_anchor.pos;
2816 selection_end.line = end;
2817 selection_end.tag = LineTag.FindTag(end, end_pos);
2818 selection_end.pos = end_pos;
2820 selection_end_anchor = false;
2823 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
2824 SetSelectionVisible (true);
2826 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2828 SetSelectionVisible (false);
2829 // ?? Do I need to invalidate here, tests seem to work without it, but I don't think they should :-s
2833 internal void SetSelectionEnd(int character_index, bool invalidate) {
2838 if (character_index < 0) {
2842 CharIndexToLineTag(character_index, out line, out tag, out pos);
2843 SetSelectionEnd(line, pos, invalidate);
2846 internal void SetSelection(Line start, int start_pos) {
2847 if (selection_visible) {
2848 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2851 selection_start.line = start;
2852 selection_start.pos = start_pos;
2853 selection_start.tag = LineTag.FindTag(start, start_pos);
2855 selection_end.line = start;
2856 selection_end.tag = selection_start.tag;
2857 selection_end.pos = start_pos;
2859 selection_anchor.line = start;
2860 selection_anchor.tag = selection_start.tag;
2861 selection_anchor.pos = start_pos;
2863 selection_end_anchor = false;
2864 SetSelectionVisible (false);
2867 internal void InvalidateSelectionArea() {
2868 Invalidate (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2871 // Return the current selection, as string
2872 internal string GetSelection() {
2873 // We return String.Empty if there is no selection
2874 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
2875 return string.Empty;
2878 if (selection_start.line == selection_end.line) {
2879 return selection_start.line.text.ToString (selection_start.pos, selection_end.pos - selection_start.pos);
2886 sb = new StringBuilder();
2887 start = selection_start.line.line_no;
2888 end = selection_end.line.line_no;
2890 sb.Append(selection_start.line.text.ToString(selection_start.pos, selection_start.line.text.Length - selection_start.pos) + Environment.NewLine);
2892 if ((start + 1) < end) {
2893 for (i = start + 1; i < end; i++) {
2894 sb.Append(GetLine(i).text.ToString() + Environment.NewLine);
2898 sb.Append(selection_end.line.text.ToString(0, selection_end.pos));
2900 return sb.ToString();
2904 internal void ReplaceSelection(string s, bool select_new) {
2907 int selection_pos_on_line = selection_start.pos;
2908 int selection_start_pos = LineTagToCharIndex (selection_start.line, selection_start.pos);
2911 // First, delete any selected text
2912 if ((selection_start.pos != selection_end.pos) || (selection_start.line != selection_end.line)) {
2913 if (selection_start.line == selection_end.line) {
2914 undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2916 DeleteChars(selection_start.tag, selection_start.pos, selection_end.pos - selection_start.pos);
2918 // The tag might have been removed, we need to recalc it
2919 selection_start.tag = selection_start.line.FindTag(selection_start.pos);
2924 start = selection_start.line.line_no;
2925 end = selection_end.line.line_no;
2927 undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2929 InvalidateSelectionArea ();
2931 // Delete first line
2932 DeleteChars(selection_start.tag, selection_start.pos, selection_start.line.text.Length - selection_start.pos);
2933 selection_start.line.recalc = true;
2936 DeleteChars(selection_end.line.tags, 0, selection_end.pos);
2940 for (i = end - 1; i >= start; i--) {
2945 // BIG FAT WARNING - selection_end.line might be stale due
2946 // to the above Delete() call. DONT USE IT before hitting the end of this method!
2948 // Join start and end
2949 Combine(selection_start.line.line_no, start);
2954 Insert(selection_start.line, selection_start.pos, false, s);
2955 undo.RecordInsertString (selection_start.line, selection_start.pos, s);
2956 ResumeRecalc (false);
2959 CharIndexToLineTag(selection_start_pos + s.Length, out selection_start.line,
2960 out selection_start.tag, out selection_start.pos);
2962 selection_end.line = selection_start.line;
2963 selection_end.pos = selection_start.pos;
2964 selection_end.tag = selection_start.tag;
2965 selection_anchor.line = selection_start.line;
2966 selection_anchor.pos = selection_start.pos;
2967 selection_anchor.tag = selection_start.tag;
2969 SetSelectionVisible (false);
2971 CharIndexToLineTag(selection_start_pos, out selection_start.line,
2972 out selection_start.tag, out selection_start.pos);
2974 CharIndexToLineTag(selection_start_pos + s.Length, out selection_end.line,
2975 out selection_end.tag, out selection_end.pos);
2977 selection_anchor.line = selection_start.line;
2978 selection_anchor.pos = selection_start.pos;
2979 selection_anchor.tag = selection_start.tag;
2981 SetSelectionVisible (true);
2984 PositionCaret (selection_start.line, selection_start.pos);
2985 UpdateView (selection_start.line, selection_pos_on_line);
2988 internal void CharIndexToLineTag(int index, out Line line_out, out LineTag tag_out, out int pos) {
2997 for (i = 1; i <= lines; i++) {
3001 chars += line.text.Length;
3003 if (index <= chars) {
3004 // we found the line
3007 while (tag != null) {
3008 if (index < (start + tag.start + tag.Length - 1)) {
3010 tag_out = LineTag.GetFinalTag (tag);
3011 pos = index - start;
3014 if (tag.next == null) {
3017 next_line = GetLine(line.line_no + 1);
3019 if (next_line != null) {
3020 line_out = next_line;
3021 tag_out = LineTag.GetFinalTag (next_line.tags);
3026 tag_out = LineTag.GetFinalTag (tag);
3027 pos = line_out.text.Length;
3036 line_out = GetLine(lines);
3037 tag = line_out.tags;
3038 while (tag.next != null) {
3042 pos = line_out.text.Length;
3045 internal int LineTagToCharIndex(Line line, int pos) {
3049 // Count first and last line
3052 // Count the lines in the middle
3054 for (i = 1; i < line.line_no; i++) {
3055 length += GetLine(i).text.Length;
3063 internal int SelectionLength() {
3064 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3068 if (selection_start.line == selection_end.line) {
3069 return selection_end.pos - selection_start.pos;
3076 // Count first and last line
3077 length = selection_start.line.text.Length - selection_start.pos + selection_end.pos + crlf_size;
3079 // Count the lines in the middle
3080 start = selection_start.line.line_no + 1;
3081 end = selection_end.line.line_no;
3084 for (i = start; i < end; i++) {
3085 Line line = GetLine (i);
3086 length += line.text.Length + LineEndingLength (line.ending);
3097 /// <summary>Give it a Line number and it returns the Line object at with that line number</summary>
3098 internal Line GetLine(int LineNo) {
3099 Line line = document;
3101 while (line != sentinel) {
3102 if (LineNo == line.line_no) {
3104 } else if (LineNo < line.line_no) {
3114 /// <summary>Retrieve the previous tag; walks line boundaries</summary>
3115 internal LineTag PreviousTag(LineTag tag) {
3118 if (tag.previous != null) {
3119 return tag.previous;
3123 if (tag.line.line_no == 1) {
3127 l = GetLine(tag.line.line_no - 1);
3132 while (t.next != null) {
3141 /// <summary>Retrieve the next tag; walks line boundaries</summary>
3142 internal LineTag NextTag(LineTag tag) {
3145 if (tag.next != null) {
3150 l = GetLine(tag.line.line_no + 1);
3158 internal Line ParagraphStart(Line line) {
3159 while (line.ending == LineEnding.Wrap) {
3160 line = GetLine(line.line_no - 1);
3165 internal Line ParagraphEnd(Line line) {
3168 while (line.ending == LineEnding.Wrap) {
3169 l = GetLine(line.line_no + 1);
3170 if ((l == null) || (l.ending != LineEnding.Wrap)) {
3178 /// <summary>Give it a pixel offset coordinate and it returns the Line covering that are (offset
3179 /// is either X or Y depending on if we are multiline
3181 internal Line GetLineByPixel (int offset, bool exact)
3183 Line line = document;
3187 while (line != sentinel) {
3189 if ((offset >= line.Y) && (offset < (line.Y+line.height))) {
3191 } else if (offset < line.Y) {
3198 while (line != sentinel) {
3200 if ((offset >= line.X) && (offset < (line.X + line.Width)))
3202 else if (offset < line.X)
3215 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3216 internal LineTag FindTag(int x, int y, out int index, bool exact) {
3220 line = GetLineByPixel(y, exact);
3227 // Alignment adjustment
3231 if (x >= tag.X && x < (tag.X+tag.Width)) {
3234 end = tag.start + tag.Length - 1;
3236 for (int pos = tag.start; pos < end; pos++) {
3237 if (x < line.widths[pos]) {
3239 return LineTag.GetFinalTag (tag);
3243 return LineTag.GetFinalTag (tag);
3245 if (tag.next != null) {
3253 index = line.text.Length;
3254 return LineTag.GetFinalTag (tag);
3259 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3260 internal LineTag FindCursor(int x, int y, out int index) {
3264 line = GetLineByPixel(multiline ? y : x, false);
3267 /// Special case going leftwards of the first tag
3270 return LineTag.GetFinalTag (tag);
3274 if (x >= tag.X && x < (tag.X+tag.Width)) {
3279 for (int pos = tag.start - 1; pos < end; pos++) {
3280 // When clicking on a character, we position the cursor to whatever edge
3281 // of the character the click was closer
3282 if (x < (line.X + line.widths[pos] + ((line.widths[pos+1]-line.widths[pos])/2))) {
3284 return LineTag.GetFinalTag (tag);
3288 return LineTag.GetFinalTag (tag);
3290 if (tag.next != null) {
3293 index = line.TextLengthWithoutEnding ();
3294 return LineTag.GetFinalTag (tag);
3299 /// <summary>Format area of document in specified font and color</summary>
3300 /// <param name="start_pos">1-based start position on start_line</param>
3301 /// <param name="end_pos">1-based end position on end_line </param>
3302 internal void FormatText (Line start_line, int start_pos, Line end_line, int end_pos, Font font,
3303 Color color, Color back_color, FormatSpecified specified)
3307 // First, format the first line
3308 if (start_line != end_line) {
3310 LineTag.FormatText(start_line, start_pos, start_line.text.Length - start_pos + 1, font, color, back_color, specified);
3313 LineTag.FormatText(end_line, 1, end_pos, font, color, back_color, specified);
3315 // Now all the lines inbetween
3316 for (int i = start_line.line_no + 1; i < end_line.line_no; i++) {
3318 LineTag.FormatText(l, 1, l.text.Length, font, color, back_color, specified);
3321 // Special case, single line
3322 LineTag.FormatText(start_line, start_pos, end_pos - start_pos, font, color, back_color, specified);
3326 internal void RecalculateAlignments ()
3335 while (line_no <= lines) {
3336 line = GetLine(line_no);
3339 switch (line.alignment) {
3340 case HorizontalAlignment.Left:
3341 line.align_shift = 0;
3343 case HorizontalAlignment.Center:
3344 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3346 case HorizontalAlignment.Right:
3347 line.align_shift = viewport_width - (int)line.widths[line.text.Length] - right_margin;
3357 /// <summary>Calculate formatting for the whole document</summary>
3358 internal bool RecalculateDocument(Graphics g) {
3359 return RecalculateDocument(g, 1, this.lines, false);
3362 /// <summary>Calculate formatting starting at a certain line</summary>
3363 internal bool RecalculateDocument(Graphics g, int start) {
3364 return RecalculateDocument(g, start, this.lines, false);
3367 /// <summary>Calculate formatting within two given line numbers</summary>
3368 internal bool RecalculateDocument(Graphics g, int start, int end) {
3369 return RecalculateDocument(g, start, end, false);
3372 /// <summary>With optimize on, returns true if line heights changed</summary>
3373 internal bool RecalculateDocument(Graphics g, int start, int end, bool optimize) {
3381 if (recalc_suspended > 0) {
3382 recalc_pending = true;
3383 recalc_start = Math.Min (recalc_start, start);
3384 recalc_end = Math.Max (recalc_end, end);
3385 recalc_optimize = optimize;
3389 // Fixup the positions, they can go kinda nuts
3390 start = Math.Max (start, 1);
3391 end = Math.Min (end, lines);
3393 offset = GetLine(start).offset;
3398 changed = true; // We always return true if we run non-optimized
3403 while (line_no <= (end + this.lines - shift)) {
3404 line = GetLine(line_no++);
3405 line.offset = offset;
3409 line.RecalculateLine(g, this);
3411 if (line.recalc && line.RecalculateLine(g, this)) {
3413 // If the height changed, all subsequent lines change
3420 line.RecalculatePasswordLine(g, this);
3422 if (line.recalc && line.RecalculatePasswordLine(g, this)) {
3424 // If the height changed, all subsequent lines change
3431 if (line.widths[line.text.Length] > new_width) {
3432 new_width = (int)line.widths[line.text.Length];
3435 // Calculate alignment
3436 if (line.alignment != HorizontalAlignment.Left) {
3437 if (line.alignment == HorizontalAlignment.Center) {
3438 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3440 line.align_shift = viewport_width - (int)line.widths[line.text.Length] - 1;
3445 offset += line.height;
3447 offset += (int) line.widths [line.text.Length];
3449 if (line_no > lines) {
3454 if (document_x != new_width) {
3455 document_x = new_width;
3456 if (WidthChanged != null) {
3457 WidthChanged(this, null);
3461 RecalculateAlignments();
3463 line = GetLine(lines);
3465 if (document_y != line.Y + line.height) {
3466 document_y = line.Y + line.height;
3467 if (HeightChanged != null) {
3468 HeightChanged(this, null);
3475 internal int Size() {
3479 private void owner_HandleCreated(object sender, EventArgs e) {
3480 RecalculateDocument(owner.CreateGraphicsInternal());
3484 private void owner_VisibleChanged(object sender, EventArgs e) {
3485 if (owner.Visible) {
3486 RecalculateDocument(owner.CreateGraphicsInternal());
3490 internal static bool IsWordSeparator (char ch)
3505 internal int FindWordSeparator(Line line, int pos, bool forward) {
3508 len = line.text.Length;
3511 for (int i = pos + 1; i < len; i++) {
3512 if (IsWordSeparator(line.Text[i])) {
3518 for (int i = pos - 1; i > 0; i--) {
3519 if (IsWordSeparator(line.Text[i - 1])) {
3527 /* Search document for text */
3528 internal bool FindChars(char[] chars, Marker start, Marker end, out Marker result) {
3534 // Search for occurence of any char in the chars array
3535 result = new Marker();
3538 line_no = start.line.line_no;
3540 while (line_no <= end.line.line_no) {
3541 line_len = line.text.Length;
3542 while (pos < line_len) {
3543 for (int i = 0; i < chars.Length; i++) {
3544 if (line.text[pos] == chars[i]) {
3546 if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
3560 line = GetLine(line_no);
3566 // This version does not build one big string for searching, instead it handles
3567 // line-boundaries, which is faster and less memory intensive
3568 // FIXME - Depending on culture stuff we might have to create a big string and use culturespecific
3569 // search stuff and change it to accept and return positions instead of Markers (which would match
3570 // RichTextBox behaviour better but would be inconsistent with the rest of TextControl)
3571 internal bool Find(string search, Marker start, Marker end, out Marker result, RichTextBoxFinds options) {
3573 string search_string;
3585 result = new Marker();
3586 word_option = ((options & RichTextBoxFinds.WholeWord) != 0);
3587 ignore_case = ((options & RichTextBoxFinds.MatchCase) == 0);
3588 reverse = ((options & RichTextBoxFinds.Reverse) != 0);
3591 line_no = start.line.line_no;
3595 // Prep our search string, lowercasing it if we do case-independent matching
3598 sb = new StringBuilder(search);
3599 for (int i = 0; i < sb.Length; i++) {
3600 sb[i] = Char.ToLower(sb[i]);
3602 search_string = sb.ToString();
3604 search_string = search;
3607 // We need to check if the character before our start position is a wordbreak
3610 if ((pos == 0) || (IsWordSeparator(line.text[pos - 1]))) {
3617 if (IsWordSeparator(line.text[pos - 1])) {
3623 // Need to check the end of the previous line
3626 prev_line = GetLine(line_no - 1);
3627 if (prev_line.ending == LineEnding.Wrap) {
3628 if (IsWordSeparator(prev_line.text[prev_line.text.Length - 1])) {
3642 // To avoid duplication of this loop with reverse logic, we search
3643 // through the document, remembering the last match and when returning
3644 // report that last remembered match
3646 last = new Marker();
3647 last.height = -1; // Abused - we use it to track change
3649 while (line_no <= end.line.line_no) {
3650 if (line_no != end.line.line_no) {
3651 line_len = line.text.Length;
3656 while (pos < line_len) {
3658 if (word_option && (current == search_string.Length)) {
3659 if (IsWordSeparator(line.text[pos])) {
3672 c = Char.ToLower(line.text[pos]);
3677 if (c == search_string[current]) {
3683 if (!word_option || (word_option && (word || (current > 0)))) {
3687 if (!word_option && (current == search_string.Length)) {
3704 if (IsWordSeparator(c)) {
3712 // Mark that we just saw a word boundary
3713 if (line.ending != LineEnding.Wrap || line.line_no == lines - 1) {
3717 if (current == search_string.Length) {
3733 line = GetLine(line_no);
3737 if (last.height != -1) {
3747 // if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
3759 internal void GetMarker(out Marker mark, bool start) {
3760 mark = new Marker();
3763 mark.line = GetLine(1);
3764 mark.tag = mark.line.tags;
3767 mark.line = GetLine(lines);
3768 mark.tag = mark.line.tags;
3769 while (mark.tag.next != null) {
3770 mark.tag = mark.tag.next;
3772 mark.pos = mark.line.text.Length;
3775 #endregion // Internal Methods
3778 internal event EventHandler CaretMoved;
3779 internal event EventHandler WidthChanged;
3780 internal event EventHandler HeightChanged;
3781 internal event EventHandler LengthChanged;
3782 #endregion // Events
3784 #region Administrative
3785 public IEnumerator GetEnumerator() {
3790 public override bool Equals(object obj) {
3795 if (!(obj is Document)) {
3803 if (ToString().Equals(((Document)obj).ToString())) {
3810 public override int GetHashCode() {
3814 public override string ToString() {
3815 return "document " + this.document_id;
3817 #endregion // Administrative
3820 internal class PictureTag : LineTag {
3822 internal RTF.Picture picture;
3824 internal PictureTag (Line line, int start, RTF.Picture picture) : base (line, start)
3826 this.picture = picture;
3829 public override bool IsTextTag {
3830 get { return false; }
3833 internal override SizeF SizeOfPosition (Graphics dc, int pos)
3835 return picture.Size;
3838 internal override int MaxHeight ()
3840 return (int) (picture.Height + 0.5F);
3843 internal override void Draw (Graphics dc, Color color, float xoff, float y, int start, int end)
3845 picture.DrawImage (dc, xoff + line.widths [start], y, false);
3848 internal override void Draw (Graphics dc, Color color, float xoff, float y, int start, int end, string text)
3850 picture.DrawImage (dc, xoff + + line.widths [start], y, false);
3853 public override string Text ()
3859 internal class UndoManager {
3861 internal enum ActionType {
3865 // This is basically just cut & paste
3873 internal class Action {
3874 internal ActionType type;
3875 internal int line_no;
3877 internal object data;
3880 #region Local Variables
3881 private Document document;
3882 private Stack undo_actions;
3883 private Stack redo_actions;
3885 //private int caret_line;
3886 //private int caret_pos;
3888 // When performing an action, we lock the queue, so that the action can't be undone
3889 private bool locked;
3890 #endregion // Local Variables
3892 #region Constructors
3893 internal UndoManager (Document document)
3895 this.document = document;
3896 undo_actions = new Stack (50);
3897 redo_actions = new Stack (50);
3899 #endregion // Constructors
3902 internal bool CanUndo {
3903 get { return undo_actions.Count > 0; }
3906 internal bool CanRedo {
3907 get { return redo_actions.Count > 0; }
3910 internal string UndoActionName {
3912 foreach (Action action in undo_actions) {
3913 if (action.type == ActionType.UserActionBegin)
3914 return (string) action.data;
3915 if (action.type == ActionType.Typing)
3916 return Locale.GetText ("Typing");
3918 return String.Empty;
3922 internal string RedoActionName {
3924 foreach (Action action in redo_actions) {
3925 if (action.type == ActionType.UserActionBegin)
3926 return (string) action.data;
3927 if (action.type == ActionType.Typing)
3928 return Locale.GetText ("Typing");
3930 return String.Empty;
3933 #endregion // Properties
3935 #region Internal Methods
3936 internal void Clear ()
3938 undo_actions.Clear();
3939 redo_actions.Clear();
3942 internal void Undo ()
3945 bool user_action_finished = false;
3947 if (undo_actions.Count == 0)
3950 // Nuke the redo queue
3951 redo_actions.Clear ();
3956 action = (Action) undo_actions.Pop ();
3958 // Put onto redo stack
3959 redo_actions.Push(action);
3962 switch(action.type) {
3964 case ActionType.UserActionBegin:
3965 user_action_finished = true;
3968 case ActionType.UserActionEnd:
3972 case ActionType.InsertString:
3973 start = document.GetLine (action.line_no);
3974 document.SuspendUpdate ();
3975 document.DeleteMultiline (start, action.pos, ((string) action.data).Length + 1);
3976 document.PositionCaret (start, action.pos);
3977 document.SetSelectionToCaret (true);
3978 document.ResumeUpdate (true);
3981 case ActionType.Typing:
3982 start = document.GetLine (action.line_no);
3983 document.SuspendUpdate ();
3984 document.DeleteMultiline (start, action.pos, ((StringBuilder) action.data).Length);
3985 document.PositionCaret (start, action.pos);
3986 document.SetSelectionToCaret (true);
3987 document.ResumeUpdate (true);
3989 // This is an open ended operation, so only a single typing operation can be undone at once
3990 user_action_finished = true;
3993 case ActionType.DeleteString:
3994 start = document.GetLine (action.line_no);
3995 document.SuspendUpdate ();
3996 Insert (start, action.pos, (Line) action.data, true);
3997 document.ResumeUpdate (true);
4000 } while (!user_action_finished && undo_actions.Count > 0);
4005 internal void Redo ()
4008 bool user_action_finished = false;
4010 if (redo_actions.Count == 0)
4013 // You can't undo anything after redoing
4014 undo_actions.Clear ();
4021 action = (Action) redo_actions.Pop ();
4023 switch (action.type) {
4025 case ActionType.UserActionBegin:
4029 case ActionType.UserActionEnd:
4030 user_action_finished = true;
4033 case ActionType.InsertString:
4034 start = document.GetLine (action.line_no);
4035 document.SuspendUpdate ();
4036 start_index = document.LineTagToCharIndex (start, action.pos);
4037 document.InsertString (start, action.pos, (string) action.data);
4038 document.CharIndexToLineTag (start_index + ((string) action.data).Length,
4039 out document.caret.line, out document.caret.tag,
4040 out document.caret.pos);
4041 document.UpdateCaret ();
4042 document.SetSelectionToCaret (true);
4043 document.ResumeUpdate (true);
4046 case ActionType.Typing:
4047 start = document.GetLine (action.line_no);
4048 document.SuspendUpdate ();
4049 start_index = document.LineTagToCharIndex (start, action.pos);
4050 document.InsertString (start, action.pos, ((StringBuilder) action.data).ToString ());
4051 document.CharIndexToLineTag (start_index + ((StringBuilder) action.data).Length,
4052 out document.caret.line, out document.caret.tag,
4053 out document.caret.pos);
4054 document.UpdateCaret ();
4055 document.SetSelectionToCaret (true);
4056 document.ResumeUpdate (true);
4058 // This is an open ended operation, so only a single typing operation can be undone at once
4059 user_action_finished = true;
4062 case ActionType.DeleteString:
4063 start = document.GetLine (action.line_no);
4064 document.SuspendUpdate ();
4065 document.DeleteMultiline (start, action.pos, ((Line) action.data).text.Length);
4066 document.PositionCaret (start, action.pos);
4067 document.SetSelectionToCaret (true);
4068 document.ResumeUpdate (true);
4072 } while (!user_action_finished && redo_actions.Count > 0);
4076 #endregion // Internal Methods
4078 #region Private Methods
4080 public void BeginUserAction (string name)
4085 Action ua = new Action ();
4086 ua.type = ActionType.UserActionBegin;
4089 undo_actions.Push (ua);
4092 public void EndUserAction ()
4097 Action ua = new Action ();
4098 ua.type = ActionType.UserActionEnd;
4100 undo_actions.Push (ua);
4103 // start_pos, end_pos = 1 based
4104 public void RecordDeleteString (Line start_line, int start_pos, Line end_line, int end_pos)
4109 Action a = new Action ();
4111 // We cant simply store the string, because then formatting would be lost
4112 a.type = ActionType.DeleteString;
4113 a.line_no = start_line.line_no;
4115 a.data = Duplicate (start_line, start_pos, end_line, end_pos);
4117 undo_actions.Push(a);
4120 public void RecordInsertString (Line line, int pos, string str)
4122 if (locked || str.Length == 0)
4125 Action a = new Action ();
4127 a.type = ActionType.InsertString;
4129 a.line_no = line.line_no;
4132 undo_actions.Push (a);
4135 public void RecordTyping (Line line, int pos, char ch)
4142 if (undo_actions.Count > 0)
4143 a = (Action) undo_actions.Peek ();
4145 if (a == null || a.type != ActionType.Typing) {
4147 a.type = ActionType.Typing;
4148 a.data = new StringBuilder ();
4149 a.line_no = line.line_no;
4152 undo_actions.Push (a);
4155 StringBuilder data = (StringBuilder) a.data;
4159 // start_pos = 1-based
4160 // end_pos = 1-based
4161 public Line Duplicate(Line start_line, int start_pos, Line end_line, int end_pos)
4167 LineTag current_tag;
4172 line = new Line (start_line.document, start_line.ending);
4175 for (int i = start_line.line_no; i <= end_line.line_no; i++) {
4176 current = document.GetLine(i);
4178 if (start_line.line_no == i) {
4184 if (end_line.line_no == i) {
4187 end = current.text.Length;
4194 line.text = new StringBuilder (current.text.ToString (start, end - start));
4196 // Copy tags from start to start+length onto new line
4197 current_tag = current.FindTag (start);
4198 while ((current_tag != null) && (current_tag.start <= end)) {
4199 if ((current_tag.start <= start) && (start < (current_tag.start + current_tag.Length))) {
4200 // start tag is within this tag
4203 tag_start = current_tag.start;
4206 tag = new LineTag(line, tag_start - start + 1);
4207 tag.CopyFormattingFrom (current_tag);
4209 current_tag = current_tag.next;
4211 // Add the new tag to the line
4212 if (line.tags == null) {
4218 while (tail.next != null) {
4222 tag.previous = tail;
4226 if ((i + 1) <= end_line.line_no) {
4227 line.ending = current.ending;
4229 // Chain them (we use right/left as next/previous)
4230 line.right = new Line (start_line.document, start_line.ending);
4231 line.right.left = line;
4239 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
4240 internal void Insert(Line line, int pos, Line insert, bool select)
4248 // Handle special case first
4249 if (insert.right == null) {
4251 // Single line insert
4252 document.Split(line, pos);
4254 if (insert.tags == null) {
4255 return; // Blank line
4258 //Insert our tags at the end
4261 while (tag.next != null) {
4265 offset = tag.start + tag.Length - 1;
4267 tag.next = insert.tags;
4268 line.text.Insert(offset, insert.text.ToString());
4270 // Adjust start locations
4272 while (tag != null) {
4273 tag.start += offset;
4277 // Put it back together
4278 document.Combine(line.line_no, line.line_no + 1);
4281 document.SetSelectionStart (line, pos, false);
4282 document.SetSelectionEnd (line, pos + insert.text.Length, false);
4285 document.UpdateView(line, pos);
4293 while (current != null) {
4295 if (current == insert) {
4296 // Inserting the first line we split the line (and make space)
4297 document.Split(line.line_no, pos);
4298 //Insert our tags at the end of the line
4302 if (tag != null && tag.Length != 0) {
4303 while (tag.next != null) {
4306 offset = tag.start + tag.Length - 1;
4307 tag.next = current.tags;
4308 tag.next.previous = tag;
4314 line.tags = current.tags;
4315 line.tags.previous = null;
4319 line.ending = current.ending;
4321 document.Split(line.line_no, 0);
4323 line.tags = current.tags;
4324 line.tags.previous = null;
4325 line.ending = current.ending;
4329 // Adjust start locations and line pointers
4330 while (tag != null) {
4331 tag.start += offset - 1;
4336 line.text.Insert(offset, current.text.ToString());
4337 line.Grow(line.text.Length);
4340 line = document.GetLine(line.line_no + 1);
4342 // FIXME? Test undo of line-boundaries
4343 if ((current.right == null) && (current.tags.Length != 0)) {
4344 document.Combine(line.line_no - 1, line.line_no);
4346 current = current.right;
4351 // Recalculate our document
4352 document.UpdateView(first, lines, pos);
4355 #endregion // Private Methods