* TextControl.cs: Push the cursor record onto the undo stack
[mono-project.git] / mcs / class / Managed.Windows.Forms / System.Windows.Forms / TextControl.cs
blob51539584daa1beaf22c5b2c474cadb97c04342f2
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:
8 //
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
11 //
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)
22 // Authors:
23 // Peter Bartok pbartok@novell.com
27 // NOT COMPLETE
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, images, etc.
34 // - Implement CaretPgUp/PgDown
36 // NOTE:
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
46 #undef Debug
48 using System;
49 using System.Collections;
50 using System.Drawing;
51 using System.Drawing.Text;
52 using System.Text;
54 namespace System.Windows.Forms {
55 internal enum LineColor {
56 Red = 0,
57 Black = 1
60 internal enum CaretSelection {
61 Position, // Selection=Caret
62 Word, // Selection=Word under caret
63 Line // Selection=Line under caret
66 internal class FontDefinition {
67 internal String face;
68 internal int size;
69 internal FontStyle add_style;
70 internal FontStyle remove_style;
71 internal Color color;
72 internal Font font_obj;
74 internal FontDefinition() {
75 face = null;
76 size = 0;
77 color = Color.Empty;
81 internal enum CaretDirection {
82 CharForward, // Move a char to the right
83 CharBack, // Move a char to the left
84 LineUp, // Move a line up
85 LineDown, // Move a line down
86 Home, // Move to the beginning of the line
87 End, // Move to the end of the line
88 PgUp, // Move one page up
89 PgDn, // Move one page down
90 CtrlPgUp, // Move caret to the first visible char in the viewport
91 CtrlPgDn, // Move caret to the last visible char in the viewport
92 CtrlHome, // Move to the beginning of the document
93 CtrlEnd, // Move to the end of the document
94 WordBack, // Move to the beginning of the previous word (or beginning of line)
95 WordForward, // Move to the beginning of the next word (or end of line)
96 SelectionStart, // Move to the beginning of the current selection
97 SelectionEnd // Move to the end of the current selection
100 // Being cloneable should allow for nice line and document copies...
101 internal class Line : ICloneable, IComparable {
102 #region Local Variables
103 // Stuff that matters for our line
104 internal StringBuilder text; // Characters for the line
105 internal float[] widths; // Width of each character; always one larger than text.Length
106 internal int space; // Number of elements in text and widths
107 internal int line_no; // Line number
108 internal LineTag tags; // Tags describing the text
109 internal int Y; // Baseline
110 internal int height; // Height of the line (height of tallest tag)
111 internal int ascent; // Ascent of the line (ascent of the tallest tag)
112 internal HorizontalAlignment alignment; // Alignment of the line
113 internal int align_shift; // Pixel shift caused by the alignment
114 internal bool soft_break; // Tag is 'broken soft' and continuation from previous line
115 internal int indent; // Left indent for the first line
116 internal int hanging_indent; // Hanging indent (left indent for all but the first line)
117 internal int right_indent; // Right indent for all lines
120 // Stuff that's important for the tree
121 internal Line parent; // Our parent line
122 internal Line left; // Line with smaller line number
123 internal Line right; // Line with higher line number
124 internal LineColor color; // We're doing a black/red tree. this is the node color
125 internal int DEFAULT_TEXT_LEN; //
126 internal static StringFormat string_format; // For calculating widths/heights
127 internal bool recalc; // Line changed
128 #endregion // Local Variables
130 #region Constructors
131 internal Line() {
132 color = LineColor.Red;
133 left = null;
134 right = null;
135 parent = null;
136 text = null;
137 recalc = true;
138 soft_break = false;
139 alignment = HorizontalAlignment.Left;
141 if (string_format == null) {
142 string_format = new StringFormat(StringFormat.GenericTypographic);
143 string_format.Trimming = StringTrimming.None;
144 string_format.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;
148 internal Line(int LineNo, string Text, Font font, Brush color) : this() {
149 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
151 text = new StringBuilder(Text, space);
152 line_no = LineNo;
154 widths = new float[space + 1];
155 tags = new LineTag(this, 1, text.Length);
156 tags.font = font;
157 tags.color = color;
160 internal Line(int LineNo, string Text, HorizontalAlignment align, Font font, Brush color) : this() {
161 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
163 text = new StringBuilder(Text, space);
164 line_no = LineNo;
165 alignment = align;
167 widths = new float[space + 1];
168 tags = new LineTag(this, 1, text.Length);
169 tags.font = font;
170 tags.color = color;
173 internal Line(int LineNo, string Text, LineTag tag) : this() {
174 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
176 text = new StringBuilder(Text, space);
177 line_no = LineNo;
179 widths = new float[space + 1];
180 tags = tag;
183 #endregion // Constructors
185 #region Internal Properties
186 internal int Indent {
187 get {
188 return indent;
191 set {
192 indent = value;
193 recalc = true;
197 internal int HangingIndent {
198 get {
199 return hanging_indent;
202 set {
203 hanging_indent = value;
204 recalc = true;
208 internal int RightIndent {
209 get {
210 return right_indent;
213 set {
214 right_indent = value;
215 recalc = true;
220 internal int Height {
221 get {
222 return height;
225 set {
226 height = value;
230 internal int LineNo {
231 get {
232 return line_no;
235 set {
236 line_no = value;
240 internal string Text {
241 get {
242 return text.ToString();
245 set {
246 text = new StringBuilder(value, value.Length > DEFAULT_TEXT_LEN ? value.Length : DEFAULT_TEXT_LEN);
250 internal HorizontalAlignment Alignment {
251 get {
252 return alignment;
255 set {
256 if (alignment != value) {
257 alignment = value;
258 recalc = true;
262 #if no
263 internal StringBuilder Text {
264 get {
265 return text;
268 set {
269 text = value;
272 #endif
273 #endregion // Internal Properties
275 #region Internal Methods
276 // Make sure we always have enoughs space in text and widths
277 internal void Grow(int minimum) {
278 int length;
279 float[] new_widths;
281 length = text.Length;
283 if ((length + minimum) > space) {
284 // We need to grow; double the size
286 if ((length + minimum) > (space * 2)) {
287 new_widths = new float[length + minimum * 2 + 1];
288 space = length + minimum * 2;
289 } else {
290 new_widths = new float[space * 2 + 1];
291 space *= 2;
293 widths.CopyTo(new_widths, 0);
295 widths = new_widths;
299 internal void Streamline(int lines) {
300 LineTag current;
301 LineTag next;
303 current = this.tags;
304 next = current.next;
306 // Catch what the loop below wont; eliminate 0 length
307 // tags, but only if there are other tags after us
308 while ((current.length == 0) && (next != null)) {
309 tags = next;
310 tags.previous = null;
311 current = next;
312 next = current.next;
315 if (next == null) {
316 return;
319 while (next != null) {
320 // Take out 0 length tags unless it's the last tag in the document
321 if (next.length == 0) {
322 if ((next.next != null) || (line_no != lines)) {
323 current.next = next.next;
324 if (current.next != null) {
325 current.next.previous = current;
327 next = current.next;
328 continue;
331 if (current.Combine(next)) {
332 next = current.next;
333 continue;
336 current = current.next;
337 next = current.next;
341 /// <summary> Find the tag on a line based on the character position, pos is 0-based</summary>
342 internal LineTag FindTag(int pos) {
343 LineTag tag;
345 if (pos == 0) {
346 return tags;
349 tag = this.tags;
351 if (pos >= text.Length) {
352 pos = text.Length - 1;
355 while (tag != null) {
356 if (((tag.start - 1) <= pos) && (pos < (tag.start + tag.length - 1))) {
357 return tag;
359 tag = tag.next;
361 return null;
364 /// <summary>
365 /// Recalculate a single line using the same char for every character in the line
366 /// </summary>
368 internal bool RecalculatePasswordLine(Graphics g, Document doc) {
369 LineTag tag;
370 int pos;
371 int len;
372 float w;
373 bool ret;
374 int descent;
376 pos = 0;
377 len = this.text.Length;
378 tag = this.tags;
379 ascent = 0;
380 tag.shift = 0;
381 tag.width = 0;
383 this.recalc = false;
384 widths[0] = indent;
385 tag.X = indent;
387 w = g.MeasureString(doc.password_char, tags.font, 10000, string_format).Width;
389 if (this.height != (int)tag.font.Height) {
390 ret = true;
391 } else {
392 ret = false;
395 this.height = (int)tag.font.Height;
396 tag.height = this.height;
398 XplatUI.GetFontMetrics(g, tag.font, out tag.ascent, out descent);
399 this.ascent = tag.ascent;
401 while (pos < len) {
402 tag.width += w;
403 pos++;
404 widths[pos] = widths[pos-1] + w;
407 return ret;
410 /// <summary>
411 /// Go through all tags on a line and recalculate all size-related values;
412 /// returns true if lineheight changed
413 /// </summary>
414 internal bool RecalculateLine(Graphics g, Document doc) {
415 LineTag tag;
416 int pos;
417 int len;
418 SizeF size;
419 float w;
420 int prev_height;
421 bool retval;
422 bool wrapped;
423 Line line;
424 int wrap_pos;
425 float wrap_width;
427 pos = 0;
428 len = this.text.Length;
429 tag = this.tags;
430 prev_height = this.height; // For drawing optimization calculations
431 this.height = 0; // Reset line height
432 this.ascent = 0; // Reset the ascent for the line
433 tag.shift = 0;
434 tag.width = 0;
436 if (this.soft_break) {
437 widths[0] = hanging_indent;
438 } else {
439 widths[0] = indent;
442 this.recalc = false;
443 retval = false;
444 wrapped = false;
446 wrap_pos = 0;
447 wrap_width = 0;
449 while (pos < len) {
450 size = g.MeasureString(this.text.ToString(pos, 1), tag.font, 10000, string_format);
452 while (tag.length == 0) { // We should always have tags after a tag.length==0 unless len==0
453 tag.width = 0;
454 tag.ascent = 0;
455 if (tag.previous != null) {
456 tag.X = tag.previous.X;
457 } else {
458 tag.X = (int)widths[pos];
460 tag = tag.next;
461 tag.width = 0;
462 tag.shift = 0;
465 w = size.Width;
467 if (Char.IsWhiteSpace(text[pos])) {
468 wrap_pos = pos + 1;
469 wrap_width = tag.width + w;
472 if (doc.wrap) {
473 if ((wrap_pos > 0) && (wrap_pos != len) && (widths[pos] + w) + 27 > (doc.viewport_width - this.right_indent)) {
474 pos = wrap_pos;
475 tag.width = wrap_width;
476 doc.Split(this, tag, pos, true);
477 len = this.text.Length;
478 retval = true;
479 wrapped = true;
483 // Contract all soft lines that follow back into our line
484 if (!wrapped) {
485 tag.width += w;
487 pos++;
489 widths[pos] = widths[pos-1] + w;
491 if (pos == len) {
492 line = doc.GetLine(this.line_no + 1);
493 if ((line != null) && (line.soft_break)) {
494 // Pull the previous line back into this one
495 doc.Combine(this.line_no, this.line_no + 1);
496 len = this.text.Length;
497 retval = true;
502 if (pos == (tag.start-1 + tag.length)) {
503 // We just found the end of our current tag
504 tag.height = (int)tag.font.Height;
506 // Check if we're the tallest on the line (so far)
507 if (tag.height > this.height) {
508 this.height = tag.height; // Yep; make sure the line knows
511 if (tag.ascent == 0) {
512 int descent;
514 XplatUI.GetFontMetrics(g, tag.font, out tag.ascent, out descent);
517 if (tag.ascent > this.ascent) {
518 LineTag t;
520 // We have a tag that has a taller ascent than the line;
522 t = tags;
523 while (t != tag) {
524 t.shift = tag.ascent - t.ascent;
525 t = t.next;
528 // Save on our line
529 this.ascent = tag.ascent;
530 } else {
531 tag.shift = this.ascent - tag.ascent;
534 // Update our horizontal starting pixel position
535 if (tag.previous == null) {
536 tag.X = (int)widths[0];
537 } else {
538 tag.X = tag.previous.X + (int)tag.previous.width;
541 tag = tag.next;
542 if (tag != null) {
543 tag.width = 0;
544 tag.shift = 0;
545 wrap_pos = pos;
546 wrap_width = tag.width;
551 if (this.height == 0) {
552 this.height = tags.font.Height;
553 tag.height = this.height;
556 if (prev_height != this.height) {
557 retval = true;
559 return retval;
561 #endregion // Internal Methods
563 #region Administrative
564 public int CompareTo(object obj) {
565 if (obj == null) {
566 return 1;
569 if (! (obj is Line)) {
570 throw new ArgumentException("Object is not of type Line", "obj");
573 if (line_no < ((Line)obj).line_no) {
574 return -1;
575 } else if (line_no > ((Line)obj).line_no) {
576 return 1;
577 } else {
578 return 0;
582 public object Clone() {
583 Line clone;
585 clone = new Line();
587 clone.text = text;
589 if (left != null) {
590 clone.left = (Line)left.Clone();
593 if (left != null) {
594 clone.left = (Line)left.Clone();
597 return clone;
600 internal object CloneLine() {
601 Line clone;
603 clone = new Line();
605 clone.text = text;
607 return clone;
610 public override bool Equals(object obj) {
611 if (obj == null) {
612 return false;
615 if (!(obj is Line)) {
616 return false;
619 if (obj == this) {
620 return true;
623 if (line_no == ((Line)obj).line_no) {
624 return true;
627 return false;
630 public override int GetHashCode() {
631 return base.GetHashCode ();
634 public override string ToString() {
635 return "Line " + line_no;
638 #endregion // Administrative
641 internal class Document : ICloneable, IEnumerable {
642 #region Structures
643 // FIXME - go through code and check for places where
644 // we do explicit comparisons instead of using the compare overloads
645 internal struct Marker {
646 internal Line line;
647 internal LineTag tag;
648 internal int pos;
649 internal int height;
651 public static bool operator<(Marker lhs, Marker rhs) {
652 if (lhs.line.line_no < rhs.line.line_no) {
653 return true;
656 if (lhs.line.line_no == rhs.line.line_no) {
657 if (lhs.pos < rhs.pos) {
658 return true;
661 return false;
664 public static bool operator>(Marker lhs, Marker rhs) {
665 if (lhs.line.line_no > rhs.line.line_no) {
666 return true;
669 if (lhs.line.line_no == rhs.line.line_no) {
670 if (lhs.pos > rhs.pos) {
671 return true;
674 return false;
677 public static bool operator==(Marker lhs, Marker rhs) {
678 if ((lhs.line.line_no == rhs.line.line_no) && (lhs.pos == rhs.pos)) {
679 return true;
681 return false;
684 public static bool operator!=(Marker lhs, Marker rhs) {
685 if ((lhs.line.line_no != rhs.line.line_no) || (lhs.pos != rhs.pos)) {
686 return true;
688 return false;
691 public void Combine(Line move_to_line, int move_to_line_length) {
692 line = move_to_line;
693 pos += move_to_line_length;
694 tag = LineTag.FindTag(line, pos);
697 // This is for future use, right now Document.Split does it by hand, with some added shortcut logic
698 public void Split(Line move_to_line, int split_at) {
699 line = move_to_line;
700 pos -= split_at;
701 tag = LineTag.FindTag(line, pos);
704 public override bool Equals(object obj) {
705 return this==(Marker)obj;
708 public override int GetHashCode() {
709 return base.GetHashCode ();
712 public override string ToString() {
713 return "Marker Line " + line + ", Position " + pos;
717 #endregion Structures
719 #region Local Variables
720 private Line document;
721 private int lines;
722 private Line sentinel;
723 private Line last_found;
724 private int document_id;
725 private Random random = new Random();
726 internal string password_char;
727 private StringBuilder password_cache;
728 private bool calc_pass;
729 private int char_count;
731 private bool no_recalc;
732 private bool recalc_pending;
733 private int recalc_start;
734 private int recalc_end;
735 private bool recalc_optimize;
737 internal bool multiline;
738 internal bool wrap;
740 internal UndoClass undo;
742 internal Marker caret;
743 internal Marker selection_start;
744 internal Marker selection_end;
745 internal bool selection_visible;
746 internal Marker selection_anchor;
747 internal Marker selection_prev;
748 internal bool selection_end_anchor;
750 internal int viewport_x;
751 internal int viewport_y; // The visible area of the document
752 internal int viewport_width;
753 internal int viewport_height;
755 internal int document_x; // Width of the document
756 internal int document_y; // Height of the document
758 internal Rectangle invalid;
760 internal int crlf_size; // 1 or 2, depending on whether we use \r\n or just \n
762 internal TextBoxBase owner; // Who's owning us?
763 static internal int caret_width = 1;
764 static internal int caret_shift = 1;
765 #endregion // Local Variables
767 #region Constructors
768 internal Document(TextBoxBase owner) {
769 lines = 0;
771 this.owner = owner;
773 multiline = true;
774 password_char = "";
775 calc_pass = false;
776 no_recalc = false;
777 recalc_pending = false;
779 // Tree related stuff
780 sentinel = new Line();
781 sentinel.color = LineColor.Black;
783 document = sentinel;
784 last_found = sentinel;
786 // We always have a blank line
787 owner.HandleCreated += new EventHandler(owner_HandleCreated);
788 owner.VisibleChanged += new EventHandler(owner_VisibleChanged);
789 Add(1, "", owner.Font, ThemeEngine.Current.ResPool.GetSolidBrush(owner.ForeColor));
790 lines=1;
792 undo = new UndoClass(this);
794 selection_visible = false;
795 selection_start.line = this.document;
796 selection_start.pos = 0;
797 selection_start.tag = selection_start.line.tags;
798 selection_end.line = this.document;
799 selection_end.pos = 0;
800 selection_end.tag = selection_end.line.tags;
801 selection_anchor.line = this.document;
802 selection_anchor.pos = 0;
803 selection_anchor.tag = selection_anchor.line.tags;
804 caret.line = this.document;
805 caret.pos = 0;
806 caret.tag = caret.line.tags;
808 viewport_x = 0;
809 viewport_y = 0;
811 crlf_size = 2;
813 // Default selection is empty
815 document_id = random.Next();
817 #endregion
819 #region Internal Properties
820 internal Line Root {
821 get {
822 return document;
825 set {
826 document = value;
830 internal int Lines {
831 get {
832 return lines;
836 internal Line CaretLine {
837 get {
838 return caret.line;
842 internal int CaretPosition {
843 get {
844 return caret.pos;
848 internal Point Caret {
849 get {
850 return new Point((int)caret.tag.line.widths[caret.pos] + caret.line.align_shift, caret.line.Y);
854 internal LineTag CaretTag {
855 get {
856 return caret.tag;
859 set {
860 caret.tag = value;
864 internal int CRLFSize {
865 get {
866 return crlf_size;
869 set {
870 crlf_size = value;
874 internal string PasswordChar {
875 get {
876 return password_char;
879 set {
880 password_char = value;
881 if ((password_char.Length != 0) && (password_char[0] != '\0')) {
882 char ch;
884 calc_pass = true;
885 ch = value[0];
886 password_cache = new StringBuilder(1024);
887 for (int i = 0; i < 1024; i++) {
888 password_cache.Append(ch);
890 } else {
891 calc_pass = false;
892 password_cache = null;
897 internal int ViewPortX {
898 get {
899 return viewport_x;
902 set {
903 viewport_x = value;
907 internal int Length {
908 get {
909 return char_count + lines - 1; // Add \n for each line but the last
913 private int CharCount {
914 get {
915 return char_count;
918 set {
919 char_count = value;
921 if (LengthChanged != null) {
922 LengthChanged(this, EventArgs.Empty);
927 ///<summary>Setting NoRecalc to true will prevent the document from being recalculated.
928 ///This ensures that coordinates of added text are predictable after adding the text even with wrapped view</summary>
929 internal bool NoRecalc {
930 get {
931 return no_recalc;
934 set {
935 no_recalc = value;
936 if (!no_recalc && recalc_pending) {
937 RecalculateDocument(owner.CreateGraphicsInternal(), recalc_start, recalc_end, recalc_optimize);
938 recalc_pending = false;
943 internal int ViewPortY {
944 get {
945 return viewport_y;
948 set {
949 viewport_y = value;
953 internal int ViewPortWidth {
954 get {
955 return viewport_width;
958 set {
959 viewport_width = value;
963 internal int ViewPortHeight {
964 get {
965 return viewport_height;
968 set {
969 viewport_height = value;
974 internal int Width {
975 get {
976 return this.document_x;
980 internal int Height {
981 get {
982 return this.document_y;
986 internal bool SelectionVisible {
987 get {
988 return selection_visible;
992 internal bool Wrap {
993 get {
994 return wrap;
997 set {
998 wrap = value;
1002 #endregion // Internal Properties
1004 #region Private Methods
1005 // For debugging
1006 internal int DumpTree(Line line, bool with_tags) {
1007 int total;
1009 total = 1;
1011 Console.Write("Line {0} [# {1}], Y: {1} Text {2}", line.line_no, line.GetHashCode(), line.Y, line.text != null ? line.text.ToString() : "undefined");
1013 if (line.left == sentinel) {
1014 Console.Write(", left = sentinel");
1015 } else if (line.left == null) {
1016 Console.Write(", left = NULL");
1019 if (line.right == sentinel) {
1020 Console.Write(", right = sentinel");
1021 } else if (line.right == null) {
1022 Console.Write(", right = NULL");
1025 Console.WriteLine("");
1027 if (with_tags) {
1028 LineTag tag;
1029 int count;
1030 int length;
1032 tag = line.tags;
1033 count = 1;
1034 length = 0;
1035 Console.Write(" Tags: ");
1036 while (tag != null) {
1037 Console.Write("{0} <{1}>-<{2}> ", count++, tag.start, tag.length);
1038 length += tag.length;
1040 if (tag.line != line) {
1041 Console.Write("BAD line link");
1042 throw new Exception("Bad line link in tree");
1044 tag = tag.next;
1045 if (tag != null) {
1046 Console.Write(", ");
1049 if (length > line.text.Length) {
1050 throw new Exception(String.Format("Length of tags more than length of text on line (expected {0} calculated {1})", line.text.Length, length));
1051 } else if (length < line.text.Length) {
1052 throw new Exception(String.Format("Length of tags less than length of text on line (expected {0} calculated {1})", line.text.Length, length));
1054 Console.WriteLine("");
1056 if (line.left != null) {
1057 if (line.left != sentinel) {
1058 total += DumpTree(line.left, with_tags);
1060 } else {
1061 if (line != sentinel) {
1062 throw new Exception("Left should not be NULL");
1066 if (line.right != null) {
1067 if (line.right != sentinel) {
1068 total += DumpTree(line.right, with_tags);
1070 } else {
1071 if (line != sentinel) {
1072 throw new Exception("Right should not be NULL");
1076 for (int i = 1; i <= this.lines; i++) {
1077 if (GetLine(i) == null) {
1078 throw new Exception(String.Format("Hole in line order, missing {0}", i));
1082 if (line == this.Root) {
1083 if (total < this.lines) {
1084 throw new Exception(String.Format("Not enough nodes in tree, found {0}, expected {1}", total, this.lines));
1085 } else if (total > this.lines) {
1086 throw new Exception(String.Format("Too many nodes in tree, found {0}, expected {1}", total, this.lines));
1090 return total;
1093 private void DecrementLines(int line_no) {
1094 int current;
1096 current = line_no;
1097 while (current <= lines) {
1098 GetLine(current).line_no--;
1099 current++;
1101 return;
1104 private void IncrementLines(int line_no) {
1105 int current;
1107 current = this.lines;
1108 while (current >= line_no) {
1109 GetLine(current).line_no++;
1110 current--;
1112 return;
1115 private void RebalanceAfterAdd(Line line1) {
1116 Line line2;
1118 while ((line1 != document) && (line1.parent.color == LineColor.Red)) {
1119 if (line1.parent == line1.parent.parent.left) {
1120 line2 = line1.parent.parent.right;
1122 if ((line2 != null) && (line2.color == LineColor.Red)) {
1123 line1.parent.color = LineColor.Black;
1124 line2.color = LineColor.Black;
1125 line1.parent.parent.color = LineColor.Red;
1126 line1 = line1.parent.parent;
1127 } else {
1128 if (line1 == line1.parent.right) {
1129 line1 = line1.parent;
1130 RotateLeft(line1);
1133 line1.parent.color = LineColor.Black;
1134 line1.parent.parent.color = LineColor.Red;
1136 RotateRight(line1.parent.parent);
1138 } else {
1139 line2 = line1.parent.parent.left;
1141 if ((line2 != null) && (line2.color == LineColor.Red)) {
1142 line1.parent.color = LineColor.Black;
1143 line2.color = LineColor.Black;
1144 line1.parent.parent.color = LineColor.Red;
1145 line1 = line1.parent.parent;
1146 } else {
1147 if (line1 == line1.parent.left) {
1148 line1 = line1.parent;
1149 RotateRight(line1);
1152 line1.parent.color = LineColor.Black;
1153 line1.parent.parent.color = LineColor.Red;
1154 RotateLeft(line1.parent.parent);
1158 document.color = LineColor.Black;
1161 private void RebalanceAfterDelete(Line line1) {
1162 Line line2;
1164 while ((line1 != document) && (line1.color == LineColor.Black)) {
1165 if (line1 == line1.parent.left) {
1166 line2 = line1.parent.right;
1167 if (line2.color == LineColor.Red) {
1168 line2.color = LineColor.Black;
1169 line1.parent.color = LineColor.Red;
1170 RotateLeft(line1.parent);
1171 line2 = line1.parent.right;
1173 if ((line2.left.color == LineColor.Black) && (line2.right.color == LineColor.Black)) {
1174 line2.color = LineColor.Red;
1175 line1 = line1.parent;
1176 } else {
1177 if (line2.right.color == LineColor.Black) {
1178 line2.left.color = LineColor.Black;
1179 line2.color = LineColor.Red;
1180 RotateRight(line2);
1181 line2 = line1.parent.right;
1183 line2.color = line1.parent.color;
1184 line1.parent.color = LineColor.Black;
1185 line2.right.color = LineColor.Black;
1186 RotateLeft(line1.parent);
1187 line1 = document;
1189 } else {
1190 line2 = line1.parent.left;
1191 if (line2.color == LineColor.Red) {
1192 line2.color = LineColor.Black;
1193 line1.parent.color = LineColor.Red;
1194 RotateRight(line1.parent);
1195 line2 = line1.parent.left;
1197 if ((line2.right.color == LineColor.Black) && (line2.left.color == LineColor.Black)) {
1198 line2.color = LineColor.Red;
1199 line1 = line1.parent;
1200 } else {
1201 if (line2.left.color == LineColor.Black) {
1202 line2.right.color = LineColor.Black;
1203 line2.color = LineColor.Red;
1204 RotateLeft(line2);
1205 line2 = line1.parent.left;
1207 line2.color = line1.parent.color;
1208 line1.parent.color = LineColor.Black;
1209 line2.left.color = LineColor.Black;
1210 RotateRight(line1.parent);
1211 line1 = document;
1215 line1.color = LineColor.Black;
1218 private void RotateLeft(Line line1) {
1219 Line line2 = line1.right;
1221 line1.right = line2.left;
1223 if (line2.left != sentinel) {
1224 line2.left.parent = line1;
1227 if (line2 != sentinel) {
1228 line2.parent = line1.parent;
1231 if (line1.parent != null) {
1232 if (line1 == line1.parent.left) {
1233 line1.parent.left = line2;
1234 } else {
1235 line1.parent.right = line2;
1237 } else {
1238 document = line2;
1241 line2.left = line1;
1242 if (line1 != sentinel) {
1243 line1.parent = line2;
1247 private void RotateRight(Line line1) {
1248 Line line2 = line1.left;
1250 line1.left = line2.right;
1252 if (line2.right != sentinel) {
1253 line2.right.parent = line1;
1256 if (line2 != sentinel) {
1257 line2.parent = line1.parent;
1260 if (line1.parent != null) {
1261 if (line1 == line1.parent.right) {
1262 line1.parent.right = line2;
1263 } else {
1264 line1.parent.left = line2;
1266 } else {
1267 document = line2;
1270 line2.right = line1;
1271 if (line1 != sentinel) {
1272 line1.parent = line2;
1277 internal void UpdateView(Line line, int pos) {
1278 if (!owner.IsHandleCreated) {
1279 return;
1282 if (no_recalc) {
1283 recalc_start = line.line_no;
1284 recalc_end = line.line_no;
1285 recalc_optimize = true;
1286 recalc_pending = true;
1287 return;
1290 // Optimize invalidation based on Line alignment
1291 if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no, true)) {
1292 // Lineheight changed, invalidate the rest of the document
1293 if ((line.Y - viewport_y) >=0 ) {
1294 // We formatted something that's in view, only draw parts of the screen
1295 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, owner.Height - line.Y - viewport_y));
1296 } else {
1297 // The tag was above the visible area, draw everything
1298 owner.Invalidate();
1300 } else {
1301 switch(line.alignment) {
1302 case HorizontalAlignment.Left: {
1303 owner.Invalidate(new Rectangle((int)line.widths[pos] - viewport_x - 1, line.Y - viewport_y, viewport_width, line.height + 1));
1304 break;
1307 case HorizontalAlignment.Center: {
1308 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, line.height + 1));
1309 break;
1312 case HorizontalAlignment.Right: {
1313 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, (int)line.widths[pos + 1] - viewport_x + line.align_shift, line.height + 1));
1314 break;
1321 // Update display from line, down line_count lines; pos is unused, but required for the signature
1322 internal void UpdateView(Line line, int line_count, int pos) {
1323 if (!owner.IsHandleCreated) {
1324 return;
1327 if (no_recalc) {
1328 recalc_start = line.line_no;
1329 recalc_end = line.line_no + line_count - 1;
1330 recalc_optimize = true;
1331 recalc_pending = true;
1332 return;
1335 if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no + line_count - 1, true)) {
1336 // Lineheight changed, invalidate the rest of the document
1337 if ((line.Y - viewport_y) >=0 ) {
1338 // We formatted something that's in view, only draw parts of the screen
1339 //blah Console.WriteLine("TextControl.cs(981) Invalidate called in UpdateView(line, line_count, pos)");
1340 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, owner.Height - line.Y - viewport_y));
1341 } else {
1342 // The tag was above the visible area, draw everything
1343 //blah Console.WriteLine("TextControl.cs(985) Invalidate called in UpdateView(line, line_count, pos)");
1344 owner.Invalidate();
1346 } else {
1347 Line end_line;
1349 end_line = GetLine(line.line_no + line_count -1);
1350 if (end_line == null) {
1351 end_line = line;
1354 //blah Console.WriteLine("TextControl.cs(996) Invalidate called in UpdateView(line, line_count, pos)");
1355 owner.Invalidate(new Rectangle(0 - viewport_x, line.Y - viewport_y, (int)line.widths[line.text.Length], end_line.Y + end_line.height));
1358 #endregion // Private Methods
1360 #region Internal Methods
1361 // Clear the document and reset state
1362 internal void Empty() {
1364 document = sentinel;
1365 last_found = sentinel;
1366 lines = 0;
1368 // We always have a blank line
1369 Add(1, "", owner.Font, ThemeEngine.Current.ResPool.GetSolidBrush(owner.ForeColor));
1370 this.RecalculateDocument(owner.CreateGraphicsInternal());
1371 PositionCaret(0, 0);
1373 selection_visible = false;
1374 selection_start.line = this.document;
1375 selection_start.pos = 0;
1376 selection_start.tag = selection_start.line.tags;
1377 selection_end.line = this.document;
1378 selection_end.pos = 0;
1379 selection_end.tag = selection_end.line.tags;
1380 char_count = 0;
1382 viewport_x = 0;
1383 viewport_y = 0;
1385 document_x = 0;
1386 document_y = 0;
1389 internal void PositionCaret(Line line, int pos) {
1390 if (owner.IsHandleCreated) {
1391 undo.RecordCursor();
1394 caret.tag = line.FindTag(pos);
1395 caret.line = line;
1396 caret.pos = pos;
1397 caret.height = caret.tag.height;
1399 if (owner.IsHandleCreated) {
1400 if (owner.Focused) {
1401 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.align_shift - viewport_x, caret.line.Y + caret.tag.shift - viewport_y + caret_shift);
1404 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1410 internal void PositionCaret(int x, int y) {
1411 if (!owner.IsHandleCreated) {
1412 return;
1415 undo.RecordCursor();
1417 caret.tag = FindCursor(x, y, out caret.pos);
1418 caret.line = caret.tag.line;
1419 caret.height = caret.tag.height;
1421 if (owner.Focused) {
1422 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.align_shift - viewport_x, caret.line.Y + caret.tag.shift - viewport_y + caret_shift);
1425 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1428 internal void CaretHasFocus() {
1429 if ((caret.tag != null) && owner.IsHandleCreated) {
1430 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1431 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.align_shift - viewport_x, caret.line.Y + caret.tag.shift - viewport_y + caret_shift);
1433 if (!selection_visible) {
1434 XplatUI.CaretVisible(owner.Handle, true);
1435 } else {
1436 XplatUI.CaretVisible(owner.Handle, false);
1441 internal void CaretLostFocus() {
1442 if (!owner.IsHandleCreated) {
1443 return;
1445 XplatUI.DestroyCaret(owner.Handle);
1448 internal void AlignCaret() {
1449 if (!owner.IsHandleCreated) {
1450 return;
1453 undo.RecordCursor();
1455 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1456 caret.height = caret.tag.height;
1458 if (owner.Focused) {
1459 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1460 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.align_shift - viewport_x, caret.line.Y + caret.tag.shift - viewport_y + caret_shift);
1461 XplatUI.CaretVisible(owner.Handle, true);
1464 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1467 internal void UpdateCaret() {
1468 if (!owner.IsHandleCreated || caret.tag == null) {
1469 return;
1472 undo.RecordCursor();
1474 if (caret.tag.height != caret.height) {
1475 caret.height = caret.tag.height;
1476 if (owner.Focused) {
1477 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1481 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.align_shift - viewport_x, caret.line.Y + caret.tag.shift - viewport_y + caret_shift);
1482 XplatUI.CaretVisible(owner.Handle, true);
1484 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1487 internal void DisplayCaret() {
1488 if (!owner.IsHandleCreated) {
1489 return;
1492 if (owner.Focused) {
1493 XplatUI.CaretVisible(owner.Handle, true);
1497 internal void HideCaret() {
1498 if (!owner.IsHandleCreated) {
1499 return;
1502 if (owner.Focused) {
1503 XplatUI.CaretVisible(owner.Handle, false);
1507 internal void MoveCaret(CaretDirection direction) {
1508 // FIXME should we use IsWordSeparator to detect whitespace, instead
1509 // of looking for actual spaces in the Word move cases?
1510 switch(direction) {
1511 case CaretDirection.CharForward: {
1512 caret.pos++;
1513 if (caret.pos > caret.line.text.Length) {
1514 if (multiline) {
1515 // Go into next line
1516 if (caret.line.line_no < this.lines) {
1517 caret.line = GetLine(caret.line.line_no+1);
1518 caret.pos = 0;
1519 caret.tag = caret.line.tags;
1520 } else {
1521 caret.pos--;
1523 } else {
1524 // Single line; we stay where we are
1525 caret.pos--;
1527 } else {
1528 if ((caret.tag.start - 1 + caret.tag.length) < caret.pos) {
1529 caret.tag = caret.tag.next;
1532 UpdateCaret();
1533 return;
1536 case CaretDirection.CharBack: {
1537 if (caret.pos > 0) {
1538 // caret.pos--; // folded into the if below
1539 if (--caret.pos > 0) {
1540 if (caret.tag.start > caret.pos) {
1541 caret.tag = caret.tag.previous;
1544 } else {
1545 if (caret.line.line_no > 1) {
1546 caret.line = GetLine(caret.line.line_no - 1);
1547 caret.pos = caret.line.text.Length;
1548 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1551 UpdateCaret();
1552 return;
1555 case CaretDirection.WordForward: {
1556 int len;
1558 len = caret.line.text.Length;
1559 if (caret.pos < len) {
1560 while ((caret.pos < len) && (caret.line.text[caret.pos] != ' ')) {
1561 caret.pos++;
1563 if (caret.pos < len) {
1564 // Skip any whitespace
1565 while ((caret.pos < len) && (caret.line.text[caret.pos] == ' ')) {
1566 caret.pos++;
1569 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1570 } else {
1571 if (caret.line.line_no < this.lines) {
1572 caret.line = GetLine(caret.line.line_no + 1);
1573 caret.pos = 0;
1574 caret.tag = caret.line.tags;
1577 UpdateCaret();
1578 return;
1581 case CaretDirection.WordBack: {
1582 if (caret.pos > 0) {
1583 caret.pos--;
1585 while ((caret.pos > 0) && (caret.line.text[caret.pos] == ' ')) {
1586 caret.pos--;
1589 while ((caret.pos > 0) && (caret.line.text[caret.pos] != ' ')) {
1590 caret.pos--;
1593 if (caret.line.text.ToString(caret.pos, 1) == " ") {
1594 if (caret.pos != 0) {
1595 caret.pos++;
1596 } else {
1597 caret.line = GetLine(caret.line.line_no - 1);
1598 caret.pos = caret.line.text.Length;
1601 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1602 } else {
1603 if (caret.line.line_no > 1) {
1604 caret.line = GetLine(caret.line.line_no - 1);
1605 caret.pos = caret.line.text.Length;
1606 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1609 UpdateCaret();
1610 return;
1613 case CaretDirection.LineUp: {
1614 if (caret.line.line_no > 1) {
1615 int pixel;
1617 pixel = (int)caret.line.widths[caret.pos];
1618 PositionCaret(pixel, GetLine(caret.line.line_no - 1).Y);
1619 if (!owner.IsHandleCreated) {
1620 return;
1622 XplatUI.CaretVisible(owner.Handle, true);
1624 return;
1627 case CaretDirection.LineDown: {
1628 if (caret.line.line_no < lines) {
1629 int pixel;
1631 pixel = (int)caret.line.widths[caret.pos];
1632 PositionCaret(pixel, GetLine(caret.line.line_no + 1).Y);
1633 if (!owner.IsHandleCreated) {
1634 return;
1636 XplatUI.CaretVisible(owner.Handle, true);
1638 return;
1641 case CaretDirection.Home: {
1642 if (caret.pos > 0) {
1643 caret.pos = 0;
1644 caret.tag = caret.line.tags;
1645 UpdateCaret();
1647 return;
1650 case CaretDirection.End: {
1651 if (caret.pos < caret.line.text.Length) {
1652 caret.pos = caret.line.text.Length;
1653 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1654 UpdateCaret();
1656 return;
1659 case CaretDirection.PgUp: {
1660 int index;
1661 int new_y;
1662 Line line;
1664 if ((viewport_y - viewport_height) < 0) {
1665 // We're just placing the caret at the end of the document, no scrolling needed
1666 owner.vscroll.Value = 0;
1667 line = GetLine(1);
1668 PositionCaret(line, 0);
1669 XplatUI.CaretVisible(owner.Handle, true);
1670 owner.Invalidate();
1671 return;
1674 new_y = caret.line.Y - viewport_height;
1675 if (new_y < 0) {
1676 line = GetLine(1);
1677 PositionCaret(line, 0);
1678 } else {
1679 line = FindTag((int)caret.line.widths[caret.pos], caret.line.Y - viewport_height, out index, false).line;
1680 if (caret.pos > 0) {
1681 PositionCaret(line, index);
1682 } else {
1683 PositionCaret(line, 0);
1687 // Line up to fill line starts
1688 new_y = viewport_y - viewport_height;
1689 line = FindTag(0, new_y, out index, false).line;
1690 if (line != null) {
1691 owner.vscroll.Value = line.Y;
1692 } else {
1693 owner.vscroll.Value = new_y;
1695 XplatUI.CaretVisible(owner.Handle, true);
1696 owner.Invalidate();
1697 return;
1700 case CaretDirection.PgDn: {
1701 int index;
1702 int new_y;
1703 Line line;
1705 if ((viewport_y + viewport_height) > document_y) {
1706 // We're just placing the caret at the end of the document, no scrolling needed
1707 owner.vscroll.Value = owner.vscroll.Maximum;
1708 line = GetLine(lines);
1709 PositionCaret(line, line.Text.Length);
1710 XplatUI.CaretVisible(owner.Handle, true);
1711 owner.Invalidate();
1712 return;
1715 new_y = caret.line.Y + viewport_height;
1716 if (new_y > document_y) {
1717 line = GetLine(lines);
1718 PositionCaret(line, line.text.Length);
1719 } else {
1720 line = FindTag((int)caret.line.widths[caret.pos], caret.line.Y + viewport_height, out index, false).line;
1721 if (caret.pos > 0) {
1722 PositionCaret(line, index);
1723 } else {
1724 PositionCaret(line, 0);
1728 // Line up to fill line starts
1729 new_y = viewport_y + viewport_height;
1730 line = FindTag(0, new_y, out index, false).line;
1731 if (line != null) {
1732 if (line.Y > owner.vscroll.Maximum) {
1733 owner.vscroll.Value = owner.vscroll.Maximum;
1734 } else {
1735 owner.vscroll.Value = line.Y;
1737 } else {
1738 owner.vscroll.Value = new_y;
1740 XplatUI.CaretVisible(owner.Handle, true);
1741 owner.Invalidate();
1742 return;
1745 case CaretDirection.CtrlPgUp: {
1746 PositionCaret(0, viewport_y);
1747 if (!owner.IsHandleCreated) {
1748 return;
1750 XplatUI.CaretVisible(owner.Handle, true);
1751 return;
1754 case CaretDirection.CtrlPgDn: {
1755 Line line;
1756 LineTag tag;
1757 int index;
1759 tag = FindTag(0, viewport_y + viewport_height, out index, false);
1760 if (tag.line.line_no > 1) {
1761 line = GetLine(tag.line.line_no - 1);
1762 } else {
1763 line = tag.line;
1765 PositionCaret(line, line.Text.Length);
1766 if (!owner.IsHandleCreated) {
1767 return;
1769 XplatUI.CaretVisible(owner.Handle, true);
1770 return;
1773 case CaretDirection.CtrlHome: {
1774 caret.line = GetLine(1);
1775 caret.pos = 0;
1776 caret.tag = caret.line.tags;
1778 UpdateCaret();
1779 return;
1782 case CaretDirection.CtrlEnd: {
1783 caret.line = GetLine(lines);
1784 caret.pos = caret.line.text.Length;
1785 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1787 UpdateCaret();
1788 return;
1791 case CaretDirection.SelectionStart: {
1792 caret.line = selection_start.line;
1793 caret.pos = selection_start.pos;
1794 caret.tag = selection_start.tag;
1796 UpdateCaret();
1797 return;
1800 case CaretDirection.SelectionEnd: {
1801 caret.line = selection_end.line;
1802 caret.pos = selection_end.pos;
1803 caret.tag = selection_end.tag;
1805 UpdateCaret();
1806 return;
1811 // Draw the document
1812 internal void Draw(Graphics g, Rectangle clip) {
1813 Line line; // Current line being drawn
1814 LineTag tag; // Current tag being drawn
1815 int start; // First line to draw
1816 int end; // Last line to draw
1817 StringBuilder text; // String representing the current line
1818 int line_no; //
1819 Brush disabled;
1820 Brush hilight;
1821 Brush hilight_text;
1823 // First, figure out from what line to what line we need to draw
1824 start = GetLineByPixel(clip.Top + viewport_y, false).line_no;
1825 end = GetLineByPixel(clip.Bottom + viewport_y, false).line_no;
1826 //Console.WriteLine("Starting drawing at line {0}, ending at line {1} (clip-bottom:{2})", start, end, clip.Bottom);
1828 // Now draw our elements; try to only draw those that are visible
1829 line_no = start;
1831 #if Debug
1832 DateTime n = DateTime.Now;
1833 Console.WriteLine("Started drawing: {0}s {1}ms", n.Second, n.Millisecond);
1834 #endif
1836 disabled = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorGrayText);
1837 hilight = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorHighlight);
1838 hilight_text = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorHighlightText);
1840 while (line_no <= end) {
1841 line = GetLine(line_no);
1842 #if not
1843 if (owner.backcolor_set || (owner.Enabled && !owner.read_only)) {
1844 g.FillRectangle(ThemeEngine.Current.ResPool.GetSolidBrush(owner.BackColor), new Rectangle(clip.Left, line.Y - viewport_y, clip.Width, line.Y - viewport_y + line.Height));
1845 } else {
1846 g.FillRectangle(ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorControl), new Rectangle(clip.Left, line.Y - viewport_y, clip.Width, line.Y - viewport_y + line.Height));
1848 #endif
1851 tag = line.tags;
1852 if (!calc_pass) {
1853 text = line.text;
1854 } else {
1855 // This fails if there's a password > 1024 chars...
1856 text = this.password_cache;
1858 while (tag != null) {
1859 if (tag.length == 0) {
1860 tag = tag.next;
1861 continue;
1864 if (((tag.X + tag.width) > (clip.Left - viewport_x)) || (tag.X < (clip.Right - viewport_x))) {
1865 // Check for selection
1866 if ((!selection_visible) || (!owner.ShowSelection) || (line_no < selection_start.line.line_no) || (line_no > selection_end.line.line_no)) {
1867 // regular drawing, no selection to deal with
1868 //g.DrawString(s.Substring(tag.start-1, tag.length), tag.font, tag.color, tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
1869 if (owner.is_enabled) {
1870 g.DrawString(text.ToString(tag.start-1, tag.length), tag.font, tag.color, tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
1871 } else {
1872 Color a;
1873 Color b;
1875 a = ((SolidBrush)tag.color).Color;
1876 b = ThemeEngine.Current.ColorWindowText;
1878 if ((a.R == b.R) && (a.G == b.G) && (a.B == b.B)) {
1879 g.DrawString(text.ToString(tag.start-1, tag.length), tag.font, disabled, tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
1880 } else {
1881 g.DrawString(text.ToString(tag.start-1, tag.length), tag.font, tag.color, tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
1884 } else {
1885 // we might have to draw our selection
1886 if ((line_no != selection_start.line.line_no) && (line_no != selection_end.line.line_no)) {
1887 // Special case, whole line is selected, draw this tag selected
1888 g.FillRectangle(
1889 hilight, // Brush
1890 tag.X + line.align_shift - viewport_x, // X
1891 line.Y + tag.shift - viewport_y, // Y
1892 line.widths[tag.start + tag.length - 1], // width
1893 tag.height // Height
1896 g.DrawString(
1897 //s.Substring(tag.start-1, tag.length), // String
1898 text.ToString(tag.start-1, tag.length), // String
1899 tag.font, // Font
1900 hilight_text, // Brush
1901 tag.X + line.align_shift - viewport_x, // X
1902 line.Y + tag.shift - viewport_y, // Y
1903 StringFormat.GenericTypographic);
1904 } else {
1905 bool highlight;
1906 bool partial;
1908 highlight = false;
1909 partial = false;
1911 // One or more, but not all tags on the line are selected
1912 if ((selection_start.tag == tag) && (selection_end.tag == tag)) {
1913 // Single tag selected, draw "normalSELECTEDnormal"
1914 partial = true;
1915 // First, the regular part
1916 g.DrawString(
1917 //s.Substring(tag.start - 1, selection_start.pos - tag.start + 1), // String
1918 text.ToString(tag.start - 1, selection_start.pos - tag.start + 1), // String
1919 tag.font, // Font
1920 tag.color, // Brush
1921 tag.X + line.align_shift - viewport_x, // X
1922 line.Y + tag.shift - viewport_y, // Y
1923 StringFormat.GenericTypographic);
1925 // Now the highlight
1926 g.FillRectangle(
1927 hilight, // Brush
1928 line.widths[selection_start.pos] + line.align_shift, // X
1929 line.Y + tag.shift - viewport_y, // Y
1930 line.widths[selection_end.pos] - line.widths[selection_start.pos], // Width
1931 tag.height); // Height
1933 g.DrawString(
1934 //s.Substring(selection_start.pos, selection_end.pos - selection_start.pos), // String
1935 text.ToString(selection_start.pos, selection_end.pos - selection_start.pos), // String
1936 tag.font, // Font
1937 hilight_text, // Brush
1938 line.widths[selection_start.pos] + line.align_shift - viewport_x, // X
1939 line.Y + tag.shift - viewport_y, // Y
1940 StringFormat.GenericTypographic);
1942 // And back to the regular
1943 g.DrawString(
1944 //s.Substring(selection_end.pos, tag.start + tag.length - selection_end.pos - 1), // String
1945 text.ToString(selection_end.pos, tag.start + tag.length - selection_end.pos - 1), // String
1946 tag.font, // Font
1947 tag.color, // Brush
1948 line.widths[selection_end.pos] + line.align_shift - viewport_x, // X
1949 line.Y + tag.shift - viewport_y, // Y
1950 StringFormat.GenericTypographic);
1952 } else if (selection_start.tag == tag) {
1953 partial = true;
1955 // The highlighted part
1956 g.FillRectangle(
1957 hilight,
1958 line.widths[selection_start.pos] + line.align_shift,
1959 line.Y + tag.shift - viewport_y,
1960 line.widths[tag.start + tag.length - 1] - line.widths[selection_start.pos],
1961 tag.height);
1963 g.DrawString(
1964 //s.Substring(selection_start.pos, tag.start + tag.length - selection_start.pos - 1), // String
1965 text.ToString(selection_start.pos, tag.start + tag.length - selection_start.pos - 1), // String
1966 tag.font, // Font
1967 hilight_text, // Brush
1968 line.widths[selection_start.pos] + line.align_shift - viewport_x, // X
1969 line.Y + tag.shift - viewport_y, // Y
1970 StringFormat.GenericTypographic);
1972 // The regular part
1973 g.DrawString(
1974 //s.Substring(tag.start - 1, selection_start.pos - tag.start + 1), // String
1975 text.ToString(tag.start - 1, selection_start.pos - tag.start + 1), // String
1976 tag.font, // Font
1977 tag.color, // Brush
1978 tag.X + line.align_shift - viewport_x, // X
1979 line.Y + tag.shift - viewport_y, // Y
1980 StringFormat.GenericTypographic);
1981 } else if (selection_end.tag == tag) {
1982 partial = true;
1984 // The highlighted part
1985 g.FillRectangle(
1986 hilight,
1987 tag.X + line.align_shift - viewport_x,
1988 line.Y + tag.shift - viewport_y,
1989 line.widths[selection_end.pos] - line.widths[tag.start - 1],
1990 tag.height);
1992 g.DrawString(
1993 //s.Substring(tag.start - 1, selection_end.pos - tag.start + 1), // String
1994 text.ToString(tag.start - 1, selection_end.pos - tag.start + 1), // String
1995 tag.font, // Font
1996 hilight_text, // Brush
1997 tag.X + line.align_shift - viewport_x, // X
1998 line.Y + tag.shift - viewport_y, // Y
1999 StringFormat.GenericTypographic);
2001 // The regular part
2002 g.DrawString(
2003 //s.Substring(selection_end.pos, tag.start + tag.length - selection_end.pos - 1), // String
2004 text.ToString(selection_end.pos, tag.start + tag.length - selection_end.pos - 1), // String
2005 tag.font, // Font
2006 tag.color, // Brush
2007 line.widths[selection_end.pos] + line.align_shift - viewport_x, // X
2008 line.Y + tag.shift - viewport_y, // Y
2009 StringFormat.GenericTypographic);
2010 } else {
2011 // no partially selected tags here, simple checks...
2012 if (selection_start.line == line) {
2013 int begin;
2014 int stop;
2016 begin = tag.start - 1;
2017 stop = tag.start + tag.length - 1;
2018 if (selection_end.line == line) {
2019 if ((begin >= selection_start.pos) && (stop < selection_end.pos)) {
2020 highlight = true;
2022 } else {
2023 if (stop > selection_start.pos) {
2024 highlight = true;
2027 } else if (selection_end.line == line) {
2028 if ((tag.start - 1) < selection_end.pos) {
2029 highlight = true;
2034 if (!partial) {
2035 if (highlight) {
2036 g.FillRectangle(
2037 hilight,
2038 tag.X + line.align_shift - viewport_x,
2039 line.Y + tag.shift - viewport_y,
2040 line.widths[tag.start + tag.length - 1] - line.widths[tag.start - 1],
2041 tag.height);
2043 g.DrawString(
2044 //s.Substring(tag.start-1, tag.length), // String
2045 text.ToString(tag.start-1, tag.length), // String
2046 tag.font, // Font
2047 hilight_text, // Brush
2048 tag.X + line.align_shift - viewport_x, // X
2049 line.Y + tag.shift - viewport_y, // Y
2050 StringFormat.GenericTypographic);
2051 } else {
2052 g.DrawString(
2053 //s.Substring(tag.start-1, tag.length), // String
2054 text.ToString(tag.start-1, tag.length), // String
2055 tag.font, // Font
2056 tag.color, // Brush
2057 tag.X + line.align_shift - viewport_x, // X
2058 line.Y + tag.shift - viewport_y, // Y
2059 StringFormat.GenericTypographic);
2067 tag = tag.next;
2070 line_no++;
2072 #if Debug
2073 n = DateTime.Now;
2074 Console.WriteLine("Finished drawing: {0}s {1}ms", n.Second, n.Millisecond);
2075 #endif
2079 internal void Insert(Line line, int pos, string s) {
2080 Insert(line, null, pos, false, s);
2083 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
2084 internal void Insert(Line line, LineTag tag, int pos, bool update_caret, string s) {
2085 int i;
2086 int base_line;
2087 string[] ins;
2088 int insert_lines;
2091 // The formatting at the insertion point is used for the inserted text
2092 if (tag == null) {
2093 tag = LineTag.FindTag(line, pos);
2096 base_line = line.line_no;
2098 ins = s.Split(new char[] {'\n'});
2100 for (int j = 0; j < ins.Length; j++) {
2101 if (ins[j].EndsWith("\r")) {
2102 ins[j] = ins[j].Substring(0, ins[j].Length - 1);
2106 insert_lines = ins.Length;
2108 // Bump the text at insertion point a line down if we're inserting more than one line
2109 if (insert_lines > 1) {
2110 Split(line, pos);
2111 // Remainder of start line is now in base_line + 1
2114 // Insert the first line
2115 InsertString(tag, pos, ins[0]);
2117 if (insert_lines > 1) {
2118 for (i = 1; i < insert_lines; i++) {
2119 Add(base_line + i, ins[i], line.alignment, tag.font, tag.color);
2121 if (!s.EndsWith("\n\n")) {
2122 this.Combine(base_line + insert_lines - 1, base_line + insert_lines);
2126 UpdateView(line, insert_lines + 1, pos);
2128 if (update_caret) {
2129 // Move caret to the end of the inserted text
2130 if (insert_lines > 1) {
2131 PositionCaret(GetLine(line.line_no + insert_lines - 1), ins[ins.Length - 1].Length);
2132 } else {
2133 PositionCaret(line, pos + ins[0].Length);
2135 if (owner.IsHandleCreated) {
2136 XplatUI.CaretVisible(owner.Handle, true);
2141 // Inserts a character at the given position
2142 internal void InsertString(Line line, int pos, string s) {
2143 InsertString(line.FindTag(pos), pos, s);
2146 // Inserts a string at the given position
2147 internal void InsertString(LineTag tag, int pos, string s) {
2148 Line line;
2149 int len;
2151 len = s.Length;
2153 CharCount += len;
2155 line = tag.line;
2156 line.text.Insert(pos, s);
2157 tag.length += len;
2159 tag = tag.next;
2160 while (tag != null) {
2161 tag.start += len;
2162 tag = tag.next;
2164 line.Grow(len);
2165 line.recalc = true;
2167 UpdateView(line, pos);
2170 // Inserts a string at the caret position
2171 internal void InsertStringAtCaret(string s, bool move_caret) {
2172 LineTag tag;
2173 int len;
2175 len = s.Length;
2177 CharCount += len;
2179 caret.line.text.Insert(caret.pos, s);
2180 caret.tag.length += len;
2182 if (caret.tag.next != null) {
2183 tag = caret.tag.next;
2184 while (tag != null) {
2185 tag.start += len;
2186 tag = tag.next;
2189 caret.line.Grow(len);
2190 caret.line.recalc = true;
2192 UpdateView(caret.line, caret.pos);
2193 if (move_caret) {
2194 caret.pos += len;
2195 UpdateCaret();
2201 // Inserts a character at the given position
2202 internal void InsertChar(Line line, int pos, char ch) {
2203 InsertChar(line.FindTag(pos), pos, ch);
2206 // Inserts a character at the given position
2207 internal void InsertChar(LineTag tag, int pos, char ch) {
2208 Line line;
2210 CharCount++;
2212 line = tag.line;
2213 line.text.Insert(pos, ch);
2214 tag.length++;
2216 tag = tag.next;
2217 while (tag != null) {
2218 tag.start++;
2219 tag = tag.next;
2221 line.Grow(1);
2222 line.recalc = true;
2224 UpdateView(line, pos);
2227 // Inserts a character at the current caret position
2228 internal void InsertCharAtCaret(char ch, bool move_caret) {
2229 LineTag tag;
2231 CharCount++;
2233 caret.line.text.Insert(caret.pos, ch);
2234 caret.tag.length++;
2236 if (caret.tag.next != null) {
2237 tag = caret.tag.next;
2238 while (tag != null) {
2239 tag.start++;
2240 tag = tag.next;
2243 caret.line.Grow(1);
2244 caret.line.recalc = true;
2246 UpdateView(caret.line, caret.pos);
2247 if (move_caret) {
2248 caret.pos++;
2249 UpdateCaret();
2250 SetSelectionToCaret(true);
2254 // Deletes n characters at the given position; it will not delete past line limits
2255 // pos is 0-based
2256 internal void DeleteChars(LineTag tag, int pos, int count) {
2257 Line line;
2258 bool streamline;
2260 streamline = false;
2261 line = tag.line;
2263 CharCount -= count;
2265 if (pos == line.text.Length) {
2266 return;
2269 line.text.Remove(pos, count);
2271 // Make sure the tag points to the right spot
2272 while ((tag != null) && (tag.start + tag.length - 1) <= pos) {
2273 tag = tag.next;
2276 if (tag == null) {
2277 return;
2280 // Check if we're crossing tag boundaries
2281 if ((pos + count) > (tag.start + tag.length - 1)) {
2282 int left;
2284 // We have to delete cross tag boundaries
2285 streamline = true;
2286 left = count;
2288 left -= tag.start + tag.length - pos - 1;
2289 tag.length -= tag.start + tag.length - pos - 1;
2291 tag = tag.next;
2292 while ((tag != null) && (left > 0)) {
2293 tag.start -= count - left;
2294 if (tag.length > left) {
2295 tag.length -= left;
2296 left = 0;
2297 } else {
2298 left -= tag.length;
2299 tag.length = 0;
2301 tag = tag.next;
2304 } else {
2305 // We got off easy, same tag
2307 tag.length -= count;
2309 if (tag.length == 0) {
2310 streamline = true;
2314 // Adjust the start point of any tags following
2315 if (tag != null) {
2316 tag = tag.next;
2317 while (tag != null) {
2318 tag.start -= count;
2319 tag = tag.next;
2323 line.recalc = true;
2324 if (streamline) {
2325 line.Streamline(lines);
2328 UpdateView(line, pos);
2331 // Deletes a character at or after the given position (depending on forward); it will not delete past line limits
2332 internal void DeleteChar(LineTag tag, int pos, bool forward) {
2333 Line line;
2334 bool streamline;
2336 CharCount--;
2338 streamline = false;
2339 line = tag.line;
2341 if ((pos == 0 && forward == false) || (pos == line.text.Length && forward == true)) {
2342 return;
2346 if (forward) {
2347 line.text.Remove(pos, 1);
2349 while ((tag != null) && (tag.start + tag.length - 1) <= pos) {
2350 tag = tag.next;
2353 if (tag == null) {
2354 return;
2357 tag.length--;
2359 if (tag.length == 0) {
2360 streamline = true;
2362 } else {
2363 pos--;
2364 line.text.Remove(pos, 1);
2365 if (pos >= (tag.start - 1)) {
2366 tag.length--;
2367 if (tag.length == 0) {
2368 streamline = true;
2370 } else if (tag.previous != null) {
2371 tag.previous.length--;
2372 if (tag.previous.length == 0) {
2373 streamline = true;
2378 tag = tag.next;
2379 while (tag != null) {
2380 tag.start--;
2381 tag = tag.next;
2383 line.recalc = true;
2384 if (streamline) {
2385 line.Streamline(lines);
2388 UpdateView(line, pos);
2391 // Combine two lines
2392 internal void Combine(int FirstLine, int SecondLine) {
2393 Combine(GetLine(FirstLine), GetLine(SecondLine));
2396 internal void Combine(Line first, Line second) {
2397 LineTag last;
2398 int shift;
2400 // Combine the two tag chains into one
2401 last = first.tags;
2403 while (last.next != null) {
2404 last = last.next;
2407 last.next = second.tags;
2408 last.next.previous = last;
2410 shift = last.start + last.length - 1;
2412 // Fix up references within the chain
2413 last = last.next;
2414 while (last != null) {
2415 last.line = first;
2416 last.start += shift;
2417 last = last.next;
2420 // Combine both lines' strings
2421 first.text.Insert(first.text.Length, second.text.ToString());
2422 first.Grow(first.text.Length);
2424 // Remove the reference to our (now combined) tags from the doomed line
2425 second.tags = null;
2427 // Renumber lines
2428 DecrementLines(first.line_no + 2); // first.line_no + 1 will be deleted, so we need to start renumbering one later
2430 // Mop up
2431 first.recalc = true;
2432 first.height = 0; // This forces RecalcDocument/UpdateView to redraw from this line on
2433 first.Streamline(lines);
2435 // Update Caret, Selection, etc
2436 if (caret.line == second) {
2437 caret.Combine(first, shift);
2439 if (selection_anchor.line == second) {
2440 selection_anchor.Combine(first, shift);
2442 if (selection_start.line == second) {
2443 selection_start.Combine(first, shift);
2445 if (selection_end.line == second) {
2446 selection_end.Combine(first, shift);
2449 #if Debug
2450 Line check_first;
2451 Line check_second;
2453 check_first = GetLine(first.line_no);
2454 check_second = GetLine(check_first.line_no + 1);
2456 Console.WriteLine("Pre-delete: Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2457 #endif
2459 this.Delete(second);
2461 #if Debug
2462 check_first = GetLine(first.line_no);
2463 check_second = GetLine(check_first.line_no + 1);
2465 Console.WriteLine("Post-delete Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2466 #endif
2469 // Split the line at the position into two
2470 internal void Split(int LineNo, int pos) {
2471 Line line;
2472 LineTag tag;
2474 line = GetLine(LineNo);
2475 tag = LineTag.FindTag(line, pos);
2476 Split(line, tag, pos, false);
2479 internal void Split(Line line, int pos) {
2480 LineTag tag;
2482 tag = LineTag.FindTag(line, pos);
2483 Split(line, tag, pos, false);
2486 ///<summary>Split line at given tag and position into two lines</summary>
2487 ///<param name="soft">True if the split should be marked as 'soft', indicating that it can be contracted
2488 ///if more space becomes available on previous line</param>
2489 internal void Split(Line line, LineTag tag, int pos, bool soft) {
2490 LineTag new_tag;
2491 Line new_line;
2492 bool move_caret;
2493 bool move_sel_start;
2494 bool move_sel_end;
2496 move_caret = false;
2497 move_sel_start = false;
2498 move_sel_end = false;
2500 // Adjust selection and cursors
2501 if (soft && (caret.line == line) && (caret.pos >= pos)) {
2502 move_caret = true;
2504 if (selection_start.line == line && selection_start.pos > pos) {
2505 move_sel_start = true;
2508 if (selection_end.line == line && selection_end.pos > pos) {
2509 move_sel_end = true;
2512 // cover the easy case first
2513 if (pos == line.text.Length) {
2514 Add(line.line_no + 1, "", line.alignment, tag.font, tag.color);
2516 new_line = GetLine(line.line_no + 1);
2518 if (soft) {
2519 if (move_caret) {
2520 caret.line = new_line;
2521 caret.line.soft_break = true;
2522 caret.tag = new_line.tags;
2523 caret.pos = 0;
2524 } else {
2525 new_line.soft_break = true;
2529 if (move_sel_start) {
2530 selection_start.line = new_line;
2531 selection_start.pos = 0;
2532 selection_start.tag = new_line.tags;
2535 if (move_sel_end) {
2536 selection_end.line = new_line;
2537 selection_end.pos = 0;
2538 selection_end.tag = new_line.tags;
2540 return;
2543 // We need to move the rest of the text into the new line
2544 Add(line.line_no + 1, line.text.ToString(pos, line.text.Length - pos), line.alignment, tag.font, tag.color);
2546 // Now transfer our tags from this line to the next
2547 new_line = GetLine(line.line_no + 1);
2548 line.recalc = true;
2549 new_line.recalc = true;
2551 if ((tag.start - 1) == pos) {
2552 int shift;
2554 // We can simply break the chain and move the tag into the next line
2555 if (tag == line.tags) {
2556 new_tag = new LineTag(line, 1, 0);
2557 new_tag.font = tag.font;
2558 new_tag.color = tag.color;
2559 line.tags = new_tag;
2562 if (tag.previous != null) {
2563 tag.previous.next = null;
2565 new_line.tags = tag;
2566 tag.previous = null;
2567 tag.line = new_line;
2569 // Walk the list and correct the start location of the tags we just bumped into the next line
2570 shift = tag.start - 1;
2572 new_tag = tag;
2573 while (new_tag != null) {
2574 new_tag.start -= shift;
2575 new_tag.line = new_line;
2576 new_tag = new_tag.next;
2578 } else {
2579 int shift;
2581 new_tag = new LineTag(new_line, 1, tag.start - 1 + tag.length - pos);
2582 new_tag.next = tag.next;
2583 new_tag.font = tag.font;
2584 new_tag.color = tag.color;
2585 new_line.tags = new_tag;
2586 if (new_tag.next != null) {
2587 new_tag.next.previous = new_tag;
2589 tag.next = null;
2590 tag.length = pos - tag.start + 1;
2592 shift = pos;
2593 new_tag = new_tag.next;
2594 while (new_tag != null) {
2595 new_tag.start -= shift;
2596 new_tag.line = new_line;
2597 new_tag = new_tag.next;
2602 if (soft) {
2603 if (move_caret) {
2604 caret.line = new_line;
2605 caret.pos = caret.pos - pos;
2606 caret.tag = caret.line.FindTag(caret.pos);
2608 new_line.soft_break = true;
2611 if (move_sel_start) {
2612 selection_start.line = new_line;
2613 selection_start.pos = selection_start.pos - pos;
2614 selection_start.tag = new_line.FindTag(selection_start.pos);
2617 if (move_sel_end) {
2618 selection_end.line = new_line;
2619 selection_end.pos = selection_end.pos - pos;
2620 selection_end.tag = new_line.FindTag(selection_end.pos);
2623 CharCount -= line.text.Length - pos;
2624 line.text.Remove(pos, line.text.Length - pos);
2627 // Adds a line of text, with given font.
2628 // Bumps any line at that line number that already exists down
2629 internal void Add(int LineNo, string Text, Font font, Brush color) {
2630 Add(LineNo, Text, HorizontalAlignment.Left, font, color);
2633 internal void Add(int LineNo, string Text, HorizontalAlignment align, Font font, Brush color) {
2634 Line add;
2635 Line line;
2636 int line_no;
2638 CharCount += Text.Length;
2640 if (LineNo<1 || Text == null) {
2641 if (LineNo<1) {
2642 throw new ArgumentNullException("LineNo", "Line numbers must be positive");
2643 } else {
2644 throw new ArgumentNullException("Text", "Cannot insert NULL line");
2648 add = new Line(LineNo, Text, align, font, color);
2650 line = document;
2651 while (line != sentinel) {
2652 add.parent = line;
2653 line_no = line.line_no;
2655 if (LineNo > line_no) {
2656 line = line.right;
2657 } else if (LineNo < line_no) {
2658 line = line.left;
2659 } else {
2660 // Bump existing line numbers; walk all nodes to the right of this one and increment line_no
2661 IncrementLines(line.line_no);
2662 line = line.left;
2666 add.left = sentinel;
2667 add.right = sentinel;
2669 if (add.parent != null) {
2670 if (LineNo > add.parent.line_no) {
2671 add.parent.right = add;
2672 } else {
2673 add.parent.left = add;
2675 } else {
2676 // Root node
2677 document = add;
2680 RebalanceAfterAdd(add);
2682 lines++;
2685 internal virtual void Clear() {
2686 lines = 0;
2687 CharCount = 0;
2688 document = sentinel;
2691 public virtual object Clone() {
2692 Document clone;
2694 clone = new Document(null);
2696 clone.lines = this.lines;
2697 clone.document = (Line)document.Clone();
2699 return clone;
2702 internal void Delete(int LineNo) {
2703 Line line;
2705 if (LineNo>lines) {
2706 return;
2709 line = GetLine(LineNo);
2711 CharCount -= line.text.Length;
2713 DecrementLines(LineNo + 1);
2714 Delete(line);
2717 internal void Delete(Line line1) {
2718 Line line2;// = new Line();
2719 Line line3;
2721 if ((line1.left == sentinel) || (line1.right == sentinel)) {
2722 line3 = line1;
2723 } else {
2724 line3 = line1.right;
2725 while (line3.left != sentinel) {
2726 line3 = line3.left;
2730 if (line3.left != sentinel) {
2731 line2 = line3.left;
2732 } else {
2733 line2 = line3.right;
2736 line2.parent = line3.parent;
2737 if (line3.parent != null) {
2738 if(line3 == line3.parent.left) {
2739 line3.parent.left = line2;
2740 } else {
2741 line3.parent.right = line2;
2743 } else {
2744 document = line2;
2747 if (line3 != line1) {
2748 LineTag tag;
2750 if (selection_start.line == line3) {
2751 selection_start.line = line1;
2754 if (selection_end.line == line3) {
2755 selection_end.line = line1;
2758 if (selection_anchor.line == line3) {
2759 selection_anchor.line = line1;
2762 if (caret.line == line3) {
2763 caret.line = line1;
2767 line1.alignment = line3.alignment;
2768 line1.ascent = line3.ascent;
2769 line1.hanging_indent = line3.hanging_indent;
2770 line1.height = line3.height;
2771 line1.indent = line3.indent;
2772 line1.line_no = line3.line_no;
2773 line1.recalc = line3.recalc;
2774 line1.right_indent = line3.right_indent;
2775 line1.soft_break = line3.soft_break;
2776 line1.space = line3.space;
2777 line1.tags = line3.tags;
2778 line1.text = line3.text;
2779 line1.widths = line3.widths;
2780 line1.Y = line3.Y;
2782 tag = line1.tags;
2783 while (tag != null) {
2784 tag.line = line1;
2785 tag = tag.next;
2789 if (line3.color == LineColor.Black)
2790 RebalanceAfterDelete(line2);
2792 this.lines--;
2794 last_found = sentinel;
2797 // Invalidate a section of the document to trigger redraw
2798 internal void Invalidate(Line start, int start_pos, Line end, int end_pos) {
2799 Line l1;
2800 Line l2;
2801 int p1;
2802 int p2;
2804 if ((start == end) && (start_pos == end_pos)) {
2805 return;
2808 if (end_pos == -1) {
2809 end_pos = end.text.Length;
2812 // figure out what's before what so the logic below is straightforward
2813 if (start.line_no < end.line_no) {
2814 l1 = start;
2815 p1 = start_pos;
2817 l2 = end;
2818 p2 = end_pos;
2819 } else if (start.line_no > end.line_no) {
2820 l1 = end;
2821 p1 = end_pos;
2823 l2 = start;
2824 p2 = start_pos;
2825 } else {
2826 if (start_pos < end_pos) {
2827 l1 = start;
2828 p1 = start_pos;
2830 l2 = end;
2831 p2 = end_pos;
2832 } else {
2833 l1 = end;
2834 p1 = end_pos;
2836 l2 = start;
2837 p2 = start_pos;
2840 #if Debug
2841 Console.WriteLine("Invaliding from {0}:{1} to {2}:{3}", l1.line_no, p1, l2.line_no, p2);
2842 #endif
2844 owner.Invalidate(
2845 new Rectangle(
2846 (int)l1.widths[p1] + l1.align_shift - viewport_x,
2847 l1.Y - viewport_y,
2848 (int)l2.widths[p2] - (int)l1.widths[p1] + 1,
2849 l1.height
2852 return;
2855 #if Debug
2856 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.align_shift - viewport_x, l1.Y - viewport_y, viewport_width, l1.height);
2857 #endif
2859 // Three invalidates:
2860 // First line from start
2861 owner.Invalidate(new Rectangle((int)l1.widths[p1] + l1.align_shift - viewport_x, l1.Y - viewport_y, viewport_width, l1.height));
2863 // lines inbetween
2864 if ((l1.line_no + 1) < l2.line_no) {
2865 int y;
2867 y = GetLine(l1.line_no + 1).Y;
2868 owner.Invalidate(new Rectangle(0, y - viewport_y, viewport_width, GetLine(l2.line_no).Y - y - viewport_y));
2870 #if Debug
2871 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, GetLine(l2.line_no).Y - y - viewport_y);
2872 #endif
2875 // Last line to end
2876 owner.Invalidate(new Rectangle((int)l2.widths[0] + l2.align_shift - viewport_x, l2.Y - viewport_y, (int)l2.widths[p2] + 1, l2.height));
2877 #if Debug
2878 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.align_shift - viewport_x, l2.Y - viewport_y, (int)l2.widths[p2] + 1, l2.height);
2879 #endif
2882 /// <summary>Select text around caret</summary>
2883 internal void ExpandSelection(CaretSelection mode, bool to_caret) {
2884 if (to_caret) {
2885 // We're expanding the selection to the caret position
2886 switch(mode) {
2887 case CaretSelection.Line: {
2888 // Invalidate the selection delta
2889 if (caret > selection_prev) {
2890 Invalidate(selection_prev.line, 0, caret.line, caret.line.text.Length);
2891 } else {
2892 Invalidate(selection_prev.line, selection_prev.line.text.Length, caret.line, 0);
2895 if (caret.line.line_no <= selection_anchor.line.line_no) {
2896 selection_start.line = caret.line;
2897 selection_start.tag = caret.line.tags;
2898 selection_start.pos = 0;
2900 selection_end.line = selection_anchor.line;
2901 selection_end.tag = selection_anchor.tag;
2902 selection_end.pos = selection_anchor.pos;
2904 selection_end_anchor = true;
2905 } else {
2906 selection_start.line = selection_anchor.line;
2907 selection_start.pos = selection_anchor.height;
2908 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height);
2910 selection_end.line = caret.line;
2911 selection_end.tag = caret.line.tags;
2912 selection_end.pos = caret.line.text.Length;
2914 selection_end_anchor = false;
2916 selection_prev.line = caret.line;
2917 selection_prev.tag = caret.tag;
2918 selection_prev.pos = caret.pos;
2920 break;
2923 case CaretSelection.Word: {
2924 int start_pos;
2925 int end_pos;
2927 start_pos = FindWordSeparator(caret.line, caret.pos, false);
2928 end_pos = FindWordSeparator(caret.line, caret.pos, true);
2931 // Invalidate the selection delta
2932 if (caret > selection_prev) {
2933 Invalidate(selection_prev.line, selection_prev.pos, caret.line, end_pos);
2934 } else {
2935 Invalidate(selection_prev.line, selection_prev.pos, caret.line, start_pos);
2937 if (caret < selection_anchor) {
2938 selection_start.line = caret.line;
2939 selection_start.tag = caret.line.FindTag(start_pos);
2940 selection_start.pos = start_pos;
2942 selection_end.line = selection_anchor.line;
2943 selection_end.tag = selection_anchor.tag;
2944 selection_end.pos = selection_anchor.pos;
2946 selection_prev.line = caret.line;
2947 selection_prev.tag = caret.tag;
2948 selection_prev.pos = start_pos;
2950 selection_end_anchor = true;
2951 } else {
2952 selection_start.line = selection_anchor.line;
2953 selection_start.pos = selection_anchor.height;
2954 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height);
2956 selection_end.line = caret.line;
2957 selection_end.tag = caret.line.FindTag(end_pos);
2958 selection_end.pos = end_pos;
2960 selection_prev.line = caret.line;
2961 selection_prev.tag = caret.tag;
2962 selection_prev.pos = end_pos;
2964 selection_end_anchor = false;
2966 break;
2969 case CaretSelection.Position: {
2970 SetSelectionToCaret(false);
2971 return;
2974 } else {
2975 // We're setting the selection 'around' the caret position
2976 switch(mode) {
2977 case CaretSelection.Line: {
2978 this.Invalidate(caret.line, 0, caret.line, caret.line.text.Length);
2980 selection_start.line = caret.line;
2981 selection_start.tag = caret.line.tags;
2982 selection_start.pos = 0;
2984 selection_end.line = caret.line;
2985 selection_end.pos = caret.line.text.Length;
2986 selection_end.tag = caret.line.FindTag(selection_end.pos);
2988 selection_anchor.line = selection_end.line;
2989 selection_anchor.tag = selection_end.tag;
2990 selection_anchor.pos = selection_end.pos;
2991 selection_anchor.height = 0;
2993 selection_prev.line = caret.line;
2994 selection_prev.tag = caret.tag;
2995 selection_prev.pos = caret.pos;
2997 this.selection_end_anchor = true;
2999 break;
3002 case CaretSelection.Word: {
3003 int start_pos;
3004 int end_pos;
3006 start_pos = FindWordSeparator(caret.line, caret.pos, false);
3007 end_pos = FindWordSeparator(caret.line, caret.pos, true);
3009 this.Invalidate(selection_start.line, start_pos, caret.line, end_pos);
3011 selection_start.line = caret.line;
3012 selection_start.tag = caret.line.FindTag(start_pos);
3013 selection_start.pos = start_pos;
3015 selection_end.line = caret.line;
3016 selection_end.tag = caret.line.FindTag(end_pos);
3017 selection_end.pos = end_pos;
3019 selection_anchor.line = selection_end.line;
3020 selection_anchor.tag = selection_end.tag;
3021 selection_anchor.pos = selection_end.pos;
3022 selection_anchor.height = start_pos;
3024 selection_prev.line = caret.line;
3025 selection_prev.tag = caret.tag;
3026 selection_prev.pos = caret.pos;
3028 this.selection_end_anchor = true;
3030 break;
3035 if (selection_start == selection_end) {
3036 selection_visible = false;
3037 } else {
3038 selection_visible = true;
3042 internal void SetSelectionToCaret(bool start) {
3043 if (start) {
3044 // Invalidate old selection; selection is being reset to empty
3045 this.Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3047 selection_start.line = caret.line;
3048 selection_start.tag = caret.tag;
3049 selection_start.pos = caret.pos;
3051 // start always also selects end
3052 selection_end.line = caret.line;
3053 selection_end.tag = caret.tag;
3054 selection_end.pos = caret.pos;
3056 selection_anchor.line = caret.line;
3057 selection_anchor.tag = caret.tag;
3058 selection_anchor.pos = caret.pos;
3059 } else {
3060 // Invalidate from previous end to caret (aka new end)
3061 if (selection_end_anchor) {
3062 if (selection_start != caret) {
3063 this.Invalidate(selection_start.line, selection_start.pos, caret.line, caret.pos);
3065 } else {
3066 if (selection_end != caret) {
3067 this.Invalidate(selection_end.line, selection_end.pos, caret.line, caret.pos);
3071 if (caret < selection_anchor) {
3072 selection_start.line = caret.line;
3073 selection_start.tag = caret.tag;
3074 selection_start.pos = caret.pos;
3076 selection_end.line = selection_anchor.line;
3077 selection_end.tag = selection_anchor.tag;
3078 selection_end.pos = selection_anchor.pos;
3080 selection_end_anchor = true;
3081 } else {
3082 selection_start.line = selection_anchor.line;
3083 selection_start.tag = selection_anchor.tag;
3084 selection_start.pos = selection_anchor.pos;
3086 selection_end.line = caret.line;
3087 selection_end.tag = caret.tag;
3088 selection_end.pos = caret.pos;
3090 selection_end_anchor = false;
3094 if (selection_start == selection_end) {
3095 selection_visible = false;
3096 } else {
3097 selection_visible = true;
3101 internal void SetSelection(Line start, int start_pos, Line end, int end_pos) {
3102 if (selection_visible) {
3103 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3106 if ((end.line_no < start.line_no) || ((end == start) && (end_pos <= start_pos))) {
3107 selection_start.line = end;
3108 selection_start.tag = LineTag.FindTag(end, end_pos);
3109 selection_start.pos = end_pos;
3111 selection_end.line = start;
3112 selection_end.tag = LineTag.FindTag(start, start_pos);
3113 selection_end.pos = start_pos;
3115 selection_end_anchor = true;
3116 } else {
3117 selection_start.line = start;
3118 selection_start.tag = LineTag.FindTag(start, start_pos);
3119 selection_start.pos = start_pos;
3121 selection_end.line = end;
3122 selection_end.tag = LineTag.FindTag(end, end_pos);
3123 selection_end.pos = end_pos;
3125 selection_end_anchor = false;
3128 selection_anchor.line = start;
3129 selection_anchor.tag = selection_start.tag;
3130 selection_anchor.pos = start_pos;
3132 if (((start == end) && (start_pos == end_pos)) || start == null || end == null) {
3133 selection_visible = false;
3134 } else {
3135 selection_visible = true;
3140 internal void SetSelectionStart(Line start, int start_pos) {
3141 // Invalidate from the previous to the new start pos
3142 Invalidate(selection_start.line, selection_start.pos, start, start_pos);
3144 selection_start.line = start;
3145 selection_start.pos = start_pos;
3146 selection_start.tag = LineTag.FindTag(start, start_pos);
3148 selection_anchor.line = start;
3149 selection_anchor.pos = start_pos;
3150 selection_anchor.tag = selection_start.tag;
3152 selection_end_anchor = false;
3154 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
3155 selection_visible = true;
3157 // This could be calculated better
3158 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3163 internal void SetSelectionStart(int character_index) {
3164 Line line;
3165 LineTag tag;
3166 int pos;
3168 if (character_index < 0) {
3169 return;
3172 CharIndexToLineTag(character_index, out line, out tag, out pos);
3173 SetSelectionStart(line, pos);
3176 internal void SetSelectionEnd(Line end, int end_pos) {
3177 if ((end.line_no < selection_anchor.line.line_no) || ((end == selection_anchor.line) && (end_pos <= selection_anchor.pos))) {
3178 selection_start.line = end;
3179 selection_start.tag = LineTag.FindTag(end, end_pos);
3180 selection_start.pos = end_pos;
3182 selection_end.line = selection_anchor.line;
3183 selection_end.tag = selection_anchor.tag;
3184 selection_end.pos = selection_anchor.pos;
3186 selection_end_anchor = true;
3187 } else {
3188 selection_start.line = selection_anchor.line;
3189 selection_start.tag = selection_anchor.tag;
3190 selection_start.pos = selection_anchor.pos;
3192 selection_end.line = end;
3193 selection_end.tag = LineTag.FindTag(end, end_pos);
3194 selection_end.pos = end_pos;
3196 selection_end_anchor = false;
3199 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
3200 selection_visible = true;
3201 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3205 internal void SetSelectionEnd(int character_index) {
3206 Line line;
3207 LineTag tag;
3208 int pos;
3210 if (character_index < 0) {
3211 return;
3214 CharIndexToLineTag(character_index, out line, out tag, out pos);
3215 SetSelectionEnd(line, pos);
3218 internal void SetSelection(Line start, int start_pos) {
3219 if (selection_visible) {
3220 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3223 selection_start.line = start;
3224 selection_start.pos = start_pos;
3225 selection_start.tag = LineTag.FindTag(start, start_pos);
3227 selection_end.line = start;
3228 selection_end.tag = selection_start.tag;
3229 selection_end.pos = start_pos;
3231 selection_anchor.line = start;
3232 selection_anchor.tag = selection_start.tag;
3233 selection_anchor.pos = start_pos;
3235 selection_end_anchor = false;
3236 selection_visible = false;
3239 internal void InvalidateSelectionArea() {
3240 Invalidate (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3243 // Return the current selection, as string
3244 internal string GetSelection() {
3245 // We return String.Empty if there is no selection
3246 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3247 return string.Empty;
3250 if (!multiline || (selection_start.line == selection_end.line)) {
3251 return selection_start.line.text.ToString(selection_start.pos, selection_end.pos - selection_start.pos);
3252 } else {
3253 StringBuilder sb;
3254 int i;
3255 int start;
3256 int end;
3258 sb = new StringBuilder();
3259 start = selection_start.line.line_no;
3260 end = selection_end.line.line_no;
3262 sb.Append(selection_start.line.text.ToString(selection_start.pos, selection_start.line.text.Length - selection_start.pos) + Environment.NewLine);
3264 if ((start + 1) < end) {
3265 for (i = start + 1; i < end; i++) {
3266 sb.Append(GetLine(i).text.ToString() + Environment.NewLine);
3270 sb.Append(selection_end.line.text.ToString(0, selection_end.pos));
3272 return sb.ToString();
3276 internal void ReplaceSelection(string s) {
3277 int i;
3279 // First, delete any selected text
3280 if ((selection_start.pos != selection_end.pos) || (selection_start.line != selection_end.line)) {
3281 if (!multiline || (selection_start.line == selection_end.line)) {
3282 undo.RecordDeleteChars(selection_start.line, selection_start.pos + 1, selection_end.pos - selection_start.pos);
3284 DeleteChars(selection_start.tag, selection_start.pos, selection_end.pos - selection_start.pos);
3286 // The tag might have been removed, we need to recalc it
3287 selection_start.tag = selection_start.line.FindTag(selection_start.pos);
3288 } else {
3289 int start;
3290 int end;
3292 start = selection_start.line.line_no;
3293 end = selection_end.line.line_no;
3295 undo.RecordDelete(selection_start.line, selection_start.pos + 1, selection_end.line, selection_end.pos);
3297 // Delete first line
3298 DeleteChars(selection_start.tag, selection_start.pos, selection_start.line.text.Length - selection_start.pos);
3300 // Delete last line
3301 DeleteChars(selection_end.line.tags, 0, selection_end.pos);
3303 start++;
3304 if (start < end) {
3305 for (i = end - 1; i >= start; i--) {
3306 Delete(i);
3310 // BIG FAT WARNING - selection_end.line might be stale due
3311 // to the above Delete() call. DONT USE IT before hitting the end of this method!
3313 // Join start and end
3314 Combine(selection_start.line.line_no, start);
3318 Insert(selection_start.line, null, selection_start.pos, true, s);
3320 selection_end.line = selection_start.line;
3321 selection_end.pos = selection_start.pos;
3322 selection_end.tag = selection_start.tag;
3324 selection_visible = false;
3327 internal void CharIndexToLineTag(int index, out Line line_out, out LineTag tag_out, out int pos) {
3328 Line line;
3329 LineTag tag;
3330 int i;
3331 int chars;
3332 int start;
3334 chars = 0;
3336 for (i = 1; i <= lines; i++) {
3337 line = GetLine(i);
3339 start = chars;
3340 chars += line.text.Length + crlf_size;
3342 if (index <= chars) {
3343 // we found the line
3344 tag = line.tags;
3346 while (tag != null) {
3347 if (index < (start + tag.start + tag.length)) {
3348 line_out = line;
3349 tag_out = tag;
3350 pos = index - start;
3351 return;
3353 if (tag.next == null) {
3354 Line next_line;
3356 next_line = GetLine(line.line_no + 1);
3358 if (next_line != null) {
3359 line_out = next_line;
3360 tag_out = next_line.tags;
3361 pos = 0;
3362 return;
3363 } else {
3364 line_out = line;
3365 tag_out = tag;
3366 pos = line_out.text.Length;
3367 return;
3370 tag = tag.next;
3375 line_out = GetLine(lines);
3376 tag = line_out.tags;
3377 while (tag.next != null) {
3378 tag = tag.next;
3380 tag_out = tag;
3381 pos = line_out.text.Length;
3384 internal int LineTagToCharIndex(Line line, int pos) {
3385 int i;
3386 int length;
3388 // Count first and last line
3389 length = 0;
3391 // Count the lines in the middle
3393 for (i = 1; i < line.line_no; i++) {
3394 length += GetLine(i).text.Length + crlf_size;
3397 length += pos;
3399 return length;
3402 internal int SelectionLength() {
3403 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3404 return 0;
3407 if (!multiline || (selection_start.line == selection_end.line)) {
3408 return selection_end.pos - selection_start.pos;
3409 } else {
3410 int i;
3411 int start;
3412 int end;
3413 int length;
3415 // Count first and last line
3416 length = selection_start.line.text.Length - selection_start.pos + selection_end.pos + crlf_size;
3418 // Count the lines in the middle
3419 start = selection_start.line.line_no + 1;
3420 end = selection_end.line.line_no;
3422 if (start < end) {
3423 for (i = start; i < end; i++) {
3424 length += GetLine(i).text.Length + crlf_size;
3428 return length;
3435 /// <summary>Give it a Line number and it returns the Line object at with that line number</summary>
3436 internal Line GetLine(int LineNo) {
3437 Line line = document;
3439 while (line != sentinel) {
3440 if (LineNo == line.line_no) {
3441 return line;
3442 } else if (LineNo < line.line_no) {
3443 line = line.left;
3444 } else {
3445 line = line.right;
3449 return null;
3452 /// <summary>Retrieve the previous tag; walks line boundaries</summary>
3453 internal LineTag PreviousTag(LineTag tag) {
3454 Line l;
3456 if (tag.previous != null) {
3457 return tag.previous;
3460 // Next line
3461 if (tag.line.line_no == 1) {
3462 return null;
3465 l = GetLine(tag.line.line_no - 1);
3466 if (l != null) {
3467 LineTag t;
3469 t = l.tags;
3470 while (t.next != null) {
3471 t = t.next;
3473 return t;
3476 return null;
3479 /// <summary>Retrieve the next tag; walks line boundaries</summary>
3480 internal LineTag NextTag(LineTag tag) {
3481 Line l;
3483 if (tag.next != null) {
3484 return tag.next;
3487 // Next line
3488 l = GetLine(tag.line.line_no + 1);
3489 if (l != null) {
3490 return l.tags;
3493 return null;
3496 internal Line ParagraphStart(Line line) {
3497 while (line.soft_break) {
3498 line = GetLine(line.line_no - 1);
3500 return line;
3503 internal Line ParagraphEnd(Line line) {
3504 Line l;
3506 while (line.soft_break) {
3507 l = GetLine(line.line_no + 1);
3508 if ((l == null) || (!l.soft_break)) {
3509 break;
3511 line = l;
3513 return line;
3516 /// <summary>Give it a Y pixel coordinate and it returns the Line covering that Y coordinate</summary>
3517 internal Line GetLineByPixel(int y, bool exact) {
3518 Line line = document;
3519 Line last = null;
3521 while (line != sentinel) {
3522 last = line;
3523 if ((y >= line.Y) && (y < (line.Y+line.height))) {
3524 return line;
3525 } else if (y < line.Y) {
3526 line = line.left;
3527 } else {
3528 line = line.right;
3532 if (exact) {
3533 return null;
3535 return last;
3538 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3539 internal LineTag FindTag(int x, int y, out int index, bool exact) {
3540 Line line;
3541 LineTag tag;
3543 line = GetLineByPixel(y, exact);
3544 if (line == null) {
3545 index = 0;
3546 return null;
3548 tag = line.tags;
3550 // Alignment adjustment
3551 x += line.align_shift;
3553 while (true) {
3554 if (x >= tag.X && x < (tag.X+tag.width)) {
3555 int end;
3557 end = tag.start + tag.length - 1;
3559 for (int pos = tag.start; pos < end; pos++) {
3560 if (x < line.widths[pos]) {
3561 index = pos;
3562 return tag;
3565 index=end;
3566 return tag;
3568 if (tag.next != null) {
3569 tag = tag.next;
3570 } else {
3571 if (exact) {
3572 index = 0;
3573 return null;
3576 index = line.text.Length;
3577 return tag;
3582 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3583 internal LineTag FindCursor(int x, int y, out int index) {
3584 Line line;
3585 LineTag tag;
3587 line = GetLineByPixel(y, false);
3588 tag = line.tags;
3590 // Adjust for alignment
3591 x -= line.align_shift;
3593 while (true) {
3594 if (x >= tag.X && x < (tag.X+tag.width)) {
3595 int end;
3597 end = tag.start + tag.length - 1;
3599 for (int pos = tag.start-1; pos < end; pos++) {
3600 // When clicking on a character, we position the cursor to whatever edge
3601 // of the character the click was closer
3602 if (x < (line.widths[pos] + ((line.widths[pos+1]-line.widths[pos])/2))) {
3603 index = pos;
3604 return tag;
3607 index=end;
3608 return tag;
3610 if (tag.next != null) {
3611 tag = tag.next;
3612 } else {
3613 index = line.text.Length;
3614 return tag;
3619 /// <summary>Format area of document in specified font and color</summary>
3620 /// <param name="start_pos">1-based start position on start_line</param>
3621 /// <param name="end_pos">1-based end position on end_line </param>
3622 internal void FormatText(Line start_line, int start_pos, Line end_line, int end_pos, Font font, Brush color) {
3623 Line l;
3625 // First, format the first line
3626 if (start_line != end_line) {
3627 // First line
3628 LineTag.FormatText(start_line, start_pos, start_line.text.Length - start_pos + 1, font, color);
3630 // Format last line
3631 LineTag.FormatText(end_line, 1, end_pos, font, color);
3633 // Now all the lines inbetween
3634 for (int i = start_line.line_no + 1; i < end_line.line_no; i++) {
3635 l = GetLine(i);
3636 LineTag.FormatText(l, 1, l.text.Length, font, color);
3638 } else {
3639 // Special case, single line
3640 LineTag.FormatText(start_line, start_pos, end_pos - start_pos, font, color);
3644 /// <summary>Re-format areas of the document in specified font and color</summary>
3645 /// <param name="start_pos">1-based start position on start_line</param>
3646 /// <param name="end_pos">1-based end position on end_line </param>
3647 /// <param name="font">Font specifying attributes</param>
3648 /// <param name="color">Color (or NULL) to apply</param>
3649 /// <param name="apply">Attributes from font and color to apply</param>
3650 internal void FormatText(Line start_line, int start_pos, Line end_line, int end_pos, FontDefinition attributes) {
3651 Line l;
3653 // First, format the first line
3654 if (start_line != end_line) {
3655 // First line
3656 LineTag.FormatText(start_line, start_pos, start_line.text.Length - start_pos + 1, attributes);
3658 // Format last line
3659 LineTag.FormatText(end_line, 1, end_pos - 1, attributes);
3661 // Now all the lines inbetween
3662 for (int i = start_line.line_no + 1; i < end_line.line_no; i++) {
3663 l = GetLine(i);
3664 LineTag.FormatText(l, 1, l.text.Length, attributes);
3666 } else {
3667 // Special case, single line
3668 LineTag.FormatText(start_line, start_pos, end_pos - start_pos, attributes);
3672 internal void RecalculateAlignments() {
3673 Line line;
3674 int line_no;
3676 line_no = 1;
3678 while (line_no <= lines) {
3679 line = GetLine(line_no);
3681 if (line != null && line.alignment != HorizontalAlignment.Left) {
3682 if (line.alignment == HorizontalAlignment.Center) {
3683 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3684 } else {
3685 line.align_shift = viewport_width - (int)line.widths[line.text.Length] - 1;
3689 line_no++;
3691 return;
3694 /// <summary>Calculate formatting for the whole document</summary>
3695 internal bool RecalculateDocument(Graphics g) {
3696 return RecalculateDocument(g, 1, this.lines, false);
3699 /// <summary>Calculate formatting starting at a certain line</summary>
3700 internal bool RecalculateDocument(Graphics g, int start) {
3701 return RecalculateDocument(g, start, this.lines, false);
3704 /// <summary>Calculate formatting within two given line numbers</summary>
3705 internal bool RecalculateDocument(Graphics g, int start, int end) {
3706 return RecalculateDocument(g, start, end, false);
3709 /// <summary>With optimize on, returns true if line heights changed</summary>
3710 internal bool RecalculateDocument(Graphics g, int start, int end, bool optimize) {
3711 Line line;
3712 int line_no;
3713 int Y;
3714 int new_width;
3715 bool changed;
3716 int shift;
3718 if (no_recalc) {
3719 recalc_pending = true;
3720 recalc_start = start;
3721 recalc_end = end;
3722 recalc_optimize = optimize;
3723 return false;
3726 Y = GetLine(start).Y;
3727 line_no = start;
3728 new_width = 0;
3729 shift = this.lines;
3730 if (!optimize) {
3731 changed = true; // We always return true if we run non-optimized
3732 } else {
3733 changed = false;
3736 while (line_no <= (end + this.lines - shift)) {
3737 line = GetLine(line_no++);
3738 line.Y = Y;
3740 if (!calc_pass) {
3741 if (!optimize) {
3742 line.RecalculateLine(g, this);
3743 } else {
3744 if (line.recalc && line.RecalculateLine(g, this)) {
3745 changed = true;
3746 // If the height changed, all subsequent lines change
3747 end = this.lines;
3748 shift = this.lines;
3751 } else {
3752 if (!optimize) {
3753 line.RecalculatePasswordLine(g, this);
3754 } else {
3755 if (line.recalc && line.RecalculatePasswordLine(g, this)) {
3756 changed = true;
3757 // If the height changed, all subsequent lines change
3758 end = this.lines;
3759 shift = this.lines;
3764 if (line.widths[line.text.Length] > new_width) {
3765 new_width = (int)line.widths[line.text.Length];
3768 // Calculate alignment
3769 if (line.alignment != HorizontalAlignment.Left) {
3770 if (line.alignment == HorizontalAlignment.Center) {
3771 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3772 } else {
3773 line.align_shift = viewport_width - (int)line.widths[line.text.Length] - 1;
3777 Y += line.height;
3779 if (line_no > lines) {
3780 break;
3784 if (document_x != new_width) {
3785 document_x = new_width;
3786 if (WidthChanged != null) {
3787 WidthChanged(this, null);
3791 RecalculateAlignments();
3793 line = GetLine(lines);
3795 if (document_y != line.Y + line.height) {
3796 document_y = line.Y + line.height;
3797 if (HeightChanged != null) {
3798 HeightChanged(this, null);
3801 UpdateCaret();
3802 return changed;
3805 internal int Size() {
3806 return lines;
3809 private void owner_HandleCreated(object sender, EventArgs e) {
3810 RecalculateDocument(owner.CreateGraphicsInternal());
3811 AlignCaret();
3814 private void owner_VisibleChanged(object sender, EventArgs e) {
3815 if (owner.Visible) {
3816 RecalculateDocument(owner.CreateGraphicsInternal());
3820 internal static bool IsWordSeparator(char ch) {
3821 switch(ch) {
3822 case ' ':
3823 case '\t':
3824 case '(':
3825 case ')': {
3826 return true;
3829 default: {
3830 return false;
3834 internal int FindWordSeparator(Line line, int pos, bool forward) {
3835 int len;
3837 len = line.text.Length;
3839 if (forward) {
3840 for (int i = pos + 1; i < len; i++) {
3841 if (IsWordSeparator(line.Text[i])) {
3842 return i + 1;
3845 return len;
3846 } else {
3847 for (int i = pos - 1; i > 0; i--) {
3848 if (IsWordSeparator(line.Text[i - 1])) {
3849 return i;
3852 return 0;
3856 /* Search document for text */
3857 internal bool FindChars(char[] chars, Marker start, Marker end, out Marker result) {
3858 Line line;
3859 int line_no;
3860 int pos;
3861 int line_len;
3863 // Search for occurence of any char in the chars array
3864 result = new Marker();
3866 line = start.line;
3867 line_no = start.line.line_no;
3868 pos = start.pos;
3869 while (line_no <= end.line.line_no) {
3870 line_len = line.text.Length;
3871 while (pos < line_len) {
3872 for (int i = 0; i < chars.Length; i++) {
3873 if (line.text[pos] == chars[i]) {
3874 // Special case
3875 if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
3876 return false;
3879 result.line = line;
3880 result.pos = pos;
3881 return true;
3884 pos++;
3887 pos = 0;
3888 line_no++;
3889 line = GetLine(line_no);
3892 return false;
3895 // This version does not build one big string for searching, instead it handles
3896 // line-boundaries, which is faster and less memory intensive
3897 // FIXME - Depending on culture stuff we might have to create a big string and use culturespecific
3898 // search stuff and change it to accept and return positions instead of Markers (which would match
3899 // RichTextBox behaviour better but would be inconsistent with the rest of TextControl)
3900 internal bool Find(string search, Marker start, Marker end, out Marker result, RichTextBoxFinds options) {
3901 Marker last;
3902 string search_string;
3903 Line line;
3904 int line_no;
3905 int pos;
3906 int line_len;
3907 int current;
3908 bool word;
3909 bool word_option;
3910 bool ignore_case;
3911 bool reverse;
3912 char c;
3914 result = new Marker();
3915 word_option = ((options & RichTextBoxFinds.WholeWord) != 0);
3916 ignore_case = ((options & RichTextBoxFinds.MatchCase) == 0);
3917 reverse = ((options & RichTextBoxFinds.Reverse) != 0);
3919 line = start.line;
3920 line_no = start.line.line_no;
3921 pos = start.pos;
3922 current = 0;
3924 // Prep our search string, lowercasing it if we do case-independent matching
3925 if (ignore_case) {
3926 StringBuilder sb;
3927 sb = new StringBuilder(search);
3928 for (int i = 0; i < sb.Length; i++) {
3929 sb[i] = Char.ToLower(sb[i]);
3931 search_string = sb.ToString();
3932 } else {
3933 search_string = search;
3936 // We need to check if the character before our start position is a wordbreak
3937 if (word_option) {
3938 if (line_no == 1) {
3939 if ((pos == 0) || (IsWordSeparator(line.text[pos - 1]))) {
3940 word = true;
3941 } else {
3942 word = false;
3944 } else {
3945 if (pos > 0) {
3946 if (IsWordSeparator(line.text[pos - 1])) {
3947 word = true;
3948 } else {
3949 word = false;
3951 } else {
3952 // Need to check the end of the previous line
3953 Line prev_line;
3955 prev_line = GetLine(line_no - 1);
3956 if (prev_line.soft_break) {
3957 if (IsWordSeparator(prev_line.text[prev_line.text.Length - 1])) {
3958 word = true;
3959 } else {
3960 word = false;
3962 } else {
3963 word = true;
3967 } else {
3968 word = false;
3971 // To avoid duplication of this loop with reverse logic, we search
3972 // through the document, remembering the last match and when returning
3973 // report that last remembered match
3975 last = new Marker();
3976 last.height = -1; // Abused - we use it to track change
3978 while (line_no <= end.line.line_no) {
3979 if (line_no != end.line.line_no) {
3980 line_len = line.text.Length;
3981 } else {
3982 line_len = end.pos;
3985 while (pos < line_len) {
3986 if (word_option && (current == search_string.Length)) {
3987 if (IsWordSeparator(line.text[pos])) {
3988 if (!reverse) {
3989 goto FindFound;
3990 } else {
3991 last = result;
3992 current = 0;
3994 } else {
3995 current = 0;
3999 if (ignore_case) {
4000 c = Char.ToLower(line.text[pos]);
4001 } else {
4002 c = line.text[pos];
4005 if (c == search_string[current]) {
4006 if (current == 0) {
4007 result.line = line;
4008 result.pos = pos;
4010 if (!word_option || (word_option && (word || (current > 0)))) {
4011 current++;
4014 if (!word_option && (current == search_string.Length)) {
4015 if (!reverse) {
4016 goto FindFound;
4017 } else {
4018 last = result;
4019 current = 0;
4022 } else {
4023 current = 0;
4025 pos++;
4027 if (!word_option) {
4028 continue;
4031 if (IsWordSeparator(c)) {
4032 word = true;
4033 } else {
4034 word = false;
4038 if (word_option) {
4039 // Mark that we just saw a word boundary
4040 if (!line.soft_break) {
4041 word = true;
4044 if (current == search_string.Length) {
4045 if (word) {
4046 if (!reverse) {
4047 goto FindFound;
4048 } else {
4049 last = result;
4050 current = 0;
4052 } else {
4053 current = 0;
4058 pos = 0;
4059 line_no++;
4060 line = GetLine(line_no);
4063 if (reverse) {
4064 if (last.height != -1) {
4065 result = last;
4066 return true;
4070 return false;
4072 FindFound:
4073 if (!reverse) {
4074 // if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
4075 // return false;
4076 // }
4077 return true;
4080 result = last;
4081 return true;
4085 /* Marker stuff */
4086 internal void GetMarker(out Marker mark, bool start) {
4087 mark = new Marker();
4089 if (start) {
4090 mark.line = GetLine(1);
4091 mark.tag = mark.line.tags;
4092 mark.pos = 0;
4093 } else {
4094 mark.line = GetLine(lines);
4095 mark.tag = mark.line.tags;
4096 while (mark.tag.next != null) {
4097 mark.tag = mark.tag.next;
4099 mark.pos = mark.line.text.Length;
4102 #endregion // Internal Methods
4104 #region Events
4105 internal event EventHandler CaretMoved;
4106 internal event EventHandler WidthChanged;
4107 internal event EventHandler HeightChanged;
4108 internal event EventHandler LengthChanged;
4109 #endregion // Events
4111 #region Administrative
4112 public IEnumerator GetEnumerator() {
4113 // FIXME
4114 return null;
4117 public override bool Equals(object obj) {
4118 if (obj == null) {
4119 return false;
4122 if (!(obj is Document)) {
4123 return false;
4126 if (obj == this) {
4127 return true;
4130 if (ToString().Equals(((Document)obj).ToString())) {
4131 return true;
4134 return false;
4137 public override int GetHashCode() {
4138 return document_id;
4141 public override string ToString() {
4142 return "document " + this.document_id;
4144 #endregion // Administrative
4147 internal class LineTag {
4148 #region Local Variables;
4149 // Payload; formatting
4150 internal Font font; // System.Drawing.Font object for this tag
4151 internal Brush color; // System.Drawing.Brush object
4153 // Payload; text
4154 internal int start; // start, in chars; index into Line.text
4155 internal int length; // length, in chars
4156 internal bool r_to_l; // Which way is the font
4158 // Drawing support
4159 internal int height; // Height in pixels of the text this tag describes
4160 internal int X; // X location of the text this tag describes
4161 internal float width; // Width in pixels of the text this tag describes
4162 internal int ascent; // Ascent of the font for this tag
4163 internal int shift; // Shift down for this tag, to stay on baseline
4165 // Administrative
4166 internal Line line; // The line we're on
4167 internal LineTag next; // Next tag on the same line
4168 internal LineTag previous; // Previous tag on the same line
4169 #endregion;
4171 #region Constructors
4172 internal LineTag(Line line, int start, int length) {
4173 this.line = line;
4174 this.start = start;
4175 this.length = length;
4176 this.X = 0;
4177 this.width = 0;
4179 #endregion // Constructors
4181 #region Internal Methods
4182 ///<summary>Break a tag into two with identical attributes; pos is 1-based; returns tag starting at &gt;pos&lt; or null if end-of-line</summary>
4183 internal LineTag Break(int pos) {
4184 LineTag new_tag;
4186 // Sanity
4187 if (pos == this.start) {
4188 return this;
4189 } else if (pos >= (start + length)) {
4190 return null;
4193 new_tag = new LineTag(line, pos, start + length - pos);
4194 new_tag.color = color;
4195 new_tag.font = font;
4196 this.length -= new_tag.length;
4197 new_tag.next = this.next;
4198 this.next = new_tag;
4199 new_tag.previous = this;
4200 if (new_tag.next != null) {
4201 new_tag.next.previous = new_tag;
4204 return new_tag;
4207 ///<summary>Create new font and brush from existing font and given new attributes. Returns true if fontheight changes</summary>
4208 internal static bool GenerateTextFormat(Font font_from, Brush color_from, FontDefinition attributes, out Font new_font, out Brush new_color) {
4209 float size;
4210 string face;
4211 FontStyle style;
4212 GraphicsUnit unit;
4214 if (attributes.font_obj == null) {
4215 size = font_from.SizeInPoints;
4216 unit = font_from.Unit;
4217 face = font_from.Name;
4218 style = font_from.Style;
4220 if (attributes.face != null) {
4221 face = attributes.face;
4224 if (attributes.size != 0) {
4225 size = attributes.size;
4228 style |= attributes.add_style;
4229 style &= ~attributes.remove_style;
4231 // Create new font
4232 new_font = new Font(face, size, style, unit);
4233 } else {
4234 new_font = attributes.font_obj;
4237 // Create 'new' color brush
4238 if (attributes.color != Color.Empty) {
4239 new_color = new SolidBrush(attributes.color);
4240 } else {
4241 new_color = color_from;
4244 if (new_font.Height == font_from.Height) {
4245 return false;
4247 return true;
4250 /// <summary>Applies 'font' and 'brush' to characters starting at 'start' for 'length' chars;
4251 /// Removes any previous tags overlapping the same area;
4252 /// returns true if lineheight has changed</summary>
4253 /// <param name="start">1-based character position on line</param>
4254 internal static bool FormatText(Line line, int start, int length, Font font, Brush color) {
4255 LineTag tag;
4256 LineTag start_tag;
4257 int end;
4258 bool retval = false; // Assume line-height doesn't change
4260 // Too simple?
4261 if (font.Height != line.height) {
4262 retval = true;
4264 line.recalc = true; // This forces recalculation of the line in RecalculateDocument
4266 // A little sanity, not sure if it's needed, might be able to remove for speed
4267 if (length > line.text.Length) {
4268 length = line.text.Length;
4271 tag = line.tags;
4272 end = start + length;
4274 // Common special case
4275 if ((start == 1) && (length == tag.length)) {
4276 tag.ascent = 0;
4277 tag.font = font;
4278 tag.color = color;
4279 return retval;
4282 //Console.WriteLine("Finding tag for {0} {1}", line, start);
4283 start_tag = FindTag(line, start);
4284 if (start_tag == null) { // FIXME - is there a better way to handle this, or do we even need it?
4285 throw new Exception(String.Format("Could not find start_tag in document at line {0} position {1}", line.line_no, start));
4288 tag = new LineTag(line, start, length);
4289 tag.font = font;
4290 tag.color = color;
4292 if (start == 1) {
4293 line.tags = tag;
4295 //Console.WriteLine("Start tag: '{0}'", start_tag!=null ? start_tag.ToString() : "NULL");
4296 if (start_tag.start == start) {
4297 tag.next = start_tag;
4298 tag.previous = start_tag.previous;
4299 if (start_tag.previous != null) {
4300 start_tag.previous.next = tag;
4302 start_tag.previous = tag;
4303 } else {
4304 // Insert ourselves 'in the middle'
4305 if ((start_tag.next != null) && (start_tag.next.start < end)) {
4306 tag.next = start_tag.next;
4307 } else {
4308 tag.next = new LineTag(line, start_tag.start, start_tag.length);
4309 tag.next.font = start_tag.font;
4310 tag.next.color = start_tag.color;
4312 if (start_tag.next != null) {
4313 tag.next.next = start_tag.next;
4314 tag.next.next.previous = tag.next;
4317 tag.next.previous = tag;
4319 start_tag.length = start - start_tag.start;
4321 tag.previous = start_tag;
4322 start_tag.next = tag;
4323 #if nope
4324 if (tag.next.start > (tag.start + tag.length)) {
4325 tag.next.length += tag.next.start - (tag.start + tag.length);
4326 tag.next.start = tag.start + tag.length;
4328 #endif
4331 // Elimination loop
4332 tag = tag.next;
4333 while ((tag != null) && (tag.start < end)) {
4334 if ((tag.start + tag.length) <= end) {
4335 // remove the tag
4336 tag.previous.next = tag.next;
4337 if (tag.next != null) {
4338 tag.next.previous = tag.previous;
4340 tag = tag.previous;
4341 } else {
4342 // Adjust the length of the tag
4343 tag.length = (tag.start + tag.length) - end;
4344 tag.start = end;
4346 tag = tag.next;
4349 return retval;
4352 /// <summary>Applies font attributes specified to characters starting at 'start' for 'length' chars;
4353 /// Breaks tags at start and end point, keeping middle tags with altered attributes.
4354 /// Returns true if lineheight has changed</summary>
4355 /// <param name="start">1-based character position on line</param>
4356 internal static bool FormatText(Line line, int start, int length, FontDefinition attributes) {
4357 LineTag tag;
4358 LineTag start_tag;
4359 LineTag end_tag;
4360 bool retval = false; // Assume line-height doesn't change
4362 line.recalc = true; // This forces recalculation of the line in RecalculateDocument
4364 // A little sanity, not sure if it's needed, might be able to remove for speed
4365 if (length > line.text.Length) {
4366 length = line.text.Length;
4369 tag = line.tags;
4371 // Common special case
4372 if ((start == 1) && (length == tag.length)) {
4373 tag.ascent = 0;
4374 GenerateTextFormat(tag.font, tag.color, attributes, out tag.font, out tag.color);
4375 return retval;
4378 start_tag = FindTag(line, start);
4380 if (start_tag == null) {
4381 if (length == 0) {
4382 // We are 'starting' after all valid tags; create a new tag with the right attributes
4383 start_tag = FindTag(line, line.text.Length - 1);
4384 start_tag.next = new LineTag(line, line.text.Length + 1, 0);
4385 start_tag.next.font = start_tag.font;
4386 start_tag.next.color = start_tag.color;
4387 start_tag.next.previous = start_tag;
4388 start_tag = start_tag.next;
4389 } else {
4390 throw new Exception(String.Format("Could not find start_tag in document at line {0} position {1}", line.line_no, start));
4392 } else {
4393 start_tag = start_tag.Break(start);
4396 end_tag = FindTag(line, start + length);
4397 if (end_tag != null) {
4398 end_tag = end_tag.Break(start + length);
4401 // start_tag or end_tag might be null; we're cool with that
4402 // we now walk from start_tag to end_tag, applying new attributes
4403 tag = start_tag;
4404 while ((tag != null) && tag != end_tag) {
4405 if (LineTag.GenerateTextFormat(tag.font, tag.color, attributes, out tag.font, out tag.color)) {
4406 retval = true;
4408 tag = tag.next;
4410 return retval;
4414 /// <summary>Finds the tag that describes the character at position 'pos' on 'line'</summary>
4415 internal static LineTag FindTag(Line line, int pos) {
4416 LineTag tag = line.tags;
4418 // Beginning of line is a bit special
4419 if (pos == 0) {
4420 return tag;
4423 while (tag != null) {
4424 if ((tag.start <= pos) && (pos < (tag.start+tag.length))) {
4425 return tag;
4428 tag = tag.next;
4431 return null;
4434 /// <summary>Combines 'this' tag with 'other' tag</summary>
4435 internal bool Combine(LineTag other) {
4436 if (!this.Equals(other)) {
4437 return false;
4440 this.width += other.width;
4441 this.length += other.length;
4442 this.next = other.next;
4443 if (this.next != null) {
4444 this.next.previous = this;
4447 return true;
4451 /// <summary>Remove 'this' tag ; to be called when formatting is to be removed</summary>
4452 internal bool Remove() {
4453 if ((this.start == 1) && (this.next == null)) {
4454 // We cannot remove the only tag
4455 return false;
4457 if (this.start != 1) {
4458 this.previous.length += this.length;
4459 this.previous.width = -1;
4460 this.previous.next = this.next;
4461 this.next.previous = this.previous;
4462 } else {
4463 this.next.start = 1;
4464 this.next.length += this.length;
4465 this.next.width = -1;
4466 this.line.tags = this.next;
4467 this.next.previous = null;
4469 return true;
4473 /// <summary>Checks if 'this' tag describes the same formatting options as 'obj'</summary>
4474 public override bool Equals(object obj) {
4475 LineTag other;
4477 if (obj == null) {
4478 return false;
4481 if (!(obj is LineTag)) {
4482 return false;
4485 if (obj == this) {
4486 return true;
4489 other = (LineTag)obj;
4491 if (this.font.Equals(other.font) && this.color.Equals(other.color)) { // FIXME add checking for things like link or type later
4492 return true;
4495 return false;
4498 public override int GetHashCode() {
4499 return base.GetHashCode ();
4502 public override string ToString() {
4503 return "Tag starts at index " + this.start + "length " + this.length + " text: " + this.line.Text.Substring(this.start-1, this.length) + "Font " + this.font.ToString();
4506 #endregion // Internal Methods
4509 internal class UndoClass {
4510 internal enum ActionType {
4511 InsertChar,
4512 InsertString,
4513 DeleteChar,
4514 DeleteChars,
4515 CursorMove,
4516 Mark,
4519 internal class Action {
4520 internal ActionType type;
4521 internal int line_no;
4522 internal int pos;
4523 internal object data;
4526 #region Local Variables
4527 private Document document;
4528 private Stack undo_actions;
4529 private Stack redo_actions;
4530 private int caret_line;
4531 private int caret_pos;
4532 #endregion // Local Variables
4534 #region Constructors
4535 internal UndoClass(Document doc) {
4536 document = doc;
4537 undo_actions = new Stack(50);
4538 redo_actions = new Stack(50);
4540 #endregion // Constructors
4542 #region Properties
4543 [MonoTODO("Change this to be configurable")]
4544 internal int UndoLevels {
4545 get {
4546 return undo_actions.Count;
4550 [MonoTODO("Change this to be configurable")]
4551 internal int RedoLevels {
4552 get {
4553 return redo_actions.Count;
4557 [MonoTODO("Come up with good naming and localization")]
4558 internal string UndoName {
4559 get {
4560 Action action;
4562 action = (Action)undo_actions.Peek();
4563 switch(action.type) {
4564 case ActionType.InsertChar: {
4565 Locale.GetText("Insert character");
4566 break;
4569 case ActionType.DeleteChar: {
4570 Locale.GetText("Delete character");
4571 break;
4574 case ActionType.InsertString: {
4575 Locale.GetText("Insert string");
4576 break;
4579 case ActionType.DeleteChars: {
4580 Locale.GetText("Delete string");
4581 break;
4584 case ActionType.CursorMove: {
4585 Locale.GetText("Cursor move");
4586 break;
4589 return null;
4593 internal string RedoName() {
4594 return null;
4596 #endregion // Properties
4598 #region Internal Methods
4599 internal void Clear() {
4600 undo_actions.Clear();
4601 redo_actions.Clear();
4604 internal void Undo() {
4605 Action action;
4607 if (undo_actions.Count == 0) {
4608 return;
4611 action = (Action)undo_actions.Pop();
4613 // Put onto redo stack
4614 redo_actions.Push(action);
4616 // Do the thing
4617 switch(action.type) {
4618 case ActionType.InsertChar: {
4619 // FIXME - implement me
4620 break;
4623 case ActionType.DeleteChars: {
4624 this.Insert(document.GetLine(action.line_no), action.pos, (Line)action.data);
4625 Undo(); // Grab the cursor location
4626 break;
4629 case ActionType.CursorMove: {
4630 document.caret.line = document.GetLine(action.line_no);
4631 if (document.caret.line == null) {
4632 Undo();
4633 break;
4636 document.caret.tag = document.caret.line.FindTag(action.pos);
4637 document.caret.pos = action.pos;
4638 document.caret.height = document.caret.tag.height;
4640 if (document.owner.IsHandleCreated) {
4641 XplatUI.DestroyCaret(document.owner.Handle);
4642 XplatUI.CreateCaret(document.owner.Handle, 2, document.caret.height);
4643 XplatUI.SetCaretPos(document.owner.Handle, (int)document.caret.tag.line.widths[document.caret.pos] + document.caret.line.align_shift - document.viewport_x, document.caret.line.Y + document.caret.tag.shift - document.viewport_y + Document.caret_shift);
4644 XplatUI.CaretVisible(document.owner.Handle, true);
4647 // FIXME - enable call
4648 //if (document.CaretMoved != null) document.CaretMoved(this, EventArgs.Empty);
4649 break;
4654 internal void Redo() {
4655 if (redo_actions.Count == 0) {
4656 return;
4659 #endregion // Internal Methods
4661 #region Private Methods
4662 // pos = 1-based
4663 public void RecordDeleteChars(Line line, int pos, int length) {
4664 RecordDelete(line, pos, line, pos + length - 1);
4667 // start_pos, end_pos = 1 based
4668 public void RecordDelete(Line start_line, int start_pos, Line end_line, int end_pos) {
4669 Line l;
4670 Action a;
4672 l = Duplicate(start_line, start_pos, end_line, end_pos);
4674 a = new Action();
4675 a.type = ActionType.DeleteChars;
4676 a.data = l;
4677 a.line_no = start_line.line_no;
4678 a.pos = start_pos - 1;
4680 // Record the cursor position before, since the actions will occur in reverse order
4681 RecordCursor();
4682 undo_actions.Push(a);
4685 public void RecordCursor() {
4686 if (document.caret.line == null) {
4687 return;
4690 RecordCursor(document.caret.line, document.caret.pos);
4693 public void RecordCursor(Line line, int pos) {
4694 Action a;
4696 if ((line.line_no == caret_line) && (pos == caret_pos)) {
4697 return;
4700 caret_line = line.line_no;
4701 caret_pos = pos;
4703 a = new Action();
4704 a.type = ActionType.CursorMove;
4705 a.line_no = line.line_no;
4706 a.pos = pos;
4708 undo_actions.Push(a);
4711 // start_pos = 1-based
4712 // end_pos = 1-based
4713 public Line Duplicate(Line start_line, int start_pos, Line end_line, int end_pos) {
4714 Line ret;
4715 Line line;
4716 Line current;
4717 LineTag tag;
4718 LineTag current_tag;
4719 int start;
4720 int end;
4721 int tag_start;
4722 int tag_length;
4724 line = new Line();
4725 ret = line;
4727 for (int i = start_line.line_no; i <= end_line.line_no; i++) {
4728 current = document.GetLine(i);
4730 if (start_line.line_no == i) {
4731 start = start_pos;
4732 } else {
4733 start = 1;
4736 if (end_line.line_no == i) {
4737 end = end_pos;
4738 } else {
4739 end = current.text.Length;
4742 // Text for the tag
4743 line.text = new StringBuilder(current.text.ToString(start - 1, end - start + 1));
4745 // Copy tags from start to start+length onto new line
4746 current_tag = current.FindTag(start - 1);
4747 while ((current_tag != null) && (current_tag.start < end)) {
4748 if ((current_tag.start <= start) && (start < (current_tag.start + current_tag.length))) {
4749 // start tag is within this tag
4750 tag_start = start;
4751 } else {
4752 tag_start = current_tag.start;
4755 if (end < (current_tag.start + current_tag.length)) {
4756 tag_length = end - tag_start + 1;
4757 } else {
4758 tag_length = current_tag.start + current_tag.length - tag_start;
4760 tag = new LineTag(line, tag_start - start + 1, tag_length);
4761 tag.color = current_tag.color;
4762 tag.font = current_tag.font;
4764 current_tag = current_tag.next;
4766 // Add the new tag to the line
4767 if (line.tags == null) {
4768 line.tags = tag;
4769 } else {
4770 LineTag tail;
4771 tail = line.tags;
4773 while (tail.next != null) {
4774 tail = tail.next;
4776 tail.next = tag;
4777 tag.previous = tail;
4781 if ((i + 1) <= end_line.line_no) {
4782 line.soft_break = current.soft_break;
4784 // Chain them (we use right/left as next/previous)
4785 line.right = new Line();
4786 line.right.left = line;
4787 line = line.right;
4791 return ret;
4794 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
4795 internal void Insert(Line line, int pos, Line insert) {
4796 Line current;
4797 LineTag tag;
4798 int offset;
4799 int lines;
4800 Line first;
4802 // Handle special case first
4803 if (insert.right == null) {
4805 // Single line insert
4806 document.Split(line, pos);
4808 if (insert.tags == null) {
4809 return; // Blank line
4812 //Insert our tags at the end
4813 tag = line.tags;
4815 while (tag.next != null) {
4816 tag = tag.next;
4819 offset = tag.start + tag.length - 1;
4821 tag.next = insert.tags;
4822 line.text.Insert(offset, insert.text.ToString());
4824 // Adjust start locations
4825 tag = tag.next;
4826 while (tag != null) {
4827 tag.start += offset;
4828 tag.line = line;
4829 tag = tag.next;
4831 // Put it back together
4832 document.Combine(line.line_no, line.line_no + 1);
4833 document.UpdateView(line, pos);
4834 return;
4837 first = line;
4838 lines = 1;
4839 current = insert;
4840 while (current != null) {
4841 if (current == insert) {
4842 // Inserting the first line we split the line (and make space)
4843 document.Split(line, pos);
4844 //Insert our tags at the end of the line
4845 tag = line.tags;
4847 if (tag != null) {
4848 while (tag.next != null) {
4849 tag = tag.next;
4851 offset = tag.start + tag.length - 1;
4852 tag.next = current.tags;
4853 tag.next.previous = tag;
4855 tag = tag.next;
4857 } else {
4858 offset = 0;
4859 line.tags = current.tags;
4860 line.tags.previous = null;
4861 tag = line.tags;
4863 } else {
4864 document.Split(line.line_no, 0);
4865 offset = 0;
4866 line.tags = current.tags;
4867 line.tags.previous = null;
4868 tag = line.tags;
4870 // Adjust start locations and line pointers
4871 while (tag != null) {
4872 tag.start += offset;
4873 tag.line = line;
4874 tag = tag.next;
4877 line.text.Insert(offset, current.text.ToString());
4878 line.Grow(line.text.Length);
4880 line.recalc = true;
4881 line = document.GetLine(line.line_no + 1);
4883 // FIXME? Test undo of line-boundaries
4884 if ((current.right == null) && (current.tags.length != 0)) {
4885 document.Combine(line.line_no - 1, line.line_no);
4887 current = current.right;
4888 lines++;
4892 // Recalculate our document
4893 document.UpdateView(first, lines, pos);
4894 return;
4896 #endregion // Private Methods