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;
54 namespace System.Windows.Forms {
55 internal enum LineColor {
60 internal enum CaretSelection {
61 Position, // Selection=Caret
62 Word, // Selection=Word under caret
63 Line // Selection=Line under caret
66 internal class FontDefinition {
69 internal FontStyle add_style;
70 internal FontStyle remove_style;
72 internal Font font_obj;
76 internal enum FormatSpecified {
84 internal enum CaretDirection {
85 CharForward, // Move a char to the right
86 CharBack, // Move a char to the left
87 LineUp, // Move a line up
88 LineDown, // Move a line down
89 Home, // Move to the beginning of the line
90 End, // Move to the end of the line
91 PgUp, // Move one page up
92 PgDn, // Move one page down
93 CtrlPgUp, // Move caret to the first visible char in the viewport
94 CtrlPgDn, // Move caret to the last visible char in the viewport
95 CtrlHome, // Move to the beginning of the document
96 CtrlEnd, // Move to the end of the document
97 WordBack, // Move to the beginning of the previous word (or beginning of line)
98 WordForward, // Move to the beginning of the next word (or end of line)
99 SelectionStart, // Move to the beginning of the current selection
100 SelectionEnd, // Move to the end of the current selection
101 CharForwardNoWrap, // Move a char forward, but don't wrap onto the next line
102 CharBackNoWrap // Move a char backward, but don't wrap onto the previous line
105 // Being cloneable should allow for nice line and document copies...
106 internal class Line : ICloneable, IComparable {
107 #region Local Variables
109 internal Document document;
111 // Stuff that matters for our line
112 internal StringBuilder text; // Characters for the line
113 internal float[] widths; // Width of each character; always one larger than text.Length
114 internal int space; // Number of elements in text and widths
115 internal int line_no; // Line number
116 internal LineTag tags; // Tags describing the text
117 internal int offset; // Baseline can be on the X or Y axis depending if we are in multiline mode or not
118 internal int height; // Height of the line (height of tallest tag)
119 internal int ascent; // Ascent of the line (ascent of the tallest tag)
120 internal HorizontalAlignment alignment; // Alignment of the line
121 internal int align_shift; // Pixel shift caused by the alignment
122 internal bool soft_break; // Tag is 'broken soft' and continuation from previous line
123 internal int indent; // Left indent for the first line
124 internal int hanging_indent; // Hanging indent (left indent for all but the first line)
125 internal int right_indent; // Right indent for all lines
126 internal bool carriage_return;
129 // Stuff that's important for the tree
130 internal Line parent; // Our parent line
131 internal Line left; // Line with smaller line number
132 internal Line right; // Line with higher line number
133 internal LineColor color; // We're doing a black/red tree. this is the node color
134 internal int DEFAULT_TEXT_LEN; //
135 internal bool recalc; // Line changed
136 #endregion // Local Variables
139 internal Line (Document document)
141 this.document = document;
142 color = LineColor.Red;
149 alignment = HorizontalAlignment.Left;
152 internal Line(Document document, int LineNo, string Text, Font font, SolidBrush color) : this (document) {
153 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
155 text = new StringBuilder(Text, space);
158 widths = new float[space + 1];
159 tags = new LineTag(this, 1);
164 internal Line(Document document, int LineNo, string Text, HorizontalAlignment align, Font font, SolidBrush color) : this(document) {
165 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
167 text = new StringBuilder(Text, space);
171 widths = new float[space + 1];
172 tags = new LineTag(this, 1);
177 internal Line(Document document, int LineNo, string Text, LineTag tag) : this(document) {
178 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
180 text = new StringBuilder(Text, space);
183 widths = new float[space + 1];
187 #endregion // Constructors
189 #region Internal Properties
193 if (!document.multiline)
201 if (document.multiline)
209 int res = (int) widths [text.Length];
210 if (!document.multiline) {
217 internal int Indent {
228 internal int HangingIndent {
230 return hanging_indent;
234 hanging_indent = value;
239 internal int RightIndent {
245 right_indent = value;
251 internal int Height {
261 internal int LineNo {
271 internal string Text {
273 return text.ToString();
277 text = new StringBuilder(value, value.Length > DEFAULT_TEXT_LEN ? value.Length : DEFAULT_TEXT_LEN);
281 internal HorizontalAlignment Alignment {
287 if (alignment != value) {
294 internal StringBuilder Text {
304 #endregion // Internal Properties
306 #region Internal Methods
307 // Make sure we always have enoughs space in text and widths
308 internal void Grow(int minimum) {
312 length = text.Length;
314 if ((length + minimum) > space) {
315 // We need to grow; double the size
317 if ((length + minimum) > (space * 2)) {
318 new_widths = new float[length + minimum * 2 + 1];
319 space = length + minimum * 2;
321 new_widths = new float[space * 2 + 1];
324 widths.CopyTo(new_widths, 0);
330 internal void Streamline(int lines) {
337 // Catch what the loop below wont; eliminate 0 length
338 // tags, but only if there are other tags after us
339 while ((current.length == 0) && (next != null)) {
341 tags.previous = null;
350 while (next != null) {
351 // Take out 0 length tags unless it's the last tag in the document
352 if (next.length == 0) {
353 if ((next.next != null) || (line_no != lines)) {
354 current.next = next.next;
355 if (current.next != null) {
356 current.next.previous = current;
362 if (current.Combine(next)) {
367 current = current.next;
372 /// <summary> Find the tag on a line based on the character position, pos is 0-based</summary>
373 internal LineTag FindTag(int pos) {
382 if (pos >= text.Length) {
383 pos = text.Length - 1;
386 while (tag != null) {
387 if (((tag.start - 1) <= pos) && (pos < (tag.start + tag.length - 1))) {
388 return LineTag.GetFinalTag (tag);
396 /// Recalculate a single line using the same char for every character in the line
399 internal bool RecalculatePasswordLine(Graphics g, Document doc) {
408 len = this.text.Length;
416 w = g.MeasureString(doc.password_char, tags.font, 10000, Document.string_format).Width;
418 if (this.height != (int)tag.font.Height) {
424 this.height = (int)tag.font.Height;
425 tag.height = this.height;
427 XplatUI.GetFontMetrics(g, tag.font, out tag.ascent, out descent);
428 this.ascent = tag.ascent;
432 widths[pos] = widths[pos-1] + w;
439 /// Go through all tags on a line and recalculate all size-related values;
440 /// returns true if lineheight changed
442 internal bool RecalculateLine(Graphics g, Document doc) {
455 len = this.text.Length;
457 prev_height = this.height; // For drawing optimization calculations
458 this.height = 0; // Reset line height
459 this.ascent = 0; // Reset the ascent for the line
462 if (this.soft_break) {
463 widths[0] = hanging_indent;
476 while (tag.length == 0) { // We should always have tags after a tag.length==0 unless len==0
482 size = tag.SizeOfPosition (g, pos);
485 if (Char.IsWhiteSpace(text[pos])) {
490 if ((wrap_pos > 0) && (wrap_pos != len) && (widths[pos] + w) + 5 > (doc.viewport_width - this.right_indent)) {
491 // Make sure to set the last width of the line before wrapping
492 widths [pos + 1] = widths [pos] + w;
496 doc.Split(this, tag, pos, this.soft_break);
497 this.soft_break = true;
498 len = this.text.Length;
502 } else if (pos > 1 && (widths[pos] + w) > (doc.viewport_width - this.right_indent)) {
503 // No suitable wrap position was found so break right in the middle of a word
505 // Make sure to set the last width of the line before wrapping
506 widths [pos + 1] = widths [pos] + w;
508 doc.Split(this, tag, pos, this.soft_break);
509 this.soft_break = true;
510 len = this.text.Length;
516 // Contract all soft lines that follow back into our line
520 widths[pos] = widths[pos-1] + w;
523 line = doc.GetLine(this.line_no + 1);
524 if ((line != null) && soft_break) {
525 // Pull the two lines together
526 doc.Combine(this.line_no, this.line_no + 1);
527 len = this.text.Length;
533 if (pos == (tag.start-1 + tag.length)) {
534 // We just found the end of our current tag
535 tag.height = tag.MaxHeight ();
537 // Check if we're the tallest on the line (so far)
538 if (tag.height > this.height) {
539 this.height = tag.height; // Yep; make sure the line knows
542 if (tag.ascent == 0) {
545 XplatUI.GetFontMetrics(g, tag.font, out tag.ascent, out descent);
548 if (tag.ascent > this.ascent) {
551 // We have a tag that has a taller ascent than the line;
553 while (t != null && t != tag) {
554 t.shift = tag.ascent - t.ascent;
559 this.ascent = tag.ascent;
561 tag.shift = this.ascent - tag.ascent;
572 if (this.height == 0) {
573 this.height = tags.font.Height;
574 tag.height = this.height;
577 if (prev_height != this.height) {
582 #endregion // Internal Methods
584 #region Administrative
585 public int CompareTo(object obj) {
590 if (! (obj is Line)) {
591 throw new ArgumentException("Object is not of type Line", "obj");
594 if (line_no < ((Line)obj).line_no) {
596 } else if (line_no > ((Line)obj).line_no) {
603 public object Clone() {
606 clone = new Line (document);
611 clone.left = (Line)left.Clone();
615 clone.left = (Line)left.Clone();
621 internal object CloneLine() {
624 clone = new Line (document);
631 public override bool Equals(object obj) {
636 if (!(obj is Line)) {
644 if (line_no == ((Line)obj).line_no) {
651 public override int GetHashCode() {
652 return base.GetHashCode ();
655 public override string ToString() {
656 return "Line " + line_no;
659 #endregion // Administrative
662 internal class Document : ICloneable, IEnumerable {
664 // FIXME - go through code and check for places where
665 // we do explicit comparisons instead of using the compare overloads
666 internal struct Marker {
668 internal LineTag tag;
672 public static bool operator<(Marker lhs, Marker rhs) {
673 if (lhs.line.line_no < rhs.line.line_no) {
677 if (lhs.line.line_no == rhs.line.line_no) {
678 if (lhs.pos < rhs.pos) {
685 public static bool operator>(Marker lhs, Marker rhs) {
686 if (lhs.line.line_no > rhs.line.line_no) {
690 if (lhs.line.line_no == rhs.line.line_no) {
691 if (lhs.pos > rhs.pos) {
698 public static bool operator==(Marker lhs, Marker rhs) {
699 if ((lhs.line.line_no == rhs.line.line_no) && (lhs.pos == rhs.pos)) {
705 public static bool operator!=(Marker lhs, Marker rhs) {
706 if ((lhs.line.line_no != rhs.line.line_no) || (lhs.pos != rhs.pos)) {
712 public void Combine(Line move_to_line, int move_to_line_length) {
714 pos += move_to_line_length;
715 tag = LineTag.FindTag(line, pos);
718 // This is for future use, right now Document.Split does it by hand, with some added shortcut logic
719 public void Split(Line move_to_line, int split_at) {
722 tag = LineTag.FindTag(line, pos);
725 public override bool Equals(object obj) {
726 return this==(Marker)obj;
729 public override int GetHashCode() {
730 return base.GetHashCode ();
733 public override string ToString() {
734 return "Marker Line " + line + ", Position " + pos;
738 #endregion Structures
740 #region Local Variables
741 private Line document;
743 private Line sentinel;
744 private int document_id;
745 private Random random = new Random();
746 internal string password_char;
747 private StringBuilder password_cache;
748 private bool calc_pass;
749 private int char_count;
751 // For calculating widths/heights
752 public static readonly StringFormat string_format = new StringFormat (StringFormat.GenericTypographic);
754 private int recalc_suspended;
755 private bool recalc_pending;
756 private int recalc_start = 1; // This starts at one, since lines are 1 based
757 private int recalc_end;
758 private bool recalc_optimize;
760 private int update_suspended;
761 private bool update_pending;
762 private int update_start = 1;
764 internal bool multiline;
767 internal UndoManager undo;
769 internal Marker caret;
770 internal Marker selection_start;
771 internal Marker selection_end;
772 internal bool selection_visible;
773 internal Marker selection_anchor;
774 internal Marker selection_prev;
775 internal bool selection_end_anchor;
777 internal int viewport_x;
778 internal int viewport_y; // The visible area of the document
779 internal int viewport_width;
780 internal int viewport_height;
782 internal int document_x; // Width of the document
783 internal int document_y; // Height of the document
785 internal Rectangle invalid;
787 internal int crlf_size; // 1 or 2, depending on whether we use \r\n or just \n
789 internal TextBoxBase owner; // Who's owning us?
790 static internal int caret_width = 1;
791 static internal int caret_shift = 1;
792 #endregion // Local Variables
795 internal Document(TextBoxBase owner) {
803 recalc_pending = false;
805 // Tree related stuff
806 sentinel = new Line (this);
807 sentinel.color = LineColor.Black;
811 // We always have a blank line
812 owner.HandleCreated += new EventHandler(owner_HandleCreated);
813 owner.VisibleChanged += new EventHandler(owner_VisibleChanged);
815 Add(1, "", owner.Font, ThemeEngine.Current.ResPool.GetSolidBrush(owner.ForeColor));
816 Line l = GetLine (1);
819 undo = new UndoManager (this);
821 selection_visible = false;
822 selection_start.line = this.document;
823 selection_start.pos = 0;
824 selection_start.tag = selection_start.line.tags;
825 selection_end.line = this.document;
826 selection_end.pos = 0;
827 selection_end.tag = selection_end.line.tags;
828 selection_anchor.line = this.document;
829 selection_anchor.pos = 0;
830 selection_anchor.tag = selection_anchor.line.tags;
831 caret.line = this.document;
833 caret.tag = caret.line.tags;
840 // Default selection is empty
842 document_id = random.Next();
844 string_format.Trimming = StringTrimming.None;
845 string_format.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;
849 #region Internal Properties
866 internal Line CaretLine {
872 internal int CaretPosition {
878 internal Point Caret {
880 return new Point((int)caret.tag.line.widths[caret.pos] + caret.line.X, caret.line.Y);
884 internal LineTag CaretTag {
894 internal int CRLFSize {
904 internal string PasswordChar {
906 return password_char;
910 password_char = value;
911 PasswordCache.Length = 0;
912 if ((password_char.Length != 0) && (password_char[0] != '\0')) {
920 private StringBuilder PasswordCache {
922 if (password_cache == null)
923 password_cache = new StringBuilder();
924 return password_cache;
928 internal int ViewPortX {
938 internal int Length {
940 return char_count + lines - 1; // Add \n for each line but the last
944 private int CharCount {
952 if (LengthChanged != null) {
953 LengthChanged(this, EventArgs.Empty);
958 internal int ViewPortY {
968 internal int ViewPortWidth {
970 return viewport_width;
974 viewport_width = value;
978 internal int ViewPortHeight {
980 return viewport_height;
984 viewport_height = value;
991 return this.document_x;
995 internal int Height {
997 return this.document_y;
1001 internal bool SelectionVisible {
1003 return selection_visible;
1007 internal bool Wrap {
1017 #endregion // Internal Properties
1019 #region Private Methods
1021 internal void SuspendRecalc ()
1026 internal void ResumeRecalc (bool immediate_update)
1028 if (recalc_suspended > 0)
1031 if (immediate_update && recalc_suspended == 0 && recalc_pending) {
1032 RecalculateDocument (owner.CreateGraphicsInternal(), recalc_start, recalc_end, recalc_optimize);
1033 recalc_pending = false;
1037 internal void SuspendUpdate ()
1042 internal void ResumeUpdate (bool immediate_update)
1044 if (update_suspended > 0)
1047 if (immediate_update && update_suspended == 0 && update_pending) {
1048 UpdateView (GetLine (update_start), 0);
1049 update_pending = false;
1054 internal int DumpTree(Line line, bool with_tags) {
1059 Console.Write("Line {0} [# {1}], Y: {2}, soft: {3}, Text: '{4}'",
1060 line.line_no, line.GetHashCode(), line.Y, line.soft_break,
1061 line.text != null ? line.text.ToString() : "undefined");
1063 if (line.left == sentinel) {
1064 Console.Write(", left = sentinel");
1065 } else if (line.left == null) {
1066 Console.Write(", left = NULL");
1069 if (line.right == sentinel) {
1070 Console.Write(", right = sentinel");
1071 } else if (line.right == null) {
1072 Console.Write(", right = NULL");
1075 Console.WriteLine("");
1085 Console.Write(" Tags: ");
1086 while (tag != null) {
1087 Console.Write("{0} <{1}>-<{2}>", count++, tag.start, tag.end
1088 /*line.text.ToString (tag.start - 1, tag.length)*/);
1089 length += tag.length;
1091 if (tag.line != line) {
1092 Console.Write("BAD line link");
1093 throw new Exception("Bad line link in tree");
1097 Console.Write(", ");
1100 if (length > line.text.Length) {
1101 throw new Exception(String.Format("Length of tags more than length of text on line (expected {0} calculated {1})", line.text.Length, length));
1102 } else if (length < line.text.Length) {
1103 throw new Exception(String.Format("Length of tags less than length of text on line (expected {0} calculated {1})", line.text.Length, length));
1105 Console.WriteLine("");
1107 if (line.left != null) {
1108 if (line.left != sentinel) {
1109 total += DumpTree(line.left, with_tags);
1112 if (line != sentinel) {
1113 throw new Exception("Left should not be NULL");
1117 if (line.right != null) {
1118 if (line.right != sentinel) {
1119 total += DumpTree(line.right, with_tags);
1122 if (line != sentinel) {
1123 throw new Exception("Right should not be NULL");
1127 for (int i = 1; i <= this.lines; i++) {
1128 if (GetLine(i) == null) {
1129 throw new Exception(String.Format("Hole in line order, missing {0}", i));
1133 if (line == this.Root) {
1134 if (total < this.lines) {
1135 throw new Exception(String.Format("Not enough nodes in tree, found {0}, expected {1}", total, this.lines));
1136 } else if (total > this.lines) {
1137 throw new Exception(String.Format("Too many nodes in tree, found {0}, expected {1}", total, this.lines));
1144 private void SetSelectionVisible (bool value)
1146 selection_visible = value;
1148 // cursor and selection are enemies, we can't have both in the same room at the same time
1149 if (owner.IsHandleCreated && !owner.show_caret_w_selection)
1150 XplatUI.CaretVisible (owner.Handle, !selection_visible);
1153 private void DecrementLines(int line_no) {
1157 while (current <= lines) {
1158 GetLine(current).line_no--;
1164 private void IncrementLines(int line_no) {
1167 current = this.lines;
1168 while (current >= line_no) {
1169 GetLine(current).line_no++;
1175 private void RebalanceAfterAdd(Line line1) {
1178 while ((line1 != document) && (line1.parent.color == LineColor.Red)) {
1179 if (line1.parent == line1.parent.parent.left) {
1180 line2 = line1.parent.parent.right;
1182 if ((line2 != null) && (line2.color == LineColor.Red)) {
1183 line1.parent.color = LineColor.Black;
1184 line2.color = LineColor.Black;
1185 line1.parent.parent.color = LineColor.Red;
1186 line1 = line1.parent.parent;
1188 if (line1 == line1.parent.right) {
1189 line1 = line1.parent;
1193 line1.parent.color = LineColor.Black;
1194 line1.parent.parent.color = LineColor.Red;
1196 RotateRight(line1.parent.parent);
1199 line2 = line1.parent.parent.left;
1201 if ((line2 != null) && (line2.color == LineColor.Red)) {
1202 line1.parent.color = LineColor.Black;
1203 line2.color = LineColor.Black;
1204 line1.parent.parent.color = LineColor.Red;
1205 line1 = line1.parent.parent;
1207 if (line1 == line1.parent.left) {
1208 line1 = line1.parent;
1212 line1.parent.color = LineColor.Black;
1213 line1.parent.parent.color = LineColor.Red;
1214 RotateLeft(line1.parent.parent);
1218 document.color = LineColor.Black;
1221 private void RebalanceAfterDelete(Line line1) {
1224 while ((line1 != document) && (line1.color == LineColor.Black)) {
1225 if (line1 == line1.parent.left) {
1226 line2 = line1.parent.right;
1227 if (line2.color == LineColor.Red) {
1228 line2.color = LineColor.Black;
1229 line1.parent.color = LineColor.Red;
1230 RotateLeft(line1.parent);
1231 line2 = line1.parent.right;
1233 if ((line2.left.color == LineColor.Black) && (line2.right.color == LineColor.Black)) {
1234 line2.color = LineColor.Red;
1235 line1 = line1.parent;
1237 if (line2.right.color == LineColor.Black) {
1238 line2.left.color = LineColor.Black;
1239 line2.color = LineColor.Red;
1241 line2 = line1.parent.right;
1243 line2.color = line1.parent.color;
1244 line1.parent.color = LineColor.Black;
1245 line2.right.color = LineColor.Black;
1246 RotateLeft(line1.parent);
1250 line2 = line1.parent.left;
1251 if (line2.color == LineColor.Red) {
1252 line2.color = LineColor.Black;
1253 line1.parent.color = LineColor.Red;
1254 RotateRight(line1.parent);
1255 line2 = line1.parent.left;
1257 if ((line2.right.color == LineColor.Black) && (line2.left.color == LineColor.Black)) {
1258 line2.color = LineColor.Red;
1259 line1 = line1.parent;
1261 if (line2.left.color == LineColor.Black) {
1262 line2.right.color = LineColor.Black;
1263 line2.color = LineColor.Red;
1265 line2 = line1.parent.left;
1267 line2.color = line1.parent.color;
1268 line1.parent.color = LineColor.Black;
1269 line2.left.color = LineColor.Black;
1270 RotateRight(line1.parent);
1275 line1.color = LineColor.Black;
1278 private void RotateLeft(Line line1) {
1279 Line line2 = line1.right;
1281 line1.right = line2.left;
1283 if (line2.left != sentinel) {
1284 line2.left.parent = line1;
1287 if (line2 != sentinel) {
1288 line2.parent = line1.parent;
1291 if (line1.parent != null) {
1292 if (line1 == line1.parent.left) {
1293 line1.parent.left = line2;
1295 line1.parent.right = line2;
1302 if (line1 != sentinel) {
1303 line1.parent = line2;
1307 private void RotateRight(Line line1) {
1308 Line line2 = line1.left;
1310 line1.left = line2.right;
1312 if (line2.right != sentinel) {
1313 line2.right.parent = line1;
1316 if (line2 != sentinel) {
1317 line2.parent = line1.parent;
1320 if (line1.parent != null) {
1321 if (line1 == line1.parent.right) {
1322 line1.parent.right = line2;
1324 line1.parent.left = line2;
1330 line2.right = line1;
1331 if (line1 != sentinel) {
1332 line1.parent = line2;
1337 internal void UpdateView(Line line, int pos) {
1338 if (!owner.IsHandleCreated) {
1342 if (update_suspended > 0) {
1343 update_start = Math.Min (update_start, line.line_no);
1344 // update_end = Math.Max (update_end, line.line_no);
1345 // recalc_optimize = true;
1346 update_pending = true;
1350 // Optimize invalidation based on Line alignment
1351 if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no, true)) {
1352 // Lineheight changed, invalidate the rest of the document
1353 if ((line.Y - viewport_y) >=0 ) {
1354 // We formatted something that's in view, only draw parts of the screen
1355 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, owner.Height - line.Y - viewport_y));
1357 // The tag was above the visible area, draw everything
1361 switch(line.alignment) {
1362 case HorizontalAlignment.Left: {
1363 owner.Invalidate(new Rectangle(line.X + (int)line.widths[pos] - viewport_x - 1, line.Y - viewport_y, viewport_width, line.height + 1));
1367 case HorizontalAlignment.Center: {
1368 owner.Invalidate(new Rectangle(line.X, line.Y - viewport_y, viewport_width, line.height + 1));
1372 case HorizontalAlignment.Right: {
1373 owner.Invalidate(new Rectangle(line.X, line.Y - viewport_y, (int)line.widths[pos + 1] - viewport_x + line.X, line.height + 1));
1381 // Update display from line, down line_count lines; pos is unused, but required for the signature
1382 internal void UpdateView(Line line, int line_count, int pos) {
1383 if (!owner.IsHandleCreated) {
1387 if (recalc_suspended > 0) {
1388 recalc_start = Math.Min (recalc_start, line.line_no);
1389 recalc_end = Math.Max (recalc_end, line.line_no + line_count - 1);
1390 recalc_optimize = true;
1391 recalc_pending = true;
1395 if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no + line_count - 1, true)) {
1396 // Lineheight changed, invalidate the rest of the document
1397 if ((line.Y - viewport_y) >=0 ) {
1398 // We formatted something that's in view, only draw parts of the screen
1399 //blah Console.WriteLine("TextControl.cs(981) Invalidate called in UpdateView(line, line_count, pos)");
1400 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, owner.Height - line.Y - viewport_y));
1402 // The tag was above the visible area, draw everything
1403 //blah Console.WriteLine("TextControl.cs(985) Invalidate called in UpdateView(line, line_count, pos)");
1409 end_line = GetLine(line.line_no + line_count -1);
1410 if (end_line == null) {
1414 //blah Console.WriteLine("TextControl.cs(996) Invalidate called in UpdateView(line, line_count, pos)");
1415 owner.Invalidate(new Rectangle(0 - viewport_x, line.Y - viewport_y, (int)line.widths[line.text.Length], end_line.Y + end_line.height));
1418 #endregion // Private Methods
1420 #region Internal Methods
1421 // Clear the document and reset state
1422 internal void Empty() {
1424 document = sentinel;
1427 // We always have a blank line
1428 Add(1, "", owner.Font, ThemeEngine.Current.ResPool.GetSolidBrush(owner.ForeColor));
1429 Line l = GetLine (1);
1430 l.soft_break = true;
1432 this.RecalculateDocument(owner.CreateGraphicsInternal());
1433 PositionCaret(0, 0);
1435 SetSelectionVisible (false);
1437 selection_start.line = this.document;
1438 selection_start.pos = 0;
1439 selection_start.tag = selection_start.line.tags;
1440 selection_end.line = this.document;
1441 selection_end.pos = 0;
1442 selection_end.tag = selection_end.line.tags;
1451 if (owner.IsHandleCreated)
1452 owner.Invalidate ();
1455 internal void PositionCaret(Line line, int pos) {
1456 caret.tag = line.FindTag(pos);
1460 if (owner.IsHandleCreated) {
1461 if (owner.Focused) {
1462 if (caret.height != caret.tag.height)
1463 XplatUI.CreateCaret (owner.Handle, caret_width, caret.height);
1464 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);
1467 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1470 // We set this at the end because we use the heights to determine whether or
1471 // not we need to recreate the caret
1472 caret.height = caret.tag.height;
1476 internal void PositionCaret(int x, int y) {
1477 if (!owner.IsHandleCreated) {
1481 caret.tag = FindCursor(x, y, out caret.pos);
1482 caret.line = caret.tag.line;
1483 caret.height = caret.tag.height;
1485 if (owner.Focused) {
1486 XplatUI.CreateCaret (owner.Handle, caret_width, caret.height);
1487 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);
1490 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1493 internal void CaretHasFocus() {
1494 if ((caret.tag != null) && owner.IsHandleCreated) {
1495 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1496 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);
1501 if (owner.IsHandleCreated && selection_visible) {
1502 InvalidateSelectionArea ();
1506 internal void CaretLostFocus() {
1507 if (!owner.IsHandleCreated) {
1510 XplatUI.DestroyCaret(owner.Handle);
1513 internal void AlignCaret() {
1514 if (!owner.IsHandleCreated) {
1518 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1519 caret.height = caret.tag.height;
1521 if (owner.Focused) {
1522 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1523 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);
1527 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1530 internal void UpdateCaret() {
1531 if (!owner.IsHandleCreated || caret.tag == null) {
1535 if (caret.tag.height != caret.height) {
1536 caret.height = caret.tag.height;
1537 if (owner.Focused) {
1538 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1542 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);
1546 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1549 internal void DisplayCaret() {
1550 if (!owner.IsHandleCreated) {
1554 if (owner.ShowSelection && (!selection_visible || owner.show_caret_w_selection)) {
1555 XplatUI.CaretVisible(owner.Handle, true);
1559 internal void HideCaret() {
1560 if (!owner.IsHandleCreated) {
1564 if (owner.Focused) {
1565 XplatUI.CaretVisible(owner.Handle, false);
1569 internal void MoveCaret(CaretDirection direction) {
1570 // FIXME should we use IsWordSeparator to detect whitespace, instead
1571 // of looking for actual spaces in the Word move cases?
1573 bool nowrap = false;
1575 case CaretDirection.CharForwardNoWrap:
1577 goto case CaretDirection.CharForward;
1578 case CaretDirection.CharForward: {
1580 if (caret.pos > caret.line.text.Length) {
1582 // Go into next line
1583 if (caret.line.line_no < this.lines) {
1584 caret.line = GetLine(caret.line.line_no+1);
1586 caret.tag = caret.line.tags;
1591 // Single line; we stay where we are
1595 if ((caret.tag.start - 1 + caret.tag.length) < caret.pos) {
1596 caret.tag = caret.tag.next;
1603 case CaretDirection.CharBackNoWrap:
1605 goto case CaretDirection.CharBack;
1606 case CaretDirection.CharBack: {
1607 if (caret.pos > 0) {
1608 // caret.pos--; // folded into the if below
1609 if (--caret.pos > 0) {
1610 if (caret.tag.start > caret.pos) {
1611 caret.tag = caret.tag.previous;
1615 if (caret.line.line_no > 1 && !nowrap) {
1616 caret.line = GetLine(caret.line.line_no - 1);
1617 caret.pos = caret.line.text.Length;
1618 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1625 case CaretDirection.WordForward: {
1628 len = caret.line.text.Length;
1629 if (caret.pos < len) {
1630 while ((caret.pos < len) && (caret.line.text[caret.pos] != ' ')) {
1633 if (caret.pos < len) {
1634 // Skip any whitespace
1635 while ((caret.pos < len) && (caret.line.text[caret.pos] == ' ')) {
1639 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1641 if (caret.line.line_no < this.lines) {
1642 caret.line = GetLine(caret.line.line_no + 1);
1644 caret.tag = caret.line.tags;
1651 case CaretDirection.WordBack: {
1652 if (caret.pos > 0) {
1655 while ((caret.pos > 0) && (caret.line.text[caret.pos] == ' ')) {
1659 while ((caret.pos > 0) && (caret.line.text[caret.pos] != ' ')) {
1663 if (caret.line.text.ToString(caret.pos, 1) == " ") {
1664 if (caret.pos != 0) {
1667 caret.line = GetLine(caret.line.line_no - 1);
1668 caret.pos = caret.line.text.Length;
1671 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1673 if (caret.line.line_no > 1) {
1674 caret.line = GetLine(caret.line.line_no - 1);
1675 caret.pos = caret.line.text.Length;
1676 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1683 case CaretDirection.LineUp: {
1684 if (caret.line.line_no > 1) {
1687 pixel = (int)caret.line.widths[caret.pos];
1688 PositionCaret(pixel, GetLine(caret.line.line_no - 1).Y);
1695 case CaretDirection.LineDown: {
1696 if (caret.line.line_no < lines) {
1699 pixel = (int)caret.line.widths[caret.pos];
1700 PositionCaret(pixel, GetLine(caret.line.line_no + 1).Y);
1707 case CaretDirection.Home: {
1708 if (caret.pos > 0) {
1710 caret.tag = caret.line.tags;
1716 case CaretDirection.End: {
1717 if (caret.pos < caret.line.text.Length) {
1718 caret.pos = caret.line.text.Length;
1719 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1725 case CaretDirection.PgUp: {
1727 int new_y, y_offset;
1729 if (viewport_y == 0) {
1731 // This should probably be handled elsewhere
1732 if (!(owner is RichTextBox)) {
1733 // Page down doesn't do anything in a regular TextBox
1734 // if the bottom of the document
1735 // is already visible, the page and the caret stay still
1739 // We're just placing the caret at the end of the document, no scrolling needed
1740 owner.vscroll.Value = 0;
1741 Line line = GetLine (1);
1742 PositionCaret (line, 0);
1745 y_offset = caret.line.Y - viewport_y;
1746 new_y = caret.line.Y - viewport_height;
1748 owner.vscroll.Value = Math.Max (new_y, 0);
1749 PositionCaret ((int)caret.line.widths[caret.pos], y_offset + viewport_y);
1753 case CaretDirection.PgDn: {
1754 int new_y, y_offset;
1756 if ((viewport_y + viewport_height) > document_y) {
1758 // This should probably be handled elsewhere
1759 if (!(owner is RichTextBox)) {
1760 // Page up doesn't do anything in a regular TextBox
1761 // if the bottom of the document
1762 // is already visible, the page and the caret stay still
1766 // We're just placing the caret at the end of the document, no scrolling needed
1767 owner.vscroll.Value = owner.vscroll.Maximum - viewport_height + 1;
1768 Line line = GetLine (lines);
1769 PositionCaret (line, line.Text.Length);
1772 y_offset = caret.line.Y - viewport_y;
1773 new_y = caret.line.Y + viewport_height;
1775 owner.vscroll.Value = Math.Min (new_y, owner.vscroll.Maximum - viewport_height + 1);
1776 PositionCaret ((int)caret.line.widths[caret.pos], y_offset + viewport_y);
1781 case CaretDirection.CtrlPgUp: {
1782 PositionCaret(0, viewport_y);
1787 case CaretDirection.CtrlPgDn: {
1792 tag = FindTag(0, viewport_y + viewport_height, out index, false);
1793 if (tag.line.line_no > 1) {
1794 line = GetLine(tag.line.line_no - 1);
1798 PositionCaret(line, line.Text.Length);
1803 case CaretDirection.CtrlHome: {
1804 caret.line = GetLine(1);
1806 caret.tag = caret.line.tags;
1812 case CaretDirection.CtrlEnd: {
1813 caret.line = GetLine(lines);
1814 caret.pos = caret.line.text.Length;
1815 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1821 case CaretDirection.SelectionStart: {
1822 caret.line = selection_start.line;
1823 caret.pos = selection_start.pos;
1824 caret.tag = selection_start.tag;
1830 case CaretDirection.SelectionEnd: {
1831 caret.line = selection_end.line;
1832 caret.pos = selection_end.pos;
1833 caret.tag = selection_end.tag;
1841 internal void DumpDoc ()
1843 Console.WriteLine ("<doc>");
1844 for (int i = 1; i < lines; i++) {
1845 Line line = GetLine (i);
1846 Console.WriteLine ("<line no='{0}'>", line.line_no);
1848 LineTag tag = line.tags;
1849 while (tag != null) {
1850 Console.Write ("\t<tag type='{0}' span='{1}->{2}'>", tag.GetType (), tag.start, tag.length);
1851 Console.Write (tag.Text ());
1852 Console.WriteLine ("\t</tag>");
1855 Console.WriteLine ("</line>");
1857 Console.WriteLine ("</doc>");
1860 internal void Draw (Graphics g, Rectangle clip)
1862 Line line; // Current line being drawn
1863 LineTag tag; // Current tag being drawn
1864 int start; // First line to draw
1865 int end; // Last line to draw
1866 StringBuilder text; // String representing the current line
1869 Brush current_brush;
1870 Brush disabled_brush;
1874 // First, figure out from what line to what line we need to draw
1877 start = GetLineByPixel(clip.Top + viewport_y, false).line_no;
1878 end = GetLineByPixel(clip.Bottom + viewport_y, false).line_no;
1880 start = GetLineByPixel(clip.Left + viewport_x, false).line_no;
1881 end = GetLineByPixel(clip.Right + viewport_x, false).line_no;
1884 /// Make sure that we aren't drawing one more line then we need to
1885 line = GetLine (end - 1);
1886 if (line != null && clip.Bottom == line.Y + line.height + viewport_y)
1892 DateTime n = DateTime.Now;
1893 Console.WriteLine ("Started drawing: {0}s {1}ms", n.Second, n.Millisecond);
1894 Console.WriteLine ("CLIP: {0}", clip);
1895 Console.WriteLine ("S: {0}", GetLine (start).text);
1896 Console.WriteLine ("E: {0}", GetLine (end).text);
1899 disabled_brush = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorGrayText);
1900 hilight = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorHighlight);
1901 hilight_text = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorHighlightText);
1903 // Non multiline selection can be handled outside of the loop
1904 if (!multiline && selection_visible && owner.ShowSelection) {
1905 g.FillRectangle (hilight,
1906 selection_start.line.widths [selection_start.pos] +
1907 selection_start.line.X - viewport_x,
1908 selection_start.line.Y,
1909 (selection_end.line.X + selection_end.line.widths [selection_end.pos]) -
1910 (selection_start.line.X + selection_start.line.widths [selection_start.pos]),
1911 selection_start.line.height);
1914 while (line_no <= end) {
1915 line = GetLine (line_no);
1916 float line_y = line.Y - viewport_y;
1922 if (PasswordCache.Length < line.text.Length)
1923 PasswordCache.Append(Char.Parse(password_char), line.text.Length - PasswordCache.Length);
1924 else if (PasswordCache.Length > line.text.Length)
1925 PasswordCache.Remove(line.text.Length, PasswordCache.Length - line.text.Length);
1926 text = PasswordCache;
1929 int line_selection_start = text.Length + 1;
1930 int line_selection_end = text.Length + 1;
1931 if (selection_visible && owner.ShowSelection &&
1932 (line_no >= selection_start.line.line_no) &&
1933 (line_no <= selection_end.line.line_no)) {
1935 if (line_no == selection_start.line.line_no)
1936 line_selection_start = selection_start.pos + 1;
1938 line_selection_start = 1;
1940 if (line_no == selection_end.line.line_no)
1941 line_selection_end = selection_end.pos + 1;
1943 line_selection_end = text.Length + 1;
1945 if (line_selection_end == line_selection_start) {
1946 // There isn't really selection
1947 line_selection_start = text.Length + 1;
1948 line_selection_end = line_selection_start;
1949 } else if (multiline) {
1950 // lets draw some selection baby!! (non multiline selection is drawn outside the loop)
1951 g.FillRectangle (hilight,
1952 line.widths [line_selection_start - 1] + line.X - viewport_x,
1953 line_y, line.widths [line_selection_end - 1] - line.widths [line_selection_start - 1],
1958 current_brush = line.tags.color;
1959 while (tag != null) {
1962 if (tag.length == 0) {
1967 if (((tag.X + tag.width) < (clip.Left - viewport_x)) && (tag.X > (clip.Right - viewport_x))) {
1972 if (tag.back_color != null) {
1973 g.FillRectangle (tag.back_color, tag.X + line.X - viewport_x,
1974 line_y + tag.shift, tag.width, line.height);
1977 tag_brush = tag.color;
1978 current_brush = tag_brush;
1980 if (!owner.is_enabled) {
1981 Color a = ((SolidBrush) tag.color).Color;
1982 Color b = ThemeEngine.Current.ColorWindowText;
1984 if ((a.R == b.R) && (a.G == b.G) && (a.B == b.B)) {
1985 tag_brush = disabled_brush;
1989 int tag_pos = tag.start;
1990 current_brush = tag_brush;
1991 while (tag_pos < tag.start + tag.length) {
1992 int old_tag_pos = tag_pos;
1994 if (tag_pos >= line_selection_start && tag_pos < line_selection_end) {
1995 current_brush = hilight_text;
1996 tag_pos = Math.Min (tag.end, line_selection_end);
1997 } else if (tag_pos < line_selection_start) {
1998 current_brush = tag.color;
1999 tag_pos = Math.Min (tag.end, line_selection_start);
2001 current_brush = tag.color;
2005 tag.Draw (g, current_brush,
2006 line.widths [Math.Max (0, old_tag_pos - 1)] + line.X - viewport_x,
2008 old_tag_pos - 1, Math.Min (tag.length, tag_pos - old_tag_pos),
2017 private void InsertLineString (Line line, int pos, string s)
2019 bool carriage_return = false;
2021 if (s.EndsWith ("\r")) {
2022 s = s.Substring (0, s.Length - 1);
2023 carriage_return = true;
2026 InsertString (line, pos, s);
2028 if (carriage_return) {
2029 Line l = GetLine (line.line_no);
2030 l.carriage_return = true;
2034 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
2035 internal void Insert(Line line, int pos, bool update_caret, string s) {
2040 LineTag tag = LineTag.FindTag (line, pos);
2044 base_line = line.line_no;
2045 old_line_count = lines;
2047 break_index = s.IndexOf ('\n');
2049 // Bump the text at insertion point a line down if we're inserting more than one line
2050 if (break_index > -1) {
2052 line.soft_break = false;
2053 // Remainder of start line is now in base_line + 1
2056 if (break_index == -1)
2057 break_index = s.Length;
2059 InsertLineString (line, pos, s.Substring (0, break_index));
2062 while (break_index < s.Length) {
2064 int next_break = s.IndexOf ('\n', break_index);
2065 int adjusted_next_break;
2066 bool carriage_return = false;
2068 if (next_break == -1) {
2069 next_break = s.Length;
2073 adjusted_next_break = next_break;
2074 if (s [next_break - 1] == '\r') {
2075 adjusted_next_break--;
2076 carriage_return = true;
2079 string line_text = s.Substring (break_index, adjusted_next_break - break_index);
2080 Add (base_line + count, line_text, line.alignment, tag.font, tag.color);
2082 if (carriage_return) {
2083 Line last = GetLine (base_line + count);
2084 last.carriage_return = true;
2087 last.soft_break = true;
2089 Line last = GetLine (base_line + count);
2090 last.soft_break = true;
2094 break_index = next_break + 1;
2097 ResumeRecalc (true);
2099 UpdateView(line, lines - old_line_count + 1, pos);
2102 // Move caret to the end of the inserted text
2103 Line l = GetLine (line.line_no + lines - old_line_count);
2104 PositionCaret(l, l.text.Length);
2109 // Inserts a character at the given position
2110 internal void InsertString(Line line, int pos, string s) {
2111 InsertString(line.FindTag(pos), pos, s);
2114 // Inserts a string at the given position
2115 internal void InsertString(LineTag tag, int pos, string s) {
2124 line.text.Insert(pos, s);
2127 while (tag != null) {
2134 UpdateView(line, pos);
2137 // Inserts a string at the caret position
2138 internal void InsertStringAtCaret(string s, bool move_caret) {
2140 InsertString (caret.tag, caret.pos, s);
2142 UpdateView(caret.line, caret.pos);
2144 caret.pos += s.Length;
2151 // Inserts a character at the given position
2152 internal void InsertChar(Line line, int pos, char ch) {
2153 InsertChar(line.FindTag(pos), pos, ch);
2156 // Inserts a character at the given position
2157 internal void InsertChar(LineTag tag, int pos, char ch) {
2163 line.text.Insert(pos, ch);
2167 while (tag != null) {
2174 undo.RecordTyping (line, pos, ch);
2175 UpdateView(line, pos);
2178 // Inserts a character at the current caret position
2179 internal void InsertCharAtCaret(char ch, bool move_caret) {
2185 caret.line.text.Insert(caret.pos, ch);
2188 if (caret.tag.next != null) {
2189 tag = caret.tag.next;
2190 while (tag != null) {
2196 caret.line.recalc = true;
2198 InsertChar (caret.tag, caret.pos, ch);
2200 UpdateView(caret.line, caret.pos);
2204 SetSelectionToCaret(true);
2209 internal void InsertImage (Line line, int pos, Image image)
2217 // Just a place holder basically
2218 line.text.Insert (pos, "I");
2220 ImageTag image_tag = new ImageTag (line, pos, image);
2222 tag = LineTag.FindTag (line, pos);
2223 next_tag = tag.Break (pos);
2224 image_tag.CopyFormattingFrom (tag);
2225 tag.next = image_tag;
2228 next_tag = LineTag.FindTag (line, pos);
2229 image_tag.CopyFormattingFrom (next_tag);
2230 image_tag.next = next_tag;
2231 next_tag.previous = image_tag;
2232 line.tags = image_tag;
2235 image_tag.next = next_tag;
2236 image_tag.previous = tag;
2239 tag = image_tag.next;
2240 while (tag != null) {
2248 UpdateView (line, pos);
2251 internal void DeleteMultiline (Line start_line, int pos, int length)
2253 Marker start = new Marker ();
2254 Marker end = new Marker ();
2255 int start_index = LineTagToCharIndex (start_line, pos);
2257 start.line = start_line;
2259 start.tag = LineTag.FindTag (start_line, pos);
2261 CharIndexToLineTag (start_index + length, out end.line,
2262 out end.tag, out end.pos);
2266 if (start.line == end.line) {
2267 DeleteChars (start.tag, pos, end.pos - pos);
2270 // Delete first and last lines
2271 DeleteChars (start.tag, start.pos, start.line.text.Length - start.pos);
2272 DeleteChars (end.line.tags, 0, end.pos);
2274 int current = start.line.line_no + 1;
2275 if (current < end.line.line_no) {
2276 for (int i = end.line.line_no - 1; i >= current; i--) {
2281 // BIG FAT WARNING - selection_end.line might be stale due
2282 // to the above Delete() call. DONT USE IT before hitting the end of this method!
2284 // Join start and end
2285 Combine (start.line.line_no, current);
2288 ResumeUpdate (true);
2292 // Deletes n characters at the given position; it will not delete past line limits
2294 internal void DeleteChars(LineTag tag, int pos, int count) {
2303 if (pos == line.text.Length) {
2307 line.text.Remove(pos, count);
2309 // Make sure the tag points to the right spot
2310 while ((tag != null) && (tag.end) < pos) {
2318 // Check if we're crossing tag boundaries
2319 if ((pos + count) > (tag.start + tag.length - 1)) {
2322 // We have to delete cross tag boundaries
2326 left -= tag.start + tag.length - pos - 1;
2329 while ((tag != null) && (left > 0)) {
2330 tag.start -= count - left;
2332 if (tag.length > left) {
2341 // We got off easy, same tag
2343 if (tag.length == 0) {
2348 // Delete empty orphaned tags at the end
2350 while (walk != null && walk.next != null && walk.next.length == 0) {
2352 walk.next = walk.next.next;
2353 if (walk.next != null)
2354 walk.next.previous = t;
2358 // Adjust the start point of any tags following
2361 while (tag != null) {
2369 line.Streamline(lines);
2372 UpdateView(line, pos);
2375 // Deletes a character at or after the given position (depending on forward); it will not delete past line limits
2376 internal void DeleteChar(LineTag tag, int pos, bool forward) {
2385 if ((pos == 0 && forward == false) || (pos == line.text.Length && forward == true)) {
2391 line.text.Remove(pos, 1);
2393 while ((tag != null) && (tag.start + tag.length - 1) <= pos) {
2403 if (tag.length == 0) {
2408 line.text.Remove(pos, 1);
2409 if (pos >= (tag.start - 1)) {
2411 if (tag.length == 0) {
2414 } else if (tag.previous != null) {
2415 // tag.previous.length--;
2416 if (tag.previous.length == 0) {
2422 // Delete empty orphaned tags at the end
2424 while (walk != null && walk.next != null && walk.next.length == 0) {
2426 walk.next = walk.next.next;
2427 if (walk.next != null)
2428 walk.next.previous = t;
2433 while (tag != null) {
2439 line.Streamline(lines);
2442 UpdateView(line, pos);
2445 // Combine two lines
2446 internal void Combine(int FirstLine, int SecondLine) {
2447 Combine(GetLine(FirstLine), GetLine(SecondLine));
2450 internal void Combine(Line first, Line second) {
2454 // Combine the two tag chains into one
2457 // Maintain the line ending style
2458 first.soft_break = second.soft_break;
2460 while (last.next != null) {
2464 // need to get the shift before setting the next tag since that effects length
2465 shift = last.start + last.length - 1;
2466 last.next = second.tags;
2467 last.next.previous = last;
2469 // Fix up references within the chain
2471 while (last != null) {
2473 last.start += shift;
2477 // Combine both lines' strings
2478 first.text.Insert(first.text.Length, second.text.ToString());
2479 first.Grow(first.text.Length);
2481 // Remove the reference to our (now combined) tags from the doomed line
2485 DecrementLines(first.line_no + 2); // first.line_no + 1 will be deleted, so we need to start renumbering one later
2488 first.recalc = true;
2489 first.height = 0; // This forces RecalcDocument/UpdateView to redraw from this line on
2490 first.Streamline(lines);
2492 // Update Caret, Selection, etc
2493 if (caret.line == second) {
2494 caret.Combine(first, shift);
2496 if (selection_anchor.line == second) {
2497 selection_anchor.Combine(first, shift);
2499 if (selection_start.line == second) {
2500 selection_start.Combine(first, shift);
2502 if (selection_end.line == second) {
2503 selection_end.Combine(first, shift);
2510 check_first = GetLine(first.line_no);
2511 check_second = GetLine(check_first.line_no + 1);
2513 Console.WriteLine("Pre-delete: Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2516 this.Delete(second);
2519 check_first = GetLine(first.line_no);
2520 check_second = GetLine(check_first.line_no + 1);
2522 Console.WriteLine("Post-delete Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2526 // Split the line at the position into two
2527 internal void Split(int LineNo, int pos) {
2531 line = GetLine(LineNo);
2532 tag = LineTag.FindTag(line, pos);
2533 Split(line, tag, pos, false);
2536 internal void Split(Line line, int pos) {
2539 tag = LineTag.FindTag(line, pos);
2540 Split(line, tag, pos, false);
2543 ///<summary>Split line at given tag and position into two lines</summary>
2544 ///<param name="soft">True if the split should be marked as 'soft', indicating that it can be contracted
2545 ///if more space becomes available on previous line</param>
2546 internal void Split(Line line, LineTag tag, int pos, bool soft) {
2550 bool move_sel_start;
2554 move_sel_start = false;
2555 move_sel_end = false;
2557 // Adjust selection and cursors
2558 if (caret.line == line && caret.pos >= pos) {
2561 if (selection_start.line == line && selection_start.pos > pos) {
2562 move_sel_start = true;
2565 if (selection_end.line == line && selection_end.pos > pos) {
2566 move_sel_end = true;
2569 // cover the easy case first
2570 if (pos == line.text.Length) {
2571 Add(line.line_no + 1, "", line.alignment, tag.font, tag.color);
2573 new_line = GetLine(line.line_no + 1);
2575 line.carriage_return = false;
2576 new_line.carriage_return = line.carriage_return;
2577 new_line.soft_break = soft;
2580 caret.line = new_line;
2581 caret.tag = new_line.tags;
2585 if (move_sel_start) {
2586 selection_start.line = new_line;
2587 selection_start.pos = 0;
2588 selection_start.tag = new_line.tags;
2592 selection_end.line = new_line;
2593 selection_end.pos = 0;
2594 selection_end.tag = new_line.tags;
2599 // We need to move the rest of the text into the new line
2600 Add (line.line_no + 1, line.text.ToString (pos, line.text.Length - pos), line.alignment, tag.font, tag.color);
2602 // Now transfer our tags from this line to the next
2603 new_line = GetLine(line.line_no + 1);
2605 line.carriage_return = false;
2606 new_line.carriage_return = line.carriage_return;
2607 new_line.soft_break = soft;
2610 new_line.recalc = true;
2612 if ((tag.start - 1) == pos) {
2615 // We can simply break the chain and move the tag into the next line
2616 if (tag == line.tags) {
2617 new_tag = new LineTag(line, 1);
2618 new_tag.CopyFormattingFrom (tag);
2619 line.tags = new_tag;
2622 if (tag.previous != null) {
2623 tag.previous.next = null;
2625 new_line.tags = tag;
2626 tag.previous = null;
2627 tag.line = new_line;
2629 // Walk the list and correct the start location of the tags we just bumped into the next line
2630 shift = tag.start - 1;
2633 while (new_tag != null) {
2634 new_tag.start -= shift;
2635 new_tag.line = new_line;
2636 new_tag = new_tag.next;
2641 new_tag = new LineTag (new_line, 1);
2642 new_tag.next = tag.next;
2643 new_tag.CopyFormattingFrom (tag);
2644 new_line.tags = new_tag;
2645 if (new_tag.next != null) {
2646 new_tag.next.previous = new_tag;
2651 new_tag = new_tag.next;
2652 while (new_tag != null) {
2653 new_tag.start -= shift;
2654 new_tag.line = new_line;
2655 new_tag = new_tag.next;
2661 caret.line = new_line;
2662 caret.pos = caret.pos - pos;
2663 caret.tag = caret.line.FindTag(caret.pos);
2666 if (move_sel_start) {
2667 selection_start.line = new_line;
2668 selection_start.pos = selection_start.pos - pos;
2669 selection_start.tag = new_line.FindTag(selection_start.pos);
2673 selection_end.line = new_line;
2674 selection_end.pos = selection_end.pos - pos;
2675 selection_end.tag = new_line.FindTag(selection_end.pos);
2678 CharCount -= line.text.Length - pos;
2679 line.text.Remove(pos, line.text.Length - pos);
2682 // Adds a line of text, with given font.
2683 // Bumps any line at that line number that already exists down
2684 internal void Add(int LineNo, string Text, Font font, SolidBrush color) {
2685 Add(LineNo, Text, HorizontalAlignment.Left, font, color);
2688 internal void Add(int LineNo, string Text, HorizontalAlignment align, Font font, SolidBrush color) {
2693 CharCount += Text.Length;
2695 if (LineNo<1 || Text == null) {
2697 throw new ArgumentNullException("LineNo", "Line numbers must be positive");
2699 throw new ArgumentNullException("Text", "Cannot insert NULL line");
2703 add = new Line (this, LineNo, Text, align, font, color);
2706 while (line != sentinel) {
2708 line_no = line.line_no;
2710 if (LineNo > line_no) {
2712 } else if (LineNo < line_no) {
2715 // Bump existing line numbers; walk all nodes to the right of this one and increment line_no
2716 IncrementLines(line.line_no);
2721 add.left = sentinel;
2722 add.right = sentinel;
2724 if (add.parent != null) {
2725 if (LineNo > add.parent.line_no) {
2726 add.parent.right = add;
2728 add.parent.left = add;
2735 RebalanceAfterAdd(add);
2740 internal virtual void Clear() {
2743 document = sentinel;
2746 public virtual object Clone() {
2749 clone = new Document(null);
2751 clone.lines = this.lines;
2752 clone.document = (Line)document.Clone();
2757 internal void Delete(int LineNo) {
2764 line = GetLine(LineNo);
2766 CharCount -= line.text.Length;
2768 DecrementLines(LineNo + 1);
2772 internal void Delete(Line line1) {
2773 Line line2;// = new Line();
2776 if ((line1.left == sentinel) || (line1.right == sentinel)) {
2779 line3 = line1.right;
2780 while (line3.left != sentinel) {
2785 if (line3.left != sentinel) {
2788 line2 = line3.right;
2791 line2.parent = line3.parent;
2792 if (line3.parent != null) {
2793 if(line3 == line3.parent.left) {
2794 line3.parent.left = line2;
2796 line3.parent.right = line2;
2802 if (line3 != line1) {
2805 if (selection_start.line == line3) {
2806 selection_start.line = line1;
2809 if (selection_end.line == line3) {
2810 selection_end.line = line1;
2813 if (selection_anchor.line == line3) {
2814 selection_anchor.line = line1;
2817 if (caret.line == line3) {
2822 line1.alignment = line3.alignment;
2823 line1.ascent = line3.ascent;
2824 line1.hanging_indent = line3.hanging_indent;
2825 line1.height = line3.height;
2826 line1.indent = line3.indent;
2827 line1.line_no = line3.line_no;
2828 line1.recalc = line3.recalc;
2829 line1.right_indent = line3.right_indent;
2830 line1.soft_break = line3.soft_break;
2831 line1.space = line3.space;
2832 line1.tags = line3.tags;
2833 line1.text = line3.text;
2834 line1.widths = line3.widths;
2835 line1.offset = line3.offset;
2838 while (tag != null) {
2844 if (line3.color == LineColor.Black)
2845 RebalanceAfterDelete(line2);
2850 // Invalidate a section of the document to trigger redraw
2851 internal void Invalidate(Line start, int start_pos, Line end, int end_pos) {
2857 if ((start == end) && (start_pos == end_pos)) {
2861 if (end_pos == -1) {
2862 end_pos = end.text.Length;
2865 // figure out what's before what so the logic below is straightforward
2866 if (start.line_no < end.line_no) {
2872 } else if (start.line_no > end.line_no) {
2879 if (start_pos < end_pos) {
2893 int endpoint = (int) l1.widths [p2];
2894 if (p2 == l1.text.Length + 1) {
2895 endpoint = (int) viewport_width;
2899 Console.WriteLine("Invaliding backwards from {0}:{1} to {2}:{3} {4}",
2900 l1.line_no, p1, l2.line_no, p2,
2902 (int)l1.widths[p1] + l1.X - viewport_x,
2910 owner.Invalidate(new Rectangle (
2911 (int)l1.widths[p1] + l1.X - viewport_x,
2913 endpoint - (int)l1.widths[p1] + 1,
2919 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);
2920 Console.WriteLine ("invalidate start line: {0} position: {1}", l1.text, p1);
2923 // Three invalidates:
2924 // First line from start
2925 owner.Invalidate(new Rectangle((int)l1.widths[p1] + l1.X - viewport_x, l1.Y - viewport_y, viewport_width, l1.height));
2929 if ((l1.line_no + 1) < l2.line_no) {
2932 y = GetLine(l1.line_no + 1).Y;
2933 owner.Invalidate(new Rectangle(0, y - viewport_y, viewport_width, l2.Y - y));
2936 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);
2942 owner.Invalidate(new Rectangle((int)l2.widths[0] + l2.X - viewport_x, l2.Y - viewport_y, (int)l2.widths[p2] + 1, l2.height));
2944 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);
2949 /// <summary>Select text around caret</summary>
2950 internal void ExpandSelection(CaretSelection mode, bool to_caret) {
2952 // We're expanding the selection to the caret position
2954 case CaretSelection.Line: {
2955 // Invalidate the selection delta
2956 if (caret > selection_prev) {
2957 Invalidate(selection_prev.line, 0, caret.line, caret.line.text.Length);
2959 Invalidate(selection_prev.line, selection_prev.line.text.Length, caret.line, 0);
2962 if (caret.line.line_no <= selection_anchor.line.line_no) {
2963 selection_start.line = caret.line;
2964 selection_start.tag = caret.line.tags;
2965 selection_start.pos = 0;
2967 selection_end.line = selection_anchor.line;
2968 selection_end.tag = selection_anchor.tag;
2969 selection_end.pos = selection_anchor.pos;
2971 selection_end_anchor = true;
2973 selection_start.line = selection_anchor.line;
2974 selection_start.pos = selection_anchor.height;
2975 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height);
2977 selection_end.line = caret.line;
2978 selection_end.tag = caret.line.tags;
2979 selection_end.pos = caret.line.text.Length;
2981 selection_end_anchor = false;
2983 selection_prev.line = caret.line;
2984 selection_prev.tag = caret.tag;
2985 selection_prev.pos = caret.pos;
2990 case CaretSelection.Word: {
2994 start_pos = FindWordSeparator(caret.line, caret.pos, false);
2995 end_pos = FindWordSeparator(caret.line, caret.pos, true);
2998 // Invalidate the selection delta
2999 if (caret > selection_prev) {
3000 Invalidate(selection_prev.line, selection_prev.pos, caret.line, end_pos);
3002 Invalidate(selection_prev.line, selection_prev.pos, caret.line, start_pos);
3004 if (caret < selection_anchor) {
3005 selection_start.line = caret.line;
3006 selection_start.tag = caret.line.FindTag(start_pos);
3007 selection_start.pos = start_pos;
3009 selection_end.line = selection_anchor.line;
3010 selection_end.tag = selection_anchor.tag;
3011 selection_end.pos = selection_anchor.pos;
3013 selection_prev.line = caret.line;
3014 selection_prev.tag = caret.tag;
3015 selection_prev.pos = start_pos;
3017 selection_end_anchor = true;
3019 selection_start.line = selection_anchor.line;
3020 selection_start.pos = selection_anchor.height;
3021 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height);
3023 selection_end.line = caret.line;
3024 selection_end.tag = caret.line.FindTag(end_pos);
3025 selection_end.pos = end_pos;
3027 selection_prev.line = caret.line;
3028 selection_prev.tag = caret.tag;
3029 selection_prev.pos = end_pos;
3031 selection_end_anchor = false;
3036 case CaretSelection.Position: {
3037 SetSelectionToCaret(false);
3042 // We're setting the selection 'around' the caret position
3044 case CaretSelection.Line: {
3045 this.Invalidate(caret.line, 0, caret.line, caret.line.text.Length);
3047 selection_start.line = caret.line;
3048 selection_start.tag = caret.line.tags;
3049 selection_start.pos = 0;
3051 selection_end.line = caret.line;
3052 selection_end.pos = caret.line.text.Length;
3053 selection_end.tag = caret.line.FindTag(selection_end.pos);
3055 selection_anchor.line = selection_end.line;
3056 selection_anchor.tag = selection_end.tag;
3057 selection_anchor.pos = selection_end.pos;
3058 selection_anchor.height = 0;
3060 selection_prev.line = caret.line;
3061 selection_prev.tag = caret.tag;
3062 selection_prev.pos = caret.pos;
3064 this.selection_end_anchor = true;
3069 case CaretSelection.Word: {
3073 start_pos = FindWordSeparator(caret.line, caret.pos, false);
3074 end_pos = FindWordSeparator(caret.line, caret.pos, true);
3076 this.Invalidate(selection_start.line, start_pos, caret.line, end_pos);
3078 selection_start.line = caret.line;
3079 selection_start.tag = caret.line.FindTag(start_pos);
3080 selection_start.pos = start_pos;
3082 selection_end.line = caret.line;
3083 selection_end.tag = caret.line.FindTag(end_pos);
3084 selection_end.pos = end_pos;
3086 selection_anchor.line = selection_end.line;
3087 selection_anchor.tag = selection_end.tag;
3088 selection_anchor.pos = selection_end.pos;
3089 selection_anchor.height = start_pos;
3091 selection_prev.line = caret.line;
3092 selection_prev.tag = caret.tag;
3093 selection_prev.pos = caret.pos;
3095 this.selection_end_anchor = true;
3102 SetSelectionVisible (!(selection_start == selection_end));
3105 internal void SetSelectionToCaret(bool start) {
3107 // Invalidate old selection; selection is being reset to empty
3108 this.Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3110 selection_start.line = caret.line;
3111 selection_start.tag = caret.tag;
3112 selection_start.pos = caret.pos;
3114 // start always also selects end
3115 selection_end.line = caret.line;
3116 selection_end.tag = caret.tag;
3117 selection_end.pos = caret.pos;
3119 selection_anchor.line = caret.line;
3120 selection_anchor.tag = caret.tag;
3121 selection_anchor.pos = caret.pos;
3123 // Invalidate from previous end to caret (aka new end)
3124 if (selection_end_anchor) {
3125 if (selection_start != caret) {
3126 this.Invalidate(selection_start.line, selection_start.pos, caret.line, caret.pos);
3129 if (selection_end != caret) {
3130 this.Invalidate(selection_end.line, selection_end.pos, caret.line, caret.pos);
3134 if (caret < selection_anchor) {
3135 selection_start.line = caret.line;
3136 selection_start.tag = caret.tag;
3137 selection_start.pos = caret.pos;
3139 selection_end.line = selection_anchor.line;
3140 selection_end.tag = selection_anchor.tag;
3141 selection_end.pos = selection_anchor.pos;
3143 selection_end_anchor = true;
3145 selection_start.line = selection_anchor.line;
3146 selection_start.tag = selection_anchor.tag;
3147 selection_start.pos = selection_anchor.pos;
3149 selection_end.line = caret.line;
3150 selection_end.tag = caret.tag;
3151 selection_end.pos = caret.pos;
3153 selection_end_anchor = false;
3157 SetSelectionVisible (!(selection_start == selection_end));
3160 internal void SetSelection(Line start, int start_pos, Line end, int end_pos) {
3161 if (selection_visible) {
3162 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3165 if ((end.line_no < start.line_no) || ((end == start) && (end_pos <= start_pos))) {
3166 selection_start.line = end;
3167 selection_start.tag = LineTag.FindTag(end, end_pos);
3168 selection_start.pos = end_pos;
3170 selection_end.line = start;
3171 selection_end.tag = LineTag.FindTag(start, start_pos);
3172 selection_end.pos = start_pos;
3174 selection_end_anchor = true;
3176 selection_start.line = start;
3177 selection_start.tag = LineTag.FindTag(start, start_pos);
3178 selection_start.pos = start_pos;
3180 selection_end.line = end;
3181 selection_end.tag = LineTag.FindTag(end, end_pos);
3182 selection_end.pos = end_pos;
3184 selection_end_anchor = false;
3187 selection_anchor.line = start;
3188 selection_anchor.tag = selection_start.tag;
3189 selection_anchor.pos = start_pos;
3191 if (((start == end) && (start_pos == end_pos)) || start == null || end == null) {
3192 SetSelectionVisible (false);
3194 SetSelectionVisible (true);
3195 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3199 internal void SetSelectionStart(Line start, int start_pos) {
3200 // Invalidate from the previous to the new start pos
3201 Invalidate(selection_start.line, selection_start.pos, start, start_pos);
3203 selection_start.line = start;
3204 selection_start.pos = start_pos;
3205 selection_start.tag = LineTag.FindTag(start, start_pos);
3207 selection_anchor.line = start;
3208 selection_anchor.pos = start_pos;
3209 selection_anchor.tag = selection_start.tag;
3211 selection_end_anchor = false;
3214 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
3215 SetSelectionVisible (true);
3217 SetSelectionVisible (false);
3220 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3223 internal void SetSelectionStart(int character_index) {
3228 if (character_index < 0) {
3232 CharIndexToLineTag(character_index, out line, out tag, out pos);
3233 SetSelectionStart(line, pos);
3236 internal void SetSelectionEnd(Line end, int end_pos) {
3238 if (end == selection_end.line && end_pos == selection_start.pos) {
3239 selection_anchor.line = selection_start.line;
3240 selection_anchor.tag = selection_start.tag;
3241 selection_anchor.pos = selection_start.pos;
3243 selection_end.line = selection_start.line;
3244 selection_end.tag = selection_start.tag;
3245 selection_end.pos = selection_start.pos;
3247 selection_end_anchor = false;
3248 } else if ((end.line_no < selection_anchor.line.line_no) || ((end == selection_anchor.line) && (end_pos <= selection_anchor.pos))) {
3249 selection_start.line = end;
3250 selection_start.tag = LineTag.FindTag(end, end_pos);
3251 selection_start.pos = end_pos;
3253 selection_end.line = selection_anchor.line;
3254 selection_end.tag = selection_anchor.tag;
3255 selection_end.pos = selection_anchor.pos;
3257 selection_end_anchor = true;
3259 selection_start.line = selection_anchor.line;
3260 selection_start.tag = selection_anchor.tag;
3261 selection_start.pos = selection_anchor.pos;
3263 selection_end.line = end;
3264 selection_end.tag = LineTag.FindTag(end, end_pos);
3265 selection_end.pos = end_pos;
3267 selection_end_anchor = false;
3270 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
3271 SetSelectionVisible (true);
3272 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3274 SetSelectionVisible (false);
3275 // ?? Do I need to invalidate here, tests seem to work without it, but I don't think they should :-s
3279 internal void SetSelectionEnd(int character_index) {
3284 if (character_index < 0) {
3288 CharIndexToLineTag(character_index, out line, out tag, out pos);
3289 SetSelectionEnd(line, pos);
3292 internal void SetSelection(Line start, int start_pos) {
3293 if (selection_visible) {
3294 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3297 selection_start.line = start;
3298 selection_start.pos = start_pos;
3299 selection_start.tag = LineTag.FindTag(start, start_pos);
3301 selection_end.line = start;
3302 selection_end.tag = selection_start.tag;
3303 selection_end.pos = start_pos;
3305 selection_anchor.line = start;
3306 selection_anchor.tag = selection_start.tag;
3307 selection_anchor.pos = start_pos;
3309 selection_end_anchor = false;
3310 SetSelectionVisible (false);
3313 internal void InvalidateSelectionArea() {
3314 Invalidate (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3317 // Return the current selection, as string
3318 internal string GetSelection() {
3319 // We return String.Empty if there is no selection
3320 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3321 return string.Empty;
3324 if (selection_start.line == selection_end.line) {
3325 return selection_start.line.text.ToString (selection_start.pos, selection_end.pos - selection_start.pos);
3332 sb = new StringBuilder();
3333 start = selection_start.line.line_no;
3334 end = selection_end.line.line_no;
3336 sb.Append(selection_start.line.text.ToString(selection_start.pos, selection_start.line.text.Length - selection_start.pos) + Environment.NewLine);
3338 if ((start + 1) < end) {
3339 for (i = start + 1; i < end; i++) {
3340 sb.Append(GetLine(i).text.ToString() + Environment.NewLine);
3344 sb.Append(selection_end.line.text.ToString(0, selection_end.pos));
3346 return sb.ToString();
3350 internal void ReplaceSelection(string s, bool select_new) {
3353 int selection_pos_on_line = selection_start.pos;
3354 int selection_start_pos = LineTagToCharIndex (selection_start.line, selection_start.pos);
3357 // First, delete any selected text
3358 if ((selection_start.pos != selection_end.pos) || (selection_start.line != selection_end.line)) {
3359 if (selection_start.line == selection_end.line) {
3360 undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3362 DeleteChars(selection_start.tag, selection_start.pos, selection_end.pos - selection_start.pos);
3364 // The tag might have been removed, we need to recalc it
3365 selection_start.tag = selection_start.line.FindTag(selection_start.pos);
3370 start = selection_start.line.line_no;
3371 end = selection_end.line.line_no;
3373 undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3375 // Delete first line
3376 DeleteChars(selection_start.tag, selection_start.pos, selection_start.line.text.Length - selection_start.pos);
3379 DeleteChars(selection_end.line.tags, 0, selection_end.pos);
3383 for (i = end - 1; i >= start; i--) {
3388 // BIG FAT WARNING - selection_end.line might be stale due
3389 // to the above Delete() call. DONT USE IT before hitting the end of this method!
3391 // Join start and end
3392 Combine(selection_start.line.line_no, start);
3397 Insert(selection_start.line, selection_start.pos, false, s);
3398 undo.RecordInsertString (selection_start.line, selection_start.pos, s);
3399 ResumeRecalc (false);
3402 CharIndexToLineTag(selection_start_pos + s.Length, out selection_start.line,
3403 out selection_start.tag, out selection_start.pos);
3405 selection_end.line = selection_start.line;
3406 selection_end.pos = selection_start.pos;
3407 selection_end.tag = selection_start.tag;
3408 selection_anchor.line = selection_start.line;
3409 selection_anchor.pos = selection_start.pos;
3410 selection_anchor.tag = selection_start.tag;
3412 SetSelectionVisible (false);
3414 CharIndexToLineTag(selection_start_pos, out selection_start.line,
3415 out selection_start.tag, out selection_start.pos);
3417 CharIndexToLineTag(selection_start_pos + s.Length, out selection_end.line,
3418 out selection_end.tag, out selection_end.pos);
3420 selection_anchor.line = selection_start.line;
3421 selection_anchor.pos = selection_start.pos;
3422 selection_anchor.tag = selection_start.tag;
3424 SetSelectionVisible (true);
3427 PositionCaret (selection_start.line, selection_start.pos);
3428 UpdateView (selection_start.line, selection_pos_on_line);
3431 internal void CharIndexToLineTag(int index, out Line line_out, out LineTag tag_out, out int pos) {
3440 for (i = 1; i <= lines; i++) {
3444 chars += line.text.Length + (line.soft_break ? 0 : crlf_size);
3446 if (index <= chars) {
3447 // we found the line
3450 while (tag != null) {
3451 if (index < (start + tag.start + tag.length)) {
3453 tag_out = LineTag.GetFinalTag (tag);
3454 pos = index - start;
3457 if (tag.next == null) {
3460 next_line = GetLine(line.line_no + 1);
3462 if (next_line != null) {
3463 line_out = next_line;
3464 tag_out = LineTag.GetFinalTag (next_line.tags);
3469 tag_out = LineTag.GetFinalTag (tag);
3470 pos = line_out.text.Length;
3479 line_out = GetLine(lines);
3480 tag = line_out.tags;
3481 while (tag.next != null) {
3485 pos = line_out.text.Length;
3488 internal int LineTagToCharIndex(Line line, int pos) {
3492 // Count first and last line
3495 // Count the lines in the middle
3497 for (i = 1; i < line.line_no; i++) {
3498 length += GetLine(i).text.Length + (line.soft_break ? 0 : crlf_size);
3506 internal int SelectionLength() {
3507 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3511 if (selection_start.line == selection_end.line) {
3512 return selection_end.pos - selection_start.pos;
3519 // Count first and last line
3520 length = selection_start.line.text.Length - selection_start.pos + selection_end.pos + crlf_size;
3522 // Count the lines in the middle
3523 start = selection_start.line.line_no + 1;
3524 end = selection_end.line.line_no;
3527 for (i = start; i < end; i++) {
3528 Line line = GetLine (i);
3529 length += line.text.Length + (line.soft_break ? 0 : crlf_size);
3540 /// <summary>Give it a Line number and it returns the Line object at with that line number</summary>
3541 internal Line GetLine(int LineNo) {
3542 Line line = document;
3544 while (line != sentinel) {
3545 if (LineNo == line.line_no) {
3547 } else if (LineNo < line.line_no) {
3557 /// <summary>Retrieve the previous tag; walks line boundaries</summary>
3558 internal LineTag PreviousTag(LineTag tag) {
3561 if (tag.previous != null) {
3562 return tag.previous;
3566 if (tag.line.line_no == 1) {
3570 l = GetLine(tag.line.line_no - 1);
3575 while (t.next != null) {
3584 /// <summary>Retrieve the next tag; walks line boundaries</summary>
3585 internal LineTag NextTag(LineTag tag) {
3588 if (tag.next != null) {
3593 l = GetLine(tag.line.line_no + 1);
3601 internal Line ParagraphStart(Line line) {
3602 while (line.soft_break) {
3603 line = GetLine(line.line_no - 1);
3608 internal Line ParagraphEnd(Line line) {
3611 while (line.soft_break) {
3612 l = GetLine(line.line_no + 1);
3613 if ((l == null) || (!l.soft_break)) {
3621 /// <summary>Give it a pixel offset coordinate and it returns the Line covering that are (offset
3622 /// is either X or Y depending on if we are multiline
3624 internal Line GetLineByPixel (int offset, bool exact)
3626 Line line = document;
3630 while (line != sentinel) {
3632 if ((offset >= line.Y) && (offset < (line.Y+line.height))) {
3634 } else if (offset < line.Y) {
3641 while (line != sentinel) {
3643 if ((offset >= line.X) && (offset < (line.X + line.Width)))
3645 else if (offset < line.X)
3658 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3659 internal LineTag FindTag(int x, int y, out int index, bool exact) {
3663 line = GetLineByPixel(y, exact);
3670 // Alignment adjustment
3674 if (x >= tag.X && x < (tag.X+tag.width)) {
3677 end = tag.start + tag.length - 1;
3679 for (int pos = tag.start; pos < end; pos++) {
3680 if (x < line.widths[pos]) {
3682 return LineTag.GetFinalTag (tag);
3686 return LineTag.GetFinalTag (tag);
3688 if (tag.next != null) {
3696 index = line.text.Length;
3697 return LineTag.GetFinalTag (tag);
3702 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3703 internal LineTag FindCursor(int x, int y, out int index) {
3707 line = GetLineByPixel(multiline ? y : x, false);
3711 if (x >= tag.X && x < (tag.X+tag.width)) {
3716 for (int pos = tag.start; pos < end; pos++) {
3717 // When clicking on a character, we position the cursor to whatever edge
3718 // of the character the click was closer
3719 if (x < (line.X + line.widths[pos] + ((line.widths[pos+1]-line.widths[pos])/2))) {
3725 return LineTag.GetFinalTag (tag);
3727 if (tag.next != null) {
3730 index = line.text.Length;
3731 return LineTag.GetFinalTag (tag);
3736 /// <summary>Format area of document in specified font and color</summary>
3737 /// <param name="start_pos">1-based start position on start_line</param>
3738 /// <param name="end_pos">1-based end position on end_line </param>
3739 internal void FormatText (Line start_line, int start_pos, Line end_line, int end_pos, Font font,
3740 SolidBrush color, SolidBrush back_color, FormatSpecified specified)
3744 // First, format the first line
3745 if (start_line != end_line) {
3747 LineTag.FormatText(start_line, start_pos, start_line.text.Length - start_pos + 1, font, color, back_color, specified);
3750 LineTag.FormatText(end_line, 1, end_pos, font, color, back_color, specified);
3752 // Now all the lines inbetween
3753 for (int i = start_line.line_no + 1; i < end_line.line_no; i++) {
3755 LineTag.FormatText(l, 1, l.text.Length, font, color, back_color, specified);
3758 // Special case, single line
3759 LineTag.FormatText(start_line, start_pos, end_pos - start_pos, font, color, back_color, specified);
3763 /// <summary>Re-format areas of the document in specified font and color</summary>
3764 /// <param name="start_pos">1-based start position on start_line</param>
3765 /// <param name="end_pos">1-based end position on end_line </param>
3766 /// <param name="font">Font specifying attributes</param>
3767 /// <param name="color">Color (or NULL) to apply</param>
3768 /// <param name="apply">Attributes from font and color to apply</param>
3769 internal void FormatText(Line start_line, int start_pos, Line end_line, int end_pos, FontDefinition attributes) {
3772 // First, format the first line
3773 if (start_line != end_line) {
3775 LineTag.FormatText(start_line, start_pos, start_line.text.Length - start_pos + 1, attributes);
3778 LineTag.FormatText(end_line, 1, end_pos - 1, attributes);
3780 // Now all the lines inbetween
3781 for (int i = start_line.line_no + 1; i < end_line.line_no; i++) {
3783 LineTag.FormatText(l, 1, l.text.Length, attributes);
3786 // Special case, single line
3787 LineTag.FormatText(start_line, start_pos, end_pos - start_pos, attributes);
3791 internal void RecalculateAlignments ()
3798 while (line_no <= lines) {
3799 line = GetLine(line_no);
3802 switch (line.alignment) {
3803 case HorizontalAlignment.Left:
3804 line.align_shift = 0;
3806 case HorizontalAlignment.Center:
3807 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3809 case HorizontalAlignment.Right:
3810 line.align_shift = viewport_width - (int)line.widths[line.text.Length];
3820 /// <summary>Calculate formatting for the whole document</summary>
3821 internal bool RecalculateDocument(Graphics g) {
3822 return RecalculateDocument(g, 1, this.lines, false);
3825 /// <summary>Calculate formatting starting at a certain line</summary>
3826 internal bool RecalculateDocument(Graphics g, int start) {
3827 return RecalculateDocument(g, start, this.lines, false);
3830 /// <summary>Calculate formatting within two given line numbers</summary>
3831 internal bool RecalculateDocument(Graphics g, int start, int end) {
3832 return RecalculateDocument(g, start, end, false);
3835 /// <summary>With optimize on, returns true if line heights changed</summary>
3836 internal bool RecalculateDocument(Graphics g, int start, int end, bool optimize) {
3844 if (recalc_suspended > 0) {
3845 recalc_pending = true;
3846 recalc_start = Math.Min (recalc_start, start);
3847 recalc_end = Math.Max (recalc_end, end);
3848 recalc_optimize = optimize;
3852 // Fixup the positions, they can go kinda nuts
3853 start = Math.Max (start, 1);
3854 end = Math.Min (end, lines);
3856 offset = GetLine(start).offset;
3861 changed = true; // We always return true if we run non-optimized
3866 while (line_no <= (end + this.lines - shift)) {
3867 line = GetLine(line_no++);
3868 line.offset = offset;
3872 line.RecalculateLine(g, this);
3874 if (line.recalc && line.RecalculateLine(g, this)) {
3876 // If the height changed, all subsequent lines change
3883 line.RecalculatePasswordLine(g, this);
3885 if (line.recalc && line.RecalculatePasswordLine(g, this)) {
3887 // If the height changed, all subsequent lines change
3894 if (line.widths[line.text.Length] > new_width) {
3895 new_width = (int)line.widths[line.text.Length];
3898 // Calculate alignment
3899 if (line.alignment != HorizontalAlignment.Left) {
3900 if (line.alignment == HorizontalAlignment.Center) {
3901 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3903 line.align_shift = viewport_width - (int)line.widths[line.text.Length] - 1;
3908 offset += line.height;
3910 offset += (int) line.widths [line.text.Length] + 2;
3912 if (line_no > lines) {
3917 if (document_x != new_width) {
3918 document_x = new_width;
3919 if (WidthChanged != null) {
3920 WidthChanged(this, null);
3924 RecalculateAlignments();
3926 line = GetLine(lines);
3928 if (document_y != line.Y + line.height) {
3929 document_y = line.Y + line.height;
3930 if (HeightChanged != null) {
3931 HeightChanged(this, null);
3938 internal int Size() {
3942 private void owner_HandleCreated(object sender, EventArgs e) {
3943 RecalculateDocument(owner.CreateGraphicsInternal());
3947 private void owner_VisibleChanged(object sender, EventArgs e) {
3948 if (owner.Visible) {
3949 RecalculateDocument(owner.CreateGraphicsInternal());
3953 internal static bool IsWordSeparator(char ch) {
3967 internal int FindWordSeparator(Line line, int pos, bool forward) {
3970 len = line.text.Length;
3973 for (int i = pos + 1; i < len; i++) {
3974 if (IsWordSeparator(line.Text[i])) {
3980 for (int i = pos - 1; i > 0; i--) {
3981 if (IsWordSeparator(line.Text[i - 1])) {
3989 /* Search document for text */
3990 internal bool FindChars(char[] chars, Marker start, Marker end, out Marker result) {
3996 // Search for occurence of any char in the chars array
3997 result = new Marker();
4000 line_no = start.line.line_no;
4002 while (line_no <= end.line.line_no) {
4003 line_len = line.text.Length;
4004 while (pos < line_len) {
4005 for (int i = 0; i < chars.Length; i++) {
4006 if (line.text[pos] == chars[i]) {
4008 if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
4022 line = GetLine(line_no);
4028 // This version does not build one big string for searching, instead it handles
4029 // line-boundaries, which is faster and less memory intensive
4030 // FIXME - Depending on culture stuff we might have to create a big string and use culturespecific
4031 // search stuff and change it to accept and return positions instead of Markers (which would match
4032 // RichTextBox behaviour better but would be inconsistent with the rest of TextControl)
4033 internal bool Find(string search, Marker start, Marker end, out Marker result, RichTextBoxFinds options) {
4035 string search_string;
4047 result = new Marker();
4048 word_option = ((options & RichTextBoxFinds.WholeWord) != 0);
4049 ignore_case = ((options & RichTextBoxFinds.MatchCase) == 0);
4050 reverse = ((options & RichTextBoxFinds.Reverse) != 0);
4053 line_no = start.line.line_no;
4057 // Prep our search string, lowercasing it if we do case-independent matching
4060 sb = new StringBuilder(search);
4061 for (int i = 0; i < sb.Length; i++) {
4062 sb[i] = Char.ToLower(sb[i]);
4064 search_string = sb.ToString();
4066 search_string = search;
4069 // We need to check if the character before our start position is a wordbreak
4072 if ((pos == 0) || (IsWordSeparator(line.text[pos - 1]))) {
4079 if (IsWordSeparator(line.text[pos - 1])) {
4085 // Need to check the end of the previous line
4088 prev_line = GetLine(line_no - 1);
4089 if (prev_line.soft_break) {
4090 if (IsWordSeparator(prev_line.text[prev_line.text.Length - 1])) {
4104 // To avoid duplication of this loop with reverse logic, we search
4105 // through the document, remembering the last match and when returning
4106 // report that last remembered match
4108 last = new Marker();
4109 last.height = -1; // Abused - we use it to track change
4111 while (line_no <= end.line.line_no) {
4112 if (line_no != end.line.line_no) {
4113 line_len = line.text.Length;
4118 while (pos < line_len) {
4119 if (word_option && (current == search_string.Length)) {
4120 if (IsWordSeparator(line.text[pos])) {
4133 c = Char.ToLower(line.text[pos]);
4138 if (c == search_string[current]) {
4143 if (!word_option || (word_option && (word || (current > 0)))) {
4147 if (!word_option && (current == search_string.Length)) {
4164 if (IsWordSeparator(c)) {
4172 // Mark that we just saw a word boundary
4173 if (!line.soft_break) {
4177 if (current == search_string.Length) {
4193 line = GetLine(line_no);
4197 if (last.height != -1) {
4207 // if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
4219 internal void GetMarker(out Marker mark, bool start) {
4220 mark = new Marker();
4223 mark.line = GetLine(1);
4224 mark.tag = mark.line.tags;
4227 mark.line = GetLine(lines);
4228 mark.tag = mark.line.tags;
4229 while (mark.tag.next != null) {
4230 mark.tag = mark.tag.next;
4232 mark.pos = mark.line.text.Length;
4235 #endregion // Internal Methods
4238 internal event EventHandler CaretMoved;
4239 internal event EventHandler WidthChanged;
4240 internal event EventHandler HeightChanged;
4241 internal event EventHandler LengthChanged;
4242 #endregion // Events
4244 #region Administrative
4245 public IEnumerator GetEnumerator() {
4250 public override bool Equals(object obj) {
4255 if (!(obj is Document)) {
4263 if (ToString().Equals(((Document)obj).ToString())) {
4270 public override int GetHashCode() {
4274 public override string ToString() {
4275 return "document " + this.document_id;
4277 #endregion // Administrative
4280 internal class ImageTag : LineTag {
4282 internal Image image;
4284 internal ImageTag (Line line, int start, Image image) : base (line, start)
4289 internal override SizeF SizeOfPosition (Graphics dc, int pos)
4294 internal override int MaxHeight ()
4296 return image.Height;
4299 internal override void Draw (Graphics dc, Brush brush, float x, float y, int start, int end)
4301 dc.DrawImage (image, x, y);
4304 internal override void Draw (Graphics dc, Brush brush, float x, float y, int start, int end, string text)
4306 dc.DrawImage (image, x, y);
4309 public override string Text ()
4315 internal class LineTag {
4316 #region Local Variables;
4317 // Payload; formatting
4318 internal Font font; // System.Drawing.Font object for this tag
4319 internal SolidBrush color; // The font color for this tag
4321 // In 2.0 tags can have background colours. I'm not going to #ifdef
4322 // at this level though since I want to reduce code paths
4323 internal SolidBrush back_color;
4326 internal int start; // start, in chars; index into Line.text
4327 internal bool r_to_l; // Which way is the font
4330 internal int height; // Height in pixels of the text this tag describes
4332 internal int ascent; // Ascent of the font for this tag
4333 internal int shift; // Shift down for this tag, to stay on baseline
4336 internal Line line; // The line we're on
4337 internal LineTag next; // Next tag on the same line
4338 internal LineTag previous; // Previous tag on the same line
4341 #region Constructors
4342 internal LineTag(Line line, int start) {
4346 #endregion // Constructors
4348 #region Internal Methods
4354 return line.X + line.widths [start - 1];
4359 get { return start + length; }
4362 public float width {
4366 return line.widths [start + length - 1] - (start != 0 ? line.widths [start - 1] : 0);
4374 res = next.start - start;
4376 res = line.text.Length - (start - 1);
4378 return res > 0 ? res : 0;
4382 internal virtual SizeF SizeOfPosition (Graphics dc, int pos)
4384 return dc.MeasureString (line.text.ToString (pos, 1), font, 10000, Document.string_format);
4387 internal virtual int MaxHeight ()
4392 internal virtual void Draw (Graphics dc, Brush brush, float x, float y, int start, int end)
4394 dc.DrawString (line.text.ToString (start, end), font, brush, x, y, StringFormat.GenericTypographic);
4397 internal virtual void Draw (Graphics dc, Brush brush, float x, float y, int start, int end, string text)
4399 dc.DrawString (text.Substring (start, end), font, brush, x, y, StringFormat.GenericTypographic);
4402 ///<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>
4403 internal LineTag Break(int pos) {
4408 if (pos == this.start) {
4410 } else if (pos >= (start + length)) {
4414 new_tag = new LineTag(line, pos);
4415 new_tag.CopyFormattingFrom (this);
4417 new_tag.next = this.next;
4418 this.next = new_tag;
4419 new_tag.previous = this;
4421 if (new_tag.next != null) {
4422 new_tag.next.previous = new_tag;
4428 public virtual string Text ()
4430 return line.text.ToString (start - 1, length);
4433 public void CopyFormattingFrom (LineTag other)
4435 height = other.height;
4437 color = other.color;
4438 back_color = other.back_color;
4441 ///<summary>Create new font and brush from existing font and given new attributes. Returns true if fontheight changes</summary>
4442 internal static bool GenerateTextFormat(Font font_from, SolidBrush color_from, FontDefinition attributes, out Font new_font, out SolidBrush new_color) {
4448 if (attributes.font_obj == null) {
4449 size = font_from.SizeInPoints;
4450 unit = font_from.Unit;
4451 face = font_from.Name;
4452 style = font_from.Style;
4454 if (attributes.face != null) {
4455 face = attributes.face;
4458 if (attributes.size != 0) {
4459 size = attributes.size;
4462 style |= attributes.add_style;
4463 style &= ~attributes.remove_style;
4466 new_font = new Font(face, size, style, unit);
4468 new_font = attributes.font_obj;
4471 // Create 'new' color brush
4472 if (attributes.color != Color.Empty) {
4473 new_color = new SolidBrush(attributes.color);
4475 new_color = color_from;
4478 if (new_font.Height == font_from.Height) {
4484 /// <summary>Applies 'font' and 'brush' to characters starting at 'start' for 'length' chars;
4485 /// Removes any previous tags overlapping the same area;
4486 /// returns true if lineheight has changed</summary>
4487 /// <param name="start">1-based character position on line</param>
4488 internal static bool FormatText(Line line, int start, int length, Font font, SolidBrush color, SolidBrush back_color, FormatSpecified specified)
4494 bool retval = false; // Assume line-height doesn't change
4497 if (((FormatSpecified.Font & specified) == FormatSpecified.Font) && font.Height != line.height) {
4500 line.recalc = true; // This forces recalculation of the line in RecalculateDocument
4502 // A little sanity, not sure if it's needed, might be able to remove for speed
4503 if (length > line.text.Length) {
4504 length = line.text.Length;
4508 end = start + length;
4510 // Common special case
4511 if ((start == 1) && (length == tag.length)) {
4513 SetFormat (tag, font, color, back_color, specified);
4517 start_tag = FindTag (line, start);
4519 tag = start_tag.Break (start);
4521 while (tag != null && tag.end <= end) {
4522 SetFormat (tag, font, color, back_color, specified);
4526 if (end != line.text.Length) {
4527 /// Now do the last tag
4528 end_tag = FindTag (line, end);
4530 if (end_tag != null) {
4531 end_tag.Break (end);
4532 SetFormat (end_tag, font, color, back_color, specified);
4539 private static void SetFormat (LineTag tag, Font font, SolidBrush color, SolidBrush back_color, FormatSpecified specified)
4541 if ((FormatSpecified.Font & specified) == FormatSpecified.Font)
4543 if ((FormatSpecified.Color & specified) == FormatSpecified.Color)
4545 if ((FormatSpecified.BackColor & specified) == FormatSpecified.BackColor) {
4546 tag.back_color = back_color;
4548 // Console.WriteLine ("setting format: {0} {1} new color {2}", color.Color, specified, tag.color.Color);
4551 /// <summary>Applies font attributes specified to characters starting at 'start' for 'length' chars;
4552 /// Breaks tags at start and end point, keeping middle tags with altered attributes.
4553 /// Returns true if lineheight has changed</summary>
4554 /// <param name="start">1-based character position on line</param>
4555 internal static bool FormatText(Line line, int start, int length, FontDefinition attributes) {
4559 bool retval = false; // Assume line-height doesn't change
4561 line.recalc = true; // This forces recalculation of the line in RecalculateDocument
4563 // A little sanity, not sure if it's needed, might be able to remove for speed
4564 if (length > line.text.Length) {
4565 length = line.text.Length;
4570 // Common special case
4571 if ((start == 1) && (length == tag.length)) {
4573 GenerateTextFormat(tag.font, tag.color, attributes, out tag.font, out tag.color);
4577 start_tag = FindTag(line, start);
4579 if (start_tag == null) {
4581 // We are 'starting' after all valid tags; create a new tag with the right attributes
4582 start_tag = FindTag(line, line.text.Length - 1);
4583 start_tag.next = new LineTag(line, line.text.Length + 1);
4584 start_tag.next.CopyFormattingFrom (start_tag);
4585 start_tag.next.previous = start_tag;
4586 start_tag = start_tag.next;
4588 throw new Exception(String.Format("Could not find start_tag in document at line {0} position {1}", line.line_no, start));
4591 start_tag = start_tag.Break(start);
4594 end_tag = FindTag(line, start + length);
4595 if (end_tag != null) {
4596 end_tag = end_tag.Break(start + length);
4599 // start_tag or end_tag might be null; we're cool with that
4600 // we now walk from start_tag to end_tag, applying new attributes
4602 while ((tag != null) && tag != end_tag) {
4603 if (LineTag.GenerateTextFormat(tag.font, tag.color, attributes, out tag.font, out tag.color)) {
4612 /// <summary>Finds the tag that describes the character at position 'pos' on 'line'</summary>
4613 internal static LineTag FindTag(Line line, int pos) {
4614 LineTag tag = line.tags;
4616 // Beginning of line is a bit special
4618 // Not sure if we should get the final tag here
4622 while (tag != null) {
4623 if ((tag.start <= pos) && (pos <= tag.end)) {
4624 return GetFinalTag (tag);
4633 // There can be multiple tags at the same position, we want to make
4634 // sure we are using the very last tag at the given position
4635 internal static LineTag GetFinalTag (LineTag tag)
4639 while (res.next != null && res.next.length == 0)
4644 /// <summary>Combines 'this' tag with 'other' tag</summary>
4645 internal bool Combine(LineTag other) {
4646 if (!this.Equals(other)) {
4650 this.next = other.next;
4651 if (this.next != null) {
4652 this.next.previous = this;
4659 /// <summary>Remove 'this' tag ; to be called when formatting is to be removed</summary>
4660 internal bool Remove() {
4661 if ((this.start == 1) && (this.next == null)) {
4662 // We cannot remove the only tag
4665 if (this.start != 1) {
4666 this.previous.next = this.next;
4667 this.next.previous = this.previous;
4669 this.next.start = 1;
4670 this.line.tags = this.next;
4671 this.next.previous = null;
4677 /// <summary>Checks if 'this' tag describes the same formatting options as 'obj'</summary>
4678 public override bool Equals(object obj) {
4685 if (!(obj is LineTag)) {
4693 other = (LineTag)obj;
4695 if (this.font.Equals(other.font) && this.color.Equals(other.color)) { // FIXME add checking for things like link or type later
4702 public override int GetHashCode() {
4703 return base.GetHashCode ();
4706 public override string ToString() {
4708 return "Tag starts at index " + this.start + " length " + this.length + " text: " + Text () + "Font " + this.font.ToString();
4709 return "Zero Lengthed tag at index " + this.start;
4712 #endregion // Internal Methods
4715 internal class UndoManager {
4717 internal enum ActionType {
4721 // This is basically just cut & paste
4729 internal class Action {
4730 internal ActionType type;
4731 internal int line_no;
4733 internal object data;
4736 #region Local Variables
4737 private Document document;
4738 private Stack undo_actions;
4739 private Stack redo_actions;
4741 private int caret_line;
4742 private int caret_pos;
4744 // When performing an action, we lock the queue, so that the action can't be undone
4745 private bool locked;
4746 #endregion // Local Variables
4748 #region Constructors
4749 internal UndoManager (Document document)
4751 this.document = document;
4752 undo_actions = new Stack (50);
4753 redo_actions = new Stack (50);
4755 #endregion // Constructors
4758 internal bool CanUndo {
4759 get { return undo_actions.Count > 0; }
4762 internal bool CanRedo {
4763 get { return redo_actions.Count > 0; }
4766 internal string UndoActionName {
4768 foreach (Action action in undo_actions) {
4769 if (action.type == ActionType.UserActionBegin)
4770 return (string) action.data;
4771 if (action.type == ActionType.Typing)
4772 return Locale.GetText ("Typing");
4774 return String.Empty;
4778 internal string RedoActionName {
4780 foreach (Action action in redo_actions) {
4781 if (action.type == ActionType.UserActionBegin)
4782 return (string) action.data;
4783 if (action.type == ActionType.Typing)
4784 return Locale.GetText ("Typing");
4786 return String.Empty;
4789 #endregion // Properties
4791 #region Internal Methods
4792 internal void Clear ()
4794 undo_actions.Clear();
4795 redo_actions.Clear();
4798 internal void Undo ()
4801 bool user_action_finished = false;
4803 if (undo_actions.Count == 0)
4806 // Nuke the redo queue
4807 redo_actions.Clear ();
4812 action = (Action) undo_actions.Pop ();
4814 // Put onto redo stack
4815 redo_actions.Push(action);
4818 switch(action.type) {
4820 case ActionType.UserActionBegin:
4821 user_action_finished = true;
4824 case ActionType.UserActionEnd:
4828 case ActionType.InsertString:
4829 start = document.GetLine (action.line_no);
4830 document.SuspendUpdate ();
4831 document.DeleteMultiline (start, action.pos, ((string) action.data).Length + 1);
4832 document.PositionCaret (start, action.pos);
4833 document.SetSelectionToCaret (true);
4834 document.ResumeUpdate (true);
4837 case ActionType.Typing:
4838 start = document.GetLine (action.line_no);
4839 document.SuspendUpdate ();
4840 document.DeleteMultiline (start, action.pos, ((StringBuilder) action.data).Length);
4841 document.PositionCaret (start, action.pos);
4842 document.SetSelectionToCaret (true);
4843 document.ResumeUpdate (true);
4845 // This is an open ended operation, so only a single typing operation can be undone at once
4846 user_action_finished = true;
4849 case ActionType.DeleteString:
4850 start = document.GetLine (action.line_no);
4851 document.SuspendUpdate ();
4852 Insert (start, action.pos, (Line) action.data, true);
4853 document.ResumeUpdate (true);
4856 } while (!user_action_finished && undo_actions.Count > 0);
4861 internal void Redo ()
4864 bool user_action_finished = false;
4866 if (redo_actions.Count == 0)
4869 // You can't undo anything after redoing
4870 undo_actions.Clear ();
4877 action = (Action) redo_actions.Pop ();
4879 switch (action.type) {
4881 case ActionType.UserActionBegin:
4885 case ActionType.UserActionEnd:
4886 user_action_finished = true;
4889 case ActionType.InsertString:
4890 start = document.GetLine (action.line_no);
4891 document.SuspendUpdate ();
4892 start_index = document.LineTagToCharIndex (start, action.pos);
4893 document.InsertString (start, action.pos, (string) action.data);
4894 document.CharIndexToLineTag (start_index + ((string) action.data).Length,
4895 out document.caret.line, out document.caret.tag,
4896 out document.caret.pos);
4897 document.UpdateCaret ();
4898 document.SetSelectionToCaret (true);
4899 document.ResumeUpdate (true);
4902 case ActionType.Typing:
4903 start = document.GetLine (action.line_no);
4904 document.SuspendUpdate ();
4905 start_index = document.LineTagToCharIndex (start, action.pos);
4906 document.InsertString (start, action.pos, ((StringBuilder) action.data).ToString ());
4907 document.CharIndexToLineTag (start_index + ((StringBuilder) action.data).Length,
4908 out document.caret.line, out document.caret.tag,
4909 out document.caret.pos);
4910 document.UpdateCaret ();
4911 document.SetSelectionToCaret (true);
4912 document.ResumeUpdate (true);
4914 // This is an open ended operation, so only a single typing operation can be undone at once
4915 user_action_finished = true;
4918 case ActionType.DeleteString:
4919 start = document.GetLine (action.line_no);
4920 document.SuspendUpdate ();
4921 document.DeleteMultiline (start, action.pos, ((Line) action.data).text.Length);
4922 document.PositionCaret (start, action.pos);
4923 document.SetSelectionToCaret (true);
4924 document.ResumeUpdate (true);
4928 } while (!user_action_finished && redo_actions.Count > 0);
4932 #endregion // Internal Methods
4934 #region Private Methods
4936 public void BeginUserAction (string name)
4941 Action ua = new Action ();
4942 ua.type = ActionType.UserActionBegin;
4945 undo_actions.Push (ua);
4948 public void EndUserAction ()
4953 Action ua = new Action ();
4954 ua.type = ActionType.UserActionEnd;
4956 undo_actions.Push (ua);
4959 // start_pos, end_pos = 1 based
4960 public void RecordDeleteString (Line start_line, int start_pos, Line end_line, int end_pos)
4965 Action a = new Action ();
4967 // We cant simply store the string, because then formatting would be lost
4968 a.type = ActionType.DeleteString;
4969 a.line_no = start_line.line_no;
4971 a.data = Duplicate (start_line, start_pos, end_line, end_pos);
4973 undo_actions.Push(a);
4976 public void RecordInsertString (Line line, int pos, string str)
4978 if (locked || str.Length == 0)
4981 Action a = new Action ();
4983 a.type = ActionType.InsertString;
4985 a.line_no = line.line_no;
4988 undo_actions.Push (a);
4991 public void RecordTyping (Line line, int pos, char ch)
4998 if (undo_actions.Count > 0)
4999 a = (Action) undo_actions.Peek ();
5001 if (a == null || a.type != ActionType.Typing) {
5003 a.type = ActionType.Typing;
5004 a.data = new StringBuilder ();
5005 a.line_no = line.line_no;
5008 undo_actions.Push (a);
5011 StringBuilder data = (StringBuilder) a.data;
5015 // start_pos = 1-based
5016 // end_pos = 1-based
5017 public Line Duplicate(Line start_line, int start_pos, Line end_line, int end_pos)
5023 LineTag current_tag;
5028 line = new Line (start_line.document);
5031 for (int i = start_line.line_no; i <= end_line.line_no; i++) {
5032 current = document.GetLine(i);
5034 if (start_line.line_no == i) {
5040 if (end_line.line_no == i) {
5043 end = current.text.Length;
5050 line.text = new StringBuilder (current.text.ToString (start, end - start));
5052 // Copy tags from start to start+length onto new line
5053 current_tag = current.FindTag (start);
5054 while ((current_tag != null) && (current_tag.start < end)) {
5055 if ((current_tag.start <= start) && (start < (current_tag.start + current_tag.length))) {
5056 // start tag is within this tag
5059 tag_start = current_tag.start;
5062 tag = new LineTag(line, tag_start - start + 1);
5063 tag.CopyFormattingFrom (current_tag);
5065 current_tag = current_tag.next;
5067 // Add the new tag to the line
5068 if (line.tags == null) {
5074 while (tail.next != null) {
5078 tag.previous = tail;
5082 if ((i + 1) <= end_line.line_no) {
5083 line.soft_break = current.soft_break;
5085 // Chain them (we use right/left as next/previous)
5086 line.right = new Line (start_line.document);
5087 line.right.left = line;
5095 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
5096 internal void Insert(Line line, int pos, Line insert, bool select)
5104 // Handle special case first
5105 if (insert.right == null) {
5107 // Single line insert
5108 document.Split(line, pos);
5110 if (insert.tags == null) {
5111 return; // Blank line
5114 //Insert our tags at the end
5117 while (tag.next != null) {
5121 offset = tag.start + tag.length - 1;
5123 tag.next = insert.tags;
5124 line.text.Insert(offset, insert.text.ToString());
5126 // Adjust start locations
5128 while (tag != null) {
5129 tag.start += offset;
5133 // Put it back together
5134 document.Combine(line.line_no, line.line_no + 1);
5137 document.SetSelectionStart (line, pos);
5138 document.SetSelectionEnd (line, pos + insert.text.Length);
5141 document.UpdateView(line, pos);
5149 while (current != null) {
5150 if (current == insert) {
5151 // Inserting the first line we split the line (and make space)
5152 document.Split(line, pos);
5153 //Insert our tags at the end of the line
5157 while (tag.next != null) {
5160 offset = tag.start + tag.length - 1;
5161 tag.next = current.tags;
5162 tag.next.previous = tag;
5168 line.tags = current.tags;
5169 line.tags.previous = null;
5173 document.Split(line.line_no, 0);
5175 line.tags = current.tags;
5176 line.tags.previous = null;
5179 // Adjust start locations and line pointers
5180 while (tag != null) {
5181 tag.start += offset;
5186 line.text.Insert(offset, current.text.ToString());
5187 line.Grow(line.text.Length);
5190 line = document.GetLine(line.line_no + 1);
5192 // FIXME? Test undo of line-boundaries
5193 if ((current.right == null) && (current.tags.length != 0)) {
5194 document.Combine(line.line_no - 1, line.line_no);
5196 current = current.right;
5201 // Recalculate our document
5202 document.UpdateView(first, lines, pos);
5205 #endregion // Private Methods