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 // Being cloneable should allow for nice line and document copies...
107 internal class Line : ICloneable, IComparable {
108 #region Local Variables
110 internal Document document;
112 // Stuff that matters for our line
113 internal StringBuilder text; // Characters for the line
114 internal float[] widths; // Width of each character; always one larger than text.Length
115 internal int space; // Number of elements in text and widths
116 internal int line_no; // Line number
117 internal LineTag tags; // Tags describing the text
118 internal int offset; // Baseline can be on the X or Y axis depending if we are in multiline mode or not
119 internal int height; // Height of the line (height of tallest tag)
120 internal int ascent; // Ascent of the line (ascent of the tallest tag)
121 internal HorizontalAlignment alignment; // Alignment of the line
122 internal int align_shift; // Pixel shift caused by the alignment
123 internal bool soft_break; // Tag is 'broken soft' and continuation from previous line
124 internal int indent; // Left indent for the first line
125 internal int hanging_indent; // Hanging indent (left indent for all but the first line)
126 internal int right_indent; // Right indent for all lines
127 internal bool carriage_return;
130 // Stuff that's important for the tree
131 internal Line parent; // Our parent line
132 internal Line left; // Line with smaller line number
133 internal Line right; // Line with higher line number
134 internal LineColor color; // We're doing a black/red tree. this is the node color
135 internal int DEFAULT_TEXT_LEN; //
136 internal bool recalc; // Line changed
137 internal int left_margin = 2; // A left margin for all lines
138 internal int top_margin = 2;
139 internal int right_margin = 2;
140 #endregion // Local Variables
143 internal Line (Document document)
145 this.document = document;
146 color = LineColor.Red;
153 alignment = document.alignment;
156 internal Line(Document document, int LineNo, string Text, Font font, SolidBrush color) : this (document) {
157 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
159 text = new StringBuilder(Text, space);
162 widths = new float[space + 1];
163 tags = new LineTag(this, 1);
168 internal Line(Document document, int LineNo, string Text, HorizontalAlignment align, Font font, SolidBrush color) : this(document) {
169 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
171 text = new StringBuilder(Text, space);
175 widths = new float[space + 1];
176 tags = new LineTag(this, 1);
181 internal Line(Document document, int LineNo, string Text, LineTag tag) : this(document) {
182 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
184 text = new StringBuilder(Text, space);
187 widths = new float[space + 1];
191 #endregion // Constructors
193 #region Internal Properties
197 int tm = document.owner.actual_border_style == BorderStyle.FixedSingle ? top_margin : 0;
198 if (!document.multiline)
206 if (document.multiline)
208 return offset + align_shift;
214 int res = (int) widths [text.Length];
215 if (!document.multiline) {
222 internal int Indent {
233 internal int HangingIndent {
235 return hanging_indent;
239 hanging_indent = value;
244 internal int RightIndent {
250 right_indent = value;
256 internal int Height {
266 internal int LineNo {
276 internal string Text {
278 return text.ToString();
282 text = new StringBuilder(value, value.Length > DEFAULT_TEXT_LEN ? value.Length : DEFAULT_TEXT_LEN);
286 internal HorizontalAlignment Alignment {
292 if (alignment != value) {
299 internal StringBuilder Text {
309 #endregion // Internal Properties
311 #region Internal Methods
312 // Make sure we always have enoughs space in text and widths
313 internal void Grow(int minimum) {
317 length = text.Length;
319 if ((length + minimum) > space) {
320 // We need to grow; double the size
322 if ((length + minimum) > (space * 2)) {
323 new_widths = new float[length + minimum * 2 + 1];
324 space = length + minimum * 2;
326 new_widths = new float[space * 2 + 1];
329 widths.CopyTo(new_widths, 0);
335 internal void Streamline(int lines) {
343 // Catch what the loop below wont; eliminate 0 length
344 // tags, but only if there are other tags after us
345 // We only eliminate text tags if there is another text tag
346 // after it. Otherwise we wind up trying to type on picture tags
348 while ((current.length == 0) && (next != null) && (next.IsTextTag)) {
350 tags.previous = null;
360 while (next != null) {
361 // Take out 0 length tags unless it's the last tag in the document
362 if (current.IsTextTag && next.length == 0 && next.IsTextTag) {
363 if ((next.next != null) || (line_no != lines)) {
364 current.next = next.next;
365 if (current.next != null) {
366 current.next.previous = current;
372 if (current.Combine(next)) {
377 current = current.next;
382 /// <summary> Find the tag on a line based on the character position, pos is 0-based</summary>
383 internal LineTag FindTag(int pos) {
392 if (pos >= text.Length) {
393 pos = text.Length - 1;
396 while (tag != null) {
397 if (((tag.start - 1) <= pos) && (pos < (tag.start + tag.length - 1))) {
398 return LineTag.GetFinalTag (tag);
406 /// Recalculate a single line using the same char for every character in the line
409 internal bool RecalculatePasswordLine(Graphics g, Document doc) {
418 len = this.text.Length;
424 widths[0] = left_margin + indent;
426 w = g.MeasureString(doc.password_char, tags.font, 10000, Document.string_format).Width;
428 if (this.height != (int)tag.font.Height) {
434 this.height = (int)tag.font.Height;
435 tag.height = this.height;
437 XplatUI.GetFontMetrics(g, tag.font, out tag.ascent, out descent);
438 this.ascent = tag.ascent;
442 widths[pos] = widths[pos-1] + w;
449 /// Go through all tags on a line and recalculate all size-related values;
450 /// returns true if lineheight changed
452 internal bool RecalculateLine(Graphics g, Document doc) {
465 len = this.text.Length;
467 prev_height = this.height; // For drawing optimization calculations
468 this.height = 0; // Reset line height
469 this.ascent = 0; // Reset the ascent for the line
472 if (this.soft_break) {
473 widths[0] = left_margin + hanging_indent;
475 widths[0] = left_margin + indent;
486 while (tag.length == 0) { // We should always have tags after a tag.length==0 unless len==0
492 size = tag.SizeOfPosition (g, pos);
495 if (Char.IsWhiteSpace(text[pos])) {
500 if ((wrap_pos > 0) && (wrap_pos != len) && (widths[pos] + w) + 5 > (doc.viewport_width - this.right_indent)) {
501 // Make sure to set the last width of the line before wrapping
502 widths [pos + 1] = widths [pos] + w;
506 doc.Split(this, tag, pos, this.soft_break);
507 this.soft_break = true;
508 len = this.text.Length;
512 } else if (pos > 1 && (widths[pos] + w) > (doc.viewport_width - this.right_indent)) {
513 // No suitable wrap position was found so break right in the middle of a word
515 // Make sure to set the last width of the line before wrapping
516 widths [pos + 1] = widths [pos] + w;
518 doc.Split(this, tag, pos, this.soft_break);
519 this.soft_break = true;
520 len = this.text.Length;
526 // Contract all soft lines that follow back into our line
530 widths[pos] = widths[pos-1] + w;
533 line = doc.GetLine(this.line_no + 1);
534 if ((line != null) && soft_break) {
535 // Pull the two lines together
536 doc.Combine(this.line_no, this.line_no + 1);
537 len = this.text.Length;
543 if (pos == (tag.start-1 + tag.length)) {
544 // We just found the end of our current tag
545 tag.height = tag.MaxHeight ();
547 // Check if we're the tallest on the line (so far)
548 if (tag.height > this.height) {
549 this.height = tag.height; // Yep; make sure the line knows
552 if (tag.ascent == 0) {
555 XplatUI.GetFontMetrics(g, tag.font, out tag.ascent, out descent);
558 if (tag.ascent > this.ascent) {
561 // We have a tag that has a taller ascent than the line;
563 while (t != null && t != tag) {
564 t.shift = tag.ascent - t.ascent;
569 this.ascent = tag.ascent;
571 tag.shift = this.ascent - tag.ascent;
582 if (this.height == 0) {
583 this.height = tags.font.Height;
584 tag.height = this.height;
587 if (prev_height != this.height) {
592 #endregion // Internal Methods
594 #region Administrative
595 public int CompareTo(object obj) {
600 if (! (obj is Line)) {
601 throw new ArgumentException("Object is not of type Line", "obj");
604 if (line_no < ((Line)obj).line_no) {
606 } else if (line_no > ((Line)obj).line_no) {
613 public object Clone() {
616 clone = new Line (document);
621 clone.left = (Line)left.Clone();
625 clone.left = (Line)left.Clone();
631 internal object CloneLine() {
634 clone = new Line (document);
641 public override bool Equals(object obj) {
646 if (!(obj is Line)) {
654 if (line_no == ((Line)obj).line_no) {
661 public override int GetHashCode() {
662 return base.GetHashCode ();
665 public override string ToString() {
666 return "Line " + line_no;
669 #endregion // Administrative
672 internal class Document : ICloneable, IEnumerable {
674 // FIXME - go through code and check for places where
675 // we do explicit comparisons instead of using the compare overloads
676 internal struct Marker {
678 internal LineTag tag;
682 public static bool operator<(Marker lhs, Marker rhs) {
683 if (lhs.line.line_no < rhs.line.line_no) {
687 if (lhs.line.line_no == rhs.line.line_no) {
688 if (lhs.pos < rhs.pos) {
695 public static bool operator>(Marker lhs, Marker rhs) {
696 if (lhs.line.line_no > rhs.line.line_no) {
700 if (lhs.line.line_no == rhs.line.line_no) {
701 if (lhs.pos > rhs.pos) {
708 public static bool operator==(Marker lhs, Marker rhs) {
709 if ((lhs.line.line_no == rhs.line.line_no) && (lhs.pos == rhs.pos)) {
715 public static bool operator!=(Marker lhs, Marker rhs) {
716 if ((lhs.line.line_no != rhs.line.line_no) || (lhs.pos != rhs.pos)) {
722 public void Combine(Line move_to_line, int move_to_line_length) {
724 pos += move_to_line_length;
725 tag = LineTag.FindTag(line, pos);
728 // This is for future use, right now Document.Split does it by hand, with some added shortcut logic
729 public void Split(Line move_to_line, int split_at) {
732 tag = LineTag.FindTag(line, pos);
735 public override bool Equals(object obj) {
736 return this==(Marker)obj;
739 public override int GetHashCode() {
740 return base.GetHashCode ();
743 public override string ToString() {
744 return "Marker Line " + line + ", Position " + pos;
748 #endregion Structures
750 #region Local Variables
751 private Line document;
753 private Line sentinel;
754 private int document_id;
755 private Random random = new Random();
756 internal string password_char;
757 private StringBuilder password_cache;
758 private bool calc_pass;
759 private int char_count;
761 // For calculating widths/heights
762 public static readonly StringFormat string_format = new StringFormat (StringFormat.GenericTypographic);
764 private int recalc_suspended;
765 private bool recalc_pending;
766 private int recalc_start = 1; // This starts at one, since lines are 1 based
767 private int recalc_end;
768 private bool recalc_optimize;
770 private int update_suspended;
771 private bool update_pending;
772 private int update_start = 1;
774 internal bool multiline;
775 internal HorizontalAlignment alignment;
778 internal UndoManager undo;
780 internal Marker caret;
781 internal Marker selection_start;
782 internal Marker selection_end;
783 internal bool selection_visible;
784 internal Marker selection_anchor;
785 internal Marker selection_prev;
786 internal bool selection_end_anchor;
788 internal int viewport_x;
789 internal int viewport_y; // The visible area of the document
790 internal int viewport_width;
791 internal int viewport_height;
793 internal int document_x; // Width of the document
794 internal int document_y; // Height of the document
796 internal Rectangle invalid;
798 internal int crlf_size; // 1 or 2, depending on whether we use \r\n or just \n
800 internal TextBoxBase owner; // Who's owning us?
801 static internal int caret_width = 1;
802 static internal int caret_shift = 1;
803 #endregion // Local Variables
806 internal Document(TextBoxBase owner) {
814 recalc_pending = false;
816 // Tree related stuff
817 sentinel = new Line (this);
818 sentinel.color = LineColor.Black;
822 // We always have a blank line
823 owner.HandleCreated += new EventHandler(owner_HandleCreated);
824 owner.VisibleChanged += new EventHandler(owner_VisibleChanged);
826 Add(1, "", owner.Font, ThemeEngine.Current.ResPool.GetSolidBrush(owner.ForeColor));
827 Line l = GetLine (1);
830 undo = new UndoManager (this);
832 selection_visible = false;
833 selection_start.line = this.document;
834 selection_start.pos = 0;
835 selection_start.tag = selection_start.line.tags;
836 selection_end.line = this.document;
837 selection_end.pos = 0;
838 selection_end.tag = selection_end.line.tags;
839 selection_anchor.line = this.document;
840 selection_anchor.pos = 0;
841 selection_anchor.tag = selection_anchor.line.tags;
842 caret.line = this.document;
844 caret.tag = caret.line.tags;
851 // Default selection is empty
853 document_id = random.Next();
855 string_format.Trimming = StringTrimming.None;
856 string_format.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;
860 #region Internal Properties
877 internal Line CaretLine {
883 internal int CaretPosition {
889 internal Point Caret {
891 return new Point((int)caret.tag.line.widths[caret.pos] + caret.line.X, caret.line.Y);
895 internal LineTag CaretTag {
905 internal int CRLFSize {
915 internal string PasswordChar {
917 return password_char;
921 password_char = value;
922 PasswordCache.Length = 0;
923 if ((password_char.Length != 0) && (password_char[0] != '\0')) {
931 private StringBuilder PasswordCache {
933 if (password_cache == null)
934 password_cache = new StringBuilder();
935 return password_cache;
939 internal int ViewPortX {
949 internal int Length {
951 return char_count + lines - 1; // Add \n for each line but the last
955 private int CharCount {
963 if (LengthChanged != null) {
964 LengthChanged(this, EventArgs.Empty);
969 internal int ViewPortY {
979 internal int ViewPortWidth {
981 return viewport_width;
985 viewport_width = value;
989 internal int ViewPortHeight {
991 return viewport_height;
995 viewport_height = value;
1000 internal int Width {
1002 return this.document_x;
1006 internal int Height {
1008 return this.document_y;
1012 internal bool SelectionVisible {
1014 return selection_visible;
1018 internal bool Wrap {
1028 #endregion // Internal Properties
1030 #region Private Methods
1032 internal void SuspendRecalc ()
1037 internal void ResumeRecalc (bool immediate_update)
1039 if (recalc_suspended > 0)
1042 if (immediate_update && recalc_suspended == 0 && recalc_pending) {
1043 RecalculateDocument (owner.CreateGraphicsInternal(), recalc_start, recalc_end, recalc_optimize);
1044 recalc_pending = false;
1048 internal void SuspendUpdate ()
1053 internal void ResumeUpdate (bool immediate_update)
1055 if (update_suspended > 0)
1058 if (immediate_update && update_suspended == 0 && update_pending) {
1059 UpdateView (GetLine (update_start), 0);
1060 update_pending = false;
1065 internal int DumpTree(Line line, bool with_tags) {
1070 Console.Write("Line {0} [# {1}], Y: {2}, soft: {3}, Text: '{4}'",
1071 line.line_no, line.GetHashCode(), line.Y, line.soft_break,
1072 line.text != null ? line.text.ToString() : "undefined");
1074 if (line.left == sentinel) {
1075 Console.Write(", left = sentinel");
1076 } else if (line.left == null) {
1077 Console.Write(", left = NULL");
1080 if (line.right == sentinel) {
1081 Console.Write(", right = sentinel");
1082 } else if (line.right == null) {
1083 Console.Write(", right = NULL");
1086 Console.WriteLine("");
1096 Console.Write(" Tags: ");
1097 while (tag != null) {
1098 Console.Write("{0} <{1}>-<{2}>", count++, tag.start, tag.end
1099 /*line.text.ToString (tag.start - 1, tag.length)*/);
1100 length += tag.length;
1102 if (tag.line != line) {
1103 Console.Write("BAD line link");
1104 throw new Exception("Bad line link in tree");
1108 Console.Write(", ");
1111 if (length > line.text.Length) {
1112 throw new Exception(String.Format("Length of tags more than length of text on line (expected {0} calculated {1})", line.text.Length, length));
1113 } else if (length < line.text.Length) {
1114 throw new Exception(String.Format("Length of tags less than length of text on line (expected {0} calculated {1})", line.text.Length, length));
1116 Console.WriteLine("");
1118 if (line.left != null) {
1119 if (line.left != sentinel) {
1120 total += DumpTree(line.left, with_tags);
1123 if (line != sentinel) {
1124 throw new Exception("Left should not be NULL");
1128 if (line.right != null) {
1129 if (line.right != sentinel) {
1130 total += DumpTree(line.right, with_tags);
1133 if (line != sentinel) {
1134 throw new Exception("Right should not be NULL");
1138 for (int i = 1; i <= this.lines; i++) {
1139 if (GetLine(i) == null) {
1140 throw new Exception(String.Format("Hole in line order, missing {0}", i));
1144 if (line == this.Root) {
1145 if (total < this.lines) {
1146 throw new Exception(String.Format("Not enough nodes in tree, found {0}, expected {1}", total, this.lines));
1147 } else if (total > this.lines) {
1148 throw new Exception(String.Format("Too many nodes in tree, found {0}, expected {1}", total, this.lines));
1155 private void SetSelectionVisible (bool value)
1157 selection_visible = value;
1159 // cursor and selection are enemies, we can't have both in the same room at the same time
1160 if (owner.IsHandleCreated && !owner.show_caret_w_selection)
1161 XplatUI.CaretVisible (owner.Handle, !selection_visible);
1164 private void DecrementLines(int line_no) {
1168 while (current <= lines) {
1169 GetLine(current).line_no--;
1175 private void IncrementLines(int line_no) {
1178 current = this.lines;
1179 while (current >= line_no) {
1180 GetLine(current).line_no++;
1186 private void RebalanceAfterAdd(Line line1) {
1189 while ((line1 != document) && (line1.parent.color == LineColor.Red)) {
1190 if (line1.parent == line1.parent.parent.left) {
1191 line2 = line1.parent.parent.right;
1193 if ((line2 != null) && (line2.color == LineColor.Red)) {
1194 line1.parent.color = LineColor.Black;
1195 line2.color = LineColor.Black;
1196 line1.parent.parent.color = LineColor.Red;
1197 line1 = line1.parent.parent;
1199 if (line1 == line1.parent.right) {
1200 line1 = line1.parent;
1204 line1.parent.color = LineColor.Black;
1205 line1.parent.parent.color = LineColor.Red;
1207 RotateRight(line1.parent.parent);
1210 line2 = line1.parent.parent.left;
1212 if ((line2 != null) && (line2.color == LineColor.Red)) {
1213 line1.parent.color = LineColor.Black;
1214 line2.color = LineColor.Black;
1215 line1.parent.parent.color = LineColor.Red;
1216 line1 = line1.parent.parent;
1218 if (line1 == line1.parent.left) {
1219 line1 = line1.parent;
1223 line1.parent.color = LineColor.Black;
1224 line1.parent.parent.color = LineColor.Red;
1225 RotateLeft(line1.parent.parent);
1229 document.color = LineColor.Black;
1232 private void RebalanceAfterDelete(Line line1) {
1235 while ((line1 != document) && (line1.color == LineColor.Black)) {
1236 if (line1 == line1.parent.left) {
1237 line2 = line1.parent.right;
1238 if (line2.color == LineColor.Red) {
1239 line2.color = LineColor.Black;
1240 line1.parent.color = LineColor.Red;
1241 RotateLeft(line1.parent);
1242 line2 = line1.parent.right;
1244 if ((line2.left.color == LineColor.Black) && (line2.right.color == LineColor.Black)) {
1245 line2.color = LineColor.Red;
1246 line1 = line1.parent;
1248 if (line2.right.color == LineColor.Black) {
1249 line2.left.color = LineColor.Black;
1250 line2.color = LineColor.Red;
1252 line2 = line1.parent.right;
1254 line2.color = line1.parent.color;
1255 line1.parent.color = LineColor.Black;
1256 line2.right.color = LineColor.Black;
1257 RotateLeft(line1.parent);
1261 line2 = line1.parent.left;
1262 if (line2.color == LineColor.Red) {
1263 line2.color = LineColor.Black;
1264 line1.parent.color = LineColor.Red;
1265 RotateRight(line1.parent);
1266 line2 = line1.parent.left;
1268 if ((line2.right.color == LineColor.Black) && (line2.left.color == LineColor.Black)) {
1269 line2.color = LineColor.Red;
1270 line1 = line1.parent;
1272 if (line2.left.color == LineColor.Black) {
1273 line2.right.color = LineColor.Black;
1274 line2.color = LineColor.Red;
1276 line2 = line1.parent.left;
1278 line2.color = line1.parent.color;
1279 line1.parent.color = LineColor.Black;
1280 line2.left.color = LineColor.Black;
1281 RotateRight(line1.parent);
1286 line1.color = LineColor.Black;
1289 private void RotateLeft(Line line1) {
1290 Line line2 = line1.right;
1292 line1.right = line2.left;
1294 if (line2.left != sentinel) {
1295 line2.left.parent = line1;
1298 if (line2 != sentinel) {
1299 line2.parent = line1.parent;
1302 if (line1.parent != null) {
1303 if (line1 == line1.parent.left) {
1304 line1.parent.left = line2;
1306 line1.parent.right = line2;
1313 if (line1 != sentinel) {
1314 line1.parent = line2;
1318 private void RotateRight(Line line1) {
1319 Line line2 = line1.left;
1321 line1.left = line2.right;
1323 if (line2.right != sentinel) {
1324 line2.right.parent = line1;
1327 if (line2 != sentinel) {
1328 line2.parent = line1.parent;
1331 if (line1.parent != null) {
1332 if (line1 == line1.parent.right) {
1333 line1.parent.right = line2;
1335 line1.parent.left = line2;
1341 line2.right = line1;
1342 if (line1 != sentinel) {
1343 line1.parent = line2;
1348 internal void UpdateView(Line line, int pos) {
1349 if (!owner.IsHandleCreated) {
1353 if (update_suspended > 0) {
1354 update_start = Math.Min (update_start, line.line_no);
1355 // update_end = Math.Max (update_end, line.line_no);
1356 // recalc_optimize = true;
1357 update_pending = true;
1361 // Optimize invalidation based on Line alignment
1362 if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no, true)) {
1363 // Lineheight changed, invalidate the rest of the document
1364 if ((line.Y - viewport_y) >=0 ) {
1365 // We formatted something that's in view, only draw parts of the screen
1366 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, owner.Height - line.Y - viewport_y));
1368 // The tag was above the visible area, draw everything
1372 switch(line.alignment) {
1373 case HorizontalAlignment.Left: {
1374 owner.Invalidate(new Rectangle(line.X + (int)line.widths[pos] - viewport_x - 1, line.Y - viewport_y, viewport_width, line.height + 1));
1378 case HorizontalAlignment.Center: {
1379 owner.Invalidate(new Rectangle(line.X, line.Y - viewport_y, viewport_width, line.height + 1));
1383 case HorizontalAlignment.Right: {
1384 owner.Invalidate(new Rectangle(line.X, line.Y - viewport_y, (int)line.widths[pos + 1] - viewport_x + line.X, line.height + 1));
1392 // Update display from line, down line_count lines; pos is unused, but required for the signature
1393 internal void UpdateView(Line line, int line_count, int pos) {
1394 if (!owner.IsHandleCreated) {
1398 if (recalc_suspended > 0) {
1399 recalc_start = Math.Min (recalc_start, line.line_no);
1400 recalc_end = Math.Max (recalc_end, line.line_no + line_count - 1);
1401 recalc_optimize = true;
1402 recalc_pending = true;
1406 if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no + line_count - 1, true)) {
1407 // Lineheight changed, invalidate the rest of the document
1408 if ((line.Y - viewport_y) >=0 ) {
1409 // We formatted something that's in view, only draw parts of the screen
1410 //blah Console.WriteLine("TextControl.cs(981) Invalidate called in UpdateView(line, line_count, pos)");
1411 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, owner.Height - line.Y - viewport_y));
1413 // The tag was above the visible area, draw everything
1414 //blah Console.WriteLine("TextControl.cs(985) Invalidate called in UpdateView(line, line_count, pos)");
1420 end_line = GetLine(line.line_no + line_count -1);
1421 if (end_line == null) {
1425 //blah Console.WriteLine("TextControl.cs(996) Invalidate called in UpdateView(line, line_count, pos)");
1426 owner.Invalidate(new Rectangle(0 - viewport_x, line.Y - viewport_y, (int)line.widths[line.text.Length], end_line.Y + end_line.height));
1429 #endregion // Private Methods
1431 #region Internal Methods
1432 // Clear the document and reset state
1433 internal void Empty() {
1435 document = sentinel;
1438 // We always have a blank line
1439 Add(1, "", owner.Font, ThemeEngine.Current.ResPool.GetSolidBrush(owner.ForeColor));
1440 Line l = GetLine (1);
1441 l.soft_break = true;
1443 this.RecalculateDocument(owner.CreateGraphicsInternal());
1444 PositionCaret(0, 0);
1446 SetSelectionVisible (false);
1448 selection_start.line = this.document;
1449 selection_start.pos = 0;
1450 selection_start.tag = selection_start.line.tags;
1451 selection_end.line = this.document;
1452 selection_end.pos = 0;
1453 selection_end.tag = selection_end.line.tags;
1462 if (owner.IsHandleCreated)
1463 owner.Invalidate ();
1466 internal void PositionCaret(Line line, int pos) {
1467 caret.tag = line.FindTag (pos);
1469 MoveCaretToTextTag ();
1474 if (owner.IsHandleCreated) {
1475 if (owner.Focused) {
1476 if (caret.height != caret.tag.height)
1477 XplatUI.CreateCaret (owner.Handle, caret_width, caret.height);
1478 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);
1481 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1484 // We set this at the end because we use the heights to determine whether or
1485 // not we need to recreate the caret
1486 caret.height = caret.tag.height;
1490 internal void PositionCaret(int x, int y) {
1491 if (!owner.IsHandleCreated) {
1495 caret.tag = FindCursor(x, y, out caret.pos);
1497 MoveCaretToTextTag ();
1499 caret.line = caret.tag.line;
1500 caret.height = caret.tag.height;
1502 if (owner.Focused) {
1503 XplatUI.CreateCaret (owner.Handle, caret_width, caret.height);
1504 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);
1507 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1510 internal void CaretHasFocus() {
1511 if ((caret.tag != null) && owner.IsHandleCreated) {
1512 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1513 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);
1518 if (owner.IsHandleCreated && selection_visible) {
1519 InvalidateSelectionArea ();
1523 internal void CaretLostFocus() {
1524 if (!owner.IsHandleCreated) {
1527 XplatUI.DestroyCaret(owner.Handle);
1530 internal void AlignCaret() {
1531 if (!owner.IsHandleCreated) {
1535 caret.tag = LineTag.FindTag (caret.line, caret.pos);
1537 MoveCaretToTextTag ();
1539 caret.height = caret.tag.height;
1541 if (owner.Focused) {
1542 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1543 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);
1547 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1550 internal void UpdateCaret() {
1551 if (!owner.IsHandleCreated || caret.tag == null) {
1555 MoveCaretToTextTag ();
1557 if (caret.tag.height != caret.height) {
1558 caret.height = caret.tag.height;
1559 if (owner.Focused) {
1560 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1564 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);
1568 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1571 internal void DisplayCaret() {
1572 if (!owner.IsHandleCreated) {
1576 if (owner.ShowSelection && (!selection_visible || owner.show_caret_w_selection)) {
1577 XplatUI.CaretVisible(owner.Handle, true);
1581 internal void HideCaret() {
1582 if (!owner.IsHandleCreated) {
1586 if (owner.Focused) {
1587 XplatUI.CaretVisible(owner.Handle, false);
1592 internal void MoveCaretToTextTag ()
1594 if (caret.tag == null || caret.tag.IsTextTag)
1599 if (caret.pos < caret.tag.start) {
1600 caret.tag = caret.tag.previous;
1602 caret.tag = caret.tag.next;
1606 internal void MoveCaret(CaretDirection direction) {
1607 // FIXME should we use IsWordSeparator to detect whitespace, instead
1608 // of looking for actual spaces in the Word move cases?
1610 bool nowrap = false;
1612 case CaretDirection.CharForwardNoWrap:
1614 goto case CaretDirection.CharForward;
1615 case CaretDirection.CharForward: {
1617 if (caret.pos > caret.line.text.Length) {
1619 // Go into next line
1620 if (caret.line.line_no < this.lines) {
1621 caret.line = GetLine(caret.line.line_no+1);
1623 caret.tag = caret.line.tags;
1628 // Single line; we stay where we are
1632 if ((caret.tag.start - 1 + caret.tag.length) < caret.pos) {
1633 caret.tag = caret.tag.next;
1640 case CaretDirection.CharBackNoWrap:
1642 goto case CaretDirection.CharBack;
1643 case CaretDirection.CharBack: {
1644 if (caret.pos > 0) {
1645 // caret.pos--; // folded into the if below
1647 if (--caret.pos > 0) {
1648 if (caret.tag.start > caret.pos) {
1649 caret.tag = caret.tag.previous;
1653 if (caret.line.line_no > 1 && !nowrap) {
1654 caret.line = GetLine(caret.line.line_no - 1);
1655 caret.pos = caret.line.text.Length;
1656 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1663 case CaretDirection.WordForward: {
1666 len = caret.line.text.Length;
1667 if (caret.pos < len) {
1668 while ((caret.pos < len) && (caret.line.text[caret.pos] != ' ')) {
1671 if (caret.pos < len) {
1672 // Skip any whitespace
1673 while ((caret.pos < len) && (caret.line.text[caret.pos] == ' ')) {
1677 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1679 if (caret.line.line_no < this.lines) {
1680 caret.line = GetLine(caret.line.line_no + 1);
1682 caret.tag = caret.line.tags;
1689 case CaretDirection.WordBack: {
1690 if (caret.pos > 0) {
1693 while ((caret.pos > 0) && (caret.line.text[caret.pos] == ' ')) {
1697 while ((caret.pos > 0) && (caret.line.text[caret.pos] != ' ')) {
1701 if (caret.line.text.ToString(caret.pos, 1) == " ") {
1702 if (caret.pos != 0) {
1705 caret.line = GetLine(caret.line.line_no - 1);
1706 caret.pos = caret.line.text.Length;
1709 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1711 if (caret.line.line_no > 1) {
1712 caret.line = GetLine(caret.line.line_no - 1);
1713 caret.pos = caret.line.text.Length;
1714 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1721 case CaretDirection.LineUp: {
1722 if (caret.line.line_no > 1) {
1725 pixel = (int)caret.line.widths[caret.pos];
1726 PositionCaret(pixel, GetLine(caret.line.line_no - 1).Y);
1733 case CaretDirection.LineDown: {
1734 if (caret.line.line_no < lines) {
1737 pixel = (int)caret.line.widths[caret.pos];
1738 PositionCaret(pixel, GetLine(caret.line.line_no + 1).Y);
1745 case CaretDirection.Home: {
1746 if (caret.pos > 0) {
1748 caret.tag = caret.line.tags;
1754 case CaretDirection.End: {
1755 if (caret.pos < caret.line.text.Length) {
1756 caret.pos = caret.line.text.Length;
1757 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1763 case CaretDirection.PgUp: {
1765 int new_y, y_offset;
1767 if (viewport_y == 0) {
1769 // This should probably be handled elsewhere
1770 if (!(owner is RichTextBox)) {
1771 // Page down doesn't do anything in a regular TextBox
1772 // if the bottom of the document
1773 // is already visible, the page and the caret stay still
1777 // We're just placing the caret at the end of the document, no scrolling needed
1778 owner.vscroll.Value = 0;
1779 Line line = GetLine (1);
1780 PositionCaret (line, 0);
1783 y_offset = caret.line.Y - viewport_y;
1784 new_y = caret.line.Y - viewport_height;
1786 owner.vscroll.Value = Math.Max (new_y, 0);
1787 PositionCaret ((int)caret.line.widths[caret.pos], y_offset + viewport_y);
1791 case CaretDirection.PgDn: {
1792 int new_y, y_offset;
1794 if ((viewport_y + viewport_height) > document_y) {
1796 // This should probably be handled elsewhere
1797 if (!(owner is RichTextBox)) {
1798 // Page up doesn't do anything in a regular TextBox
1799 // if the bottom of the document
1800 // is already visible, the page and the caret stay still
1804 // We're just placing the caret at the end of the document, no scrolling needed
1805 owner.vscroll.Value = owner.vscroll.Maximum - viewport_height + 1;
1806 Line line = GetLine (lines);
1807 PositionCaret (line, line.Text.Length);
1810 y_offset = caret.line.Y - viewport_y;
1811 new_y = caret.line.Y + viewport_height;
1813 owner.vscroll.Value = Math.Min (new_y, owner.vscroll.Maximum - viewport_height + 1);
1814 PositionCaret ((int)caret.line.widths[caret.pos], y_offset + viewport_y);
1819 case CaretDirection.CtrlPgUp: {
1820 PositionCaret(0, viewport_y);
1825 case CaretDirection.CtrlPgDn: {
1830 tag = FindTag(0, viewport_y + viewport_height, out index, false);
1831 if (tag.line.line_no > 1) {
1832 line = GetLine(tag.line.line_no - 1);
1836 PositionCaret(line, line.Text.Length);
1841 case CaretDirection.CtrlHome: {
1842 caret.line = GetLine(1);
1844 caret.tag = caret.line.tags;
1850 case CaretDirection.CtrlEnd: {
1851 caret.line = GetLine(lines);
1852 caret.pos = caret.line.text.Length;
1853 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1859 case CaretDirection.SelectionStart: {
1860 caret.line = selection_start.line;
1861 caret.pos = selection_start.pos;
1862 caret.tag = selection_start.tag;
1868 case CaretDirection.SelectionEnd: {
1869 caret.line = selection_end.line;
1870 caret.pos = selection_end.pos;
1871 caret.tag = selection_end.tag;
1879 internal void DumpDoc ()
1881 Console.WriteLine ("<doc>");
1882 for (int i = 1; i < lines; i++) {
1883 Line line = GetLine (i);
1884 Console.WriteLine ("<line no='{0}'>", line.line_no);
1886 LineTag tag = line.tags;
1887 while (tag != null) {
1888 Console.Write ("\t<tag type='{0}' span='{1}->{2}'>", tag.GetType (), tag.start, tag.length);
1889 Console.Write (tag.Text ());
1890 Console.WriteLine ("</tag>");
1893 Console.WriteLine ("</line>");
1895 Console.WriteLine ("</doc>");
1898 internal void Draw (Graphics g, Rectangle clip)
1900 Line line; // Current line being drawn
1901 LineTag tag; // Current tag being drawn
1902 int start; // First line to draw
1903 int end; // Last line to draw
1904 StringBuilder text; // String representing the current line
1907 Brush current_brush;
1908 Brush disabled_brush;
1909 Brush readonly_brush;
1913 // First, figure out from what line to what line we need to draw
1916 start = GetLineByPixel(clip.Top + viewport_y, false).line_no;
1917 end = GetLineByPixel(clip.Bottom + viewport_y, false).line_no;
1919 start = GetLineByPixel(clip.Left + viewport_x, false).line_no;
1920 end = GetLineByPixel(clip.Right + viewport_x, false).line_no;
1924 /// We draw the single border ourself
1926 if (owner.actual_border_style == BorderStyle.FixedSingle) {
1927 ControlPaint.DrawBorder (g, owner.Bounds, Color.Black, ButtonBorderStyle.Solid);
1930 /// Make sure that we aren't drawing one more line then we need to
1931 line = GetLine (end - 1);
1932 if (line != null && clip.Bottom == line.Y + line.height + viewport_y)
1938 DateTime n = DateTime.Now;
1939 Console.WriteLine ("Started drawing: {0}s {1}ms", n.Second, n.Millisecond);
1940 Console.WriteLine ("CLIP: {0}", clip);
1941 Console.WriteLine ("S: {0}", GetLine (start).text);
1942 Console.WriteLine ("E: {0}", GetLine (end).text);
1945 disabled_brush = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorGrayText);
1946 readonly_brush = ThemeEngine.Current.ResPool.GetSolidBrush (ThemeEngine.Current.ColorControlText);
1947 hilight = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorHighlight);
1948 hilight_text = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorHighlightText);
1950 // Non multiline selection can be handled outside of the loop
1951 if (!multiline && selection_visible && owner.ShowSelection) {
1952 g.FillRectangle (hilight,
1953 selection_start.line.widths [selection_start.pos] +
1954 selection_start.line.X - viewport_x,
1955 selection_start.line.Y,
1956 (selection_end.line.X + selection_end.line.widths [selection_end.pos]) -
1957 (selection_start.line.X + selection_start.line.widths [selection_start.pos]),
1958 selection_start.line.height);
1961 while (line_no <= end) {
1962 line = GetLine (line_no);
1963 float line_y = line.Y - viewport_y;
1969 if (PasswordCache.Length < line.text.Length)
1970 PasswordCache.Append(Char.Parse(password_char), line.text.Length - PasswordCache.Length);
1971 else if (PasswordCache.Length > line.text.Length)
1972 PasswordCache.Remove(line.text.Length, PasswordCache.Length - line.text.Length);
1973 text = PasswordCache;
1976 int line_selection_start = text.Length + 1;
1977 int line_selection_end = text.Length + 1;
1978 if (selection_visible && owner.ShowSelection &&
1979 (line_no >= selection_start.line.line_no) &&
1980 (line_no <= selection_end.line.line_no)) {
1982 if (line_no == selection_start.line.line_no)
1983 line_selection_start = selection_start.pos + 1;
1985 line_selection_start = 1;
1987 if (line_no == selection_end.line.line_no)
1988 line_selection_end = selection_end.pos + 1;
1990 line_selection_end = text.Length + 1;
1992 if (line_selection_end == line_selection_start) {
1993 // There isn't really selection
1994 line_selection_start = text.Length + 1;
1995 line_selection_end = line_selection_start;
1996 } else if (multiline) {
1997 // lets draw some selection baby!! (non multiline selection is drawn outside the loop)
1998 g.FillRectangle (hilight,
1999 line.widths [line_selection_start - 1] + line.X - viewport_x,
2000 line_y, line.widths [line_selection_end - 1] - line.widths [line_selection_start - 1],
2005 current_brush = line.tags.color;
2006 while (tag != null) {
2009 if (tag.length == 0) {
2014 if (((tag.X + tag.width) < (clip.Left - viewport_x)) && (tag.X > (clip.Right - viewport_x))) {
2019 if (tag.back_color != null) {
2020 g.FillRectangle (tag.back_color, tag.X + line.X - viewport_x,
2021 line_y + tag.shift, tag.width, line.height);
2024 tag_brush = tag.color;
2025 current_brush = tag_brush;
2027 if (!owner.is_enabled) {
2028 Color a = ((SolidBrush) tag.color).Color;
2029 Color b = ThemeEngine.Current.ColorWindowText;
2031 if ((a.R == b.R) && (a.G == b.G) && (a.B == b.B)) {
2032 tag_brush = disabled_brush;
2034 } else if (owner.read_only && !owner.backcolor_set) {
2035 tag_brush = readonly_brush;
2038 int tag_pos = tag.start;
2039 current_brush = tag_brush;
2040 while (tag_pos < tag.start + tag.length) {
2041 int old_tag_pos = tag_pos;
2043 if (tag_pos >= line_selection_start && tag_pos < line_selection_end) {
2044 current_brush = hilight_text;
2045 tag_pos = Math.Min (tag.end, line_selection_end);
2046 } else if (tag_pos < line_selection_start) {
2047 current_brush = tag_brush;
2048 tag_pos = Math.Min (tag.end, line_selection_start);
2050 current_brush = tag_brush;
2054 tag.Draw (g, current_brush,
2055 line.widths [Math.Max (0, old_tag_pos - 1)] + line.X - viewport_x,
2057 old_tag_pos - 1, Math.Min (tag.length, tag_pos - old_tag_pos),
2066 private void InsertLineString (Line line, int pos, string s)
2068 bool carriage_return = false;
2070 if (s.EndsWith ("\r")) {
2071 s = s.Substring (0, s.Length - 1);
2072 carriage_return = true;
2075 InsertString (line, pos, s);
2077 if (carriage_return) {
2078 Line l = GetLine (line.line_no);
2079 l.carriage_return = true;
2083 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
2084 internal void Insert(Line line, int pos, bool update_caret, string s) {
2089 LineTag tag = LineTag.FindTag (line, pos);
2093 base_line = line.line_no;
2094 old_line_count = lines;
2096 break_index = s.IndexOf ('\n');
2098 // Bump the text at insertion point a line down if we're inserting more than one line
2099 if (break_index > -1) {
2101 line.soft_break = false;
2102 // Remainder of start line is now in base_line + 1
2105 if (break_index == -1)
2106 break_index = s.Length;
2108 InsertLineString (line, pos, s.Substring (0, break_index));
2111 while (break_index < s.Length) {
2113 int next_break = s.IndexOf ('\n', break_index);
2114 int adjusted_next_break;
2115 bool carriage_return = false;
2117 if (next_break == -1) {
2118 next_break = s.Length;
2122 adjusted_next_break = next_break;
2123 if (s [next_break - 1] == '\r') {
2124 adjusted_next_break--;
2125 carriage_return = true;
2128 string line_text = s.Substring (break_index, adjusted_next_break - break_index);
2129 Add (base_line + count, line_text, line.alignment, tag.font, tag.color);
2131 if (carriage_return) {
2132 Line last = GetLine (base_line + count);
2133 last.carriage_return = true;
2136 last.soft_break = true;
2138 Line last = GetLine (base_line + count);
2139 last.soft_break = true;
2143 break_index = next_break + 1;
2146 ResumeRecalc (true);
2148 UpdateView(line, lines - old_line_count + 1, pos);
2151 // Move caret to the end of the inserted text
2152 Line l = GetLine (line.line_no + lines - old_line_count);
2153 PositionCaret(l, l.text.Length);
2158 // Inserts a character at the given position
2159 internal void InsertString(Line line, int pos, string s) {
2160 InsertString(line.FindTag(pos), pos, s);
2163 // Inserts a string at the given position
2164 internal void InsertString(LineTag tag, int pos, string s) {
2173 line.text.Insert(pos, s);
2176 while (tag != null) {
2183 UpdateView(line, pos);
2186 // Inserts a string at the caret position
2187 internal void InsertStringAtCaret(string s, bool move_caret) {
2189 InsertString (caret.tag, caret.pos, s);
2191 UpdateView(caret.line, caret.pos);
2193 caret.pos += s.Length;
2200 // Inserts a character at the given position
2201 internal void InsertChar(Line line, int pos, char ch) {
2202 InsertChar(line.FindTag(pos), pos, ch);
2205 // Inserts a character at the given position
2206 internal void InsertChar(LineTag tag, int pos, char ch) {
2212 line.text.Insert(pos, ch);
2215 while (tag != null) {
2222 undo.RecordTyping (line, pos, ch);
2223 UpdateView(line, pos);
2226 // Inserts a character at the current caret position
2227 internal void InsertCharAtCaret(char ch, bool move_caret) {
2233 caret.line.text.Insert(caret.pos, ch);
2236 if (caret.tag.next != null) {
2237 tag = caret.tag.next;
2238 while (tag != null) {
2244 caret.line.recalc = true;
2246 InsertChar (caret.tag, caret.pos, ch);
2248 UpdateView(caret.line, caret.pos);
2252 SetSelectionToCaret(true);
2257 internal void InsertPicture (Line line, int pos, RTF.Picture picture)
2265 // Just a place holder basically
2266 line.text.Insert (pos, "I");
2268 PictureTag picture_tag = new PictureTag (line, pos + 1, picture);
2270 tag = LineTag.FindTag (line, pos);
2271 picture_tag.CopyFormattingFrom (tag);
2272 next_tag = tag.Break (pos + 1);
2273 picture_tag.previous = tag;
2274 picture_tag.next = tag.next;
2275 tag.next = picture_tag;
2278 // Picture tags need to be surrounded by text tags
2280 if (picture_tag.next == null) {
2281 picture_tag.next = new LineTag (line, pos + 1);
2282 picture_tag.next.CopyFormattingFrom (tag);
2283 picture_tag.next.previous = picture_tag;
2286 tag = picture_tag.next;
2287 while (tag != null) {
2295 UpdateView (line, pos);
2298 internal void DeleteMultiline (Line start_line, int pos, int length)
2300 Marker start = new Marker ();
2301 Marker end = new Marker ();
2302 int start_index = LineTagToCharIndex (start_line, pos);
2304 start.line = start_line;
2306 start.tag = LineTag.FindTag (start_line, pos);
2308 CharIndexToLineTag (start_index + length, out end.line,
2309 out end.tag, out end.pos);
2313 if (start.line == end.line) {
2314 DeleteChars (start.tag, pos, end.pos - pos);
2317 // Delete first and last lines
2318 DeleteChars (start.tag, start.pos, start.line.text.Length - start.pos);
2319 DeleteChars (end.line.tags, 0, end.pos);
2321 int current = start.line.line_no + 1;
2322 if (current < end.line.line_no) {
2323 for (int i = end.line.line_no - 1; i >= current; i--) {
2328 // BIG FAT WARNING - selection_end.line might be stale due
2329 // to the above Delete() call. DONT USE IT before hitting the end of this method!
2331 // Join start and end
2332 Combine (start.line.line_no, current);
2335 ResumeUpdate (true);
2339 // Deletes n characters at the given position; it will not delete past line limits
2341 internal void DeleteChars(LineTag tag, int pos, int count) {
2350 if (pos == line.text.Length) {
2354 line.text.Remove(pos, count);
2356 // Make sure the tag points to the right spot
2357 while ((tag != null) && (tag.end) < pos) {
2365 // Check if we're crossing tag boundaries
2366 if ((pos + count) > (tag.start + tag.length - 1)) {
2369 // We have to delete cross tag boundaries
2373 left -= tag.start + tag.length - pos - 1;
2376 while ((tag != null) && (left > 0)) {
2377 tag.start -= count - left;
2379 if (tag.length > left) {
2388 // We got off easy, same tag
2390 if (tag.length == 0) {
2395 // Delete empty orphaned tags at the end
2397 while (walk != null && walk.next != null && walk.next.length == 0) {
2399 walk.next = walk.next.next;
2400 if (walk.next != null)
2401 walk.next.previous = t;
2405 // Adjust the start point of any tags following
2408 while (tag != null) {
2416 line.Streamline(lines);
2419 UpdateView(line, pos);
2422 // Deletes a character at or after the given position (depending on forward); it will not delete past line limits
2423 internal void DeleteChar(LineTag tag, int pos, bool forward) {
2432 if ((pos == 0 && forward == false) || (pos == line.text.Length && forward == true)) {
2438 line.text.Remove(pos, 1);
2440 while ((tag != null) && (tag.start + tag.length - 1) <= pos) {
2450 if (tag.length == 0) {
2455 line.text.Remove(pos, 1);
2456 if (pos >= (tag.start - 1)) {
2458 if (tag.length == 0) {
2461 } else if (tag.previous != null) {
2462 // tag.previous.length--;
2463 if (tag.previous.length == 0) {
2469 // Delete empty orphaned tags at the end
2471 while (walk != null && walk.next != null && walk.next.length == 0) {
2473 walk.next = walk.next.next;
2474 if (walk.next != null)
2475 walk.next.previous = t;
2480 while (tag != null) {
2486 line.Streamline(lines);
2489 UpdateView(line, pos);
2492 // Combine two lines
2493 internal void Combine(int FirstLine, int SecondLine) {
2494 Combine(GetLine(FirstLine), GetLine(SecondLine));
2497 internal void Combine(Line first, Line second) {
2501 // Combine the two tag chains into one
2504 // Maintain the line ending style
2505 first.soft_break = second.soft_break;
2507 while (last.next != null) {
2511 // need to get the shift before setting the next tag since that effects length
2512 shift = last.start + last.length - 1;
2513 last.next = second.tags;
2514 last.next.previous = last;
2516 // Fix up references within the chain
2518 while (last != null) {
2520 last.start += shift;
2524 // Combine both lines' strings
2525 first.text.Insert(first.text.Length, second.text.ToString());
2526 first.Grow(first.text.Length);
2528 // Remove the reference to our (now combined) tags from the doomed line
2532 DecrementLines(first.line_no + 2); // first.line_no + 1 will be deleted, so we need to start renumbering one later
2535 first.recalc = true;
2536 first.height = 0; // This forces RecalcDocument/UpdateView to redraw from this line on
2537 first.Streamline(lines);
2539 // Update Caret, Selection, etc
2540 if (caret.line == second) {
2541 caret.Combine(first, shift);
2543 if (selection_anchor.line == second) {
2544 selection_anchor.Combine(first, shift);
2546 if (selection_start.line == second) {
2547 selection_start.Combine(first, shift);
2549 if (selection_end.line == second) {
2550 selection_end.Combine(first, shift);
2557 check_first = GetLine(first.line_no);
2558 check_second = GetLine(check_first.line_no + 1);
2560 Console.WriteLine("Pre-delete: Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2563 this.Delete(second);
2566 check_first = GetLine(first.line_no);
2567 check_second = GetLine(check_first.line_no + 1);
2569 Console.WriteLine("Post-delete Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2573 // Split the line at the position into two
2574 internal void Split(int LineNo, int pos) {
2578 line = GetLine(LineNo);
2579 tag = LineTag.FindTag(line, pos);
2580 Split(line, tag, pos, false);
2583 internal void Split(Line line, int pos) {
2586 tag = LineTag.FindTag(line, pos);
2587 Split(line, tag, pos, false);
2590 ///<summary>Split line at given tag and position into two lines</summary>
2591 ///<param name="soft">True if the split should be marked as 'soft', indicating that it can be contracted
2592 ///if more space becomes available on previous line</param>
2593 internal void Split(Line line, LineTag tag, int pos, bool soft) {
2597 bool move_sel_start;
2601 move_sel_start = false;
2602 move_sel_end = false;
2604 // Adjust selection and cursors
2605 if (caret.line == line && caret.pos >= pos) {
2608 if (selection_start.line == line && selection_start.pos > pos) {
2609 move_sel_start = true;
2612 if (selection_end.line == line && selection_end.pos > pos) {
2613 move_sel_end = true;
2616 // cover the easy case first
2617 if (pos == line.text.Length) {
2618 Add(line.line_no + 1, "", line.alignment, tag.font, tag.color);
2620 new_line = GetLine(line.line_no + 1);
2622 line.carriage_return = false;
2623 new_line.carriage_return = line.carriage_return;
2624 new_line.soft_break = soft;
2627 caret.line = new_line;
2628 caret.tag = new_line.tags;
2632 if (move_sel_start) {
2633 selection_start.line = new_line;
2634 selection_start.pos = 0;
2635 selection_start.tag = new_line.tags;
2639 selection_end.line = new_line;
2640 selection_end.pos = 0;
2641 selection_end.tag = new_line.tags;
2646 // We need to move the rest of the text into the new line
2647 Add (line.line_no + 1, line.text.ToString (pos, line.text.Length - pos), line.alignment, tag.font, tag.color);
2649 // Now transfer our tags from this line to the next
2650 new_line = GetLine(line.line_no + 1);
2652 line.carriage_return = false;
2653 new_line.carriage_return = line.carriage_return;
2654 new_line.soft_break = soft;
2657 new_line.recalc = true;
2659 if ((tag.start - 1) == pos) {
2662 // We can simply break the chain and move the tag into the next line
2663 if (tag == line.tags) {
2664 new_tag = new LineTag(line, 1);
2665 new_tag.CopyFormattingFrom (tag);
2666 line.tags = new_tag;
2669 if (tag.previous != null) {
2670 tag.previous.next = null;
2672 new_line.tags = tag;
2673 tag.previous = null;
2674 tag.line = new_line;
2676 // Walk the list and correct the start location of the tags we just bumped into the next line
2677 shift = tag.start - 1;
2680 while (new_tag != null) {
2681 new_tag.start -= shift;
2682 new_tag.line = new_line;
2683 new_tag = new_tag.next;
2688 new_tag = new LineTag (new_line, 1);
2689 new_tag.next = tag.next;
2690 new_tag.CopyFormattingFrom (tag);
2691 new_line.tags = new_tag;
2692 if (new_tag.next != null) {
2693 new_tag.next.previous = new_tag;
2698 new_tag = new_tag.next;
2699 while (new_tag != null) {
2700 new_tag.start -= shift;
2701 new_tag.line = new_line;
2702 new_tag = new_tag.next;
2708 caret.line = new_line;
2709 caret.pos = caret.pos - pos;
2710 caret.tag = caret.line.FindTag(caret.pos);
2713 if (move_sel_start) {
2714 selection_start.line = new_line;
2715 selection_start.pos = selection_start.pos - pos;
2716 selection_start.tag = new_line.FindTag(selection_start.pos);
2720 selection_end.line = new_line;
2721 selection_end.pos = selection_end.pos - pos;
2722 selection_end.tag = new_line.FindTag(selection_end.pos);
2725 CharCount -= line.text.Length - pos;
2726 line.text.Remove(pos, line.text.Length - pos);
2729 // Adds a line of text, with given font.
2730 // Bumps any line at that line number that already exists down
2731 internal void Add(int LineNo, string Text, Font font, SolidBrush color) {
2732 Add(LineNo, Text, alignment, font, color);
2735 internal void Add(int LineNo, string Text, HorizontalAlignment align, Font font, SolidBrush color) {
2740 CharCount += Text.Length;
2742 if (LineNo<1 || Text == null) {
2744 throw new ArgumentNullException("LineNo", "Line numbers must be positive");
2746 throw new ArgumentNullException("Text", "Cannot insert NULL line");
2750 add = new Line (this, LineNo, Text, align, font, color);
2753 while (line != sentinel) {
2755 line_no = line.line_no;
2757 if (LineNo > line_no) {
2759 } else if (LineNo < line_no) {
2762 // Bump existing line numbers; walk all nodes to the right of this one and increment line_no
2763 IncrementLines(line.line_no);
2768 add.left = sentinel;
2769 add.right = sentinel;
2771 if (add.parent != null) {
2772 if (LineNo > add.parent.line_no) {
2773 add.parent.right = add;
2775 add.parent.left = add;
2782 RebalanceAfterAdd(add);
2787 internal virtual void Clear() {
2790 document = sentinel;
2793 public virtual object Clone() {
2796 clone = new Document(null);
2798 clone.lines = this.lines;
2799 clone.document = (Line)document.Clone();
2804 internal void Delete(int LineNo) {
2811 line = GetLine(LineNo);
2813 CharCount -= line.text.Length;
2815 DecrementLines(LineNo + 1);
2819 internal void Delete(Line line1) {
2820 Line line2;// = new Line();
2823 if ((line1.left == sentinel) || (line1.right == sentinel)) {
2826 line3 = line1.right;
2827 while (line3.left != sentinel) {
2832 if (line3.left != sentinel) {
2835 line2 = line3.right;
2838 line2.parent = line3.parent;
2839 if (line3.parent != null) {
2840 if(line3 == line3.parent.left) {
2841 line3.parent.left = line2;
2843 line3.parent.right = line2;
2849 if (line3 != line1) {
2852 if (selection_start.line == line3) {
2853 selection_start.line = line1;
2856 if (selection_end.line == line3) {
2857 selection_end.line = line1;
2860 if (selection_anchor.line == line3) {
2861 selection_anchor.line = line1;
2864 if (caret.line == line3) {
2869 line1.alignment = line3.alignment;
2870 line1.ascent = line3.ascent;
2871 line1.hanging_indent = line3.hanging_indent;
2872 line1.height = line3.height;
2873 line1.indent = line3.indent;
2874 line1.line_no = line3.line_no;
2875 line1.recalc = line3.recalc;
2876 line1.right_indent = line3.right_indent;
2877 line1.soft_break = line3.soft_break;
2878 line1.space = line3.space;
2879 line1.tags = line3.tags;
2880 line1.text = line3.text;
2881 line1.widths = line3.widths;
2882 line1.offset = line3.offset;
2885 while (tag != null) {
2891 if (line3.color == LineColor.Black)
2892 RebalanceAfterDelete(line2);
2897 // Invalidate a section of the document to trigger redraw
2898 internal void Invalidate(Line start, int start_pos, Line end, int end_pos) {
2904 if ((start == end) && (start_pos == end_pos)) {
2908 if (end_pos == -1) {
2909 end_pos = end.text.Length;
2912 // figure out what's before what so the logic below is straightforward
2913 if (start.line_no < end.line_no) {
2919 } else if (start.line_no > end.line_no) {
2926 if (start_pos < end_pos) {
2940 int endpoint = (int) l1.widths [p2];
2941 if (p2 == l1.text.Length + 1) {
2942 endpoint = (int) viewport_width;
2946 Console.WriteLine("Invaliding backwards from {0}:{1} to {2}:{3} {4}",
2947 l1.line_no, p1, l2.line_no, p2,
2949 (int)l1.widths[p1] + l1.X - viewport_x,
2957 owner.Invalidate(new Rectangle (
2958 (int)l1.widths[p1] + l1.X - viewport_x,
2960 endpoint - (int)l1.widths[p1] + 1,
2966 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);
2967 Console.WriteLine ("invalidate start line: {0} position: {1}", l1.text, p1);
2970 // Three invalidates:
2971 // First line from start
2972 owner.Invalidate(new Rectangle((int)l1.widths[p1] + l1.X - viewport_x, l1.Y - viewport_y, viewport_width, l1.height));
2976 if ((l1.line_no + 1) < l2.line_no) {
2979 y = GetLine(l1.line_no + 1).Y;
2980 owner.Invalidate(new Rectangle(0, y - viewport_y, viewport_width, l2.Y - y));
2983 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);
2989 owner.Invalidate(new Rectangle((int)l2.widths[0] + l2.X - viewport_x, l2.Y - viewport_y, (int)l2.widths[p2] + 1, l2.height));
2991 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);
2996 /// <summary>Select text around caret</summary>
2997 internal void ExpandSelection(CaretSelection mode, bool to_caret) {
2999 // We're expanding the selection to the caret position
3001 case CaretSelection.Line: {
3002 // Invalidate the selection delta
3003 if (caret > selection_prev) {
3004 Invalidate(selection_prev.line, 0, caret.line, caret.line.text.Length);
3006 Invalidate(selection_prev.line, selection_prev.line.text.Length, caret.line, 0);
3009 if (caret.line.line_no <= selection_anchor.line.line_no) {
3010 selection_start.line = caret.line;
3011 selection_start.tag = caret.line.tags;
3012 selection_start.pos = 0;
3014 selection_end.line = selection_anchor.line;
3015 selection_end.tag = selection_anchor.tag;
3016 selection_end.pos = selection_anchor.pos;
3018 selection_end_anchor = true;
3020 selection_start.line = selection_anchor.line;
3021 selection_start.pos = selection_anchor.height;
3022 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height);
3024 selection_end.line = caret.line;
3025 selection_end.tag = caret.line.tags;
3026 selection_end.pos = caret.line.text.Length;
3028 selection_end_anchor = false;
3030 selection_prev.line = caret.line;
3031 selection_prev.tag = caret.tag;
3032 selection_prev.pos = caret.pos;
3037 case CaretSelection.Word: {
3041 start_pos = FindWordSeparator(caret.line, caret.pos, false);
3042 end_pos = FindWordSeparator(caret.line, caret.pos, true);
3045 // Invalidate the selection delta
3046 if (caret > selection_prev) {
3047 Invalidate(selection_prev.line, selection_prev.pos, caret.line, end_pos);
3049 Invalidate(selection_prev.line, selection_prev.pos, caret.line, start_pos);
3051 if (caret < selection_anchor) {
3052 selection_start.line = caret.line;
3053 selection_start.tag = caret.line.FindTag(start_pos);
3054 selection_start.pos = start_pos;
3056 selection_end.line = selection_anchor.line;
3057 selection_end.tag = selection_anchor.tag;
3058 selection_end.pos = selection_anchor.pos;
3060 selection_prev.line = caret.line;
3061 selection_prev.tag = caret.tag;
3062 selection_prev.pos = start_pos;
3064 selection_end_anchor = true;
3066 selection_start.line = selection_anchor.line;
3067 selection_start.pos = selection_anchor.height;
3068 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height);
3070 selection_end.line = caret.line;
3071 selection_end.tag = caret.line.FindTag(end_pos);
3072 selection_end.pos = end_pos;
3074 selection_prev.line = caret.line;
3075 selection_prev.tag = caret.tag;
3076 selection_prev.pos = end_pos;
3078 selection_end_anchor = false;
3083 case CaretSelection.Position: {
3084 SetSelectionToCaret(false);
3089 // We're setting the selection 'around' the caret position
3091 case CaretSelection.Line: {
3092 this.Invalidate(caret.line, 0, caret.line, caret.line.text.Length);
3094 selection_start.line = caret.line;
3095 selection_start.tag = caret.line.tags;
3096 selection_start.pos = 0;
3098 selection_end.line = caret.line;
3099 selection_end.pos = caret.line.text.Length;
3100 selection_end.tag = caret.line.FindTag(selection_end.pos);
3102 selection_anchor.line = selection_end.line;
3103 selection_anchor.tag = selection_end.tag;
3104 selection_anchor.pos = selection_end.pos;
3105 selection_anchor.height = 0;
3107 selection_prev.line = caret.line;
3108 selection_prev.tag = caret.tag;
3109 selection_prev.pos = caret.pos;
3111 this.selection_end_anchor = true;
3116 case CaretSelection.Word: {
3120 start_pos = FindWordSeparator(caret.line, caret.pos, false);
3121 end_pos = FindWordSeparator(caret.line, caret.pos, true);
3123 this.Invalidate(selection_start.line, start_pos, caret.line, end_pos);
3125 selection_start.line = caret.line;
3126 selection_start.tag = caret.line.FindTag(start_pos);
3127 selection_start.pos = start_pos;
3129 selection_end.line = caret.line;
3130 selection_end.tag = caret.line.FindTag(end_pos);
3131 selection_end.pos = end_pos;
3133 selection_anchor.line = selection_end.line;
3134 selection_anchor.tag = selection_end.tag;
3135 selection_anchor.pos = selection_end.pos;
3136 selection_anchor.height = start_pos;
3138 selection_prev.line = caret.line;
3139 selection_prev.tag = caret.tag;
3140 selection_prev.pos = caret.pos;
3142 this.selection_end_anchor = true;
3149 SetSelectionVisible (!(selection_start == selection_end));
3152 internal void SetSelectionToCaret(bool start) {
3154 // Invalidate old selection; selection is being reset to empty
3155 this.Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3157 selection_start.line = caret.line;
3158 selection_start.tag = caret.tag;
3159 selection_start.pos = caret.pos;
3161 // start always also selects end
3162 selection_end.line = caret.line;
3163 selection_end.tag = caret.tag;
3164 selection_end.pos = caret.pos;
3166 selection_anchor.line = caret.line;
3167 selection_anchor.tag = caret.tag;
3168 selection_anchor.pos = caret.pos;
3170 // Invalidate from previous end to caret (aka new end)
3171 if (selection_end_anchor) {
3172 if (selection_start != caret) {
3173 this.Invalidate(selection_start.line, selection_start.pos, caret.line, caret.pos);
3176 if (selection_end != caret) {
3177 this.Invalidate(selection_end.line, selection_end.pos, caret.line, caret.pos);
3181 if (caret < selection_anchor) {
3182 selection_start.line = caret.line;
3183 selection_start.tag = caret.tag;
3184 selection_start.pos = caret.pos;
3186 selection_end.line = selection_anchor.line;
3187 selection_end.tag = selection_anchor.tag;
3188 selection_end.pos = selection_anchor.pos;
3190 selection_end_anchor = true;
3192 selection_start.line = selection_anchor.line;
3193 selection_start.tag = selection_anchor.tag;
3194 selection_start.pos = selection_anchor.pos;
3196 selection_end.line = caret.line;
3197 selection_end.tag = caret.tag;
3198 selection_end.pos = caret.pos;
3200 selection_end_anchor = false;
3204 SetSelectionVisible (!(selection_start == selection_end));
3207 internal void SetSelection(Line start, int start_pos, Line end, int end_pos) {
3208 if (selection_visible) {
3209 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3212 if ((end.line_no < start.line_no) || ((end == start) && (end_pos <= start_pos))) {
3213 selection_start.line = end;
3214 selection_start.tag = LineTag.FindTag(end, end_pos);
3215 selection_start.pos = end_pos;
3217 selection_end.line = start;
3218 selection_end.tag = LineTag.FindTag(start, start_pos);
3219 selection_end.pos = start_pos;
3221 selection_end_anchor = true;
3223 selection_start.line = start;
3224 selection_start.tag = LineTag.FindTag(start, start_pos);
3225 selection_start.pos = start_pos;
3227 selection_end.line = end;
3228 selection_end.tag = LineTag.FindTag(end, end_pos);
3229 selection_end.pos = end_pos;
3231 selection_end_anchor = false;
3234 selection_anchor.line = start;
3235 selection_anchor.tag = selection_start.tag;
3236 selection_anchor.pos = start_pos;
3238 if (((start == end) && (start_pos == end_pos)) || start == null || end == null) {
3239 SetSelectionVisible (false);
3241 SetSelectionVisible (true);
3242 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3246 internal void SetSelectionStart(Line start, int start_pos) {
3247 // Invalidate from the previous to the new start pos
3248 Invalidate(selection_start.line, selection_start.pos, start, start_pos);
3250 selection_start.line = start;
3251 selection_start.pos = start_pos;
3252 selection_start.tag = LineTag.FindTag(start, start_pos);
3254 selection_anchor.line = start;
3255 selection_anchor.pos = start_pos;
3256 selection_anchor.tag = selection_start.tag;
3258 selection_end_anchor = false;
3261 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
3262 SetSelectionVisible (true);
3264 SetSelectionVisible (false);
3267 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3270 internal void SetSelectionStart(int character_index) {
3275 if (character_index < 0) {
3279 CharIndexToLineTag(character_index, out line, out tag, out pos);
3280 SetSelectionStart(line, pos);
3283 internal void SetSelectionEnd(Line end, int end_pos) {
3285 if (end == selection_end.line && end_pos == selection_start.pos) {
3286 selection_anchor.line = selection_start.line;
3287 selection_anchor.tag = selection_start.tag;
3288 selection_anchor.pos = selection_start.pos;
3290 selection_end.line = selection_start.line;
3291 selection_end.tag = selection_start.tag;
3292 selection_end.pos = selection_start.pos;
3294 selection_end_anchor = false;
3295 } else if ((end.line_no < selection_anchor.line.line_no) || ((end == selection_anchor.line) && (end_pos <= selection_anchor.pos))) {
3296 selection_start.line = end;
3297 selection_start.tag = LineTag.FindTag(end, end_pos);
3298 selection_start.pos = end_pos;
3300 selection_end.line = selection_anchor.line;
3301 selection_end.tag = selection_anchor.tag;
3302 selection_end.pos = selection_anchor.pos;
3304 selection_end_anchor = true;
3306 selection_start.line = selection_anchor.line;
3307 selection_start.tag = selection_anchor.tag;
3308 selection_start.pos = selection_anchor.pos;
3310 selection_end.line = end;
3311 selection_end.tag = LineTag.FindTag(end, end_pos);
3312 selection_end.pos = end_pos;
3314 selection_end_anchor = false;
3317 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
3318 SetSelectionVisible (true);
3319 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3321 SetSelectionVisible (false);
3322 // ?? Do I need to invalidate here, tests seem to work without it, but I don't think they should :-s
3326 internal void SetSelectionEnd(int character_index) {
3331 if (character_index < 0) {
3335 CharIndexToLineTag(character_index, out line, out tag, out pos);
3336 SetSelectionEnd(line, pos);
3339 internal void SetSelection(Line start, int start_pos) {
3340 if (selection_visible) {
3341 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3344 selection_start.line = start;
3345 selection_start.pos = start_pos;
3346 selection_start.tag = LineTag.FindTag(start, start_pos);
3348 selection_end.line = start;
3349 selection_end.tag = selection_start.tag;
3350 selection_end.pos = start_pos;
3352 selection_anchor.line = start;
3353 selection_anchor.tag = selection_start.tag;
3354 selection_anchor.pos = start_pos;
3356 selection_end_anchor = false;
3357 SetSelectionVisible (false);
3360 internal void InvalidateSelectionArea() {
3361 Invalidate (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3364 // Return the current selection, as string
3365 internal string GetSelection() {
3366 // We return String.Empty if there is no selection
3367 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3368 return string.Empty;
3371 if (selection_start.line == selection_end.line) {
3372 return selection_start.line.text.ToString (selection_start.pos, selection_end.pos - selection_start.pos);
3379 sb = new StringBuilder();
3380 start = selection_start.line.line_no;
3381 end = selection_end.line.line_no;
3383 sb.Append(selection_start.line.text.ToString(selection_start.pos, selection_start.line.text.Length - selection_start.pos) + Environment.NewLine);
3385 if ((start + 1) < end) {
3386 for (i = start + 1; i < end; i++) {
3387 sb.Append(GetLine(i).text.ToString() + Environment.NewLine);
3391 sb.Append(selection_end.line.text.ToString(0, selection_end.pos));
3393 return sb.ToString();
3397 internal void ReplaceSelection(string s, bool select_new) {
3400 int selection_pos_on_line = selection_start.pos;
3401 int selection_start_pos = LineTagToCharIndex (selection_start.line, selection_start.pos);
3404 // First, delete any selected text
3405 if ((selection_start.pos != selection_end.pos) || (selection_start.line != selection_end.line)) {
3406 if (selection_start.line == selection_end.line) {
3407 undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3409 DeleteChars(selection_start.tag, selection_start.pos, selection_end.pos - selection_start.pos);
3411 // The tag might have been removed, we need to recalc it
3412 selection_start.tag = selection_start.line.FindTag(selection_start.pos);
3417 start = selection_start.line.line_no;
3418 end = selection_end.line.line_no;
3420 undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3422 // Delete first line
3423 DeleteChars(selection_start.tag, selection_start.pos, selection_start.line.text.Length - selection_start.pos);
3426 DeleteChars(selection_end.line.tags, 0, selection_end.pos);
3430 for (i = end - 1; i >= start; i--) {
3435 // BIG FAT WARNING - selection_end.line might be stale due
3436 // to the above Delete() call. DONT USE IT before hitting the end of this method!
3438 // Join start and end
3439 Combine(selection_start.line.line_no, start);
3444 Insert(selection_start.line, selection_start.pos, false, s);
3445 undo.RecordInsertString (selection_start.line, selection_start.pos, s);
3446 ResumeRecalc (false);
3449 CharIndexToLineTag(selection_start_pos + s.Length, out selection_start.line,
3450 out selection_start.tag, out selection_start.pos);
3452 selection_end.line = selection_start.line;
3453 selection_end.pos = selection_start.pos;
3454 selection_end.tag = selection_start.tag;
3455 selection_anchor.line = selection_start.line;
3456 selection_anchor.pos = selection_start.pos;
3457 selection_anchor.tag = selection_start.tag;
3459 SetSelectionVisible (false);
3461 CharIndexToLineTag(selection_start_pos, out selection_start.line,
3462 out selection_start.tag, out selection_start.pos);
3464 CharIndexToLineTag(selection_start_pos + s.Length, out selection_end.line,
3465 out selection_end.tag, out selection_end.pos);
3467 selection_anchor.line = selection_start.line;
3468 selection_anchor.pos = selection_start.pos;
3469 selection_anchor.tag = selection_start.tag;
3471 SetSelectionVisible (true);
3474 PositionCaret (selection_start.line, selection_start.pos);
3475 UpdateView (selection_start.line, selection_pos_on_line);
3478 internal void CharIndexToLineTag(int index, out Line line_out, out LineTag tag_out, out int pos) {
3487 for (i = 1; i <= lines; i++) {
3491 chars += line.text.Length + (line.soft_break ? 0 : crlf_size);
3493 if (index <= chars) {
3494 // we found the line
3497 while (tag != null) {
3498 if (index < (start + tag.start + tag.length)) {
3500 tag_out = LineTag.GetFinalTag (tag);
3501 pos = index - start;
3504 if (tag.next == null) {
3507 next_line = GetLine(line.line_no + 1);
3509 if (next_line != null) {
3510 line_out = next_line;
3511 tag_out = LineTag.GetFinalTag (next_line.tags);
3516 tag_out = LineTag.GetFinalTag (tag);
3517 pos = line_out.text.Length;
3526 line_out = GetLine(lines);
3527 tag = line_out.tags;
3528 while (tag.next != null) {
3532 pos = line_out.text.Length;
3535 internal int LineTagToCharIndex(Line line, int pos) {
3539 // Count first and last line
3542 // Count the lines in the middle
3544 for (i = 1; i < line.line_no; i++) {
3545 length += GetLine(i).text.Length + (line.soft_break ? 0 : crlf_size);
3553 internal int SelectionLength() {
3554 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3558 if (selection_start.line == selection_end.line) {
3559 return selection_end.pos - selection_start.pos;
3566 // Count first and last line
3567 length = selection_start.line.text.Length - selection_start.pos + selection_end.pos + crlf_size;
3569 // Count the lines in the middle
3570 start = selection_start.line.line_no + 1;
3571 end = selection_end.line.line_no;
3574 for (i = start; i < end; i++) {
3575 Line line = GetLine (i);
3576 length += line.text.Length + (line.soft_break ? 0 : crlf_size);
3587 /// <summary>Give it a Line number and it returns the Line object at with that line number</summary>
3588 internal Line GetLine(int LineNo) {
3589 Line line = document;
3591 while (line != sentinel) {
3592 if (LineNo == line.line_no) {
3594 } else if (LineNo < line.line_no) {
3604 /// <summary>Retrieve the previous tag; walks line boundaries</summary>
3605 internal LineTag PreviousTag(LineTag tag) {
3608 if (tag.previous != null) {
3609 return tag.previous;
3613 if (tag.line.line_no == 1) {
3617 l = GetLine(tag.line.line_no - 1);
3622 while (t.next != null) {
3631 /// <summary>Retrieve the next tag; walks line boundaries</summary>
3632 internal LineTag NextTag(LineTag tag) {
3635 if (tag.next != null) {
3640 l = GetLine(tag.line.line_no + 1);
3648 internal Line ParagraphStart(Line line) {
3649 while (line.soft_break) {
3650 line = GetLine(line.line_no - 1);
3655 internal Line ParagraphEnd(Line line) {
3658 while (line.soft_break) {
3659 l = GetLine(line.line_no + 1);
3660 if ((l == null) || (!l.soft_break)) {
3668 /// <summary>Give it a pixel offset coordinate and it returns the Line covering that are (offset
3669 /// is either X or Y depending on if we are multiline
3671 internal Line GetLineByPixel (int offset, bool exact)
3673 Line line = document;
3677 while (line != sentinel) {
3679 if ((offset >= line.Y) && (offset < (line.Y+line.height))) {
3681 } else if (offset < line.Y) {
3688 while (line != sentinel) {
3690 if ((offset >= line.X) && (offset < (line.X + line.Width)))
3692 else if (offset < line.X)
3705 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3706 internal LineTag FindTag(int x, int y, out int index, bool exact) {
3710 line = GetLineByPixel(y, exact);
3717 // Alignment adjustment
3721 if (x >= tag.X && x < (tag.X+tag.width)) {
3724 end = tag.start + tag.length - 1;
3726 for (int pos = tag.start; pos < end; pos++) {
3727 if (x < line.widths[pos]) {
3729 return LineTag.GetFinalTag (tag);
3733 return LineTag.GetFinalTag (tag);
3735 if (tag.next != null) {
3743 index = line.text.Length;
3744 return LineTag.GetFinalTag (tag);
3749 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3750 internal LineTag FindCursor(int x, int y, out int index) {
3754 line = GetLineByPixel(multiline ? y : x, false);
3757 /// Special case going leftwards of the first tag
3760 return LineTag.GetFinalTag (tag);
3764 if (x >= tag.X && x < (tag.X+tag.width)) {
3769 for (int pos = tag.start - 1; pos < end; pos++) {
3770 // When clicking on a character, we position the cursor to whatever edge
3771 // of the character the click was closer
3772 if (x < (line.X + line.widths[pos] + ((line.widths[pos+1]-line.widths[pos])/2))) {
3774 return LineTag.GetFinalTag (tag);
3778 return LineTag.GetFinalTag (tag);
3780 if (tag.next != null) {
3783 index = line.text.Length;
3784 return LineTag.GetFinalTag (tag);
3789 /// <summary>Format area of document in specified font and color</summary>
3790 /// <param name="start_pos">1-based start position on start_line</param>
3791 /// <param name="end_pos">1-based end position on end_line </param>
3792 internal void FormatText (Line start_line, int start_pos, Line end_line, int end_pos, Font font,
3793 SolidBrush color, SolidBrush back_color, FormatSpecified specified)
3797 // First, format the first line
3798 if (start_line != end_line) {
3800 LineTag.FormatText(start_line, start_pos, start_line.text.Length - start_pos + 1, font, color, back_color, specified);
3803 LineTag.FormatText(end_line, 1, end_pos, font, color, back_color, specified);
3805 // Now all the lines inbetween
3806 for (int i = start_line.line_no + 1; i < end_line.line_no; i++) {
3808 LineTag.FormatText(l, 1, l.text.Length, font, color, back_color, specified);
3811 // Special case, single line
3812 LineTag.FormatText(start_line, start_pos, end_pos - start_pos, font, color, back_color, specified);
3816 /// <summary>Re-format areas of the document in specified font and color</summary>
3817 /// <param name="start_pos">1-based start position on start_line</param>
3818 /// <param name="end_pos">1-based end position on end_line </param>
3819 /// <param name="font">Font specifying attributes</param>
3820 /// <param name="color">Color (or NULL) to apply</param>
3821 /// <param name="apply">Attributes from font and color to apply</param>
3822 internal void FormatText(Line start_line, int start_pos, Line end_line, int end_pos, FontDefinition attributes) {
3825 // First, format the first line
3826 if (start_line != end_line) {
3828 LineTag.FormatText(start_line, start_pos, start_line.text.Length - start_pos + 1, attributes);
3831 LineTag.FormatText(end_line, 1, end_pos - 1, attributes);
3833 // Now all the lines inbetween
3834 for (int i = start_line.line_no + 1; i < end_line.line_no; i++) {
3836 LineTag.FormatText(l, 1, l.text.Length, attributes);
3839 // Special case, single line
3840 LineTag.FormatText(start_line, start_pos, end_pos - start_pos, attributes);
3844 internal void RecalculateAlignments ()
3853 while (line_no <= lines) {
3854 line = GetLine(line_no);
3857 switch (line.alignment) {
3858 case HorizontalAlignment.Left:
3859 line.align_shift = 0;
3861 case HorizontalAlignment.Center:
3862 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3864 case HorizontalAlignment.Right:
3865 line.align_shift = viewport_width - (int)line.widths[line.text.Length] - line.right_margin;
3875 /// <summary>Calculate formatting for the whole document</summary>
3876 internal bool RecalculateDocument(Graphics g) {
3877 return RecalculateDocument(g, 1, this.lines, false);
3880 /// <summary>Calculate formatting starting at a certain line</summary>
3881 internal bool RecalculateDocument(Graphics g, int start) {
3882 return RecalculateDocument(g, start, this.lines, false);
3885 /// <summary>Calculate formatting within two given line numbers</summary>
3886 internal bool RecalculateDocument(Graphics g, int start, int end) {
3887 return RecalculateDocument(g, start, end, false);
3890 /// <summary>With optimize on, returns true if line heights changed</summary>
3891 internal bool RecalculateDocument(Graphics g, int start, int end, bool optimize) {
3899 if (recalc_suspended > 0) {
3900 recalc_pending = true;
3901 recalc_start = Math.Min (recalc_start, start);
3902 recalc_end = Math.Max (recalc_end, end);
3903 recalc_optimize = optimize;
3907 // Fixup the positions, they can go kinda nuts
3908 start = Math.Max (start, 1);
3909 end = Math.Min (end, lines);
3911 offset = GetLine(start).offset;
3916 changed = true; // We always return true if we run non-optimized
3921 while (line_no <= (end + this.lines - shift)) {
3922 line = GetLine(line_no++);
3923 line.offset = offset;
3927 line.RecalculateLine(g, this);
3929 if (line.recalc && line.RecalculateLine(g, this)) {
3931 // If the height changed, all subsequent lines change
3938 line.RecalculatePasswordLine(g, this);
3940 if (line.recalc && line.RecalculatePasswordLine(g, this)) {
3942 // If the height changed, all subsequent lines change
3949 if (line.widths[line.text.Length] > new_width) {
3950 new_width = (int)line.widths[line.text.Length];
3953 // Calculate alignment
3954 if (line.alignment != HorizontalAlignment.Left) {
3955 if (line.alignment == HorizontalAlignment.Center) {
3956 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3958 line.align_shift = viewport_width - (int)line.widths[line.text.Length] - 1;
3963 offset += line.height;
3965 offset += (int) line.widths [line.text.Length] + 2;
3967 if (line_no > lines) {
3972 if (document_x != new_width) {
3973 document_x = new_width;
3974 if (WidthChanged != null) {
3975 WidthChanged(this, null);
3979 RecalculateAlignments();
3981 line = GetLine(lines);
3983 if (document_y != line.Y + line.height) {
3984 document_y = line.Y + line.height;
3985 if (HeightChanged != null) {
3986 HeightChanged(this, null);
3993 internal int Size() {
3997 private void owner_HandleCreated(object sender, EventArgs e) {
3998 RecalculateDocument(owner.CreateGraphicsInternal());
4002 private void owner_VisibleChanged(object sender, EventArgs e) {
4003 if (owner.Visible) {
4004 RecalculateDocument(owner.CreateGraphicsInternal());
4008 internal static bool IsWordSeparator(char ch) {
4022 internal int FindWordSeparator(Line line, int pos, bool forward) {
4025 len = line.text.Length;
4028 for (int i = pos + 1; i < len; i++) {
4029 if (IsWordSeparator(line.Text[i])) {
4035 for (int i = pos - 1; i > 0; i--) {
4036 if (IsWordSeparator(line.Text[i - 1])) {
4044 /* Search document for text */
4045 internal bool FindChars(char[] chars, Marker start, Marker end, out Marker result) {
4051 // Search for occurence of any char in the chars array
4052 result = new Marker();
4055 line_no = start.line.line_no;
4057 while (line_no <= end.line.line_no) {
4058 line_len = line.text.Length;
4059 while (pos < line_len) {
4060 for (int i = 0; i < chars.Length; i++) {
4061 if (line.text[pos] == chars[i]) {
4063 if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
4077 line = GetLine(line_no);
4083 // This version does not build one big string for searching, instead it handles
4084 // line-boundaries, which is faster and less memory intensive
4085 // FIXME - Depending on culture stuff we might have to create a big string and use culturespecific
4086 // search stuff and change it to accept and return positions instead of Markers (which would match
4087 // RichTextBox behaviour better but would be inconsistent with the rest of TextControl)
4088 internal bool Find(string search, Marker start, Marker end, out Marker result, RichTextBoxFinds options) {
4090 string search_string;
4102 result = new Marker();
4103 word_option = ((options & RichTextBoxFinds.WholeWord) != 0);
4104 ignore_case = ((options & RichTextBoxFinds.MatchCase) == 0);
4105 reverse = ((options & RichTextBoxFinds.Reverse) != 0);
4108 line_no = start.line.line_no;
4112 // Prep our search string, lowercasing it if we do case-independent matching
4115 sb = new StringBuilder(search);
4116 for (int i = 0; i < sb.Length; i++) {
4117 sb[i] = Char.ToLower(sb[i]);
4119 search_string = sb.ToString();
4121 search_string = search;
4124 // We need to check if the character before our start position is a wordbreak
4127 if ((pos == 0) || (IsWordSeparator(line.text[pos - 1]))) {
4134 if (IsWordSeparator(line.text[pos - 1])) {
4140 // Need to check the end of the previous line
4143 prev_line = GetLine(line_no - 1);
4144 if (prev_line.soft_break) {
4145 if (IsWordSeparator(prev_line.text[prev_line.text.Length - 1])) {
4159 // To avoid duplication of this loop with reverse logic, we search
4160 // through the document, remembering the last match and when returning
4161 // report that last remembered match
4163 last = new Marker();
4164 last.height = -1; // Abused - we use it to track change
4166 while (line_no <= end.line.line_no) {
4167 if (line_no != end.line.line_no) {
4168 line_len = line.text.Length;
4173 while (pos < line_len) {
4174 if (word_option && (current == search_string.Length)) {
4175 if (IsWordSeparator(line.text[pos])) {
4188 c = Char.ToLower(line.text[pos]);
4193 if (c == search_string[current]) {
4198 if (!word_option || (word_option && (word || (current > 0)))) {
4202 if (!word_option && (current == search_string.Length)) {
4219 if (IsWordSeparator(c)) {
4227 // Mark that we just saw a word boundary
4228 if (!line.soft_break) {
4232 if (current == search_string.Length) {
4248 line = GetLine(line_no);
4252 if (last.height != -1) {
4262 // if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
4274 internal void GetMarker(out Marker mark, bool start) {
4275 mark = new Marker();
4278 mark.line = GetLine(1);
4279 mark.tag = mark.line.tags;
4282 mark.line = GetLine(lines);
4283 mark.tag = mark.line.tags;
4284 while (mark.tag.next != null) {
4285 mark.tag = mark.tag.next;
4287 mark.pos = mark.line.text.Length;
4290 #endregion // Internal Methods
4293 internal event EventHandler CaretMoved;
4294 internal event EventHandler WidthChanged;
4295 internal event EventHandler HeightChanged;
4296 internal event EventHandler LengthChanged;
4297 #endregion // Events
4299 #region Administrative
4300 public IEnumerator GetEnumerator() {
4305 public override bool Equals(object obj) {
4310 if (!(obj is Document)) {
4318 if (ToString().Equals(((Document)obj).ToString())) {
4325 public override int GetHashCode() {
4329 public override string ToString() {
4330 return "document " + this.document_id;
4332 #endregion // Administrative
4335 internal class PictureTag : LineTag {
4337 internal RTF.Picture picture;
4339 internal PictureTag (Line line, int start, RTF.Picture picture) : base (line, start)
4341 this.picture = picture;
4344 public override bool IsTextTag {
4345 get { return false; }
4348 internal override SizeF SizeOfPosition (Graphics dc, int pos)
4350 return picture.Size;
4353 internal override int MaxHeight ()
4355 return (int) (picture.Height + 0.5F);
4358 internal override void Draw (Graphics dc, Brush brush, float x, float y, int start, int end)
4360 picture.DrawImage (dc, x, y, false);
4363 internal override void Draw (Graphics dc, Brush brush, float x, float y, int start, int end, string text)
4365 picture.DrawImage (dc, x, y, false);
4368 public override string Text ()
4374 internal class LineTag {
4375 #region Local Variables;
4376 // Payload; formatting
4377 internal Font font; // System.Drawing.Font object for this tag
4378 internal SolidBrush color; // The font color for this tag
4380 // In 2.0 tags can have background colours. I'm not going to #ifdef
4381 // at this level though since I want to reduce code paths
4382 internal SolidBrush back_color;
4385 internal int start; // start, in chars; index into Line.text
4386 internal bool r_to_l; // Which way is the font
4389 internal int height; // Height in pixels of the text this tag describes
4391 internal int ascent; // Ascent of the font for this tag
4392 internal int shift; // Shift down for this tag, to stay on baseline
4395 internal Line line; // The line we're on
4396 internal LineTag next; // Next tag on the same line
4397 internal LineTag previous; // Previous tag on the same line
4400 #region Constructors
4401 internal LineTag(Line line, int start) {
4405 #endregion // Constructors
4407 #region Internal Methods
4413 return line.X + line.widths [start - 1];
4418 get { return start + length; }
4421 public float width {
4425 return line.widths [start + length - 1] - (start != 0 ? line.widths [start - 1] : 0);
4433 res = next.start - start;
4435 res = line.text.Length - (start - 1);
4437 return res > 0 ? res : 0;
4441 public virtual bool IsTextTag {
4442 get { return true; }
4445 internal virtual SizeF SizeOfPosition (Graphics dc, int pos)
4447 return dc.MeasureString (line.text.ToString (pos, 1), font, 10000, Document.string_format);
4450 internal virtual int MaxHeight ()
4455 internal virtual void Draw (Graphics dc, Brush brush, float x, float y, int start, int end)
4457 dc.DrawString (line.text.ToString (start, end), font, brush, x, y, StringFormat.GenericTypographic);
4460 internal virtual void Draw (Graphics dc, Brush brush, float x, float y, int start, int end, string text)
4462 dc.DrawString (text.Substring (start, end), font, brush, x, y, StringFormat.GenericTypographic);
4465 ///<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>
4466 internal LineTag Break(int pos) {
4471 if (pos == this.start) {
4473 } else if (pos >= (start + length)) {
4477 new_tag = new LineTag(line, pos);
4478 new_tag.CopyFormattingFrom (this);
4480 new_tag.next = this.next;
4481 this.next = new_tag;
4482 new_tag.previous = this;
4484 if (new_tag.next != null) {
4485 new_tag.next.previous = new_tag;
4491 public virtual string Text ()
4493 return line.text.ToString (start - 1, length);
4496 public void CopyFormattingFrom (LineTag other)
4498 height = other.height;
4500 color = other.color;
4501 back_color = other.back_color;
4504 ///<summary>Create new font and brush from existing font and given new attributes. Returns true if fontheight changes</summary>
4505 internal static bool GenerateTextFormat(Font font_from, SolidBrush color_from, FontDefinition attributes, out Font new_font, out SolidBrush new_color) {
4511 if (attributes.font_obj == null) {
4512 size = font_from.SizeInPoints;
4513 unit = font_from.Unit;
4514 face = font_from.Name;
4515 style = font_from.Style;
4517 if (attributes.face != null) {
4518 face = attributes.face;
4521 if (attributes.size != 0) {
4522 size = attributes.size;
4525 style |= attributes.add_style;
4526 style &= ~attributes.remove_style;
4529 new_font = new Font(face, size, style, unit);
4531 new_font = attributes.font_obj;
4534 // Create 'new' color brush
4535 if (attributes.color != Color.Empty) {
4536 new_color = new SolidBrush(attributes.color);
4538 new_color = color_from;
4541 if (new_font.Height == font_from.Height) {
4547 /// <summary>Applies 'font' and 'brush' to characters starting at 'start' for 'length' chars;
4548 /// Removes any previous tags overlapping the same area;
4549 /// returns true if lineheight has changed</summary>
4550 /// <param name="start">1-based character position on line</param>
4551 internal static bool FormatText(Line line, int start, int length, Font font, SolidBrush color, SolidBrush back_color, FormatSpecified specified)
4557 bool retval = false; // Assume line-height doesn't change
4560 if (((FormatSpecified.Font & specified) == FormatSpecified.Font) && font.Height != line.height) {
4563 line.recalc = true; // This forces recalculation of the line in RecalculateDocument
4565 // A little sanity, not sure if it's needed, might be able to remove for speed
4566 if (length > line.text.Length) {
4567 length = line.text.Length;
4571 end = start + length;
4573 // Common special case
4574 if ((start == 1) && (length == tag.length)) {
4576 SetFormat (tag, font, color, back_color, specified);
4580 start_tag = FindTag (line, start);
4582 tag = start_tag.Break (start);
4584 while (tag != null && tag.end <= end) {
4585 SetFormat (tag, font, color, back_color, specified);
4589 if (end != line.text.Length) {
4590 /// Now do the last tag
4591 end_tag = FindTag (line, end);
4593 if (end_tag != null) {
4594 end_tag.Break (end);
4595 SetFormat (end_tag, font, color, back_color, specified);
4602 private static void SetFormat (LineTag tag, Font font, SolidBrush color, SolidBrush back_color, FormatSpecified specified)
4604 if ((FormatSpecified.Font & specified) == FormatSpecified.Font)
4606 if ((FormatSpecified.Color & specified) == FormatSpecified.Color)
4608 if ((FormatSpecified.BackColor & specified) == FormatSpecified.BackColor) {
4609 tag.back_color = back_color;
4611 // Console.WriteLine ("setting format: {0} {1} new color {2}", color.Color, specified, tag.color.Color);
4614 /// <summary>Applies font attributes specified to characters starting at 'start' for 'length' chars;
4615 /// Breaks tags at start and end point, keeping middle tags with altered attributes.
4616 /// Returns true if lineheight has changed</summary>
4617 /// <param name="start">1-based character position on line</param>
4618 internal static bool FormatText(Line line, int start, int length, FontDefinition attributes) {
4622 bool retval = false; // Assume line-height doesn't change
4624 line.recalc = true; // This forces recalculation of the line in RecalculateDocument
4626 // A little sanity, not sure if it's needed, might be able to remove for speed
4627 if (length > line.text.Length) {
4628 length = line.text.Length;
4633 // Common special case
4634 if ((start == 1) && (length == tag.length)) {
4636 GenerateTextFormat(tag.font, tag.color, attributes, out tag.font, out tag.color);
4640 start_tag = FindTag(line, start);
4642 if (start_tag == null) {
4644 // We are 'starting' after all valid tags; create a new tag with the right attributes
4645 start_tag = FindTag(line, line.text.Length - 1);
4646 start_tag.next = new LineTag(line, line.text.Length + 1);
4647 start_tag.next.CopyFormattingFrom (start_tag);
4648 start_tag.next.previous = start_tag;
4649 start_tag = start_tag.next;
4651 throw new Exception(String.Format("Could not find start_tag in document at line {0} position {1}", line.line_no, start));
4654 start_tag = start_tag.Break(start);
4657 end_tag = FindTag(line, start + length);
4658 if (end_tag != null) {
4659 end_tag = end_tag.Break(start + length);
4662 // start_tag or end_tag might be null; we're cool with that
4663 // we now walk from start_tag to end_tag, applying new attributes
4665 while ((tag != null) && tag != end_tag) {
4666 if (LineTag.GenerateTextFormat(tag.font, tag.color, attributes, out tag.font, out tag.color)) {
4675 /// <summary>Finds the tag that describes the character at position 'pos' on 'line'</summary>
4676 internal static LineTag FindTag(Line line, int pos) {
4677 LineTag tag = line.tags;
4679 // Beginning of line is a bit special
4681 // Not sure if we should get the final tag here
4685 while (tag != null) {
4686 if ((tag.start <= pos) && (pos <= tag.end)) {
4687 return GetFinalTag (tag);
4696 // There can be multiple tags at the same position, we want to make
4697 // sure we are using the very last tag at the given position
4698 internal static LineTag GetFinalTag (LineTag tag)
4702 while (res.length == 0 && res.next != null && res.next.length == 0)
4708 /// <summary>Combines 'this' tag with 'other' tag</summary>
4709 internal bool Combine(LineTag other) {
4710 if (!this.Equals(other)) {
4714 this.next = other.next;
4715 if (this.next != null) {
4716 this.next.previous = this;
4723 /// <summary>Remove 'this' tag ; to be called when formatting is to be removed</summary>
4724 internal bool Remove() {
4725 if ((this.start == 1) && (this.next == null)) {
4726 // We cannot remove the only tag
4729 if (this.start != 1) {
4730 this.previous.next = this.next;
4731 this.next.previous = this.previous;
4733 this.next.start = 1;
4734 this.line.tags = this.next;
4735 this.next.previous = null;
4741 /// <summary>Checks if 'this' tag describes the same formatting options as 'obj'</summary>
4742 public override bool Equals(object obj) {
4749 if (!(obj is LineTag)) {
4757 other = (LineTag)obj;
4759 if (other.IsTextTag != IsTextTag)
4762 if (this.font.Equals(other.font) && this.color.Equals(other.color)) { // FIXME add checking for things like link or type later
4769 public override int GetHashCode() {
4770 return base.GetHashCode ();
4773 public override string ToString() {
4775 return GetType () + " Tag starts at index " + this.start + " length " + this.length + " text: " + Text () + "Font " + this.font.ToString();
4776 return "Zero Lengthed tag at index " + this.start;
4779 #endregion // Internal Methods
4782 internal class UndoManager {
4784 internal enum ActionType {
4788 // This is basically just cut & paste
4796 internal class Action {
4797 internal ActionType type;
4798 internal int line_no;
4800 internal object data;
4803 #region Local Variables
4804 private Document document;
4805 private Stack undo_actions;
4806 private Stack redo_actions;
4808 private int caret_line;
4809 private int caret_pos;
4811 // When performing an action, we lock the queue, so that the action can't be undone
4812 private bool locked;
4813 #endregion // Local Variables
4815 #region Constructors
4816 internal UndoManager (Document document)
4818 this.document = document;
4819 undo_actions = new Stack (50);
4820 redo_actions = new Stack (50);
4822 #endregion // Constructors
4825 internal bool CanUndo {
4826 get { return undo_actions.Count > 0; }
4829 internal bool CanRedo {
4830 get { return redo_actions.Count > 0; }
4833 internal string UndoActionName {
4835 foreach (Action action in undo_actions) {
4836 if (action.type == ActionType.UserActionBegin)
4837 return (string) action.data;
4838 if (action.type == ActionType.Typing)
4839 return Locale.GetText ("Typing");
4841 return String.Empty;
4845 internal string RedoActionName {
4847 foreach (Action action in redo_actions) {
4848 if (action.type == ActionType.UserActionBegin)
4849 return (string) action.data;
4850 if (action.type == ActionType.Typing)
4851 return Locale.GetText ("Typing");
4853 return String.Empty;
4856 #endregion // Properties
4858 #region Internal Methods
4859 internal void Clear ()
4861 undo_actions.Clear();
4862 redo_actions.Clear();
4865 internal void Undo ()
4868 bool user_action_finished = false;
4870 if (undo_actions.Count == 0)
4873 // Nuke the redo queue
4874 redo_actions.Clear ();
4879 action = (Action) undo_actions.Pop ();
4881 // Put onto redo stack
4882 redo_actions.Push(action);
4885 switch(action.type) {
4887 case ActionType.UserActionBegin:
4888 user_action_finished = true;
4891 case ActionType.UserActionEnd:
4895 case ActionType.InsertString:
4896 start = document.GetLine (action.line_no);
4897 document.SuspendUpdate ();
4898 document.DeleteMultiline (start, action.pos, ((string) action.data).Length + 1);
4899 document.PositionCaret (start, action.pos);
4900 document.SetSelectionToCaret (true);
4901 document.ResumeUpdate (true);
4904 case ActionType.Typing:
4905 start = document.GetLine (action.line_no);
4906 document.SuspendUpdate ();
4907 document.DeleteMultiline (start, action.pos, ((StringBuilder) action.data).Length);
4908 document.PositionCaret (start, action.pos);
4909 document.SetSelectionToCaret (true);
4910 document.ResumeUpdate (true);
4912 // This is an open ended operation, so only a single typing operation can be undone at once
4913 user_action_finished = true;
4916 case ActionType.DeleteString:
4917 start = document.GetLine (action.line_no);
4918 document.SuspendUpdate ();
4919 Insert (start, action.pos, (Line) action.data, true);
4920 document.ResumeUpdate (true);
4923 } while (!user_action_finished && undo_actions.Count > 0);
4928 internal void Redo ()
4931 bool user_action_finished = false;
4933 if (redo_actions.Count == 0)
4936 // You can't undo anything after redoing
4937 undo_actions.Clear ();
4944 action = (Action) redo_actions.Pop ();
4946 switch (action.type) {
4948 case ActionType.UserActionBegin:
4952 case ActionType.UserActionEnd:
4953 user_action_finished = true;
4956 case ActionType.InsertString:
4957 start = document.GetLine (action.line_no);
4958 document.SuspendUpdate ();
4959 start_index = document.LineTagToCharIndex (start, action.pos);
4960 document.InsertString (start, action.pos, (string) action.data);
4961 document.CharIndexToLineTag (start_index + ((string) action.data).Length,
4962 out document.caret.line, out document.caret.tag,
4963 out document.caret.pos);
4964 document.UpdateCaret ();
4965 document.SetSelectionToCaret (true);
4966 document.ResumeUpdate (true);
4969 case ActionType.Typing:
4970 start = document.GetLine (action.line_no);
4971 document.SuspendUpdate ();
4972 start_index = document.LineTagToCharIndex (start, action.pos);
4973 document.InsertString (start, action.pos, ((StringBuilder) action.data).ToString ());
4974 document.CharIndexToLineTag (start_index + ((StringBuilder) action.data).Length,
4975 out document.caret.line, out document.caret.tag,
4976 out document.caret.pos);
4977 document.UpdateCaret ();
4978 document.SetSelectionToCaret (true);
4979 document.ResumeUpdate (true);
4981 // This is an open ended operation, so only a single typing operation can be undone at once
4982 user_action_finished = true;
4985 case ActionType.DeleteString:
4986 start = document.GetLine (action.line_no);
4987 document.SuspendUpdate ();
4988 document.DeleteMultiline (start, action.pos, ((Line) action.data).text.Length);
4989 document.PositionCaret (start, action.pos);
4990 document.SetSelectionToCaret (true);
4991 document.ResumeUpdate (true);
4995 } while (!user_action_finished && redo_actions.Count > 0);
4999 #endregion // Internal Methods
5001 #region Private Methods
5003 public void BeginUserAction (string name)
5008 Action ua = new Action ();
5009 ua.type = ActionType.UserActionBegin;
5012 undo_actions.Push (ua);
5015 public void EndUserAction ()
5020 Action ua = new Action ();
5021 ua.type = ActionType.UserActionEnd;
5023 undo_actions.Push (ua);
5026 // start_pos, end_pos = 1 based
5027 public void RecordDeleteString (Line start_line, int start_pos, Line end_line, int end_pos)
5032 Action a = new Action ();
5034 // We cant simply store the string, because then formatting would be lost
5035 a.type = ActionType.DeleteString;
5036 a.line_no = start_line.line_no;
5038 a.data = Duplicate (start_line, start_pos, end_line, end_pos);
5040 undo_actions.Push(a);
5043 public void RecordInsertString (Line line, int pos, string str)
5045 if (locked || str.Length == 0)
5048 Action a = new Action ();
5050 a.type = ActionType.InsertString;
5052 a.line_no = line.line_no;
5055 undo_actions.Push (a);
5058 public void RecordTyping (Line line, int pos, char ch)
5065 if (undo_actions.Count > 0)
5066 a = (Action) undo_actions.Peek ();
5068 if (a == null || a.type != ActionType.Typing) {
5070 a.type = ActionType.Typing;
5071 a.data = new StringBuilder ();
5072 a.line_no = line.line_no;
5075 undo_actions.Push (a);
5078 StringBuilder data = (StringBuilder) a.data;
5082 // start_pos = 1-based
5083 // end_pos = 1-based
5084 public Line Duplicate(Line start_line, int start_pos, Line end_line, int end_pos)
5090 LineTag current_tag;
5095 line = new Line (start_line.document);
5098 for (int i = start_line.line_no; i <= end_line.line_no; i++) {
5099 current = document.GetLine(i);
5101 if (start_line.line_no == i) {
5107 if (end_line.line_no == i) {
5110 end = current.text.Length;
5117 line.text = new StringBuilder (current.text.ToString (start, end - start));
5119 // Copy tags from start to start+length onto new line
5120 current_tag = current.FindTag (start);
5121 while ((current_tag != null) && (current_tag.start < end)) {
5122 if ((current_tag.start <= start) && (start < (current_tag.start + current_tag.length))) {
5123 // start tag is within this tag
5126 tag_start = current_tag.start;
5129 tag = new LineTag(line, tag_start - start + 1);
5130 tag.CopyFormattingFrom (current_tag);
5132 current_tag = current_tag.next;
5134 // Add the new tag to the line
5135 if (line.tags == null) {
5141 while (tail.next != null) {
5145 tag.previous = tail;
5149 if ((i + 1) <= end_line.line_no) {
5150 line.soft_break = current.soft_break;
5152 // Chain them (we use right/left as next/previous)
5153 line.right = new Line (start_line.document);
5154 line.right.left = line;
5162 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
5163 internal void Insert(Line line, int pos, Line insert, bool select)
5171 // Handle special case first
5172 if (insert.right == null) {
5174 // Single line insert
5175 document.Split(line, pos);
5177 if (insert.tags == null) {
5178 return; // Blank line
5181 //Insert our tags at the end
5184 while (tag.next != null) {
5188 offset = tag.start + tag.length - 1;
5190 tag.next = insert.tags;
5191 line.text.Insert(offset, insert.text.ToString());
5193 // Adjust start locations
5195 while (tag != null) {
5196 tag.start += offset;
5200 // Put it back together
5201 document.Combine(line.line_no, line.line_no + 1);
5204 document.SetSelectionStart (line, pos);
5205 document.SetSelectionEnd (line, pos + insert.text.Length);
5208 document.UpdateView(line, pos);
5216 while (current != null) {
5217 if (current == insert) {
5218 // Inserting the first line we split the line (and make space)
5219 document.Split(line, pos);
5220 //Insert our tags at the end of the line
5224 while (tag.next != null) {
5227 offset = tag.start + tag.length - 1;
5228 tag.next = current.tags;
5229 tag.next.previous = tag;
5235 line.tags = current.tags;
5236 line.tags.previous = null;
5240 document.Split(line.line_no, 0);
5242 line.tags = current.tags;
5243 line.tags.previous = null;
5246 // Adjust start locations and line pointers
5247 while (tag != null) {
5248 tag.start += offset;
5253 line.text.Insert(offset, current.text.ToString());
5254 line.Grow(line.text.Length);
5257 line = document.GetLine(line.line_no + 1);
5259 // FIXME? Test undo of line-boundaries
5260 if ((current.right == null) && (current.tags.length != 0)) {
5261 document.Combine(line.line_no - 1, line.line_no);
5263 current = current.right;
5268 // Recalculate our document
5269 document.UpdateView(first, lines, pos);
5272 #endregion // Private Methods