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;
76 internal enum FormatSpecified {
84 internal enum CaretDirection {
85 CharForward, // Move a char to the right
86 CharBack, // Move a char to the left
87 LineUp, // Move a line up
88 LineDown, // Move a line down
89 Home, // Move to the beginning of the line
90 End, // Move to the end of the line
91 PgUp, // Move one page up
92 PgDn, // Move one page down
93 CtrlPgUp, // Move caret to the first visible char in the viewport
94 CtrlPgDn, // Move caret to the last visible char in the viewport
95 CtrlHome, // Move to the beginning of the document
96 CtrlEnd, // Move to the end of the document
97 WordBack, // Move to the beginning of the previous word (or beginning of line)
98 WordForward, // Move to the beginning of the next word (or end of line)
99 SelectionStart, // Move to the beginning of the current selection
100 SelectionEnd, // Move to the end of the current selection
101 CharForwardNoWrap, // Move a char forward, but don't wrap onto the next line
102 CharBackNoWrap // Move a char backward, but don't wrap onto the previous line
105 // Being cloneable should allow for nice line and document copies...
106 internal class Line : ICloneable, IComparable {
107 #region Local Variables
108 // Stuff that matters for our line
109 internal StringBuilder text; // Characters for the line
110 internal float[] widths; // Width of each character; always one larger than text.Length
111 internal int space; // Number of elements in text and widths
112 internal int line_no; // Line number
113 internal LineTag tags; // Tags describing the text
114 internal int Y; // Baseline
115 internal int height; // Height of the line (height of tallest tag)
116 internal int ascent; // Ascent of the line (ascent of the tallest tag)
117 internal HorizontalAlignment alignment; // Alignment of the line
118 internal int align_shift; // Pixel shift caused by the alignment
119 internal bool soft_break; // Tag is 'broken soft' and continuation from previous line
120 internal int indent; // Left indent for the first line
121 internal int hanging_indent; // Hanging indent (left indent for all but the first line)
122 internal int right_indent; // Right indent for all lines
123 internal bool carriage_return;
126 // Stuff that's important for the tree
127 internal Line parent; // Our parent line
128 internal Line left; // Line with smaller line number
129 internal Line right; // Line with higher line number
130 internal LineColor color; // We're doing a black/red tree. this is the node color
131 internal int DEFAULT_TEXT_LEN; //
132 internal bool recalc; // Line changed
133 #endregion // Local Variables
137 color = LineColor.Red;
144 alignment = HorizontalAlignment.Left;
147 internal Line(int LineNo, string Text, Font font, SolidBrush color) : this() {
148 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
150 text = new StringBuilder(Text, space);
153 widths = new float[space + 1];
154 tags = new LineTag(this, 1);
159 internal Line(int LineNo, string Text, HorizontalAlignment align, Font font, SolidBrush color) : this() {
160 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
162 text = new StringBuilder(Text, space);
166 widths = new float[space + 1];
167 tags = new LineTag(this, 1);
172 internal Line(int LineNo, string Text, LineTag tag) : this() {
173 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
175 text = new StringBuilder(Text, space);
178 widths = new float[space + 1];
182 #endregion // Constructors
184 #region Internal Properties
185 internal int Indent {
196 internal int HangingIndent {
198 return hanging_indent;
202 hanging_indent = value;
207 internal int RightIndent {
213 right_indent = value;
219 internal int Height {
229 internal int LineNo {
239 internal string Text {
241 return text.ToString();
245 text = new StringBuilder(value, value.Length > DEFAULT_TEXT_LEN ? value.Length : DEFAULT_TEXT_LEN);
249 internal HorizontalAlignment Alignment {
255 if (alignment != value) {
262 internal StringBuilder Text {
272 #endregion // Internal Properties
274 #region Internal Methods
275 // Make sure we always have enoughs space in text and widths
276 internal void Grow(int minimum) {
280 length = text.Length;
282 if ((length + minimum) > space) {
283 // We need to grow; double the size
285 if ((length + minimum) > (space * 2)) {
286 new_widths = new float[length + minimum * 2 + 1];
287 space = length + minimum * 2;
289 new_widths = new float[space * 2 + 1];
292 widths.CopyTo(new_widths, 0);
298 internal void Streamline(int lines) {
305 // Catch what the loop below wont; eliminate 0 length
306 // tags, but only if there are other tags after us
307 while ((current.length == 0) && (next != null)) {
309 tags.previous = null;
318 while (next != null) {
319 // Take out 0 length tags unless it's the last tag in the document
320 if (next.length == 0) {
321 if ((next.next != null) || (line_no != lines)) {
322 current.next = next.next;
323 if (current.next != null) {
324 current.next.previous = current;
330 if (current.Combine(next)) {
335 current = current.next;
340 /// <summary> Find the tag on a line based on the character position, pos is 0-based</summary>
341 internal LineTag FindTag(int pos) {
350 if (pos >= text.Length) {
351 pos = text.Length - 1;
354 while (tag != null) {
355 if (((tag.start - 1) <= pos) && (pos < (tag.start + tag.length - 1))) {
356 return LineTag.GetFinalTag (tag);
364 /// Recalculate a single line using the same char for every character in the line
367 internal bool RecalculatePasswordLine(Graphics g, Document doc) {
376 len = this.text.Length;
384 w = g.MeasureString(doc.password_char, tags.font, 10000, Document.string_format).Width;
386 if (this.height != (int)tag.font.Height) {
392 this.height = (int)tag.font.Height;
393 tag.height = this.height;
395 XplatUI.GetFontMetrics(g, tag.font, out tag.ascent, out descent);
396 this.ascent = tag.ascent;
400 widths[pos] = widths[pos-1] + w;
407 /// Go through all tags on a line and recalculate all size-related values;
408 /// returns true if lineheight changed
410 internal bool RecalculateLine(Graphics g, Document doc) {
423 len = this.text.Length;
425 prev_height = this.height; // For drawing optimization calculations
426 this.height = 0; // Reset line height
427 this.ascent = 0; // Reset the ascent for the line
430 if (this.soft_break) {
431 widths[0] = hanging_indent;
444 while (tag.length == 0) { // We should always have tags after a tag.length==0 unless len==0
450 size = tag.SizeOfPosition (g, pos);
453 if (Char.IsWhiteSpace(text[pos])) {
458 if ((wrap_pos > 0) && (wrap_pos != len) && (widths[pos] + w) + 5 > (doc.viewport_width - this.right_indent)) {
459 // Make sure to set the last width of the line before wrapping
460 widths [pos + 1] = widths [pos] + w;
464 doc.Split(this, tag, pos, this.soft_break);
465 this.soft_break = true;
466 len = this.text.Length;
470 } else if (pos > 1 && (widths[pos] + w) > (doc.viewport_width - this.right_indent)) {
471 // No suitable wrap position was found so break right in the middle of a word
473 // Make sure to set the last width of the line before wrapping
474 widths [pos + 1] = widths [pos] + w;
476 doc.Split(this, tag, pos, this.soft_break);
477 this.soft_break = true;
478 len = this.text.Length;
484 // Contract all soft lines that follow back into our line
488 widths[pos] = widths[pos-1] + w;
491 line = doc.GetLine(this.line_no + 1);
492 if ((line != null) && soft_break) {
493 // Pull the two lines together
494 doc.Combine(this.line_no, this.line_no + 1);
495 len = this.text.Length;
501 if (pos == (tag.start-1 + tag.length)) {
502 // We just found the end of our current tag
503 tag.height = tag.MaxHeight ();
505 // Check if we're the tallest on the line (so far)
506 if (tag.height > this.height) {
507 this.height = tag.height; // Yep; make sure the line knows
510 if (tag.ascent == 0) {
513 XplatUI.GetFontMetrics(g, tag.font, out tag.ascent, out descent);
516 if (tag.ascent > this.ascent) {
519 // We have a tag that has a taller ascent than the line;
521 while (t != null && t != tag) {
522 t.shift = tag.ascent - t.ascent;
527 this.ascent = tag.ascent;
529 tag.shift = this.ascent - tag.ascent;
540 if (this.height == 0) {
541 this.height = tags.font.Height;
542 tag.height = this.height;
545 if (prev_height != this.height) {
550 #endregion // Internal Methods
552 #region Administrative
553 public int CompareTo(object obj) {
558 if (! (obj is Line)) {
559 throw new ArgumentException("Object is not of type Line", "obj");
562 if (line_no < ((Line)obj).line_no) {
564 } else if (line_no > ((Line)obj).line_no) {
571 public object Clone() {
579 clone.left = (Line)left.Clone();
583 clone.left = (Line)left.Clone();
589 internal object CloneLine() {
599 public override bool Equals(object obj) {
604 if (!(obj is Line)) {
612 if (line_no == ((Line)obj).line_no) {
619 public override int GetHashCode() {
620 return base.GetHashCode ();
623 public override string ToString() {
624 return "Line " + line_no;
627 #endregion // Administrative
630 internal class Document : ICloneable, IEnumerable {
632 // FIXME - go through code and check for places where
633 // we do explicit comparisons instead of using the compare overloads
634 internal struct Marker {
636 internal LineTag tag;
640 public static bool operator<(Marker lhs, Marker rhs) {
641 if (lhs.line.line_no < rhs.line.line_no) {
645 if (lhs.line.line_no == rhs.line.line_no) {
646 if (lhs.pos < rhs.pos) {
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) && (lhs.pos == rhs.pos)) {
673 public static bool operator!=(Marker lhs, Marker rhs) {
674 if ((lhs.line.line_no != rhs.line.line_no) || (lhs.pos != rhs.pos)) {
680 public void Combine(Line move_to_line, int move_to_line_length) {
682 pos += move_to_line_length;
683 tag = LineTag.FindTag(line, pos);
686 // This is for future use, right now Document.Split does it by hand, with some added shortcut logic
687 public void Split(Line move_to_line, int split_at) {
690 tag = LineTag.FindTag(line, pos);
693 public override bool Equals(object obj) {
694 return this==(Marker)obj;
697 public override int GetHashCode() {
698 return base.GetHashCode ();
701 public override string ToString() {
702 return "Marker Line " + line + ", Position " + pos;
706 #endregion Structures
708 #region Local Variables
709 private Line document;
711 private Line sentinel;
712 private int document_id;
713 private Random random = new Random();
714 internal string password_char;
715 private StringBuilder password_cache;
716 private bool calc_pass;
717 private int char_count;
719 // For calculating widths/heights
720 public static readonly StringFormat string_format = new StringFormat (StringFormat.GenericTypographic);
722 private int recalc_suspended;
723 private bool recalc_pending;
724 private int recalc_start = 1; // This starts at one, since lines are 1 based
725 private int recalc_end;
726 private bool recalc_optimize;
728 internal bool multiline;
731 internal UndoClass undo;
733 internal Marker caret;
734 internal Marker selection_start;
735 internal Marker selection_end;
736 internal bool selection_visible;
737 internal Marker selection_anchor;
738 internal Marker selection_prev;
739 internal bool selection_end_anchor;
741 internal int viewport_x;
742 internal int viewport_y; // The visible area of the document
743 internal int viewport_width;
744 internal int viewport_height;
746 internal int document_x; // Width of the document
747 internal int document_y; // Height of the document
749 internal Rectangle invalid;
751 internal int crlf_size; // 1 or 2, depending on whether we use \r\n or just \n
753 internal TextBoxBase owner; // Who's owning us?
754 static internal int caret_width = 1;
755 static internal int caret_shift = 1;
756 #endregion // Local Variables
759 internal Document(TextBoxBase owner) {
767 recalc_pending = false;
769 // Tree related stuff
770 sentinel = new Line();
771 sentinel.color = LineColor.Black;
775 // We always have a blank line
776 owner.HandleCreated += new EventHandler(owner_HandleCreated);
777 owner.VisibleChanged += new EventHandler(owner_VisibleChanged);
779 Add(1, "", owner.Font, ThemeEngine.Current.ResPool.GetSolidBrush(owner.ForeColor));
780 Line l = GetLine (1);
783 undo = new UndoClass(this);
785 selection_visible = false;
786 selection_start.line = this.document;
787 selection_start.pos = 0;
788 selection_start.tag = selection_start.line.tags;
789 selection_end.line = this.document;
790 selection_end.pos = 0;
791 selection_end.tag = selection_end.line.tags;
792 selection_anchor.line = this.document;
793 selection_anchor.pos = 0;
794 selection_anchor.tag = selection_anchor.line.tags;
795 caret.line = this.document;
797 caret.tag = caret.line.tags;
804 // Default selection is empty
806 document_id = random.Next();
808 string_format.Trimming = StringTrimming.None;
809 string_format.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;
813 #region Internal Properties
830 internal Line CaretLine {
836 internal int CaretPosition {
842 internal Point Caret {
844 return new Point((int)caret.tag.line.widths[caret.pos] + caret.line.align_shift, caret.line.Y);
848 internal LineTag CaretTag {
858 internal int CRLFSize {
868 internal string PasswordChar {
870 return password_char;
874 password_char = value;
875 if ((password_char.Length != 0) && (password_char[0] != '\0')) {
880 password_cache = new StringBuilder(1024);
881 for (int i = 0; i < 1024; i++) {
882 password_cache.Append(ch);
886 password_cache = null;
891 internal int ViewPortX {
901 internal int Length {
903 return char_count + lines - 1; // Add \n for each line but the last
907 private int CharCount {
915 if (LengthChanged != null) {
916 LengthChanged(this, EventArgs.Empty);
921 internal int ViewPortY {
931 internal int ViewPortWidth {
933 return viewport_width;
937 viewport_width = value;
941 internal int ViewPortHeight {
943 return viewport_height;
947 viewport_height = value;
954 return this.document_x;
958 internal int Height {
960 return this.document_y;
964 internal bool SelectionVisible {
966 return selection_visible;
980 #endregion // Internal Properties
982 #region Private Methods
984 internal void SuspendRecalc ()
989 internal void ResumeRecalc (bool immediate_update)
991 if (recalc_suspended > 0)
994 if (immediate_update && recalc_suspended == 0 && recalc_pending) {
995 RecalculateDocument (owner.CreateGraphicsInternal(), recalc_start, recalc_end, recalc_optimize);
996 recalc_pending = false;
1001 internal int DumpTree(Line line, bool with_tags) {
1006 Console.Write("Line {0} [# {1}], Y: {2}, soft: {3}, Text: '{4}'",
1007 line.line_no, line.GetHashCode(), line.Y, line.soft_break,
1008 line.text != null ? line.text.ToString() : "undefined");
1010 if (line.left == sentinel) {
1011 Console.Write(", left = sentinel");
1012 } else if (line.left == null) {
1013 Console.Write(", left = NULL");
1016 if (line.right == sentinel) {
1017 Console.Write(", right = sentinel");
1018 } else if (line.right == null) {
1019 Console.Write(", right = NULL");
1022 Console.WriteLine("");
1032 Console.Write(" Tags: ");
1033 while (tag != null) {
1034 Console.Write("{0} <{1}>-<{2}>", count++, tag.start, tag.end
1035 /*line.text.ToString (tag.start - 1, tag.length)*/);
1036 length += tag.length;
1038 if (tag.line != line) {
1039 Console.Write("BAD line link");
1040 throw new Exception("Bad line link in tree");
1044 Console.Write(", ");
1047 if (length > line.text.Length) {
1048 throw new Exception(String.Format("Length of tags more than length of text on line (expected {0} calculated {1})", line.text.Length, length));
1049 } else if (length < line.text.Length) {
1050 throw new Exception(String.Format("Length of tags less than length of text on line (expected {0} calculated {1})", line.text.Length, length));
1052 Console.WriteLine("");
1054 if (line.left != null) {
1055 if (line.left != sentinel) {
1056 total += DumpTree(line.left, with_tags);
1059 if (line != sentinel) {
1060 throw new Exception("Left should not be NULL");
1064 if (line.right != null) {
1065 if (line.right != sentinel) {
1066 total += DumpTree(line.right, with_tags);
1069 if (line != sentinel) {
1070 throw new Exception("Right should not be NULL");
1074 for (int i = 1; i <= this.lines; i++) {
1075 if (GetLine(i) == null) {
1076 throw new Exception(String.Format("Hole in line order, missing {0}", i));
1080 if (line == this.Root) {
1081 if (total < this.lines) {
1082 throw new Exception(String.Format("Not enough nodes in tree, found {0}, expected {1}", total, this.lines));
1083 } else if (total > this.lines) {
1084 throw new Exception(String.Format("Too many nodes in tree, found {0}, expected {1}", total, this.lines));
1091 private void SetSelectionVisible (bool value)
1093 selection_visible = value;
1095 // cursor and selection are enemies, we can't have both in the same room at the same time
1096 if (owner.IsHandleCreated && !owner.show_caret_w_selection)
1097 XplatUI.CaretVisible (owner.Handle, !selection_visible);
1100 private void DecrementLines(int line_no) {
1104 while (current <= lines) {
1105 GetLine(current).line_no--;
1111 private void IncrementLines(int line_no) {
1114 current = this.lines;
1115 while (current >= line_no) {
1116 GetLine(current).line_no++;
1122 private void RebalanceAfterAdd(Line line1) {
1125 while ((line1 != document) && (line1.parent.color == LineColor.Red)) {
1126 if (line1.parent == line1.parent.parent.left) {
1127 line2 = line1.parent.parent.right;
1129 if ((line2 != null) && (line2.color == LineColor.Red)) {
1130 line1.parent.color = LineColor.Black;
1131 line2.color = LineColor.Black;
1132 line1.parent.parent.color = LineColor.Red;
1133 line1 = line1.parent.parent;
1135 if (line1 == line1.parent.right) {
1136 line1 = line1.parent;
1140 line1.parent.color = LineColor.Black;
1141 line1.parent.parent.color = LineColor.Red;
1143 RotateRight(line1.parent.parent);
1146 line2 = line1.parent.parent.left;
1148 if ((line2 != null) && (line2.color == LineColor.Red)) {
1149 line1.parent.color = LineColor.Black;
1150 line2.color = LineColor.Black;
1151 line1.parent.parent.color = LineColor.Red;
1152 line1 = line1.parent.parent;
1154 if (line1 == line1.parent.left) {
1155 line1 = line1.parent;
1159 line1.parent.color = LineColor.Black;
1160 line1.parent.parent.color = LineColor.Red;
1161 RotateLeft(line1.parent.parent);
1165 document.color = LineColor.Black;
1168 private void RebalanceAfterDelete(Line line1) {
1171 while ((line1 != document) && (line1.color == LineColor.Black)) {
1172 if (line1 == line1.parent.left) {
1173 line2 = line1.parent.right;
1174 if (line2.color == LineColor.Red) {
1175 line2.color = LineColor.Black;
1176 line1.parent.color = LineColor.Red;
1177 RotateLeft(line1.parent);
1178 line2 = line1.parent.right;
1180 if ((line2.left.color == LineColor.Black) && (line2.right.color == LineColor.Black)) {
1181 line2.color = LineColor.Red;
1182 line1 = line1.parent;
1184 if (line2.right.color == LineColor.Black) {
1185 line2.left.color = LineColor.Black;
1186 line2.color = LineColor.Red;
1188 line2 = line1.parent.right;
1190 line2.color = line1.parent.color;
1191 line1.parent.color = LineColor.Black;
1192 line2.right.color = LineColor.Black;
1193 RotateLeft(line1.parent);
1197 line2 = line1.parent.left;
1198 if (line2.color == LineColor.Red) {
1199 line2.color = LineColor.Black;
1200 line1.parent.color = LineColor.Red;
1201 RotateRight(line1.parent);
1202 line2 = line1.parent.left;
1204 if ((line2.right.color == LineColor.Black) && (line2.left.color == LineColor.Black)) {
1205 line2.color = LineColor.Red;
1206 line1 = line1.parent;
1208 if (line2.left.color == LineColor.Black) {
1209 line2.right.color = LineColor.Black;
1210 line2.color = LineColor.Red;
1212 line2 = line1.parent.left;
1214 line2.color = line1.parent.color;
1215 line1.parent.color = LineColor.Black;
1216 line2.left.color = LineColor.Black;
1217 RotateRight(line1.parent);
1222 line1.color = LineColor.Black;
1225 private void RotateLeft(Line line1) {
1226 Line line2 = line1.right;
1228 line1.right = line2.left;
1230 if (line2.left != sentinel) {
1231 line2.left.parent = line1;
1234 if (line2 != sentinel) {
1235 line2.parent = line1.parent;
1238 if (line1.parent != null) {
1239 if (line1 == line1.parent.left) {
1240 line1.parent.left = line2;
1242 line1.parent.right = line2;
1249 if (line1 != sentinel) {
1250 line1.parent = line2;
1254 private void RotateRight(Line line1) {
1255 Line line2 = line1.left;
1257 line1.left = line2.right;
1259 if (line2.right != sentinel) {
1260 line2.right.parent = line1;
1263 if (line2 != sentinel) {
1264 line2.parent = line1.parent;
1267 if (line1.parent != null) {
1268 if (line1 == line1.parent.right) {
1269 line1.parent.right = line2;
1271 line1.parent.left = line2;
1277 line2.right = line1;
1278 if (line1 != sentinel) {
1279 line1.parent = line2;
1284 internal void UpdateView(Line line, int pos) {
1285 if (!owner.IsHandleCreated) {
1289 if (recalc_suspended > 0) {
1290 recalc_start = Math.Min (recalc_start, line.line_no);
1291 recalc_end = Math.Max (recalc_end, line.line_no);
1292 recalc_optimize = true;
1293 recalc_pending = true;
1297 // Optimize invalidation based on Line alignment
1298 if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no, true)) {
1299 // Lineheight changed, invalidate the rest of the document
1300 if ((line.Y - viewport_y) >=0 ) {
1301 // We formatted something that's in view, only draw parts of the screen
1302 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, owner.Height - line.Y - viewport_y));
1304 // The tag was above the visible area, draw everything
1308 switch(line.alignment) {
1309 case HorizontalAlignment.Left: {
1310 owner.Invalidate(new Rectangle((int)line.widths[pos] - viewport_x - 1, line.Y - viewport_y, viewport_width, line.height + 1));
1314 case HorizontalAlignment.Center: {
1315 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, line.height + 1));
1319 case HorizontalAlignment.Right: {
1320 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, (int)line.widths[pos + 1] - viewport_x + line.align_shift, line.height + 1));
1328 // Update display from line, down line_count lines; pos is unused, but required for the signature
1329 internal void UpdateView(Line line, int line_count, int pos) {
1330 if (!owner.IsHandleCreated) {
1334 if (recalc_suspended > 0) {
1335 recalc_start = Math.Min (recalc_start, line.line_no);
1336 recalc_end = Math.Max (recalc_end, line.line_no + line_count - 1);
1337 recalc_optimize = true;
1338 recalc_pending = true;
1342 if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no + line_count - 1, true)) {
1343 // Lineheight changed, invalidate the rest of the document
1344 if ((line.Y - viewport_y) >=0 ) {
1345 // We formatted something that's in view, only draw parts of the screen
1346 //blah Console.WriteLine("TextControl.cs(981) Invalidate called in UpdateView(line, line_count, pos)");
1347 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, owner.Height - line.Y - viewport_y));
1349 // The tag was above the visible area, draw everything
1350 //blah Console.WriteLine("TextControl.cs(985) Invalidate called in UpdateView(line, line_count, pos)");
1356 end_line = GetLine(line.line_no + line_count -1);
1357 if (end_line == null) {
1361 //blah Console.WriteLine("TextControl.cs(996) Invalidate called in UpdateView(line, line_count, pos)");
1362 owner.Invalidate(new Rectangle(0 - viewport_x, line.Y - viewport_y, (int)line.widths[line.text.Length], end_line.Y + end_line.height));
1365 #endregion // Private Methods
1367 #region Internal Methods
1368 // Clear the document and reset state
1369 internal void Empty() {
1371 document = sentinel;
1374 // We always have a blank line
1375 Add(1, "", owner.Font, ThemeEngine.Current.ResPool.GetSolidBrush(owner.ForeColor));
1376 Line l = GetLine (1);
1377 l.soft_break = true;
1379 this.RecalculateDocument(owner.CreateGraphicsInternal());
1380 PositionCaret(0, 0);
1382 SetSelectionVisible (false);
1384 selection_start.line = this.document;
1385 selection_start.pos = 0;
1386 selection_start.tag = selection_start.line.tags;
1387 selection_end.line = this.document;
1388 selection_end.pos = 0;
1389 selection_end.tag = selection_end.line.tags;
1398 if (owner.IsHandleCreated)
1399 owner.Invalidate ();
1402 internal void PositionCaret(Line line, int pos) {
1403 if (owner.IsHandleCreated) {
1404 undo.RecordCursor();
1407 caret.tag = line.FindTag(pos);
1411 if (owner.IsHandleCreated) {
1412 if (owner.Focused) {
1413 if (caret.height != caret.tag.height)
1414 XplatUI.CreateCaret (owner.Handle, caret_width, caret.height);
1415 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);
1418 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1421 // We set this at the end because we use the heights to determine whether or
1422 // not we need to recreate the caret
1423 caret.height = caret.tag.height;
1427 internal void PositionCaret(int x, int y) {
1428 if (!owner.IsHandleCreated) {
1432 undo.RecordCursor();
1434 caret.tag = FindCursor(x, y, out caret.pos);
1435 caret.line = caret.tag.line;
1436 caret.height = caret.tag.height;
1438 if (owner.Focused) {
1439 XplatUI.CreateCaret (owner.Handle, caret_width, caret.height);
1440 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);
1443 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1446 internal void CaretHasFocus() {
1447 if ((caret.tag != null) && owner.IsHandleCreated) {
1448 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1449 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.align_shift - viewport_x, caret.line.Y + caret.tag.shift - viewport_y + caret_shift);
1454 if (owner.IsHandleCreated && selection_visible) {
1455 InvalidateSelectionArea ();
1459 internal void CaretLostFocus() {
1460 if (!owner.IsHandleCreated) {
1463 XplatUI.DestroyCaret(owner.Handle);
1466 internal void AlignCaret() {
1467 if (!owner.IsHandleCreated) {
1471 undo.RecordCursor();
1473 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1474 caret.height = caret.tag.height;
1476 if (owner.Focused) {
1477 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1478 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);
1482 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1485 internal void UpdateCaret() {
1486 if (!owner.IsHandleCreated || caret.tag == null) {
1490 undo.RecordCursor();
1492 if (caret.tag.height != caret.height) {
1493 caret.height = caret.tag.height;
1494 if (owner.Focused) {
1495 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1499 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.align_shift - viewport_x, caret.line.Y + caret.tag.shift - viewport_y + caret_shift);
1503 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1506 internal void DisplayCaret() {
1507 if (!owner.IsHandleCreated) {
1511 if (owner.Focused && (!selection_visible || owner.show_caret_w_selection)) {
1512 XplatUI.CaretVisible(owner.Handle, true);
1516 internal void HideCaret() {
1517 if (!owner.IsHandleCreated) {
1521 if (owner.Focused) {
1522 XplatUI.CaretVisible(owner.Handle, false);
1526 internal void MoveCaret(CaretDirection direction) {
1527 // FIXME should we use IsWordSeparator to detect whitespace, instead
1528 // of looking for actual spaces in the Word move cases?
1530 bool nowrap = false;
1532 case CaretDirection.CharForwardNoWrap:
1534 goto case CaretDirection.CharForward;
1535 case CaretDirection.CharForward: {
1537 if (caret.pos > caret.line.text.Length) {
1538 if (multiline && !nowrap) {
1539 // Go into next line
1540 if (caret.line.line_no < this.lines) {
1541 caret.line = GetLine(caret.line.line_no+1);
1543 caret.tag = caret.line.tags;
1548 // Single line; we stay where we are
1552 if ((caret.tag.start - 1 + caret.tag.length) < caret.pos) {
1553 caret.tag = caret.tag.next;
1560 case CaretDirection.CharBackNoWrap:
1562 goto case CaretDirection.CharBack;
1563 case CaretDirection.CharBack: {
1564 if (caret.pos > 0) {
1565 // caret.pos--; // folded into the if below
1566 if (--caret.pos > 0) {
1567 if (caret.tag.start > caret.pos) {
1568 caret.tag = caret.tag.previous;
1572 if (caret.line.line_no > 1 && !nowrap) {
1573 caret.line = GetLine(caret.line.line_no - 1);
1574 caret.pos = caret.line.text.Length;
1575 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1582 case CaretDirection.WordForward: {
1585 len = caret.line.text.Length;
1586 if (caret.pos < len) {
1587 while ((caret.pos < len) && (caret.line.text[caret.pos] != ' ')) {
1590 if (caret.pos < len) {
1591 // Skip any whitespace
1592 while ((caret.pos < len) && (caret.line.text[caret.pos] == ' ')) {
1596 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1598 if (caret.line.line_no < this.lines) {
1599 caret.line = GetLine(caret.line.line_no + 1);
1601 caret.tag = caret.line.tags;
1608 case CaretDirection.WordBack: {
1609 if (caret.pos > 0) {
1612 while ((caret.pos > 0) && (caret.line.text[caret.pos] == ' ')) {
1616 while ((caret.pos > 0) && (caret.line.text[caret.pos] != ' ')) {
1620 if (caret.line.text.ToString(caret.pos, 1) == " ") {
1621 if (caret.pos != 0) {
1624 caret.line = GetLine(caret.line.line_no - 1);
1625 caret.pos = caret.line.text.Length;
1628 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1630 if (caret.line.line_no > 1) {
1631 caret.line = GetLine(caret.line.line_no - 1);
1632 caret.pos = caret.line.text.Length;
1633 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1640 case CaretDirection.LineUp: {
1641 if (caret.line.line_no > 1) {
1644 pixel = (int)caret.line.widths[caret.pos];
1645 PositionCaret(pixel, GetLine(caret.line.line_no - 1).Y);
1652 case CaretDirection.LineDown: {
1653 if (caret.line.line_no < lines) {
1656 pixel = (int)caret.line.widths[caret.pos];
1657 PositionCaret(pixel, GetLine(caret.line.line_no + 1).Y);
1664 case CaretDirection.Home: {
1665 if (caret.pos > 0) {
1667 caret.tag = caret.line.tags;
1673 case CaretDirection.End: {
1674 if (caret.pos < caret.line.text.Length) {
1675 caret.pos = caret.line.text.Length;
1676 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1682 case CaretDirection.PgUp: {
1684 int new_y, y_offset;
1686 if (viewport_y == 0) {
1688 // This should probably be handled elsewhere
1689 if (!(owner is RichTextBox)) {
1690 // Page down doesn't do anything in a regular TextBox
1691 // if the bottom of the document
1692 // is already visible, the page and the caret stay still
1696 // We're just placing the caret at the end of the document, no scrolling needed
1697 owner.vscroll.Value = 0;
1698 Line line = GetLine (1);
1699 PositionCaret (line, 0);
1702 y_offset = caret.line.Y - viewport_y;
1703 new_y = caret.line.Y - viewport_height;
1705 owner.vscroll.Value = Math.Max (new_y, 0);
1706 PositionCaret ((int)caret.line.widths[caret.pos], y_offset + viewport_y);
1710 case CaretDirection.PgDn: {
1711 int new_y, y_offset;
1713 if ((viewport_y + viewport_height) > document_y) {
1715 // This should probably be handled elsewhere
1716 if (!(owner is RichTextBox)) {
1717 // Page up doesn't do anything in a regular TextBox
1718 // if the bottom of the document
1719 // is already visible, the page and the caret stay still
1723 // We're just placing the caret at the end of the document, no scrolling needed
1724 owner.vscroll.Value = owner.vscroll.Maximum - viewport_height + 1;
1725 Line line = GetLine (lines);
1726 PositionCaret (line, line.Text.Length);
1729 y_offset = caret.line.Y - viewport_y;
1730 new_y = caret.line.Y + viewport_height;
1732 owner.vscroll.Value = Math.Min (new_y, owner.vscroll.Maximum - viewport_height + 1);
1733 PositionCaret ((int)caret.line.widths[caret.pos], y_offset + viewport_y);
1738 case CaretDirection.CtrlPgUp: {
1739 PositionCaret(0, viewport_y);
1744 case CaretDirection.CtrlPgDn: {
1749 tag = FindTag(0, viewport_y + viewport_height, out index, false);
1750 if (tag.line.line_no > 1) {
1751 line = GetLine(tag.line.line_no - 1);
1755 PositionCaret(line, line.Text.Length);
1760 case CaretDirection.CtrlHome: {
1761 caret.line = GetLine(1);
1763 caret.tag = caret.line.tags;
1769 case CaretDirection.CtrlEnd: {
1770 caret.line = GetLine(lines);
1771 caret.pos = caret.line.text.Length;
1772 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1778 case CaretDirection.SelectionStart: {
1779 caret.line = selection_start.line;
1780 caret.pos = selection_start.pos;
1781 caret.tag = selection_start.tag;
1787 case CaretDirection.SelectionEnd: {
1788 caret.line = selection_end.line;
1789 caret.pos = selection_end.pos;
1790 caret.tag = selection_end.tag;
1798 internal void DumpDoc ()
1800 Console.WriteLine ("<doc>");
1801 for (int i = 1; i < lines; i++) {
1802 Line line = GetLine (i);
1803 Console.WriteLine ("<line no='{0}'>", line.line_no);
1805 LineTag tag = line.tags;
1806 while (tag != null) {
1807 Console.Write ("\t<tag color='{0}'>", tag.color.Color);
1808 Console.Write (tag.Text ());
1809 Console.WriteLine ("\t</tag>");
1812 Console.WriteLine ("</line>");
1814 Console.WriteLine ("</doc>");
1817 internal void Draw (Graphics g, Rectangle clip)
1819 Line line; // Current line being drawn
1820 LineTag tag; // Current tag being drawn
1821 int start; // First line to draw
1822 int end; // Last line to draw
1823 StringBuilder text; // String representing the current line
1826 Brush current_brush;
1827 Brush disabled_brush;
1831 // First, figure out from what line to what line we need to draw
1832 start = GetLineByPixel(clip.Top + viewport_y, false).line_no;
1833 end = GetLineByPixel(clip.Bottom + viewport_y, false).line_no;
1835 /// Make sure that we aren't drawing one more line then we need to
1836 line = GetLine (end - 1);
1837 if (line != null && clip.Bottom == line.Y + line.height + viewport_y)
1842 g.FillRectangle (new SolidBrush (Color.White), clip);
1844 DateTime n = DateTime.Now;
1845 Console.WriteLine ("Started drawing: {0}s {1}ms", n.Second, n.Millisecond);
1846 Console.WriteLine ("CLIP: {0}", clip);
1847 Console.WriteLine ("S: {0}", GetLine (start).text);
1848 Console.WriteLine ("E: {0}", GetLine (end).text);
1851 disabled_brush = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorGrayText);
1852 hilight = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorHighlight);
1853 hilight_text = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorHighlightText);
1855 while (line_no <= end) {
1856 line = GetLine (line_no);
1862 // This fails if there's a password > 1024 chars...
1863 text = this.password_cache;
1866 int line_selection_start = text.Length + 1;
1867 int line_selection_end = text.Length + 1;
1868 if (selection_visible && owner.ShowSelection &&
1869 (line_no >= selection_start.line.line_no) &&
1870 (line_no <= selection_end.line.line_no)) {
1872 if (line_no == selection_start.line.line_no)
1873 line_selection_start = selection_start.pos + 1;
1875 line_selection_start = 1;
1877 if (line_no == selection_end.line.line_no)
1878 line_selection_end = selection_end.pos + 1;
1880 line_selection_end = text.Length + 1;
1882 if (line_selection_end == line_selection_start) {
1883 // There isn't really selection
1884 line_selection_start = text.Length + 1;
1885 line_selection_end = line_selection_start;
1887 // lets draw some selection baby!!
1889 g.FillRectangle (hilight,
1890 line.widths [line_selection_start - 1] + line.align_shift - viewport_x,
1891 line.Y - viewport_y,
1892 line.widths [line_selection_end - 1] - line.widths [line_selection_start - 1],
1897 current_brush = line.tags.color;
1898 while (tag != null) {
1901 if (tag.length == 0) {
1906 if (((tag.X + tag.width) < (clip.Left - viewport_x)) && (tag.X > (clip.Right - viewport_x))) {
1911 if (tag.back_color != null) {
1912 g.FillRectangle (tag.back_color, tag.X + line.align_shift - viewport_x,
1913 line.Y + tag.shift - viewport_y, tag.width, line.height);
1916 tag_brush = tag.color;
1917 current_brush = tag_brush;
1919 if (!owner.is_enabled) {
1920 Color a = ((SolidBrush) tag.color).Color;
1921 Color b = ThemeEngine.Current.ColorWindowText;
1923 if ((a.R == b.R) && (a.G == b.G) && (a.B == b.B)) {
1924 tag_brush = disabled_brush;
1928 int tag_pos = tag.start;
1929 current_brush = tag_brush;
1930 while (tag_pos < tag.start + tag.length) {
1931 int old_tag_pos = tag_pos;
1933 if (tag_pos >= line_selection_start && tag_pos < line_selection_end) {
1934 current_brush = hilight_text;
1935 tag_pos = Math.Min (tag.end, line_selection_end);
1936 } else if (tag_pos < line_selection_start) {
1937 current_brush = tag.color;
1938 tag_pos = Math.Min (tag.end, line_selection_start);
1940 current_brush = tag.color;
1944 tag.Draw (g, current_brush,
1945 line.widths [old_tag_pos - 1] + line.align_shift - viewport_x,
1946 line.Y + tag.shift - viewport_y,
1947 old_tag_pos - 1, Math.Min (tag.length, tag_pos - old_tag_pos));
1955 private void InsertLineString (Line line, int pos, string s)
1957 bool carriage_return = false;
1959 if (s.EndsWith ("\r")) {
1960 s = s.Substring (0, s.Length - 1);
1961 carriage_return = true;
1964 InsertString (line, pos, s);
1966 if (carriage_return) {
1967 Line l = GetLine (line.line_no);
1968 l.carriage_return = true;
1972 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
1973 internal void Insert(Line line, int pos, bool update_caret, string s) {
1978 LineTag tag = LineTag.FindTag (line, pos);
1981 undo.BeginCompoundAction ();
1983 base_line = line.line_no;
1984 old_line_count = lines;
1986 break_index = s.IndexOf ('\n');
1988 // Bump the text at insertion point a line down if we're inserting more than one line
1989 if (break_index > -1) {
1991 line.soft_break = false;
1992 // Remainder of start line is now in base_line + 1
1995 if (break_index == -1)
1996 break_index = s.Length;
1998 InsertLineString (line, pos, s.Substring (0, break_index));
2001 while (break_index < s.Length) {
2003 int next_break = s.IndexOf ('\n', break_index);
2004 int adjusted_next_break;
2005 bool carriage_return = false;
2007 if (next_break == -1) {
2008 next_break = s.Length;
2012 adjusted_next_break = next_break;
2013 if (s [next_break - 1] == '\r') {
2014 adjusted_next_break--;
2015 carriage_return = true;
2018 string line_text = s.Substring (break_index, adjusted_next_break - break_index);
2019 Add (base_line + count, line_text, line.alignment, tag.font, tag.color);
2021 if (carriage_return) {
2022 Line last = GetLine (base_line + count);
2023 last.carriage_return = true;
2026 last.soft_break = true;
2028 Line last = GetLine (base_line + count);
2029 last.soft_break = true;
2033 break_index = next_break + 1;
2036 ResumeRecalc (true);
2038 UpdateView(line, lines - old_line_count + 1, pos);
2041 // Move caret to the end of the inserted text
2042 Line l = GetLine (line.line_no + lines - old_line_count);
2043 PositionCaret(l, l.text.Length);
2047 undo.EndCompoundAction ();
2050 // Inserts a character at the given position
2051 internal void InsertString(Line line, int pos, string s) {
2052 InsertString(line.FindTag(pos), pos, s);
2055 // Inserts a string at the given position
2056 internal void InsertString(LineTag tag, int pos, string s) {
2065 line.text.Insert(pos, s);
2068 while (tag != null) {
2075 UpdateView(line, pos);
2078 // Inserts a string at the caret position
2079 internal void InsertStringAtCaret(string s, bool move_caret) {
2081 InsertString (caret.tag, caret.pos, s);
2083 UpdateView(caret.line, caret.pos);
2085 caret.pos += s.Length;
2092 // Inserts a character at the given position
2093 internal void InsertChar(Line line, int pos, char ch) {
2094 InsertChar(line.FindTag(pos), pos, ch);
2097 // Inserts a character at the given position
2098 internal void InsertChar(LineTag tag, int pos, char ch) {
2104 line.text.Insert(pos, ch);
2108 while (tag != null) {
2115 UpdateView(line, pos);
2118 // Inserts a character at the current caret position
2119 internal void InsertCharAtCaret(char ch, bool move_caret) {
2125 caret.line.text.Insert(caret.pos, ch);
2128 if (caret.tag.next != null) {
2129 tag = caret.tag.next;
2130 while (tag != null) {
2136 caret.line.recalc = true;
2138 InsertChar (caret.tag, caret.pos, ch);
2140 UpdateView(caret.line, caret.pos);
2144 SetSelectionToCaret(true);
2148 internal void InsertImage (LineTag tag, int pos, Image image)
2156 line.text.Insert (pos, "I");
2158 LineTag next_tag = tag.Break (pos);
2159 ImageTag image_tag = new ImageTag (line, pos, image);
2160 image_tag.CopyFormattingFrom (tag);
2161 image_tag.next = next_tag;
2162 image_tag.previous = tag;
2163 tag.next = image_tag;
2165 tag = image_tag.next;
2166 while (tag != null) {
2175 UpdateView (line, pos);
2178 internal void DeleteMultiline (Line start_line, int pos, int length)
2180 Marker start = new Marker ();
2181 Marker end = new Marker ();
2182 int start_index = LineTagToCharIndex (start_line, pos);
2184 start.line = start_line;
2186 start.tag = LineTag.FindTag (start_line, pos);
2188 CharIndexToLineTag (start_index + length, out end.line,
2189 out end.tag, out end.pos);
2191 if (start.line == end.line) {
2192 DeleteChars (start.tag, pos, end.pos - pos);
2195 // Delete first and last lines
2196 DeleteChars (start.tag, start.pos, start.line.text.Length - start.pos);
2197 DeleteChars (end.line.tags, 0, end.pos);
2199 int current = start.line.line_no + 1;
2200 if (current < end.line.line_no) {
2201 for (int i = end.line.line_no - 1; i >= current; i--) {
2206 // BIG FAT WARNING - selection_end.line might be stale due
2207 // to the above Delete() call. DONT USE IT before hitting the end of this method!
2209 // Join start and end
2210 Combine (start.line.line_no, current);
2215 // Deletes n characters at the given position; it will not delete past line limits
2217 internal void DeleteChars(LineTag tag, int pos, int count) {
2226 if (pos == line.text.Length) {
2230 line.text.Remove(pos, count);
2232 // Make sure the tag points to the right spot
2233 while ((tag != null) && (tag.start + tag.length - 1) <= pos) {
2241 // Check if we're crossing tag boundaries
2242 if ((pos + count) > (tag.start + tag.length - 1)) {
2245 // We have to delete cross tag boundaries
2249 left -= tag.start + tag.length - pos - 1;
2252 while ((tag != null) && (left > 0)) {
2253 tag.start -= count - left;
2255 if (tag.length > left) {
2264 // We got off easy, same tag
2266 if (tag.length == 0) {
2271 // Delete empty orphaned tags at the end
2273 while (walk != null && walk.next != null && walk.next.length == 0) {
2275 walk.next = walk.next.next;
2276 if (walk.next != null)
2277 walk.next.previous = t;
2281 // Adjust the start point of any tags following
2284 while (tag != null) {
2292 line.Streamline(lines);
2295 UpdateView(line, pos);
2298 // Deletes a character at or after the given position (depending on forward); it will not delete past line limits
2299 internal void DeleteChar(LineTag tag, int pos, bool forward) {
2308 if ((pos == 0 && forward == false) || (pos == line.text.Length && forward == true)) {
2314 line.text.Remove(pos, 1);
2316 while ((tag != null) && (tag.start + tag.length - 1) <= pos) {
2326 if (tag.length == 0) {
2331 line.text.Remove(pos, 1);
2332 if (pos >= (tag.start - 1)) {
2334 if (tag.length == 0) {
2337 } else if (tag.previous != null) {
2338 // tag.previous.length--;
2339 if (tag.previous.length == 0) {
2345 // Delete empty orphaned tags at the end
2347 while (walk != null && walk.next != null && walk.next.length == 0) {
2349 walk.next = walk.next.next;
2350 if (walk.next != null)
2351 walk.next.previous = t;
2356 while (tag != null) {
2362 line.Streamline(lines);
2365 UpdateView(line, pos);
2368 // Combine two lines
2369 internal void Combine(int FirstLine, int SecondLine) {
2370 Combine(GetLine(FirstLine), GetLine(SecondLine));
2373 internal void Combine(Line first, Line second) {
2377 // Combine the two tag chains into one
2380 // Maintain the line ending style
2381 first.soft_break = second.soft_break;
2383 while (last.next != null) {
2387 // need to get the shift before setting the next tag since that effects length
2388 shift = last.start + last.length - 1;
2389 last.next = second.tags;
2390 last.next.previous = last;
2392 // Fix up references within the chain
2394 while (last != null) {
2396 last.start += shift;
2400 // Combine both lines' strings
2401 first.text.Insert(first.text.Length, second.text.ToString());
2402 first.Grow(first.text.Length);
2404 // Remove the reference to our (now combined) tags from the doomed line
2408 DecrementLines(first.line_no + 2); // first.line_no + 1 will be deleted, so we need to start renumbering one later
2411 first.recalc = true;
2412 first.height = 0; // This forces RecalcDocument/UpdateView to redraw from this line on
2413 first.Streamline(lines);
2415 // Update Caret, Selection, etc
2416 if (caret.line == second) {
2417 caret.Combine(first, shift);
2419 if (selection_anchor.line == second) {
2420 selection_anchor.Combine(first, shift);
2422 if (selection_start.line == second) {
2423 selection_start.Combine(first, shift);
2425 if (selection_end.line == second) {
2426 selection_end.Combine(first, shift);
2433 check_first = GetLine(first.line_no);
2434 check_second = GetLine(check_first.line_no + 1);
2436 Console.WriteLine("Pre-delete: Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2439 this.Delete(second);
2442 check_first = GetLine(first.line_no);
2443 check_second = GetLine(check_first.line_no + 1);
2445 Console.WriteLine("Post-delete Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2449 // Split the line at the position into two
2450 internal void Split(int LineNo, int pos) {
2454 line = GetLine(LineNo);
2455 tag = LineTag.FindTag(line, pos);
2456 Split(line, tag, pos, false);
2459 internal void Split(Line line, int pos) {
2462 tag = LineTag.FindTag(line, pos);
2463 Split(line, tag, pos, false);
2466 ///<summary>Split line at given tag and position into two lines</summary>
2467 ///<param name="soft">True if the split should be marked as 'soft', indicating that it can be contracted
2468 ///if more space becomes available on previous line</param>
2469 internal void Split(Line line, LineTag tag, int pos, bool soft) {
2473 bool move_sel_start;
2477 move_sel_start = false;
2478 move_sel_end = false;
2480 // Adjust selection and cursors
2481 if (caret.line == line && caret.pos >= pos) {
2484 if (selection_start.line == line && selection_start.pos > pos) {
2485 move_sel_start = true;
2488 if (selection_end.line == line && selection_end.pos > pos) {
2489 move_sel_end = true;
2492 // cover the easy case first
2493 if (pos == line.text.Length) {
2494 Add(line.line_no + 1, "", line.alignment, tag.font, tag.color);
2496 new_line = GetLine(line.line_no + 1);
2498 line.carriage_return = false;
2499 new_line.carriage_return = line.carriage_return;
2500 new_line.soft_break = soft;
2503 caret.line = new_line;
2504 caret.tag = new_line.tags;
2508 if (move_sel_start) {
2509 selection_start.line = new_line;
2510 selection_start.pos = 0;
2511 selection_start.tag = new_line.tags;
2515 selection_end.line = new_line;
2516 selection_end.pos = 0;
2517 selection_end.tag = new_line.tags;
2522 // We need to move the rest of the text into the new line
2523 Add (line.line_no + 1, line.text.ToString (pos, line.text.Length - pos), line.alignment, tag.font, tag.color);
2525 // Now transfer our tags from this line to the next
2526 new_line = GetLine(line.line_no + 1);
2528 line.carriage_return = false;
2529 new_line.carriage_return = line.carriage_return;
2530 new_line.soft_break = soft;
2533 new_line.recalc = true;
2535 if ((tag.start - 1) == pos) {
2538 // We can simply break the chain and move the tag into the next line
2539 if (tag == line.tags) {
2540 new_tag = new LineTag(line, 1);
2541 new_tag.CopyFormattingFrom (tag);
2542 line.tags = new_tag;
2545 if (tag.previous != null) {
2546 tag.previous.next = null;
2548 new_line.tags = tag;
2549 tag.previous = null;
2550 tag.line = new_line;
2552 // Walk the list and correct the start location of the tags we just bumped into the next line
2553 shift = tag.start - 1;
2556 while (new_tag != null) {
2557 new_tag.start -= shift;
2558 new_tag.line = new_line;
2559 new_tag = new_tag.next;
2564 new_tag = new LineTag (new_line, 1);
2565 new_tag.next = tag.next;
2566 new_tag.CopyFormattingFrom (tag);
2567 new_line.tags = new_tag;
2568 if (new_tag.next != null) {
2569 new_tag.next.previous = new_tag;
2574 new_tag = new_tag.next;
2575 while (new_tag != null) {
2576 new_tag.start -= shift;
2577 new_tag.line = new_line;
2578 new_tag = new_tag.next;
2584 caret.line = new_line;
2585 caret.pos = caret.pos - pos;
2586 caret.tag = caret.line.FindTag(caret.pos);
2589 if (move_sel_start) {
2590 selection_start.line = new_line;
2591 selection_start.pos = selection_start.pos - pos;
2592 selection_start.tag = new_line.FindTag(selection_start.pos);
2596 selection_end.line = new_line;
2597 selection_end.pos = selection_end.pos - pos;
2598 selection_end.tag = new_line.FindTag(selection_end.pos);
2601 CharCount -= line.text.Length - pos;
2602 line.text.Remove(pos, line.text.Length - pos);
2605 // Adds a line of text, with given font.
2606 // Bumps any line at that line number that already exists down
2607 internal void Add(int LineNo, string Text, Font font, SolidBrush color) {
2608 Add(LineNo, Text, HorizontalAlignment.Left, font, color);
2611 internal void Add(int LineNo, string Text, HorizontalAlignment align, Font font, SolidBrush color) {
2616 CharCount += Text.Length;
2618 if (LineNo<1 || Text == null) {
2620 throw new ArgumentNullException("LineNo", "Line numbers must be positive");
2622 throw new ArgumentNullException("Text", "Cannot insert NULL line");
2626 add = new Line(LineNo, Text, align, font, color);
2629 while (line != sentinel) {
2631 line_no = line.line_no;
2633 if (LineNo > line_no) {
2635 } else if (LineNo < line_no) {
2638 // Bump existing line numbers; walk all nodes to the right of this one and increment line_no
2639 IncrementLines(line.line_no);
2644 add.left = sentinel;
2645 add.right = sentinel;
2647 if (add.parent != null) {
2648 if (LineNo > add.parent.line_no) {
2649 add.parent.right = add;
2651 add.parent.left = add;
2658 RebalanceAfterAdd(add);
2663 internal virtual void Clear() {
2666 document = sentinel;
2669 public virtual object Clone() {
2672 clone = new Document(null);
2674 clone.lines = this.lines;
2675 clone.document = (Line)document.Clone();
2680 internal void Delete(int LineNo) {
2687 line = GetLine(LineNo);
2689 CharCount -= line.text.Length;
2691 DecrementLines(LineNo + 1);
2695 internal void Delete(Line line1) {
2696 Line line2;// = new Line();
2699 if ((line1.left == sentinel) || (line1.right == sentinel)) {
2702 line3 = line1.right;
2703 while (line3.left != sentinel) {
2708 if (line3.left != sentinel) {
2711 line2 = line3.right;
2714 line2.parent = line3.parent;
2715 if (line3.parent != null) {
2716 if(line3 == line3.parent.left) {
2717 line3.parent.left = line2;
2719 line3.parent.right = line2;
2725 if (line3 != line1) {
2728 if (selection_start.line == line3) {
2729 selection_start.line = line1;
2732 if (selection_end.line == line3) {
2733 selection_end.line = line1;
2736 if (selection_anchor.line == line3) {
2737 selection_anchor.line = line1;
2740 if (caret.line == line3) {
2745 line1.alignment = line3.alignment;
2746 line1.ascent = line3.ascent;
2747 line1.hanging_indent = line3.hanging_indent;
2748 line1.height = line3.height;
2749 line1.indent = line3.indent;
2750 line1.line_no = line3.line_no;
2751 line1.recalc = line3.recalc;
2752 line1.right_indent = line3.right_indent;
2753 line1.soft_break = line3.soft_break;
2754 line1.space = line3.space;
2755 line1.tags = line3.tags;
2756 line1.text = line3.text;
2757 line1.widths = line3.widths;
2761 while (tag != null) {
2767 if (line3.color == LineColor.Black)
2768 RebalanceAfterDelete(line2);
2773 // Invalidate a section of the document to trigger redraw
2774 internal void Invalidate(Line start, int start_pos, Line end, int end_pos) {
2780 if ((start == end) && (start_pos == end_pos)) {
2784 if (end_pos == -1) {
2785 end_pos = end.text.Length;
2788 // figure out what's before what so the logic below is straightforward
2789 if (start.line_no < end.line_no) {
2795 } else if (start.line_no > end.line_no) {
2802 if (start_pos < end_pos) {
2817 Console.WriteLine("Invaliding from {0}:{1} to {2}:{3} {4}",
2818 l1.line_no, p1, l2.line_no, p2,
2820 (int)l1.widths[p1] + l1.align_shift - viewport_x,
2822 (int)l2.widths[p2] - (int)l1.widths[p1] + 1,
2830 (int)l1.widths[p1] + l1.align_shift - viewport_x,
2832 (int)l2.widths[p2] - (int)l1.widths[p1] + 1,
2840 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);
2841 Console.WriteLine ("invalidate start line: {0} position: {1}", l1.text, p1);
2844 // Three invalidates:
2845 // First line from start
2846 owner.Invalidate(new Rectangle((int)l1.widths[p1] + l1.align_shift - viewport_x, l1.Y - viewport_y, viewport_width, l1.height));
2850 if ((l1.line_no + 1) < l2.line_no) {
2853 y = GetLine(l1.line_no + 1).Y;
2854 owner.Invalidate(new Rectangle(0, y - viewport_y, viewport_width, l2.Y - y));
2857 Console.WriteLine("Invaliding from {0}:{1} to {2}:{3} Middle => x={4}, y={5}, {6}x{7}", l1.line_no, p1, l2.line_no, p2, 0, y - viewport_y, viewport_width, l2.Y - y);
2863 owner.Invalidate(new Rectangle((int)l2.widths[0] + l2.align_shift - viewport_x, l2.Y - viewport_y, (int)l2.widths[p2] + 1, l2.height));
2865 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);
2870 /// <summary>Select text around caret</summary>
2871 internal void ExpandSelection(CaretSelection mode, bool to_caret) {
2873 // We're expanding the selection to the caret position
2875 case CaretSelection.Line: {
2876 // Invalidate the selection delta
2877 if (caret > selection_prev) {
2878 Invalidate(selection_prev.line, 0, caret.line, caret.line.text.Length);
2880 Invalidate(selection_prev.line, selection_prev.line.text.Length, caret.line, 0);
2883 if (caret.line.line_no <= selection_anchor.line.line_no) {
2884 selection_start.line = caret.line;
2885 selection_start.tag = caret.line.tags;
2886 selection_start.pos = 0;
2888 selection_end.line = selection_anchor.line;
2889 selection_end.tag = selection_anchor.tag;
2890 selection_end.pos = selection_anchor.pos;
2892 selection_end_anchor = true;
2894 selection_start.line = selection_anchor.line;
2895 selection_start.pos = selection_anchor.height;
2896 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height);
2898 selection_end.line = caret.line;
2899 selection_end.tag = caret.line.tags;
2900 selection_end.pos = caret.line.text.Length;
2902 selection_end_anchor = false;
2904 selection_prev.line = caret.line;
2905 selection_prev.tag = caret.tag;
2906 selection_prev.pos = caret.pos;
2911 case CaretSelection.Word: {
2915 start_pos = FindWordSeparator(caret.line, caret.pos, false);
2916 end_pos = FindWordSeparator(caret.line, caret.pos, true);
2919 // Invalidate the selection delta
2920 if (caret > selection_prev) {
2921 Invalidate(selection_prev.line, selection_prev.pos, caret.line, end_pos);
2923 Invalidate(selection_prev.line, selection_prev.pos, caret.line, start_pos);
2925 if (caret < selection_anchor) {
2926 selection_start.line = caret.line;
2927 selection_start.tag = caret.line.FindTag(start_pos);
2928 selection_start.pos = start_pos;
2930 selection_end.line = selection_anchor.line;
2931 selection_end.tag = selection_anchor.tag;
2932 selection_end.pos = selection_anchor.pos;
2934 selection_prev.line = caret.line;
2935 selection_prev.tag = caret.tag;
2936 selection_prev.pos = start_pos;
2938 selection_end_anchor = true;
2940 selection_start.line = selection_anchor.line;
2941 selection_start.pos = selection_anchor.height;
2942 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height);
2944 selection_end.line = caret.line;
2945 selection_end.tag = caret.line.FindTag(end_pos);
2946 selection_end.pos = end_pos;
2948 selection_prev.line = caret.line;
2949 selection_prev.tag = caret.tag;
2950 selection_prev.pos = end_pos;
2952 selection_end_anchor = false;
2957 case CaretSelection.Position: {
2958 SetSelectionToCaret(false);
2963 // We're setting the selection 'around' the caret position
2965 case CaretSelection.Line: {
2966 this.Invalidate(caret.line, 0, caret.line, caret.line.text.Length);
2968 selection_start.line = caret.line;
2969 selection_start.tag = caret.line.tags;
2970 selection_start.pos = 0;
2972 selection_end.line = caret.line;
2973 selection_end.pos = caret.line.text.Length;
2974 selection_end.tag = caret.line.FindTag(selection_end.pos);
2976 selection_anchor.line = selection_end.line;
2977 selection_anchor.tag = selection_end.tag;
2978 selection_anchor.pos = selection_end.pos;
2979 selection_anchor.height = 0;
2981 selection_prev.line = caret.line;
2982 selection_prev.tag = caret.tag;
2983 selection_prev.pos = caret.pos;
2985 this.selection_end_anchor = true;
2990 case CaretSelection.Word: {
2994 start_pos = FindWordSeparator(caret.line, caret.pos, false);
2995 end_pos = FindWordSeparator(caret.line, caret.pos, true);
2997 this.Invalidate(selection_start.line, start_pos, caret.line, end_pos);
2999 selection_start.line = caret.line;
3000 selection_start.tag = caret.line.FindTag(start_pos);
3001 selection_start.pos = start_pos;
3003 selection_end.line = caret.line;
3004 selection_end.tag = caret.line.FindTag(end_pos);
3005 selection_end.pos = end_pos;
3007 selection_anchor.line = selection_end.line;
3008 selection_anchor.tag = selection_end.tag;
3009 selection_anchor.pos = selection_end.pos;
3010 selection_anchor.height = start_pos;
3012 selection_prev.line = caret.line;
3013 selection_prev.tag = caret.tag;
3014 selection_prev.pos = caret.pos;
3016 this.selection_end_anchor = true;
3023 SetSelectionVisible (!(selection_start == selection_end));
3026 internal void SetSelectionToCaret(bool start) {
3028 // Invalidate old selection; selection is being reset to empty
3029 this.Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3031 selection_start.line = caret.line;
3032 selection_start.tag = caret.tag;
3033 selection_start.pos = caret.pos;
3035 // start always also selects end
3036 selection_end.line = caret.line;
3037 selection_end.tag = caret.tag;
3038 selection_end.pos = caret.pos;
3040 selection_anchor.line = caret.line;
3041 selection_anchor.tag = caret.tag;
3042 selection_anchor.pos = caret.pos;
3044 // Invalidate from previous end to caret (aka new end)
3045 if (selection_end_anchor) {
3046 if (selection_start != caret) {
3047 this.Invalidate(selection_start.line, selection_start.pos, caret.line, caret.pos);
3050 if (selection_end != caret) {
3051 this.Invalidate(selection_end.line, selection_end.pos, caret.line, caret.pos);
3055 if (caret < selection_anchor) {
3056 selection_start.line = caret.line;
3057 selection_start.tag = caret.tag;
3058 selection_start.pos = caret.pos;
3060 selection_end.line = selection_anchor.line;
3061 selection_end.tag = selection_anchor.tag;
3062 selection_end.pos = selection_anchor.pos;
3064 selection_end_anchor = true;
3066 selection_start.line = selection_anchor.line;
3067 selection_start.tag = selection_anchor.tag;
3068 selection_start.pos = selection_anchor.pos;
3070 selection_end.line = caret.line;
3071 selection_end.tag = caret.tag;
3072 selection_end.pos = caret.pos;
3074 selection_end_anchor = false;
3078 SetSelectionVisible (!(selection_start == selection_end));
3081 internal void SetSelection(Line start, int start_pos, Line end, int end_pos) {
3082 if (selection_visible) {
3083 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3086 if ((end.line_no < start.line_no) || ((end == start) && (end_pos <= start_pos))) {
3087 selection_start.line = end;
3088 selection_start.tag = LineTag.FindTag(end, end_pos);
3089 selection_start.pos = end_pos;
3091 selection_end.line = start;
3092 selection_end.tag = LineTag.FindTag(start, start_pos);
3093 selection_end.pos = start_pos;
3095 selection_end_anchor = true;
3097 selection_start.line = start;
3098 selection_start.tag = LineTag.FindTag(start, start_pos);
3099 selection_start.pos = start_pos;
3101 selection_end.line = end;
3102 selection_end.tag = LineTag.FindTag(end, end_pos);
3103 selection_end.pos = end_pos;
3105 selection_end_anchor = false;
3108 selection_anchor.line = start;
3109 selection_anchor.tag = selection_start.tag;
3110 selection_anchor.pos = start_pos;
3112 if (((start == end) && (start_pos == end_pos)) || start == null || end == null) {
3113 SetSelectionVisible (false);
3115 SetSelectionVisible (true);
3116 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3120 internal void SetSelectionStart(Line start, int start_pos) {
3121 // Invalidate from the previous to the new start pos
3122 Invalidate(selection_start.line, selection_start.pos, start, start_pos);
3124 selection_start.line = start;
3125 selection_start.pos = start_pos;
3126 selection_start.tag = LineTag.FindTag(start, start_pos);
3128 selection_anchor.line = start;
3129 selection_anchor.pos = start_pos;
3130 selection_anchor.tag = selection_start.tag;
3132 selection_end_anchor = false;
3135 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
3136 SetSelectionVisible (true);
3138 SetSelectionVisible (false);
3141 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3144 internal void SetSelectionStart(int character_index) {
3149 if (character_index < 0) {
3153 CharIndexToLineTag(character_index, out line, out tag, out pos);
3154 SetSelectionStart(line, pos);
3157 internal void SetSelectionEnd(Line end, int end_pos) {
3159 if (end == selection_end.line && end_pos == selection_start.pos) {
3160 selection_anchor.line = selection_start.line;
3161 selection_anchor.tag = selection_start.tag;
3162 selection_anchor.pos = selection_start.pos;
3164 selection_end.line = selection_start.line;
3165 selection_end.tag = selection_start.tag;
3166 selection_end.pos = selection_start.pos;
3168 selection_end_anchor = false;
3169 } else if ((end.line_no < selection_anchor.line.line_no) || ((end == selection_anchor.line) && (end_pos <= selection_anchor.pos))) {
3170 selection_start.line = end;
3171 selection_start.tag = LineTag.FindTag(end, end_pos);
3172 selection_start.pos = end_pos;
3174 selection_end.line = selection_anchor.line;
3175 selection_end.tag = selection_anchor.tag;
3176 selection_end.pos = selection_anchor.pos;
3178 selection_end_anchor = true;
3180 selection_start.line = selection_anchor.line;
3181 selection_start.tag = selection_anchor.tag;
3182 selection_start.pos = selection_anchor.pos;
3184 selection_end.line = end;
3185 selection_end.tag = LineTag.FindTag(end, end_pos);
3186 selection_end.pos = end_pos;
3188 selection_end_anchor = false;
3191 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
3192 SetSelectionVisible (true);
3193 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3195 SetSelectionVisible (false);
3196 // ?? Do I need to invalidate here, tests seem to work without it, but I don't think they should :-s
3200 internal void SetSelectionEnd(int character_index) {
3205 if (character_index < 0) {
3209 CharIndexToLineTag(character_index, out line, out tag, out pos);
3210 SetSelectionEnd(line, pos);
3213 internal void SetSelection(Line start, int start_pos) {
3214 if (selection_visible) {
3215 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3218 selection_start.line = start;
3219 selection_start.pos = start_pos;
3220 selection_start.tag = LineTag.FindTag(start, start_pos);
3222 selection_end.line = start;
3223 selection_end.tag = selection_start.tag;
3224 selection_end.pos = start_pos;
3226 selection_anchor.line = start;
3227 selection_anchor.tag = selection_start.tag;
3228 selection_anchor.pos = start_pos;
3230 selection_end_anchor = false;
3231 SetSelectionVisible (false);
3234 internal void InvalidateSelectionArea() {
3235 Invalidate (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3238 // Return the current selection, as string
3239 internal string GetSelection() {
3240 // We return String.Empty if there is no selection
3241 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3242 return string.Empty;
3245 if (!multiline || (selection_start.line == selection_end.line)) {
3246 return selection_start.line.text.ToString(selection_start.pos, selection_end.pos - selection_start.pos);
3253 sb = new StringBuilder();
3254 start = selection_start.line.line_no;
3255 end = selection_end.line.line_no;
3257 sb.Append(selection_start.line.text.ToString(selection_start.pos, selection_start.line.text.Length - selection_start.pos) + Environment.NewLine);
3259 if ((start + 1) < end) {
3260 for (i = start + 1; i < end; i++) {
3261 sb.Append(GetLine(i).text.ToString() + Environment.NewLine);
3265 sb.Append(selection_end.line.text.ToString(0, selection_end.pos));
3267 return sb.ToString();
3271 internal void ReplaceSelection(string s, bool select_new) {
3274 undo.BeginCompoundAction ();
3276 int selection_pos_on_line = selection_start.pos;
3277 int selection_start_pos = LineTagToCharIndex (selection_start.line, selection_start.pos);
3280 // First, delete any selected text
3281 if ((selection_start.pos != selection_end.pos) || (selection_start.line != selection_end.line)) {
3282 if (!multiline || (selection_start.line == selection_end.line)) {
3283 undo.RecordDeleteChars(selection_start.line, selection_start.pos + 1, selection_end.pos - selection_start.pos);
3285 DeleteChars(selection_start.tag, selection_start.pos, selection_end.pos - selection_start.pos);
3287 // The tag might have been removed, we need to recalc it
3288 selection_start.tag = selection_start.line.FindTag(selection_start.pos);
3293 start = selection_start.line.line_no;
3294 end = selection_end.line.line_no;
3296 undo.RecordDelete(selection_start.line, selection_start.pos + 1, selection_end.line, selection_end.pos);
3298 // Delete first line
3299 DeleteChars(selection_start.tag, selection_start.pos, selection_start.line.text.Length - selection_start.pos);
3302 DeleteChars(selection_end.line.tags, 0, selection_end.pos);
3306 for (i = end - 1; i >= start; i--) {
3311 // BIG FAT WARNING - selection_end.line might be stale due
3312 // to the above Delete() call. DONT USE IT before hitting the end of this method!
3314 // Join start and end
3315 Combine(selection_start.line.line_no, start);
3320 Insert(selection_start.line, selection_start.pos, false, s);
3321 undo.RecordInsertString (selection_start.line, selection_start.pos, s);
3322 ResumeRecalc (false);
3325 CharIndexToLineTag(selection_start_pos + s.Length, out selection_start.line,
3326 out selection_start.tag, out selection_start.pos);
3328 selection_end.line = selection_start.line;
3329 selection_end.pos = selection_start.pos;
3330 selection_end.tag = selection_start.tag;
3331 selection_anchor.line = selection_start.line;
3332 selection_anchor.pos = selection_start.pos;
3333 selection_anchor.tag = selection_start.tag;
3335 SetSelectionVisible (false);
3337 CharIndexToLineTag(selection_start_pos, out selection_start.line,
3338 out selection_start.tag, out selection_start.pos);
3340 CharIndexToLineTag(selection_start_pos + s.Length, out selection_end.line,
3341 out selection_end.tag, out selection_end.pos);
3343 selection_anchor.line = selection_start.line;
3344 selection_anchor.pos = selection_start.pos;
3345 selection_anchor.tag = selection_start.tag;
3347 SetSelectionVisible (true);
3350 PositionCaret (selection_start.line, selection_start.pos);
3351 UpdateView (selection_start.line, selection_pos_on_line);
3353 undo.EndCompoundAction ();
3356 internal void CharIndexToLineTag(int index, out Line line_out, out LineTag tag_out, out int pos) {
3365 for (i = 1; i <= lines; i++) {
3369 chars += line.text.Length + (line.soft_break ? 0 : crlf_size);
3371 if (index <= chars) {
3372 // we found the line
3375 while (tag != null) {
3376 if (index < (start + tag.start + tag.length)) {
3378 tag_out = LineTag.GetFinalTag (tag);
3379 pos = index - start;
3382 if (tag.next == null) {
3385 next_line = GetLine(line.line_no + 1);
3387 if (next_line != null) {
3388 line_out = next_line;
3389 tag_out = LineTag.GetFinalTag (next_line.tags);
3394 tag_out = LineTag.GetFinalTag (tag);
3395 pos = line_out.text.Length;
3404 line_out = GetLine(lines);
3405 tag = line_out.tags;
3406 while (tag.next != null) {
3410 pos = line_out.text.Length;
3413 internal int LineTagToCharIndex(Line line, int pos) {
3417 // Count first and last line
3420 // Count the lines in the middle
3422 for (i = 1; i < line.line_no; i++) {
3423 length += GetLine(i).text.Length + crlf_size;
3431 internal int SelectionLength() {
3432 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3436 if (!multiline || (selection_start.line == selection_end.line)) {
3437 return selection_end.pos - selection_start.pos;
3444 // Count first and last line
3445 length = selection_start.line.text.Length - selection_start.pos + selection_end.pos + crlf_size;
3447 // Count the lines in the middle
3448 start = selection_start.line.line_no + 1;
3449 end = selection_end.line.line_no;
3452 for (i = start; i < end; i++) {
3453 length += GetLine(i).text.Length + crlf_size;
3464 /// <summary>Give it a Line number and it returns the Line object at with that line number</summary>
3465 internal Line GetLine(int LineNo) {
3466 Line line = document;
3468 while (line != sentinel) {
3469 if (LineNo == line.line_no) {
3471 } else if (LineNo < line.line_no) {
3481 /// <summary>Retrieve the previous tag; walks line boundaries</summary>
3482 internal LineTag PreviousTag(LineTag tag) {
3485 if (tag.previous != null) {
3486 return tag.previous;
3490 if (tag.line.line_no == 1) {
3494 l = GetLine(tag.line.line_no - 1);
3499 while (t.next != null) {
3508 /// <summary>Retrieve the next tag; walks line boundaries</summary>
3509 internal LineTag NextTag(LineTag tag) {
3512 if (tag.next != null) {
3517 l = GetLine(tag.line.line_no + 1);
3525 internal Line ParagraphStart(Line line) {
3526 while (line.soft_break) {
3527 line = GetLine(line.line_no - 1);
3532 internal Line ParagraphEnd(Line line) {
3535 while (line.soft_break) {
3536 l = GetLine(line.line_no + 1);
3537 if ((l == null) || (!l.soft_break)) {
3545 /// <summary>Give it a Y pixel coordinate and it returns the Line covering that Y coordinate</summary>
3546 internal Line GetLineByPixel(int y, bool exact) {
3547 Line line = document;
3550 while (line != sentinel) {
3552 if ((y >= line.Y) && (y < (line.Y+line.height))) {
3554 } else if (y < line.Y) {
3567 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3568 internal LineTag FindTag(int x, int y, out int index, bool exact) {
3572 line = GetLineByPixel(y, exact);
3579 // Alignment adjustment
3580 x += line.align_shift;
3583 if (x >= tag.X && x < (tag.X+tag.width)) {
3586 end = tag.start + tag.length - 1;
3588 for (int pos = tag.start; pos < end; pos++) {
3589 if (x < line.widths[pos]) {
3591 return LineTag.GetFinalTag (tag);
3595 return LineTag.GetFinalTag (tag);
3597 if (tag.next != null) {
3605 index = line.text.Length;
3606 return LineTag.GetFinalTag (tag);
3611 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3612 internal LineTag FindCursor(int x, int y, out int index) {
3616 line = GetLineByPixel(y, false);
3619 // Adjust for alignment
3620 x -= line.align_shift;
3623 if (x >= tag.X && x < (tag.X+tag.width)) {
3628 for (int pos = tag.start-1; pos < end; pos++) {
3629 // When clicking on a character, we position the cursor to whatever edge
3630 // of the character the click was closer
3631 if (x < (line.widths[pos] + ((line.widths[pos+1]-line.widths[pos])/2))) {
3637 return LineTag.GetFinalTag (tag);
3639 if (tag.next != null) {
3642 index = line.text.Length;
3643 return LineTag.GetFinalTag (tag);
3648 /// <summary>Format area of document in specified font and color</summary>
3649 /// <param name="start_pos">1-based start position on start_line</param>
3650 /// <param name="end_pos">1-based end position on end_line </param>
3651 internal void FormatText (Line start_line, int start_pos, Line end_line, int end_pos, Font font,
3652 SolidBrush color, SolidBrush back_color, FormatSpecified specified)
3656 // First, format the first line
3657 if (start_line != end_line) {
3659 LineTag.FormatText(start_line, start_pos, start_line.text.Length - start_pos + 1, font, color, back_color, specified);
3662 LineTag.FormatText(end_line, 1, end_pos, font, color, back_color, specified);
3664 // Now all the lines inbetween
3665 for (int i = start_line.line_no + 1; i < end_line.line_no; i++) {
3667 LineTag.FormatText(l, 1, l.text.Length, font, color, back_color, specified);
3670 // Special case, single line
3671 LineTag.FormatText(start_line, start_pos, end_pos - start_pos, font, color, back_color, specified);
3675 /// <summary>Re-format areas of the document in specified font and color</summary>
3676 /// <param name="start_pos">1-based start position on start_line</param>
3677 /// <param name="end_pos">1-based end position on end_line </param>
3678 /// <param name="font">Font specifying attributes</param>
3679 /// <param name="color">Color (or NULL) to apply</param>
3680 /// <param name="apply">Attributes from font and color to apply</param>
3681 internal void FormatText(Line start_line, int start_pos, Line end_line, int end_pos, FontDefinition attributes) {
3684 // First, format the first line
3685 if (start_line != end_line) {
3687 LineTag.FormatText(start_line, start_pos, start_line.text.Length - start_pos + 1, attributes);
3690 LineTag.FormatText(end_line, 1, end_pos - 1, attributes);
3692 // Now all the lines inbetween
3693 for (int i = start_line.line_no + 1; i < end_line.line_no; i++) {
3695 LineTag.FormatText(l, 1, l.text.Length, attributes);
3698 // Special case, single line
3699 LineTag.FormatText(start_line, start_pos, end_pos - start_pos, attributes);
3703 internal void RecalculateAlignments() {
3709 while (line_no <= lines) {
3710 line = GetLine(line_no);
3713 switch (line.alignment) {
3714 case HorizontalAlignment.Left:
3715 line.align_shift = 0;
3717 case HorizontalAlignment.Center:
3718 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3720 case HorizontalAlignment.Right:
3721 line.align_shift = viewport_width - (int)line.widths[line.text.Length];
3731 /// <summary>Calculate formatting for the whole document</summary>
3732 internal bool RecalculateDocument(Graphics g) {
3733 return RecalculateDocument(g, 1, this.lines, false);
3736 /// <summary>Calculate formatting starting at a certain line</summary>
3737 internal bool RecalculateDocument(Graphics g, int start) {
3738 return RecalculateDocument(g, start, this.lines, false);
3741 /// <summary>Calculate formatting within two given line numbers</summary>
3742 internal bool RecalculateDocument(Graphics g, int start, int end) {
3743 return RecalculateDocument(g, start, end, false);
3746 /// <summary>With optimize on, returns true if line heights changed</summary>
3747 internal bool RecalculateDocument(Graphics g, int start, int end, bool optimize) {
3755 if (recalc_suspended > 0) {
3756 recalc_pending = true;
3757 recalc_start = Math.Min (recalc_start, start);
3758 recalc_end = Math.Max (recalc_end, end);
3759 recalc_optimize = optimize;
3763 // Fixup the positions, they can go kinda nuts
3764 start = Math.Max (start, 1);
3765 end = Math.Min (end, lines);
3767 Y = GetLine(start).Y;
3772 changed = true; // We always return true if we run non-optimized
3777 while (line_no <= (end + this.lines - shift)) {
3778 line = GetLine(line_no++);
3783 line.RecalculateLine(g, this);
3785 if (line.recalc && line.RecalculateLine(g, this)) {
3787 // If the height changed, all subsequent lines change
3794 line.RecalculatePasswordLine(g, this);
3796 if (line.recalc && line.RecalculatePasswordLine(g, this)) {
3798 // If the height changed, all subsequent lines change
3805 if (line.widths[line.text.Length] > new_width) {
3806 new_width = (int)line.widths[line.text.Length];
3809 // Calculate alignment
3810 if (line.alignment != HorizontalAlignment.Left) {
3811 if (line.alignment == HorizontalAlignment.Center) {
3812 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3814 line.align_shift = viewport_width - (int)line.widths[line.text.Length] - 1;
3820 if (line_no > lines) {
3825 if (document_x != new_width) {
3826 document_x = new_width;
3827 if (WidthChanged != null) {
3828 WidthChanged(this, null);
3832 RecalculateAlignments();
3834 line = GetLine(lines);
3836 if (document_y != line.Y + line.height) {
3837 document_y = line.Y + line.height;
3838 if (HeightChanged != null) {
3839 HeightChanged(this, null);
3846 internal int Size() {
3850 private void owner_HandleCreated(object sender, EventArgs e) {
3851 RecalculateDocument(owner.CreateGraphicsInternal());
3855 private void owner_VisibleChanged(object sender, EventArgs e) {
3856 if (owner.Visible) {
3857 RecalculateDocument(owner.CreateGraphicsInternal());
3861 internal static bool IsWordSeparator(char ch) {
3875 internal int FindWordSeparator(Line line, int pos, bool forward) {
3878 len = line.text.Length;
3881 for (int i = pos + 1; i < len; i++) {
3882 if (IsWordSeparator(line.Text[i])) {
3888 for (int i = pos - 1; i > 0; i--) {
3889 if (IsWordSeparator(line.Text[i - 1])) {
3897 /* Search document for text */
3898 internal bool FindChars(char[] chars, Marker start, Marker end, out Marker result) {
3904 // Search for occurence of any char in the chars array
3905 result = new Marker();
3908 line_no = start.line.line_no;
3910 while (line_no <= end.line.line_no) {
3911 line_len = line.text.Length;
3912 while (pos < line_len) {
3913 for (int i = 0; i < chars.Length; i++) {
3914 if (line.text[pos] == chars[i]) {
3916 if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
3930 line = GetLine(line_no);
3936 // This version does not build one big string for searching, instead it handles
3937 // line-boundaries, which is faster and less memory intensive
3938 // FIXME - Depending on culture stuff we might have to create a big string and use culturespecific
3939 // search stuff and change it to accept and return positions instead of Markers (which would match
3940 // RichTextBox behaviour better but would be inconsistent with the rest of TextControl)
3941 internal bool Find(string search, Marker start, Marker end, out Marker result, RichTextBoxFinds options) {
3943 string search_string;
3955 result = new Marker();
3956 word_option = ((options & RichTextBoxFinds.WholeWord) != 0);
3957 ignore_case = ((options & RichTextBoxFinds.MatchCase) == 0);
3958 reverse = ((options & RichTextBoxFinds.Reverse) != 0);
3961 line_no = start.line.line_no;
3965 // Prep our search string, lowercasing it if we do case-independent matching
3968 sb = new StringBuilder(search);
3969 for (int i = 0; i < sb.Length; i++) {
3970 sb[i] = Char.ToLower(sb[i]);
3972 search_string = sb.ToString();
3974 search_string = search;
3977 // We need to check if the character before our start position is a wordbreak
3980 if ((pos == 0) || (IsWordSeparator(line.text[pos - 1]))) {
3987 if (IsWordSeparator(line.text[pos - 1])) {
3993 // Need to check the end of the previous line
3996 prev_line = GetLine(line_no - 1);
3997 if (prev_line.soft_break) {
3998 if (IsWordSeparator(prev_line.text[prev_line.text.Length - 1])) {
4012 // To avoid duplication of this loop with reverse logic, we search
4013 // through the document, remembering the last match and when returning
4014 // report that last remembered match
4016 last = new Marker();
4017 last.height = -1; // Abused - we use it to track change
4019 while (line_no <= end.line.line_no) {
4020 if (line_no != end.line.line_no) {
4021 line_len = line.text.Length;
4026 while (pos < line_len) {
4027 if (word_option && (current == search_string.Length)) {
4028 if (IsWordSeparator(line.text[pos])) {
4041 c = Char.ToLower(line.text[pos]);
4046 if (c == search_string[current]) {
4051 if (!word_option || (word_option && (word || (current > 0)))) {
4055 if (!word_option && (current == search_string.Length)) {
4072 if (IsWordSeparator(c)) {
4080 // Mark that we just saw a word boundary
4081 if (!line.soft_break) {
4085 if (current == search_string.Length) {
4101 line = GetLine(line_no);
4105 if (last.height != -1) {
4115 // if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
4127 internal void GetMarker(out Marker mark, bool start) {
4128 mark = new Marker();
4131 mark.line = GetLine(1);
4132 mark.tag = mark.line.tags;
4135 mark.line = GetLine(lines);
4136 mark.tag = mark.line.tags;
4137 while (mark.tag.next != null) {
4138 mark.tag = mark.tag.next;
4140 mark.pos = mark.line.text.Length;
4143 #endregion // Internal Methods
4146 internal event EventHandler CaretMoved;
4147 internal event EventHandler WidthChanged;
4148 internal event EventHandler HeightChanged;
4149 internal event EventHandler LengthChanged;
4150 #endregion // Events
4152 #region Administrative
4153 public IEnumerator GetEnumerator() {
4158 public override bool Equals(object obj) {
4163 if (!(obj is Document)) {
4171 if (ToString().Equals(((Document)obj).ToString())) {
4178 public override int GetHashCode() {
4182 public override string ToString() {
4183 return "document " + this.document_id;
4185 #endregion // Administrative
4188 internal class ImageTag : LineTag {
4190 internal Image image;
4192 internal ImageTag (Line line, int start, Image image) : base (line, start)
4197 internal override SizeF SizeOfPosition (Graphics dc, int pos)
4202 internal override int MaxHeight ()
4204 return image.Height;
4207 internal override void Draw (Graphics dc, Brush brush, float x, float y, int start, int end)
4209 dc.DrawImage (image, x, y);
4213 internal class LineTag {
4214 #region Local Variables;
4215 // Payload; formatting
4216 internal Font font; // System.Drawing.Font object for this tag
4217 internal SolidBrush color; // The font color for this tag
4219 // In 2.0 tags can have background colours. I'm not going to #ifdef
4220 // at this level though since I want to reduce code paths
4221 internal SolidBrush back_color;
4224 internal int start; // start, in chars; index into Line.text
4225 internal bool r_to_l; // Which way is the font
4228 internal int height; // Height in pixels of the text this tag describes
4230 internal int ascent; // Ascent of the font for this tag
4231 internal int shift; // Shift down for this tag, to stay on baseline
4234 internal Line line; // The line we're on
4235 internal LineTag next; // Next tag on the same line
4236 internal LineTag previous; // Previous tag on the same line
4239 #region Constructors
4240 internal LineTag(Line line, int start) {
4244 #endregion // Constructors
4246 #region Internal Methods
4252 return line.widths [start - 1];
4257 get { return start + length; }
4260 public float width {
4264 return line.widths [start + length - 1] - line.widths [start - 1];
4272 res = next.start - start;
4274 res = line.text.Length - (start - 1);
4276 return res > 0 ? res : 0;
4280 internal virtual SizeF SizeOfPosition (Graphics dc, int pos)
4282 return dc.MeasureString (line.text.ToString (pos, 1), font, 10000, Document.string_format);
4285 internal virtual int MaxHeight ()
4290 internal virtual void Draw (Graphics dc, Brush brush, float x, float y, int start, int end)
4292 dc.DrawString (line.text.ToString (start, end), font, brush, x, y, StringFormat.GenericTypographic);
4295 ///<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>
4296 internal LineTag Break(int pos) {
4301 if (pos == this.start) {
4303 } else if (pos >= (start + length)) {
4307 new_tag = new LineTag(line, pos);
4308 new_tag.CopyFormattingFrom (this);
4310 new_tag.next = this.next;
4311 this.next = new_tag;
4312 new_tag.previous = this;
4314 if (new_tag.next != null) {
4315 new_tag.next.previous = new_tag;
4321 public string Text ()
4323 return line.text.ToString (start - 1, length);
4326 public void CopyFormattingFrom (LineTag other)
4328 height = other.height;
4330 color = other.color;
4331 back_color = other.back_color;
4334 ///<summary>Create new font and brush from existing font and given new attributes. Returns true if fontheight changes</summary>
4335 internal static bool GenerateTextFormat(Font font_from, SolidBrush color_from, FontDefinition attributes, out Font new_font, out SolidBrush new_color) {
4341 if (attributes.font_obj == null) {
4342 size = font_from.SizeInPoints;
4343 unit = font_from.Unit;
4344 face = font_from.Name;
4345 style = font_from.Style;
4347 if (attributes.face != null) {
4348 face = attributes.face;
4351 if (attributes.size != 0) {
4352 size = attributes.size;
4355 style |= attributes.add_style;
4356 style &= ~attributes.remove_style;
4359 new_font = new Font(face, size, style, unit);
4361 new_font = attributes.font_obj;
4364 // Create 'new' color brush
4365 if (attributes.color != Color.Empty) {
4366 new_color = new SolidBrush(attributes.color);
4368 new_color = color_from;
4371 if (new_font.Height == font_from.Height) {
4377 /// <summary>Applies 'font' and 'brush' to characters starting at 'start' for 'length' chars;
4378 /// Removes any previous tags overlapping the same area;
4379 /// returns true if lineheight has changed</summary>
4380 /// <param name="start">1-based character position on line</param>
4381 internal static bool FormatText(Line line, int start, int length, Font font, SolidBrush color, SolidBrush back_color, FormatSpecified specified)
4387 bool retval = false; // Assume line-height doesn't change
4390 if (((FormatSpecified.Font & specified) == FormatSpecified.Font) && font.Height != line.height) {
4393 line.recalc = true; // This forces recalculation of the line in RecalculateDocument
4395 // A little sanity, not sure if it's needed, might be able to remove for speed
4396 if (length > line.text.Length) {
4397 length = line.text.Length;
4401 end = start + length;
4403 // Common special case
4404 if ((start == 1) && (length == tag.length)) {
4406 SetFormat (tag, font, color, back_color, specified);
4410 start_tag = FindTag (line, start);
4412 tag = start_tag.Break (start);
4414 while (tag != null && tag.end <= end) {
4415 SetFormat (tag, font, color, back_color, specified);
4419 if (end != line.text.Length) {
4420 /// Now do the last tag
4421 end_tag = FindTag (line, end);
4423 if (end_tag != null) {
4424 end_tag.Break (end);
4425 SetFormat (end_tag, font, color, back_color, specified);
4432 private static void SetFormat (LineTag tag, Font font, SolidBrush color, SolidBrush back_color, FormatSpecified specified)
4434 if ((FormatSpecified.Font & specified) == FormatSpecified.Font)
4436 if ((FormatSpecified.Color & specified) == FormatSpecified.Color)
4438 if ((FormatSpecified.BackColor & specified) == FormatSpecified.BackColor) {
4439 tag.back_color = back_color;
4441 // Console.WriteLine ("setting format: {0} {1} new color {2}", color.Color, specified, tag.color.Color);
4444 /// <summary>Applies font attributes specified to characters starting at 'start' for 'length' chars;
4445 /// Breaks tags at start and end point, keeping middle tags with altered attributes.
4446 /// Returns true if lineheight has changed</summary>
4447 /// <param name="start">1-based character position on line</param>
4448 internal static bool FormatText(Line line, int start, int length, FontDefinition attributes) {
4452 bool retval = false; // Assume line-height doesn't change
4454 line.recalc = true; // This forces recalculation of the line in RecalculateDocument
4456 // A little sanity, not sure if it's needed, might be able to remove for speed
4457 if (length > line.text.Length) {
4458 length = line.text.Length;
4463 // Common special case
4464 if ((start == 1) && (length == tag.length)) {
4466 GenerateTextFormat(tag.font, tag.color, attributes, out tag.font, out tag.color);
4470 start_tag = FindTag(line, start);
4472 if (start_tag == null) {
4474 // We are 'starting' after all valid tags; create a new tag with the right attributes
4475 start_tag = FindTag(line, line.text.Length - 1);
4476 start_tag.next = new LineTag(line, line.text.Length + 1);
4477 start_tag.next.CopyFormattingFrom (start_tag);
4478 start_tag.next.previous = start_tag;
4479 start_tag = start_tag.next;
4481 throw new Exception(String.Format("Could not find start_tag in document at line {0} position {1}", line.line_no, start));
4484 start_tag = start_tag.Break(start);
4487 end_tag = FindTag(line, start + length);
4488 if (end_tag != null) {
4489 end_tag = end_tag.Break(start + length);
4492 // start_tag or end_tag might be null; we're cool with that
4493 // we now walk from start_tag to end_tag, applying new attributes
4495 while ((tag != null) && tag != end_tag) {
4496 if (LineTag.GenerateTextFormat(tag.font, tag.color, attributes, out tag.font, out tag.color)) {
4505 /// <summary>Finds the tag that describes the character at position 'pos' on 'line'</summary>
4506 internal static LineTag FindTag(Line line, int pos) {
4507 LineTag tag = line.tags;
4509 // Beginning of line is a bit special
4511 // Not sure if we should get the final tag here
4515 while (tag != null) {
4516 if ((tag.start <= pos) && (pos <= tag.end)) {
4517 return GetFinalTag (tag);
4526 // There can be multiple tags at the same position, we want to make
4527 // sure we are using the very last tag at the given position
4528 internal static LineTag GetFinalTag (LineTag tag)
4532 while (res.next != null && res.next.length == 0)
4537 /// <summary>Combines 'this' tag with 'other' tag</summary>
4538 internal bool Combine(LineTag other) {
4539 if (!this.Equals(other)) {
4543 this.next = other.next;
4544 if (this.next != null) {
4545 this.next.previous = this;
4552 /// <summary>Remove 'this' tag ; to be called when formatting is to be removed</summary>
4553 internal bool Remove() {
4554 if ((this.start == 1) && (this.next == null)) {
4555 // We cannot remove the only tag
4558 if (this.start != 1) {
4559 this.previous.next = this.next;
4560 this.next.previous = this.previous;
4562 this.next.start = 1;
4563 this.line.tags = this.next;
4564 this.next.previous = null;
4570 /// <summary>Checks if 'this' tag describes the same formatting options as 'obj'</summary>
4571 public override bool Equals(object obj) {
4578 if (!(obj is LineTag)) {
4586 other = (LineTag)obj;
4588 if (this.font.Equals(other.font) && this.color.Equals(other.color)) { // FIXME add checking for things like link or type later
4595 public override int GetHashCode() {
4596 return base.GetHashCode ();
4599 public override string ToString() {
4601 return "Tag starts at index " + this.start + " length " + this.length + " text: " + Text () + "Font " + this.font.ToString();
4602 return "Zero Lengthed tag at index " + this.start;
4605 #endregion // Internal Methods
4608 internal class UndoClass {
4609 internal enum ActionType {
4623 internal class Action {
4624 internal ActionType type;
4625 internal int line_no;
4627 internal object data;
4630 #region Local Variables
4631 private Document document;
4632 private Stack undo_actions;
4633 private Stack redo_actions;
4635 private int caret_line;
4636 private int caret_pos;
4637 #endregion // Local Variables
4639 #region Constructors
4640 internal UndoClass(Document doc) {
4642 undo_actions = new Stack(50);
4643 redo_actions = new Stack(50);
4645 #endregion // Constructors
4648 internal bool CanUndo {
4650 foreach (Action action in undo_actions) {
4651 if (action.type == ActionType.UserActionEnd)
4658 internal bool CanRedo {
4660 foreach (Action action in undo_actions) {
4661 if (action.type == ActionType.UserActionBegin)
4668 internal string UndoActionName
4673 action = (Action)undo_actions.Peek();
4675 if (action.type == ActionType.CompoundEnd)
4676 return (string) action.data;
4678 switch(action.type) {
4679 case ActionType.InsertChar: {
4680 Locale.GetText("Insert character");
4684 case ActionType.DeleteChar: {
4685 Locale.GetText("Delete character");
4689 case ActionType.InsertString: {
4690 Locale.GetText("Insert string");
4694 case ActionType.DeleteChars: {
4695 Locale.GetText("Delete string");
4699 case ActionType.CursorMove: {
4700 Locale.GetText("Cursor move");
4708 internal string RedoActionName
4714 #endregion // Properties
4716 #region Internal Methods
4717 internal void Clear() {
4718 undo_actions.Clear();
4719 redo_actions.Clear();
4722 internal void Undo() {
4724 int compound_stack = 0;
4726 if (undo_actions.Count == 0) {
4733 action = (Action)undo_actions.Pop();
4735 // Put onto redo stack
4736 redo_actions.Push(action);
4739 switch(action.type) {
4740 case ActionType.CompoundEnd:
4744 case ActionType.CompoundBegin:
4748 case ActionType.InsertString:
4749 document.DeleteMultiline (document.GetLine (action.line_no),
4750 action.pos, ((string) action.data).Length + 1);
4753 case ActionType.InsertChar: {
4754 // FIXME - implement me
4758 case ActionType.DeleteChars: {
4759 this.Insert(document.GetLine(action.line_no), action.pos, (Line)action.data);
4760 Undo(); // Grab the cursor location
4764 case ActionType.CursorMove: {
4765 document.caret.line = document.GetLine(action.line_no);
4766 if (document.caret.line == null) {
4771 document.caret.tag = document.caret.line.FindTag(action.pos);
4772 document.caret.pos = action.pos;
4773 document.caret.height = document.caret.tag.height;
4775 if (document.owner.IsHandleCreated) {
4776 XplatUI.DestroyCaret(document.owner.Handle);
4777 XplatUI.CreateCaret(document.owner.Handle, 2, document.caret.height);
4778 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);
4780 document.DisplayCaret ();
4783 // FIXME - enable call
4784 //if (document.CaretMoved != null) document.CaretMoved(this, EventArgs.Empty);
4788 } while (compound_stack > 0);
4791 internal void Redo() {
4792 if (redo_actions.Count == 0) {
4796 #endregion // Internal Methods
4798 #region Private Methods
4800 public void BeginCompoundAction ()
4802 Action cb = new Action ();
4803 cb.type = ActionType.CompoundBegin;
4805 undo_actions.Push (cb);
4808 public void EndCompoundAction ()
4810 Action ce = new Action ();
4811 ce.type = ActionType.CompoundEnd;
4813 undo_actions.Push (ce);
4817 public void RecordDeleteChars(Line line, int pos, int length) {
4818 RecordDelete(line, pos, line, pos + length - 1);
4821 // start_pos, end_pos = 1 based
4822 public void RecordDelete(Line start_line, int start_pos, Line end_line, int end_pos) {
4826 l = Duplicate(start_line, start_pos, end_line, end_pos);
4829 a.type = ActionType.DeleteChars;
4831 a.line_no = start_line.line_no;
4832 a.pos = start_pos - 1;
4834 // Record the cursor position before, since the actions will occur in reverse order
4836 undo_actions.Push(a);
4839 public void RecordInsertString (Line line, int pos, string str)
4841 Action a = new Action ();
4843 a.type = ActionType.InsertString;
4845 a.line_no = line.line_no;
4848 undo_actions.Push (a);
4851 public void RecordCursor() {
4852 if (document.caret.line == null) {
4856 RecordCursor(document.caret.line, document.caret.pos);
4859 public void RecordCursor(Line line, int pos) {
4862 if ((line.line_no == caret_line) && (pos == caret_pos)) {
4866 caret_line = line.line_no;
4870 a.type = ActionType.CursorMove;
4871 a.line_no = line.line_no;
4874 undo_actions.Push(a);
4877 // start_pos = 1-based
4878 // end_pos = 1-based
4879 public Line Duplicate(Line start_line, int start_pos, Line end_line, int end_pos) {
4884 LineTag current_tag;
4892 for (int i = start_line.line_no; i <= end_line.line_no; i++) {
4893 current = document.GetLine(i);
4895 if (start_line.line_no == i) {
4901 if (end_line.line_no == i) {
4904 end = current.text.Length;
4908 line.text = new StringBuilder(current.text.ToString(start - 1, end - start + 1));
4910 // Copy tags from start to start+length onto new line
4911 current_tag = current.FindTag(start - 1);
4912 while ((current_tag != null) && (current_tag.start < end)) {
4913 if ((current_tag.start <= start) && (start < (current_tag.start + current_tag.length))) {
4914 // start tag is within this tag
4917 tag_start = current_tag.start;
4920 tag = new LineTag(line, tag_start - start + 1);
4921 tag.CopyFormattingFrom (current_tag);
4923 current_tag = current_tag.next;
4925 // Add the new tag to the line
4926 if (line.tags == null) {
4932 while (tail.next != null) {
4936 tag.previous = tail;
4940 if ((i + 1) <= end_line.line_no) {
4941 line.soft_break = current.soft_break;
4943 // Chain them (we use right/left as next/previous)
4944 line.right = new Line();
4945 line.right.left = line;
4953 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
4954 internal void Insert(Line line, int pos, Line insert) {
4961 // Handle special case first
4962 if (insert.right == null) {
4964 // Single line insert
4965 document.Split(line, pos);
4967 if (insert.tags == null) {
4968 return; // Blank line
4971 //Insert our tags at the end
4974 while (tag.next != null) {
4978 offset = tag.start + tag.length - 1;
4980 tag.next = insert.tags;
4981 line.text.Insert(offset, insert.text.ToString());
4983 // Adjust start locations
4985 while (tag != null) {
4986 tag.start += offset;
4990 // Put it back together
4991 document.Combine(line.line_no, line.line_no + 1);
4992 document.UpdateView(line, pos);
4999 while (current != null) {
5000 if (current == insert) {
5001 // Inserting the first line we split the line (and make space)
5002 document.Split(line, pos);
5003 //Insert our tags at the end of the line
5007 while (tag.next != null) {
5010 offset = tag.start + tag.length - 1;
5011 tag.next = current.tags;
5012 tag.next.previous = tag;
5018 line.tags = current.tags;
5019 line.tags.previous = null;
5023 document.Split(line.line_no, 0);
5025 line.tags = current.tags;
5026 line.tags.previous = null;
5029 // Adjust start locations and line pointers
5030 while (tag != null) {
5031 tag.start += offset;
5036 line.text.Insert(offset, current.text.ToString());
5037 line.Grow(line.text.Length);
5040 line = document.GetLine(line.line_no + 1);
5042 // FIXME? Test undo of line-boundaries
5043 if ((current.right == null) && (current.tags.length != 0)) {
5044 document.Combine(line.line_no - 1, line.line_no);
5046 current = current.right;
5051 // Recalculate our document
5052 document.UpdateView(first, lines, pos);
5055 #endregion // Private Methods