* TextControl.cs: Make PageUp and PageDown more like the
[mono-project.git] / mcs / class / Managed.Windows.Forms / System.Windows.Forms / TextControl.cs
blob64c0b8e11119fbaca5e2da835821423c18a85249
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
98 CharForwardNoWrap, // Move a char forward, but don't wrap onto the next line
99 CharBackNoWrap // Move a char backward, but don't wrap onto the previous line
102 // Being cloneable should allow for nice line and document copies...
103 internal class Line : ICloneable, IComparable {
104 #region Local Variables
105 // Stuff that matters for our line
106 internal StringBuilder text; // Characters for the line
107 internal float[] widths; // Width of each character; always one larger than text.Length
108 internal int space; // Number of elements in text and widths
109 internal int line_no; // Line number
110 internal LineTag tags; // Tags describing the text
111 internal int Y; // Baseline
112 internal int height; // Height of the line (height of tallest tag)
113 internal int ascent; // Ascent of the line (ascent of the tallest tag)
114 internal HorizontalAlignment alignment; // Alignment of the line
115 internal int align_shift; // Pixel shift caused by the alignment
116 internal bool soft_break; // Tag is 'broken soft' and continuation from previous line
117 internal int indent; // Left indent for the first line
118 internal int hanging_indent; // Hanging indent (left indent for all but the first line)
119 internal int right_indent; // Right indent for all lines
122 // Stuff that's important for the tree
123 internal Line parent; // Our parent line
124 internal Line left; // Line with smaller line number
125 internal Line right; // Line with higher line number
126 internal LineColor color; // We're doing a black/red tree. this is the node color
127 internal int DEFAULT_TEXT_LEN; //
128 internal static StringFormat string_format; // For calculating widths/heights
129 internal bool recalc; // Line changed
130 #endregion // Local Variables
132 #region Constructors
133 internal Line() {
134 color = LineColor.Red;
135 left = null;
136 right = null;
137 parent = null;
138 text = null;
139 recalc = true;
140 soft_break = false;
141 alignment = HorizontalAlignment.Left;
143 if (string_format == null) {
144 string_format = new StringFormat(StringFormat.GenericTypographic);
145 string_format.Trimming = StringTrimming.None;
146 string_format.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;
150 internal Line(int LineNo, string Text, Font font, Brush color) : this() {
151 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
153 text = new StringBuilder(Text, space);
154 line_no = LineNo;
156 widths = new float[space + 1];
157 tags = new LineTag(this, 1, text.Length);
158 tags.font = font;
159 tags.color = color;
162 internal Line(int LineNo, string Text, HorizontalAlignment align, Font font, Brush color) : this() {
163 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
165 text = new StringBuilder(Text, space);
166 line_no = LineNo;
167 alignment = align;
169 widths = new float[space + 1];
170 tags = new LineTag(this, 1, text.Length);
171 tags.font = font;
172 tags.color = color;
175 internal Line(int LineNo, string Text, LineTag tag) : this() {
176 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
178 text = new StringBuilder(Text, space);
179 line_no = LineNo;
181 widths = new float[space + 1];
182 tags = tag;
185 #endregion // Constructors
187 #region Internal Properties
188 internal int Indent {
189 get {
190 return indent;
193 set {
194 indent = value;
195 recalc = true;
199 internal int HangingIndent {
200 get {
201 return hanging_indent;
204 set {
205 hanging_indent = value;
206 recalc = true;
210 internal int RightIndent {
211 get {
212 return right_indent;
215 set {
216 right_indent = value;
217 recalc = true;
222 internal int Height {
223 get {
224 return height;
227 set {
228 height = value;
232 internal int LineNo {
233 get {
234 return line_no;
237 set {
238 line_no = value;
242 internal string Text {
243 get {
244 return text.ToString();
247 set {
248 text = new StringBuilder(value, value.Length > DEFAULT_TEXT_LEN ? value.Length : DEFAULT_TEXT_LEN);
252 internal HorizontalAlignment Alignment {
253 get {
254 return alignment;
257 set {
258 if (alignment != value) {
259 alignment = value;
260 recalc = true;
264 #if no
265 internal StringBuilder Text {
266 get {
267 return text;
270 set {
271 text = value;
274 #endif
275 #endregion // Internal Properties
277 #region Internal Methods
278 // Make sure we always have enoughs space in text and widths
279 internal void Grow(int minimum) {
280 int length;
281 float[] new_widths;
283 length = text.Length;
285 if ((length + minimum) > space) {
286 // We need to grow; double the size
288 if ((length + minimum) > (space * 2)) {
289 new_widths = new float[length + minimum * 2 + 1];
290 space = length + minimum * 2;
291 } else {
292 new_widths = new float[space * 2 + 1];
293 space *= 2;
295 widths.CopyTo(new_widths, 0);
297 widths = new_widths;
301 internal void Streamline(int lines) {
302 LineTag current;
303 LineTag next;
305 current = this.tags;
306 next = current.next;
308 // Catch what the loop below wont; eliminate 0 length
309 // tags, but only if there are other tags after us
310 while ((current.length == 0) && (next != null)) {
311 tags = next;
312 tags.previous = null;
313 current = next;
314 next = current.next;
317 if (next == null) {
318 return;
321 while (next != null) {
322 // Take out 0 length tags unless it's the last tag in the document
323 if (next.length == 0) {
324 if ((next.next != null) || (line_no != lines)) {
325 current.next = next.next;
326 if (current.next != null) {
327 current.next.previous = current;
329 next = current.next;
330 continue;
333 if (current.Combine(next)) {
334 next = current.next;
335 continue;
338 current = current.next;
339 next = current.next;
343 /// <summary> Find the tag on a line based on the character position, pos is 0-based</summary>
344 internal LineTag FindTag(int pos) {
345 LineTag tag;
347 if (pos == 0) {
348 return tags;
351 tag = this.tags;
353 if (pos >= text.Length) {
354 pos = text.Length - 1;
357 while (tag != null) {
358 if (((tag.start - 1) <= pos) && (pos < (tag.start + tag.length - 1))) {
359 return LineTag.GetFinalTag (tag);
361 tag = tag.next;
363 return null;
366 /// <summary>
367 /// Recalculate a single line using the same char for every character in the line
368 /// </summary>
370 internal bool RecalculatePasswordLine(Graphics g, Document doc) {
371 LineTag tag;
372 int pos;
373 int len;
374 float w;
375 bool ret;
376 int descent;
378 pos = 0;
379 len = this.text.Length;
380 tag = this.tags;
381 ascent = 0;
382 tag.shift = 0;
383 tag.width = 0;
385 this.recalc = false;
386 widths[0] = indent;
387 tag.X = indent;
389 w = g.MeasureString(doc.password_char, tags.font, 10000, string_format).Width;
391 if (this.height != (int)tag.font.Height) {
392 ret = true;
393 } else {
394 ret = false;
397 this.height = (int)tag.font.Height;
398 tag.height = this.height;
400 XplatUI.GetFontMetrics(g, tag.font, out tag.ascent, out descent);
401 this.ascent = tag.ascent;
403 while (pos < len) {
404 tag.width += w;
405 pos++;
406 widths[pos] = widths[pos-1] + w;
409 return ret;
412 /// <summary>
413 /// Go through all tags on a line and recalculate all size-related values;
414 /// returns true if lineheight changed
415 /// </summary>
416 internal bool RecalculateLine(Graphics g, Document doc) {
417 LineTag tag;
418 int pos;
419 int len;
420 SizeF size;
421 float w;
422 int prev_height;
423 bool retval;
424 bool wrapped;
425 Line line;
426 int wrap_pos;
427 float wrap_width;
429 pos = 0;
430 len = this.text.Length;
431 tag = this.tags;
432 prev_height = this.height; // For drawing optimization calculations
433 this.height = 0; // Reset line height
434 this.ascent = 0; // Reset the ascent for the line
435 tag.shift = 0;
436 tag.width = 0;
438 if (this.soft_break) {
439 widths[0] = hanging_indent;
440 } else {
441 widths[0] = indent;
444 this.recalc = false;
445 retval = false;
446 wrapped = false;
448 wrap_pos = 0;
449 wrap_width = 0;
451 while (pos < len) {
452 size = g.MeasureString(this.text.ToString(pos, 1), tag.font, 10000, string_format);
454 while (tag.length == 0) { // We should always have tags after a tag.length==0 unless len==0
455 tag.width = 0;
456 tag.ascent = 0;
457 if (tag.previous != null) {
458 tag.X = tag.previous.X;
459 } else {
460 tag.X = (int)widths[pos];
462 tag = tag.next;
463 tag.width = 0;
464 tag.shift = 0;
467 w = size.Width;
469 if (Char.IsWhiteSpace(text[pos])) {
470 wrap_pos = pos + 1;
471 wrap_width = tag.width + w;
474 if (doc.wrap) {
475 if ((wrap_pos > 0) && (wrap_pos != len) && (widths[pos] + w) + 27 > (doc.viewport_width - this.right_indent)) {
476 pos = wrap_pos;
477 tag.width = wrap_width;
478 doc.Split(this, tag, pos, true);
479 len = this.text.Length;
480 retval = true;
481 wrapped = true;
482 } else if ((widths[pos] + w) > (doc.viewport_width - this.right_indent)) {
483 // No suitable wrap position was found so break right in the middle of a word
484 tag.width = tag.width + w;
485 doc.Split(this, tag, pos, true);
486 len = this.text.Length;
487 retval = true;
488 wrapped = true;
492 // Contract all soft lines that follow back into our line
493 if (!wrapped) {
494 tag.width += w;
496 pos++;
498 widths[pos] = widths[pos-1] + w;
500 if (pos == len) {
501 line = doc.GetLine(this.line_no + 1);
502 if ((line != null) && (line.soft_break)) {
503 // Pull the previous line back into this one
504 doc.Combine(this.line_no, this.line_no + 1);
505 len = this.text.Length;
506 retval = true;
511 if (pos == (tag.start-1 + tag.length)) {
512 // We just found the end of our current tag
513 tag.height = (int)tag.font.Height;
515 // Check if we're the tallest on the line (so far)
516 if (tag.height > this.height) {
517 this.height = tag.height; // Yep; make sure the line knows
520 if (tag.ascent == 0) {
521 int descent;
523 XplatUI.GetFontMetrics(g, tag.font, out tag.ascent, out descent);
526 if (tag.ascent > this.ascent) {
527 LineTag t;
529 // We have a tag that has a taller ascent than the line;
531 t = tags;
532 while (t != tag) {
533 t.shift = tag.ascent - t.ascent;
534 t = t.next;
537 // Save on our line
538 this.ascent = tag.ascent;
539 } else {
540 tag.shift = this.ascent - tag.ascent;
543 // Update our horizontal starting pixel position
544 if (tag.previous == null) {
545 tag.X = (int)widths[0];
546 } else {
547 tag.X = tag.previous.X + (int)tag.previous.width;
550 tag = tag.next;
551 if (tag != null) {
552 tag.width = 0;
553 tag.shift = 0;
554 wrap_pos = pos;
555 wrap_width = tag.width;
560 if (this.height == 0) {
561 this.height = tags.font.Height;
562 tag.height = this.height;
565 if (prev_height != this.height) {
566 retval = true;
568 return retval;
570 #endregion // Internal Methods
572 #region Administrative
573 public int CompareTo(object obj) {
574 if (obj == null) {
575 return 1;
578 if (! (obj is Line)) {
579 throw new ArgumentException("Object is not of type Line", "obj");
582 if (line_no < ((Line)obj).line_no) {
583 return -1;
584 } else if (line_no > ((Line)obj).line_no) {
585 return 1;
586 } else {
587 return 0;
591 public object Clone() {
592 Line clone;
594 clone = new Line();
596 clone.text = text;
598 if (left != null) {
599 clone.left = (Line)left.Clone();
602 if (left != null) {
603 clone.left = (Line)left.Clone();
606 return clone;
609 internal object CloneLine() {
610 Line clone;
612 clone = new Line();
614 clone.text = text;
616 return clone;
619 public override bool Equals(object obj) {
620 if (obj == null) {
621 return false;
624 if (!(obj is Line)) {
625 return false;
628 if (obj == this) {
629 return true;
632 if (line_no == ((Line)obj).line_no) {
633 return true;
636 return false;
639 public override int GetHashCode() {
640 return base.GetHashCode ();
643 public override string ToString() {
644 return "Line " + line_no;
647 #endregion // Administrative
650 internal class Document : ICloneable, IEnumerable {
651 #region Structures
652 // FIXME - go through code and check for places where
653 // we do explicit comparisons instead of using the compare overloads
654 internal struct Marker {
655 internal Line line;
656 internal LineTag tag;
657 internal int pos;
658 internal int height;
660 public static bool operator<(Marker lhs, Marker rhs) {
661 if (lhs.line.line_no < rhs.line.line_no) {
662 return true;
665 if (lhs.line.line_no == rhs.line.line_no) {
666 if (lhs.pos < rhs.pos) {
667 return true;
670 return false;
673 public static bool operator>(Marker lhs, Marker rhs) {
674 if (lhs.line.line_no > rhs.line.line_no) {
675 return true;
678 if (lhs.line.line_no == rhs.line.line_no) {
679 if (lhs.pos > rhs.pos) {
680 return true;
683 return false;
686 public static bool operator==(Marker lhs, Marker rhs) {
687 if ((lhs.line.line_no == rhs.line.line_no) && (lhs.pos == rhs.pos)) {
688 return true;
690 return false;
693 public static bool operator!=(Marker lhs, Marker rhs) {
694 if ((lhs.line.line_no != rhs.line.line_no) || (lhs.pos != rhs.pos)) {
695 return true;
697 return false;
700 public void Combine(Line move_to_line, int move_to_line_length) {
701 line = move_to_line;
702 pos += move_to_line_length;
703 tag = LineTag.FindTag(line, pos);
706 // This is for future use, right now Document.Split does it by hand, with some added shortcut logic
707 public void Split(Line move_to_line, int split_at) {
708 line = move_to_line;
709 pos -= split_at;
710 tag = LineTag.FindTag(line, pos);
713 public override bool Equals(object obj) {
714 return this==(Marker)obj;
717 public override int GetHashCode() {
718 return base.GetHashCode ();
721 public override string ToString() {
722 return "Marker Line " + line + ", Position " + pos;
726 #endregion Structures
728 #region Local Variables
729 private Line document;
730 private int lines;
731 private Line sentinel;
732 private Line last_found;
733 private int document_id;
734 private Random random = new Random();
735 internal string password_char;
736 private StringBuilder password_cache;
737 private bool calc_pass;
738 private int char_count;
740 private bool no_recalc;
741 private bool recalc_pending;
742 private int recalc_start;
743 private int recalc_end;
744 private bool recalc_optimize;
746 internal bool multiline;
747 internal bool wrap;
749 internal UndoClass undo;
751 internal Marker caret;
752 internal Marker selection_start;
753 internal Marker selection_end;
754 internal bool selection_visible;
755 internal Marker selection_anchor;
756 internal Marker selection_prev;
757 internal bool selection_end_anchor;
759 internal int viewport_x;
760 internal int viewport_y; // The visible area of the document
761 internal int viewport_width;
762 internal int viewport_height;
764 internal int document_x; // Width of the document
765 internal int document_y; // Height of the document
767 internal Rectangle invalid;
769 internal int crlf_size; // 1 or 2, depending on whether we use \r\n or just \n
771 internal TextBoxBase owner; // Who's owning us?
772 static internal int caret_width = 1;
773 static internal int caret_shift = 1;
774 #endregion // Local Variables
776 #region Constructors
777 internal Document(TextBoxBase owner) {
778 lines = 0;
780 this.owner = owner;
782 multiline = true;
783 password_char = "";
784 calc_pass = false;
785 no_recalc = false;
786 recalc_pending = false;
788 // Tree related stuff
789 sentinel = new Line();
790 sentinel.color = LineColor.Black;
792 document = sentinel;
793 last_found = sentinel;
795 // We always have a blank line
796 owner.HandleCreated += new EventHandler(owner_HandleCreated);
797 owner.VisibleChanged += new EventHandler(owner_VisibleChanged);
799 Add(1, "", owner.Font, ThemeEngine.Current.ResPool.GetSolidBrush(owner.ForeColor));
800 Line l = GetLine (1);
801 l.soft_break = true;
803 undo = new UndoClass(this);
805 selection_visible = false;
806 selection_start.line = this.document;
807 selection_start.pos = 0;
808 selection_start.tag = selection_start.line.tags;
809 selection_end.line = this.document;
810 selection_end.pos = 0;
811 selection_end.tag = selection_end.line.tags;
812 selection_anchor.line = this.document;
813 selection_anchor.pos = 0;
814 selection_anchor.tag = selection_anchor.line.tags;
815 caret.line = this.document;
816 caret.pos = 0;
817 caret.tag = caret.line.tags;
819 viewport_x = 0;
820 viewport_y = 0;
822 crlf_size = 2;
824 // Default selection is empty
826 document_id = random.Next();
828 #endregion
830 #region Internal Properties
831 internal Line Root {
832 get {
833 return document;
836 set {
837 document = value;
841 internal int Lines {
842 get {
843 return lines;
847 internal Line CaretLine {
848 get {
849 return caret.line;
853 internal int CaretPosition {
854 get {
855 return caret.pos;
859 internal Point Caret {
860 get {
861 return new Point((int)caret.tag.line.widths[caret.pos] + caret.line.align_shift, caret.line.Y);
865 internal LineTag CaretTag {
866 get {
867 return caret.tag;
870 set {
871 caret.tag = value;
875 internal int CRLFSize {
876 get {
877 return crlf_size;
880 set {
881 crlf_size = value;
885 internal string PasswordChar {
886 get {
887 return password_char;
890 set {
891 password_char = value;
892 if ((password_char.Length != 0) && (password_char[0] != '\0')) {
893 char ch;
895 calc_pass = true;
896 ch = value[0];
897 password_cache = new StringBuilder(1024);
898 for (int i = 0; i < 1024; i++) {
899 password_cache.Append(ch);
901 } else {
902 calc_pass = false;
903 password_cache = null;
908 internal int ViewPortX {
909 get {
910 return viewport_x;
913 set {
914 viewport_x = value;
918 internal int Length {
919 get {
920 return char_count + lines - 1; // Add \n for each line but the last
924 private int CharCount {
925 get {
926 return char_count;
929 set {
930 char_count = value;
932 if (LengthChanged != null) {
933 LengthChanged(this, EventArgs.Empty);
938 ///<summary>Setting NoRecalc to true will prevent the document from being recalculated.
939 ///This ensures that coordinates of added text are predictable after adding the text even with wrapped view</summary>
940 internal bool NoRecalc {
941 get {
942 return no_recalc;
945 set {
946 no_recalc = value;
947 if (!no_recalc && recalc_pending) {
948 RecalculateDocument(owner.CreateGraphicsInternal(), recalc_start, recalc_end, recalc_optimize);
949 recalc_pending = false;
954 internal int ViewPortY {
955 get {
956 return viewport_y;
959 set {
960 viewport_y = value;
964 internal int ViewPortWidth {
965 get {
966 return viewport_width;
969 set {
970 viewport_width = value;
974 internal int ViewPortHeight {
975 get {
976 return viewport_height;
979 set {
980 viewport_height = value;
985 internal int Width {
986 get {
987 return this.document_x;
991 internal int Height {
992 get {
993 return this.document_y;
997 internal bool SelectionVisible {
998 get {
999 return selection_visible;
1003 internal bool Wrap {
1004 get {
1005 return wrap;
1008 set {
1009 wrap = value;
1013 #endregion // Internal Properties
1015 #region Private Methods
1016 // For debugging
1017 internal int DumpTree(Line line, bool with_tags) {
1018 int total;
1020 total = 1;
1022 Console.Write("Line {0} [# {1}], Y: {2} Text {3}", line.line_no, line.GetHashCode(), line.Y, line.text != null ? line.text.ToString() : "undefined");
1024 if (line.left == sentinel) {
1025 Console.Write(", left = sentinel");
1026 } else if (line.left == null) {
1027 Console.Write(", left = NULL");
1030 if (line.right == sentinel) {
1031 Console.Write(", right = sentinel");
1032 } else if (line.right == null) {
1033 Console.Write(", right = NULL");
1036 Console.WriteLine("");
1038 if (with_tags) {
1039 LineTag tag;
1040 int count;
1041 int length;
1043 tag = line.tags;
1044 count = 1;
1045 length = 0;
1046 Console.Write(" Tags: ");
1047 while (tag != null) {
1048 Console.Write("{0} <{1}>-<{2}> ", count++, tag.start, tag.length);
1049 length += tag.length;
1051 if (tag.line != line) {
1052 Console.Write("BAD line link");
1053 throw new Exception("Bad line link in tree");
1055 tag = tag.next;
1056 if (tag != null) {
1057 Console.Write(", ");
1060 if (length > line.text.Length) {
1061 throw new Exception(String.Format("Length of tags more than length of text on line (expected {0} calculated {1})", line.text.Length, length));
1062 } else if (length < line.text.Length) {
1063 throw new Exception(String.Format("Length of tags less than length of text on line (expected {0} calculated {1})", line.text.Length, length));
1065 Console.WriteLine("");
1067 if (line.left != null) {
1068 if (line.left != sentinel) {
1069 total += DumpTree(line.left, with_tags);
1071 } else {
1072 if (line != sentinel) {
1073 throw new Exception("Left should not be NULL");
1077 if (line.right != null) {
1078 if (line.right != sentinel) {
1079 total += DumpTree(line.right, with_tags);
1081 } else {
1082 if (line != sentinel) {
1083 throw new Exception("Right should not be NULL");
1087 for (int i = 1; i <= this.lines; i++) {
1088 if (GetLine(i) == null) {
1089 throw new Exception(String.Format("Hole in line order, missing {0}", i));
1093 if (line == this.Root) {
1094 if (total < this.lines) {
1095 throw new Exception(String.Format("Not enough nodes in tree, found {0}, expected {1}", total, this.lines));
1096 } else if (total > this.lines) {
1097 throw new Exception(String.Format("Too many nodes in tree, found {0}, expected {1}", total, this.lines));
1101 return total;
1104 private void SetSelectionVisible (bool value)
1106 selection_visible = value;
1108 // cursor and selection are enemies, we can't have both in the same room at the same time
1109 if (owner.IsHandleCreated)
1110 XplatUI.CaretVisible (owner.Handle, !selection_visible);
1113 private void DecrementLines(int line_no) {
1114 int current;
1116 current = line_no;
1117 while (current <= lines) {
1118 GetLine(current).line_no--;
1119 current++;
1121 return;
1124 private void IncrementLines(int line_no) {
1125 int current;
1127 current = this.lines;
1128 while (current >= line_no) {
1129 GetLine(current).line_no++;
1130 current--;
1132 return;
1135 private void RebalanceAfterAdd(Line line1) {
1136 Line line2;
1138 while ((line1 != document) && (line1.parent.color == LineColor.Red)) {
1139 if (line1.parent == line1.parent.parent.left) {
1140 line2 = line1.parent.parent.right;
1142 if ((line2 != null) && (line2.color == LineColor.Red)) {
1143 line1.parent.color = LineColor.Black;
1144 line2.color = LineColor.Black;
1145 line1.parent.parent.color = LineColor.Red;
1146 line1 = line1.parent.parent;
1147 } else {
1148 if (line1 == line1.parent.right) {
1149 line1 = line1.parent;
1150 RotateLeft(line1);
1153 line1.parent.color = LineColor.Black;
1154 line1.parent.parent.color = LineColor.Red;
1156 RotateRight(line1.parent.parent);
1158 } else {
1159 line2 = line1.parent.parent.left;
1161 if ((line2 != null) && (line2.color == LineColor.Red)) {
1162 line1.parent.color = LineColor.Black;
1163 line2.color = LineColor.Black;
1164 line1.parent.parent.color = LineColor.Red;
1165 line1 = line1.parent.parent;
1166 } else {
1167 if (line1 == line1.parent.left) {
1168 line1 = line1.parent;
1169 RotateRight(line1);
1172 line1.parent.color = LineColor.Black;
1173 line1.parent.parent.color = LineColor.Red;
1174 RotateLeft(line1.parent.parent);
1178 document.color = LineColor.Black;
1181 private void RebalanceAfterDelete(Line line1) {
1182 Line line2;
1184 while ((line1 != document) && (line1.color == LineColor.Black)) {
1185 if (line1 == line1.parent.left) {
1186 line2 = line1.parent.right;
1187 if (line2.color == LineColor.Red) {
1188 line2.color = LineColor.Black;
1189 line1.parent.color = LineColor.Red;
1190 RotateLeft(line1.parent);
1191 line2 = line1.parent.right;
1193 if ((line2.left.color == LineColor.Black) && (line2.right.color == LineColor.Black)) {
1194 line2.color = LineColor.Red;
1195 line1 = line1.parent;
1196 } else {
1197 if (line2.right.color == LineColor.Black) {
1198 line2.left.color = LineColor.Black;
1199 line2.color = LineColor.Red;
1200 RotateRight(line2);
1201 line2 = line1.parent.right;
1203 line2.color = line1.parent.color;
1204 line1.parent.color = LineColor.Black;
1205 line2.right.color = LineColor.Black;
1206 RotateLeft(line1.parent);
1207 line1 = document;
1209 } else {
1210 line2 = line1.parent.left;
1211 if (line2.color == LineColor.Red) {
1212 line2.color = LineColor.Black;
1213 line1.parent.color = LineColor.Red;
1214 RotateRight(line1.parent);
1215 line2 = line1.parent.left;
1217 if ((line2.right.color == LineColor.Black) && (line2.left.color == LineColor.Black)) {
1218 line2.color = LineColor.Red;
1219 line1 = line1.parent;
1220 } else {
1221 if (line2.left.color == LineColor.Black) {
1222 line2.right.color = LineColor.Black;
1223 line2.color = LineColor.Red;
1224 RotateLeft(line2);
1225 line2 = line1.parent.left;
1227 line2.color = line1.parent.color;
1228 line1.parent.color = LineColor.Black;
1229 line2.left.color = LineColor.Black;
1230 RotateRight(line1.parent);
1231 line1 = document;
1235 line1.color = LineColor.Black;
1238 private void RotateLeft(Line line1) {
1239 Line line2 = line1.right;
1241 line1.right = line2.left;
1243 if (line2.left != sentinel) {
1244 line2.left.parent = line1;
1247 if (line2 != sentinel) {
1248 line2.parent = line1.parent;
1251 if (line1.parent != null) {
1252 if (line1 == line1.parent.left) {
1253 line1.parent.left = line2;
1254 } else {
1255 line1.parent.right = line2;
1257 } else {
1258 document = line2;
1261 line2.left = line1;
1262 if (line1 != sentinel) {
1263 line1.parent = line2;
1267 private void RotateRight(Line line1) {
1268 Line line2 = line1.left;
1270 line1.left = line2.right;
1272 if (line2.right != sentinel) {
1273 line2.right.parent = line1;
1276 if (line2 != sentinel) {
1277 line2.parent = line1.parent;
1280 if (line1.parent != null) {
1281 if (line1 == line1.parent.right) {
1282 line1.parent.right = line2;
1283 } else {
1284 line1.parent.left = line2;
1286 } else {
1287 document = line2;
1290 line2.right = line1;
1291 if (line1 != sentinel) {
1292 line1.parent = line2;
1297 internal void UpdateView(Line line, int pos) {
1298 if (!owner.IsHandleCreated) {
1299 return;
1302 if (no_recalc) {
1303 recalc_start = line.line_no;
1304 recalc_end = line.line_no;
1305 recalc_optimize = true;
1306 recalc_pending = true;
1307 return;
1310 // Optimize invalidation based on Line alignment
1311 if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no, true)) {
1312 // Lineheight changed, invalidate the rest of the document
1313 if ((line.Y - viewport_y) >=0 ) {
1314 // We formatted something that's in view, only draw parts of the screen
1315 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, owner.Height - line.Y - viewport_y));
1316 } else {
1317 // The tag was above the visible area, draw everything
1318 owner.Invalidate();
1320 } else {
1321 switch(line.alignment) {
1322 case HorizontalAlignment.Left: {
1323 owner.Invalidate(new Rectangle((int)line.widths[pos] - viewport_x - 1, line.Y - viewport_y, viewport_width, line.height + 1));
1324 break;
1327 case HorizontalAlignment.Center: {
1328 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, line.height + 1));
1329 break;
1332 case HorizontalAlignment.Right: {
1333 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, (int)line.widths[pos + 1] - viewport_x + line.align_shift, line.height + 1));
1334 break;
1341 // Update display from line, down line_count lines; pos is unused, but required for the signature
1342 internal void UpdateView(Line line, int line_count, int pos) {
1343 if (!owner.IsHandleCreated) {
1344 return;
1347 if (no_recalc) {
1348 recalc_start = line.line_no;
1349 recalc_end = line.line_no + line_count - 1;
1350 recalc_optimize = true;
1351 recalc_pending = true;
1352 return;
1355 if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no + line_count - 1, true)) {
1356 // Lineheight changed, invalidate the rest of the document
1357 if ((line.Y - viewport_y) >=0 ) {
1358 // We formatted something that's in view, only draw parts of the screen
1359 //blah Console.WriteLine("TextControl.cs(981) Invalidate called in UpdateView(line, line_count, pos)");
1360 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, owner.Height - line.Y - viewport_y));
1361 } else {
1362 // The tag was above the visible area, draw everything
1363 //blah Console.WriteLine("TextControl.cs(985) Invalidate called in UpdateView(line, line_count, pos)");
1364 owner.Invalidate();
1366 } else {
1367 Line end_line;
1369 end_line = GetLine(line.line_no + line_count -1);
1370 if (end_line == null) {
1371 end_line = line;
1374 //blah Console.WriteLine("TextControl.cs(996) Invalidate called in UpdateView(line, line_count, pos)");
1375 owner.Invalidate(new Rectangle(0 - viewport_x, line.Y - viewport_y, (int)line.widths[line.text.Length], end_line.Y + end_line.height));
1378 #endregion // Private Methods
1380 #region Internal Methods
1381 // Clear the document and reset state
1382 internal void Empty() {
1384 document = sentinel;
1385 last_found = sentinel;
1386 lines = 0;
1388 // We always have a blank line
1389 Add(1, "", owner.Font, ThemeEngine.Current.ResPool.GetSolidBrush(owner.ForeColor));
1390 Line l = GetLine (1);
1391 l.soft_break = true;
1393 this.RecalculateDocument(owner.CreateGraphicsInternal());
1394 PositionCaret(0, 0);
1396 SetSelectionVisible (false);
1398 selection_start.line = this.document;
1399 selection_start.pos = 0;
1400 selection_start.tag = selection_start.line.tags;
1401 selection_end.line = this.document;
1402 selection_end.pos = 0;
1403 selection_end.tag = selection_end.line.tags;
1404 char_count = 0;
1406 viewport_x = 0;
1407 viewport_y = 0;
1409 document_x = 0;
1410 document_y = 0;
1413 internal void PositionCaret(Line line, int pos) {
1414 if (owner.IsHandleCreated) {
1415 undo.RecordCursor();
1418 caret.tag = line.FindTag(pos);
1419 caret.line = line;
1420 caret.pos = pos;
1421 caret.height = caret.tag.height;
1423 if (owner.IsHandleCreated) {
1424 if (owner.Focused) {
1425 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);
1428 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1434 internal void PositionCaret(int x, int y) {
1435 if (!owner.IsHandleCreated) {
1436 return;
1439 undo.RecordCursor();
1441 caret.tag = FindCursor(x, y, out caret.pos);
1442 caret.line = caret.tag.line;
1443 caret.height = caret.tag.height;
1445 if (owner.Focused) {
1446 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);
1449 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1452 internal void CaretHasFocus() {
1453 if ((caret.tag != null) && owner.IsHandleCreated) {
1454 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1455 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);
1457 DisplayCaret ();
1461 internal void CaretLostFocus() {
1462 if (!owner.IsHandleCreated) {
1463 return;
1465 XplatUI.DestroyCaret(owner.Handle);
1468 internal void AlignCaret() {
1469 if (!owner.IsHandleCreated) {
1470 return;
1473 undo.RecordCursor();
1475 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1476 caret.height = caret.tag.height;
1478 if (owner.Focused) {
1479 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1480 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);
1481 DisplayCaret ();
1484 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1487 internal void UpdateCaret() {
1488 if (!owner.IsHandleCreated || caret.tag == null) {
1489 return;
1492 undo.RecordCursor();
1494 if (caret.tag.height != caret.height) {
1495 caret.height = caret.tag.height;
1496 if (owner.Focused) {
1497 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1501 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);
1503 DisplayCaret ();
1505 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1508 internal void DisplayCaret() {
1509 if (!owner.IsHandleCreated) {
1510 return;
1513 if (owner.Focused && !selection_visible) {
1514 XplatUI.CaretVisible(owner.Handle, true);
1518 internal void HideCaret() {
1519 if (!owner.IsHandleCreated) {
1520 return;
1523 if (owner.Focused) {
1524 XplatUI.CaretVisible(owner.Handle, false);
1528 internal void MoveCaret(CaretDirection direction) {
1529 // FIXME should we use IsWordSeparator to detect whitespace, instead
1530 // of looking for actual spaces in the Word move cases?
1532 bool nowrap = false;
1533 switch(direction) {
1534 case CaretDirection.CharForwardNoWrap:
1535 nowrap = true;
1536 goto case CaretDirection.CharForward;
1537 case CaretDirection.CharForward: {
1538 caret.pos++;
1539 if (caret.pos > caret.line.text.Length) {
1540 if (multiline && !nowrap) {
1541 // Go into next line
1542 if (caret.line.line_no < this.lines) {
1543 caret.line = GetLine(caret.line.line_no+1);
1544 caret.pos = 0;
1545 caret.tag = caret.line.tags;
1546 } else {
1547 caret.pos--;
1549 } else {
1550 // Single line; we stay where we are
1551 caret.pos--;
1553 } else {
1554 if ((caret.tag.start - 1 + caret.tag.length) < caret.pos) {
1555 caret.tag = caret.tag.next;
1558 UpdateCaret();
1559 return;
1562 case CaretDirection.CharBackNoWrap:
1563 nowrap = true;
1564 goto case CaretDirection.CharBack;
1565 case CaretDirection.CharBack: {
1566 if (caret.pos > 0) {
1567 // caret.pos--; // folded into the if below
1568 if (--caret.pos > 0) {
1569 if (caret.tag.start > caret.pos) {
1570 caret.tag = caret.tag.previous;
1573 } else {
1574 if (caret.line.line_no > 1 && !nowrap) {
1575 caret.line = GetLine(caret.line.line_no - 1);
1576 caret.pos = caret.line.text.Length;
1577 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1580 UpdateCaret();
1581 return;
1584 case CaretDirection.WordForward: {
1585 int len;
1587 len = caret.line.text.Length;
1588 if (caret.pos < len) {
1589 while ((caret.pos < len) && (caret.line.text[caret.pos] != ' ')) {
1590 caret.pos++;
1592 if (caret.pos < len) {
1593 // Skip any whitespace
1594 while ((caret.pos < len) && (caret.line.text[caret.pos] == ' ')) {
1595 caret.pos++;
1598 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1599 } else {
1600 if (caret.line.line_no < this.lines) {
1601 caret.line = GetLine(caret.line.line_no + 1);
1602 caret.pos = 0;
1603 caret.tag = caret.line.tags;
1606 UpdateCaret();
1607 return;
1610 case CaretDirection.WordBack: {
1611 if (caret.pos > 0) {
1612 caret.pos--;
1614 while ((caret.pos > 0) && (caret.line.text[caret.pos] == ' ')) {
1615 caret.pos--;
1618 while ((caret.pos > 0) && (caret.line.text[caret.pos] != ' ')) {
1619 caret.pos--;
1622 if (caret.line.text.ToString(caret.pos, 1) == " ") {
1623 if (caret.pos != 0) {
1624 caret.pos++;
1625 } else {
1626 caret.line = GetLine(caret.line.line_no - 1);
1627 caret.pos = caret.line.text.Length;
1630 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1631 } else {
1632 if (caret.line.line_no > 1) {
1633 caret.line = GetLine(caret.line.line_no - 1);
1634 caret.pos = caret.line.text.Length;
1635 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1638 UpdateCaret();
1639 return;
1642 case CaretDirection.LineUp: {
1643 if (caret.line.line_no > 1) {
1644 int pixel;
1646 pixel = (int)caret.line.widths[caret.pos];
1647 PositionCaret(pixel, GetLine(caret.line.line_no - 1).Y);
1649 DisplayCaret ();
1651 return;
1654 case CaretDirection.LineDown: {
1655 if (caret.line.line_no < lines) {
1656 int pixel;
1658 pixel = (int)caret.line.widths[caret.pos];
1659 PositionCaret(pixel, GetLine(caret.line.line_no + 1).Y);
1661 DisplayCaret ();
1663 return;
1666 case CaretDirection.Home: {
1667 if (caret.pos > 0) {
1668 caret.pos = 0;
1669 caret.tag = caret.line.tags;
1670 UpdateCaret();
1672 return;
1675 case CaretDirection.End: {
1676 if (caret.pos < caret.line.text.Length) {
1677 caret.pos = caret.line.text.Length;
1678 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1679 UpdateCaret();
1681 return;
1684 case CaretDirection.PgUp: {
1686 int new_y, y_offset;
1688 if (viewport_y == 0) {
1690 // This should probably be handled elsewhere
1691 if (!(owner is RichTextBox)) {
1692 // Page down doesn't do anything in a regular TextBox
1693 // if the bottom of the document
1694 // is already visible, the page and the caret stay still
1695 return;
1698 // We're just placing the caret at the end of the document, no scrolling needed
1699 owner.vscroll.Value = 0;
1700 Line line = GetLine (1);
1701 PositionCaret (line, 0);
1704 y_offset = caret.line.Y - viewport_y;
1705 new_y = caret.line.Y - viewport_height;
1707 owner.vscroll.Value = Math.Max (new_y, 0);
1708 PositionCaret ((int)caret.line.widths[caret.pos], y_offset + viewport_y);
1709 return;
1712 case CaretDirection.PgDn: {
1713 int new_y, y_offset;
1715 if ((viewport_y + viewport_height) > document_y) {
1717 // This should probably be handled elsewhere
1718 if (!(owner is RichTextBox)) {
1719 // Page up doesn't do anything in a regular TextBox
1720 // if the bottom of the document
1721 // is already visible, the page and the caret stay still
1722 return;
1725 // We're just placing the caret at the end of the document, no scrolling needed
1726 owner.vscroll.Value = owner.vscroll.Maximum - viewport_height + 1;
1727 Line line = GetLine (lines);
1728 PositionCaret (line, line.Text.Length);
1731 y_offset = caret.line.Y - viewport_y;
1732 new_y = caret.line.Y + viewport_height;
1734 owner.vscroll.Value = Math.Min (new_y, owner.vscroll.Maximum - viewport_height + 1);
1735 PositionCaret ((int)caret.line.widths[caret.pos], y_offset + viewport_y);
1737 return;
1740 case CaretDirection.CtrlPgUp: {
1741 PositionCaret(0, viewport_y);
1742 DisplayCaret ();
1743 return;
1746 case CaretDirection.CtrlPgDn: {
1747 Line line;
1748 LineTag tag;
1749 int index;
1751 tag = FindTag(0, viewport_y + viewport_height, out index, false);
1752 if (tag.line.line_no > 1) {
1753 line = GetLine(tag.line.line_no - 1);
1754 } else {
1755 line = tag.line;
1757 PositionCaret(line, line.Text.Length);
1758 DisplayCaret ();
1759 return;
1762 case CaretDirection.CtrlHome: {
1763 caret.line = GetLine(1);
1764 caret.pos = 0;
1765 caret.tag = caret.line.tags;
1767 UpdateCaret();
1768 return;
1771 case CaretDirection.CtrlEnd: {
1772 caret.line = GetLine(lines);
1773 caret.pos = caret.line.text.Length;
1774 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1776 UpdateCaret();
1777 return;
1780 case CaretDirection.SelectionStart: {
1781 caret.line = selection_start.line;
1782 caret.pos = selection_start.pos;
1783 caret.tag = selection_start.tag;
1785 UpdateCaret();
1786 return;
1789 case CaretDirection.SelectionEnd: {
1790 caret.line = selection_end.line;
1791 caret.pos = selection_end.pos;
1792 caret.tag = selection_end.tag;
1794 UpdateCaret();
1795 return;
1800 // Draw the document
1801 internal void Draw(Graphics g, Rectangle clip) {
1802 Line line; // Current line being drawn
1803 LineTag tag; // Current tag being drawn
1804 int start; // First line to draw
1805 int end; // Last line to draw
1806 StringBuilder text; // String representing the current line
1807 int line_no; //
1808 Brush disabled;
1809 Brush hilight;
1810 Brush hilight_text;
1812 // First, figure out from what line to what line we need to draw
1813 start = GetLineByPixel(clip.Top + viewport_y, false).line_no;
1814 end = GetLineByPixel(clip.Bottom + viewport_y, false).line_no;
1815 //Console.WriteLine("Starting drawing at line {0}, ending at line {1} (clip-bottom:{2})", start, end, clip.Bottom);
1817 // Now draw our elements; try to only draw those that are visible
1818 line_no = start;
1820 #if Debug
1821 DateTime n = DateTime.Now;
1822 Console.WriteLine("Started drawing: {0}s {1}ms", n.Second, n.Millisecond);
1823 #endif
1825 disabled = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorGrayText);
1826 hilight = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorHighlight);
1827 hilight_text = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorHighlightText);
1829 while (line_no <= end) {
1830 line = GetLine(line_no);
1831 #if not
1832 if (owner.backcolor_set || (owner.Enabled && !owner.read_only)) {
1833 g.FillRectangle(ThemeEngine.Current.ResPool.GetSolidBrush(owner.BackColor), new Rectangle(clip.Left, line.Y - viewport_y, clip.Width, line.Y - viewport_y + line.Height));
1834 } else {
1835 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));
1837 #endif
1840 tag = line.tags;
1841 if (!calc_pass) {
1842 text = line.text;
1843 } else {
1844 // This fails if there's a password > 1024 chars...
1845 text = this.password_cache;
1847 while (tag != null) {
1848 if (tag.length == 0) {
1849 tag = tag.next;
1850 continue;
1853 if (((tag.X + tag.width) > (clip.Left - viewport_x)) || (tag.X < (clip.Right - viewport_x))) {
1854 // Check for selection
1855 if ((!selection_visible) || (!owner.ShowSelection) || (line_no < selection_start.line.line_no) || (line_no > selection_end.line.line_no)) {
1856 // regular drawing, no selection to deal with
1857 //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);
1858 if (owner.is_enabled) {
1859 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);
1860 } else {
1861 Color a;
1862 Color b;
1864 a = ((SolidBrush)tag.color).Color;
1865 b = ThemeEngine.Current.ColorWindowText;
1867 if ((a.R == b.R) && (a.G == b.G) && (a.B == b.B)) {
1868 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);
1869 } else {
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);
1873 } else {
1874 // we might have to draw our selection
1875 if ((line_no != selection_start.line.line_no) && (line_no != selection_end.line.line_no)) {
1876 // Special case, whole line is selected, draw this tag selected
1877 g.FillRectangle(
1878 hilight, // Brush
1879 tag.X + line.align_shift - viewport_x, // X
1880 line.Y + tag.shift - viewport_y, // Y
1881 line.widths[tag.start + tag.length - 1], // width
1882 tag.height // Height
1885 g.DrawString(
1886 //s.Substring(tag.start-1, tag.length), // String
1887 text.ToString(tag.start-1, tag.length), // String
1888 tag.font, // Font
1889 hilight_text, // Brush
1890 tag.X + line.align_shift - viewport_x, // X
1891 line.Y + tag.shift - viewport_y, // Y
1892 StringFormat.GenericTypographic);
1893 } else {
1894 bool highlight;
1895 bool partial;
1897 highlight = false;
1898 partial = false;
1900 // One or more, but not all tags on the line are selected
1901 if ((selection_start.tag == tag) && (selection_end.tag == tag)) {
1902 // Single tag selected, draw "normalSELECTEDnormal"
1903 partial = true;
1904 // First, the regular part
1905 g.DrawString(
1906 //s.Substring(tag.start - 1, selection_start.pos - tag.start + 1), // String
1907 text.ToString(tag.start - 1, selection_start.pos - tag.start + 1), // String
1908 tag.font, // Font
1909 tag.color, // Brush
1910 tag.X + line.align_shift - viewport_x, // X
1911 line.Y + tag.shift - viewport_y, // Y
1912 StringFormat.GenericTypographic);
1914 // Now the highlight
1915 g.FillRectangle(
1916 hilight, // Brush
1917 line.widths[selection_start.pos] + line.align_shift - viewport_x, // X
1918 line.Y + tag.shift - viewport_y, // Y
1919 line.widths[selection_end.pos] - line.widths[selection_start.pos], // Width
1920 tag.height); // Height
1922 g.DrawString(
1923 //s.Substring(selection_start.pos, selection_end.pos - selection_start.pos), // String
1924 text.ToString(selection_start.pos, selection_end.pos - selection_start.pos), // String
1925 tag.font, // Font
1926 hilight_text, // Brush
1927 line.widths[selection_start.pos] + line.align_shift - viewport_x, // X
1928 line.Y + tag.shift - viewport_y, // Y
1929 StringFormat.GenericTypographic);
1931 // And back to the regular
1932 g.DrawString(
1933 //s.Substring(selection_end.pos, tag.start + tag.length - selection_end.pos - 1), // String
1934 text.ToString(selection_end.pos, tag.start + tag.length - selection_end.pos - 1), // String
1935 tag.font, // Font
1936 tag.color, // Brush
1937 line.widths[selection_end.pos] + line.align_shift - viewport_x, // X
1938 line.Y + tag.shift - viewport_y, // Y
1939 StringFormat.GenericTypographic);
1941 } else if (selection_start.tag == tag) {
1942 partial = true;
1944 // The highlighted part
1945 g.FillRectangle(
1946 hilight,
1947 line.widths[selection_start.pos] + line.align_shift - viewport_x,
1948 line.Y + tag.shift - viewport_y,
1949 line.widths[tag.start + tag.length - 1] - line.widths[selection_start.pos],
1950 tag.height);
1952 g.DrawString(
1953 //s.Substring(selection_start.pos, tag.start + tag.length - selection_start.pos - 1), // String
1954 text.ToString(selection_start.pos, tag.start + tag.length - selection_start.pos - 1), // String
1955 tag.font, // Font
1956 hilight_text, // Brush
1957 line.widths[selection_start.pos] + line.align_shift - viewport_x, // X
1958 line.Y + tag.shift - viewport_y, // Y
1959 StringFormat.GenericTypographic);
1961 // The regular part
1962 g.DrawString(
1963 //s.Substring(tag.start - 1, selection_start.pos - tag.start + 1), // String
1964 text.ToString(tag.start - 1, selection_start.pos - tag.start + 1), // String
1965 tag.font, // Font
1966 tag.color, // Brush
1967 tag.X + line.align_shift - viewport_x, // X
1968 line.Y + tag.shift - viewport_y, // Y
1969 StringFormat.GenericTypographic);
1970 } else if (selection_end.tag == tag) {
1971 partial = true;
1973 // The highlighted part
1974 g.FillRectangle(
1975 hilight,
1976 tag.X + line.align_shift - viewport_x,
1977 line.Y + tag.shift - viewport_y,
1978 line.widths[selection_end.pos] - line.widths[tag.start - 1],
1979 tag.height);
1981 g.DrawString(
1982 //s.Substring(tag.start - 1, selection_end.pos - tag.start + 1), // String
1983 text.ToString(tag.start - 1, selection_end.pos - tag.start + 1), // String
1984 tag.font, // Font
1985 hilight_text, // Brush
1986 tag.X + line.align_shift - viewport_x, // X
1987 line.Y + tag.shift - viewport_y, // Y
1988 StringFormat.GenericTypographic);
1990 // The regular part
1991 g.DrawString(
1992 //s.Substring(selection_end.pos, tag.start + tag.length - selection_end.pos - 1), // String
1993 text.ToString(selection_end.pos, tag.start + tag.length - selection_end.pos - 1), // String
1994 tag.font, // Font
1995 tag.color, // Brush
1996 line.widths[selection_end.pos] + line.align_shift - viewport_x, // X
1997 line.Y + tag.shift - viewport_y, // Y
1998 StringFormat.GenericTypographic);
1999 } else {
2000 // no partially selected tags here, simple checks...
2001 if (selection_start.line == line) {
2002 int begin;
2003 int stop;
2005 begin = tag.start - 1;
2006 stop = tag.start + tag.length - 1;
2007 if (selection_end.line == line) {
2008 if ((begin >= selection_start.pos) && (stop < selection_end.pos)) {
2009 highlight = true;
2011 } else {
2012 if (stop > selection_start.pos) {
2013 highlight = true;
2016 } else if (selection_end.line == line) {
2017 if ((tag.start - 1) < selection_end.pos) {
2018 highlight = true;
2023 if (!partial) {
2024 if (highlight) {
2025 g.FillRectangle(
2026 hilight,
2027 tag.X + line.align_shift - viewport_x,
2028 line.Y + tag.shift - viewport_y,
2029 line.widths[tag.start + tag.length - 1] - line.widths[tag.start - 1],
2030 tag.height);
2032 g.DrawString(
2033 //s.Substring(tag.start-1, tag.length), // String
2034 text.ToString(tag.start-1, tag.length), // String
2035 tag.font, // Font
2036 hilight_text, // Brush
2037 tag.X + line.align_shift - viewport_x, // X
2038 line.Y + tag.shift - viewport_y, // Y
2039 StringFormat.GenericTypographic);
2040 } else {
2041 g.DrawString(
2042 //s.Substring(tag.start-1, tag.length), // String
2043 text.ToString(tag.start-1, tag.length), // String
2044 tag.font, // Font
2045 tag.color, // Brush
2046 tag.X + line.align_shift - viewport_x, // X
2047 line.Y + tag.shift - viewport_y, // Y
2048 StringFormat.GenericTypographic);
2056 tag = tag.next;
2059 line_no++;
2061 #if Debug
2062 n = DateTime.Now;
2063 Console.WriteLine("Finished drawing: {0}s {1}ms", n.Second, n.Millisecond);
2064 #endif
2068 internal void Insert(Line line, int pos, string s) {
2069 Insert(line, null, pos, false, s);
2072 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
2073 internal void Insert(Line line, LineTag tag, int pos, bool update_caret, string s) {
2074 int i;
2075 int base_line;
2076 string[] ins;
2077 int insert_lines;
2078 int old_line_count;
2080 // The formatting at the insertion point is used for the inserted text
2081 if (tag == null) {
2082 tag = LineTag.FindTag(line, pos);
2085 base_line = line.line_no;
2087 ins = s.Split(new char[] {'\n'});
2089 for (int j = 0; j < ins.Length; j++) {
2090 if (ins[j].EndsWith("\r")) {
2091 ins[j] = ins[j].Substring(0, ins[j].Length - 1);
2095 insert_lines = ins.Length;
2096 old_line_count = lines;
2098 // Bump the text at insertion point a line down if we're inserting more than one line
2099 if (insert_lines > 1) {
2100 Split(line, pos);
2101 // Remainder of start line is now in base_line + 1
2104 // Insert the first line
2105 InsertString(tag, pos, ins[0]);
2107 if (insert_lines > 1) {
2108 for (i = 1; i < insert_lines; i++) {
2109 Add(base_line + i, ins[i], line.alignment, tag.font, tag.color);
2111 if (!s.EndsWith("\n\n")) {
2112 this.Combine(base_line + (lines - old_line_count) - 1, base_line + lines - old_line_count);
2116 UpdateView(line, lines - old_line_count + 1, pos);
2118 if (update_caret) {
2119 // Move caret to the end of the inserted text
2120 if (insert_lines > 1) {
2121 Line l = GetLine (line.line_no + lines - old_line_count);
2122 PositionCaret(l, l.text.Length);
2123 } else {
2124 PositionCaret(line, pos + ins[0].Length);
2126 DisplayCaret ();
2130 // Inserts a character at the given position
2131 internal void InsertString(Line line, int pos, string s) {
2132 InsertString(line.FindTag(pos), pos, s);
2135 // Inserts a string at the given position
2136 internal void InsertString(LineTag tag, int pos, string s) {
2137 Line line;
2138 int len;
2140 len = s.Length;
2142 CharCount += len;
2144 line = tag.line;
2145 line.text.Insert(pos, s);
2146 tag.length += len;
2148 // TODO: sometimes getting a null tag here when pasting ???
2149 tag = tag.next;
2150 while (tag != null) {
2151 tag.start += len;
2152 tag = tag.next;
2154 line.Grow(len);
2155 line.recalc = true;
2157 UpdateView(line, pos);
2160 // Inserts a string at the caret position
2161 internal void InsertStringAtCaret(string s, bool move_caret) {
2162 LineTag tag;
2163 int len;
2165 len = s.Length;
2167 CharCount += len;
2169 caret.line.text.Insert(caret.pos, s);
2170 caret.tag.length += len;
2172 if (caret.tag.next != null) {
2173 tag = caret.tag.next;
2174 while (tag != null) {
2175 tag.start += len;
2176 tag = tag.next;
2179 caret.line.Grow(len);
2180 caret.line.recalc = true;
2182 UpdateView(caret.line, caret.pos);
2183 if (move_caret) {
2184 caret.pos += len;
2185 UpdateCaret();
2191 // Inserts a character at the given position
2192 internal void InsertChar(Line line, int pos, char ch) {
2193 InsertChar(line.FindTag(pos), pos, ch);
2196 // Inserts a character at the given position
2197 internal void InsertChar(LineTag tag, int pos, char ch) {
2198 Line line;
2200 CharCount++;
2202 line = tag.line;
2203 line.text.Insert(pos, ch);
2204 tag.length++;
2206 tag = tag.next;
2207 while (tag != null) {
2208 tag.start++;
2209 tag = tag.next;
2211 line.Grow(1);
2212 line.recalc = true;
2214 UpdateView(line, pos);
2217 // Inserts a character at the current caret position
2218 internal void InsertCharAtCaret(char ch, bool move_caret) {
2219 LineTag tag;
2221 CharCount++;
2223 caret.line.text.Insert(caret.pos, ch);
2224 caret.tag.length++;
2226 if (caret.tag.next != null) {
2227 tag = caret.tag.next;
2228 while (tag != null) {
2229 tag.start++;
2230 tag = tag.next;
2233 caret.line.Grow(1);
2234 caret.line.recalc = true;
2236 UpdateView(caret.line, caret.pos);
2237 if (move_caret) {
2238 caret.pos++;
2239 UpdateCaret();
2240 SetSelectionToCaret(true);
2244 // Deletes n characters at the given position; it will not delete past line limits
2245 // pos is 0-based
2246 internal void DeleteChars(LineTag tag, int pos, int count) {
2247 Line line;
2248 bool streamline;
2250 streamline = false;
2251 line = tag.line;
2253 CharCount -= count;
2255 if (pos == line.text.Length) {
2256 return;
2259 line.text.Remove(pos, count);
2261 // Make sure the tag points to the right spot
2262 while ((tag != null) && (tag.start + tag.length - 1) <= pos) {
2263 tag = tag.next;
2266 if (tag == null) {
2267 return;
2270 // Check if we're crossing tag boundaries
2271 if ((pos + count) > (tag.start + tag.length - 1)) {
2272 int left;
2274 // We have to delete cross tag boundaries
2275 streamline = true;
2276 left = count;
2278 left -= tag.start + tag.length - pos - 1;
2279 tag.length -= tag.start + tag.length - pos - 1;
2281 tag = tag.next;
2282 while ((tag != null) && (left > 0)) {
2283 tag.start -= count - left;
2284 if (tag.length > left) {
2285 tag.length -= left;
2286 left = 0;
2287 } else {
2288 left -= tag.length;
2289 tag.length = 0;
2291 tag = tag.next;
2294 } else {
2295 // We got off easy, same tag
2297 tag.length -= count;
2299 if (tag.length == 0) {
2300 streamline = true;
2304 // Delete empty orphaned tags at the end
2305 LineTag walk = tag;
2306 while (walk != null && walk.next != null && walk.next.length == 0) {
2307 LineTag t = walk;
2308 walk.next = walk.next.next;
2309 if (walk.next != null)
2310 walk.next.previous = t;
2311 walk = walk.next;
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 // Delete empty orphaned tags at the end
2379 LineTag walk = tag;
2380 while (walk != null && walk.next != null && walk.next.length == 0) {
2381 LineTag t = walk;
2382 walk.next = walk.next.next;
2383 if (walk.next != null)
2384 walk.next.previous = t;
2385 walk = walk.next;
2388 tag = tag.next;
2389 while (tag != null) {
2390 tag.start--;
2391 tag = tag.next;
2393 line.recalc = true;
2394 if (streamline) {
2395 line.Streamline(lines);
2398 UpdateView(line, pos);
2401 // Combine two lines
2402 internal void Combine(int FirstLine, int SecondLine) {
2403 Combine(GetLine(FirstLine), GetLine(SecondLine));
2406 internal void Combine(Line first, Line second) {
2407 LineTag last;
2408 int shift;
2410 // Combine the two tag chains into one
2411 last = first.tags;
2413 // Maintain the line ending style
2414 first.soft_break = second.soft_break;
2416 while (last.next != null) {
2417 last = last.next;
2420 last.next = second.tags;
2421 last.next.previous = last;
2423 shift = last.start + last.length - 1;
2425 // Fix up references within the chain
2426 last = last.next;
2427 while (last != null) {
2428 last.line = first;
2429 last.start += shift;
2430 last = last.next;
2433 // Combine both lines' strings
2434 first.text.Insert(first.text.Length, second.text.ToString());
2435 first.Grow(first.text.Length);
2437 // Remove the reference to our (now combined) tags from the doomed line
2438 second.tags = null;
2440 // Renumber lines
2441 DecrementLines(first.line_no + 2); // first.line_no + 1 will be deleted, so we need to start renumbering one later
2443 // Mop up
2444 first.recalc = true;
2445 first.height = 0; // This forces RecalcDocument/UpdateView to redraw from this line on
2446 first.Streamline(lines);
2448 // Update Caret, Selection, etc
2449 if (caret.line == second) {
2450 caret.Combine(first, shift);
2452 if (selection_anchor.line == second) {
2453 selection_anchor.Combine(first, shift);
2455 if (selection_start.line == second) {
2456 selection_start.Combine(first, shift);
2458 if (selection_end.line == second) {
2459 selection_end.Combine(first, shift);
2462 #if Debug
2463 Line check_first;
2464 Line check_second;
2466 check_first = GetLine(first.line_no);
2467 check_second = GetLine(check_first.line_no + 1);
2469 Console.WriteLine("Pre-delete: Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2470 #endif
2472 this.Delete(second);
2474 #if Debug
2475 check_first = GetLine(first.line_no);
2476 check_second = GetLine(check_first.line_no + 1);
2478 Console.WriteLine("Post-delete Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2479 #endif
2482 // Split the line at the position into two
2483 internal void Split(int LineNo, int pos) {
2484 Line line;
2485 LineTag tag;
2487 line = GetLine(LineNo);
2488 tag = LineTag.FindTag(line, pos);
2489 Split(line, tag, pos, false);
2492 internal void Split(Line line, int pos) {
2493 LineTag tag;
2495 tag = LineTag.FindTag(line, pos);
2496 Split(line, tag, pos, false);
2499 ///<summary>Split line at given tag and position into two lines</summary>
2500 ///<param name="soft">True if the split should be marked as 'soft', indicating that it can be contracted
2501 ///if more space becomes available on previous line</param>
2502 internal void Split(Line line, LineTag tag, int pos, bool soft) {
2503 LineTag new_tag;
2504 Line new_line;
2505 bool move_caret;
2506 bool move_sel_start;
2507 bool move_sel_end;
2509 move_caret = false;
2510 move_sel_start = false;
2511 move_sel_end = false;
2513 // Adjust selection and cursors
2514 if (soft && (caret.line == line) && (caret.pos >= pos)) {
2515 move_caret = true;
2517 if (selection_start.line == line && selection_start.pos > pos) {
2518 move_sel_start = true;
2521 if (selection_end.line == line && selection_end.pos > pos) {
2522 move_sel_end = true;
2525 // cover the easy case first
2526 if (pos == line.text.Length) {
2527 Add(line.line_no + 1, "", line.alignment, tag.font, tag.color);
2529 new_line = GetLine(line.line_no + 1);
2531 if (soft) {
2532 if (move_caret) {
2533 caret.line = new_line;
2534 caret.line.soft_break = true;
2535 caret.tag = new_line.tags;
2536 caret.pos = 0;
2537 } else {
2538 new_line.soft_break = true;
2542 if (move_sel_start) {
2543 selection_start.line = new_line;
2544 selection_start.pos = 0;
2545 selection_start.tag = new_line.tags;
2548 if (move_sel_end) {
2549 selection_end.line = new_line;
2550 selection_end.pos = 0;
2551 selection_end.tag = new_line.tags;
2553 return;
2556 // We need to move the rest of the text into the new line
2557 Add(line.line_no + 1, line.text.ToString(pos, line.text.Length - pos), line.alignment, tag.font, tag.color);
2559 // Now transfer our tags from this line to the next
2560 new_line = GetLine(line.line_no + 1);
2561 line.recalc = true;
2562 new_line.recalc = true;
2564 if ((tag.start - 1) == pos) {
2565 int shift;
2567 // We can simply break the chain and move the tag into the next line
2568 if (tag == line.tags) {
2569 new_tag = new LineTag(line, 1, 0);
2570 new_tag.font = tag.font;
2571 new_tag.color = tag.color;
2572 line.tags = new_tag;
2575 if (tag.previous != null) {
2576 tag.previous.next = null;
2578 new_line.tags = tag;
2579 tag.previous = null;
2580 tag.line = new_line;
2582 // Walk the list and correct the start location of the tags we just bumped into the next line
2583 shift = tag.start - 1;
2585 new_tag = tag;
2586 while (new_tag != null) {
2587 new_tag.start -= shift;
2588 new_tag.line = new_line;
2589 new_tag = new_tag.next;
2591 } else {
2592 int shift;
2594 new_tag = new LineTag(new_line, 1, tag.start - 1 + tag.length - pos);
2595 new_tag.next = tag.next;
2596 new_tag.font = tag.font;
2597 new_tag.color = tag.color;
2598 new_line.tags = new_tag;
2599 if (new_tag.next != null) {
2600 new_tag.next.previous = new_tag;
2602 tag.next = null;
2603 tag.length = pos - tag.start + 1;
2605 shift = pos;
2606 new_tag = new_tag.next;
2607 while (new_tag != null) {
2608 new_tag.start -= shift;
2609 new_tag.line = new_line;
2610 new_tag = new_tag.next;
2615 if (soft) {
2616 if (move_caret) {
2617 caret.line = new_line;
2618 caret.pos = caret.pos - pos;
2619 caret.tag = caret.line.FindTag(caret.pos);
2621 new_line.soft_break = true;
2624 if (move_sel_start) {
2625 selection_start.line = new_line;
2626 selection_start.pos = selection_start.pos - pos;
2627 selection_start.tag = new_line.FindTag(selection_start.pos);
2630 if (move_sel_end) {
2631 selection_end.line = new_line;
2632 selection_end.pos = selection_end.pos - pos;
2633 selection_end.tag = new_line.FindTag(selection_end.pos);
2636 CharCount -= line.text.Length - pos;
2637 line.text.Remove(pos, line.text.Length - pos);
2640 // Adds a line of text, with given font.
2641 // Bumps any line at that line number that already exists down
2642 internal void Add(int LineNo, string Text, Font font, Brush color) {
2643 Add(LineNo, Text, HorizontalAlignment.Left, font, color);
2646 internal void Add(int LineNo, string Text, HorizontalAlignment align, Font font, Brush color) {
2647 Line add;
2648 Line line;
2649 int line_no;
2651 CharCount += Text.Length;
2653 if (LineNo<1 || Text == null) {
2654 if (LineNo<1) {
2655 throw new ArgumentNullException("LineNo", "Line numbers must be positive");
2656 } else {
2657 throw new ArgumentNullException("Text", "Cannot insert NULL line");
2661 add = new Line(LineNo, Text, align, font, color);
2663 line = document;
2664 while (line != sentinel) {
2665 add.parent = line;
2666 line_no = line.line_no;
2668 if (LineNo > line_no) {
2669 line = line.right;
2670 } else if (LineNo < line_no) {
2671 line = line.left;
2672 } else {
2673 // Bump existing line numbers; walk all nodes to the right of this one and increment line_no
2674 IncrementLines(line.line_no);
2675 line = line.left;
2679 add.left = sentinel;
2680 add.right = sentinel;
2682 if (add.parent != null) {
2683 if (LineNo > add.parent.line_no) {
2684 add.parent.right = add;
2685 } else {
2686 add.parent.left = add;
2688 } else {
2689 // Root node
2690 document = add;
2693 RebalanceAfterAdd(add);
2695 lines++;
2698 internal virtual void Clear() {
2699 lines = 0;
2700 CharCount = 0;
2701 document = sentinel;
2704 public virtual object Clone() {
2705 Document clone;
2707 clone = new Document(null);
2709 clone.lines = this.lines;
2710 clone.document = (Line)document.Clone();
2712 return clone;
2715 internal void Delete(int LineNo) {
2716 Line line;
2718 if (LineNo>lines) {
2719 return;
2722 line = GetLine(LineNo);
2724 CharCount -= line.text.Length;
2726 DecrementLines(LineNo + 1);
2727 Delete(line);
2730 internal void Delete(Line line1) {
2731 Line line2;// = new Line();
2732 Line line3;
2734 if ((line1.left == sentinel) || (line1.right == sentinel)) {
2735 line3 = line1;
2736 } else {
2737 line3 = line1.right;
2738 while (line3.left != sentinel) {
2739 line3 = line3.left;
2743 if (line3.left != sentinel) {
2744 line2 = line3.left;
2745 } else {
2746 line2 = line3.right;
2749 line2.parent = line3.parent;
2750 if (line3.parent != null) {
2751 if(line3 == line3.parent.left) {
2752 line3.parent.left = line2;
2753 } else {
2754 line3.parent.right = line2;
2756 } else {
2757 document = line2;
2760 if (line3 != line1) {
2761 LineTag tag;
2763 if (selection_start.line == line3) {
2764 selection_start.line = line1;
2767 if (selection_end.line == line3) {
2768 selection_end.line = line1;
2771 if (selection_anchor.line == line3) {
2772 selection_anchor.line = line1;
2775 if (caret.line == line3) {
2776 caret.line = line1;
2780 line1.alignment = line3.alignment;
2781 line1.ascent = line3.ascent;
2782 line1.hanging_indent = line3.hanging_indent;
2783 line1.height = line3.height;
2784 line1.indent = line3.indent;
2785 line1.line_no = line3.line_no;
2786 line1.recalc = line3.recalc;
2787 line1.right_indent = line3.right_indent;
2788 line1.soft_break = line3.soft_break;
2789 line1.space = line3.space;
2790 line1.tags = line3.tags;
2791 line1.text = line3.text;
2792 line1.widths = line3.widths;
2793 line1.Y = line3.Y;
2795 tag = line1.tags;
2796 while (tag != null) {
2797 tag.line = line1;
2798 tag = tag.next;
2802 if (line3.color == LineColor.Black)
2803 RebalanceAfterDelete(line2);
2805 this.lines--;
2807 last_found = sentinel;
2810 // Invalidate a section of the document to trigger redraw
2811 internal void Invalidate(Line start, int start_pos, Line end, int end_pos) {
2812 Line l1;
2813 Line l2;
2814 int p1;
2815 int p2;
2817 if ((start == end) && (start_pos == end_pos)) {
2818 return;
2821 if (end_pos == -1) {
2822 end_pos = end.text.Length;
2825 // figure out what's before what so the logic below is straightforward
2826 if (start.line_no < end.line_no) {
2827 l1 = start;
2828 p1 = start_pos;
2830 l2 = end;
2831 p2 = end_pos;
2832 } else if (start.line_no > end.line_no) {
2833 l1 = end;
2834 p1 = end_pos;
2836 l2 = start;
2837 p2 = start_pos;
2838 } else {
2839 if (start_pos < end_pos) {
2840 l1 = start;
2841 p1 = start_pos;
2843 l2 = end;
2844 p2 = end_pos;
2845 } else {
2846 l1 = end;
2847 p1 = end_pos;
2849 l2 = start;
2850 p2 = start_pos;
2853 #if Debug
2854 Console.WriteLine("Invaliding from {0}:{1} to {2}:{3}", l1.line_no, p1, l2.line_no, p2);
2855 #endif
2857 owner.Invalidate(
2858 new Rectangle(
2859 (int)l1.widths[p1] + l1.align_shift - viewport_x,
2860 l1.Y - viewport_y,
2861 (int)l2.widths[p2] - (int)l1.widths[p1] + 1,
2862 l1.height
2865 return;
2868 #if Debug
2869 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);
2870 #endif
2872 // Three invalidates:
2873 // First line from start
2874 owner.Invalidate(new Rectangle((int)l1.widths[p1] + l1.align_shift - viewport_x, l1.Y - viewport_y, viewport_width, l1.height));
2876 // lines inbetween
2877 if ((l1.line_no + 1) < l2.line_no) {
2878 int y;
2880 y = GetLine(l1.line_no + 1).Y;
2881 owner.Invalidate(new Rectangle(0, y - viewport_y, viewport_width, GetLine(l2.line_no).Y - y - viewport_y));
2883 #if Debug
2884 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);
2885 #endif
2888 // Last line to end
2889 owner.Invalidate(new Rectangle((int)l2.widths[0] + l2.align_shift - viewport_x, l2.Y - viewport_y, (int)l2.widths[p2] + 1, l2.height));
2890 #if Debug
2891 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);
2892 #endif
2895 /// <summary>Select text around caret</summary>
2896 internal void ExpandSelection(CaretSelection mode, bool to_caret) {
2897 if (to_caret) {
2898 // We're expanding the selection to the caret position
2899 switch(mode) {
2900 case CaretSelection.Line: {
2901 // Invalidate the selection delta
2902 if (caret > selection_prev) {
2903 Invalidate(selection_prev.line, 0, caret.line, caret.line.text.Length);
2904 } else {
2905 Invalidate(selection_prev.line, selection_prev.line.text.Length, caret.line, 0);
2908 if (caret.line.line_no <= selection_anchor.line.line_no) {
2909 selection_start.line = caret.line;
2910 selection_start.tag = caret.line.tags;
2911 selection_start.pos = 0;
2913 selection_end.line = selection_anchor.line;
2914 selection_end.tag = selection_anchor.tag;
2915 selection_end.pos = selection_anchor.pos;
2917 selection_end_anchor = true;
2918 } else {
2919 selection_start.line = selection_anchor.line;
2920 selection_start.pos = selection_anchor.height;
2921 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height);
2923 selection_end.line = caret.line;
2924 selection_end.tag = caret.line.tags;
2925 selection_end.pos = caret.line.text.Length;
2927 selection_end_anchor = false;
2929 selection_prev.line = caret.line;
2930 selection_prev.tag = caret.tag;
2931 selection_prev.pos = caret.pos;
2933 break;
2936 case CaretSelection.Word: {
2937 int start_pos;
2938 int end_pos;
2940 start_pos = FindWordSeparator(caret.line, caret.pos, false);
2941 end_pos = FindWordSeparator(caret.line, caret.pos, true);
2944 // Invalidate the selection delta
2945 if (caret > selection_prev) {
2946 Invalidate(selection_prev.line, selection_prev.pos, caret.line, end_pos);
2947 } else {
2948 Invalidate(selection_prev.line, selection_prev.pos, caret.line, start_pos);
2950 if (caret < selection_anchor) {
2951 selection_start.line = caret.line;
2952 selection_start.tag = caret.line.FindTag(start_pos);
2953 selection_start.pos = start_pos;
2955 selection_end.line = selection_anchor.line;
2956 selection_end.tag = selection_anchor.tag;
2957 selection_end.pos = selection_anchor.pos;
2959 selection_prev.line = caret.line;
2960 selection_prev.tag = caret.tag;
2961 selection_prev.pos = start_pos;
2963 selection_end_anchor = true;
2964 } else {
2965 selection_start.line = selection_anchor.line;
2966 selection_start.pos = selection_anchor.height;
2967 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height);
2969 selection_end.line = caret.line;
2970 selection_end.tag = caret.line.FindTag(end_pos);
2971 selection_end.pos = end_pos;
2973 selection_prev.line = caret.line;
2974 selection_prev.tag = caret.tag;
2975 selection_prev.pos = end_pos;
2977 selection_end_anchor = false;
2979 break;
2982 case CaretSelection.Position: {
2983 SetSelectionToCaret(false);
2984 return;
2987 } else {
2988 // We're setting the selection 'around' the caret position
2989 switch(mode) {
2990 case CaretSelection.Line: {
2991 this.Invalidate(caret.line, 0, caret.line, caret.line.text.Length);
2993 selection_start.line = caret.line;
2994 selection_start.tag = caret.line.tags;
2995 selection_start.pos = 0;
2997 selection_end.line = caret.line;
2998 selection_end.pos = caret.line.text.Length;
2999 selection_end.tag = caret.line.FindTag(selection_end.pos);
3001 selection_anchor.line = selection_end.line;
3002 selection_anchor.tag = selection_end.tag;
3003 selection_anchor.pos = selection_end.pos;
3004 selection_anchor.height = 0;
3006 selection_prev.line = caret.line;
3007 selection_prev.tag = caret.tag;
3008 selection_prev.pos = caret.pos;
3010 this.selection_end_anchor = true;
3012 break;
3015 case CaretSelection.Word: {
3016 int start_pos;
3017 int end_pos;
3019 start_pos = FindWordSeparator(caret.line, caret.pos, false);
3020 end_pos = FindWordSeparator(caret.line, caret.pos, true);
3022 this.Invalidate(selection_start.line, start_pos, caret.line, end_pos);
3024 selection_start.line = caret.line;
3025 selection_start.tag = caret.line.FindTag(start_pos);
3026 selection_start.pos = start_pos;
3028 selection_end.line = caret.line;
3029 selection_end.tag = caret.line.FindTag(end_pos);
3030 selection_end.pos = end_pos;
3032 selection_anchor.line = selection_end.line;
3033 selection_anchor.tag = selection_end.tag;
3034 selection_anchor.pos = selection_end.pos;
3035 selection_anchor.height = start_pos;
3037 selection_prev.line = caret.line;
3038 selection_prev.tag = caret.tag;
3039 selection_prev.pos = caret.pos;
3041 this.selection_end_anchor = true;
3043 break;
3048 SetSelectionVisible (!(selection_start == selection_end));
3051 internal void SetSelectionToCaret(bool start) {
3052 if (start) {
3053 // Invalidate old selection; selection is being reset to empty
3054 this.Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3056 selection_start.line = caret.line;
3057 selection_start.tag = caret.tag;
3058 selection_start.pos = caret.pos;
3060 // start always also selects end
3061 selection_end.line = caret.line;
3062 selection_end.tag = caret.tag;
3063 selection_end.pos = caret.pos;
3065 selection_anchor.line = caret.line;
3066 selection_anchor.tag = caret.tag;
3067 selection_anchor.pos = caret.pos;
3068 } else {
3069 // Invalidate from previous end to caret (aka new end)
3070 if (selection_end_anchor) {
3071 if (selection_start != caret) {
3072 this.Invalidate(selection_start.line, selection_start.pos, caret.line, caret.pos);
3074 } else {
3075 if (selection_end != caret) {
3076 this.Invalidate(selection_end.line, selection_end.pos, caret.line, caret.pos);
3080 if (caret < selection_anchor) {
3081 selection_start.line = caret.line;
3082 selection_start.tag = caret.tag;
3083 selection_start.pos = caret.pos;
3085 selection_end.line = selection_anchor.line;
3086 selection_end.tag = selection_anchor.tag;
3087 selection_end.pos = selection_anchor.pos;
3089 selection_end_anchor = true;
3090 } else {
3091 selection_start.line = selection_anchor.line;
3092 selection_start.tag = selection_anchor.tag;
3093 selection_start.pos = selection_anchor.pos;
3095 selection_end.line = caret.line;
3096 selection_end.tag = caret.tag;
3097 selection_end.pos = caret.pos;
3099 selection_end_anchor = false;
3103 SetSelectionVisible (!(selection_start == selection_end));
3106 internal void SetSelection(Line start, int start_pos, Line end, int end_pos) {
3107 if (selection_visible) {
3108 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3111 if ((end.line_no < start.line_no) || ((end == start) && (end_pos <= start_pos))) {
3112 selection_start.line = end;
3113 selection_start.tag = LineTag.FindTag(end, end_pos);
3114 selection_start.pos = end_pos;
3116 selection_end.line = start;
3117 selection_end.tag = LineTag.FindTag(start, start_pos);
3118 selection_end.pos = start_pos;
3120 selection_end_anchor = true;
3121 } else {
3122 selection_start.line = start;
3123 selection_start.tag = LineTag.FindTag(start, start_pos);
3124 selection_start.pos = start_pos;
3126 selection_end.line = end;
3127 selection_end.tag = LineTag.FindTag(end, end_pos);
3128 selection_end.pos = end_pos;
3130 selection_end_anchor = false;
3133 selection_anchor.line = start;
3134 selection_anchor.tag = selection_start.tag;
3135 selection_anchor.pos = start_pos;
3137 if (((start == end) && (start_pos == end_pos)) || start == null || end == null) {
3138 SetSelectionVisible (false);
3139 } else {
3140 SetSelectionVisible (true);
3141 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3145 internal void SetSelectionStart(Line start, int start_pos) {
3146 // Invalidate from the previous to the new start pos
3147 Invalidate(selection_start.line, selection_start.pos, start, start_pos);
3149 selection_start.line = start;
3150 selection_start.pos = start_pos;
3151 selection_start.tag = LineTag.FindTag(start, start_pos);
3153 selection_anchor.line = start;
3154 selection_anchor.pos = start_pos;
3155 selection_anchor.tag = selection_start.tag;
3157 selection_end_anchor = false;
3160 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
3161 SetSelectionVisible (true);
3162 } else {
3163 SetSelectionVisible (false);
3166 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3169 internal void SetSelectionStart(int character_index) {
3170 Line line;
3171 LineTag tag;
3172 int pos;
3174 if (character_index < 0) {
3175 return;
3178 CharIndexToLineTag(character_index, out line, out tag, out pos);
3179 SetSelectionStart(line, pos);
3182 internal void SetSelectionEnd(Line end, int end_pos) {
3184 if (end == selection_end.line && end_pos == selection_start.pos) {
3185 selection_anchor.line = selection_start.line;
3186 selection_anchor.tag = selection_start.tag;
3187 selection_anchor.pos = selection_start.pos;
3189 selection_end.line = selection_start.line;
3190 selection_end.tag = selection_start.tag;
3191 selection_end.pos = selection_start.pos;
3193 selection_end_anchor = false;
3194 } else if ((end.line_no < selection_anchor.line.line_no) || ((end == selection_anchor.line) && (end_pos <= selection_anchor.pos))) {
3195 selection_start.line = end;
3196 selection_start.tag = LineTag.FindTag(end, end_pos);
3197 selection_start.pos = end_pos;
3199 selection_end.line = selection_anchor.line;
3200 selection_end.tag = selection_anchor.tag;
3201 selection_end.pos = selection_anchor.pos;
3203 selection_end_anchor = true;
3204 } else {
3205 selection_start.line = selection_anchor.line;
3206 selection_start.tag = selection_anchor.tag;
3207 selection_start.pos = selection_anchor.pos;
3209 selection_end.line = end;
3210 selection_end.tag = LineTag.FindTag(end, end_pos);
3211 selection_end.pos = end_pos;
3213 selection_end_anchor = false;
3216 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
3217 SetSelectionVisible (true);
3218 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3219 } else {
3220 SetSelectionVisible (false);
3221 // ?? Do I need to invalidate here, tests seem to work without it, but I don't think they should :-s
3225 internal void SetSelectionEnd(int character_index) {
3226 Line line;
3227 LineTag tag;
3228 int pos;
3230 if (character_index < 0) {
3231 return;
3234 CharIndexToLineTag(character_index, out line, out tag, out pos);
3235 SetSelectionEnd(line, pos);
3238 internal void SetSelection(Line start, int start_pos) {
3239 if (selection_visible) {
3240 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3243 selection_start.line = start;
3244 selection_start.pos = start_pos;
3245 selection_start.tag = LineTag.FindTag(start, start_pos);
3247 selection_end.line = start;
3248 selection_end.tag = selection_start.tag;
3249 selection_end.pos = start_pos;
3251 selection_anchor.line = start;
3252 selection_anchor.tag = selection_start.tag;
3253 selection_anchor.pos = start_pos;
3255 selection_end_anchor = false;
3256 SetSelectionVisible (false);
3259 internal void InvalidateSelectionArea() {
3260 Invalidate (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3263 // Return the current selection, as string
3264 internal string GetSelection() {
3265 // We return String.Empty if there is no selection
3266 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3267 return string.Empty;
3270 if (!multiline || (selection_start.line == selection_end.line)) {
3271 return selection_start.line.text.ToString(selection_start.pos, selection_end.pos - selection_start.pos);
3272 } else {
3273 StringBuilder sb;
3274 int i;
3275 int start;
3276 int end;
3278 sb = new StringBuilder();
3279 start = selection_start.line.line_no;
3280 end = selection_end.line.line_no;
3282 sb.Append(selection_start.line.text.ToString(selection_start.pos, selection_start.line.text.Length - selection_start.pos) + Environment.NewLine);
3284 if ((start + 1) < end) {
3285 for (i = start + 1; i < end; i++) {
3286 sb.Append(GetLine(i).text.ToString() + Environment.NewLine);
3290 sb.Append(selection_end.line.text.ToString(0, selection_end.pos));
3292 return sb.ToString();
3296 internal void ReplaceSelection(string s) {
3297 int i;
3299 int selection_start_pos = LineTagToCharIndex (selection_start.line, selection_start.pos);
3300 // First, delete any selected text
3301 if ((selection_start.pos != selection_end.pos) || (selection_start.line != selection_end.line)) {
3302 if (!multiline || (selection_start.line == selection_end.line)) {
3303 undo.RecordDeleteChars(selection_start.line, selection_start.pos + 1, selection_end.pos - selection_start.pos);
3305 DeleteChars(selection_start.tag, selection_start.pos, selection_end.pos - selection_start.pos);
3307 // The tag might have been removed, we need to recalc it
3308 selection_start.tag = selection_start.line.FindTag(selection_start.pos);
3309 } else {
3310 int start;
3311 int end;
3313 start = selection_start.line.line_no;
3314 end = selection_end.line.line_no;
3316 undo.RecordDelete(selection_start.line, selection_start.pos + 1, selection_end.line, selection_end.pos);
3318 // Delete first line
3319 DeleteChars(selection_start.tag, selection_start.pos, selection_start.line.text.Length - selection_start.pos);
3321 // Delete last line
3322 DeleteChars(selection_end.line.tags, 0, selection_end.pos);
3324 start++;
3325 if (start < end) {
3326 for (i = end - 1; i >= start; i--) {
3327 Delete(i);
3331 // BIG FAT WARNING - selection_end.line might be stale due
3332 // to the above Delete() call. DONT USE IT before hitting the end of this method!
3334 // Join start and end
3335 Combine(selection_start.line.line_no, start);
3340 Insert(selection_start.line, null, selection_start.pos, true, s);
3342 CharIndexToLineTag(selection_start_pos + s.Length + 1, out selection_start.line,
3343 out selection_start.tag, out selection_start.pos);
3345 selection_end.line = selection_start.line;
3346 selection_end.pos = selection_start.pos;
3347 selection_end.tag = selection_start.tag;
3348 selection_anchor.line = selection_start.line;
3349 selection_anchor.pos = selection_start.pos;
3350 selection_anchor.tag = selection_start.tag;
3352 SetSelectionVisible (false);
3355 internal void CharIndexToLineTag(int index, out Line line_out, out LineTag tag_out, out int pos) {
3356 Line line;
3357 LineTag tag;
3358 int i;
3359 int chars;
3360 int start;
3362 chars = 0;
3364 for (i = 1; i <= lines; i++) {
3365 line = GetLine(i);
3367 start = chars;
3368 chars += line.text.Length + crlf_size;
3370 if (index <= chars) {
3371 // we found the line
3372 tag = line.tags;
3374 while (tag != null) {
3375 if (index < (start + tag.start + tag.length)) {
3376 line_out = line;
3377 tag_out = LineTag.GetFinalTag (tag);
3378 pos = index - start;
3379 return;
3381 if (tag.next == null) {
3382 Line next_line;
3384 next_line = GetLine(line.line_no + 1);
3386 if (next_line != null) {
3387 line_out = next_line;
3388 tag_out = LineTag.GetFinalTag (next_line.tags);
3389 pos = 0;
3390 return;
3391 } else {
3392 line_out = line;
3393 tag_out = LineTag.GetFinalTag (tag);
3394 pos = line_out.text.Length;
3395 return;
3398 tag = tag.next;
3403 line_out = GetLine(lines);
3404 tag = line_out.tags;
3405 while (tag.next != null) {
3406 tag = tag.next;
3408 tag_out = tag;
3409 pos = line_out.text.Length;
3412 internal int LineTagToCharIndex(Line line, int pos) {
3413 int i;
3414 int length;
3416 // Count first and last line
3417 length = 0;
3419 // Count the lines in the middle
3421 for (i = 1; i < line.line_no; i++) {
3422 length += GetLine(i).text.Length + crlf_size;
3425 length += pos;
3427 return length;
3430 internal int SelectionLength() {
3431 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3432 return 0;
3435 if (!multiline || (selection_start.line == selection_end.line)) {
3436 return selection_end.pos - selection_start.pos;
3437 } else {
3438 int i;
3439 int start;
3440 int end;
3441 int length;
3443 // Count first and last line
3444 length = selection_start.line.text.Length - selection_start.pos + selection_end.pos + crlf_size;
3446 // Count the lines in the middle
3447 start = selection_start.line.line_no + 1;
3448 end = selection_end.line.line_no;
3450 if (start < end) {
3451 for (i = start; i < end; i++) {
3452 length += GetLine(i).text.Length + crlf_size;
3456 return length;
3463 /// <summary>Give it a Line number and it returns the Line object at with that line number</summary>
3464 internal Line GetLine(int LineNo) {
3465 Line line = document;
3467 while (line != sentinel) {
3468 if (LineNo == line.line_no) {
3469 return line;
3470 } else if (LineNo < line.line_no) {
3471 line = line.left;
3472 } else {
3473 line = line.right;
3477 return null;
3480 /// <summary>Retrieve the previous tag; walks line boundaries</summary>
3481 internal LineTag PreviousTag(LineTag tag) {
3482 Line l;
3484 if (tag.previous != null) {
3485 return tag.previous;
3488 // Next line
3489 if (tag.line.line_no == 1) {
3490 return null;
3493 l = GetLine(tag.line.line_no - 1);
3494 if (l != null) {
3495 LineTag t;
3497 t = l.tags;
3498 while (t.next != null) {
3499 t = t.next;
3501 return t;
3504 return null;
3507 /// <summary>Retrieve the next tag; walks line boundaries</summary>
3508 internal LineTag NextTag(LineTag tag) {
3509 Line l;
3511 if (tag.next != null) {
3512 return tag.next;
3515 // Next line
3516 l = GetLine(tag.line.line_no + 1);
3517 if (l != null) {
3518 return l.tags;
3521 return null;
3524 internal Line ParagraphStart(Line line) {
3525 while (line.soft_break) {
3526 line = GetLine(line.line_no - 1);
3528 return line;
3531 internal Line ParagraphEnd(Line line) {
3532 Line l;
3534 while (line.soft_break) {
3535 l = GetLine(line.line_no + 1);
3536 if ((l == null) || (!l.soft_break)) {
3537 break;
3539 line = l;
3541 return line;
3544 /// <summary>Give it a Y pixel coordinate and it returns the Line covering that Y coordinate</summary>
3545 internal Line GetLineByPixel(int y, bool exact) {
3546 Line line = document;
3547 Line last = null;
3549 while (line != sentinel) {
3550 last = line;
3551 if ((y >= line.Y) && (y < (line.Y+line.height))) {
3552 return line;
3553 } else if (y < line.Y) {
3554 line = line.left;
3555 } else {
3556 line = line.right;
3560 if (exact) {
3561 return null;
3563 return last;
3566 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3567 internal LineTag FindTag(int x, int y, out int index, bool exact) {
3568 Line line;
3569 LineTag tag;
3571 line = GetLineByPixel(y, exact);
3572 if (line == null) {
3573 index = 0;
3574 return null;
3576 tag = line.tags;
3578 // Alignment adjustment
3579 x += line.align_shift;
3581 while (true) {
3582 if (x >= tag.X && x < (tag.X+tag.width)) {
3583 int end;
3585 end = tag.start + tag.length - 1;
3587 for (int pos = tag.start; pos < end; pos++) {
3588 if (x < line.widths[pos]) {
3589 index = pos;
3590 return LineTag.GetFinalTag (tag);
3593 index=end;
3594 return LineTag.GetFinalTag (tag);
3596 if (tag.next != null) {
3597 tag = tag.next;
3598 } else {
3599 if (exact) {
3600 index = 0;
3601 return null;
3604 index = line.text.Length;
3605 return LineTag.GetFinalTag (tag);
3610 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3611 internal LineTag FindCursor(int x, int y, out int index) {
3612 Line line;
3613 LineTag tag;
3615 line = GetLineByPixel(y, false);
3616 tag = line.tags;
3618 // Adjust for alignment
3619 x -= line.align_shift;
3621 while (true) {
3622 if (x >= tag.X && x < (tag.X+tag.width)) {
3623 int end;
3625 end = tag.start + tag.length - 1;
3627 for (int pos = tag.start-1; pos < end; pos++) {
3628 // When clicking on a character, we position the cursor to whatever edge
3629 // of the character the click was closer
3630 if (x < (line.widths[pos] + ((line.widths[pos+1]-line.widths[pos])/2))) {
3631 index = pos;
3632 return tag;
3635 index=end;
3636 return tag;
3638 if (tag.next != null) {
3639 tag = tag.next;
3640 } else {
3641 index = line.text.Length;
3642 return tag;
3647 /// <summary>Format area of document in specified font and color</summary>
3648 /// <param name="start_pos">1-based start position on start_line</param>
3649 /// <param name="end_pos">1-based end position on end_line </param>
3650 internal void FormatText(Line start_line, int start_pos, Line end_line, int end_pos, Font font, Brush color) {
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, font, color);
3658 // Format last line
3659 LineTag.FormatText(end_line, 1, end_pos, font, color);
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, font, color);
3666 } else {
3667 // Special case, single line
3668 LineTag.FormatText(start_line, start_pos, end_pos - start_pos, font, color);
3672 /// <summary>Re-format areas of the document in specified font and color</summary>
3673 /// <param name="start_pos">1-based start position on start_line</param>
3674 /// <param name="end_pos">1-based end position on end_line </param>
3675 /// <param name="font">Font specifying attributes</param>
3676 /// <param name="color">Color (or NULL) to apply</param>
3677 /// <param name="apply">Attributes from font and color to apply</param>
3678 internal void FormatText(Line start_line, int start_pos, Line end_line, int end_pos, FontDefinition attributes) {
3679 Line l;
3681 // First, format the first line
3682 if (start_line != end_line) {
3683 // First line
3684 LineTag.FormatText(start_line, start_pos, start_line.text.Length - start_pos + 1, attributes);
3686 // Format last line
3687 LineTag.FormatText(end_line, 1, end_pos - 1, attributes);
3689 // Now all the lines inbetween
3690 for (int i = start_line.line_no + 1; i < end_line.line_no; i++) {
3691 l = GetLine(i);
3692 LineTag.FormatText(l, 1, l.text.Length, attributes);
3694 } else {
3695 // Special case, single line
3696 LineTag.FormatText(start_line, start_pos, end_pos - start_pos, attributes);
3700 internal void RecalculateAlignments() {
3701 Line line;
3702 int line_no;
3704 line_no = 1;
3706 while (line_no <= lines) {
3707 line = GetLine(line_no);
3709 if (line != null) {
3710 switch (line.alignment) {
3711 case HorizontalAlignment.Left:
3712 line.align_shift = 0;
3713 break;
3714 case HorizontalAlignment.Center:
3715 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3716 break;
3717 case HorizontalAlignment.Right:
3718 line.align_shift = viewport_width - (int)line.widths[line.text.Length];
3719 break;
3723 line_no++;
3725 return;
3728 /// <summary>Calculate formatting for the whole document</summary>
3729 internal bool RecalculateDocument(Graphics g) {
3730 return RecalculateDocument(g, 1, this.lines, false);
3733 /// <summary>Calculate formatting starting at a certain line</summary>
3734 internal bool RecalculateDocument(Graphics g, int start) {
3735 return RecalculateDocument(g, start, this.lines, false);
3738 /// <summary>Calculate formatting within two given line numbers</summary>
3739 internal bool RecalculateDocument(Graphics g, int start, int end) {
3740 return RecalculateDocument(g, start, end, false);
3743 /// <summary>With optimize on, returns true if line heights changed</summary>
3744 internal bool RecalculateDocument(Graphics g, int start, int end, bool optimize) {
3745 Line line;
3746 int line_no;
3747 int Y;
3748 int new_width;
3749 bool changed;
3750 int shift;
3752 if (no_recalc) {
3753 recalc_pending = true;
3754 recalc_start = start;
3755 recalc_end = end;
3756 recalc_optimize = optimize;
3757 return false;
3760 Y = GetLine(start).Y;
3761 line_no = start;
3762 new_width = 0;
3763 shift = this.lines;
3764 if (!optimize) {
3765 changed = true; // We always return true if we run non-optimized
3766 } else {
3767 changed = false;
3770 while (line_no <= (end + this.lines - shift)) {
3771 line = GetLine(line_no++);
3772 line.Y = Y;
3774 if (!calc_pass) {
3775 if (!optimize) {
3776 line.RecalculateLine(g, this);
3777 } else {
3778 if (line.recalc && line.RecalculateLine(g, this)) {
3779 changed = true;
3780 // If the height changed, all subsequent lines change
3781 end = this.lines;
3782 shift = this.lines;
3785 } else {
3786 if (!optimize) {
3787 line.RecalculatePasswordLine(g, this);
3788 } else {
3789 if (line.recalc && line.RecalculatePasswordLine(g, this)) {
3790 changed = true;
3791 // If the height changed, all subsequent lines change
3792 end = this.lines;
3793 shift = this.lines;
3798 if (line.widths[line.text.Length] > new_width) {
3799 new_width = (int)line.widths[line.text.Length];
3802 // Calculate alignment
3803 if (line.alignment != HorizontalAlignment.Left) {
3804 if (line.alignment == HorizontalAlignment.Center) {
3805 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3806 } else {
3807 line.align_shift = viewport_width - (int)line.widths[line.text.Length] - 1;
3811 Y += line.height;
3813 if (line_no > lines) {
3814 break;
3818 if (document_x != new_width) {
3819 document_x = new_width;
3820 if (WidthChanged != null) {
3821 WidthChanged(this, null);
3825 RecalculateAlignments();
3827 line = GetLine(lines);
3829 if (document_y != line.Y + line.height) {
3830 document_y = line.Y + line.height;
3831 if (HeightChanged != null) {
3832 HeightChanged(this, null);
3835 UpdateCaret();
3836 return changed;
3839 internal int Size() {
3840 return lines;
3843 private void owner_HandleCreated(object sender, EventArgs e) {
3844 RecalculateDocument(owner.CreateGraphicsInternal());
3845 AlignCaret();
3848 private void owner_VisibleChanged(object sender, EventArgs e) {
3849 if (owner.Visible) {
3850 RecalculateDocument(owner.CreateGraphicsInternal());
3854 internal static bool IsWordSeparator(char ch) {
3855 switch(ch) {
3856 case ' ':
3857 case '\t':
3858 case '(':
3859 case ')': {
3860 return true;
3863 default: {
3864 return false;
3868 internal int FindWordSeparator(Line line, int pos, bool forward) {
3869 int len;
3871 len = line.text.Length;
3873 if (forward) {
3874 for (int i = pos + 1; i < len; i++) {
3875 if (IsWordSeparator(line.Text[i])) {
3876 return i + 1;
3879 return len;
3880 } else {
3881 for (int i = pos - 1; i > 0; i--) {
3882 if (IsWordSeparator(line.Text[i - 1])) {
3883 return i;
3886 return 0;
3890 /* Search document for text */
3891 internal bool FindChars(char[] chars, Marker start, Marker end, out Marker result) {
3892 Line line;
3893 int line_no;
3894 int pos;
3895 int line_len;
3897 // Search for occurence of any char in the chars array
3898 result = new Marker();
3900 line = start.line;
3901 line_no = start.line.line_no;
3902 pos = start.pos;
3903 while (line_no <= end.line.line_no) {
3904 line_len = line.text.Length;
3905 while (pos < line_len) {
3906 for (int i = 0; i < chars.Length; i++) {
3907 if (line.text[pos] == chars[i]) {
3908 // Special case
3909 if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
3910 return false;
3913 result.line = line;
3914 result.pos = pos;
3915 return true;
3918 pos++;
3921 pos = 0;
3922 line_no++;
3923 line = GetLine(line_no);
3926 return false;
3929 // This version does not build one big string for searching, instead it handles
3930 // line-boundaries, which is faster and less memory intensive
3931 // FIXME - Depending on culture stuff we might have to create a big string and use culturespecific
3932 // search stuff and change it to accept and return positions instead of Markers (which would match
3933 // RichTextBox behaviour better but would be inconsistent with the rest of TextControl)
3934 internal bool Find(string search, Marker start, Marker end, out Marker result, RichTextBoxFinds options) {
3935 Marker last;
3936 string search_string;
3937 Line line;
3938 int line_no;
3939 int pos;
3940 int line_len;
3941 int current;
3942 bool word;
3943 bool word_option;
3944 bool ignore_case;
3945 bool reverse;
3946 char c;
3948 result = new Marker();
3949 word_option = ((options & RichTextBoxFinds.WholeWord) != 0);
3950 ignore_case = ((options & RichTextBoxFinds.MatchCase) == 0);
3951 reverse = ((options & RichTextBoxFinds.Reverse) != 0);
3953 line = start.line;
3954 line_no = start.line.line_no;
3955 pos = start.pos;
3956 current = 0;
3958 // Prep our search string, lowercasing it if we do case-independent matching
3959 if (ignore_case) {
3960 StringBuilder sb;
3961 sb = new StringBuilder(search);
3962 for (int i = 0; i < sb.Length; i++) {
3963 sb[i] = Char.ToLower(sb[i]);
3965 search_string = sb.ToString();
3966 } else {
3967 search_string = search;
3970 // We need to check if the character before our start position is a wordbreak
3971 if (word_option) {
3972 if (line_no == 1) {
3973 if ((pos == 0) || (IsWordSeparator(line.text[pos - 1]))) {
3974 word = true;
3975 } else {
3976 word = false;
3978 } else {
3979 if (pos > 0) {
3980 if (IsWordSeparator(line.text[pos - 1])) {
3981 word = true;
3982 } else {
3983 word = false;
3985 } else {
3986 // Need to check the end of the previous line
3987 Line prev_line;
3989 prev_line = GetLine(line_no - 1);
3990 if (prev_line.soft_break) {
3991 if (IsWordSeparator(prev_line.text[prev_line.text.Length - 1])) {
3992 word = true;
3993 } else {
3994 word = false;
3996 } else {
3997 word = true;
4001 } else {
4002 word = false;
4005 // To avoid duplication of this loop with reverse logic, we search
4006 // through the document, remembering the last match and when returning
4007 // report that last remembered match
4009 last = new Marker();
4010 last.height = -1; // Abused - we use it to track change
4012 while (line_no <= end.line.line_no) {
4013 if (line_no != end.line.line_no) {
4014 line_len = line.text.Length;
4015 } else {
4016 line_len = end.pos;
4019 while (pos < line_len) {
4020 if (word_option && (current == search_string.Length)) {
4021 if (IsWordSeparator(line.text[pos])) {
4022 if (!reverse) {
4023 goto FindFound;
4024 } else {
4025 last = result;
4026 current = 0;
4028 } else {
4029 current = 0;
4033 if (ignore_case) {
4034 c = Char.ToLower(line.text[pos]);
4035 } else {
4036 c = line.text[pos];
4039 if (c == search_string[current]) {
4040 if (current == 0) {
4041 result.line = line;
4042 result.pos = pos;
4044 if (!word_option || (word_option && (word || (current > 0)))) {
4045 current++;
4048 if (!word_option && (current == search_string.Length)) {
4049 if (!reverse) {
4050 goto FindFound;
4051 } else {
4052 last = result;
4053 current = 0;
4056 } else {
4057 current = 0;
4059 pos++;
4061 if (!word_option) {
4062 continue;
4065 if (IsWordSeparator(c)) {
4066 word = true;
4067 } else {
4068 word = false;
4072 if (word_option) {
4073 // Mark that we just saw a word boundary
4074 if (!line.soft_break) {
4075 word = true;
4078 if (current == search_string.Length) {
4079 if (word) {
4080 if (!reverse) {
4081 goto FindFound;
4082 } else {
4083 last = result;
4084 current = 0;
4086 } else {
4087 current = 0;
4092 pos = 0;
4093 line_no++;
4094 line = GetLine(line_no);
4097 if (reverse) {
4098 if (last.height != -1) {
4099 result = last;
4100 return true;
4104 return false;
4106 FindFound:
4107 if (!reverse) {
4108 // if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
4109 // return false;
4110 // }
4111 return true;
4114 result = last;
4115 return true;
4119 /* Marker stuff */
4120 internal void GetMarker(out Marker mark, bool start) {
4121 mark = new Marker();
4123 if (start) {
4124 mark.line = GetLine(1);
4125 mark.tag = mark.line.tags;
4126 mark.pos = 0;
4127 } else {
4128 mark.line = GetLine(lines);
4129 mark.tag = mark.line.tags;
4130 while (mark.tag.next != null) {
4131 mark.tag = mark.tag.next;
4133 mark.pos = mark.line.text.Length;
4136 #endregion // Internal Methods
4138 #region Events
4139 internal event EventHandler CaretMoved;
4140 internal event EventHandler WidthChanged;
4141 internal event EventHandler HeightChanged;
4142 internal event EventHandler LengthChanged;
4143 #endregion // Events
4145 #region Administrative
4146 public IEnumerator GetEnumerator() {
4147 // FIXME
4148 return null;
4151 public override bool Equals(object obj) {
4152 if (obj == null) {
4153 return false;
4156 if (!(obj is Document)) {
4157 return false;
4160 if (obj == this) {
4161 return true;
4164 if (ToString().Equals(((Document)obj).ToString())) {
4165 return true;
4168 return false;
4171 public override int GetHashCode() {
4172 return document_id;
4175 public override string ToString() {
4176 return "document " + this.document_id;
4178 #endregion // Administrative
4181 internal class LineTag {
4182 #region Local Variables;
4183 // Payload; formatting
4184 internal Font font; // System.Drawing.Font object for this tag
4185 internal Brush color; // System.Drawing.Brush object
4187 // Payload; text
4188 internal int start; // start, in chars; index into Line.text
4189 internal int length; // length, in chars
4190 internal bool r_to_l; // Which way is the font
4192 // Drawing support
4193 internal int height; // Height in pixels of the text this tag describes
4194 internal int X; // X location of the text this tag describes
4195 internal float width; // Width in pixels of the text this tag describes
4196 internal int ascent; // Ascent of the font for this tag
4197 internal int shift; // Shift down for this tag, to stay on baseline
4199 // Administrative
4200 internal Line line; // The line we're on
4201 internal LineTag next; // Next tag on the same line
4202 internal LineTag previous; // Previous tag on the same line
4203 #endregion;
4205 #region Constructors
4206 internal LineTag(Line line, int start, int length) {
4207 this.line = line;
4208 this.start = start;
4209 this.length = length;
4210 this.X = 0;
4211 this.width = 0;
4213 #endregion // Constructors
4215 #region Internal Methods
4216 ///<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>
4217 internal LineTag Break(int pos) {
4218 LineTag new_tag;
4220 // Sanity
4221 if (pos == this.start) {
4222 return this;
4223 } else if (pos >= (start + length)) {
4224 return null;
4227 new_tag = new LineTag(line, pos, start + length - pos);
4228 new_tag.color = color;
4229 new_tag.font = font;
4230 this.length -= new_tag.length;
4231 new_tag.next = this.next;
4232 this.next = new_tag;
4233 new_tag.previous = this;
4234 if (new_tag.next != null) {
4235 new_tag.next.previous = new_tag;
4238 return new_tag;
4241 ///<summary>Create new font and brush from existing font and given new attributes. Returns true if fontheight changes</summary>
4242 internal static bool GenerateTextFormat(Font font_from, Brush color_from, FontDefinition attributes, out Font new_font, out Brush new_color) {
4243 float size;
4244 string face;
4245 FontStyle style;
4246 GraphicsUnit unit;
4248 if (attributes.font_obj == null) {
4249 size = font_from.SizeInPoints;
4250 unit = font_from.Unit;
4251 face = font_from.Name;
4252 style = font_from.Style;
4254 if (attributes.face != null) {
4255 face = attributes.face;
4258 if (attributes.size != 0) {
4259 size = attributes.size;
4262 style |= attributes.add_style;
4263 style &= ~attributes.remove_style;
4265 // Create new font
4266 new_font = new Font(face, size, style, unit);
4267 } else {
4268 new_font = attributes.font_obj;
4271 // Create 'new' color brush
4272 if (attributes.color != Color.Empty) {
4273 new_color = new SolidBrush(attributes.color);
4274 } else {
4275 new_color = color_from;
4278 if (new_font.Height == font_from.Height) {
4279 return false;
4281 return true;
4284 /// <summary>Applies 'font' and 'brush' to characters starting at 'start' for 'length' chars;
4285 /// Removes any previous tags overlapping the same area;
4286 /// returns true if lineheight has changed</summary>
4287 /// <param name="start">1-based character position on line</param>
4288 internal static bool FormatText(Line line, int start, int length, Font font, Brush color) {
4289 LineTag tag;
4290 LineTag start_tag;
4291 LineTag end_tag;
4292 int end;
4293 bool retval = false; // Assume line-height doesn't change
4295 // Too simple?
4296 if (font.Height != line.height) {
4297 retval = true;
4299 line.recalc = true; // This forces recalculation of the line in RecalculateDocument
4301 // A little sanity, not sure if it's needed, might be able to remove for speed
4302 if (length > line.text.Length) {
4303 length = line.text.Length;
4306 tag = line.tags;
4307 end = start + length;
4309 // Common special case
4310 if ((start == 1) && (length == tag.length)) {
4311 tag.ascent = 0;
4312 tag.font = font;
4313 tag.color = color;
4314 return retval;
4317 //Console.WriteLine("Finding tag for {0} {1}", line, start);
4318 start_tag = FindTag(line, start);
4319 end_tag = FindTag (line, end);
4321 if (start_tag == null) { // FIXME - is there a better way to handle this, or do we even need it?
4322 throw new Exception(String.Format("Could not find start_tag in document at line {0} position {1}", line.line_no, start));
4325 tag = new LineTag(line, start, length);
4326 tag.font = font;
4327 tag.color = color;
4329 if (start == 1) {
4330 line.tags = tag;
4332 //Console.WriteLine("Start tag: '{0}'", start_tag!=null ? start_tag.ToString() : "NULL");
4333 if (start_tag.start == start) {
4334 tag.next = start_tag;
4335 tag.previous = start_tag.previous;
4336 if (start_tag.previous != null) {
4337 start_tag.previous.next = tag;
4339 start_tag.previous = tag;
4340 } else {
4341 tag.next = end_tag;
4343 if (end_tag != null) {
4344 // Shorten up the end tag
4345 end_tag.previous = tag;
4346 end_tag.length = end - start_tag.start + start_tag.length;
4347 end_tag.start = end;
4351 // Elimination loop
4352 tag = tag.next;
4353 while (tag != end_tag) {
4354 if ((tag.start + tag.length) <= end) {
4355 // remove the tag
4356 tag.previous.next = tag.next;
4357 if (tag.next != null) {
4358 tag.next.previous = tag.previous;
4360 tag = tag.previous;
4362 tag = tag.next;
4365 return retval;
4368 /// <summary>Applies font attributes specified to characters starting at 'start' for 'length' chars;
4369 /// Breaks tags at start and end point, keeping middle tags with altered attributes.
4370 /// Returns true if lineheight has changed</summary>
4371 /// <param name="start">1-based character position on line</param>
4372 internal static bool FormatText(Line line, int start, int length, FontDefinition attributes) {
4373 LineTag tag;
4374 LineTag start_tag;
4375 LineTag end_tag;
4376 bool retval = false; // Assume line-height doesn't change
4378 line.recalc = true; // This forces recalculation of the line in RecalculateDocument
4380 // A little sanity, not sure if it's needed, might be able to remove for speed
4381 if (length > line.text.Length) {
4382 length = line.text.Length;
4385 tag = line.tags;
4387 // Common special case
4388 if ((start == 1) && (length == tag.length)) {
4389 tag.ascent = 0;
4390 GenerateTextFormat(tag.font, tag.color, attributes, out tag.font, out tag.color);
4391 return retval;
4394 start_tag = FindTag(line, start);
4396 if (start_tag == null) {
4397 if (length == 0) {
4398 // We are 'starting' after all valid tags; create a new tag with the right attributes
4399 start_tag = FindTag(line, line.text.Length - 1);
4400 start_tag.next = new LineTag(line, line.text.Length + 1, 0);
4401 start_tag.next.font = start_tag.font;
4402 start_tag.next.color = start_tag.color;
4403 start_tag.next.previous = start_tag;
4404 start_tag = start_tag.next;
4405 } else {
4406 throw new Exception(String.Format("Could not find start_tag in document at line {0} position {1}", line.line_no, start));
4408 } else {
4409 start_tag = start_tag.Break(start);
4412 end_tag = FindTag(line, start + length);
4413 if (end_tag != null) {
4414 end_tag = end_tag.Break(start + length);
4417 // start_tag or end_tag might be null; we're cool with that
4418 // we now walk from start_tag to end_tag, applying new attributes
4419 tag = start_tag;
4420 while ((tag != null) && tag != end_tag) {
4421 if (LineTag.GenerateTextFormat(tag.font, tag.color, attributes, out tag.font, out tag.color)) {
4422 retval = true;
4424 tag = tag.next;
4426 return retval;
4430 /// <summary>Finds the tag that describes the character at position 'pos' on 'line'</summary>
4431 internal static LineTag FindTag(Line line, int pos) {
4432 LineTag tag = line.tags;
4434 // Beginning of line is a bit special
4435 if (pos == 0) {
4436 // Not sure if we should get the final tag here
4437 return tag;
4440 while (tag != null) {
4441 if ((tag.start <= pos) && (pos < (tag.start+tag.length))) {
4442 return GetFinalTag (tag);
4445 tag = tag.next;
4448 return null;
4451 // There can be multiple tags at the same position, we want to make
4452 // sure we are using the very last tag at the given position
4453 internal static LineTag GetFinalTag (LineTag tag)
4455 LineTag res = tag;
4457 while (res.next != null && res.next.length == 0)
4458 res = res.next;
4459 return res;
4462 /// <summary>Combines 'this' tag with 'other' tag</summary>
4463 internal bool Combine(LineTag other) {
4464 if (!this.Equals(other)) {
4465 return false;
4468 this.width += other.width;
4469 this.length += other.length;
4470 this.next = other.next;
4471 if (this.next != null) {
4472 this.next.previous = this;
4475 return true;
4479 /// <summary>Remove 'this' tag ; to be called when formatting is to be removed</summary>
4480 internal bool Remove() {
4481 if ((this.start == 1) && (this.next == null)) {
4482 // We cannot remove the only tag
4483 return false;
4485 if (this.start != 1) {
4486 this.previous.length += this.length;
4487 this.previous.width = -1;
4488 this.previous.next = this.next;
4489 this.next.previous = this.previous;
4490 } else {
4491 this.next.start = 1;
4492 this.next.length += this.length;
4493 this.next.width = -1;
4494 this.line.tags = this.next;
4495 this.next.previous = null;
4497 return true;
4501 /// <summary>Checks if 'this' tag describes the same formatting options as 'obj'</summary>
4502 public override bool Equals(object obj) {
4503 LineTag other;
4505 if (obj == null) {
4506 return false;
4509 if (!(obj is LineTag)) {
4510 return false;
4513 if (obj == this) {
4514 return true;
4517 other = (LineTag)obj;
4519 if (this.font.Equals(other.font) && this.color.Equals(other.color)) { // FIXME add checking for things like link or type later
4520 return true;
4523 return false;
4526 public override int GetHashCode() {
4527 return base.GetHashCode ();
4530 public override string ToString() {
4531 if (length > 0)
4532 return "Tag starts at index " + this.start + "length " + this.length + " text: " + this.line.Text.Substring(this.start-1, this.length) + "Font " + this.font.ToString();
4533 return "Zero Lengthed tag at index " + this.start;
4536 #endregion // Internal Methods
4539 internal class UndoClass {
4540 internal enum ActionType {
4541 InsertChar,
4542 InsertString,
4543 DeleteChar,
4544 DeleteChars,
4545 CursorMove,
4546 Mark,
4549 internal class Action {
4550 internal ActionType type;
4551 internal int line_no;
4552 internal int pos;
4553 internal object data;
4556 #region Local Variables
4557 private Document document;
4558 private Stack undo_actions;
4559 private Stack redo_actions;
4560 private int caret_line;
4561 private int caret_pos;
4562 #endregion // Local Variables
4564 #region Constructors
4565 internal UndoClass(Document doc) {
4566 document = doc;
4567 undo_actions = new Stack(50);
4568 redo_actions = new Stack(50);
4570 #endregion // Constructors
4572 #region Properties
4573 [MonoTODO("Change this to be configurable")]
4574 internal int UndoLevels {
4575 get {
4576 return undo_actions.Count;
4580 [MonoTODO("Change this to be configurable")]
4581 internal int RedoLevels {
4582 get {
4583 return redo_actions.Count;
4587 [MonoTODO("Come up with good naming and localization")]
4588 internal string UndoName {
4589 get {
4590 Action action;
4592 action = (Action)undo_actions.Peek();
4593 switch(action.type) {
4594 case ActionType.InsertChar: {
4595 Locale.GetText("Insert character");
4596 break;
4599 case ActionType.DeleteChar: {
4600 Locale.GetText("Delete character");
4601 break;
4604 case ActionType.InsertString: {
4605 Locale.GetText("Insert string");
4606 break;
4609 case ActionType.DeleteChars: {
4610 Locale.GetText("Delete string");
4611 break;
4614 case ActionType.CursorMove: {
4615 Locale.GetText("Cursor move");
4616 break;
4619 return null;
4623 internal string RedoName() {
4624 return null;
4626 #endregion // Properties
4628 #region Internal Methods
4629 internal void Clear() {
4630 undo_actions.Clear();
4631 redo_actions.Clear();
4634 internal void Undo() {
4635 Action action;
4637 if (undo_actions.Count == 0) {
4638 return;
4641 action = (Action)undo_actions.Pop();
4643 // Put onto redo stack
4644 redo_actions.Push(action);
4646 // Do the thing
4647 switch(action.type) {
4648 case ActionType.InsertChar: {
4649 // FIXME - implement me
4650 break;
4653 case ActionType.DeleteChars: {
4654 this.Insert(document.GetLine(action.line_no), action.pos, (Line)action.data);
4655 Undo(); // Grab the cursor location
4656 break;
4659 case ActionType.CursorMove: {
4660 document.caret.line = document.GetLine(action.line_no);
4661 if (document.caret.line == null) {
4662 Undo();
4663 break;
4666 document.caret.tag = document.caret.line.FindTag(action.pos);
4667 document.caret.pos = action.pos;
4668 document.caret.height = document.caret.tag.height;
4670 if (document.owner.IsHandleCreated) {
4671 XplatUI.DestroyCaret(document.owner.Handle);
4672 XplatUI.CreateCaret(document.owner.Handle, 2, document.caret.height);
4673 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);
4675 document.DisplayCaret ();
4678 // FIXME - enable call
4679 //if (document.CaretMoved != null) document.CaretMoved(this, EventArgs.Empty);
4680 break;
4685 internal void Redo() {
4686 if (redo_actions.Count == 0) {
4687 return;
4690 #endregion // Internal Methods
4692 #region Private Methods
4693 // pos = 1-based
4694 public void RecordDeleteChars(Line line, int pos, int length) {
4695 RecordDelete(line, pos, line, pos + length - 1);
4698 // start_pos, end_pos = 1 based
4699 public void RecordDelete(Line start_line, int start_pos, Line end_line, int end_pos) {
4700 Line l;
4701 Action a;
4703 l = Duplicate(start_line, start_pos, end_line, end_pos);
4705 a = new Action();
4706 a.type = ActionType.DeleteChars;
4707 a.data = l;
4708 a.line_no = start_line.line_no;
4709 a.pos = start_pos - 1;
4711 // Record the cursor position before, since the actions will occur in reverse order
4712 RecordCursor();
4713 undo_actions.Push(a);
4716 public void RecordCursor() {
4717 if (document.caret.line == null) {
4718 return;
4721 RecordCursor(document.caret.line, document.caret.pos);
4724 public void RecordCursor(Line line, int pos) {
4725 Action a;
4727 if ((line.line_no == caret_line) && (pos == caret_pos)) {
4728 return;
4731 caret_line = line.line_no;
4732 caret_pos = pos;
4734 a = new Action();
4735 a.type = ActionType.CursorMove;
4736 a.line_no = line.line_no;
4737 a.pos = pos;
4739 undo_actions.Push(a);
4742 // start_pos = 1-based
4743 // end_pos = 1-based
4744 public Line Duplicate(Line start_line, int start_pos, Line end_line, int end_pos) {
4745 Line ret;
4746 Line line;
4747 Line current;
4748 LineTag tag;
4749 LineTag current_tag;
4750 int start;
4751 int end;
4752 int tag_start;
4753 int tag_length;
4755 line = new Line();
4756 ret = line;
4758 for (int i = start_line.line_no; i <= end_line.line_no; i++) {
4759 current = document.GetLine(i);
4761 if (start_line.line_no == i) {
4762 start = start_pos;
4763 } else {
4764 start = 1;
4767 if (end_line.line_no == i) {
4768 end = end_pos;
4769 } else {
4770 end = current.text.Length;
4773 // Text for the tag
4774 line.text = new StringBuilder(current.text.ToString(start - 1, end - start + 1));
4776 // Copy tags from start to start+length onto new line
4777 current_tag = current.FindTag(start - 1);
4778 while ((current_tag != null) && (current_tag.start < end)) {
4779 if ((current_tag.start <= start) && (start < (current_tag.start + current_tag.length))) {
4780 // start tag is within this tag
4781 tag_start = start;
4782 } else {
4783 tag_start = current_tag.start;
4786 if (end < (current_tag.start + current_tag.length)) {
4787 tag_length = end - tag_start + 1;
4788 } else {
4789 tag_length = current_tag.start + current_tag.length - tag_start;
4791 tag = new LineTag(line, tag_start - start + 1, tag_length);
4792 tag.color = current_tag.color;
4793 tag.font = current_tag.font;
4795 current_tag = current_tag.next;
4797 // Add the new tag to the line
4798 if (line.tags == null) {
4799 line.tags = tag;
4800 } else {
4801 LineTag tail;
4802 tail = line.tags;
4804 while (tail.next != null) {
4805 tail = tail.next;
4807 tail.next = tag;
4808 tag.previous = tail;
4812 if ((i + 1) <= end_line.line_no) {
4813 line.soft_break = current.soft_break;
4815 // Chain them (we use right/left as next/previous)
4816 line.right = new Line();
4817 line.right.left = line;
4818 line = line.right;
4822 return ret;
4825 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
4826 internal void Insert(Line line, int pos, Line insert) {
4827 Line current;
4828 LineTag tag;
4829 int offset;
4830 int lines;
4831 Line first;
4833 // Handle special case first
4834 if (insert.right == null) {
4836 // Single line insert
4837 document.Split(line, pos);
4839 if (insert.tags == null) {
4840 return; // Blank line
4843 //Insert our tags at the end
4844 tag = line.tags;
4846 while (tag.next != null) {
4847 tag = tag.next;
4850 offset = tag.start + tag.length - 1;
4852 tag.next = insert.tags;
4853 line.text.Insert(offset, insert.text.ToString());
4855 // Adjust start locations
4856 tag = tag.next;
4857 while (tag != null) {
4858 tag.start += offset;
4859 tag.line = line;
4860 tag = tag.next;
4862 // Put it back together
4863 document.Combine(line.line_no, line.line_no + 1);
4864 document.UpdateView(line, pos);
4865 return;
4868 first = line;
4869 lines = 1;
4870 current = insert;
4871 while (current != null) {
4872 if (current == insert) {
4873 // Inserting the first line we split the line (and make space)
4874 document.Split(line, pos);
4875 //Insert our tags at the end of the line
4876 tag = line.tags;
4878 if (tag != null) {
4879 while (tag.next != null) {
4880 tag = tag.next;
4882 offset = tag.start + tag.length - 1;
4883 tag.next = current.tags;
4884 tag.next.previous = tag;
4886 tag = tag.next;
4888 } else {
4889 offset = 0;
4890 line.tags = current.tags;
4891 line.tags.previous = null;
4892 tag = line.tags;
4894 } else {
4895 document.Split(line.line_no, 0);
4896 offset = 0;
4897 line.tags = current.tags;
4898 line.tags.previous = null;
4899 tag = line.tags;
4901 // Adjust start locations and line pointers
4902 while (tag != null) {
4903 tag.start += offset;
4904 tag.line = line;
4905 tag = tag.next;
4908 line.text.Insert(offset, current.text.ToString());
4909 line.Grow(line.text.Length);
4911 line.recalc = true;
4912 line = document.GetLine(line.line_no + 1);
4914 // FIXME? Test undo of line-boundaries
4915 if ((current.right == null) && (current.tags.length != 0)) {
4916 document.Combine(line.line_no - 1, line.line_no);
4918 current = current.right;
4919 lines++;
4923 // Recalculate our document
4924 document.UpdateView(first, lines, pos);
4925 return;
4927 #endregion // Private Methods