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, 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;
53 using RTF=System.Windows.Forms.RTF;
55 namespace System.Windows.Forms {
56 internal enum LineColor {
61 internal enum CaretSelection {
62 Position, // Selection=Caret
63 Word, // Selection=Word under caret
64 Line // Selection=Line under caret
67 internal class FontDefinition {
70 internal FontStyle add_style;
71 internal FontStyle remove_style;
73 internal Font font_obj;
77 internal enum FormatSpecified {
85 internal enum CaretDirection {
86 CharForward, // Move a char to the right
87 CharBack, // Move a char to the left
88 LineUp, // Move a line up
89 LineDown, // Move a line down
90 Home, // Move to the beginning of the line
91 End, // Move to the end of the line
92 PgUp, // Move one page up
93 PgDn, // Move one page down
94 CtrlPgUp, // Move caret to the first visible char in the viewport
95 CtrlPgDn, // Move caret to the last visible char in the viewport
96 CtrlHome, // Move to the beginning of the document
97 CtrlEnd, // Move to the end of the document
98 WordBack, // Move to the beginning of the previous word (or beginning of line)
99 WordForward, // Move to the beginning of the next word (or end of line)
100 SelectionStart, // Move to the beginning of the current selection
101 SelectionEnd, // Move to the end of the current selection
102 CharForwardNoWrap, // Move a char forward, but don't wrap onto the next line
103 CharBackNoWrap // Move a char backward, but don't wrap onto the previous line
106 internal enum LineEnding {
107 Wrap, // line wraps to the next line
116 // Being cloneable should allow for nice line and document copies...
117 internal class Line : ICloneable, IComparable {
118 #region Local Variables
120 internal Document document;
122 // Stuff that matters for our line
123 internal StringBuilder text; // Characters for the line
124 internal float[] widths; // Width of each character; always one larger than text.Length
125 internal int space; // Number of elements in text and widths
126 internal int line_no; // Line number
127 internal LineTag tags; // Tags describing the text
128 internal int offset; // Baseline can be on the X or Y axis depending if we are in multiline mode or not
129 internal int height; // Height of the line (height of tallest tag)
130 internal int ascent; // Ascent of the line (ascent of the tallest tag)
131 internal HorizontalAlignment alignment; // Alignment of the line
132 internal int align_shift; // Pixel shift caused by the alignment
133 internal int indent; // Left indent for the first line
134 internal int hanging_indent; // Hanging indent (left indent for all but the first line)
135 internal int right_indent; // Right indent for all lines
136 internal LineEnding ending;
139 // Stuff that's important for the tree
140 internal Line parent; // Our parent line
141 internal Line left; // Line with smaller line number
142 internal Line right; // Line with higher line number
143 internal LineColor color; // We're doing a black/red tree. this is the node color
144 internal int DEFAULT_TEXT_LEN; //
145 internal bool recalc; // Line changed
146 internal int left_margin = 2; // A left margin for all lines
147 internal int top_margin = 2;
148 internal int right_margin = 2;
149 #endregion // Local Variables
152 internal Line (Document document, LineEnding ending)
154 this.document = document;
155 color = LineColor.Red;
161 alignment = document.alignment;
166 internal Line(Document document, int LineNo, string Text, Font font, SolidBrush color, LineEnding ending) : this (document, ending)
168 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
170 text = new StringBuilder(Text, space);
172 this.ending = ending;
174 widths = new float[space + 1];
177 tags = new LineTag(this, 1);
182 internal Line(Document document, int LineNo, string Text, HorizontalAlignment align, Font font, SolidBrush color, LineEnding ending) : this(document, ending)
184 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
186 text = new StringBuilder(Text, space);
188 this.ending = ending;
191 widths = new float[space + 1];
194 tags = new LineTag(this, 1);
199 internal Line(Document document, int LineNo, string Text, LineTag tag, LineEnding ending) : this(document, ending)
201 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
203 text = new StringBuilder(Text, space);
204 this.ending = ending;
207 widths = new float[space + 1];
211 #endregion // Constructors
213 #region Internal Properties
217 int tm = document.owner.actual_border_style == BorderStyle.FixedSingle ? top_margin : 0;
218 if (!document.multiline)
226 if (document.multiline)
228 return offset + align_shift;
234 int res = (int) widths [text.Length];
235 if (!document.multiline) {
242 internal int Indent {
253 internal int HangingIndent {
255 return hanging_indent;
259 hanging_indent = value;
264 internal int RightIndent {
270 right_indent = value;
276 internal int Height {
286 internal int LineNo {
296 internal string Text {
298 return text.ToString();
302 text = new StringBuilder(value, value.Length > DEFAULT_TEXT_LEN ? value.Length : DEFAULT_TEXT_LEN);
306 internal HorizontalAlignment Alignment {
312 if (alignment != value) {
319 internal StringBuilder Text {
329 #endregion // Internal Properties
331 #region Internal Methods
333 // This doesn't do exactly what you would think, it just pulls of the \n part of the ending
334 internal string TextWithoutEnding ()
336 return text.ToString (0, text.Length - document.LineEndingLength (ending));
339 internal int TextLengthWithoutEnding ()
341 return text.Length - document.LineEndingLength (ending);
344 internal void DrawEnding (Graphics dc, float y)
346 if (document.multiline)
349 while (last.next != null)
352 string end_str = null;
353 switch (document.LineEndingLength (ending)) {
360 end_str = "\u0013\u0013";
363 end_str = "\u0013\u0013\u0013";
366 dc.DrawString (end_str, last.font, last.color, X + widths [TextLengthWithoutEnding ()] - document.viewport_x,
367 y, Document.string_format);
371 // Make sure we always have enoughs space in text and widths
372 internal void Grow(int minimum) {
376 length = text.Length;
378 if ((length + minimum) > space) {
379 // We need to grow; double the size
381 if ((length + minimum) > (space * 2)) {
382 new_widths = new float[length + minimum * 2 + 1];
383 space = length + minimum * 2;
385 new_widths = new float[space * 2 + 1];
388 widths.CopyTo(new_widths, 0);
394 internal void Streamline(int lines) {
402 // Catch what the loop below wont; eliminate 0 length
403 // tags, but only if there are other tags after us
404 // We only eliminate text tags if there is another text tag
405 // after it. Otherwise we wind up trying to type on picture tags
407 while ((current.length == 0) && (next != null) && (next.IsTextTag)) {
409 tags.previous = null;
419 while (next != null) {
420 // Take out 0 length tags unless it's the last tag in the document
421 if (current.IsTextTag && next.length == 0 && next.IsTextTag) {
422 if ((next.next != null) || (line_no != lines)) {
423 current.next = next.next;
424 if (current.next != null) {
425 current.next.previous = current;
431 if (current.Combine(next)) {
436 current = current.next;
441 /// <summary> Find the tag on a line based on the character position, pos is 0-based</summary>
442 internal LineTag FindTag(int pos) {
451 if (pos >= text.Length) {
452 pos = text.Length - 1;
455 while (tag != null) {
456 if (((tag.start - 1) <= pos) && (pos < (tag.start + tag.length - 1))) {
457 return LineTag.GetFinalTag (tag);
465 /// Recalculate a single line using the same char for every character in the line
468 internal bool RecalculatePasswordLine(Graphics g, Document doc) {
477 len = this.text.Length;
483 widths[0] = left_margin + indent;
485 w = g.MeasureString(doc.password_char, tags.font, 10000, Document.string_format).Width;
487 if (this.height != (int)tag.font.Height) {
493 this.height = (int)tag.font.Height;
494 tag.height = this.height;
496 XplatUI.GetFontMetrics(g, tag.font, out tag.ascent, out descent);
497 this.ascent = tag.ascent;
501 widths[pos] = widths[pos-1] + w;
508 /// Go through all tags on a line and recalculate all size-related values;
509 /// returns true if lineheight changed
511 internal bool RecalculateLine(Graphics g, Document doc) {
524 len = this.text.Length;
526 prev_offset = this.offset; // For drawing optimization calculations
527 this.height = 0; // Reset line height
528 this.ascent = 0; // Reset the ascent for the line
531 if (ending == LineEnding.Wrap) {
532 widths[0] = left_margin + hanging_indent;
534 widths[0] = left_margin + indent;
545 while (tag.length == 0) { // We should always have tags after a tag.length==0 unless len==0
551 size = tag.SizeOfPosition (g, pos);
554 if (Char.IsWhiteSpace(text[pos])) {
559 if ((wrap_pos > 0) && (wrap_pos != len) && (widths[pos] + w) + 5 > (doc.viewport_width - this.right_indent)) {
560 // Make sure to set the last width of the line before wrapping
561 widths [pos + 1] = widths [pos] + w;
565 doc.Split(this, tag, pos);
566 ending = LineEnding.Wrap;
567 len = this.text.Length;
571 } else if (pos > 1 && (widths[pos] + w) > (doc.viewport_width - this.right_indent)) {
572 // No suitable wrap position was found so break right in the middle of a word
574 // Make sure to set the last width of the line before wrapping
575 widths [pos + 1] = widths [pos] + w;
577 doc.Split(this, tag, pos);
578 ending = LineEnding.Wrap;
579 len = this.text.Length;
585 // Contract all wrapped lines that follow back into our line
589 widths[pos] = widths[pos-1] + w;
592 line = doc.GetLine(this.line_no + 1);
593 if ((line != null) && (ending == LineEnding.Wrap || ending == LineEnding.None)) {
594 // Pull the two lines together
595 doc.Combine(this.line_no, this.line_no + 1);
596 len = this.text.Length;
602 if (pos == (tag.start-1 + tag.length)) {
603 // We just found the end of our current tag
604 tag.height = tag.MaxHeight ();
606 // Check if we're the tallest on the line (so far)
607 if (tag.height > this.height) {
608 this.height = tag.height; // Yep; make sure the line knows
611 if (tag.ascent == 0) {
614 XplatUI.GetFontMetrics(g, tag.font, out tag.ascent, out descent);
617 if (tag.ascent > this.ascent) {
620 // We have a tag that has a taller ascent than the line;
622 while (t != null && t != tag) {
623 t.shift = tag.ascent - t.ascent;
628 this.ascent = tag.ascent;
630 tag.shift = this.ascent - tag.ascent;
641 if (this.height == 0) {
642 this.height = tags.font.Height;
643 tag.height = this.height;
646 if (prev_offset != offset) {
651 #endregion // Internal Methods
653 #region Administrative
654 public int CompareTo(object obj) {
659 if (! (obj is Line)) {
660 throw new ArgumentException("Object is not of type Line", "obj");
663 if (line_no < ((Line)obj).line_no) {
665 } else if (line_no > ((Line)obj).line_no) {
672 public object Clone() {
675 clone = new Line (document, ending);
680 clone.left = (Line)left.Clone();
684 clone.left = (Line)left.Clone();
690 internal object CloneLine() {
693 clone = new Line (document, ending);
700 public override bool Equals(object obj) {
705 if (!(obj is Line)) {
713 if (line_no == ((Line)obj).line_no) {
720 public override int GetHashCode() {
721 return base.GetHashCode ();
724 public override string ToString() {
725 return "Line " + line_no;
728 #endregion // Administrative
731 internal class Document : ICloneable, IEnumerable {
733 // FIXME - go through code and check for places where
734 // we do explicit comparisons instead of using the compare overloads
735 internal struct Marker {
737 internal LineTag tag;
741 public static bool operator<(Marker lhs, Marker rhs) {
742 if (lhs.line.line_no < rhs.line.line_no) {
746 if (lhs.line.line_no == rhs.line.line_no) {
747 if (lhs.pos < rhs.pos) {
754 public static bool operator>(Marker lhs, Marker rhs) {
755 if (lhs.line.line_no > rhs.line.line_no) {
759 if (lhs.line.line_no == rhs.line.line_no) {
760 if (lhs.pos > rhs.pos) {
767 public static bool operator==(Marker lhs, Marker rhs) {
768 if ((lhs.line.line_no == rhs.line.line_no) && (lhs.pos == rhs.pos)) {
774 public static bool operator!=(Marker lhs, Marker rhs) {
775 if ((lhs.line.line_no != rhs.line.line_no) || (lhs.pos != rhs.pos)) {
781 public void Combine(Line move_to_line, int move_to_line_length) {
783 pos += move_to_line_length;
784 tag = LineTag.FindTag(line, pos);
787 // This is for future use, right now Document.Split does it by hand, with some added shortcut logic
788 public void Split(Line move_to_line, int split_at) {
791 tag = LineTag.FindTag(line, pos);
794 public override bool Equals(object obj) {
795 return this==(Marker)obj;
798 public override int GetHashCode() {
799 return base.GetHashCode ();
802 public override string ToString() {
803 return "Marker Line " + line + ", Position " + pos;
807 #endregion Structures
809 #region Local Variables
810 private Line document;
812 private Line sentinel;
813 private int document_id;
814 private Random random = new Random();
815 internal string password_char;
816 private StringBuilder password_cache;
817 private bool calc_pass;
818 private int char_count;
820 // For calculating widths/heights
821 public static readonly StringFormat string_format = new StringFormat (StringFormat.GenericTypographic);
823 private int recalc_suspended;
824 private bool recalc_pending;
825 private int recalc_start = 1; // This starts at one, since lines are 1 based
826 private int recalc_end;
827 private bool recalc_optimize;
829 private int update_suspended;
830 private bool update_pending;
831 private int update_start = 1;
833 internal bool multiline;
834 internal HorizontalAlignment alignment;
837 internal UndoManager undo;
839 internal Marker caret;
840 internal Marker selection_start;
841 internal Marker selection_end;
842 internal bool selection_visible;
843 internal Marker selection_anchor;
844 internal Marker selection_prev;
845 internal bool selection_end_anchor;
847 internal int viewport_x;
848 internal int viewport_y; // The visible area of the document
849 internal int viewport_width;
850 internal int viewport_height;
852 internal int document_x; // Width of the document
853 internal int document_y; // Height of the document
855 internal Rectangle invalid;
857 internal int crlf_size; // 1 or 2, depending on whether we use \r\n or just \n
859 internal TextBoxBase owner; // Who's owning us?
860 static internal int caret_width = 1;
861 static internal int caret_shift = 1;
862 #endregion // Local Variables
865 internal Document (TextBoxBase owner)
874 recalc_pending = false;
876 // Tree related stuff
877 sentinel = new Line (this, LineEnding.None);
878 sentinel.color = LineColor.Black;
882 // We always have a blank line
883 owner.HandleCreated += new EventHandler(owner_HandleCreated);
884 owner.VisibleChanged += new EventHandler(owner_VisibleChanged);
886 Add (1, String.Empty, owner.Font, ThemeEngine.Current.ResPool.GetSolidBrush (owner.ForeColor), LineEnding.None);
888 undo = new UndoManager (this);
890 selection_visible = false;
891 selection_start.line = this.document;
892 selection_start.pos = 0;
893 selection_start.tag = selection_start.line.tags;
894 selection_end.line = this.document;
895 selection_end.pos = 0;
896 selection_end.tag = selection_end.line.tags;
897 selection_anchor.line = this.document;
898 selection_anchor.pos = 0;
899 selection_anchor.tag = selection_anchor.line.tags;
900 caret.line = this.document;
902 caret.tag = caret.line.tags;
909 // Default selection is empty
911 document_id = random.Next();
913 string_format.Trimming = StringTrimming.None;
914 string_format.FormatFlags = StringFormatFlags.DisplayFormatControl;
918 #region Internal Properties
935 internal Line CaretLine {
941 internal int CaretPosition {
947 internal Point Caret {
949 return new Point((int)caret.tag.line.widths[caret.pos] + caret.line.X, caret.line.Y);
953 internal LineTag CaretTag {
963 internal int CRLFSize {
973 internal string PasswordChar {
975 return password_char;
979 password_char = value;
980 PasswordCache.Length = 0;
981 if ((password_char.Length != 0) && (password_char[0] != '\0')) {
989 private StringBuilder PasswordCache {
991 if (password_cache == null)
992 password_cache = new StringBuilder();
993 return password_cache;
997 internal int ViewPortX {
1007 internal int Length {
1009 return char_count + lines - 1; // Add \n for each line but the last
1013 private int CharCount {
1021 if (LengthChanged != null) {
1022 LengthChanged(this, EventArgs.Empty);
1027 internal int ViewPortY {
1037 internal int ViewPortWidth {
1039 return viewport_width;
1043 viewport_width = value;
1047 internal int ViewPortHeight {
1049 return viewport_height;
1053 viewport_height = value;
1058 internal int Width {
1060 return this.document_x;
1064 internal int Height {
1066 return this.document_y;
1070 internal bool SelectionVisible {
1072 return selection_visible;
1076 internal bool Wrap {
1086 #endregion // Internal Properties
1088 #region Private Methods
1090 internal void SuspendRecalc ()
1095 internal void ResumeRecalc (bool immediate_update)
1097 if (recalc_suspended > 0)
1100 if (immediate_update && recalc_suspended == 0 && recalc_pending) {
1101 RecalculateDocument (owner.CreateGraphicsInternal(), recalc_start, recalc_end, recalc_optimize);
1102 recalc_pending = false;
1106 internal void SuspendUpdate ()
1111 internal void ResumeUpdate (bool immediate_update)
1113 if (update_suspended > 0)
1116 if (immediate_update && update_suspended == 0 && update_pending) {
1117 UpdateView (GetLine (update_start), 0);
1118 update_pending = false;
1123 internal int DumpTree(Line line, bool with_tags) {
1128 Console.Write("Line {0} [# {1}], Y: {2}, ending style: {3}, Text: '{4}'",
1129 line.line_no, line.GetHashCode(), line.Y, line.ending,
1130 line.text != null ? line.text.ToString() : "undefined");
1132 if (line.left == sentinel) {
1133 Console.Write(", left = sentinel");
1134 } else if (line.left == null) {
1135 Console.Write(", left = NULL");
1138 if (line.right == sentinel) {
1139 Console.Write(", right = sentinel");
1140 } else if (line.right == null) {
1141 Console.Write(", right = NULL");
1144 Console.WriteLine("");
1154 Console.Write(" Tags: ");
1155 while (tag != null) {
1156 Console.Write("{0} <{1}>-<{2}>", count++, tag.start, tag.end
1157 /*line.text.ToString (tag.start - 1, tag.length)*/);
1158 length += tag.length;
1160 if (tag.line != line) {
1161 Console.Write("BAD line link");
1162 throw new Exception("Bad line link in tree");
1166 Console.Write(", ");
1169 if (length > line.text.Length) {
1170 throw new Exception(String.Format("Length of tags more than length of text on line (expected {0} calculated {1})", line.text.Length, length));
1171 } else if (length < line.text.Length) {
1172 throw new Exception(String.Format("Length of tags less than length of text on line (expected {0} calculated {1})", line.text.Length, length));
1174 Console.WriteLine("");
1176 if (line.left != null) {
1177 if (line.left != sentinel) {
1178 total += DumpTree(line.left, with_tags);
1181 if (line != sentinel) {
1182 throw new Exception("Left should not be NULL");
1186 if (line.right != null) {
1187 if (line.right != sentinel) {
1188 total += DumpTree(line.right, with_tags);
1191 if (line != sentinel) {
1192 throw new Exception("Right should not be NULL");
1196 for (int i = 1; i <= this.lines; i++) {
1197 if (GetLine(i) == null) {
1198 throw new Exception(String.Format("Hole in line order, missing {0}", i));
1202 if (line == this.Root) {
1203 if (total < this.lines) {
1204 throw new Exception(String.Format("Not enough nodes in tree, found {0}, expected {1}", total, this.lines));
1205 } else if (total > this.lines) {
1206 throw new Exception(String.Format("Too many nodes in tree, found {0}, expected {1}", total, this.lines));
1213 private void SetSelectionVisible (bool value)
1215 selection_visible = value;
1217 // cursor and selection are enemies, we can't have both in the same room at the same time
1218 if (owner.IsHandleCreated && !owner.show_caret_w_selection)
1219 XplatUI.CaretVisible (owner.Handle, !selection_visible);
1222 private void DecrementLines(int line_no) {
1226 while (current <= lines) {
1227 GetLine(current).line_no--;
1233 private void IncrementLines(int line_no) {
1236 current = this.lines;
1237 while (current >= line_no) {
1238 GetLine(current).line_no++;
1244 private void RebalanceAfterAdd(Line line1) {
1247 while ((line1 != document) && (line1.parent.color == LineColor.Red)) {
1248 if (line1.parent == line1.parent.parent.left) {
1249 line2 = line1.parent.parent.right;
1251 if ((line2 != null) && (line2.color == LineColor.Red)) {
1252 line1.parent.color = LineColor.Black;
1253 line2.color = LineColor.Black;
1254 line1.parent.parent.color = LineColor.Red;
1255 line1 = line1.parent.parent;
1257 if (line1 == line1.parent.right) {
1258 line1 = line1.parent;
1262 line1.parent.color = LineColor.Black;
1263 line1.parent.parent.color = LineColor.Red;
1265 RotateRight(line1.parent.parent);
1268 line2 = line1.parent.parent.left;
1270 if ((line2 != null) && (line2.color == LineColor.Red)) {
1271 line1.parent.color = LineColor.Black;
1272 line2.color = LineColor.Black;
1273 line1.parent.parent.color = LineColor.Red;
1274 line1 = line1.parent.parent;
1276 if (line1 == line1.parent.left) {
1277 line1 = line1.parent;
1281 line1.parent.color = LineColor.Black;
1282 line1.parent.parent.color = LineColor.Red;
1283 RotateLeft(line1.parent.parent);
1287 document.color = LineColor.Black;
1290 private void RebalanceAfterDelete(Line line1) {
1293 while ((line1 != document) && (line1.color == LineColor.Black)) {
1294 if (line1 == line1.parent.left) {
1295 line2 = line1.parent.right;
1296 if (line2.color == LineColor.Red) {
1297 line2.color = LineColor.Black;
1298 line1.parent.color = LineColor.Red;
1299 RotateLeft(line1.parent);
1300 line2 = line1.parent.right;
1302 if ((line2.left.color == LineColor.Black) && (line2.right.color == LineColor.Black)) {
1303 line2.color = LineColor.Red;
1304 line1 = line1.parent;
1306 if (line2.right.color == LineColor.Black) {
1307 line2.left.color = LineColor.Black;
1308 line2.color = LineColor.Red;
1310 line2 = line1.parent.right;
1312 line2.color = line1.parent.color;
1313 line1.parent.color = LineColor.Black;
1314 line2.right.color = LineColor.Black;
1315 RotateLeft(line1.parent);
1319 line2 = line1.parent.left;
1320 if (line2.color == LineColor.Red) {
1321 line2.color = LineColor.Black;
1322 line1.parent.color = LineColor.Red;
1323 RotateRight(line1.parent);
1324 line2 = line1.parent.left;
1326 if ((line2.right.color == LineColor.Black) && (line2.left.color == LineColor.Black)) {
1327 line2.color = LineColor.Red;
1328 line1 = line1.parent;
1330 if (line2.left.color == LineColor.Black) {
1331 line2.right.color = LineColor.Black;
1332 line2.color = LineColor.Red;
1334 line2 = line1.parent.left;
1336 line2.color = line1.parent.color;
1337 line1.parent.color = LineColor.Black;
1338 line2.left.color = LineColor.Black;
1339 RotateRight(line1.parent);
1344 line1.color = LineColor.Black;
1347 private void RotateLeft(Line line1) {
1348 Line line2 = line1.right;
1350 line1.right = line2.left;
1352 if (line2.left != sentinel) {
1353 line2.left.parent = line1;
1356 if (line2 != sentinel) {
1357 line2.parent = line1.parent;
1360 if (line1.parent != null) {
1361 if (line1 == line1.parent.left) {
1362 line1.parent.left = line2;
1364 line1.parent.right = line2;
1371 if (line1 != sentinel) {
1372 line1.parent = line2;
1376 private void RotateRight(Line line1) {
1377 Line line2 = line1.left;
1379 line1.left = line2.right;
1381 if (line2.right != sentinel) {
1382 line2.right.parent = line1;
1385 if (line2 != sentinel) {
1386 line2.parent = line1.parent;
1389 if (line1.parent != null) {
1390 if (line1 == line1.parent.right) {
1391 line1.parent.right = line2;
1393 line1.parent.left = line2;
1399 line2.right = line1;
1400 if (line1 != sentinel) {
1401 line1.parent = line2;
1406 internal void UpdateView(Line line, int pos) {
1407 if (!owner.IsHandleCreated) {
1411 if (update_suspended > 0) {
1412 update_start = Math.Min (update_start, line.line_no);
1413 // update_end = Math.Max (update_end, line.line_no);
1414 // recalc_optimize = true;
1415 update_pending = true;
1419 // Optimize invalidation based on Line alignment
1420 if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no, true)) {
1421 // Lineheight changed, invalidate the rest of the document
1422 if ((line.Y - viewport_y) >=0 ) {
1423 // We formatted something that's in view, only draw parts of the screen
1424 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, owner.Height - line.Y - viewport_y));
1426 // The tag was above the visible area, draw everything
1430 switch(line.alignment) {
1431 case HorizontalAlignment.Left: {
1432 owner.Invalidate(new Rectangle(line.X + (int)line.widths[pos] - viewport_x - 1, line.Y - viewport_y, viewport_width, line.height + 1));
1436 case HorizontalAlignment.Center: {
1437 owner.Invalidate(new Rectangle(line.X, line.Y - viewport_y, viewport_width, line.height + 1));
1441 case HorizontalAlignment.Right: {
1442 owner.Invalidate(new Rectangle(line.X, line.Y - viewport_y, (int)line.widths[pos + 1] - viewport_x + line.X, line.height + 1));
1450 // Update display from line, down line_count lines; pos is unused, but required for the signature
1451 internal void UpdateView(Line line, int line_count, int pos) {
1452 if (!owner.IsHandleCreated) {
1456 if (recalc_suspended > 0) {
1457 recalc_start = Math.Min (recalc_start, line.line_no);
1458 recalc_end = Math.Max (recalc_end, line.line_no + line_count - 1);
1459 recalc_optimize = true;
1460 recalc_pending = true;
1464 if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no + line_count - 1, true)) {
1465 // Lineheight changed, invalidate the rest of the document
1466 if ((line.Y - viewport_y) >=0 ) {
1467 // We formatted something that's in view, only draw parts of the screen
1468 //blah Console.WriteLine("TextControl.cs(981) Invalidate called in UpdateView(line, line_count, pos)");
1469 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, owner.Height - line.Y - viewport_y));
1471 // The tag was above the visible area, draw everything
1472 //blah Console.WriteLine("TextControl.cs(985) Invalidate called in UpdateView(line, line_count, pos)");
1478 end_line = GetLine(line.line_no + line_count -1);
1479 if (end_line == null) {
1483 //blah Console.WriteLine("TextControl.cs(996) Invalidate called in UpdateView(line, line_count, pos)");
1484 owner.Invalidate(new Rectangle(0 - viewport_x, line.Y - viewport_y, (int)line.widths[line.text.Length], end_line.Y + end_line.height));
1487 #endregion // Private Methods
1489 #region Internal Methods
1490 // Clear the document and reset state
1491 internal void Empty() {
1493 document = sentinel;
1496 // We always have a blank line
1497 Add (1, String.Empty, owner.Font, ThemeEngine.Current.ResPool.GetSolidBrush (owner.ForeColor), LineEnding.None);
1499 this.RecalculateDocument(owner.CreateGraphicsInternal());
1500 PositionCaret(0, 0);
1502 SetSelectionVisible (false);
1504 selection_start.line = this.document;
1505 selection_start.pos = 0;
1506 selection_start.tag = selection_start.line.tags;
1507 selection_end.line = this.document;
1508 selection_end.pos = 0;
1509 selection_end.tag = selection_end.line.tags;
1518 if (owner.IsHandleCreated)
1519 owner.Invalidate ();
1522 internal void PositionCaret(Line line, int pos) {
1523 caret.tag = line.FindTag (pos);
1525 MoveCaretToTextTag ();
1530 if (owner.IsHandleCreated) {
1531 if (owner.Focused) {
1532 if (caret.height != caret.tag.height)
1533 XplatUI.CreateCaret (owner.Handle, caret_width, caret.height);
1534 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.X - viewport_x, caret.line.Y + caret.tag.shift - viewport_y + caret_shift);
1537 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1540 // We set this at the end because we use the heights to determine whether or
1541 // not we need to recreate the caret
1542 caret.height = caret.tag.height;
1546 internal void PositionCaret(int x, int y) {
1547 if (!owner.IsHandleCreated) {
1551 caret.tag = FindCursor(x, y, out caret.pos);
1553 MoveCaretToTextTag ();
1555 caret.line = caret.tag.line;
1556 caret.height = caret.tag.height;
1558 if (owner.ShowSelection && (!selection_visible || owner.show_caret_w_selection)) {
1559 XplatUI.CreateCaret (owner.Handle, caret_width, caret.height);
1560 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.X - viewport_x, caret.line.Y + caret.tag.shift - viewport_y + caret_shift);
1563 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1566 internal void CaretHasFocus() {
1567 if ((caret.tag != null) && owner.IsHandleCreated) {
1568 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1569 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.X - viewport_x, caret.line.Y + caret.tag.shift - viewport_y + caret_shift);
1574 if (owner.IsHandleCreated && SelectionLength () > 0) {
1575 InvalidateSelectionArea ();
1579 internal void CaretLostFocus() {
1580 if (!owner.IsHandleCreated) {
1583 XplatUI.DestroyCaret(owner.Handle);
1586 internal void AlignCaret() {
1587 if (!owner.IsHandleCreated) {
1591 caret.tag = LineTag.FindTag (caret.line, caret.pos);
1593 MoveCaretToTextTag ();
1595 caret.height = caret.tag.height;
1597 if (owner.Focused) {
1598 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1599 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.X - viewport_x, caret.line.Y + caret.tag.shift - viewport_y + caret_shift);
1603 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1606 internal void UpdateCaret() {
1607 if (!owner.IsHandleCreated || caret.tag == null) {
1611 MoveCaretToTextTag ();
1613 if (caret.tag.height != caret.height) {
1614 caret.height = caret.tag.height;
1615 if (owner.Focused) {
1616 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1620 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.X - viewport_x, caret.line.Y + caret.tag.shift - viewport_y + caret_shift);
1624 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1627 internal void DisplayCaret() {
1628 if (!owner.IsHandleCreated) {
1632 if (owner.ShowSelection && (!selection_visible || owner.show_caret_w_selection)) {
1633 XplatUI.CaretVisible(owner.Handle, true);
1637 internal void HideCaret() {
1638 if (!owner.IsHandleCreated) {
1642 if (owner.Focused) {
1643 XplatUI.CaretVisible(owner.Handle, false);
1648 internal void MoveCaretToTextTag ()
1650 if (caret.tag == null || caret.tag.IsTextTag)
1655 if (caret.pos < caret.tag.start) {
1656 caret.tag = caret.tag.previous;
1658 caret.tag = caret.tag.next;
1662 internal void MoveCaret(CaretDirection direction) {
1663 // FIXME should we use IsWordSeparator to detect whitespace, instead
1664 // of looking for actual spaces in the Word move cases?
1666 bool nowrap = false;
1668 case CaretDirection.CharForwardNoWrap:
1670 goto case CaretDirection.CharForward;
1671 case CaretDirection.CharForward: {
1673 if (caret.pos > caret.line.text.Length) {
1675 // Go into next line
1676 if (caret.line.line_no < this.lines) {
1677 caret.line = GetLine(caret.line.line_no+1);
1679 caret.tag = caret.line.tags;
1684 // Single line; we stay where we are
1688 if ((caret.tag.start - 1 + caret.tag.length) < caret.pos) {
1689 caret.tag = caret.tag.next;
1696 case CaretDirection.CharBackNoWrap:
1698 goto case CaretDirection.CharBack;
1699 case CaretDirection.CharBack: {
1700 if (caret.pos > 0) {
1701 // caret.pos--; // folded into the if below
1703 if (--caret.pos > 0) {
1704 if (caret.tag.start > caret.pos) {
1705 caret.tag = caret.tag.previous;
1709 if (caret.line.line_no > 1 && !nowrap) {
1710 caret.line = GetLine(caret.line.line_no - 1);
1711 caret.pos = caret.line.text.Length;
1712 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1719 case CaretDirection.WordForward: {
1722 len = caret.line.text.Length;
1723 if (caret.pos < len) {
1724 while ((caret.pos < len) && (caret.line.text[caret.pos] != ' ')) {
1727 if (caret.pos < len) {
1728 // Skip any whitespace
1729 while ((caret.pos < len) && (caret.line.text[caret.pos] == ' ')) {
1733 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1735 if (caret.line.line_no < this.lines) {
1736 caret.line = GetLine(caret.line.line_no + 1);
1738 caret.tag = caret.line.tags;
1745 case CaretDirection.WordBack: {
1746 if (caret.pos > 0) {
1749 while ((caret.pos > 0) && (caret.line.text[caret.pos] == ' ')) {
1753 while ((caret.pos > 0) && (caret.line.text[caret.pos] != ' ')) {
1757 if (caret.line.text.ToString(caret.pos, 1) == " ") {
1758 if (caret.pos != 0) {
1761 caret.line = GetLine(caret.line.line_no - 1);
1762 caret.pos = caret.line.text.Length;
1765 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1767 if (caret.line.line_no > 1) {
1768 caret.line = GetLine(caret.line.line_no - 1);
1769 caret.pos = caret.line.text.Length;
1770 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1777 case CaretDirection.LineUp: {
1778 if (caret.line.line_no > 1) {
1781 pixel = (int)caret.line.widths[caret.pos];
1782 PositionCaret(pixel, GetLine(caret.line.line_no - 1).Y);
1789 case CaretDirection.LineDown: {
1790 if (caret.line.line_no < lines) {
1793 pixel = (int)caret.line.widths[caret.pos];
1794 PositionCaret(pixel, GetLine(caret.line.line_no + 1).Y);
1801 case CaretDirection.Home: {
1802 if (caret.pos > 0) {
1804 caret.tag = caret.line.tags;
1810 case CaretDirection.End: {
1811 if (caret.pos < caret.line.text.Length) {
1812 caret.pos = caret.line.text.Length;
1813 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1819 case CaretDirection.PgUp: {
1821 int new_y, y_offset;
1823 if (viewport_y == 0) {
1825 // This should probably be handled elsewhere
1826 if (!(owner is RichTextBox)) {
1827 // Page down doesn't do anything in a regular TextBox
1828 // if the bottom of the document
1829 // is already visible, the page and the caret stay still
1833 // We're just placing the caret at the end of the document, no scrolling needed
1834 owner.vscroll.Value = 0;
1835 Line line = GetLine (1);
1836 PositionCaret (line, 0);
1839 y_offset = caret.line.Y - viewport_y;
1840 new_y = caret.line.Y - viewport_height;
1842 owner.vscroll.Value = Math.Max (new_y, 0);
1843 PositionCaret ((int)caret.line.widths[caret.pos], y_offset + viewport_y);
1847 case CaretDirection.PgDn: {
1848 int new_y, y_offset;
1850 if ((viewport_y + viewport_height) > document_y) {
1852 // This should probably be handled elsewhere
1853 if (!(owner is RichTextBox)) {
1854 // Page up doesn't do anything in a regular TextBox
1855 // if the bottom of the document
1856 // is already visible, the page and the caret stay still
1860 // We're just placing the caret at the end of the document, no scrolling needed
1861 owner.vscroll.Value = owner.vscroll.Maximum - viewport_height + 1;
1862 Line line = GetLine (lines);
1863 PositionCaret (line, line.Text.Length);
1866 y_offset = caret.line.Y - viewport_y;
1867 new_y = caret.line.Y + viewport_height;
1869 owner.vscroll.Value = Math.Min (new_y, owner.vscroll.Maximum - viewport_height + 1);
1870 PositionCaret ((int)caret.line.widths[caret.pos], y_offset + viewport_y);
1875 case CaretDirection.CtrlPgUp: {
1876 PositionCaret(0, viewport_y);
1881 case CaretDirection.CtrlPgDn: {
1886 tag = FindTag(0, viewport_y + viewport_height, out index, false);
1887 if (tag.line.line_no > 1) {
1888 line = GetLine(tag.line.line_no - 1);
1892 PositionCaret(line, line.Text.Length);
1897 case CaretDirection.CtrlHome: {
1898 caret.line = GetLine(1);
1900 caret.tag = caret.line.tags;
1906 case CaretDirection.CtrlEnd: {
1907 caret.line = GetLine(lines);
1908 caret.pos = caret.line.text.Length;
1909 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1915 case CaretDirection.SelectionStart: {
1916 caret.line = selection_start.line;
1917 caret.pos = selection_start.pos;
1918 caret.tag = selection_start.tag;
1924 case CaretDirection.SelectionEnd: {
1925 caret.line = selection_end.line;
1926 caret.pos = selection_end.pos;
1927 caret.tag = selection_end.tag;
1935 internal void DumpDoc ()
1937 Console.WriteLine ("<doc lines='{0}'>", lines);
1938 for (int i = 1; i <= lines ; i++) {
1939 Line line = GetLine (i);
1940 Console.WriteLine ("<line no='{0}' ending='{1}'>", line.line_no, line.ending);
1942 LineTag tag = line.tags;
1943 while (tag != null) {
1944 Console.Write ("\t<tag type='{0}' span='{1}->{2}' font='{3}' color='{4}'>",
1945 tag.GetType (), tag.start, tag.length, tag.font, tag.color.Color);
1946 Console.Write (tag.Text ());
1947 Console.WriteLine ("</tag>");
1950 Console.WriteLine ("</line>");
1952 Console.WriteLine ("</doc>");
1955 internal void Draw (Graphics g, Rectangle clip)
1957 Line line; // Current line being drawn
1958 LineTag tag; // Current tag being drawn
1959 int start; // First line to draw
1960 int end; // Last line to draw
1961 StringBuilder text; // String representing the current line
1964 Brush current_brush;
1965 Brush disabled_brush;
1966 Brush readonly_brush;
1970 // First, figure out from what line to what line we need to draw
1973 start = GetLineByPixel(clip.Top + viewport_y, false).line_no;
1974 end = GetLineByPixel(clip.Bottom + viewport_y, false).line_no;
1976 start = GetLineByPixel(clip.Left + viewport_x, false).line_no;
1977 end = GetLineByPixel(clip.Right + viewport_x, false).line_no;
1981 /// We draw the single border ourself
1983 if (owner.actual_border_style == BorderStyle.FixedSingle) {
1984 ControlPaint.DrawBorder (g, owner.Bounds, Color.Black, ButtonBorderStyle.Solid);
1987 /// Make sure that we aren't drawing one more line then we need to
1988 line = GetLine (end - 1);
1989 if (line != null && clip.Bottom == line.Y + line.height + viewport_y)
1995 DateTime n = DateTime.Now;
1996 Console.WriteLine ("Started drawing: {0}s {1}ms", n.Second, n.Millisecond);
1997 Console.WriteLine ("CLIP: {0}", clip);
1998 Console.WriteLine ("S: {0}", GetLine (start).text);
1999 Console.WriteLine ("E: {0}", GetLine (end).text);
2002 disabled_brush = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorGrayText);
2003 readonly_brush = ThemeEngine.Current.ResPool.GetSolidBrush (ThemeEngine.Current.ColorControlText);
2004 hilight = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorHighlight);
2005 hilight_text = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorHighlightText);
2007 // Non multiline selection can be handled outside of the loop
2008 if (!multiline && selection_visible && owner.ShowSelection) {
2009 g.FillRectangle (hilight,
2010 selection_start.line.widths [selection_start.pos] +
2011 selection_start.line.X - viewport_x,
2012 selection_start.line.Y,
2013 (selection_end.line.X + selection_end.line.widths [selection_end.pos]) -
2014 (selection_start.line.X + selection_start.line.widths [selection_start.pos]),
2015 selection_start.line.height);
2018 while (line_no <= end) {
2019 line = GetLine (line_no);
2020 float line_y = line.Y - viewport_y;
2026 if (PasswordCache.Length < line.text.Length)
2027 PasswordCache.Append(Char.Parse(password_char), line.text.Length - PasswordCache.Length);
2028 else if (PasswordCache.Length > line.text.Length)
2029 PasswordCache.Remove(line.text.Length, PasswordCache.Length - line.text.Length);
2030 text = PasswordCache;
2033 int line_selection_start = text.Length + 1;
2034 int line_selection_end = text.Length + 1;
2035 if (selection_visible && owner.ShowSelection &&
2036 (line_no >= selection_start.line.line_no) &&
2037 (line_no <= selection_end.line.line_no)) {
2039 if (line_no == selection_start.line.line_no)
2040 line_selection_start = selection_start.pos + 1;
2042 line_selection_start = 1;
2044 if (line_no == selection_end.line.line_no)
2045 line_selection_end = selection_end.pos + 1;
2047 line_selection_end = text.Length + 1;
2049 if (line_selection_end == line_selection_start) {
2050 // There isn't really selection
2051 line_selection_start = text.Length + 1;
2052 line_selection_end = line_selection_start;
2053 } else if (multiline) {
2054 // lets draw some selection baby!! (non multiline selection is drawn outside the loop)
2055 g.FillRectangle (hilight,
2056 line.widths [line_selection_start - 1] + line.X - viewport_x,
2057 line_y, line.widths [line_selection_end - 1] - line.widths [line_selection_start - 1],
2062 current_brush = line.tags.color;
2063 while (tag != null) {
2066 if (tag.length == 0) {
2071 if (((tag.X + tag.width) < (clip.Left - viewport_x)) && (tag.X > (clip.Right - viewport_x))) {
2076 if (tag.back_color != null) {
2077 g.FillRectangle (tag.back_color, tag.X + line.X - viewport_x,
2078 line_y + tag.shift, tag.width, line.height);
2081 tag_brush = tag.color;
2082 current_brush = tag_brush;
2084 if (!owner.is_enabled) {
2085 Color a = ((SolidBrush) tag.color).Color;
2086 Color b = ThemeEngine.Current.ColorWindowText;
2088 if ((a.R == b.R) && (a.G == b.G) && (a.B == b.B)) {
2089 tag_brush = disabled_brush;
2091 } else if (owner.read_only && !owner.backcolor_set) {
2092 tag_brush = readonly_brush;
2095 int tag_pos = tag.start;
2096 current_brush = tag_brush;
2097 while (tag_pos < tag.start + tag.length) {
2098 int old_tag_pos = tag_pos;
2100 if (tag_pos >= line_selection_start && tag_pos < line_selection_end) {
2101 current_brush = hilight_text;
2102 tag_pos = Math.Min (tag.end, line_selection_end);
2103 } else if (tag_pos < line_selection_start) {
2104 current_brush = tag_brush;
2105 tag_pos = Math.Min (tag.end, line_selection_start);
2107 current_brush = tag_brush;
2111 tag.Draw (g, current_brush,
2112 line.widths [Math.Max (0, old_tag_pos - 1)] + line.X - viewport_x,
2114 old_tag_pos - 1, Math.Min (tag.length, tag_pos - old_tag_pos),
2120 line.DrawEnding (g, line_y);
2125 internal int GetLineEnding (string line, int start, out LineEnding ending)
2129 res = line.IndexOf ('\r', start);
2131 if (res + 2 < line.Length && line [res + 1] == '\r' && line [res + 2] == '\n') {
2132 ending = LineEnding.Soft;
2135 if (res + 1 < line.Length && line [res + 1] == '\n') {
2136 ending = LineEnding.Hard;
2139 ending = LineEnding.Limp;
2143 res = line.IndexOf ('\n', start);
2145 ending = LineEnding.Rich;
2149 ending = LineEnding.Wrap;
2153 internal int LineEndingLength (LineEnding ending)
2158 case LineEnding.Limp:
2159 case LineEnding.Rich:
2162 case LineEnding.Hard:
2165 case LineEnding.Soft:
2173 internal string LineEndingToString (LineEnding ending)
2175 string res = String.Empty;
2177 case LineEnding.Limp:
2180 case LineEnding.Hard:
2183 case LineEnding.Soft:
2186 case LineEnding.Rich:
2194 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
2195 internal void Insert(Line line, int pos, bool update_caret, string s) {
2201 LineTag tag = LineTag.FindTag (line, pos);
2205 base_line = line.line_no;
2206 old_line_count = lines;
2208 break_index = GetLineEnding (s, 0, out ending);
2210 // Bump the text at insertion point a line down if we're inserting more than one line
2211 if (break_index != s.Length) {
2213 line.ending = ending;
2214 // Remainder of start line is now in base_line + 1
2217 InsertString (line, pos, s.Substring (0, break_index + LineEndingLength (ending)));
2219 break_index += LineEndingLength (ending);
2220 while (break_index < s.Length) {
2221 int next_break = GetLineEnding (s, break_index, out ending);
2222 string line_text = s.Substring (break_index, next_break - break_index +
2223 LineEndingLength (ending));
2225 Add (base_line + count, line_text, line.alignment, tag.font, tag.color, ending);
2227 Line last = GetLine (base_line + count);
2228 last.ending = ending;
2231 break_index = next_break + LineEndingLength (ending);
2234 ResumeRecalc (true);
2236 UpdateView(line, lines - old_line_count + 1, pos);
2239 // Move caret to the end of the inserted text
2240 Line l = GetLine (line.line_no + lines - old_line_count);
2241 PositionCaret(l, l.text.Length);
2246 // Inserts a character at the given position
2247 internal void InsertString(Line line, int pos, string s) {
2248 InsertString(line.FindTag(pos), pos, s);
2251 // Inserts a string at the given position
2252 internal void InsertString(LineTag tag, int pos, string s) {
2261 line.text.Insert(pos, s);
2264 while (tag != null) {
2271 UpdateView(line, pos);
2274 // Inserts a string at the caret position
2275 internal void InsertStringAtCaret(string s, bool move_caret) {
2277 InsertString (caret.tag, caret.pos, s);
2279 UpdateView(caret.line, caret.pos);
2281 caret.pos += s.Length;
2288 // Inserts a character at the given position
2289 internal void InsertChar(Line line, int pos, char ch) {
2290 InsertChar(line.FindTag(pos), pos, ch);
2293 // Inserts a character at the given position
2294 internal void InsertChar(LineTag tag, int pos, char ch) {
2300 line.text.Insert(pos, ch);
2303 while (tag != null) {
2310 undo.RecordTyping (line, pos, ch);
2311 UpdateView(line, pos);
2314 // Inserts a character at the current caret position
2315 internal void InsertCharAtCaret(char ch, bool move_caret) {
2321 caret.line.text.Insert(caret.pos, ch);
2324 if (caret.tag.next != null) {
2325 tag = caret.tag.next;
2326 while (tag != null) {
2332 caret.line.recalc = true;
2334 InsertChar (caret.tag, caret.pos, ch);
2336 UpdateView(caret.line, caret.pos);
2340 SetSelectionToCaret(true);
2345 internal void InsertPicture (Line line, int pos, RTF.Picture picture)
2353 // Just a place holder basically
2354 line.text.Insert (pos, "I");
2356 PictureTag picture_tag = new PictureTag (line, pos + 1, picture);
2358 tag = LineTag.FindTag (line, pos);
2359 picture_tag.CopyFormattingFrom (tag);
2360 next_tag = tag.Break (pos + 1);
2361 picture_tag.previous = tag;
2362 picture_tag.next = tag.next;
2363 tag.next = picture_tag;
2366 // Picture tags need to be surrounded by text tags
2368 if (picture_tag.next == null) {
2369 picture_tag.next = new LineTag (line, pos + 1);
2370 picture_tag.next.CopyFormattingFrom (tag);
2371 picture_tag.next.previous = picture_tag;
2374 tag = picture_tag.next;
2375 while (tag != null) {
2383 UpdateView (line, pos);
2386 internal void DeleteMultiline (Line start_line, int pos, int length)
2388 Marker start = new Marker ();
2389 Marker end = new Marker ();
2390 int start_index = LineTagToCharIndex (start_line, pos);
2392 start.line = start_line;
2394 start.tag = LineTag.FindTag (start_line, pos);
2396 CharIndexToLineTag (start_index + length, out end.line,
2397 out end.tag, out end.pos);
2401 if (start.line == end.line) {
2402 DeleteChars (start.tag, pos, end.pos - pos);
2405 // Delete first and last lines
2406 DeleteChars (start.tag, start.pos, start.line.text.Length - start.pos);
2407 DeleteChars (end.line.tags, 0, end.pos);
2409 int current = start.line.line_no + 1;
2410 if (current < end.line.line_no) {
2411 for (int i = end.line.line_no - 1; i >= current; i--) {
2416 // BIG FAT WARNING - selection_end.line might be stale due
2417 // to the above Delete() call. DONT USE IT before hitting the end of this method!
2419 // Join start and end
2420 Combine (start.line.line_no, current);
2423 ResumeUpdate (true);
2427 // Deletes n characters at the given position; it will not delete past line limits
2429 internal void DeleteChars(LineTag tag, int pos, int count) {
2438 if (pos == line.text.Length) {
2442 line.text.Remove(pos, count);
2444 // Make sure the tag points to the right spot
2445 while ((tag != null) && (tag.end) < pos) {
2453 // Check if we're crossing tag boundaries
2454 if ((pos + count) > (tag.start + tag.length - 1)) {
2457 // We have to delete cross tag boundaries
2461 left -= tag.start + tag.length - pos - 1;
2464 while ((tag != null) && (left > 0)) {
2465 tag.start -= count - left;
2467 if (tag.length > left) {
2476 // We got off easy, same tag
2478 if (tag.length == 0) {
2483 // Delete empty orphaned tags at the end
2485 while (walk != null && walk.next != null && walk.next.length == 0) {
2487 walk.next = walk.next.next;
2488 if (walk.next != null)
2489 walk.next.previous = t;
2493 // Adjust the start point of any tags following
2496 while (tag != null) {
2504 line.Streamline(lines);
2508 if (pos >= line.TextLengthWithoutEnding ()) {
2509 LineEnding ending = line.ending;
2510 GetLineEnding (line.text.ToString (), 0, out ending);
2511 if (ending != line.ending) {
2512 line.ending = ending;
2515 UpdateView (line, lines, pos);
2516 owner.Invalidate ();
2522 UpdateView (line, lines, pos);
2523 owner.Invalidate ();
2525 UpdateView(line, pos);
2528 // Deletes a character at or after the given position (depending on forward); it will not delete past line limits
2529 internal void DeleteChar(LineTag tag, int pos, bool forward) {
2538 if ((pos == 0 && forward == false) || (pos == line.text.Length && forward == true)) {
2544 line.text.Remove(pos, 1);
2546 while ((tag != null) && (tag.start + tag.length - 1) <= pos) {
2556 if (tag.length == 0) {
2561 line.text.Remove(pos, 1);
2562 if (pos >= (tag.start - 1)) {
2564 if (tag.length == 0) {
2567 } else if (tag.previous != null) {
2568 // tag.previous.length--;
2569 if (tag.previous.length == 0) {
2575 // Delete empty orphaned tags at the end
2577 while (walk != null && walk.next != null && walk.next.length == 0) {
2579 walk.next = walk.next.next;
2580 if (walk.next != null)
2581 walk.next.previous = t;
2586 while (tag != null) {
2592 line.Streamline(lines);
2596 if (pos >= line.TextLengthWithoutEnding ()) {
2597 LineEnding ending = line.ending;
2598 GetLineEnding (line.text.ToString (), 0, out ending);
2599 if (ending != line.ending) {
2600 line.ending = ending;
2603 UpdateView (line, lines, pos);
2604 owner.Invalidate ();
2610 UpdateView (line, lines, pos);
2611 owner.Invalidate ();
2613 UpdateView(line, pos);
2616 // Combine two lines
2617 internal void Combine(int FirstLine, int SecondLine) {
2618 Combine(GetLine(FirstLine), GetLine(SecondLine));
2621 internal void Combine(Line first, Line second) {
2625 // Combine the two tag chains into one
2628 // Maintain the line ending style
2629 first.ending = second.ending;
2631 while (last.next != null) {
2635 // need to get the shift before setting the next tag since that effects length
2636 shift = last.start + last.length - 1;
2637 last.next = second.tags;
2638 last.next.previous = last;
2640 // Fix up references within the chain
2642 while (last != null) {
2644 last.start += shift;
2648 // Combine both lines' strings
2649 first.text.Insert(first.text.Length, second.text.ToString());
2650 first.Grow(first.text.Length);
2652 // Remove the reference to our (now combined) tags from the doomed line
2656 DecrementLines(first.line_no + 2); // first.line_no + 1 will be deleted, so we need to start renumbering one later
2659 first.recalc = true;
2660 first.height = 0; // This forces RecalcDocument/UpdateView to redraw from this line on
2661 first.Streamline(lines);
2663 // Update Caret, Selection, etc
2664 if (caret.line == second) {
2665 caret.Combine(first, shift);
2667 if (selection_anchor.line == second) {
2668 selection_anchor.Combine(first, shift);
2670 if (selection_start.line == second) {
2671 selection_start.Combine(first, shift);
2673 if (selection_end.line == second) {
2674 selection_end.Combine(first, shift);
2681 check_first = GetLine(first.line_no);
2682 check_second = GetLine(check_first.line_no + 1);
2684 Console.WriteLine("Pre-delete: Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2687 this.Delete(second);
2690 check_first = GetLine(first.line_no);
2691 check_second = GetLine(check_first.line_no + 1);
2693 Console.WriteLine("Post-delete Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2697 // Split the line at the position into two
2698 internal void Split(int LineNo, int pos) {
2702 line = GetLine(LineNo);
2703 tag = LineTag.FindTag(line, pos);
2704 Split(line, tag, pos);
2707 internal void Split(Line line, int pos) {
2710 tag = LineTag.FindTag(line, pos);
2711 Split(line, tag, pos);
2714 ///<summary>Split line at given tag and position into two lines</summary>
2715 ///if more space becomes available on previous line</param>
2716 internal void Split(Line line, LineTag tag, int pos) {
2720 bool move_sel_start;
2724 move_sel_start = false;
2725 move_sel_end = false;
2727 // Adjust selection and cursors
2728 if (caret.line == line && caret.pos >= pos) {
2731 if (selection_start.line == line && selection_start.pos > pos) {
2732 move_sel_start = true;
2735 if (selection_end.line == line && selection_end.pos > pos) {
2736 move_sel_end = true;
2739 // cover the easy case first
2740 if (pos == line.text.Length) {
2741 Add (line.line_no + 1, String.Empty, line.alignment, tag.font, tag.color, line.ending);
2743 new_line = GetLine (line.line_no + 1);
2746 caret.line = new_line;
2747 caret.tag = new_line.tags;
2751 if (move_sel_start) {
2752 selection_start.line = new_line;
2753 selection_start.pos = 0;
2754 selection_start.tag = new_line.tags;
2758 selection_end.line = new_line;
2759 selection_end.pos = 0;
2760 selection_end.tag = new_line.tags;
2765 // We need to move the rest of the text into the new line
2766 Add (line.line_no + 1, line.text.ToString (pos, line.text.Length - pos), line.alignment, tag.font, tag.color, line.ending);
2768 // Now transfer our tags from this line to the next
2769 new_line = GetLine(line.line_no + 1);
2772 new_line.recalc = true;
2774 if ((tag.start - 1) == pos) {
2777 // We can simply break the chain and move the tag into the next line
2778 if (tag == line.tags) {
2779 new_tag = new LineTag(line, 1);
2780 new_tag.CopyFormattingFrom (tag);
2781 line.tags = new_tag;
2784 if (tag.previous != null) {
2785 tag.previous.next = null;
2787 new_line.tags = tag;
2788 tag.previous = null;
2789 tag.line = new_line;
2791 // Walk the list and correct the start location of the tags we just bumped into the next line
2792 shift = tag.start - 1;
2795 while (new_tag != null) {
2796 new_tag.start -= shift;
2797 new_tag.line = new_line;
2798 new_tag = new_tag.next;
2803 new_tag = new LineTag (new_line, 1);
2804 new_tag.next = tag.next;
2805 new_tag.CopyFormattingFrom (tag);
2806 new_line.tags = new_tag;
2807 if (new_tag.next != null) {
2808 new_tag.next.previous = new_tag;
2813 new_tag = new_tag.next;
2814 while (new_tag != null) {
2815 new_tag.start -= shift;
2816 new_tag.line = new_line;
2817 new_tag = new_tag.next;
2823 caret.line = new_line;
2824 caret.pos = caret.pos - pos;
2825 caret.tag = caret.line.FindTag(caret.pos);
2828 if (move_sel_start) {
2829 selection_start.line = new_line;
2830 selection_start.pos = selection_start.pos - pos;
2831 selection_start.tag = new_line.FindTag(selection_start.pos);
2835 selection_end.line = new_line;
2836 selection_end.pos = selection_end.pos - pos;
2837 selection_end.tag = new_line.FindTag(selection_end.pos);
2840 CharCount -= line.text.Length - pos;
2841 line.text.Remove(pos, line.text.Length - pos);
2844 // Adds a line of text, with given font.
2845 // Bumps any line at that line number that already exists down
2846 internal void Add (int LineNo, string Text, Font font, SolidBrush color, LineEnding ending)
2848 Add (LineNo, Text, alignment, font, color, ending);
2851 internal void Add (int LineNo, string Text, HorizontalAlignment align, Font font, SolidBrush color, LineEnding ending)
2857 CharCount += Text.Length;
2859 if (LineNo<1 || Text == null) {
2861 throw new ArgumentNullException("LineNo", "Line numbers must be positive");
2863 throw new ArgumentNullException("Text", "Cannot insert NULL line");
2867 add = new Line (this, LineNo, Text, align, font, color, ending);
2870 while (line != sentinel) {
2872 line_no = line.line_no;
2874 if (LineNo > line_no) {
2876 } else if (LineNo < line_no) {
2879 // Bump existing line numbers; walk all nodes to the right of this one and increment line_no
2880 IncrementLines(line.line_no);
2885 add.left = sentinel;
2886 add.right = sentinel;
2888 if (add.parent != null) {
2889 if (LineNo > add.parent.line_no) {
2890 add.parent.right = add;
2892 add.parent.left = add;
2899 RebalanceAfterAdd(add);
2904 internal virtual void Clear() {
2907 document = sentinel;
2910 public virtual object Clone() {
2913 clone = new Document(null);
2915 clone.lines = this.lines;
2916 clone.document = (Line)document.Clone();
2921 internal void Delete(int LineNo) {
2928 line = GetLine(LineNo);
2930 CharCount -= line.text.Length;
2932 DecrementLines(LineNo + 1);
2936 internal void Delete(Line line1) {
2937 Line line2;// = new Line();
2940 if ((line1.left == sentinel) || (line1.right == sentinel)) {
2943 line3 = line1.right;
2944 while (line3.left != sentinel) {
2949 if (line3.left != sentinel) {
2952 line2 = line3.right;
2955 line2.parent = line3.parent;
2956 if (line3.parent != null) {
2957 if(line3 == line3.parent.left) {
2958 line3.parent.left = line2;
2960 line3.parent.right = line2;
2966 if (line3 != line1) {
2969 if (selection_start.line == line3) {
2970 selection_start.line = line1;
2973 if (selection_end.line == line3) {
2974 selection_end.line = line1;
2977 if (selection_anchor.line == line3) {
2978 selection_anchor.line = line1;
2981 if (caret.line == line3) {
2986 line1.alignment = line3.alignment;
2987 line1.ascent = line3.ascent;
2988 line1.hanging_indent = line3.hanging_indent;
2989 line1.height = line3.height;
2990 line1.indent = line3.indent;
2991 line1.line_no = line3.line_no;
2992 line1.recalc = line3.recalc;
2993 line1.right_indent = line3.right_indent;
2994 line1.ending = line3.ending;
2995 line1.space = line3.space;
2996 line1.tags = line3.tags;
2997 line1.text = line3.text;
2998 line1.widths = line3.widths;
2999 line1.offset = line3.offset;
3002 while (tag != null) {
3008 if (line3.color == LineColor.Black)
3009 RebalanceAfterDelete(line2);
3014 // Invalidate a section of the document to trigger redraw
3015 internal void Invalidate(Line start, int start_pos, Line end, int end_pos) {
3021 if ((start == end) && (start_pos == end_pos)) {
3025 if (end_pos == -1) {
3026 end_pos = end.text.Length;
3029 // figure out what's before what so the logic below is straightforward
3030 if (start.line_no < end.line_no) {
3036 } else if (start.line_no > end.line_no) {
3043 if (start_pos < end_pos) {
3057 int endpoint = (int) l1.widths [p2];
3058 if (p2 == l1.text.Length + 1) {
3059 endpoint = (int) viewport_width;
3063 Console.WriteLine("Invaliding backwards from {0}:{1} to {2}:{3} {4}",
3064 l1.line_no, p1, l2.line_no, p2,
3066 (int)l1.widths[p1] + l1.X - viewport_x,
3074 owner.Invalidate(new Rectangle (
3075 (int)l1.widths[p1] + l1.X - viewport_x,
3077 endpoint - (int)l1.widths[p1] + 1,
3083 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.X - viewport_x, l1.Y - viewport_y, viewport_width, l1.height);
3084 Console.WriteLine ("invalidate start line: {0} position: {1}", l1.text, p1);
3087 // Three invalidates:
3088 // First line from start
3089 owner.Invalidate(new Rectangle((int)l1.widths[p1] + l1.X - viewport_x, l1.Y - viewport_y, viewport_width, l1.height));
3093 if ((l1.line_no + 1) < l2.line_no) {
3096 y = GetLine(l1.line_no + 1).Y;
3097 owner.Invalidate(new Rectangle(0, y - viewport_y, viewport_width, l2.Y - y));
3100 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);
3106 owner.Invalidate(new Rectangle((int)l2.widths[0] + l2.X - viewport_x, l2.Y - viewport_y, (int)l2.widths[p2] + 1, l2.height));
3108 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.X - viewport_x, l2.Y - viewport_y, (int)l2.widths[p2] + 1, l2.height);
3113 /// <summary>Select text around caret</summary>
3114 internal void ExpandSelection(CaretSelection mode, bool to_caret) {
3116 // We're expanding the selection to the caret position
3118 case CaretSelection.Line: {
3119 // Invalidate the selection delta
3120 if (caret > selection_prev) {
3121 Invalidate(selection_prev.line, 0, caret.line, caret.line.text.Length);
3123 Invalidate(selection_prev.line, selection_prev.line.text.Length, caret.line, 0);
3126 if (caret.line.line_no <= selection_anchor.line.line_no) {
3127 selection_start.line = caret.line;
3128 selection_start.tag = caret.line.tags;
3129 selection_start.pos = 0;
3131 selection_end.line = selection_anchor.line;
3132 selection_end.tag = selection_anchor.tag;
3133 selection_end.pos = selection_anchor.pos;
3135 selection_end_anchor = true;
3137 selection_start.line = selection_anchor.line;
3138 selection_start.pos = selection_anchor.height;
3139 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height);
3141 selection_end.line = caret.line;
3142 selection_end.tag = caret.line.tags;
3143 selection_end.pos = caret.line.text.Length;
3145 selection_end_anchor = false;
3147 selection_prev.line = caret.line;
3148 selection_prev.tag = caret.tag;
3149 selection_prev.pos = caret.pos;
3154 case CaretSelection.Word: {
3158 start_pos = FindWordSeparator(caret.line, caret.pos, false);
3159 end_pos = FindWordSeparator(caret.line, caret.pos, true);
3162 // Invalidate the selection delta
3163 if (caret > selection_prev) {
3164 Invalidate(selection_prev.line, selection_prev.pos, caret.line, end_pos);
3166 Invalidate(selection_prev.line, selection_prev.pos, caret.line, start_pos);
3168 if (caret < selection_anchor) {
3169 selection_start.line = caret.line;
3170 selection_start.tag = caret.line.FindTag(start_pos);
3171 selection_start.pos = start_pos;
3173 selection_end.line = selection_anchor.line;
3174 selection_end.tag = selection_anchor.tag;
3175 selection_end.pos = selection_anchor.pos;
3177 selection_prev.line = caret.line;
3178 selection_prev.tag = caret.tag;
3179 selection_prev.pos = start_pos;
3181 selection_end_anchor = true;
3183 selection_start.line = selection_anchor.line;
3184 selection_start.pos = selection_anchor.height;
3185 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height);
3187 selection_end.line = caret.line;
3188 selection_end.tag = caret.line.FindTag(end_pos);
3189 selection_end.pos = end_pos;
3191 selection_prev.line = caret.line;
3192 selection_prev.tag = caret.tag;
3193 selection_prev.pos = end_pos;
3195 selection_end_anchor = false;
3200 case CaretSelection.Position: {
3201 SetSelectionToCaret(false);
3206 // We're setting the selection 'around' the caret position
3208 case CaretSelection.Line: {
3209 this.Invalidate(caret.line, 0, caret.line, caret.line.text.Length);
3211 selection_start.line = caret.line;
3212 selection_start.tag = caret.line.tags;
3213 selection_start.pos = 0;
3215 selection_end.line = caret.line;
3216 selection_end.pos = caret.line.text.Length;
3217 selection_end.tag = caret.line.FindTag(selection_end.pos);
3219 selection_anchor.line = selection_end.line;
3220 selection_anchor.tag = selection_end.tag;
3221 selection_anchor.pos = selection_end.pos;
3222 selection_anchor.height = 0;
3224 selection_prev.line = caret.line;
3225 selection_prev.tag = caret.tag;
3226 selection_prev.pos = caret.pos;
3228 this.selection_end_anchor = true;
3233 case CaretSelection.Word: {
3237 start_pos = FindWordSeparator(caret.line, caret.pos, false);
3238 end_pos = FindWordSeparator(caret.line, caret.pos, true);
3240 this.Invalidate(selection_start.line, start_pos, caret.line, end_pos);
3242 selection_start.line = caret.line;
3243 selection_start.tag = caret.line.FindTag(start_pos);
3244 selection_start.pos = start_pos;
3246 selection_end.line = caret.line;
3247 selection_end.tag = caret.line.FindTag(end_pos);
3248 selection_end.pos = end_pos;
3250 selection_anchor.line = selection_end.line;
3251 selection_anchor.tag = selection_end.tag;
3252 selection_anchor.pos = selection_end.pos;
3253 selection_anchor.height = start_pos;
3255 selection_prev.line = caret.line;
3256 selection_prev.tag = caret.tag;
3257 selection_prev.pos = caret.pos;
3259 this.selection_end_anchor = true;
3266 SetSelectionVisible (!(selection_start == selection_end));
3269 internal void SetSelectionToCaret(bool start) {
3271 // Invalidate old selection; selection is being reset to empty
3272 this.Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3274 selection_start.line = caret.line;
3275 selection_start.tag = caret.tag;
3276 selection_start.pos = caret.pos;
3278 // start always also selects end
3279 selection_end.line = caret.line;
3280 selection_end.tag = caret.tag;
3281 selection_end.pos = caret.pos;
3283 selection_anchor.line = caret.line;
3284 selection_anchor.tag = caret.tag;
3285 selection_anchor.pos = caret.pos;
3287 // Invalidate from previous end to caret (aka new end)
3288 if (selection_end_anchor) {
3289 if (selection_start != caret) {
3290 this.Invalidate(selection_start.line, selection_start.pos, caret.line, caret.pos);
3293 if (selection_end != caret) {
3294 this.Invalidate(selection_end.line, selection_end.pos, caret.line, caret.pos);
3298 if (caret < selection_anchor) {
3299 selection_start.line = caret.line;
3300 selection_start.tag = caret.tag;
3301 selection_start.pos = caret.pos;
3303 selection_end.line = selection_anchor.line;
3304 selection_end.tag = selection_anchor.tag;
3305 selection_end.pos = selection_anchor.pos;
3307 selection_end_anchor = true;
3309 selection_start.line = selection_anchor.line;
3310 selection_start.tag = selection_anchor.tag;
3311 selection_start.pos = selection_anchor.pos;
3313 selection_end.line = caret.line;
3314 selection_end.tag = caret.tag;
3315 selection_end.pos = caret.pos;
3317 selection_end_anchor = false;
3321 SetSelectionVisible (!(selection_start == selection_end));
3324 internal void SetSelection(Line start, int start_pos, Line end, int end_pos) {
3325 if (selection_visible) {
3326 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3329 if ((end.line_no < start.line_no) || ((end == start) && (end_pos <= start_pos))) {
3330 selection_start.line = end;
3331 selection_start.tag = LineTag.FindTag(end, end_pos);
3332 selection_start.pos = end_pos;
3334 selection_end.line = start;
3335 selection_end.tag = LineTag.FindTag(start, start_pos);
3336 selection_end.pos = start_pos;
3338 selection_end_anchor = true;
3340 selection_start.line = start;
3341 selection_start.tag = LineTag.FindTag(start, start_pos);
3342 selection_start.pos = start_pos;
3344 selection_end.line = end;
3345 selection_end.tag = LineTag.FindTag(end, end_pos);
3346 selection_end.pos = end_pos;
3348 selection_end_anchor = false;
3351 selection_anchor.line = start;
3352 selection_anchor.tag = selection_start.tag;
3353 selection_anchor.pos = start_pos;
3355 if (((start == end) && (start_pos == end_pos)) || start == null || end == null) {
3356 SetSelectionVisible (false);
3358 SetSelectionVisible (true);
3359 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3363 internal void SetSelectionStart(Line start, int start_pos, bool invalidate) {
3364 // Invalidate from the previous to the new start pos
3366 Invalidate(selection_start.line, selection_start.pos, start, start_pos);
3368 selection_start.line = start;
3369 selection_start.pos = start_pos;
3370 selection_start.tag = LineTag.FindTag(start, start_pos);
3372 selection_anchor.line = start;
3373 selection_anchor.pos = start_pos;
3374 selection_anchor.tag = selection_start.tag;
3376 selection_end_anchor = false;
3379 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
3380 SetSelectionVisible (true);
3382 SetSelectionVisible (false);
3386 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3389 internal void SetSelectionStart(int character_index, bool invalidate) {
3394 if (character_index < 0) {
3398 CharIndexToLineTag(character_index, out line, out tag, out pos);
3399 SetSelectionStart(line, pos, invalidate);
3402 internal void SetSelectionEnd(Line end, int end_pos, bool invalidate) {
3404 if (end == selection_end.line && end_pos == selection_start.pos) {
3405 selection_anchor.line = selection_start.line;
3406 selection_anchor.tag = selection_start.tag;
3407 selection_anchor.pos = selection_start.pos;
3409 selection_end.line = selection_start.line;
3410 selection_end.tag = selection_start.tag;
3411 selection_end.pos = selection_start.pos;
3413 selection_end_anchor = false;
3414 } else if ((end.line_no < selection_anchor.line.line_no) || ((end == selection_anchor.line) && (end_pos <= selection_anchor.pos))) {
3415 selection_start.line = end;
3416 selection_start.tag = LineTag.FindTag(end, end_pos);
3417 selection_start.pos = end_pos;
3419 selection_end.line = selection_anchor.line;
3420 selection_end.tag = selection_anchor.tag;
3421 selection_end.pos = selection_anchor.pos;
3423 selection_end_anchor = true;
3425 selection_start.line = selection_anchor.line;
3426 selection_start.tag = selection_anchor.tag;
3427 selection_start.pos = selection_anchor.pos;
3429 selection_end.line = end;
3430 selection_end.tag = LineTag.FindTag(end, end_pos);
3431 selection_end.pos = end_pos;
3433 selection_end_anchor = false;
3436 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
3437 SetSelectionVisible (true);
3439 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3441 SetSelectionVisible (false);
3442 // ?? Do I need to invalidate here, tests seem to work without it, but I don't think they should :-s
3446 internal void SetSelectionEnd(int character_index, bool invalidate) {
3451 if (character_index < 0) {
3455 CharIndexToLineTag(character_index, out line, out tag, out pos);
3456 SetSelectionEnd(line, pos, invalidate);
3459 internal void SetSelection(Line start, int start_pos) {
3460 if (selection_visible) {
3461 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3464 selection_start.line = start;
3465 selection_start.pos = start_pos;
3466 selection_start.tag = LineTag.FindTag(start, start_pos);
3468 selection_end.line = start;
3469 selection_end.tag = selection_start.tag;
3470 selection_end.pos = start_pos;
3472 selection_anchor.line = start;
3473 selection_anchor.tag = selection_start.tag;
3474 selection_anchor.pos = start_pos;
3476 selection_end_anchor = false;
3477 SetSelectionVisible (false);
3480 internal void InvalidateSelectionArea() {
3481 Invalidate (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3484 // Return the current selection, as string
3485 internal string GetSelection() {
3486 // We return String.Empty if there is no selection
3487 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3488 return string.Empty;
3491 if (selection_start.line == selection_end.line) {
3492 return selection_start.line.text.ToString (selection_start.pos, selection_end.pos - selection_start.pos);
3499 sb = new StringBuilder();
3500 start = selection_start.line.line_no;
3501 end = selection_end.line.line_no;
3503 sb.Append(selection_start.line.text.ToString(selection_start.pos, selection_start.line.text.Length - selection_start.pos) + Environment.NewLine);
3505 if ((start + 1) < end) {
3506 for (i = start + 1; i < end; i++) {
3507 sb.Append(GetLine(i).text.ToString() + Environment.NewLine);
3511 sb.Append(selection_end.line.text.ToString(0, selection_end.pos));
3513 return sb.ToString();
3517 internal void ReplaceSelection(string s, bool select_new) {
3520 int selection_pos_on_line = selection_start.pos;
3521 int selection_start_pos = LineTagToCharIndex (selection_start.line, selection_start.pos);
3524 // First, delete any selected text
3525 if ((selection_start.pos != selection_end.pos) || (selection_start.line != selection_end.line)) {
3526 if (selection_start.line == selection_end.line) {
3527 undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3529 DeleteChars(selection_start.tag, selection_start.pos, selection_end.pos - selection_start.pos);
3531 // The tag might have been removed, we need to recalc it
3532 selection_start.tag = selection_start.line.FindTag(selection_start.pos);
3537 start = selection_start.line.line_no;
3538 end = selection_end.line.line_no;
3540 undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3542 // Delete first line
3543 DeleteChars(selection_start.tag, selection_start.pos, selection_start.line.text.Length - selection_start.pos);
3546 DeleteChars(selection_end.line.tags, 0, selection_end.pos);
3550 for (i = end - 1; i >= start; i--) {
3555 // BIG FAT WARNING - selection_end.line might be stale due
3556 // to the above Delete() call. DONT USE IT before hitting the end of this method!
3558 // Join start and end
3559 Combine(selection_start.line.line_no, start);
3564 Insert(selection_start.line, selection_start.pos, false, s);
3565 undo.RecordInsertString (selection_start.line, selection_start.pos, s);
3566 ResumeRecalc (false);
3569 CharIndexToLineTag(selection_start_pos + s.Length, out selection_start.line,
3570 out selection_start.tag, out selection_start.pos);
3572 selection_end.line = selection_start.line;
3573 selection_end.pos = selection_start.pos;
3574 selection_end.tag = selection_start.tag;
3575 selection_anchor.line = selection_start.line;
3576 selection_anchor.pos = selection_start.pos;
3577 selection_anchor.tag = selection_start.tag;
3579 SetSelectionVisible (false);
3581 CharIndexToLineTag(selection_start_pos, out selection_start.line,
3582 out selection_start.tag, out selection_start.pos);
3584 CharIndexToLineTag(selection_start_pos + s.Length, out selection_end.line,
3585 out selection_end.tag, out selection_end.pos);
3587 selection_anchor.line = selection_start.line;
3588 selection_anchor.pos = selection_start.pos;
3589 selection_anchor.tag = selection_start.tag;
3591 SetSelectionVisible (true);
3594 PositionCaret (selection_start.line, selection_start.pos);
3595 UpdateView (selection_start.line, selection_pos_on_line);
3598 internal void CharIndexToLineTag(int index, out Line line_out, out LineTag tag_out, out int pos) {
3607 for (i = 1; i <= lines; i++) {
3611 chars += line.text.Length + LineEndingLength (line.ending);
3613 if (index <= chars) {
3614 // we found the line
3617 while (tag != null) {
3618 if (index < (start + tag.start + tag.length)) {
3620 tag_out = LineTag.GetFinalTag (tag);
3621 pos = index - start;
3624 if (tag.next == null) {
3627 next_line = GetLine(line.line_no + 1);
3629 if (next_line != null) {
3630 line_out = next_line;
3631 tag_out = LineTag.GetFinalTag (next_line.tags);
3636 tag_out = LineTag.GetFinalTag (tag);
3637 pos = line_out.text.Length;
3646 line_out = GetLine(lines);
3647 tag = line_out.tags;
3648 while (tag.next != null) {
3652 pos = line_out.text.Length;
3655 internal int LineTagToCharIndex(Line line, int pos) {
3659 // Count first and last line
3662 // Count the lines in the middle
3664 for (i = 1; i < line.line_no; i++) {
3665 length += GetLine(i).text.Length + LineEndingLength (line.ending);
3673 internal int SelectionLength() {
3674 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3678 if (selection_start.line == selection_end.line) {
3679 return selection_end.pos - selection_start.pos;
3686 // Count first and last line
3687 length = selection_start.line.text.Length - selection_start.pos + selection_end.pos + crlf_size;
3689 // Count the lines in the middle
3690 start = selection_start.line.line_no + 1;
3691 end = selection_end.line.line_no;
3694 for (i = start; i < end; i++) {
3695 Line line = GetLine (i);
3696 length += line.text.Length + LineEndingLength (line.ending);
3707 /// <summary>Give it a Line number and it returns the Line object at with that line number</summary>
3708 internal Line GetLine(int LineNo) {
3709 Line line = document;
3711 while (line != sentinel) {
3712 if (LineNo == line.line_no) {
3714 } else if (LineNo < line.line_no) {
3724 /// <summary>Retrieve the previous tag; walks line boundaries</summary>
3725 internal LineTag PreviousTag(LineTag tag) {
3728 if (tag.previous != null) {
3729 return tag.previous;
3733 if (tag.line.line_no == 1) {
3737 l = GetLine(tag.line.line_no - 1);
3742 while (t.next != null) {
3751 /// <summary>Retrieve the next tag; walks line boundaries</summary>
3752 internal LineTag NextTag(LineTag tag) {
3755 if (tag.next != null) {
3760 l = GetLine(tag.line.line_no + 1);
3768 internal Line ParagraphStart(Line line) {
3769 while (line.ending == LineEnding.Wrap) {
3770 line = GetLine(line.line_no - 1);
3775 internal Line ParagraphEnd(Line line) {
3778 while (line.ending == LineEnding.Wrap) {
3779 l = GetLine(line.line_no + 1);
3780 if ((l == null) || (l.ending != LineEnding.Wrap)) {
3788 /// <summary>Give it a pixel offset coordinate and it returns the Line covering that are (offset
3789 /// is either X or Y depending on if we are multiline
3791 internal Line GetLineByPixel (int offset, bool exact)
3793 Line line = document;
3797 while (line != sentinel) {
3799 if ((offset >= line.Y) && (offset < (line.Y+line.height))) {
3801 } else if (offset < line.Y) {
3808 while (line != sentinel) {
3810 if ((offset >= line.X) && (offset < (line.X + line.Width)))
3812 else if (offset < line.X)
3825 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3826 internal LineTag FindTag(int x, int y, out int index, bool exact) {
3830 line = GetLineByPixel(y, exact);
3837 // Alignment adjustment
3841 if (x >= tag.X && x < (tag.X+tag.width)) {
3844 end = tag.start + tag.length - 1;
3846 for (int pos = tag.start; pos < end; pos++) {
3847 if (x < line.widths[pos]) {
3849 return LineTag.GetFinalTag (tag);
3853 return LineTag.GetFinalTag (tag);
3855 if (tag.next != null) {
3863 index = line.text.Length;
3864 return LineTag.GetFinalTag (tag);
3869 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3870 internal LineTag FindCursor(int x, int y, out int index) {
3874 line = GetLineByPixel(multiline ? y : x, false);
3877 /// Special case going leftwards of the first tag
3880 return LineTag.GetFinalTag (tag);
3884 if (x >= tag.X && x < (tag.X+tag.width)) {
3889 for (int pos = tag.start - 1; pos < end; pos++) {
3890 // When clicking on a character, we position the cursor to whatever edge
3891 // of the character the click was closer
3892 if (x < (line.X + line.widths[pos] + ((line.widths[pos+1]-line.widths[pos])/2))) {
3894 return LineTag.GetFinalTag (tag);
3898 return LineTag.GetFinalTag (tag);
3900 if (tag.next != null) {
3903 index = line.text.Length;
3904 return LineTag.GetFinalTag (tag);
3909 /// <summary>Format area of document in specified font and color</summary>
3910 /// <param name="start_pos">1-based start position on start_line</param>
3911 /// <param name="end_pos">1-based end position on end_line </param>
3912 internal void FormatText (Line start_line, int start_pos, Line end_line, int end_pos, Font font,
3913 SolidBrush color, SolidBrush back_color, FormatSpecified specified)
3917 // First, format the first line
3918 if (start_line != end_line) {
3920 LineTag.FormatText(start_line, start_pos, start_line.text.Length - start_pos + 1, font, color, back_color, specified);
3923 LineTag.FormatText(end_line, 1, end_pos, font, color, back_color, specified);
3925 // Now all the lines inbetween
3926 for (int i = start_line.line_no + 1; i < end_line.line_no; i++) {
3928 LineTag.FormatText(l, 1, l.text.Length, font, color, back_color, specified);
3931 // Special case, single line
3932 LineTag.FormatText(start_line, start_pos, end_pos - start_pos, font, color, back_color, specified);
3936 /// <summary>Re-format areas of the document in specified font and color</summary>
3937 /// <param name="start_pos">1-based start position on start_line</param>
3938 /// <param name="end_pos">1-based end position on end_line </param>
3939 /// <param name="font">Font specifying attributes</param>
3940 /// <param name="color">Color (or NULL) to apply</param>
3941 /// <param name="apply">Attributes from font and color to apply</param>
3942 internal void FormatText(Line start_line, int start_pos, Line end_line, int end_pos, FontDefinition attributes) {
3945 // First, format the first line
3946 if (start_line != end_line) {
3948 LineTag.FormatText(start_line, start_pos, start_line.text.Length - start_pos + 1, attributes);
3951 LineTag.FormatText(end_line, 1, end_pos - 1, attributes);
3953 // Now all the lines inbetween
3954 for (int i = start_line.line_no + 1; i < end_line.line_no; i++) {
3956 LineTag.FormatText(l, 1, l.text.Length, attributes);
3959 // Special case, single line
3960 LineTag.FormatText(start_line, start_pos, end_pos - start_pos, attributes);
3964 internal void RecalculateAlignments ()
3973 while (line_no <= lines) {
3974 line = GetLine(line_no);
3977 switch (line.alignment) {
3978 case HorizontalAlignment.Left:
3979 line.align_shift = 0;
3981 case HorizontalAlignment.Center:
3982 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3984 case HorizontalAlignment.Right:
3985 line.align_shift = viewport_width - (int)line.widths[line.text.Length] - line.right_margin;
3995 /// <summary>Calculate formatting for the whole document</summary>
3996 internal bool RecalculateDocument(Graphics g) {
3997 return RecalculateDocument(g, 1, this.lines, false);
4000 /// <summary>Calculate formatting starting at a certain line</summary>
4001 internal bool RecalculateDocument(Graphics g, int start) {
4002 return RecalculateDocument(g, start, this.lines, false);
4005 /// <summary>Calculate formatting within two given line numbers</summary>
4006 internal bool RecalculateDocument(Graphics g, int start, int end) {
4007 return RecalculateDocument(g, start, end, false);
4010 /// <summary>With optimize on, returns true if line heights changed</summary>
4011 internal bool RecalculateDocument(Graphics g, int start, int end, bool optimize) {
4019 if (recalc_suspended > 0) {
4020 recalc_pending = true;
4021 recalc_start = Math.Min (recalc_start, start);
4022 recalc_end = Math.Max (recalc_end, end);
4023 recalc_optimize = optimize;
4027 // Fixup the positions, they can go kinda nuts
4028 start = Math.Max (start, 1);
4029 end = Math.Min (end, lines);
4031 offset = GetLine(start).offset;
4036 changed = true; // We always return true if we run non-optimized
4041 while (line_no <= (end + this.lines - shift)) {
4042 line = GetLine(line_no++);
4043 line.offset = offset;
4047 line.RecalculateLine(g, this);
4049 if (line.recalc && line.RecalculateLine(g, this)) {
4051 // If the height changed, all subsequent lines change
4058 line.RecalculatePasswordLine(g, this);
4060 if (line.recalc && line.RecalculatePasswordLine(g, this)) {
4062 // If the height changed, all subsequent lines change
4069 if (line.widths[line.text.Length] > new_width) {
4070 new_width = (int)line.widths[line.text.Length];
4073 // Calculate alignment
4074 if (line.alignment != HorizontalAlignment.Left) {
4075 if (line.alignment == HorizontalAlignment.Center) {
4076 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
4078 line.align_shift = viewport_width - (int)line.widths[line.text.Length] - 1;
4083 offset += line.height;
4085 offset += (int) line.widths [line.text.Length];
4087 if (line_no > lines) {
4092 if (document_x != new_width) {
4093 document_x = new_width;
4094 if (WidthChanged != null) {
4095 WidthChanged(this, null);
4099 RecalculateAlignments();
4101 line = GetLine(lines);
4103 if (document_y != line.Y + line.height) {
4104 document_y = line.Y + line.height;
4105 if (HeightChanged != null) {
4106 HeightChanged(this, null);
4113 internal int Size() {
4117 private void owner_HandleCreated(object sender, EventArgs e) {
4118 RecalculateDocument(owner.CreateGraphicsInternal());
4122 private void owner_VisibleChanged(object sender, EventArgs e) {
4123 if (owner.Visible) {
4124 RecalculateDocument(owner.CreateGraphicsInternal());
4128 internal static bool IsWordSeparator (char ch)
4143 internal int FindWordSeparator(Line line, int pos, bool forward) {
4146 len = line.text.Length;
4149 for (int i = pos + 1; i < len; i++) {
4150 if (IsWordSeparator(line.Text[i])) {
4156 for (int i = pos - 1; i > 0; i--) {
4157 if (IsWordSeparator(line.Text[i - 1])) {
4165 /* Search document for text */
4166 internal bool FindChars(char[] chars, Marker start, Marker end, out Marker result) {
4172 // Search for occurence of any char in the chars array
4173 result = new Marker();
4176 line_no = start.line.line_no;
4178 while (line_no <= end.line.line_no) {
4179 line_len = line.text.Length;
4180 while (pos < line_len) {
4181 for (int i = 0; i < chars.Length; i++) {
4182 if (line.text[pos] == chars[i]) {
4184 if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
4198 line = GetLine(line_no);
4204 // This version does not build one big string for searching, instead it handles
4205 // line-boundaries, which is faster and less memory intensive
4206 // FIXME - Depending on culture stuff we might have to create a big string and use culturespecific
4207 // search stuff and change it to accept and return positions instead of Markers (which would match
4208 // RichTextBox behaviour better but would be inconsistent with the rest of TextControl)
4209 internal bool Find(string search, Marker start, Marker end, out Marker result, RichTextBoxFinds options) {
4211 string search_string;
4223 result = new Marker();
4224 word_option = ((options & RichTextBoxFinds.WholeWord) != 0);
4225 ignore_case = ((options & RichTextBoxFinds.MatchCase) == 0);
4226 reverse = ((options & RichTextBoxFinds.Reverse) != 0);
4229 line_no = start.line.line_no;
4233 // Prep our search string, lowercasing it if we do case-independent matching
4236 sb = new StringBuilder(search);
4237 for (int i = 0; i < sb.Length; i++) {
4238 sb[i] = Char.ToLower(sb[i]);
4240 search_string = sb.ToString();
4242 search_string = search;
4245 // We need to check if the character before our start position is a wordbreak
4248 if ((pos == 0) || (IsWordSeparator(line.text[pos - 1]))) {
4255 if (IsWordSeparator(line.text[pos - 1])) {
4261 // Need to check the end of the previous line
4264 prev_line = GetLine(line_no - 1);
4265 if (prev_line.ending == LineEnding.Wrap) {
4266 if (IsWordSeparator(prev_line.text[prev_line.text.Length - 1])) {
4280 // To avoid duplication of this loop with reverse logic, we search
4281 // through the document, remembering the last match and when returning
4282 // report that last remembered match
4284 last = new Marker();
4285 last.height = -1; // Abused - we use it to track change
4287 while (line_no <= end.line.line_no) {
4288 if (line_no != end.line.line_no) {
4289 line_len = line.text.Length;
4294 while (pos < line_len) {
4296 if (word_option && (current == search_string.Length)) {
4297 if (IsWordSeparator(line.text[pos])) {
4310 c = Char.ToLower(line.text[pos]);
4315 if (c == search_string[current]) {
4321 if (!word_option || (word_option && (word || (current > 0)))) {
4325 if (!word_option && (current == search_string.Length)) {
4342 if (IsWordSeparator(c)) {
4350 // Mark that we just saw a word boundary
4351 if (line.ending != LineEnding.Wrap || line.line_no == lines - 1) {
4355 if (current == search_string.Length) {
4371 line = GetLine(line_no);
4375 if (last.height != -1) {
4385 // if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
4397 internal void GetMarker(out Marker mark, bool start) {
4398 mark = new Marker();
4401 mark.line = GetLine(1);
4402 mark.tag = mark.line.tags;
4405 mark.line = GetLine(lines);
4406 mark.tag = mark.line.tags;
4407 while (mark.tag.next != null) {
4408 mark.tag = mark.tag.next;
4410 mark.pos = mark.line.text.Length;
4413 #endregion // Internal Methods
4416 internal event EventHandler CaretMoved;
4417 internal event EventHandler WidthChanged;
4418 internal event EventHandler HeightChanged;
4419 internal event EventHandler LengthChanged;
4420 #endregion // Events
4422 #region Administrative
4423 public IEnumerator GetEnumerator() {
4428 public override bool Equals(object obj) {
4433 if (!(obj is Document)) {
4441 if (ToString().Equals(((Document)obj).ToString())) {
4448 public override int GetHashCode() {
4452 public override string ToString() {
4453 return "document " + this.document_id;
4455 #endregion // Administrative
4458 internal class PictureTag : LineTag {
4460 internal RTF.Picture picture;
4462 internal PictureTag (Line line, int start, RTF.Picture picture) : base (line, start)
4464 this.picture = picture;
4467 public override bool IsTextTag {
4468 get { return false; }
4471 internal override SizeF SizeOfPosition (Graphics dc, int pos)
4473 return picture.Size;
4476 internal override int MaxHeight ()
4478 return (int) (picture.Height + 0.5F);
4481 internal override void Draw (Graphics dc, Brush brush, float x, float y, int start, int end)
4483 picture.DrawImage (dc, x, y, false);
4486 internal override void Draw (Graphics dc, Brush brush, float x, float y, int start, int end, string text)
4488 picture.DrawImage (dc, x, y, false);
4491 public override string Text ()
4497 internal class LineTag {
4498 #region Local Variables;
4499 // Payload; formatting
4500 internal Font font; // System.Drawing.Font object for this tag
4501 internal SolidBrush color; // The font color for this tag
4503 // In 2.0 tags can have background colours. I'm not going to #ifdef
4504 // at this level though since I want to reduce code paths
4505 internal SolidBrush back_color;
4508 internal int start; // start, in chars; index into Line.text
4509 internal bool r_to_l; // Which way is the font
4512 internal int height; // Height in pixels of the text this tag describes
4514 internal int ascent; // Ascent of the font for this tag
4515 internal int shift; // Shift down for this tag, to stay on baseline
4518 internal Line line; // The line we're on
4519 internal LineTag next; // Next tag on the same line
4520 internal LineTag previous; // Previous tag on the same line
4523 #region Constructors
4524 internal LineTag(Line line, int start) {
4528 #endregion // Constructors
4530 #region Internal Methods
4536 return line.X + line.widths [start - 1];
4541 get { return start + length; }
4544 public float width {
4548 return line.widths [start + length - 1] - (start != 0 ? line.widths [start - 1] : 0);
4556 res = next.start - start;
4558 res = line.text.Length - (start - 1);
4560 return res > 0 ? res : 0;
4564 public virtual bool IsTextTag {
4565 get { return true; }
4568 internal virtual SizeF SizeOfPosition (Graphics dc, int pos)
4571 if (pos >= line.TextLengthWithoutEnding () && line.document.multiline)
4574 string text = line.text.ToString (pos, 1);
4575 switch ((int) text [0]) {
4578 return dc.MeasureString ("\u0013", font, 10000, Document.string_format);
4581 return dc.MeasureString (text, font, 10000, Document.string_format);
4584 internal virtual int MaxHeight ()
4589 internal virtual void Draw (Graphics dc, Brush brush, float x, float y, int start, int end)
4591 dc.DrawString (line.text.ToString (start, end), font, brush, x, y, StringFormat.GenericTypographic);
4594 internal virtual void Draw (Graphics dc, Brush brush, float x, float y, int start, int end, string text)
4596 dc.DrawString (text.Substring (start, end), font, brush, x, y, StringFormat.GenericTypographic);
4599 ///<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>
4600 internal LineTag Break(int pos) {
4605 if (pos == this.start) {
4607 } else if (pos >= (start + length)) {
4611 new_tag = new LineTag(line, pos);
4612 new_tag.CopyFormattingFrom (this);
4614 new_tag.next = this.next;
4615 this.next = new_tag;
4616 new_tag.previous = this;
4618 if (new_tag.next != null) {
4619 new_tag.next.previous = new_tag;
4625 public virtual string Text ()
4627 return line.text.ToString (start - 1, length);
4630 public void CopyFormattingFrom (LineTag other)
4632 height = other.height;
4634 color = other.color;
4635 back_color = other.back_color;
4638 ///<summary>Create new font and brush from existing font and given new attributes. Returns true if fontheight changes</summary>
4639 internal static bool GenerateTextFormat(Font font_from, SolidBrush color_from, FontDefinition attributes, out Font new_font, out SolidBrush new_color) {
4645 if (attributes.font_obj == null) {
4646 size = font_from.SizeInPoints;
4647 unit = font_from.Unit;
4648 face = font_from.Name;
4649 style = font_from.Style;
4651 if (attributes.face != null) {
4652 face = attributes.face;
4655 if (attributes.size != 0) {
4656 size = attributes.size;
4659 style |= attributes.add_style;
4660 style &= ~attributes.remove_style;
4663 new_font = new Font(face, size, style, unit);
4665 new_font = attributes.font_obj;
4668 // Create 'new' color brush
4669 if (attributes.color != Color.Empty) {
4670 new_color = new SolidBrush(attributes.color);
4672 new_color = color_from;
4675 if (new_font.Height == font_from.Height) {
4681 /// <summary>Applies 'font' and 'brush' to characters starting at 'start' for 'length' chars;
4682 /// Removes any previous tags overlapping the same area;
4683 /// returns true if lineheight has changed</summary>
4684 /// <param name="start">1-based character position on line</param>
4685 internal static bool FormatText(Line line, int start, int length, Font font, SolidBrush color, SolidBrush back_color, FormatSpecified specified)
4691 bool retval = false; // Assume line-height doesn't change
4694 if (((FormatSpecified.Font & specified) == FormatSpecified.Font) && font.Height != line.height) {
4697 line.recalc = true; // This forces recalculation of the line in RecalculateDocument
4699 // A little sanity, not sure if it's needed, might be able to remove for speed
4700 if (length > line.text.Length) {
4701 length = line.text.Length;
4705 end = start + length;
4707 // Common special case
4708 if ((start == 1) && (length == tag.length)) {
4710 SetFormat (tag, font, color, back_color, specified);
4714 start_tag = FindTag (line, start);
4715 tag = start_tag.Break (start);
4717 while (tag != null && tag.end <= end) {
4718 SetFormat (tag, font, color, back_color, specified);
4722 if (tag != null && tag.end != end) {
4723 /// Now do the last tag
4724 end_tag = FindTag (line, end);
4726 if (end_tag != null) {
4727 end_tag.Break (end);
4728 SetFormat (end_tag, font, color, back_color, specified);
4735 private static void SetFormat (LineTag tag, Font font, SolidBrush color, SolidBrush back_color, FormatSpecified specified)
4737 if ((FormatSpecified.Font & specified) == FormatSpecified.Font)
4739 if ((FormatSpecified.Color & specified) == FormatSpecified.Color)
4741 if ((FormatSpecified.BackColor & specified) == FormatSpecified.BackColor) {
4742 tag.back_color = back_color;
4744 // Console.WriteLine ("setting format: {0} {1} new color {2}", color.Color, specified, tag.color.Color);
4747 /// <summary>Applies font attributes specified to characters starting at 'start' for 'length' chars;
4748 /// Breaks tags at start and end point, keeping middle tags with altered attributes.
4749 /// Returns true if lineheight has changed</summary>
4750 /// <param name="start">1-based character position on line</param>
4751 internal static bool FormatText(Line line, int start, int length, FontDefinition attributes) {
4755 bool retval = false; // Assume line-height doesn't change
4757 line.recalc = true; // This forces recalculation of the line in RecalculateDocument
4759 // A little sanity, not sure if it's needed, might be able to remove for speed
4760 if (length > line.text.Length) {
4761 length = line.text.Length;
4766 // Common special case
4767 if ((start == 1) && (length == tag.length)) {
4769 GenerateTextFormat(tag.font, tag.color, attributes, out tag.font, out tag.color);
4773 start_tag = FindTag(line, start);
4775 if (start_tag == null) {
4777 // We are 'starting' after all valid tags; create a new tag with the right attributes
4778 start_tag = FindTag(line, line.text.Length - 1);
4779 start_tag.next = new LineTag(line, line.text.Length + 1);
4780 start_tag.next.CopyFormattingFrom (start_tag);
4781 start_tag.next.previous = start_tag;
4782 start_tag = start_tag.next;
4784 throw new Exception(String.Format("Could not find start_tag in document at line {0} position {1}", line.line_no, start));
4787 start_tag = start_tag.Break(start);
4790 end_tag = FindTag(line, start + length);
4791 if (end_tag != null) {
4792 end_tag = end_tag.Break(start + length);
4795 // start_tag or end_tag might be null; we're cool with that
4796 // we now walk from start_tag to end_tag, applying new attributes
4798 while ((tag != null) && tag != end_tag) {
4799 if (LineTag.GenerateTextFormat(tag.font, tag.color, attributes, out tag.font, out tag.color)) {
4808 /// <summary>Finds the tag that describes the character at position 'pos' on 'line'</summary>
4809 internal static LineTag FindTag(Line line, int pos) {
4810 LineTag tag = line.tags;
4812 // Beginning of line is a bit special
4814 // Not sure if we should get the final tag here
4818 while (tag != null) {
4819 if ((tag.start <= pos) && (pos <= tag.end)) {
4820 return GetFinalTag (tag);
4829 // There can be multiple tags at the same position, we want to make
4830 // sure we are using the very last tag at the given position
4831 internal static LineTag GetFinalTag (LineTag tag)
4835 while (res.length == 0 && res.next != null && res.next.length == 0)
4841 /// <summary>Combines 'this' tag with 'other' tag</summary>
4842 internal bool Combine(LineTag other) {
4843 if (!this.Equals(other)) {
4847 this.next = other.next;
4848 if (this.next != null) {
4849 this.next.previous = this;
4856 /// <summary>Remove 'this' tag ; to be called when formatting is to be removed</summary>
4857 internal bool Remove() {
4858 if ((this.start == 1) && (this.next == null)) {
4859 // We cannot remove the only tag
4862 if (this.start != 1) {
4863 this.previous.next = this.next;
4864 this.next.previous = this.previous;
4866 this.next.start = 1;
4867 this.line.tags = this.next;
4868 this.next.previous = null;
4874 /// <summary>Checks if 'this' tag describes the same formatting options as 'obj'</summary>
4875 public override bool Equals(object obj) {
4882 if (!(obj is LineTag)) {
4890 other = (LineTag)obj;
4892 if (other.IsTextTag != IsTextTag)
4895 if (this.font.Equals(other.font) && this.color.Equals(other.color)) { // FIXME add checking for things like link or type later
4902 public override int GetHashCode() {
4903 return base.GetHashCode ();
4906 public override string ToString() {
4908 return GetType () + " Tag starts at index " + this.start + " length " + this.length + " text: " + Text () + "Font " + this.font.ToString();
4909 return "Zero Lengthed tag at index " + this.start;
4912 #endregion // Internal Methods
4915 internal class UndoManager {
4917 internal enum ActionType {
4921 // This is basically just cut & paste
4929 internal class Action {
4930 internal ActionType type;
4931 internal int line_no;
4933 internal object data;
4936 #region Local Variables
4937 private Document document;
4938 private Stack undo_actions;
4939 private Stack redo_actions;
4941 private int caret_line;
4942 private int caret_pos;
4944 // When performing an action, we lock the queue, so that the action can't be undone
4945 private bool locked;
4946 #endregion // Local Variables
4948 #region Constructors
4949 internal UndoManager (Document document)
4951 this.document = document;
4952 undo_actions = new Stack (50);
4953 redo_actions = new Stack (50);
4955 #endregion // Constructors
4958 internal bool CanUndo {
4959 get { return undo_actions.Count > 0; }
4962 internal bool CanRedo {
4963 get { return redo_actions.Count > 0; }
4966 internal string UndoActionName {
4968 foreach (Action action in undo_actions) {
4969 if (action.type == ActionType.UserActionBegin)
4970 return (string) action.data;
4971 if (action.type == ActionType.Typing)
4972 return Locale.GetText ("Typing");
4974 return String.Empty;
4978 internal string RedoActionName {
4980 foreach (Action action in redo_actions) {
4981 if (action.type == ActionType.UserActionBegin)
4982 return (string) action.data;
4983 if (action.type == ActionType.Typing)
4984 return Locale.GetText ("Typing");
4986 return String.Empty;
4989 #endregion // Properties
4991 #region Internal Methods
4992 internal void Clear ()
4994 undo_actions.Clear();
4995 redo_actions.Clear();
4998 internal void Undo ()
5001 bool user_action_finished = false;
5003 if (undo_actions.Count == 0)
5006 // Nuke the redo queue
5007 redo_actions.Clear ();
5012 action = (Action) undo_actions.Pop ();
5014 // Put onto redo stack
5015 redo_actions.Push(action);
5018 switch(action.type) {
5020 case ActionType.UserActionBegin:
5021 user_action_finished = true;
5024 case ActionType.UserActionEnd:
5028 case ActionType.InsertString:
5029 start = document.GetLine (action.line_no);
5030 document.SuspendUpdate ();
5031 document.DeleteMultiline (start, action.pos, ((string) action.data).Length + 1);
5032 document.PositionCaret (start, action.pos);
5033 document.SetSelectionToCaret (true);
5034 document.ResumeUpdate (true);
5037 case ActionType.Typing:
5038 start = document.GetLine (action.line_no);
5039 document.SuspendUpdate ();
5040 document.DeleteMultiline (start, action.pos, ((StringBuilder) action.data).Length);
5041 document.PositionCaret (start, action.pos);
5042 document.SetSelectionToCaret (true);
5043 document.ResumeUpdate (true);
5045 // This is an open ended operation, so only a single typing operation can be undone at once
5046 user_action_finished = true;
5049 case ActionType.DeleteString:
5050 start = document.GetLine (action.line_no);
5051 document.SuspendUpdate ();
5052 Insert (start, action.pos, (Line) action.data, true);
5053 document.ResumeUpdate (true);
5056 } while (!user_action_finished && undo_actions.Count > 0);
5061 internal void Redo ()
5064 bool user_action_finished = false;
5066 if (redo_actions.Count == 0)
5069 // You can't undo anything after redoing
5070 undo_actions.Clear ();
5077 action = (Action) redo_actions.Pop ();
5079 switch (action.type) {
5081 case ActionType.UserActionBegin:
5085 case ActionType.UserActionEnd:
5086 user_action_finished = true;
5089 case ActionType.InsertString:
5090 start = document.GetLine (action.line_no);
5091 document.SuspendUpdate ();
5092 start_index = document.LineTagToCharIndex (start, action.pos);
5093 document.InsertString (start, action.pos, (string) action.data);
5094 document.CharIndexToLineTag (start_index + ((string) action.data).Length,
5095 out document.caret.line, out document.caret.tag,
5096 out document.caret.pos);
5097 document.UpdateCaret ();
5098 document.SetSelectionToCaret (true);
5099 document.ResumeUpdate (true);
5102 case ActionType.Typing:
5103 start = document.GetLine (action.line_no);
5104 document.SuspendUpdate ();
5105 start_index = document.LineTagToCharIndex (start, action.pos);
5106 document.InsertString (start, action.pos, ((StringBuilder) action.data).ToString ());
5107 document.CharIndexToLineTag (start_index + ((StringBuilder) action.data).Length,
5108 out document.caret.line, out document.caret.tag,
5109 out document.caret.pos);
5110 document.UpdateCaret ();
5111 document.SetSelectionToCaret (true);
5112 document.ResumeUpdate (true);
5114 // This is an open ended operation, so only a single typing operation can be undone at once
5115 user_action_finished = true;
5118 case ActionType.DeleteString:
5119 start = document.GetLine (action.line_no);
5120 document.SuspendUpdate ();
5121 document.DeleteMultiline (start, action.pos, ((Line) action.data).text.Length);
5122 document.PositionCaret (start, action.pos);
5123 document.SetSelectionToCaret (true);
5124 document.ResumeUpdate (true);
5128 } while (!user_action_finished && redo_actions.Count > 0);
5132 #endregion // Internal Methods
5134 #region Private Methods
5136 public void BeginUserAction (string name)
5141 Action ua = new Action ();
5142 ua.type = ActionType.UserActionBegin;
5145 undo_actions.Push (ua);
5148 public void EndUserAction ()
5153 Action ua = new Action ();
5154 ua.type = ActionType.UserActionEnd;
5156 undo_actions.Push (ua);
5159 // start_pos, end_pos = 1 based
5160 public void RecordDeleteString (Line start_line, int start_pos, Line end_line, int end_pos)
5165 Action a = new Action ();
5167 // We cant simply store the string, because then formatting would be lost
5168 a.type = ActionType.DeleteString;
5169 a.line_no = start_line.line_no;
5171 a.data = Duplicate (start_line, start_pos, end_line, end_pos);
5173 undo_actions.Push(a);
5176 public void RecordInsertString (Line line, int pos, string str)
5178 if (locked || str.Length == 0)
5181 Action a = new Action ();
5183 a.type = ActionType.InsertString;
5185 a.line_no = line.line_no;
5188 undo_actions.Push (a);
5191 public void RecordTyping (Line line, int pos, char ch)
5198 if (undo_actions.Count > 0)
5199 a = (Action) undo_actions.Peek ();
5201 if (a == null || a.type != ActionType.Typing) {
5203 a.type = ActionType.Typing;
5204 a.data = new StringBuilder ();
5205 a.line_no = line.line_no;
5208 undo_actions.Push (a);
5211 StringBuilder data = (StringBuilder) a.data;
5215 // start_pos = 1-based
5216 // end_pos = 1-based
5217 public Line Duplicate(Line start_line, int start_pos, Line end_line, int end_pos)
5223 LineTag current_tag;
5228 line = new Line (start_line.document, start_line.ending);
5231 for (int i = start_line.line_no; i <= end_line.line_no; i++) {
5232 current = document.GetLine(i);
5234 if (start_line.line_no == i) {
5240 if (end_line.line_no == i) {
5243 end = current.text.Length;
5250 line.text = new StringBuilder (current.text.ToString (start, end - start));
5252 // Copy tags from start to start+length onto new line
5253 current_tag = current.FindTag (start);
5254 while ((current_tag != null) && (current_tag.start < end)) {
5255 if ((current_tag.start <= start) && (start < (current_tag.start + current_tag.length))) {
5256 // start tag is within this tag
5259 tag_start = current_tag.start;
5262 tag = new LineTag(line, tag_start - start + 1);
5263 tag.CopyFormattingFrom (current_tag);
5265 current_tag = current_tag.next;
5267 // Add the new tag to the line
5268 if (line.tags == null) {
5274 while (tail.next != null) {
5278 tag.previous = tail;
5282 if ((i + 1) <= end_line.line_no) {
5283 line.ending = current.ending;
5285 // Chain them (we use right/left as next/previous)
5286 line.right = new Line (start_line.document, start_line.ending);
5287 line.right.left = line;
5295 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
5296 internal void Insert(Line line, int pos, Line insert, bool select)
5304 // Handle special case first
5305 if (insert.right == null) {
5307 // Single line insert
5308 document.Split(line, pos);
5310 if (insert.tags == null) {
5311 return; // Blank line
5314 //Insert our tags at the end
5317 while (tag.next != null) {
5321 offset = tag.start + tag.length - 1;
5323 tag.next = insert.tags;
5324 line.text.Insert(offset, insert.text.ToString());
5326 // Adjust start locations
5328 while (tag != null) {
5329 tag.start += offset;
5333 // Put it back together
5334 document.Combine(line.line_no, line.line_no + 1);
5337 document.SetSelectionStart (line, pos, false);
5338 document.SetSelectionEnd (line, pos + insert.text.Length, false);
5341 document.UpdateView(line, pos);
5349 while (current != null) {
5350 if (current == insert) {
5351 // Inserting the first line we split the line (and make space)
5352 document.Split(line, pos);
5353 //Insert our tags at the end of the line
5357 while (tag.next != null) {
5360 offset = tag.start + tag.length - 1;
5361 tag.next = current.tags;
5362 tag.next.previous = tag;
5368 line.tags = current.tags;
5369 line.tags.previous = null;
5373 document.Split(line.line_no, 0);
5375 line.tags = current.tags;
5376 line.tags.previous = null;
5379 // Adjust start locations and line pointers
5380 while (tag != null) {
5381 tag.start += offset;
5386 line.text.Insert(offset, current.text.ToString());
5387 line.Grow(line.text.Length);
5390 line = document.GetLine(line.line_no + 1);
5392 // FIXME? Test undo of line-boundaries
5393 if ((current.right == null) && (current.tags.length != 0)) {
5394 document.Combine(line.line_no - 1, line.line_no);
5396 current = current.right;
5401 // Recalculate our document
5402 document.UpdateView(first, lines, pos);
5405 #endregion // Private Methods