* TextBoxBase.cs: We need to cap this value, since Maximum -
[mono-project.git] / mcs / class / Managed.Windows.Forms / System.Windows.Forms / TextControl.cs
blob064178388c4434906f9b9c3fc9730e033c8ed63b
1 // Permission is hereby granted, free of charge, to any person obtaining
2 // a copy of this software and associated documentation files (the
3 // "Software"), to deal in the Software without restriction, including
4 // without limitation the rights to use, copy, modify, merge, publish,
5 // distribute, sublicense, and/or sell copies of the Software, and to
6 // permit persons to whom the Software is furnished to do so, subject to
7 // the following conditions:
8 //
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
11 //
12 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 // Copyright (c) 2004-2006 Novell, Inc. (http://www.novell.com)
22 // Authors:
23 // Peter Bartok pbartok@novell.com
27 // NOT COMPLETE
29 // There's still plenty of things missing, I've got most of it planned, just hadn't had
30 // the time to write it all yet.
31 // Stuff missing (in no particular order):
32 // - Align text after RecalculateLine
33 // - Implement tag types for hotlinks, images, etc.
34 // - Implement CaretPgUp/PgDown
36 // NOTE:
37 // selection_start.pos and selection_end.pos are 0-based
38 // selection_start.pos = first selected char
39 // selection_end.pos = first NOT-selected char
41 // FormatText methods are 1-based (as are all tags, LineTag.Start is 1 for
42 // the first character on a line; the reason is that 0 is the position
43 // *before* the first character on a line
46 #undef Debug
48 using System;
49 using System.Collections;
50 using System.Drawing;
51 using System.Drawing.Text;
52 using System.Text;
54 namespace System.Windows.Forms {
55 internal enum LineColor {
56 Red = 0,
57 Black = 1
60 internal enum CaretSelection {
61 Position, // Selection=Caret
62 Word, // Selection=Word under caret
63 Line // Selection=Line under caret
66 internal class FontDefinition {
67 internal String face;
68 internal int size;
69 internal FontStyle add_style;
70 internal FontStyle remove_style;
71 internal Color color;
72 internal Font font_obj;
74 internal FontDefinition() {
75 face = null;
76 size = 0;
77 color = Color.Empty;
81 internal enum CaretDirection {
82 CharForward, // Move a char to the right
83 CharBack, // Move a char to the left
84 LineUp, // Move a line up
85 LineDown, // Move a line down
86 Home, // Move to the beginning of the line
87 End, // Move to the end of the line
88 PgUp, // Move one page up
89 PgDn, // Move one page down
90 CtrlPgUp, // Move caret to the first visible char in the viewport
91 CtrlPgDn, // Move caret to the last visible char in the viewport
92 CtrlHome, // Move to the beginning of the document
93 CtrlEnd, // Move to the end of the document
94 WordBack, // Move to the beginning of the previous word (or beginning of line)
95 WordForward, // Move to the beginning of the next word (or end of line)
96 SelectionStart, // Move to the beginning of the current selection
97 SelectionEnd, // Move to the end of the current selection
98 CharForwardNoWrap, // Move a char forward, but don't wrap onto the next line
99 CharBackNoWrap // Move a char backward, but don't wrap onto the previous line
102 // Being cloneable should allow for nice line and document copies...
103 internal class Line : ICloneable, IComparable {
104 #region Local Variables
105 // Stuff that matters for our line
106 internal StringBuilder text; // Characters for the line
107 internal float[] widths; // Width of each character; always one larger than text.Length
108 internal int space; // Number of elements in text and widths
109 internal int line_no; // Line number
110 internal LineTag tags; // Tags describing the text
111 internal int Y; // Baseline
112 internal int height; // Height of the line (height of tallest tag)
113 internal int ascent; // Ascent of the line (ascent of the tallest tag)
114 internal HorizontalAlignment alignment; // Alignment of the line
115 internal int align_shift; // Pixel shift caused by the alignment
116 internal bool soft_break; // Tag is 'broken soft' and continuation from previous line
117 internal int indent; // Left indent for the first line
118 internal int hanging_indent; // Hanging indent (left indent for all but the first line)
119 internal int right_indent; // Right indent for all lines
120 internal bool carriage_return;
123 // Stuff that's important for the tree
124 internal Line parent; // Our parent line
125 internal Line left; // Line with smaller line number
126 internal Line right; // Line with higher line number
127 internal LineColor color; // We're doing a black/red tree. this is the node color
128 internal int DEFAULT_TEXT_LEN; //
129 internal static StringFormat string_format; // For calculating widths/heights
130 internal bool recalc; // Line changed
131 #endregion // Local Variables
133 #region Constructors
134 internal Line() {
135 color = LineColor.Red;
136 left = null;
137 right = null;
138 parent = null;
139 text = null;
140 recalc = true;
141 soft_break = false;
142 alignment = HorizontalAlignment.Left;
144 if (string_format == null) {
145 string_format = new StringFormat(StringFormat.GenericTypographic);
146 string_format.Trimming = StringTrimming.None;
147 string_format.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;
151 internal Line(int LineNo, string Text, Font font, Brush color) : this() {
152 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
154 text = new StringBuilder(Text, space);
155 line_no = LineNo;
157 widths = new float[space + 1];
158 tags = new LineTag(this, 1, text.Length);
159 tags.font = font;
160 tags.color = color;
163 internal Line(int LineNo, string Text, HorizontalAlignment align, Font font, Brush color) : this() {
164 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
166 text = new StringBuilder(Text, space);
167 line_no = LineNo;
168 alignment = align;
170 widths = new float[space + 1];
171 tags = new LineTag(this, 1, text.Length);
172 tags.font = font;
173 tags.color = color;
176 internal Line(int LineNo, string Text, LineTag tag) : this() {
177 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
179 text = new StringBuilder(Text, space);
180 line_no = LineNo;
182 widths = new float[space + 1];
183 tags = tag;
186 #endregion // Constructors
188 #region Internal Properties
189 internal int Indent {
190 get {
191 return indent;
194 set {
195 indent = value;
196 recalc = true;
200 internal int HangingIndent {
201 get {
202 return hanging_indent;
205 set {
206 hanging_indent = value;
207 recalc = true;
211 internal int RightIndent {
212 get {
213 return right_indent;
216 set {
217 right_indent = value;
218 recalc = true;
223 internal int Height {
224 get {
225 return height;
228 set {
229 height = value;
233 internal int LineNo {
234 get {
235 return line_no;
238 set {
239 line_no = value;
243 internal string Text {
244 get {
245 return text.ToString();
248 set {
249 text = new StringBuilder(value, value.Length > DEFAULT_TEXT_LEN ? value.Length : DEFAULT_TEXT_LEN);
253 internal HorizontalAlignment Alignment {
254 get {
255 return alignment;
258 set {
259 if (alignment != value) {
260 alignment = value;
261 recalc = true;
265 #if no
266 internal StringBuilder Text {
267 get {
268 return text;
271 set {
272 text = value;
275 #endif
276 #endregion // Internal Properties
278 #region Internal Methods
279 // Make sure we always have enoughs space in text and widths
280 internal void Grow(int minimum) {
281 int length;
282 float[] new_widths;
284 length = text.Length;
286 if ((length + minimum) > space) {
287 // We need to grow; double the size
289 if ((length + minimum) > (space * 2)) {
290 new_widths = new float[length + minimum * 2 + 1];
291 space = length + minimum * 2;
292 } else {
293 new_widths = new float[space * 2 + 1];
294 space *= 2;
296 widths.CopyTo(new_widths, 0);
298 widths = new_widths;
302 internal void Streamline(int lines) {
303 LineTag current;
304 LineTag next;
306 current = this.tags;
307 next = current.next;
309 // Catch what the loop below wont; eliminate 0 length
310 // tags, but only if there are other tags after us
311 while ((current.length == 0) && (next != null)) {
312 tags = next;
313 tags.previous = null;
314 current = next;
315 next = current.next;
318 if (next == null) {
319 return;
322 while (next != null) {
323 // Take out 0 length tags unless it's the last tag in the document
324 if (next.length == 0) {
325 if ((next.next != null) || (line_no != lines)) {
326 current.next = next.next;
327 if (current.next != null) {
328 current.next.previous = current;
330 next = current.next;
331 continue;
334 if (current.Combine(next)) {
335 next = current.next;
336 continue;
339 current = current.next;
340 next = current.next;
344 /// <summary> Find the tag on a line based on the character position, pos is 0-based</summary>
345 internal LineTag FindTag(int pos) {
346 LineTag tag;
348 if (pos == 0) {
349 return tags;
352 tag = this.tags;
354 if (pos >= text.Length) {
355 pos = text.Length - 1;
358 while (tag != null) {
359 if (((tag.start - 1) <= pos) && (pos < (tag.start + tag.length - 1))) {
360 return LineTag.GetFinalTag (tag);
362 tag = tag.next;
364 return null;
367 /// <summary>
368 /// Recalculate a single line using the same char for every character in the line
369 /// </summary>
371 internal bool RecalculatePasswordLine(Graphics g, Document doc) {
372 LineTag tag;
373 int pos;
374 int len;
375 float w;
376 bool ret;
377 int descent;
379 pos = 0;
380 len = this.text.Length;
381 tag = this.tags;
382 ascent = 0;
383 tag.shift = 0;
384 tag.width = 0;
386 this.recalc = false;
387 widths[0] = indent;
388 tag.X = indent;
390 w = g.MeasureString(doc.password_char, tags.font, 10000, string_format).Width;
392 if (this.height != (int)tag.font.Height) {
393 ret = true;
394 } else {
395 ret = false;
398 this.height = (int)tag.font.Height;
399 tag.height = this.height;
401 XplatUI.GetFontMetrics(g, tag.font, out tag.ascent, out descent);
402 this.ascent = tag.ascent;
404 while (pos < len) {
405 tag.width += w;
406 pos++;
407 widths[pos] = widths[pos-1] + w;
410 return ret;
413 /// <summary>
414 /// Go through all tags on a line and recalculate all size-related values;
415 /// returns true if lineheight changed
416 /// </summary>
417 internal bool RecalculateLine(Graphics g, Document doc) {
418 LineTag tag;
419 int pos;
420 int len;
421 SizeF size;
422 float w;
423 int prev_height;
424 bool retval;
425 bool wrapped;
426 Line line;
427 int wrap_pos;
428 float wrap_width;
430 pos = 0;
431 len = this.text.Length;
432 tag = this.tags;
433 prev_height = this.height; // For drawing optimization calculations
434 this.height = 0; // Reset line height
435 this.ascent = 0; // Reset the ascent for the line
436 tag.shift = 0;
437 tag.width = 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.width = 0;
457 tag.ascent = 0;
458 if (tag.previous != null) {
459 tag.X = tag.previous.X;
460 } else {
461 tag.X = (int)widths[pos];
463 tag = tag.next;
464 tag.width = 0;
465 tag.shift = 0;
468 w = size.Width;
470 if (Char.IsWhiteSpace(text[pos])) {
471 wrap_pos = pos + 1;
472 wrap_width = tag.width + w;
475 if (doc.wrap) {
476 if ((wrap_pos > 0) && (wrap_pos != len) && (widths[pos] + w) + 5 > (doc.viewport_width - this.right_indent)) {
477 pos = wrap_pos;
478 tag.width = wrap_width;
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
486 tag.width = tag.width + w;
487 doc.Split(this, tag, pos, this.soft_break);
488 this.soft_break = true;
489 len = this.text.Length;
490 retval = true;
491 wrapped = true;
495 // Contract all soft lines that follow back into our line
496 if (!wrapped) {
497 tag.width += w;
499 pos++;
501 widths[pos] = widths[pos-1] + w;
503 if (pos == len) {
504 line = doc.GetLine(this.line_no + 1);
505 if ((line != null) && soft_break) {
506 // Pull the two lines together
507 doc.Combine(this.line_no, this.line_no + 1);
508 len = this.text.Length;
509 retval = true;
514 if (pos == (tag.start-1 + tag.length)) {
515 // We just found the end of our current tag
516 tag.height = (int)tag.font.Height;
518 // Check if we're the tallest on the line (so far)
519 if (tag.height > this.height) {
520 this.height = tag.height; // Yep; make sure the line knows
523 if (tag.ascent == 0) {
524 int descent;
526 XplatUI.GetFontMetrics(g, tag.font, out tag.ascent, out descent);
529 if (tag.ascent > this.ascent) {
530 LineTag t;
532 // We have a tag that has a taller ascent than the line;
534 t = tags;
535 while (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.width = 0;
556 tag.shift = 0;
557 wrap_pos = pos;
558 wrap_width = tag.width;
563 if (this.height == 0) {
564 this.height = tags.font.Height;
565 tag.height = this.height;
568 if (prev_height != this.height) {
569 retval = true;
571 return retval;
573 #endregion // Internal Methods
575 #region Administrative
576 public int CompareTo(object obj) {
577 if (obj == null) {
578 return 1;
581 if (! (obj is Line)) {
582 throw new ArgumentException("Object is not of type Line", "obj");
585 if (line_no < ((Line)obj).line_no) {
586 return -1;
587 } else if (line_no > ((Line)obj).line_no) {
588 return 1;
589 } else {
590 return 0;
594 public object Clone() {
595 Line clone;
597 clone = new Line();
599 clone.text = text;
601 if (left != null) {
602 clone.left = (Line)left.Clone();
605 if (left != null) {
606 clone.left = (Line)left.Clone();
609 return clone;
612 internal object CloneLine() {
613 Line clone;
615 clone = new Line();
617 clone.text = text;
619 return clone;
622 public override bool Equals(object obj) {
623 if (obj == null) {
624 return false;
627 if (!(obj is Line)) {
628 return false;
631 if (obj == this) {
632 return true;
635 if (line_no == ((Line)obj).line_no) {
636 return true;
639 return false;
642 public override int GetHashCode() {
643 return base.GetHashCode ();
646 public override string ToString() {
647 return "Line " + line_no;
650 #endregion // Administrative
653 internal class Document : ICloneable, IEnumerable {
654 #region Structures
655 // FIXME - go through code and check for places where
656 // we do explicit comparisons instead of using the compare overloads
657 internal struct Marker {
658 internal Line line;
659 internal LineTag tag;
660 internal int pos;
661 internal int height;
663 public static bool operator<(Marker lhs, Marker rhs) {
664 if (lhs.line.line_no < rhs.line.line_no) {
665 return true;
668 if (lhs.line.line_no == rhs.line.line_no) {
669 if (lhs.pos < rhs.pos) {
670 return true;
673 return false;
676 public static bool operator>(Marker lhs, Marker rhs) {
677 if (lhs.line.line_no > rhs.line.line_no) {
678 return true;
681 if (lhs.line.line_no == rhs.line.line_no) {
682 if (lhs.pos > rhs.pos) {
683 return true;
686 return false;
689 public static bool operator==(Marker lhs, Marker rhs) {
690 if ((lhs.line.line_no == rhs.line.line_no) && (lhs.pos == rhs.pos)) {
691 return true;
693 return false;
696 public static bool operator!=(Marker lhs, Marker rhs) {
697 if ((lhs.line.line_no != rhs.line.line_no) || (lhs.pos != rhs.pos)) {
698 return true;
700 return false;
703 public void Combine(Line move_to_line, int move_to_line_length) {
704 line = move_to_line;
705 pos += move_to_line_length;
706 tag = LineTag.FindTag(line, pos);
709 // This is for future use, right now Document.Split does it by hand, with some added shortcut logic
710 public void Split(Line move_to_line, int split_at) {
711 line = move_to_line;
712 pos -= split_at;
713 tag = LineTag.FindTag(line, pos);
716 public override bool Equals(object obj) {
717 return this==(Marker)obj;
720 public override int GetHashCode() {
721 return base.GetHashCode ();
724 public override string ToString() {
725 return "Marker Line " + line + ", Position " + pos;
729 #endregion Structures
731 #region Local Variables
732 private Line document;
733 private int lines;
734 private Line sentinel;
735 private int document_id;
736 private Random random = new Random();
737 internal string password_char;
738 private StringBuilder password_cache;
739 private bool calc_pass;
740 private int char_count;
742 private bool no_recalc;
743 private bool recalc_pending;
744 private int recalc_start;
745 private int recalc_end;
746 private bool recalc_optimize;
748 internal bool multiline;
749 internal bool wrap;
751 internal UndoClass undo;
753 internal Marker caret;
754 internal Marker selection_start;
755 internal Marker selection_end;
756 internal bool selection_visible;
757 internal Marker selection_anchor;
758 internal Marker selection_prev;
759 internal bool selection_end_anchor;
761 internal int viewport_x;
762 internal int viewport_y; // The visible area of the document
763 internal int viewport_width;
764 internal int viewport_height;
766 internal int document_x; // Width of the document
767 internal int document_y; // Height of the document
769 internal Rectangle invalid;
771 internal int crlf_size; // 1 or 2, depending on whether we use \r\n or just \n
773 internal TextBoxBase owner; // Who's owning us?
774 static internal int caret_width = 1;
775 static internal int caret_shift = 1;
776 #endregion // Local Variables
778 #region Constructors
779 internal Document(TextBoxBase owner) {
780 lines = 0;
782 this.owner = owner;
784 multiline = true;
785 password_char = "";
786 calc_pass = false;
787 no_recalc = false;
788 recalc_pending = false;
790 // Tree related stuff
791 sentinel = new Line();
792 sentinel.color = LineColor.Black;
794 document = sentinel;
796 // We always have a blank line
797 owner.HandleCreated += new EventHandler(owner_HandleCreated);
798 owner.VisibleChanged += new EventHandler(owner_VisibleChanged);
800 Add(1, "", owner.Font, ThemeEngine.Current.ResPool.GetSolidBrush(owner.ForeColor));
801 Line l = GetLine (1);
802 l.soft_break = true;
804 undo = new UndoClass(this);
806 selection_visible = false;
807 selection_start.line = this.document;
808 selection_start.pos = 0;
809 selection_start.tag = selection_start.line.tags;
810 selection_end.line = this.document;
811 selection_end.pos = 0;
812 selection_end.tag = selection_end.line.tags;
813 selection_anchor.line = this.document;
814 selection_anchor.pos = 0;
815 selection_anchor.tag = selection_anchor.line.tags;
816 caret.line = this.document;
817 caret.pos = 0;
818 caret.tag = caret.line.tags;
820 viewport_x = 0;
821 viewport_y = 0;
823 crlf_size = 2;
825 // Default selection is empty
827 document_id = random.Next();
829 #endregion
831 #region Internal Properties
832 internal Line Root {
833 get {
834 return document;
837 set {
838 document = value;
842 internal int Lines {
843 get {
844 return lines;
848 internal Line CaretLine {
849 get {
850 return caret.line;
854 internal int CaretPosition {
855 get {
856 return caret.pos;
860 internal Point Caret {
861 get {
862 return new Point((int)caret.tag.line.widths[caret.pos] + caret.line.align_shift, caret.line.Y);
866 internal LineTag CaretTag {
867 get {
868 return caret.tag;
871 set {
872 caret.tag = value;
876 internal int CRLFSize {
877 get {
878 return crlf_size;
881 set {
882 crlf_size = value;
886 internal string PasswordChar {
887 get {
888 return password_char;
891 set {
892 password_char = value;
893 if ((password_char.Length != 0) && (password_char[0] != '\0')) {
894 char ch;
896 calc_pass = true;
897 ch = value[0];
898 password_cache = new StringBuilder(1024);
899 for (int i = 0; i < 1024; i++) {
900 password_cache.Append(ch);
902 } else {
903 calc_pass = false;
904 password_cache = null;
909 internal int ViewPortX {
910 get {
911 return viewport_x;
914 set {
915 viewport_x = value;
919 internal int Length {
920 get {
921 return char_count + lines - 1; // Add \n for each line but the last
925 private int CharCount {
926 get {
927 return char_count;
930 set {
931 char_count = value;
933 if (LengthChanged != null) {
934 LengthChanged(this, EventArgs.Empty);
939 ///<summary>Setting NoRecalc to true will prevent the document from being recalculated.
940 ///This ensures that coordinates of added text are predictable after adding the text even with wrapped view</summary>
941 internal bool NoRecalc {
942 get {
943 return no_recalc;
946 set {
947 no_recalc = value;
948 if (!no_recalc && recalc_pending) {
949 RecalculateDocument(owner.CreateGraphicsInternal(), recalc_start, recalc_end, recalc_optimize);
950 recalc_pending = false;
955 internal int ViewPortY {
956 get {
957 return viewport_y;
960 set {
961 viewport_y = value;
965 internal int ViewPortWidth {
966 get {
967 return viewport_width;
970 set {
971 viewport_width = value;
975 internal int ViewPortHeight {
976 get {
977 return viewport_height;
980 set {
981 viewport_height = value;
986 internal int Width {
987 get {
988 return this.document_x;
992 internal int Height {
993 get {
994 return this.document_y;
998 internal bool SelectionVisible {
999 get {
1000 return selection_visible;
1004 internal bool Wrap {
1005 get {
1006 return wrap;
1009 set {
1010 wrap = value;
1014 #endregion // Internal Properties
1016 #region Private Methods
1017 // For debugging
1018 internal int DumpTree(Line line, bool with_tags) {
1019 int total;
1021 total = 1;
1023 Console.Write("Line {0} [# {1}], Y: {2}, soft: {3}, Text: '{4}'",
1024 line.line_no, line.GetHashCode(), line.Y, line.soft_break,
1025 line.text != null ? line.text.ToString() : "undefined");
1027 if (line.left == sentinel) {
1028 Console.Write(", left = sentinel");
1029 } else if (line.left == null) {
1030 Console.Write(", left = NULL");
1033 if (line.right == sentinel) {
1034 Console.Write(", right = sentinel");
1035 } else if (line.right == null) {
1036 Console.Write(", right = NULL");
1039 Console.WriteLine("");
1041 if (with_tags) {
1042 LineTag tag;
1043 int count;
1044 int length;
1046 tag = line.tags;
1047 count = 1;
1048 length = 0;
1049 Console.Write(" Tags: ");
1050 while (tag != null) {
1051 Console.Write("{0} <{1}>-<{2}> ", count++, tag.start, 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;
1426 caret.height = caret.tag.height;
1428 if (owner.IsHandleCreated) {
1429 if (owner.Focused) {
1430 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.align_shift - viewport_x, caret.line.Y + caret.tag.shift - viewport_y + caret_shift);
1433 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1439 internal void PositionCaret(int x, int y) {
1440 if (!owner.IsHandleCreated) {
1441 return;
1444 undo.RecordCursor();
1446 caret.tag = FindCursor(x, y, out caret.pos);
1447 caret.line = caret.tag.line;
1448 caret.height = caret.tag.height;
1450 if (owner.Focused) {
1451 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);
1454 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1457 internal void CaretHasFocus() {
1458 if ((caret.tag != null) && owner.IsHandleCreated) {
1459 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1460 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.align_shift - viewport_x, caret.line.Y + caret.tag.shift - viewport_y + caret_shift);
1462 DisplayCaret ();
1465 if (owner.IsHandleCreated && selection_visible) {
1466 InvalidateSelectionArea ();
1470 internal void CaretLostFocus() {
1471 if (!owner.IsHandleCreated) {
1472 return;
1474 XplatUI.DestroyCaret(owner.Handle);
1477 internal void AlignCaret() {
1478 if (!owner.IsHandleCreated) {
1479 return;
1482 undo.RecordCursor();
1484 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1485 caret.height = caret.tag.height;
1487 if (owner.Focused) {
1488 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1489 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);
1490 DisplayCaret ();
1493 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1496 internal void UpdateCaret() {
1497 if (!owner.IsHandleCreated || caret.tag == null) {
1498 return;
1501 undo.RecordCursor();
1503 if (caret.tag.height != caret.height) {
1504 caret.height = caret.tag.height;
1505 if (owner.Focused) {
1506 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1510 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);
1512 DisplayCaret ();
1514 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1517 internal void DisplayCaret() {
1518 if (!owner.IsHandleCreated) {
1519 return;
1522 if (owner.Focused && (!selection_visible || owner.show_caret_w_selection)) {
1523 XplatUI.CaretVisible(owner.Handle, true);
1527 internal void HideCaret() {
1528 if (!owner.IsHandleCreated) {
1529 return;
1532 if (owner.Focused) {
1533 XplatUI.CaretVisible(owner.Handle, false);
1537 internal void MoveCaret(CaretDirection direction) {
1538 // FIXME should we use IsWordSeparator to detect whitespace, instead
1539 // of looking for actual spaces in the Word move cases?
1541 bool nowrap = false;
1542 switch(direction) {
1543 case CaretDirection.CharForwardNoWrap:
1544 nowrap = true;
1545 goto case CaretDirection.CharForward;
1546 case CaretDirection.CharForward: {
1547 caret.pos++;
1548 if (caret.pos > caret.line.text.Length) {
1549 if (multiline && !nowrap) {
1550 // Go into next line
1551 if (caret.line.line_no < this.lines) {
1552 caret.line = GetLine(caret.line.line_no+1);
1553 caret.pos = 0;
1554 caret.tag = caret.line.tags;
1555 } else {
1556 caret.pos--;
1558 } else {
1559 // Single line; we stay where we are
1560 caret.pos--;
1562 } else {
1563 if ((caret.tag.start - 1 + caret.tag.length) < caret.pos) {
1564 caret.tag = caret.tag.next;
1567 UpdateCaret();
1568 return;
1571 case CaretDirection.CharBackNoWrap:
1572 nowrap = true;
1573 goto case CaretDirection.CharBack;
1574 case CaretDirection.CharBack: {
1575 if (caret.pos > 0) {
1576 // caret.pos--; // folded into the if below
1577 if (--caret.pos > 0) {
1578 if (caret.tag.start > caret.pos) {
1579 caret.tag = caret.tag.previous;
1582 } else {
1583 if (caret.line.line_no > 1 && !nowrap) {
1584 caret.line = GetLine(caret.line.line_no - 1);
1585 caret.pos = caret.line.text.Length;
1586 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1589 UpdateCaret();
1590 return;
1593 case CaretDirection.WordForward: {
1594 int len;
1596 len = caret.line.text.Length;
1597 if (caret.pos < len) {
1598 while ((caret.pos < len) && (caret.line.text[caret.pos] != ' ')) {
1599 caret.pos++;
1601 if (caret.pos < len) {
1602 // Skip any whitespace
1603 while ((caret.pos < len) && (caret.line.text[caret.pos] == ' ')) {
1604 caret.pos++;
1607 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1608 } else {
1609 if (caret.line.line_no < this.lines) {
1610 caret.line = GetLine(caret.line.line_no + 1);
1611 caret.pos = 0;
1612 caret.tag = caret.line.tags;
1615 UpdateCaret();
1616 return;
1619 case CaretDirection.WordBack: {
1620 if (caret.pos > 0) {
1621 caret.pos--;
1623 while ((caret.pos > 0) && (caret.line.text[caret.pos] == ' ')) {
1624 caret.pos--;
1627 while ((caret.pos > 0) && (caret.line.text[caret.pos] != ' ')) {
1628 caret.pos--;
1631 if (caret.line.text.ToString(caret.pos, 1) == " ") {
1632 if (caret.pos != 0) {
1633 caret.pos++;
1634 } else {
1635 caret.line = GetLine(caret.line.line_no - 1);
1636 caret.pos = caret.line.text.Length;
1639 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1640 } else {
1641 if (caret.line.line_no > 1) {
1642 caret.line = GetLine(caret.line.line_no - 1);
1643 caret.pos = caret.line.text.Length;
1644 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1647 UpdateCaret();
1648 return;
1651 case CaretDirection.LineUp: {
1652 if (caret.line.line_no > 1) {
1653 int pixel;
1655 pixel = (int)caret.line.widths[caret.pos];
1656 PositionCaret(pixel, GetLine(caret.line.line_no - 1).Y);
1658 DisplayCaret ();
1660 return;
1663 case CaretDirection.LineDown: {
1664 if (caret.line.line_no < lines) {
1665 int pixel;
1667 pixel = (int)caret.line.widths[caret.pos];
1668 PositionCaret(pixel, GetLine(caret.line.line_no + 1).Y);
1670 DisplayCaret ();
1672 return;
1675 case CaretDirection.Home: {
1676 if (caret.pos > 0) {
1677 caret.pos = 0;
1678 caret.tag = caret.line.tags;
1679 UpdateCaret();
1681 return;
1684 case CaretDirection.End: {
1685 if (caret.pos < caret.line.text.Length) {
1686 caret.pos = caret.line.text.Length;
1687 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1688 UpdateCaret();
1690 return;
1693 case CaretDirection.PgUp: {
1695 int new_y, y_offset;
1697 if (viewport_y == 0) {
1699 // This should probably be handled elsewhere
1700 if (!(owner is RichTextBox)) {
1701 // Page down doesn't do anything in a regular TextBox
1702 // if the bottom of the document
1703 // is already visible, the page and the caret stay still
1704 return;
1707 // We're just placing the caret at the end of the document, no scrolling needed
1708 owner.vscroll.Value = 0;
1709 Line line = GetLine (1);
1710 PositionCaret (line, 0);
1713 y_offset = caret.line.Y - viewport_y;
1714 new_y = caret.line.Y - viewport_height;
1716 owner.vscroll.Value = Math.Max (new_y, 0);
1717 PositionCaret ((int)caret.line.widths[caret.pos], y_offset + viewport_y);
1718 return;
1721 case CaretDirection.PgDn: {
1722 int new_y, y_offset;
1724 if ((viewport_y + viewport_height) > document_y) {
1726 // This should probably be handled elsewhere
1727 if (!(owner is RichTextBox)) {
1728 // Page up doesn't do anything in a regular TextBox
1729 // if the bottom of the document
1730 // is already visible, the page and the caret stay still
1731 return;
1734 // We're just placing the caret at the end of the document, no scrolling needed
1735 owner.vscroll.Value = owner.vscroll.Maximum - viewport_height + 1;
1736 Line line = GetLine (lines);
1737 PositionCaret (line, line.Text.Length);
1740 y_offset = caret.line.Y - viewport_y;
1741 new_y = caret.line.Y + viewport_height;
1743 owner.vscroll.Value = Math.Min (new_y, owner.vscroll.Maximum - viewport_height + 1);
1744 PositionCaret ((int)caret.line.widths[caret.pos], y_offset + viewport_y);
1746 return;
1749 case CaretDirection.CtrlPgUp: {
1750 PositionCaret(0, viewport_y);
1751 DisplayCaret ();
1752 return;
1755 case CaretDirection.CtrlPgDn: {
1756 Line line;
1757 LineTag tag;
1758 int index;
1760 tag = FindTag(0, viewport_y + viewport_height, out index, false);
1761 if (tag.line.line_no > 1) {
1762 line = GetLine(tag.line.line_no - 1);
1763 } else {
1764 line = tag.line;
1766 PositionCaret(line, line.Text.Length);
1767 DisplayCaret ();
1768 return;
1771 case CaretDirection.CtrlHome: {
1772 caret.line = GetLine(1);
1773 caret.pos = 0;
1774 caret.tag = caret.line.tags;
1776 UpdateCaret();
1777 return;
1780 case CaretDirection.CtrlEnd: {
1781 caret.line = GetLine(lines);
1782 caret.pos = caret.line.text.Length;
1783 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1785 UpdateCaret();
1786 return;
1789 case CaretDirection.SelectionStart: {
1790 caret.line = selection_start.line;
1791 caret.pos = selection_start.pos;
1792 caret.tag = selection_start.tag;
1794 UpdateCaret();
1795 return;
1798 case CaretDirection.SelectionEnd: {
1799 caret.line = selection_end.line;
1800 caret.pos = selection_end.pos;
1801 caret.tag = selection_end.tag;
1803 UpdateCaret();
1804 return;
1809 // Draw the document
1810 internal void Draw(Graphics g, Rectangle clip) {
1811 Line line; // Current line being drawn
1812 LineTag tag; // Current tag being drawn
1813 int start; // First line to draw
1814 int end; // Last line to draw
1815 StringBuilder text; // String representing the current line
1816 int line_no; //
1817 Brush disabled;
1818 Brush hilight;
1819 Brush hilight_text;
1821 // First, figure out from what line to what line we need to draw
1822 start = GetLineByPixel(clip.Top + viewport_y, false).line_no;
1823 end = GetLineByPixel(clip.Bottom + viewport_y, false).line_no;
1824 //Console.WriteLine("Starting drawing at line {0}, ending at line {1} (clip-bottom:{2})", start, end, clip.Bottom);
1826 // Now draw our elements; try to only draw those that are visible
1827 line_no = start;
1829 #if Debug
1830 DateTime n = DateTime.Now;
1831 Console.WriteLine("Started drawing: {0}s {1}ms", n.Second, n.Millisecond);
1832 #endif
1834 disabled = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorGrayText);
1835 hilight = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorHighlight);
1836 hilight_text = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorHighlightText);
1838 while (line_no <= end) {
1839 line = GetLine(line_no);
1840 #if not
1841 if (owner.backcolor_set || (owner.Enabled && !owner.read_only)) {
1842 g.FillRectangle(ThemeEngine.Current.ResPool.GetSolidBrush(owner.BackColor), new Rectangle(clip.Left, line.Y - viewport_y, clip.Width, line.Y - viewport_y + line.Height));
1843 } else {
1844 g.FillRectangle(ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorControl), new Rectangle(clip.Left, line.Y - viewport_y, clip.Width, line.Y - viewport_y + line.Height));
1846 #endif
1849 tag = line.tags;
1850 if (!calc_pass) {
1851 text = line.text;
1852 } else {
1853 // This fails if there's a password > 1024 chars...
1854 text = this.password_cache;
1856 while (tag != null) {
1857 if (tag.length == 0) {
1858 tag = tag.next;
1859 continue;
1862 if (((tag.X + tag.width) > (clip.Left - viewport_x)) || (tag.X < (clip.Right - viewport_x))) {
1863 // Check for selection
1864 if ((!selection_visible) || (!owner.ShowSelection) || (line_no < selection_start.line.line_no) || (line_no > selection_end.line.line_no)) {
1865 // regular drawing, no selection to deal with
1866 //g.DrawString(s.Substring(tag.start-1, tag.length), tag.font, tag.color, tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
1867 if (owner.is_enabled) {
1868 g.DrawString(text.ToString(tag.start-1, tag.length), tag.font, tag.color, tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
1869 } else {
1870 Color a;
1871 Color b;
1873 a = ((SolidBrush)tag.color).Color;
1874 b = ThemeEngine.Current.ColorWindowText;
1876 if ((a.R == b.R) && (a.G == b.G) && (a.B == b.B)) {
1877 g.DrawString(text.ToString(tag.start-1, tag.length), tag.font, disabled, tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
1878 } else {
1879 g.DrawString(text.ToString(tag.start-1, tag.length), tag.font, tag.color, tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
1882 } else {
1883 // we might have to draw our selection
1884 if ((line_no != selection_start.line.line_no) && (line_no != selection_end.line.line_no)) {
1885 // Special case, whole line is selected, draw this tag selected
1886 g.FillRectangle(
1887 hilight, // Brush
1888 tag.X + line.align_shift - viewport_x, // X
1889 line.Y + tag.shift - viewport_y, // Y
1890 line.widths[tag.start + tag.length - 1], // width
1891 tag.height // Height
1894 g.DrawString(
1895 //s.Substring(tag.start-1, tag.length), // String
1896 text.ToString(tag.start-1, tag.length), // String
1897 tag.font, // Font
1898 hilight_text, // Brush
1899 tag.X + line.align_shift - viewport_x, // X
1900 line.Y + tag.shift - viewport_y, // Y
1901 StringFormat.GenericTypographic);
1902 } else {
1903 bool highlight;
1904 bool partial;
1906 highlight = false;
1907 partial = false;
1909 // One or more, but not all tags on the line are selected
1910 if ((selection_start.tag == tag) && (selection_end.tag == tag)) {
1911 // Single tag selected, draw "normalSELECTEDnormal"
1912 partial = true;
1913 // First, the regular part
1914 g.DrawString(
1915 //s.Substring(tag.start - 1, selection_start.pos - tag.start + 1), // String
1916 text.ToString(tag.start - 1, selection_start.pos - tag.start + 1), // String
1917 tag.font, // Font
1918 tag.color, // Brush
1919 tag.X + line.align_shift - viewport_x, // X
1920 line.Y + tag.shift - viewport_y, // Y
1921 StringFormat.GenericTypographic);
1923 // Now the highlight
1924 g.FillRectangle(
1925 hilight, // Brush
1926 line.widths[selection_start.pos] + line.align_shift - viewport_x, // X
1927 line.Y + tag.shift - viewport_y, // Y
1928 line.widths[selection_end.pos] - line.widths[selection_start.pos], // Width
1929 tag.height); // Height
1931 g.DrawString(
1932 //s.Substring(selection_start.pos, selection_end.pos - selection_start.pos), // String
1933 text.ToString(selection_start.pos, selection_end.pos - selection_start.pos), // String
1934 tag.font, // Font
1935 hilight_text, // Brush
1936 line.widths[selection_start.pos] + line.align_shift - viewport_x, // X
1937 line.Y + tag.shift - viewport_y, // Y
1938 StringFormat.GenericTypographic);
1940 // And back to the regular
1941 g.DrawString(
1942 //s.Substring(selection_end.pos, tag.start + tag.length - selection_end.pos - 1), // String
1943 text.ToString(selection_end.pos, tag.start + tag.length - selection_end.pos - 1), // String
1944 tag.font, // Font
1945 tag.color, // Brush
1946 line.widths[selection_end.pos] + line.align_shift - viewport_x, // X
1947 line.Y + tag.shift - viewport_y, // Y
1948 StringFormat.GenericTypographic);
1950 } else if (selection_start.tag == tag) {
1951 partial = true;
1953 // The highlighted part
1954 g.FillRectangle(
1955 hilight,
1956 line.widths[selection_start.pos] + line.align_shift - viewport_x,
1957 line.Y + tag.shift - viewport_y,
1958 line.widths[tag.start + tag.length - 1] - line.widths[selection_start.pos],
1959 tag.height);
1961 g.DrawString(
1962 //s.Substring(selection_start.pos, tag.start + tag.length - selection_start.pos - 1), // String
1963 text.ToString(selection_start.pos, tag.start + tag.length - selection_start.pos - 1), // String
1964 tag.font, // Font
1965 hilight_text, // Brush
1966 line.widths[selection_start.pos] + line.align_shift - viewport_x, // X
1967 line.Y + tag.shift - viewport_y, // Y
1968 StringFormat.GenericTypographic);
1970 // The regular part
1971 g.DrawString(
1972 //s.Substring(tag.start - 1, selection_start.pos - tag.start + 1), // String
1973 text.ToString(tag.start - 1, selection_start.pos - tag.start + 1), // String
1974 tag.font, // Font
1975 tag.color, // Brush
1976 tag.X + line.align_shift - viewport_x, // X
1977 line.Y + tag.shift - viewport_y, // Y
1978 StringFormat.GenericTypographic);
1979 } else if (selection_end.tag == tag) {
1980 partial = true;
1982 // The highlighted part
1983 g.FillRectangle(
1984 hilight,
1985 tag.X + line.align_shift - viewport_x,
1986 line.Y + tag.shift - viewport_y,
1987 line.widths[selection_end.pos] - line.widths[tag.start - 1],
1988 tag.height);
1990 g.DrawString(
1991 //s.Substring(tag.start - 1, selection_end.pos - tag.start + 1), // String
1992 text.ToString(tag.start - 1, selection_end.pos - tag.start + 1), // String
1993 tag.font, // Font
1994 hilight_text, // Brush
1995 tag.X + line.align_shift - viewport_x, // X
1996 line.Y + tag.shift - viewport_y, // Y
1997 StringFormat.GenericTypographic);
1999 // The regular part
2000 g.DrawString(
2001 //s.Substring(selection_end.pos, tag.start + tag.length - selection_end.pos - 1), // String
2002 text.ToString(selection_end.pos, tag.start + tag.length - selection_end.pos - 1), // String
2003 tag.font, // Font
2004 tag.color, // Brush
2005 line.widths[selection_end.pos] + line.align_shift - viewport_x, // X
2006 line.Y + tag.shift - viewport_y, // Y
2007 StringFormat.GenericTypographic);
2008 } else {
2009 // no partially selected tags here, simple checks...
2010 if (selection_start.line == line) {
2011 int begin;
2012 int stop;
2014 begin = tag.start - 1;
2015 stop = tag.start + tag.length - 1;
2016 if (selection_end.line == line) {
2017 if ((begin >= selection_start.pos) && (stop < selection_end.pos)) {
2018 highlight = true;
2020 } else {
2021 if (stop > selection_start.pos) {
2022 highlight = true;
2025 } else if (selection_end.line == line) {
2026 if ((tag.start - 1) < selection_end.pos) {
2027 highlight = true;
2032 if (!partial) {
2033 if (highlight) {
2034 g.FillRectangle(
2035 hilight,
2036 tag.X + line.align_shift - viewport_x,
2037 line.Y + tag.shift - viewport_y,
2038 line.widths[tag.start + tag.length - 1] - line.widths[tag.start - 1],
2039 tag.height);
2041 g.DrawString(
2042 //s.Substring(tag.start-1, tag.length), // String
2043 text.ToString(tag.start-1, tag.length), // String
2044 tag.font, // Font
2045 hilight_text, // Brush
2046 tag.X + line.align_shift - viewport_x, // X
2047 line.Y + tag.shift - viewport_y, // Y
2048 StringFormat.GenericTypographic);
2049 } else {
2050 g.DrawString(
2051 //s.Substring(tag.start-1, tag.length), // String
2052 text.ToString(tag.start-1, tag.length), // String
2053 tag.font, // Font
2054 tag.color, // Brush
2055 tag.X + line.align_shift - viewport_x, // X
2056 line.Y + tag.shift - viewport_y, // Y
2057 StringFormat.GenericTypographic);
2065 tag = tag.next;
2068 line_no++;
2070 #if Debug
2071 n = DateTime.Now;
2072 Console.WriteLine("Finished drawing: {0}s {1}ms", n.Second, n.Millisecond);
2073 #endif
2077 private void InsertLineString (Line line, int pos, string s)
2079 bool carriage_return = false;
2081 if (s.EndsWith ("\r")) {
2082 s = s.Substring (0, s.Length - 1);
2083 carriage_return = true;
2086 InsertString (line, pos, s);
2088 if (carriage_return) {
2089 Line l = GetLine (line.line_no);
2090 l.carriage_return = true;
2094 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
2095 internal void Insert(Line line, int pos, bool update_caret, string s) {
2096 int break_index;
2097 int base_line;
2098 int old_line_count;
2099 int count = 1;
2100 LineTag tag = LineTag.FindTag (line, pos);
2102 NoRecalc = true;
2103 undo.BeginCompoundAction ();
2105 base_line = line.line_no;
2106 old_line_count = lines;
2108 break_index = s.IndexOf ('\n');
2110 // Bump the text at insertion point a line down if we're inserting more than one line
2111 if (break_index > -1) {
2112 Split(line, pos);
2113 line.soft_break = false;
2114 // Remainder of start line is now in base_line + 1
2117 if (break_index == -1)
2118 break_index = s.Length;
2120 InsertLineString (line, pos, s.Substring (0, break_index));
2121 break_index++;
2123 while (break_index < s.Length) {
2124 bool soft = false;
2125 int next_break = s.IndexOf ('\n', break_index);
2126 int adjusted_next_break;
2127 bool carriage_return = false;
2129 if (next_break == -1) {
2130 next_break = s.Length;
2131 soft = true;
2134 adjusted_next_break = next_break;
2135 if (s [next_break - 1] == '\r') {
2136 adjusted_next_break--;
2137 carriage_return = true;
2140 string line_text = s.Substring (break_index, adjusted_next_break - break_index);
2141 Add (base_line + count, line_text, line.alignment, tag.font, tag.color);
2143 if (carriage_return) {
2144 Line last = GetLine (base_line + count);
2145 last.carriage_return = true;
2147 if (soft)
2148 last.soft_break = true;
2149 } else if (soft) {
2150 Line last = GetLine (base_line + count);
2151 last.soft_break = true;
2154 count++;
2155 break_index = next_break + 1;
2158 NoRecalc = false;
2160 UpdateView(line, lines - old_line_count + 1, pos);
2162 if (update_caret) {
2163 // Move caret to the end of the inserted text
2164 Line l = GetLine (line.line_no + lines - old_line_count);
2165 PositionCaret(l, l.text.Length);
2166 DisplayCaret ();
2169 undo.EndCompoundAction ();
2172 // Inserts a character at the given position
2173 internal void InsertString(Line line, int pos, string s) {
2174 InsertString(line.FindTag(pos), pos, s);
2177 // Inserts a string at the given position
2178 internal void InsertString(LineTag tag, int pos, string s) {
2179 Line line;
2180 int len;
2182 len = s.Length;
2184 CharCount += len;
2186 line = tag.line;
2187 line.text.Insert(pos, s);
2188 tag.length += len;
2190 // TODO: sometimes getting a null tag here when pasting ???
2191 tag = tag.next;
2192 while (tag != null) {
2193 tag.start += len;
2194 tag = tag.next;
2196 line.Grow(len);
2197 line.recalc = true;
2199 UpdateView(line, pos);
2202 // Inserts a string at the caret position
2203 internal void InsertStringAtCaret(string s, bool move_caret) {
2204 LineTag tag;
2205 int len;
2207 len = s.Length;
2209 CharCount += len;
2211 caret.line.text.Insert(caret.pos, s);
2212 caret.tag.length += len;
2214 if (caret.tag.next != null) {
2215 tag = caret.tag.next;
2216 while (tag != null) {
2217 tag.start += len;
2218 tag = tag.next;
2221 caret.line.Grow(len);
2222 caret.line.recalc = true;
2224 UpdateView(caret.line, caret.pos);
2225 if (move_caret) {
2226 caret.pos += len;
2227 UpdateCaret();
2233 // Inserts a character at the given position
2234 internal void InsertChar(Line line, int pos, char ch) {
2235 InsertChar(line.FindTag(pos), pos, ch);
2238 // Inserts a character at the given position
2239 internal void InsertChar(LineTag tag, int pos, char ch) {
2240 Line line;
2242 CharCount++;
2244 line = tag.line;
2245 line.text.Insert(pos, ch);
2246 tag.length++;
2248 tag = tag.next;
2249 while (tag != null) {
2250 tag.start++;
2251 tag = tag.next;
2253 line.Grow(1);
2254 line.recalc = true;
2256 UpdateView(line, pos);
2259 // Inserts a character at the current caret position
2260 internal void InsertCharAtCaret(char ch, bool move_caret) {
2261 LineTag tag;
2263 CharCount++;
2265 caret.line.text.Insert(caret.pos, ch);
2266 caret.tag.length++;
2268 if (caret.tag.next != null) {
2269 tag = caret.tag.next;
2270 while (tag != null) {
2271 tag.start++;
2272 tag = tag.next;
2275 caret.line.Grow(1);
2276 caret.line.recalc = true;
2278 UpdateView(caret.line, caret.pos);
2279 if (move_caret) {
2280 caret.pos++;
2281 UpdateCaret();
2282 SetSelectionToCaret(true);
2286 internal void DeleteMultiline (Line start_line, int pos, int length)
2288 Marker start = new Marker ();
2289 Marker end = new Marker ();
2290 int start_index = LineTagToCharIndex (start_line, pos);
2292 start.line = start_line;
2293 start.pos = pos;
2294 start.tag = LineTag.FindTag (start_line, pos);
2296 CharIndexToLineTag (start_index + length, out end.line,
2297 out end.tag, out end.pos);
2299 if (start.line == end.line) {
2300 DeleteChars (start.tag, pos, end.pos - pos);
2301 } else {
2303 // Delete first and last lines
2304 DeleteChars (start.tag, start.pos, start.line.text.Length - start.pos);
2305 DeleteChars (end.line.tags, 0, end.pos);
2307 int current = start.line.line_no + 1;
2308 if (current < end.line.line_no) {
2309 for (int i = end.line.line_no - 1; i >= current; i--) {
2310 Delete (i);
2314 // BIG FAT WARNING - selection_end.line might be stale due
2315 // to the above Delete() call. DONT USE IT before hitting the end of this method!
2317 // Join start and end
2318 Combine (start.line.line_no, current);
2323 // Deletes n characters at the given position; it will not delete past line limits
2324 // pos is 0-based
2325 internal void DeleteChars(LineTag tag, int pos, int count) {
2326 Line line;
2327 bool streamline;
2329 streamline = false;
2330 line = tag.line;
2332 CharCount -= count;
2334 if (pos == line.text.Length) {
2335 return;
2338 line.text.Remove(pos, count);
2340 // Make sure the tag points to the right spot
2341 while ((tag != null) && (tag.start + tag.length - 1) <= pos) {
2342 tag = tag.next;
2345 if (tag == null) {
2346 return;
2349 // Check if we're crossing tag boundaries
2350 if ((pos + count) > (tag.start + tag.length - 1)) {
2351 int left;
2353 // We have to delete cross tag boundaries
2354 streamline = true;
2355 left = count;
2357 left -= tag.start + tag.length - pos - 1;
2358 tag.length -= tag.start + tag.length - pos - 1;
2360 tag = tag.next;
2361 while ((tag != null) && (left > 0)) {
2362 tag.start -= count - left;
2363 if (tag.length > left) {
2364 tag.length -= left;
2365 left = 0;
2366 } else {
2367 left -= tag.length;
2368 tag.length = 0;
2370 tag = tag.next;
2373 } else {
2374 // We got off easy, same tag
2376 tag.length -= count;
2378 if (tag.length == 0) {
2379 streamline = true;
2383 // Delete empty orphaned tags at the end
2384 LineTag walk = tag;
2385 while (walk != null && walk.next != null && walk.next.length == 0) {
2386 LineTag t = walk;
2387 walk.next = walk.next.next;
2388 if (walk.next != null)
2389 walk.next.previous = t;
2390 walk = walk.next;
2393 // Adjust the start point of any tags following
2394 if (tag != null) {
2395 tag = tag.next;
2396 while (tag != null) {
2397 tag.start -= count;
2398 tag = tag.next;
2402 line.recalc = true;
2403 if (streamline) {
2404 line.Streamline(lines);
2407 UpdateView(line, pos);
2410 // Deletes a character at or after the given position (depending on forward); it will not delete past line limits
2411 internal void DeleteChar(LineTag tag, int pos, bool forward) {
2412 Line line;
2413 bool streamline;
2415 CharCount--;
2417 streamline = false;
2418 line = tag.line;
2420 if ((pos == 0 && forward == false) || (pos == line.text.Length && forward == true)) {
2421 return;
2425 if (forward) {
2426 line.text.Remove(pos, 1);
2428 while ((tag != null) && (tag.start + tag.length - 1) <= pos) {
2429 tag = tag.next;
2432 if (tag == null) {
2433 return;
2436 tag.length--;
2438 if (tag.length == 0) {
2439 streamline = true;
2441 } else {
2442 pos--;
2443 line.text.Remove(pos, 1);
2444 if (pos >= (tag.start - 1)) {
2445 tag.length--;
2446 if (tag.length == 0) {
2447 streamline = true;
2449 } else if (tag.previous != null) {
2450 tag.previous.length--;
2451 if (tag.previous.length == 0) {
2452 streamline = true;
2457 // Delete empty orphaned tags at the end
2458 LineTag walk = tag;
2459 while (walk != null && walk.next != null && walk.next.length == 0) {
2460 LineTag t = walk;
2461 walk.next = walk.next.next;
2462 if (walk.next != null)
2463 walk.next.previous = t;
2464 walk = walk.next;
2467 tag = tag.next;
2468 while (tag != null) {
2469 tag.start--;
2470 tag = tag.next;
2472 line.recalc = true;
2473 if (streamline) {
2474 line.Streamline(lines);
2477 UpdateView(line, pos);
2480 // Combine two lines
2481 internal void Combine(int FirstLine, int SecondLine) {
2482 Combine(GetLine(FirstLine), GetLine(SecondLine));
2485 internal void Combine(Line first, Line second) {
2486 LineTag last;
2487 int shift;
2489 // Combine the two tag chains into one
2490 last = first.tags;
2492 // Maintain the line ending style
2493 first.soft_break = second.soft_break;
2495 while (last.next != null) {
2496 last = last.next;
2499 last.next = second.tags;
2500 last.next.previous = last;
2502 shift = last.start + last.length - 1;
2504 // Fix up references within the chain
2505 last = last.next;
2506 while (last != null) {
2507 last.line = first;
2508 last.start += shift;
2509 last = last.next;
2512 // Combine both lines' strings
2513 first.text.Insert(first.text.Length, second.text.ToString());
2514 first.Grow(first.text.Length);
2516 // Remove the reference to our (now combined) tags from the doomed line
2517 second.tags = null;
2519 // Renumber lines
2520 DecrementLines(first.line_no + 2); // first.line_no + 1 will be deleted, so we need to start renumbering one later
2522 // Mop up
2523 first.recalc = true;
2524 first.height = 0; // This forces RecalcDocument/UpdateView to redraw from this line on
2525 first.Streamline(lines);
2527 // Update Caret, Selection, etc
2528 if (caret.line == second) {
2529 caret.Combine(first, shift);
2531 if (selection_anchor.line == second) {
2532 selection_anchor.Combine(first, shift);
2534 if (selection_start.line == second) {
2535 selection_start.Combine(first, shift);
2537 if (selection_end.line == second) {
2538 selection_end.Combine(first, shift);
2541 #if Debug
2542 Line check_first;
2543 Line check_second;
2545 check_first = GetLine(first.line_no);
2546 check_second = GetLine(check_first.line_no + 1);
2548 Console.WriteLine("Pre-delete: Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2549 #endif
2551 this.Delete(second);
2553 #if Debug
2554 check_first = GetLine(first.line_no);
2555 check_second = GetLine(check_first.line_no + 1);
2557 Console.WriteLine("Post-delete Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2558 #endif
2561 // Split the line at the position into two
2562 internal void Split(int LineNo, int pos) {
2563 Line line;
2564 LineTag tag;
2566 line = GetLine(LineNo);
2567 tag = LineTag.FindTag(line, pos);
2568 Split(line, tag, pos, false);
2571 internal void Split(Line line, int pos) {
2572 LineTag tag;
2574 tag = LineTag.FindTag(line, pos);
2575 Split(line, tag, pos, false);
2578 ///<summary>Split line at given tag and position into two lines</summary>
2579 ///<param name="soft">True if the split should be marked as 'soft', indicating that it can be contracted
2580 ///if more space becomes available on previous line</param>
2581 internal void Split(Line line, LineTag tag, int pos, bool soft) {
2582 LineTag new_tag;
2583 Line new_line;
2584 bool move_caret;
2585 bool move_sel_start;
2586 bool move_sel_end;
2588 move_caret = false;
2589 move_sel_start = false;
2590 move_sel_end = false;
2592 // Adjust selection and cursors
2593 if (soft && (caret.line == line) && (caret.pos >= pos)) {
2594 move_caret = true;
2596 if (selection_start.line == line && selection_start.pos > pos) {
2597 move_sel_start = true;
2600 if (selection_end.line == line && selection_end.pos > pos) {
2601 move_sel_end = true;
2604 // cover the easy case first
2605 if (pos == line.text.Length) {
2606 Add(line.line_no + 1, "", line.alignment, tag.font, tag.color);
2608 new_line = GetLine(line.line_no + 1);
2610 line.carriage_return = false;
2611 new_line.carriage_return = line.carriage_return;
2613 if (soft) {
2614 if (move_caret) {
2615 caret.line = new_line;
2616 caret.line.soft_break = true;
2617 caret.tag = new_line.tags;
2618 caret.pos = 0;
2619 } else {
2620 new_line.soft_break = true;
2624 if (move_sel_start) {
2625 selection_start.line = new_line;
2626 selection_start.pos = 0;
2627 selection_start.tag = new_line.tags;
2630 if (move_sel_end) {
2631 selection_end.line = new_line;
2632 selection_end.pos = 0;
2633 selection_end.tag = new_line.tags;
2635 return;
2638 // We need to move the rest of the text into the new line
2639 Add(line.line_no + 1, line.text.ToString(pos, line.text.Length - pos), line.alignment, tag.font, tag.color);
2641 // Now transfer our tags from this line to the next
2642 new_line = GetLine(line.line_no + 1);
2644 line.carriage_return = false;
2645 new_line.carriage_return = line.carriage_return;
2647 line.recalc = true;
2648 new_line.recalc = true;
2650 if ((tag.start - 1) == pos) {
2651 int shift;
2653 // We can simply break the chain and move the tag into the next line
2654 if (tag == line.tags) {
2655 new_tag = new LineTag(line, 1, 0);
2656 new_tag.font = tag.font;
2657 new_tag.color = tag.color;
2658 line.tags = new_tag;
2661 if (tag.previous != null) {
2662 tag.previous.next = null;
2664 new_line.tags = tag;
2665 tag.previous = null;
2666 tag.line = new_line;
2668 // Walk the list and correct the start location of the tags we just bumped into the next line
2669 shift = tag.start - 1;
2671 new_tag = tag;
2672 while (new_tag != null) {
2673 new_tag.start -= shift;
2674 new_tag.line = new_line;
2675 new_tag = new_tag.next;
2677 } else {
2678 int shift;
2680 new_tag = new LineTag(new_line, 1, tag.start - 1 + tag.length - pos);
2681 new_tag.next = tag.next;
2682 new_tag.font = tag.font;
2683 new_tag.color = tag.color;
2684 new_line.tags = new_tag;
2685 if (new_tag.next != null) {
2686 new_tag.next.previous = new_tag;
2688 tag.next = null;
2689 tag.length = pos - tag.start + 1;
2691 shift = pos;
2692 new_tag = new_tag.next;
2693 while (new_tag != null) {
2694 new_tag.start -= shift;
2695 new_tag.line = new_line;
2696 new_tag = new_tag.next;
2701 if (soft) {
2702 if (move_caret) {
2703 caret.line = new_line;
2704 caret.pos = caret.pos - pos;
2705 caret.tag = caret.line.FindTag(caret.pos);
2707 new_line.soft_break = true;
2710 if (move_sel_start) {
2711 selection_start.line = new_line;
2712 selection_start.pos = selection_start.pos - pos;
2713 selection_start.tag = new_line.FindTag(selection_start.pos);
2716 if (move_sel_end) {
2717 selection_end.line = new_line;
2718 selection_end.pos = selection_end.pos - pos;
2719 selection_end.tag = new_line.FindTag(selection_end.pos);
2722 CharCount -= line.text.Length - pos;
2723 line.text.Remove(pos, line.text.Length - pos);
2726 // Adds a line of text, with given font.
2727 // Bumps any line at that line number that already exists down
2728 internal void Add(int LineNo, string Text, Font font, Brush color) {
2729 Add(LineNo, Text, HorizontalAlignment.Left, font, color);
2732 internal void Add(int LineNo, string Text, HorizontalAlignment align, Font font, Brush color) {
2733 Line add;
2734 Line line;
2735 int line_no;
2737 CharCount += Text.Length;
2739 if (LineNo<1 || Text == null) {
2740 if (LineNo<1) {
2741 throw new ArgumentNullException("LineNo", "Line numbers must be positive");
2742 } else {
2743 throw new ArgumentNullException("Text", "Cannot insert NULL line");
2747 add = new Line(LineNo, Text, align, font, color);
2749 line = document;
2750 while (line != sentinel) {
2751 add.parent = line;
2752 line_no = line.line_no;
2754 if (LineNo > line_no) {
2755 line = line.right;
2756 } else if (LineNo < line_no) {
2757 line = line.left;
2758 } else {
2759 // Bump existing line numbers; walk all nodes to the right of this one and increment line_no
2760 IncrementLines(line.line_no);
2761 line = line.left;
2765 add.left = sentinel;
2766 add.right = sentinel;
2768 if (add.parent != null) {
2769 if (LineNo > add.parent.line_no) {
2770 add.parent.right = add;
2771 } else {
2772 add.parent.left = add;
2774 } else {
2775 // Root node
2776 document = add;
2779 RebalanceAfterAdd(add);
2781 lines++;
2784 internal virtual void Clear() {
2785 lines = 0;
2786 CharCount = 0;
2787 document = sentinel;
2790 public virtual object Clone() {
2791 Document clone;
2793 clone = new Document(null);
2795 clone.lines = this.lines;
2796 clone.document = (Line)document.Clone();
2798 return clone;
2801 internal void Delete(int LineNo) {
2802 Line line;
2804 if (LineNo>lines) {
2805 return;
2808 line = GetLine(LineNo);
2810 CharCount -= line.text.Length;
2812 DecrementLines(LineNo + 1);
2813 Delete(line);
2816 internal void Delete(Line line1) {
2817 Line line2;// = new Line();
2818 Line line3;
2820 if ((line1.left == sentinel) || (line1.right == sentinel)) {
2821 line3 = line1;
2822 } else {
2823 line3 = line1.right;
2824 while (line3.left != sentinel) {
2825 line3 = line3.left;
2829 if (line3.left != sentinel) {
2830 line2 = line3.left;
2831 } else {
2832 line2 = line3.right;
2835 line2.parent = line3.parent;
2836 if (line3.parent != null) {
2837 if(line3 == line3.parent.left) {
2838 line3.parent.left = line2;
2839 } else {
2840 line3.parent.right = line2;
2842 } else {
2843 document = line2;
2846 if (line3 != line1) {
2847 LineTag tag;
2849 if (selection_start.line == line3) {
2850 selection_start.line = line1;
2853 if (selection_end.line == line3) {
2854 selection_end.line = line1;
2857 if (selection_anchor.line == line3) {
2858 selection_anchor.line = line1;
2861 if (caret.line == line3) {
2862 caret.line = line1;
2866 line1.alignment = line3.alignment;
2867 line1.ascent = line3.ascent;
2868 line1.hanging_indent = line3.hanging_indent;
2869 line1.height = line3.height;
2870 line1.indent = line3.indent;
2871 line1.line_no = line3.line_no;
2872 line1.recalc = line3.recalc;
2873 line1.right_indent = line3.right_indent;
2874 line1.soft_break = line3.soft_break;
2875 line1.space = line3.space;
2876 line1.tags = line3.tags;
2877 line1.text = line3.text;
2878 line1.widths = line3.widths;
2879 line1.Y = line3.Y;
2881 tag = line1.tags;
2882 while (tag != null) {
2883 tag.line = line1;
2884 tag = tag.next;
2888 if (line3.color == LineColor.Black)
2889 RebalanceAfterDelete(line2);
2891 this.lines--;
2894 // Invalidate a section of the document to trigger redraw
2895 internal void Invalidate(Line start, int start_pos, Line end, int end_pos) {
2896 Line l1;
2897 Line l2;
2898 int p1;
2899 int p2;
2901 if ((start == end) && (start_pos == end_pos)) {
2902 return;
2905 if (end_pos == -1) {
2906 end_pos = end.text.Length;
2909 // figure out what's before what so the logic below is straightforward
2910 if (start.line_no < end.line_no) {
2911 l1 = start;
2912 p1 = start_pos;
2914 l2 = end;
2915 p2 = end_pos;
2916 } else if (start.line_no > end.line_no) {
2917 l1 = end;
2918 p1 = end_pos;
2920 l2 = start;
2921 p2 = start_pos;
2922 } else {
2923 if (start_pos < end_pos) {
2924 l1 = start;
2925 p1 = start_pos;
2927 l2 = end;
2928 p2 = end_pos;
2929 } else {
2930 l1 = end;
2931 p1 = end_pos;
2933 l2 = start;
2934 p2 = start_pos;
2937 #if Debug
2938 Console.WriteLine("Invaliding from {0}:{1} to {2}:{3}", l1.line_no, p1, l2.line_no, p2);
2939 #endif
2941 owner.Invalidate(
2942 new Rectangle(
2943 (int)l1.widths[p1] + l1.align_shift - viewport_x,
2944 l1.Y - viewport_y,
2945 (int)l2.widths[p2] - (int)l1.widths[p1] + 1,
2946 l1.height
2949 return;
2952 #if Debug
2953 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);
2954 #endif
2956 // Three invalidates:
2957 // First line from start
2958 owner.Invalidate(new Rectangle((int)l1.widths[p1] + l1.align_shift - viewport_x, l1.Y - viewport_y, viewport_width, l1.height));
2960 // lines inbetween
2961 if ((l1.line_no + 1) < l2.line_no) {
2962 int y;
2964 y = GetLine(l1.line_no + 1).Y;
2965 owner.Invalidate(new Rectangle(0, y - viewport_y, viewport_width, GetLine(l2.line_no).Y - y - viewport_y));
2967 #if Debug
2968 Console.WriteLine("Invaliding from {0}:{1} to {2}:{3} Middle => x={4}, y={5}, {6}x{7}", l1.line_no, p1, l2.line_no, p2, 0, y - viewport_y, viewport_width, GetLine(l2.line_no).Y - y - viewport_y);
2969 #endif
2972 // Last line to end
2973 owner.Invalidate(new Rectangle((int)l2.widths[0] + l2.align_shift - viewport_x, l2.Y - viewport_y, (int)l2.widths[p2] + 1, l2.height));
2974 #if Debug
2975 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);
2976 #endif
2979 /// <summary>Select text around caret</summary>
2980 internal void ExpandSelection(CaretSelection mode, bool to_caret) {
2981 if (to_caret) {
2982 // We're expanding the selection to the caret position
2983 switch(mode) {
2984 case CaretSelection.Line: {
2985 // Invalidate the selection delta
2986 if (caret > selection_prev) {
2987 Invalidate(selection_prev.line, 0, caret.line, caret.line.text.Length);
2988 } else {
2989 Invalidate(selection_prev.line, selection_prev.line.text.Length, caret.line, 0);
2992 if (caret.line.line_no <= selection_anchor.line.line_no) {
2993 selection_start.line = caret.line;
2994 selection_start.tag = caret.line.tags;
2995 selection_start.pos = 0;
2997 selection_end.line = selection_anchor.line;
2998 selection_end.tag = selection_anchor.tag;
2999 selection_end.pos = selection_anchor.pos;
3001 selection_end_anchor = true;
3002 } else {
3003 selection_start.line = selection_anchor.line;
3004 selection_start.pos = selection_anchor.height;
3005 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height);
3007 selection_end.line = caret.line;
3008 selection_end.tag = caret.line.tags;
3009 selection_end.pos = caret.line.text.Length;
3011 selection_end_anchor = false;
3013 selection_prev.line = caret.line;
3014 selection_prev.tag = caret.tag;
3015 selection_prev.pos = caret.pos;
3017 break;
3020 case CaretSelection.Word: {
3021 int start_pos;
3022 int end_pos;
3024 start_pos = FindWordSeparator(caret.line, caret.pos, false);
3025 end_pos = FindWordSeparator(caret.line, caret.pos, true);
3028 // Invalidate the selection delta
3029 if (caret > selection_prev) {
3030 Invalidate(selection_prev.line, selection_prev.pos, caret.line, end_pos);
3031 } else {
3032 Invalidate(selection_prev.line, selection_prev.pos, caret.line, start_pos);
3034 if (caret < selection_anchor) {
3035 selection_start.line = caret.line;
3036 selection_start.tag = caret.line.FindTag(start_pos);
3037 selection_start.pos = start_pos;
3039 selection_end.line = selection_anchor.line;
3040 selection_end.tag = selection_anchor.tag;
3041 selection_end.pos = selection_anchor.pos;
3043 selection_prev.line = caret.line;
3044 selection_prev.tag = caret.tag;
3045 selection_prev.pos = start_pos;
3047 selection_end_anchor = true;
3048 } else {
3049 selection_start.line = selection_anchor.line;
3050 selection_start.pos = selection_anchor.height;
3051 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height);
3053 selection_end.line = caret.line;
3054 selection_end.tag = caret.line.FindTag(end_pos);
3055 selection_end.pos = end_pos;
3057 selection_prev.line = caret.line;
3058 selection_prev.tag = caret.tag;
3059 selection_prev.pos = end_pos;
3061 selection_end_anchor = false;
3063 break;
3066 case CaretSelection.Position: {
3067 SetSelectionToCaret(false);
3068 return;
3071 } else {
3072 // We're setting the selection 'around' the caret position
3073 switch(mode) {
3074 case CaretSelection.Line: {
3075 this.Invalidate(caret.line, 0, caret.line, caret.line.text.Length);
3077 selection_start.line = caret.line;
3078 selection_start.tag = caret.line.tags;
3079 selection_start.pos = 0;
3081 selection_end.line = caret.line;
3082 selection_end.pos = caret.line.text.Length;
3083 selection_end.tag = caret.line.FindTag(selection_end.pos);
3085 selection_anchor.line = selection_end.line;
3086 selection_anchor.tag = selection_end.tag;
3087 selection_anchor.pos = selection_end.pos;
3088 selection_anchor.height = 0;
3090 selection_prev.line = caret.line;
3091 selection_prev.tag = caret.tag;
3092 selection_prev.pos = caret.pos;
3094 this.selection_end_anchor = true;
3096 break;
3099 case CaretSelection.Word: {
3100 int start_pos;
3101 int end_pos;
3103 start_pos = FindWordSeparator(caret.line, caret.pos, false);
3104 end_pos = FindWordSeparator(caret.line, caret.pos, true);
3106 this.Invalidate(selection_start.line, start_pos, caret.line, end_pos);
3108 selection_start.line = caret.line;
3109 selection_start.tag = caret.line.FindTag(start_pos);
3110 selection_start.pos = start_pos;
3112 selection_end.line = caret.line;
3113 selection_end.tag = caret.line.FindTag(end_pos);
3114 selection_end.pos = end_pos;
3116 selection_anchor.line = selection_end.line;
3117 selection_anchor.tag = selection_end.tag;
3118 selection_anchor.pos = selection_end.pos;
3119 selection_anchor.height = start_pos;
3121 selection_prev.line = caret.line;
3122 selection_prev.tag = caret.tag;
3123 selection_prev.pos = caret.pos;
3125 this.selection_end_anchor = true;
3127 break;
3132 SetSelectionVisible (!(selection_start == selection_end));
3135 internal void SetSelectionToCaret(bool start) {
3136 if (start) {
3137 // Invalidate old selection; selection is being reset to empty
3138 this.Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3140 selection_start.line = caret.line;
3141 selection_start.tag = caret.tag;
3142 selection_start.pos = caret.pos;
3144 // start always also selects end
3145 selection_end.line = caret.line;
3146 selection_end.tag = caret.tag;
3147 selection_end.pos = caret.pos;
3149 selection_anchor.line = caret.line;
3150 selection_anchor.tag = caret.tag;
3151 selection_anchor.pos = caret.pos;
3152 } else {
3153 // Invalidate from previous end to caret (aka new end)
3154 if (selection_end_anchor) {
3155 if (selection_start != caret) {
3156 this.Invalidate(selection_start.line, selection_start.pos, caret.line, caret.pos);
3158 } else {
3159 if (selection_end != caret) {
3160 this.Invalidate(selection_end.line, selection_end.pos, caret.line, caret.pos);
3164 if (caret < selection_anchor) {
3165 selection_start.line = caret.line;
3166 selection_start.tag = caret.tag;
3167 selection_start.pos = caret.pos;
3169 selection_end.line = selection_anchor.line;
3170 selection_end.tag = selection_anchor.tag;
3171 selection_end.pos = selection_anchor.pos;
3173 selection_end_anchor = true;
3174 } else {
3175 selection_start.line = selection_anchor.line;
3176 selection_start.tag = selection_anchor.tag;
3177 selection_start.pos = selection_anchor.pos;
3179 selection_end.line = caret.line;
3180 selection_end.tag = caret.tag;
3181 selection_end.pos = caret.pos;
3183 selection_end_anchor = false;
3187 SetSelectionVisible (!(selection_start == selection_end));
3190 internal void SetSelection(Line start, int start_pos, Line end, int end_pos) {
3191 if (selection_visible) {
3192 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3195 if ((end.line_no < start.line_no) || ((end == start) && (end_pos <= start_pos))) {
3196 selection_start.line = end;
3197 selection_start.tag = LineTag.FindTag(end, end_pos);
3198 selection_start.pos = end_pos;
3200 selection_end.line = start;
3201 selection_end.tag = LineTag.FindTag(start, start_pos);
3202 selection_end.pos = start_pos;
3204 selection_end_anchor = true;
3205 } else {
3206 selection_start.line = start;
3207 selection_start.tag = LineTag.FindTag(start, start_pos);
3208 selection_start.pos = start_pos;
3210 selection_end.line = end;
3211 selection_end.tag = LineTag.FindTag(end, end_pos);
3212 selection_end.pos = end_pos;
3214 selection_end_anchor = false;
3217 selection_anchor.line = start;
3218 selection_anchor.tag = selection_start.tag;
3219 selection_anchor.pos = start_pos;
3221 if (((start == end) && (start_pos == end_pos)) || start == null || end == null) {
3222 SetSelectionVisible (false);
3223 } else {
3224 SetSelectionVisible (true);
3225 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3229 internal void SetSelectionStart(Line start, int start_pos) {
3230 // Invalidate from the previous to the new start pos
3231 Invalidate(selection_start.line, selection_start.pos, start, start_pos);
3233 selection_start.line = start;
3234 selection_start.pos = start_pos;
3235 selection_start.tag = LineTag.FindTag(start, start_pos);
3237 selection_anchor.line = start;
3238 selection_anchor.pos = start_pos;
3239 selection_anchor.tag = selection_start.tag;
3241 selection_end_anchor = false;
3244 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
3245 SetSelectionVisible (true);
3246 } else {
3247 SetSelectionVisible (false);
3250 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3253 internal void SetSelectionStart(int character_index) {
3254 Line line;
3255 LineTag tag;
3256 int pos;
3258 if (character_index < 0) {
3259 return;
3262 CharIndexToLineTag(character_index, out line, out tag, out pos);
3263 SetSelectionStart(line, pos);
3266 internal void SetSelectionEnd(Line end, int end_pos) {
3268 if (end == selection_end.line && end_pos == selection_start.pos) {
3269 selection_anchor.line = selection_start.line;
3270 selection_anchor.tag = selection_start.tag;
3271 selection_anchor.pos = selection_start.pos;
3273 selection_end.line = selection_start.line;
3274 selection_end.tag = selection_start.tag;
3275 selection_end.pos = selection_start.pos;
3277 selection_end_anchor = false;
3278 } else if ((end.line_no < selection_anchor.line.line_no) || ((end == selection_anchor.line) && (end_pos <= selection_anchor.pos))) {
3279 selection_start.line = end;
3280 selection_start.tag = LineTag.FindTag(end, end_pos);
3281 selection_start.pos = end_pos;
3283 selection_end.line = selection_anchor.line;
3284 selection_end.tag = selection_anchor.tag;
3285 selection_end.pos = selection_anchor.pos;
3287 selection_end_anchor = true;
3288 } else {
3289 selection_start.line = selection_anchor.line;
3290 selection_start.tag = selection_anchor.tag;
3291 selection_start.pos = selection_anchor.pos;
3293 selection_end.line = end;
3294 selection_end.tag = LineTag.FindTag(end, end_pos);
3295 selection_end.pos = end_pos;
3297 selection_end_anchor = false;
3300 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
3301 SetSelectionVisible (true);
3302 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3303 } else {
3304 SetSelectionVisible (false);
3305 // ?? Do I need to invalidate here, tests seem to work without it, but I don't think they should :-s
3309 internal void SetSelectionEnd(int character_index) {
3310 Line line;
3311 LineTag tag;
3312 int pos;
3314 if (character_index < 0) {
3315 return;
3318 CharIndexToLineTag(character_index, out line, out tag, out pos);
3319 SetSelectionEnd(line, pos);
3322 internal void SetSelection(Line start, int start_pos) {
3323 if (selection_visible) {
3324 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3327 selection_start.line = start;
3328 selection_start.pos = start_pos;
3329 selection_start.tag = LineTag.FindTag(start, start_pos);
3331 selection_end.line = start;
3332 selection_end.tag = selection_start.tag;
3333 selection_end.pos = start_pos;
3335 selection_anchor.line = start;
3336 selection_anchor.tag = selection_start.tag;
3337 selection_anchor.pos = start_pos;
3339 selection_end_anchor = false;
3340 SetSelectionVisible (false);
3343 internal void InvalidateSelectionArea() {
3344 Invalidate (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3347 // Return the current selection, as string
3348 internal string GetSelection() {
3349 // We return String.Empty if there is no selection
3350 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3351 return string.Empty;
3354 if (!multiline || (selection_start.line == selection_end.line)) {
3355 return selection_start.line.text.ToString(selection_start.pos, selection_end.pos - selection_start.pos);
3356 } else {
3357 StringBuilder sb;
3358 int i;
3359 int start;
3360 int end;
3362 sb = new StringBuilder();
3363 start = selection_start.line.line_no;
3364 end = selection_end.line.line_no;
3366 sb.Append(selection_start.line.text.ToString(selection_start.pos, selection_start.line.text.Length - selection_start.pos) + Environment.NewLine);
3368 if ((start + 1) < end) {
3369 for (i = start + 1; i < end; i++) {
3370 sb.Append(GetLine(i).text.ToString() + Environment.NewLine);
3374 sb.Append(selection_end.line.text.ToString(0, selection_end.pos));
3376 return sb.ToString();
3380 internal void ReplaceSelection(string s, bool select_new) {
3381 int i;
3383 undo.BeginCompoundAction ();
3385 int selection_start_pos = LineTagToCharIndex (selection_start.line, selection_start.pos);
3386 // First, delete any selected text
3387 if ((selection_start.pos != selection_end.pos) || (selection_start.line != selection_end.line)) {
3388 if (!multiline || (selection_start.line == selection_end.line)) {
3389 undo.RecordDeleteChars(selection_start.line, selection_start.pos + 1, selection_end.pos - selection_start.pos);
3391 DeleteChars(selection_start.tag, selection_start.pos, selection_end.pos - selection_start.pos);
3393 // The tag might have been removed, we need to recalc it
3394 selection_start.tag = selection_start.line.FindTag(selection_start.pos);
3395 } else {
3396 int start;
3397 int end;
3399 start = selection_start.line.line_no;
3400 end = selection_end.line.line_no;
3402 undo.RecordDelete(selection_start.line, selection_start.pos + 1, selection_end.line, selection_end.pos);
3404 // Delete first line
3405 DeleteChars(selection_start.tag, selection_start.pos, selection_start.line.text.Length - selection_start.pos);
3407 // Delete last line
3408 DeleteChars(selection_end.line.tags, 0, selection_end.pos);
3410 start++;
3411 if (start < end) {
3412 for (i = end - 1; i >= start; i--) {
3413 Delete(i);
3417 // BIG FAT WARNING - selection_end.line might be stale due
3418 // to the above Delete() call. DONT USE IT before hitting the end of this method!
3420 // Join start and end
3421 Combine(selection_start.line.line_no, start);
3425 Insert(selection_start.line, selection_start.pos, true, s);
3426 undo.RecordInsertString (selection_start.line, selection_start.pos, s);
3428 if (!select_new) {
3429 CharIndexToLineTag(selection_start_pos + s.Length, out selection_start.line,
3430 out selection_start.tag, out selection_start.pos);
3432 selection_end.line = selection_start.line;
3433 selection_end.pos = selection_start.pos;
3434 selection_end.tag = selection_start.tag;
3435 selection_anchor.line = selection_start.line;
3436 selection_anchor.pos = selection_start.pos;
3437 selection_anchor.tag = selection_start.tag;
3439 SetSelectionVisible (false);
3440 } else {
3441 CharIndexToLineTag(selection_start_pos, out selection_start.line,
3442 out selection_start.tag, out selection_start.pos);
3444 CharIndexToLineTag(selection_start_pos + s.Length, out selection_end.line,
3445 out selection_end.tag, out selection_end.pos);
3447 selection_anchor.line = selection_start.line;
3448 selection_anchor.pos = selection_start.pos;
3449 selection_anchor.tag = selection_start.tag;
3451 SetSelectionVisible (true);
3454 undo.EndCompoundAction ();
3457 internal void CharIndexToLineTag(int index, out Line line_out, out LineTag tag_out, out int pos) {
3458 Line line;
3459 LineTag tag;
3460 int i;
3461 int chars;
3462 int start;
3464 chars = 0;
3466 for (i = 1; i <= lines; i++) {
3467 line = GetLine(i);
3469 start = chars;
3470 chars += line.text.Length + crlf_size;
3472 if (index <= chars) {
3473 // we found the line
3474 tag = line.tags;
3476 while (tag != null) {
3477 if (index < (start + tag.start + tag.length)) {
3478 line_out = line;
3479 tag_out = LineTag.GetFinalTag (tag);
3480 pos = index - start;
3481 return;
3483 if (tag.next == null) {
3484 Line next_line;
3486 next_line = GetLine(line.line_no + 1);
3488 if (next_line != null) {
3489 line_out = next_line;
3490 tag_out = LineTag.GetFinalTag (next_line.tags);
3491 pos = 0;
3492 return;
3493 } else {
3494 line_out = line;
3495 tag_out = LineTag.GetFinalTag (tag);
3496 pos = line_out.text.Length;
3497 return;
3500 tag = tag.next;
3505 line_out = GetLine(lines);
3506 tag = line_out.tags;
3507 while (tag.next != null) {
3508 tag = tag.next;
3510 tag_out = tag;
3511 pos = line_out.text.Length;
3514 internal int LineTagToCharIndex(Line line, int pos) {
3515 int i;
3516 int length;
3518 // Count first and last line
3519 length = 0;
3521 // Count the lines in the middle
3523 for (i = 1; i < line.line_no; i++) {
3524 length += GetLine(i).text.Length + crlf_size;
3527 length += pos;
3529 return length;
3532 internal int SelectionLength() {
3533 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3534 return 0;
3537 if (!multiline || (selection_start.line == selection_end.line)) {
3538 return selection_end.pos - selection_start.pos;
3539 } else {
3540 int i;
3541 int start;
3542 int end;
3543 int length;
3545 // Count first and last line
3546 length = selection_start.line.text.Length - selection_start.pos + selection_end.pos + crlf_size;
3548 // Count the lines in the middle
3549 start = selection_start.line.line_no + 1;
3550 end = selection_end.line.line_no;
3552 if (start < end) {
3553 for (i = start; i < end; i++) {
3554 length += GetLine(i).text.Length + crlf_size;
3558 return length;
3565 /// <summary>Give it a Line number and it returns the Line object at with that line number</summary>
3566 internal Line GetLine(int LineNo) {
3567 Line line = document;
3569 while (line != sentinel) {
3570 if (LineNo == line.line_no) {
3571 return line;
3572 } else if (LineNo < line.line_no) {
3573 line = line.left;
3574 } else {
3575 line = line.right;
3579 return null;
3582 /// <summary>Retrieve the previous tag; walks line boundaries</summary>
3583 internal LineTag PreviousTag(LineTag tag) {
3584 Line l;
3586 if (tag.previous != null) {
3587 return tag.previous;
3590 // Next line
3591 if (tag.line.line_no == 1) {
3592 return null;
3595 l = GetLine(tag.line.line_no - 1);
3596 if (l != null) {
3597 LineTag t;
3599 t = l.tags;
3600 while (t.next != null) {
3601 t = t.next;
3603 return t;
3606 return null;
3609 /// <summary>Retrieve the next tag; walks line boundaries</summary>
3610 internal LineTag NextTag(LineTag tag) {
3611 Line l;
3613 if (tag.next != null) {
3614 return tag.next;
3617 // Next line
3618 l = GetLine(tag.line.line_no + 1);
3619 if (l != null) {
3620 return l.tags;
3623 return null;
3626 internal Line ParagraphStart(Line line) {
3627 while (line.soft_break) {
3628 line = GetLine(line.line_no - 1);
3630 return line;
3633 internal Line ParagraphEnd(Line line) {
3634 Line l;
3636 while (line.soft_break) {
3637 l = GetLine(line.line_no + 1);
3638 if ((l == null) || (!l.soft_break)) {
3639 break;
3641 line = l;
3643 return line;
3646 /// <summary>Give it a Y pixel coordinate and it returns the Line covering that Y coordinate</summary>
3647 internal Line GetLineByPixel(int y, bool exact) {
3648 Line line = document;
3649 Line last = null;
3651 while (line != sentinel) {
3652 last = line;
3653 if ((y >= line.Y) && (y < (line.Y+line.height))) {
3654 return line;
3655 } else if (y < line.Y) {
3656 line = line.left;
3657 } else {
3658 line = line.right;
3662 if (exact) {
3663 return null;
3665 return last;
3668 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3669 internal LineTag FindTag(int x, int y, out int index, bool exact) {
3670 Line line;
3671 LineTag tag;
3673 line = GetLineByPixel(y, exact);
3674 if (line == null) {
3675 index = 0;
3676 return null;
3678 tag = line.tags;
3680 // Alignment adjustment
3681 x += line.align_shift;
3683 while (true) {
3684 if (x >= tag.X && x < (tag.X+tag.width)) {
3685 int end;
3687 end = tag.start + tag.length - 1;
3689 for (int pos = tag.start; pos < end; pos++) {
3690 if (x < line.widths[pos]) {
3691 index = pos;
3692 return LineTag.GetFinalTag (tag);
3695 index=end;
3696 return LineTag.GetFinalTag (tag);
3698 if (tag.next != null) {
3699 tag = tag.next;
3700 } else {
3701 if (exact) {
3702 index = 0;
3703 return null;
3706 index = line.text.Length;
3707 return LineTag.GetFinalTag (tag);
3712 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3713 internal LineTag FindCursor(int x, int y, out int index) {
3714 Line line;
3715 LineTag tag;
3717 line = GetLineByPixel(y, false);
3718 tag = line.tags;
3720 // Adjust for alignment
3721 x -= line.align_shift;
3723 while (true) {
3724 if (x >= tag.X && x < (tag.X+tag.width)) {
3725 int end;
3727 end = tag.start + tag.length - 1;
3729 for (int pos = tag.start-1; pos < end; pos++) {
3730 // When clicking on a character, we position the cursor to whatever edge
3731 // of the character the click was closer
3732 if (x < (line.widths[pos] + ((line.widths[pos+1]-line.widths[pos])/2))) {
3733 index = pos;
3734 return tag;
3737 index=end;
3738 return tag;
3740 if (tag.next != null) {
3741 tag = tag.next;
3742 } else {
3743 index = line.text.Length;
3744 return tag;
3749 /// <summary>Format area of document in specified font and color</summary>
3750 /// <param name="start_pos">1-based start position on start_line</param>
3751 /// <param name="end_pos">1-based end position on end_line </param>
3752 internal void FormatText(Line start_line, int start_pos, Line end_line, int end_pos, Font font, Brush color) {
3753 Line l;
3755 // First, format the first line
3756 if (start_line != end_line) {
3757 // First line
3758 LineTag.FormatText(start_line, start_pos, start_line.text.Length - start_pos + 1, font, color);
3760 // Format last line
3761 LineTag.FormatText(end_line, 1, end_pos, font, color);
3763 // Now all the lines inbetween
3764 for (int i = start_line.line_no + 1; i < end_line.line_no; i++) {
3765 l = GetLine(i);
3766 LineTag.FormatText(l, 1, l.text.Length, font, color);
3768 } else {
3769 // Special case, single line
3770 LineTag.FormatText(start_line, start_pos, end_pos - start_pos, font, color);
3774 /// <summary>Re-format areas of the document in specified font and color</summary>
3775 /// <param name="start_pos">1-based start position on start_line</param>
3776 /// <param name="end_pos">1-based end position on end_line </param>
3777 /// <param name="font">Font specifying attributes</param>
3778 /// <param name="color">Color (or NULL) to apply</param>
3779 /// <param name="apply">Attributes from font and color to apply</param>
3780 internal void FormatText(Line start_line, int start_pos, Line end_line, int end_pos, FontDefinition attributes) {
3781 Line l;
3783 // First, format the first line
3784 if (start_line != end_line) {
3785 // First line
3786 LineTag.FormatText(start_line, start_pos, start_line.text.Length - start_pos + 1, attributes);
3788 // Format last line
3789 LineTag.FormatText(end_line, 1, end_pos - 1, attributes);
3791 // Now all the lines inbetween
3792 for (int i = start_line.line_no + 1; i < end_line.line_no; i++) {
3793 l = GetLine(i);
3794 LineTag.FormatText(l, 1, l.text.Length, attributes);
3796 } else {
3797 // Special case, single line
3798 LineTag.FormatText(start_line, start_pos, end_pos - start_pos, attributes);
3802 internal void RecalculateAlignments() {
3803 Line line;
3804 int line_no;
3806 line_no = 1;
3808 while (line_no <= lines) {
3809 line = GetLine(line_no);
3811 if (line != null) {
3812 switch (line.alignment) {
3813 case HorizontalAlignment.Left:
3814 line.align_shift = 0;
3815 break;
3816 case HorizontalAlignment.Center:
3817 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3818 break;
3819 case HorizontalAlignment.Right:
3820 line.align_shift = viewport_width - (int)line.widths[line.text.Length];
3821 break;
3825 line_no++;
3827 return;
3830 /// <summary>Calculate formatting for the whole document</summary>
3831 internal bool RecalculateDocument(Graphics g) {
3832 return RecalculateDocument(g, 1, this.lines, false);
3835 /// <summary>Calculate formatting starting at a certain line</summary>
3836 internal bool RecalculateDocument(Graphics g, int start) {
3837 return RecalculateDocument(g, start, this.lines, false);
3840 /// <summary>Calculate formatting within two given line numbers</summary>
3841 internal bool RecalculateDocument(Graphics g, int start, int end) {
3842 return RecalculateDocument(g, start, end, false);
3845 /// <summary>With optimize on, returns true if line heights changed</summary>
3846 internal bool RecalculateDocument(Graphics g, int start, int end, bool optimize) {
3847 Line line;
3848 int line_no;
3849 int Y;
3850 int new_width;
3851 bool changed;
3852 int shift;
3854 if (no_recalc) {
3855 recalc_pending = true;
3856 recalc_start = start;
3857 recalc_end = end;
3858 recalc_optimize = optimize;
3859 return false;
3862 Y = GetLine(start).Y;
3863 line_no = start;
3864 new_width = 0;
3865 shift = this.lines;
3866 if (!optimize) {
3867 changed = true; // We always return true if we run non-optimized
3868 } else {
3869 changed = false;
3872 while (line_no <= (end + this.lines - shift)) {
3873 line = GetLine(line_no++);
3874 line.Y = Y;
3876 if (!calc_pass) {
3877 if (!optimize) {
3878 line.RecalculateLine(g, this);
3879 } else {
3880 if (line.recalc && line.RecalculateLine(g, this)) {
3881 changed = true;
3882 // If the height changed, all subsequent lines change
3883 end = this.lines;
3884 shift = this.lines;
3887 } else {
3888 if (!optimize) {
3889 line.RecalculatePasswordLine(g, this);
3890 } else {
3891 if (line.recalc && line.RecalculatePasswordLine(g, this)) {
3892 changed = true;
3893 // If the height changed, all subsequent lines change
3894 end = this.lines;
3895 shift = this.lines;
3900 if (line.widths[line.text.Length] > new_width) {
3901 new_width = (int)line.widths[line.text.Length];
3904 // Calculate alignment
3905 if (line.alignment != HorizontalAlignment.Left) {
3906 if (line.alignment == HorizontalAlignment.Center) {
3907 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3908 } else {
3909 line.align_shift = viewport_width - (int)line.widths[line.text.Length] - 1;
3913 Y += line.height;
3915 if (line_no > lines) {
3916 break;
3920 if (document_x != new_width) {
3921 document_x = new_width;
3922 if (WidthChanged != null) {
3923 WidthChanged(this, null);
3927 RecalculateAlignments();
3929 line = GetLine(lines);
3931 if (document_y != line.Y + line.height) {
3932 document_y = line.Y + line.height;
3933 if (HeightChanged != null) {
3934 HeightChanged(this, null);
3937 UpdateCaret();
3938 return changed;
3941 internal int Size() {
3942 return lines;
3945 private void owner_HandleCreated(object sender, EventArgs e) {
3946 RecalculateDocument(owner.CreateGraphicsInternal());
3947 AlignCaret();
3950 private void owner_VisibleChanged(object sender, EventArgs e) {
3951 if (owner.Visible) {
3952 RecalculateDocument(owner.CreateGraphicsInternal());
3956 internal static bool IsWordSeparator(char ch) {
3957 switch(ch) {
3958 case ' ':
3959 case '\t':
3960 case '(':
3961 case ')': {
3962 return true;
3965 default: {
3966 return false;
3970 internal int FindWordSeparator(Line line, int pos, bool forward) {
3971 int len;
3973 len = line.text.Length;
3975 if (forward) {
3976 for (int i = pos + 1; i < len; i++) {
3977 if (IsWordSeparator(line.Text[i])) {
3978 return i + 1;
3981 return len;
3982 } else {
3983 for (int i = pos - 1; i > 0; i--) {
3984 if (IsWordSeparator(line.Text[i - 1])) {
3985 return i;
3988 return 0;
3992 /* Search document for text */
3993 internal bool FindChars(char[] chars, Marker start, Marker end, out Marker result) {
3994 Line line;
3995 int line_no;
3996 int pos;
3997 int line_len;
3999 // Search for occurence of any char in the chars array
4000 result = new Marker();
4002 line = start.line;
4003 line_no = start.line.line_no;
4004 pos = start.pos;
4005 while (line_no <= end.line.line_no) {
4006 line_len = line.text.Length;
4007 while (pos < line_len) {
4008 for (int i = 0; i < chars.Length; i++) {
4009 if (line.text[pos] == chars[i]) {
4010 // Special case
4011 if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
4012 return false;
4015 result.line = line;
4016 result.pos = pos;
4017 return true;
4020 pos++;
4023 pos = 0;
4024 line_no++;
4025 line = GetLine(line_no);
4028 return false;
4031 // This version does not build one big string for searching, instead it handles
4032 // line-boundaries, which is faster and less memory intensive
4033 // FIXME - Depending on culture stuff we might have to create a big string and use culturespecific
4034 // search stuff and change it to accept and return positions instead of Markers (which would match
4035 // RichTextBox behaviour better but would be inconsistent with the rest of TextControl)
4036 internal bool Find(string search, Marker start, Marker end, out Marker result, RichTextBoxFinds options) {
4037 Marker last;
4038 string search_string;
4039 Line line;
4040 int line_no;
4041 int pos;
4042 int line_len;
4043 int current;
4044 bool word;
4045 bool word_option;
4046 bool ignore_case;
4047 bool reverse;
4048 char c;
4050 result = new Marker();
4051 word_option = ((options & RichTextBoxFinds.WholeWord) != 0);
4052 ignore_case = ((options & RichTextBoxFinds.MatchCase) == 0);
4053 reverse = ((options & RichTextBoxFinds.Reverse) != 0);
4055 line = start.line;
4056 line_no = start.line.line_no;
4057 pos = start.pos;
4058 current = 0;
4060 // Prep our search string, lowercasing it if we do case-independent matching
4061 if (ignore_case) {
4062 StringBuilder sb;
4063 sb = new StringBuilder(search);
4064 for (int i = 0; i < sb.Length; i++) {
4065 sb[i] = Char.ToLower(sb[i]);
4067 search_string = sb.ToString();
4068 } else {
4069 search_string = search;
4072 // We need to check if the character before our start position is a wordbreak
4073 if (word_option) {
4074 if (line_no == 1) {
4075 if ((pos == 0) || (IsWordSeparator(line.text[pos - 1]))) {
4076 word = true;
4077 } else {
4078 word = false;
4080 } else {
4081 if (pos > 0) {
4082 if (IsWordSeparator(line.text[pos - 1])) {
4083 word = true;
4084 } else {
4085 word = false;
4087 } else {
4088 // Need to check the end of the previous line
4089 Line prev_line;
4091 prev_line = GetLine(line_no - 1);
4092 if (prev_line.soft_break) {
4093 if (IsWordSeparator(prev_line.text[prev_line.text.Length - 1])) {
4094 word = true;
4095 } else {
4096 word = false;
4098 } else {
4099 word = true;
4103 } else {
4104 word = false;
4107 // To avoid duplication of this loop with reverse logic, we search
4108 // through the document, remembering the last match and when returning
4109 // report that last remembered match
4111 last = new Marker();
4112 last.height = -1; // Abused - we use it to track change
4114 while (line_no <= end.line.line_no) {
4115 if (line_no != end.line.line_no) {
4116 line_len = line.text.Length;
4117 } else {
4118 line_len = end.pos;
4121 while (pos < line_len) {
4122 if (word_option && (current == search_string.Length)) {
4123 if (IsWordSeparator(line.text[pos])) {
4124 if (!reverse) {
4125 goto FindFound;
4126 } else {
4127 last = result;
4128 current = 0;
4130 } else {
4131 current = 0;
4135 if (ignore_case) {
4136 c = Char.ToLower(line.text[pos]);
4137 } else {
4138 c = line.text[pos];
4141 if (c == search_string[current]) {
4142 if (current == 0) {
4143 result.line = line;
4144 result.pos = pos;
4146 if (!word_option || (word_option && (word || (current > 0)))) {
4147 current++;
4150 if (!word_option && (current == search_string.Length)) {
4151 if (!reverse) {
4152 goto FindFound;
4153 } else {
4154 last = result;
4155 current = 0;
4158 } else {
4159 current = 0;
4161 pos++;
4163 if (!word_option) {
4164 continue;
4167 if (IsWordSeparator(c)) {
4168 word = true;
4169 } else {
4170 word = false;
4174 if (word_option) {
4175 // Mark that we just saw a word boundary
4176 if (!line.soft_break) {
4177 word = true;
4180 if (current == search_string.Length) {
4181 if (word) {
4182 if (!reverse) {
4183 goto FindFound;
4184 } else {
4185 last = result;
4186 current = 0;
4188 } else {
4189 current = 0;
4194 pos = 0;
4195 line_no++;
4196 line = GetLine(line_no);
4199 if (reverse) {
4200 if (last.height != -1) {
4201 result = last;
4202 return true;
4206 return false;
4208 FindFound:
4209 if (!reverse) {
4210 // if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
4211 // return false;
4212 // }
4213 return true;
4216 result = last;
4217 return true;
4221 /* Marker stuff */
4222 internal void GetMarker(out Marker mark, bool start) {
4223 mark = new Marker();
4225 if (start) {
4226 mark.line = GetLine(1);
4227 mark.tag = mark.line.tags;
4228 mark.pos = 0;
4229 } else {
4230 mark.line = GetLine(lines);
4231 mark.tag = mark.line.tags;
4232 while (mark.tag.next != null) {
4233 mark.tag = mark.tag.next;
4235 mark.pos = mark.line.text.Length;
4238 #endregion // Internal Methods
4240 #region Events
4241 internal event EventHandler CaretMoved;
4242 internal event EventHandler WidthChanged;
4243 internal event EventHandler HeightChanged;
4244 internal event EventHandler LengthChanged;
4245 #endregion // Events
4247 #region Administrative
4248 public IEnumerator GetEnumerator() {
4249 // FIXME
4250 return null;
4253 public override bool Equals(object obj) {
4254 if (obj == null) {
4255 return false;
4258 if (!(obj is Document)) {
4259 return false;
4262 if (obj == this) {
4263 return true;
4266 if (ToString().Equals(((Document)obj).ToString())) {
4267 return true;
4270 return false;
4273 public override int GetHashCode() {
4274 return document_id;
4277 public override string ToString() {
4278 return "document " + this.document_id;
4280 #endregion // Administrative
4283 internal class LineTag {
4284 #region Local Variables;
4285 // Payload; formatting
4286 internal Font font; // System.Drawing.Font object for this tag
4287 internal Brush color; // System.Drawing.Brush object
4289 // Payload; text
4290 internal int start; // start, in chars; index into Line.text
4291 internal int length; // length, in chars
4292 internal bool r_to_l; // Which way is the font
4294 // Drawing support
4295 internal int height; // Height in pixels of the text this tag describes
4296 internal int X; // X location of the text this tag describes
4297 internal float width; // Width in pixels of the text this tag describes
4298 internal int ascent; // Ascent of the font for this tag
4299 internal int shift; // Shift down for this tag, to stay on baseline
4301 // Administrative
4302 internal Line line; // The line we're on
4303 internal LineTag next; // Next tag on the same line
4304 internal LineTag previous; // Previous tag on the same line
4305 #endregion;
4307 #region Constructors
4308 internal LineTag(Line line, int start, int length) {
4309 this.line = line;
4310 this.start = start;
4311 this.length = length;
4312 this.X = 0;
4313 this.width = 0;
4315 #endregion // Constructors
4317 #region Internal Methods
4318 ///<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>
4319 internal LineTag Break(int pos) {
4320 LineTag new_tag;
4322 // Sanity
4323 if (pos == this.start) {
4324 return this;
4325 } else if (pos >= (start + length)) {
4326 return null;
4329 new_tag = new LineTag(line, pos, start + length - pos);
4330 new_tag.color = color;
4331 new_tag.font = font;
4332 this.length -= new_tag.length;
4333 new_tag.next = this.next;
4334 this.next = new_tag;
4335 new_tag.previous = this;
4336 if (new_tag.next != null) {
4337 new_tag.next.previous = new_tag;
4340 return new_tag;
4343 ///<summary>Create new font and brush from existing font and given new attributes. Returns true if fontheight changes</summary>
4344 internal static bool GenerateTextFormat(Font font_from, Brush color_from, FontDefinition attributes, out Font new_font, out Brush new_color) {
4345 float size;
4346 string face;
4347 FontStyle style;
4348 GraphicsUnit unit;
4350 if (attributes.font_obj == null) {
4351 size = font_from.SizeInPoints;
4352 unit = font_from.Unit;
4353 face = font_from.Name;
4354 style = font_from.Style;
4356 if (attributes.face != null) {
4357 face = attributes.face;
4360 if (attributes.size != 0) {
4361 size = attributes.size;
4364 style |= attributes.add_style;
4365 style &= ~attributes.remove_style;
4367 // Create new font
4368 new_font = new Font(face, size, style, unit);
4369 } else {
4370 new_font = attributes.font_obj;
4373 // Create 'new' color brush
4374 if (attributes.color != Color.Empty) {
4375 new_color = new SolidBrush(attributes.color);
4376 } else {
4377 new_color = color_from;
4380 if (new_font.Height == font_from.Height) {
4381 return false;
4383 return true;
4386 /// <summary>Applies 'font' and 'brush' to characters starting at 'start' for 'length' chars;
4387 /// Removes any previous tags overlapping the same area;
4388 /// returns true if lineheight has changed</summary>
4389 /// <param name="start">1-based character position on line</param>
4390 internal static bool FormatText(Line line, int start, int length, Font font, Brush color) {
4391 LineTag tag;
4392 LineTag start_tag;
4393 LineTag end_tag;
4394 int end;
4395 bool retval = false; // Assume line-height doesn't change
4397 // Too simple?
4398 if (font.Height != line.height) {
4399 retval = true;
4401 line.recalc = true; // This forces recalculation of the line in RecalculateDocument
4403 // A little sanity, not sure if it's needed, might be able to remove for speed
4404 if (length > line.text.Length) {
4405 length = line.text.Length;
4408 tag = line.tags;
4409 end = start + length;
4411 // Common special case
4412 if ((start == 1) && (length == tag.length)) {
4413 tag.ascent = 0;
4414 tag.font = font;
4415 tag.color = color;
4416 return retval;
4419 //Console.WriteLine("Finding tag for {0} {1}", line, start);
4420 start_tag = FindTag(line, start);
4421 end_tag = FindTag (line, end);
4423 if (start_tag == null) { // FIXME - is there a better way to handle this, or do we even need it?
4424 throw new Exception(String.Format("Could not find start_tag in document at line {0} position {1}", line.line_no, start));
4427 tag = new LineTag(line, start, length);
4428 tag.font = font;
4429 tag.color = color;
4431 if (start == 1) {
4432 line.tags = tag;
4434 //Console.WriteLine("Start tag: '{0}'", start_tag!=null ? start_tag.ToString() : "NULL");
4435 if (start_tag.start == start) {
4436 tag.next = start_tag;
4437 tag.previous = start_tag.previous;
4438 if (start_tag.previous != null) {
4439 start_tag.previous.next = tag;
4441 start_tag.previous = tag;
4442 } else {
4443 tag.next = end_tag;
4445 if (end_tag != null) {
4446 // Shorten up the end tag
4447 end_tag.previous = tag;
4448 end_tag.length = end - start_tag.start + start_tag.length;
4449 end_tag.start = end;
4453 // Elimination loop
4454 tag = tag.next;
4455 while (tag != end_tag) {
4456 if ((tag.start + tag.length) <= end) {
4457 // remove the tag
4458 tag.previous.next = tag.next;
4459 if (tag.next != null) {
4460 tag.next.previous = tag.previous;
4462 tag = tag.previous;
4464 tag = tag.next;
4467 return retval;
4470 /// <summary>Applies font attributes specified to characters starting at 'start' for 'length' chars;
4471 /// Breaks tags at start and end point, keeping middle tags with altered attributes.
4472 /// Returns true if lineheight has changed</summary>
4473 /// <param name="start">1-based character position on line</param>
4474 internal static bool FormatText(Line line, int start, int length, FontDefinition attributes) {
4475 LineTag tag;
4476 LineTag start_tag;
4477 LineTag end_tag;
4478 bool retval = false; // Assume line-height doesn't change
4480 line.recalc = true; // This forces recalculation of the line in RecalculateDocument
4482 // A little sanity, not sure if it's needed, might be able to remove for speed
4483 if (length > line.text.Length) {
4484 length = line.text.Length;
4487 tag = line.tags;
4489 // Common special case
4490 if ((start == 1) && (length == tag.length)) {
4491 tag.ascent = 0;
4492 GenerateTextFormat(tag.font, tag.color, attributes, out tag.font, out tag.color);
4493 return retval;
4496 start_tag = FindTag(line, start);
4498 if (start_tag == null) {
4499 if (length == 0) {
4500 // We are 'starting' after all valid tags; create a new tag with the right attributes
4501 start_tag = FindTag(line, line.text.Length - 1);
4502 start_tag.next = new LineTag(line, line.text.Length + 1, 0);
4503 start_tag.next.font = start_tag.font;
4504 start_tag.next.color = start_tag.color;
4505 start_tag.next.previous = start_tag;
4506 start_tag = start_tag.next;
4507 } else {
4508 throw new Exception(String.Format("Could not find start_tag in document at line {0} position {1}", line.line_no, start));
4510 } else {
4511 start_tag = start_tag.Break(start);
4514 end_tag = FindTag(line, start + length);
4515 if (end_tag != null) {
4516 end_tag = end_tag.Break(start + length);
4519 // start_tag or end_tag might be null; we're cool with that
4520 // we now walk from start_tag to end_tag, applying new attributes
4521 tag = start_tag;
4522 while ((tag != null) && tag != end_tag) {
4523 if (LineTag.GenerateTextFormat(tag.font, tag.color, attributes, out tag.font, out tag.color)) {
4524 retval = true;
4526 tag = tag.next;
4528 return retval;
4532 /// <summary>Finds the tag that describes the character at position 'pos' on 'line'</summary>
4533 internal static LineTag FindTag(Line line, int pos) {
4534 LineTag tag = line.tags;
4536 // Beginning of line is a bit special
4537 if (pos == 0) {
4538 // Not sure if we should get the final tag here
4539 return tag;
4542 while (tag != null) {
4543 if ((tag.start <= pos) && (pos < (tag.start+tag.length))) {
4544 return GetFinalTag (tag);
4547 tag = tag.next;
4550 return null;
4553 // There can be multiple tags at the same position, we want to make
4554 // sure we are using the very last tag at the given position
4555 internal static LineTag GetFinalTag (LineTag tag)
4557 LineTag res = tag;
4559 while (res.next != null && res.next.length == 0)
4560 res = res.next;
4561 return res;
4564 /// <summary>Combines 'this' tag with 'other' tag</summary>
4565 internal bool Combine(LineTag other) {
4566 if (!this.Equals(other)) {
4567 return false;
4570 this.width += other.width;
4571 this.length += other.length;
4572 this.next = other.next;
4573 if (this.next != null) {
4574 this.next.previous = this;
4577 return true;
4581 /// <summary>Remove 'this' tag ; to be called when formatting is to be removed</summary>
4582 internal bool Remove() {
4583 if ((this.start == 1) && (this.next == null)) {
4584 // We cannot remove the only tag
4585 return false;
4587 if (this.start != 1) {
4588 this.previous.length += this.length;
4589 this.previous.width = -1;
4590 this.previous.next = this.next;
4591 this.next.previous = this.previous;
4592 } else {
4593 this.next.start = 1;
4594 this.next.length += this.length;
4595 this.next.width = -1;
4596 this.line.tags = this.next;
4597 this.next.previous = null;
4599 return true;
4603 /// <summary>Checks if 'this' tag describes the same formatting options as 'obj'</summary>
4604 public override bool Equals(object obj) {
4605 LineTag other;
4607 if (obj == null) {
4608 return false;
4611 if (!(obj is LineTag)) {
4612 return false;
4615 if (obj == this) {
4616 return true;
4619 other = (LineTag)obj;
4621 if (this.font.Equals(other.font) && this.color.Equals(other.color)) { // FIXME add checking for things like link or type later
4622 return true;
4625 return false;
4628 public override int GetHashCode() {
4629 return base.GetHashCode ();
4632 public override string ToString() {
4633 if (length > 0)
4634 return "Tag starts at index " + this.start + "length " + this.length + " text: " + this.line.Text.Substring(this.start-1, this.length) + "Font " + this.font.ToString();
4635 return "Zero Lengthed tag at index " + this.start;
4638 #endregion // Internal Methods
4641 internal class UndoClass {
4642 internal enum ActionType {
4643 InsertChar,
4644 InsertString,
4645 DeleteChar,
4646 DeleteChars,
4647 CursorMove,
4648 Mark,
4649 CompoundBegin,
4650 CompoundEnd,
4653 internal class Action {
4654 internal ActionType type;
4655 internal int line_no;
4656 internal int pos;
4657 internal object data;
4660 #region Local Variables
4661 private Document document;
4662 private Stack undo_actions;
4663 private Stack redo_actions;
4665 private int undo_levels;
4666 private int redo_levels;
4667 private int caret_line;
4668 private int caret_pos;
4669 #endregion // Local Variables
4671 #region Constructors
4672 internal UndoClass(Document doc) {
4673 document = doc;
4674 undo_actions = new Stack(50);
4675 redo_actions = new Stack(50);
4677 #endregion // Constructors
4679 #region Properties
4680 internal int UndoLevels {
4681 get {
4682 return undo_levels;
4686 internal int RedoLevels {
4687 get {
4688 return redo_levels;
4692 internal string UndoName {
4693 get {
4694 Action action;
4696 action = (Action)undo_actions.Peek();
4698 if (action.type == ActionType.CompoundEnd)
4699 return (string) action.data;
4701 switch(action.type) {
4702 case ActionType.InsertChar: {
4703 Locale.GetText("Insert character");
4704 break;
4707 case ActionType.DeleteChar: {
4708 Locale.GetText("Delete character");
4709 break;
4712 case ActionType.InsertString: {
4713 Locale.GetText("Insert string");
4714 break;
4717 case ActionType.DeleteChars: {
4718 Locale.GetText("Delete string");
4719 break;
4722 case ActionType.CursorMove: {
4723 Locale.GetText("Cursor move");
4724 break;
4727 return null;
4731 internal string RedoName() {
4732 return null;
4734 #endregion // Properties
4736 #region Internal Methods
4737 internal void Clear() {
4738 undo_actions.Clear();
4739 redo_actions.Clear();
4740 undo_levels = 0;
4741 redo_levels = 0;
4744 internal void Undo() {
4745 Action action;
4746 int compound_stack = 0;
4748 if (undo_actions.Count == 0) {
4749 return;
4754 do {
4755 action = (Action)undo_actions.Pop();
4757 // Put onto redo stack
4758 redo_actions.Push(action);
4760 // Do the thing
4761 switch(action.type) {
4762 case ActionType.CompoundEnd:
4763 compound_stack++;
4764 break;
4766 case ActionType.CompoundBegin:
4767 compound_stack--;
4768 undo_levels--;
4769 redo_levels++;
4770 break;
4772 case ActionType.InsertString:
4773 document.DeleteMultiline (document.GetLine (action.line_no),
4774 action.pos, ((string) action.data).Length + 1);
4775 break;
4777 case ActionType.InsertChar: {
4778 // FIXME - implement me
4779 break;
4782 case ActionType.DeleteChars: {
4783 this.Insert(document.GetLine(action.line_no), action.pos, (Line)action.data);
4784 Undo(); // Grab the cursor location
4785 break;
4788 case ActionType.CursorMove: {
4789 document.caret.line = document.GetLine(action.line_no);
4790 if (document.caret.line == null) {
4791 Undo();
4792 break;
4795 document.caret.tag = document.caret.line.FindTag(action.pos);
4796 document.caret.pos = action.pos;
4797 document.caret.height = document.caret.tag.height;
4799 if (document.owner.IsHandleCreated) {
4800 XplatUI.DestroyCaret(document.owner.Handle);
4801 XplatUI.CreateCaret(document.owner.Handle, 2, document.caret.height);
4802 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);
4804 document.DisplayCaret ();
4807 // FIXME - enable call
4808 //if (document.CaretMoved != null) document.CaretMoved(this, EventArgs.Empty);
4809 break;
4812 } while (compound_stack > 0);
4815 internal void Redo() {
4816 if (redo_actions.Count == 0) {
4817 return;
4820 #endregion // Internal Methods
4822 #region Private Methods
4824 public void BeginCompoundAction ()
4826 Action cb = new Action ();
4827 cb.type = ActionType.CompoundBegin;
4829 undo_actions.Push (cb);
4832 public void EndCompoundAction ()
4834 Action ce = new Action ();
4835 ce.type = ActionType.CompoundEnd;
4837 undo_actions.Push (ce);
4838 undo_levels++;
4841 // pos = 1-based
4842 public void RecordDeleteChars(Line line, int pos, int length) {
4843 RecordDelete(line, pos, line, pos + length - 1);
4846 // start_pos, end_pos = 1 based
4847 public void RecordDelete(Line start_line, int start_pos, Line end_line, int end_pos) {
4848 Line l;
4849 Action a;
4851 l = Duplicate(start_line, start_pos, end_line, end_pos);
4853 a = new Action();
4854 a.type = ActionType.DeleteChars;
4855 a.data = l;
4856 a.line_no = start_line.line_no;
4857 a.pos = start_pos - 1;
4859 // Record the cursor position before, since the actions will occur in reverse order
4860 RecordCursor();
4861 undo_actions.Push(a);
4864 public void RecordInsertString (Line line, int pos, string str)
4866 Action a = new Action ();
4868 a.type = ActionType.InsertString;
4869 a.data = str;
4870 a.line_no = line.line_no;
4871 a.pos = pos;
4873 undo_actions.Push (a);
4876 public void RecordCursor() {
4877 if (document.caret.line == null) {
4878 return;
4881 RecordCursor(document.caret.line, document.caret.pos);
4884 public void RecordCursor(Line line, int pos) {
4885 Action a;
4887 if ((line.line_no == caret_line) && (pos == caret_pos)) {
4888 return;
4891 caret_line = line.line_no;
4892 caret_pos = pos;
4894 a = new Action();
4895 a.type = ActionType.CursorMove;
4896 a.line_no = line.line_no;
4897 a.pos = pos;
4899 undo_actions.Push(a);
4902 // start_pos = 1-based
4903 // end_pos = 1-based
4904 public Line Duplicate(Line start_line, int start_pos, Line end_line, int end_pos) {
4905 Line ret;
4906 Line line;
4907 Line current;
4908 LineTag tag;
4909 LineTag current_tag;
4910 int start;
4911 int end;
4912 int tag_start;
4913 int tag_length;
4915 line = new Line();
4916 ret = line;
4918 for (int i = start_line.line_no; i <= end_line.line_no; i++) {
4919 current = document.GetLine(i);
4921 if (start_line.line_no == i) {
4922 start = start_pos;
4923 } else {
4924 start = 1;
4927 if (end_line.line_no == i) {
4928 end = end_pos;
4929 } else {
4930 end = current.text.Length;
4933 // Text for the tag
4934 line.text = new StringBuilder(current.text.ToString(start - 1, end - start + 1));
4936 // Copy tags from start to start+length onto new line
4937 current_tag = current.FindTag(start - 1);
4938 while ((current_tag != null) && (current_tag.start < end)) {
4939 if ((current_tag.start <= start) && (start < (current_tag.start + current_tag.length))) {
4940 // start tag is within this tag
4941 tag_start = start;
4942 } else {
4943 tag_start = current_tag.start;
4946 if (end < (current_tag.start + current_tag.length)) {
4947 tag_length = end - tag_start + 1;
4948 } else {
4949 tag_length = current_tag.start + current_tag.length - tag_start;
4951 tag = new LineTag(line, tag_start - start + 1, tag_length);
4952 tag.color = current_tag.color;
4953 tag.font = current_tag.font;
4955 current_tag = current_tag.next;
4957 // Add the new tag to the line
4958 if (line.tags == null) {
4959 line.tags = tag;
4960 } else {
4961 LineTag tail;
4962 tail = line.tags;
4964 while (tail.next != null) {
4965 tail = tail.next;
4967 tail.next = tag;
4968 tag.previous = tail;
4972 if ((i + 1) <= end_line.line_no) {
4973 line.soft_break = current.soft_break;
4975 // Chain them (we use right/left as next/previous)
4976 line.right = new Line();
4977 line.right.left = line;
4978 line = line.right;
4982 return ret;
4985 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
4986 internal void Insert(Line line, int pos, Line insert) {
4987 Line current;
4988 LineTag tag;
4989 int offset;
4990 int lines;
4991 Line first;
4993 // Handle special case first
4994 if (insert.right == null) {
4996 // Single line insert
4997 document.Split(line, pos);
4999 if (insert.tags == null) {
5000 return; // Blank line
5003 //Insert our tags at the end
5004 tag = line.tags;
5006 while (tag.next != null) {
5007 tag = tag.next;
5010 offset = tag.start + tag.length - 1;
5012 tag.next = insert.tags;
5013 line.text.Insert(offset, insert.text.ToString());
5015 // Adjust start locations
5016 tag = tag.next;
5017 while (tag != null) {
5018 tag.start += offset;
5019 tag.line = line;
5020 tag = tag.next;
5022 // Put it back together
5023 document.Combine(line.line_no, line.line_no + 1);
5024 document.UpdateView(line, pos);
5025 return;
5028 first = line;
5029 lines = 1;
5030 current = insert;
5031 while (current != null) {
5032 if (current == insert) {
5033 // Inserting the first line we split the line (and make space)
5034 document.Split(line, pos);
5035 //Insert our tags at the end of the line
5036 tag = line.tags;
5038 if (tag != null) {
5039 while (tag.next != null) {
5040 tag = tag.next;
5042 offset = tag.start + tag.length - 1;
5043 tag.next = current.tags;
5044 tag.next.previous = tag;
5046 tag = tag.next;
5048 } else {
5049 offset = 0;
5050 line.tags = current.tags;
5051 line.tags.previous = null;
5052 tag = line.tags;
5054 } else {
5055 document.Split(line.line_no, 0);
5056 offset = 0;
5057 line.tags = current.tags;
5058 line.tags.previous = null;
5059 tag = line.tags;
5061 // Adjust start locations and line pointers
5062 while (tag != null) {
5063 tag.start += offset;
5064 tag.line = line;
5065 tag = tag.next;
5068 line.text.Insert(offset, current.text.ToString());
5069 line.Grow(line.text.Length);
5071 line.recalc = true;
5072 line = document.GetLine(line.line_no + 1);
5074 // FIXME? Test undo of line-boundaries
5075 if ((current.right == null) && (current.tags.length != 0)) {
5076 document.Combine(line.line_no - 1, line.line_no);
5078 current = current.right;
5079 lines++;
5083 // Recalculate our document
5084 document.UpdateView(first, lines, pos);
5085 return;
5087 #endregion // Private Methods