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) {
338 // Catch what the loop below wont; eliminate 0 length
339 // tags, but only if there are other tags after us
340 // We only eliminate text tags if there is another text tag
341 // after it. Otherwise we wind up trying to type on image tags
343 while ((current.length == 0) && (next != null) && (next.IsTextTag)) {
345 tags.previous = null;
355 while (next != null) {
356 // Take out 0 length tags unless it's the last tag in the document
357 if (current.IsTextTag && next.length == 0 && next.IsTextTag) {
358 if ((next.next != null) || (line_no != lines)) {
359 current.next = next.next;
360 if (current.next != null) {
361 current.next.previous = current;
367 if (current.Combine(next)) {
372 current = current.next;
377 /// <summary> Find the tag on a line based on the character position, pos is 0-based</summary>
378 internal LineTag FindTag(int pos) {
387 if (pos >= text.Length) {
388 pos = text.Length - 1;
391 while (tag != null) {
392 if (((tag.start - 1) <= pos) && (pos < (tag.start + tag.length - 1))) {
393 return LineTag.GetFinalTag (tag);
401 /// Recalculate a single line using the same char for every character in the line
404 internal bool RecalculatePasswordLine(Graphics g, Document doc) {
413 len = this.text.Length;
421 w = g.MeasureString(doc.password_char, tags.font, 10000, Document.string_format).Width;
423 if (this.height != (int)tag.font.Height) {
429 this.height = (int)tag.font.Height;
430 tag.height = this.height;
432 XplatUI.GetFontMetrics(g, tag.font, out tag.ascent, out descent);
433 this.ascent = tag.ascent;
437 widths[pos] = widths[pos-1] + w;
444 /// Go through all tags on a line and recalculate all size-related values;
445 /// returns true if lineheight changed
447 internal bool RecalculateLine(Graphics g, Document doc) {
460 len = this.text.Length;
462 prev_height = this.height; // For drawing optimization calculations
463 this.height = 0; // Reset line height
464 this.ascent = 0; // Reset the ascent for the line
467 if (this.soft_break) {
468 widths[0] = hanging_indent;
481 while (tag.length == 0) { // We should always have tags after a tag.length==0 unless len==0
487 size = tag.SizeOfPosition (g, pos);
490 if (Char.IsWhiteSpace(text[pos])) {
495 if ((wrap_pos > 0) && (wrap_pos != len) && (widths[pos] + w) + 5 > (doc.viewport_width - this.right_indent)) {
496 // Make sure to set the last width of the line before wrapping
497 widths [pos + 1] = widths [pos] + w;
501 doc.Split(this, tag, pos, this.soft_break);
502 this.soft_break = true;
503 len = this.text.Length;
507 } else if (pos > 1 && (widths[pos] + w) > (doc.viewport_width - this.right_indent)) {
508 // No suitable wrap position was found so break right in the middle of a word
510 // Make sure to set the last width of the line before wrapping
511 widths [pos + 1] = widths [pos] + w;
513 doc.Split(this, tag, pos, this.soft_break);
514 this.soft_break = true;
515 len = this.text.Length;
521 // Contract all soft lines that follow back into our line
525 widths[pos] = widths[pos-1] + w;
528 line = doc.GetLine(this.line_no + 1);
529 if ((line != null) && soft_break) {
530 // Pull the two lines together
531 doc.Combine(this.line_no, this.line_no + 1);
532 len = this.text.Length;
538 if (pos == (tag.start-1 + tag.length)) {
539 // We just found the end of our current tag
540 tag.height = tag.MaxHeight ();
542 // Check if we're the tallest on the line (so far)
543 if (tag.height > this.height) {
544 this.height = tag.height; // Yep; make sure the line knows
547 if (tag.ascent == 0) {
550 XplatUI.GetFontMetrics(g, tag.font, out tag.ascent, out descent);
553 if (tag.ascent > this.ascent) {
556 // We have a tag that has a taller ascent than the line;
558 while (t != null && t != tag) {
559 t.shift = tag.ascent - t.ascent;
564 this.ascent = tag.ascent;
566 tag.shift = this.ascent - tag.ascent;
577 if (this.height == 0) {
578 this.height = tags.font.Height;
579 tag.height = this.height;
582 if (prev_height != this.height) {
587 #endregion // Internal Methods
589 #region Administrative
590 public int CompareTo(object obj) {
595 if (! (obj is Line)) {
596 throw new ArgumentException("Object is not of type Line", "obj");
599 if (line_no < ((Line)obj).line_no) {
601 } else if (line_no > ((Line)obj).line_no) {
608 public object Clone() {
611 clone = new Line (document);
616 clone.left = (Line)left.Clone();
620 clone.left = (Line)left.Clone();
626 internal object CloneLine() {
629 clone = new Line (document);
636 public override bool Equals(object obj) {
641 if (!(obj is Line)) {
649 if (line_no == ((Line)obj).line_no) {
656 public override int GetHashCode() {
657 return base.GetHashCode ();
660 public override string ToString() {
661 return "Line " + line_no;
664 #endregion // Administrative
667 internal class Document : ICloneable, IEnumerable {
669 // FIXME - go through code and check for places where
670 // we do explicit comparisons instead of using the compare overloads
671 internal struct Marker {
673 internal LineTag tag;
677 public static bool operator<(Marker lhs, Marker rhs) {
678 if (lhs.line.line_no < rhs.line.line_no) {
682 if (lhs.line.line_no == rhs.line.line_no) {
683 if (lhs.pos < rhs.pos) {
690 public static bool operator>(Marker lhs, Marker rhs) {
691 if (lhs.line.line_no > rhs.line.line_no) {
695 if (lhs.line.line_no == rhs.line.line_no) {
696 if (lhs.pos > rhs.pos) {
703 public static bool operator==(Marker lhs, Marker rhs) {
704 if ((lhs.line.line_no == rhs.line.line_no) && (lhs.pos == rhs.pos)) {
710 public static bool operator!=(Marker lhs, Marker rhs) {
711 if ((lhs.line.line_no != rhs.line.line_no) || (lhs.pos != rhs.pos)) {
717 public void Combine(Line move_to_line, int move_to_line_length) {
719 pos += move_to_line_length;
720 tag = LineTag.FindTag(line, pos);
723 // This is for future use, right now Document.Split does it by hand, with some added shortcut logic
724 public void Split(Line move_to_line, int split_at) {
727 tag = LineTag.FindTag(line, pos);
730 public override bool Equals(object obj) {
731 return this==(Marker)obj;
734 public override int GetHashCode() {
735 return base.GetHashCode ();
738 public override string ToString() {
739 return "Marker Line " + line + ", Position " + pos;
743 #endregion Structures
745 #region Local Variables
746 private Line document;
748 private Line sentinel;
749 private int document_id;
750 private Random random = new Random();
751 internal string password_char;
752 private StringBuilder password_cache;
753 private bool calc_pass;
754 private int char_count;
756 // For calculating widths/heights
757 public static readonly StringFormat string_format = new StringFormat (StringFormat.GenericTypographic);
759 private int recalc_suspended;
760 private bool recalc_pending;
761 private int recalc_start = 1; // This starts at one, since lines are 1 based
762 private int recalc_end;
763 private bool recalc_optimize;
765 private int update_suspended;
766 private bool update_pending;
767 private int update_start = 1;
769 internal bool multiline;
772 internal UndoManager undo;
774 internal Marker caret;
775 internal Marker selection_start;
776 internal Marker selection_end;
777 internal bool selection_visible;
778 internal Marker selection_anchor;
779 internal Marker selection_prev;
780 internal bool selection_end_anchor;
782 internal int viewport_x;
783 internal int viewport_y; // The visible area of the document
784 internal int viewport_width;
785 internal int viewport_height;
787 internal int document_x; // Width of the document
788 internal int document_y; // Height of the document
790 internal Rectangle invalid;
792 internal int crlf_size; // 1 or 2, depending on whether we use \r\n or just \n
794 internal TextBoxBase owner; // Who's owning us?
795 static internal int caret_width = 1;
796 static internal int caret_shift = 1;
797 #endregion // Local Variables
800 internal Document(TextBoxBase owner) {
808 recalc_pending = false;
810 // Tree related stuff
811 sentinel = new Line (this);
812 sentinel.color = LineColor.Black;
816 // We always have a blank line
817 owner.HandleCreated += new EventHandler(owner_HandleCreated);
818 owner.VisibleChanged += new EventHandler(owner_VisibleChanged);
820 Add(1, "", owner.Font, ThemeEngine.Current.ResPool.GetSolidBrush(owner.ForeColor));
821 Line l = GetLine (1);
824 undo = new UndoManager (this);
826 selection_visible = false;
827 selection_start.line = this.document;
828 selection_start.pos = 0;
829 selection_start.tag = selection_start.line.tags;
830 selection_end.line = this.document;
831 selection_end.pos = 0;
832 selection_end.tag = selection_end.line.tags;
833 selection_anchor.line = this.document;
834 selection_anchor.pos = 0;
835 selection_anchor.tag = selection_anchor.line.tags;
836 caret.line = this.document;
838 caret.tag = caret.line.tags;
845 // Default selection is empty
847 document_id = random.Next();
849 string_format.Trimming = StringTrimming.None;
850 string_format.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;
854 #region Internal Properties
871 internal Line CaretLine {
877 internal int CaretPosition {
883 internal Point Caret {
885 return new Point((int)caret.tag.line.widths[caret.pos] + caret.line.X, caret.line.Y);
889 internal LineTag CaretTag {
899 internal int CRLFSize {
909 internal string PasswordChar {
911 return password_char;
915 password_char = value;
916 PasswordCache.Length = 0;
917 if ((password_char.Length != 0) && (password_char[0] != '\0')) {
925 private StringBuilder PasswordCache {
927 if (password_cache == null)
928 password_cache = new StringBuilder();
929 return password_cache;
933 internal int ViewPortX {
943 internal int Length {
945 return char_count + lines - 1; // Add \n for each line but the last
949 private int CharCount {
957 if (LengthChanged != null) {
958 LengthChanged(this, EventArgs.Empty);
963 internal int ViewPortY {
973 internal int ViewPortWidth {
975 return viewport_width;
979 viewport_width = value;
983 internal int ViewPortHeight {
985 return viewport_height;
989 viewport_height = value;
996 return this.document_x;
1000 internal int Height {
1002 return this.document_y;
1006 internal bool SelectionVisible {
1008 return selection_visible;
1012 internal bool Wrap {
1022 #endregion // Internal Properties
1024 #region Private Methods
1026 internal void SuspendRecalc ()
1031 internal void ResumeRecalc (bool immediate_update)
1033 if (recalc_suspended > 0)
1036 if (immediate_update && recalc_suspended == 0 && recalc_pending) {
1037 RecalculateDocument (owner.CreateGraphicsInternal(), recalc_start, recalc_end, recalc_optimize);
1038 recalc_pending = false;
1042 internal void SuspendUpdate ()
1047 internal void ResumeUpdate (bool immediate_update)
1049 if (update_suspended > 0)
1052 if (immediate_update && update_suspended == 0 && update_pending) {
1053 UpdateView (GetLine (update_start), 0);
1054 update_pending = false;
1059 internal int DumpTree(Line line, bool with_tags) {
1064 Console.Write("Line {0} [# {1}], Y: {2}, soft: {3}, Text: '{4}'",
1065 line.line_no, line.GetHashCode(), line.Y, line.soft_break,
1066 line.text != null ? line.text.ToString() : "undefined");
1068 if (line.left == sentinel) {
1069 Console.Write(", left = sentinel");
1070 } else if (line.left == null) {
1071 Console.Write(", left = NULL");
1074 if (line.right == sentinel) {
1075 Console.Write(", right = sentinel");
1076 } else if (line.right == null) {
1077 Console.Write(", right = NULL");
1080 Console.WriteLine("");
1090 Console.Write(" Tags: ");
1091 while (tag != null) {
1092 Console.Write("{0} <{1}>-<{2}>", count++, tag.start, tag.end
1093 /*line.text.ToString (tag.start - 1, tag.length)*/);
1094 length += tag.length;
1096 if (tag.line != line) {
1097 Console.Write("BAD line link");
1098 throw new Exception("Bad line link in tree");
1102 Console.Write(", ");
1105 if (length > line.text.Length) {
1106 throw new Exception(String.Format("Length of tags more than length of text on line (expected {0} calculated {1})", line.text.Length, length));
1107 } else if (length < line.text.Length) {
1108 throw new Exception(String.Format("Length of tags less than length of text on line (expected {0} calculated {1})", line.text.Length, length));
1110 Console.WriteLine("");
1112 if (line.left != null) {
1113 if (line.left != sentinel) {
1114 total += DumpTree(line.left, with_tags);
1117 if (line != sentinel) {
1118 throw new Exception("Left should not be NULL");
1122 if (line.right != null) {
1123 if (line.right != sentinel) {
1124 total += DumpTree(line.right, with_tags);
1127 if (line != sentinel) {
1128 throw new Exception("Right should not be NULL");
1132 for (int i = 1; i <= this.lines; i++) {
1133 if (GetLine(i) == null) {
1134 throw new Exception(String.Format("Hole in line order, missing {0}", i));
1138 if (line == this.Root) {
1139 if (total < this.lines) {
1140 throw new Exception(String.Format("Not enough nodes in tree, found {0}, expected {1}", total, this.lines));
1141 } else if (total > this.lines) {
1142 throw new Exception(String.Format("Too many nodes in tree, found {0}, expected {1}", total, this.lines));
1149 private void SetSelectionVisible (bool value)
1151 selection_visible = value;
1153 // cursor and selection are enemies, we can't have both in the same room at the same time
1154 if (owner.IsHandleCreated && !owner.show_caret_w_selection)
1155 XplatUI.CaretVisible (owner.Handle, !selection_visible);
1158 private void DecrementLines(int line_no) {
1162 while (current <= lines) {
1163 GetLine(current).line_no--;
1169 private void IncrementLines(int line_no) {
1172 current = this.lines;
1173 while (current >= line_no) {
1174 GetLine(current).line_no++;
1180 private void RebalanceAfterAdd(Line line1) {
1183 while ((line1 != document) && (line1.parent.color == LineColor.Red)) {
1184 if (line1.parent == line1.parent.parent.left) {
1185 line2 = line1.parent.parent.right;
1187 if ((line2 != null) && (line2.color == LineColor.Red)) {
1188 line1.parent.color = LineColor.Black;
1189 line2.color = LineColor.Black;
1190 line1.parent.parent.color = LineColor.Red;
1191 line1 = line1.parent.parent;
1193 if (line1 == line1.parent.right) {
1194 line1 = line1.parent;
1198 line1.parent.color = LineColor.Black;
1199 line1.parent.parent.color = LineColor.Red;
1201 RotateRight(line1.parent.parent);
1204 line2 = line1.parent.parent.left;
1206 if ((line2 != null) && (line2.color == LineColor.Red)) {
1207 line1.parent.color = LineColor.Black;
1208 line2.color = LineColor.Black;
1209 line1.parent.parent.color = LineColor.Red;
1210 line1 = line1.parent.parent;
1212 if (line1 == line1.parent.left) {
1213 line1 = line1.parent;
1217 line1.parent.color = LineColor.Black;
1218 line1.parent.parent.color = LineColor.Red;
1219 RotateLeft(line1.parent.parent);
1223 document.color = LineColor.Black;
1226 private void RebalanceAfterDelete(Line line1) {
1229 while ((line1 != document) && (line1.color == LineColor.Black)) {
1230 if (line1 == line1.parent.left) {
1231 line2 = line1.parent.right;
1232 if (line2.color == LineColor.Red) {
1233 line2.color = LineColor.Black;
1234 line1.parent.color = LineColor.Red;
1235 RotateLeft(line1.parent);
1236 line2 = line1.parent.right;
1238 if ((line2.left.color == LineColor.Black) && (line2.right.color == LineColor.Black)) {
1239 line2.color = LineColor.Red;
1240 line1 = line1.parent;
1242 if (line2.right.color == LineColor.Black) {
1243 line2.left.color = LineColor.Black;
1244 line2.color = LineColor.Red;
1246 line2 = line1.parent.right;
1248 line2.color = line1.parent.color;
1249 line1.parent.color = LineColor.Black;
1250 line2.right.color = LineColor.Black;
1251 RotateLeft(line1.parent);
1255 line2 = line1.parent.left;
1256 if (line2.color == LineColor.Red) {
1257 line2.color = LineColor.Black;
1258 line1.parent.color = LineColor.Red;
1259 RotateRight(line1.parent);
1260 line2 = line1.parent.left;
1262 if ((line2.right.color == LineColor.Black) && (line2.left.color == LineColor.Black)) {
1263 line2.color = LineColor.Red;
1264 line1 = line1.parent;
1266 if (line2.left.color == LineColor.Black) {
1267 line2.right.color = LineColor.Black;
1268 line2.color = LineColor.Red;
1270 line2 = line1.parent.left;
1272 line2.color = line1.parent.color;
1273 line1.parent.color = LineColor.Black;
1274 line2.left.color = LineColor.Black;
1275 RotateRight(line1.parent);
1280 line1.color = LineColor.Black;
1283 private void RotateLeft(Line line1) {
1284 Line line2 = line1.right;
1286 line1.right = line2.left;
1288 if (line2.left != sentinel) {
1289 line2.left.parent = line1;
1292 if (line2 != sentinel) {
1293 line2.parent = line1.parent;
1296 if (line1.parent != null) {
1297 if (line1 == line1.parent.left) {
1298 line1.parent.left = line2;
1300 line1.parent.right = line2;
1307 if (line1 != sentinel) {
1308 line1.parent = line2;
1312 private void RotateRight(Line line1) {
1313 Line line2 = line1.left;
1315 line1.left = line2.right;
1317 if (line2.right != sentinel) {
1318 line2.right.parent = line1;
1321 if (line2 != sentinel) {
1322 line2.parent = line1.parent;
1325 if (line1.parent != null) {
1326 if (line1 == line1.parent.right) {
1327 line1.parent.right = line2;
1329 line1.parent.left = line2;
1335 line2.right = line1;
1336 if (line1 != sentinel) {
1337 line1.parent = line2;
1342 internal void UpdateView(Line line, int pos) {
1343 if (!owner.IsHandleCreated) {
1347 if (update_suspended > 0) {
1348 update_start = Math.Min (update_start, line.line_no);
1349 // update_end = Math.Max (update_end, line.line_no);
1350 // recalc_optimize = true;
1351 update_pending = true;
1355 // Optimize invalidation based on Line alignment
1356 if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no, true)) {
1357 // Lineheight changed, invalidate the rest of the document
1358 if ((line.Y - viewport_y) >=0 ) {
1359 // We formatted something that's in view, only draw parts of the screen
1360 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, owner.Height - line.Y - viewport_y));
1362 // The tag was above the visible area, draw everything
1366 switch(line.alignment) {
1367 case HorizontalAlignment.Left: {
1368 owner.Invalidate(new Rectangle(line.X + (int)line.widths[pos] - viewport_x - 1, line.Y - viewport_y, viewport_width, line.height + 1));
1372 case HorizontalAlignment.Center: {
1373 owner.Invalidate(new Rectangle(line.X, line.Y - viewport_y, viewport_width, line.height + 1));
1377 case HorizontalAlignment.Right: {
1378 owner.Invalidate(new Rectangle(line.X, line.Y - viewport_y, (int)line.widths[pos + 1] - viewport_x + line.X, line.height + 1));
1386 // Update display from line, down line_count lines; pos is unused, but required for the signature
1387 internal void UpdateView(Line line, int line_count, int pos) {
1388 if (!owner.IsHandleCreated) {
1392 if (recalc_suspended > 0) {
1393 recalc_start = Math.Min (recalc_start, line.line_no);
1394 recalc_end = Math.Max (recalc_end, line.line_no + line_count - 1);
1395 recalc_optimize = true;
1396 recalc_pending = true;
1400 if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no + line_count - 1, true)) {
1401 // Lineheight changed, invalidate the rest of the document
1402 if ((line.Y - viewport_y) >=0 ) {
1403 // We formatted something that's in view, only draw parts of the screen
1404 //blah Console.WriteLine("TextControl.cs(981) Invalidate called in UpdateView(line, line_count, pos)");
1405 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, owner.Height - line.Y - viewport_y));
1407 // The tag was above the visible area, draw everything
1408 //blah Console.WriteLine("TextControl.cs(985) Invalidate called in UpdateView(line, line_count, pos)");
1414 end_line = GetLine(line.line_no + line_count -1);
1415 if (end_line == null) {
1419 //blah Console.WriteLine("TextControl.cs(996) Invalidate called in UpdateView(line, line_count, pos)");
1420 owner.Invalidate(new Rectangle(0 - viewport_x, line.Y - viewport_y, (int)line.widths[line.text.Length], end_line.Y + end_line.height));
1423 #endregion // Private Methods
1425 #region Internal Methods
1426 // Clear the document and reset state
1427 internal void Empty() {
1429 document = sentinel;
1432 // We always have a blank line
1433 Add(1, "", owner.Font, ThemeEngine.Current.ResPool.GetSolidBrush(owner.ForeColor));
1434 Line l = GetLine (1);
1435 l.soft_break = true;
1437 this.RecalculateDocument(owner.CreateGraphicsInternal());
1438 PositionCaret(0, 0);
1440 SetSelectionVisible (false);
1442 selection_start.line = this.document;
1443 selection_start.pos = 0;
1444 selection_start.tag = selection_start.line.tags;
1445 selection_end.line = this.document;
1446 selection_end.pos = 0;
1447 selection_end.tag = selection_end.line.tags;
1456 if (owner.IsHandleCreated)
1457 owner.Invalidate ();
1460 internal void PositionCaret(Line line, int pos) {
1461 caret.tag = line.FindTag (pos);
1463 MoveCaretToTextTag ();
1468 if (owner.IsHandleCreated) {
1469 if (owner.Focused) {
1470 if (caret.height != caret.tag.height)
1471 XplatUI.CreateCaret (owner.Handle, caret_width, caret.height);
1472 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);
1475 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1478 // We set this at the end because we use the heights to determine whether or
1479 // not we need to recreate the caret
1480 caret.height = caret.tag.height;
1484 internal void PositionCaret(int x, int y) {
1485 if (!owner.IsHandleCreated) {
1489 caret.tag = FindCursor(x, y, out caret.pos);
1491 MoveCaretToTextTag ();
1493 caret.line = caret.tag.line;
1494 caret.height = caret.tag.height;
1496 if (owner.Focused) {
1497 XplatUI.CreateCaret (owner.Handle, caret_width, caret.height);
1498 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 (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1504 internal void CaretHasFocus() {
1505 if ((caret.tag != null) && owner.IsHandleCreated) {
1506 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1507 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);
1512 if (owner.IsHandleCreated && selection_visible) {
1513 InvalidateSelectionArea ();
1517 internal void CaretLostFocus() {
1518 if (!owner.IsHandleCreated) {
1521 XplatUI.DestroyCaret(owner.Handle);
1524 internal void AlignCaret() {
1525 if (!owner.IsHandleCreated) {
1529 caret.tag = LineTag.FindTag (caret.line, caret.pos);
1531 MoveCaretToTextTag ();
1533 caret.height = caret.tag.height;
1535 if (owner.Focused) {
1536 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1537 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);
1541 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1544 internal void UpdateCaret() {
1545 if (!owner.IsHandleCreated || caret.tag == null) {
1549 MoveCaretToTextTag ();
1551 if (caret.tag.height != caret.height) {
1552 caret.height = caret.tag.height;
1553 if (owner.Focused) {
1554 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1558 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);
1562 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1565 internal void DisplayCaret() {
1566 if (!owner.IsHandleCreated) {
1570 if (owner.ShowSelection && (!selection_visible || owner.show_caret_w_selection)) {
1571 XplatUI.CaretVisible(owner.Handle, true);
1575 internal void HideCaret() {
1576 if (!owner.IsHandleCreated) {
1580 if (owner.Focused) {
1581 XplatUI.CaretVisible(owner.Handle, false);
1586 internal void MoveCaretToTextTag ()
1588 if (caret.tag == null || caret.tag.IsTextTag)
1593 if (caret.pos < caret.tag.start) {
1594 caret.tag = caret.tag.previous;
1596 caret.tag = caret.tag.next;
1600 internal void MoveCaret(CaretDirection direction) {
1601 // FIXME should we use IsWordSeparator to detect whitespace, instead
1602 // of looking for actual spaces in the Word move cases?
1604 bool nowrap = false;
1606 case CaretDirection.CharForwardNoWrap:
1608 goto case CaretDirection.CharForward;
1609 case CaretDirection.CharForward: {
1611 if (caret.pos > caret.line.text.Length) {
1613 // Go into next line
1614 if (caret.line.line_no < this.lines) {
1615 caret.line = GetLine(caret.line.line_no+1);
1617 caret.tag = caret.line.tags;
1622 // Single line; we stay where we are
1626 if ((caret.tag.start - 1 + caret.tag.length) < caret.pos) {
1627 caret.tag = caret.tag.next;
1634 case CaretDirection.CharBackNoWrap:
1636 goto case CaretDirection.CharBack;
1637 case CaretDirection.CharBack: {
1638 if (caret.pos > 0) {
1639 // caret.pos--; // folded into the if below
1641 if (--caret.pos > 0) {
1642 if (caret.tag.start > caret.pos) {
1643 caret.tag = caret.tag.previous;
1647 if (caret.line.line_no > 1 && !nowrap) {
1648 caret.line = GetLine(caret.line.line_no - 1);
1649 caret.pos = caret.line.text.Length;
1650 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1657 case CaretDirection.WordForward: {
1660 len = caret.line.text.Length;
1661 if (caret.pos < len) {
1662 while ((caret.pos < len) && (caret.line.text[caret.pos] != ' ')) {
1665 if (caret.pos < len) {
1666 // Skip any whitespace
1667 while ((caret.pos < len) && (caret.line.text[caret.pos] == ' ')) {
1671 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1673 if (caret.line.line_no < this.lines) {
1674 caret.line = GetLine(caret.line.line_no + 1);
1676 caret.tag = caret.line.tags;
1683 case CaretDirection.WordBack: {
1684 if (caret.pos > 0) {
1687 while ((caret.pos > 0) && (caret.line.text[caret.pos] == ' ')) {
1691 while ((caret.pos > 0) && (caret.line.text[caret.pos] != ' ')) {
1695 if (caret.line.text.ToString(caret.pos, 1) == " ") {
1696 if (caret.pos != 0) {
1699 caret.line = GetLine(caret.line.line_no - 1);
1700 caret.pos = caret.line.text.Length;
1703 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1705 if (caret.line.line_no > 1) {
1706 caret.line = GetLine(caret.line.line_no - 1);
1707 caret.pos = caret.line.text.Length;
1708 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1715 case CaretDirection.LineUp: {
1716 if (caret.line.line_no > 1) {
1719 pixel = (int)caret.line.widths[caret.pos];
1720 PositionCaret(pixel, GetLine(caret.line.line_no - 1).Y);
1727 case CaretDirection.LineDown: {
1728 if (caret.line.line_no < lines) {
1731 pixel = (int)caret.line.widths[caret.pos];
1732 PositionCaret(pixel, GetLine(caret.line.line_no + 1).Y);
1739 case CaretDirection.Home: {
1740 if (caret.pos > 0) {
1742 caret.tag = caret.line.tags;
1748 case CaretDirection.End: {
1749 if (caret.pos < caret.line.text.Length) {
1750 caret.pos = caret.line.text.Length;
1751 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1757 case CaretDirection.PgUp: {
1759 int new_y, y_offset;
1761 if (viewport_y == 0) {
1763 // This should probably be handled elsewhere
1764 if (!(owner is RichTextBox)) {
1765 // Page down doesn't do anything in a regular TextBox
1766 // if the bottom of the document
1767 // is already visible, the page and the caret stay still
1771 // We're just placing the caret at the end of the document, no scrolling needed
1772 owner.vscroll.Value = 0;
1773 Line line = GetLine (1);
1774 PositionCaret (line, 0);
1777 y_offset = caret.line.Y - viewport_y;
1778 new_y = caret.line.Y - viewport_height;
1780 owner.vscroll.Value = Math.Max (new_y, 0);
1781 PositionCaret ((int)caret.line.widths[caret.pos], y_offset + viewport_y);
1785 case CaretDirection.PgDn: {
1786 int new_y, y_offset;
1788 if ((viewport_y + viewport_height) > document_y) {
1790 // This should probably be handled elsewhere
1791 if (!(owner is RichTextBox)) {
1792 // Page up doesn't do anything in a regular TextBox
1793 // if the bottom of the document
1794 // is already visible, the page and the caret stay still
1798 // We're just placing the caret at the end of the document, no scrolling needed
1799 owner.vscroll.Value = owner.vscroll.Maximum - viewport_height + 1;
1800 Line line = GetLine (lines);
1801 PositionCaret (line, line.Text.Length);
1804 y_offset = caret.line.Y - viewport_y;
1805 new_y = caret.line.Y + viewport_height;
1807 owner.vscroll.Value = Math.Min (new_y, owner.vscroll.Maximum - viewport_height + 1);
1808 PositionCaret ((int)caret.line.widths[caret.pos], y_offset + viewport_y);
1813 case CaretDirection.CtrlPgUp: {
1814 PositionCaret(0, viewport_y);
1819 case CaretDirection.CtrlPgDn: {
1824 tag = FindTag(0, viewport_y + viewport_height, out index, false);
1825 if (tag.line.line_no > 1) {
1826 line = GetLine(tag.line.line_no - 1);
1830 PositionCaret(line, line.Text.Length);
1835 case CaretDirection.CtrlHome: {
1836 caret.line = GetLine(1);
1838 caret.tag = caret.line.tags;
1844 case CaretDirection.CtrlEnd: {
1845 caret.line = GetLine(lines);
1846 caret.pos = caret.line.text.Length;
1847 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1853 case CaretDirection.SelectionStart: {
1854 caret.line = selection_start.line;
1855 caret.pos = selection_start.pos;
1856 caret.tag = selection_start.tag;
1862 case CaretDirection.SelectionEnd: {
1863 caret.line = selection_end.line;
1864 caret.pos = selection_end.pos;
1865 caret.tag = selection_end.tag;
1873 internal void DumpDoc ()
1875 Console.WriteLine ("<doc>");
1876 for (int i = 1; i < lines; i++) {
1877 Line line = GetLine (i);
1878 Console.WriteLine ("<line no='{0}'>", line.line_no);
1880 LineTag tag = line.tags;
1881 while (tag != null) {
1882 Console.Write ("\t<tag type='{0}' span='{1}->{2}'>", tag.GetType (), tag.start, tag.length);
1883 Console.Write (tag.Text ());
1884 Console.WriteLine ("</tag>");
1887 Console.WriteLine ("</line>");
1889 Console.WriteLine ("</doc>");
1892 internal void Draw (Graphics g, Rectangle clip)
1894 Line line; // Current line being drawn
1895 LineTag tag; // Current tag being drawn
1896 int start; // First line to draw
1897 int end; // Last line to draw
1898 StringBuilder text; // String representing the current line
1901 Brush current_brush;
1902 Brush disabled_brush;
1906 // First, figure out from what line to what line we need to draw
1909 start = GetLineByPixel(clip.Top + viewport_y, false).line_no;
1910 end = GetLineByPixel(clip.Bottom + viewport_y, false).line_no;
1912 start = GetLineByPixel(clip.Left + viewport_x, false).line_no;
1913 end = GetLineByPixel(clip.Right + viewport_x, false).line_no;
1916 /// Make sure that we aren't drawing one more line then we need to
1917 line = GetLine (end - 1);
1918 if (line != null && clip.Bottom == line.Y + line.height + viewport_y)
1924 DateTime n = DateTime.Now;
1925 Console.WriteLine ("Started drawing: {0}s {1}ms", n.Second, n.Millisecond);
1926 Console.WriteLine ("CLIP: {0}", clip);
1927 Console.WriteLine ("S: {0}", GetLine (start).text);
1928 Console.WriteLine ("E: {0}", GetLine (end).text);
1931 disabled_brush = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorGrayText);
1932 hilight = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorHighlight);
1933 hilight_text = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorHighlightText);
1935 // Non multiline selection can be handled outside of the loop
1936 if (!multiline && selection_visible && owner.ShowSelection) {
1937 g.FillRectangle (hilight,
1938 selection_start.line.widths [selection_start.pos] +
1939 selection_start.line.X - viewport_x,
1940 selection_start.line.Y,
1941 (selection_end.line.X + selection_end.line.widths [selection_end.pos]) -
1942 (selection_start.line.X + selection_start.line.widths [selection_start.pos]),
1943 selection_start.line.height);
1946 while (line_no <= end) {
1947 line = GetLine (line_no);
1948 float line_y = line.Y - viewport_y;
1954 if (PasswordCache.Length < line.text.Length)
1955 PasswordCache.Append(Char.Parse(password_char), line.text.Length - PasswordCache.Length);
1956 else if (PasswordCache.Length > line.text.Length)
1957 PasswordCache.Remove(line.text.Length, PasswordCache.Length - line.text.Length);
1958 text = PasswordCache;
1961 int line_selection_start = text.Length + 1;
1962 int line_selection_end = text.Length + 1;
1963 if (selection_visible && owner.ShowSelection &&
1964 (line_no >= selection_start.line.line_no) &&
1965 (line_no <= selection_end.line.line_no)) {
1967 if (line_no == selection_start.line.line_no)
1968 line_selection_start = selection_start.pos + 1;
1970 line_selection_start = 1;
1972 if (line_no == selection_end.line.line_no)
1973 line_selection_end = selection_end.pos + 1;
1975 line_selection_end = text.Length + 1;
1977 if (line_selection_end == line_selection_start) {
1978 // There isn't really selection
1979 line_selection_start = text.Length + 1;
1980 line_selection_end = line_selection_start;
1981 } else if (multiline) {
1982 // lets draw some selection baby!! (non multiline selection is drawn outside the loop)
1983 g.FillRectangle (hilight,
1984 line.widths [line_selection_start - 1] + line.X - viewport_x,
1985 line_y, line.widths [line_selection_end - 1] - line.widths [line_selection_start - 1],
1990 current_brush = line.tags.color;
1991 while (tag != null) {
1994 if (tag.length == 0) {
1999 if (((tag.X + tag.width) < (clip.Left - viewport_x)) && (tag.X > (clip.Right - viewport_x))) {
2004 if (tag.back_color != null) {
2005 g.FillRectangle (tag.back_color, tag.X + line.X - viewport_x,
2006 line_y + tag.shift, tag.width, line.height);
2009 tag_brush = tag.color;
2010 current_brush = tag_brush;
2012 if (!owner.is_enabled) {
2013 Color a = ((SolidBrush) tag.color).Color;
2014 Color b = ThemeEngine.Current.ColorWindowText;
2016 if ((a.R == b.R) && (a.G == b.G) && (a.B == b.B)) {
2017 tag_brush = disabled_brush;
2021 int tag_pos = tag.start;
2022 current_brush = tag_brush;
2023 while (tag_pos < tag.start + tag.length) {
2024 int old_tag_pos = tag_pos;
2026 if (tag_pos >= line_selection_start && tag_pos < line_selection_end) {
2027 current_brush = hilight_text;
2028 tag_pos = Math.Min (tag.end, line_selection_end);
2029 } else if (tag_pos < line_selection_start) {
2030 current_brush = tag.color;
2031 tag_pos = Math.Min (tag.end, line_selection_start);
2033 current_brush = tag.color;
2037 tag.Draw (g, current_brush,
2038 line.widths [Math.Max (0, old_tag_pos - 1)] + line.X - viewport_x,
2040 old_tag_pos - 1, Math.Min (tag.length, tag_pos - old_tag_pos),
2049 private void InsertLineString (Line line, int pos, string s)
2051 bool carriage_return = false;
2053 if (s.EndsWith ("\r")) {
2054 s = s.Substring (0, s.Length - 1);
2055 carriage_return = true;
2058 InsertString (line, pos, s);
2060 if (carriage_return) {
2061 Line l = GetLine (line.line_no);
2062 l.carriage_return = true;
2066 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
2067 internal void Insert(Line line, int pos, bool update_caret, string s) {
2072 LineTag tag = LineTag.FindTag (line, pos);
2076 base_line = line.line_no;
2077 old_line_count = lines;
2079 break_index = s.IndexOf ('\n');
2081 // Bump the text at insertion point a line down if we're inserting more than one line
2082 if (break_index > -1) {
2084 line.soft_break = false;
2085 // Remainder of start line is now in base_line + 1
2088 if (break_index == -1)
2089 break_index = s.Length;
2091 InsertLineString (line, pos, s.Substring (0, break_index));
2094 while (break_index < s.Length) {
2096 int next_break = s.IndexOf ('\n', break_index);
2097 int adjusted_next_break;
2098 bool carriage_return = false;
2100 if (next_break == -1) {
2101 next_break = s.Length;
2105 adjusted_next_break = next_break;
2106 if (s [next_break - 1] == '\r') {
2107 adjusted_next_break--;
2108 carriage_return = true;
2111 string line_text = s.Substring (break_index, adjusted_next_break - break_index);
2112 Add (base_line + count, line_text, line.alignment, tag.font, tag.color);
2114 if (carriage_return) {
2115 Line last = GetLine (base_line + count);
2116 last.carriage_return = true;
2119 last.soft_break = true;
2121 Line last = GetLine (base_line + count);
2122 last.soft_break = true;
2126 break_index = next_break + 1;
2129 ResumeRecalc (true);
2131 UpdateView(line, lines - old_line_count + 1, pos);
2134 // Move caret to the end of the inserted text
2135 Line l = GetLine (line.line_no + lines - old_line_count);
2136 PositionCaret(l, l.text.Length);
2141 // Inserts a character at the given position
2142 internal void InsertString(Line line, int pos, string s) {
2143 InsertString(line.FindTag(pos), pos, s);
2146 // Inserts a string at the given position
2147 internal void InsertString(LineTag tag, int pos, string s) {
2156 line.text.Insert(pos, s);
2159 while (tag != null) {
2166 UpdateView(line, pos);
2169 // Inserts a string at the caret position
2170 internal void InsertStringAtCaret(string s, bool move_caret) {
2172 InsertString (caret.tag, caret.pos, s);
2174 UpdateView(caret.line, caret.pos);
2176 caret.pos += s.Length;
2183 // Inserts a character at the given position
2184 internal void InsertChar(Line line, int pos, char ch) {
2185 InsertChar(line.FindTag(pos), pos, ch);
2188 // Inserts a character at the given position
2189 internal void InsertChar(LineTag tag, int pos, char ch) {
2195 line.text.Insert(pos, ch);
2198 while (tag != null) {
2205 undo.RecordTyping (line, pos, ch);
2206 UpdateView(line, pos);
2209 // Inserts a character at the current caret position
2210 internal void InsertCharAtCaret(char ch, bool move_caret) {
2216 caret.line.text.Insert(caret.pos, ch);
2219 if (caret.tag.next != null) {
2220 tag = caret.tag.next;
2221 while (tag != null) {
2227 caret.line.recalc = true;
2229 InsertChar (caret.tag, caret.pos, ch);
2231 UpdateView(caret.line, caret.pos);
2235 SetSelectionToCaret(true);
2240 internal void InsertImage (Line line, int pos, Image image)
2248 // Just a place holder basically
2249 line.text.Insert (pos, "I");
2251 ImageTag image_tag = new ImageTag (line, pos + 1, image);
2253 tag = LineTag.FindTag (line, pos);
2254 image_tag.CopyFormattingFrom (tag);
2255 next_tag = tag.Break (pos + 1);
2256 image_tag.previous = tag;
2257 image_tag.next = tag.next;
2258 tag.next = image_tag;
2261 // Images tags need to be surrounded by text tags
2263 if (image_tag.next == null) {
2264 image_tag.next = new LineTag (line, pos + 1);
2265 image_tag.next.CopyFormattingFrom (tag);
2266 image_tag.next.previous = image_tag;
2269 tag = image_tag.next;
2270 while (tag != null) {
2278 UpdateView (line, pos);
2281 internal void DeleteMultiline (Line start_line, int pos, int length)
2283 Marker start = new Marker ();
2284 Marker end = new Marker ();
2285 int start_index = LineTagToCharIndex (start_line, pos);
2287 start.line = start_line;
2289 start.tag = LineTag.FindTag (start_line, pos);
2291 CharIndexToLineTag (start_index + length, out end.line,
2292 out end.tag, out end.pos);
2296 if (start.line == end.line) {
2297 DeleteChars (start.tag, pos, end.pos - pos);
2300 // Delete first and last lines
2301 DeleteChars (start.tag, start.pos, start.line.text.Length - start.pos);
2302 DeleteChars (end.line.tags, 0, end.pos);
2304 int current = start.line.line_no + 1;
2305 if (current < end.line.line_no) {
2306 for (int i = end.line.line_no - 1; i >= current; i--) {
2311 // BIG FAT WARNING - selection_end.line might be stale due
2312 // to the above Delete() call. DONT USE IT before hitting the end of this method!
2314 // Join start and end
2315 Combine (start.line.line_no, current);
2318 ResumeUpdate (true);
2322 // Deletes n characters at the given position; it will not delete past line limits
2324 internal void DeleteChars(LineTag tag, int pos, int count) {
2333 if (pos == line.text.Length) {
2337 line.text.Remove(pos, count);
2339 // Make sure the tag points to the right spot
2340 while ((tag != null) && (tag.end) < pos) {
2348 // Check if we're crossing tag boundaries
2349 if ((pos + count) > (tag.start + tag.length - 1)) {
2352 // We have to delete cross tag boundaries
2356 left -= tag.start + tag.length - pos - 1;
2359 while ((tag != null) && (left > 0)) {
2360 tag.start -= count - left;
2362 if (tag.length > left) {
2371 // We got off easy, same tag
2373 if (tag.length == 0) {
2378 // Delete empty orphaned tags at the end
2380 while (walk != null && walk.next != null && walk.next.length == 0) {
2382 walk.next = walk.next.next;
2383 if (walk.next != null)
2384 walk.next.previous = t;
2388 // Adjust the start point of any tags following
2391 while (tag != null) {
2399 line.Streamline(lines);
2402 UpdateView(line, pos);
2405 // Deletes a character at or after the given position (depending on forward); it will not delete past line limits
2406 internal void DeleteChar(LineTag tag, int pos, bool forward) {
2415 if ((pos == 0 && forward == false) || (pos == line.text.Length && forward == true)) {
2421 line.text.Remove(pos, 1);
2423 while ((tag != null) && (tag.start + tag.length - 1) <= pos) {
2433 if (tag.length == 0) {
2438 line.text.Remove(pos, 1);
2439 if (pos >= (tag.start - 1)) {
2441 if (tag.length == 0) {
2444 } else if (tag.previous != null) {
2445 // tag.previous.length--;
2446 if (tag.previous.length == 0) {
2452 // Delete empty orphaned tags at the end
2454 while (walk != null && walk.next != null && walk.next.length == 0) {
2456 walk.next = walk.next.next;
2457 if (walk.next != null)
2458 walk.next.previous = t;
2463 while (tag != null) {
2469 line.Streamline(lines);
2472 UpdateView(line, pos);
2475 // Combine two lines
2476 internal void Combine(int FirstLine, int SecondLine) {
2477 Combine(GetLine(FirstLine), GetLine(SecondLine));
2480 internal void Combine(Line first, Line second) {
2484 // Combine the two tag chains into one
2487 // Maintain the line ending style
2488 first.soft_break = second.soft_break;
2490 while (last.next != null) {
2494 // need to get the shift before setting the next tag since that effects length
2495 shift = last.start + last.length - 1;
2496 last.next = second.tags;
2497 last.next.previous = last;
2499 // Fix up references within the chain
2501 while (last != null) {
2503 last.start += shift;
2507 // Combine both lines' strings
2508 first.text.Insert(first.text.Length, second.text.ToString());
2509 first.Grow(first.text.Length);
2511 // Remove the reference to our (now combined) tags from the doomed line
2515 DecrementLines(first.line_no + 2); // first.line_no + 1 will be deleted, so we need to start renumbering one later
2518 first.recalc = true;
2519 first.height = 0; // This forces RecalcDocument/UpdateView to redraw from this line on
2520 first.Streamline(lines);
2522 // Update Caret, Selection, etc
2523 if (caret.line == second) {
2524 caret.Combine(first, shift);
2526 if (selection_anchor.line == second) {
2527 selection_anchor.Combine(first, shift);
2529 if (selection_start.line == second) {
2530 selection_start.Combine(first, shift);
2532 if (selection_end.line == second) {
2533 selection_end.Combine(first, shift);
2540 check_first = GetLine(first.line_no);
2541 check_second = GetLine(check_first.line_no + 1);
2543 Console.WriteLine("Pre-delete: Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2546 this.Delete(second);
2549 check_first = GetLine(first.line_no);
2550 check_second = GetLine(check_first.line_no + 1);
2552 Console.WriteLine("Post-delete Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2556 // Split the line at the position into two
2557 internal void Split(int LineNo, int pos) {
2561 line = GetLine(LineNo);
2562 tag = LineTag.FindTag(line, pos);
2563 Split(line, tag, pos, false);
2566 internal void Split(Line line, int pos) {
2569 tag = LineTag.FindTag(line, pos);
2570 Split(line, tag, pos, false);
2573 ///<summary>Split line at given tag and position into two lines</summary>
2574 ///<param name="soft">True if the split should be marked as 'soft', indicating that it can be contracted
2575 ///if more space becomes available on previous line</param>
2576 internal void Split(Line line, LineTag tag, int pos, bool soft) {
2580 bool move_sel_start;
2584 move_sel_start = false;
2585 move_sel_end = false;
2587 // Adjust selection and cursors
2588 if (caret.line == line && caret.pos >= pos) {
2591 if (selection_start.line == line && selection_start.pos > pos) {
2592 move_sel_start = true;
2595 if (selection_end.line == line && selection_end.pos > pos) {
2596 move_sel_end = true;
2599 // cover the easy case first
2600 if (pos == line.text.Length) {
2601 Add(line.line_no + 1, "", line.alignment, tag.font, tag.color);
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 caret.line = new_line;
2611 caret.tag = new_line.tags;
2615 if (move_sel_start) {
2616 selection_start.line = new_line;
2617 selection_start.pos = 0;
2618 selection_start.tag = new_line.tags;
2622 selection_end.line = new_line;
2623 selection_end.pos = 0;
2624 selection_end.tag = new_line.tags;
2629 // We need to move the rest of the text into the new line
2630 Add (line.line_no + 1, line.text.ToString (pos, line.text.Length - pos), line.alignment, tag.font, tag.color);
2632 // Now transfer our tags from this line to the next
2633 new_line = GetLine(line.line_no + 1);
2635 line.carriage_return = false;
2636 new_line.carriage_return = line.carriage_return;
2637 new_line.soft_break = soft;
2640 new_line.recalc = true;
2642 if ((tag.start - 1) == pos) {
2645 // We can simply break the chain and move the tag into the next line
2646 if (tag == line.tags) {
2647 new_tag = new LineTag(line, 1);
2648 new_tag.CopyFormattingFrom (tag);
2649 line.tags = new_tag;
2652 if (tag.previous != null) {
2653 tag.previous.next = null;
2655 new_line.tags = tag;
2656 tag.previous = null;
2657 tag.line = new_line;
2659 // Walk the list and correct the start location of the tags we just bumped into the next line
2660 shift = tag.start - 1;
2663 while (new_tag != null) {
2664 new_tag.start -= shift;
2665 new_tag.line = new_line;
2666 new_tag = new_tag.next;
2671 new_tag = new LineTag (new_line, 1);
2672 new_tag.next = tag.next;
2673 new_tag.CopyFormattingFrom (tag);
2674 new_line.tags = new_tag;
2675 if (new_tag.next != null) {
2676 new_tag.next.previous = new_tag;
2681 new_tag = new_tag.next;
2682 while (new_tag != null) {
2683 new_tag.start -= shift;
2684 new_tag.line = new_line;
2685 new_tag = new_tag.next;
2691 caret.line = new_line;
2692 caret.pos = caret.pos - pos;
2693 caret.tag = caret.line.FindTag(caret.pos);
2696 if (move_sel_start) {
2697 selection_start.line = new_line;
2698 selection_start.pos = selection_start.pos - pos;
2699 selection_start.tag = new_line.FindTag(selection_start.pos);
2703 selection_end.line = new_line;
2704 selection_end.pos = selection_end.pos - pos;
2705 selection_end.tag = new_line.FindTag(selection_end.pos);
2708 CharCount -= line.text.Length - pos;
2709 line.text.Remove(pos, line.text.Length - pos);
2712 // Adds a line of text, with given font.
2713 // Bumps any line at that line number that already exists down
2714 internal void Add(int LineNo, string Text, Font font, SolidBrush color) {
2715 Add(LineNo, Text, HorizontalAlignment.Left, font, color);
2718 internal void Add(int LineNo, string Text, HorizontalAlignment align, Font font, SolidBrush color) {
2723 CharCount += Text.Length;
2725 if (LineNo<1 || Text == null) {
2727 throw new ArgumentNullException("LineNo", "Line numbers must be positive");
2729 throw new ArgumentNullException("Text", "Cannot insert NULL line");
2733 add = new Line (this, LineNo, Text, align, font, color);
2736 while (line != sentinel) {
2738 line_no = line.line_no;
2740 if (LineNo > line_no) {
2742 } else if (LineNo < line_no) {
2745 // Bump existing line numbers; walk all nodes to the right of this one and increment line_no
2746 IncrementLines(line.line_no);
2751 add.left = sentinel;
2752 add.right = sentinel;
2754 if (add.parent != null) {
2755 if (LineNo > add.parent.line_no) {
2756 add.parent.right = add;
2758 add.parent.left = add;
2765 RebalanceAfterAdd(add);
2770 internal virtual void Clear() {
2773 document = sentinel;
2776 public virtual object Clone() {
2779 clone = new Document(null);
2781 clone.lines = this.lines;
2782 clone.document = (Line)document.Clone();
2787 internal void Delete(int LineNo) {
2794 line = GetLine(LineNo);
2796 CharCount -= line.text.Length;
2798 DecrementLines(LineNo + 1);
2802 internal void Delete(Line line1) {
2803 Line line2;// = new Line();
2806 if ((line1.left == sentinel) || (line1.right == sentinel)) {
2809 line3 = line1.right;
2810 while (line3.left != sentinel) {
2815 if (line3.left != sentinel) {
2818 line2 = line3.right;
2821 line2.parent = line3.parent;
2822 if (line3.parent != null) {
2823 if(line3 == line3.parent.left) {
2824 line3.parent.left = line2;
2826 line3.parent.right = line2;
2832 if (line3 != line1) {
2835 if (selection_start.line == line3) {
2836 selection_start.line = line1;
2839 if (selection_end.line == line3) {
2840 selection_end.line = line1;
2843 if (selection_anchor.line == line3) {
2844 selection_anchor.line = line1;
2847 if (caret.line == line3) {
2852 line1.alignment = line3.alignment;
2853 line1.ascent = line3.ascent;
2854 line1.hanging_indent = line3.hanging_indent;
2855 line1.height = line3.height;
2856 line1.indent = line3.indent;
2857 line1.line_no = line3.line_no;
2858 line1.recalc = line3.recalc;
2859 line1.right_indent = line3.right_indent;
2860 line1.soft_break = line3.soft_break;
2861 line1.space = line3.space;
2862 line1.tags = line3.tags;
2863 line1.text = line3.text;
2864 line1.widths = line3.widths;
2865 line1.offset = line3.offset;
2868 while (tag != null) {
2874 if (line3.color == LineColor.Black)
2875 RebalanceAfterDelete(line2);
2880 // Invalidate a section of the document to trigger redraw
2881 internal void Invalidate(Line start, int start_pos, Line end, int end_pos) {
2887 if ((start == end) && (start_pos == end_pos)) {
2891 if (end_pos == -1) {
2892 end_pos = end.text.Length;
2895 // figure out what's before what so the logic below is straightforward
2896 if (start.line_no < end.line_no) {
2902 } else if (start.line_no > end.line_no) {
2909 if (start_pos < end_pos) {
2923 int endpoint = (int) l1.widths [p2];
2924 if (p2 == l1.text.Length + 1) {
2925 endpoint = (int) viewport_width;
2929 Console.WriteLine("Invaliding backwards from {0}:{1} to {2}:{3} {4}",
2930 l1.line_no, p1, l2.line_no, p2,
2932 (int)l1.widths[p1] + l1.X - viewport_x,
2940 owner.Invalidate(new Rectangle (
2941 (int)l1.widths[p1] + l1.X - viewport_x,
2943 endpoint - (int)l1.widths[p1] + 1,
2949 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);
2950 Console.WriteLine ("invalidate start line: {0} position: {1}", l1.text, p1);
2953 // Three invalidates:
2954 // First line from start
2955 owner.Invalidate(new Rectangle((int)l1.widths[p1] + l1.X - viewport_x, l1.Y - viewport_y, viewport_width, l1.height));
2959 if ((l1.line_no + 1) < l2.line_no) {
2962 y = GetLine(l1.line_no + 1).Y;
2963 owner.Invalidate(new Rectangle(0, y - viewport_y, viewport_width, l2.Y - y));
2966 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);
2972 owner.Invalidate(new Rectangle((int)l2.widths[0] + l2.X - viewport_x, l2.Y - viewport_y, (int)l2.widths[p2] + 1, l2.height));
2974 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);
2979 /// <summary>Select text around caret</summary>
2980 internal void ExpandSelection(CaretSelection mode, bool to_caret) {
2982 // We're expanding the selection to the caret position
2984 case CaretSelection.Line: {
2985 // Invalidate the selection delta
2986 if (caret > selection_prev) {
2987 Invalidate(selection_prev.line, 0, caret.line, caret.line.text.Length);
2989 Invalidate(selection_prev.line, selection_prev.line.text.Length, caret.line, 0);
2992 if (caret.line.line_no <= selection_anchor.line.line_no) {
2993 selection_start.line = caret.line;
2994 selection_start.tag = caret.line.tags;
2995 selection_start.pos = 0;
2997 selection_end.line = selection_anchor.line;
2998 selection_end.tag = selection_anchor.tag;
2999 selection_end.pos = selection_anchor.pos;
3001 selection_end_anchor = true;
3003 selection_start.line = selection_anchor.line;
3004 selection_start.pos = selection_anchor.height;
3005 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height);
3007 selection_end.line = caret.line;
3008 selection_end.tag = caret.line.tags;
3009 selection_end.pos = caret.line.text.Length;
3011 selection_end_anchor = false;
3013 selection_prev.line = caret.line;
3014 selection_prev.tag = caret.tag;
3015 selection_prev.pos = caret.pos;
3020 case CaretSelection.Word: {
3024 start_pos = FindWordSeparator(caret.line, caret.pos, false);
3025 end_pos = FindWordSeparator(caret.line, caret.pos, true);
3028 // Invalidate the selection delta
3029 if (caret > selection_prev) {
3030 Invalidate(selection_prev.line, selection_prev.pos, caret.line, end_pos);
3032 Invalidate(selection_prev.line, selection_prev.pos, caret.line, start_pos);
3034 if (caret < selection_anchor) {
3035 selection_start.line = caret.line;
3036 selection_start.tag = caret.line.FindTag(start_pos);
3037 selection_start.pos = start_pos;
3039 selection_end.line = selection_anchor.line;
3040 selection_end.tag = selection_anchor.tag;
3041 selection_end.pos = selection_anchor.pos;
3043 selection_prev.line = caret.line;
3044 selection_prev.tag = caret.tag;
3045 selection_prev.pos = start_pos;
3047 selection_end_anchor = true;
3049 selection_start.line = selection_anchor.line;
3050 selection_start.pos = selection_anchor.height;
3051 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height);
3053 selection_end.line = caret.line;
3054 selection_end.tag = caret.line.FindTag(end_pos);
3055 selection_end.pos = end_pos;
3057 selection_prev.line = caret.line;
3058 selection_prev.tag = caret.tag;
3059 selection_prev.pos = end_pos;
3061 selection_end_anchor = false;
3066 case CaretSelection.Position: {
3067 SetSelectionToCaret(false);
3072 // We're setting the selection 'around' the caret position
3074 case CaretSelection.Line: {
3075 this.Invalidate(caret.line, 0, caret.line, caret.line.text.Length);
3077 selection_start.line = caret.line;
3078 selection_start.tag = caret.line.tags;
3079 selection_start.pos = 0;
3081 selection_end.line = caret.line;
3082 selection_end.pos = caret.line.text.Length;
3083 selection_end.tag = caret.line.FindTag(selection_end.pos);
3085 selection_anchor.line = selection_end.line;
3086 selection_anchor.tag = selection_end.tag;
3087 selection_anchor.pos = selection_end.pos;
3088 selection_anchor.height = 0;
3090 selection_prev.line = caret.line;
3091 selection_prev.tag = caret.tag;
3092 selection_prev.pos = caret.pos;
3094 this.selection_end_anchor = true;
3099 case CaretSelection.Word: {
3103 start_pos = FindWordSeparator(caret.line, caret.pos, false);
3104 end_pos = FindWordSeparator(caret.line, caret.pos, true);
3106 this.Invalidate(selection_start.line, start_pos, caret.line, end_pos);
3108 selection_start.line = caret.line;
3109 selection_start.tag = caret.line.FindTag(start_pos);
3110 selection_start.pos = start_pos;
3112 selection_end.line = caret.line;
3113 selection_end.tag = caret.line.FindTag(end_pos);
3114 selection_end.pos = end_pos;
3116 selection_anchor.line = selection_end.line;
3117 selection_anchor.tag = selection_end.tag;
3118 selection_anchor.pos = selection_end.pos;
3119 selection_anchor.height = start_pos;
3121 selection_prev.line = caret.line;
3122 selection_prev.tag = caret.tag;
3123 selection_prev.pos = caret.pos;
3125 this.selection_end_anchor = true;
3132 SetSelectionVisible (!(selection_start == selection_end));
3135 internal void SetSelectionToCaret(bool start) {
3137 // Invalidate old selection; selection is being reset to empty
3138 this.Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3140 selection_start.line = caret.line;
3141 selection_start.tag = caret.tag;
3142 selection_start.pos = caret.pos;
3144 // start always also selects end
3145 selection_end.line = caret.line;
3146 selection_end.tag = caret.tag;
3147 selection_end.pos = caret.pos;
3149 selection_anchor.line = caret.line;
3150 selection_anchor.tag = caret.tag;
3151 selection_anchor.pos = caret.pos;
3153 // Invalidate from previous end to caret (aka new end)
3154 if (selection_end_anchor) {
3155 if (selection_start != caret) {
3156 this.Invalidate(selection_start.line, selection_start.pos, caret.line, caret.pos);
3159 if (selection_end != caret) {
3160 this.Invalidate(selection_end.line, selection_end.pos, caret.line, caret.pos);
3164 if (caret < selection_anchor) {
3165 selection_start.line = caret.line;
3166 selection_start.tag = caret.tag;
3167 selection_start.pos = caret.pos;
3169 selection_end.line = selection_anchor.line;
3170 selection_end.tag = selection_anchor.tag;
3171 selection_end.pos = selection_anchor.pos;
3173 selection_end_anchor = true;
3175 selection_start.line = selection_anchor.line;
3176 selection_start.tag = selection_anchor.tag;
3177 selection_start.pos = selection_anchor.pos;
3179 selection_end.line = caret.line;
3180 selection_end.tag = caret.tag;
3181 selection_end.pos = caret.pos;
3183 selection_end_anchor = false;
3187 SetSelectionVisible (!(selection_start == selection_end));
3190 internal void SetSelection(Line start, int start_pos, Line end, int end_pos) {
3191 if (selection_visible) {
3192 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3195 if ((end.line_no < start.line_no) || ((end == start) && (end_pos <= start_pos))) {
3196 selection_start.line = end;
3197 selection_start.tag = LineTag.FindTag(end, end_pos);
3198 selection_start.pos = end_pos;
3200 selection_end.line = start;
3201 selection_end.tag = LineTag.FindTag(start, start_pos);
3202 selection_end.pos = start_pos;
3204 selection_end_anchor = true;
3206 selection_start.line = start;
3207 selection_start.tag = LineTag.FindTag(start, start_pos);
3208 selection_start.pos = start_pos;
3210 selection_end.line = end;
3211 selection_end.tag = LineTag.FindTag(end, end_pos);
3212 selection_end.pos = end_pos;
3214 selection_end_anchor = false;
3217 selection_anchor.line = start;
3218 selection_anchor.tag = selection_start.tag;
3219 selection_anchor.pos = start_pos;
3221 if (((start == end) && (start_pos == end_pos)) || start == null || end == null) {
3222 SetSelectionVisible (false);
3224 SetSelectionVisible (true);
3225 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3229 internal void SetSelectionStart(Line start, int start_pos) {
3230 // Invalidate from the previous to the new start pos
3231 Invalidate(selection_start.line, selection_start.pos, start, start_pos);
3233 selection_start.line = start;
3234 selection_start.pos = start_pos;
3235 selection_start.tag = LineTag.FindTag(start, start_pos);
3237 selection_anchor.line = start;
3238 selection_anchor.pos = start_pos;
3239 selection_anchor.tag = selection_start.tag;
3241 selection_end_anchor = false;
3244 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
3245 SetSelectionVisible (true);
3247 SetSelectionVisible (false);
3250 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3253 internal void SetSelectionStart(int character_index) {
3258 if (character_index < 0) {
3262 CharIndexToLineTag(character_index, out line, out tag, out pos);
3263 SetSelectionStart(line, pos);
3266 internal void SetSelectionEnd(Line end, int end_pos) {
3268 if (end == selection_end.line && end_pos == selection_start.pos) {
3269 selection_anchor.line = selection_start.line;
3270 selection_anchor.tag = selection_start.tag;
3271 selection_anchor.pos = selection_start.pos;
3273 selection_end.line = selection_start.line;
3274 selection_end.tag = selection_start.tag;
3275 selection_end.pos = selection_start.pos;
3277 selection_end_anchor = false;
3278 } else if ((end.line_no < selection_anchor.line.line_no) || ((end == selection_anchor.line) && (end_pos <= selection_anchor.pos))) {
3279 selection_start.line = end;
3280 selection_start.tag = LineTag.FindTag(end, end_pos);
3281 selection_start.pos = end_pos;
3283 selection_end.line = selection_anchor.line;
3284 selection_end.tag = selection_anchor.tag;
3285 selection_end.pos = selection_anchor.pos;
3287 selection_end_anchor = true;
3289 selection_start.line = selection_anchor.line;
3290 selection_start.tag = selection_anchor.tag;
3291 selection_start.pos = selection_anchor.pos;
3293 selection_end.line = end;
3294 selection_end.tag = LineTag.FindTag(end, end_pos);
3295 selection_end.pos = end_pos;
3297 selection_end_anchor = false;
3300 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
3301 SetSelectionVisible (true);
3302 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3304 SetSelectionVisible (false);
3305 // ?? Do I need to invalidate here, tests seem to work without it, but I don't think they should :-s
3309 internal void SetSelectionEnd(int character_index) {
3314 if (character_index < 0) {
3318 CharIndexToLineTag(character_index, out line, out tag, out pos);
3319 SetSelectionEnd(line, pos);
3322 internal void SetSelection(Line start, int start_pos) {
3323 if (selection_visible) {
3324 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3327 selection_start.line = start;
3328 selection_start.pos = start_pos;
3329 selection_start.tag = LineTag.FindTag(start, start_pos);
3331 selection_end.line = start;
3332 selection_end.tag = selection_start.tag;
3333 selection_end.pos = start_pos;
3335 selection_anchor.line = start;
3336 selection_anchor.tag = selection_start.tag;
3337 selection_anchor.pos = start_pos;
3339 selection_end_anchor = false;
3340 SetSelectionVisible (false);
3343 internal void InvalidateSelectionArea() {
3344 Invalidate (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3347 // Return the current selection, as string
3348 internal string GetSelection() {
3349 // We return String.Empty if there is no selection
3350 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3351 return string.Empty;
3354 if (selection_start.line == selection_end.line) {
3355 return selection_start.line.text.ToString (selection_start.pos, selection_end.pos - selection_start.pos);
3362 sb = new StringBuilder();
3363 start = selection_start.line.line_no;
3364 end = selection_end.line.line_no;
3366 sb.Append(selection_start.line.text.ToString(selection_start.pos, selection_start.line.text.Length - selection_start.pos) + Environment.NewLine);
3368 if ((start + 1) < end) {
3369 for (i = start + 1; i < end; i++) {
3370 sb.Append(GetLine(i).text.ToString() + Environment.NewLine);
3374 sb.Append(selection_end.line.text.ToString(0, selection_end.pos));
3376 return sb.ToString();
3380 internal void ReplaceSelection(string s, bool select_new) {
3383 int selection_pos_on_line = selection_start.pos;
3384 int selection_start_pos = LineTagToCharIndex (selection_start.line, selection_start.pos);
3387 // First, delete any selected text
3388 if ((selection_start.pos != selection_end.pos) || (selection_start.line != selection_end.line)) {
3389 if (selection_start.line == selection_end.line) {
3390 undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3392 DeleteChars(selection_start.tag, selection_start.pos, selection_end.pos - selection_start.pos);
3394 // The tag might have been removed, we need to recalc it
3395 selection_start.tag = selection_start.line.FindTag(selection_start.pos);
3400 start = selection_start.line.line_no;
3401 end = selection_end.line.line_no;
3403 undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3405 // Delete first line
3406 DeleteChars(selection_start.tag, selection_start.pos, selection_start.line.text.Length - selection_start.pos);
3409 DeleteChars(selection_end.line.tags, 0, selection_end.pos);
3413 for (i = end - 1; i >= start; i--) {
3418 // BIG FAT WARNING - selection_end.line might be stale due
3419 // to the above Delete() call. DONT USE IT before hitting the end of this method!
3421 // Join start and end
3422 Combine(selection_start.line.line_no, start);
3427 Insert(selection_start.line, selection_start.pos, false, s);
3428 undo.RecordInsertString (selection_start.line, selection_start.pos, s);
3429 ResumeRecalc (false);
3432 CharIndexToLineTag(selection_start_pos + s.Length, out selection_start.line,
3433 out selection_start.tag, out selection_start.pos);
3435 selection_end.line = selection_start.line;
3436 selection_end.pos = selection_start.pos;
3437 selection_end.tag = selection_start.tag;
3438 selection_anchor.line = selection_start.line;
3439 selection_anchor.pos = selection_start.pos;
3440 selection_anchor.tag = selection_start.tag;
3442 SetSelectionVisible (false);
3444 CharIndexToLineTag(selection_start_pos, out selection_start.line,
3445 out selection_start.tag, out selection_start.pos);
3447 CharIndexToLineTag(selection_start_pos + s.Length, out selection_end.line,
3448 out selection_end.tag, out selection_end.pos);
3450 selection_anchor.line = selection_start.line;
3451 selection_anchor.pos = selection_start.pos;
3452 selection_anchor.tag = selection_start.tag;
3454 SetSelectionVisible (true);
3457 PositionCaret (selection_start.line, selection_start.pos);
3458 UpdateView (selection_start.line, selection_pos_on_line);
3461 internal void CharIndexToLineTag(int index, out Line line_out, out LineTag tag_out, out int pos) {
3470 for (i = 1; i <= lines; i++) {
3474 chars += line.text.Length + (line.soft_break ? 0 : crlf_size);
3476 if (index <= chars) {
3477 // we found the line
3480 while (tag != null) {
3481 if (index < (start + tag.start + tag.length)) {
3483 tag_out = LineTag.GetFinalTag (tag);
3484 pos = index - start;
3487 if (tag.next == null) {
3490 next_line = GetLine(line.line_no + 1);
3492 if (next_line != null) {
3493 line_out = next_line;
3494 tag_out = LineTag.GetFinalTag (next_line.tags);
3499 tag_out = LineTag.GetFinalTag (tag);
3500 pos = line_out.text.Length;
3509 line_out = GetLine(lines);
3510 tag = line_out.tags;
3511 while (tag.next != null) {
3515 pos = line_out.text.Length;
3518 internal int LineTagToCharIndex(Line line, int pos) {
3522 // Count first and last line
3525 // Count the lines in the middle
3527 for (i = 1; i < line.line_no; i++) {
3528 length += GetLine(i).text.Length + (line.soft_break ? 0 : crlf_size);
3536 internal int SelectionLength() {
3537 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3541 if (selection_start.line == selection_end.line) {
3542 return selection_end.pos - selection_start.pos;
3549 // Count first and last line
3550 length = selection_start.line.text.Length - selection_start.pos + selection_end.pos + crlf_size;
3552 // Count the lines in the middle
3553 start = selection_start.line.line_no + 1;
3554 end = selection_end.line.line_no;
3557 for (i = start; i < end; i++) {
3558 Line line = GetLine (i);
3559 length += line.text.Length + (line.soft_break ? 0 : crlf_size);
3570 /// <summary>Give it a Line number and it returns the Line object at with that line number</summary>
3571 internal Line GetLine(int LineNo) {
3572 Line line = document;
3574 while (line != sentinel) {
3575 if (LineNo == line.line_no) {
3577 } else if (LineNo < line.line_no) {
3587 /// <summary>Retrieve the previous tag; walks line boundaries</summary>
3588 internal LineTag PreviousTag(LineTag tag) {
3591 if (tag.previous != null) {
3592 return tag.previous;
3596 if (tag.line.line_no == 1) {
3600 l = GetLine(tag.line.line_no - 1);
3605 while (t.next != null) {
3614 /// <summary>Retrieve the next tag; walks line boundaries</summary>
3615 internal LineTag NextTag(LineTag tag) {
3618 if (tag.next != null) {
3623 l = GetLine(tag.line.line_no + 1);
3631 internal Line ParagraphStart(Line line) {
3632 while (line.soft_break) {
3633 line = GetLine(line.line_no - 1);
3638 internal Line ParagraphEnd(Line line) {
3641 while (line.soft_break) {
3642 l = GetLine(line.line_no + 1);
3643 if ((l == null) || (!l.soft_break)) {
3651 /// <summary>Give it a pixel offset coordinate and it returns the Line covering that are (offset
3652 /// is either X or Y depending on if we are multiline
3654 internal Line GetLineByPixel (int offset, bool exact)
3656 Line line = document;
3660 while (line != sentinel) {
3662 if ((offset >= line.Y) && (offset < (line.Y+line.height))) {
3664 } else if (offset < line.Y) {
3671 while (line != sentinel) {
3673 if ((offset >= line.X) && (offset < (line.X + line.Width)))
3675 else if (offset < line.X)
3688 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3689 internal LineTag FindTag(int x, int y, out int index, bool exact) {
3693 line = GetLineByPixel(y, exact);
3700 // Alignment adjustment
3704 if (x >= tag.X && x < (tag.X+tag.width)) {
3707 end = tag.start + tag.length - 1;
3709 for (int pos = tag.start; pos < end; pos++) {
3710 if (x < line.widths[pos]) {
3712 return LineTag.GetFinalTag (tag);
3716 return LineTag.GetFinalTag (tag);
3718 if (tag.next != null) {
3726 index = line.text.Length;
3727 return LineTag.GetFinalTag (tag);
3732 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3733 internal LineTag FindCursor(int x, int y, out int index) {
3737 line = GetLineByPixel(multiline ? y : x, false);
3741 if (x >= tag.X && x < (tag.X+tag.width)) {
3746 for (int pos = tag.start - 1; pos < end; pos++) {
3747 // When clicking on a character, we position the cursor to whatever edge
3748 // of the character the click was closer
3749 if (x < (line.X + line.widths[pos] + ((line.widths[pos+1]-line.widths[pos])/2))) {
3751 return LineTag.GetFinalTag (tag);
3755 return LineTag.GetFinalTag (tag);
3757 if (tag.next != null) {
3760 index = line.text.Length;
3761 return LineTag.GetFinalTag (tag);
3766 /// <summary>Format area of document in specified font and color</summary>
3767 /// <param name="start_pos">1-based start position on start_line</param>
3768 /// <param name="end_pos">1-based end position on end_line </param>
3769 internal void FormatText (Line start_line, int start_pos, Line end_line, int end_pos, Font font,
3770 SolidBrush color, SolidBrush back_color, FormatSpecified specified)
3774 // First, format the first line
3775 if (start_line != end_line) {
3777 LineTag.FormatText(start_line, start_pos, start_line.text.Length - start_pos + 1, font, color, back_color, specified);
3780 LineTag.FormatText(end_line, 1, end_pos, font, color, back_color, specified);
3782 // Now all the lines inbetween
3783 for (int i = start_line.line_no + 1; i < end_line.line_no; i++) {
3785 LineTag.FormatText(l, 1, l.text.Length, font, color, back_color, specified);
3788 // Special case, single line
3789 LineTag.FormatText(start_line, start_pos, end_pos - start_pos, font, color, back_color, specified);
3793 /// <summary>Re-format areas of the document in specified font and color</summary>
3794 /// <param name="start_pos">1-based start position on start_line</param>
3795 /// <param name="end_pos">1-based end position on end_line </param>
3796 /// <param name="font">Font specifying attributes</param>
3797 /// <param name="color">Color (or NULL) to apply</param>
3798 /// <param name="apply">Attributes from font and color to apply</param>
3799 internal void FormatText(Line start_line, int start_pos, Line end_line, int end_pos, FontDefinition attributes) {
3802 // First, format the first line
3803 if (start_line != end_line) {
3805 LineTag.FormatText(start_line, start_pos, start_line.text.Length - start_pos + 1, attributes);
3808 LineTag.FormatText(end_line, 1, end_pos - 1, attributes);
3810 // Now all the lines inbetween
3811 for (int i = start_line.line_no + 1; i < end_line.line_no; i++) {
3813 LineTag.FormatText(l, 1, l.text.Length, attributes);
3816 // Special case, single line
3817 LineTag.FormatText(start_line, start_pos, end_pos - start_pos, attributes);
3821 internal void RecalculateAlignments ()
3828 while (line_no <= lines) {
3829 line = GetLine(line_no);
3832 switch (line.alignment) {
3833 case HorizontalAlignment.Left:
3834 line.align_shift = 0;
3836 case HorizontalAlignment.Center:
3837 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3839 case HorizontalAlignment.Right:
3840 line.align_shift = viewport_width - (int)line.widths[line.text.Length];
3850 /// <summary>Calculate formatting for the whole document</summary>
3851 internal bool RecalculateDocument(Graphics g) {
3852 return RecalculateDocument(g, 1, this.lines, false);
3855 /// <summary>Calculate formatting starting at a certain line</summary>
3856 internal bool RecalculateDocument(Graphics g, int start) {
3857 return RecalculateDocument(g, start, this.lines, false);
3860 /// <summary>Calculate formatting within two given line numbers</summary>
3861 internal bool RecalculateDocument(Graphics g, int start, int end) {
3862 return RecalculateDocument(g, start, end, false);
3865 /// <summary>With optimize on, returns true if line heights changed</summary>
3866 internal bool RecalculateDocument(Graphics g, int start, int end, bool optimize) {
3874 if (recalc_suspended > 0) {
3875 recalc_pending = true;
3876 recalc_start = Math.Min (recalc_start, start);
3877 recalc_end = Math.Max (recalc_end, end);
3878 recalc_optimize = optimize;
3882 // Fixup the positions, they can go kinda nuts
3883 start = Math.Max (start, 1);
3884 end = Math.Min (end, lines);
3886 offset = GetLine(start).offset;
3891 changed = true; // We always return true if we run non-optimized
3896 while (line_no <= (end + this.lines - shift)) {
3897 line = GetLine(line_no++);
3898 line.offset = offset;
3902 line.RecalculateLine(g, this);
3904 if (line.recalc && line.RecalculateLine(g, this)) {
3906 // If the height changed, all subsequent lines change
3913 line.RecalculatePasswordLine(g, this);
3915 if (line.recalc && line.RecalculatePasswordLine(g, this)) {
3917 // If the height changed, all subsequent lines change
3924 if (line.widths[line.text.Length] > new_width) {
3925 new_width = (int)line.widths[line.text.Length];
3928 // Calculate alignment
3929 if (line.alignment != HorizontalAlignment.Left) {
3930 if (line.alignment == HorizontalAlignment.Center) {
3931 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3933 line.align_shift = viewport_width - (int)line.widths[line.text.Length] - 1;
3938 offset += line.height;
3940 offset += (int) line.widths [line.text.Length] + 2;
3942 if (line_no > lines) {
3947 if (document_x != new_width) {
3948 document_x = new_width;
3949 if (WidthChanged != null) {
3950 WidthChanged(this, null);
3954 RecalculateAlignments();
3956 line = GetLine(lines);
3958 if (document_y != line.Y + line.height) {
3959 document_y = line.Y + line.height;
3960 if (HeightChanged != null) {
3961 HeightChanged(this, null);
3968 internal int Size() {
3972 private void owner_HandleCreated(object sender, EventArgs e) {
3973 RecalculateDocument(owner.CreateGraphicsInternal());
3977 private void owner_VisibleChanged(object sender, EventArgs e) {
3978 if (owner.Visible) {
3979 RecalculateDocument(owner.CreateGraphicsInternal());
3983 internal static bool IsWordSeparator(char ch) {
3997 internal int FindWordSeparator(Line line, int pos, bool forward) {
4000 len = line.text.Length;
4003 for (int i = pos + 1; i < len; i++) {
4004 if (IsWordSeparator(line.Text[i])) {
4010 for (int i = pos - 1; i > 0; i--) {
4011 if (IsWordSeparator(line.Text[i - 1])) {
4019 /* Search document for text */
4020 internal bool FindChars(char[] chars, Marker start, Marker end, out Marker result) {
4026 // Search for occurence of any char in the chars array
4027 result = new Marker();
4030 line_no = start.line.line_no;
4032 while (line_no <= end.line.line_no) {
4033 line_len = line.text.Length;
4034 while (pos < line_len) {
4035 for (int i = 0; i < chars.Length; i++) {
4036 if (line.text[pos] == chars[i]) {
4038 if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
4052 line = GetLine(line_no);
4058 // This version does not build one big string for searching, instead it handles
4059 // line-boundaries, which is faster and less memory intensive
4060 // FIXME - Depending on culture stuff we might have to create a big string and use culturespecific
4061 // search stuff and change it to accept and return positions instead of Markers (which would match
4062 // RichTextBox behaviour better but would be inconsistent with the rest of TextControl)
4063 internal bool Find(string search, Marker start, Marker end, out Marker result, RichTextBoxFinds options) {
4065 string search_string;
4077 result = new Marker();
4078 word_option = ((options & RichTextBoxFinds.WholeWord) != 0);
4079 ignore_case = ((options & RichTextBoxFinds.MatchCase) == 0);
4080 reverse = ((options & RichTextBoxFinds.Reverse) != 0);
4083 line_no = start.line.line_no;
4087 // Prep our search string, lowercasing it if we do case-independent matching
4090 sb = new StringBuilder(search);
4091 for (int i = 0; i < sb.Length; i++) {
4092 sb[i] = Char.ToLower(sb[i]);
4094 search_string = sb.ToString();
4096 search_string = search;
4099 // We need to check if the character before our start position is a wordbreak
4102 if ((pos == 0) || (IsWordSeparator(line.text[pos - 1]))) {
4109 if (IsWordSeparator(line.text[pos - 1])) {
4115 // Need to check the end of the previous line
4118 prev_line = GetLine(line_no - 1);
4119 if (prev_line.soft_break) {
4120 if (IsWordSeparator(prev_line.text[prev_line.text.Length - 1])) {
4134 // To avoid duplication of this loop with reverse logic, we search
4135 // through the document, remembering the last match and when returning
4136 // report that last remembered match
4138 last = new Marker();
4139 last.height = -1; // Abused - we use it to track change
4141 while (line_no <= end.line.line_no) {
4142 if (line_no != end.line.line_no) {
4143 line_len = line.text.Length;
4148 while (pos < line_len) {
4149 if (word_option && (current == search_string.Length)) {
4150 if (IsWordSeparator(line.text[pos])) {
4163 c = Char.ToLower(line.text[pos]);
4168 if (c == search_string[current]) {
4173 if (!word_option || (word_option && (word || (current > 0)))) {
4177 if (!word_option && (current == search_string.Length)) {
4194 if (IsWordSeparator(c)) {
4202 // Mark that we just saw a word boundary
4203 if (!line.soft_break) {
4207 if (current == search_string.Length) {
4223 line = GetLine(line_no);
4227 if (last.height != -1) {
4237 // if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
4249 internal void GetMarker(out Marker mark, bool start) {
4250 mark = new Marker();
4253 mark.line = GetLine(1);
4254 mark.tag = mark.line.tags;
4257 mark.line = GetLine(lines);
4258 mark.tag = mark.line.tags;
4259 while (mark.tag.next != null) {
4260 mark.tag = mark.tag.next;
4262 mark.pos = mark.line.text.Length;
4265 #endregion // Internal Methods
4268 internal event EventHandler CaretMoved;
4269 internal event EventHandler WidthChanged;
4270 internal event EventHandler HeightChanged;
4271 internal event EventHandler LengthChanged;
4272 #endregion // Events
4274 #region Administrative
4275 public IEnumerator GetEnumerator() {
4280 public override bool Equals(object obj) {
4285 if (!(obj is Document)) {
4293 if (ToString().Equals(((Document)obj).ToString())) {
4300 public override int GetHashCode() {
4304 public override string ToString() {
4305 return "document " + this.document_id;
4307 #endregion // Administrative
4310 internal class ImageTag : LineTag {
4312 internal Image image;
4314 internal ImageTag (Line line, int start, Image image) : base (line, start)
4319 public override bool IsTextTag {
4320 get { return false; }
4323 internal override SizeF SizeOfPosition (Graphics dc, int pos)
4328 internal override int MaxHeight ()
4330 return image.Height;
4333 internal override void Draw (Graphics dc, Brush brush, float x, float y, int start, int end)
4335 dc.DrawImage (image, x, y);
4338 internal override void Draw (Graphics dc, Brush brush, float x, float y, int start, int end, string text)
4340 dc.DrawImage (image, x, y);
4343 public override string Text ()
4349 internal class LineTag {
4350 #region Local Variables;
4351 // Payload; formatting
4352 internal Font font; // System.Drawing.Font object for this tag
4353 internal SolidBrush color; // The font color for this tag
4355 // In 2.0 tags can have background colours. I'm not going to #ifdef
4356 // at this level though since I want to reduce code paths
4357 internal SolidBrush back_color;
4360 internal int start; // start, in chars; index into Line.text
4361 internal bool r_to_l; // Which way is the font
4364 internal int height; // Height in pixels of the text this tag describes
4366 internal int ascent; // Ascent of the font for this tag
4367 internal int shift; // Shift down for this tag, to stay on baseline
4370 internal Line line; // The line we're on
4371 internal LineTag next; // Next tag on the same line
4372 internal LineTag previous; // Previous tag on the same line
4375 #region Constructors
4376 internal LineTag(Line line, int start) {
4380 #endregion // Constructors
4382 #region Internal Methods
4388 return line.X + line.widths [start - 1];
4393 get { return start + length; }
4396 public float width {
4400 return line.widths [start + length - 1] - (start != 0 ? line.widths [start - 1] : 0);
4408 res = next.start - start;
4410 res = line.text.Length - (start - 1);
4412 return res > 0 ? res : 0;
4416 public virtual bool IsTextTag {
4417 get { return true; }
4420 internal virtual SizeF SizeOfPosition (Graphics dc, int pos)
4422 return dc.MeasureString (line.text.ToString (pos, 1), font, 10000, Document.string_format);
4425 internal virtual int MaxHeight ()
4430 internal virtual void Draw (Graphics dc, Brush brush, float x, float y, int start, int end)
4432 dc.DrawString (line.text.ToString (start, end), font, brush, x, y, StringFormat.GenericTypographic);
4435 internal virtual void Draw (Graphics dc, Brush brush, float x, float y, int start, int end, string text)
4437 dc.DrawString (text.Substring (start, end), font, brush, x, y, StringFormat.GenericTypographic);
4440 ///<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>
4441 internal LineTag Break(int pos) {
4446 if (pos == this.start) {
4448 } else if (pos >= (start + length)) {
4452 new_tag = new LineTag(line, pos);
4453 new_tag.CopyFormattingFrom (this);
4455 new_tag.next = this.next;
4456 this.next = new_tag;
4457 new_tag.previous = this;
4459 if (new_tag.next != null) {
4460 new_tag.next.previous = new_tag;
4466 public virtual string Text ()
4468 return line.text.ToString (start - 1, length);
4471 public void CopyFormattingFrom (LineTag other)
4473 height = other.height;
4475 color = other.color;
4476 back_color = other.back_color;
4479 ///<summary>Create new font and brush from existing font and given new attributes. Returns true if fontheight changes</summary>
4480 internal static bool GenerateTextFormat(Font font_from, SolidBrush color_from, FontDefinition attributes, out Font new_font, out SolidBrush new_color) {
4486 if (attributes.font_obj == null) {
4487 size = font_from.SizeInPoints;
4488 unit = font_from.Unit;
4489 face = font_from.Name;
4490 style = font_from.Style;
4492 if (attributes.face != null) {
4493 face = attributes.face;
4496 if (attributes.size != 0) {
4497 size = attributes.size;
4500 style |= attributes.add_style;
4501 style &= ~attributes.remove_style;
4504 new_font = new Font(face, size, style, unit);
4506 new_font = attributes.font_obj;
4509 // Create 'new' color brush
4510 if (attributes.color != Color.Empty) {
4511 new_color = new SolidBrush(attributes.color);
4513 new_color = color_from;
4516 if (new_font.Height == font_from.Height) {
4522 /// <summary>Applies 'font' and 'brush' to characters starting at 'start' for 'length' chars;
4523 /// Removes any previous tags overlapping the same area;
4524 /// returns true if lineheight has changed</summary>
4525 /// <param name="start">1-based character position on line</param>
4526 internal static bool FormatText(Line line, int start, int length, Font font, SolidBrush color, SolidBrush back_color, FormatSpecified specified)
4532 bool retval = false; // Assume line-height doesn't change
4535 if (((FormatSpecified.Font & specified) == FormatSpecified.Font) && font.Height != line.height) {
4538 line.recalc = true; // This forces recalculation of the line in RecalculateDocument
4540 // A little sanity, not sure if it's needed, might be able to remove for speed
4541 if (length > line.text.Length) {
4542 length = line.text.Length;
4546 end = start + length;
4548 // Common special case
4549 if ((start == 1) && (length == tag.length)) {
4551 SetFormat (tag, font, color, back_color, specified);
4555 start_tag = FindTag (line, start);
4557 tag = start_tag.Break (start);
4559 while (tag != null && tag.end <= end) {
4560 SetFormat (tag, font, color, back_color, specified);
4564 if (end != line.text.Length) {
4565 /// Now do the last tag
4566 end_tag = FindTag (line, end);
4568 if (end_tag != null) {
4569 end_tag.Break (end);
4570 SetFormat (end_tag, font, color, back_color, specified);
4577 private static void SetFormat (LineTag tag, Font font, SolidBrush color, SolidBrush back_color, FormatSpecified specified)
4579 if ((FormatSpecified.Font & specified) == FormatSpecified.Font)
4581 if ((FormatSpecified.Color & specified) == FormatSpecified.Color)
4583 if ((FormatSpecified.BackColor & specified) == FormatSpecified.BackColor) {
4584 tag.back_color = back_color;
4586 // Console.WriteLine ("setting format: {0} {1} new color {2}", color.Color, specified, tag.color.Color);
4589 /// <summary>Applies font attributes specified to characters starting at 'start' for 'length' chars;
4590 /// Breaks tags at start and end point, keeping middle tags with altered attributes.
4591 /// Returns true if lineheight has changed</summary>
4592 /// <param name="start">1-based character position on line</param>
4593 internal static bool FormatText(Line line, int start, int length, FontDefinition attributes) {
4597 bool retval = false; // Assume line-height doesn't change
4599 line.recalc = true; // This forces recalculation of the line in RecalculateDocument
4601 // A little sanity, not sure if it's needed, might be able to remove for speed
4602 if (length > line.text.Length) {
4603 length = line.text.Length;
4608 // Common special case
4609 if ((start == 1) && (length == tag.length)) {
4611 GenerateTextFormat(tag.font, tag.color, attributes, out tag.font, out tag.color);
4615 start_tag = FindTag(line, start);
4617 if (start_tag == null) {
4619 // We are 'starting' after all valid tags; create a new tag with the right attributes
4620 start_tag = FindTag(line, line.text.Length - 1);
4621 start_tag.next = new LineTag(line, line.text.Length + 1);
4622 start_tag.next.CopyFormattingFrom (start_tag);
4623 start_tag.next.previous = start_tag;
4624 start_tag = start_tag.next;
4626 throw new Exception(String.Format("Could not find start_tag in document at line {0} position {1}", line.line_no, start));
4629 start_tag = start_tag.Break(start);
4632 end_tag = FindTag(line, start + length);
4633 if (end_tag != null) {
4634 end_tag = end_tag.Break(start + length);
4637 // start_tag or end_tag might be null; we're cool with that
4638 // we now walk from start_tag to end_tag, applying new attributes
4640 while ((tag != null) && tag != end_tag) {
4641 if (LineTag.GenerateTextFormat(tag.font, tag.color, attributes, out tag.font, out tag.color)) {
4650 /// <summary>Finds the tag that describes the character at position 'pos' on 'line'</summary>
4651 internal static LineTag FindTag(Line line, int pos) {
4652 LineTag tag = line.tags;
4654 // Beginning of line is a bit special
4656 // Not sure if we should get the final tag here
4660 while (tag != null) {
4661 if ((tag.start <= pos) && (pos <= tag.end)) {
4662 return GetFinalTag (tag);
4671 // There can be multiple tags at the same position, we want to make
4672 // sure we are using the very last tag at the given position
4673 internal static LineTag GetFinalTag (LineTag tag)
4677 while (res.length == 0 && res.next != null && res.next.length == 0)
4683 /// <summary>Combines 'this' tag with 'other' tag</summary>
4684 internal bool Combine(LineTag other) {
4685 if (!this.Equals(other)) {
4689 this.next = other.next;
4690 if (this.next != null) {
4691 this.next.previous = this;
4698 /// <summary>Remove 'this' tag ; to be called when formatting is to be removed</summary>
4699 internal bool Remove() {
4700 if ((this.start == 1) && (this.next == null)) {
4701 // We cannot remove the only tag
4704 if (this.start != 1) {
4705 this.previous.next = this.next;
4706 this.next.previous = this.previous;
4708 this.next.start = 1;
4709 this.line.tags = this.next;
4710 this.next.previous = null;
4716 /// <summary>Checks if 'this' tag describes the same formatting options as 'obj'</summary>
4717 public override bool Equals(object obj) {
4724 if (!(obj is LineTag)) {
4732 other = (LineTag)obj;
4734 if (other.IsTextTag != IsTextTag)
4737 if (this.font.Equals(other.font) && this.color.Equals(other.color)) { // FIXME add checking for things like link or type later
4744 public override int GetHashCode() {
4745 return base.GetHashCode ();
4748 public override string ToString() {
4750 return GetType () + " Tag starts at index " + this.start + " length " + this.length + " text: " + Text () + "Font " + this.font.ToString();
4751 return "Zero Lengthed tag at index " + this.start;
4754 #endregion // Internal Methods
4757 internal class UndoManager {
4759 internal enum ActionType {
4763 // This is basically just cut & paste
4771 internal class Action {
4772 internal ActionType type;
4773 internal int line_no;
4775 internal object data;
4778 #region Local Variables
4779 private Document document;
4780 private Stack undo_actions;
4781 private Stack redo_actions;
4783 private int caret_line;
4784 private int caret_pos;
4786 // When performing an action, we lock the queue, so that the action can't be undone
4787 private bool locked;
4788 #endregion // Local Variables
4790 #region Constructors
4791 internal UndoManager (Document document)
4793 this.document = document;
4794 undo_actions = new Stack (50);
4795 redo_actions = new Stack (50);
4797 #endregion // Constructors
4800 internal bool CanUndo {
4801 get { return undo_actions.Count > 0; }
4804 internal bool CanRedo {
4805 get { return redo_actions.Count > 0; }
4808 internal string UndoActionName {
4810 foreach (Action action in undo_actions) {
4811 if (action.type == ActionType.UserActionBegin)
4812 return (string) action.data;
4813 if (action.type == ActionType.Typing)
4814 return Locale.GetText ("Typing");
4816 return String.Empty;
4820 internal string RedoActionName {
4822 foreach (Action action in redo_actions) {
4823 if (action.type == ActionType.UserActionBegin)
4824 return (string) action.data;
4825 if (action.type == ActionType.Typing)
4826 return Locale.GetText ("Typing");
4828 return String.Empty;
4831 #endregion // Properties
4833 #region Internal Methods
4834 internal void Clear ()
4836 undo_actions.Clear();
4837 redo_actions.Clear();
4840 internal void Undo ()
4843 bool user_action_finished = false;
4845 if (undo_actions.Count == 0)
4848 // Nuke the redo queue
4849 redo_actions.Clear ();
4854 action = (Action) undo_actions.Pop ();
4856 // Put onto redo stack
4857 redo_actions.Push(action);
4860 switch(action.type) {
4862 case ActionType.UserActionBegin:
4863 user_action_finished = true;
4866 case ActionType.UserActionEnd:
4870 case ActionType.InsertString:
4871 start = document.GetLine (action.line_no);
4872 document.SuspendUpdate ();
4873 document.DeleteMultiline (start, action.pos, ((string) action.data).Length + 1);
4874 document.PositionCaret (start, action.pos);
4875 document.SetSelectionToCaret (true);
4876 document.ResumeUpdate (true);
4879 case ActionType.Typing:
4880 start = document.GetLine (action.line_no);
4881 document.SuspendUpdate ();
4882 document.DeleteMultiline (start, action.pos, ((StringBuilder) action.data).Length);
4883 document.PositionCaret (start, action.pos);
4884 document.SetSelectionToCaret (true);
4885 document.ResumeUpdate (true);
4887 // This is an open ended operation, so only a single typing operation can be undone at once
4888 user_action_finished = true;
4891 case ActionType.DeleteString:
4892 start = document.GetLine (action.line_no);
4893 document.SuspendUpdate ();
4894 Insert (start, action.pos, (Line) action.data, true);
4895 document.ResumeUpdate (true);
4898 } while (!user_action_finished && undo_actions.Count > 0);
4903 internal void Redo ()
4906 bool user_action_finished = false;
4908 if (redo_actions.Count == 0)
4911 // You can't undo anything after redoing
4912 undo_actions.Clear ();
4919 action = (Action) redo_actions.Pop ();
4921 switch (action.type) {
4923 case ActionType.UserActionBegin:
4927 case ActionType.UserActionEnd:
4928 user_action_finished = true;
4931 case ActionType.InsertString:
4932 start = document.GetLine (action.line_no);
4933 document.SuspendUpdate ();
4934 start_index = document.LineTagToCharIndex (start, action.pos);
4935 document.InsertString (start, action.pos, (string) action.data);
4936 document.CharIndexToLineTag (start_index + ((string) action.data).Length,
4937 out document.caret.line, out document.caret.tag,
4938 out document.caret.pos);
4939 document.UpdateCaret ();
4940 document.SetSelectionToCaret (true);
4941 document.ResumeUpdate (true);
4944 case ActionType.Typing:
4945 start = document.GetLine (action.line_no);
4946 document.SuspendUpdate ();
4947 start_index = document.LineTagToCharIndex (start, action.pos);
4948 document.InsertString (start, action.pos, ((StringBuilder) action.data).ToString ());
4949 document.CharIndexToLineTag (start_index + ((StringBuilder) action.data).Length,
4950 out document.caret.line, out document.caret.tag,
4951 out document.caret.pos);
4952 document.UpdateCaret ();
4953 document.SetSelectionToCaret (true);
4954 document.ResumeUpdate (true);
4956 // This is an open ended operation, so only a single typing operation can be undone at once
4957 user_action_finished = true;
4960 case ActionType.DeleteString:
4961 start = document.GetLine (action.line_no);
4962 document.SuspendUpdate ();
4963 document.DeleteMultiline (start, action.pos, ((Line) action.data).text.Length);
4964 document.PositionCaret (start, action.pos);
4965 document.SetSelectionToCaret (true);
4966 document.ResumeUpdate (true);
4970 } while (!user_action_finished && redo_actions.Count > 0);
4974 #endregion // Internal Methods
4976 #region Private Methods
4978 public void BeginUserAction (string name)
4983 Action ua = new Action ();
4984 ua.type = ActionType.UserActionBegin;
4987 undo_actions.Push (ua);
4990 public void EndUserAction ()
4995 Action ua = new Action ();
4996 ua.type = ActionType.UserActionEnd;
4998 undo_actions.Push (ua);
5001 // start_pos, end_pos = 1 based
5002 public void RecordDeleteString (Line start_line, int start_pos, Line end_line, int end_pos)
5007 Action a = new Action ();
5009 // We cant simply store the string, because then formatting would be lost
5010 a.type = ActionType.DeleteString;
5011 a.line_no = start_line.line_no;
5013 a.data = Duplicate (start_line, start_pos, end_line, end_pos);
5015 undo_actions.Push(a);
5018 public void RecordInsertString (Line line, int pos, string str)
5020 if (locked || str.Length == 0)
5023 Action a = new Action ();
5025 a.type = ActionType.InsertString;
5027 a.line_no = line.line_no;
5030 undo_actions.Push (a);
5033 public void RecordTyping (Line line, int pos, char ch)
5040 if (undo_actions.Count > 0)
5041 a = (Action) undo_actions.Peek ();
5043 if (a == null || a.type != ActionType.Typing) {
5045 a.type = ActionType.Typing;
5046 a.data = new StringBuilder ();
5047 a.line_no = line.line_no;
5050 undo_actions.Push (a);
5053 StringBuilder data = (StringBuilder) a.data;
5057 // start_pos = 1-based
5058 // end_pos = 1-based
5059 public Line Duplicate(Line start_line, int start_pos, Line end_line, int end_pos)
5065 LineTag current_tag;
5070 line = new Line (start_line.document);
5073 for (int i = start_line.line_no; i <= end_line.line_no; i++) {
5074 current = document.GetLine(i);
5076 if (start_line.line_no == i) {
5082 if (end_line.line_no == i) {
5085 end = current.text.Length;
5092 line.text = new StringBuilder (current.text.ToString (start, end - start));
5094 // Copy tags from start to start+length onto new line
5095 current_tag = current.FindTag (start);
5096 while ((current_tag != null) && (current_tag.start < end)) {
5097 if ((current_tag.start <= start) && (start < (current_tag.start + current_tag.length))) {
5098 // start tag is within this tag
5101 tag_start = current_tag.start;
5104 tag = new LineTag(line, tag_start - start + 1);
5105 tag.CopyFormattingFrom (current_tag);
5107 current_tag = current_tag.next;
5109 // Add the new tag to the line
5110 if (line.tags == null) {
5116 while (tail.next != null) {
5120 tag.previous = tail;
5124 if ((i + 1) <= end_line.line_no) {
5125 line.soft_break = current.soft_break;
5127 // Chain them (we use right/left as next/previous)
5128 line.right = new Line (start_line.document);
5129 line.right.left = line;
5137 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
5138 internal void Insert(Line line, int pos, Line insert, bool select)
5146 // Handle special case first
5147 if (insert.right == null) {
5149 // Single line insert
5150 document.Split(line, pos);
5152 if (insert.tags == null) {
5153 return; // Blank line
5156 //Insert our tags at the end
5159 while (tag.next != null) {
5163 offset = tag.start + tag.length - 1;
5165 tag.next = insert.tags;
5166 line.text.Insert(offset, insert.text.ToString());
5168 // Adjust start locations
5170 while (tag != null) {
5171 tag.start += offset;
5175 // Put it back together
5176 document.Combine(line.line_no, line.line_no + 1);
5179 document.SetSelectionStart (line, pos);
5180 document.SetSelectionEnd (line, pos + insert.text.Length);
5183 document.UpdateView(line, pos);
5191 while (current != null) {
5192 if (current == insert) {
5193 // Inserting the first line we split the line (and make space)
5194 document.Split(line, pos);
5195 //Insert our tags at the end of the line
5199 while (tag.next != null) {
5202 offset = tag.start + tag.length - 1;
5203 tag.next = current.tags;
5204 tag.next.previous = tag;
5210 line.tags = current.tags;
5211 line.tags.previous = null;
5215 document.Split(line.line_no, 0);
5217 line.tags = current.tags;
5218 line.tags.previous = null;
5221 // Adjust start locations and line pointers
5222 while (tag != null) {
5223 tag.start += offset;
5228 line.text.Insert(offset, current.text.ToString());
5229 line.Grow(line.text.Length);
5232 line = document.GetLine(line.line_no + 1);
5234 // FIXME? Test undo of line-boundaries
5235 if ((current.right == null) && (current.tags.length != 0)) {
5236 document.Combine(line.line_no - 1, line.line_no);
5238 current = current.right;
5243 // Recalculate our document
5244 document.UpdateView(first, lines, pos);
5247 #endregion // Private Methods