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, ThemeEngine.Current.ResPool.GetSolidBrush (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, ThemeEngine.Current.ResPool.GetSolidBrush (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.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 Brush current_brush;
1358 Brush disabled_brush;
1359 Brush readonly_brush;
1363 // First, figure out from what line to what line we need to draw
1366 start = GetLineByPixel(clip.Top + viewport_y, false).line_no;
1367 end = GetLineByPixel(clip.Bottom + viewport_y, false).line_no;
1369 start = GetLineByPixel(clip.Left + viewport_x, false).line_no;
1370 end = GetLineByPixel(clip.Right + viewport_x, false).line_no;
1374 /// We draw the single border ourself
1376 if (owner.actual_border_style == BorderStyle.FixedSingle) {
1377 ControlPaint.DrawBorder (g, owner.ClientRectangle, Color.Black, ButtonBorderStyle.Solid);
1380 /// Make sure that we aren't drawing one more line then we need to
1381 line = GetLine (end - 1);
1382 if (line != null && clip.Bottom == line.Y + line.height + viewport_y)
1388 DateTime n = DateTime.Now;
1389 Console.WriteLine ("Started drawing: {0}s {1}ms", n.Second, n.Millisecond);
1390 Console.WriteLine ("CLIP: {0}", clip);
1391 Console.WriteLine ("S: {0}", GetLine (start).text);
1392 Console.WriteLine ("E: {0}", GetLine (end).text);
1395 disabled_brush = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorGrayText);
1396 readonly_brush = ThemeEngine.Current.ResPool.GetSolidBrush (ThemeEngine.Current.ColorControlText);
1397 hilight = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorHighlight);
1398 hilight_text = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorHighlightText);
1400 // Non multiline selection can be handled outside of the loop
1401 if (!multiline && selection_visible && owner.ShowSelection) {
1402 g.FillRectangle (hilight,
1403 selection_start.line.widths [selection_start.pos] +
1404 selection_start.line.X - viewport_x,
1405 selection_start.line.Y,
1406 (selection_end.line.X + selection_end.line.widths [selection_end.pos]) -
1407 (selection_start.line.X + selection_start.line.widths [selection_start.pos]),
1408 selection_start.line.height);
1411 while (line_no <= end) {
1412 line = GetLine (line_no);
1413 float line_y = line.Y - viewport_y;
1419 if (PasswordCache.Length < line.text.Length)
1420 PasswordCache.Append(Char.Parse(password_char), line.text.Length - PasswordCache.Length);
1421 else if (PasswordCache.Length > line.text.Length)
1422 PasswordCache.Remove(line.text.Length, PasswordCache.Length - line.text.Length);
1423 text = PasswordCache;
1426 int line_selection_start = text.Length + 1;
1427 int line_selection_end = text.Length + 1;
1428 if (selection_visible && owner.ShowSelection &&
1429 (line_no >= selection_start.line.line_no) &&
1430 (line_no <= selection_end.line.line_no)) {
1432 if (line_no == selection_start.line.line_no)
1433 line_selection_start = selection_start.pos + 1;
1435 line_selection_start = 1;
1437 if (line_no == selection_end.line.line_no)
1438 line_selection_end = selection_end.pos + 1;
1440 line_selection_end = text.Length + 1;
1442 if (line_selection_end == line_selection_start) {
1443 // There isn't really selection
1444 line_selection_start = text.Length + 1;
1445 line_selection_end = line_selection_start;
1446 } else if (multiline) {
1447 // lets draw some selection baby!! (non multiline selection is drawn outside the loop)
1448 g.FillRectangle (hilight,
1449 line.widths [line_selection_start - 1] + line.X - viewport_x,
1450 line_y, line.widths [line_selection_end - 1] - line.widths [line_selection_start - 1],
1455 current_brush = line.tags.color;
1456 while (tag != null) {
1459 if (tag.Length == 0) {
1464 if (((tag.X + tag.Width) < (clip.Left - viewport_x)) && (tag.X > (clip.Right - viewport_x))) {
1469 if (tag.back_color != Color.Empty) {
1470 g.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (tag.back_color), tag.X + line.X - viewport_x,
1471 line_y + tag.shift, tag.Width, line.height);
1474 tag_brush = tag.color;
1475 current_brush = tag_brush;
1477 if (!owner.Enabled) {
1478 Color a = ((SolidBrush) tag.color).Color;
1479 Color b = ThemeEngine.Current.ColorWindowText;
1481 if ((a.R == b.R) && (a.G == b.G) && (a.B == b.B)) {
1482 tag_brush = disabled_brush;
1484 } else if (owner.read_only && !owner.backcolor_set) {
1485 tag_brush = readonly_brush;
1488 int tag_pos = tag.start;
1489 current_brush = tag_brush;
1490 while (tag_pos < tag.start + tag.Length) {
1491 int old_tag_pos = tag_pos;
1493 if (tag_pos >= line_selection_start && tag_pos < line_selection_end) {
1494 current_brush = hilight_text;
1495 tag_pos = Math.Min (tag.End, line_selection_end);
1496 } else if (tag_pos < line_selection_start) {
1497 current_brush = tag_brush;
1498 tag_pos = Math.Min (tag.End, line_selection_start);
1500 current_brush = tag_brush;
1504 tag.Draw (g, current_brush,
1505 line.X - viewport_x,
1507 old_tag_pos - 1, Math.Min (tag.start + tag.Length, tag_pos) - 1,
1513 line.DrawEnding (g, line_y);
1518 internal int GetLineEnding (string line, int start, out LineEnding ending)
1522 res = line.IndexOf ('\r', start);
1524 if (res + 2 < line.Length && line [res + 1] == '\r' && line [res + 2] == '\n') {
1525 ending = LineEnding.Soft;
1528 if (res + 1 < line.Length && line [res + 1] == '\n') {
1529 ending = LineEnding.Hard;
1532 ending = LineEnding.Limp;
1536 res = line.IndexOf ('\n', start);
1538 ending = LineEnding.Rich;
1542 ending = LineEnding.Wrap;
1546 internal int LineEndingLength (LineEnding ending)
1551 case LineEnding.Limp:
1552 case LineEnding.Rich:
1555 case LineEnding.Hard:
1558 case LineEnding.Soft:
1566 internal string LineEndingToString (LineEnding ending)
1568 string res = String.Empty;
1570 case LineEnding.Limp:
1573 case LineEnding.Hard:
1576 case LineEnding.Soft:
1579 case LineEnding.Rich:
1587 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
1588 internal void Insert(Line line, int pos, bool update_caret, string s) {
1594 LineTag tag = LineTag.FindTag (line, pos);
1598 base_line = line.line_no;
1599 old_line_count = lines;
1601 break_index = GetLineEnding (s, 0, out ending);
1603 // Bump the text at insertion point a line down if we're inserting more than one line
1604 if (break_index != s.Length) {
1606 line.ending = ending;
1607 // Remainder of start line is now in base_line + 1
1610 InsertString (line, pos, s.Substring (0, break_index + LineEndingLength (ending)));
1612 break_index += LineEndingLength (ending);
1613 while (break_index < s.Length) {
1614 int next_break = GetLineEnding (s, break_index, out ending);
1615 string line_text = s.Substring (break_index, next_break - break_index +
1616 LineEndingLength (ending));
1618 Add (base_line + count, line_text, line.alignment, tag.font, tag.color, ending);
1620 Line last = GetLine (base_line + count);
1621 last.ending = ending;
1624 break_index = next_break + LineEndingLength (ending);
1627 ResumeRecalc (true);
1629 UpdateView(line, lines - old_line_count + 1, pos);
1632 // Move caret to the end of the inserted text
1633 Line l = GetLine (line.line_no + lines - old_line_count);
1634 PositionCaret(l, l.text.Length);
1639 // Inserts a character at the given position
1640 internal void InsertString(Line line, int pos, string s) {
1641 InsertString(line.FindTag(pos), pos, s);
1644 // Inserts a string at the given position
1645 internal void InsertString(LineTag tag, int pos, string s) {
1654 line.text.Insert(pos, s);
1657 while (tag != null) {
1664 UpdateView(line, pos);
1667 // Inserts a string at the caret position
1668 internal void InsertStringAtCaret(string s, bool move_caret) {
1670 InsertString (caret.tag, caret.pos, s);
1672 UpdateView(caret.line, caret.pos);
1674 caret.pos += s.Length;
1681 // Inserts a character at the given position
1682 internal void InsertChar(Line line, int pos, char ch) {
1683 InsertChar(line.FindTag(pos), pos, ch);
1686 // Inserts a character at the given position
1687 internal void InsertChar(LineTag tag, int pos, char ch) {
1693 line.text.Insert(pos, ch);
1696 while (tag != null) {
1703 undo.RecordTyping (line, pos, ch);
1704 UpdateView(line, pos);
1707 // Inserts a character at the current caret position
1708 internal void InsertCharAtCaret(char ch, bool move_caret) {
1714 caret.line.text.Insert(caret.pos, ch);
1717 if (caret.tag.next != null) {
1718 tag = caret.tag.next;
1719 while (tag != null) {
1725 caret.line.recalc = true;
1727 InsertChar (caret.tag, caret.pos, ch);
1729 UpdateView(caret.line, caret.pos);
1733 SetSelectionToCaret(true);
1738 internal void InsertPicture (Line line, int pos, RTF.Picture picture)
1746 // Just a place holder basically
1747 line.text.Insert (pos, "I");
1749 PictureTag picture_tag = new PictureTag (line, pos + 1, picture);
1751 tag = LineTag.FindTag (line, pos);
1752 picture_tag.CopyFormattingFrom (tag);
1753 /*next_tag = */tag.Break (pos + 1);
1754 picture_tag.previous = tag;
1755 picture_tag.next = tag.next;
1756 tag.next = picture_tag;
1759 // Picture tags need to be surrounded by text tags
1761 if (picture_tag.next == null) {
1762 picture_tag.next = new LineTag (line, pos + 1);
1763 picture_tag.next.CopyFormattingFrom (tag);
1764 picture_tag.next.previous = picture_tag;
1767 tag = picture_tag.next;
1768 while (tag != null) {
1776 UpdateView (line, pos);
1779 internal void DeleteMultiline (Line start_line, int pos, int length)
1781 Marker start = new Marker ();
1782 Marker end = new Marker ();
1783 int start_index = LineTagToCharIndex (start_line, pos);
1785 start.line = start_line;
1787 start.tag = LineTag.FindTag (start_line, pos);
1789 CharIndexToLineTag (start_index + length, out end.line,
1790 out end.tag, out end.pos);
1794 if (start.line == end.line) {
1795 DeleteChars (start.tag, pos, end.pos - pos);
1798 // Delete first and last lines
1799 DeleteChars (start.tag, start.pos, start.line.text.Length - start.pos);
1800 DeleteChars (end.line.tags, 0, end.pos);
1802 int current = start.line.line_no + 1;
1803 if (current < end.line.line_no) {
1804 for (int i = end.line.line_no - 1; i >= current; i--) {
1809 // BIG FAT WARNING - selection_end.line might be stale due
1810 // to the above Delete() call. DONT USE IT before hitting the end of this method!
1812 // Join start and end
1813 Combine (start.line.line_no, current);
1816 ResumeUpdate (true);
1820 // Deletes n characters at the given position; it will not delete past line limits
1822 internal void DeleteChars(LineTag tag, int pos, int count) {
1831 if (pos == line.text.Length) {
1835 line.text.Remove(pos, count);
1837 // Make sure the tag points to the right spot
1838 while ((tag != null) && (tag.End) < pos) {
1846 // Check if we're crossing tag boundaries
1847 if ((pos + count) > (tag.start + tag.Length - 1)) {
1850 // We have to delete cross tag boundaries
1854 left -= tag.start + tag.Length - pos - 1;
1857 while ((tag != null) && (left > 0)) {
1858 tag.start -= count - left;
1860 if (tag.Length > left) {
1869 // We got off easy, same tag
1871 if (tag.Length == 0) {
1876 // Delete empty orphaned tags at the end
1878 while (walk != null && walk.next != null && walk.next.Length == 0) {
1880 walk.next = walk.next.next;
1881 if (walk.next != null)
1882 walk.next.previous = t;
1886 // Adjust the start point of any tags following
1889 while (tag != null) {
1897 line.Streamline(lines);
1901 if (pos >= line.TextLengthWithoutEnding ()) {
1902 LineEnding ending = line.ending;
1903 GetLineEnding (line.text.ToString (), 0, out ending);
1904 if (ending != line.ending) {
1905 line.ending = ending;
1908 UpdateView (line, lines, pos);
1909 owner.Invalidate ();
1915 UpdateView (line, lines, pos);
1916 owner.Invalidate ();
1918 UpdateView(line, pos);
1921 // Deletes a character at or after the given position (depending on forward); it will not delete past line limits
1922 internal void DeleteChar(LineTag tag, int pos, bool forward) {
1931 if ((pos == 0 && forward == false) || (pos == line.text.Length && forward == true)) {
1937 line.text.Remove(pos, 1);
1939 while ((tag != null) && (tag.start + tag.Length - 1) <= pos) {
1949 if (tag.Length == 0) {
1954 line.text.Remove(pos, 1);
1955 if (pos >= (tag.start - 1)) {
1957 if (tag.Length == 0) {
1960 } else if (tag.previous != null) {
1961 // tag.previous.length--;
1962 if (tag.previous.Length == 0) {
1968 // Delete empty orphaned tags at the end
1970 while (walk != null && walk.next != null && walk.next.Length == 0) {
1972 walk.next = walk.next.next;
1973 if (walk.next != null)
1974 walk.next.previous = t;
1979 while (tag != null) {
1985 line.Streamline(lines);
1989 if (pos >= line.TextLengthWithoutEnding ()) {
1990 LineEnding ending = line.ending;
1991 GetLineEnding (line.text.ToString (), 0, out ending);
1992 if (ending != line.ending) {
1993 line.ending = ending;
1996 UpdateView (line, lines, pos);
1997 owner.Invalidate ();
2003 UpdateView (line, lines, pos);
2004 owner.Invalidate ();
2006 UpdateView(line, pos);
2009 // Combine two lines
2010 internal void Combine(int FirstLine, int SecondLine) {
2011 Combine(GetLine(FirstLine), GetLine(SecondLine));
2014 internal void Combine(Line first, Line second) {
2018 // strip the ending off of the first lines text
2019 first.text.Length = first.text.Length - LineEndingLength (first.ending);
2021 // Combine the two tag chains into one
2024 // Maintain the line ending style
2025 first.ending = second.ending;
2027 while (last.next != null) {
2031 // need to get the shift before setting the next tag since that effects length
2032 shift = last.start + last.Length - 1;
2033 last.next = second.tags;
2034 last.next.previous = last;
2036 // Fix up references within the chain
2038 while (last != null) {
2040 last.start += shift;
2044 // Combine both lines' strings
2045 first.text.Insert(first.text.Length, second.text.ToString());
2046 first.Grow(first.text.Length);
2048 // Remove the reference to our (now combined) tags from the doomed line
2052 DecrementLines(first.line_no + 2); // first.line_no + 1 will be deleted, so we need to start renumbering one later
2055 first.recalc = true;
2056 first.height = 0; // This forces RecalcDocument/UpdateView to redraw from this line on
2057 first.Streamline(lines);
2059 // Update Caret, Selection, etc
2060 if (caret.line == second) {
2061 caret.Combine(first, shift);
2063 if (selection_anchor.line == second) {
2064 selection_anchor.Combine(first, shift);
2066 if (selection_start.line == second) {
2067 selection_start.Combine(first, shift);
2069 if (selection_end.line == second) {
2070 selection_end.Combine(first, shift);
2077 check_first = GetLine(first.line_no);
2078 check_second = GetLine(check_first.line_no + 1);
2080 Console.WriteLine("Pre-delete: Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2083 this.Delete(second);
2086 check_first = GetLine(first.line_no);
2087 check_second = GetLine(check_first.line_no + 1);
2089 Console.WriteLine("Post-delete Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2093 // Split the line at the position into two
2094 internal void Split(int LineNo, int pos) {
2098 line = GetLine(LineNo);
2099 tag = LineTag.FindTag(line, pos);
2100 Split(line, tag, pos);
2103 internal void Split(Line line, int pos) {
2106 tag = LineTag.FindTag(line, pos);
2107 Split(line, tag, pos);
2110 ///<summary>Split line at given tag and position into two lines</summary>
2111 ///if more space becomes available on previous line</param>
2112 internal void Split(Line line, LineTag tag, int pos) {
2116 bool move_sel_start;
2120 move_sel_start = false;
2121 move_sel_end = false;
2123 // Adjust selection and cursors
2124 if (caret.line == line && caret.pos >= pos) {
2127 if (selection_start.line == line && selection_start.pos > pos) {
2128 move_sel_start = true;
2131 if (selection_end.line == line && selection_end.pos > pos) {
2132 move_sel_end = true;
2135 // cover the easy case first
2136 if (pos == line.text.Length) {
2137 Add (line.line_no + 1, String.Empty, line.alignment, tag.font, tag.color, line.ending);
2139 new_line = GetLine (line.line_no + 1);
2142 caret.line = new_line;
2143 caret.tag = new_line.tags;
2147 if (move_sel_start) {
2148 selection_start.line = new_line;
2149 selection_start.pos = 0;
2150 selection_start.tag = new_line.tags;
2154 selection_end.line = new_line;
2155 selection_end.pos = 0;
2156 selection_end.tag = new_line.tags;
2161 // We need to move the rest of the text into the new line
2162 Add (line.line_no + 1, line.text.ToString (pos, line.text.Length - pos), line.alignment, tag.font, tag.color, line.ending);
2164 // Now transfer our tags from this line to the next
2165 new_line = GetLine(line.line_no + 1);
2168 new_line.recalc = true;
2170 if ((tag.start - 1) == pos) {
2173 // We can simply break the chain and move the tag into the next line
2174 if (tag == line.tags) {
2175 new_tag = new LineTag(line, 1);
2176 new_tag.CopyFormattingFrom (tag);
2177 line.tags = new_tag;
2180 if (tag.previous != null) {
2181 tag.previous.next = null;
2183 new_line.tags = tag;
2184 tag.previous = null;
2185 tag.line = new_line;
2187 // Walk the list and correct the start location of the tags we just bumped into the next line
2188 shift = tag.start - 1;
2191 while (new_tag != null) {
2192 new_tag.start -= shift;
2193 new_tag.line = new_line;
2194 new_tag = new_tag.next;
2199 new_tag = new LineTag (new_line, 1);
2200 new_tag.next = tag.next;
2201 new_tag.CopyFormattingFrom (tag);
2202 new_line.tags = new_tag;
2203 if (new_tag.next != null) {
2204 new_tag.next.previous = new_tag;
2209 new_tag = new_tag.next;
2210 while (new_tag != null) {
2211 new_tag.start -= shift;
2212 new_tag.line = new_line;
2213 new_tag = new_tag.next;
2219 caret.line = new_line;
2220 caret.pos = caret.pos - pos;
2221 caret.tag = caret.line.FindTag(caret.pos);
2224 if (move_sel_start) {
2225 selection_start.line = new_line;
2226 selection_start.pos = selection_start.pos - pos;
2227 selection_start.tag = new_line.FindTag(selection_start.pos);
2231 selection_end.line = new_line;
2232 selection_end.pos = selection_end.pos - pos;
2233 selection_end.tag = new_line.FindTag(selection_end.pos);
2236 CharCount -= line.text.Length - pos;
2237 line.text.Remove(pos, line.text.Length - pos);
2240 // Adds a line of text, with given font.
2241 // Bumps any line at that line number that already exists down
2242 internal void Add (int LineNo, string Text, Font font, SolidBrush color, LineEnding ending)
2244 Add (LineNo, Text, alignment, font, color, ending);
2247 internal void Add (int LineNo, string Text, HorizontalAlignment align, Font font, SolidBrush color, LineEnding ending)
2253 CharCount += Text.Length;
2255 if (LineNo<1 || Text == null) {
2257 throw new ArgumentNullException("LineNo", "Line numbers must be positive");
2259 throw new ArgumentNullException("Text", "Cannot insert NULL line");
2263 add = new Line (this, LineNo, Text, align, font, color, ending);
2266 while (line != sentinel) {
2268 line_no = line.line_no;
2270 if (LineNo > line_no) {
2272 } else if (LineNo < line_no) {
2275 // Bump existing line numbers; walk all nodes to the right of this one and increment line_no
2276 IncrementLines(line.line_no);
2281 add.left = sentinel;
2282 add.right = sentinel;
2284 if (add.parent != null) {
2285 if (LineNo > add.parent.line_no) {
2286 add.parent.right = add;
2288 add.parent.left = add;
2295 RebalanceAfterAdd(add);
2300 internal virtual void Clear() {
2303 document = sentinel;
2306 public virtual object Clone() {
2309 clone = new Document(null);
2311 clone.lines = this.lines;
2312 clone.document = (Line)document.Clone();
2317 internal void Delete(int LineNo) {
2324 line = GetLine(LineNo);
2326 CharCount -= line.text.Length;
2328 DecrementLines(LineNo + 1);
2332 internal void Delete(Line line1) {
2333 Line line2;// = new Line();
2336 if ((line1.left == sentinel) || (line1.right == sentinel)) {
2339 line3 = line1.right;
2340 while (line3.left != sentinel) {
2345 if (line3.left != sentinel) {
2348 line2 = line3.right;
2351 line2.parent = line3.parent;
2352 if (line3.parent != null) {
2353 if(line3 == line3.parent.left) {
2354 line3.parent.left = line2;
2356 line3.parent.right = line2;
2362 if (line3 != line1) {
2365 if (selection_start.line == line3) {
2366 selection_start.line = line1;
2369 if (selection_end.line == line3) {
2370 selection_end.line = line1;
2373 if (selection_anchor.line == line3) {
2374 selection_anchor.line = line1;
2377 if (caret.line == line3) {
2382 line1.alignment = line3.alignment;
2383 line1.ascent = line3.ascent;
2384 line1.hanging_indent = line3.hanging_indent;
2385 line1.height = line3.height;
2386 line1.indent = line3.indent;
2387 line1.line_no = line3.line_no;
2388 line1.recalc = line3.recalc;
2389 line1.right_indent = line3.right_indent;
2390 line1.ending = line3.ending;
2391 line1.space = line3.space;
2392 line1.tags = line3.tags;
2393 line1.text = line3.text;
2394 line1.widths = line3.widths;
2395 line1.offset = line3.offset;
2398 while (tag != null) {
2404 if (line3.color == LineColor.Black)
2405 RebalanceAfterDelete(line2);
2410 // Invalidate a section of the document to trigger redraw
2411 internal void Invalidate(Line start, int start_pos, Line end, int end_pos) {
2417 if ((start == end) && (start_pos == end_pos)) {
2421 if (end_pos == -1) {
2422 end_pos = end.text.Length;
2425 // figure out what's before what so the logic below is straightforward
2426 if (start.line_no < end.line_no) {
2432 } else if (start.line_no > end.line_no) {
2439 if (start_pos < end_pos) {
2453 int endpoint = (int) l1.widths [p2];
2454 if (p2 == l1.text.Length + 1) {
2455 endpoint = (int) viewport_width;
2459 Console.WriteLine("Invaliding backwards from {0}:{1} to {2}:{3} {4}",
2460 l1.line_no, p1, l2.line_no, p2,
2462 (int)l1.widths[p1] + l1.X - viewport_x,
2470 owner.Invalidate(new Rectangle (
2471 (int)l1.widths[p1] + l1.X - viewport_x,
2473 endpoint - (int)l1.widths[p1] + 1,
2479 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);
2480 Console.WriteLine ("invalidate start line: {0} position: {1}", l1.text, p1);
2483 // Three invalidates:
2484 // First line from start
2485 owner.Invalidate(new Rectangle((int)l1.widths[p1] + l1.X - viewport_x, l1.Y - viewport_y, viewport_width, l1.height));
2489 if ((l1.line_no + 1) < l2.line_no) {
2492 y = GetLine(l1.line_no + 1).Y;
2493 owner.Invalidate(new Rectangle(0, y - viewport_y, viewport_width, l2.Y - y));
2496 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);
2502 owner.Invalidate(new Rectangle((int)l2.widths[0] + l2.X - viewport_x, l2.Y - viewport_y, (int)l2.widths[p2] + 1, l2.height));
2504 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);
2509 /// <summary>Select text around caret</summary>
2510 internal void ExpandSelection(CaretSelection mode, bool to_caret) {
2512 // We're expanding the selection to the caret position
2514 case CaretSelection.Line: {
2515 // Invalidate the selection delta
2516 if (caret > selection_prev) {
2517 Invalidate(selection_prev.line, 0, caret.line, caret.line.text.Length);
2519 Invalidate(selection_prev.line, selection_prev.line.text.Length, caret.line, 0);
2522 if (caret.line.line_no <= selection_anchor.line.line_no) {
2523 selection_start.line = caret.line;
2524 selection_start.tag = caret.line.tags;
2525 selection_start.pos = 0;
2527 selection_end.line = selection_anchor.line;
2528 selection_end.tag = selection_anchor.tag;
2529 selection_end.pos = selection_anchor.pos;
2531 selection_end_anchor = true;
2533 selection_start.line = selection_anchor.line;
2534 selection_start.pos = selection_anchor.height;
2535 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height);
2537 selection_end.line = caret.line;
2538 selection_end.tag = caret.line.tags;
2539 selection_end.pos = caret.line.text.Length;
2541 selection_end_anchor = false;
2543 selection_prev.line = caret.line;
2544 selection_prev.tag = caret.tag;
2545 selection_prev.pos = caret.pos;
2550 case CaretSelection.Word: {
2554 start_pos = FindWordSeparator(caret.line, caret.pos, false);
2555 end_pos = FindWordSeparator(caret.line, caret.pos, true);
2558 // Invalidate the selection delta
2559 if (caret > selection_prev) {
2560 Invalidate(selection_prev.line, selection_prev.pos, caret.line, end_pos);
2562 Invalidate(selection_prev.line, selection_prev.pos, caret.line, start_pos);
2564 if (caret < selection_anchor) {
2565 selection_start.line = caret.line;
2566 selection_start.tag = caret.line.FindTag(start_pos);
2567 selection_start.pos = start_pos;
2569 selection_end.line = selection_anchor.line;
2570 selection_end.tag = selection_anchor.tag;
2571 selection_end.pos = selection_anchor.pos;
2573 selection_prev.line = caret.line;
2574 selection_prev.tag = caret.tag;
2575 selection_prev.pos = start_pos;
2577 selection_end_anchor = true;
2579 selection_start.line = selection_anchor.line;
2580 selection_start.pos = selection_anchor.height;
2581 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height);
2583 selection_end.line = caret.line;
2584 selection_end.tag = caret.line.FindTag(end_pos);
2585 selection_end.pos = end_pos;
2587 selection_prev.line = caret.line;
2588 selection_prev.tag = caret.tag;
2589 selection_prev.pos = end_pos;
2591 selection_end_anchor = false;
2596 case CaretSelection.Position: {
2597 SetSelectionToCaret(false);
2602 // We're setting the selection 'around' the caret position
2604 case CaretSelection.Line: {
2605 this.Invalidate(caret.line, 0, caret.line, caret.line.text.Length);
2607 selection_start.line = caret.line;
2608 selection_start.tag = caret.line.tags;
2609 selection_start.pos = 0;
2611 selection_end.line = caret.line;
2612 selection_end.pos = caret.line.text.Length;
2613 selection_end.tag = caret.line.FindTag(selection_end.pos);
2615 selection_anchor.line = selection_end.line;
2616 selection_anchor.tag = selection_end.tag;
2617 selection_anchor.pos = selection_end.pos;
2618 selection_anchor.height = 0;
2620 selection_prev.line = caret.line;
2621 selection_prev.tag = caret.tag;
2622 selection_prev.pos = caret.pos;
2624 this.selection_end_anchor = true;
2629 case CaretSelection.Word: {
2633 start_pos = FindWordSeparator(caret.line, caret.pos, false);
2634 end_pos = FindWordSeparator(caret.line, caret.pos, true);
2636 this.Invalidate(selection_start.line, start_pos, caret.line, end_pos);
2638 selection_start.line = caret.line;
2639 selection_start.tag = caret.line.FindTag(start_pos);
2640 selection_start.pos = start_pos;
2642 selection_end.line = caret.line;
2643 selection_end.tag = caret.line.FindTag(end_pos);
2644 selection_end.pos = end_pos;
2646 selection_anchor.line = selection_end.line;
2647 selection_anchor.tag = selection_end.tag;
2648 selection_anchor.pos = selection_end.pos;
2649 selection_anchor.height = start_pos;
2651 selection_prev.line = caret.line;
2652 selection_prev.tag = caret.tag;
2653 selection_prev.pos = caret.pos;
2655 this.selection_end_anchor = true;
2662 SetSelectionVisible (!(selection_start == selection_end));
2665 internal void SetSelectionToCaret(bool start) {
2667 // Invalidate old selection; selection is being reset to empty
2668 this.Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2670 selection_start.line = caret.line;
2671 selection_start.tag = caret.tag;
2672 selection_start.pos = caret.pos;
2674 // start always also selects end
2675 selection_end.line = caret.line;
2676 selection_end.tag = caret.tag;
2677 selection_end.pos = caret.pos;
2679 selection_anchor.line = caret.line;
2680 selection_anchor.tag = caret.tag;
2681 selection_anchor.pos = caret.pos;
2683 // Invalidate from previous end to caret (aka new end)
2684 if (selection_end_anchor) {
2685 if (selection_start != caret) {
2686 this.Invalidate(selection_start.line, selection_start.pos, caret.line, caret.pos);
2689 if (selection_end != caret) {
2690 this.Invalidate(selection_end.line, selection_end.pos, caret.line, caret.pos);
2694 if (caret < selection_anchor) {
2695 selection_start.line = caret.line;
2696 selection_start.tag = caret.tag;
2697 selection_start.pos = caret.pos;
2699 selection_end.line = selection_anchor.line;
2700 selection_end.tag = selection_anchor.tag;
2701 selection_end.pos = selection_anchor.pos;
2703 selection_end_anchor = true;
2705 selection_start.line = selection_anchor.line;
2706 selection_start.tag = selection_anchor.tag;
2707 selection_start.pos = selection_anchor.pos;
2709 selection_end.line = caret.line;
2710 selection_end.tag = caret.tag;
2711 selection_end.pos = caret.pos;
2713 selection_end_anchor = false;
2717 SetSelectionVisible (!(selection_start == selection_end));
2720 internal void SetSelection(Line start, int start_pos, Line end, int end_pos) {
2721 if (selection_visible) {
2722 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2725 if ((end.line_no < start.line_no) || ((end == start) && (end_pos <= start_pos))) {
2726 selection_start.line = end;
2727 selection_start.tag = LineTag.FindTag(end, end_pos);
2728 selection_start.pos = end_pos;
2730 selection_end.line = start;
2731 selection_end.tag = LineTag.FindTag(start, start_pos);
2732 selection_end.pos = start_pos;
2734 selection_end_anchor = true;
2736 selection_start.line = start;
2737 selection_start.tag = LineTag.FindTag(start, start_pos);
2738 selection_start.pos = start_pos;
2740 selection_end.line = end;
2741 selection_end.tag = LineTag.FindTag(end, end_pos);
2742 selection_end.pos = end_pos;
2744 selection_end_anchor = false;
2747 selection_anchor.line = start;
2748 selection_anchor.tag = selection_start.tag;
2749 selection_anchor.pos = start_pos;
2751 if (((start == end) && (start_pos == end_pos)) || start == null || end == null) {
2752 SetSelectionVisible (false);
2754 SetSelectionVisible (true);
2755 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2759 internal void SetSelectionStart(Line start, int start_pos, bool invalidate) {
2760 // Invalidate from the previous to the new start pos
2762 Invalidate(selection_start.line, selection_start.pos, start, start_pos);
2764 selection_start.line = start;
2765 selection_start.pos = start_pos;
2766 selection_start.tag = LineTag.FindTag(start, start_pos);
2768 selection_anchor.line = start;
2769 selection_anchor.pos = start_pos;
2770 selection_anchor.tag = selection_start.tag;
2772 selection_end_anchor = false;
2775 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
2776 SetSelectionVisible (true);
2778 SetSelectionVisible (false);
2782 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2785 internal void SetSelectionStart(int character_index, bool invalidate) {
2790 if (character_index < 0) {
2794 CharIndexToLineTag(character_index, out line, out tag, out pos);
2795 SetSelectionStart(line, pos, invalidate);
2798 internal void SetSelectionEnd(Line end, int end_pos, bool invalidate) {
2800 if (end == selection_end.line && end_pos == selection_start.pos) {
2801 selection_anchor.line = selection_start.line;
2802 selection_anchor.tag = selection_start.tag;
2803 selection_anchor.pos = selection_start.pos;
2805 selection_end.line = selection_start.line;
2806 selection_end.tag = selection_start.tag;
2807 selection_end.pos = selection_start.pos;
2809 selection_end_anchor = false;
2810 } else if ((end.line_no < selection_anchor.line.line_no) || ((end == selection_anchor.line) && (end_pos <= selection_anchor.pos))) {
2811 selection_start.line = end;
2812 selection_start.tag = LineTag.FindTag(end, end_pos);
2813 selection_start.pos = end_pos;
2815 selection_end.line = selection_anchor.line;
2816 selection_end.tag = selection_anchor.tag;
2817 selection_end.pos = selection_anchor.pos;
2819 selection_end_anchor = true;
2821 selection_start.line = selection_anchor.line;
2822 selection_start.tag = selection_anchor.tag;
2823 selection_start.pos = selection_anchor.pos;
2825 selection_end.line = end;
2826 selection_end.tag = LineTag.FindTag(end, end_pos);
2827 selection_end.pos = end_pos;
2829 selection_end_anchor = false;
2832 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
2833 SetSelectionVisible (true);
2835 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2837 SetSelectionVisible (false);
2838 // ?? Do I need to invalidate here, tests seem to work without it, but I don't think they should :-s
2842 internal void SetSelectionEnd(int character_index, bool invalidate) {
2847 if (character_index < 0) {
2851 CharIndexToLineTag(character_index, out line, out tag, out pos);
2852 SetSelectionEnd(line, pos, invalidate);
2855 internal void SetSelection(Line start, int start_pos) {
2856 if (selection_visible) {
2857 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2860 selection_start.line = start;
2861 selection_start.pos = start_pos;
2862 selection_start.tag = LineTag.FindTag(start, start_pos);
2864 selection_end.line = start;
2865 selection_end.tag = selection_start.tag;
2866 selection_end.pos = start_pos;
2868 selection_anchor.line = start;
2869 selection_anchor.tag = selection_start.tag;
2870 selection_anchor.pos = start_pos;
2872 selection_end_anchor = false;
2873 SetSelectionVisible (false);
2876 internal void InvalidateSelectionArea() {
2877 Invalidate (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2880 // Return the current selection, as string
2881 internal string GetSelection() {
2882 // We return String.Empty if there is no selection
2883 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
2884 return string.Empty;
2887 if (selection_start.line == selection_end.line) {
2888 return selection_start.line.text.ToString (selection_start.pos, selection_end.pos - selection_start.pos);
2895 sb = new StringBuilder();
2896 start = selection_start.line.line_no;
2897 end = selection_end.line.line_no;
2899 sb.Append(selection_start.line.text.ToString(selection_start.pos, selection_start.line.text.Length - selection_start.pos) + Environment.NewLine);
2901 if ((start + 1) < end) {
2902 for (i = start + 1; i < end; i++) {
2903 sb.Append(GetLine(i).text.ToString() + Environment.NewLine);
2907 sb.Append(selection_end.line.text.ToString(0, selection_end.pos));
2909 return sb.ToString();
2913 internal void ReplaceSelection(string s, bool select_new) {
2916 int selection_pos_on_line = selection_start.pos;
2917 int selection_start_pos = LineTagToCharIndex (selection_start.line, selection_start.pos);
2920 // First, delete any selected text
2921 if ((selection_start.pos != selection_end.pos) || (selection_start.line != selection_end.line)) {
2922 if (selection_start.line == selection_end.line) {
2923 undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2925 DeleteChars(selection_start.tag, selection_start.pos, selection_end.pos - selection_start.pos);
2927 // The tag might have been removed, we need to recalc it
2928 selection_start.tag = selection_start.line.FindTag(selection_start.pos);
2933 start = selection_start.line.line_no;
2934 end = selection_end.line.line_no;
2936 undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2938 InvalidateSelectionArea ();
2940 // Delete first line
2941 DeleteChars(selection_start.tag, selection_start.pos, selection_start.line.text.Length - selection_start.pos);
2942 selection_start.line.recalc = true;
2945 DeleteChars(selection_end.line.tags, 0, selection_end.pos);
2949 for (i = end - 1; i >= start; i--) {
2954 // BIG FAT WARNING - selection_end.line might be stale due
2955 // to the above Delete() call. DONT USE IT before hitting the end of this method!
2957 // Join start and end
2958 Combine(selection_start.line.line_no, start);
2963 Insert(selection_start.line, selection_start.pos, false, s);
2964 undo.RecordInsertString (selection_start.line, selection_start.pos, s);
2965 ResumeRecalc (false);
2968 CharIndexToLineTag(selection_start_pos + s.Length, out selection_start.line,
2969 out selection_start.tag, out selection_start.pos);
2971 selection_end.line = selection_start.line;
2972 selection_end.pos = selection_start.pos;
2973 selection_end.tag = selection_start.tag;
2974 selection_anchor.line = selection_start.line;
2975 selection_anchor.pos = selection_start.pos;
2976 selection_anchor.tag = selection_start.tag;
2978 SetSelectionVisible (false);
2980 CharIndexToLineTag(selection_start_pos, out selection_start.line,
2981 out selection_start.tag, out selection_start.pos);
2983 CharIndexToLineTag(selection_start_pos + s.Length, out selection_end.line,
2984 out selection_end.tag, out selection_end.pos);
2986 selection_anchor.line = selection_start.line;
2987 selection_anchor.pos = selection_start.pos;
2988 selection_anchor.tag = selection_start.tag;
2990 SetSelectionVisible (true);
2993 PositionCaret (selection_start.line, selection_start.pos);
2994 UpdateView (selection_start.line, selection_pos_on_line);
2997 internal void CharIndexToLineTag(int index, out Line line_out, out LineTag tag_out, out int pos) {
3006 for (i = 1; i <= lines; i++) {
3010 chars += line.text.Length;
3012 if (index <= chars) {
3013 // we found the line
3016 while (tag != null) {
3017 if (index < (start + tag.start + tag.Length - 1)) {
3019 tag_out = LineTag.GetFinalTag (tag);
3020 pos = index - start;
3023 if (tag.next == null) {
3026 next_line = GetLine(line.line_no + 1);
3028 if (next_line != null) {
3029 line_out = next_line;
3030 tag_out = LineTag.GetFinalTag (next_line.tags);
3035 tag_out = LineTag.GetFinalTag (tag);
3036 pos = line_out.text.Length;
3045 line_out = GetLine(lines);
3046 tag = line_out.tags;
3047 while (tag.next != null) {
3051 pos = line_out.text.Length;
3054 internal int LineTagToCharIndex(Line line, int pos) {
3058 // Count first and last line
3061 // Count the lines in the middle
3063 for (i = 1; i < line.line_no; i++) {
3064 length += GetLine(i).text.Length;
3072 internal int SelectionLength() {
3073 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3077 if (selection_start.line == selection_end.line) {
3078 return selection_end.pos - selection_start.pos;
3085 // Count first and last line
3086 length = selection_start.line.text.Length - selection_start.pos + selection_end.pos + crlf_size;
3088 // Count the lines in the middle
3089 start = selection_start.line.line_no + 1;
3090 end = selection_end.line.line_no;
3093 for (i = start; i < end; i++) {
3094 Line line = GetLine (i);
3095 length += line.text.Length + LineEndingLength (line.ending);
3106 /// <summary>Give it a Line number and it returns the Line object at with that line number</summary>
3107 internal Line GetLine(int LineNo) {
3108 Line line = document;
3110 while (line != sentinel) {
3111 if (LineNo == line.line_no) {
3113 } else if (LineNo < line.line_no) {
3123 /// <summary>Retrieve the previous tag; walks line boundaries</summary>
3124 internal LineTag PreviousTag(LineTag tag) {
3127 if (tag.previous != null) {
3128 return tag.previous;
3132 if (tag.line.line_no == 1) {
3136 l = GetLine(tag.line.line_no - 1);
3141 while (t.next != null) {
3150 /// <summary>Retrieve the next tag; walks line boundaries</summary>
3151 internal LineTag NextTag(LineTag tag) {
3154 if (tag.next != null) {
3159 l = GetLine(tag.line.line_no + 1);
3167 internal Line ParagraphStart(Line line) {
3168 while (line.ending == LineEnding.Wrap) {
3169 line = GetLine(line.line_no - 1);
3174 internal Line ParagraphEnd(Line line) {
3177 while (line.ending == LineEnding.Wrap) {
3178 l = GetLine(line.line_no + 1);
3179 if ((l == null) || (l.ending != LineEnding.Wrap)) {
3187 /// <summary>Give it a pixel offset coordinate and it returns the Line covering that are (offset
3188 /// is either X or Y depending on if we are multiline
3190 internal Line GetLineByPixel (int offset, bool exact)
3192 Line line = document;
3196 while (line != sentinel) {
3198 if ((offset >= line.Y) && (offset < (line.Y+line.height))) {
3200 } else if (offset < line.Y) {
3207 while (line != sentinel) {
3209 if ((offset >= line.X) && (offset < (line.X + line.Width)))
3211 else if (offset < line.X)
3224 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3225 internal LineTag FindTag(int x, int y, out int index, bool exact) {
3229 line = GetLineByPixel(y, exact);
3236 // Alignment adjustment
3240 if (x >= tag.X && x < (tag.X+tag.Width)) {
3243 end = tag.start + tag.Length - 1;
3245 for (int pos = tag.start; pos < end; pos++) {
3246 if (x < line.widths[pos]) {
3248 return LineTag.GetFinalTag (tag);
3252 return LineTag.GetFinalTag (tag);
3254 if (tag.next != null) {
3262 index = line.text.Length;
3263 return LineTag.GetFinalTag (tag);
3268 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3269 internal LineTag FindCursor(int x, int y, out int index) {
3273 line = GetLineByPixel(multiline ? y : x, false);
3276 /// Special case going leftwards of the first tag
3279 return LineTag.GetFinalTag (tag);
3283 if (x >= tag.X && x < (tag.X+tag.Width)) {
3288 for (int pos = tag.start - 1; pos < end; pos++) {
3289 // When clicking on a character, we position the cursor to whatever edge
3290 // of the character the click was closer
3291 if (x < (line.X + line.widths[pos] + ((line.widths[pos+1]-line.widths[pos])/2))) {
3293 return LineTag.GetFinalTag (tag);
3297 return LineTag.GetFinalTag (tag);
3299 if (tag.next != null) {
3302 index = line.TextLengthWithoutEnding ();
3303 return LineTag.GetFinalTag (tag);
3308 /// <summary>Format area of document in specified font and color</summary>
3309 /// <param name="start_pos">1-based start position on start_line</param>
3310 /// <param name="end_pos">1-based end position on end_line </param>
3311 internal void FormatText (Line start_line, int start_pos, Line end_line, int end_pos, Font font,
3312 SolidBrush color, Color back_color, FormatSpecified specified)
3316 // First, format the first line
3317 if (start_line != end_line) {
3319 LineTag.FormatText(start_line, start_pos, start_line.text.Length - start_pos + 1, font, color, back_color, specified);
3322 LineTag.FormatText(end_line, 1, end_pos, font, color, back_color, specified);
3324 // Now all the lines inbetween
3325 for (int i = start_line.line_no + 1; i < end_line.line_no; i++) {
3327 LineTag.FormatText(l, 1, l.text.Length, font, color, back_color, specified);
3330 // Special case, single line
3331 LineTag.FormatText(start_line, start_pos, end_pos - start_pos, font, color, back_color, specified);
3335 internal void RecalculateAlignments ()
3344 while (line_no <= lines) {
3345 line = GetLine(line_no);
3348 switch (line.alignment) {
3349 case HorizontalAlignment.Left:
3350 line.align_shift = 0;
3352 case HorizontalAlignment.Center:
3353 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3355 case HorizontalAlignment.Right:
3356 line.align_shift = viewport_width - (int)line.widths[line.text.Length] - right_margin;
3366 /// <summary>Calculate formatting for the whole document</summary>
3367 internal bool RecalculateDocument(Graphics g) {
3368 return RecalculateDocument(g, 1, this.lines, false);
3371 /// <summary>Calculate formatting starting at a certain line</summary>
3372 internal bool RecalculateDocument(Graphics g, int start) {
3373 return RecalculateDocument(g, start, this.lines, false);
3376 /// <summary>Calculate formatting within two given line numbers</summary>
3377 internal bool RecalculateDocument(Graphics g, int start, int end) {
3378 return RecalculateDocument(g, start, end, false);
3381 /// <summary>With optimize on, returns true if line heights changed</summary>
3382 internal bool RecalculateDocument(Graphics g, int start, int end, bool optimize) {
3390 if (recalc_suspended > 0) {
3391 recalc_pending = true;
3392 recalc_start = Math.Min (recalc_start, start);
3393 recalc_end = Math.Max (recalc_end, end);
3394 recalc_optimize = optimize;
3398 // Fixup the positions, they can go kinda nuts
3399 start = Math.Max (start, 1);
3400 end = Math.Min (end, lines);
3402 offset = GetLine(start).offset;
3407 changed = true; // We always return true if we run non-optimized
3412 while (line_no <= (end + this.lines - shift)) {
3413 line = GetLine(line_no++);
3414 line.offset = offset;
3418 line.RecalculateLine(g, this);
3420 if (line.recalc && line.RecalculateLine(g, this)) {
3422 // If the height changed, all subsequent lines change
3429 line.RecalculatePasswordLine(g, this);
3431 if (line.recalc && line.RecalculatePasswordLine(g, this)) {
3433 // If the height changed, all subsequent lines change
3440 if (line.widths[line.text.Length] > new_width) {
3441 new_width = (int)line.widths[line.text.Length];
3444 // Calculate alignment
3445 if (line.alignment != HorizontalAlignment.Left) {
3446 if (line.alignment == HorizontalAlignment.Center) {
3447 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3449 line.align_shift = viewport_width - (int)line.widths[line.text.Length] - 1;
3454 offset += line.height;
3456 offset += (int) line.widths [line.text.Length];
3458 if (line_no > lines) {
3463 if (document_x != new_width) {
3464 document_x = new_width;
3465 if (WidthChanged != null) {
3466 WidthChanged(this, null);
3470 RecalculateAlignments();
3472 line = GetLine(lines);
3474 if (document_y != line.Y + line.height) {
3475 document_y = line.Y + line.height;
3476 if (HeightChanged != null) {
3477 HeightChanged(this, null);
3484 internal int Size() {
3488 private void owner_HandleCreated(object sender, EventArgs e) {
3489 RecalculateDocument(owner.CreateGraphicsInternal());
3493 private void owner_VisibleChanged(object sender, EventArgs e) {
3494 if (owner.Visible) {
3495 RecalculateDocument(owner.CreateGraphicsInternal());
3499 internal static bool IsWordSeparator (char ch)
3514 internal int FindWordSeparator(Line line, int pos, bool forward) {
3517 len = line.text.Length;
3520 for (int i = pos + 1; i < len; i++) {
3521 if (IsWordSeparator(line.Text[i])) {
3527 for (int i = pos - 1; i > 0; i--) {
3528 if (IsWordSeparator(line.Text[i - 1])) {
3536 /* Search document for text */
3537 internal bool FindChars(char[] chars, Marker start, Marker end, out Marker result) {
3543 // Search for occurence of any char in the chars array
3544 result = new Marker();
3547 line_no = start.line.line_no;
3549 while (line_no <= end.line.line_no) {
3550 line_len = line.text.Length;
3551 while (pos < line_len) {
3552 for (int i = 0; i < chars.Length; i++) {
3553 if (line.text[pos] == chars[i]) {
3555 if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
3569 line = GetLine(line_no);
3575 // This version does not build one big string for searching, instead it handles
3576 // line-boundaries, which is faster and less memory intensive
3577 // FIXME - Depending on culture stuff we might have to create a big string and use culturespecific
3578 // search stuff and change it to accept and return positions instead of Markers (which would match
3579 // RichTextBox behaviour better but would be inconsistent with the rest of TextControl)
3580 internal bool Find(string search, Marker start, Marker end, out Marker result, RichTextBoxFinds options) {
3582 string search_string;
3594 result = new Marker();
3595 word_option = ((options & RichTextBoxFinds.WholeWord) != 0);
3596 ignore_case = ((options & RichTextBoxFinds.MatchCase) == 0);
3597 reverse = ((options & RichTextBoxFinds.Reverse) != 0);
3600 line_no = start.line.line_no;
3604 // Prep our search string, lowercasing it if we do case-independent matching
3607 sb = new StringBuilder(search);
3608 for (int i = 0; i < sb.Length; i++) {
3609 sb[i] = Char.ToLower(sb[i]);
3611 search_string = sb.ToString();
3613 search_string = search;
3616 // We need to check if the character before our start position is a wordbreak
3619 if ((pos == 0) || (IsWordSeparator(line.text[pos - 1]))) {
3626 if (IsWordSeparator(line.text[pos - 1])) {
3632 // Need to check the end of the previous line
3635 prev_line = GetLine(line_no - 1);
3636 if (prev_line.ending == LineEnding.Wrap) {
3637 if (IsWordSeparator(prev_line.text[prev_line.text.Length - 1])) {
3651 // To avoid duplication of this loop with reverse logic, we search
3652 // through the document, remembering the last match and when returning
3653 // report that last remembered match
3655 last = new Marker();
3656 last.height = -1; // Abused - we use it to track change
3658 while (line_no <= end.line.line_no) {
3659 if (line_no != end.line.line_no) {
3660 line_len = line.text.Length;
3665 while (pos < line_len) {
3667 if (word_option && (current == search_string.Length)) {
3668 if (IsWordSeparator(line.text[pos])) {
3681 c = Char.ToLower(line.text[pos]);
3686 if (c == search_string[current]) {
3692 if (!word_option || (word_option && (word || (current > 0)))) {
3696 if (!word_option && (current == search_string.Length)) {
3713 if (IsWordSeparator(c)) {
3721 // Mark that we just saw a word boundary
3722 if (line.ending != LineEnding.Wrap || line.line_no == lines - 1) {
3726 if (current == search_string.Length) {
3742 line = GetLine(line_no);
3746 if (last.height != -1) {
3756 // if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
3768 internal void GetMarker(out Marker mark, bool start) {
3769 mark = new Marker();
3772 mark.line = GetLine(1);
3773 mark.tag = mark.line.tags;
3776 mark.line = GetLine(lines);
3777 mark.tag = mark.line.tags;
3778 while (mark.tag.next != null) {
3779 mark.tag = mark.tag.next;
3781 mark.pos = mark.line.text.Length;
3784 #endregion // Internal Methods
3787 internal event EventHandler CaretMoved;
3788 internal event EventHandler WidthChanged;
3789 internal event EventHandler HeightChanged;
3790 internal event EventHandler LengthChanged;
3791 #endregion // Events
3793 #region Administrative
3794 public IEnumerator GetEnumerator() {
3799 public override bool Equals(object obj) {
3804 if (!(obj is Document)) {
3812 if (ToString().Equals(((Document)obj).ToString())) {
3819 public override int GetHashCode() {
3823 public override string ToString() {
3824 return "document " + this.document_id;
3826 #endregion // Administrative
3829 internal class PictureTag : LineTag {
3831 internal RTF.Picture picture;
3833 internal PictureTag (Line line, int start, RTF.Picture picture) : base (line, start)
3835 this.picture = picture;
3838 public override bool IsTextTag {
3839 get { return false; }
3842 internal override SizeF SizeOfPosition (Graphics dc, int pos)
3844 return picture.Size;
3847 internal override int MaxHeight ()
3849 return (int) (picture.Height + 0.5F);
3852 internal override void Draw (Graphics dc, Brush brush, float xoff, float y, int start, int end)
3854 picture.DrawImage (dc, xoff + line.widths [start], y, false);
3857 internal override void Draw (Graphics dc, Brush brush, float xoff, float y, int start, int end, string text)
3859 picture.DrawImage (dc, xoff + + line.widths [start], y, false);
3862 public override string Text ()
3868 internal class UndoManager {
3870 internal enum ActionType {
3874 // This is basically just cut & paste
3882 internal class Action {
3883 internal ActionType type;
3884 internal int line_no;
3886 internal object data;
3889 #region Local Variables
3890 private Document document;
3891 private Stack undo_actions;
3892 private Stack redo_actions;
3894 //private int caret_line;
3895 //private int caret_pos;
3897 // When performing an action, we lock the queue, so that the action can't be undone
3898 private bool locked;
3899 #endregion // Local Variables
3901 #region Constructors
3902 internal UndoManager (Document document)
3904 this.document = document;
3905 undo_actions = new Stack (50);
3906 redo_actions = new Stack (50);
3908 #endregion // Constructors
3911 internal bool CanUndo {
3912 get { return undo_actions.Count > 0; }
3915 internal bool CanRedo {
3916 get { return redo_actions.Count > 0; }
3919 internal string UndoActionName {
3921 foreach (Action action in undo_actions) {
3922 if (action.type == ActionType.UserActionBegin)
3923 return (string) action.data;
3924 if (action.type == ActionType.Typing)
3925 return Locale.GetText ("Typing");
3927 return String.Empty;
3931 internal string RedoActionName {
3933 foreach (Action action in redo_actions) {
3934 if (action.type == ActionType.UserActionBegin)
3935 return (string) action.data;
3936 if (action.type == ActionType.Typing)
3937 return Locale.GetText ("Typing");
3939 return String.Empty;
3942 #endregion // Properties
3944 #region Internal Methods
3945 internal void Clear ()
3947 undo_actions.Clear();
3948 redo_actions.Clear();
3951 internal void Undo ()
3954 bool user_action_finished = false;
3956 if (undo_actions.Count == 0)
3959 // Nuke the redo queue
3960 redo_actions.Clear ();
3965 action = (Action) undo_actions.Pop ();
3967 // Put onto redo stack
3968 redo_actions.Push(action);
3971 switch(action.type) {
3973 case ActionType.UserActionBegin:
3974 user_action_finished = true;
3977 case ActionType.UserActionEnd:
3981 case ActionType.InsertString:
3982 start = document.GetLine (action.line_no);
3983 document.SuspendUpdate ();
3984 document.DeleteMultiline (start, action.pos, ((string) action.data).Length + 1);
3985 document.PositionCaret (start, action.pos);
3986 document.SetSelectionToCaret (true);
3987 document.ResumeUpdate (true);
3990 case ActionType.Typing:
3991 start = document.GetLine (action.line_no);
3992 document.SuspendUpdate ();
3993 document.DeleteMultiline (start, action.pos, ((StringBuilder) action.data).Length);
3994 document.PositionCaret (start, action.pos);
3995 document.SetSelectionToCaret (true);
3996 document.ResumeUpdate (true);
3998 // This is an open ended operation, so only a single typing operation can be undone at once
3999 user_action_finished = true;
4002 case ActionType.DeleteString:
4003 start = document.GetLine (action.line_no);
4004 document.SuspendUpdate ();
4005 Insert (start, action.pos, (Line) action.data, true);
4006 document.ResumeUpdate (true);
4009 } while (!user_action_finished && undo_actions.Count > 0);
4014 internal void Redo ()
4017 bool user_action_finished = false;
4019 if (redo_actions.Count == 0)
4022 // You can't undo anything after redoing
4023 undo_actions.Clear ();
4030 action = (Action) redo_actions.Pop ();
4032 switch (action.type) {
4034 case ActionType.UserActionBegin:
4038 case ActionType.UserActionEnd:
4039 user_action_finished = true;
4042 case ActionType.InsertString:
4043 start = document.GetLine (action.line_no);
4044 document.SuspendUpdate ();
4045 start_index = document.LineTagToCharIndex (start, action.pos);
4046 document.InsertString (start, action.pos, (string) action.data);
4047 document.CharIndexToLineTag (start_index + ((string) action.data).Length,
4048 out document.caret.line, out document.caret.tag,
4049 out document.caret.pos);
4050 document.UpdateCaret ();
4051 document.SetSelectionToCaret (true);
4052 document.ResumeUpdate (true);
4055 case ActionType.Typing:
4056 start = document.GetLine (action.line_no);
4057 document.SuspendUpdate ();
4058 start_index = document.LineTagToCharIndex (start, action.pos);
4059 document.InsertString (start, action.pos, ((StringBuilder) action.data).ToString ());
4060 document.CharIndexToLineTag (start_index + ((StringBuilder) action.data).Length,
4061 out document.caret.line, out document.caret.tag,
4062 out document.caret.pos);
4063 document.UpdateCaret ();
4064 document.SetSelectionToCaret (true);
4065 document.ResumeUpdate (true);
4067 // This is an open ended operation, so only a single typing operation can be undone at once
4068 user_action_finished = true;
4071 case ActionType.DeleteString:
4072 start = document.GetLine (action.line_no);
4073 document.SuspendUpdate ();
4074 document.DeleteMultiline (start, action.pos, ((Line) action.data).text.Length);
4075 document.PositionCaret (start, action.pos);
4076 document.SetSelectionToCaret (true);
4077 document.ResumeUpdate (true);
4081 } while (!user_action_finished && redo_actions.Count > 0);
4085 #endregion // Internal Methods
4087 #region Private Methods
4089 public void BeginUserAction (string name)
4094 Action ua = new Action ();
4095 ua.type = ActionType.UserActionBegin;
4098 undo_actions.Push (ua);
4101 public void EndUserAction ()
4106 Action ua = new Action ();
4107 ua.type = ActionType.UserActionEnd;
4109 undo_actions.Push (ua);
4112 // start_pos, end_pos = 1 based
4113 public void RecordDeleteString (Line start_line, int start_pos, Line end_line, int end_pos)
4118 Action a = new Action ();
4120 // We cant simply store the string, because then formatting would be lost
4121 a.type = ActionType.DeleteString;
4122 a.line_no = start_line.line_no;
4124 a.data = Duplicate (start_line, start_pos, end_line, end_pos);
4126 undo_actions.Push(a);
4129 public void RecordInsertString (Line line, int pos, string str)
4131 if (locked || str.Length == 0)
4134 Action a = new Action ();
4136 a.type = ActionType.InsertString;
4138 a.line_no = line.line_no;
4141 undo_actions.Push (a);
4144 public void RecordTyping (Line line, int pos, char ch)
4151 if (undo_actions.Count > 0)
4152 a = (Action) undo_actions.Peek ();
4154 if (a == null || a.type != ActionType.Typing) {
4156 a.type = ActionType.Typing;
4157 a.data = new StringBuilder ();
4158 a.line_no = line.line_no;
4161 undo_actions.Push (a);
4164 StringBuilder data = (StringBuilder) a.data;
4168 // start_pos = 1-based
4169 // end_pos = 1-based
4170 public Line Duplicate(Line start_line, int start_pos, Line end_line, int end_pos)
4176 LineTag current_tag;
4181 line = new Line (start_line.document, start_line.ending);
4184 for (int i = start_line.line_no; i <= end_line.line_no; i++) {
4185 current = document.GetLine(i);
4187 if (start_line.line_no == i) {
4193 if (end_line.line_no == i) {
4196 end = current.text.Length;
4203 line.text = new StringBuilder (current.text.ToString (start, end - start));
4205 // Copy tags from start to start+length onto new line
4206 current_tag = current.FindTag (start);
4207 while ((current_tag != null) && (current_tag.start <= end)) {
4208 if ((current_tag.start <= start) && (start < (current_tag.start + current_tag.Length))) {
4209 // start tag is within this tag
4212 tag_start = current_tag.start;
4215 tag = new LineTag(line, tag_start - start + 1);
4216 tag.CopyFormattingFrom (current_tag);
4218 current_tag = current_tag.next;
4220 // Add the new tag to the line
4221 if (line.tags == null) {
4227 while (tail.next != null) {
4231 tag.previous = tail;
4235 if ((i + 1) <= end_line.line_no) {
4236 line.ending = current.ending;
4238 // Chain them (we use right/left as next/previous)
4239 line.right = new Line (start_line.document, start_line.ending);
4240 line.right.left = line;
4248 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
4249 internal void Insert(Line line, int pos, Line insert, bool select)
4257 // Handle special case first
4258 if (insert.right == null) {
4260 // Single line insert
4261 document.Split(line, pos);
4263 if (insert.tags == null) {
4264 return; // Blank line
4267 //Insert our tags at the end
4270 while (tag.next != null) {
4274 offset = tag.start + tag.Length - 1;
4276 tag.next = insert.tags;
4277 line.text.Insert(offset, insert.text.ToString());
4279 // Adjust start locations
4281 while (tag != null) {
4282 tag.start += offset;
4286 // Put it back together
4287 document.Combine(line.line_no, line.line_no + 1);
4290 document.SetSelectionStart (line, pos, false);
4291 document.SetSelectionEnd (line, pos + insert.text.Length, false);
4294 document.UpdateView(line, pos);
4302 while (current != null) {
4304 if (current == insert) {
4305 // Inserting the first line we split the line (and make space)
4306 document.Split(line.line_no, pos);
4307 //Insert our tags at the end of the line
4311 if (tag != null && tag.Length != 0) {
4312 while (tag.next != null) {
4315 offset = tag.start + tag.Length - 1;
4316 tag.next = current.tags;
4317 tag.next.previous = tag;
4323 line.tags = current.tags;
4324 line.tags.previous = null;
4328 line.ending = current.ending;
4330 document.Split(line.line_no, 0);
4332 line.tags = current.tags;
4333 line.tags.previous = null;
4334 line.ending = current.ending;
4338 // Adjust start locations and line pointers
4339 while (tag != null) {
4340 tag.start += offset - 1;
4345 line.text.Insert(offset, current.text.ToString());
4346 line.Grow(line.text.Length);
4349 line = document.GetLine(line.line_no + 1);
4351 // FIXME? Test undo of line-boundaries
4352 if ((current.right == null) && (current.tags.Length != 0)) {
4353 document.Combine(line.line_no - 1, line.line_no);
4355 current = current.right;
4360 // Recalculate our document
4361 document.UpdateView(first, lines, pos);
4364 #endregion // Private Methods