* TextControl.cs: Need to prevent wrap calculations when
[mono-project.git] / mcs / class / Managed.Windows.Forms / System.Windows.Forms / TextControl.cs
blobf468048ef788c8ff728087204fc18950656aa5b8
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;
75 [Flags]
76 internal enum FormatSpecified {
77 None,
79 BackColor = 2,
80 Font = 4,
81 Color = 8,
84 internal enum CaretDirection {
85 CharForward, // Move a char to the right
86 CharBack, // Move a char to the left
87 LineUp, // Move a line up
88 LineDown, // Move a line down
89 Home, // Move to the beginning of the line
90 End, // Move to the end of the line
91 PgUp, // Move one page up
92 PgDn, // Move one page down
93 CtrlPgUp, // Move caret to the first visible char in the viewport
94 CtrlPgDn, // Move caret to the last visible char in the viewport
95 CtrlHome, // Move to the beginning of the document
96 CtrlEnd, // Move to the end of the document
97 WordBack, // Move to the beginning of the previous word (or beginning of line)
98 WordForward, // Move to the beginning of the next word (or end of line)
99 SelectionStart, // Move to the beginning of the current selection
100 SelectionEnd, // Move to the end of the current selection
101 CharForwardNoWrap, // Move a char forward, but don't wrap onto the next line
102 CharBackNoWrap // Move a char backward, but don't wrap onto the previous line
105 // Being cloneable should allow for nice line and document copies...
106 internal class Line : ICloneable, IComparable {
107 #region Local Variables
108 // Stuff that matters for our line
109 internal StringBuilder text; // Characters for the line
110 internal float[] widths; // Width of each character; always one larger than text.Length
111 internal int space; // Number of elements in text and widths
112 internal int line_no; // Line number
113 internal LineTag tags; // Tags describing the text
114 internal int Y; // Baseline
115 internal int height; // Height of the line (height of tallest tag)
116 internal int ascent; // Ascent of the line (ascent of the tallest tag)
117 internal HorizontalAlignment alignment; // Alignment of the line
118 internal int align_shift; // Pixel shift caused by the alignment
119 internal bool soft_break; // Tag is 'broken soft' and continuation from previous line
120 internal int indent; // Left indent for the first line
121 internal int hanging_indent; // Hanging indent (left indent for all but the first line)
122 internal int right_indent; // Right indent for all lines
123 internal bool carriage_return;
126 // Stuff that's important for the tree
127 internal Line parent; // Our parent line
128 internal Line left; // Line with smaller line number
129 internal Line right; // Line with higher line number
130 internal LineColor color; // We're doing a black/red tree. this is the node color
131 internal int DEFAULT_TEXT_LEN; //
132 internal static StringFormat string_format; // For calculating widths/heights
133 internal bool recalc; // Line changed
134 #endregion // Local Variables
136 #region Constructors
137 internal Line() {
138 color = LineColor.Red;
139 left = null;
140 right = null;
141 parent = null;
142 text = null;
143 recalc = true;
144 soft_break = false;
145 alignment = HorizontalAlignment.Left;
147 if (string_format == null) {
148 string_format = new StringFormat(StringFormat.GenericTypographic);
149 string_format.Trimming = StringTrimming.None;
150 string_format.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;
154 internal Line(int LineNo, string Text, Font font, SolidBrush color) : this() {
155 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
157 text = new StringBuilder(Text, space);
158 line_no = LineNo;
160 widths = new float[space + 1];
161 tags = new LineTag(this, 1, text.Length);
162 tags.font = font;
163 tags.color = color;
166 internal Line(int LineNo, string Text, HorizontalAlignment align, Font font, SolidBrush color) : this() {
167 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
169 text = new StringBuilder(Text, space);
170 line_no = LineNo;
171 alignment = align;
173 widths = new float[space + 1];
174 tags = new LineTag(this, 1, text.Length);
175 tags.font = font;
176 tags.color = color;
179 internal Line(int LineNo, string Text, LineTag tag) : this() {
180 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
182 text = new StringBuilder(Text, space);
183 line_no = LineNo;
185 widths = new float[space + 1];
186 tags = tag;
189 #endregion // Constructors
191 #region Internal Properties
192 internal int Indent {
193 get {
194 return indent;
197 set {
198 indent = value;
199 recalc = true;
203 internal int HangingIndent {
204 get {
205 return hanging_indent;
208 set {
209 hanging_indent = value;
210 recalc = true;
214 internal int RightIndent {
215 get {
216 return right_indent;
219 set {
220 right_indent = value;
221 recalc = true;
226 internal int Height {
227 get {
228 return height;
231 set {
232 height = value;
236 internal int LineNo {
237 get {
238 return line_no;
241 set {
242 line_no = value;
246 internal string Text {
247 get {
248 return text.ToString();
251 set {
252 text = new StringBuilder(value, value.Length > DEFAULT_TEXT_LEN ? value.Length : DEFAULT_TEXT_LEN);
256 internal HorizontalAlignment Alignment {
257 get {
258 return alignment;
261 set {
262 if (alignment != value) {
263 alignment = value;
264 recalc = true;
268 #if no
269 internal StringBuilder Text {
270 get {
271 return text;
274 set {
275 text = value;
278 #endif
279 #endregion // Internal Properties
281 #region Internal Methods
282 // Make sure we always have enoughs space in text and widths
283 internal void Grow(int minimum) {
284 int length;
285 float[] new_widths;
287 length = text.Length;
289 if ((length + minimum) > space) {
290 // We need to grow; double the size
292 if ((length + minimum) > (space * 2)) {
293 new_widths = new float[length + minimum * 2 + 1];
294 space = length + minimum * 2;
295 } else {
296 new_widths = new float[space * 2 + 1];
297 space *= 2;
299 widths.CopyTo(new_widths, 0);
301 widths = new_widths;
305 internal void Streamline(int lines) {
306 LineTag current;
307 LineTag next;
309 current = this.tags;
310 next = current.next;
312 // Catch what the loop below wont; eliminate 0 length
313 // tags, but only if there are other tags after us
314 while ((current.length == 0) && (next != null)) {
315 tags = next;
316 tags.previous = null;
317 current = next;
318 next = current.next;
321 if (next == null) {
322 return;
325 while (next != null) {
326 // Take out 0 length tags unless it's the last tag in the document
327 if (next.length == 0) {
328 if ((next.next != null) || (line_no != lines)) {
329 current.next = next.next;
330 if (current.next != null) {
331 current.next.previous = current;
333 next = current.next;
334 continue;
337 if (current.Combine(next)) {
338 next = current.next;
339 continue;
342 current = current.next;
343 next = current.next;
347 /// <summary> Find the tag on a line based on the character position, pos is 0-based</summary>
348 internal LineTag FindTag(int pos) {
349 LineTag tag;
351 if (pos == 0) {
352 return tags;
355 tag = this.tags;
357 if (pos >= text.Length) {
358 pos = text.Length - 1;
361 while (tag != null) {
362 if (((tag.start - 1) <= pos) && (pos < (tag.start + tag.length - 1))) {
363 return LineTag.GetFinalTag (tag);
365 tag = tag.next;
367 return null;
370 /// <summary>
371 /// Recalculate a single line using the same char for every character in the line
372 /// </summary>
374 internal bool RecalculatePasswordLine(Graphics g, Document doc) {
375 LineTag tag;
376 int pos;
377 int len;
378 float w;
379 bool ret;
380 int descent;
382 pos = 0;
383 len = this.text.Length;
384 tag = this.tags;
385 ascent = 0;
386 tag.shift = 0;
388 this.recalc = false;
389 widths[0] = indent;
390 tag.X = indent;
392 w = g.MeasureString(doc.password_char, tags.font, 10000, string_format).Width;
394 if (this.height != (int)tag.font.Height) {
395 ret = true;
396 } else {
397 ret = false;
400 this.height = (int)tag.font.Height;
401 tag.height = this.height;
403 XplatUI.GetFontMetrics(g, tag.font, out tag.ascent, out descent);
404 this.ascent = tag.ascent;
406 while (pos < len) {
407 pos++;
408 widths[pos] = widths[pos-1] + w;
411 return ret;
414 /// <summary>
415 /// Go through all tags on a line and recalculate all size-related values;
416 /// returns true if lineheight changed
417 /// </summary>
418 internal bool RecalculateLine(Graphics g, Document doc) {
419 LineTag tag;
420 int pos;
421 int len;
422 SizeF size;
423 float w;
424 int prev_height;
425 bool retval;
426 bool wrapped;
427 Line line;
428 int wrap_pos;
429 float wrap_width;
431 pos = 0;
432 len = this.text.Length;
433 tag = this.tags;
434 prev_height = this.height; // For drawing optimization calculations
435 this.height = 0; // Reset line height
436 this.ascent = 0; // Reset the ascent for the line
437 tag.shift = 0;
439 if (this.soft_break) {
440 widths[0] = hanging_indent;
441 } else {
442 widths[0] = indent;
445 this.recalc = false;
446 retval = false;
447 wrapped = false;
449 wrap_pos = 0;
450 wrap_width = 0;
452 while (pos < len) {
453 size = g.MeasureString(this.text.ToString(pos, 1), tag.font, 10000, string_format);
455 while (tag.length == 0) { // We should always have tags after a tag.length==0 unless len==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.shift = 0;
466 w = size.Width;
468 if (Char.IsWhiteSpace(text[pos])) {
469 wrap_pos = pos + 1;
470 wrap_width = tag.width + w;
473 if (doc.wrap) {
474 if ((wrap_pos > 0) && (wrap_pos != len) && (widths[pos] + w) + 5 > (doc.viewport_width - this.right_indent)) {
475 // Make sure to set the last width of the line before wrapping
476 widths [pos + 1] = widths [pos] + w;
478 pos = wrap_pos;
479 doc.Split(this, tag, pos, this.soft_break);
480 this.soft_break = true;
481 len = this.text.Length;
482 retval = true;
483 wrapped = true;
484 } else if (pos > 1 && (widths[pos] + w) > (doc.viewport_width - this.right_indent)) {
485 // No suitable wrap position was found so break right in the middle of a word
487 // Make sure to set the last width of the line before wrapping
488 widths [pos + 1] = widths [pos] + w;
490 doc.Split(this, tag, pos, this.soft_break);
491 this.soft_break = true;
492 len = this.text.Length;
493 retval = true;
494 wrapped = true;
498 // Contract all soft lines that follow back into our line
499 if (!wrapped) {
500 pos++;
502 widths[pos] = widths[pos-1] + w;
504 if (pos == len) {
505 line = doc.GetLine(this.line_no + 1);
506 if ((line != null) && soft_break) {
507 // Pull the two lines together
508 doc.Combine(this.line_no, this.line_no + 1);
509 len = this.text.Length;
510 retval = true;
515 if (pos == (tag.start-1 + tag.length)) {
516 // We just found the end of our current tag
517 tag.height = (int)tag.font.Height;
519 // Check if we're the tallest on the line (so far)
520 if (tag.height > this.height) {
521 this.height = tag.height; // Yep; make sure the line knows
524 if (tag.ascent == 0) {
525 int descent;
527 XplatUI.GetFontMetrics(g, tag.font, out tag.ascent, out descent);
530 if (tag.ascent > this.ascent) {
531 LineTag t;
533 // We have a tag that has a taller ascent than the line;
534 t = tags;
535 while (t != null && t != tag) {
536 t.shift = tag.ascent - t.ascent;
537 t = t.next;
540 // Save on our line
541 this.ascent = tag.ascent;
542 } else {
543 tag.shift = this.ascent - tag.ascent;
546 // Update our horizontal starting pixel position
547 if (tag.previous == null) {
548 tag.X = (int)widths[0];
549 } else {
550 tag.X = tag.previous.X + (int)tag.previous.width;
553 tag = tag.next;
554 if (tag != null) {
555 tag.shift = 0;
556 wrap_pos = pos;
557 wrap_width = tag.width;
562 if (this.height == 0) {
563 this.height = tags.font.Height;
564 tag.height = this.height;
567 if (prev_height != this.height) {
568 retval = true;
570 return retval;
572 #endregion // Internal Methods
574 #region Administrative
575 public int CompareTo(object obj) {
576 if (obj == null) {
577 return 1;
580 if (! (obj is Line)) {
581 throw new ArgumentException("Object is not of type Line", "obj");
584 if (line_no < ((Line)obj).line_no) {
585 return -1;
586 } else if (line_no > ((Line)obj).line_no) {
587 return 1;
588 } else {
589 return 0;
593 public object Clone() {
594 Line clone;
596 clone = new Line();
598 clone.text = text;
600 if (left != null) {
601 clone.left = (Line)left.Clone();
604 if (left != null) {
605 clone.left = (Line)left.Clone();
608 return clone;
611 internal object CloneLine() {
612 Line clone;
614 clone = new Line();
616 clone.text = text;
618 return clone;
621 public override bool Equals(object obj) {
622 if (obj == null) {
623 return false;
626 if (!(obj is Line)) {
627 return false;
630 if (obj == this) {
631 return true;
634 if (line_no == ((Line)obj).line_no) {
635 return true;
638 return false;
641 public override int GetHashCode() {
642 return base.GetHashCode ();
645 public override string ToString() {
646 return "Line " + line_no;
649 #endregion // Administrative
652 internal class Document : ICloneable, IEnumerable {
653 #region Structures
654 // FIXME - go through code and check for places where
655 // we do explicit comparisons instead of using the compare overloads
656 internal struct Marker {
657 internal Line line;
658 internal LineTag tag;
659 internal int pos;
660 internal int height;
662 public static bool operator<(Marker lhs, Marker rhs) {
663 if (lhs.line.line_no < rhs.line.line_no) {
664 return true;
667 if (lhs.line.line_no == rhs.line.line_no) {
668 if (lhs.pos < rhs.pos) {
669 return true;
672 return false;
675 public static bool operator>(Marker lhs, Marker rhs) {
676 if (lhs.line.line_no > rhs.line.line_no) {
677 return true;
680 if (lhs.line.line_no == rhs.line.line_no) {
681 if (lhs.pos > rhs.pos) {
682 return true;
685 return false;
688 public static bool operator==(Marker lhs, Marker rhs) {
689 if ((lhs.line.line_no == rhs.line.line_no) && (lhs.pos == rhs.pos)) {
690 return true;
692 return false;
695 public static bool operator!=(Marker lhs, Marker rhs) {
696 if ((lhs.line.line_no != rhs.line.line_no) || (lhs.pos != rhs.pos)) {
697 return true;
699 return false;
702 public void Combine(Line move_to_line, int move_to_line_length) {
703 line = move_to_line;
704 pos += move_to_line_length;
705 tag = LineTag.FindTag(line, pos);
708 // This is for future use, right now Document.Split does it by hand, with some added shortcut logic
709 public void Split(Line move_to_line, int split_at) {
710 line = move_to_line;
711 pos -= split_at;
712 tag = LineTag.FindTag(line, pos);
715 public override bool Equals(object obj) {
716 return this==(Marker)obj;
719 public override int GetHashCode() {
720 return base.GetHashCode ();
723 public override string ToString() {
724 return "Marker Line " + line + ", Position " + pos;
728 #endregion Structures
730 #region Local Variables
731 private Line document;
732 private int lines;
733 private Line sentinel;
734 private int document_id;
735 private Random random = new Random();
736 internal string password_char;
737 private StringBuilder password_cache;
738 private bool calc_pass;
739 private int char_count;
741 private bool no_recalc;
742 private bool recalc_pending;
743 private int recalc_start;
744 private int recalc_end;
745 private bool recalc_optimize;
747 internal bool multiline;
748 internal bool wrap;
750 internal UndoClass undo;
752 internal Marker caret;
753 internal Marker selection_start;
754 internal Marker selection_end;
755 internal bool selection_visible;
756 internal Marker selection_anchor;
757 internal Marker selection_prev;
758 internal bool selection_end_anchor;
760 internal int viewport_x;
761 internal int viewport_y; // The visible area of the document
762 internal int viewport_width;
763 internal int viewport_height;
765 internal int document_x; // Width of the document
766 internal int document_y; // Height of the document
768 internal Rectangle invalid;
770 internal int crlf_size; // 1 or 2, depending on whether we use \r\n or just \n
772 internal TextBoxBase owner; // Who's owning us?
773 static internal int caret_width = 1;
774 static internal int caret_shift = 1;
775 #endregion // Local Variables
777 #region Constructors
778 internal Document(TextBoxBase owner) {
779 lines = 0;
781 this.owner = owner;
783 multiline = true;
784 password_char = "";
785 calc_pass = false;
786 no_recalc = false;
787 recalc_pending = false;
789 // Tree related stuff
790 sentinel = new Line();
791 sentinel.color = LineColor.Black;
793 document = 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}, soft: {3}, Text: '{4}'",
1023 line.line_no, line.GetHashCode(), line.Y, line.soft_break,
1024 line.text != null ? line.text.ToString() : "undefined");
1026 if (line.left == sentinel) {
1027 Console.Write(", left = sentinel");
1028 } else if (line.left == null) {
1029 Console.Write(", left = NULL");
1032 if (line.right == sentinel) {
1033 Console.Write(", right = sentinel");
1034 } else if (line.right == null) {
1035 Console.Write(", right = NULL");
1038 Console.WriteLine("");
1040 if (with_tags) {
1041 LineTag tag;
1042 int count;
1043 int length;
1045 tag = line.tags;
1046 count = 1;
1047 length = 0;
1048 Console.Write(" Tags: ");
1049 while (tag != null) {
1050 Console.Write("{0} <{1}>-<{2}>", count++, tag.start, tag.start + tag.length
1051 /*line.text.ToString (tag.start - 1, tag.length)*/);
1052 length += tag.length;
1054 if (tag.line != line) {
1055 Console.Write("BAD line link");
1056 throw new Exception("Bad line link in tree");
1058 tag = tag.next;
1059 if (tag != null) {
1060 Console.Write(", ");
1063 if (length > line.text.Length) {
1064 throw new Exception(String.Format("Length of tags more than length of text on line (expected {0} calculated {1})", line.text.Length, length));
1065 } else if (length < line.text.Length) {
1066 throw new Exception(String.Format("Length of tags less than length of text on line (expected {0} calculated {1})", line.text.Length, length));
1068 Console.WriteLine("");
1070 if (line.left != null) {
1071 if (line.left != sentinel) {
1072 total += DumpTree(line.left, with_tags);
1074 } else {
1075 if (line != sentinel) {
1076 throw new Exception("Left should not be NULL");
1080 if (line.right != null) {
1081 if (line.right != sentinel) {
1082 total += DumpTree(line.right, with_tags);
1084 } else {
1085 if (line != sentinel) {
1086 throw new Exception("Right should not be NULL");
1090 for (int i = 1; i <= this.lines; i++) {
1091 if (GetLine(i) == null) {
1092 throw new Exception(String.Format("Hole in line order, missing {0}", i));
1096 if (line == this.Root) {
1097 if (total < this.lines) {
1098 throw new Exception(String.Format("Not enough nodes in tree, found {0}, expected {1}", total, this.lines));
1099 } else if (total > this.lines) {
1100 throw new Exception(String.Format("Too many nodes in tree, found {0}, expected {1}", total, this.lines));
1104 return total;
1107 private void SetSelectionVisible (bool value)
1109 selection_visible = value;
1111 // cursor and selection are enemies, we can't have both in the same room at the same time
1112 if (owner.IsHandleCreated && !owner.show_caret_w_selection)
1113 XplatUI.CaretVisible (owner.Handle, !selection_visible);
1116 private void DecrementLines(int line_no) {
1117 int current;
1119 current = line_no;
1120 while (current <= lines) {
1121 GetLine(current).line_no--;
1122 current++;
1124 return;
1127 private void IncrementLines(int line_no) {
1128 int current;
1130 current = this.lines;
1131 while (current >= line_no) {
1132 GetLine(current).line_no++;
1133 current--;
1135 return;
1138 private void RebalanceAfterAdd(Line line1) {
1139 Line line2;
1141 while ((line1 != document) && (line1.parent.color == LineColor.Red)) {
1142 if (line1.parent == line1.parent.parent.left) {
1143 line2 = line1.parent.parent.right;
1145 if ((line2 != null) && (line2.color == LineColor.Red)) {
1146 line1.parent.color = LineColor.Black;
1147 line2.color = LineColor.Black;
1148 line1.parent.parent.color = LineColor.Red;
1149 line1 = line1.parent.parent;
1150 } else {
1151 if (line1 == line1.parent.right) {
1152 line1 = line1.parent;
1153 RotateLeft(line1);
1156 line1.parent.color = LineColor.Black;
1157 line1.parent.parent.color = LineColor.Red;
1159 RotateRight(line1.parent.parent);
1161 } else {
1162 line2 = line1.parent.parent.left;
1164 if ((line2 != null) && (line2.color == LineColor.Red)) {
1165 line1.parent.color = LineColor.Black;
1166 line2.color = LineColor.Black;
1167 line1.parent.parent.color = LineColor.Red;
1168 line1 = line1.parent.parent;
1169 } else {
1170 if (line1 == line1.parent.left) {
1171 line1 = line1.parent;
1172 RotateRight(line1);
1175 line1.parent.color = LineColor.Black;
1176 line1.parent.parent.color = LineColor.Red;
1177 RotateLeft(line1.parent.parent);
1181 document.color = LineColor.Black;
1184 private void RebalanceAfterDelete(Line line1) {
1185 Line line2;
1187 while ((line1 != document) && (line1.color == LineColor.Black)) {
1188 if (line1 == line1.parent.left) {
1189 line2 = line1.parent.right;
1190 if (line2.color == LineColor.Red) {
1191 line2.color = LineColor.Black;
1192 line1.parent.color = LineColor.Red;
1193 RotateLeft(line1.parent);
1194 line2 = line1.parent.right;
1196 if ((line2.left.color == LineColor.Black) && (line2.right.color == LineColor.Black)) {
1197 line2.color = LineColor.Red;
1198 line1 = line1.parent;
1199 } else {
1200 if (line2.right.color == LineColor.Black) {
1201 line2.left.color = LineColor.Black;
1202 line2.color = LineColor.Red;
1203 RotateRight(line2);
1204 line2 = line1.parent.right;
1206 line2.color = line1.parent.color;
1207 line1.parent.color = LineColor.Black;
1208 line2.right.color = LineColor.Black;
1209 RotateLeft(line1.parent);
1210 line1 = document;
1212 } else {
1213 line2 = line1.parent.left;
1214 if (line2.color == LineColor.Red) {
1215 line2.color = LineColor.Black;
1216 line1.parent.color = LineColor.Red;
1217 RotateRight(line1.parent);
1218 line2 = line1.parent.left;
1220 if ((line2.right.color == LineColor.Black) && (line2.left.color == LineColor.Black)) {
1221 line2.color = LineColor.Red;
1222 line1 = line1.parent;
1223 } else {
1224 if (line2.left.color == LineColor.Black) {
1225 line2.right.color = LineColor.Black;
1226 line2.color = LineColor.Red;
1227 RotateLeft(line2);
1228 line2 = line1.parent.left;
1230 line2.color = line1.parent.color;
1231 line1.parent.color = LineColor.Black;
1232 line2.left.color = LineColor.Black;
1233 RotateRight(line1.parent);
1234 line1 = document;
1238 line1.color = LineColor.Black;
1241 private void RotateLeft(Line line1) {
1242 Line line2 = line1.right;
1244 line1.right = line2.left;
1246 if (line2.left != sentinel) {
1247 line2.left.parent = line1;
1250 if (line2 != sentinel) {
1251 line2.parent = line1.parent;
1254 if (line1.parent != null) {
1255 if (line1 == line1.parent.left) {
1256 line1.parent.left = line2;
1257 } else {
1258 line1.parent.right = line2;
1260 } else {
1261 document = line2;
1264 line2.left = line1;
1265 if (line1 != sentinel) {
1266 line1.parent = line2;
1270 private void RotateRight(Line line1) {
1271 Line line2 = line1.left;
1273 line1.left = line2.right;
1275 if (line2.right != sentinel) {
1276 line2.right.parent = line1;
1279 if (line2 != sentinel) {
1280 line2.parent = line1.parent;
1283 if (line1.parent != null) {
1284 if (line1 == line1.parent.right) {
1285 line1.parent.right = line2;
1286 } else {
1287 line1.parent.left = line2;
1289 } else {
1290 document = line2;
1293 line2.right = line1;
1294 if (line1 != sentinel) {
1295 line1.parent = line2;
1300 internal void UpdateView(Line line, int pos) {
1301 if (!owner.IsHandleCreated) {
1302 return;
1305 if (no_recalc) {
1306 recalc_start = line.line_no;
1307 recalc_end = line.line_no;
1308 recalc_optimize = true;
1309 recalc_pending = true;
1310 return;
1313 // Optimize invalidation based on Line alignment
1314 if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no, true)) {
1315 // Lineheight changed, invalidate the rest of the document
1316 if ((line.Y - viewport_y) >=0 ) {
1317 // We formatted something that's in view, only draw parts of the screen
1318 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, owner.Height - line.Y - viewport_y));
1319 } else {
1320 // The tag was above the visible area, draw everything
1321 owner.Invalidate();
1323 } else {
1324 switch(line.alignment) {
1325 case HorizontalAlignment.Left: {
1326 owner.Invalidate(new Rectangle((int)line.widths[pos] - viewport_x - 1, line.Y - viewport_y, viewport_width, line.height + 1));
1327 break;
1330 case HorizontalAlignment.Center: {
1331 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, line.height + 1));
1332 break;
1335 case HorizontalAlignment.Right: {
1336 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, (int)line.widths[pos + 1] - viewport_x + line.align_shift, line.height + 1));
1337 break;
1344 // Update display from line, down line_count lines; pos is unused, but required for the signature
1345 internal void UpdateView(Line line, int line_count, int pos) {
1346 if (!owner.IsHandleCreated) {
1347 return;
1350 if (no_recalc) {
1351 recalc_start = line.line_no;
1352 recalc_end = line.line_no + line_count - 1;
1353 recalc_optimize = true;
1354 recalc_pending = true;
1355 return;
1358 if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no + line_count - 1, true)) {
1359 // Lineheight changed, invalidate the rest of the document
1360 if ((line.Y - viewport_y) >=0 ) {
1361 // We formatted something that's in view, only draw parts of the screen
1362 //blah Console.WriteLine("TextControl.cs(981) Invalidate called in UpdateView(line, line_count, pos)");
1363 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, owner.Height - line.Y - viewport_y));
1364 } else {
1365 // The tag was above the visible area, draw everything
1366 //blah Console.WriteLine("TextControl.cs(985) Invalidate called in UpdateView(line, line_count, pos)");
1367 owner.Invalidate();
1369 } else {
1370 Line end_line;
1372 end_line = GetLine(line.line_no + line_count -1);
1373 if (end_line == null) {
1374 end_line = line;
1377 //blah Console.WriteLine("TextControl.cs(996) Invalidate called in UpdateView(line, line_count, pos)");
1378 owner.Invalidate(new Rectangle(0 - viewport_x, line.Y - viewport_y, (int)line.widths[line.text.Length], end_line.Y + end_line.height));
1381 #endregion // Private Methods
1383 #region Internal Methods
1384 // Clear the document and reset state
1385 internal void Empty() {
1387 document = sentinel;
1388 lines = 0;
1390 // We always have a blank line
1391 Add(1, "", owner.Font, ThemeEngine.Current.ResPool.GetSolidBrush(owner.ForeColor));
1392 Line l = GetLine (1);
1393 l.soft_break = true;
1395 this.RecalculateDocument(owner.CreateGraphicsInternal());
1396 PositionCaret(0, 0);
1398 SetSelectionVisible (false);
1400 selection_start.line = this.document;
1401 selection_start.pos = 0;
1402 selection_start.tag = selection_start.line.tags;
1403 selection_end.line = this.document;
1404 selection_end.pos = 0;
1405 selection_end.tag = selection_end.line.tags;
1406 char_count = 0;
1408 viewport_x = 0;
1409 viewport_y = 0;
1411 document_x = 0;
1412 document_y = 0;
1414 if (owner.IsHandleCreated)
1415 owner.Invalidate ();
1418 internal void PositionCaret(Line line, int pos) {
1419 if (owner.IsHandleCreated) {
1420 undo.RecordCursor();
1423 caret.tag = line.FindTag(pos);
1424 caret.line = line;
1425 caret.pos = pos;
1427 if (owner.IsHandleCreated) {
1428 if (owner.Focused) {
1429 if (caret.height != caret.tag.height)
1430 XplatUI.CreateCaret (owner.Handle, caret_width, caret.height);
1431 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.align_shift - viewport_x, caret.line.Y + caret.tag.shift - viewport_y + caret_shift);
1434 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1437 // We set this at the end because we use the heights to determine whether or
1438 // not we need to recreate the caret
1439 caret.height = caret.tag.height;
1443 internal void PositionCaret(int x, int y) {
1444 if (!owner.IsHandleCreated) {
1445 return;
1448 undo.RecordCursor();
1450 caret.tag = FindCursor(x, y, out caret.pos);
1451 caret.line = caret.tag.line;
1452 caret.height = caret.tag.height;
1454 if (owner.Focused) {
1455 XplatUI.CreateCaret (owner.Handle, caret_width, caret.height);
1456 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);
1459 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1462 internal void CaretHasFocus() {
1463 if ((caret.tag != null) && owner.IsHandleCreated) {
1464 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1465 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);
1467 DisplayCaret ();
1470 if (owner.IsHandleCreated && selection_visible) {
1471 InvalidateSelectionArea ();
1475 internal void CaretLostFocus() {
1476 if (!owner.IsHandleCreated) {
1477 return;
1479 XplatUI.DestroyCaret(owner.Handle);
1482 internal void AlignCaret() {
1483 if (!owner.IsHandleCreated) {
1484 return;
1487 undo.RecordCursor();
1489 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1490 caret.height = caret.tag.height;
1492 if (owner.Focused) {
1493 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1494 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);
1495 DisplayCaret ();
1498 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1501 internal void UpdateCaret() {
1502 if (!owner.IsHandleCreated || caret.tag == null) {
1503 return;
1506 undo.RecordCursor();
1508 if (caret.tag.height != caret.height) {
1509 caret.height = caret.tag.height;
1510 if (owner.Focused) {
1511 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1515 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);
1517 DisplayCaret ();
1519 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1522 internal void DisplayCaret() {
1523 if (!owner.IsHandleCreated) {
1524 return;
1527 if (owner.Focused && (!selection_visible || owner.show_caret_w_selection)) {
1528 XplatUI.CaretVisible(owner.Handle, true);
1532 internal void HideCaret() {
1533 if (!owner.IsHandleCreated) {
1534 return;
1537 if (owner.Focused) {
1538 XplatUI.CaretVisible(owner.Handle, false);
1542 internal void MoveCaret(CaretDirection direction) {
1543 // FIXME should we use IsWordSeparator to detect whitespace, instead
1544 // of looking for actual spaces in the Word move cases?
1546 bool nowrap = false;
1547 switch(direction) {
1548 case CaretDirection.CharForwardNoWrap:
1549 nowrap = true;
1550 goto case CaretDirection.CharForward;
1551 case CaretDirection.CharForward: {
1552 caret.pos++;
1553 if (caret.pos > caret.line.text.Length) {
1554 if (multiline && !nowrap) {
1555 // Go into next line
1556 if (caret.line.line_no < this.lines) {
1557 caret.line = GetLine(caret.line.line_no+1);
1558 caret.pos = 0;
1559 caret.tag = caret.line.tags;
1560 } else {
1561 caret.pos--;
1563 } else {
1564 // Single line; we stay where we are
1565 caret.pos--;
1567 } else {
1568 if ((caret.tag.start - 1 + caret.tag.length) < caret.pos) {
1569 caret.tag = caret.tag.next;
1572 UpdateCaret();
1573 return;
1576 case CaretDirection.CharBackNoWrap:
1577 nowrap = true;
1578 goto case CaretDirection.CharBack;
1579 case CaretDirection.CharBack: {
1580 if (caret.pos > 0) {
1581 // caret.pos--; // folded into the if below
1582 if (--caret.pos > 0) {
1583 if (caret.tag.start > caret.pos) {
1584 caret.tag = caret.tag.previous;
1587 } else {
1588 if (caret.line.line_no > 1 && !nowrap) {
1589 caret.line = GetLine(caret.line.line_no - 1);
1590 caret.pos = caret.line.text.Length;
1591 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1594 UpdateCaret();
1595 return;
1598 case CaretDirection.WordForward: {
1599 int len;
1601 len = caret.line.text.Length;
1602 if (caret.pos < len) {
1603 while ((caret.pos < len) && (caret.line.text[caret.pos] != ' ')) {
1604 caret.pos++;
1606 if (caret.pos < len) {
1607 // Skip any whitespace
1608 while ((caret.pos < len) && (caret.line.text[caret.pos] == ' ')) {
1609 caret.pos++;
1612 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1613 } else {
1614 if (caret.line.line_no < this.lines) {
1615 caret.line = GetLine(caret.line.line_no + 1);
1616 caret.pos = 0;
1617 caret.tag = caret.line.tags;
1620 UpdateCaret();
1621 return;
1624 case CaretDirection.WordBack: {
1625 if (caret.pos > 0) {
1626 caret.pos--;
1628 while ((caret.pos > 0) && (caret.line.text[caret.pos] == ' ')) {
1629 caret.pos--;
1632 while ((caret.pos > 0) && (caret.line.text[caret.pos] != ' ')) {
1633 caret.pos--;
1636 if (caret.line.text.ToString(caret.pos, 1) == " ") {
1637 if (caret.pos != 0) {
1638 caret.pos++;
1639 } else {
1640 caret.line = GetLine(caret.line.line_no - 1);
1641 caret.pos = caret.line.text.Length;
1644 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1645 } else {
1646 if (caret.line.line_no > 1) {
1647 caret.line = GetLine(caret.line.line_no - 1);
1648 caret.pos = caret.line.text.Length;
1649 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1652 UpdateCaret();
1653 return;
1656 case CaretDirection.LineUp: {
1657 if (caret.line.line_no > 1) {
1658 int pixel;
1660 pixel = (int)caret.line.widths[caret.pos];
1661 PositionCaret(pixel, GetLine(caret.line.line_no - 1).Y);
1663 DisplayCaret ();
1665 return;
1668 case CaretDirection.LineDown: {
1669 if (caret.line.line_no < lines) {
1670 int pixel;
1672 pixel = (int)caret.line.widths[caret.pos];
1673 PositionCaret(pixel, GetLine(caret.line.line_no + 1).Y);
1675 DisplayCaret ();
1677 return;
1680 case CaretDirection.Home: {
1681 if (caret.pos > 0) {
1682 caret.pos = 0;
1683 caret.tag = caret.line.tags;
1684 UpdateCaret();
1686 return;
1689 case CaretDirection.End: {
1690 if (caret.pos < caret.line.text.Length) {
1691 caret.pos = caret.line.text.Length;
1692 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1693 UpdateCaret();
1695 return;
1698 case CaretDirection.PgUp: {
1700 int new_y, y_offset;
1702 if (viewport_y == 0) {
1704 // This should probably be handled elsewhere
1705 if (!(owner is RichTextBox)) {
1706 // Page down doesn't do anything in a regular TextBox
1707 // if the bottom of the document
1708 // is already visible, the page and the caret stay still
1709 return;
1712 // We're just placing the caret at the end of the document, no scrolling needed
1713 owner.vscroll.Value = 0;
1714 Line line = GetLine (1);
1715 PositionCaret (line, 0);
1718 y_offset = caret.line.Y - viewport_y;
1719 new_y = caret.line.Y - viewport_height;
1721 owner.vscroll.Value = Math.Max (new_y, 0);
1722 PositionCaret ((int)caret.line.widths[caret.pos], y_offset + viewport_y);
1723 return;
1726 case CaretDirection.PgDn: {
1727 int new_y, y_offset;
1729 if ((viewport_y + viewport_height) > document_y) {
1731 // This should probably be handled elsewhere
1732 if (!(owner is RichTextBox)) {
1733 // Page up doesn't do anything in a regular TextBox
1734 // if the bottom of the document
1735 // is already visible, the page and the caret stay still
1736 return;
1739 // We're just placing the caret at the end of the document, no scrolling needed
1740 owner.vscroll.Value = owner.vscroll.Maximum - viewport_height + 1;
1741 Line line = GetLine (lines);
1742 PositionCaret (line, line.Text.Length);
1745 y_offset = caret.line.Y - viewport_y;
1746 new_y = caret.line.Y + viewport_height;
1748 owner.vscroll.Value = Math.Min (new_y, owner.vscroll.Maximum - viewport_height + 1);
1749 PositionCaret ((int)caret.line.widths[caret.pos], y_offset + viewport_y);
1751 return;
1754 case CaretDirection.CtrlPgUp: {
1755 PositionCaret(0, viewport_y);
1756 DisplayCaret ();
1757 return;
1760 case CaretDirection.CtrlPgDn: {
1761 Line line;
1762 LineTag tag;
1763 int index;
1765 tag = FindTag(0, viewport_y + viewport_height, out index, false);
1766 if (tag.line.line_no > 1) {
1767 line = GetLine(tag.line.line_no - 1);
1768 } else {
1769 line = tag.line;
1771 PositionCaret(line, line.Text.Length);
1772 DisplayCaret ();
1773 return;
1776 case CaretDirection.CtrlHome: {
1777 caret.line = GetLine(1);
1778 caret.pos = 0;
1779 caret.tag = caret.line.tags;
1781 UpdateCaret();
1782 return;
1785 case CaretDirection.CtrlEnd: {
1786 caret.line = GetLine(lines);
1787 caret.pos = caret.line.text.Length;
1788 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1790 UpdateCaret();
1791 return;
1794 case CaretDirection.SelectionStart: {
1795 caret.line = selection_start.line;
1796 caret.pos = selection_start.pos;
1797 caret.tag = selection_start.tag;
1799 UpdateCaret();
1800 return;
1803 case CaretDirection.SelectionEnd: {
1804 caret.line = selection_end.line;
1805 caret.pos = selection_end.pos;
1806 caret.tag = selection_end.tag;
1808 UpdateCaret();
1809 return;
1814 internal void Draw (Graphics g, Rectangle clip)
1816 Line line; // Current line being drawn
1817 LineTag tag; // Current tag being drawn
1818 int start; // First line to draw
1819 int end; // Last line to draw
1820 StringBuilder text; // String representing the current line
1821 int line_no;
1822 Brush tag_brush;
1823 Brush current_brush;
1824 Brush disabled_brush;
1825 Brush hilight;
1826 Brush hilight_text;
1828 // First, figure out from what line to what line we need to draw
1829 start = GetLineByPixel(clip.Top + viewport_y, false).line_no;
1830 end = GetLineByPixel(clip.Bottom + viewport_y, false).line_no;
1832 /// Make sure that we aren't drawing one more line then we need to
1833 line = GetLine (end - 1);
1834 if (line != null && clip.Bottom == line.Y + line.height + viewport_y)
1835 end--;
1837 line_no = start;
1839 #if Debug
1840 DateTime n = DateTime.Now;
1841 Console.WriteLine ("Started drawing: {0}s {1}ms", n.Second, n.Millisecond);
1842 Console.WriteLine ("CLIP: {0}", clip);
1843 Console.WriteLine ("S: {0}", GetLine (start).text);
1844 Console.WriteLine ("E: {0}", GetLine (end).text);
1845 #endif
1847 disabled_brush = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorGrayText);
1848 hilight = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorHighlight);
1849 hilight_text = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorHighlightText);
1851 while (line_no <= end) {
1852 line = GetLine (line_no);
1854 tag = line.tags;
1855 if (!calc_pass) {
1856 text = line.text;
1857 } else {
1858 // This fails if there's a password > 1024 chars...
1859 text = this.password_cache;
1862 int line_selection_start = text.Length + 1;
1863 int line_selection_end = text.Length + 1;
1864 if (selection_visible && owner.ShowSelection &&
1865 (line_no >= selection_start.line.line_no) &&
1866 (line_no <= selection_end.line.line_no)) {
1868 if (line_no == selection_start.line.line_no)
1869 line_selection_start = selection_start.pos + 1;
1870 else
1871 line_selection_start = 1;
1873 if (line_no == selection_end.line.line_no)
1874 line_selection_end = selection_end.pos + 1;
1875 else
1876 line_selection_end = text.Length + 1;
1878 if (line_selection_end == line_selection_start) {
1879 // There isn't really selection
1880 line_selection_start = text.Length + 1;
1881 line_selection_end = line_selection_start;
1882 } else {
1883 // lets draw some selection baby!!
1885 g.FillRectangle (hilight,
1886 line.widths [line_selection_start - 1] + line.align_shift - viewport_x,
1887 line.Y - viewport_y,
1888 line.widths [line_selection_end - 1] - line.widths [line_selection_start - 1],
1889 line.height);
1893 current_brush = line.tags.color;
1894 while (tag != null) {
1897 // Skip empty tags
1898 if (tag.length == 0) {
1899 tag = tag.next;
1900 continue;
1903 if (((tag.X + tag.width) < (clip.Left - viewport_x)) || (tag.X > (clip.Right - viewport_x))) {
1904 tag = tag.next;
1905 continue;
1908 if (tag.back_color != null) {
1909 g.FillRectangle (tag.back_color, tag.X + line.align_shift - viewport_x,
1910 line.Y + tag.shift - viewport_y, tag.width, line.height);
1913 tag_brush = tag.color;
1914 current_brush = tag_brush;
1916 if (!owner.is_enabled) {
1917 Color a = ((SolidBrush) tag.color).Color;
1918 Color b = ThemeEngine.Current.ColorWindowText;
1920 if ((a.R == b.R) && (a.G == b.G) && (a.B == b.B)) {
1921 tag_brush = disabled_brush;
1925 int tag_pos = tag.start;
1926 current_brush = tag_brush;
1927 while (tag_pos < tag.start + tag.length) {
1928 int old_tag_pos = tag_pos;
1930 if (tag_pos >= line_selection_start && tag_pos < line_selection_end) {
1931 current_brush = hilight_text;
1932 tag_pos = Math.Min (tag.start + tag.length, line_selection_end);
1933 } else if (tag_pos < line_selection_start) {
1934 current_brush = tag.color;
1935 tag_pos = Math.Min (tag.start + tag.length, line_selection_start);
1936 } else {
1937 current_brush = tag.color;
1938 tag_pos = tag.start + tag.length;
1942 g.DrawString (text.ToString (old_tag_pos - 1,
1943 Math.Min (tag.length, tag_pos - old_tag_pos)),
1944 tag.font, current_brush,
1945 line.widths [old_tag_pos - 1] + line.align_shift - viewport_x,
1946 line.Y + tag.shift - viewport_y,
1947 StringFormat.GenericTypographic);
1949 tag = tag.next;
1951 line_no++;
1955 private void InsertLineString (Line line, int pos, string s)
1957 bool carriage_return = false;
1959 if (s.EndsWith ("\r")) {
1960 s = s.Substring (0, s.Length - 1);
1961 carriage_return = true;
1964 InsertString (line, pos, s);
1966 if (carriage_return) {
1967 Line l = GetLine (line.line_no);
1968 l.carriage_return = true;
1972 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
1973 internal void Insert(Line line, int pos, bool update_caret, string s) {
1974 int break_index;
1975 int base_line;
1976 int old_line_count;
1977 int count = 1;
1978 LineTag tag = LineTag.FindTag (line, pos);
1980 NoRecalc = true;
1981 undo.BeginCompoundAction ();
1983 base_line = line.line_no;
1984 old_line_count = lines;
1986 break_index = s.IndexOf ('\n');
1988 // Bump the text at insertion point a line down if we're inserting more than one line
1989 if (break_index > -1) {
1990 Split(line, pos);
1991 line.soft_break = false;
1992 // Remainder of start line is now in base_line + 1
1995 if (break_index == -1)
1996 break_index = s.Length;
1998 InsertLineString (line, pos, s.Substring (0, break_index));
1999 break_index++;
2001 while (break_index < s.Length) {
2002 bool soft = false;
2003 int next_break = s.IndexOf ('\n', break_index);
2004 int adjusted_next_break;
2005 bool carriage_return = false;
2007 if (next_break == -1) {
2008 next_break = s.Length;
2009 soft = true;
2012 adjusted_next_break = next_break;
2013 if (s [next_break - 1] == '\r') {
2014 adjusted_next_break--;
2015 carriage_return = true;
2018 string line_text = s.Substring (break_index, adjusted_next_break - break_index);
2019 Add (base_line + count, line_text, line.alignment, tag.font, tag.color);
2021 if (carriage_return) {
2022 Line last = GetLine (base_line + count);
2023 last.carriage_return = true;
2025 if (soft)
2026 last.soft_break = true;
2027 } else if (soft) {
2028 Line last = GetLine (base_line + count);
2029 last.soft_break = true;
2032 count++;
2033 break_index = next_break + 1;
2036 NoRecalc = false;
2038 UpdateView(line, lines - old_line_count + 1, pos);
2040 if (update_caret) {
2041 // Move caret to the end of the inserted text
2042 Line l = GetLine (line.line_no + lines - old_line_count);
2043 PositionCaret(l, l.text.Length);
2044 DisplayCaret ();
2047 undo.EndCompoundAction ();
2050 // Inserts a character at the given position
2051 internal void InsertString(Line line, int pos, string s) {
2052 InsertString(line.FindTag(pos), pos, s);
2055 // Inserts a string at the given position
2056 internal void InsertString(LineTag tag, int pos, string s) {
2057 Line line;
2058 int len;
2060 len = s.Length;
2062 CharCount += len;
2064 line = tag.line;
2065 line.text.Insert(pos, s);
2066 tag.length += len;
2068 // TODO: sometimes getting a null tag here when pasting ???
2069 tag = tag.next;
2070 while (tag != null) {
2071 tag.start += len;
2072 tag = tag.next;
2074 line.Grow(len);
2075 line.recalc = true;
2077 UpdateView(line, pos);
2080 // Inserts a string at the caret position
2081 internal void InsertStringAtCaret(string s, bool move_caret) {
2082 LineTag tag;
2083 int len;
2085 len = s.Length;
2087 CharCount += len;
2089 caret.line.text.Insert(caret.pos, s);
2090 caret.tag.length += len;
2092 if (caret.tag.next != null) {
2093 tag = caret.tag.next;
2094 while (tag != null) {
2095 tag.start += len;
2096 tag = tag.next;
2099 caret.line.Grow(len);
2100 caret.line.recalc = true;
2102 UpdateView(caret.line, caret.pos);
2103 if (move_caret) {
2104 caret.pos += len;
2105 UpdateCaret();
2111 // Inserts a character at the given position
2112 internal void InsertChar(Line line, int pos, char ch) {
2113 InsertChar(line.FindTag(pos), pos, ch);
2116 // Inserts a character at the given position
2117 internal void InsertChar(LineTag tag, int pos, char ch) {
2118 Line line;
2120 CharCount++;
2122 line = tag.line;
2123 line.text.Insert(pos, ch);
2124 tag.length++;
2126 tag = tag.next;
2127 while (tag != null) {
2128 tag.start++;
2129 tag = tag.next;
2131 line.Grow(1);
2132 line.recalc = true;
2134 UpdateView(line, pos);
2137 // Inserts a character at the current caret position
2138 internal void InsertCharAtCaret(char ch, bool move_caret) {
2139 LineTag tag;
2141 CharCount++;
2143 caret.line.text.Insert(caret.pos, ch);
2144 caret.tag.length++;
2146 if (caret.tag.next != null) {
2147 tag = caret.tag.next;
2148 while (tag != null) {
2149 tag.start++;
2150 tag = tag.next;
2153 caret.line.Grow(1);
2154 caret.line.recalc = true;
2156 UpdateView(caret.line, caret.pos);
2157 if (move_caret) {
2158 caret.pos++;
2159 UpdateCaret();
2160 SetSelectionToCaret(true);
2164 internal void DeleteMultiline (Line start_line, int pos, int length)
2166 Marker start = new Marker ();
2167 Marker end = new Marker ();
2168 int start_index = LineTagToCharIndex (start_line, pos);
2170 start.line = start_line;
2171 start.pos = pos;
2172 start.tag = LineTag.FindTag (start_line, pos);
2174 CharIndexToLineTag (start_index + length, out end.line,
2175 out end.tag, out end.pos);
2177 if (start.line == end.line) {
2178 DeleteChars (start.tag, pos, end.pos - pos);
2179 } else {
2181 // Delete first and last lines
2182 DeleteChars (start.tag, start.pos, start.line.text.Length - start.pos);
2183 DeleteChars (end.line.tags, 0, end.pos);
2185 int current = start.line.line_no + 1;
2186 if (current < end.line.line_no) {
2187 for (int i = end.line.line_no - 1; i >= current; i--) {
2188 Delete (i);
2192 // BIG FAT WARNING - selection_end.line might be stale due
2193 // to the above Delete() call. DONT USE IT before hitting the end of this method!
2195 // Join start and end
2196 Combine (start.line.line_no, current);
2201 // Deletes n characters at the given position; it will not delete past line limits
2202 // pos is 0-based
2203 internal void DeleteChars(LineTag tag, int pos, int count) {
2204 Line line;
2205 bool streamline;
2207 streamline = false;
2208 line = tag.line;
2210 CharCount -= count;
2212 if (pos == line.text.Length) {
2213 return;
2216 line.text.Remove(pos, count);
2218 // Make sure the tag points to the right spot
2219 while ((tag != null) && (tag.start + tag.length - 1) <= pos) {
2220 tag = tag.next;
2223 if (tag == null) {
2224 return;
2227 // Check if we're crossing tag boundaries
2228 if ((pos + count) > (tag.start + tag.length - 1)) {
2229 int left;
2231 // We have to delete cross tag boundaries
2232 streamline = true;
2233 left = count;
2235 left -= tag.start + tag.length - pos - 1;
2236 tag.length -= tag.start + tag.length - pos - 1;
2238 tag = tag.next;
2239 while ((tag != null) && (left > 0)) {
2240 tag.start -= count - left;
2241 if (tag.length > left) {
2242 tag.length -= left;
2243 left = 0;
2244 } else {
2245 left -= tag.length;
2246 tag.length = 0;
2248 tag = tag.next;
2251 } else {
2252 // We got off easy, same tag
2254 tag.length -= count;
2256 if (tag.length == 0) {
2257 streamline = true;
2261 // Delete empty orphaned tags at the end
2262 LineTag walk = tag;
2263 while (walk != null && walk.next != null && walk.next.length == 0) {
2264 LineTag t = walk;
2265 walk.next = walk.next.next;
2266 if (walk.next != null)
2267 walk.next.previous = t;
2268 walk = walk.next;
2271 // Adjust the start point of any tags following
2272 if (tag != null) {
2273 tag = tag.next;
2274 while (tag != null) {
2275 tag.start -= count;
2276 tag = tag.next;
2280 line.recalc = true;
2281 if (streamline) {
2282 line.Streamline(lines);
2285 UpdateView(line, pos);
2288 // Deletes a character at or after the given position (depending on forward); it will not delete past line limits
2289 internal void DeleteChar(LineTag tag, int pos, bool forward) {
2290 Line line;
2291 bool streamline;
2293 CharCount--;
2295 streamline = false;
2296 line = tag.line;
2298 if ((pos == 0 && forward == false) || (pos == line.text.Length && forward == true)) {
2299 return;
2303 if (forward) {
2304 line.text.Remove(pos, 1);
2306 while ((tag != null) && (tag.start + tag.length - 1) <= pos) {
2307 tag = tag.next;
2310 if (tag == null) {
2311 return;
2314 tag.length--;
2316 if (tag.length == 0) {
2317 streamline = true;
2319 } else {
2320 pos--;
2321 line.text.Remove(pos, 1);
2322 if (pos >= (tag.start - 1)) {
2323 tag.length--;
2324 if (tag.length == 0) {
2325 streamline = true;
2327 } else if (tag.previous != null) {
2328 tag.previous.length--;
2329 if (tag.previous.length == 0) {
2330 streamline = true;
2335 // Delete empty orphaned tags at the end
2336 LineTag walk = tag;
2337 while (walk != null && walk.next != null && walk.next.length == 0) {
2338 LineTag t = walk;
2339 walk.next = walk.next.next;
2340 if (walk.next != null)
2341 walk.next.previous = t;
2342 walk = walk.next;
2345 tag = tag.next;
2346 while (tag != null) {
2347 tag.start--;
2348 tag = tag.next;
2350 line.recalc = true;
2351 if (streamline) {
2352 line.Streamline(lines);
2355 UpdateView(line, pos);
2358 // Combine two lines
2359 internal void Combine(int FirstLine, int SecondLine) {
2360 Combine(GetLine(FirstLine), GetLine(SecondLine));
2363 internal void Combine(Line first, Line second) {
2364 LineTag last;
2365 int shift;
2367 // Combine the two tag chains into one
2368 last = first.tags;
2370 // Maintain the line ending style
2371 first.soft_break = second.soft_break;
2373 while (last.next != null) {
2374 last = last.next;
2377 last.next = second.tags;
2378 last.next.previous = last;
2380 shift = last.start + last.length - 1;
2382 // Fix up references within the chain
2383 last = last.next;
2384 while (last != null) {
2385 last.line = first;
2386 last.start += shift;
2387 last = last.next;
2390 // Combine both lines' strings
2391 first.text.Insert(first.text.Length, second.text.ToString());
2392 first.Grow(first.text.Length);
2394 // Remove the reference to our (now combined) tags from the doomed line
2395 second.tags = null;
2397 // Renumber lines
2398 DecrementLines(first.line_no + 2); // first.line_no + 1 will be deleted, so we need to start renumbering one later
2400 // Mop up
2401 first.recalc = true;
2402 first.height = 0; // This forces RecalcDocument/UpdateView to redraw from this line on
2403 first.Streamline(lines);
2405 // Update Caret, Selection, etc
2406 if (caret.line == second) {
2407 caret.Combine(first, shift);
2409 if (selection_anchor.line == second) {
2410 selection_anchor.Combine(first, shift);
2412 if (selection_start.line == second) {
2413 selection_start.Combine(first, shift);
2415 if (selection_end.line == second) {
2416 selection_end.Combine(first, shift);
2419 #if Debug
2420 Line check_first;
2421 Line check_second;
2423 check_first = GetLine(first.line_no);
2424 check_second = GetLine(check_first.line_no + 1);
2426 Console.WriteLine("Pre-delete: Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2427 #endif
2429 this.Delete(second);
2431 #if Debug
2432 check_first = GetLine(first.line_no);
2433 check_second = GetLine(check_first.line_no + 1);
2435 Console.WriteLine("Post-delete Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2436 #endif
2439 // Split the line at the position into two
2440 internal void Split(int LineNo, int pos) {
2441 Line line;
2442 LineTag tag;
2444 line = GetLine(LineNo);
2445 tag = LineTag.FindTag(line, pos);
2446 Split(line, tag, pos, false);
2449 internal void Split(Line line, int pos) {
2450 LineTag tag;
2452 tag = LineTag.FindTag(line, pos);
2453 Split(line, tag, pos, false);
2456 ///<summary>Split line at given tag and position into two lines</summary>
2457 ///<param name="soft">True if the split should be marked as 'soft', indicating that it can be contracted
2458 ///if more space becomes available on previous line</param>
2459 internal void Split(Line line, LineTag tag, int pos, bool soft) {
2460 LineTag new_tag;
2461 Line new_line;
2462 bool move_caret;
2463 bool move_sel_start;
2464 bool move_sel_end;
2466 move_caret = false;
2467 move_sel_start = false;
2468 move_sel_end = false;
2470 // Adjust selection and cursors
2471 if (caret.line == line && caret.pos >= pos) {
2472 move_caret = true;
2474 if (selection_start.line == line && selection_start.pos > pos) {
2475 move_sel_start = true;
2478 if (selection_end.line == line && selection_end.pos > pos) {
2479 move_sel_end = true;
2482 // cover the easy case first
2483 if (pos == line.text.Length) {
2484 Add(line.line_no + 1, "", line.alignment, tag.font, tag.color);
2486 new_line = GetLine(line.line_no + 1);
2488 line.carriage_return = false;
2489 new_line.carriage_return = line.carriage_return;
2490 new_line.soft_break = soft;
2492 if (move_caret) {
2493 caret.line = new_line;
2494 caret.tag = new_line.tags;
2495 caret.pos = 0;
2498 if (move_sel_start) {
2499 selection_start.line = new_line;
2500 selection_start.pos = 0;
2501 selection_start.tag = new_line.tags;
2504 if (move_sel_end) {
2505 selection_end.line = new_line;
2506 selection_end.pos = 0;
2507 selection_end.tag = new_line.tags;
2509 return;
2512 // We need to move the rest of the text into the new line
2513 Add(line.line_no + 1, line.text.ToString(pos, line.text.Length - pos), line.alignment, tag.font, tag.color);
2515 // Now transfer our tags from this line to the next
2516 new_line = GetLine(line.line_no + 1);
2518 line.carriage_return = false;
2519 new_line.carriage_return = line.carriage_return;
2520 new_line.soft_break = soft;
2522 line.recalc = true;
2523 new_line.recalc = true;
2525 if ((tag.start - 1) == pos) {
2526 int shift;
2528 // We can simply break the chain and move the tag into the next line
2529 if (tag == line.tags) {
2530 new_tag = new LineTag(line, 1, 0);
2531 new_tag.CopyFormattingFrom (tag);
2532 line.tags = new_tag;
2535 if (tag.previous != null) {
2536 tag.previous.next = null;
2538 new_line.tags = tag;
2539 tag.previous = null;
2540 tag.line = new_line;
2542 // Walk the list and correct the start location of the tags we just bumped into the next line
2543 shift = tag.start - 1;
2545 new_tag = tag;
2546 while (new_tag != null) {
2547 new_tag.start -= shift;
2548 new_tag.line = new_line;
2549 new_tag = new_tag.next;
2551 } else {
2552 int shift;
2554 new_tag = new LineTag(new_line, 1, tag.start - 1 + tag.length - pos);
2555 new_tag.next = tag.next;
2556 new_tag.CopyFormattingFrom (tag);
2557 new_line.tags = new_tag;
2558 if (new_tag.next != null) {
2559 new_tag.next.previous = new_tag;
2561 tag.next = null;
2562 tag.length = pos - tag.start + 1;
2564 shift = pos;
2565 new_tag = new_tag.next;
2566 while (new_tag != null) {
2567 new_tag.start -= shift;
2568 new_tag.line = new_line;
2569 new_tag = new_tag.next;
2574 if (move_caret) {
2575 caret.line = new_line;
2576 caret.pos = caret.pos - pos;
2577 caret.tag = caret.line.FindTag(caret.pos);
2580 if (move_sel_start) {
2581 selection_start.line = new_line;
2582 selection_start.pos = selection_start.pos - pos;
2583 selection_start.tag = new_line.FindTag(selection_start.pos);
2586 if (move_sel_end) {
2587 selection_end.line = new_line;
2588 selection_end.pos = selection_end.pos - pos;
2589 selection_end.tag = new_line.FindTag(selection_end.pos);
2592 CharCount -= line.text.Length - pos;
2593 line.text.Remove(pos, line.text.Length - pos);
2596 // Adds a line of text, with given font.
2597 // Bumps any line at that line number that already exists down
2598 internal void Add(int LineNo, string Text, Font font, SolidBrush color) {
2599 Add(LineNo, Text, HorizontalAlignment.Left, font, color);
2602 internal void Add(int LineNo, string Text, HorizontalAlignment align, Font font, SolidBrush color) {
2603 Line add;
2604 Line line;
2605 int line_no;
2607 CharCount += Text.Length;
2609 if (LineNo<1 || Text == null) {
2610 if (LineNo<1) {
2611 throw new ArgumentNullException("LineNo", "Line numbers must be positive");
2612 } else {
2613 throw new ArgumentNullException("Text", "Cannot insert NULL line");
2617 add = new Line(LineNo, Text, align, font, color);
2619 line = document;
2620 while (line != sentinel) {
2621 add.parent = line;
2622 line_no = line.line_no;
2624 if (LineNo > line_no) {
2625 line = line.right;
2626 } else if (LineNo < line_no) {
2627 line = line.left;
2628 } else {
2629 // Bump existing line numbers; walk all nodes to the right of this one and increment line_no
2630 IncrementLines(line.line_no);
2631 line = line.left;
2635 add.left = sentinel;
2636 add.right = sentinel;
2638 if (add.parent != null) {
2639 if (LineNo > add.parent.line_no) {
2640 add.parent.right = add;
2641 } else {
2642 add.parent.left = add;
2644 } else {
2645 // Root node
2646 document = add;
2649 RebalanceAfterAdd(add);
2651 lines++;
2654 internal virtual void Clear() {
2655 lines = 0;
2656 CharCount = 0;
2657 document = sentinel;
2660 public virtual object Clone() {
2661 Document clone;
2663 clone = new Document(null);
2665 clone.lines = this.lines;
2666 clone.document = (Line)document.Clone();
2668 return clone;
2671 internal void Delete(int LineNo) {
2672 Line line;
2674 if (LineNo>lines) {
2675 return;
2678 line = GetLine(LineNo);
2680 CharCount -= line.text.Length;
2682 DecrementLines(LineNo + 1);
2683 Delete(line);
2686 internal void Delete(Line line1) {
2687 Line line2;// = new Line();
2688 Line line3;
2690 if ((line1.left == sentinel) || (line1.right == sentinel)) {
2691 line3 = line1;
2692 } else {
2693 line3 = line1.right;
2694 while (line3.left != sentinel) {
2695 line3 = line3.left;
2699 if (line3.left != sentinel) {
2700 line2 = line3.left;
2701 } else {
2702 line2 = line3.right;
2705 line2.parent = line3.parent;
2706 if (line3.parent != null) {
2707 if(line3 == line3.parent.left) {
2708 line3.parent.left = line2;
2709 } else {
2710 line3.parent.right = line2;
2712 } else {
2713 document = line2;
2716 if (line3 != line1) {
2717 LineTag tag;
2719 if (selection_start.line == line3) {
2720 selection_start.line = line1;
2723 if (selection_end.line == line3) {
2724 selection_end.line = line1;
2727 if (selection_anchor.line == line3) {
2728 selection_anchor.line = line1;
2731 if (caret.line == line3) {
2732 caret.line = line1;
2736 line1.alignment = line3.alignment;
2737 line1.ascent = line3.ascent;
2738 line1.hanging_indent = line3.hanging_indent;
2739 line1.height = line3.height;
2740 line1.indent = line3.indent;
2741 line1.line_no = line3.line_no;
2742 line1.recalc = line3.recalc;
2743 line1.right_indent = line3.right_indent;
2744 line1.soft_break = line3.soft_break;
2745 line1.space = line3.space;
2746 line1.tags = line3.tags;
2747 line1.text = line3.text;
2748 line1.widths = line3.widths;
2749 line1.Y = line3.Y;
2751 tag = line1.tags;
2752 while (tag != null) {
2753 tag.line = line1;
2754 tag = tag.next;
2758 if (line3.color == LineColor.Black)
2759 RebalanceAfterDelete(line2);
2761 this.lines--;
2764 // Invalidate a section of the document to trigger redraw
2765 internal void Invalidate(Line start, int start_pos, Line end, int end_pos) {
2766 Line l1;
2767 Line l2;
2768 int p1;
2769 int p2;
2771 if ((start == end) && (start_pos == end_pos)) {
2772 return;
2775 if (end_pos == -1) {
2776 end_pos = end.text.Length;
2779 // figure out what's before what so the logic below is straightforward
2780 if (start.line_no < end.line_no) {
2781 l1 = start;
2782 p1 = start_pos;
2784 l2 = end;
2785 p2 = end_pos;
2786 } else if (start.line_no > end.line_no) {
2787 l1 = end;
2788 p1 = end_pos;
2790 l2 = start;
2791 p2 = start_pos;
2792 } else {
2793 if (start_pos < end_pos) {
2794 l1 = start;
2795 p1 = start_pos;
2797 l2 = end;
2798 p2 = end_pos;
2799 } else {
2800 l1 = end;
2801 p1 = end_pos;
2803 l2 = start;
2804 p2 = start_pos;
2807 #if Debug
2808 Console.WriteLine("Invaliding from {0}:{1} to {2}:{3} {4}",
2809 l1.line_no, p1, l2.line_no, p2,
2810 new Rectangle(
2811 (int)l1.widths[p1] + l1.align_shift - viewport_x,
2812 l1.Y - viewport_y,
2813 (int)l2.widths[p2] - (int)l1.widths[p1] + 1,
2814 l1.height
2817 #endif
2819 owner.Invalidate(
2820 new Rectangle(
2821 (int)l1.widths[p1] + l1.align_shift - viewport_x,
2822 l1.Y - viewport_y,
2823 (int)l2.widths[p2] - (int)l1.widths[p1] + 1,
2824 l1.height
2827 return;
2830 #if Debug
2831 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);
2832 Console.WriteLine ("invalidate start line: {0} position: {1}", l1.text, p1);
2833 #endif
2835 // Three invalidates:
2836 // First line from start
2837 owner.Invalidate(new Rectangle((int)l1.widths[p1] + l1.align_shift - viewport_x, l1.Y - viewport_y, viewport_width, l1.height));
2840 // lines inbetween
2841 if ((l1.line_no + 1) < l2.line_no) {
2842 int y;
2844 y = GetLine(l1.line_no + 1).Y;
2845 owner.Invalidate(new Rectangle(0, y - viewport_y, viewport_width, l2.Y - y));
2847 #if Debug
2848 Console.WriteLine("Invaliding from {0}:{1} to {2}:{3} Middle => x={4}, y={5}, {6}x{7}", l1.line_no, p1, l2.line_no, p2, 0, y - viewport_y, viewport_width, l2.Y - y);
2849 #endif
2853 // Last line to end
2854 owner.Invalidate(new Rectangle((int)l2.widths[0] + l2.align_shift - viewport_x, l2.Y - viewport_y, (int)l2.widths[p2] + 1, l2.height));
2855 #if Debug
2856 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);
2857 #endif
2860 /// <summary>Select text around caret</summary>
2861 internal void ExpandSelection(CaretSelection mode, bool to_caret) {
2862 if (to_caret) {
2863 // We're expanding the selection to the caret position
2864 switch(mode) {
2865 case CaretSelection.Line: {
2866 // Invalidate the selection delta
2867 if (caret > selection_prev) {
2868 Invalidate(selection_prev.line, 0, caret.line, caret.line.text.Length);
2869 } else {
2870 Invalidate(selection_prev.line, selection_prev.line.text.Length, caret.line, 0);
2873 if (caret.line.line_no <= selection_anchor.line.line_no) {
2874 selection_start.line = caret.line;
2875 selection_start.tag = caret.line.tags;
2876 selection_start.pos = 0;
2878 selection_end.line = selection_anchor.line;
2879 selection_end.tag = selection_anchor.tag;
2880 selection_end.pos = selection_anchor.pos;
2882 selection_end_anchor = true;
2883 } else {
2884 selection_start.line = selection_anchor.line;
2885 selection_start.pos = selection_anchor.height;
2886 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height);
2888 selection_end.line = caret.line;
2889 selection_end.tag = caret.line.tags;
2890 selection_end.pos = caret.line.text.Length;
2892 selection_end_anchor = false;
2894 selection_prev.line = caret.line;
2895 selection_prev.tag = caret.tag;
2896 selection_prev.pos = caret.pos;
2898 break;
2901 case CaretSelection.Word: {
2902 int start_pos;
2903 int end_pos;
2905 start_pos = FindWordSeparator(caret.line, caret.pos, false);
2906 end_pos = FindWordSeparator(caret.line, caret.pos, true);
2909 // Invalidate the selection delta
2910 if (caret > selection_prev) {
2911 Invalidate(selection_prev.line, selection_prev.pos, caret.line, end_pos);
2912 } else {
2913 Invalidate(selection_prev.line, selection_prev.pos, caret.line, start_pos);
2915 if (caret < selection_anchor) {
2916 selection_start.line = caret.line;
2917 selection_start.tag = caret.line.FindTag(start_pos);
2918 selection_start.pos = start_pos;
2920 selection_end.line = selection_anchor.line;
2921 selection_end.tag = selection_anchor.tag;
2922 selection_end.pos = selection_anchor.pos;
2924 selection_prev.line = caret.line;
2925 selection_prev.tag = caret.tag;
2926 selection_prev.pos = start_pos;
2928 selection_end_anchor = true;
2929 } else {
2930 selection_start.line = selection_anchor.line;
2931 selection_start.pos = selection_anchor.height;
2932 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height);
2934 selection_end.line = caret.line;
2935 selection_end.tag = caret.line.FindTag(end_pos);
2936 selection_end.pos = end_pos;
2938 selection_prev.line = caret.line;
2939 selection_prev.tag = caret.tag;
2940 selection_prev.pos = end_pos;
2942 selection_end_anchor = false;
2944 break;
2947 case CaretSelection.Position: {
2948 SetSelectionToCaret(false);
2949 return;
2952 } else {
2953 // We're setting the selection 'around' the caret position
2954 switch(mode) {
2955 case CaretSelection.Line: {
2956 this.Invalidate(caret.line, 0, caret.line, caret.line.text.Length);
2958 selection_start.line = caret.line;
2959 selection_start.tag = caret.line.tags;
2960 selection_start.pos = 0;
2962 selection_end.line = caret.line;
2963 selection_end.pos = caret.line.text.Length;
2964 selection_end.tag = caret.line.FindTag(selection_end.pos);
2966 selection_anchor.line = selection_end.line;
2967 selection_anchor.tag = selection_end.tag;
2968 selection_anchor.pos = selection_end.pos;
2969 selection_anchor.height = 0;
2971 selection_prev.line = caret.line;
2972 selection_prev.tag = caret.tag;
2973 selection_prev.pos = caret.pos;
2975 this.selection_end_anchor = true;
2977 break;
2980 case CaretSelection.Word: {
2981 int start_pos;
2982 int end_pos;
2984 start_pos = FindWordSeparator(caret.line, caret.pos, false);
2985 end_pos = FindWordSeparator(caret.line, caret.pos, true);
2987 this.Invalidate(selection_start.line, start_pos, caret.line, end_pos);
2989 selection_start.line = caret.line;
2990 selection_start.tag = caret.line.FindTag(start_pos);
2991 selection_start.pos = start_pos;
2993 selection_end.line = caret.line;
2994 selection_end.tag = caret.line.FindTag(end_pos);
2995 selection_end.pos = end_pos;
2997 selection_anchor.line = selection_end.line;
2998 selection_anchor.tag = selection_end.tag;
2999 selection_anchor.pos = selection_end.pos;
3000 selection_anchor.height = start_pos;
3002 selection_prev.line = caret.line;
3003 selection_prev.tag = caret.tag;
3004 selection_prev.pos = caret.pos;
3006 this.selection_end_anchor = true;
3008 break;
3013 SetSelectionVisible (!(selection_start == selection_end));
3016 internal void SetSelectionToCaret(bool start) {
3017 if (start) {
3018 // Invalidate old selection; selection is being reset to empty
3019 this.Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3021 selection_start.line = caret.line;
3022 selection_start.tag = caret.tag;
3023 selection_start.pos = caret.pos;
3025 // start always also selects end
3026 selection_end.line = caret.line;
3027 selection_end.tag = caret.tag;
3028 selection_end.pos = caret.pos;
3030 selection_anchor.line = caret.line;
3031 selection_anchor.tag = caret.tag;
3032 selection_anchor.pos = caret.pos;
3033 } else {
3034 // Invalidate from previous end to caret (aka new end)
3035 if (selection_end_anchor) {
3036 if (selection_start != caret) {
3037 this.Invalidate(selection_start.line, selection_start.pos, caret.line, caret.pos);
3039 } else {
3040 if (selection_end != caret) {
3041 this.Invalidate(selection_end.line, selection_end.pos, caret.line, caret.pos);
3045 if (caret < selection_anchor) {
3046 selection_start.line = caret.line;
3047 selection_start.tag = caret.tag;
3048 selection_start.pos = caret.pos;
3050 selection_end.line = selection_anchor.line;
3051 selection_end.tag = selection_anchor.tag;
3052 selection_end.pos = selection_anchor.pos;
3054 selection_end_anchor = true;
3055 } else {
3056 selection_start.line = selection_anchor.line;
3057 selection_start.tag = selection_anchor.tag;
3058 selection_start.pos = selection_anchor.pos;
3060 selection_end.line = caret.line;
3061 selection_end.tag = caret.tag;
3062 selection_end.pos = caret.pos;
3064 selection_end_anchor = false;
3068 SetSelectionVisible (!(selection_start == selection_end));
3071 internal void SetSelection(Line start, int start_pos, Line end, int end_pos) {
3072 if (selection_visible) {
3073 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3076 if ((end.line_no < start.line_no) || ((end == start) && (end_pos <= start_pos))) {
3077 selection_start.line = end;
3078 selection_start.tag = LineTag.FindTag(end, end_pos);
3079 selection_start.pos = end_pos;
3081 selection_end.line = start;
3082 selection_end.tag = LineTag.FindTag(start, start_pos);
3083 selection_end.pos = start_pos;
3085 selection_end_anchor = true;
3086 } else {
3087 selection_start.line = start;
3088 selection_start.tag = LineTag.FindTag(start, start_pos);
3089 selection_start.pos = start_pos;
3091 selection_end.line = end;
3092 selection_end.tag = LineTag.FindTag(end, end_pos);
3093 selection_end.pos = end_pos;
3095 selection_end_anchor = false;
3098 selection_anchor.line = start;
3099 selection_anchor.tag = selection_start.tag;
3100 selection_anchor.pos = start_pos;
3102 if (((start == end) && (start_pos == end_pos)) || start == null || end == null) {
3103 SetSelectionVisible (false);
3104 } else {
3105 SetSelectionVisible (true);
3106 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3110 internal void SetSelectionStart(Line start, int start_pos) {
3111 // Invalidate from the previous to the new start pos
3112 Invalidate(selection_start.line, selection_start.pos, start, start_pos);
3114 selection_start.line = start;
3115 selection_start.pos = start_pos;
3116 selection_start.tag = LineTag.FindTag(start, start_pos);
3118 selection_anchor.line = start;
3119 selection_anchor.pos = start_pos;
3120 selection_anchor.tag = selection_start.tag;
3122 selection_end_anchor = false;
3125 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
3126 SetSelectionVisible (true);
3127 } else {
3128 SetSelectionVisible (false);
3131 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3134 internal void SetSelectionStart(int character_index) {
3135 Line line;
3136 LineTag tag;
3137 int pos;
3139 if (character_index < 0) {
3140 return;
3143 CharIndexToLineTag(character_index, out line, out tag, out pos);
3144 SetSelectionStart(line, pos);
3147 internal void SetSelectionEnd(Line end, int end_pos) {
3149 if (end == selection_end.line && end_pos == selection_start.pos) {
3150 selection_anchor.line = selection_start.line;
3151 selection_anchor.tag = selection_start.tag;
3152 selection_anchor.pos = selection_start.pos;
3154 selection_end.line = selection_start.line;
3155 selection_end.tag = selection_start.tag;
3156 selection_end.pos = selection_start.pos;
3158 selection_end_anchor = false;
3159 } else if ((end.line_no < selection_anchor.line.line_no) || ((end == selection_anchor.line) && (end_pos <= selection_anchor.pos))) {
3160 selection_start.line = end;
3161 selection_start.tag = LineTag.FindTag(end, end_pos);
3162 selection_start.pos = end_pos;
3164 selection_end.line = selection_anchor.line;
3165 selection_end.tag = selection_anchor.tag;
3166 selection_end.pos = selection_anchor.pos;
3168 selection_end_anchor = true;
3169 } else {
3170 selection_start.line = selection_anchor.line;
3171 selection_start.tag = selection_anchor.tag;
3172 selection_start.pos = selection_anchor.pos;
3174 selection_end.line = end;
3175 selection_end.tag = LineTag.FindTag(end, end_pos);
3176 selection_end.pos = end_pos;
3178 selection_end_anchor = false;
3181 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
3182 SetSelectionVisible (true);
3183 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3184 } else {
3185 SetSelectionVisible (false);
3186 // ?? Do I need to invalidate here, tests seem to work without it, but I don't think they should :-s
3190 internal void SetSelectionEnd(int character_index) {
3191 Line line;
3192 LineTag tag;
3193 int pos;
3195 if (character_index < 0) {
3196 return;
3199 CharIndexToLineTag(character_index, out line, out tag, out pos);
3200 SetSelectionEnd(line, pos);
3203 internal void SetSelection(Line start, int start_pos) {
3204 if (selection_visible) {
3205 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3208 selection_start.line = start;
3209 selection_start.pos = start_pos;
3210 selection_start.tag = LineTag.FindTag(start, start_pos);
3212 selection_end.line = start;
3213 selection_end.tag = selection_start.tag;
3214 selection_end.pos = start_pos;
3216 selection_anchor.line = start;
3217 selection_anchor.tag = selection_start.tag;
3218 selection_anchor.pos = start_pos;
3220 selection_end_anchor = false;
3221 SetSelectionVisible (false);
3224 internal void InvalidateSelectionArea() {
3225 Invalidate (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3228 // Return the current selection, as string
3229 internal string GetSelection() {
3230 // We return String.Empty if there is no selection
3231 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3232 return string.Empty;
3235 if (!multiline || (selection_start.line == selection_end.line)) {
3236 return selection_start.line.text.ToString(selection_start.pos, selection_end.pos - selection_start.pos);
3237 } else {
3238 StringBuilder sb;
3239 int i;
3240 int start;
3241 int end;
3243 sb = new StringBuilder();
3244 start = selection_start.line.line_no;
3245 end = selection_end.line.line_no;
3247 sb.Append(selection_start.line.text.ToString(selection_start.pos, selection_start.line.text.Length - selection_start.pos) + Environment.NewLine);
3249 if ((start + 1) < end) {
3250 for (i = start + 1; i < end; i++) {
3251 sb.Append(GetLine(i).text.ToString() + Environment.NewLine);
3255 sb.Append(selection_end.line.text.ToString(0, selection_end.pos));
3257 return sb.ToString();
3261 internal void ReplaceSelection(string s, bool select_new) {
3262 int i;
3264 undo.BeginCompoundAction ();
3266 InvalidateSelectionArea ();
3268 int selection_start_pos = LineTagToCharIndex (selection_start.line, selection_start.pos);
3269 NoRecalc = true;
3271 // First, delete any selected text
3272 if ((selection_start.pos != selection_end.pos) || (selection_start.line != selection_end.line)) {
3273 if (!multiline || (selection_start.line == selection_end.line)) {
3274 undo.RecordDeleteChars(selection_start.line, selection_start.pos + 1, selection_end.pos - selection_start.pos);
3276 DeleteChars(selection_start.tag, selection_start.pos, selection_end.pos - selection_start.pos);
3278 // The tag might have been removed, we need to recalc it
3279 selection_start.tag = selection_start.line.FindTag(selection_start.pos);
3280 } else {
3281 int start;
3282 int end;
3284 start = selection_start.line.line_no;
3285 end = selection_end.line.line_no;
3287 undo.RecordDelete(selection_start.line, selection_start.pos + 1, selection_end.line, selection_end.pos);
3289 // Delete first line
3290 DeleteChars(selection_start.tag, selection_start.pos, selection_start.line.text.Length - selection_start.pos);
3292 // Delete last line
3293 DeleteChars(selection_end.line.tags, 0, selection_end.pos);
3295 start++;
3296 if (start < end) {
3297 for (i = end - 1; i >= start; i--) {
3298 Delete(i);
3302 // BIG FAT WARNING - selection_end.line might be stale due
3303 // to the above Delete() call. DONT USE IT before hitting the end of this method!
3305 // Join start and end
3306 Combine(selection_start.line.line_no, start);
3311 Insert(selection_start.line, selection_start.pos, false, s);
3312 NoRecalc = false;
3314 undo.RecordInsertString (selection_start.line, selection_start.pos, s);
3316 if (!select_new) {
3317 CharIndexToLineTag(selection_start_pos + s.Length, out selection_start.line,
3318 out selection_start.tag, out selection_start.pos);
3320 selection_end.line = selection_start.line;
3321 selection_end.pos = selection_start.pos;
3322 selection_end.tag = selection_start.tag;
3323 selection_anchor.line = selection_start.line;
3324 selection_anchor.pos = selection_start.pos;
3325 selection_anchor.tag = selection_start.tag;
3327 SetSelectionVisible (false);
3328 } else {
3329 CharIndexToLineTag(selection_start_pos, out selection_start.line,
3330 out selection_start.tag, out selection_start.pos);
3332 CharIndexToLineTag(selection_start_pos + s.Length, out selection_end.line,
3333 out selection_end.tag, out selection_end.pos);
3335 selection_anchor.line = selection_start.line;
3336 selection_anchor.pos = selection_start.pos;
3337 selection_anchor.tag = selection_start.tag;
3339 SetSelectionVisible (true);
3342 PositionCaret (selection_start.line, selection_start.pos);
3344 undo.EndCompoundAction ();
3347 internal void CharIndexToLineTag(int index, out Line line_out, out LineTag tag_out, out int pos) {
3348 Line line;
3349 LineTag tag;
3350 int i;
3351 int chars;
3352 int start;
3354 chars = 0;
3356 for (i = 1; i <= lines; i++) {
3357 line = GetLine(i);
3359 start = chars;
3360 chars += line.text.Length + (line.soft_break ? 0 : crlf_size);
3362 if (index <= chars) {
3363 // we found the line
3364 tag = line.tags;
3366 while (tag != null) {
3367 if (index < (start + tag.start + tag.length)) {
3368 line_out = line;
3369 tag_out = LineTag.GetFinalTag (tag);
3370 pos = index - start;
3371 return;
3373 if (tag.next == null) {
3374 Line next_line;
3376 next_line = GetLine(line.line_no + 1);
3378 if (next_line != null) {
3379 line_out = next_line;
3380 tag_out = LineTag.GetFinalTag (next_line.tags);
3381 pos = 0;
3382 return;
3383 } else {
3384 line_out = line;
3385 tag_out = LineTag.GetFinalTag (tag);
3386 pos = line_out.text.Length;
3387 return;
3390 tag = tag.next;
3395 line_out = GetLine(lines);
3396 tag = line_out.tags;
3397 while (tag.next != null) {
3398 tag = tag.next;
3400 tag_out = tag;
3401 pos = line_out.text.Length;
3404 internal int LineTagToCharIndex(Line line, int pos) {
3405 int i;
3406 int length;
3408 // Count first and last line
3409 length = 0;
3411 // Count the lines in the middle
3413 for (i = 1; i < line.line_no; i++) {
3414 length += GetLine(i).text.Length + crlf_size;
3417 length += pos;
3419 return length;
3422 internal int SelectionLength() {
3423 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3424 return 0;
3427 if (!multiline || (selection_start.line == selection_end.line)) {
3428 return selection_end.pos - selection_start.pos;
3429 } else {
3430 int i;
3431 int start;
3432 int end;
3433 int length;
3435 // Count first and last line
3436 length = selection_start.line.text.Length - selection_start.pos + selection_end.pos + crlf_size;
3438 // Count the lines in the middle
3439 start = selection_start.line.line_no + 1;
3440 end = selection_end.line.line_no;
3442 if (start < end) {
3443 for (i = start; i < end; i++) {
3444 length += GetLine(i).text.Length + crlf_size;
3448 return length;
3455 /// <summary>Give it a Line number and it returns the Line object at with that line number</summary>
3456 internal Line GetLine(int LineNo) {
3457 Line line = document;
3459 while (line != sentinel) {
3460 if (LineNo == line.line_no) {
3461 return line;
3462 } else if (LineNo < line.line_no) {
3463 line = line.left;
3464 } else {
3465 line = line.right;
3469 return null;
3472 /// <summary>Retrieve the previous tag; walks line boundaries</summary>
3473 internal LineTag PreviousTag(LineTag tag) {
3474 Line l;
3476 if (tag.previous != null) {
3477 return tag.previous;
3480 // Next line
3481 if (tag.line.line_no == 1) {
3482 return null;
3485 l = GetLine(tag.line.line_no - 1);
3486 if (l != null) {
3487 LineTag t;
3489 t = l.tags;
3490 while (t.next != null) {
3491 t = t.next;
3493 return t;
3496 return null;
3499 /// <summary>Retrieve the next tag; walks line boundaries</summary>
3500 internal LineTag NextTag(LineTag tag) {
3501 Line l;
3503 if (tag.next != null) {
3504 return tag.next;
3507 // Next line
3508 l = GetLine(tag.line.line_no + 1);
3509 if (l != null) {
3510 return l.tags;
3513 return null;
3516 internal Line ParagraphStart(Line line) {
3517 while (line.soft_break) {
3518 line = GetLine(line.line_no - 1);
3520 return line;
3523 internal Line ParagraphEnd(Line line) {
3524 Line l;
3526 while (line.soft_break) {
3527 l = GetLine(line.line_no + 1);
3528 if ((l == null) || (!l.soft_break)) {
3529 break;
3531 line = l;
3533 return line;
3536 /// <summary>Give it a Y pixel coordinate and it returns the Line covering that Y coordinate</summary>
3537 internal Line GetLineByPixel(int y, bool exact) {
3538 Line line = document;
3539 Line last = null;
3541 while (line != sentinel) {
3542 last = line;
3543 if ((y >= line.Y) && (y < (line.Y+line.height))) {
3544 return line;
3545 } else if (y < line.Y) {
3546 line = line.left;
3547 } else {
3548 line = line.right;
3552 if (exact) {
3553 return null;
3555 return last;
3558 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3559 internal LineTag FindTag(int x, int y, out int index, bool exact) {
3560 Line line;
3561 LineTag tag;
3563 line = GetLineByPixel(y, exact);
3564 if (line == null) {
3565 index = 0;
3566 return null;
3568 tag = line.tags;
3570 // Alignment adjustment
3571 x += line.align_shift;
3573 while (true) {
3574 if (x >= tag.X && x < (tag.X+tag.width)) {
3575 int end;
3577 end = tag.start + tag.length - 1;
3579 for (int pos = tag.start; pos < end; pos++) {
3580 if (x < line.widths[pos]) {
3581 index = pos;
3582 return LineTag.GetFinalTag (tag);
3585 index=end;
3586 return LineTag.GetFinalTag (tag);
3588 if (tag.next != null) {
3589 tag = tag.next;
3590 } else {
3591 if (exact) {
3592 index = 0;
3593 return null;
3596 index = line.text.Length;
3597 return LineTag.GetFinalTag (tag);
3602 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3603 internal LineTag FindCursor(int x, int y, out int index) {
3604 Line line;
3605 LineTag tag;
3607 line = GetLineByPixel(y, false);
3608 tag = line.tags;
3610 // Adjust for alignment
3611 x -= line.align_shift;
3613 while (true) {
3614 if (x >= tag.X && x < (tag.X+tag.width)) {
3615 int end;
3617 end = tag.start + tag.length - 1;
3619 for (int pos = tag.start-1; pos < end; pos++) {
3620 // When clicking on a character, we position the cursor to whatever edge
3621 // of the character the click was closer
3622 if (x < (line.widths[pos] + ((line.widths[pos+1]-line.widths[pos])/2))) {
3623 index = pos;
3624 return tag;
3627 index=end;
3628 return tag;
3630 if (tag.next != null) {
3631 tag = tag.next;
3632 } else {
3633 index = line.text.Length;
3634 return tag;
3639 /// <summary>Format area of document in specified font and color</summary>
3640 /// <param name="start_pos">1-based start position on start_line</param>
3641 /// <param name="end_pos">1-based end position on end_line </param>
3642 internal void FormatText (Line start_line, int start_pos, Line end_line, int end_pos, Font font,
3643 SolidBrush color, SolidBrush back_color, FormatSpecified specified)
3645 Line l;
3647 // First, format the first line
3648 if (start_line != end_line) {
3649 // First line
3650 LineTag.FormatText(start_line, start_pos, start_line.text.Length - start_pos + 1, font, color, back_color, specified);
3652 // Format last line
3653 LineTag.FormatText(end_line, 1, end_pos, font, color, back_color, specified);
3655 // Now all the lines inbetween
3656 for (int i = start_line.line_no + 1; i < end_line.line_no; i++) {
3657 l = GetLine(i);
3658 LineTag.FormatText(l, 1, l.text.Length, font, color, back_color, specified);
3660 } else {
3661 // Special case, single line
3662 LineTag.FormatText(start_line, start_pos, end_pos - start_pos, font, color, back_color, specified);
3666 /// <summary>Re-format areas of the document in specified font and color</summary>
3667 /// <param name="start_pos">1-based start position on start_line</param>
3668 /// <param name="end_pos">1-based end position on end_line </param>
3669 /// <param name="font">Font specifying attributes</param>
3670 /// <param name="color">Color (or NULL) to apply</param>
3671 /// <param name="apply">Attributes from font and color to apply</param>
3672 internal void FormatText(Line start_line, int start_pos, Line end_line, int end_pos, FontDefinition attributes) {
3673 Line l;
3675 // First, format the first line
3676 if (start_line != end_line) {
3677 // First line
3678 LineTag.FormatText(start_line, start_pos, start_line.text.Length - start_pos + 1, attributes);
3680 // Format last line
3681 LineTag.FormatText(end_line, 1, end_pos - 1, attributes);
3683 // Now all the lines inbetween
3684 for (int i = start_line.line_no + 1; i < end_line.line_no; i++) {
3685 l = GetLine(i);
3686 LineTag.FormatText(l, 1, l.text.Length, attributes);
3688 } else {
3689 // Special case, single line
3690 LineTag.FormatText(start_line, start_pos, end_pos - start_pos, attributes);
3694 internal void RecalculateAlignments() {
3695 Line line;
3696 int line_no;
3698 line_no = 1;
3700 while (line_no <= lines) {
3701 line = GetLine(line_no);
3703 if (line != null) {
3704 switch (line.alignment) {
3705 case HorizontalAlignment.Left:
3706 line.align_shift = 0;
3707 break;
3708 case HorizontalAlignment.Center:
3709 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3710 break;
3711 case HorizontalAlignment.Right:
3712 line.align_shift = viewport_width - (int)line.widths[line.text.Length];
3713 break;
3717 line_no++;
3719 return;
3722 /// <summary>Calculate formatting for the whole document</summary>
3723 internal bool RecalculateDocument(Graphics g) {
3724 return RecalculateDocument(g, 1, this.lines, false);
3727 /// <summary>Calculate formatting starting at a certain line</summary>
3728 internal bool RecalculateDocument(Graphics g, int start) {
3729 return RecalculateDocument(g, start, this.lines, false);
3732 /// <summary>Calculate formatting within two given line numbers</summary>
3733 internal bool RecalculateDocument(Graphics g, int start, int end) {
3734 return RecalculateDocument(g, start, end, false);
3737 /// <summary>With optimize on, returns true if line heights changed</summary>
3738 internal bool RecalculateDocument(Graphics g, int start, int end, bool optimize) {
3739 Line line;
3740 int line_no;
3741 int Y;
3742 int new_width;
3743 bool changed;
3744 int shift;
3746 if (no_recalc) {
3747 recalc_pending = true;
3748 recalc_start = start;
3749 recalc_end = end;
3750 recalc_optimize = optimize;
3751 return false;
3754 Y = GetLine(start).Y;
3755 line_no = start;
3756 new_width = 0;
3757 shift = this.lines;
3758 if (!optimize) {
3759 changed = true; // We always return true if we run non-optimized
3760 } else {
3761 changed = false;
3764 while (line_no <= (end + this.lines - shift)) {
3765 line = GetLine(line_no++);
3766 line.Y = Y;
3768 if (!calc_pass) {
3769 if (!optimize) {
3770 line.RecalculateLine(g, this);
3771 } else {
3772 if (line.recalc && line.RecalculateLine(g, this)) {
3773 changed = true;
3774 // If the height changed, all subsequent lines change
3775 end = this.lines;
3776 shift = this.lines;
3779 } else {
3780 if (!optimize) {
3781 line.RecalculatePasswordLine(g, this);
3782 } else {
3783 if (line.recalc && line.RecalculatePasswordLine(g, this)) {
3784 changed = true;
3785 // If the height changed, all subsequent lines change
3786 end = this.lines;
3787 shift = this.lines;
3792 if (line.widths[line.text.Length] > new_width) {
3793 new_width = (int)line.widths[line.text.Length];
3796 // Calculate alignment
3797 if (line.alignment != HorizontalAlignment.Left) {
3798 if (line.alignment == HorizontalAlignment.Center) {
3799 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3800 } else {
3801 line.align_shift = viewport_width - (int)line.widths[line.text.Length] - 1;
3805 Y += line.height;
3807 if (line_no > lines) {
3808 break;
3812 if (document_x != new_width) {
3813 document_x = new_width;
3814 if (WidthChanged != null) {
3815 WidthChanged(this, null);
3819 RecalculateAlignments();
3821 line = GetLine(lines);
3823 if (document_y != line.Y + line.height) {
3824 document_y = line.Y + line.height;
3825 if (HeightChanged != null) {
3826 HeightChanged(this, null);
3829 UpdateCaret();
3830 return changed;
3833 internal int Size() {
3834 return lines;
3837 private void owner_HandleCreated(object sender, EventArgs e) {
3838 RecalculateDocument(owner.CreateGraphicsInternal());
3839 AlignCaret();
3842 private void owner_VisibleChanged(object sender, EventArgs e) {
3843 if (owner.Visible) {
3844 RecalculateDocument(owner.CreateGraphicsInternal());
3848 internal static bool IsWordSeparator(char ch) {
3849 switch(ch) {
3850 case ' ':
3851 case '\t':
3852 case '(':
3853 case ')': {
3854 return true;
3857 default: {
3858 return false;
3862 internal int FindWordSeparator(Line line, int pos, bool forward) {
3863 int len;
3865 len = line.text.Length;
3867 if (forward) {
3868 for (int i = pos + 1; i < len; i++) {
3869 if (IsWordSeparator(line.Text[i])) {
3870 return i + 1;
3873 return len;
3874 } else {
3875 for (int i = pos - 1; i > 0; i--) {
3876 if (IsWordSeparator(line.Text[i - 1])) {
3877 return i;
3880 return 0;
3884 /* Search document for text */
3885 internal bool FindChars(char[] chars, Marker start, Marker end, out Marker result) {
3886 Line line;
3887 int line_no;
3888 int pos;
3889 int line_len;
3891 // Search for occurence of any char in the chars array
3892 result = new Marker();
3894 line = start.line;
3895 line_no = start.line.line_no;
3896 pos = start.pos;
3897 while (line_no <= end.line.line_no) {
3898 line_len = line.text.Length;
3899 while (pos < line_len) {
3900 for (int i = 0; i < chars.Length; i++) {
3901 if (line.text[pos] == chars[i]) {
3902 // Special case
3903 if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
3904 return false;
3907 result.line = line;
3908 result.pos = pos;
3909 return true;
3912 pos++;
3915 pos = 0;
3916 line_no++;
3917 line = GetLine(line_no);
3920 return false;
3923 // This version does not build one big string for searching, instead it handles
3924 // line-boundaries, which is faster and less memory intensive
3925 // FIXME - Depending on culture stuff we might have to create a big string and use culturespecific
3926 // search stuff and change it to accept and return positions instead of Markers (which would match
3927 // RichTextBox behaviour better but would be inconsistent with the rest of TextControl)
3928 internal bool Find(string search, Marker start, Marker end, out Marker result, RichTextBoxFinds options) {
3929 Marker last;
3930 string search_string;
3931 Line line;
3932 int line_no;
3933 int pos;
3934 int line_len;
3935 int current;
3936 bool word;
3937 bool word_option;
3938 bool ignore_case;
3939 bool reverse;
3940 char c;
3942 result = new Marker();
3943 word_option = ((options & RichTextBoxFinds.WholeWord) != 0);
3944 ignore_case = ((options & RichTextBoxFinds.MatchCase) == 0);
3945 reverse = ((options & RichTextBoxFinds.Reverse) != 0);
3947 line = start.line;
3948 line_no = start.line.line_no;
3949 pos = start.pos;
3950 current = 0;
3952 // Prep our search string, lowercasing it if we do case-independent matching
3953 if (ignore_case) {
3954 StringBuilder sb;
3955 sb = new StringBuilder(search);
3956 for (int i = 0; i < sb.Length; i++) {
3957 sb[i] = Char.ToLower(sb[i]);
3959 search_string = sb.ToString();
3960 } else {
3961 search_string = search;
3964 // We need to check if the character before our start position is a wordbreak
3965 if (word_option) {
3966 if (line_no == 1) {
3967 if ((pos == 0) || (IsWordSeparator(line.text[pos - 1]))) {
3968 word = true;
3969 } else {
3970 word = false;
3972 } else {
3973 if (pos > 0) {
3974 if (IsWordSeparator(line.text[pos - 1])) {
3975 word = true;
3976 } else {
3977 word = false;
3979 } else {
3980 // Need to check the end of the previous line
3981 Line prev_line;
3983 prev_line = GetLine(line_no - 1);
3984 if (prev_line.soft_break) {
3985 if (IsWordSeparator(prev_line.text[prev_line.text.Length - 1])) {
3986 word = true;
3987 } else {
3988 word = false;
3990 } else {
3991 word = true;
3995 } else {
3996 word = false;
3999 // To avoid duplication of this loop with reverse logic, we search
4000 // through the document, remembering the last match and when returning
4001 // report that last remembered match
4003 last = new Marker();
4004 last.height = -1; // Abused - we use it to track change
4006 while (line_no <= end.line.line_no) {
4007 if (line_no != end.line.line_no) {
4008 line_len = line.text.Length;
4009 } else {
4010 line_len = end.pos;
4013 while (pos < line_len) {
4014 if (word_option && (current == search_string.Length)) {
4015 if (IsWordSeparator(line.text[pos])) {
4016 if (!reverse) {
4017 goto FindFound;
4018 } else {
4019 last = result;
4020 current = 0;
4022 } else {
4023 current = 0;
4027 if (ignore_case) {
4028 c = Char.ToLower(line.text[pos]);
4029 } else {
4030 c = line.text[pos];
4033 if (c == search_string[current]) {
4034 if (current == 0) {
4035 result.line = line;
4036 result.pos = pos;
4038 if (!word_option || (word_option && (word || (current > 0)))) {
4039 current++;
4042 if (!word_option && (current == search_string.Length)) {
4043 if (!reverse) {
4044 goto FindFound;
4045 } else {
4046 last = result;
4047 current = 0;
4050 } else {
4051 current = 0;
4053 pos++;
4055 if (!word_option) {
4056 continue;
4059 if (IsWordSeparator(c)) {
4060 word = true;
4061 } else {
4062 word = false;
4066 if (word_option) {
4067 // Mark that we just saw a word boundary
4068 if (!line.soft_break) {
4069 word = true;
4072 if (current == search_string.Length) {
4073 if (word) {
4074 if (!reverse) {
4075 goto FindFound;
4076 } else {
4077 last = result;
4078 current = 0;
4080 } else {
4081 current = 0;
4086 pos = 0;
4087 line_no++;
4088 line = GetLine(line_no);
4091 if (reverse) {
4092 if (last.height != -1) {
4093 result = last;
4094 return true;
4098 return false;
4100 FindFound:
4101 if (!reverse) {
4102 // if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
4103 // return false;
4104 // }
4105 return true;
4108 result = last;
4109 return true;
4113 /* Marker stuff */
4114 internal void GetMarker(out Marker mark, bool start) {
4115 mark = new Marker();
4117 if (start) {
4118 mark.line = GetLine(1);
4119 mark.tag = mark.line.tags;
4120 mark.pos = 0;
4121 } else {
4122 mark.line = GetLine(lines);
4123 mark.tag = mark.line.tags;
4124 while (mark.tag.next != null) {
4125 mark.tag = mark.tag.next;
4127 mark.pos = mark.line.text.Length;
4130 #endregion // Internal Methods
4132 #region Events
4133 internal event EventHandler CaretMoved;
4134 internal event EventHandler WidthChanged;
4135 internal event EventHandler HeightChanged;
4136 internal event EventHandler LengthChanged;
4137 #endregion // Events
4139 #region Administrative
4140 public IEnumerator GetEnumerator() {
4141 // FIXME
4142 return null;
4145 public override bool Equals(object obj) {
4146 if (obj == null) {
4147 return false;
4150 if (!(obj is Document)) {
4151 return false;
4154 if (obj == this) {
4155 return true;
4158 if (ToString().Equals(((Document)obj).ToString())) {
4159 return true;
4162 return false;
4165 public override int GetHashCode() {
4166 return document_id;
4169 public override string ToString() {
4170 return "document " + this.document_id;
4172 #endregion // Administrative
4175 internal class LineTag {
4176 #region Local Variables;
4177 // Payload; formatting
4178 internal Font font; // System.Drawing.Font object for this tag
4179 internal SolidBrush color; // The font color for this tag
4181 // In 2.0 tags can have background colours. I'm not going to #ifdef
4182 // at this level though since I want to reduce code paths
4183 internal SolidBrush back_color;
4185 // Payload; text
4186 internal int start; // start, in chars; index into Line.text
4187 internal int length; // length, in chars
4188 internal bool r_to_l; // Which way is the font
4190 // Drawing support
4191 internal int height; // Height in pixels of the text this tag describes
4192 internal int X; // X location of the text this tag describes
4193 // internal float width; // Width in pixels of the text this tag describes
4194 internal int ascent; // Ascent of the font for this tag
4195 internal int shift; // Shift down for this tag, to stay on baseline
4197 // Administrative
4198 internal Line line; // The line we're on
4199 internal LineTag next; // Next tag on the same line
4200 internal LineTag previous; // Previous tag on the same line
4201 #endregion;
4203 #region Constructors
4204 internal LineTag(Line line, int start, int length) {
4205 this.line = line;
4206 this.start = start;
4207 this.length = length;
4208 this.X = 0;
4210 #endregion // Constructors
4212 #region Internal Methods
4214 public int End {
4215 get { return start + length; }
4218 public float width {
4219 get {
4220 if (length == 0)
4221 return 0;
4222 return line.widths [start + length - 1];
4225 ///<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>
4226 internal LineTag Break(int pos) {
4228 LineTag new_tag;
4230 // Sanity
4231 if (pos == this.start) {
4232 return this;
4233 } else if (pos >= (start + length)) {
4234 return null;
4237 new_tag = new LineTag(line, pos, start + length - pos);
4238 new_tag.CopyFormattingFrom (this);
4239 this.length -= new_tag.length;
4240 new_tag.next = this.next;
4241 this.next = new_tag;
4242 new_tag.previous = this;
4243 if (new_tag.next != null) {
4244 new_tag.next.previous = new_tag;
4247 return new_tag;
4250 public void CopyFormattingFrom (LineTag other)
4252 height = other.height;
4253 font = other.font;
4254 color = other.color;
4255 back_color = other.back_color;
4258 ///<summary>Create new font and brush from existing font and given new attributes. Returns true if fontheight changes</summary>
4259 internal static bool GenerateTextFormat(Font font_from, SolidBrush color_from, FontDefinition attributes, out Font new_font, out SolidBrush new_color) {
4260 float size;
4261 string face;
4262 FontStyle style;
4263 GraphicsUnit unit;
4265 if (attributes.font_obj == null) {
4266 size = font_from.SizeInPoints;
4267 unit = font_from.Unit;
4268 face = font_from.Name;
4269 style = font_from.Style;
4271 if (attributes.face != null) {
4272 face = attributes.face;
4275 if (attributes.size != 0) {
4276 size = attributes.size;
4279 style |= attributes.add_style;
4280 style &= ~attributes.remove_style;
4282 // Create new font
4283 new_font = new Font(face, size, style, unit);
4284 } else {
4285 new_font = attributes.font_obj;
4288 // Create 'new' color brush
4289 if (attributes.color != Color.Empty) {
4290 new_color = new SolidBrush(attributes.color);
4291 } else {
4292 new_color = color_from;
4295 if (new_font.Height == font_from.Height) {
4296 return false;
4298 return true;
4301 /// <summary>Applies 'font' and 'brush' to characters starting at 'start' for 'length' chars;
4302 /// Removes any previous tags overlapping the same area;
4303 /// returns true if lineheight has changed</summary>
4304 /// <param name="start">1-based character position on line</param>
4305 internal static bool FormatText(Line line, int start, int length, Font font, SolidBrush color, SolidBrush back_color, FormatSpecified specified)
4307 LineTag tag;
4308 LineTag start_tag;
4309 LineTag end_tag;
4310 int end;
4311 bool retval = false; // Assume line-height doesn't change
4313 // Too simple?
4314 if (((FormatSpecified.Font & specified) == FormatSpecified.Font) && font.Height != line.height) {
4315 retval = true;
4317 line.recalc = true; // This forces recalculation of the line in RecalculateDocument
4319 // A little sanity, not sure if it's needed, might be able to remove for speed
4320 if (length > line.text.Length) {
4321 length = line.text.Length;
4324 tag = line.tags;
4325 end = start + length;
4327 // Common special case
4328 if ((start == 1) && (length == tag.length)) {
4329 tag.ascent = 0;
4330 SetFormat (tag, font, color, back_color, specified);
4331 return retval;
4334 start_tag = FindTag (line, start);
4336 tag = start_tag.Break (start);
4338 while (tag != null && tag.End < end) {
4339 SetFormat (tag, font, color, back_color, specified);
4340 tag = tag.next;
4343 if (end != line.text.Length) {
4344 /// Now do the last tag
4345 end_tag = FindTag (line, end);
4347 if (end_tag != null) {
4348 end_tag.Break (end);
4349 SetFormat (end_tag, font, color, back_color, specified);
4353 return retval;
4356 private static void SetFormat (LineTag tag, Font font, SolidBrush color, SolidBrush back_color, FormatSpecified specified)
4358 if ((FormatSpecified.Font & specified) == FormatSpecified.Font)
4359 tag.font = font;
4360 if ((FormatSpecified.Color & specified) == FormatSpecified.Color)
4361 tag.color = color;
4362 if ((FormatSpecified.BackColor & specified) == FormatSpecified.BackColor) {
4363 tag.back_color = back_color;
4367 /// <summary>Applies font attributes specified to characters starting at 'start' for 'length' chars;
4368 /// Breaks tags at start and end point, keeping middle tags with altered attributes.
4369 /// Returns true if lineheight has changed</summary>
4370 /// <param name="start">1-based character position on line</param>
4371 internal static bool FormatText(Line line, int start, int length, FontDefinition attributes) {
4372 LineTag tag;
4373 LineTag start_tag;
4374 LineTag end_tag;
4375 bool retval = false; // Assume line-height doesn't change
4377 line.recalc = true; // This forces recalculation of the line in RecalculateDocument
4379 // A little sanity, not sure if it's needed, might be able to remove for speed
4380 if (length > line.text.Length) {
4381 length = line.text.Length;
4384 tag = line.tags;
4386 // Common special case
4387 if ((start == 1) && (length == tag.length)) {
4388 tag.ascent = 0;
4389 GenerateTextFormat(tag.font, tag.color, attributes, out tag.font, out tag.color);
4390 return retval;
4393 start_tag = FindTag(line, start);
4395 if (start_tag == null) {
4396 if (length == 0) {
4397 // We are 'starting' after all valid tags; create a new tag with the right attributes
4398 start_tag = FindTag(line, line.text.Length - 1);
4399 start_tag.next = new LineTag(line, line.text.Length + 1, 0);
4400 start_tag.next.CopyFormattingFrom (start_tag);
4401 start_tag.next.previous = start_tag;
4402 start_tag = start_tag.next;
4403 } else {
4404 throw new Exception(String.Format("Could not find start_tag in document at line {0} position {1}", line.line_no, start));
4406 } else {
4407 start_tag = start_tag.Break(start);
4410 end_tag = FindTag(line, start + length);
4411 if (end_tag != null) {
4412 end_tag = end_tag.Break(start + length);
4415 // start_tag or end_tag might be null; we're cool with that
4416 // we now walk from start_tag to end_tag, applying new attributes
4417 tag = start_tag;
4418 while ((tag != null) && tag != end_tag) {
4419 if (LineTag.GenerateTextFormat(tag.font, tag.color, attributes, out tag.font, out tag.color)) {
4420 retval = true;
4422 tag = tag.next;
4424 return retval;
4428 /// <summary>Finds the tag that describes the character at position 'pos' on 'line'</summary>
4429 internal static LineTag FindTag(Line line, int pos) {
4430 LineTag tag = line.tags;
4432 // Beginning of line is a bit special
4433 if (pos == 0) {
4434 // Not sure if we should get the final tag here
4435 return tag;
4438 while (tag != null) {
4439 if ((tag.start <= pos) && (pos < (tag.start+tag.length))) {
4440 return GetFinalTag (tag);
4443 tag = tag.next;
4446 return null;
4449 // There can be multiple tags at the same position, we want to make
4450 // sure we are using the very last tag at the given position
4451 internal static LineTag GetFinalTag (LineTag tag)
4453 LineTag res = tag;
4455 while (res.next != null && res.next.length == 0)
4456 res = res.next;
4457 return res;
4460 /// <summary>Combines 'this' tag with 'other' tag</summary>
4461 internal bool Combine(LineTag other) {
4462 if (!this.Equals(other)) {
4463 return false;
4466 this.length += other.length;
4467 this.next = other.next;
4468 if (this.next != null) {
4469 this.next.previous = this;
4472 return true;
4476 /// <summary>Remove 'this' tag ; to be called when formatting is to be removed</summary>
4477 internal bool Remove() {
4478 if ((this.start == 1) && (this.next == null)) {
4479 // We cannot remove the only tag
4480 return false;
4482 if (this.start != 1) {
4483 this.previous.length += this.length;
4484 this.previous.next = this.next;
4485 this.next.previous = this.previous;
4486 } else {
4487 this.next.start = 1;
4488 this.next.length += this.length;
4489 this.line.tags = this.next;
4490 this.next.previous = null;
4492 return true;
4496 /// <summary>Checks if 'this' tag describes the same formatting options as 'obj'</summary>
4497 public override bool Equals(object obj) {
4498 LineTag other;
4500 if (obj == null) {
4501 return false;
4504 if (!(obj is LineTag)) {
4505 return false;
4508 if (obj == this) {
4509 return true;
4512 other = (LineTag)obj;
4514 if (this.font.Equals(other.font) && this.color.Equals(other.color)) { // FIXME add checking for things like link or type later
4515 return true;
4518 return false;
4521 public override int GetHashCode() {
4522 return base.GetHashCode ();
4525 public override string ToString() {
4526 if (length > 0)
4527 return "Tag starts at index " + this.start + " length " + this.length + " text: " + this.line.Text.Substring(this.start-1, this.length) + "Font " + this.font.ToString();
4528 return "Zero Lengthed tag at index " + this.start;
4531 #endregion // Internal Methods
4534 internal class UndoClass {
4535 internal enum ActionType {
4536 InsertChar,
4537 InsertString,
4538 DeleteChar,
4539 DeleteChars,
4540 CursorMove,
4541 Mark,
4542 CompoundBegin,
4543 CompoundEnd,
4546 internal class Action {
4547 internal ActionType type;
4548 internal int line_no;
4549 internal int pos;
4550 internal object data;
4553 #region Local Variables
4554 private Document document;
4555 private Stack undo_actions;
4556 private Stack redo_actions;
4558 private int undo_levels;
4559 private int redo_levels;
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 internal int UndoLevels {
4574 get {
4575 return undo_levels;
4579 internal int RedoLevels {
4580 get {
4581 return redo_levels;
4585 internal string UndoName {
4586 get {
4587 Action action;
4589 action = (Action)undo_actions.Peek();
4591 if (action.type == ActionType.CompoundEnd)
4592 return (string) action.data;
4594 switch(action.type) {
4595 case ActionType.InsertChar: {
4596 Locale.GetText("Insert character");
4597 break;
4600 case ActionType.DeleteChar: {
4601 Locale.GetText("Delete character");
4602 break;
4605 case ActionType.InsertString: {
4606 Locale.GetText("Insert string");
4607 break;
4610 case ActionType.DeleteChars: {
4611 Locale.GetText("Delete string");
4612 break;
4615 case ActionType.CursorMove: {
4616 Locale.GetText("Cursor move");
4617 break;
4620 return null;
4624 internal string RedoName() {
4625 return null;
4627 #endregion // Properties
4629 #region Internal Methods
4630 internal void Clear() {
4631 undo_actions.Clear();
4632 redo_actions.Clear();
4633 undo_levels = 0;
4634 redo_levels = 0;
4637 internal void Undo() {
4638 Action action;
4639 int compound_stack = 0;
4641 if (undo_actions.Count == 0) {
4642 return;
4647 do {
4648 action = (Action)undo_actions.Pop();
4650 // Put onto redo stack
4651 redo_actions.Push(action);
4653 // Do the thing
4654 switch(action.type) {
4655 case ActionType.CompoundEnd:
4656 compound_stack++;
4657 break;
4659 case ActionType.CompoundBegin:
4660 compound_stack--;
4661 undo_levels--;
4662 redo_levels++;
4663 break;
4665 case ActionType.InsertString:
4666 document.DeleteMultiline (document.GetLine (action.line_no),
4667 action.pos, ((string) action.data).Length + 1);
4668 break;
4670 case ActionType.InsertChar: {
4671 // FIXME - implement me
4672 break;
4675 case ActionType.DeleteChars: {
4676 this.Insert(document.GetLine(action.line_no), action.pos, (Line)action.data);
4677 Undo(); // Grab the cursor location
4678 break;
4681 case ActionType.CursorMove: {
4682 document.caret.line = document.GetLine(action.line_no);
4683 if (document.caret.line == null) {
4684 Undo();
4685 break;
4688 document.caret.tag = document.caret.line.FindTag(action.pos);
4689 document.caret.pos = action.pos;
4690 document.caret.height = document.caret.tag.height;
4692 if (document.owner.IsHandleCreated) {
4693 XplatUI.DestroyCaret(document.owner.Handle);
4694 XplatUI.CreateCaret(document.owner.Handle, 2, document.caret.height);
4695 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);
4697 document.DisplayCaret ();
4700 // FIXME - enable call
4701 //if (document.CaretMoved != null) document.CaretMoved(this, EventArgs.Empty);
4702 break;
4705 } while (compound_stack > 0);
4708 internal void Redo() {
4709 if (redo_actions.Count == 0) {
4710 return;
4713 #endregion // Internal Methods
4715 #region Private Methods
4717 public void BeginCompoundAction ()
4719 Action cb = new Action ();
4720 cb.type = ActionType.CompoundBegin;
4722 undo_actions.Push (cb);
4725 public void EndCompoundAction ()
4727 Action ce = new Action ();
4728 ce.type = ActionType.CompoundEnd;
4730 undo_actions.Push (ce);
4731 undo_levels++;
4734 // pos = 1-based
4735 public void RecordDeleteChars(Line line, int pos, int length) {
4736 RecordDelete(line, pos, line, pos + length - 1);
4739 // start_pos, end_pos = 1 based
4740 public void RecordDelete(Line start_line, int start_pos, Line end_line, int end_pos) {
4741 Line l;
4742 Action a;
4744 l = Duplicate(start_line, start_pos, end_line, end_pos);
4746 a = new Action();
4747 a.type = ActionType.DeleteChars;
4748 a.data = l;
4749 a.line_no = start_line.line_no;
4750 a.pos = start_pos - 1;
4752 // Record the cursor position before, since the actions will occur in reverse order
4753 RecordCursor();
4754 undo_actions.Push(a);
4757 public void RecordInsertString (Line line, int pos, string str)
4759 Action a = new Action ();
4761 a.type = ActionType.InsertString;
4762 a.data = str;
4763 a.line_no = line.line_no;
4764 a.pos = pos;
4766 undo_actions.Push (a);
4769 public void RecordCursor() {
4770 if (document.caret.line == null) {
4771 return;
4774 RecordCursor(document.caret.line, document.caret.pos);
4777 public void RecordCursor(Line line, int pos) {
4778 Action a;
4780 if ((line.line_no == caret_line) && (pos == caret_pos)) {
4781 return;
4784 caret_line = line.line_no;
4785 caret_pos = pos;
4787 a = new Action();
4788 a.type = ActionType.CursorMove;
4789 a.line_no = line.line_no;
4790 a.pos = pos;
4792 undo_actions.Push(a);
4795 // start_pos = 1-based
4796 // end_pos = 1-based
4797 public Line Duplicate(Line start_line, int start_pos, Line end_line, int end_pos) {
4798 Line ret;
4799 Line line;
4800 Line current;
4801 LineTag tag;
4802 LineTag current_tag;
4803 int start;
4804 int end;
4805 int tag_start;
4806 int tag_length;
4808 line = new Line();
4809 ret = line;
4811 for (int i = start_line.line_no; i <= end_line.line_no; i++) {
4812 current = document.GetLine(i);
4814 if (start_line.line_no == i) {
4815 start = start_pos;
4816 } else {
4817 start = 1;
4820 if (end_line.line_no == i) {
4821 end = end_pos;
4822 } else {
4823 end = current.text.Length;
4826 // Text for the tag
4827 line.text = new StringBuilder(current.text.ToString(start - 1, end - start + 1));
4829 // Copy tags from start to start+length onto new line
4830 current_tag = current.FindTag(start - 1);
4831 while ((current_tag != null) && (current_tag.start < end)) {
4832 if ((current_tag.start <= start) && (start < (current_tag.start + current_tag.length))) {
4833 // start tag is within this tag
4834 tag_start = start;
4835 } else {
4836 tag_start = current_tag.start;
4839 if (end < (current_tag.start + current_tag.length)) {
4840 tag_length = end - tag_start + 1;
4841 } else {
4842 tag_length = current_tag.start + current_tag.length - tag_start;
4844 tag = new LineTag(line, tag_start - start + 1, tag_length);
4845 tag.CopyFormattingFrom (current_tag);
4847 current_tag = current_tag.next;
4849 // Add the new tag to the line
4850 if (line.tags == null) {
4851 line.tags = tag;
4852 } else {
4853 LineTag tail;
4854 tail = line.tags;
4856 while (tail.next != null) {
4857 tail = tail.next;
4859 tail.next = tag;
4860 tag.previous = tail;
4864 if ((i + 1) <= end_line.line_no) {
4865 line.soft_break = current.soft_break;
4867 // Chain them (we use right/left as next/previous)
4868 line.right = new Line();
4869 line.right.left = line;
4870 line = line.right;
4874 return ret;
4877 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
4878 internal void Insert(Line line, int pos, Line insert) {
4879 Line current;
4880 LineTag tag;
4881 int offset;
4882 int lines;
4883 Line first;
4885 // Handle special case first
4886 if (insert.right == null) {
4888 // Single line insert
4889 document.Split(line, pos);
4891 if (insert.tags == null) {
4892 return; // Blank line
4895 //Insert our tags at the end
4896 tag = line.tags;
4898 while (tag.next != null) {
4899 tag = tag.next;
4902 offset = tag.start + tag.length - 1;
4904 tag.next = insert.tags;
4905 line.text.Insert(offset, insert.text.ToString());
4907 // Adjust start locations
4908 tag = tag.next;
4909 while (tag != null) {
4910 tag.start += offset;
4911 tag.line = line;
4912 tag = tag.next;
4914 // Put it back together
4915 document.Combine(line.line_no, line.line_no + 1);
4916 document.UpdateView(line, pos);
4917 return;
4920 first = line;
4921 lines = 1;
4922 current = insert;
4923 while (current != null) {
4924 if (current == insert) {
4925 // Inserting the first line we split the line (and make space)
4926 document.Split(line, pos);
4927 //Insert our tags at the end of the line
4928 tag = line.tags;
4930 if (tag != null) {
4931 while (tag.next != null) {
4932 tag = tag.next;
4934 offset = tag.start + tag.length - 1;
4935 tag.next = current.tags;
4936 tag.next.previous = tag;
4938 tag = tag.next;
4940 } else {
4941 offset = 0;
4942 line.tags = current.tags;
4943 line.tags.previous = null;
4944 tag = line.tags;
4946 } else {
4947 document.Split(line.line_no, 0);
4948 offset = 0;
4949 line.tags = current.tags;
4950 line.tags.previous = null;
4951 tag = line.tags;
4953 // Adjust start locations and line pointers
4954 while (tag != null) {
4955 tag.start += offset;
4956 tag.line = line;
4957 tag = tag.next;
4960 line.text.Insert(offset, current.text.ToString());
4961 line.Grow(line.text.Length);
4963 line.recalc = true;
4964 line = document.GetLine(line.line_no + 1);
4966 // FIXME? Test undo of line-boundaries
4967 if ((current.right == null) && (current.tags.length != 0)) {
4968 document.Combine(line.line_no - 1, line.line_no);
4970 current = current.right;
4971 lines++;
4975 // Recalculate our document
4976 document.UpdateView(first, lines, pos);
4977 return;
4979 #endregion // Private Methods