* TextBoxBase.cs: Use the new SuspendRecalc/ResumeRecalc methods
[mono-project.git] / mcs / class / Managed.Windows.Forms / System.Windows.Forms / TextControl.cs
blob6bbc358cc11c903f4beadca762229f68e2596b4e
1 // Permission is hereby granted, free of charge, to any person obtaining
2 // a copy of this software and associated documentation files (the
3 // "Software"), to deal in the Software without restriction, including
4 // without limitation the rights to use, copy, modify, merge, publish,
5 // distribute, sublicense, and/or sell copies of the Software, and to
6 // permit persons to whom the Software is furnished to do so, subject to
7 // the following conditions:
8 //
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
11 //
12 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 // Copyright (c) 2004-2006 Novell, Inc. (http://www.novell.com)
22 // Authors:
23 // Peter Bartok pbartok@novell.com
27 // NOT COMPLETE
29 // There's still plenty of things missing, I've got most of it planned, just hadn't had
30 // the time to write it all yet.
31 // Stuff missing (in no particular order):
32 // - Align text after RecalculateLine
33 // - Implement tag types for hotlinks, images, etc.
34 // - Implement CaretPgUp/PgDown
36 // NOTE:
37 // selection_start.pos and selection_end.pos are 0-based
38 // selection_start.pos = first selected char
39 // selection_end.pos = first NOT-selected char
41 // FormatText methods are 1-based (as are all tags, LineTag.Start is 1 for
42 // the first character on a line; the reason is that 0 is the position
43 // *before* the first character on a line
46 #undef Debug
48 using System;
49 using System.Collections;
50 using System.Drawing;
51 using System.Drawing.Text;
52 using System.Text;
54 namespace System.Windows.Forms {
55 internal enum LineColor {
56 Red = 0,
57 Black = 1
60 internal enum CaretSelection {
61 Position, // Selection=Caret
62 Word, // Selection=Word under caret
63 Line // Selection=Line under caret
66 internal class FontDefinition {
67 internal String face;
68 internal int size;
69 internal FontStyle add_style;
70 internal FontStyle remove_style;
71 internal Color color;
72 internal Font font_obj;
75 [Flags]
76 internal enum FormatSpecified {
77 None,
79 BackColor = 2,
80 Font = 4,
81 Color = 8,
84 internal enum CaretDirection {
85 CharForward, // Move a char to the right
86 CharBack, // Move a char to the left
87 LineUp, // Move a line up
88 LineDown, // Move a line down
89 Home, // Move to the beginning of the line
90 End, // Move to the end of the line
91 PgUp, // Move one page up
92 PgDn, // Move one page down
93 CtrlPgUp, // Move caret to the first visible char in the viewport
94 CtrlPgDn, // Move caret to the last visible char in the viewport
95 CtrlHome, // Move to the beginning of the document
96 CtrlEnd, // Move to the end of the document
97 WordBack, // Move to the beginning of the previous word (or beginning of line)
98 WordForward, // Move to the beginning of the next word (or end of line)
99 SelectionStart, // Move to the beginning of the current selection
100 SelectionEnd, // Move to the end of the current selection
101 CharForwardNoWrap, // Move a char forward, but don't wrap onto the next line
102 CharBackNoWrap // Move a char backward, but don't wrap onto the previous line
105 // Being cloneable should allow for nice line and document copies...
106 internal class Line : ICloneable, IComparable {
107 #region Local Variables
108 // Stuff that matters for our line
109 internal StringBuilder text; // Characters for the line
110 internal float[] widths; // Width of each character; always one larger than text.Length
111 internal int space; // Number of elements in text and widths
112 internal int line_no; // Line number
113 internal LineTag tags; // Tags describing the text
114 internal int Y; // Baseline
115 internal int height; // Height of the line (height of tallest tag)
116 internal int ascent; // Ascent of the line (ascent of the tallest tag)
117 internal HorizontalAlignment alignment; // Alignment of the line
118 internal int align_shift; // Pixel shift caused by the alignment
119 internal bool soft_break; // Tag is 'broken soft' and continuation from previous line
120 internal int indent; // Left indent for the first line
121 internal int hanging_indent; // Hanging indent (left indent for all but the first line)
122 internal int right_indent; // Right indent for all lines
123 internal bool carriage_return;
126 // Stuff that's important for the tree
127 internal Line parent; // Our parent line
128 internal Line left; // Line with smaller line number
129 internal Line right; // Line with higher line number
130 internal LineColor color; // We're doing a black/red tree. this is the node color
131 internal int DEFAULT_TEXT_LEN; //
132 internal bool recalc; // Line changed
133 #endregion // Local Variables
135 #region Constructors
136 internal Line() {
137 color = LineColor.Red;
138 left = null;
139 right = null;
140 parent = null;
141 text = null;
142 recalc = true;
143 soft_break = false;
144 alignment = HorizontalAlignment.Left;
147 internal Line(int LineNo, string Text, Font font, SolidBrush color) : this() {
148 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
150 text = new StringBuilder(Text, space);
151 line_no = LineNo;
153 widths = new float[space + 1];
154 tags = new LineTag(this, 1);
155 tags.font = font;
156 tags.color = color;
159 internal Line(int LineNo, string Text, HorizontalAlignment align, Font font, SolidBrush color) : this() {
160 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
162 text = new StringBuilder(Text, space);
163 line_no = LineNo;
164 alignment = align;
166 widths = new float[space + 1];
167 tags = new LineTag(this, 1);
168 tags.font = font;
169 tags.color = color;
172 internal Line(int LineNo, string Text, LineTag tag) : this() {
173 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
175 text = new StringBuilder(Text, space);
176 line_no = LineNo;
178 widths = new float[space + 1];
179 tags = tag;
182 #endregion // Constructors
184 #region Internal Properties
185 internal int Indent {
186 get {
187 return indent;
190 set {
191 indent = value;
192 recalc = true;
196 internal int HangingIndent {
197 get {
198 return hanging_indent;
201 set {
202 hanging_indent = value;
203 recalc = true;
207 internal int RightIndent {
208 get {
209 return right_indent;
212 set {
213 right_indent = value;
214 recalc = true;
219 internal int Height {
220 get {
221 return height;
224 set {
225 height = value;
229 internal int LineNo {
230 get {
231 return line_no;
234 set {
235 line_no = value;
239 internal string Text {
240 get {
241 return text.ToString();
244 set {
245 text = new StringBuilder(value, value.Length > DEFAULT_TEXT_LEN ? value.Length : DEFAULT_TEXT_LEN);
249 internal HorizontalAlignment Alignment {
250 get {
251 return alignment;
254 set {
255 if (alignment != value) {
256 alignment = value;
257 recalc = true;
261 #if no
262 internal StringBuilder Text {
263 get {
264 return text;
267 set {
268 text = value;
271 #endif
272 #endregion // Internal Properties
274 #region Internal Methods
275 // Make sure we always have enoughs space in text and widths
276 internal void Grow(int minimum) {
277 int length;
278 float[] new_widths;
280 length = text.Length;
282 if ((length + minimum) > space) {
283 // We need to grow; double the size
285 if ((length + minimum) > (space * 2)) {
286 new_widths = new float[length + minimum * 2 + 1];
287 space = length + minimum * 2;
288 } else {
289 new_widths = new float[space * 2 + 1];
290 space *= 2;
292 widths.CopyTo(new_widths, 0);
294 widths = new_widths;
298 internal void Streamline(int lines) {
299 LineTag current;
300 LineTag next;
302 current = this.tags;
303 next = current.next;
305 // Catch what the loop below wont; eliminate 0 length
306 // tags, but only if there are other tags after us
307 while ((current.length == 0) && (next != null)) {
308 tags = next;
309 tags.previous = null;
310 current = next;
311 next = current.next;
314 if (next == null) {
315 return;
318 while (next != null) {
319 // Take out 0 length tags unless it's the last tag in the document
320 if (next.length == 0) {
321 if ((next.next != null) || (line_no != lines)) {
322 current.next = next.next;
323 if (current.next != null) {
324 current.next.previous = current;
326 next = current.next;
327 continue;
330 if (current.Combine(next)) {
331 next = current.next;
332 continue;
335 current = current.next;
336 next = current.next;
340 /// <summary> Find the tag on a line based on the character position, pos is 0-based</summary>
341 internal LineTag FindTag(int pos) {
342 LineTag tag;
344 if (pos == 0) {
345 return tags;
348 tag = this.tags;
350 if (pos >= text.Length) {
351 pos = text.Length - 1;
354 while (tag != null) {
355 if (((tag.start - 1) <= pos) && (pos < (tag.start + tag.length - 1))) {
356 return LineTag.GetFinalTag (tag);
358 tag = tag.next;
360 return null;
363 /// <summary>
364 /// Recalculate a single line using the same char for every character in the line
365 /// </summary>
367 internal bool RecalculatePasswordLine(Graphics g, Document doc) {
368 LineTag tag;
369 int pos;
370 int len;
371 float w;
372 bool ret;
373 int descent;
375 pos = 0;
376 len = this.text.Length;
377 tag = this.tags;
378 ascent = 0;
379 tag.shift = 0;
381 this.recalc = false;
382 widths[0] = indent;
383 tag.X = indent;
385 w = g.MeasureString(doc.password_char, tags.font, 10000, Document.string_format).Width;
387 if (this.height != (int)tag.font.Height) {
388 ret = true;
389 } else {
390 ret = false;
393 this.height = (int)tag.font.Height;
394 tag.height = this.height;
396 XplatUI.GetFontMetrics(g, tag.font, out tag.ascent, out descent);
397 this.ascent = tag.ascent;
399 while (pos < len) {
400 pos++;
401 widths[pos] = widths[pos-1] + w;
404 return ret;
407 /// <summary>
408 /// Go through all tags on a line and recalculate all size-related values;
409 /// returns true if lineheight changed
410 /// </summary>
411 internal bool RecalculateLine(Graphics g, Document doc) {
412 LineTag tag;
413 int pos;
414 int len;
415 SizeF size;
416 float w;
417 int prev_height;
418 bool retval;
419 bool wrapped;
420 Line line;
421 int wrap_pos;
423 pos = 0;
424 len = this.text.Length;
425 tag = this.tags;
426 prev_height = this.height; // For drawing optimization calculations
427 this.height = 0; // Reset line height
428 this.ascent = 0; // Reset the ascent for the line
429 tag.shift = 0;
431 if (this.soft_break) {
432 widths[0] = hanging_indent;
433 } else {
434 widths[0] = indent;
437 this.recalc = false;
438 retval = false;
439 wrapped = false;
441 wrap_pos = 0;
443 while (pos < len) {
445 while (tag.length == 0) { // We should always have tags after a tag.length==0 unless len==0
446 tag.ascent = 0;
447 if (tag.previous != null) {
448 tag.X = tag.previous.X;
449 } else {
450 tag.X = (int)widths[pos];
452 tag = tag.next;
453 tag.shift = 0;
456 size = tag.SizeOfPosition (g, pos);
457 w = size.Width;
459 if (Char.IsWhiteSpace(text[pos])) {
460 wrap_pos = pos + 1;
463 if (doc.wrap) {
464 if ((wrap_pos > 0) && (wrap_pos != len) && (widths[pos] + w) + 5 > (doc.viewport_width - this.right_indent)) {
465 // Make sure to set the last width of the line before wrapping
466 widths [pos + 1] = widths [pos] + w;
468 pos = wrap_pos;
469 len = text.Length;
470 doc.Split(this, tag, pos, this.soft_break);
471 this.soft_break = true;
472 len = this.text.Length;
474 retval = true;
475 wrapped = true;
476 } else if (pos > 1 && (widths[pos] + w) > (doc.viewport_width - this.right_indent)) {
477 // No suitable wrap position was found so break right in the middle of a word
479 // Make sure to set the last width of the line before wrapping
480 widths [pos + 1] = widths [pos] + w;
482 doc.Split(this, tag, pos, this.soft_break);
483 this.soft_break = true;
484 len = this.text.Length;
485 retval = true;
486 wrapped = true;
490 // Contract all soft lines that follow back into our line
491 if (!wrapped) {
492 pos++;
494 widths[pos] = widths[pos-1] + w;
496 if (pos == len) {
497 line = doc.GetLine(this.line_no + 1);
498 if ((line != null) && soft_break) {
499 // Pull the two lines together
500 doc.Combine(this.line_no, this.line_no + 1);
501 len = this.text.Length;
502 retval = true;
507 if (pos == (tag.start-1 + tag.length)) {
508 // We just found the end of our current tag
509 tag.height = tag.MaxHeight ();
511 // Check if we're the tallest on the line (so far)
512 if (tag.height > this.height) {
513 this.height = tag.height; // Yep; make sure the line knows
516 if (tag.ascent == 0) {
517 int descent;
519 XplatUI.GetFontMetrics(g, tag.font, out tag.ascent, out descent);
522 if (tag.ascent > this.ascent) {
523 LineTag t;
525 // We have a tag that has a taller ascent than the line;
526 t = tags;
527 while (t != null && t != tag) {
528 t.shift = tag.ascent - t.ascent;
529 t = t.next;
532 // Save on our line
533 this.ascent = tag.ascent;
534 } else {
535 tag.shift = this.ascent - tag.ascent;
538 // Update our horizontal starting pixel position
539 if (tag.previous == null) {
540 tag.X = (int)widths[0];
541 } else {
542 tag.X = tag.previous.X + (int)tag.previous.width;
545 tag = tag.next;
546 if (tag != null) {
547 tag.shift = 0;
548 wrap_pos = pos;
553 if (this.height == 0) {
554 this.height = tags.font.Height;
555 tag.height = this.height;
558 if (prev_height != this.height) {
559 retval = true;
561 return retval;
563 #endregion // Internal Methods
565 #region Administrative
566 public int CompareTo(object obj) {
567 if (obj == null) {
568 return 1;
571 if (! (obj is Line)) {
572 throw new ArgumentException("Object is not of type Line", "obj");
575 if (line_no < ((Line)obj).line_no) {
576 return -1;
577 } else if (line_no > ((Line)obj).line_no) {
578 return 1;
579 } else {
580 return 0;
584 public object Clone() {
585 Line clone;
587 clone = new Line();
589 clone.text = text;
591 if (left != null) {
592 clone.left = (Line)left.Clone();
595 if (left != null) {
596 clone.left = (Line)left.Clone();
599 return clone;
602 internal object CloneLine() {
603 Line clone;
605 clone = new Line();
607 clone.text = text;
609 return clone;
612 public override bool Equals(object obj) {
613 if (obj == null) {
614 return false;
617 if (!(obj is Line)) {
618 return false;
621 if (obj == this) {
622 return true;
625 if (line_no == ((Line)obj).line_no) {
626 return true;
629 return false;
632 public override int GetHashCode() {
633 return base.GetHashCode ();
636 public override string ToString() {
637 return "Line " + line_no;
640 #endregion // Administrative
643 internal class Document : ICloneable, IEnumerable {
644 #region Structures
645 // FIXME - go through code and check for places where
646 // we do explicit comparisons instead of using the compare overloads
647 internal struct Marker {
648 internal Line line;
649 internal LineTag tag;
650 internal int pos;
651 internal int height;
653 public static bool operator<(Marker lhs, Marker rhs) {
654 if (lhs.line.line_no < rhs.line.line_no) {
655 return true;
658 if (lhs.line.line_no == rhs.line.line_no) {
659 if (lhs.pos < rhs.pos) {
660 return true;
663 return false;
666 public static bool operator>(Marker lhs, Marker rhs) {
667 if (lhs.line.line_no > rhs.line.line_no) {
668 return true;
671 if (lhs.line.line_no == rhs.line.line_no) {
672 if (lhs.pos > rhs.pos) {
673 return true;
676 return false;
679 public static bool operator==(Marker lhs, Marker rhs) {
680 if ((lhs.line.line_no == rhs.line.line_no) && (lhs.pos == rhs.pos)) {
681 return true;
683 return false;
686 public static bool operator!=(Marker lhs, Marker rhs) {
687 if ((lhs.line.line_no != rhs.line.line_no) || (lhs.pos != rhs.pos)) {
688 return true;
690 return false;
693 public void Combine(Line move_to_line, int move_to_line_length) {
694 line = move_to_line;
695 pos += move_to_line_length;
696 tag = LineTag.FindTag(line, pos);
699 // This is for future use, right now Document.Split does it by hand, with some added shortcut logic
700 public void Split(Line move_to_line, int split_at) {
701 line = move_to_line;
702 pos -= split_at;
703 tag = LineTag.FindTag(line, pos);
706 public override bool Equals(object obj) {
707 return this==(Marker)obj;
710 public override int GetHashCode() {
711 return base.GetHashCode ();
714 public override string ToString() {
715 return "Marker Line " + line + ", Position " + pos;
719 #endregion Structures
721 #region Local Variables
722 private Line document;
723 private int lines;
724 private Line sentinel;
725 private int document_id;
726 private Random random = new Random();
727 internal string password_char;
728 private StringBuilder password_cache;
729 private bool calc_pass;
730 private int char_count;
732 // For calculating widths/heights
733 public static readonly StringFormat string_format = new StringFormat (StringFormat.GenericTypographic);
735 private int recalc_suspended;
736 private bool recalc_pending;
737 private int recalc_start = 1; // This starts at one, since lines are 1 based
738 private int recalc_end;
739 private bool recalc_optimize;
741 internal bool multiline;
742 internal bool wrap;
744 internal UndoClass undo;
746 internal Marker caret;
747 internal Marker selection_start;
748 internal Marker selection_end;
749 internal bool selection_visible;
750 internal Marker selection_anchor;
751 internal Marker selection_prev;
752 internal bool selection_end_anchor;
754 internal int viewport_x;
755 internal int viewport_y; // The visible area of the document
756 internal int viewport_width;
757 internal int viewport_height;
759 internal int document_x; // Width of the document
760 internal int document_y; // Height of the document
762 internal Rectangle invalid;
764 internal int crlf_size; // 1 or 2, depending on whether we use \r\n or just \n
766 internal TextBoxBase owner; // Who's owning us?
767 static internal int caret_width = 1;
768 static internal int caret_shift = 1;
769 #endregion // Local Variables
771 #region Constructors
772 internal Document(TextBoxBase owner) {
773 lines = 0;
775 this.owner = owner;
777 multiline = true;
778 password_char = "";
779 calc_pass = false;
780 recalc_pending = false;
782 // Tree related stuff
783 sentinel = new Line();
784 sentinel.color = LineColor.Black;
786 document = sentinel;
788 // We always have a blank line
789 owner.HandleCreated += new EventHandler(owner_HandleCreated);
790 owner.VisibleChanged += new EventHandler(owner_VisibleChanged);
792 Add(1, "", owner.Font, ThemeEngine.Current.ResPool.GetSolidBrush(owner.ForeColor));
793 Line l = GetLine (1);
794 l.soft_break = true;
796 undo = new UndoClass(this);
798 selection_visible = false;
799 selection_start.line = this.document;
800 selection_start.pos = 0;
801 selection_start.tag = selection_start.line.tags;
802 selection_end.line = this.document;
803 selection_end.pos = 0;
804 selection_end.tag = selection_end.line.tags;
805 selection_anchor.line = this.document;
806 selection_anchor.pos = 0;
807 selection_anchor.tag = selection_anchor.line.tags;
808 caret.line = this.document;
809 caret.pos = 0;
810 caret.tag = caret.line.tags;
812 viewport_x = 0;
813 viewport_y = 0;
815 crlf_size = 2;
817 // Default selection is empty
819 document_id = random.Next();
821 string_format.Trimming = StringTrimming.None;
822 string_format.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;
824 #endregion
826 #region Internal Properties
827 internal Line Root {
828 get {
829 return document;
832 set {
833 document = value;
837 internal int Lines {
838 get {
839 return lines;
843 internal Line CaretLine {
844 get {
845 return caret.line;
849 internal int CaretPosition {
850 get {
851 return caret.pos;
855 internal Point Caret {
856 get {
857 return new Point((int)caret.tag.line.widths[caret.pos] + caret.line.align_shift, caret.line.Y);
861 internal LineTag CaretTag {
862 get {
863 return caret.tag;
866 set {
867 caret.tag = value;
871 internal int CRLFSize {
872 get {
873 return crlf_size;
876 set {
877 crlf_size = value;
881 internal string PasswordChar {
882 get {
883 return password_char;
886 set {
887 password_char = value;
888 if ((password_char.Length != 0) && (password_char[0] != '\0')) {
889 char ch;
891 calc_pass = true;
892 ch = value[0];
893 password_cache = new StringBuilder(1024);
894 for (int i = 0; i < 1024; i++) {
895 password_cache.Append(ch);
897 } else {
898 calc_pass = false;
899 password_cache = null;
904 internal int ViewPortX {
905 get {
906 return viewport_x;
909 set {
910 viewport_x = value;
914 internal int Length {
915 get {
916 return char_count + lines - 1; // Add \n for each line but the last
920 private int CharCount {
921 get {
922 return char_count;
925 set {
926 char_count = value;
928 if (LengthChanged != null) {
929 LengthChanged(this, EventArgs.Empty);
934 internal int ViewPortY {
935 get {
936 return viewport_y;
939 set {
940 viewport_y = value;
944 internal int ViewPortWidth {
945 get {
946 return viewport_width;
949 set {
950 viewport_width = value;
954 internal int ViewPortHeight {
955 get {
956 return viewport_height;
959 set {
960 viewport_height = value;
965 internal int Width {
966 get {
967 return this.document_x;
971 internal int Height {
972 get {
973 return this.document_y;
977 internal bool SelectionVisible {
978 get {
979 return selection_visible;
983 internal bool Wrap {
984 get {
985 return wrap;
988 set {
989 wrap = value;
993 #endregion // Internal Properties
995 #region Private Methods
997 internal void SuspendRecalc ()
999 recalc_suspended++;
1002 internal void ResumeRecalc (bool immediate_update)
1004 if (recalc_suspended > 0)
1005 recalc_suspended--;
1007 if (immediate_update && recalc_suspended == 0 && recalc_pending) {
1008 RecalculateDocument (owner.CreateGraphicsInternal(), recalc_start, recalc_end, recalc_optimize);
1009 recalc_pending = false;
1013 // For debugging
1014 internal int DumpTree(Line line, bool with_tags) {
1015 int total;
1017 total = 1;
1019 Console.Write("Line {0} [# {1}], Y: {2}, soft: {3}, Text: '{4}'",
1020 line.line_no, line.GetHashCode(), line.Y, line.soft_break,
1021 line.text != null ? line.text.ToString() : "undefined");
1023 if (line.left == sentinel) {
1024 Console.Write(", left = sentinel");
1025 } else if (line.left == null) {
1026 Console.Write(", left = NULL");
1029 if (line.right == sentinel) {
1030 Console.Write(", right = sentinel");
1031 } else if (line.right == null) {
1032 Console.Write(", right = NULL");
1035 Console.WriteLine("");
1037 if (with_tags) {
1038 LineTag tag;
1039 int count;
1040 int length;
1042 tag = line.tags;
1043 count = 1;
1044 length = 0;
1045 Console.Write(" Tags: ");
1046 while (tag != null) {
1047 Console.Write("{0} <{1}>-<{2}>", count++, tag.start, tag.end
1048 /*line.text.ToString (tag.start - 1, tag.length)*/);
1049 length += tag.length;
1051 if (tag.line != line) {
1052 Console.Write("BAD line link");
1053 throw new Exception("Bad line link in tree");
1055 tag = tag.next;
1056 if (tag != null) {
1057 Console.Write(", ");
1060 if (length > line.text.Length) {
1061 throw new Exception(String.Format("Length of tags more than length of text on line (expected {0} calculated {1})", line.text.Length, length));
1062 } else if (length < line.text.Length) {
1063 throw new Exception(String.Format("Length of tags less than length of text on line (expected {0} calculated {1})", line.text.Length, length));
1065 Console.WriteLine("");
1067 if (line.left != null) {
1068 if (line.left != sentinel) {
1069 total += DumpTree(line.left, with_tags);
1071 } else {
1072 if (line != sentinel) {
1073 throw new Exception("Left should not be NULL");
1077 if (line.right != null) {
1078 if (line.right != sentinel) {
1079 total += DumpTree(line.right, with_tags);
1081 } else {
1082 if (line != sentinel) {
1083 throw new Exception("Right should not be NULL");
1087 for (int i = 1; i <= this.lines; i++) {
1088 if (GetLine(i) == null) {
1089 throw new Exception(String.Format("Hole in line order, missing {0}", i));
1093 if (line == this.Root) {
1094 if (total < this.lines) {
1095 throw new Exception(String.Format("Not enough nodes in tree, found {0}, expected {1}", total, this.lines));
1096 } else if (total > this.lines) {
1097 throw new Exception(String.Format("Too many nodes in tree, found {0}, expected {1}", total, this.lines));
1101 return total;
1104 private void SetSelectionVisible (bool value)
1106 selection_visible = value;
1108 // cursor and selection are enemies, we can't have both in the same room at the same time
1109 if (owner.IsHandleCreated && !owner.show_caret_w_selection)
1110 XplatUI.CaretVisible (owner.Handle, !selection_visible);
1113 private void DecrementLines(int line_no) {
1114 int current;
1116 current = line_no;
1117 while (current <= lines) {
1118 GetLine(current).line_no--;
1119 current++;
1121 return;
1124 private void IncrementLines(int line_no) {
1125 int current;
1127 current = this.lines;
1128 while (current >= line_no) {
1129 GetLine(current).line_no++;
1130 current--;
1132 return;
1135 private void RebalanceAfterAdd(Line line1) {
1136 Line line2;
1138 while ((line1 != document) && (line1.parent.color == LineColor.Red)) {
1139 if (line1.parent == line1.parent.parent.left) {
1140 line2 = line1.parent.parent.right;
1142 if ((line2 != null) && (line2.color == LineColor.Red)) {
1143 line1.parent.color = LineColor.Black;
1144 line2.color = LineColor.Black;
1145 line1.parent.parent.color = LineColor.Red;
1146 line1 = line1.parent.parent;
1147 } else {
1148 if (line1 == line1.parent.right) {
1149 line1 = line1.parent;
1150 RotateLeft(line1);
1153 line1.parent.color = LineColor.Black;
1154 line1.parent.parent.color = LineColor.Red;
1156 RotateRight(line1.parent.parent);
1158 } else {
1159 line2 = line1.parent.parent.left;
1161 if ((line2 != null) && (line2.color == LineColor.Red)) {
1162 line1.parent.color = LineColor.Black;
1163 line2.color = LineColor.Black;
1164 line1.parent.parent.color = LineColor.Red;
1165 line1 = line1.parent.parent;
1166 } else {
1167 if (line1 == line1.parent.left) {
1168 line1 = line1.parent;
1169 RotateRight(line1);
1172 line1.parent.color = LineColor.Black;
1173 line1.parent.parent.color = LineColor.Red;
1174 RotateLeft(line1.parent.parent);
1178 document.color = LineColor.Black;
1181 private void RebalanceAfterDelete(Line line1) {
1182 Line line2;
1184 while ((line1 != document) && (line1.color == LineColor.Black)) {
1185 if (line1 == line1.parent.left) {
1186 line2 = line1.parent.right;
1187 if (line2.color == LineColor.Red) {
1188 line2.color = LineColor.Black;
1189 line1.parent.color = LineColor.Red;
1190 RotateLeft(line1.parent);
1191 line2 = line1.parent.right;
1193 if ((line2.left.color == LineColor.Black) && (line2.right.color == LineColor.Black)) {
1194 line2.color = LineColor.Red;
1195 line1 = line1.parent;
1196 } else {
1197 if (line2.right.color == LineColor.Black) {
1198 line2.left.color = LineColor.Black;
1199 line2.color = LineColor.Red;
1200 RotateRight(line2);
1201 line2 = line1.parent.right;
1203 line2.color = line1.parent.color;
1204 line1.parent.color = LineColor.Black;
1205 line2.right.color = LineColor.Black;
1206 RotateLeft(line1.parent);
1207 line1 = document;
1209 } else {
1210 line2 = line1.parent.left;
1211 if (line2.color == LineColor.Red) {
1212 line2.color = LineColor.Black;
1213 line1.parent.color = LineColor.Red;
1214 RotateRight(line1.parent);
1215 line2 = line1.parent.left;
1217 if ((line2.right.color == LineColor.Black) && (line2.left.color == LineColor.Black)) {
1218 line2.color = LineColor.Red;
1219 line1 = line1.parent;
1220 } else {
1221 if (line2.left.color == LineColor.Black) {
1222 line2.right.color = LineColor.Black;
1223 line2.color = LineColor.Red;
1224 RotateLeft(line2);
1225 line2 = line1.parent.left;
1227 line2.color = line1.parent.color;
1228 line1.parent.color = LineColor.Black;
1229 line2.left.color = LineColor.Black;
1230 RotateRight(line1.parent);
1231 line1 = document;
1235 line1.color = LineColor.Black;
1238 private void RotateLeft(Line line1) {
1239 Line line2 = line1.right;
1241 line1.right = line2.left;
1243 if (line2.left != sentinel) {
1244 line2.left.parent = line1;
1247 if (line2 != sentinel) {
1248 line2.parent = line1.parent;
1251 if (line1.parent != null) {
1252 if (line1 == line1.parent.left) {
1253 line1.parent.left = line2;
1254 } else {
1255 line1.parent.right = line2;
1257 } else {
1258 document = line2;
1261 line2.left = line1;
1262 if (line1 != sentinel) {
1263 line1.parent = line2;
1267 private void RotateRight(Line line1) {
1268 Line line2 = line1.left;
1270 line1.left = line2.right;
1272 if (line2.right != sentinel) {
1273 line2.right.parent = line1;
1276 if (line2 != sentinel) {
1277 line2.parent = line1.parent;
1280 if (line1.parent != null) {
1281 if (line1 == line1.parent.right) {
1282 line1.parent.right = line2;
1283 } else {
1284 line1.parent.left = line2;
1286 } else {
1287 document = line2;
1290 line2.right = line1;
1291 if (line1 != sentinel) {
1292 line1.parent = line2;
1297 internal void UpdateView(Line line, int pos) {
1298 if (!owner.IsHandleCreated) {
1299 return;
1302 if (recalc_suspended > 0) {
1303 recalc_start = Math.Min (recalc_start, line.line_no);
1304 recalc_end = Math.Max (recalc_end, line.line_no);
1305 recalc_optimize = true;
1306 recalc_pending = true;
1307 return;
1310 // Optimize invalidation based on Line alignment
1311 if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no, true)) {
1312 // Lineheight changed, invalidate the rest of the document
1313 if ((line.Y - viewport_y) >=0 ) {
1314 // We formatted something that's in view, only draw parts of the screen
1315 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, owner.Height - line.Y - viewport_y));
1316 } else {
1317 // The tag was above the visible area, draw everything
1318 owner.Invalidate();
1320 } else {
1321 switch(line.alignment) {
1322 case HorizontalAlignment.Left: {
1323 owner.Invalidate(new Rectangle((int)line.widths[pos] - viewport_x - 1, line.Y - viewport_y, viewport_width, line.height + 1));
1324 break;
1327 case HorizontalAlignment.Center: {
1328 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, line.height + 1));
1329 break;
1332 case HorizontalAlignment.Right: {
1333 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, (int)line.widths[pos + 1] - viewport_x + line.align_shift, line.height + 1));
1334 break;
1341 // Update display from line, down line_count lines; pos is unused, but required for the signature
1342 internal void UpdateView(Line line, int line_count, int pos) {
1343 if (!owner.IsHandleCreated) {
1344 return;
1347 if (recalc_suspended > 0) {
1348 recalc_start = Math.Min (recalc_start, line.line_no);
1349 recalc_end = Math.Max (recalc_end, line.line_no + line_count - 1);
1350 recalc_optimize = true;
1351 recalc_pending = true;
1352 return;
1355 if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no + line_count - 1, true)) {
1356 // Lineheight changed, invalidate the rest of the document
1357 if ((line.Y - viewport_y) >=0 ) {
1358 // We formatted something that's in view, only draw parts of the screen
1359 //blah Console.WriteLine("TextControl.cs(981) Invalidate called in UpdateView(line, line_count, pos)");
1360 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, owner.Height - line.Y - viewport_y));
1361 } else {
1362 // The tag was above the visible area, draw everything
1363 //blah Console.WriteLine("TextControl.cs(985) Invalidate called in UpdateView(line, line_count, pos)");
1364 owner.Invalidate();
1366 } else {
1367 Line end_line;
1369 end_line = GetLine(line.line_no + line_count -1);
1370 if (end_line == null) {
1371 end_line = line;
1374 //blah Console.WriteLine("TextControl.cs(996) Invalidate called in UpdateView(line, line_count, pos)");
1375 owner.Invalidate(new Rectangle(0 - viewport_x, line.Y - viewport_y, (int)line.widths[line.text.Length], end_line.Y + end_line.height));
1378 #endregion // Private Methods
1380 #region Internal Methods
1381 // Clear the document and reset state
1382 internal void Empty() {
1384 document = sentinel;
1385 lines = 0;
1387 // We always have a blank line
1388 Add(1, "", owner.Font, ThemeEngine.Current.ResPool.GetSolidBrush(owner.ForeColor));
1389 Line l = GetLine (1);
1390 l.soft_break = true;
1392 this.RecalculateDocument(owner.CreateGraphicsInternal());
1393 PositionCaret(0, 0);
1395 SetSelectionVisible (false);
1397 selection_start.line = this.document;
1398 selection_start.pos = 0;
1399 selection_start.tag = selection_start.line.tags;
1400 selection_end.line = this.document;
1401 selection_end.pos = 0;
1402 selection_end.tag = selection_end.line.tags;
1403 char_count = 0;
1405 viewport_x = 0;
1406 viewport_y = 0;
1408 document_x = 0;
1409 document_y = 0;
1411 if (owner.IsHandleCreated)
1412 owner.Invalidate ();
1415 internal void PositionCaret(Line line, int pos) {
1416 if (owner.IsHandleCreated) {
1417 undo.RecordCursor();
1420 caret.tag = line.FindTag(pos);
1421 caret.line = line;
1422 caret.pos = pos;
1424 if (owner.IsHandleCreated) {
1425 if (owner.Focused) {
1426 if (caret.height != caret.tag.height)
1427 XplatUI.CreateCaret (owner.Handle, caret_width, caret.height);
1428 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);
1431 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1434 // We set this at the end because we use the heights to determine whether or
1435 // not we need to recreate the caret
1436 caret.height = caret.tag.height;
1440 internal void PositionCaret(int x, int y) {
1441 if (!owner.IsHandleCreated) {
1442 return;
1445 undo.RecordCursor();
1447 caret.tag = FindCursor(x, y, out caret.pos);
1448 caret.line = caret.tag.line;
1449 caret.height = caret.tag.height;
1451 if (owner.Focused) {
1452 XplatUI.CreateCaret (owner.Handle, caret_width, caret.height);
1453 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);
1456 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1459 internal void CaretHasFocus() {
1460 if ((caret.tag != null) && owner.IsHandleCreated) {
1461 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1462 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);
1464 DisplayCaret ();
1467 if (owner.IsHandleCreated && selection_visible) {
1468 InvalidateSelectionArea ();
1472 internal void CaretLostFocus() {
1473 if (!owner.IsHandleCreated) {
1474 return;
1476 XplatUI.DestroyCaret(owner.Handle);
1479 internal void AlignCaret() {
1480 if (!owner.IsHandleCreated) {
1481 return;
1484 undo.RecordCursor();
1486 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1487 caret.height = caret.tag.height;
1489 if (owner.Focused) {
1490 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1491 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);
1492 DisplayCaret ();
1495 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1498 internal void UpdateCaret() {
1499 if (!owner.IsHandleCreated || caret.tag == null) {
1500 return;
1503 undo.RecordCursor();
1505 if (caret.tag.height != caret.height) {
1506 caret.height = caret.tag.height;
1507 if (owner.Focused) {
1508 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1512 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);
1514 DisplayCaret ();
1516 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1519 internal void DisplayCaret() {
1520 if (!owner.IsHandleCreated) {
1521 return;
1524 if (owner.Focused && (!selection_visible || owner.show_caret_w_selection)) {
1525 XplatUI.CaretVisible(owner.Handle, true);
1529 internal void HideCaret() {
1530 if (!owner.IsHandleCreated) {
1531 return;
1534 if (owner.Focused) {
1535 XplatUI.CaretVisible(owner.Handle, false);
1539 internal void MoveCaret(CaretDirection direction) {
1540 // FIXME should we use IsWordSeparator to detect whitespace, instead
1541 // of looking for actual spaces in the Word move cases?
1543 bool nowrap = false;
1544 switch(direction) {
1545 case CaretDirection.CharForwardNoWrap:
1546 nowrap = true;
1547 goto case CaretDirection.CharForward;
1548 case CaretDirection.CharForward: {
1549 caret.pos++;
1550 if (caret.pos > caret.line.text.Length) {
1551 if (multiline && !nowrap) {
1552 // Go into next line
1553 if (caret.line.line_no < this.lines) {
1554 caret.line = GetLine(caret.line.line_no+1);
1555 caret.pos = 0;
1556 caret.tag = caret.line.tags;
1557 } else {
1558 caret.pos--;
1560 } else {
1561 // Single line; we stay where we are
1562 caret.pos--;
1564 } else {
1565 if ((caret.tag.start - 1 + caret.tag.length) < caret.pos) {
1566 caret.tag = caret.tag.next;
1569 UpdateCaret();
1570 return;
1573 case CaretDirection.CharBackNoWrap:
1574 nowrap = true;
1575 goto case CaretDirection.CharBack;
1576 case CaretDirection.CharBack: {
1577 if (caret.pos > 0) {
1578 // caret.pos--; // folded into the if below
1579 if (--caret.pos > 0) {
1580 if (caret.tag.start > caret.pos) {
1581 caret.tag = caret.tag.previous;
1584 } else {
1585 if (caret.line.line_no > 1 && !nowrap) {
1586 caret.line = GetLine(caret.line.line_no - 1);
1587 caret.pos = caret.line.text.Length;
1588 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1591 UpdateCaret();
1592 return;
1595 case CaretDirection.WordForward: {
1596 int len;
1598 len = caret.line.text.Length;
1599 if (caret.pos < len) {
1600 while ((caret.pos < len) && (caret.line.text[caret.pos] != ' ')) {
1601 caret.pos++;
1603 if (caret.pos < len) {
1604 // Skip any whitespace
1605 while ((caret.pos < len) && (caret.line.text[caret.pos] == ' ')) {
1606 caret.pos++;
1609 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1610 } else {
1611 if (caret.line.line_no < this.lines) {
1612 caret.line = GetLine(caret.line.line_no + 1);
1613 caret.pos = 0;
1614 caret.tag = caret.line.tags;
1617 UpdateCaret();
1618 return;
1621 case CaretDirection.WordBack: {
1622 if (caret.pos > 0) {
1623 caret.pos--;
1625 while ((caret.pos > 0) && (caret.line.text[caret.pos] == ' ')) {
1626 caret.pos--;
1629 while ((caret.pos > 0) && (caret.line.text[caret.pos] != ' ')) {
1630 caret.pos--;
1633 if (caret.line.text.ToString(caret.pos, 1) == " ") {
1634 if (caret.pos != 0) {
1635 caret.pos++;
1636 } else {
1637 caret.line = GetLine(caret.line.line_no - 1);
1638 caret.pos = caret.line.text.Length;
1641 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1642 } else {
1643 if (caret.line.line_no > 1) {
1644 caret.line = GetLine(caret.line.line_no - 1);
1645 caret.pos = caret.line.text.Length;
1646 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1649 UpdateCaret();
1650 return;
1653 case CaretDirection.LineUp: {
1654 if (caret.line.line_no > 1) {
1655 int pixel;
1657 pixel = (int)caret.line.widths[caret.pos];
1658 PositionCaret(pixel, GetLine(caret.line.line_no - 1).Y);
1660 DisplayCaret ();
1662 return;
1665 case CaretDirection.LineDown: {
1666 if (caret.line.line_no < lines) {
1667 int pixel;
1669 pixel = (int)caret.line.widths[caret.pos];
1670 PositionCaret(pixel, GetLine(caret.line.line_no + 1).Y);
1672 DisplayCaret ();
1674 return;
1677 case CaretDirection.Home: {
1678 if (caret.pos > 0) {
1679 caret.pos = 0;
1680 caret.tag = caret.line.tags;
1681 UpdateCaret();
1683 return;
1686 case CaretDirection.End: {
1687 if (caret.pos < caret.line.text.Length) {
1688 caret.pos = caret.line.text.Length;
1689 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1690 UpdateCaret();
1692 return;
1695 case CaretDirection.PgUp: {
1697 int new_y, y_offset;
1699 if (viewport_y == 0) {
1701 // This should probably be handled elsewhere
1702 if (!(owner is RichTextBox)) {
1703 // Page down doesn't do anything in a regular TextBox
1704 // if the bottom of the document
1705 // is already visible, the page and the caret stay still
1706 return;
1709 // We're just placing the caret at the end of the document, no scrolling needed
1710 owner.vscroll.Value = 0;
1711 Line line = GetLine (1);
1712 PositionCaret (line, 0);
1715 y_offset = caret.line.Y - viewport_y;
1716 new_y = caret.line.Y - viewport_height;
1718 owner.vscroll.Value = Math.Max (new_y, 0);
1719 PositionCaret ((int)caret.line.widths[caret.pos], y_offset + viewport_y);
1720 return;
1723 case CaretDirection.PgDn: {
1724 int new_y, y_offset;
1726 if ((viewport_y + viewport_height) > document_y) {
1728 // This should probably be handled elsewhere
1729 if (!(owner is RichTextBox)) {
1730 // Page up doesn't do anything in a regular TextBox
1731 // if the bottom of the document
1732 // is already visible, the page and the caret stay still
1733 return;
1736 // We're just placing the caret at the end of the document, no scrolling needed
1737 owner.vscroll.Value = owner.vscroll.Maximum - viewport_height + 1;
1738 Line line = GetLine (lines);
1739 PositionCaret (line, line.Text.Length);
1742 y_offset = caret.line.Y - viewport_y;
1743 new_y = caret.line.Y + viewport_height;
1745 owner.vscroll.Value = Math.Min (new_y, owner.vscroll.Maximum - viewport_height + 1);
1746 PositionCaret ((int)caret.line.widths[caret.pos], y_offset + viewport_y);
1748 return;
1751 case CaretDirection.CtrlPgUp: {
1752 PositionCaret(0, viewport_y);
1753 DisplayCaret ();
1754 return;
1757 case CaretDirection.CtrlPgDn: {
1758 Line line;
1759 LineTag tag;
1760 int index;
1762 tag = FindTag(0, viewport_y + viewport_height, out index, false);
1763 if (tag.line.line_no > 1) {
1764 line = GetLine(tag.line.line_no - 1);
1765 } else {
1766 line = tag.line;
1768 PositionCaret(line, line.Text.Length);
1769 DisplayCaret ();
1770 return;
1773 case CaretDirection.CtrlHome: {
1774 caret.line = GetLine(1);
1775 caret.pos = 0;
1776 caret.tag = caret.line.tags;
1778 UpdateCaret();
1779 return;
1782 case CaretDirection.CtrlEnd: {
1783 caret.line = GetLine(lines);
1784 caret.pos = caret.line.text.Length;
1785 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1787 UpdateCaret();
1788 return;
1791 case CaretDirection.SelectionStart: {
1792 caret.line = selection_start.line;
1793 caret.pos = selection_start.pos;
1794 caret.tag = selection_start.tag;
1796 UpdateCaret();
1797 return;
1800 case CaretDirection.SelectionEnd: {
1801 caret.line = selection_end.line;
1802 caret.pos = selection_end.pos;
1803 caret.tag = selection_end.tag;
1805 UpdateCaret();
1806 return;
1811 internal void DumpDoc ()
1813 Console.WriteLine ("<doc>");
1814 for (int i = 1; i < lines; i++) {
1815 Line line = GetLine (i);
1816 Console.WriteLine ("<line no='{0}'>", line.line_no);
1818 LineTag tag = line.tags;
1819 while (tag != null) {
1820 Console.Write ("\t<tag color='{0}'>", tag.color.Color);
1821 Console.Write (tag.Text ());
1822 Console.WriteLine ("\t</tag>");
1823 tag = tag.next;
1825 Console.WriteLine ("</line>");
1827 Console.WriteLine ("</doc>");
1830 internal void Draw (Graphics g, Rectangle clip)
1832 Line line; // Current line being drawn
1833 LineTag tag; // Current tag being drawn
1834 int start; // First line to draw
1835 int end; // Last line to draw
1836 StringBuilder text; // String representing the current line
1837 int line_no;
1838 Brush tag_brush;
1839 Brush current_brush;
1840 Brush disabled_brush;
1841 Brush hilight;
1842 Brush hilight_text;
1844 // First, figure out from what line to what line we need to draw
1845 start = GetLineByPixel(clip.Top + viewport_y, false).line_no;
1846 end = GetLineByPixel(clip.Bottom + viewport_y, false).line_no;
1848 /// Make sure that we aren't drawing one more line then we need to
1849 line = GetLine (end - 1);
1850 if (line != null && clip.Bottom == line.Y + line.height + viewport_y)
1851 end--;
1853 line_no = start;
1855 g.FillRectangle (new SolidBrush (Color.White), clip);
1856 #if Debug
1857 DateTime n = DateTime.Now;
1858 Console.WriteLine ("Started drawing: {0}s {1}ms", n.Second, n.Millisecond);
1859 Console.WriteLine ("CLIP: {0}", clip);
1860 Console.WriteLine ("S: {0}", GetLine (start).text);
1861 Console.WriteLine ("E: {0}", GetLine (end).text);
1862 #endif
1864 disabled_brush = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorGrayText);
1865 hilight = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorHighlight);
1866 hilight_text = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorHighlightText);
1868 while (line_no <= end) {
1869 line = GetLine (line_no);
1871 tag = line.tags;
1872 if (!calc_pass) {
1873 text = line.text;
1874 } else {
1875 // This fails if there's a password > 1024 chars...
1876 text = this.password_cache;
1879 int line_selection_start = text.Length + 1;
1880 int line_selection_end = text.Length + 1;
1881 if (selection_visible && owner.ShowSelection &&
1882 (line_no >= selection_start.line.line_no) &&
1883 (line_no <= selection_end.line.line_no)) {
1885 if (line_no == selection_start.line.line_no)
1886 line_selection_start = selection_start.pos + 1;
1887 else
1888 line_selection_start = 1;
1890 if (line_no == selection_end.line.line_no)
1891 line_selection_end = selection_end.pos + 1;
1892 else
1893 line_selection_end = text.Length + 1;
1895 if (line_selection_end == line_selection_start) {
1896 // There isn't really selection
1897 line_selection_start = text.Length + 1;
1898 line_selection_end = line_selection_start;
1899 } else {
1900 // lets draw some selection baby!!
1902 g.FillRectangle (hilight,
1903 line.widths [line_selection_start - 1] + line.align_shift - viewport_x,
1904 line.Y - viewport_y,
1905 line.widths [line_selection_end - 1] - line.widths [line_selection_start - 1],
1906 line.height);
1910 current_brush = line.tags.color;
1911 while (tag != null) {
1913 // Skip empty tags
1914 if (tag.length == 0) {
1915 tag = tag.next;
1916 continue;
1919 if (((tag.X + tag.width) < (clip.Left - viewport_x)) || (tag.X > (clip.Right - viewport_x))) {
1920 tag = tag.next;
1921 continue;
1924 if (tag.back_color != null) {
1925 g.FillRectangle (tag.back_color, tag.X + line.align_shift - viewport_x,
1926 line.Y + tag.shift - viewport_y, tag.width, line.height);
1929 tag_brush = tag.color;
1930 current_brush = tag_brush;
1932 if (!owner.is_enabled) {
1933 Color a = ((SolidBrush) tag.color).Color;
1934 Color b = ThemeEngine.Current.ColorWindowText;
1936 if ((a.R == b.R) && (a.G == b.G) && (a.B == b.B)) {
1937 tag_brush = disabled_brush;
1941 int tag_pos = tag.start;
1942 current_brush = tag_brush;
1943 while (tag_pos < tag.start + tag.length) {
1944 int old_tag_pos = tag_pos;
1946 if (tag_pos >= line_selection_start && tag_pos < line_selection_end) {
1947 current_brush = hilight_text;
1948 tag_pos = Math.Min (tag.end, line_selection_end);
1949 } else if (tag_pos < line_selection_start) {
1950 current_brush = tag.color;
1951 tag_pos = Math.Min (tag.end, line_selection_start);
1952 } else {
1953 current_brush = tag.color;
1954 tag_pos = tag.end;
1957 tag.Draw (g, current_brush,
1958 line.widths [old_tag_pos - 1] + line.align_shift - viewport_x,
1959 line.Y + tag.shift - viewport_y,
1960 old_tag_pos - 1, Math.Min (tag.length, tag_pos - old_tag_pos));
1962 tag = tag.next;
1964 line_no++;
1968 private void InsertLineString (Line line, int pos, string s)
1970 bool carriage_return = false;
1972 if (s.EndsWith ("\r")) {
1973 s = s.Substring (0, s.Length - 1);
1974 carriage_return = true;
1977 InsertString (line, pos, s);
1979 if (carriage_return) {
1980 Line l = GetLine (line.line_no);
1981 l.carriage_return = true;
1985 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
1986 internal void Insert(Line line, int pos, bool update_caret, string s) {
1987 int break_index;
1988 int base_line;
1989 int old_line_count;
1990 int count = 1;
1991 LineTag tag = LineTag.FindTag (line, pos);
1993 SuspendRecalc ();
1994 undo.BeginCompoundAction ();
1996 base_line = line.line_no;
1997 old_line_count = lines;
1999 break_index = s.IndexOf ('\n');
2001 // Bump the text at insertion point a line down if we're inserting more than one line
2002 if (break_index > -1) {
2003 Split(line, pos);
2004 line.soft_break = false;
2005 // Remainder of start line is now in base_line + 1
2008 if (break_index == -1)
2009 break_index = s.Length;
2011 InsertLineString (line, pos, s.Substring (0, break_index));
2012 break_index++;
2014 while (break_index < s.Length) {
2015 bool soft = false;
2016 int next_break = s.IndexOf ('\n', break_index);
2017 int adjusted_next_break;
2018 bool carriage_return = false;
2020 if (next_break == -1) {
2021 next_break = s.Length;
2022 soft = true;
2025 adjusted_next_break = next_break;
2026 if (s [next_break - 1] == '\r') {
2027 adjusted_next_break--;
2028 carriage_return = true;
2031 string line_text = s.Substring (break_index, adjusted_next_break - break_index);
2032 Add (base_line + count, line_text, line.alignment, tag.font, tag.color);
2034 if (carriage_return) {
2035 Line last = GetLine (base_line + count);
2036 last.carriage_return = true;
2038 if (soft)
2039 last.soft_break = true;
2040 } else if (soft) {
2041 Line last = GetLine (base_line + count);
2042 last.soft_break = true;
2045 count++;
2046 break_index = next_break + 1;
2049 ResumeRecalc (true);
2051 UpdateView(line, lines - old_line_count + 1, pos);
2053 if (update_caret) {
2054 // Move caret to the end of the inserted text
2055 Line l = GetLine (line.line_no + lines - old_line_count);
2056 PositionCaret(l, l.text.Length);
2057 DisplayCaret ();
2060 undo.EndCompoundAction ();
2063 // Inserts a character at the given position
2064 internal void InsertString(Line line, int pos, string s) {
2065 InsertString(line.FindTag(pos), pos, s);
2068 // Inserts a string at the given position
2069 internal void InsertString(LineTag tag, int pos, string s) {
2070 Line line;
2071 int len;
2073 len = s.Length;
2075 CharCount += len;
2077 line = tag.line;
2078 line.text.Insert(pos, s);
2080 tag = tag.next;
2081 while (tag != null) {
2082 tag.start += len;
2083 tag = tag.next;
2085 line.Grow(len);
2086 line.recalc = true;
2088 UpdateView(line, pos);
2091 // Inserts a string at the caret position
2092 internal void InsertStringAtCaret(string s, bool move_caret) {
2094 InsertString (caret.tag, caret.pos, s);
2096 UpdateView(caret.line, caret.pos);
2097 if (move_caret) {
2098 caret.pos += s.Length;
2099 UpdateCaret();
2105 // Inserts a character at the given position
2106 internal void InsertChar(Line line, int pos, char ch) {
2107 InsertChar(line.FindTag(pos), pos, ch);
2110 // Inserts a character at the given position
2111 internal void InsertChar(LineTag tag, int pos, char ch) {
2112 Line line;
2114 CharCount++;
2116 line = tag.line;
2117 line.text.Insert(pos, ch);
2118 // tag.length++;
2120 tag = tag.next;
2121 while (tag != null) {
2122 tag.start++;
2123 tag = tag.next;
2125 line.Grow(1);
2126 line.recalc = true;
2128 UpdateView(line, pos);
2131 // Inserts a character at the current caret position
2132 internal void InsertCharAtCaret(char ch, bool move_caret) {
2134 LineTag tag;
2136 CharCount++;
2138 caret.line.text.Insert(caret.pos, ch);
2139 caret.tag.length++;
2141 if (caret.tag.next != null) {
2142 tag = caret.tag.next;
2143 while (tag != null) {
2144 tag.start++;
2145 tag = tag.next;
2148 caret.line.Grow(1);
2149 caret.line.recalc = true;
2151 InsertChar (caret.tag, caret.pos, ch);
2153 UpdateView(caret.line, caret.pos);
2154 if (move_caret) {
2155 caret.pos++;
2156 UpdateCaret();
2157 SetSelectionToCaret(true);
2161 internal void InsertImage (LineTag tag, int pos, Image image)
2163 Line line;
2164 int len;
2166 len = 1;
2168 line = tag.line;
2169 line.text.Insert (pos, "I");
2171 LineTag next_tag = tag.Break (pos);
2172 ImageTag image_tag = new ImageTag (line, pos, image);
2173 image_tag.CopyFormattingFrom (tag);
2174 image_tag.next = next_tag;
2175 image_tag.previous = tag;
2176 tag.next = image_tag;
2178 tag = image_tag.next;
2179 while (tag != null) {
2180 tag.start += len;
2181 tag = tag.next;
2184 line.Grow (len);
2185 line.recalc = true;
2187 DumpDoc ();
2188 UpdateView (line, pos);
2191 internal void DeleteMultiline (Line start_line, int pos, int length)
2193 Marker start = new Marker ();
2194 Marker end = new Marker ();
2195 int start_index = LineTagToCharIndex (start_line, pos);
2197 start.line = start_line;
2198 start.pos = pos;
2199 start.tag = LineTag.FindTag (start_line, pos);
2201 CharIndexToLineTag (start_index + length, out end.line,
2202 out end.tag, out end.pos);
2204 if (start.line == end.line) {
2205 DeleteChars (start.tag, pos, end.pos - pos);
2206 } else {
2208 // Delete first and last lines
2209 DeleteChars (start.tag, start.pos, start.line.text.Length - start.pos);
2210 DeleteChars (end.line.tags, 0, end.pos);
2212 int current = start.line.line_no + 1;
2213 if (current < end.line.line_no) {
2214 for (int i = end.line.line_no - 1; i >= current; i--) {
2215 Delete (i);
2219 // BIG FAT WARNING - selection_end.line might be stale due
2220 // to the above Delete() call. DONT USE IT before hitting the end of this method!
2222 // Join start and end
2223 Combine (start.line.line_no, current);
2228 // Deletes n characters at the given position; it will not delete past line limits
2229 // pos is 0-based
2230 internal void DeleteChars(LineTag tag, int pos, int count) {
2231 Line line;
2232 bool streamline;
2234 streamline = false;
2235 line = tag.line;
2237 CharCount -= count;
2239 if (pos == line.text.Length) {
2240 return;
2243 line.text.Remove(pos, count);
2245 // Make sure the tag points to the right spot
2246 while ((tag != null) && (tag.start + tag.length - 1) <= pos) {
2247 tag = tag.next;
2250 if (tag == null) {
2251 return;
2254 // Check if we're crossing tag boundaries
2255 if ((pos + count) > (tag.start + tag.length - 1)) {
2256 int left;
2258 // We have to delete cross tag boundaries
2259 streamline = true;
2260 left = count;
2262 left -= tag.start + tag.length - pos - 1;
2264 tag = tag.next;
2265 while ((tag != null) && (left > 0)) {
2266 tag.start -= count - left;
2268 if (tag.length > left) {
2269 left = 0;
2270 } else {
2271 left -= tag.length;
2272 tag = tag.next;
2276 } else {
2277 // We got off easy, same tag
2279 if (tag.length == 0) {
2280 streamline = true;
2284 // Delete empty orphaned tags at the end
2285 LineTag walk = tag;
2286 while (walk != null && walk.next != null && walk.next.length == 0) {
2287 LineTag t = walk;
2288 walk.next = walk.next.next;
2289 if (walk.next != null)
2290 walk.next.previous = t;
2291 walk = walk.next;
2294 // Adjust the start point of any tags following
2295 if (tag != null) {
2296 tag = tag.next;
2297 while (tag != null) {
2298 tag.start -= count;
2299 tag = tag.next;
2303 line.recalc = true;
2304 if (streamline) {
2305 line.Streamline(lines);
2308 UpdateView(line, pos);
2311 // Deletes a character at or after the given position (depending on forward); it will not delete past line limits
2312 internal void DeleteChar(LineTag tag, int pos, bool forward) {
2313 Line line;
2314 bool streamline;
2316 CharCount--;
2318 streamline = false;
2319 line = tag.line;
2321 if ((pos == 0 && forward == false) || (pos == line.text.Length && forward == true)) {
2322 return;
2326 if (forward) {
2327 line.text.Remove(pos, 1);
2329 while ((tag != null) && (tag.start + tag.length - 1) <= pos) {
2330 tag = tag.next;
2333 if (tag == null) {
2334 return;
2337 // tag.length--;
2339 if (tag.length == 0) {
2340 streamline = true;
2342 } else {
2343 pos--;
2344 line.text.Remove(pos, 1);
2345 if (pos >= (tag.start - 1)) {
2346 // tag.length--;
2347 if (tag.length == 0) {
2348 streamline = true;
2350 } else if (tag.previous != null) {
2351 // tag.previous.length--;
2352 if (tag.previous.length == 0) {
2353 streamline = true;
2358 // Delete empty orphaned tags at the end
2359 LineTag walk = tag;
2360 while (walk != null && walk.next != null && walk.next.length == 0) {
2361 LineTag t = walk;
2362 walk.next = walk.next.next;
2363 if (walk.next != null)
2364 walk.next.previous = t;
2365 walk = walk.next;
2368 tag = tag.next;
2369 while (tag != null) {
2370 tag.start--;
2371 tag = tag.next;
2373 line.recalc = true;
2374 if (streamline) {
2375 line.Streamline(lines);
2378 UpdateView(line, pos);
2381 // Combine two lines
2382 internal void Combine(int FirstLine, int SecondLine) {
2383 Combine(GetLine(FirstLine), GetLine(SecondLine));
2386 internal void Combine(Line first, Line second) {
2387 LineTag last;
2388 int shift;
2390 // Combine the two tag chains into one
2391 last = first.tags;
2393 // Maintain the line ending style
2394 first.soft_break = second.soft_break;
2396 while (last.next != null) {
2397 last = last.next;
2400 // need to get the shift before setting the next tag since that effects length
2401 shift = last.start + last.length - 1;
2402 last.next = second.tags;
2403 last.next.previous = last;
2405 // Fix up references within the chain
2406 last = last.next;
2407 while (last != null) {
2408 last.line = first;
2409 last.start += shift;
2410 last = last.next;
2413 // Combine both lines' strings
2414 first.text.Insert(first.text.Length, second.text.ToString());
2415 first.Grow(first.text.Length);
2417 // Remove the reference to our (now combined) tags from the doomed line
2418 second.tags = null;
2420 // Renumber lines
2421 DecrementLines(first.line_no + 2); // first.line_no + 1 will be deleted, so we need to start renumbering one later
2423 // Mop up
2424 first.recalc = true;
2425 first.height = 0; // This forces RecalcDocument/UpdateView to redraw from this line on
2426 first.Streamline(lines);
2428 // Update Caret, Selection, etc
2429 if (caret.line == second) {
2430 caret.Combine(first, shift);
2432 if (selection_anchor.line == second) {
2433 selection_anchor.Combine(first, shift);
2435 if (selection_start.line == second) {
2436 selection_start.Combine(first, shift);
2438 if (selection_end.line == second) {
2439 selection_end.Combine(first, shift);
2442 #if Debug
2443 Line check_first;
2444 Line check_second;
2446 check_first = GetLine(first.line_no);
2447 check_second = GetLine(check_first.line_no + 1);
2449 Console.WriteLine("Pre-delete: Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2450 #endif
2452 this.Delete(second);
2454 #if Debug
2455 check_first = GetLine(first.line_no);
2456 check_second = GetLine(check_first.line_no + 1);
2458 Console.WriteLine("Post-delete Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2459 #endif
2462 // Split the line at the position into two
2463 internal void Split(int LineNo, int pos) {
2464 Line line;
2465 LineTag tag;
2467 line = GetLine(LineNo);
2468 tag = LineTag.FindTag(line, pos);
2469 Split(line, tag, pos, false);
2472 internal void Split(Line line, int pos) {
2473 LineTag tag;
2475 tag = LineTag.FindTag(line, pos);
2476 Split(line, tag, pos, false);
2479 ///<summary>Split line at given tag and position into two lines</summary>
2480 ///<param name="soft">True if the split should be marked as 'soft', indicating that it can be contracted
2481 ///if more space becomes available on previous line</param>
2482 internal void Split(Line line, LineTag tag, int pos, bool soft) {
2483 LineTag new_tag;
2484 Line new_line;
2485 bool move_caret;
2486 bool move_sel_start;
2487 bool move_sel_end;
2489 move_caret = false;
2490 move_sel_start = false;
2491 move_sel_end = false;
2493 // Adjust selection and cursors
2494 if (caret.line == line && caret.pos >= pos) {
2495 move_caret = true;
2497 if (selection_start.line == line && selection_start.pos > pos) {
2498 move_sel_start = true;
2501 if (selection_end.line == line && selection_end.pos > pos) {
2502 move_sel_end = true;
2505 // cover the easy case first
2506 if (pos == line.text.Length) {
2507 Add(line.line_no + 1, "", line.alignment, tag.font, tag.color);
2509 new_line = GetLine(line.line_no + 1);
2511 line.carriage_return = false;
2512 new_line.carriage_return = line.carriage_return;
2513 new_line.soft_break = soft;
2515 if (move_caret) {
2516 caret.line = new_line;
2517 caret.tag = new_line.tags;
2518 caret.pos = 0;
2521 if (move_sel_start) {
2522 selection_start.line = new_line;
2523 selection_start.pos = 0;
2524 selection_start.tag = new_line.tags;
2527 if (move_sel_end) {
2528 selection_end.line = new_line;
2529 selection_end.pos = 0;
2530 selection_end.tag = new_line.tags;
2532 return;
2535 // We need to move the rest of the text into the new line
2536 Add (line.line_no + 1, line.text.ToString (pos, line.text.Length - pos), line.alignment, tag.font, tag.color);
2538 // Now transfer our tags from this line to the next
2539 new_line = GetLine(line.line_no + 1);
2541 line.carriage_return = false;
2542 new_line.carriage_return = line.carriage_return;
2543 new_line.soft_break = soft;
2545 line.recalc = true;
2546 new_line.recalc = true;
2548 if ((tag.start - 1) == pos) {
2549 int shift;
2551 // We can simply break the chain and move the tag into the next line
2552 if (tag == line.tags) {
2553 new_tag = new LineTag(line, 1);
2554 new_tag.CopyFormattingFrom (tag);
2555 line.tags = new_tag;
2558 if (tag.previous != null) {
2559 tag.previous.next = null;
2561 new_line.tags = tag;
2562 tag.previous = null;
2563 tag.line = new_line;
2565 // Walk the list and correct the start location of the tags we just bumped into the next line
2566 shift = tag.start - 1;
2568 new_tag = tag;
2569 while (new_tag != null) {
2570 new_tag.start -= shift;
2571 new_tag.line = new_line;
2572 new_tag = new_tag.next;
2574 } else {
2575 int shift;
2577 new_tag = new LineTag (new_line, 1);
2578 new_tag.next = tag.next;
2579 new_tag.CopyFormattingFrom (tag);
2580 new_line.tags = new_tag;
2581 if (new_tag.next != null) {
2582 new_tag.next.previous = new_tag;
2584 tag.next = null;
2586 shift = pos;
2587 new_tag = new_tag.next;
2588 while (new_tag != null) {
2589 new_tag.start -= shift;
2590 new_tag.line = new_line;
2591 new_tag = new_tag.next;
2596 if (move_caret) {
2597 caret.line = new_line;
2598 caret.pos = caret.pos - pos;
2599 caret.tag = caret.line.FindTag(caret.pos);
2602 if (move_sel_start) {
2603 selection_start.line = new_line;
2604 selection_start.pos = selection_start.pos - pos;
2605 selection_start.tag = new_line.FindTag(selection_start.pos);
2608 if (move_sel_end) {
2609 selection_end.line = new_line;
2610 selection_end.pos = selection_end.pos - pos;
2611 selection_end.tag = new_line.FindTag(selection_end.pos);
2614 CharCount -= line.text.Length - pos;
2615 line.text.Remove(pos, line.text.Length - pos);
2618 // Adds a line of text, with given font.
2619 // Bumps any line at that line number that already exists down
2620 internal void Add(int LineNo, string Text, Font font, SolidBrush color) {
2621 Add(LineNo, Text, HorizontalAlignment.Left, font, color);
2624 internal void Add(int LineNo, string Text, HorizontalAlignment align, Font font, SolidBrush color) {
2625 Line add;
2626 Line line;
2627 int line_no;
2629 CharCount += Text.Length;
2631 if (LineNo<1 || Text == null) {
2632 if (LineNo<1) {
2633 throw new ArgumentNullException("LineNo", "Line numbers must be positive");
2634 } else {
2635 throw new ArgumentNullException("Text", "Cannot insert NULL line");
2639 add = new Line(LineNo, Text, align, font, color);
2641 line = document;
2642 while (line != sentinel) {
2643 add.parent = line;
2644 line_no = line.line_no;
2646 if (LineNo > line_no) {
2647 line = line.right;
2648 } else if (LineNo < line_no) {
2649 line = line.left;
2650 } else {
2651 // Bump existing line numbers; walk all nodes to the right of this one and increment line_no
2652 IncrementLines(line.line_no);
2653 line = line.left;
2657 add.left = sentinel;
2658 add.right = sentinel;
2660 if (add.parent != null) {
2661 if (LineNo > add.parent.line_no) {
2662 add.parent.right = add;
2663 } else {
2664 add.parent.left = add;
2666 } else {
2667 // Root node
2668 document = add;
2671 RebalanceAfterAdd(add);
2673 lines++;
2676 internal virtual void Clear() {
2677 lines = 0;
2678 CharCount = 0;
2679 document = sentinel;
2682 public virtual object Clone() {
2683 Document clone;
2685 clone = new Document(null);
2687 clone.lines = this.lines;
2688 clone.document = (Line)document.Clone();
2690 return clone;
2693 internal void Delete(int LineNo) {
2694 Line line;
2696 if (LineNo>lines) {
2697 return;
2700 line = GetLine(LineNo);
2702 CharCount -= line.text.Length;
2704 DecrementLines(LineNo + 1);
2705 Delete(line);
2708 internal void Delete(Line line1) {
2709 Line line2;// = new Line();
2710 Line line3;
2712 if ((line1.left == sentinel) || (line1.right == sentinel)) {
2713 line3 = line1;
2714 } else {
2715 line3 = line1.right;
2716 while (line3.left != sentinel) {
2717 line3 = line3.left;
2721 if (line3.left != sentinel) {
2722 line2 = line3.left;
2723 } else {
2724 line2 = line3.right;
2727 line2.parent = line3.parent;
2728 if (line3.parent != null) {
2729 if(line3 == line3.parent.left) {
2730 line3.parent.left = line2;
2731 } else {
2732 line3.parent.right = line2;
2734 } else {
2735 document = line2;
2738 if (line3 != line1) {
2739 LineTag tag;
2741 if (selection_start.line == line3) {
2742 selection_start.line = line1;
2745 if (selection_end.line == line3) {
2746 selection_end.line = line1;
2749 if (selection_anchor.line == line3) {
2750 selection_anchor.line = line1;
2753 if (caret.line == line3) {
2754 caret.line = line1;
2758 line1.alignment = line3.alignment;
2759 line1.ascent = line3.ascent;
2760 line1.hanging_indent = line3.hanging_indent;
2761 line1.height = line3.height;
2762 line1.indent = line3.indent;
2763 line1.line_no = line3.line_no;
2764 line1.recalc = line3.recalc;
2765 line1.right_indent = line3.right_indent;
2766 line1.soft_break = line3.soft_break;
2767 line1.space = line3.space;
2768 line1.tags = line3.tags;
2769 line1.text = line3.text;
2770 line1.widths = line3.widths;
2771 line1.Y = line3.Y;
2773 tag = line1.tags;
2774 while (tag != null) {
2775 tag.line = line1;
2776 tag = tag.next;
2780 if (line3.color == LineColor.Black)
2781 RebalanceAfterDelete(line2);
2783 this.lines--;
2786 // Invalidate a section of the document to trigger redraw
2787 internal void Invalidate(Line start, int start_pos, Line end, int end_pos) {
2788 Line l1;
2789 Line l2;
2790 int p1;
2791 int p2;
2793 if ((start == end) && (start_pos == end_pos)) {
2794 return;
2797 if (end_pos == -1) {
2798 end_pos = end.text.Length;
2801 // figure out what's before what so the logic below is straightforward
2802 if (start.line_no < end.line_no) {
2803 l1 = start;
2804 p1 = start_pos;
2806 l2 = end;
2807 p2 = end_pos;
2808 } else if (start.line_no > end.line_no) {
2809 l1 = end;
2810 p1 = end_pos;
2812 l2 = start;
2813 p2 = start_pos;
2814 } else {
2815 if (start_pos < end_pos) {
2816 l1 = start;
2817 p1 = start_pos;
2819 l2 = end;
2820 p2 = end_pos;
2821 } else {
2822 l1 = end;
2823 p1 = end_pos;
2825 l2 = start;
2826 p2 = start_pos;
2829 #if Debug
2830 Console.WriteLine("Invaliding from {0}:{1} to {2}:{3} {4}",
2831 l1.line_no, p1, l2.line_no, p2,
2832 new Rectangle(
2833 (int)l1.widths[p1] + l1.align_shift - viewport_x,
2834 l1.Y - viewport_y,
2835 (int)l2.widths[p2] - (int)l1.widths[p1] + 1,
2836 l1.height
2839 #endif
2841 owner.Invalidate(
2842 new Rectangle(
2843 (int)l1.widths[p1] + l1.align_shift - viewport_x,
2844 l1.Y - viewport_y,
2845 (int)l2.widths[p2] - (int)l1.widths[p1] + 1,
2846 l1.height
2849 return;
2852 #if Debug
2853 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);
2854 Console.WriteLine ("invalidate start line: {0} position: {1}", l1.text, p1);
2855 #endif
2857 // Three invalidates:
2858 // First line from start
2859 owner.Invalidate(new Rectangle((int)l1.widths[p1] + l1.align_shift - viewport_x, l1.Y - viewport_y, viewport_width, l1.height));
2862 // lines inbetween
2863 if ((l1.line_no + 1) < l2.line_no) {
2864 int y;
2866 y = GetLine(l1.line_no + 1).Y;
2867 owner.Invalidate(new Rectangle(0, y - viewport_y, viewport_width, l2.Y - y));
2869 #if Debug
2870 Console.WriteLine("Invaliding from {0}:{1} to {2}:{3} Middle => x={4}, y={5}, {6}x{7}", l1.line_no, p1, l2.line_no, p2, 0, y - viewport_y, viewport_width, l2.Y - y);
2871 #endif
2875 // Last line to end
2876 owner.Invalidate(new Rectangle((int)l2.widths[0] + l2.align_shift - viewport_x, l2.Y - viewport_y, (int)l2.widths[p2] + 1, l2.height));
2877 #if Debug
2878 Console.WriteLine("Invaliding from {0}:{1} to {2}:{3} End => x={4}, y={5}, {6}x{7}", l1.line_no, p1, l2.line_no, p2, (int)l2.widths[0] + l2.align_shift - viewport_x, l2.Y - viewport_y, (int)l2.widths[p2] + 1, l2.height);
2880 #endif
2883 /// <summary>Select text around caret</summary>
2884 internal void ExpandSelection(CaretSelection mode, bool to_caret) {
2885 if (to_caret) {
2886 // We're expanding the selection to the caret position
2887 switch(mode) {
2888 case CaretSelection.Line: {
2889 // Invalidate the selection delta
2890 if (caret > selection_prev) {
2891 Invalidate(selection_prev.line, 0, caret.line, caret.line.text.Length);
2892 } else {
2893 Invalidate(selection_prev.line, selection_prev.line.text.Length, caret.line, 0);
2896 if (caret.line.line_no <= selection_anchor.line.line_no) {
2897 selection_start.line = caret.line;
2898 selection_start.tag = caret.line.tags;
2899 selection_start.pos = 0;
2901 selection_end.line = selection_anchor.line;
2902 selection_end.tag = selection_anchor.tag;
2903 selection_end.pos = selection_anchor.pos;
2905 selection_end_anchor = true;
2906 } else {
2907 selection_start.line = selection_anchor.line;
2908 selection_start.pos = selection_anchor.height;
2909 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height);
2911 selection_end.line = caret.line;
2912 selection_end.tag = caret.line.tags;
2913 selection_end.pos = caret.line.text.Length;
2915 selection_end_anchor = false;
2917 selection_prev.line = caret.line;
2918 selection_prev.tag = caret.tag;
2919 selection_prev.pos = caret.pos;
2921 break;
2924 case CaretSelection.Word: {
2925 int start_pos;
2926 int end_pos;
2928 start_pos = FindWordSeparator(caret.line, caret.pos, false);
2929 end_pos = FindWordSeparator(caret.line, caret.pos, true);
2932 // Invalidate the selection delta
2933 if (caret > selection_prev) {
2934 Invalidate(selection_prev.line, selection_prev.pos, caret.line, end_pos);
2935 } else {
2936 Invalidate(selection_prev.line, selection_prev.pos, caret.line, start_pos);
2938 if (caret < selection_anchor) {
2939 selection_start.line = caret.line;
2940 selection_start.tag = caret.line.FindTag(start_pos);
2941 selection_start.pos = start_pos;
2943 selection_end.line = selection_anchor.line;
2944 selection_end.tag = selection_anchor.tag;
2945 selection_end.pos = selection_anchor.pos;
2947 selection_prev.line = caret.line;
2948 selection_prev.tag = caret.tag;
2949 selection_prev.pos = start_pos;
2951 selection_end_anchor = true;
2952 } else {
2953 selection_start.line = selection_anchor.line;
2954 selection_start.pos = selection_anchor.height;
2955 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height);
2957 selection_end.line = caret.line;
2958 selection_end.tag = caret.line.FindTag(end_pos);
2959 selection_end.pos = end_pos;
2961 selection_prev.line = caret.line;
2962 selection_prev.tag = caret.tag;
2963 selection_prev.pos = end_pos;
2965 selection_end_anchor = false;
2967 break;
2970 case CaretSelection.Position: {
2971 SetSelectionToCaret(false);
2972 return;
2975 } else {
2976 // We're setting the selection 'around' the caret position
2977 switch(mode) {
2978 case CaretSelection.Line: {
2979 this.Invalidate(caret.line, 0, caret.line, caret.line.text.Length);
2981 selection_start.line = caret.line;
2982 selection_start.tag = caret.line.tags;
2983 selection_start.pos = 0;
2985 selection_end.line = caret.line;
2986 selection_end.pos = caret.line.text.Length;
2987 selection_end.tag = caret.line.FindTag(selection_end.pos);
2989 selection_anchor.line = selection_end.line;
2990 selection_anchor.tag = selection_end.tag;
2991 selection_anchor.pos = selection_end.pos;
2992 selection_anchor.height = 0;
2994 selection_prev.line = caret.line;
2995 selection_prev.tag = caret.tag;
2996 selection_prev.pos = caret.pos;
2998 this.selection_end_anchor = true;
3000 break;
3003 case CaretSelection.Word: {
3004 int start_pos;
3005 int end_pos;
3007 start_pos = FindWordSeparator(caret.line, caret.pos, false);
3008 end_pos = FindWordSeparator(caret.line, caret.pos, true);
3010 this.Invalidate(selection_start.line, start_pos, caret.line, end_pos);
3012 selection_start.line = caret.line;
3013 selection_start.tag = caret.line.FindTag(start_pos);
3014 selection_start.pos = start_pos;
3016 selection_end.line = caret.line;
3017 selection_end.tag = caret.line.FindTag(end_pos);
3018 selection_end.pos = end_pos;
3020 selection_anchor.line = selection_end.line;
3021 selection_anchor.tag = selection_end.tag;
3022 selection_anchor.pos = selection_end.pos;
3023 selection_anchor.height = start_pos;
3025 selection_prev.line = caret.line;
3026 selection_prev.tag = caret.tag;
3027 selection_prev.pos = caret.pos;
3029 this.selection_end_anchor = true;
3031 break;
3036 SetSelectionVisible (!(selection_start == selection_end));
3039 internal void SetSelectionToCaret(bool start) {
3040 if (start) {
3041 // Invalidate old selection; selection is being reset to empty
3042 this.Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3044 selection_start.line = caret.line;
3045 selection_start.tag = caret.tag;
3046 selection_start.pos = caret.pos;
3048 // start always also selects end
3049 selection_end.line = caret.line;
3050 selection_end.tag = caret.tag;
3051 selection_end.pos = caret.pos;
3053 selection_anchor.line = caret.line;
3054 selection_anchor.tag = caret.tag;
3055 selection_anchor.pos = caret.pos;
3056 } else {
3057 // Invalidate from previous end to caret (aka new end)
3058 if (selection_end_anchor) {
3059 if (selection_start != caret) {
3060 this.Invalidate(selection_start.line, selection_start.pos, caret.line, caret.pos);
3062 } else {
3063 if (selection_end != caret) {
3064 this.Invalidate(selection_end.line, selection_end.pos, caret.line, caret.pos);
3068 if (caret < selection_anchor) {
3069 selection_start.line = caret.line;
3070 selection_start.tag = caret.tag;
3071 selection_start.pos = caret.pos;
3073 selection_end.line = selection_anchor.line;
3074 selection_end.tag = selection_anchor.tag;
3075 selection_end.pos = selection_anchor.pos;
3077 selection_end_anchor = true;
3078 } else {
3079 selection_start.line = selection_anchor.line;
3080 selection_start.tag = selection_anchor.tag;
3081 selection_start.pos = selection_anchor.pos;
3083 selection_end.line = caret.line;
3084 selection_end.tag = caret.tag;
3085 selection_end.pos = caret.pos;
3087 selection_end_anchor = false;
3091 SetSelectionVisible (!(selection_start == selection_end));
3094 internal void SetSelection(Line start, int start_pos, Line end, int end_pos) {
3095 if (selection_visible) {
3096 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3099 if ((end.line_no < start.line_no) || ((end == start) && (end_pos <= start_pos))) {
3100 selection_start.line = end;
3101 selection_start.tag = LineTag.FindTag(end, end_pos);
3102 selection_start.pos = end_pos;
3104 selection_end.line = start;
3105 selection_end.tag = LineTag.FindTag(start, start_pos);
3106 selection_end.pos = start_pos;
3108 selection_end_anchor = true;
3109 } else {
3110 selection_start.line = start;
3111 selection_start.tag = LineTag.FindTag(start, start_pos);
3112 selection_start.pos = start_pos;
3114 selection_end.line = end;
3115 selection_end.tag = LineTag.FindTag(end, end_pos);
3116 selection_end.pos = end_pos;
3118 selection_end_anchor = false;
3121 selection_anchor.line = start;
3122 selection_anchor.tag = selection_start.tag;
3123 selection_anchor.pos = start_pos;
3125 if (((start == end) && (start_pos == end_pos)) || start == null || end == null) {
3126 SetSelectionVisible (false);
3127 } else {
3128 SetSelectionVisible (true);
3129 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3133 internal void SetSelectionStart(Line start, int start_pos) {
3134 // Invalidate from the previous to the new start pos
3135 Invalidate(selection_start.line, selection_start.pos, start, start_pos);
3137 selection_start.line = start;
3138 selection_start.pos = start_pos;
3139 selection_start.tag = LineTag.FindTag(start, start_pos);
3141 selection_anchor.line = start;
3142 selection_anchor.pos = start_pos;
3143 selection_anchor.tag = selection_start.tag;
3145 selection_end_anchor = false;
3148 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
3149 SetSelectionVisible (true);
3150 } else {
3151 SetSelectionVisible (false);
3154 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3157 internal void SetSelectionStart(int character_index) {
3158 Line line;
3159 LineTag tag;
3160 int pos;
3162 if (character_index < 0) {
3163 return;
3166 CharIndexToLineTag(character_index, out line, out tag, out pos);
3167 SetSelectionStart(line, pos);
3170 internal void SetSelectionEnd(Line end, int end_pos) {
3172 if (end == selection_end.line && end_pos == selection_start.pos) {
3173 selection_anchor.line = selection_start.line;
3174 selection_anchor.tag = selection_start.tag;
3175 selection_anchor.pos = selection_start.pos;
3177 selection_end.line = selection_start.line;
3178 selection_end.tag = selection_start.tag;
3179 selection_end.pos = selection_start.pos;
3181 selection_end_anchor = false;
3182 } else if ((end.line_no < selection_anchor.line.line_no) || ((end == selection_anchor.line) && (end_pos <= selection_anchor.pos))) {
3183 selection_start.line = end;
3184 selection_start.tag = LineTag.FindTag(end, end_pos);
3185 selection_start.pos = end_pos;
3187 selection_end.line = selection_anchor.line;
3188 selection_end.tag = selection_anchor.tag;
3189 selection_end.pos = selection_anchor.pos;
3191 selection_end_anchor = true;
3192 } else {
3193 selection_start.line = selection_anchor.line;
3194 selection_start.tag = selection_anchor.tag;
3195 selection_start.pos = selection_anchor.pos;
3197 selection_end.line = end;
3198 selection_end.tag = LineTag.FindTag(end, end_pos);
3199 selection_end.pos = end_pos;
3201 selection_end_anchor = false;
3204 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
3205 SetSelectionVisible (true);
3206 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3207 } else {
3208 SetSelectionVisible (false);
3209 // ?? Do I need to invalidate here, tests seem to work without it, but I don't think they should :-s
3213 internal void SetSelectionEnd(int character_index) {
3214 Line line;
3215 LineTag tag;
3216 int pos;
3218 if (character_index < 0) {
3219 return;
3222 CharIndexToLineTag(character_index, out line, out tag, out pos);
3223 SetSelectionEnd(line, pos);
3226 internal void SetSelection(Line start, int start_pos) {
3227 if (selection_visible) {
3228 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3231 selection_start.line = start;
3232 selection_start.pos = start_pos;
3233 selection_start.tag = LineTag.FindTag(start, start_pos);
3235 selection_end.line = start;
3236 selection_end.tag = selection_start.tag;
3237 selection_end.pos = start_pos;
3239 selection_anchor.line = start;
3240 selection_anchor.tag = selection_start.tag;
3241 selection_anchor.pos = start_pos;
3243 selection_end_anchor = false;
3244 SetSelectionVisible (false);
3247 internal void InvalidateSelectionArea() {
3248 Invalidate (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3251 // Return the current selection, as string
3252 internal string GetSelection() {
3253 // We return String.Empty if there is no selection
3254 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3255 return string.Empty;
3258 if (!multiline || (selection_start.line == selection_end.line)) {
3259 return selection_start.line.text.ToString(selection_start.pos, selection_end.pos - selection_start.pos);
3260 } else {
3261 StringBuilder sb;
3262 int i;
3263 int start;
3264 int end;
3266 sb = new StringBuilder();
3267 start = selection_start.line.line_no;
3268 end = selection_end.line.line_no;
3270 sb.Append(selection_start.line.text.ToString(selection_start.pos, selection_start.line.text.Length - selection_start.pos) + Environment.NewLine);
3272 if ((start + 1) < end) {
3273 for (i = start + 1; i < end; i++) {
3274 sb.Append(GetLine(i).text.ToString() + Environment.NewLine);
3278 sb.Append(selection_end.line.text.ToString(0, selection_end.pos));
3280 return sb.ToString();
3284 internal void ReplaceSelection(string s, bool select_new) {
3285 int i;
3287 undo.BeginCompoundAction ();
3289 int selection_start_pos = LineTagToCharIndex (selection_start.line, selection_start.pos);
3290 SuspendRecalc ();
3292 // First, delete any selected text
3293 if ((selection_start.pos != selection_end.pos) || (selection_start.line != selection_end.line)) {
3294 if (!multiline || (selection_start.line == selection_end.line)) {
3295 undo.RecordDeleteChars(selection_start.line, selection_start.pos + 1, selection_end.pos - selection_start.pos);
3297 DeleteChars(selection_start.tag, selection_start.pos, selection_end.pos - selection_start.pos);
3299 // The tag might have been removed, we need to recalc it
3300 selection_start.tag = selection_start.line.FindTag(selection_start.pos);
3301 } else {
3302 int start;
3303 int end;
3305 start = selection_start.line.line_no;
3306 end = selection_end.line.line_no;
3308 undo.RecordDelete(selection_start.line, selection_start.pos + 1, selection_end.line, selection_end.pos);
3310 // Delete first line
3311 DeleteChars(selection_start.tag, selection_start.pos, selection_start.line.text.Length - selection_start.pos);
3313 // Delete last line
3314 DeleteChars(selection_end.line.tags, 0, selection_end.pos);
3316 start++;
3317 if (start < end) {
3318 for (i = end - 1; i >= start; i--) {
3319 Delete(i);
3323 // BIG FAT WARNING - selection_end.line might be stale due
3324 // to the above Delete() call. DONT USE IT before hitting the end of this method!
3326 // Join start and end
3327 Combine(selection_start.line.line_no, start);
3332 Insert(selection_start.line, selection_start.pos, false, s);
3333 undo.RecordInsertString (selection_start.line, selection_start.pos, s);
3334 ResumeRecalc (false);
3336 if (!select_new) {
3337 CharIndexToLineTag(selection_start_pos + s.Length, out selection_start.line,
3338 out selection_start.tag, out selection_start.pos);
3340 selection_end.line = selection_start.line;
3341 selection_end.pos = selection_start.pos;
3342 selection_end.tag = selection_start.tag;
3343 selection_anchor.line = selection_start.line;
3344 selection_anchor.pos = selection_start.pos;
3345 selection_anchor.tag = selection_start.tag;
3347 SetSelectionVisible (false);
3348 } else {
3349 CharIndexToLineTag(selection_start_pos, out selection_start.line,
3350 out selection_start.tag, out selection_start.pos);
3352 CharIndexToLineTag(selection_start_pos + s.Length, out selection_end.line,
3353 out selection_end.tag, out selection_end.pos);
3355 selection_anchor.line = selection_start.line;
3356 selection_anchor.pos = selection_start.pos;
3357 selection_anchor.tag = selection_start.tag;
3359 SetSelectionVisible (true);
3362 PositionCaret (selection_start.line, selection_start.pos);
3363 UpdateView (selection_start.line, selection_start.pos);
3365 undo.EndCompoundAction ();
3368 internal void CharIndexToLineTag(int index, out Line line_out, out LineTag tag_out, out int pos) {
3369 Line line;
3370 LineTag tag;
3371 int i;
3372 int chars;
3373 int start;
3375 chars = 0;
3377 for (i = 1; i <= lines; i++) {
3378 line = GetLine(i);
3380 start = chars;
3381 chars += line.text.Length + (line.soft_break ? 0 : crlf_size);
3383 if (index <= chars) {
3384 // we found the line
3385 tag = line.tags;
3387 while (tag != null) {
3388 if (index < (start + tag.start + tag.length)) {
3389 line_out = line;
3390 tag_out = LineTag.GetFinalTag (tag);
3391 pos = index - start;
3392 return;
3394 if (tag.next == null) {
3395 Line next_line;
3397 next_line = GetLine(line.line_no + 1);
3399 if (next_line != null) {
3400 line_out = next_line;
3401 tag_out = LineTag.GetFinalTag (next_line.tags);
3402 pos = 0;
3403 return;
3404 } else {
3405 line_out = line;
3406 tag_out = LineTag.GetFinalTag (tag);
3407 pos = line_out.text.Length;
3408 return;
3411 tag = tag.next;
3416 line_out = GetLine(lines);
3417 tag = line_out.tags;
3418 while (tag.next != null) {
3419 tag = tag.next;
3421 tag_out = tag;
3422 pos = line_out.text.Length;
3425 internal int LineTagToCharIndex(Line line, int pos) {
3426 int i;
3427 int length;
3429 // Count first and last line
3430 length = 0;
3432 // Count the lines in the middle
3434 for (i = 1; i < line.line_no; i++) {
3435 length += GetLine(i).text.Length + crlf_size;
3438 length += pos;
3440 return length;
3443 internal int SelectionLength() {
3444 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3445 return 0;
3448 if (!multiline || (selection_start.line == selection_end.line)) {
3449 return selection_end.pos - selection_start.pos;
3450 } else {
3451 int i;
3452 int start;
3453 int end;
3454 int length;
3456 // Count first and last line
3457 length = selection_start.line.text.Length - selection_start.pos + selection_end.pos + crlf_size;
3459 // Count the lines in the middle
3460 start = selection_start.line.line_no + 1;
3461 end = selection_end.line.line_no;
3463 if (start < end) {
3464 for (i = start; i < end; i++) {
3465 length += GetLine(i).text.Length + crlf_size;
3469 return length;
3476 /// <summary>Give it a Line number and it returns the Line object at with that line number</summary>
3477 internal Line GetLine(int LineNo) {
3478 Line line = document;
3480 while (line != sentinel) {
3481 if (LineNo == line.line_no) {
3482 return line;
3483 } else if (LineNo < line.line_no) {
3484 line = line.left;
3485 } else {
3486 line = line.right;
3490 return null;
3493 /// <summary>Retrieve the previous tag; walks line boundaries</summary>
3494 internal LineTag PreviousTag(LineTag tag) {
3495 Line l;
3497 if (tag.previous != null) {
3498 return tag.previous;
3501 // Next line
3502 if (tag.line.line_no == 1) {
3503 return null;
3506 l = GetLine(tag.line.line_no - 1);
3507 if (l != null) {
3508 LineTag t;
3510 t = l.tags;
3511 while (t.next != null) {
3512 t = t.next;
3514 return t;
3517 return null;
3520 /// <summary>Retrieve the next tag; walks line boundaries</summary>
3521 internal LineTag NextTag(LineTag tag) {
3522 Line l;
3524 if (tag.next != null) {
3525 return tag.next;
3528 // Next line
3529 l = GetLine(tag.line.line_no + 1);
3530 if (l != null) {
3531 return l.tags;
3534 return null;
3537 internal Line ParagraphStart(Line line) {
3538 while (line.soft_break) {
3539 line = GetLine(line.line_no - 1);
3541 return line;
3544 internal Line ParagraphEnd(Line line) {
3545 Line l;
3547 while (line.soft_break) {
3548 l = GetLine(line.line_no + 1);
3549 if ((l == null) || (!l.soft_break)) {
3550 break;
3552 line = l;
3554 return line;
3557 /// <summary>Give it a Y pixel coordinate and it returns the Line covering that Y coordinate</summary>
3558 internal Line GetLineByPixel(int y, bool exact) {
3559 Line line = document;
3560 Line last = null;
3562 while (line != sentinel) {
3563 last = line;
3564 if ((y >= line.Y) && (y < (line.Y+line.height))) {
3565 return line;
3566 } else if (y < line.Y) {
3567 line = line.left;
3568 } else {
3569 line = line.right;
3573 if (exact) {
3574 return null;
3576 return last;
3579 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3580 internal LineTag FindTag(int x, int y, out int index, bool exact) {
3581 Line line;
3582 LineTag tag;
3584 line = GetLineByPixel(y, exact);
3585 if (line == null) {
3586 index = 0;
3587 return null;
3589 tag = line.tags;
3591 // Alignment adjustment
3592 x += line.align_shift;
3594 while (true) {
3595 if (x >= tag.X && x < (tag.X+tag.width)) {
3596 int end;
3598 end = tag.start + tag.length - 1;
3600 for (int pos = tag.start; pos < end; pos++) {
3601 if (x < line.widths[pos]) {
3602 index = pos;
3603 return LineTag.GetFinalTag (tag);
3606 index=end;
3607 return LineTag.GetFinalTag (tag);
3609 if (tag.next != null) {
3610 tag = tag.next;
3611 } else {
3612 if (exact) {
3613 index = 0;
3614 return null;
3617 index = line.text.Length;
3618 return LineTag.GetFinalTag (tag);
3623 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3624 internal LineTag FindCursor(int x, int y, out int index) {
3625 Line line;
3626 LineTag tag;
3628 line = GetLineByPixel(y, false);
3629 tag = line.tags;
3631 // Adjust for alignment
3632 x -= line.align_shift;
3634 while (true) {
3635 if (x >= tag.X && x < (tag.X+tag.width)) {
3636 int end;
3638 end = tag.start + tag.length - 1;
3640 for (int pos = tag.start-1; pos < end; pos++) {
3641 // When clicking on a character, we position the cursor to whatever edge
3642 // of the character the click was closer
3643 if (x < (line.widths[pos] + ((line.widths[pos+1]-line.widths[pos])/2))) {
3644 index = pos;
3645 return tag;
3648 index=end;
3649 return tag;
3651 if (tag.next != null) {
3652 tag = tag.next;
3653 } else {
3654 index = line.text.Length;
3655 return tag;
3660 /// <summary>Format area of document in specified font and color</summary>
3661 /// <param name="start_pos">1-based start position on start_line</param>
3662 /// <param name="end_pos">1-based end position on end_line </param>
3663 internal void FormatText (Line start_line, int start_pos, Line end_line, int end_pos, Font font,
3664 SolidBrush color, SolidBrush back_color, FormatSpecified specified)
3666 Line l;
3668 // First, format the first line
3669 if (start_line != end_line) {
3670 // First line
3671 LineTag.FormatText(start_line, start_pos, start_line.text.Length - start_pos + 1, font, color, back_color, specified);
3673 // Format last line
3674 LineTag.FormatText(end_line, 1, end_pos, font, color, back_color, specified);
3676 // Now all the lines inbetween
3677 for (int i = start_line.line_no + 1; i < end_line.line_no; i++) {
3678 l = GetLine(i);
3679 LineTag.FormatText(l, 1, l.text.Length, font, color, back_color, specified);
3681 } else {
3682 // Special case, single line
3683 LineTag.FormatText(start_line, start_pos, end_pos - start_pos, font, color, back_color, specified);
3687 /// <summary>Re-format areas of the document in specified font and color</summary>
3688 /// <param name="start_pos">1-based start position on start_line</param>
3689 /// <param name="end_pos">1-based end position on end_line </param>
3690 /// <param name="font">Font specifying attributes</param>
3691 /// <param name="color">Color (or NULL) to apply</param>
3692 /// <param name="apply">Attributes from font and color to apply</param>
3693 internal void FormatText(Line start_line, int start_pos, Line end_line, int end_pos, FontDefinition attributes) {
3694 Line l;
3696 // First, format the first line
3697 if (start_line != end_line) {
3698 // First line
3699 LineTag.FormatText(start_line, start_pos, start_line.text.Length - start_pos + 1, attributes);
3701 // Format last line
3702 LineTag.FormatText(end_line, 1, end_pos - 1, attributes);
3704 // Now all the lines inbetween
3705 for (int i = start_line.line_no + 1; i < end_line.line_no; i++) {
3706 l = GetLine(i);
3707 LineTag.FormatText(l, 1, l.text.Length, attributes);
3709 } else {
3710 // Special case, single line
3711 LineTag.FormatText(start_line, start_pos, end_pos - start_pos, attributes);
3715 internal void RecalculateAlignments() {
3716 Line line;
3717 int line_no;
3719 line_no = 1;
3721 while (line_no <= lines) {
3722 line = GetLine(line_no);
3724 if (line != null) {
3725 switch (line.alignment) {
3726 case HorizontalAlignment.Left:
3727 line.align_shift = 0;
3728 break;
3729 case HorizontalAlignment.Center:
3730 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3731 break;
3732 case HorizontalAlignment.Right:
3733 line.align_shift = viewport_width - (int)line.widths[line.text.Length];
3734 break;
3738 line_no++;
3740 return;
3743 /// <summary>Calculate formatting for the whole document</summary>
3744 internal bool RecalculateDocument(Graphics g) {
3745 return RecalculateDocument(g, 1, this.lines, false);
3748 /// <summary>Calculate formatting starting at a certain line</summary>
3749 internal bool RecalculateDocument(Graphics g, int start) {
3750 return RecalculateDocument(g, start, this.lines, false);
3753 /// <summary>Calculate formatting within two given line numbers</summary>
3754 internal bool RecalculateDocument(Graphics g, int start, int end) {
3755 return RecalculateDocument(g, start, end, false);
3758 /// <summary>With optimize on, returns true if line heights changed</summary>
3759 internal bool RecalculateDocument(Graphics g, int start, int end, bool optimize) {
3760 Line line;
3761 int line_no;
3762 int Y;
3763 int new_width;
3764 bool changed;
3765 int shift;
3767 if (recalc_suspended > 0) {
3768 recalc_pending = true;
3769 recalc_start = Math.Min (recalc_start, start);
3770 recalc_end = Math.Max (recalc_end, end);
3771 recalc_optimize = optimize;
3772 return false;
3775 // Fixup the positions, they can go kinda nuts
3776 start = Math.Max (start, 1);
3777 end = Math.Min (end, lines);
3779 Y = GetLine(start).Y;
3780 line_no = start;
3781 new_width = 0;
3782 shift = this.lines;
3783 if (!optimize) {
3784 changed = true; // We always return true if we run non-optimized
3785 } else {
3786 changed = false;
3789 while (line_no <= (end + this.lines - shift)) {
3790 line = GetLine(line_no++);
3791 line.Y = Y;
3793 if (!calc_pass) {
3794 if (!optimize) {
3795 line.RecalculateLine(g, this);
3796 } else {
3797 if (line.recalc && line.RecalculateLine(g, this)) {
3798 changed = true;
3799 // If the height changed, all subsequent lines change
3800 end = this.lines;
3801 shift = this.lines;
3804 } else {
3805 if (!optimize) {
3806 line.RecalculatePasswordLine(g, this);
3807 } else {
3808 if (line.recalc && line.RecalculatePasswordLine(g, this)) {
3809 changed = true;
3810 // If the height changed, all subsequent lines change
3811 end = this.lines;
3812 shift = this.lines;
3817 if (line.widths[line.text.Length] > new_width) {
3818 new_width = (int)line.widths[line.text.Length];
3821 // Calculate alignment
3822 if (line.alignment != HorizontalAlignment.Left) {
3823 if (line.alignment == HorizontalAlignment.Center) {
3824 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3825 } else {
3826 line.align_shift = viewport_width - (int)line.widths[line.text.Length] - 1;
3830 Y += line.height;
3832 if (line_no > lines) {
3833 break;
3837 if (document_x != new_width) {
3838 document_x = new_width;
3839 if (WidthChanged != null) {
3840 WidthChanged(this, null);
3844 RecalculateAlignments();
3846 line = GetLine(lines);
3848 if (document_y != line.Y + line.height) {
3849 document_y = line.Y + line.height;
3850 if (HeightChanged != null) {
3851 HeightChanged(this, null);
3854 UpdateCaret();
3855 return changed;
3858 internal int Size() {
3859 return lines;
3862 private void owner_HandleCreated(object sender, EventArgs e) {
3863 RecalculateDocument(owner.CreateGraphicsInternal());
3864 AlignCaret();
3867 private void owner_VisibleChanged(object sender, EventArgs e) {
3868 if (owner.Visible) {
3869 RecalculateDocument(owner.CreateGraphicsInternal());
3873 internal static bool IsWordSeparator(char ch) {
3874 switch(ch) {
3875 case ' ':
3876 case '\t':
3877 case '(':
3878 case ')': {
3879 return true;
3882 default: {
3883 return false;
3887 internal int FindWordSeparator(Line line, int pos, bool forward) {
3888 int len;
3890 len = line.text.Length;
3892 if (forward) {
3893 for (int i = pos + 1; i < len; i++) {
3894 if (IsWordSeparator(line.Text[i])) {
3895 return i + 1;
3898 return len;
3899 } else {
3900 for (int i = pos - 1; i > 0; i--) {
3901 if (IsWordSeparator(line.Text[i - 1])) {
3902 return i;
3905 return 0;
3909 /* Search document for text */
3910 internal bool FindChars(char[] chars, Marker start, Marker end, out Marker result) {
3911 Line line;
3912 int line_no;
3913 int pos;
3914 int line_len;
3916 // Search for occurence of any char in the chars array
3917 result = new Marker();
3919 line = start.line;
3920 line_no = start.line.line_no;
3921 pos = start.pos;
3922 while (line_no <= end.line.line_no) {
3923 line_len = line.text.Length;
3924 while (pos < line_len) {
3925 for (int i = 0; i < chars.Length; i++) {
3926 if (line.text[pos] == chars[i]) {
3927 // Special case
3928 if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
3929 return false;
3932 result.line = line;
3933 result.pos = pos;
3934 return true;
3937 pos++;
3940 pos = 0;
3941 line_no++;
3942 line = GetLine(line_no);
3945 return false;
3948 // This version does not build one big string for searching, instead it handles
3949 // line-boundaries, which is faster and less memory intensive
3950 // FIXME - Depending on culture stuff we might have to create a big string and use culturespecific
3951 // search stuff and change it to accept and return positions instead of Markers (which would match
3952 // RichTextBox behaviour better but would be inconsistent with the rest of TextControl)
3953 internal bool Find(string search, Marker start, Marker end, out Marker result, RichTextBoxFinds options) {
3954 Marker last;
3955 string search_string;
3956 Line line;
3957 int line_no;
3958 int pos;
3959 int line_len;
3960 int current;
3961 bool word;
3962 bool word_option;
3963 bool ignore_case;
3964 bool reverse;
3965 char c;
3967 result = new Marker();
3968 word_option = ((options & RichTextBoxFinds.WholeWord) != 0);
3969 ignore_case = ((options & RichTextBoxFinds.MatchCase) == 0);
3970 reverse = ((options & RichTextBoxFinds.Reverse) != 0);
3972 line = start.line;
3973 line_no = start.line.line_no;
3974 pos = start.pos;
3975 current = 0;
3977 // Prep our search string, lowercasing it if we do case-independent matching
3978 if (ignore_case) {
3979 StringBuilder sb;
3980 sb = new StringBuilder(search);
3981 for (int i = 0; i < sb.Length; i++) {
3982 sb[i] = Char.ToLower(sb[i]);
3984 search_string = sb.ToString();
3985 } else {
3986 search_string = search;
3989 // We need to check if the character before our start position is a wordbreak
3990 if (word_option) {
3991 if (line_no == 1) {
3992 if ((pos == 0) || (IsWordSeparator(line.text[pos - 1]))) {
3993 word = true;
3994 } else {
3995 word = false;
3997 } else {
3998 if (pos > 0) {
3999 if (IsWordSeparator(line.text[pos - 1])) {
4000 word = true;
4001 } else {
4002 word = false;
4004 } else {
4005 // Need to check the end of the previous line
4006 Line prev_line;
4008 prev_line = GetLine(line_no - 1);
4009 if (prev_line.soft_break) {
4010 if (IsWordSeparator(prev_line.text[prev_line.text.Length - 1])) {
4011 word = true;
4012 } else {
4013 word = false;
4015 } else {
4016 word = true;
4020 } else {
4021 word = false;
4024 // To avoid duplication of this loop with reverse logic, we search
4025 // through the document, remembering the last match and when returning
4026 // report that last remembered match
4028 last = new Marker();
4029 last.height = -1; // Abused - we use it to track change
4031 while (line_no <= end.line.line_no) {
4032 if (line_no != end.line.line_no) {
4033 line_len = line.text.Length;
4034 } else {
4035 line_len = end.pos;
4038 while (pos < line_len) {
4039 if (word_option && (current == search_string.Length)) {
4040 if (IsWordSeparator(line.text[pos])) {
4041 if (!reverse) {
4042 goto FindFound;
4043 } else {
4044 last = result;
4045 current = 0;
4047 } else {
4048 current = 0;
4052 if (ignore_case) {
4053 c = Char.ToLower(line.text[pos]);
4054 } else {
4055 c = line.text[pos];
4058 if (c == search_string[current]) {
4059 if (current == 0) {
4060 result.line = line;
4061 result.pos = pos;
4063 if (!word_option || (word_option && (word || (current > 0)))) {
4064 current++;
4067 if (!word_option && (current == search_string.Length)) {
4068 if (!reverse) {
4069 goto FindFound;
4070 } else {
4071 last = result;
4072 current = 0;
4075 } else {
4076 current = 0;
4078 pos++;
4080 if (!word_option) {
4081 continue;
4084 if (IsWordSeparator(c)) {
4085 word = true;
4086 } else {
4087 word = false;
4091 if (word_option) {
4092 // Mark that we just saw a word boundary
4093 if (!line.soft_break) {
4094 word = true;
4097 if (current == search_string.Length) {
4098 if (word) {
4099 if (!reverse) {
4100 goto FindFound;
4101 } else {
4102 last = result;
4103 current = 0;
4105 } else {
4106 current = 0;
4111 pos = 0;
4112 line_no++;
4113 line = GetLine(line_no);
4116 if (reverse) {
4117 if (last.height != -1) {
4118 result = last;
4119 return true;
4123 return false;
4125 FindFound:
4126 if (!reverse) {
4127 // if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
4128 // return false;
4129 // }
4130 return true;
4133 result = last;
4134 return true;
4138 /* Marker stuff */
4139 internal void GetMarker(out Marker mark, bool start) {
4140 mark = new Marker();
4142 if (start) {
4143 mark.line = GetLine(1);
4144 mark.tag = mark.line.tags;
4145 mark.pos = 0;
4146 } else {
4147 mark.line = GetLine(lines);
4148 mark.tag = mark.line.tags;
4149 while (mark.tag.next != null) {
4150 mark.tag = mark.tag.next;
4152 mark.pos = mark.line.text.Length;
4155 #endregion // Internal Methods
4157 #region Events
4158 internal event EventHandler CaretMoved;
4159 internal event EventHandler WidthChanged;
4160 internal event EventHandler HeightChanged;
4161 internal event EventHandler LengthChanged;
4162 #endregion // Events
4164 #region Administrative
4165 public IEnumerator GetEnumerator() {
4166 // FIXME
4167 return null;
4170 public override bool Equals(object obj) {
4171 if (obj == null) {
4172 return false;
4175 if (!(obj is Document)) {
4176 return false;
4179 if (obj == this) {
4180 return true;
4183 if (ToString().Equals(((Document)obj).ToString())) {
4184 return true;
4187 return false;
4190 public override int GetHashCode() {
4191 return document_id;
4194 public override string ToString() {
4195 return "document " + this.document_id;
4197 #endregion // Administrative
4200 internal class ImageTag : LineTag {
4202 internal Image image;
4204 internal ImageTag (Line line, int start, Image image) : base (line, start)
4206 this.image = image;
4209 internal override SizeF SizeOfPosition (Graphics dc, int pos)
4211 return image.Size;
4214 internal override int MaxHeight ()
4216 return image.Height;
4219 internal override void Draw (Graphics dc, Brush brush, float x, float y, int start, int end)
4221 dc.DrawImage (image, x, y);
4225 internal class LineTag {
4226 #region Local Variables;
4227 // Payload; formatting
4228 internal Font font; // System.Drawing.Font object for this tag
4229 internal SolidBrush color; // The font color for this tag
4231 // In 2.0 tags can have background colours. I'm not going to #ifdef
4232 // at this level though since I want to reduce code paths
4233 internal SolidBrush back_color;
4235 // Payload; text
4236 internal int start; // start, in chars; index into Line.text
4237 internal bool r_to_l; // Which way is the font
4239 // Drawing support
4240 internal int height; // Height in pixels of the text this tag describes
4241 internal int X; // X location of the text this tag describes
4243 internal int ascent; // Ascent of the font for this tag
4244 internal int shift; // Shift down for this tag, to stay on baseline
4246 // Administrative
4247 internal Line line; // The line we're on
4248 internal LineTag next; // Next tag on the same line
4249 internal LineTag previous; // Previous tag on the same line
4250 #endregion;
4252 #region Constructors
4253 internal LineTag(Line line, int start) {
4254 this.line = line;
4255 this.start = start;
4256 this.X = 0;
4258 #endregion // Constructors
4260 #region Internal Methods
4262 public int end {
4263 get { return start + length; }
4266 public float width {
4267 get {
4268 if (length == 0)
4269 return 0;
4270 return line.widths [start + length - 1];
4274 public int length {
4275 get {
4276 int res = 0;
4277 if (next != null)
4278 res = next.start - start;
4279 else
4280 res = line.text.Length - (start - 1);
4282 return res > 0 ? res : 0;
4286 internal virtual SizeF SizeOfPosition (Graphics dc, int pos)
4288 return dc.MeasureString (line.text.ToString (pos, 1), font, 10000, Document.string_format);
4291 internal virtual int MaxHeight ()
4293 return font.Height;
4296 internal virtual void Draw (Graphics dc, Brush brush, float x, float y, int start, int end)
4298 dc.DrawString (line.text.ToString (start, end), font, brush, x, y, StringFormat.GenericTypographic);
4301 ///<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>
4302 internal LineTag Break(int pos) {
4304 LineTag new_tag;
4306 // Sanity
4307 if (pos == this.start) {
4308 return this;
4309 } else if (pos >= (start + length)) {
4310 return null;
4313 new_tag = new LineTag(line, pos);
4314 new_tag.CopyFormattingFrom (this);
4316 new_tag.next = this.next;
4317 this.next = new_tag;
4318 new_tag.previous = this;
4320 if (new_tag.next != null) {
4321 new_tag.next.previous = new_tag;
4324 return new_tag;
4327 public string Text ()
4329 return line.text.ToString (start - 1, length);
4332 public void CopyFormattingFrom (LineTag other)
4334 height = other.height;
4335 font = other.font;
4336 color = other.color;
4337 back_color = other.back_color;
4340 ///<summary>Create new font and brush from existing font and given new attributes. Returns true if fontheight changes</summary>
4341 internal static bool GenerateTextFormat(Font font_from, SolidBrush color_from, FontDefinition attributes, out Font new_font, out SolidBrush new_color) {
4342 float size;
4343 string face;
4344 FontStyle style;
4345 GraphicsUnit unit;
4347 if (attributes.font_obj == null) {
4348 size = font_from.SizeInPoints;
4349 unit = font_from.Unit;
4350 face = font_from.Name;
4351 style = font_from.Style;
4353 if (attributes.face != null) {
4354 face = attributes.face;
4357 if (attributes.size != 0) {
4358 size = attributes.size;
4361 style |= attributes.add_style;
4362 style &= ~attributes.remove_style;
4364 // Create new font
4365 new_font = new Font(face, size, style, unit);
4366 } else {
4367 new_font = attributes.font_obj;
4370 // Create 'new' color brush
4371 if (attributes.color != Color.Empty) {
4372 new_color = new SolidBrush(attributes.color);
4373 } else {
4374 new_color = color_from;
4377 if (new_font.Height == font_from.Height) {
4378 return false;
4380 return true;
4383 /// <summary>Applies 'font' and 'brush' to characters starting at 'start' for 'length' chars;
4384 /// Removes any previous tags overlapping the same area;
4385 /// returns true if lineheight has changed</summary>
4386 /// <param name="start">1-based character position on line</param>
4387 internal static bool FormatText(Line line, int start, int length, Font font, SolidBrush color, SolidBrush back_color, FormatSpecified specified)
4389 LineTag tag;
4390 LineTag start_tag;
4391 LineTag end_tag;
4392 int end;
4393 bool retval = false; // Assume line-height doesn't change
4395 // Too simple?
4396 if (((FormatSpecified.Font & specified) == FormatSpecified.Font) && font.Height != line.height) {
4397 retval = true;
4399 line.recalc = true; // This forces recalculation of the line in RecalculateDocument
4401 // A little sanity, not sure if it's needed, might be able to remove for speed
4402 if (length > line.text.Length) {
4403 length = line.text.Length;
4406 tag = line.tags;
4407 end = start + length;
4409 // Common special case
4410 if ((start == 1) && (length == tag.length)) {
4411 tag.ascent = 0;
4412 SetFormat (tag, font, color, back_color, specified);
4413 return retval;
4416 start_tag = FindTag (line, start);
4418 tag = start_tag.Break (start);
4420 while (tag != null && tag.end <= end) {
4421 SetFormat (tag, font, color, back_color, specified);
4422 tag = tag.next;
4425 if (end != line.text.Length) {
4426 /// Now do the last tag
4427 end_tag = FindTag (line, end);
4429 if (end_tag != null) {
4430 end_tag.Break (end);
4431 SetFormat (end_tag, font, color, back_color, specified);
4435 return retval;
4438 private static void SetFormat (LineTag tag, Font font, SolidBrush color, SolidBrush back_color, FormatSpecified specified)
4440 if ((FormatSpecified.Font & specified) == FormatSpecified.Font)
4441 tag.font = font;
4442 if ((FormatSpecified.Color & specified) == FormatSpecified.Color)
4443 tag.color = color;
4444 if ((FormatSpecified.BackColor & specified) == FormatSpecified.BackColor) {
4445 tag.back_color = back_color;
4447 // Console.WriteLine ("setting format: {0} {1} new color {2}", color.Color, specified, tag.color.Color);
4450 /// <summary>Applies font attributes specified to characters starting at 'start' for 'length' chars;
4451 /// Breaks tags at start and end point, keeping middle tags with altered attributes.
4452 /// Returns true if lineheight has changed</summary>
4453 /// <param name="start">1-based character position on line</param>
4454 internal static bool FormatText(Line line, int start, int length, FontDefinition attributes) {
4455 LineTag tag;
4456 LineTag start_tag;
4457 LineTag end_tag;
4458 bool retval = false; // Assume line-height doesn't change
4460 line.recalc = true; // This forces recalculation of the line in RecalculateDocument
4462 // A little sanity, not sure if it's needed, might be able to remove for speed
4463 if (length > line.text.Length) {
4464 length = line.text.Length;
4467 tag = line.tags;
4469 // Common special case
4470 if ((start == 1) && (length == tag.length)) {
4471 tag.ascent = 0;
4472 GenerateTextFormat(tag.font, tag.color, attributes, out tag.font, out tag.color);
4473 return retval;
4476 start_tag = FindTag(line, start);
4478 if (start_tag == null) {
4479 if (length == 0) {
4480 // We are 'starting' after all valid tags; create a new tag with the right attributes
4481 start_tag = FindTag(line, line.text.Length - 1);
4482 start_tag.next = new LineTag(line, line.text.Length + 1);
4483 start_tag.next.CopyFormattingFrom (start_tag);
4484 start_tag.next.previous = start_tag;
4485 start_tag = start_tag.next;
4486 } else {
4487 throw new Exception(String.Format("Could not find start_tag in document at line {0} position {1}", line.line_no, start));
4489 } else {
4490 start_tag = start_tag.Break(start);
4493 end_tag = FindTag(line, start + length);
4494 if (end_tag != null) {
4495 end_tag = end_tag.Break(start + length);
4498 // start_tag or end_tag might be null; we're cool with that
4499 // we now walk from start_tag to end_tag, applying new attributes
4500 tag = start_tag;
4501 while ((tag != null) && tag != end_tag) {
4502 if (LineTag.GenerateTextFormat(tag.font, tag.color, attributes, out tag.font, out tag.color)) {
4503 retval = true;
4505 tag = tag.next;
4507 return retval;
4511 /// <summary>Finds the tag that describes the character at position 'pos' on 'line'</summary>
4512 internal static LineTag FindTag(Line line, int pos) {
4513 LineTag tag = line.tags;
4515 // Beginning of line is a bit special
4516 if (pos == 0) {
4517 // Not sure if we should get the final tag here
4518 return tag;
4521 while (tag != null) {
4522 if ((tag.start <= pos) && (pos <= tag.end)) {
4523 return GetFinalTag (tag);
4526 tag = tag.next;
4529 return null;
4532 // There can be multiple tags at the same position, we want to make
4533 // sure we are using the very last tag at the given position
4534 internal static LineTag GetFinalTag (LineTag tag)
4536 LineTag res = tag;
4538 while (res.next != null && res.next.length == 0)
4539 res = res.next;
4540 return res;
4543 /// <summary>Combines 'this' tag with 'other' tag</summary>
4544 internal bool Combine(LineTag other) {
4545 if (!this.Equals(other)) {
4546 return false;
4549 this.next = other.next;
4550 if (this.next != null) {
4551 this.next.previous = this;
4554 return true;
4558 /// <summary>Remove 'this' tag ; to be called when formatting is to be removed</summary>
4559 internal bool Remove() {
4560 if ((this.start == 1) && (this.next == null)) {
4561 // We cannot remove the only tag
4562 return false;
4564 if (this.start != 1) {
4565 this.previous.next = this.next;
4566 this.next.previous = this.previous;
4567 } else {
4568 this.next.start = 1;
4569 this.line.tags = this.next;
4570 this.next.previous = null;
4572 return true;
4576 /// <summary>Checks if 'this' tag describes the same formatting options as 'obj'</summary>
4577 public override bool Equals(object obj) {
4578 LineTag other;
4580 if (obj == null) {
4581 return false;
4584 if (!(obj is LineTag)) {
4585 return false;
4588 if (obj == this) {
4589 return true;
4592 other = (LineTag)obj;
4594 if (this.font.Equals(other.font) && this.color.Equals(other.color)) { // FIXME add checking for things like link or type later
4595 return true;
4598 return false;
4601 public override int GetHashCode() {
4602 return base.GetHashCode ();
4605 public override string ToString() {
4606 if (length > 0)
4607 return "Tag starts at index " + this.start + " length " + this.length + " text: " + Text () + "Font " + this.font.ToString();
4608 return "Zero Lengthed tag at index " + this.start;
4611 #endregion // Internal Methods
4614 internal class UndoClass {
4615 internal enum ActionType {
4616 InsertChar,
4617 InsertString,
4618 DeleteChar,
4619 DeleteChars,
4620 CursorMove,
4621 Mark,
4622 CompoundBegin,
4623 CompoundEnd,
4626 internal class Action {
4627 internal ActionType type;
4628 internal int line_no;
4629 internal int pos;
4630 internal object data;
4633 #region Local Variables
4634 private Document document;
4635 private Stack undo_actions;
4636 private Stack redo_actions;
4638 private int undo_levels;
4639 private int redo_levels;
4640 private int caret_line;
4641 private int caret_pos;
4642 #endregion // Local Variables
4644 #region Constructors
4645 internal UndoClass(Document doc) {
4646 document = doc;
4647 undo_actions = new Stack(50);
4648 redo_actions = new Stack(50);
4650 #endregion // Constructors
4652 #region Properties
4653 internal int UndoLevels {
4654 get {
4655 return undo_levels;
4659 internal int RedoLevels {
4660 get {
4661 return redo_levels;
4665 internal string UndoName {
4666 get {
4667 Action action;
4669 action = (Action)undo_actions.Peek();
4671 if (action.type == ActionType.CompoundEnd)
4672 return (string) action.data;
4674 switch(action.type) {
4675 case ActionType.InsertChar: {
4676 Locale.GetText("Insert character");
4677 break;
4680 case ActionType.DeleteChar: {
4681 Locale.GetText("Delete character");
4682 break;
4685 case ActionType.InsertString: {
4686 Locale.GetText("Insert string");
4687 break;
4690 case ActionType.DeleteChars: {
4691 Locale.GetText("Delete string");
4692 break;
4695 case ActionType.CursorMove: {
4696 Locale.GetText("Cursor move");
4697 break;
4700 return null;
4704 internal string RedoName() {
4705 return null;
4707 #endregion // Properties
4709 #region Internal Methods
4710 internal void Clear() {
4711 undo_actions.Clear();
4712 redo_actions.Clear();
4713 undo_levels = 0;
4714 redo_levels = 0;
4717 internal void Undo() {
4718 Action action;
4719 int compound_stack = 0;
4721 if (undo_actions.Count == 0) {
4722 return;
4727 do {
4728 action = (Action)undo_actions.Pop();
4730 // Put onto redo stack
4731 redo_actions.Push(action);
4733 // Do the thing
4734 switch(action.type) {
4735 case ActionType.CompoundEnd:
4736 compound_stack++;
4737 break;
4739 case ActionType.CompoundBegin:
4740 compound_stack--;
4741 undo_levels--;
4742 redo_levels++;
4743 break;
4745 case ActionType.InsertString:
4746 document.DeleteMultiline (document.GetLine (action.line_no),
4747 action.pos, ((string) action.data).Length + 1);
4748 break;
4750 case ActionType.InsertChar: {
4751 // FIXME - implement me
4752 break;
4755 case ActionType.DeleteChars: {
4756 this.Insert(document.GetLine(action.line_no), action.pos, (Line)action.data);
4757 Undo(); // Grab the cursor location
4758 break;
4761 case ActionType.CursorMove: {
4762 document.caret.line = document.GetLine(action.line_no);
4763 if (document.caret.line == null) {
4764 Undo();
4765 break;
4768 document.caret.tag = document.caret.line.FindTag(action.pos);
4769 document.caret.pos = action.pos;
4770 document.caret.height = document.caret.tag.height;
4772 if (document.owner.IsHandleCreated) {
4773 XplatUI.DestroyCaret(document.owner.Handle);
4774 XplatUI.CreateCaret(document.owner.Handle, 2, document.caret.height);
4775 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);
4777 document.DisplayCaret ();
4780 // FIXME - enable call
4781 //if (document.CaretMoved != null) document.CaretMoved(this, EventArgs.Empty);
4782 break;
4785 } while (compound_stack > 0);
4788 internal void Redo() {
4789 if (redo_actions.Count == 0) {
4790 return;
4793 #endregion // Internal Methods
4795 #region Private Methods
4797 public void BeginCompoundAction ()
4799 Action cb = new Action ();
4800 cb.type = ActionType.CompoundBegin;
4802 undo_actions.Push (cb);
4805 public void EndCompoundAction ()
4807 Action ce = new Action ();
4808 ce.type = ActionType.CompoundEnd;
4810 undo_actions.Push (ce);
4811 undo_levels++;
4814 // pos = 1-based
4815 public void RecordDeleteChars(Line line, int pos, int length) {
4816 RecordDelete(line, pos, line, pos + length - 1);
4819 // start_pos, end_pos = 1 based
4820 public void RecordDelete(Line start_line, int start_pos, Line end_line, int end_pos) {
4821 Line l;
4822 Action a;
4824 l = Duplicate(start_line, start_pos, end_line, end_pos);
4826 a = new Action();
4827 a.type = ActionType.DeleteChars;
4828 a.data = l;
4829 a.line_no = start_line.line_no;
4830 a.pos = start_pos - 1;
4832 // Record the cursor position before, since the actions will occur in reverse order
4833 RecordCursor();
4834 undo_actions.Push(a);
4837 public void RecordInsertString (Line line, int pos, string str)
4839 Action a = new Action ();
4841 a.type = ActionType.InsertString;
4842 a.data = str;
4843 a.line_no = line.line_no;
4844 a.pos = pos;
4846 undo_actions.Push (a);
4849 public void RecordCursor() {
4850 if (document.caret.line == null) {
4851 return;
4854 RecordCursor(document.caret.line, document.caret.pos);
4857 public void RecordCursor(Line line, int pos) {
4858 Action a;
4860 if ((line.line_no == caret_line) && (pos == caret_pos)) {
4861 return;
4864 caret_line = line.line_no;
4865 caret_pos = pos;
4867 a = new Action();
4868 a.type = ActionType.CursorMove;
4869 a.line_no = line.line_no;
4870 a.pos = pos;
4872 undo_actions.Push(a);
4875 // start_pos = 1-based
4876 // end_pos = 1-based
4877 public Line Duplicate(Line start_line, int start_pos, Line end_line, int end_pos) {
4878 Line ret;
4879 Line line;
4880 Line current;
4881 LineTag tag;
4882 LineTag current_tag;
4883 int start;
4884 int end;
4885 int tag_start;
4887 line = new Line();
4888 ret = line;
4890 for (int i = start_line.line_no; i <= end_line.line_no; i++) {
4891 current = document.GetLine(i);
4893 if (start_line.line_no == i) {
4894 start = start_pos;
4895 } else {
4896 start = 1;
4899 if (end_line.line_no == i) {
4900 end = end_pos;
4901 } else {
4902 end = current.text.Length;
4905 // Text for the tag
4906 line.text = new StringBuilder(current.text.ToString(start - 1, end - start + 1));
4908 // Copy tags from start to start+length onto new line
4909 current_tag = current.FindTag(start - 1);
4910 while ((current_tag != null) && (current_tag.start < end)) {
4911 if ((current_tag.start <= start) && (start < (current_tag.start + current_tag.length))) {
4912 // start tag is within this tag
4913 tag_start = start;
4914 } else {
4915 tag_start = current_tag.start;
4918 tag = new LineTag(line, tag_start - start + 1);
4919 tag.CopyFormattingFrom (current_tag);
4921 current_tag = current_tag.next;
4923 // Add the new tag to the line
4924 if (line.tags == null) {
4925 line.tags = tag;
4926 } else {
4927 LineTag tail;
4928 tail = line.tags;
4930 while (tail.next != null) {
4931 tail = tail.next;
4933 tail.next = tag;
4934 tag.previous = tail;
4938 if ((i + 1) <= end_line.line_no) {
4939 line.soft_break = current.soft_break;
4941 // Chain them (we use right/left as next/previous)
4942 line.right = new Line();
4943 line.right.left = line;
4944 line = line.right;
4948 return ret;
4951 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
4952 internal void Insert(Line line, int pos, Line insert) {
4953 Line current;
4954 LineTag tag;
4955 int offset;
4956 int lines;
4957 Line first;
4959 // Handle special case first
4960 if (insert.right == null) {
4962 // Single line insert
4963 document.Split(line, pos);
4965 if (insert.tags == null) {
4966 return; // Blank line
4969 //Insert our tags at the end
4970 tag = line.tags;
4972 while (tag.next != null) {
4973 tag = tag.next;
4976 offset = tag.start + tag.length - 1;
4978 tag.next = insert.tags;
4979 line.text.Insert(offset, insert.text.ToString());
4981 // Adjust start locations
4982 tag = tag.next;
4983 while (tag != null) {
4984 tag.start += offset;
4985 tag.line = line;
4986 tag = tag.next;
4988 // Put it back together
4989 document.Combine(line.line_no, line.line_no + 1);
4990 document.UpdateView(line, pos);
4991 return;
4994 first = line;
4995 lines = 1;
4996 current = insert;
4997 while (current != null) {
4998 if (current == insert) {
4999 // Inserting the first line we split the line (and make space)
5000 document.Split(line, pos);
5001 //Insert our tags at the end of the line
5002 tag = line.tags;
5004 if (tag != null) {
5005 while (tag.next != null) {
5006 tag = tag.next;
5008 offset = tag.start + tag.length - 1;
5009 tag.next = current.tags;
5010 tag.next.previous = tag;
5012 tag = tag.next;
5014 } else {
5015 offset = 0;
5016 line.tags = current.tags;
5017 line.tags.previous = null;
5018 tag = line.tags;
5020 } else {
5021 document.Split(line.line_no, 0);
5022 offset = 0;
5023 line.tags = current.tags;
5024 line.tags.previous = null;
5025 tag = line.tags;
5027 // Adjust start locations and line pointers
5028 while (tag != null) {
5029 tag.start += offset;
5030 tag.line = line;
5031 tag = tag.next;
5034 line.text.Insert(offset, current.text.ToString());
5035 line.Grow(line.text.Length);
5037 line.recalc = true;
5038 line = document.GetLine(line.line_no + 1);
5040 // FIXME? Test undo of line-boundaries
5041 if ((current.right == null) && (current.tags.length != 0)) {
5042 document.Combine(line.line_no - 1, line.line_no);
5044 current = current.right;
5045 lines++;
5049 // Recalculate our document
5050 document.UpdateView(first, lines, pos);
5051 return;
5053 #endregion // Private Methods