1 // Permission is hereby granted, free of charge, to any person obtaining
2 // a copy of this software and associated documentation files (the
3 // "Software"), to deal in the Software without restriction, including
4 // without limitation the rights to use, copy, modify, merge, publish,
5 // distribute, sublicense, and/or sell copies of the Software, and to
6 // permit persons to whom the Software is furnished to do so, subject to
7 // the following conditions:
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
12 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 // Copyright (c) 2004-2006 Novell, Inc. (http://www.novell.com)
23 // Peter Bartok pbartok@novell.com
29 // There's still plenty of things missing, I've got most of it planned, just hadn't had
30 // the time to write it all yet.
31 // Stuff missing (in no particular order):
32 // - Align text after RecalculateLine
33 // - Implement tag types for hotlinks, etc.
34 // - Implement CaretPgUp/PgDown
37 // selection_start.pos and selection_end.pos are 0-based
38 // selection_start.pos = first selected char
39 // selection_end.pos = first NOT-selected char
41 // FormatText methods are 1-based (as are all tags, LineTag.Start is 1 for
42 // the first character on a line; the reason is that 0 is the position
43 // *before* the first character on a line
49 using System.Collections;
51 using System.Drawing.Text;
53 using RTF=System.Windows.Forms.RTF;
55 namespace System.Windows.Forms {
56 internal enum LineColor {
61 internal enum CaretSelection {
62 Position, // Selection=Caret
63 Word, // Selection=Word under caret
64 Line // Selection=Line under caret
68 internal enum FormatSpecified {
76 internal enum CaretDirection {
77 CharForward, // Move a char to the right
78 CharBack, // Move a char to the left
79 LineUp, // Move a line up
80 LineDown, // Move a line down
81 Home, // Move to the beginning of the line
82 End, // Move to the end of the line
83 PgUp, // Move one page up
84 PgDn, // Move one page down
85 CtrlPgUp, // Move caret to the first visible char in the viewport
86 CtrlPgDn, // Move caret to the last visible char in the viewport
87 CtrlHome, // Move to the beginning of the document
88 CtrlEnd, // Move to the end of the document
89 WordBack, // Move to the beginning of the previous word (or beginning of line)
90 WordForward, // Move to the beginning of the next word (or end of line)
91 SelectionStart, // Move to the beginning of the current selection
92 SelectionEnd, // Move to the end of the current selection
93 CharForwardNoWrap, // Move a char forward, but don't wrap onto the next line
94 CharBackNoWrap // Move a char backward, but don't wrap onto the previous line
97 internal enum LineEnding {
98 Wrap = 1, // line wraps to the next line
107 internal class Document : ICloneable, IEnumerable {
109 // FIXME - go through code and check for places where
110 // we do explicit comparisons instead of using the compare overloads
111 internal struct Marker {
113 internal LineTag tag;
117 public static bool operator<(Marker lhs, Marker rhs) {
118 if (lhs.line.line_no < rhs.line.line_no) {
122 if (lhs.line.line_no == rhs.line.line_no) {
123 if (lhs.pos < rhs.pos) {
130 public static bool operator>(Marker lhs, Marker rhs) {
131 if (lhs.line.line_no > rhs.line.line_no) {
135 if (lhs.line.line_no == rhs.line.line_no) {
136 if (lhs.pos > rhs.pos) {
143 public static bool operator==(Marker lhs, Marker rhs) {
144 if ((lhs.line.line_no == rhs.line.line_no) && (lhs.pos == rhs.pos)) {
150 public static bool operator!=(Marker lhs, Marker rhs) {
151 if ((lhs.line.line_no != rhs.line.line_no) || (lhs.pos != rhs.pos)) {
157 public void Combine(Line move_to_line, int move_to_line_length) {
159 pos += move_to_line_length;
160 tag = LineTag.FindTag(line, pos);
163 // This is for future use, right now Document.Split does it by hand, with some added shortcut logic
164 public void Split(Line move_to_line, int split_at) {
167 tag = LineTag.FindTag(line, pos);
170 public override bool Equals(object obj) {
171 return this==(Marker)obj;
174 public override int GetHashCode() {
175 return base.GetHashCode ();
178 public override string ToString() {
179 return "Marker Line " + line + ", Position " + pos;
183 #endregion Structures
185 #region Local Variables
186 private Line document;
188 private Line sentinel;
189 private int document_id;
190 private Random random = new Random();
191 internal string password_char;
192 private StringBuilder password_cache;
193 private bool calc_pass;
194 private int char_count;
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 ()
495 if (recalc_suspended == 0) {
496 recalc_start = int.MaxValue;
497 recalc_end = int.MinValue;
503 internal void ResumeRecalc (bool immediate_update)
505 if (recalc_suspended > 0)
508 if (recalc_suspended == 0 && (immediate_update || recalc_pending)) {
509 RecalculateDocument (owner.CreateGraphicsInternal (), recalc_start, recalc_end, recalc_optimize);
510 recalc_pending = false;
514 internal void SuspendUpdate ()
519 internal void ResumeUpdate (bool immediate_update)
521 if (update_suspended > 0)
524 if (immediate_update && update_suspended == 0 && update_pending) {
525 UpdateView (GetLine (update_start), 0);
526 update_pending = false;
531 internal int DumpTree(Line line, bool with_tags) {
536 Console.Write("Line {0} [# {1}], Y: {2}, ending style: {3}, Text: '{4}'",
537 line.line_no, line.GetHashCode(), line.Y, line.ending,
538 line.text != null ? line.text.ToString() : "undefined");
540 if (line.left == sentinel) {
541 Console.Write(", left = sentinel");
542 } else if (line.left == null) {
543 Console.Write(", left = NULL");
546 if (line.right == sentinel) {
547 Console.Write(", right = sentinel");
548 } else if (line.right == null) {
549 Console.Write(", right = NULL");
552 Console.WriteLine("");
562 Console.Write(" Tags: ");
563 while (tag != null) {
564 Console.Write("{0} <{1}>-<{2}>", count++, tag.Start, tag.End
565 /*line.text.ToString (tag.start - 1, tag.length)*/);
566 length += tag.Length;
568 if (tag.Line != line) {
569 Console.Write("BAD line link");
570 throw new Exception("Bad line link in tree");
577 if (length > line.text.Length) {
578 throw new Exception(String.Format("Length of tags more than length of text on line (expected {0} calculated {1})", line.text.Length, length));
579 } else if (length < line.text.Length) {
580 throw new Exception(String.Format("Length of tags less than length of text on line (expected {0} calculated {1})", line.text.Length, length));
582 Console.WriteLine("");
584 if (line.left != null) {
585 if (line.left != sentinel) {
586 total += DumpTree(line.left, with_tags);
589 if (line != sentinel) {
590 throw new Exception("Left should not be NULL");
594 if (line.right != null) {
595 if (line.right != sentinel) {
596 total += DumpTree(line.right, with_tags);
599 if (line != sentinel) {
600 throw new Exception("Right should not be NULL");
604 for (int i = 1; i <= this.lines; i++) {
605 if (GetLine(i) == null) {
606 throw new Exception(String.Format("Hole in line order, missing {0}", i));
610 if (line == this.Root) {
611 if (total < this.lines) {
612 throw new Exception(String.Format("Not enough nodes in tree, found {0}, expected {1}", total, this.lines));
613 } else if (total > this.lines) {
614 throw new Exception(String.Format("Too many nodes in tree, found {0}, expected {1}", total, this.lines));
621 private void SetSelectionVisible (bool value)
623 selection_visible = value;
625 // cursor and selection are enemies, we can't have both in the same room at the same time
626 if (owner.IsHandleCreated && !owner.show_caret_w_selection)
627 XplatUI.CaretVisible (owner.Handle, !selection_visible);
630 private void DecrementLines(int line_no) {
634 while (current <= lines) {
635 GetLine(current).line_no--;
641 private void IncrementLines(int line_no) {
644 current = this.lines;
645 while (current >= line_no) {
646 GetLine(current).line_no++;
652 private void RebalanceAfterAdd(Line line1) {
655 while ((line1 != document) && (line1.parent.color == LineColor.Red)) {
656 if (line1.parent == line1.parent.parent.left) {
657 line2 = line1.parent.parent.right;
659 if ((line2 != null) && (line2.color == LineColor.Red)) {
660 line1.parent.color = LineColor.Black;
661 line2.color = LineColor.Black;
662 line1.parent.parent.color = LineColor.Red;
663 line1 = line1.parent.parent;
665 if (line1 == line1.parent.right) {
666 line1 = line1.parent;
670 line1.parent.color = LineColor.Black;
671 line1.parent.parent.color = LineColor.Red;
673 RotateRight(line1.parent.parent);
676 line2 = line1.parent.parent.left;
678 if ((line2 != null) && (line2.color == LineColor.Red)) {
679 line1.parent.color = LineColor.Black;
680 line2.color = LineColor.Black;
681 line1.parent.parent.color = LineColor.Red;
682 line1 = line1.parent.parent;
684 if (line1 == line1.parent.left) {
685 line1 = line1.parent;
689 line1.parent.color = LineColor.Black;
690 line1.parent.parent.color = LineColor.Red;
691 RotateLeft(line1.parent.parent);
695 document.color = LineColor.Black;
698 private void RebalanceAfterDelete(Line line1) {
701 while ((line1 != document) && (line1.color == LineColor.Black)) {
702 if (line1 == line1.parent.left) {
703 line2 = line1.parent.right;
704 if (line2.color == LineColor.Red) {
705 line2.color = LineColor.Black;
706 line1.parent.color = LineColor.Red;
707 RotateLeft(line1.parent);
708 line2 = line1.parent.right;
710 if ((line2.left.color == LineColor.Black) && (line2.right.color == LineColor.Black)) {
711 line2.color = LineColor.Red;
712 line1 = line1.parent;
714 if (line2.right.color == LineColor.Black) {
715 line2.left.color = LineColor.Black;
716 line2.color = LineColor.Red;
718 line2 = line1.parent.right;
720 line2.color = line1.parent.color;
721 line1.parent.color = LineColor.Black;
722 line2.right.color = LineColor.Black;
723 RotateLeft(line1.parent);
727 line2 = line1.parent.left;
728 if (line2.color == LineColor.Red) {
729 line2.color = LineColor.Black;
730 line1.parent.color = LineColor.Red;
731 RotateRight(line1.parent);
732 line2 = line1.parent.left;
734 if ((line2.right.color == LineColor.Black) && (line2.left.color == LineColor.Black)) {
735 line2.color = LineColor.Red;
736 line1 = line1.parent;
738 if (line2.left.color == LineColor.Black) {
739 line2.right.color = LineColor.Black;
740 line2.color = LineColor.Red;
742 line2 = line1.parent.left;
744 line2.color = line1.parent.color;
745 line1.parent.color = LineColor.Black;
746 line2.left.color = LineColor.Black;
747 RotateRight(line1.parent);
752 line1.color = LineColor.Black;
755 private void RotateLeft(Line line1) {
756 Line line2 = line1.right;
758 line1.right = line2.left;
760 if (line2.left != sentinel) {
761 line2.left.parent = line1;
764 if (line2 != sentinel) {
765 line2.parent = line1.parent;
768 if (line1.parent != null) {
769 if (line1 == line1.parent.left) {
770 line1.parent.left = line2;
772 line1.parent.right = line2;
779 if (line1 != sentinel) {
780 line1.parent = line2;
784 private void RotateRight(Line line1) {
785 Line line2 = line1.left;
787 line1.left = line2.right;
789 if (line2.right != sentinel) {
790 line2.right.parent = line1;
793 if (line2 != sentinel) {
794 line2.parent = line1.parent;
797 if (line1.parent != null) {
798 if (line1 == line1.parent.right) {
799 line1.parent.right = line2;
801 line1.parent.left = line2;
808 if (line1 != sentinel) {
809 line1.parent = line2;
814 internal void UpdateView(Line line, int pos) {
815 if (!owner.IsHandleCreated) {
819 if (update_suspended > 0) {
820 update_start = Math.Min (update_start, line.line_no);
821 // update_end = Math.Max (update_end, line.line_no);
822 // recalc_optimize = true;
823 update_pending = true;
827 // Optimize invalidation based on Line alignment
828 if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no, true)) {
829 // Lineheight changed, invalidate the rest of the document
830 if ((line.Y - viewport_y) >=0 ) {
831 // We formatted something that's in view, only draw parts of the screen
832 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, owner.Height - line.Y - viewport_y));
834 // The tag was above the visible area, draw everything
838 switch(line.alignment) {
839 case HorizontalAlignment.Left: {
840 owner.Invalidate(new Rectangle(line.X + (int)line.widths[pos] - viewport_x - 1, line.Y - viewport_y, viewport_width, line.height + 1));
844 case HorizontalAlignment.Center: {
845 owner.Invalidate(new Rectangle(line.X, line.Y - viewport_y, viewport_width, line.height + 1));
849 case HorizontalAlignment.Right: {
850 owner.Invalidate(new Rectangle(line.X, line.Y - viewport_y, (int)line.widths[pos + 1] - viewport_x + line.X, line.height + 1));
858 // Update display from line, down line_count lines; pos is unused, but required for the signature
859 internal void UpdateView(Line line, int line_count, int pos) {
860 if (!owner.IsHandleCreated) {
864 if (recalc_suspended > 0) {
865 recalc_start = Math.Min (recalc_start, line.line_no);
866 recalc_end = Math.Max (recalc_end, line.line_no + line_count);
867 recalc_optimize = true;
868 recalc_pending = true;
872 int start_line_top = line.Y;
877 end_line = GetLine (line.line_no + line_count);
878 if (end_line == null)
879 end_line = GetLine (lines);
882 end_line_bottom = end_line.Y + end_line.height;
884 if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no + line_count, true)) {
885 // Lineheight changed, invalidate the rest of the document
886 if ((line.Y - viewport_y) >=0 ) {
887 // We formatted something that's in view, only draw parts of the screen
888 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, owner.Height - line.Y - viewport_y));
890 // The tag was above the visible area, draw everything
894 int x = 0 - viewport_x;
895 int w = viewport_width;
896 int y = Math.Min (start_line_top - viewport_y, line.Y - viewport_y);
897 int h = Math.Max (end_line_bottom - y, end_line.Y + end_line.height - y);
899 owner.Invalidate (new Rectangle (x, y, w, h));
902 #endregion // Private Methods
904 #region Internal Methods
905 // Clear the document and reset state
906 internal void Empty() {
911 // We always have a blank line
912 Add (1, String.Empty, owner.Font, owner.ForeColor, LineEnding.None);
914 this.RecalculateDocument(owner.CreateGraphicsInternal());
917 SetSelectionVisible (false);
919 selection_start.line = this.document;
920 selection_start.pos = 0;
921 selection_start.tag = selection_start.line.tags;
922 selection_end.line = this.document;
923 selection_end.pos = 0;
924 selection_end.tag = selection_end.line.tags;
933 if (owner.IsHandleCreated)
937 internal void PositionCaret(Line line, int pos) {
938 caret.tag = line.FindTag (pos);
940 MoveCaretToTextTag ();
945 if (owner.IsHandleCreated) {
947 if (caret.height != caret.tag.Height)
948 XplatUI.CreateCaret (owner.Handle, caret_width, caret.height);
949 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);
952 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
955 // We set this at the end because we use the heights to determine whether or
956 // not we need to recreate the caret
957 caret.height = caret.tag.Height;
961 internal void PositionCaret(int x, int y) {
962 if (!owner.IsHandleCreated) {
966 caret.tag = FindCursor(x, y, out caret.pos);
968 MoveCaretToTextTag ();
970 caret.line = caret.tag.Line;
971 caret.height = caret.tag.Height;
973 if (owner.ShowSelection && (!selection_visible || owner.show_caret_w_selection)) {
974 XplatUI.CreateCaret (owner.Handle, caret_width, caret.height);
975 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);
978 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
981 internal void CaretHasFocus() {
982 if ((caret.tag != null) && owner.IsHandleCreated) {
983 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
984 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);
989 if (owner.IsHandleCreated && SelectionLength () > 0) {
990 InvalidateSelectionArea ();
994 internal void CaretLostFocus() {
995 if (!owner.IsHandleCreated) {
998 XplatUI.DestroyCaret(owner.Handle);
1001 internal void AlignCaret() {
1002 if (!owner.IsHandleCreated) {
1006 caret.tag = LineTag.FindTag (caret.line, caret.pos);
1008 MoveCaretToTextTag ();
1010 caret.height = caret.tag.Height;
1012 if (owner.Focused) {
1013 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1014 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);
1018 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1021 internal void UpdateCaret() {
1022 if (!owner.IsHandleCreated || caret.tag == null) {
1026 MoveCaretToTextTag ();
1028 if (caret.tag.Height != caret.height) {
1029 caret.height = caret.tag.Height;
1030 if (owner.Focused) {
1031 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1035 if (owner.Focused) {
1036 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);
1040 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1043 internal void DisplayCaret() {
1044 if (!owner.IsHandleCreated) {
1048 if (owner.ShowSelection && (!selection_visible || owner.show_caret_w_selection)) {
1049 XplatUI.CaretVisible(owner.Handle, true);
1053 internal void HideCaret() {
1054 if (!owner.IsHandleCreated) {
1058 if (owner.Focused) {
1059 XplatUI.CaretVisible(owner.Handle, false);
1064 internal void MoveCaretToTextTag ()
1066 if (caret.tag == null || caret.tag.IsTextTag)
1071 if (caret.pos < caret.tag.Start) {
1072 caret.tag = caret.tag.Previous;
1074 caret.tag = caret.tag.Next;
1078 internal void MoveCaret(CaretDirection direction) {
1079 // FIXME should we use IsWordSeparator to detect whitespace, instead
1080 // of looking for actual spaces in the Word move cases?
1082 bool nowrap = false;
1084 case CaretDirection.CharForwardNoWrap:
1086 goto case CaretDirection.CharForward;
1087 case CaretDirection.CharForward: {
1089 if (caret.pos > caret.line.TextLengthWithoutEnding ()) {
1091 // Go into next line
1092 if (caret.line.line_no < this.lines) {
1093 caret.line = GetLine(caret.line.line_no+1);
1095 caret.tag = caret.line.tags;
1100 // Single line; we stay where we are
1104 if ((caret.tag.Start - 1 + caret.tag.Length) < caret.pos) {
1105 caret.tag = caret.tag.Next;
1112 case CaretDirection.CharBackNoWrap:
1114 goto case CaretDirection.CharBack;
1115 case CaretDirection.CharBack: {
1116 if (caret.pos > 0) {
1117 // caret.pos--; // folded into the if below
1119 if (--caret.pos > 0) {
1120 if (caret.tag.Start > caret.pos) {
1121 caret.tag = caret.tag.Previous;
1125 if (caret.line.line_no > 1 && !nowrap) {
1126 caret.line = GetLine(caret.line.line_no - 1);
1127 caret.pos = caret.line.TextLengthWithoutEnding ();
1128 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1135 case CaretDirection.WordForward: {
1138 len = caret.line.text.Length;
1139 if (caret.pos < len) {
1140 while ((caret.pos < len) && (caret.line.text[caret.pos] != ' ')) {
1143 if (caret.pos < len) {
1144 // Skip any whitespace
1145 while ((caret.pos < len) && (caret.line.text[caret.pos] == ' ')) {
1149 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1151 if (caret.line.line_no < this.lines) {
1152 caret.line = GetLine(caret.line.line_no + 1);
1154 caret.tag = caret.line.tags;
1161 case CaretDirection.WordBack: {
1162 if (caret.pos > 0) {
1165 while ((caret.pos > 0) && (caret.line.text[caret.pos] == ' ')) {
1169 while ((caret.pos > 0) && (caret.line.text[caret.pos] != ' ')) {
1173 if (caret.line.text.ToString(caret.pos, 1) == " ") {
1174 if (caret.pos != 0) {
1177 caret.line = GetLine(caret.line.line_no - 1);
1178 caret.pos = caret.line.text.Length;
1181 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1183 if (caret.line.line_no > 1) {
1184 caret.line = GetLine(caret.line.line_no - 1);
1185 caret.pos = caret.line.text.Length;
1186 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1193 case CaretDirection.LineUp: {
1194 if (caret.line.line_no > 1) {
1197 pixel = (int)caret.line.widths[caret.pos];
1198 PositionCaret(pixel, GetLine(caret.line.line_no - 1).Y);
1205 case CaretDirection.LineDown: {
1206 if (caret.line.line_no < lines) {
1209 pixel = (int)caret.line.widths[caret.pos];
1210 PositionCaret(pixel, GetLine(caret.line.line_no + 1).Y);
1217 case CaretDirection.Home: {
1218 if (caret.pos > 0) {
1220 caret.tag = caret.line.tags;
1226 case CaretDirection.End: {
1227 if (caret.pos < caret.line.TextLengthWithoutEnding ()) {
1228 caret.pos = caret.line.TextLengthWithoutEnding ();
1229 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1235 case CaretDirection.PgUp: {
1237 if (viewport_y == 0 && owner.richtext) {
1238 owner.vscroll.Value = 0;
1239 Line line = GetLine (1);
1240 PositionCaret (line, 0);
1243 int y_offset = caret.line.Y + caret.line.height - 1 - viewport_y;
1245 LineTag top = FindCursor ((int) caret.line.widths [caret.pos],
1246 viewport_y - viewport_height, out index);
1248 owner.vscroll.Value = Math.Min (top.Line.Y, owner.vscroll.Maximum - viewport_height);
1249 PositionCaret ((int) caret.line.widths [caret.pos], y_offset + viewport_y);
1254 case CaretDirection.PgDn: {
1256 if (viewport_y + viewport_height >= document_y && owner.richtext) {
1257 owner.vscroll.Value = owner.vscroll.Maximum - viewport_height + 1;
1258 Line line = GetLine (lines);
1259 PositionCaret (line, line.Text.Length);
1262 int y_offset = caret.line.Y - viewport_y;
1264 LineTag top = FindCursor ((int) caret.line.widths [caret.pos],
1265 viewport_y + viewport_height, out index);
1267 owner.vscroll.Value = Math.Min (top.Line.Y, owner.vscroll.Maximum - viewport_height);
1268 PositionCaret ((int) caret.line.widths [caret.pos], y_offset + viewport_y);
1273 case CaretDirection.CtrlPgUp: {
1274 PositionCaret(0, viewport_y);
1279 case CaretDirection.CtrlPgDn: {
1284 tag = FindCursor (0, viewport_y + viewport_height, out index);
1285 if (tag.Line.line_no > 1) {
1286 line = GetLine(tag.Line.line_no - 1);
1290 PositionCaret(line, line.Text.Length);
1295 case CaretDirection.CtrlHome: {
1296 caret.line = GetLine(1);
1298 caret.tag = caret.line.tags;
1304 case CaretDirection.CtrlEnd: {
1305 caret.line = GetLine(lines);
1306 caret.pos = caret.line.TextLengthWithoutEnding ();
1307 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1313 case CaretDirection.SelectionStart: {
1314 caret.line = selection_start.line;
1315 caret.pos = selection_start.pos;
1316 caret.tag = selection_start.tag;
1322 case CaretDirection.SelectionEnd: {
1323 caret.line = selection_end.line;
1324 caret.pos = selection_end.pos;
1325 caret.tag = selection_end.tag;
1333 internal void DumpDoc ()
1335 Console.WriteLine ("<doc lines='{0}'>", lines);
1336 for (int i = 1; i <= lines ; i++) {
1337 Line line = GetLine (i);
1338 Console.WriteLine ("<line no='{0}' ending='{1}'>", line.line_no, line.ending);
1340 LineTag tag = line.tags;
1341 while (tag != null) {
1342 Console.Write ("\t<tag type='{0}' span='{1}->{2}' font='{3}' color='{4}'>",
1343 tag.GetType (), tag.Start, tag.Length, tag.Font, tag.Color);
1344 Console.Write (tag.Text ());
1345 Console.WriteLine ("</tag>");
1348 Console.WriteLine ("</line>");
1350 Console.WriteLine ("</doc>");
1353 internal void Draw (Graphics g, Rectangle clip)
1355 Line line; // Current line being drawn
1356 LineTag tag; // Current tag being drawn
1357 int start; // First line to draw
1358 int end; // Last line to draw
1359 StringBuilder text; // String representing the current line
1362 Color current_color;
1364 // First, figure out from what line to what line we need to draw
1367 start = GetLineByPixel(clip.Top + viewport_y, false).line_no;
1368 end = GetLineByPixel(clip.Bottom + viewport_y, false).line_no;
1370 start = GetLineByPixel(clip.Left + viewport_x, false).line_no;
1371 end = GetLineByPixel(clip.Right + viewport_x, false).line_no;
1375 /// We draw the single border ourself
1377 if (owner.actual_border_style == BorderStyle.FixedSingle) {
1378 ControlPaint.DrawBorder (g, owner.ClientRectangle, Color.Black, ButtonBorderStyle.Solid);
1381 /// Make sure that we aren't drawing one more line then we need to
1382 line = GetLine (end - 1);
1383 if (line != null && clip.Bottom == line.Y + line.height + viewport_y)
1389 DateTime n = DateTime.Now;
1390 Console.WriteLine ("Started drawing: {0}s {1}ms", n.Second, n.Millisecond);
1391 Console.WriteLine ("CLIP: {0}", clip);
1392 Console.WriteLine ("S: {0}", GetLine (start).text);
1393 Console.WriteLine ("E: {0}", GetLine (end).text);
1396 // Non multiline selection can be handled outside of the loop
1397 if (!multiline && selection_visible && owner.ShowSelection) {
1398 g.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (ThemeEngine.Current.ColorHighlight),
1399 selection_start.line.widths [selection_start.pos] +
1400 selection_start.line.X - viewport_x,
1401 selection_start.line.Y,
1402 (selection_end.line.X + selection_end.line.widths [selection_end.pos]) -
1403 (selection_start.line.X + selection_start.line.widths [selection_start.pos]),
1404 selection_start.line.height);
1407 while (line_no <= end) {
1408 line = GetLine (line_no);
1409 float line_y = line.Y - viewport_y;
1415 if (PasswordCache.Length < line.text.Length)
1416 PasswordCache.Append(Char.Parse(password_char), line.text.Length - PasswordCache.Length);
1417 else if (PasswordCache.Length > line.text.Length)
1418 PasswordCache.Remove(line.text.Length, PasswordCache.Length - line.text.Length);
1419 text = PasswordCache;
1422 int line_selection_start = text.Length + 1;
1423 int line_selection_end = text.Length + 1;
1424 if (selection_visible && owner.ShowSelection &&
1425 (line_no >= selection_start.line.line_no) &&
1426 (line_no <= selection_end.line.line_no)) {
1428 if (line_no == selection_start.line.line_no)
1429 line_selection_start = selection_start.pos + 1;
1431 line_selection_start = 1;
1433 if (line_no == selection_end.line.line_no)
1434 line_selection_end = selection_end.pos + 1;
1436 line_selection_end = text.Length + 1;
1438 if (line_selection_end == line_selection_start) {
1439 // There isn't really selection
1440 line_selection_start = text.Length + 1;
1441 line_selection_end = line_selection_start;
1442 } else if (multiline) {
1443 // lets draw some selection baby!! (non multiline selection is drawn outside the loop)
1444 g.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (ThemeEngine.Current.ColorHighlight),
1445 line.widths [line_selection_start - 1] + line.X - viewport_x,
1446 line_y, line.widths [line_selection_end - 1] - line.widths [line_selection_start - 1],
1451 current_color = line.tags.Color;
1452 while (tag != null) {
1455 if (tag.Length == 0) {
1460 if (((tag.X + tag.Width) < (clip.Left - viewport_x)) && (tag.X > (clip.Right - viewport_x))) {
1465 if (tag.BackColor != Color.Empty) {
1466 g.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (tag.BackColor), tag.X + line.X - viewport_x,
1467 line_y + tag.Shift, tag.Width, line.height);
1470 tag_color = tag.Color;
1471 current_color = tag_color;
1473 if (!owner.Enabled) {
1474 Color a = tag.Color;
1475 Color b = ThemeEngine.Current.ColorWindowText;
1477 if ((a.R == b.R) && (a.G == b.G) && (a.B == b.B)) {
1478 tag_color = ThemeEngine.Current.ColorGrayText;
1480 } else if (owner.read_only && !owner.backcolor_set) {
1481 tag_color = ThemeEngine.Current.ColorControlText;
1484 int tag_pos = tag.Start;
1485 current_color = tag_color;
1486 while (tag_pos < tag.Start + tag.Length) {
1487 int old_tag_pos = tag_pos;
1489 if (tag_pos >= line_selection_start && tag_pos < line_selection_end) {
1490 current_color = ThemeEngine.Current.ColorHighlightText;
1491 tag_pos = Math.Min (tag.End, line_selection_end);
1492 } else if (tag_pos < line_selection_start) {
1493 current_color = tag_color;
1494 tag_pos = Math.Min (tag.End, line_selection_start);
1496 current_color = tag_color;
1500 tag.Draw (g, current_color,
1501 line.X - viewport_x,
1503 old_tag_pos - 1, Math.Min (tag.Start + tag.Length, tag_pos) - 1,
1509 line.DrawEnding (g, line_y);
1514 private int GetLineEnding (string line, int start, out LineEnding ending)
1519 if (start >= line.Length) {
1520 ending = LineEnding.Wrap;
1524 res = line.IndexOf ('\r', start);
1525 rich_index = line.IndexOf ('\n', start);
1527 // Handle the case where we find both of them, and the \n is before the \r
1528 if (res != -1 && rich_index != -1)
1529 if (rich_index < res) {
1530 ending = LineEnding.Rich;
1535 if (res + 2 < line.Length && line [res + 1] == '\r' && line [res + 2] == '\n') {
1536 ending = LineEnding.Soft;
1539 if (res + 1 < line.Length && line [res + 1] == '\n') {
1540 ending = LineEnding.Hard;
1543 ending = LineEnding.Limp;
1547 if (rich_index != -1) {
1548 ending = LineEnding.Rich;
1552 ending = LineEnding.Wrap;
1556 // Get the line ending, but only of the types specified
1557 private int GetLineEnding (string line, int start, out LineEnding ending, LineEnding type)
1560 int last_length = 0;
1563 index = GetLineEnding (line, index + last_length, out ending);
1564 last_length = LineEndingLength (ending);
1566 ((ending & type) != ending && index != -1);
1568 return index == -1 ? line.Length : index;
1571 internal int LineEndingLength (LineEnding ending)
1574 case LineEnding.Limp:
1575 case LineEnding.Rich:
1577 case LineEnding.Hard:
1579 case LineEnding.Soft:
1586 internal string LineEndingToString (LineEnding ending)
1589 case LineEnding.Limp:
1591 case LineEnding.Hard:
1593 case LineEnding.Soft:
1595 case LineEnding.Rich:
1599 return string.Empty;
1602 // Insert text at the given position; use formatting at insertion point for inserted text
1603 internal void Insert (Line line, int pos, bool update_caret, string s)
1612 // Find the LineTag to add to
1613 LineTag tag = line.FindTag (pos);
1615 // Don't recalculate while we mess around
1618 base_line = line.line_no;
1619 old_line_count = lines;
1621 break_index = GetLineEnding (s, 0, out ending, LineEnding.Hard | LineEnding.Rich);
1623 // There are no line feeds in our text to be pasted
1624 if (break_index == s.Length) {
1625 line.InsertString (pos, s);
1627 // Add up to the first line feed to our current position
1628 line.InsertString (pos, s.Substring (0, break_index + LineEndingLength (ending)));
1630 // Split the rest of the original line to a new line
1631 Split (line, pos + (break_index + LineEndingLength (ending)));
1632 line.ending = ending;
1633 break_index += LineEndingLength (ending);
1634 split_line = GetLine (line.line_no + 1);
1636 // Insert brand new lines for any more line feeds in the inserted string
1638 int next_break = GetLineEnding (s, break_index, out ending, LineEnding.Hard | LineEnding.Rich);
1640 if (next_break == s.Length)
1643 string line_text = s.Substring (break_index, next_break - break_index +
1644 LineEndingLength (ending));
1646 Add (base_line + count, line_text, line.alignment, tag.Font, tag.Color, ending);
1648 Line last = GetLine (base_line + count);
1649 last.ending = ending;
1652 break_index = next_break + LineEndingLength (ending);
1655 // Add the remainder of the insert text to the split
1656 // part of the original line
1657 split_line.InsertString (0, s.Substring (break_index));
1660 // Allow the document to recalculate things
1661 ResumeRecalc (false);
1663 UpdateView (line, lines - old_line_count + 1, pos);
1665 // Move the caret to the end of the inserted text if requested
1667 Line l = GetLine (line.line_no + lines - old_line_count);
1668 PositionCaret (l, l.text.Length);
1673 // Inserts a string at the given position
1674 internal void InsertString (Line line, int pos, string s)
1676 // Update our character count
1677 CharCount += s.Length;
1679 // Insert the text into the Line
1680 line.InsertString (pos, s);
1683 // Inserts a character at the current caret position
1684 internal void InsertCharAtCaret (char ch, bool move_caret)
1686 caret.line.InsertString (caret.pos, ch.ToString ());
1688 undo.RecordTyping (caret.line, caret.pos, ch);
1690 UpdateView (caret.line, caret.pos);
1695 SetSelectionToCaret (true);
1699 internal void InsertPicture (Line line, int pos, RTF.Picture picture)
1707 // Just a place holder basically
1708 line.text.Insert (pos, "I");
1710 PictureTag picture_tag = new PictureTag (line, pos + 1, picture);
1712 tag = LineTag.FindTag (line, pos);
1713 picture_tag.CopyFormattingFrom (tag);
1714 /*next_tag = */tag.Break (pos + 1);
1715 picture_tag.Previous = tag;
1716 picture_tag.Next = tag.Next;
1717 tag.Next = picture_tag;
1720 // Picture tags need to be surrounded by text tags
1722 if (picture_tag.Next == null) {
1723 picture_tag.Next = new LineTag (line, pos + 1);
1724 picture_tag.Next.CopyFormattingFrom (tag);
1725 picture_tag.Next.Previous = picture_tag;
1728 tag = picture_tag.Next;
1729 while (tag != null) {
1737 UpdateView (line, pos);
1740 internal void DeleteMultiline (Line start_line, int pos, int length)
1742 Marker start = new Marker ();
1743 Marker end = new Marker ();
1744 int start_index = LineTagToCharIndex (start_line, pos);
1746 start.line = start_line;
1748 start.tag = LineTag.FindTag (start_line, pos);
1750 CharIndexToLineTag (start_index + length, out end.line,
1751 out end.tag, out end.pos);
1755 if (start.line == end.line) {
1756 DeleteChars (start.line, pos, end.pos - pos);
1759 // Delete first and last lines
1760 DeleteChars (start.line, start.pos, start.line.text.Length - start.pos);
1761 DeleteChars (end.line, 0, end.pos);
1763 int current = start.line.line_no + 1;
1764 if (current < end.line.line_no) {
1765 for (int i = end.line.line_no - 1; i >= current; i--) {
1770 // BIG FAT WARNING - selection_end.line might be stale due
1771 // to the above Delete() call. DONT USE IT before hitting the end of this method!
1773 // Join start and end
1774 Combine (start.line.line_no, current);
1777 ResumeUpdate (true);
1781 // Deletes n characters at the given position; it will not delete past line limits
1783 public void DeleteChars (Line line, int pos, int count)
1785 // Reduce our character count
1788 line.DeleteCharacters (pos, count);
1790 if (pos >= line.TextLengthWithoutEnding ()) {
1791 LineEnding ending = line.ending;
1792 GetLineEnding (line.text.ToString (), 0, out ending);
1794 if (ending != line.ending) {
1795 line.ending = ending;
1798 UpdateView (line, lines, pos);
1799 owner.Invalidate ();
1805 UpdateView (line, lines, pos);
1806 owner.Invalidate ();
1808 UpdateView (line, pos);
1811 // Deletes a character at or after the given position (depending on forward); it will not delete past line limits
1812 public void DeleteChar (Line line, int pos, bool forward)
1814 if ((pos == 0 && forward == false) || (pos == line.text.Length && forward == true))
1818 DeleteChars (line, pos, 1);
1820 DeleteChars (line, pos - 1, 1);
1823 // Combine two lines
1824 internal void Combine(int FirstLine, int SecondLine) {
1825 Combine(GetLine(FirstLine), GetLine(SecondLine));
1828 internal void Combine(Line first, Line second) {
1832 // strip the ending off of the first lines text
1833 first.text.Length = first.text.Length - LineEndingLength (first.ending);
1835 // Combine the two tag chains into one
1838 // Maintain the line ending style
1839 first.ending = second.ending;
1841 while (last.Next != null) {
1845 // need to get the shift before setting the next tag since that effects length
1846 shift = last.Start + last.Length - 1;
1847 last.Next = second.tags;
1848 last.Next.Previous = last;
1850 // Fix up references within the chain
1852 while (last != null) {
1854 last.Start += shift;
1858 // Combine both lines' strings
1859 first.text.Insert(first.text.Length, second.text.ToString());
1860 first.Grow(first.text.Length);
1862 // Remove the reference to our (now combined) tags from the doomed line
1866 DecrementLines(first.line_no + 2); // first.line_no + 1 will be deleted, so we need to start renumbering one later
1869 first.recalc = true;
1870 first.height = 0; // This forces RecalcDocument/UpdateView to redraw from this line on
1871 first.Streamline(lines);
1873 // Update Caret, Selection, etc
1874 if (caret.line == second) {
1875 caret.Combine(first, shift);
1877 if (selection_anchor.line == second) {
1878 selection_anchor.Combine(first, shift);
1880 if (selection_start.line == second) {
1881 selection_start.Combine(first, shift);
1883 if (selection_end.line == second) {
1884 selection_end.Combine(first, shift);
1891 check_first = GetLine(first.line_no);
1892 check_second = GetLine(check_first.line_no + 1);
1894 Console.WriteLine("Pre-delete: Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
1897 this.Delete(second);
1900 check_first = GetLine(first.line_no);
1901 check_second = GetLine(check_first.line_no + 1);
1903 Console.WriteLine("Post-delete Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
1907 // Split the line at the position into two
1908 internal void Split(int LineNo, int pos) {
1912 line = GetLine(LineNo);
1913 tag = LineTag.FindTag(line, pos);
1914 Split(line, tag, pos);
1917 internal void Split(Line line, int pos) {
1920 tag = LineTag.FindTag(line, pos);
1921 Split(line, tag, pos);
1924 ///<summary>Split line at given tag and position into two lines</summary>
1925 ///if more space becomes available on previous line</param>
1926 internal void Split(Line line, LineTag tag, int pos) {
1930 bool move_sel_start;
1934 move_sel_start = false;
1935 move_sel_end = false;
1937 // Adjust selection and cursors
1938 if (caret.line == line && caret.pos >= pos) {
1941 if (selection_start.line == line && selection_start.pos > pos) {
1942 move_sel_start = true;
1945 if (selection_end.line == line && selection_end.pos > pos) {
1946 move_sel_end = true;
1949 // cover the easy case first
1950 if (pos == line.text.Length) {
1951 Add (line.line_no + 1, String.Empty, line.alignment, tag.Font, tag.Color, line.ending);
1953 new_line = GetLine (line.line_no + 1);
1956 caret.line = new_line;
1957 caret.tag = new_line.tags;
1961 if (move_sel_start) {
1962 selection_start.line = new_line;
1963 selection_start.pos = 0;
1964 selection_start.tag = new_line.tags;
1968 selection_end.line = new_line;
1969 selection_end.pos = 0;
1970 selection_end.tag = new_line.tags;
1975 // We need to move the rest of the text into the new line
1976 Add (line.line_no + 1, line.text.ToString (pos, line.text.Length - pos), line.alignment, tag.Font, tag.Color, line.ending);
1978 // Now transfer our tags from this line to the next
1979 new_line = GetLine(line.line_no + 1);
1982 new_line.recalc = true;
1984 if ((tag.Start - 1) == pos) {
1987 // We can simply break the chain and move the tag into the next line
1988 if (tag == line.tags) {
1989 new_tag = new LineTag(line, 1);
1990 new_tag.CopyFormattingFrom (tag);
1991 line.tags = new_tag;
1994 if (tag.Previous != null) {
1995 tag.Previous.Next = null;
1997 new_line.tags = tag;
1998 tag.Previous = null;
1999 tag.Line = new_line;
2001 // Walk the list and correct the start location of the tags we just bumped into the next line
2002 shift = tag.Start - 1;
2005 while (new_tag != null) {
2006 new_tag.Start -= shift;
2007 new_tag.Line = new_line;
2008 new_tag = new_tag.Next;
2013 new_tag = new LineTag (new_line, 1);
2014 new_tag.Next = tag.Next;
2015 new_tag.CopyFormattingFrom (tag);
2016 new_line.tags = new_tag;
2017 if (new_tag.Next != null) {
2018 new_tag.Next.Previous = new_tag;
2023 new_tag = new_tag.Next;
2024 while (new_tag != null) {
2025 new_tag.Start -= shift;
2026 new_tag.Line = new_line;
2027 new_tag = new_tag.Next;
2033 caret.line = new_line;
2034 caret.pos = caret.pos - pos;
2035 caret.tag = caret.line.FindTag(caret.pos);
2038 if (move_sel_start) {
2039 selection_start.line = new_line;
2040 selection_start.pos = selection_start.pos - pos;
2041 selection_start.tag = new_line.FindTag(selection_start.pos);
2045 selection_end.line = new_line;
2046 selection_end.pos = selection_end.pos - pos;
2047 selection_end.tag = new_line.FindTag(selection_end.pos);
2050 CharCount -= line.text.Length - pos;
2051 line.text.Remove(pos, line.text.Length - pos);
2054 // Adds a line of text, with given font.
2055 // Bumps any line at that line number that already exists down
2056 internal void Add (int LineNo, string Text, Font font, Color color, LineEnding ending)
2058 Add (LineNo, Text, alignment, font, color, ending);
2061 internal void Add (int LineNo, string Text, HorizontalAlignment align, Font font, Color color, LineEnding ending)
2067 CharCount += Text.Length;
2069 if (LineNo<1 || Text == null) {
2071 throw new ArgumentNullException("LineNo", "Line numbers must be positive");
2073 throw new ArgumentNullException("Text", "Cannot insert NULL line");
2077 add = new Line (this, LineNo, Text, align, font, color, ending);
2080 while (line != sentinel) {
2082 line_no = line.line_no;
2084 if (LineNo > line_no) {
2086 } else if (LineNo < line_no) {
2089 // Bump existing line numbers; walk all nodes to the right of this one and increment line_no
2090 IncrementLines(line.line_no);
2095 add.left = sentinel;
2096 add.right = sentinel;
2098 if (add.parent != null) {
2099 if (LineNo > add.parent.line_no) {
2100 add.parent.right = add;
2102 add.parent.left = add;
2109 RebalanceAfterAdd(add);
2114 internal virtual void Clear() {
2117 document = sentinel;
2120 public virtual object Clone() {
2123 clone = new Document(null);
2125 clone.lines = this.lines;
2126 clone.document = (Line)document.Clone();
2131 private void Delete (int LineNo)
2138 line = GetLine (LineNo);
2140 CharCount -= line.text.Length;
2142 DecrementLines (LineNo + 1);
2146 private void Delete(Line line1) {
2147 Line line2;// = new Line();
2150 if ((line1.left == sentinel) || (line1.right == sentinel)) {
2153 line3 = line1.right;
2154 while (line3.left != sentinel) {
2159 if (line3.left != sentinel) {
2162 line2 = line3.right;
2165 line2.parent = line3.parent;
2166 if (line3.parent != null) {
2167 if(line3 == line3.parent.left) {
2168 line3.parent.left = line2;
2170 line3.parent.right = line2;
2176 if (line3 != line1) {
2179 if (selection_start.line == line3) {
2180 selection_start.line = line1;
2183 if (selection_end.line == line3) {
2184 selection_end.line = line1;
2187 if (selection_anchor.line == line3) {
2188 selection_anchor.line = line1;
2191 if (caret.line == line3) {
2196 line1.alignment = line3.alignment;
2197 line1.ascent = line3.ascent;
2198 line1.hanging_indent = line3.hanging_indent;
2199 line1.height = line3.height;
2200 line1.indent = line3.indent;
2201 line1.line_no = line3.line_no;
2202 line1.recalc = line3.recalc;
2203 line1.right_indent = line3.right_indent;
2204 line1.ending = line3.ending;
2205 line1.space = line3.space;
2206 line1.tags = line3.tags;
2207 line1.text = line3.text;
2208 line1.widths = line3.widths;
2209 line1.offset = line3.offset;
2212 while (tag != null) {
2218 if (line3.color == LineColor.Black)
2219 RebalanceAfterDelete(line2);
2224 // Invalidate a section of the document to trigger redraw
2225 internal void Invalidate(Line start, int start_pos, Line end, int end_pos) {
2231 if ((start == end) && (start_pos == end_pos)) {
2235 if (end_pos == -1) {
2236 end_pos = end.text.Length;
2239 // figure out what's before what so the logic below is straightforward
2240 if (start.line_no < end.line_no) {
2246 } else if (start.line_no > end.line_no) {
2253 if (start_pos < end_pos) {
2267 int endpoint = (int) l1.widths [p2];
2268 if (p2 == l1.text.Length + 1) {
2269 endpoint = (int) viewport_width;
2273 Console.WriteLine("Invaliding backwards from {0}:{1} to {2}:{3} {4}",
2274 l1.line_no, p1, l2.line_no, p2,
2276 (int)l1.widths[p1] + l1.X - viewport_x,
2284 owner.Invalidate(new Rectangle (
2285 (int)l1.widths[p1] + l1.X - viewport_x,
2287 endpoint - (int)l1.widths[p1] + 1,
2293 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);
2294 Console.WriteLine ("invalidate start line: {0} position: {1}", l1.text, p1);
2297 // Three invalidates:
2298 // First line from start
2299 owner.Invalidate(new Rectangle((int)l1.widths[p1] + l1.X - viewport_x, l1.Y - viewport_y, viewport_width, l1.height));
2303 if ((l1.line_no + 1) < l2.line_no) {
2306 y = GetLine(l1.line_no + 1).Y;
2307 owner.Invalidate(new Rectangle(0, y - viewport_y, viewport_width, l2.Y - y));
2310 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);
2316 owner.Invalidate(new Rectangle((int)l2.widths[0] + l2.X - viewport_x, l2.Y - viewport_y, (int)l2.widths[p2] + 1, l2.height));
2318 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);
2323 /// <summary>Select text around caret</summary>
2324 internal void ExpandSelection(CaretSelection mode, bool to_caret) {
2326 // We're expanding the selection to the caret position
2328 case CaretSelection.Line: {
2329 // Invalidate the selection delta
2330 if (caret > selection_prev) {
2331 Invalidate(selection_prev.line, 0, caret.line, caret.line.text.Length);
2333 Invalidate(selection_prev.line, selection_prev.line.text.Length, caret.line, 0);
2336 if (caret.line.line_no <= selection_anchor.line.line_no) {
2337 selection_start.line = caret.line;
2338 selection_start.tag = caret.line.tags;
2339 selection_start.pos = 0;
2341 selection_end.line = selection_anchor.line;
2342 selection_end.tag = selection_anchor.tag;
2343 selection_end.pos = selection_anchor.pos;
2345 selection_end_anchor = true;
2347 selection_start.line = selection_anchor.line;
2348 selection_start.pos = selection_anchor.height;
2349 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height);
2351 selection_end.line = caret.line;
2352 selection_end.tag = caret.line.tags;
2353 selection_end.pos = caret.line.text.Length;
2355 selection_end_anchor = false;
2357 selection_prev.line = caret.line;
2358 selection_prev.tag = caret.tag;
2359 selection_prev.pos = caret.pos;
2364 case CaretSelection.Word: {
2368 start_pos = FindWordSeparator(caret.line, caret.pos, false);
2369 end_pos = FindWordSeparator(caret.line, caret.pos, true);
2372 // Invalidate the selection delta
2373 if (caret > selection_prev) {
2374 Invalidate(selection_prev.line, selection_prev.pos, caret.line, end_pos);
2376 Invalidate(selection_prev.line, selection_prev.pos, caret.line, start_pos);
2378 if (caret < selection_anchor) {
2379 selection_start.line = caret.line;
2380 selection_start.tag = caret.line.FindTag(start_pos);
2381 selection_start.pos = start_pos;
2383 selection_end.line = selection_anchor.line;
2384 selection_end.tag = selection_anchor.tag;
2385 selection_end.pos = selection_anchor.pos;
2387 selection_prev.line = caret.line;
2388 selection_prev.tag = caret.tag;
2389 selection_prev.pos = start_pos;
2391 selection_end_anchor = true;
2393 selection_start.line = selection_anchor.line;
2394 selection_start.pos = selection_anchor.height;
2395 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height);
2397 selection_end.line = caret.line;
2398 selection_end.tag = caret.line.FindTag(end_pos);
2399 selection_end.pos = end_pos;
2401 selection_prev.line = caret.line;
2402 selection_prev.tag = caret.tag;
2403 selection_prev.pos = end_pos;
2405 selection_end_anchor = false;
2410 case CaretSelection.Position: {
2411 SetSelectionToCaret(false);
2416 // We're setting the selection 'around' the caret position
2418 case CaretSelection.Line: {
2419 this.Invalidate(caret.line, 0, caret.line, caret.line.text.Length);
2421 selection_start.line = caret.line;
2422 selection_start.tag = caret.line.tags;
2423 selection_start.pos = 0;
2425 selection_end.line = caret.line;
2426 selection_end.pos = caret.line.text.Length;
2427 selection_end.tag = caret.line.FindTag(selection_end.pos);
2429 selection_anchor.line = selection_end.line;
2430 selection_anchor.tag = selection_end.tag;
2431 selection_anchor.pos = selection_end.pos;
2432 selection_anchor.height = 0;
2434 selection_prev.line = caret.line;
2435 selection_prev.tag = caret.tag;
2436 selection_prev.pos = caret.pos;
2438 this.selection_end_anchor = true;
2443 case CaretSelection.Word: {
2447 start_pos = FindWordSeparator(caret.line, caret.pos, false);
2448 end_pos = FindWordSeparator(caret.line, caret.pos, true);
2450 this.Invalidate(selection_start.line, start_pos, caret.line, end_pos);
2452 selection_start.line = caret.line;
2453 selection_start.tag = caret.line.FindTag(start_pos);
2454 selection_start.pos = start_pos;
2456 selection_end.line = caret.line;
2457 selection_end.tag = caret.line.FindTag(end_pos);
2458 selection_end.pos = end_pos;
2460 selection_anchor.line = selection_end.line;
2461 selection_anchor.tag = selection_end.tag;
2462 selection_anchor.pos = selection_end.pos;
2463 selection_anchor.height = start_pos;
2465 selection_prev.line = caret.line;
2466 selection_prev.tag = caret.tag;
2467 selection_prev.pos = caret.pos;
2469 this.selection_end_anchor = true;
2476 SetSelectionVisible (!(selection_start == selection_end));
2479 internal void SetSelectionToCaret(bool start) {
2481 // Invalidate old selection; selection is being reset to empty
2482 this.Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2484 selection_start.line = caret.line;
2485 selection_start.tag = caret.tag;
2486 selection_start.pos = caret.pos;
2488 // start always also selects end
2489 selection_end.line = caret.line;
2490 selection_end.tag = caret.tag;
2491 selection_end.pos = caret.pos;
2493 selection_anchor.line = caret.line;
2494 selection_anchor.tag = caret.tag;
2495 selection_anchor.pos = caret.pos;
2497 // Invalidate from previous end to caret (aka new end)
2498 if (selection_end_anchor) {
2499 if (selection_start != caret) {
2500 this.Invalidate(selection_start.line, selection_start.pos, caret.line, caret.pos);
2503 if (selection_end != caret) {
2504 this.Invalidate(selection_end.line, selection_end.pos, caret.line, caret.pos);
2508 if (caret < selection_anchor) {
2509 selection_start.line = caret.line;
2510 selection_start.tag = caret.tag;
2511 selection_start.pos = caret.pos;
2513 selection_end.line = selection_anchor.line;
2514 selection_end.tag = selection_anchor.tag;
2515 selection_end.pos = selection_anchor.pos;
2517 selection_end_anchor = true;
2519 selection_start.line = selection_anchor.line;
2520 selection_start.tag = selection_anchor.tag;
2521 selection_start.pos = selection_anchor.pos;
2523 selection_end.line = caret.line;
2524 selection_end.tag = caret.tag;
2525 selection_end.pos = caret.pos;
2527 selection_end_anchor = false;
2531 SetSelectionVisible (!(selection_start == selection_end));
2534 internal void SetSelection(Line start, int start_pos, Line end, int end_pos) {
2535 if (selection_visible) {
2536 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2539 if ((end.line_no < start.line_no) || ((end == start) && (end_pos <= start_pos))) {
2540 selection_start.line = end;
2541 selection_start.tag = LineTag.FindTag(end, end_pos);
2542 selection_start.pos = end_pos;
2544 selection_end.line = start;
2545 selection_end.tag = LineTag.FindTag(start, start_pos);
2546 selection_end.pos = start_pos;
2548 selection_end_anchor = true;
2550 selection_start.line = start;
2551 selection_start.tag = LineTag.FindTag(start, start_pos);
2552 selection_start.pos = start_pos;
2554 selection_end.line = end;
2555 selection_end.tag = LineTag.FindTag(end, end_pos);
2556 selection_end.pos = end_pos;
2558 selection_end_anchor = false;
2561 selection_anchor.line = start;
2562 selection_anchor.tag = selection_start.tag;
2563 selection_anchor.pos = start_pos;
2565 if (((start == end) && (start_pos == end_pos)) || start == null || end == null) {
2566 SetSelectionVisible (false);
2568 SetSelectionVisible (true);
2569 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2573 internal void SetSelectionStart(Line start, int start_pos, bool invalidate) {
2574 // Invalidate from the previous to the new start pos
2576 Invalidate(selection_start.line, selection_start.pos, start, start_pos);
2578 selection_start.line = start;
2579 selection_start.pos = start_pos;
2580 selection_start.tag = LineTag.FindTag(start, start_pos);
2582 selection_anchor.line = start;
2583 selection_anchor.pos = start_pos;
2584 selection_anchor.tag = selection_start.tag;
2586 selection_end_anchor = false;
2589 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
2590 SetSelectionVisible (true);
2592 SetSelectionVisible (false);
2596 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2599 internal void SetSelectionStart(int character_index, bool invalidate) {
2604 if (character_index < 0) {
2608 CharIndexToLineTag(character_index, out line, out tag, out pos);
2609 SetSelectionStart(line, pos, invalidate);
2612 internal void SetSelectionEnd(Line end, int end_pos, bool invalidate) {
2614 if (end == selection_end.line && end_pos == selection_start.pos) {
2615 selection_anchor.line = selection_start.line;
2616 selection_anchor.tag = selection_start.tag;
2617 selection_anchor.pos = selection_start.pos;
2619 selection_end.line = selection_start.line;
2620 selection_end.tag = selection_start.tag;
2621 selection_end.pos = selection_start.pos;
2623 selection_end_anchor = false;
2624 } else if ((end.line_no < selection_anchor.line.line_no) || ((end == selection_anchor.line) && (end_pos <= selection_anchor.pos))) {
2625 selection_start.line = end;
2626 selection_start.tag = LineTag.FindTag(end, end_pos);
2627 selection_start.pos = end_pos;
2629 selection_end.line = selection_anchor.line;
2630 selection_end.tag = selection_anchor.tag;
2631 selection_end.pos = selection_anchor.pos;
2633 selection_end_anchor = true;
2635 selection_start.line = selection_anchor.line;
2636 selection_start.tag = selection_anchor.tag;
2637 selection_start.pos = selection_anchor.pos;
2639 selection_end.line = end;
2640 selection_end.tag = LineTag.FindTag(end, end_pos);
2641 selection_end.pos = end_pos;
2643 selection_end_anchor = false;
2646 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
2647 SetSelectionVisible (true);
2649 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2651 SetSelectionVisible (false);
2652 // ?? Do I need to invalidate here, tests seem to work without it, but I don't think they should :-s
2656 internal void SetSelectionEnd(int character_index, bool invalidate) {
2661 if (character_index < 0) {
2665 CharIndexToLineTag(character_index, out line, out tag, out pos);
2666 SetSelectionEnd(line, pos, invalidate);
2669 internal void SetSelection(Line start, int start_pos) {
2670 if (selection_visible) {
2671 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2674 selection_start.line = start;
2675 selection_start.pos = start_pos;
2676 selection_start.tag = LineTag.FindTag(start, start_pos);
2678 selection_end.line = start;
2679 selection_end.tag = selection_start.tag;
2680 selection_end.pos = start_pos;
2682 selection_anchor.line = start;
2683 selection_anchor.tag = selection_start.tag;
2684 selection_anchor.pos = start_pos;
2686 selection_end_anchor = false;
2687 SetSelectionVisible (false);
2690 internal void InvalidateSelectionArea() {
2691 Invalidate (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2694 // Return the current selection, as string
2695 internal string GetSelection() {
2696 // We return String.Empty if there is no selection
2697 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
2698 return string.Empty;
2701 if (selection_start.line == selection_end.line) {
2702 return selection_start.line.text.ToString (selection_start.pos, selection_end.pos - selection_start.pos);
2709 sb = new StringBuilder();
2710 start = selection_start.line.line_no;
2711 end = selection_end.line.line_no;
2713 sb.Append(selection_start.line.text.ToString(selection_start.pos, selection_start.line.text.Length - selection_start.pos) + Environment.NewLine);
2715 if ((start + 1) < end) {
2716 for (i = start + 1; i < end; i++) {
2717 sb.Append(GetLine(i).text.ToString() + Environment.NewLine);
2721 sb.Append(selection_end.line.text.ToString(0, selection_end.pos));
2723 return sb.ToString();
2727 internal void ReplaceSelection(string s, bool select_new) {
2730 int selection_pos_on_line = selection_start.pos;
2731 int selection_start_pos = LineTagToCharIndex (selection_start.line, selection_start.pos);
2734 // First, delete any selected text
2735 if ((selection_start.pos != selection_end.pos) || (selection_start.line != selection_end.line)) {
2736 if (selection_start.line == selection_end.line) {
2737 undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2739 DeleteChars (selection_start.line, selection_start.pos, selection_end.pos - selection_start.pos);
2741 // The tag might have been removed, we need to recalc it
2742 selection_start.tag = selection_start.line.FindTag(selection_start.pos);
2747 start = selection_start.line.line_no;
2748 end = selection_end.line.line_no;
2750 undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2752 InvalidateSelectionArea ();
2754 // Delete first line
2755 DeleteChars (selection_start.line, selection_start.pos, selection_start.line.text.Length - selection_start.pos);
2756 selection_start.line.recalc = true;
2759 DeleteChars(selection_end.line, 0, selection_end.pos);
2763 for (i = end - 1; i >= start; i--) {
2768 // BIG FAT WARNING - selection_end.line might be stale due
2769 // to the above Delete() call. DONT USE IT before hitting the end of this method!
2771 // Join start and end
2772 Combine(selection_start.line.line_no, start);
2777 Insert(selection_start.line, selection_start.pos, false, s);
2778 undo.RecordInsertString (selection_start.line, selection_start.pos, s);
2779 ResumeRecalc (false);
2781 Line begin_update_line = selection_start.line;
2782 int begin_update_pos = selection_start.pos;
2785 CharIndexToLineTag(selection_start_pos + s.Length, out selection_start.line,
2786 out selection_start.tag, out selection_start.pos);
2788 selection_end.line = selection_start.line;
2789 selection_end.pos = selection_start.pos;
2790 selection_end.tag = selection_start.tag;
2791 selection_anchor.line = selection_start.line;
2792 selection_anchor.pos = selection_start.pos;
2793 selection_anchor.tag = selection_start.tag;
2795 SetSelectionVisible (false);
2797 CharIndexToLineTag(selection_start_pos, out selection_start.line,
2798 out selection_start.tag, out selection_start.pos);
2800 CharIndexToLineTag(selection_start_pos + s.Length, out selection_end.line,
2801 out selection_end.tag, out selection_end.pos);
2803 selection_anchor.line = selection_start.line;
2804 selection_anchor.pos = selection_start.pos;
2805 selection_anchor.tag = selection_start.tag;
2807 SetSelectionVisible (true);
2810 PositionCaret (selection_start.line, selection_start.pos);
2811 UpdateView (begin_update_line, selection_end.line.line_no - begin_update_line.line_no, begin_update_pos);
2814 internal void CharIndexToLineTag(int index, out Line line_out, out LineTag tag_out, out int pos) {
2823 for (i = 1; i <= lines; i++) {
2827 chars += line.text.Length;
2829 if (index <= chars) {
2830 // we found the line
2833 while (tag != null) {
2834 if (index < (start + tag.Start + tag.Length - 1)) {
2836 tag_out = LineTag.GetFinalTag (tag);
2837 pos = index - start;
2840 if (tag.Next == null) {
2843 next_line = GetLine(line.line_no + 1);
2845 if (next_line != null) {
2846 line_out = next_line;
2847 tag_out = LineTag.GetFinalTag (next_line.tags);
2852 tag_out = LineTag.GetFinalTag (tag);
2853 pos = line_out.text.Length;
2862 line_out = GetLine(lines);
2863 tag = line_out.tags;
2864 while (tag.Next != null) {
2868 pos = line_out.text.Length;
2871 internal int LineTagToCharIndex(Line line, int pos) {
2875 // Count first and last line
2878 // Count the lines in the middle
2880 for (i = 1; i < line.line_no; i++) {
2881 length += GetLine(i).text.Length;
2889 internal int SelectionLength() {
2890 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
2894 if (selection_start.line == selection_end.line) {
2895 return selection_end.pos - selection_start.pos;
2902 // Count first and last line
2903 length = selection_start.line.text.Length - selection_start.pos + selection_end.pos + crlf_size;
2905 // Count the lines in the middle
2906 start = selection_start.line.line_no + 1;
2907 end = selection_end.line.line_no;
2910 for (i = start; i < end; i++) {
2911 Line line = GetLine (i);
2912 length += line.text.Length + LineEndingLength (line.ending);
2923 /// <summary>Give it a Line number and it returns the Line object at with that line number</summary>
2924 internal Line GetLine(int LineNo) {
2925 Line line = document;
2927 while (line != sentinel) {
2928 if (LineNo == line.line_no) {
2930 } else if (LineNo < line.line_no) {
2940 /// <summary>Retrieve the previous tag; walks line boundaries</summary>
2941 internal LineTag PreviousTag(LineTag tag) {
2944 if (tag.Previous != null) {
2945 return tag.Previous;
2949 if (tag.Line.line_no == 1) {
2953 l = GetLine(tag.Line.line_no - 1);
2958 while (t.Next != null) {
2967 /// <summary>Retrieve the next tag; walks line boundaries</summary>
2968 internal LineTag NextTag(LineTag tag) {
2971 if (tag.Next != null) {
2976 l = GetLine(tag.Line.line_no + 1);
2984 internal Line ParagraphStart(Line line) {
2985 while (line.ending == LineEnding.Wrap) {
2986 line = GetLine(line.line_no - 1);
2991 internal Line ParagraphEnd(Line line) {
2994 while (line.ending == LineEnding.Wrap) {
2995 l = GetLine(line.line_no + 1);
2996 if ((l == null) || (l.ending != LineEnding.Wrap)) {
3004 /// <summary>Give it a pixel offset coordinate and it returns the Line covering that are (offset
3005 /// is either X or Y depending on if we are multiline
3007 internal Line GetLineByPixel (int offset, bool exact)
3009 Line line = document;
3013 while (line != sentinel) {
3015 if ((offset >= line.Y) && (offset < (line.Y+line.height))) {
3017 } else if (offset < line.Y) {
3024 while (line != sentinel) {
3026 if ((offset >= line.X) && (offset < (line.X + line.Width)))
3028 else if (offset < line.X)
3041 // Give it x/y pixel coordinates and it returns the Tag at that position
3042 internal LineTag FindCursor (int x, int y, out int index)
3046 line = GetLineByPixel (multiline ? y : x, false);
3048 LineTag tag = line.GetTag (x);
3050 if (tag.Length == 0)
3053 index = tag.GetCharIndex (x);
3058 /// <summary>Format area of document in specified font and color</summary>
3059 /// <param name="start_pos">1-based start position on start_line</param>
3060 /// <param name="end_pos">1-based end position on end_line </param>
3061 internal void FormatText (Line start_line, int start_pos, Line end_line, int end_pos, Font font,
3062 Color color, Color back_color, FormatSpecified specified)
3066 // First, format the first line
3067 if (start_line != end_line) {
3069 LineTag.FormatText(start_line, start_pos, start_line.text.Length - start_pos + 1, font, color, back_color, specified);
3072 LineTag.FormatText(end_line, 1, end_pos, font, color, back_color, specified);
3074 // Now all the lines inbetween
3075 for (int i = start_line.line_no + 1; i < end_line.line_no; i++) {
3077 LineTag.FormatText(l, 1, l.text.Length, font, color, back_color, specified);
3080 // Special case, single line
3081 LineTag.FormatText(start_line, start_pos, end_pos - start_pos, font, color, back_color, specified);
3085 internal void RecalculateAlignments ()
3094 while (line_no <= lines) {
3095 line = GetLine(line_no);
3098 switch (line.alignment) {
3099 case HorizontalAlignment.Left:
3100 line.align_shift = 0;
3102 case HorizontalAlignment.Center:
3103 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3105 case HorizontalAlignment.Right:
3106 line.align_shift = viewport_width - (int)line.widths[line.text.Length] - right_margin;
3116 /// <summary>Calculate formatting for the whole document</summary>
3117 internal bool RecalculateDocument(Graphics g) {
3118 return RecalculateDocument(g, 1, this.lines, false);
3121 /// <summary>Calculate formatting starting at a certain line</summary>
3122 internal bool RecalculateDocument(Graphics g, int start) {
3123 return RecalculateDocument(g, start, this.lines, false);
3126 /// <summary>Calculate formatting within two given line numbers</summary>
3127 internal bool RecalculateDocument(Graphics g, int start, int end) {
3128 return RecalculateDocument(g, start, end, false);
3131 /// <summary>With optimize on, returns true if line heights changed</summary>
3132 internal bool RecalculateDocument(Graphics g, int start, int end, bool optimize) {
3140 if (recalc_suspended > 0) {
3141 recalc_pending = true;
3142 recalc_start = Math.Min (recalc_start, start);
3143 recalc_end = Math.Max (recalc_end, end);
3144 recalc_optimize = optimize;
3148 // Fixup the positions, they can go kinda nuts
3149 start = Math.Max (start, 1);
3150 end = Math.Min (end, lines);
3152 offset = GetLine(start).offset;
3157 changed = true; // We always return true if we run non-optimized
3162 while (line_no <= (end + this.lines - shift)) {
3163 line = GetLine(line_no++);
3164 line.offset = offset;
3168 line.RecalculateLine(g, this);
3170 if (line.recalc && line.RecalculateLine(g, this)) {
3172 // If the height changed, all subsequent lines change
3179 line.RecalculatePasswordLine(g, this);
3181 if (line.recalc && line.RecalculatePasswordLine(g, this)) {
3183 // If the height changed, all subsequent lines change
3190 if (line.widths[line.text.Length] > new_width) {
3191 new_width = (int)line.widths[line.text.Length];
3194 // Calculate alignment
3195 if (line.alignment != HorizontalAlignment.Left) {
3196 if (line.alignment == HorizontalAlignment.Center) {
3197 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3199 line.align_shift = viewport_width - (int)line.widths[line.text.Length] - 1;
3204 offset += line.height;
3206 offset += (int) line.widths [line.text.Length];
3208 if (line_no > lines) {
3213 if (document_x != new_width) {
3214 document_x = new_width;
3215 if (WidthChanged != null) {
3216 WidthChanged(this, null);
3220 RecalculateAlignments();
3222 line = GetLine(lines);
3224 if (document_y != line.Y + line.height) {
3225 document_y = line.Y + line.height;
3226 if (HeightChanged != null) {
3227 HeightChanged(this, null);
3234 internal int Size() {
3238 private void owner_HandleCreated(object sender, EventArgs e) {
3239 RecalculateDocument(owner.CreateGraphicsInternal());
3243 private void owner_VisibleChanged(object sender, EventArgs e) {
3244 if (owner.Visible) {
3245 RecalculateDocument(owner.CreateGraphicsInternal());
3249 internal static bool IsWordSeparator (char ch)
3264 internal int FindWordSeparator(Line line, int pos, bool forward) {
3267 len = line.text.Length;
3270 for (int i = pos + 1; i < len; i++) {
3271 if (IsWordSeparator(line.Text[i])) {
3277 for (int i = pos - 1; i > 0; i--) {
3278 if (IsWordSeparator(line.Text[i - 1])) {
3286 /* Search document for text */
3287 internal bool FindChars(char[] chars, Marker start, Marker end, out Marker result) {
3293 // Search for occurence of any char in the chars array
3294 result = new Marker();
3297 line_no = start.line.line_no;
3299 while (line_no <= end.line.line_no) {
3300 line_len = line.text.Length;
3301 while (pos < line_len) {
3302 for (int i = 0; i < chars.Length; i++) {
3303 if (line.text[pos] == chars[i]) {
3305 if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
3319 line = GetLine(line_no);
3325 // This version does not build one big string for searching, instead it handles
3326 // line-boundaries, which is faster and less memory intensive
3327 // FIXME - Depending on culture stuff we might have to create a big string and use culturespecific
3328 // search stuff and change it to accept and return positions instead of Markers (which would match
3329 // RichTextBox behaviour better but would be inconsistent with the rest of TextControl)
3330 internal bool Find(string search, Marker start, Marker end, out Marker result, RichTextBoxFinds options) {
3332 string search_string;
3344 result = new Marker();
3345 word_option = ((options & RichTextBoxFinds.WholeWord) != 0);
3346 ignore_case = ((options & RichTextBoxFinds.MatchCase) == 0);
3347 reverse = ((options & RichTextBoxFinds.Reverse) != 0);
3350 line_no = start.line.line_no;
3354 // Prep our search string, lowercasing it if we do case-independent matching
3357 sb = new StringBuilder(search);
3358 for (int i = 0; i < sb.Length; i++) {
3359 sb[i] = Char.ToLower(sb[i]);
3361 search_string = sb.ToString();
3363 search_string = search;
3366 // We need to check if the character before our start position is a wordbreak
3369 if ((pos == 0) || (IsWordSeparator(line.text[pos - 1]))) {
3376 if (IsWordSeparator(line.text[pos - 1])) {
3382 // Need to check the end of the previous line
3385 prev_line = GetLine(line_no - 1);
3386 if (prev_line.ending == LineEnding.Wrap) {
3387 if (IsWordSeparator(prev_line.text[prev_line.text.Length - 1])) {
3401 // To avoid duplication of this loop with reverse logic, we search
3402 // through the document, remembering the last match and when returning
3403 // report that last remembered match
3405 last = new Marker();
3406 last.height = -1; // Abused - we use it to track change
3408 while (line_no <= end.line.line_no) {
3409 if (line_no != end.line.line_no) {
3410 line_len = line.text.Length;
3415 while (pos < line_len) {
3417 if (word_option && (current == search_string.Length)) {
3418 if (IsWordSeparator(line.text[pos])) {
3431 c = Char.ToLower(line.text[pos]);
3436 if (c == search_string[current]) {
3442 if (!word_option || (word_option && (word || (current > 0)))) {
3446 if (!word_option && (current == search_string.Length)) {
3463 if (IsWordSeparator(c)) {
3471 // Mark that we just saw a word boundary
3472 if (line.ending != LineEnding.Wrap || line.line_no == lines - 1) {
3476 if (current == search_string.Length) {
3492 line = GetLine(line_no);
3496 if (last.height != -1) {
3506 // if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
3518 internal void GetMarker(out Marker mark, bool start) {
3519 mark = new Marker();
3522 mark.line = GetLine(1);
3523 mark.tag = mark.line.tags;
3526 mark.line = GetLine(lines);
3527 mark.tag = mark.line.tags;
3528 while (mark.tag.Next != null) {
3529 mark.tag = mark.tag.Next;
3531 mark.pos = mark.line.text.Length;
3534 #endregion // Internal Methods
3537 internal event EventHandler CaretMoved;
3538 internal event EventHandler WidthChanged;
3539 internal event EventHandler HeightChanged;
3540 internal event EventHandler LengthChanged;
3541 #endregion // Events
3543 #region Administrative
3544 public IEnumerator GetEnumerator() {
3549 public override bool Equals(object obj) {
3554 if (!(obj is Document)) {
3562 if (ToString().Equals(((Document)obj).ToString())) {
3569 public override int GetHashCode() {
3573 public override string ToString() {
3574 return "document " + this.document_id;
3576 #endregion // Administrative
3579 internal class PictureTag : LineTag {
3581 internal RTF.Picture picture;
3583 internal PictureTag (Line line, int start, RTF.Picture picture) : base (line, start)
3585 this.picture = picture;
3588 public override bool IsTextTag {
3589 get { return false; }
3592 public override SizeF SizeOfPosition (Graphics dc, int pos)
3594 return picture.Size;
3597 internal override int MaxHeight ()
3599 return (int) (picture.Height + 0.5F);
3602 public override void Draw (Graphics dc, Color color, float xoff, float y, int start, int end)
3604 picture.DrawImage (dc, xoff + Line.widths [start], y, false);
3607 public override void Draw (Graphics dc, Color color, float xoff, float y, int start, int end, string text)
3609 picture.DrawImage (dc, xoff + + Line.widths [start], y, false);
3612 public override string Text ()
3618 internal class UndoManager {
3620 internal enum ActionType {
3624 // This is basically just cut & paste
3632 internal class Action {
3633 internal ActionType type;
3634 internal int line_no;
3636 internal object data;
3639 #region Local Variables
3640 private Document document;
3641 private Stack undo_actions;
3642 private Stack redo_actions;
3644 //private int caret_line;
3645 //private int caret_pos;
3647 // When performing an action, we lock the queue, so that the action can't be undone
3648 private bool locked;
3649 #endregion // Local Variables
3651 #region Constructors
3652 internal UndoManager (Document document)
3654 this.document = document;
3655 undo_actions = new Stack (50);
3656 redo_actions = new Stack (50);
3658 #endregion // Constructors
3661 internal bool CanUndo {
3662 get { return undo_actions.Count > 0; }
3665 internal bool CanRedo {
3666 get { return redo_actions.Count > 0; }
3669 internal string UndoActionName {
3671 foreach (Action action in undo_actions) {
3672 if (action.type == ActionType.UserActionBegin)
3673 return (string) action.data;
3674 if (action.type == ActionType.Typing)
3675 return Locale.GetText ("Typing");
3677 return String.Empty;
3681 internal string RedoActionName {
3683 foreach (Action action in redo_actions) {
3684 if (action.type == ActionType.UserActionBegin)
3685 return (string) action.data;
3686 if (action.type == ActionType.Typing)
3687 return Locale.GetText ("Typing");
3689 return String.Empty;
3692 #endregion // Properties
3694 #region Internal Methods
3695 internal void Clear ()
3697 undo_actions.Clear();
3698 redo_actions.Clear();
3701 internal void Undo ()
3704 bool user_action_finished = false;
3706 if (undo_actions.Count == 0)
3709 // Nuke the redo queue
3710 redo_actions.Clear ();
3715 action = (Action) undo_actions.Pop ();
3717 // Put onto redo stack
3718 redo_actions.Push(action);
3721 switch(action.type) {
3723 case ActionType.UserActionBegin:
3724 user_action_finished = true;
3727 case ActionType.UserActionEnd:
3731 case ActionType.InsertString:
3732 start = document.GetLine (action.line_no);
3733 document.SuspendUpdate ();
3734 document.DeleteMultiline (start, action.pos, ((string) action.data).Length + 1);
3735 document.PositionCaret (start, action.pos);
3736 document.SetSelectionToCaret (true);
3737 document.ResumeUpdate (true);
3740 case ActionType.Typing:
3741 start = document.GetLine (action.line_no);
3742 document.SuspendUpdate ();
3743 document.DeleteMultiline (start, action.pos, ((StringBuilder) action.data).Length);
3744 document.PositionCaret (start, action.pos);
3745 document.SetSelectionToCaret (true);
3746 document.ResumeUpdate (true);
3748 // This is an open ended operation, so only a single typing operation can be undone at once
3749 user_action_finished = true;
3752 case ActionType.DeleteString:
3753 start = document.GetLine (action.line_no);
3754 document.SuspendUpdate ();
3755 Insert (start, action.pos, (Line) action.data, true);
3756 document.ResumeUpdate (true);
3759 } while (!user_action_finished && undo_actions.Count > 0);
3764 internal void Redo ()
3767 bool user_action_finished = false;
3769 if (redo_actions.Count == 0)
3772 // You can't undo anything after redoing
3773 undo_actions.Clear ();
3780 action = (Action) redo_actions.Pop ();
3782 switch (action.type) {
3784 case ActionType.UserActionBegin:
3788 case ActionType.UserActionEnd:
3789 user_action_finished = true;
3792 case ActionType.InsertString:
3793 start = document.GetLine (action.line_no);
3794 document.SuspendUpdate ();
3795 start_index = document.LineTagToCharIndex (start, action.pos);
3796 document.InsertString (start, action.pos, (string) action.data);
3797 document.CharIndexToLineTag (start_index + ((string) action.data).Length,
3798 out document.caret.line, out document.caret.tag,
3799 out document.caret.pos);
3800 document.UpdateCaret ();
3801 document.SetSelectionToCaret (true);
3802 document.ResumeUpdate (true);
3805 case ActionType.Typing:
3806 start = document.GetLine (action.line_no);
3807 document.SuspendUpdate ();
3808 start_index = document.LineTagToCharIndex (start, action.pos);
3809 document.InsertString (start, action.pos, ((StringBuilder) action.data).ToString ());
3810 document.CharIndexToLineTag (start_index + ((StringBuilder) action.data).Length,
3811 out document.caret.line, out document.caret.tag,
3812 out document.caret.pos);
3813 document.UpdateCaret ();
3814 document.SetSelectionToCaret (true);
3815 document.ResumeUpdate (true);
3817 // This is an open ended operation, so only a single typing operation can be undone at once
3818 user_action_finished = true;
3821 case ActionType.DeleteString:
3822 start = document.GetLine (action.line_no);
3823 document.SuspendUpdate ();
3824 document.DeleteMultiline (start, action.pos, ((Line) action.data).text.Length);
3825 document.PositionCaret (start, action.pos);
3826 document.SetSelectionToCaret (true);
3827 document.ResumeUpdate (true);
3831 } while (!user_action_finished && redo_actions.Count > 0);
3835 #endregion // Internal Methods
3837 #region Private Methods
3839 public void BeginUserAction (string name)
3844 Action ua = new Action ();
3845 ua.type = ActionType.UserActionBegin;
3848 undo_actions.Push (ua);
3851 public void EndUserAction ()
3856 Action ua = new Action ();
3857 ua.type = ActionType.UserActionEnd;
3859 undo_actions.Push (ua);
3862 // start_pos, end_pos = 1 based
3863 public void RecordDeleteString (Line start_line, int start_pos, Line end_line, int end_pos)
3868 Action a = new Action ();
3870 // We cant simply store the string, because then formatting would be lost
3871 a.type = ActionType.DeleteString;
3872 a.line_no = start_line.line_no;
3874 a.data = Duplicate (start_line, start_pos, end_line, end_pos);
3876 undo_actions.Push(a);
3879 public void RecordInsertString (Line line, int pos, string str)
3881 if (locked || str.Length == 0)
3884 Action a = new Action ();
3886 a.type = ActionType.InsertString;
3888 a.line_no = line.line_no;
3891 undo_actions.Push (a);
3894 public void RecordTyping (Line line, int pos, char ch)
3901 if (undo_actions.Count > 0)
3902 a = (Action) undo_actions.Peek ();
3904 if (a == null || a.type != ActionType.Typing) {
3906 a.type = ActionType.Typing;
3907 a.data = new StringBuilder ();
3908 a.line_no = line.line_no;
3911 undo_actions.Push (a);
3914 StringBuilder data = (StringBuilder) a.data;
3918 // start_pos = 1-based
3919 // end_pos = 1-based
3920 public Line Duplicate(Line start_line, int start_pos, Line end_line, int end_pos)
3926 LineTag current_tag;
3931 line = new Line (start_line.document, start_line.ending);
3934 for (int i = start_line.line_no; i <= end_line.line_no; i++) {
3935 current = document.GetLine(i);
3937 if (start_line.line_no == i) {
3943 if (end_line.line_no == i) {
3946 end = current.text.Length;
3953 line.text = new StringBuilder (current.text.ToString (start, end - start));
3955 // Copy tags from start to start+length onto new line
3956 current_tag = current.FindTag (start);
3957 while ((current_tag != null) && (current_tag.Start <= end)) {
3958 if ((current_tag.Start <= start) && (start < (current_tag.Start + current_tag.Length))) {
3959 // start tag is within this tag
3962 tag_start = current_tag.Start;
3965 tag = new LineTag(line, tag_start - start + 1);
3966 tag.CopyFormattingFrom (current_tag);
3968 current_tag = current_tag.Next;
3970 // Add the new tag to the line
3971 if (line.tags == null) {
3977 while (tail.Next != null) {
3981 tag.Previous = tail;
3985 if ((i + 1) <= end_line.line_no) {
3986 line.ending = current.ending;
3988 // Chain them (we use right/left as next/previous)
3989 line.right = new Line (start_line.document, start_line.ending);
3990 line.right.left = line;
3998 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
3999 internal void Insert(Line line, int pos, Line insert, bool select)
4007 // Handle special case first
4008 if (insert.right == null) {
4010 // Single line insert
4011 document.Split(line, pos);
4013 if (insert.tags == null) {
4014 return; // Blank line
4017 //Insert our tags at the end
4020 while (tag.Next != null) {
4024 offset = tag.Start + tag.Length - 1;
4026 tag.Next = insert.tags;
4027 line.text.Insert(offset, insert.text.ToString());
4029 // Adjust start locations
4031 while (tag != null) {
4032 tag.Start += offset;
4036 // Put it back together
4037 document.Combine(line.line_no, line.line_no + 1);
4040 document.SetSelectionStart (line, pos, false);
4041 document.SetSelectionEnd (line, pos + insert.text.Length, false);
4044 document.UpdateView(line, pos);
4052 while (current != null) {
4054 if (current == insert) {
4055 // Inserting the first line we split the line (and make space)
4056 document.Split(line.line_no, pos);
4057 //Insert our tags at the end of the line
4061 if (tag != null && tag.Length != 0) {
4062 while (tag.Next != null) {
4065 offset = tag.Start + tag.Length - 1;
4066 tag.Next = current.tags;
4067 tag.Next.Previous = tag;
4073 line.tags = current.tags;
4074 line.tags.Previous = null;
4078 line.ending = current.ending;
4080 document.Split(line.line_no, 0);
4082 line.tags = current.tags;
4083 line.tags.Previous = null;
4084 line.ending = current.ending;
4088 // Adjust start locations and line pointers
4089 while (tag != null) {
4090 tag.Start += offset - 1;
4095 line.text.Insert(offset, current.text.ToString());
4096 line.Grow(line.text.Length);
4099 line = document.GetLine(line.line_no + 1);
4101 // FIXME? Test undo of line-boundaries
4102 if ((current.right == null) && (current.tags.Length != 0)) {
4103 document.Combine(line.line_no - 1, line.line_no);
4105 current = current.right;
4110 // Recalculate our document
4111 document.UpdateView(first, lines, pos);
4114 #endregion // Private Methods