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:
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
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)
23 // Peter Bartok pbartok@novell.com
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
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
49 using System.Collections;
51 using System.Drawing.Text;
54 namespace System.Windows.Forms {
55 internal enum LineColor {
60 internal enum CaretSelection {
61 Position, // Selection=Caret
62 Word, // Selection=Word under caret
63 Line // Selection=Line under caret
66 internal class FontDefinition {
69 internal FontStyle add_style;
70 internal FontStyle remove_style;
72 internal Font font_obj;
74 internal FontDefinition() {
81 internal enum CaretDirection {
82 CharForward, // Move a char to the right
83 CharBack, // Move a char to the left
84 LineUp, // Move a line up
85 LineDown, // Move a line down
86 Home, // Move to the beginning of the line
87 End, // Move to the end of the line
88 PgUp, // Move one page up
89 PgDn, // Move one page down
90 CtrlPgUp, // Move caret to the first visible char in the viewport
91 CtrlPgDn, // Move caret to the last visible char in the viewport
92 CtrlHome, // Move to the beginning of the document
93 CtrlEnd, // Move to the end of the document
94 WordBack, // Move to the beginning of the previous word (or beginning of line)
95 WordForward, // Move to the beginning of the next word (or end of line)
96 SelectionStart, // Move to the beginning of the current selection
97 SelectionEnd, // Move to the end of the current selection
98 CharForwardNoWrap, // Move a char forward, but don't wrap onto the next line
99 CharBackNoWrap // Move a char backward, but don't wrap onto the previous line
102 // Being cloneable should allow for nice line and document copies...
103 internal class Line : ICloneable, IComparable {
104 #region Local Variables
105 // Stuff that matters for our line
106 internal StringBuilder text; // Characters for the line
107 internal float[] widths; // Width of each character; always one larger than text.Length
108 internal int space; // Number of elements in text and widths
109 internal int line_no; // Line number
110 internal LineTag tags; // Tags describing the text
111 internal int Y; // Baseline
112 internal int height; // Height of the line (height of tallest tag)
113 internal int ascent; // Ascent of the line (ascent of the tallest tag)
114 internal HorizontalAlignment alignment; // Alignment of the line
115 internal int align_shift; // Pixel shift caused by the alignment
116 internal bool soft_break; // Tag is 'broken soft' and continuation from previous line
117 internal int indent; // Left indent for the first line
118 internal int hanging_indent; // Hanging indent (left indent for all but the first line)
119 internal int right_indent; // Right indent for all lines
122 // Stuff that's important for the tree
123 internal Line parent; // Our parent line
124 internal Line left; // Line with smaller line number
125 internal Line right; // Line with higher line number
126 internal LineColor color; // We're doing a black/red tree. this is the node color
127 internal int DEFAULT_TEXT_LEN; //
128 internal static StringFormat string_format; // For calculating widths/heights
129 internal bool recalc; // Line changed
130 #endregion // Local Variables
134 color = LineColor.Red;
141 alignment = HorizontalAlignment.Left;
143 if (string_format == null) {
144 string_format = new StringFormat(StringFormat.GenericTypographic);
145 string_format.Trimming = StringTrimming.None;
146 string_format.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;
150 internal Line(int LineNo, string Text, Font font, Brush color) : this() {
151 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
153 text = new StringBuilder(Text, space);
156 widths = new float[space + 1];
157 tags = new LineTag(this, 1, text.Length);
162 internal Line(int LineNo, string Text, HorizontalAlignment align, Font font, Brush color) : this() {
163 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
165 text = new StringBuilder(Text, space);
169 widths = new float[space + 1];
170 tags = new LineTag(this, 1, text.Length);
175 internal Line(int LineNo, string Text, LineTag tag) : this() {
176 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
178 text = new StringBuilder(Text, space);
181 widths = new float[space + 1];
185 #endregion // Constructors
187 #region Internal Properties
188 internal int Indent {
199 internal int HangingIndent {
201 return hanging_indent;
205 hanging_indent = value;
210 internal int RightIndent {
216 right_indent = value;
222 internal int Height {
232 internal int LineNo {
242 internal string Text {
244 return text.ToString();
248 text = new StringBuilder(value, value.Length > DEFAULT_TEXT_LEN ? value.Length : DEFAULT_TEXT_LEN);
252 internal HorizontalAlignment Alignment {
258 if (alignment != value) {
265 internal StringBuilder Text {
275 #endregion // Internal Properties
277 #region Internal Methods
278 // Make sure we always have enoughs space in text and widths
279 internal void Grow(int minimum) {
283 length = text.Length;
285 if ((length + minimum) > space) {
286 // We need to grow; double the size
288 if ((length + minimum) > (space * 2)) {
289 new_widths = new float[length + minimum * 2 + 1];
290 space = length + minimum * 2;
292 new_widths = new float[space * 2 + 1];
295 widths.CopyTo(new_widths, 0);
301 internal void Streamline(int lines) {
308 // Catch what the loop below wont; eliminate 0 length
309 // tags, but only if there are other tags after us
310 while ((current.length == 0) && (next != null)) {
312 tags.previous = null;
321 while (next != null) {
322 // Take out 0 length tags unless it's the last tag in the document
323 if (next.length == 0) {
324 if ((next.next != null) || (line_no != lines)) {
325 current.next = next.next;
326 if (current.next != null) {
327 current.next.previous = current;
333 if (current.Combine(next)) {
338 current = current.next;
343 /// <summary> Find the tag on a line based on the character position, pos is 0-based</summary>
344 internal LineTag FindTag(int pos) {
353 if (pos >= text.Length) {
354 pos = text.Length - 1;
357 while (tag != null) {
358 if (((tag.start - 1) <= pos) && (pos < (tag.start + tag.length - 1))) {
359 return LineTag.GetFinalTag (tag);
367 /// Recalculate a single line using the same char for every character in the line
370 internal bool RecalculatePasswordLine(Graphics g, Document doc) {
379 len = this.text.Length;
389 w = g.MeasureString(doc.password_char, tags.font, 10000, string_format).Width;
391 if (this.height != (int)tag.font.Height) {
397 this.height = (int)tag.font.Height;
398 tag.height = this.height;
400 XplatUI.GetFontMetrics(g, tag.font, out tag.ascent, out descent);
401 this.ascent = tag.ascent;
406 widths[pos] = widths[pos-1] + w;
413 /// Go through all tags on a line and recalculate all size-related values;
414 /// returns true if lineheight changed
416 internal bool RecalculateLine(Graphics g, Document doc) {
430 len = this.text.Length;
432 prev_height = this.height; // For drawing optimization calculations
433 this.height = 0; // Reset line height
434 this.ascent = 0; // Reset the ascent for the line
438 if (this.soft_break) {
439 widths[0] = hanging_indent;
452 size = g.MeasureString(this.text.ToString(pos, 1), tag.font, 10000, string_format);
454 while (tag.length == 0) { // We should always have tags after a tag.length==0 unless len==0
457 if (tag.previous != null) {
458 tag.X = tag.previous.X;
460 tag.X = (int)widths[pos];
469 if (Char.IsWhiteSpace(text[pos])) {
471 wrap_width = tag.width + w;
475 if ((wrap_pos > 0) && (wrap_pos != len) && (widths[pos] + w) + 27 > (doc.viewport_width - this.right_indent)) {
477 tag.width = wrap_width;
478 doc.Split(this, tag, pos, true);
479 len = this.text.Length;
485 // Contract all soft lines that follow back into our line
491 widths[pos] = widths[pos-1] + w;
494 line = doc.GetLine(this.line_no + 1);
495 if ((line != null) && (line.soft_break)) {
496 // Pull the previous line back into this one
497 doc.Combine(this.line_no, this.line_no + 1);
498 len = this.text.Length;
504 if (pos == (tag.start-1 + tag.length)) {
505 // We just found the end of our current tag
506 tag.height = (int)tag.font.Height;
508 // Check if we're the tallest on the line (so far)
509 if (tag.height > this.height) {
510 this.height = tag.height; // Yep; make sure the line knows
513 if (tag.ascent == 0) {
516 XplatUI.GetFontMetrics(g, tag.font, out tag.ascent, out descent);
519 if (tag.ascent > this.ascent) {
522 // We have a tag that has a taller ascent than the line;
526 t.shift = tag.ascent - t.ascent;
531 this.ascent = tag.ascent;
533 tag.shift = this.ascent - tag.ascent;
536 // Update our horizontal starting pixel position
537 if (tag.previous == null) {
538 tag.X = (int)widths[0];
540 tag.X = tag.previous.X + (int)tag.previous.width;
548 wrap_width = tag.width;
553 if (this.height == 0) {
554 this.height = tags.font.Height;
555 tag.height = this.height;
558 if (prev_height != this.height) {
563 #endregion // Internal Methods
565 #region Administrative
566 public int CompareTo(object obj) {
571 if (! (obj is Line)) {
572 throw new ArgumentException("Object is not of type Line", "obj");
575 if (line_no < ((Line)obj).line_no) {
577 } else if (line_no > ((Line)obj).line_no) {
584 public object Clone() {
592 clone.left = (Line)left.Clone();
596 clone.left = (Line)left.Clone();
602 internal object CloneLine() {
612 public override bool Equals(object obj) {
617 if (!(obj is Line)) {
625 if (line_no == ((Line)obj).line_no) {
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 {
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 {
649 internal LineTag tag;
653 public static bool operator<(Marker lhs, Marker rhs) {
654 if (lhs.line.line_no < rhs.line.line_no) {
658 if (lhs.line.line_no == rhs.line.line_no) {
659 if (lhs.pos < rhs.pos) {
666 public static bool operator>(Marker lhs, Marker rhs) {
667 if (lhs.line.line_no > rhs.line.line_no) {
671 if (lhs.line.line_no == rhs.line.line_no) {
672 if (lhs.pos > rhs.pos) {
679 public static bool operator==(Marker lhs, Marker rhs) {
680 if ((lhs.line.line_no == rhs.line.line_no) && (lhs.pos == rhs.pos)) {
686 public static bool operator!=(Marker lhs, Marker rhs) {
687 if ((lhs.line.line_no != rhs.line.line_no) || (lhs.pos != rhs.pos)) {
693 public void Combine(Line move_to_line, int move_to_line_length) {
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) {
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;
724 private Line sentinel;
725 private Line last_found;
726 private int document_id;
727 private Random random = new Random();
728 internal string password_char;
729 private StringBuilder password_cache;
730 private bool calc_pass;
731 private int char_count;
733 private bool no_recalc;
734 private bool recalc_pending;
735 private int recalc_start;
736 private int recalc_end;
737 private bool recalc_optimize;
739 internal bool multiline;
742 internal UndoClass undo;
744 internal Marker caret;
745 internal Marker selection_start;
746 internal Marker selection_end;
747 internal bool selection_visible;
748 internal Marker selection_anchor;
749 internal Marker selection_prev;
750 internal bool selection_end_anchor;
752 internal int viewport_x;
753 internal int viewport_y; // The visible area of the document
754 internal int viewport_width;
755 internal int viewport_height;
757 internal int document_x; // Width of the document
758 internal int document_y; // Height of the document
760 internal Rectangle invalid;
762 internal int crlf_size; // 1 or 2, depending on whether we use \r\n or just \n
764 internal TextBoxBase owner; // Who's owning us?
765 static internal int caret_width = 1;
766 static internal int caret_shift = 1;
767 #endregion // Local Variables
770 internal Document(TextBoxBase owner) {
779 recalc_pending = false;
781 // Tree related stuff
782 sentinel = new Line();
783 sentinel.color = LineColor.Black;
786 last_found = sentinel;
788 // We always have a blank line
789 owner.HandleCreated += new EventHandler(owner_HandleCreated);
790 owner.VisibleChanged += new EventHandler(owner_VisibleChanged);
791 Add(1, "", owner.Font, ThemeEngine.Current.ResPool.GetSolidBrush(owner.ForeColor));
794 undo = new UndoClass(this);
796 selection_visible = false;
797 selection_start.line = this.document;
798 selection_start.pos = 0;
799 selection_start.tag = selection_start.line.tags;
800 selection_end.line = this.document;
801 selection_end.pos = 0;
802 selection_end.tag = selection_end.line.tags;
803 selection_anchor.line = this.document;
804 selection_anchor.pos = 0;
805 selection_anchor.tag = selection_anchor.line.tags;
806 caret.line = this.document;
808 caret.tag = caret.line.tags;
815 // Default selection is empty
817 document_id = random.Next();
821 #region Internal Properties
838 internal Line CaretLine {
844 internal int CaretPosition {
850 internal Point Caret {
852 return new Point((int)caret.tag.line.widths[caret.pos] + caret.line.align_shift, caret.line.Y);
856 internal LineTag CaretTag {
866 internal int CRLFSize {
876 internal string PasswordChar {
878 return password_char;
882 password_char = value;
883 if ((password_char.Length != 0) && (password_char[0] != '\0')) {
888 password_cache = new StringBuilder(1024);
889 for (int i = 0; i < 1024; i++) {
890 password_cache.Append(ch);
894 password_cache = null;
899 internal int ViewPortX {
909 internal int Length {
911 return char_count + lines - 1; // Add \n for each line but the last
915 private int CharCount {
923 if (LengthChanged != null) {
924 LengthChanged(this, EventArgs.Empty);
929 ///<summary>Setting NoRecalc to true will prevent the document from being recalculated.
930 ///This ensures that coordinates of added text are predictable after adding the text even with wrapped view</summary>
931 internal bool NoRecalc {
938 if (!no_recalc && recalc_pending) {
939 RecalculateDocument(owner.CreateGraphicsInternal(), recalc_start, recalc_end, recalc_optimize);
940 recalc_pending = false;
945 internal int ViewPortY {
955 internal int ViewPortWidth {
957 return viewport_width;
961 viewport_width = value;
965 internal int ViewPortHeight {
967 return viewport_height;
971 viewport_height = value;
978 return this.document_x;
982 internal int Height {
984 return this.document_y;
988 internal bool SelectionVisible {
990 return selection_visible;
1004 #endregion // Internal Properties
1006 #region Private Methods
1008 internal int DumpTree(Line line, bool with_tags) {
1013 Console.Write("Line {0} [# {1}], Y: {2} Text {3}", line.line_no, line.GetHashCode(), line.Y, line.text != null ? line.text.ToString() : "undefined");
1015 if (line.left == sentinel) {
1016 Console.Write(", left = sentinel");
1017 } else if (line.left == null) {
1018 Console.Write(", left = NULL");
1021 if (line.right == sentinel) {
1022 Console.Write(", right = sentinel");
1023 } else if (line.right == null) {
1024 Console.Write(", right = NULL");
1027 Console.WriteLine("");
1037 Console.Write(" Tags: ");
1038 while (tag != null) {
1039 Console.Write("{0} <{1}>-<{2}> ", count++, tag.start, tag.length);
1040 length += tag.length;
1042 if (tag.line != line) {
1043 Console.Write("BAD line link");
1044 throw new Exception("Bad line link in tree");
1048 Console.Write(", ");
1051 if (length > line.text.Length) {
1052 throw new Exception(String.Format("Length of tags more than length of text on line (expected {0} calculated {1})", line.text.Length, length));
1053 } else if (length < line.text.Length) {
1054 throw new Exception(String.Format("Length of tags less than length of text on line (expected {0} calculated {1})", line.text.Length, length));
1056 Console.WriteLine("");
1058 if (line.left != null) {
1059 if (line.left != sentinel) {
1060 total += DumpTree(line.left, with_tags);
1063 if (line != sentinel) {
1064 throw new Exception("Left should not be NULL");
1068 if (line.right != null) {
1069 if (line.right != sentinel) {
1070 total += DumpTree(line.right, with_tags);
1073 if (line != sentinel) {
1074 throw new Exception("Right should not be NULL");
1078 for (int i = 1; i <= this.lines; i++) {
1079 if (GetLine(i) == null) {
1080 throw new Exception(String.Format("Hole in line order, missing {0}", i));
1084 if (line == this.Root) {
1085 if (total < this.lines) {
1086 throw new Exception(String.Format("Not enough nodes in tree, found {0}, expected {1}", total, this.lines));
1087 } else if (total > this.lines) {
1088 throw new Exception(String.Format("Too many nodes in tree, found {0}, expected {1}", total, this.lines));
1095 private void DecrementLines(int line_no) {
1099 while (current <= lines) {
1100 GetLine(current).line_no--;
1106 private void IncrementLines(int line_no) {
1109 current = this.lines;
1110 while (current >= line_no) {
1111 GetLine(current).line_no++;
1117 private void RebalanceAfterAdd(Line line1) {
1120 while ((line1 != document) && (line1.parent.color == LineColor.Red)) {
1121 if (line1.parent == line1.parent.parent.left) {
1122 line2 = line1.parent.parent.right;
1124 if ((line2 != null) && (line2.color == LineColor.Red)) {
1125 line1.parent.color = LineColor.Black;
1126 line2.color = LineColor.Black;
1127 line1.parent.parent.color = LineColor.Red;
1128 line1 = line1.parent.parent;
1130 if (line1 == line1.parent.right) {
1131 line1 = line1.parent;
1135 line1.parent.color = LineColor.Black;
1136 line1.parent.parent.color = LineColor.Red;
1138 RotateRight(line1.parent.parent);
1141 line2 = line1.parent.parent.left;
1143 if ((line2 != null) && (line2.color == LineColor.Red)) {
1144 line1.parent.color = LineColor.Black;
1145 line2.color = LineColor.Black;
1146 line1.parent.parent.color = LineColor.Red;
1147 line1 = line1.parent.parent;
1149 if (line1 == line1.parent.left) {
1150 line1 = line1.parent;
1154 line1.parent.color = LineColor.Black;
1155 line1.parent.parent.color = LineColor.Red;
1156 RotateLeft(line1.parent.parent);
1160 document.color = LineColor.Black;
1163 private void RebalanceAfterDelete(Line line1) {
1166 while ((line1 != document) && (line1.color == LineColor.Black)) {
1167 if (line1 == line1.parent.left) {
1168 line2 = line1.parent.right;
1169 if (line2.color == LineColor.Red) {
1170 line2.color = LineColor.Black;
1171 line1.parent.color = LineColor.Red;
1172 RotateLeft(line1.parent);
1173 line2 = line1.parent.right;
1175 if ((line2.left.color == LineColor.Black) && (line2.right.color == LineColor.Black)) {
1176 line2.color = LineColor.Red;
1177 line1 = line1.parent;
1179 if (line2.right.color == LineColor.Black) {
1180 line2.left.color = LineColor.Black;
1181 line2.color = LineColor.Red;
1183 line2 = line1.parent.right;
1185 line2.color = line1.parent.color;
1186 line1.parent.color = LineColor.Black;
1187 line2.right.color = LineColor.Black;
1188 RotateLeft(line1.parent);
1192 line2 = line1.parent.left;
1193 if (line2.color == LineColor.Red) {
1194 line2.color = LineColor.Black;
1195 line1.parent.color = LineColor.Red;
1196 RotateRight(line1.parent);
1197 line2 = line1.parent.left;
1199 if ((line2.right.color == LineColor.Black) && (line2.left.color == LineColor.Black)) {
1200 line2.color = LineColor.Red;
1201 line1 = line1.parent;
1203 if (line2.left.color == LineColor.Black) {
1204 line2.right.color = LineColor.Black;
1205 line2.color = LineColor.Red;
1207 line2 = line1.parent.left;
1209 line2.color = line1.parent.color;
1210 line1.parent.color = LineColor.Black;
1211 line2.left.color = LineColor.Black;
1212 RotateRight(line1.parent);
1217 line1.color = LineColor.Black;
1220 private void RotateLeft(Line line1) {
1221 Line line2 = line1.right;
1223 line1.right = line2.left;
1225 if (line2.left != sentinel) {
1226 line2.left.parent = line1;
1229 if (line2 != sentinel) {
1230 line2.parent = line1.parent;
1233 if (line1.parent != null) {
1234 if (line1 == line1.parent.left) {
1235 line1.parent.left = line2;
1237 line1.parent.right = line2;
1244 if (line1 != sentinel) {
1245 line1.parent = line2;
1249 private void RotateRight(Line line1) {
1250 Line line2 = line1.left;
1252 line1.left = line2.right;
1254 if (line2.right != sentinel) {
1255 line2.right.parent = line1;
1258 if (line2 != sentinel) {
1259 line2.parent = line1.parent;
1262 if (line1.parent != null) {
1263 if (line1 == line1.parent.right) {
1264 line1.parent.right = line2;
1266 line1.parent.left = line2;
1272 line2.right = line1;
1273 if (line1 != sentinel) {
1274 line1.parent = line2;
1279 internal void UpdateView(Line line, int pos) {
1280 if (!owner.IsHandleCreated) {
1285 recalc_start = line.line_no;
1286 recalc_end = line.line_no;
1287 recalc_optimize = true;
1288 recalc_pending = true;
1292 // Optimize invalidation based on Line alignment
1293 if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no, true)) {
1294 // Lineheight changed, invalidate the rest of the document
1295 if ((line.Y - viewport_y) >=0 ) {
1296 // We formatted something that's in view, only draw parts of the screen
1297 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, owner.Height - line.Y - viewport_y));
1299 // The tag was above the visible area, draw everything
1303 switch(line.alignment) {
1304 case HorizontalAlignment.Left: {
1305 owner.Invalidate(new Rectangle((int)line.widths[pos] - viewport_x - 1, line.Y - viewport_y, viewport_width, line.height + 1));
1309 case HorizontalAlignment.Center: {
1310 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, line.height + 1));
1314 case HorizontalAlignment.Right: {
1315 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, (int)line.widths[pos + 1] - viewport_x + line.align_shift, line.height + 1));
1323 // Update display from line, down line_count lines; pos is unused, but required for the signature
1324 internal void UpdateView(Line line, int line_count, int pos) {
1325 if (!owner.IsHandleCreated) {
1330 recalc_start = line.line_no;
1331 recalc_end = line.line_no + line_count - 1;
1332 recalc_optimize = true;
1333 recalc_pending = true;
1337 if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no + line_count - 1, true)) {
1338 // Lineheight changed, invalidate the rest of the document
1339 if ((line.Y - viewport_y) >=0 ) {
1340 // We formatted something that's in view, only draw parts of the screen
1341 //blah Console.WriteLine("TextControl.cs(981) Invalidate called in UpdateView(line, line_count, pos)");
1342 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, owner.Height - line.Y - viewport_y));
1344 // The tag was above the visible area, draw everything
1345 //blah Console.WriteLine("TextControl.cs(985) Invalidate called in UpdateView(line, line_count, pos)");
1351 end_line = GetLine(line.line_no + line_count -1);
1352 if (end_line == null) {
1356 //blah Console.WriteLine("TextControl.cs(996) Invalidate called in UpdateView(line, line_count, pos)");
1357 owner.Invalidate(new Rectangle(0 - viewport_x, line.Y - viewport_y, (int)line.widths[line.text.Length], end_line.Y + end_line.height));
1360 #endregion // Private Methods
1362 #region Internal Methods
1363 // Clear the document and reset state
1364 internal void Empty() {
1366 document = sentinel;
1367 last_found = sentinel;
1370 // We always have a blank line
1371 Add(1, "", owner.Font, ThemeEngine.Current.ResPool.GetSolidBrush(owner.ForeColor));
1372 this.RecalculateDocument(owner.CreateGraphicsInternal());
1373 PositionCaret(0, 0);
1375 selection_visible = false;
1376 selection_start.line = this.document;
1377 selection_start.pos = 0;
1378 selection_start.tag = selection_start.line.tags;
1379 selection_end.line = this.document;
1380 selection_end.pos = 0;
1381 selection_end.tag = selection_end.line.tags;
1391 internal void PositionCaret(Line line, int pos) {
1392 if (owner.IsHandleCreated) {
1393 undo.RecordCursor();
1396 caret.tag = line.FindTag(pos);
1399 caret.height = caret.tag.height;
1401 if (owner.IsHandleCreated) {
1402 if (owner.Focused) {
1403 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);
1406 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1412 internal void PositionCaret(int x, int y) {
1413 if (!owner.IsHandleCreated) {
1417 undo.RecordCursor();
1419 caret.tag = FindCursor(x, y, out caret.pos);
1420 caret.line = caret.tag.line;
1421 caret.height = caret.tag.height;
1423 if (owner.Focused) {
1424 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);
1427 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1430 internal void CaretHasFocus() {
1431 if ((caret.tag != null) && owner.IsHandleCreated) {
1432 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1433 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);
1435 if (!selection_visible) {
1436 XplatUI.CaretVisible(owner.Handle, true);
1438 XplatUI.CaretVisible(owner.Handle, false);
1443 internal void CaretLostFocus() {
1444 if (!owner.IsHandleCreated) {
1447 XplatUI.DestroyCaret(owner.Handle);
1450 internal void AlignCaret() {
1451 if (!owner.IsHandleCreated) {
1455 undo.RecordCursor();
1457 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1458 caret.height = caret.tag.height;
1460 if (owner.Focused) {
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);
1463 XplatUI.CaretVisible(owner.Handle, true);
1466 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1469 internal void UpdateCaret() {
1470 if (!owner.IsHandleCreated || caret.tag == null) {
1474 undo.RecordCursor();
1476 if (caret.tag.height != caret.height) {
1477 caret.height = caret.tag.height;
1478 if (owner.Focused) {
1479 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1483 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);
1484 XplatUI.CaretVisible(owner.Handle, true);
1486 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1489 internal void DisplayCaret() {
1490 if (!owner.IsHandleCreated) {
1494 if (owner.Focused) {
1495 XplatUI.CaretVisible(owner.Handle, true);
1499 internal void HideCaret() {
1500 if (!owner.IsHandleCreated) {
1504 if (owner.Focused) {
1505 XplatUI.CaretVisible(owner.Handle, false);
1509 internal void MoveCaret(CaretDirection direction) {
1510 // FIXME should we use IsWordSeparator to detect whitespace, instead
1511 // of looking for actual spaces in the Word move cases?
1513 bool nowrap = false;
1515 case CaretDirection.CharForwardNoWrap:
1517 goto case CaretDirection.CharForward;
1518 case CaretDirection.CharForward: {
1520 if (caret.pos > caret.line.text.Length) {
1521 if (multiline && !nowrap) {
1522 // Go into next line
1523 if (caret.line.line_no < this.lines) {
1524 caret.line = GetLine(caret.line.line_no+1);
1526 caret.tag = caret.line.tags;
1531 // Single line; we stay where we are
1535 if ((caret.tag.start - 1 + caret.tag.length) < caret.pos) {
1536 caret.tag = caret.tag.next;
1543 case CaretDirection.CharBackNoWrap:
1545 goto case CaretDirection.CharBack;
1546 case CaretDirection.CharBack: {
1547 if (caret.pos > 0) {
1548 // caret.pos--; // folded into the if below
1549 if (--caret.pos > 0) {
1550 if (caret.tag.start > caret.pos) {
1551 caret.tag = caret.tag.previous;
1555 if (caret.line.line_no > 1 && !nowrap) {
1556 caret.line = GetLine(caret.line.line_no - 1);
1557 caret.pos = caret.line.text.Length;
1558 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1565 case CaretDirection.WordForward: {
1568 len = caret.line.text.Length;
1569 if (caret.pos < len) {
1570 while ((caret.pos < len) && (caret.line.text[caret.pos] != ' ')) {
1573 if (caret.pos < len) {
1574 // Skip any whitespace
1575 while ((caret.pos < len) && (caret.line.text[caret.pos] == ' ')) {
1579 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1581 if (caret.line.line_no < this.lines) {
1582 caret.line = GetLine(caret.line.line_no + 1);
1584 caret.tag = caret.line.tags;
1591 case CaretDirection.WordBack: {
1592 if (caret.pos > 0) {
1595 while ((caret.pos > 0) && (caret.line.text[caret.pos] == ' ')) {
1599 while ((caret.pos > 0) && (caret.line.text[caret.pos] != ' ')) {
1603 if (caret.line.text.ToString(caret.pos, 1) == " ") {
1604 if (caret.pos != 0) {
1607 caret.line = GetLine(caret.line.line_no - 1);
1608 caret.pos = caret.line.text.Length;
1611 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1613 if (caret.line.line_no > 1) {
1614 caret.line = GetLine(caret.line.line_no - 1);
1615 caret.pos = caret.line.text.Length;
1616 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1623 case CaretDirection.LineUp: {
1624 if (caret.line.line_no > 1) {
1627 pixel = (int)caret.line.widths[caret.pos];
1628 PositionCaret(pixel, GetLine(caret.line.line_no - 1).Y);
1629 if (!owner.IsHandleCreated) {
1632 XplatUI.CaretVisible(owner.Handle, true);
1637 case CaretDirection.LineDown: {
1638 if (caret.line.line_no < lines) {
1641 pixel = (int)caret.line.widths[caret.pos];
1642 PositionCaret(pixel, GetLine(caret.line.line_no + 1).Y);
1643 if (!owner.IsHandleCreated) {
1646 XplatUI.CaretVisible(owner.Handle, true);
1651 case CaretDirection.Home: {
1652 if (caret.pos > 0) {
1654 caret.tag = caret.line.tags;
1660 case CaretDirection.End: {
1661 if (caret.pos < caret.line.text.Length) {
1662 caret.pos = caret.line.text.Length;
1663 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1669 case CaretDirection.PgUp: {
1674 if ((viewport_y - viewport_height) < 0) {
1675 // We're just placing the caret at the end of the document, no scrolling needed
1676 owner.vscroll.Value = 0;
1678 PositionCaret(line, 0);
1679 XplatUI.CaretVisible(owner.Handle, true);
1684 new_y = caret.line.Y - viewport_height;
1687 PositionCaret(line, 0);
1689 line = FindTag((int)caret.line.widths[caret.pos], caret.line.Y - viewport_height, out index, false).line;
1690 if (caret.pos > 0) {
1691 PositionCaret(line, index);
1693 PositionCaret(line, 0);
1697 // Line up to fill line starts
1698 new_y = viewport_y - viewport_height;
1699 line = FindTag(0, new_y, out index, false).line;
1701 owner.vscroll.Value = line.Y;
1703 owner.vscroll.Value = new_y;
1705 XplatUI.CaretVisible(owner.Handle, true);
1710 case CaretDirection.PgDn: {
1715 if ((viewport_y + viewport_height) > document_y) {
1716 // We're just placing the caret at the end of the document, no scrolling needed
1717 owner.vscroll.Value = owner.vscroll.Maximum;
1718 line = GetLine(lines);
1719 PositionCaret(line, line.Text.Length);
1720 XplatUI.CaretVisible(owner.Handle, true);
1725 new_y = caret.line.Y + viewport_height;
1726 if (new_y > document_y) {
1727 line = GetLine(lines);
1728 PositionCaret(line, line.text.Length);
1730 line = FindTag((int)caret.line.widths[caret.pos], caret.line.Y + viewport_height, out index, false).line;
1731 if (caret.pos > 0) {
1732 PositionCaret(line, index);
1734 PositionCaret(line, 0);
1738 // Line up to fill line starts
1739 new_y = viewport_y + viewport_height;
1740 line = FindTag(0, new_y, out index, false).line;
1742 if (line.Y > owner.vscroll.Maximum) {
1743 owner.vscroll.Value = owner.vscroll.Maximum;
1745 owner.vscroll.Value = line.Y;
1748 owner.vscroll.Value = new_y;
1750 XplatUI.CaretVisible(owner.Handle, true);
1755 case CaretDirection.CtrlPgUp: {
1756 PositionCaret(0, viewport_y);
1757 if (!owner.IsHandleCreated) {
1760 XplatUI.CaretVisible(owner.Handle, true);
1764 case CaretDirection.CtrlPgDn: {
1769 tag = FindTag(0, viewport_y + viewport_height, out index, false);
1770 if (tag.line.line_no > 1) {
1771 line = GetLine(tag.line.line_no - 1);
1775 PositionCaret(line, line.Text.Length);
1776 if (!owner.IsHandleCreated) {
1779 XplatUI.CaretVisible(owner.Handle, true);
1783 case CaretDirection.CtrlHome: {
1784 caret.line = GetLine(1);
1786 caret.tag = caret.line.tags;
1792 case CaretDirection.CtrlEnd: {
1793 caret.line = GetLine(lines);
1794 caret.pos = caret.line.text.Length;
1795 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1801 case CaretDirection.SelectionStart: {
1802 caret.line = selection_start.line;
1803 caret.pos = selection_start.pos;
1804 caret.tag = selection_start.tag;
1810 case CaretDirection.SelectionEnd: {
1811 caret.line = selection_end.line;
1812 caret.pos = selection_end.pos;
1813 caret.tag = selection_end.tag;
1821 // Draw the document
1822 internal void Draw(Graphics g, Rectangle clip) {
1823 Line line; // Current line being drawn
1824 LineTag tag; // Current tag being drawn
1825 int start; // First line to draw
1826 int end; // Last line to draw
1827 StringBuilder text; // String representing the current line
1833 // First, figure out from what line to what line we need to draw
1834 start = GetLineByPixel(clip.Top + viewport_y, false).line_no;
1835 end = GetLineByPixel(clip.Bottom + viewport_y, false).line_no;
1836 //Console.WriteLine("Starting drawing at line {0}, ending at line {1} (clip-bottom:{2})", start, end, clip.Bottom);
1838 // Now draw our elements; try to only draw those that are visible
1842 DateTime n = DateTime.Now;
1843 Console.WriteLine("Started drawing: {0}s {1}ms", n.Second, n.Millisecond);
1846 disabled = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorGrayText);
1847 hilight = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorHighlight);
1848 hilight_text = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorHighlightText);
1850 while (line_no <= end) {
1851 line = GetLine(line_no);
1853 if (owner.backcolor_set || (owner.Enabled && !owner.read_only)) {
1854 g.FillRectangle(ThemeEngine.Current.ResPool.GetSolidBrush(owner.BackColor), new Rectangle(clip.Left, line.Y - viewport_y, clip.Width, line.Y - viewport_y + line.Height));
1856 g.FillRectangle(ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorControl), new Rectangle(clip.Left, line.Y - viewport_y, clip.Width, line.Y - viewport_y + line.Height));
1865 // This fails if there's a password > 1024 chars...
1866 text = this.password_cache;
1868 while (tag != null) {
1869 if (tag.length == 0) {
1874 if (((tag.X + tag.width) > (clip.Left - viewport_x)) || (tag.X < (clip.Right - viewport_x))) {
1875 // Check for selection
1876 if ((!selection_visible) || (!owner.ShowSelection) || (line_no < selection_start.line.line_no) || (line_no > selection_end.line.line_no)) {
1877 // regular drawing, no selection to deal with
1878 //g.DrawString(s.Substring(tag.start-1, tag.length), tag.font, tag.color, tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
1879 if (owner.is_enabled) {
1880 g.DrawString(text.ToString(tag.start-1, tag.length), tag.font, tag.color, tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
1885 a = ((SolidBrush)tag.color).Color;
1886 b = ThemeEngine.Current.ColorWindowText;
1888 if ((a.R == b.R) && (a.G == b.G) && (a.B == b.B)) {
1889 g.DrawString(text.ToString(tag.start-1, tag.length), tag.font, disabled, tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
1891 g.DrawString(text.ToString(tag.start-1, tag.length), tag.font, tag.color, tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
1895 // we might have to draw our selection
1896 if ((line_no != selection_start.line.line_no) && (line_no != selection_end.line.line_no)) {
1897 // Special case, whole line is selected, draw this tag selected
1900 tag.X + line.align_shift - viewport_x, // X
1901 line.Y + tag.shift - viewport_y, // Y
1902 line.widths[tag.start + tag.length - 1], // width
1903 tag.height // Height
1907 //s.Substring(tag.start-1, tag.length), // String
1908 text.ToString(tag.start-1, tag.length), // String
1910 hilight_text, // Brush
1911 tag.X + line.align_shift - viewport_x, // X
1912 line.Y + tag.shift - viewport_y, // Y
1913 StringFormat.GenericTypographic);
1921 // One or more, but not all tags on the line are selected
1922 if ((selection_start.tag == tag) && (selection_end.tag == tag)) {
1923 // Single tag selected, draw "normalSELECTEDnormal"
1925 // First, the regular part
1927 //s.Substring(tag.start - 1, selection_start.pos - tag.start + 1), // String
1928 text.ToString(tag.start - 1, selection_start.pos - tag.start + 1), // String
1931 tag.X + line.align_shift - viewport_x, // X
1932 line.Y + tag.shift - viewport_y, // Y
1933 StringFormat.GenericTypographic);
1935 // Now the highlight
1938 line.widths[selection_start.pos] + line.align_shift - viewport_x, // X
1939 line.Y + tag.shift - viewport_y, // Y
1940 line.widths[selection_end.pos] - line.widths[selection_start.pos], // Width
1941 tag.height); // Height
1944 //s.Substring(selection_start.pos, selection_end.pos - selection_start.pos), // String
1945 text.ToString(selection_start.pos, selection_end.pos - selection_start.pos), // String
1947 hilight_text, // Brush
1948 line.widths[selection_start.pos] + line.align_shift - viewport_x, // X
1949 line.Y + tag.shift - viewport_y, // Y
1950 StringFormat.GenericTypographic);
1952 // And back to the regular
1954 //s.Substring(selection_end.pos, tag.start + tag.length - selection_end.pos - 1), // String
1955 text.ToString(selection_end.pos, tag.start + tag.length - selection_end.pos - 1), // String
1958 line.widths[selection_end.pos] + line.align_shift - viewport_x, // X
1959 line.Y + tag.shift - viewport_y, // Y
1960 StringFormat.GenericTypographic);
1962 } else if (selection_start.tag == tag) {
1965 // The highlighted part
1968 line.widths[selection_start.pos] + line.align_shift - viewport_x,
1969 line.Y + tag.shift - viewport_y,
1970 line.widths[tag.start + tag.length - 1] - line.widths[selection_start.pos],
1974 //s.Substring(selection_start.pos, tag.start + tag.length - selection_start.pos - 1), // String
1975 text.ToString(selection_start.pos, tag.start + tag.length - selection_start.pos - 1), // String
1977 hilight_text, // Brush
1978 line.widths[selection_start.pos] + line.align_shift - viewport_x, // X
1979 line.Y + tag.shift - viewport_y, // Y
1980 StringFormat.GenericTypographic);
1984 //s.Substring(tag.start - 1, selection_start.pos - tag.start + 1), // String
1985 text.ToString(tag.start - 1, selection_start.pos - tag.start + 1), // String
1988 tag.X + line.align_shift - viewport_x, // X
1989 line.Y + tag.shift - viewport_y, // Y
1990 StringFormat.GenericTypographic);
1991 } else if (selection_end.tag == tag) {
1994 // The highlighted part
1997 tag.X + line.align_shift - viewport_x,
1998 line.Y + tag.shift - viewport_y,
1999 line.widths[selection_end.pos] - line.widths[tag.start - 1],
2003 //s.Substring(tag.start - 1, selection_end.pos - tag.start + 1), // String
2004 text.ToString(tag.start - 1, selection_end.pos - tag.start + 1), // String
2006 hilight_text, // Brush
2007 tag.X + line.align_shift - viewport_x, // X
2008 line.Y + tag.shift - viewport_y, // Y
2009 StringFormat.GenericTypographic);
2013 //s.Substring(selection_end.pos, tag.start + tag.length - selection_end.pos - 1), // String
2014 text.ToString(selection_end.pos, tag.start + tag.length - selection_end.pos - 1), // String
2017 line.widths[selection_end.pos] + line.align_shift - viewport_x, // X
2018 line.Y + tag.shift - viewport_y, // Y
2019 StringFormat.GenericTypographic);
2021 // no partially selected tags here, simple checks...
2022 if (selection_start.line == line) {
2026 begin = tag.start - 1;
2027 stop = tag.start + tag.length - 1;
2028 if (selection_end.line == line) {
2029 if ((begin >= selection_start.pos) && (stop < selection_end.pos)) {
2033 if (stop > selection_start.pos) {
2037 } else if (selection_end.line == line) {
2038 if ((tag.start - 1) < selection_end.pos) {
2048 tag.X + line.align_shift - viewport_x,
2049 line.Y + tag.shift - viewport_y,
2050 line.widths[tag.start + tag.length - 1] - line.widths[tag.start - 1],
2054 //s.Substring(tag.start-1, tag.length), // String
2055 text.ToString(tag.start-1, tag.length), // String
2057 hilight_text, // Brush
2058 tag.X + line.align_shift - viewport_x, // X
2059 line.Y + tag.shift - viewport_y, // Y
2060 StringFormat.GenericTypographic);
2063 //s.Substring(tag.start-1, tag.length), // String
2064 text.ToString(tag.start-1, tag.length), // String
2067 tag.X + line.align_shift - viewport_x, // X
2068 line.Y + tag.shift - viewport_y, // Y
2069 StringFormat.GenericTypographic);
2084 Console.WriteLine("Finished drawing: {0}s {1}ms", n.Second, n.Millisecond);
2089 internal void Insert(Line line, int pos, string s) {
2090 Insert(line, null, pos, false, s);
2093 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
2094 internal void Insert(Line line, LineTag tag, int pos, bool update_caret, string s) {
2101 // The formatting at the insertion point is used for the inserted text
2103 tag = LineTag.FindTag(line, pos);
2106 base_line = line.line_no;
2108 ins = s.Split(new char[] {'\n'});
2110 for (int j = 0; j < ins.Length; j++) {
2111 if (ins[j].EndsWith("\r")) {
2112 ins[j] = ins[j].Substring(0, ins[j].Length - 1);
2116 insert_lines = ins.Length;
2117 old_line_count = lines;
2119 // Bump the text at insertion point a line down if we're inserting more than one line
2120 if (insert_lines > 1) {
2122 // Remainder of start line is now in base_line + 1
2125 // Insert the first line
2126 InsertString(tag, pos, ins[0]);
2128 if (insert_lines > 1) {
2129 for (i = 1; i < insert_lines; i++) {
2130 Add(base_line + i, ins[i], line.alignment, tag.font, tag.color);
2132 if (!s.EndsWith("\n\n")) {
2133 this.Combine(base_line + (lines - old_line_count) - 1, base_line + lines - old_line_count);
2137 UpdateView(line, lines - old_line_count, pos);
2140 // Move caret to the end of the inserted text
2141 if (insert_lines > 1) {
2142 Line l = GetLine (line.line_no + lines - old_line_count);
2143 PositionCaret(l, l.text.Length);
2145 PositionCaret(line, pos + ins[0].Length);
2147 if (owner.IsHandleCreated) {
2148 XplatUI.CaretVisible(owner.Handle, true);
2153 // Inserts a character at the given position
2154 internal void InsertString(Line line, int pos, string s) {
2155 InsertString(line.FindTag(pos), pos, s);
2158 // Inserts a string at the given position
2159 internal void InsertString(LineTag tag, int pos, string s) {
2168 line.text.Insert(pos, s);
2171 // TODO: sometimes getting a null tag here when pasting ???
2173 while (tag != null) {
2180 UpdateView(line, pos);
2183 // Inserts a string at the caret position
2184 internal void InsertStringAtCaret(string s, bool move_caret) {
2192 caret.line.text.Insert(caret.pos, s);
2193 caret.tag.length += len;
2195 if (caret.tag.next != null) {
2196 tag = caret.tag.next;
2197 while (tag != null) {
2202 caret.line.Grow(len);
2203 caret.line.recalc = true;
2205 UpdateView(caret.line, caret.pos);
2214 // Inserts a character at the given position
2215 internal void InsertChar(Line line, int pos, char ch) {
2216 InsertChar(line.FindTag(pos), pos, ch);
2219 // Inserts a character at the given position
2220 internal void InsertChar(LineTag tag, int pos, char ch) {
2226 line.text.Insert(pos, ch);
2230 while (tag != null) {
2237 UpdateView(line, pos);
2240 // Inserts a character at the current caret position
2241 internal void InsertCharAtCaret(char ch, bool move_caret) {
2246 caret.line.text.Insert(caret.pos, ch);
2249 if (caret.tag.next != null) {
2250 tag = caret.tag.next;
2251 while (tag != null) {
2257 caret.line.recalc = true;
2259 UpdateView(caret.line, caret.pos);
2263 SetSelectionToCaret(true);
2267 // Deletes n characters at the given position; it will not delete past line limits
2269 internal void DeleteChars(LineTag tag, int pos, int count) {
2278 if (pos == line.text.Length) {
2282 line.text.Remove(pos, count);
2284 // Make sure the tag points to the right spot
2285 while ((tag != null) && (tag.start + tag.length - 1) <= pos) {
2293 // Check if we're crossing tag boundaries
2294 if ((pos + count) > (tag.start + tag.length - 1)) {
2297 // We have to delete cross tag boundaries
2301 left -= tag.start + tag.length - pos - 1;
2302 tag.length -= tag.start + tag.length - pos - 1;
2305 while ((tag != null) && (left > 0)) {
2306 tag.start -= count - left;
2307 if (tag.length > left) {
2318 // We got off easy, same tag
2320 tag.length -= count;
2322 if (tag.length == 0) {
2327 // Delete empty orphaned tags at the end
2329 while (walk != null && walk.next != null && walk.next.length == 0) {
2331 walk.next = walk.next.next;
2332 if (walk.next != null)
2333 walk.next.previous = t;
2337 // Adjust the start point of any tags following
2340 while (tag != null) {
2348 line.Streamline(lines);
2351 UpdateView(line, pos);
2354 // Deletes a character at or after the given position (depending on forward); it will not delete past line limits
2355 internal void DeleteChar(LineTag tag, int pos, bool forward) {
2364 if ((pos == 0 && forward == false) || (pos == line.text.Length && forward == true)) {
2370 line.text.Remove(pos, 1);
2372 while ((tag != null) && (tag.start + tag.length - 1) <= pos) {
2382 if (tag.length == 0) {
2387 line.text.Remove(pos, 1);
2388 if (pos >= (tag.start - 1)) {
2390 if (tag.length == 0) {
2393 } else if (tag.previous != null) {
2394 tag.previous.length--;
2395 if (tag.previous.length == 0) {
2401 // Delete empty orphaned tags at the end
2403 while (walk != null && walk.next != null && walk.next.length == 0) {
2405 walk.next = walk.next.next;
2406 if (walk.next != null)
2407 walk.next.previous = t;
2412 while (tag != null) {
2418 line.Streamline(lines);
2421 UpdateView(line, pos);
2424 // Combine two lines
2425 internal void Combine(int FirstLine, int SecondLine) {
2426 Combine(GetLine(FirstLine), GetLine(SecondLine));
2429 internal void Combine(Line first, Line second) {
2433 // Combine the two tag chains into one
2436 while (last.next != null) {
2440 last.next = second.tags;
2441 last.next.previous = last;
2443 shift = last.start + last.length - 1;
2445 // Fix up references within the chain
2447 while (last != null) {
2449 last.start += shift;
2453 // Combine both lines' strings
2454 first.text.Insert(first.text.Length, second.text.ToString());
2455 first.Grow(first.text.Length);
2457 // Remove the reference to our (now combined) tags from the doomed line
2461 DecrementLines(first.line_no + 2); // first.line_no + 1 will be deleted, so we need to start renumbering one later
2464 first.recalc = true;
2465 first.height = 0; // This forces RecalcDocument/UpdateView to redraw from this line on
2466 first.Streamline(lines);
2468 // Update Caret, Selection, etc
2469 if (caret.line == second) {
2470 caret.Combine(first, shift);
2472 if (selection_anchor.line == second) {
2473 selection_anchor.Combine(first, shift);
2475 if (selection_start.line == second) {
2476 selection_start.Combine(first, shift);
2478 if (selection_end.line == second) {
2479 selection_end.Combine(first, shift);
2486 check_first = GetLine(first.line_no);
2487 check_second = GetLine(check_first.line_no + 1);
2489 Console.WriteLine("Pre-delete: Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2492 this.Delete(second);
2495 check_first = GetLine(first.line_no);
2496 check_second = GetLine(check_first.line_no + 1);
2498 Console.WriteLine("Post-delete Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2502 // Split the line at the position into two
2503 internal void Split(int LineNo, int pos) {
2507 line = GetLine(LineNo);
2508 tag = LineTag.FindTag(line, pos);
2509 Split(line, tag, pos, false);
2512 internal void Split(Line line, int pos) {
2515 tag = LineTag.FindTag(line, pos);
2516 Split(line, tag, pos, false);
2519 ///<summary>Split line at given tag and position into two lines</summary>
2520 ///<param name="soft">True if the split should be marked as 'soft', indicating that it can be contracted
2521 ///if more space becomes available on previous line</param>
2522 internal void Split(Line line, LineTag tag, int pos, bool soft) {
2526 bool move_sel_start;
2530 move_sel_start = false;
2531 move_sel_end = false;
2533 // Adjust selection and cursors
2534 if (soft && (caret.line == line) && (caret.pos >= pos)) {
2537 if (selection_start.line == line && selection_start.pos > pos) {
2538 move_sel_start = true;
2541 if (selection_end.line == line && selection_end.pos > pos) {
2542 move_sel_end = true;
2545 // cover the easy case first
2546 if (pos == line.text.Length) {
2547 Add(line.line_no + 1, "", line.alignment, tag.font, tag.color);
2549 new_line = GetLine(line.line_no + 1);
2553 caret.line = new_line;
2554 caret.line.soft_break = true;
2555 caret.tag = new_line.tags;
2558 new_line.soft_break = true;
2562 if (move_sel_start) {
2563 selection_start.line = new_line;
2564 selection_start.pos = 0;
2565 selection_start.tag = new_line.tags;
2569 selection_end.line = new_line;
2570 selection_end.pos = 0;
2571 selection_end.tag = new_line.tags;
2576 // We need to move the rest of the text into the new line
2577 Add(line.line_no + 1, line.text.ToString(pos, line.text.Length - pos), line.alignment, tag.font, tag.color);
2579 // Now transfer our tags from this line to the next
2580 new_line = GetLine(line.line_no + 1);
2582 new_line.recalc = true;
2584 if ((tag.start - 1) == pos) {
2587 // We can simply break the chain and move the tag into the next line
2588 if (tag == line.tags) {
2589 new_tag = new LineTag(line, 1, 0);
2590 new_tag.font = tag.font;
2591 new_tag.color = tag.color;
2592 line.tags = new_tag;
2595 if (tag.previous != null) {
2596 tag.previous.next = null;
2598 new_line.tags = tag;
2599 tag.previous = null;
2600 tag.line = new_line;
2602 // Walk the list and correct the start location of the tags we just bumped into the next line
2603 shift = tag.start - 1;
2606 while (new_tag != null) {
2607 new_tag.start -= shift;
2608 new_tag.line = new_line;
2609 new_tag = new_tag.next;
2614 new_tag = new LineTag(new_line, 1, tag.start - 1 + tag.length - pos);
2615 new_tag.next = tag.next;
2616 new_tag.font = tag.font;
2617 new_tag.color = tag.color;
2618 new_line.tags = new_tag;
2619 if (new_tag.next != null) {
2620 new_tag.next.previous = new_tag;
2623 tag.length = pos - tag.start + 1;
2626 new_tag = new_tag.next;
2627 while (new_tag != null) {
2628 new_tag.start -= shift;
2629 new_tag.line = new_line;
2630 new_tag = new_tag.next;
2637 caret.line = new_line;
2638 caret.pos = caret.pos - pos;
2639 caret.tag = caret.line.FindTag(caret.pos);
2641 new_line.soft_break = true;
2644 if (move_sel_start) {
2645 selection_start.line = new_line;
2646 selection_start.pos = selection_start.pos - pos;
2647 selection_start.tag = new_line.FindTag(selection_start.pos);
2651 selection_end.line = new_line;
2652 selection_end.pos = selection_end.pos - pos;
2653 selection_end.tag = new_line.FindTag(selection_end.pos);
2656 CharCount -= line.text.Length - pos;
2657 line.text.Remove(pos, line.text.Length - pos);
2660 // Adds a line of text, with given font.
2661 // Bumps any line at that line number that already exists down
2662 internal void Add(int LineNo, string Text, Font font, Brush color) {
2663 Add(LineNo, Text, HorizontalAlignment.Left, font, color);
2666 internal void Add(int LineNo, string Text, HorizontalAlignment align, Font font, Brush color) {
2671 CharCount += Text.Length;
2673 if (LineNo<1 || Text == null) {
2675 throw new ArgumentNullException("LineNo", "Line numbers must be positive");
2677 throw new ArgumentNullException("Text", "Cannot insert NULL line");
2681 add = new Line(LineNo, Text, align, font, color);
2684 while (line != sentinel) {
2686 line_no = line.line_no;
2688 if (LineNo > line_no) {
2690 } else if (LineNo < line_no) {
2693 // Bump existing line numbers; walk all nodes to the right of this one and increment line_no
2694 IncrementLines(line.line_no);
2699 add.left = sentinel;
2700 add.right = sentinel;
2702 if (add.parent != null) {
2703 if (LineNo > add.parent.line_no) {
2704 add.parent.right = add;
2706 add.parent.left = add;
2713 RebalanceAfterAdd(add);
2718 internal virtual void Clear() {
2721 document = sentinel;
2724 public virtual object Clone() {
2727 clone = new Document(null);
2729 clone.lines = this.lines;
2730 clone.document = (Line)document.Clone();
2735 internal void Delete(int LineNo) {
2742 line = GetLine(LineNo);
2744 CharCount -= line.text.Length;
2746 DecrementLines(LineNo + 1);
2750 internal void Delete(Line line1) {
2751 Line line2;// = new Line();
2754 if ((line1.left == sentinel) || (line1.right == sentinel)) {
2757 line3 = line1.right;
2758 while (line3.left != sentinel) {
2763 if (line3.left != sentinel) {
2766 line2 = line3.right;
2769 line2.parent = line3.parent;
2770 if (line3.parent != null) {
2771 if(line3 == line3.parent.left) {
2772 line3.parent.left = line2;
2774 line3.parent.right = line2;
2780 if (line3 != line1) {
2783 if (selection_start.line == line3) {
2784 selection_start.line = line1;
2787 if (selection_end.line == line3) {
2788 selection_end.line = line1;
2791 if (selection_anchor.line == line3) {
2792 selection_anchor.line = line1;
2795 if (caret.line == line3) {
2800 line1.alignment = line3.alignment;
2801 line1.ascent = line3.ascent;
2802 line1.hanging_indent = line3.hanging_indent;
2803 line1.height = line3.height;
2804 line1.indent = line3.indent;
2805 line1.line_no = line3.line_no;
2806 line1.recalc = line3.recalc;
2807 line1.right_indent = line3.right_indent;
2808 line1.soft_break = line3.soft_break;
2809 line1.space = line3.space;
2810 line1.tags = line3.tags;
2811 line1.text = line3.text;
2812 line1.widths = line3.widths;
2816 while (tag != null) {
2822 if (line3.color == LineColor.Black)
2823 RebalanceAfterDelete(line2);
2827 last_found = sentinel;
2830 // Invalidate a section of the document to trigger redraw
2831 internal void Invalidate(Line start, int start_pos, Line end, int end_pos) {
2837 if ((start == end) && (start_pos == end_pos)) {
2841 if (end_pos == -1) {
2842 end_pos = end.text.Length;
2845 // figure out what's before what so the logic below is straightforward
2846 if (start.line_no < end.line_no) {
2852 } else if (start.line_no > end.line_no) {
2859 if (start_pos < end_pos) {
2874 Console.WriteLine("Invaliding from {0}:{1} to {2}:{3}", l1.line_no, p1, l2.line_no, p2);
2879 (int)l1.widths[p1] + l1.align_shift - viewport_x,
2881 (int)l2.widths[p2] - (int)l1.widths[p1] + 1,
2889 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);
2892 // Three invalidates:
2893 // First line from start
2894 owner.Invalidate(new Rectangle((int)l1.widths[p1] + l1.align_shift - viewport_x, l1.Y - viewport_y, viewport_width, l1.height));
2897 if ((l1.line_no + 1) < l2.line_no) {
2900 y = GetLine(l1.line_no + 1).Y;
2901 owner.Invalidate(new Rectangle(0, y - viewport_y, viewport_width, GetLine(l2.line_no).Y - y - viewport_y));
2904 Console.WriteLine("Invaliding from {0}:{1} to {2}:{3} Middle => x={4}, y={5}, {6}x{7}", l1.line_no, p1, l2.line_no, p2, 0, y - viewport_y, viewport_width, GetLine(l2.line_no).Y - y - viewport_y);
2909 owner.Invalidate(new Rectangle((int)l2.widths[0] + l2.align_shift - viewport_x, l2.Y - viewport_y, (int)l2.widths[p2] + 1, l2.height));
2911 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);
2915 /// <summary>Select text around caret</summary>
2916 internal void ExpandSelection(CaretSelection mode, bool to_caret) {
2918 // We're expanding the selection to the caret position
2920 case CaretSelection.Line: {
2921 // Invalidate the selection delta
2922 if (caret > selection_prev) {
2923 Invalidate(selection_prev.line, 0, caret.line, caret.line.text.Length);
2925 Invalidate(selection_prev.line, selection_prev.line.text.Length, caret.line, 0);
2928 if (caret.line.line_no <= selection_anchor.line.line_no) {
2929 selection_start.line = caret.line;
2930 selection_start.tag = caret.line.tags;
2931 selection_start.pos = 0;
2933 selection_end.line = selection_anchor.line;
2934 selection_end.tag = selection_anchor.tag;
2935 selection_end.pos = selection_anchor.pos;
2937 selection_end_anchor = true;
2939 selection_start.line = selection_anchor.line;
2940 selection_start.pos = selection_anchor.height;
2941 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height);
2943 selection_end.line = caret.line;
2944 selection_end.tag = caret.line.tags;
2945 selection_end.pos = caret.line.text.Length;
2947 selection_end_anchor = false;
2949 selection_prev.line = caret.line;
2950 selection_prev.tag = caret.tag;
2951 selection_prev.pos = caret.pos;
2956 case CaretSelection.Word: {
2960 start_pos = FindWordSeparator(caret.line, caret.pos, false);
2961 end_pos = FindWordSeparator(caret.line, caret.pos, true);
2964 // Invalidate the selection delta
2965 if (caret > selection_prev) {
2966 Invalidate(selection_prev.line, selection_prev.pos, caret.line, end_pos);
2968 Invalidate(selection_prev.line, selection_prev.pos, caret.line, start_pos);
2970 if (caret < selection_anchor) {
2971 selection_start.line = caret.line;
2972 selection_start.tag = caret.line.FindTag(start_pos);
2973 selection_start.pos = start_pos;
2975 selection_end.line = selection_anchor.line;
2976 selection_end.tag = selection_anchor.tag;
2977 selection_end.pos = selection_anchor.pos;
2979 selection_prev.line = caret.line;
2980 selection_prev.tag = caret.tag;
2981 selection_prev.pos = start_pos;
2983 selection_end_anchor = true;
2985 selection_start.line = selection_anchor.line;
2986 selection_start.pos = selection_anchor.height;
2987 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height);
2989 selection_end.line = caret.line;
2990 selection_end.tag = caret.line.FindTag(end_pos);
2991 selection_end.pos = end_pos;
2993 selection_prev.line = caret.line;
2994 selection_prev.tag = caret.tag;
2995 selection_prev.pos = end_pos;
2997 selection_end_anchor = false;
3002 case CaretSelection.Position: {
3003 SetSelectionToCaret(false);
3008 // We're setting the selection 'around' the caret position
3010 case CaretSelection.Line: {
3011 this.Invalidate(caret.line, 0, caret.line, caret.line.text.Length);
3013 selection_start.line = caret.line;
3014 selection_start.tag = caret.line.tags;
3015 selection_start.pos = 0;
3017 selection_end.line = caret.line;
3018 selection_end.pos = caret.line.text.Length;
3019 selection_end.tag = caret.line.FindTag(selection_end.pos);
3021 selection_anchor.line = selection_end.line;
3022 selection_anchor.tag = selection_end.tag;
3023 selection_anchor.pos = selection_end.pos;
3024 selection_anchor.height = 0;
3026 selection_prev.line = caret.line;
3027 selection_prev.tag = caret.tag;
3028 selection_prev.pos = caret.pos;
3030 this.selection_end_anchor = true;
3035 case CaretSelection.Word: {
3039 start_pos = FindWordSeparator(caret.line, caret.pos, false);
3040 end_pos = FindWordSeparator(caret.line, caret.pos, true);
3042 this.Invalidate(selection_start.line, start_pos, caret.line, end_pos);
3044 selection_start.line = caret.line;
3045 selection_start.tag = caret.line.FindTag(start_pos);
3046 selection_start.pos = start_pos;
3048 selection_end.line = caret.line;
3049 selection_end.tag = caret.line.FindTag(end_pos);
3050 selection_end.pos = end_pos;
3052 selection_anchor.line = selection_end.line;
3053 selection_anchor.tag = selection_end.tag;
3054 selection_anchor.pos = selection_end.pos;
3055 selection_anchor.height = start_pos;
3057 selection_prev.line = caret.line;
3058 selection_prev.tag = caret.tag;
3059 selection_prev.pos = caret.pos;
3061 this.selection_end_anchor = true;
3068 if (selection_start == selection_end) {
3069 selection_visible = false;
3071 selection_visible = true;
3075 internal void SetSelectionToCaret(bool start) {
3077 // Invalidate old selection; selection is being reset to empty
3078 this.Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3080 selection_start.line = caret.line;
3081 selection_start.tag = caret.tag;
3082 selection_start.pos = caret.pos;
3084 // start always also selects end
3085 selection_end.line = caret.line;
3086 selection_end.tag = caret.tag;
3087 selection_end.pos = caret.pos;
3089 selection_anchor.line = caret.line;
3090 selection_anchor.tag = caret.tag;
3091 selection_anchor.pos = caret.pos;
3093 // Invalidate from previous end to caret (aka new end)
3094 if (selection_end_anchor) {
3095 if (selection_start != caret) {
3096 this.Invalidate(selection_start.line, selection_start.pos, caret.line, caret.pos);
3099 if (selection_end != caret) {
3100 this.Invalidate(selection_end.line, selection_end.pos, caret.line, caret.pos);
3104 if (caret < selection_anchor) {
3105 selection_start.line = caret.line;
3106 selection_start.tag = caret.tag;
3107 selection_start.pos = caret.pos;
3109 selection_end.line = selection_anchor.line;
3110 selection_end.tag = selection_anchor.tag;
3111 selection_end.pos = selection_anchor.pos;
3113 selection_end_anchor = true;
3115 selection_start.line = selection_anchor.line;
3116 selection_start.tag = selection_anchor.tag;
3117 selection_start.pos = selection_anchor.pos;
3119 selection_end.line = caret.line;
3120 selection_end.tag = caret.tag;
3121 selection_end.pos = caret.pos;
3123 selection_end_anchor = false;
3127 if (selection_start == selection_end) {
3128 selection_visible = false;
3130 selection_visible = true;
3134 internal void SetSelection(Line start, int start_pos, Line end, int end_pos) {
3135 if (selection_visible) {
3136 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3139 if ((end.line_no < start.line_no) || ((end == start) && (end_pos <= start_pos))) {
3140 selection_start.line = end;
3141 selection_start.tag = LineTag.FindTag(end, end_pos);
3142 selection_start.pos = end_pos;
3144 selection_end.line = start;
3145 selection_end.tag = LineTag.FindTag(start, start_pos);
3146 selection_end.pos = start_pos;
3148 selection_end_anchor = true;
3150 selection_start.line = start;
3151 selection_start.tag = LineTag.FindTag(start, start_pos);
3152 selection_start.pos = start_pos;
3154 selection_end.line = end;
3155 selection_end.tag = LineTag.FindTag(end, end_pos);
3156 selection_end.pos = end_pos;
3158 selection_end_anchor = false;
3161 selection_anchor.line = start;
3162 selection_anchor.tag = selection_start.tag;
3163 selection_anchor.pos = start_pos;
3165 if (((start == end) && (start_pos == end_pos)) || start == null || end == null) {
3166 selection_visible = false;
3168 selection_visible = true;
3173 internal void SetSelectionStart(Line start, int start_pos) {
3174 // Invalidate from the previous to the new start pos
3175 Invalidate(selection_start.line, selection_start.pos, start, start_pos);
3177 selection_start.line = start;
3178 selection_start.pos = start_pos;
3179 selection_start.tag = LineTag.FindTag(start, start_pos);
3181 selection_anchor.line = start;
3182 selection_anchor.pos = start_pos;
3183 selection_anchor.tag = selection_start.tag;
3185 selection_end_anchor = false;
3187 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
3188 selection_visible = true;
3190 // This could be calculated better
3191 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3193 selection_visible = false;
3198 internal void SetSelectionStart(int character_index) {
3203 if (character_index < 0) {
3207 CharIndexToLineTag(character_index, out line, out tag, out pos);
3208 SetSelectionStart(line, pos);
3211 internal void SetSelectionEnd(Line end, int end_pos) {
3213 if (end == selection_end.line && end_pos == selection_start.pos) {
3214 selection_anchor.line = selection_start.line;
3215 selection_anchor.tag = selection_start.tag;
3216 selection_anchor.pos = selection_start.pos;
3218 selection_end.line = selection_start.line;
3219 selection_end.tag = selection_start.tag;
3220 selection_end.pos = selection_start.pos;
3222 selection_end_anchor = false;
3223 } else if ((end.line_no < selection_anchor.line.line_no) || ((end == selection_anchor.line) && (end_pos <= selection_anchor.pos))) {
3224 selection_start.line = end;
3225 selection_start.tag = LineTag.FindTag(end, end_pos);
3226 selection_start.pos = end_pos;
3228 selection_end.line = selection_anchor.line;
3229 selection_end.tag = selection_anchor.tag;
3230 selection_end.pos = selection_anchor.pos;
3232 selection_end_anchor = true;
3234 selection_start.line = selection_anchor.line;
3235 selection_start.tag = selection_anchor.tag;
3236 selection_start.pos = selection_anchor.pos;
3238 selection_end.line = end;
3239 selection_end.tag = LineTag.FindTag(end, end_pos);
3240 selection_end.pos = end_pos;
3242 selection_end_anchor = false;
3245 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
3246 selection_visible = true;
3247 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3249 selection_visible = false;
3250 // ?? Do I need to invalidate here, tests seem to work without it, but I don't think they should :-s
3254 internal void SetSelectionEnd(int character_index) {
3259 if (character_index < 0) {
3263 CharIndexToLineTag(character_index, out line, out tag, out pos);
3264 SetSelectionEnd(line, pos);
3267 internal void SetSelection(Line start, int start_pos) {
3268 if (selection_visible) {
3269 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3272 selection_start.line = start;
3273 selection_start.pos = start_pos;
3274 selection_start.tag = LineTag.FindTag(start, start_pos);
3276 selection_end.line = start;
3277 selection_end.tag = selection_start.tag;
3278 selection_end.pos = start_pos;
3280 selection_anchor.line = start;
3281 selection_anchor.tag = selection_start.tag;
3282 selection_anchor.pos = start_pos;
3284 selection_end_anchor = false;
3285 selection_visible = false;
3288 internal void InvalidateSelectionArea() {
3289 Invalidate (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3292 // Return the current selection, as string
3293 internal string GetSelection() {
3294 // We return String.Empty if there is no selection
3295 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3296 return string.Empty;
3299 if (!multiline || (selection_start.line == selection_end.line)) {
3300 return selection_start.line.text.ToString(selection_start.pos, selection_end.pos - selection_start.pos);
3307 sb = new StringBuilder();
3308 start = selection_start.line.line_no;
3309 end = selection_end.line.line_no;
3311 sb.Append(selection_start.line.text.ToString(selection_start.pos, selection_start.line.text.Length - selection_start.pos) + Environment.NewLine);
3313 if ((start + 1) < end) {
3314 for (i = start + 1; i < end; i++) {
3315 sb.Append(GetLine(i).text.ToString() + Environment.NewLine);
3319 sb.Append(selection_end.line.text.ToString(0, selection_end.pos));
3321 return sb.ToString();
3325 internal void ReplaceSelection(string s) {
3328 // First, delete any selected text
3329 if ((selection_start.pos != selection_end.pos) || (selection_start.line != selection_end.line)) {
3330 if (!multiline || (selection_start.line == selection_end.line)) {
3331 undo.RecordDeleteChars(selection_start.line, selection_start.pos + 1, selection_end.pos - selection_start.pos);
3333 DeleteChars(selection_start.tag, selection_start.pos, selection_end.pos - selection_start.pos);
3335 // The tag might have been removed, we need to recalc it
3336 selection_start.tag = selection_start.line.FindTag(selection_start.pos);
3341 start = selection_start.line.line_no;
3342 end = selection_end.line.line_no;
3344 undo.RecordDelete(selection_start.line, selection_start.pos + 1, selection_end.line, selection_end.pos);
3346 // Delete first line
3347 DeleteChars(selection_start.tag, selection_start.pos, selection_start.line.text.Length - selection_start.pos);
3350 DeleteChars(selection_end.line.tags, 0, selection_end.pos);
3354 for (i = end - 1; i >= start; i--) {
3359 // BIG FAT WARNING - selection_end.line might be stale due
3360 // to the above Delete() call. DONT USE IT before hitting the end of this method!
3362 // Join start and end
3363 Combine(selection_start.line.line_no, start);
3368 Insert(selection_start.line, null, selection_start.pos, true, s);
3370 selection_end.line = selection_start.line;
3371 selection_end.pos = selection_start.pos;
3372 selection_end.tag = selection_start.tag;
3374 selection_visible = false;
3377 internal void CharIndexToLineTag(int index, out Line line_out, out LineTag tag_out, out int pos) {
3386 for (i = 1; i <= lines; i++) {
3390 chars += line.text.Length + crlf_size;
3392 if (index <= chars) {
3393 // we found the line
3396 while (tag != null) {
3397 if (index < (start + tag.start + tag.length)) {
3399 tag_out = LineTag.GetFinalTag (tag);
3400 pos = index - start;
3403 if (tag.next == null) {
3406 next_line = GetLine(line.line_no + 1);
3408 if (next_line != null) {
3409 line_out = next_line;
3410 tag_out = LineTag.GetFinalTag (next_line.tags);
3415 tag_out = LineTag.GetFinalTag (tag);
3416 pos = line_out.text.Length;
3425 line_out = GetLine(lines);
3426 tag = line_out.tags;
3427 while (tag.next != null) {
3431 pos = line_out.text.Length;
3434 internal int LineTagToCharIndex(Line line, int pos) {
3438 // Count first and last line
3441 // Count the lines in the middle
3443 for (i = 1; i < line.line_no; i++) {
3444 length += GetLine(i).text.Length + crlf_size;
3452 internal int SelectionLength() {
3453 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3457 if (!multiline || (selection_start.line == selection_end.line)) {
3458 return selection_end.pos - selection_start.pos;
3465 // Count first and last line
3466 length = selection_start.line.text.Length - selection_start.pos + selection_end.pos + crlf_size;
3468 // Count the lines in the middle
3469 start = selection_start.line.line_no + 1;
3470 end = selection_end.line.line_no;
3473 for (i = start; i < end; i++) {
3474 length += GetLine(i).text.Length + crlf_size;
3485 /// <summary>Give it a Line number and it returns the Line object at with that line number</summary>
3486 internal Line GetLine(int LineNo) {
3487 Line line = document;
3489 while (line != sentinel) {
3490 if (LineNo == line.line_no) {
3492 } else if (LineNo < line.line_no) {
3502 /// <summary>Retrieve the previous tag; walks line boundaries</summary>
3503 internal LineTag PreviousTag(LineTag tag) {
3506 if (tag.previous != null) {
3507 return tag.previous;
3511 if (tag.line.line_no == 1) {
3515 l = GetLine(tag.line.line_no - 1);
3520 while (t.next != null) {
3529 /// <summary>Retrieve the next tag; walks line boundaries</summary>
3530 internal LineTag NextTag(LineTag tag) {
3533 if (tag.next != null) {
3538 l = GetLine(tag.line.line_no + 1);
3546 internal Line ParagraphStart(Line line) {
3547 while (line.soft_break) {
3548 line = GetLine(line.line_no - 1);
3553 internal Line ParagraphEnd(Line line) {
3556 while (line.soft_break) {
3557 l = GetLine(line.line_no + 1);
3558 if ((l == null) || (!l.soft_break)) {
3566 /// <summary>Give it a Y pixel coordinate and it returns the Line covering that Y coordinate</summary>
3567 internal Line GetLineByPixel(int y, bool exact) {
3568 Line line = document;
3571 while (line != sentinel) {
3573 if ((y >= line.Y) && (y < (line.Y+line.height))) {
3575 } else if (y < line.Y) {
3588 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3589 internal LineTag FindTag(int x, int y, out int index, bool exact) {
3593 line = GetLineByPixel(y, exact);
3600 // Alignment adjustment
3601 x += line.align_shift;
3604 if (x >= tag.X && x < (tag.X+tag.width)) {
3607 end = tag.start + tag.length - 1;
3609 for (int pos = tag.start; pos < end; pos++) {
3610 if (x < line.widths[pos]) {
3612 return LineTag.GetFinalTag (tag);
3616 return LineTag.GetFinalTag (tag);
3618 if (tag.next != null) {
3626 index = line.text.Length;
3627 return LineTag.GetFinalTag (tag);
3632 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3633 internal LineTag FindCursor(int x, int y, out int index) {
3637 line = GetLineByPixel(y, false);
3640 // Adjust for alignment
3641 x -= line.align_shift;
3644 if (x >= tag.X && x < (tag.X+tag.width)) {
3647 end = tag.start + tag.length - 1;
3649 for (int pos = tag.start-1; pos < end; pos++) {
3650 // When clicking on a character, we position the cursor to whatever edge
3651 // of the character the click was closer
3652 if (x < (line.widths[pos] + ((line.widths[pos+1]-line.widths[pos])/2))) {
3660 if (tag.next != null) {
3663 index = line.text.Length;
3669 /// <summary>Format area of document in specified font and color</summary>
3670 /// <param name="start_pos">1-based start position on start_line</param>
3671 /// <param name="end_pos">1-based end position on end_line </param>
3672 internal void FormatText(Line start_line, int start_pos, Line end_line, int end_pos, Font font, Brush color) {
3675 // First, format the first line
3676 if (start_line != end_line) {
3678 LineTag.FormatText(start_line, start_pos, start_line.text.Length - start_pos + 1, font, color);
3681 LineTag.FormatText(end_line, 1, end_pos, font, color);
3683 // Now all the lines inbetween
3684 for (int i = start_line.line_no + 1; i < end_line.line_no; i++) {
3686 LineTag.FormatText(l, 1, l.text.Length, font, color);
3689 // Special case, single line
3690 LineTag.FormatText(start_line, start_pos, end_pos - start_pos, font, color);
3694 /// <summary>Re-format areas of the document in specified font and color</summary>
3695 /// <param name="start_pos">1-based start position on start_line</param>
3696 /// <param name="end_pos">1-based end position on end_line </param>
3697 /// <param name="font">Font specifying attributes</param>
3698 /// <param name="color">Color (or NULL) to apply</param>
3699 /// <param name="apply">Attributes from font and color to apply</param>
3700 internal void FormatText(Line start_line, int start_pos, Line end_line, int end_pos, FontDefinition attributes) {
3703 // First, format the first line
3704 if (start_line != end_line) {
3706 LineTag.FormatText(start_line, start_pos, start_line.text.Length - start_pos + 1, attributes);
3709 LineTag.FormatText(end_line, 1, end_pos - 1, attributes);
3711 // Now all the lines inbetween
3712 for (int i = start_line.line_no + 1; i < end_line.line_no; i++) {
3714 LineTag.FormatText(l, 1, l.text.Length, attributes);
3717 // Special case, single line
3718 LineTag.FormatText(start_line, start_pos, end_pos - start_pos, attributes);
3722 internal void RecalculateAlignments() {
3728 while (line_no <= lines) {
3729 line = GetLine(line_no);
3732 switch (line.alignment) {
3733 case HorizontalAlignment.Left:
3734 line.align_shift = 0;
3736 case HorizontalAlignment.Center:
3737 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3739 case HorizontalAlignment.Right:
3740 line.align_shift = viewport_width - (int)line.widths[line.text.Length];
3750 /// <summary>Calculate formatting for the whole document</summary>
3751 internal bool RecalculateDocument(Graphics g) {
3752 return RecalculateDocument(g, 1, this.lines, false);
3755 /// <summary>Calculate formatting starting at a certain line</summary>
3756 internal bool RecalculateDocument(Graphics g, int start) {
3757 return RecalculateDocument(g, start, this.lines, false);
3760 /// <summary>Calculate formatting within two given line numbers</summary>
3761 internal bool RecalculateDocument(Graphics g, int start, int end) {
3762 return RecalculateDocument(g, start, end, false);
3765 /// <summary>With optimize on, returns true if line heights changed</summary>
3766 internal bool RecalculateDocument(Graphics g, int start, int end, bool optimize) {
3775 recalc_pending = true;
3776 recalc_start = start;
3778 recalc_optimize = optimize;
3782 Y = GetLine(start).Y;
3787 changed = true; // We always return true if we run non-optimized
3792 while (line_no <= (end + this.lines - shift)) {
3793 line = GetLine(line_no++);
3798 line.RecalculateLine(g, this);
3800 if (line.recalc && line.RecalculateLine(g, this)) {
3802 // If the height changed, all subsequent lines change
3809 line.RecalculatePasswordLine(g, this);
3811 if (line.recalc && line.RecalculatePasswordLine(g, this)) {
3813 // If the height changed, all subsequent lines change
3820 if (line.widths[line.text.Length] > new_width) {
3821 new_width = (int)line.widths[line.text.Length];
3824 // Calculate alignment
3825 if (line.alignment != HorizontalAlignment.Left) {
3826 if (line.alignment == HorizontalAlignment.Center) {
3827 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3829 line.align_shift = viewport_width - (int)line.widths[line.text.Length] - 1;
3835 if (line_no > lines) {
3840 if (document_x != new_width) {
3841 document_x = new_width;
3842 if (WidthChanged != null) {
3843 WidthChanged(this, null);
3847 RecalculateAlignments();
3849 line = GetLine(lines);
3851 if (document_y != line.Y + line.height) {
3852 document_y = line.Y + line.height;
3853 if (HeightChanged != null) {
3854 HeightChanged(this, null);
3861 internal int Size() {
3865 private void owner_HandleCreated(object sender, EventArgs e) {
3866 RecalculateDocument(owner.CreateGraphicsInternal());
3870 private void owner_VisibleChanged(object sender, EventArgs e) {
3871 if (owner.Visible) {
3872 RecalculateDocument(owner.CreateGraphicsInternal());
3876 internal static bool IsWordSeparator(char ch) {
3890 internal int FindWordSeparator(Line line, int pos, bool forward) {
3893 len = line.text.Length;
3896 for (int i = pos + 1; i < len; i++) {
3897 if (IsWordSeparator(line.Text[i])) {
3903 for (int i = pos - 1; i > 0; i--) {
3904 if (IsWordSeparator(line.Text[i - 1])) {
3912 /* Search document for text */
3913 internal bool FindChars(char[] chars, Marker start, Marker end, out Marker result) {
3919 // Search for occurence of any char in the chars array
3920 result = new Marker();
3923 line_no = start.line.line_no;
3925 while (line_no <= end.line.line_no) {
3926 line_len = line.text.Length;
3927 while (pos < line_len) {
3928 for (int i = 0; i < chars.Length; i++) {
3929 if (line.text[pos] == chars[i]) {
3931 if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
3945 line = GetLine(line_no);
3951 // This version does not build one big string for searching, instead it handles
3952 // line-boundaries, which is faster and less memory intensive
3953 // FIXME - Depending on culture stuff we might have to create a big string and use culturespecific
3954 // search stuff and change it to accept and return positions instead of Markers (which would match
3955 // RichTextBox behaviour better but would be inconsistent with the rest of TextControl)
3956 internal bool Find(string search, Marker start, Marker end, out Marker result, RichTextBoxFinds options) {
3958 string search_string;
3970 result = new Marker();
3971 word_option = ((options & RichTextBoxFinds.WholeWord) != 0);
3972 ignore_case = ((options & RichTextBoxFinds.MatchCase) == 0);
3973 reverse = ((options & RichTextBoxFinds.Reverse) != 0);
3976 line_no = start.line.line_no;
3980 // Prep our search string, lowercasing it if we do case-independent matching
3983 sb = new StringBuilder(search);
3984 for (int i = 0; i < sb.Length; i++) {
3985 sb[i] = Char.ToLower(sb[i]);
3987 search_string = sb.ToString();
3989 search_string = search;
3992 // We need to check if the character before our start position is a wordbreak
3995 if ((pos == 0) || (IsWordSeparator(line.text[pos - 1]))) {
4002 if (IsWordSeparator(line.text[pos - 1])) {
4008 // Need to check the end of the previous line
4011 prev_line = GetLine(line_no - 1);
4012 if (prev_line.soft_break) {
4013 if (IsWordSeparator(prev_line.text[prev_line.text.Length - 1])) {
4027 // To avoid duplication of this loop with reverse logic, we search
4028 // through the document, remembering the last match and when returning
4029 // report that last remembered match
4031 last = new Marker();
4032 last.height = -1; // Abused - we use it to track change
4034 while (line_no <= end.line.line_no) {
4035 if (line_no != end.line.line_no) {
4036 line_len = line.text.Length;
4041 while (pos < line_len) {
4042 if (word_option && (current == search_string.Length)) {
4043 if (IsWordSeparator(line.text[pos])) {
4056 c = Char.ToLower(line.text[pos]);
4061 if (c == search_string[current]) {
4066 if (!word_option || (word_option && (word || (current > 0)))) {
4070 if (!word_option && (current == search_string.Length)) {
4087 if (IsWordSeparator(c)) {
4095 // Mark that we just saw a word boundary
4096 if (!line.soft_break) {
4100 if (current == search_string.Length) {
4116 line = GetLine(line_no);
4120 if (last.height != -1) {
4130 // if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
4142 internal void GetMarker(out Marker mark, bool start) {
4143 mark = new Marker();
4146 mark.line = GetLine(1);
4147 mark.tag = mark.line.tags;
4150 mark.line = GetLine(lines);
4151 mark.tag = mark.line.tags;
4152 while (mark.tag.next != null) {
4153 mark.tag = mark.tag.next;
4155 mark.pos = mark.line.text.Length;
4158 #endregion // Internal Methods
4161 internal event EventHandler CaretMoved;
4162 internal event EventHandler WidthChanged;
4163 internal event EventHandler HeightChanged;
4164 internal event EventHandler LengthChanged;
4165 #endregion // Events
4167 #region Administrative
4168 public IEnumerator GetEnumerator() {
4173 public override bool Equals(object obj) {
4178 if (!(obj is Document)) {
4186 if (ToString().Equals(((Document)obj).ToString())) {
4193 public override int GetHashCode() {
4197 public override string ToString() {
4198 return "document " + this.document_id;
4200 #endregion // Administrative
4203 internal class LineTag {
4204 #region Local Variables;
4205 // Payload; formatting
4206 internal Font font; // System.Drawing.Font object for this tag
4207 internal Brush color; // System.Drawing.Brush object
4210 internal int start; // start, in chars; index into Line.text
4211 internal int length; // length, in chars
4212 internal bool r_to_l; // Which way is the font
4215 internal int height; // Height in pixels of the text this tag describes
4216 internal int X; // X location of the text this tag describes
4217 internal float width; // Width in pixels of the text this tag describes
4218 internal int ascent; // Ascent of the font for this tag
4219 internal int shift; // Shift down for this tag, to stay on baseline
4222 internal Line line; // The line we're on
4223 internal LineTag next; // Next tag on the same line
4224 internal LineTag previous; // Previous tag on the same line
4227 #region Constructors
4228 internal LineTag(Line line, int start, int length) {
4231 this.length = length;
4235 #endregion // Constructors
4237 #region Internal Methods
4238 ///<summary>Break a tag into two with identical attributes; pos is 1-based; returns tag starting at >pos< or null if end-of-line</summary>
4239 internal LineTag Break(int pos) {
4243 if (pos == this.start) {
4245 } else if (pos >= (start + length)) {
4249 new_tag = new LineTag(line, pos, start + length - pos);
4250 new_tag.color = color;
4251 new_tag.font = font;
4252 this.length -= new_tag.length;
4253 new_tag.next = this.next;
4254 this.next = new_tag;
4255 new_tag.previous = this;
4256 if (new_tag.next != null) {
4257 new_tag.next.previous = new_tag;
4263 ///<summary>Create new font and brush from existing font and given new attributes. Returns true if fontheight changes</summary>
4264 internal static bool GenerateTextFormat(Font font_from, Brush color_from, FontDefinition attributes, out Font new_font, out Brush new_color) {
4270 if (attributes.font_obj == null) {
4271 size = font_from.SizeInPoints;
4272 unit = font_from.Unit;
4273 face = font_from.Name;
4274 style = font_from.Style;
4276 if (attributes.face != null) {
4277 face = attributes.face;
4280 if (attributes.size != 0) {
4281 size = attributes.size;
4284 style |= attributes.add_style;
4285 style &= ~attributes.remove_style;
4288 new_font = new Font(face, size, style, unit);
4290 new_font = attributes.font_obj;
4293 // Create 'new' color brush
4294 if (attributes.color != Color.Empty) {
4295 new_color = new SolidBrush(attributes.color);
4297 new_color = color_from;
4300 if (new_font.Height == font_from.Height) {
4306 /// <summary>Applies 'font' and 'brush' to characters starting at 'start' for 'length' chars;
4307 /// Removes any previous tags overlapping the same area;
4308 /// returns true if lineheight has changed</summary>
4309 /// <param name="start">1-based character position on line</param>
4310 internal static bool FormatText(Line line, int start, int length, Font font, Brush color) {
4314 bool retval = false; // Assume line-height doesn't change
4317 if (font.Height != line.height) {
4320 line.recalc = true; // This forces recalculation of the line in RecalculateDocument
4322 // A little sanity, not sure if it's needed, might be able to remove for speed
4323 if (length > line.text.Length) {
4324 length = line.text.Length;
4328 end = start + length;
4330 // Common special case
4331 if ((start == 1) && (length == tag.length)) {
4338 //Console.WriteLine("Finding tag for {0} {1}", line, start);
4339 start_tag = FindTag(line, start);
4340 if (start_tag == null) { // FIXME - is there a better way to handle this, or do we even need it?
4341 throw new Exception(String.Format("Could not find start_tag in document at line {0} position {1}", line.line_no, start));
4344 tag = new LineTag(line, start, length);
4351 //Console.WriteLine("Start tag: '{0}'", start_tag!=null ? start_tag.ToString() : "NULL");
4352 if (start_tag.start == start) {
4353 tag.next = start_tag;
4354 tag.previous = start_tag.previous;
4355 if (start_tag.previous != null) {
4356 start_tag.previous.next = tag;
4358 start_tag.previous = tag;
4360 // Insert ourselves 'in the middle'
4361 if ((start_tag.next != null) && (start_tag.next.start < end)) {
4362 tag.next = start_tag.next;
4364 tag.next = new LineTag(line, start_tag.start, start_tag.length);
4365 tag.next.font = start_tag.font;
4366 tag.next.color = start_tag.color;
4368 if (start_tag.next != null) {
4369 tag.next.next = start_tag.next;
4370 tag.next.next.previous = tag.next;
4373 tag.next.previous = tag;
4375 start_tag.length = start - start_tag.start;
4377 tag.previous = start_tag;
4378 start_tag.next = tag;
4380 if (tag.next.start > (tag.start + tag.length)) {
4381 tag.next.length += tag.next.start - (tag.start + tag.length);
4382 tag.next.start = tag.start + tag.length;
4389 while ((tag != null) && (tag.start < end)) {
4390 if ((tag.start + tag.length) <= end) {
4392 tag.previous.next = tag.next;
4393 if (tag.next != null) {
4394 tag.next.previous = tag.previous;
4398 // Adjust the length of the tag
4399 tag.length = (tag.start + tag.length) - end;
4408 /// <summary>Applies font attributes specified to characters starting at 'start' for 'length' chars;
4409 /// Breaks tags at start and end point, keeping middle tags with altered attributes.
4410 /// Returns true if lineheight has changed</summary>
4411 /// <param name="start">1-based character position on line</param>
4412 internal static bool FormatText(Line line, int start, int length, FontDefinition attributes) {
4416 bool retval = false; // Assume line-height doesn't change
4418 line.recalc = true; // This forces recalculation of the line in RecalculateDocument
4420 // A little sanity, not sure if it's needed, might be able to remove for speed
4421 if (length > line.text.Length) {
4422 length = line.text.Length;
4427 // Common special case
4428 if ((start == 1) && (length == tag.length)) {
4430 GenerateTextFormat(tag.font, tag.color, attributes, out tag.font, out tag.color);
4434 start_tag = FindTag(line, start);
4436 if (start_tag == null) {
4438 // We are 'starting' after all valid tags; create a new tag with the right attributes
4439 start_tag = FindTag(line, line.text.Length - 1);
4440 start_tag.next = new LineTag(line, line.text.Length + 1, 0);
4441 start_tag.next.font = start_tag.font;
4442 start_tag.next.color = start_tag.color;
4443 start_tag.next.previous = start_tag;
4444 start_tag = start_tag.next;
4446 throw new Exception(String.Format("Could not find start_tag in document at line {0} position {1}", line.line_no, start));
4449 start_tag = start_tag.Break(start);
4452 end_tag = FindTag(line, start + length);
4453 if (end_tag != null) {
4454 end_tag = end_tag.Break(start + length);
4457 // start_tag or end_tag might be null; we're cool with that
4458 // we now walk from start_tag to end_tag, applying new attributes
4460 while ((tag != null) && tag != end_tag) {
4461 if (LineTag.GenerateTextFormat(tag.font, tag.color, attributes, out tag.font, out tag.color)) {
4470 /// <summary>Finds the tag that describes the character at position 'pos' on 'line'</summary>
4471 internal static LineTag FindTag(Line line, int pos) {
4472 LineTag tag = line.tags;
4474 // Beginning of line is a bit special
4476 // Not sure if we should get the final tag here
4480 while (tag != null) {
4481 if ((tag.start <= pos) && (pos < (tag.start+tag.length))) {
4482 return GetFinalTag (tag);
4491 // There can be multiple tags at the same position, we want to make
4492 // sure we are using the very last tag at the given position
4493 internal static LineTag GetFinalTag (LineTag tag)
4497 while (res.next != null && res.next.length == 0)
4502 /// <summary>Combines 'this' tag with 'other' tag</summary>
4503 internal bool Combine(LineTag other) {
4504 if (!this.Equals(other)) {
4508 this.width += other.width;
4509 this.length += other.length;
4510 this.next = other.next;
4511 if (this.next != null) {
4512 this.next.previous = this;
4519 /// <summary>Remove 'this' tag ; to be called when formatting is to be removed</summary>
4520 internal bool Remove() {
4521 if ((this.start == 1) && (this.next == null)) {
4522 // We cannot remove the only tag
4525 if (this.start != 1) {
4526 this.previous.length += this.length;
4527 this.previous.width = -1;
4528 this.previous.next = this.next;
4529 this.next.previous = this.previous;
4531 this.next.start = 1;
4532 this.next.length += this.length;
4533 this.next.width = -1;
4534 this.line.tags = this.next;
4535 this.next.previous = null;
4541 /// <summary>Checks if 'this' tag describes the same formatting options as 'obj'</summary>
4542 public override bool Equals(object obj) {
4549 if (!(obj is LineTag)) {
4557 other = (LineTag)obj;
4559 if (this.font.Equals(other.font) && this.color.Equals(other.color)) { // FIXME add checking for things like link or type later
4566 public override int GetHashCode() {
4567 return base.GetHashCode ();
4570 public override string ToString() {
4572 return "Tag starts at index " + this.start + "length " + this.length + " text: " + this.line.Text.Substring(this.start-1, this.length) + "Font " + this.font.ToString();
4573 return "Zero Lengthed tag at index " + this.start;
4576 #endregion // Internal Methods
4579 internal class UndoClass {
4580 internal enum ActionType {
4589 internal class Action {
4590 internal ActionType type;
4591 internal int line_no;
4593 internal object data;
4596 #region Local Variables
4597 private Document document;
4598 private Stack undo_actions;
4599 private Stack redo_actions;
4600 private int caret_line;
4601 private int caret_pos;
4602 #endregion // Local Variables
4604 #region Constructors
4605 internal UndoClass(Document doc) {
4607 undo_actions = new Stack(50);
4608 redo_actions = new Stack(50);
4610 #endregion // Constructors
4613 [MonoTODO("Change this to be configurable")]
4614 internal int UndoLevels {
4616 return undo_actions.Count;
4620 [MonoTODO("Change this to be configurable")]
4621 internal int RedoLevels {
4623 return redo_actions.Count;
4627 [MonoTODO("Come up with good naming and localization")]
4628 internal string UndoName {
4632 action = (Action)undo_actions.Peek();
4633 switch(action.type) {
4634 case ActionType.InsertChar: {
4635 Locale.GetText("Insert character");
4639 case ActionType.DeleteChar: {
4640 Locale.GetText("Delete character");
4644 case ActionType.InsertString: {
4645 Locale.GetText("Insert string");
4649 case ActionType.DeleteChars: {
4650 Locale.GetText("Delete string");
4654 case ActionType.CursorMove: {
4655 Locale.GetText("Cursor move");
4663 internal string RedoName() {
4666 #endregion // Properties
4668 #region Internal Methods
4669 internal void Clear() {
4670 undo_actions.Clear();
4671 redo_actions.Clear();
4674 internal void Undo() {
4677 if (undo_actions.Count == 0) {
4681 action = (Action)undo_actions.Pop();
4683 // Put onto redo stack
4684 redo_actions.Push(action);
4687 switch(action.type) {
4688 case ActionType.InsertChar: {
4689 // FIXME - implement me
4693 case ActionType.DeleteChars: {
4694 this.Insert(document.GetLine(action.line_no), action.pos, (Line)action.data);
4695 Undo(); // Grab the cursor location
4699 case ActionType.CursorMove: {
4700 document.caret.line = document.GetLine(action.line_no);
4701 if (document.caret.line == null) {
4706 document.caret.tag = document.caret.line.FindTag(action.pos);
4707 document.caret.pos = action.pos;
4708 document.caret.height = document.caret.tag.height;
4710 if (document.owner.IsHandleCreated) {
4711 XplatUI.DestroyCaret(document.owner.Handle);
4712 XplatUI.CreateCaret(document.owner.Handle, 2, document.caret.height);
4713 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);
4714 XplatUI.CaretVisible(document.owner.Handle, true);
4717 // FIXME - enable call
4718 //if (document.CaretMoved != null) document.CaretMoved(this, EventArgs.Empty);
4724 internal void Redo() {
4725 if (redo_actions.Count == 0) {
4729 #endregion // Internal Methods
4731 #region Private Methods
4733 public void RecordDeleteChars(Line line, int pos, int length) {
4734 RecordDelete(line, pos, line, pos + length - 1);
4737 // start_pos, end_pos = 1 based
4738 public void RecordDelete(Line start_line, int start_pos, Line end_line, int end_pos) {
4742 l = Duplicate(start_line, start_pos, end_line, end_pos);
4745 a.type = ActionType.DeleteChars;
4747 a.line_no = start_line.line_no;
4748 a.pos = start_pos - 1;
4750 // Record the cursor position before, since the actions will occur in reverse order
4752 undo_actions.Push(a);
4755 public void RecordCursor() {
4756 if (document.caret.line == null) {
4760 RecordCursor(document.caret.line, document.caret.pos);
4763 public void RecordCursor(Line line, int pos) {
4766 if ((line.line_no == caret_line) && (pos == caret_pos)) {
4770 caret_line = line.line_no;
4774 a.type = ActionType.CursorMove;
4775 a.line_no = line.line_no;
4778 undo_actions.Push(a);
4781 // start_pos = 1-based
4782 // end_pos = 1-based
4783 public Line Duplicate(Line start_line, int start_pos, Line end_line, int end_pos) {
4788 LineTag current_tag;
4797 for (int i = start_line.line_no; i <= end_line.line_no; i++) {
4798 current = document.GetLine(i);
4800 if (start_line.line_no == i) {
4806 if (end_line.line_no == i) {
4809 end = current.text.Length;
4813 line.text = new StringBuilder(current.text.ToString(start - 1, end - start + 1));
4815 // Copy tags from start to start+length onto new line
4816 current_tag = current.FindTag(start - 1);
4817 while ((current_tag != null) && (current_tag.start < end)) {
4818 if ((current_tag.start <= start) && (start < (current_tag.start + current_tag.length))) {
4819 // start tag is within this tag
4822 tag_start = current_tag.start;
4825 if (end < (current_tag.start + current_tag.length)) {
4826 tag_length = end - tag_start + 1;
4828 tag_length = current_tag.start + current_tag.length - tag_start;
4830 tag = new LineTag(line, tag_start - start + 1, tag_length);
4831 tag.color = current_tag.color;
4832 tag.font = current_tag.font;
4834 current_tag = current_tag.next;
4836 // Add the new tag to the line
4837 if (line.tags == null) {
4843 while (tail.next != null) {
4847 tag.previous = tail;
4851 if ((i + 1) <= end_line.line_no) {
4852 line.soft_break = current.soft_break;
4854 // Chain them (we use right/left as next/previous)
4855 line.right = new Line();
4856 line.right.left = line;
4864 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
4865 internal void Insert(Line line, int pos, Line insert) {
4872 // Handle special case first
4873 if (insert.right == null) {
4875 // Single line insert
4876 document.Split(line, pos);
4878 if (insert.tags == null) {
4879 return; // Blank line
4882 //Insert our tags at the end
4885 while (tag.next != null) {
4889 offset = tag.start + tag.length - 1;
4891 tag.next = insert.tags;
4892 line.text.Insert(offset, insert.text.ToString());
4894 // Adjust start locations
4896 while (tag != null) {
4897 tag.start += offset;
4901 // Put it back together
4902 document.Combine(line.line_no, line.line_no + 1);
4903 document.UpdateView(line, pos);
4910 while (current != null) {
4911 if (current == insert) {
4912 // Inserting the first line we split the line (and make space)
4913 document.Split(line, pos);
4914 //Insert our tags at the end of the line
4918 while (tag.next != null) {
4921 offset = tag.start + tag.length - 1;
4922 tag.next = current.tags;
4923 tag.next.previous = tag;
4929 line.tags = current.tags;
4930 line.tags.previous = null;
4934 document.Split(line.line_no, 0);
4936 line.tags = current.tags;
4937 line.tags.previous = null;
4940 // Adjust start locations and line pointers
4941 while (tag != null) {
4942 tag.start += offset;
4947 line.text.Insert(offset, current.text.ToString());
4948 line.Grow(line.text.Length);
4951 line = document.GetLine(line.line_no + 1);
4953 // FIXME? Test undo of line-boundaries
4954 if ((current.right == null) && (current.tags.length != 0)) {
4955 document.Combine(line.line_no - 1, line.line_no);
4957 current = current.right;
4962 // Recalculate our document
4963 document.UpdateView(first, lines, pos);
4966 #endregion // Private Methods