1 // Permission is hereby granted, free of charge, to any person obtaining
2 // a copy of this software and associated documentation files (the
3 // "Software"), to deal in the Software without restriction, including
4 // without limitation the rights to use, copy, modify, merge, publish,
5 // distribute, sublicense, and/or sell copies of the Software, and to
6 // permit persons to whom the Software is furnished to do so, subject to
7 // the following conditions:
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
12 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 // Copyright (c) 2004-2006 Novell, Inc. (http://www.novell.com)
23 // Peter Bartok pbartok@novell.com
29 // There's still plenty of things missing, I've got most of it planned, just hadn't had
30 // the time to write it all yet.
31 // Stuff missing (in no particular order):
32 // - Align text after RecalculateLine
33 // - Implement tag types for hotlinks, etc.
34 // - Implement CaretPgUp/PgDown
37 // selection_start.pos and selection_end.pos are 0-based
38 // selection_start.pos = first selected char
39 // selection_end.pos = first NOT-selected char
41 // FormatText methods are 1-based (as are all tags, LineTag.Start is 1 for
42 // the first character on a line; the reason is that 0 is the position
43 // *before* the first character on a line
49 using System.Collections;
51 using System.Drawing.Text;
53 using RTF=System.Windows.Forms.RTF;
55 namespace System.Windows.Forms {
56 internal enum LineColor {
61 internal enum CaretSelection {
62 Position, // Selection=Caret
63 Word, // Selection=Word under caret
64 Line // Selection=Line under caret
67 internal class FontDefinition {
70 internal FontStyle add_style;
71 internal FontStyle remove_style;
73 internal Font font_obj;
77 internal enum FormatSpecified {
85 internal enum CaretDirection {
86 CharForward, // Move a char to the right
87 CharBack, // Move a char to the left
88 LineUp, // Move a line up
89 LineDown, // Move a line down
90 Home, // Move to the beginning of the line
91 End, // Move to the end of the line
92 PgUp, // Move one page up
93 PgDn, // Move one page down
94 CtrlPgUp, // Move caret to the first visible char in the viewport
95 CtrlPgDn, // Move caret to the last visible char in the viewport
96 CtrlHome, // Move to the beginning of the document
97 CtrlEnd, // Move to the end of the document
98 WordBack, // Move to the beginning of the previous word (or beginning of line)
99 WordForward, // Move to the beginning of the next word (or end of line)
100 SelectionStart, // Move to the beginning of the current selection
101 SelectionEnd, // Move to the end of the current selection
102 CharForwardNoWrap, // Move a char forward, but don't wrap onto the next line
103 CharBackNoWrap // Move a char backward, but don't wrap onto the previous line
106 internal enum LineEnding {
107 Wrap, // line wraps to the next line
116 // Being cloneable should allow for nice line and document copies...
117 internal class Line : ICloneable, IComparable {
118 #region Local Variables
120 internal Document document;
122 // Stuff that matters for our line
123 internal StringBuilder text; // Characters for the line
124 internal float[] widths; // Width of each character; always one larger than text.Length
125 internal int space; // Number of elements in text and widths
126 internal int line_no; // Line number
127 internal LineTag tags; // Tags describing the text
128 internal int offset; // Baseline can be on the X or Y axis depending if we are in multiline mode or not
129 internal int height; // Height of the line (height of tallest tag)
130 internal int ascent; // Ascent of the line (ascent of the tallest tag)
131 internal HorizontalAlignment alignment; // Alignment of the line
132 internal int align_shift; // Pixel shift caused by the alignment
133 internal int indent; // Left indent for the first line
134 internal int hanging_indent; // Hanging indent (left indent for all but the first line)
135 internal int right_indent; // Right indent for all lines
136 internal LineEnding ending;
139 // Stuff that's important for the tree
140 internal Line parent; // Our parent line
141 internal Line left; // Line with smaller line number
142 internal Line right; // Line with higher line number
143 internal LineColor color; // We're doing a black/red tree. this is the node color
144 internal int DEFAULT_TEXT_LEN; //
145 internal bool recalc; // Line changed
146 #endregion // Local Variables
149 internal Line (Document document, LineEnding ending)
151 this.document = document;
152 color = LineColor.Red;
158 alignment = document.alignment;
160 this.ending = ending;
163 internal Line(Document document, int LineNo, string Text, Font font, SolidBrush color, LineEnding ending) : this (document, ending)
165 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
167 text = new StringBuilder(Text, space);
169 this.ending = ending;
171 widths = new float[space + 1];
174 tags = new LineTag(this, 1);
179 internal Line(Document document, int LineNo, string Text, HorizontalAlignment align, Font font, SolidBrush color, LineEnding ending) : this(document, ending)
181 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
183 text = new StringBuilder(Text, space);
185 this.ending = ending;
188 widths = new float[space + 1];
191 tags = new LineTag(this, 1);
196 internal Line(Document document, int LineNo, string Text, LineTag tag, LineEnding ending) : this(document, ending)
198 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
200 text = new StringBuilder(Text, space);
201 this.ending = ending;
204 widths = new float[space + 1];
208 #endregion // Constructors
210 #region Internal Properties
214 if (!document.multiline)
215 return document.top_margin;
216 return document.top_margin + offset;
222 if (document.multiline)
224 return offset + align_shift;
230 int res = (int) widths [text.Length];
231 if (!document.multiline) {
238 internal int Indent {
249 internal int HangingIndent {
251 return hanging_indent;
255 hanging_indent = value;
260 internal int RightIndent {
266 right_indent = value;
272 internal int Height {
282 internal int LineNo {
292 internal string Text {
294 return text.ToString();
298 text = new StringBuilder(value, value.Length > DEFAULT_TEXT_LEN ? value.Length : DEFAULT_TEXT_LEN);
302 internal HorizontalAlignment Alignment {
308 if (alignment != value) {
315 internal StringBuilder Text {
325 #endregion // Internal Properties
327 #region Internal Methods
329 // This doesn't do exactly what you would think, it just pulls of the \n part of the ending
330 internal string TextWithoutEnding ()
332 return text.ToString (0, text.Length - document.LineEndingLength (ending));
335 internal int TextLengthWithoutEnding ()
337 return text.Length - document.LineEndingLength (ending);
340 internal void DrawEnding (Graphics dc, float y)
342 if (document.multiline)
345 while (last.next != null)
348 string end_str = null;
349 switch (document.LineEndingLength (ending)) {
356 end_str = "\u0013\u0013";
359 end_str = "\u0013\u0013\u0013";
362 dc.DrawString (end_str, last.font, last.color, X + widths [TextLengthWithoutEnding ()] - document.viewport_x,
363 y, Document.string_format);
367 // Make sure we always have enoughs space in text and widths
368 internal void Grow(int minimum) {
372 length = text.Length;
374 if ((length + minimum) > space) {
375 // We need to grow; double the size
377 if ((length + minimum) > (space * 2)) {
378 new_widths = new float[length + minimum * 2 + 1];
379 space = length + minimum * 2;
381 new_widths = new float[space * 2 + 1];
384 widths.CopyTo(new_widths, 0);
390 internal void Streamline(int lines) {
398 // Catch what the loop below wont; eliminate 0 length
399 // tags, but only if there are other tags after us
400 // We only eliminate text tags if there is another text tag
401 // after it. Otherwise we wind up trying to type on picture tags
403 while ((current.length == 0) && (next != null) && (next.IsTextTag)) {
405 tags.previous = null;
415 while (next != null) {
416 // Take out 0 length tags unless it's the last tag in the document
417 if (current.IsTextTag && next.length == 0 && next.IsTextTag) {
418 if ((next.next != null) || (line_no != lines)) {
419 current.next = next.next;
420 if (current.next != null) {
421 current.next.previous = current;
427 if (current.Combine(next)) {
432 current = current.next;
437 /// <summary> Find the tag on a line based on the character position, pos is 0-based</summary>
438 internal LineTag FindTag(int pos) {
447 if (pos >= text.Length) {
448 pos = text.Length - 1;
451 while (tag != null) {
452 if (((tag.start - 1) <= pos) && (pos < (tag.start + tag.length - 1))) {
453 return LineTag.GetFinalTag (tag);
461 /// Recalculate a single line using the same char for every character in the line
464 internal bool RecalculatePasswordLine(Graphics g, Document doc) {
473 len = this.text.Length;
479 widths[0] = document.left_margin + indent;
481 w = g.MeasureString(doc.password_char, tags.font, 10000, Document.string_format).Width;
483 if (this.height != (int)tag.font.Height) {
489 this.height = (int)tag.font.Height;
490 tag.height = this.height;
492 XplatUI.GetFontMetrics(g, tag.font, out tag.ascent, out descent);
493 this.ascent = tag.ascent;
497 widths[pos] = widths[pos-1] + w;
504 /// Go through all tags on a line and recalculate all size-related values;
505 /// returns true if lineheight changed
507 internal bool RecalculateLine(Graphics g, Document doc) {
520 len = this.text.Length;
522 prev_offset = this.offset; // For drawing optimization calculations
523 this.height = 0; // Reset line height
524 this.ascent = 0; // Reset the ascent for the line
527 if (ending == LineEnding.Wrap) {
528 widths[0] = document.left_margin + hanging_indent;
530 widths[0] = document.left_margin + indent;
541 while (tag.length == 0) { // We should always have tags after a tag.length==0 unless len==0
547 size = tag.SizeOfPosition (g, pos);
550 if (Char.IsWhiteSpace(text[pos])) {
555 if ((wrap_pos > 0) && (wrap_pos != len) && (widths[pos] + w) + 5 > (doc.viewport_width - this.right_indent)) {
556 // Make sure to set the last width of the line before wrapping
557 widths [pos + 1] = widths [pos] + w;
561 doc.Split(this, tag, pos);
562 ending = LineEnding.Wrap;
563 len = this.text.Length;
567 } else if (pos > 1 && (widths[pos] + w) > (doc.viewport_width - this.right_indent)) {
568 // No suitable wrap position was found so break right in the middle of a word
570 // Make sure to set the last width of the line before wrapping
571 widths [pos + 1] = widths [pos] + w;
573 doc.Split(this, tag, pos);
574 ending = LineEnding.Wrap;
575 len = this.text.Length;
581 // Contract all wrapped lines that follow back into our line
585 widths[pos] = widths[pos-1] + w;
588 line = doc.GetLine(this.line_no + 1);
589 if ((line != null) && (ending == LineEnding.Wrap || ending == LineEnding.None)) {
590 // Pull the two lines together
591 doc.Combine(this.line_no, this.line_no + 1);
592 len = this.text.Length;
598 if (pos == (tag.start-1 + tag.length)) {
599 // We just found the end of our current tag
600 tag.height = tag.MaxHeight ();
602 // Check if we're the tallest on the line (so far)
603 if (tag.height > this.height) {
604 this.height = tag.height; // Yep; make sure the line knows
607 if (tag.ascent == 0) {
610 XplatUI.GetFontMetrics(g, tag.font, out tag.ascent, out descent);
613 if (tag.ascent > this.ascent) {
616 // We have a tag that has a taller ascent than the line;
618 while (t != null && t != tag) {
619 t.shift = tag.ascent - t.ascent;
624 this.ascent = tag.ascent;
626 tag.shift = this.ascent - tag.ascent;
637 if (this.height == 0) {
638 this.height = tags.font.Height;
639 tag.height = this.height;
642 if (prev_offset != offset) {
647 #endregion // Internal Methods
649 #region Administrative
650 public int CompareTo(object obj) {
655 if (! (obj is Line)) {
656 throw new ArgumentException("Object is not of type Line", "obj");
659 if (line_no < ((Line)obj).line_no) {
661 } else if (line_no > ((Line)obj).line_no) {
668 public object Clone() {
671 clone = new Line (document, ending);
676 clone.left = (Line)left.Clone();
680 clone.left = (Line)left.Clone();
686 internal object CloneLine() {
689 clone = new Line (document, ending);
696 public override bool Equals(object obj) {
701 if (!(obj is Line)) {
709 if (line_no == ((Line)obj).line_no) {
716 public override int GetHashCode() {
717 return base.GetHashCode ();
720 public override string ToString() {
721 return "Line " + line_no;
724 #endregion // Administrative
727 internal class Document : ICloneable, IEnumerable {
729 // FIXME - go through code and check for places where
730 // we do explicit comparisons instead of using the compare overloads
731 internal struct Marker {
733 internal LineTag tag;
737 public static bool operator<(Marker lhs, Marker rhs) {
738 if (lhs.line.line_no < rhs.line.line_no) {
742 if (lhs.line.line_no == rhs.line.line_no) {
743 if (lhs.pos < rhs.pos) {
750 public static bool operator>(Marker lhs, Marker rhs) {
751 if (lhs.line.line_no > rhs.line.line_no) {
755 if (lhs.line.line_no == rhs.line.line_no) {
756 if (lhs.pos > rhs.pos) {
763 public static bool operator==(Marker lhs, Marker rhs) {
764 if ((lhs.line.line_no == rhs.line.line_no) && (lhs.pos == rhs.pos)) {
770 public static bool operator!=(Marker lhs, Marker rhs) {
771 if ((lhs.line.line_no != rhs.line.line_no) || (lhs.pos != rhs.pos)) {
777 public void Combine(Line move_to_line, int move_to_line_length) {
779 pos += move_to_line_length;
780 tag = LineTag.FindTag(line, pos);
783 // This is for future use, right now Document.Split does it by hand, with some added shortcut logic
784 public void Split(Line move_to_line, int split_at) {
787 tag = LineTag.FindTag(line, pos);
790 public override bool Equals(object obj) {
791 return this==(Marker)obj;
794 public override int GetHashCode() {
795 return base.GetHashCode ();
798 public override string ToString() {
799 return "Marker Line " + line + ", Position " + pos;
803 #endregion Structures
805 #region Local Variables
806 private Line document;
808 private Line sentinel;
809 private int document_id;
810 private Random random = new Random();
811 internal string password_char;
812 private StringBuilder password_cache;
813 private bool calc_pass;
814 private int char_count;
816 // For calculating widths/heights
817 public static readonly StringFormat string_format = new StringFormat (StringFormat.GenericTypographic);
819 private int recalc_suspended;
820 private bool recalc_pending;
821 private int recalc_start = 1; // This starts at one, since lines are 1 based
822 private int recalc_end;
823 private bool recalc_optimize;
825 private int update_suspended;
826 private bool update_pending;
827 private int update_start = 1;
829 internal bool multiline;
830 internal HorizontalAlignment alignment;
833 internal UndoManager undo;
835 internal Marker caret;
836 internal Marker selection_start;
837 internal Marker selection_end;
838 internal bool selection_visible;
839 internal Marker selection_anchor;
840 internal Marker selection_prev;
841 internal bool selection_end_anchor;
843 internal int viewport_x;
844 internal int viewport_y; // The visible area of the document
845 internal int viewport_width;
846 internal int viewport_height;
848 internal int document_x; // Width of the document
849 internal int document_y; // Height of the document
851 internal Rectangle invalid;
853 internal int crlf_size; // 1 or 2, depending on whether we use \r\n or just \n
855 internal TextBoxBase owner; // Who's owning us?
856 static internal int caret_width = 1;
857 static internal int caret_shift = 1;
859 internal int left_margin = 2; // A left margin for all lines
860 internal int top_margin = 2;
861 internal int right_margin = 2;
862 #endregion // Local Variables
865 internal Document (TextBoxBase owner)
874 recalc_pending = false;
876 // Tree related stuff
877 sentinel = new Line (this, LineEnding.None);
878 sentinel.color = LineColor.Black;
882 // We always have a blank line
883 owner.HandleCreated += new EventHandler(owner_HandleCreated);
884 owner.VisibleChanged += new EventHandler(owner_VisibleChanged);
886 Add (1, String.Empty, owner.Font, ThemeEngine.Current.ResPool.GetSolidBrush (owner.ForeColor), LineEnding.None);
888 undo = new UndoManager (this);
890 selection_visible = false;
891 selection_start.line = this.document;
892 selection_start.pos = 0;
893 selection_start.tag = selection_start.line.tags;
894 selection_end.line = this.document;
895 selection_end.pos = 0;
896 selection_end.tag = selection_end.line.tags;
897 selection_anchor.line = this.document;
898 selection_anchor.pos = 0;
899 selection_anchor.tag = selection_anchor.line.tags;
900 caret.line = this.document;
902 caret.tag = caret.line.tags;
909 // Default selection is empty
911 document_id = random.Next();
913 string_format.Trimming = StringTrimming.None;
914 string_format.FormatFlags = StringFormatFlags.DisplayFormatControl;
920 #region Internal Properties
937 internal Line CaretLine {
943 internal int CaretPosition {
949 internal Point Caret {
951 return new Point((int)caret.tag.line.widths[caret.pos] + caret.line.X, caret.line.Y);
955 internal LineTag CaretTag {
965 internal int CRLFSize {
975 internal string PasswordChar {
977 return password_char;
981 password_char = value;
982 PasswordCache.Length = 0;
983 if ((password_char.Length != 0) && (password_char[0] != '\0')) {
991 private StringBuilder PasswordCache {
993 if (password_cache == null)
994 password_cache = new StringBuilder();
995 return password_cache;
999 internal int ViewPortX {
1009 internal int Length {
1011 return char_count + lines - 1; // Add \n for each line but the last
1015 private int CharCount {
1023 if (LengthChanged != null) {
1024 LengthChanged(this, EventArgs.Empty);
1029 internal int ViewPortY {
1039 internal int ViewPortWidth {
1041 return viewport_width;
1045 viewport_width = value;
1049 internal int ViewPortHeight {
1051 return viewport_height;
1055 viewport_height = value;
1060 internal int Width {
1062 return this.document_x;
1066 internal int Height {
1068 return this.document_y;
1072 internal bool SelectionVisible {
1074 return selection_visible;
1078 internal bool Wrap {
1088 #endregion // Internal Properties
1090 #region Private Methods
1092 internal void UpdateMargins ()
1094 if (owner.actual_border_style == BorderStyle.FixedSingle) {
1105 internal void SuspendRecalc ()
1110 internal void ResumeRecalc (bool immediate_update)
1112 if (recalc_suspended > 0)
1115 if (immediate_update && recalc_suspended == 0 && recalc_pending) {
1116 RecalculateDocument (owner.CreateGraphicsInternal(), recalc_start, recalc_end, recalc_optimize);
1117 recalc_pending = false;
1121 internal void SuspendUpdate ()
1126 internal void ResumeUpdate (bool immediate_update)
1128 if (update_suspended > 0)
1131 if (immediate_update && update_suspended == 0 && update_pending) {
1132 UpdateView (GetLine (update_start), 0);
1133 update_pending = false;
1138 internal int DumpTree(Line line, bool with_tags) {
1143 Console.Write("Line {0} [# {1}], Y: {2}, ending style: {3}, Text: '{4}'",
1144 line.line_no, line.GetHashCode(), line.Y, line.ending,
1145 line.text != null ? line.text.ToString() : "undefined");
1147 if (line.left == sentinel) {
1148 Console.Write(", left = sentinel");
1149 } else if (line.left == null) {
1150 Console.Write(", left = NULL");
1153 if (line.right == sentinel) {
1154 Console.Write(", right = sentinel");
1155 } else if (line.right == null) {
1156 Console.Write(", right = NULL");
1159 Console.WriteLine("");
1169 Console.Write(" Tags: ");
1170 while (tag != null) {
1171 Console.Write("{0} <{1}>-<{2}>", count++, tag.start, tag.end
1172 /*line.text.ToString (tag.start - 1, tag.length)*/);
1173 length += tag.length;
1175 if (tag.line != line) {
1176 Console.Write("BAD line link");
1177 throw new Exception("Bad line link in tree");
1181 Console.Write(", ");
1184 if (length > line.text.Length) {
1185 throw new Exception(String.Format("Length of tags more than length of text on line (expected {0} calculated {1})", line.text.Length, length));
1186 } else if (length < line.text.Length) {
1187 throw new Exception(String.Format("Length of tags less than length of text on line (expected {0} calculated {1})", line.text.Length, length));
1189 Console.WriteLine("");
1191 if (line.left != null) {
1192 if (line.left != sentinel) {
1193 total += DumpTree(line.left, with_tags);
1196 if (line != sentinel) {
1197 throw new Exception("Left should not be NULL");
1201 if (line.right != null) {
1202 if (line.right != sentinel) {
1203 total += DumpTree(line.right, with_tags);
1206 if (line != sentinel) {
1207 throw new Exception("Right should not be NULL");
1211 for (int i = 1; i <= this.lines; i++) {
1212 if (GetLine(i) == null) {
1213 throw new Exception(String.Format("Hole in line order, missing {0}", i));
1217 if (line == this.Root) {
1218 if (total < this.lines) {
1219 throw new Exception(String.Format("Not enough nodes in tree, found {0}, expected {1}", total, this.lines));
1220 } else if (total > this.lines) {
1221 throw new Exception(String.Format("Too many nodes in tree, found {0}, expected {1}", total, this.lines));
1228 private void SetSelectionVisible (bool value)
1230 selection_visible = value;
1232 // cursor and selection are enemies, we can't have both in the same room at the same time
1233 if (owner.IsHandleCreated && !owner.show_caret_w_selection)
1234 XplatUI.CaretVisible (owner.Handle, !selection_visible);
1237 private void DecrementLines(int line_no) {
1241 while (current <= lines) {
1242 GetLine(current).line_no--;
1248 private void IncrementLines(int line_no) {
1251 current = this.lines;
1252 while (current >= line_no) {
1253 GetLine(current).line_no++;
1259 private void RebalanceAfterAdd(Line line1) {
1262 while ((line1 != document) && (line1.parent.color == LineColor.Red)) {
1263 if (line1.parent == line1.parent.parent.left) {
1264 line2 = line1.parent.parent.right;
1266 if ((line2 != null) && (line2.color == LineColor.Red)) {
1267 line1.parent.color = LineColor.Black;
1268 line2.color = LineColor.Black;
1269 line1.parent.parent.color = LineColor.Red;
1270 line1 = line1.parent.parent;
1272 if (line1 == line1.parent.right) {
1273 line1 = line1.parent;
1277 line1.parent.color = LineColor.Black;
1278 line1.parent.parent.color = LineColor.Red;
1280 RotateRight(line1.parent.parent);
1283 line2 = line1.parent.parent.left;
1285 if ((line2 != null) && (line2.color == LineColor.Red)) {
1286 line1.parent.color = LineColor.Black;
1287 line2.color = LineColor.Black;
1288 line1.parent.parent.color = LineColor.Red;
1289 line1 = line1.parent.parent;
1291 if (line1 == line1.parent.left) {
1292 line1 = line1.parent;
1296 line1.parent.color = LineColor.Black;
1297 line1.parent.parent.color = LineColor.Red;
1298 RotateLeft(line1.parent.parent);
1302 document.color = LineColor.Black;
1305 private void RebalanceAfterDelete(Line line1) {
1308 while ((line1 != document) && (line1.color == LineColor.Black)) {
1309 if (line1 == line1.parent.left) {
1310 line2 = line1.parent.right;
1311 if (line2.color == LineColor.Red) {
1312 line2.color = LineColor.Black;
1313 line1.parent.color = LineColor.Red;
1314 RotateLeft(line1.parent);
1315 line2 = line1.parent.right;
1317 if ((line2.left.color == LineColor.Black) && (line2.right.color == LineColor.Black)) {
1318 line2.color = LineColor.Red;
1319 line1 = line1.parent;
1321 if (line2.right.color == LineColor.Black) {
1322 line2.left.color = LineColor.Black;
1323 line2.color = LineColor.Red;
1325 line2 = line1.parent.right;
1327 line2.color = line1.parent.color;
1328 line1.parent.color = LineColor.Black;
1329 line2.right.color = LineColor.Black;
1330 RotateLeft(line1.parent);
1334 line2 = line1.parent.left;
1335 if (line2.color == LineColor.Red) {
1336 line2.color = LineColor.Black;
1337 line1.parent.color = LineColor.Red;
1338 RotateRight(line1.parent);
1339 line2 = line1.parent.left;
1341 if ((line2.right.color == LineColor.Black) && (line2.left.color == LineColor.Black)) {
1342 line2.color = LineColor.Red;
1343 line1 = line1.parent;
1345 if (line2.left.color == LineColor.Black) {
1346 line2.right.color = LineColor.Black;
1347 line2.color = LineColor.Red;
1349 line2 = line1.parent.left;
1351 line2.color = line1.parent.color;
1352 line1.parent.color = LineColor.Black;
1353 line2.left.color = LineColor.Black;
1354 RotateRight(line1.parent);
1359 line1.color = LineColor.Black;
1362 private void RotateLeft(Line line1) {
1363 Line line2 = line1.right;
1365 line1.right = line2.left;
1367 if (line2.left != sentinel) {
1368 line2.left.parent = line1;
1371 if (line2 != sentinel) {
1372 line2.parent = line1.parent;
1375 if (line1.parent != null) {
1376 if (line1 == line1.parent.left) {
1377 line1.parent.left = line2;
1379 line1.parent.right = line2;
1386 if (line1 != sentinel) {
1387 line1.parent = line2;
1391 private void RotateRight(Line line1) {
1392 Line line2 = line1.left;
1394 line1.left = line2.right;
1396 if (line2.right != sentinel) {
1397 line2.right.parent = line1;
1400 if (line2 != sentinel) {
1401 line2.parent = line1.parent;
1404 if (line1.parent != null) {
1405 if (line1 == line1.parent.right) {
1406 line1.parent.right = line2;
1408 line1.parent.left = line2;
1414 line2.right = line1;
1415 if (line1 != sentinel) {
1416 line1.parent = line2;
1421 internal void UpdateView(Line line, int pos) {
1422 if (!owner.IsHandleCreated) {
1426 if (update_suspended > 0) {
1427 update_start = Math.Min (update_start, line.line_no);
1428 // update_end = Math.Max (update_end, line.line_no);
1429 // recalc_optimize = true;
1430 update_pending = true;
1434 // Optimize invalidation based on Line alignment
1435 if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no, true)) {
1436 // Lineheight changed, invalidate the rest of the document
1437 if ((line.Y - viewport_y) >=0 ) {
1438 // We formatted something that's in view, only draw parts of the screen
1439 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, owner.Height - line.Y - viewport_y));
1441 // The tag was above the visible area, draw everything
1445 switch(line.alignment) {
1446 case HorizontalAlignment.Left: {
1447 owner.Invalidate(new Rectangle(line.X + (int)line.widths[pos] - viewport_x - 1, line.Y - viewport_y, viewport_width, line.height + 1));
1451 case HorizontalAlignment.Center: {
1452 owner.Invalidate(new Rectangle(line.X, line.Y - viewport_y, viewport_width, line.height + 1));
1456 case HorizontalAlignment.Right: {
1457 owner.Invalidate(new Rectangle(line.X, line.Y - viewport_y, (int)line.widths[pos + 1] - viewport_x + line.X, line.height + 1));
1465 // Update display from line, down line_count lines; pos is unused, but required for the signature
1466 internal void UpdateView(Line line, int line_count, int pos) {
1467 if (!owner.IsHandleCreated) {
1471 if (recalc_suspended > 0) {
1472 recalc_start = Math.Min (recalc_start, line.line_no);
1473 recalc_end = Math.Max (recalc_end, line.line_no + line_count);
1474 recalc_optimize = true;
1475 recalc_pending = true;
1479 int start_line_top = line.Y;
1481 int end_line_bottom;
1484 end_line = GetLine (line.line_no + line_count);
1485 if (end_line == null)
1486 end_line = GetLine (lines);
1489 end_line_bottom = end_line.Y + end_line.height;
1491 if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no + line_count, true)) {
1492 // Lineheight changed, invalidate the rest of the document
1493 if ((line.Y - viewport_y) >=0 ) {
1494 // We formatted something that's in view, only draw parts of the screen
1495 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, owner.Height - line.Y - viewport_y));
1497 // The tag was above the visible area, draw everything
1501 int x = 0 - viewport_x;
1502 int w = viewport_width;
1503 int y = Math.Min (start_line_top - viewport_y, line.Y - viewport_y);
1504 int h = Math.Max (end_line_bottom - y, end_line.Y + end_line.height - y);
1506 owner.Invalidate (new Rectangle (x, y, w, h));
1509 #endregion // Private Methods
1511 #region Internal Methods
1512 // Clear the document and reset state
1513 internal void Empty() {
1515 document = sentinel;
1518 // We always have a blank line
1519 Add (1, String.Empty, owner.Font, ThemeEngine.Current.ResPool.GetSolidBrush (owner.ForeColor), LineEnding.None);
1521 this.RecalculateDocument(owner.CreateGraphicsInternal());
1522 PositionCaret(0, 0);
1524 SetSelectionVisible (false);
1526 selection_start.line = this.document;
1527 selection_start.pos = 0;
1528 selection_start.tag = selection_start.line.tags;
1529 selection_end.line = this.document;
1530 selection_end.pos = 0;
1531 selection_end.tag = selection_end.line.tags;
1540 if (owner.IsHandleCreated)
1541 owner.Invalidate ();
1544 internal void PositionCaret(Line line, int pos) {
1545 caret.tag = line.FindTag (pos);
1547 MoveCaretToTextTag ();
1552 if (owner.IsHandleCreated) {
1553 if (owner.Focused) {
1554 if (caret.height != caret.tag.height)
1555 XplatUI.CreateCaret (owner.Handle, caret_width, caret.height);
1556 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);
1559 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1562 // We set this at the end because we use the heights to determine whether or
1563 // not we need to recreate the caret
1564 caret.height = caret.tag.height;
1568 internal void PositionCaret(int x, int y) {
1569 if (!owner.IsHandleCreated) {
1573 caret.tag = FindCursor(x, y, out caret.pos);
1575 MoveCaretToTextTag ();
1577 caret.line = caret.tag.line;
1578 caret.height = caret.tag.height;
1580 if (owner.ShowSelection && (!selection_visible || owner.show_caret_w_selection)) {
1581 XplatUI.CreateCaret (owner.Handle, caret_width, caret.height);
1582 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);
1585 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1588 internal void CaretHasFocus() {
1589 if ((caret.tag != null) && owner.IsHandleCreated) {
1590 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1591 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);
1596 if (owner.IsHandleCreated && SelectionLength () > 0) {
1597 InvalidateSelectionArea ();
1601 internal void CaretLostFocus() {
1602 if (!owner.IsHandleCreated) {
1605 XplatUI.DestroyCaret(owner.Handle);
1608 internal void AlignCaret() {
1609 if (!owner.IsHandleCreated) {
1613 caret.tag = LineTag.FindTag (caret.line, caret.pos);
1615 MoveCaretToTextTag ();
1617 caret.height = caret.tag.height;
1619 if (owner.Focused) {
1620 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1621 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);
1625 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1628 internal void UpdateCaret() {
1629 if (!owner.IsHandleCreated || caret.tag == null) {
1633 MoveCaretToTextTag ();
1635 if (caret.tag.height != caret.height) {
1636 caret.height = caret.tag.height;
1637 if (owner.Focused) {
1638 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1642 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);
1646 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1649 internal void DisplayCaret() {
1650 if (!owner.IsHandleCreated) {
1654 if (owner.ShowSelection && (!selection_visible || owner.show_caret_w_selection)) {
1655 XplatUI.CaretVisible(owner.Handle, true);
1659 internal void HideCaret() {
1660 if (!owner.IsHandleCreated) {
1664 if (owner.Focused) {
1665 XplatUI.CaretVisible(owner.Handle, false);
1670 internal void MoveCaretToTextTag ()
1672 if (caret.tag == null || caret.tag.IsTextTag)
1677 if (caret.pos < caret.tag.start) {
1678 caret.tag = caret.tag.previous;
1680 caret.tag = caret.tag.next;
1684 internal void MoveCaret(CaretDirection direction) {
1685 // FIXME should we use IsWordSeparator to detect whitespace, instead
1686 // of looking for actual spaces in the Word move cases?
1688 bool nowrap = false;
1690 case CaretDirection.CharForwardNoWrap:
1692 goto case CaretDirection.CharForward;
1693 case CaretDirection.CharForward: {
1695 if (caret.pos > caret.line.TextLengthWithoutEnding ()) {
1697 // Go into next line
1698 if (caret.line.line_no < this.lines) {
1699 caret.line = GetLine(caret.line.line_no+1);
1701 caret.tag = caret.line.tags;
1706 // Single line; we stay where we are
1710 if ((caret.tag.start - 1 + caret.tag.length) < caret.pos) {
1711 caret.tag = caret.tag.next;
1718 case CaretDirection.CharBackNoWrap:
1720 goto case CaretDirection.CharBack;
1721 case CaretDirection.CharBack: {
1722 if (caret.pos > 0) {
1723 // caret.pos--; // folded into the if below
1725 if (--caret.pos > 0) {
1726 if (caret.tag.start > caret.pos) {
1727 caret.tag = caret.tag.previous;
1731 if (caret.line.line_no > 1 && !nowrap) {
1732 caret.line = GetLine(caret.line.line_no - 1);
1733 caret.pos = caret.line.TextLengthWithoutEnding ();
1734 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1741 case CaretDirection.WordForward: {
1744 len = caret.line.text.Length;
1745 if (caret.pos < len) {
1746 while ((caret.pos < len) && (caret.line.text[caret.pos] != ' ')) {
1749 if (caret.pos < len) {
1750 // Skip any whitespace
1751 while ((caret.pos < len) && (caret.line.text[caret.pos] == ' ')) {
1755 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1757 if (caret.line.line_no < this.lines) {
1758 caret.line = GetLine(caret.line.line_no + 1);
1760 caret.tag = caret.line.tags;
1767 case CaretDirection.WordBack: {
1768 if (caret.pos > 0) {
1771 while ((caret.pos > 0) && (caret.line.text[caret.pos] == ' ')) {
1775 while ((caret.pos > 0) && (caret.line.text[caret.pos] != ' ')) {
1779 if (caret.line.text.ToString(caret.pos, 1) == " ") {
1780 if (caret.pos != 0) {
1783 caret.line = GetLine(caret.line.line_no - 1);
1784 caret.pos = caret.line.text.Length;
1787 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1789 if (caret.line.line_no > 1) {
1790 caret.line = GetLine(caret.line.line_no - 1);
1791 caret.pos = caret.line.text.Length;
1792 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1799 case CaretDirection.LineUp: {
1800 if (caret.line.line_no > 1) {
1803 pixel = (int)caret.line.widths[caret.pos];
1804 PositionCaret(pixel, GetLine(caret.line.line_no - 1).Y);
1811 case CaretDirection.LineDown: {
1812 if (caret.line.line_no < lines) {
1815 pixel = (int)caret.line.widths[caret.pos];
1816 PositionCaret(pixel, GetLine(caret.line.line_no + 1).Y);
1823 case CaretDirection.Home: {
1824 if (caret.pos > 0) {
1826 caret.tag = caret.line.tags;
1832 case CaretDirection.End: {
1833 if (caret.pos < caret.line.TextLengthWithoutEnding ()) {
1834 caret.pos = caret.line.TextLengthWithoutEnding ();
1835 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1841 case CaretDirection.PgUp: {
1843 int new_y, y_offset;
1845 if (viewport_y == 0) {
1847 // This should probably be handled elsewhere
1848 if (!(owner is RichTextBox)) {
1849 // Page down doesn't do anything in a regular TextBox
1850 // if the bottom of the document
1851 // is already visible, the page and the caret stay still
1855 // We're just placing the caret at the end of the document, no scrolling needed
1856 owner.vscroll.Value = 0;
1857 Line line = GetLine (1);
1858 PositionCaret (line, 0);
1861 y_offset = caret.line.Y - viewport_y;
1862 new_y = caret.line.Y - viewport_height;
1864 owner.vscroll.Value = Math.Max (new_y, 0);
1865 PositionCaret ((int)caret.line.widths[caret.pos], y_offset + viewport_y);
1869 case CaretDirection.PgDn: {
1870 int new_y, y_offset;
1872 if ((viewport_y + viewport_height) > document_y) {
1874 // This should probably be handled elsewhere
1875 if (!(owner is RichTextBox)) {
1876 // Page up doesn't do anything in a regular TextBox
1877 // if the bottom of the document
1878 // is already visible, the page and the caret stay still
1882 // We're just placing the caret at the end of the document, no scrolling needed
1883 owner.vscroll.Value = owner.vscroll.Maximum - viewport_height + 1;
1884 Line line = GetLine (lines);
1885 PositionCaret (line, line.Text.Length);
1888 y_offset = caret.line.Y - viewport_y;
1889 new_y = caret.line.Y + viewport_height;
1891 owner.vscroll.Value = Math.Min (new_y, owner.vscroll.Maximum - viewport_height + 1);
1892 PositionCaret ((int)caret.line.widths[caret.pos], y_offset + viewport_y);
1897 case CaretDirection.CtrlPgUp: {
1898 PositionCaret(0, viewport_y);
1903 case CaretDirection.CtrlPgDn: {
1908 tag = FindTag(0, viewport_y + viewport_height, out index, false);
1909 if (tag.line.line_no > 1) {
1910 line = GetLine(tag.line.line_no - 1);
1914 PositionCaret(line, line.Text.Length);
1919 case CaretDirection.CtrlHome: {
1920 caret.line = GetLine(1);
1922 caret.tag = caret.line.tags;
1928 case CaretDirection.CtrlEnd: {
1929 caret.line = GetLine(lines);
1930 caret.pos = caret.line.TextLengthWithoutEnding ();
1931 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1937 case CaretDirection.SelectionStart: {
1938 caret.line = selection_start.line;
1939 caret.pos = selection_start.pos;
1940 caret.tag = selection_start.tag;
1946 case CaretDirection.SelectionEnd: {
1947 caret.line = selection_end.line;
1948 caret.pos = selection_end.pos;
1949 caret.tag = selection_end.tag;
1957 internal void DumpDoc ()
1959 Console.WriteLine ("<doc lines='{0}'>", lines);
1960 for (int i = 1; i <= lines ; i++) {
1961 Line line = GetLine (i);
1962 Console.WriteLine ("<line no='{0}' ending='{1}'>", line.line_no, line.ending);
1964 LineTag tag = line.tags;
1965 while (tag != null) {
1966 Console.Write ("\t<tag type='{0}' span='{1}->{2}' font='{3}' color='{4}'>",
1967 tag.GetType (), tag.start, tag.length, tag.font, tag.color.Color);
1968 Console.Write (tag.Text ());
1969 Console.WriteLine ("</tag>");
1972 Console.WriteLine ("</line>");
1974 Console.WriteLine ("</doc>");
1977 internal void Draw (Graphics g, Rectangle clip)
1979 Line line; // Current line being drawn
1980 LineTag tag; // Current tag being drawn
1981 int start; // First line to draw
1982 int end; // Last line to draw
1983 StringBuilder text; // String representing the current line
1986 Brush current_brush;
1987 Brush disabled_brush;
1988 Brush readonly_brush;
1992 // First, figure out from what line to what line we need to draw
1995 start = GetLineByPixel(clip.Top + viewport_y, false).line_no;
1996 end = GetLineByPixel(clip.Bottom + viewport_y, false).line_no;
1998 start = GetLineByPixel(clip.Left + viewport_x, false).line_no;
1999 end = GetLineByPixel(clip.Right + viewport_x, false).line_no;
2003 /// We draw the single border ourself
2005 if (owner.actual_border_style == BorderStyle.FixedSingle) {
2006 ControlPaint.DrawBorder (g, owner.ClientRectangle, Color.Black, ButtonBorderStyle.Solid);
2009 /// Make sure that we aren't drawing one more line then we need to
2010 line = GetLine (end - 1);
2011 if (line != null && clip.Bottom == line.Y + line.height + viewport_y)
2017 DateTime n = DateTime.Now;
2018 Console.WriteLine ("Started drawing: {0}s {1}ms", n.Second, n.Millisecond);
2019 Console.WriteLine ("CLIP: {0}", clip);
2020 Console.WriteLine ("S: {0}", GetLine (start).text);
2021 Console.WriteLine ("E: {0}", GetLine (end).text);
2024 disabled_brush = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorGrayText);
2025 readonly_brush = ThemeEngine.Current.ResPool.GetSolidBrush (ThemeEngine.Current.ColorControlText);
2026 hilight = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorHighlight);
2027 hilight_text = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorHighlightText);
2029 // Non multiline selection can be handled outside of the loop
2030 if (!multiline && selection_visible && owner.ShowSelection) {
2031 g.FillRectangle (hilight,
2032 selection_start.line.widths [selection_start.pos] +
2033 selection_start.line.X - viewport_x,
2034 selection_start.line.Y,
2035 (selection_end.line.X + selection_end.line.widths [selection_end.pos]) -
2036 (selection_start.line.X + selection_start.line.widths [selection_start.pos]),
2037 selection_start.line.height);
2040 while (line_no <= end) {
2041 line = GetLine (line_no);
2042 float line_y = line.Y - viewport_y;
2048 if (PasswordCache.Length < line.text.Length)
2049 PasswordCache.Append(Char.Parse(password_char), line.text.Length - PasswordCache.Length);
2050 else if (PasswordCache.Length > line.text.Length)
2051 PasswordCache.Remove(line.text.Length, PasswordCache.Length - line.text.Length);
2052 text = PasswordCache;
2055 int line_selection_start = text.Length + 1;
2056 int line_selection_end = text.Length + 1;
2057 if (selection_visible && owner.ShowSelection &&
2058 (line_no >= selection_start.line.line_no) &&
2059 (line_no <= selection_end.line.line_no)) {
2061 if (line_no == selection_start.line.line_no)
2062 line_selection_start = selection_start.pos + 1;
2064 line_selection_start = 1;
2066 if (line_no == selection_end.line.line_no)
2067 line_selection_end = selection_end.pos + 1;
2069 line_selection_end = text.Length + 1;
2071 if (line_selection_end == line_selection_start) {
2072 // There isn't really selection
2073 line_selection_start = text.Length + 1;
2074 line_selection_end = line_selection_start;
2075 } else if (multiline) {
2076 // lets draw some selection baby!! (non multiline selection is drawn outside the loop)
2077 g.FillRectangle (hilight,
2078 line.widths [line_selection_start - 1] + line.X - viewport_x,
2079 line_y, line.widths [line_selection_end - 1] - line.widths [line_selection_start - 1],
2084 current_brush = line.tags.color;
2085 while (tag != null) {
2088 if (tag.length == 0) {
2093 if (((tag.X + tag.width) < (clip.Left - viewport_x)) && (tag.X > (clip.Right - viewport_x))) {
2098 if (tag.back_color != null) {
2099 g.FillRectangle (tag.back_color, tag.X + line.X - viewport_x,
2100 line_y + tag.shift, tag.width, line.height);
2103 tag_brush = tag.color;
2104 current_brush = tag_brush;
2106 if (!owner.is_enabled) {
2107 Color a = ((SolidBrush) tag.color).Color;
2108 Color b = ThemeEngine.Current.ColorWindowText;
2110 if ((a.R == b.R) && (a.G == b.G) && (a.B == b.B)) {
2111 tag_brush = disabled_brush;
2113 } else if (owner.read_only && !owner.backcolor_set) {
2114 tag_brush = readonly_brush;
2117 int tag_pos = tag.start;
2118 current_brush = tag_brush;
2119 while (tag_pos < tag.start + tag.length) {
2120 int old_tag_pos = tag_pos;
2122 if (tag_pos >= line_selection_start && tag_pos < line_selection_end) {
2123 current_brush = hilight_text;
2124 tag_pos = Math.Min (tag.end, line_selection_end);
2125 } else if (tag_pos < line_selection_start) {
2126 current_brush = tag_brush;
2127 tag_pos = Math.Min (tag.end, line_selection_start);
2129 current_brush = tag_brush;
2133 tag.Draw (g, current_brush,
2134 line.widths [Math.Max (0, old_tag_pos - 1)] + line.X - viewport_x,
2136 old_tag_pos - 1, Math.Min (tag.length, tag_pos - old_tag_pos),
2142 line.DrawEnding (g, line_y);
2147 internal int GetLineEnding (string line, int start, out LineEnding ending)
2151 res = line.IndexOf ('\r', start);
2153 if (res + 2 < line.Length && line [res + 1] == '\r' && line [res + 2] == '\n') {
2154 ending = LineEnding.Soft;
2157 if (res + 1 < line.Length && line [res + 1] == '\n') {
2158 ending = LineEnding.Hard;
2161 ending = LineEnding.Limp;
2165 res = line.IndexOf ('\n', start);
2167 ending = LineEnding.Rich;
2171 ending = LineEnding.Wrap;
2175 internal int LineEndingLength (LineEnding ending)
2180 case LineEnding.Limp:
2181 case LineEnding.Rich:
2184 case LineEnding.Hard:
2187 case LineEnding.Soft:
2195 internal string LineEndingToString (LineEnding ending)
2197 string res = String.Empty;
2199 case LineEnding.Limp:
2202 case LineEnding.Hard:
2205 case LineEnding.Soft:
2208 case LineEnding.Rich:
2216 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
2217 internal void Insert(Line line, int pos, bool update_caret, string s) {
2223 LineTag tag = LineTag.FindTag (line, pos);
2227 base_line = line.line_no;
2228 old_line_count = lines;
2230 break_index = GetLineEnding (s, 0, out ending);
2232 // Bump the text at insertion point a line down if we're inserting more than one line
2233 if (break_index != s.Length) {
2235 line.ending = ending;
2236 // Remainder of start line is now in base_line + 1
2239 InsertString (line, pos, s.Substring (0, break_index + LineEndingLength (ending)));
2241 break_index += LineEndingLength (ending);
2242 while (break_index < s.Length) {
2243 int next_break = GetLineEnding (s, break_index, out ending);
2244 string line_text = s.Substring (break_index, next_break - break_index +
2245 LineEndingLength (ending));
2247 Add (base_line + count, line_text, line.alignment, tag.font, tag.color, ending);
2249 Line last = GetLine (base_line + count);
2250 last.ending = ending;
2253 break_index = next_break + LineEndingLength (ending);
2256 ResumeRecalc (true);
2258 UpdateView(line, lines - old_line_count + 1, pos);
2261 // Move caret to the end of the inserted text
2262 Line l = GetLine (line.line_no + lines - old_line_count);
2263 PositionCaret(l, l.text.Length);
2268 // Inserts a character at the given position
2269 internal void InsertString(Line line, int pos, string s) {
2270 InsertString(line.FindTag(pos), pos, s);
2273 // Inserts a string at the given position
2274 internal void InsertString(LineTag tag, int pos, string s) {
2283 line.text.Insert(pos, s);
2286 while (tag != null) {
2293 UpdateView(line, pos);
2296 // Inserts a string at the caret position
2297 internal void InsertStringAtCaret(string s, bool move_caret) {
2299 InsertString (caret.tag, caret.pos, s);
2301 UpdateView(caret.line, caret.pos);
2303 caret.pos += s.Length;
2310 // Inserts a character at the given position
2311 internal void InsertChar(Line line, int pos, char ch) {
2312 InsertChar(line.FindTag(pos), pos, ch);
2315 // Inserts a character at the given position
2316 internal void InsertChar(LineTag tag, int pos, char ch) {
2322 line.text.Insert(pos, ch);
2325 while (tag != null) {
2332 undo.RecordTyping (line, pos, ch);
2333 UpdateView(line, pos);
2336 // Inserts a character at the current caret position
2337 internal void InsertCharAtCaret(char ch, bool move_caret) {
2343 caret.line.text.Insert(caret.pos, ch);
2346 if (caret.tag.next != null) {
2347 tag = caret.tag.next;
2348 while (tag != null) {
2354 caret.line.recalc = true;
2356 InsertChar (caret.tag, caret.pos, ch);
2358 UpdateView(caret.line, caret.pos);
2362 SetSelectionToCaret(true);
2367 internal void InsertPicture (Line line, int pos, RTF.Picture picture)
2375 // Just a place holder basically
2376 line.text.Insert (pos, "I");
2378 PictureTag picture_tag = new PictureTag (line, pos + 1, picture);
2380 tag = LineTag.FindTag (line, pos);
2381 picture_tag.CopyFormattingFrom (tag);
2382 next_tag = tag.Break (pos + 1);
2383 picture_tag.previous = tag;
2384 picture_tag.next = tag.next;
2385 tag.next = picture_tag;
2388 // Picture tags need to be surrounded by text tags
2390 if (picture_tag.next == null) {
2391 picture_tag.next = new LineTag (line, pos + 1);
2392 picture_tag.next.CopyFormattingFrom (tag);
2393 picture_tag.next.previous = picture_tag;
2396 tag = picture_tag.next;
2397 while (tag != null) {
2405 UpdateView (line, pos);
2408 internal void DeleteMultiline (Line start_line, int pos, int length)
2410 Marker start = new Marker ();
2411 Marker end = new Marker ();
2412 int start_index = LineTagToCharIndex (start_line, pos);
2414 start.line = start_line;
2416 start.tag = LineTag.FindTag (start_line, pos);
2418 CharIndexToLineTag (start_index + length, out end.line,
2419 out end.tag, out end.pos);
2423 if (start.line == end.line) {
2424 DeleteChars (start.tag, pos, end.pos - pos);
2427 // Delete first and last lines
2428 DeleteChars (start.tag, start.pos, start.line.text.Length - start.pos);
2429 DeleteChars (end.line.tags, 0, end.pos);
2431 int current = start.line.line_no + 1;
2432 if (current < end.line.line_no) {
2433 for (int i = end.line.line_no - 1; i >= current; i--) {
2438 // BIG FAT WARNING - selection_end.line might be stale due
2439 // to the above Delete() call. DONT USE IT before hitting the end of this method!
2441 // Join start and end
2442 Combine (start.line.line_no, current);
2445 ResumeUpdate (true);
2449 // Deletes n characters at the given position; it will not delete past line limits
2451 internal void DeleteChars(LineTag tag, int pos, int count) {
2460 if (pos == line.text.Length) {
2464 line.text.Remove(pos, count);
2466 // Make sure the tag points to the right spot
2467 while ((tag != null) && (tag.end) < pos) {
2475 // Check if we're crossing tag boundaries
2476 if ((pos + count) > (tag.start + tag.length - 1)) {
2479 // We have to delete cross tag boundaries
2483 left -= tag.start + tag.length - pos - 1;
2486 while ((tag != null) && (left > 0)) {
2487 tag.start -= count - left;
2489 if (tag.length > left) {
2498 // We got off easy, same tag
2500 if (tag.length == 0) {
2505 // Delete empty orphaned tags at the end
2507 while (walk != null && walk.next != null && walk.next.length == 0) {
2509 walk.next = walk.next.next;
2510 if (walk.next != null)
2511 walk.next.previous = t;
2515 // Adjust the start point of any tags following
2518 while (tag != null) {
2526 line.Streamline(lines);
2530 if (pos >= line.TextLengthWithoutEnding ()) {
2531 LineEnding ending = line.ending;
2532 GetLineEnding (line.text.ToString (), 0, out ending);
2533 if (ending != line.ending) {
2534 line.ending = ending;
2537 UpdateView (line, lines, pos);
2538 owner.Invalidate ();
2544 UpdateView (line, lines, pos);
2545 owner.Invalidate ();
2547 UpdateView(line, pos);
2550 // Deletes a character at or after the given position (depending on forward); it will not delete past line limits
2551 internal void DeleteChar(LineTag tag, int pos, bool forward) {
2560 if ((pos == 0 && forward == false) || (pos == line.text.Length && forward == true)) {
2566 line.text.Remove(pos, 1);
2568 while ((tag != null) && (tag.start + tag.length - 1) <= pos) {
2578 if (tag.length == 0) {
2583 line.text.Remove(pos, 1);
2584 if (pos >= (tag.start - 1)) {
2586 if (tag.length == 0) {
2589 } else if (tag.previous != null) {
2590 // tag.previous.length--;
2591 if (tag.previous.length == 0) {
2597 // Delete empty orphaned tags at the end
2599 while (walk != null && walk.next != null && walk.next.length == 0) {
2601 walk.next = walk.next.next;
2602 if (walk.next != null)
2603 walk.next.previous = t;
2608 while (tag != null) {
2614 line.Streamline(lines);
2618 if (pos >= line.TextLengthWithoutEnding ()) {
2619 LineEnding ending = line.ending;
2620 GetLineEnding (line.text.ToString (), 0, out ending);
2621 if (ending != line.ending) {
2622 line.ending = ending;
2625 UpdateView (line, lines, pos);
2626 owner.Invalidate ();
2632 UpdateView (line, lines, pos);
2633 owner.Invalidate ();
2635 UpdateView(line, pos);
2638 // Combine two lines
2639 internal void Combine(int FirstLine, int SecondLine) {
2640 Combine(GetLine(FirstLine), GetLine(SecondLine));
2643 internal void Combine(Line first, Line second) {
2647 // strip the ending off of the first lines text
2648 first.text.Length = first.text.Length - LineEndingLength (first.ending);
2650 // Combine the two tag chains into one
2653 // Maintain the line ending style
2654 first.ending = second.ending;
2656 while (last.next != null) {
2660 // need to get the shift before setting the next tag since that effects length
2661 shift = last.start + last.length - 1;
2662 last.next = second.tags;
2663 last.next.previous = last;
2665 // Fix up references within the chain
2667 while (last != null) {
2669 last.start += shift;
2673 // Combine both lines' strings
2674 first.text.Insert(first.text.Length, second.text.ToString());
2675 first.Grow(first.text.Length);
2677 // Remove the reference to our (now combined) tags from the doomed line
2681 DecrementLines(first.line_no + 2); // first.line_no + 1 will be deleted, so we need to start renumbering one later
2684 first.recalc = true;
2685 first.height = 0; // This forces RecalcDocument/UpdateView to redraw from this line on
2686 first.Streamline(lines);
2688 // Update Caret, Selection, etc
2689 if (caret.line == second) {
2690 caret.Combine(first, shift);
2692 if (selection_anchor.line == second) {
2693 selection_anchor.Combine(first, shift);
2695 if (selection_start.line == second) {
2696 selection_start.Combine(first, shift);
2698 if (selection_end.line == second) {
2699 selection_end.Combine(first, shift);
2706 check_first = GetLine(first.line_no);
2707 check_second = GetLine(check_first.line_no + 1);
2709 Console.WriteLine("Pre-delete: Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2712 this.Delete(second);
2715 check_first = GetLine(first.line_no);
2716 check_second = GetLine(check_first.line_no + 1);
2718 Console.WriteLine("Post-delete Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2722 // Split the line at the position into two
2723 internal void Split(int LineNo, int pos) {
2727 line = GetLine(LineNo);
2728 tag = LineTag.FindTag(line, pos);
2729 Split(line, tag, pos);
2732 internal void Split(Line line, int pos) {
2735 tag = LineTag.FindTag(line, pos);
2736 Split(line, tag, pos);
2739 ///<summary>Split line at given tag and position into two lines</summary>
2740 ///if more space becomes available on previous line</param>
2741 internal void Split(Line line, LineTag tag, int pos) {
2745 bool move_sel_start;
2749 move_sel_start = false;
2750 move_sel_end = false;
2752 // Adjust selection and cursors
2753 if (caret.line == line && caret.pos >= pos) {
2756 if (selection_start.line == line && selection_start.pos > pos) {
2757 move_sel_start = true;
2760 if (selection_end.line == line && selection_end.pos > pos) {
2761 move_sel_end = true;
2764 // cover the easy case first
2765 if (pos == line.text.Length) {
2766 Add (line.line_no + 1, String.Empty, line.alignment, tag.font, tag.color, line.ending);
2768 new_line = GetLine (line.line_no + 1);
2771 caret.line = new_line;
2772 caret.tag = new_line.tags;
2776 if (move_sel_start) {
2777 selection_start.line = new_line;
2778 selection_start.pos = 0;
2779 selection_start.tag = new_line.tags;
2783 selection_end.line = new_line;
2784 selection_end.pos = 0;
2785 selection_end.tag = new_line.tags;
2790 // We need to move the rest of the text into the new line
2791 Add (line.line_no + 1, line.text.ToString (pos, line.text.Length - pos), line.alignment, tag.font, tag.color, line.ending);
2793 // Now transfer our tags from this line to the next
2794 new_line = GetLine(line.line_no + 1);
2797 new_line.recalc = true;
2799 if ((tag.start - 1) == pos) {
2802 // We can simply break the chain and move the tag into the next line
2803 if (tag == line.tags) {
2804 new_tag = new LineTag(line, 1);
2805 new_tag.CopyFormattingFrom (tag);
2806 line.tags = new_tag;
2809 if (tag.previous != null) {
2810 tag.previous.next = null;
2812 new_line.tags = tag;
2813 tag.previous = null;
2814 tag.line = new_line;
2816 // Walk the list and correct the start location of the tags we just bumped into the next line
2817 shift = tag.start - 1;
2820 while (new_tag != null) {
2821 new_tag.start -= shift;
2822 new_tag.line = new_line;
2823 new_tag = new_tag.next;
2828 new_tag = new LineTag (new_line, 1);
2829 new_tag.next = tag.next;
2830 new_tag.CopyFormattingFrom (tag);
2831 new_line.tags = new_tag;
2832 if (new_tag.next != null) {
2833 new_tag.next.previous = new_tag;
2838 new_tag = new_tag.next;
2839 while (new_tag != null) {
2840 new_tag.start -= shift;
2841 new_tag.line = new_line;
2842 new_tag = new_tag.next;
2848 caret.line = new_line;
2849 caret.pos = caret.pos - pos;
2850 caret.tag = caret.line.FindTag(caret.pos);
2853 if (move_sel_start) {
2854 selection_start.line = new_line;
2855 selection_start.pos = selection_start.pos - pos;
2856 selection_start.tag = new_line.FindTag(selection_start.pos);
2860 selection_end.line = new_line;
2861 selection_end.pos = selection_end.pos - pos;
2862 selection_end.tag = new_line.FindTag(selection_end.pos);
2865 CharCount -= line.text.Length - pos;
2866 line.text.Remove(pos, line.text.Length - pos);
2869 // Adds a line of text, with given font.
2870 // Bumps any line at that line number that already exists down
2871 internal void Add (int LineNo, string Text, Font font, SolidBrush color, LineEnding ending)
2873 Add (LineNo, Text, alignment, font, color, ending);
2876 internal void Add (int LineNo, string Text, HorizontalAlignment align, Font font, SolidBrush color, LineEnding ending)
2882 CharCount += Text.Length;
2884 if (LineNo<1 || Text == null) {
2886 throw new ArgumentNullException("LineNo", "Line numbers must be positive");
2888 throw new ArgumentNullException("Text", "Cannot insert NULL line");
2892 add = new Line (this, LineNo, Text, align, font, color, ending);
2895 while (line != sentinel) {
2897 line_no = line.line_no;
2899 if (LineNo > line_no) {
2901 } else if (LineNo < line_no) {
2904 // Bump existing line numbers; walk all nodes to the right of this one and increment line_no
2905 IncrementLines(line.line_no);
2910 add.left = sentinel;
2911 add.right = sentinel;
2913 if (add.parent != null) {
2914 if (LineNo > add.parent.line_no) {
2915 add.parent.right = add;
2917 add.parent.left = add;
2924 RebalanceAfterAdd(add);
2929 internal virtual void Clear() {
2932 document = sentinel;
2935 public virtual object Clone() {
2938 clone = new Document(null);
2940 clone.lines = this.lines;
2941 clone.document = (Line)document.Clone();
2946 internal void Delete(int LineNo) {
2953 line = GetLine(LineNo);
2955 CharCount -= line.text.Length;
2957 DecrementLines(LineNo + 1);
2961 internal void Delete(Line line1) {
2962 Line line2;// = new Line();
2965 if ((line1.left == sentinel) || (line1.right == sentinel)) {
2968 line3 = line1.right;
2969 while (line3.left != sentinel) {
2974 if (line3.left != sentinel) {
2977 line2 = line3.right;
2980 line2.parent = line3.parent;
2981 if (line3.parent != null) {
2982 if(line3 == line3.parent.left) {
2983 line3.parent.left = line2;
2985 line3.parent.right = line2;
2991 if (line3 != line1) {
2994 if (selection_start.line == line3) {
2995 selection_start.line = line1;
2998 if (selection_end.line == line3) {
2999 selection_end.line = line1;
3002 if (selection_anchor.line == line3) {
3003 selection_anchor.line = line1;
3006 if (caret.line == line3) {
3011 line1.alignment = line3.alignment;
3012 line1.ascent = line3.ascent;
3013 line1.hanging_indent = line3.hanging_indent;
3014 line1.height = line3.height;
3015 line1.indent = line3.indent;
3016 line1.line_no = line3.line_no;
3017 line1.recalc = line3.recalc;
3018 line1.right_indent = line3.right_indent;
3019 line1.ending = line3.ending;
3020 line1.space = line3.space;
3021 line1.tags = line3.tags;
3022 line1.text = line3.text;
3023 line1.widths = line3.widths;
3024 line1.offset = line3.offset;
3027 while (tag != null) {
3033 if (line3.color == LineColor.Black)
3034 RebalanceAfterDelete(line2);
3039 // Invalidate a section of the document to trigger redraw
3040 internal void Invalidate(Line start, int start_pos, Line end, int end_pos) {
3046 if ((start == end) && (start_pos == end_pos)) {
3050 if (end_pos == -1) {
3051 end_pos = end.text.Length;
3054 // figure out what's before what so the logic below is straightforward
3055 if (start.line_no < end.line_no) {
3061 } else if (start.line_no > end.line_no) {
3068 if (start_pos < end_pos) {
3082 int endpoint = (int) l1.widths [p2];
3083 if (p2 == l1.text.Length + 1) {
3084 endpoint = (int) viewport_width;
3088 Console.WriteLine("Invaliding backwards from {0}:{1} to {2}:{3} {4}",
3089 l1.line_no, p1, l2.line_no, p2,
3091 (int)l1.widths[p1] + l1.X - viewport_x,
3099 owner.Invalidate(new Rectangle (
3100 (int)l1.widths[p1] + l1.X - viewport_x,
3102 endpoint - (int)l1.widths[p1] + 1,
3108 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);
3109 Console.WriteLine ("invalidate start line: {0} position: {1}", l1.text, p1);
3112 // Three invalidates:
3113 // First line from start
3114 owner.Invalidate(new Rectangle((int)l1.widths[p1] + l1.X - viewport_x, l1.Y - viewport_y, viewport_width, l1.height));
3118 if ((l1.line_no + 1) < l2.line_no) {
3121 y = GetLine(l1.line_no + 1).Y;
3122 owner.Invalidate(new Rectangle(0, y - viewport_y, viewport_width, l2.Y - y));
3125 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);
3131 owner.Invalidate(new Rectangle((int)l2.widths[0] + l2.X - viewport_x, l2.Y - viewport_y, (int)l2.widths[p2] + 1, l2.height));
3133 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);
3138 /// <summary>Select text around caret</summary>
3139 internal void ExpandSelection(CaretSelection mode, bool to_caret) {
3141 // We're expanding the selection to the caret position
3143 case CaretSelection.Line: {
3144 // Invalidate the selection delta
3145 if (caret > selection_prev) {
3146 Invalidate(selection_prev.line, 0, caret.line, caret.line.text.Length);
3148 Invalidate(selection_prev.line, selection_prev.line.text.Length, caret.line, 0);
3151 if (caret.line.line_no <= selection_anchor.line.line_no) {
3152 selection_start.line = caret.line;
3153 selection_start.tag = caret.line.tags;
3154 selection_start.pos = 0;
3156 selection_end.line = selection_anchor.line;
3157 selection_end.tag = selection_anchor.tag;
3158 selection_end.pos = selection_anchor.pos;
3160 selection_end_anchor = true;
3162 selection_start.line = selection_anchor.line;
3163 selection_start.pos = selection_anchor.height;
3164 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height);
3166 selection_end.line = caret.line;
3167 selection_end.tag = caret.line.tags;
3168 selection_end.pos = caret.line.text.Length;
3170 selection_end_anchor = false;
3172 selection_prev.line = caret.line;
3173 selection_prev.tag = caret.tag;
3174 selection_prev.pos = caret.pos;
3179 case CaretSelection.Word: {
3183 start_pos = FindWordSeparator(caret.line, caret.pos, false);
3184 end_pos = FindWordSeparator(caret.line, caret.pos, true);
3187 // Invalidate the selection delta
3188 if (caret > selection_prev) {
3189 Invalidate(selection_prev.line, selection_prev.pos, caret.line, end_pos);
3191 Invalidate(selection_prev.line, selection_prev.pos, caret.line, start_pos);
3193 if (caret < selection_anchor) {
3194 selection_start.line = caret.line;
3195 selection_start.tag = caret.line.FindTag(start_pos);
3196 selection_start.pos = start_pos;
3198 selection_end.line = selection_anchor.line;
3199 selection_end.tag = selection_anchor.tag;
3200 selection_end.pos = selection_anchor.pos;
3202 selection_prev.line = caret.line;
3203 selection_prev.tag = caret.tag;
3204 selection_prev.pos = start_pos;
3206 selection_end_anchor = true;
3208 selection_start.line = selection_anchor.line;
3209 selection_start.pos = selection_anchor.height;
3210 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height);
3212 selection_end.line = caret.line;
3213 selection_end.tag = caret.line.FindTag(end_pos);
3214 selection_end.pos = end_pos;
3216 selection_prev.line = caret.line;
3217 selection_prev.tag = caret.tag;
3218 selection_prev.pos = end_pos;
3220 selection_end_anchor = false;
3225 case CaretSelection.Position: {
3226 SetSelectionToCaret(false);
3231 // We're setting the selection 'around' the caret position
3233 case CaretSelection.Line: {
3234 this.Invalidate(caret.line, 0, caret.line, caret.line.text.Length);
3236 selection_start.line = caret.line;
3237 selection_start.tag = caret.line.tags;
3238 selection_start.pos = 0;
3240 selection_end.line = caret.line;
3241 selection_end.pos = caret.line.text.Length;
3242 selection_end.tag = caret.line.FindTag(selection_end.pos);
3244 selection_anchor.line = selection_end.line;
3245 selection_anchor.tag = selection_end.tag;
3246 selection_anchor.pos = selection_end.pos;
3247 selection_anchor.height = 0;
3249 selection_prev.line = caret.line;
3250 selection_prev.tag = caret.tag;
3251 selection_prev.pos = caret.pos;
3253 this.selection_end_anchor = true;
3258 case CaretSelection.Word: {
3262 start_pos = FindWordSeparator(caret.line, caret.pos, false);
3263 end_pos = FindWordSeparator(caret.line, caret.pos, true);
3265 this.Invalidate(selection_start.line, start_pos, caret.line, end_pos);
3267 selection_start.line = caret.line;
3268 selection_start.tag = caret.line.FindTag(start_pos);
3269 selection_start.pos = start_pos;
3271 selection_end.line = caret.line;
3272 selection_end.tag = caret.line.FindTag(end_pos);
3273 selection_end.pos = end_pos;
3275 selection_anchor.line = selection_end.line;
3276 selection_anchor.tag = selection_end.tag;
3277 selection_anchor.pos = selection_end.pos;
3278 selection_anchor.height = start_pos;
3280 selection_prev.line = caret.line;
3281 selection_prev.tag = caret.tag;
3282 selection_prev.pos = caret.pos;
3284 this.selection_end_anchor = true;
3291 SetSelectionVisible (!(selection_start == selection_end));
3294 internal void SetSelectionToCaret(bool start) {
3296 // Invalidate old selection; selection is being reset to empty
3297 this.Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3299 selection_start.line = caret.line;
3300 selection_start.tag = caret.tag;
3301 selection_start.pos = caret.pos;
3303 // start always also selects end
3304 selection_end.line = caret.line;
3305 selection_end.tag = caret.tag;
3306 selection_end.pos = caret.pos;
3308 selection_anchor.line = caret.line;
3309 selection_anchor.tag = caret.tag;
3310 selection_anchor.pos = caret.pos;
3312 // Invalidate from previous end to caret (aka new end)
3313 if (selection_end_anchor) {
3314 if (selection_start != caret) {
3315 this.Invalidate(selection_start.line, selection_start.pos, caret.line, caret.pos);
3318 if (selection_end != caret) {
3319 this.Invalidate(selection_end.line, selection_end.pos, caret.line, caret.pos);
3323 if (caret < selection_anchor) {
3324 selection_start.line = caret.line;
3325 selection_start.tag = caret.tag;
3326 selection_start.pos = caret.pos;
3328 selection_end.line = selection_anchor.line;
3329 selection_end.tag = selection_anchor.tag;
3330 selection_end.pos = selection_anchor.pos;
3332 selection_end_anchor = true;
3334 selection_start.line = selection_anchor.line;
3335 selection_start.tag = selection_anchor.tag;
3336 selection_start.pos = selection_anchor.pos;
3338 selection_end.line = caret.line;
3339 selection_end.tag = caret.tag;
3340 selection_end.pos = caret.pos;
3342 selection_end_anchor = false;
3346 SetSelectionVisible (!(selection_start == selection_end));
3349 internal void SetSelection(Line start, int start_pos, Line end, int end_pos) {
3350 if (selection_visible) {
3351 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3354 if ((end.line_no < start.line_no) || ((end == start) && (end_pos <= start_pos))) {
3355 selection_start.line = end;
3356 selection_start.tag = LineTag.FindTag(end, end_pos);
3357 selection_start.pos = end_pos;
3359 selection_end.line = start;
3360 selection_end.tag = LineTag.FindTag(start, start_pos);
3361 selection_end.pos = start_pos;
3363 selection_end_anchor = true;
3365 selection_start.line = start;
3366 selection_start.tag = LineTag.FindTag(start, start_pos);
3367 selection_start.pos = start_pos;
3369 selection_end.line = end;
3370 selection_end.tag = LineTag.FindTag(end, end_pos);
3371 selection_end.pos = end_pos;
3373 selection_end_anchor = false;
3376 selection_anchor.line = start;
3377 selection_anchor.tag = selection_start.tag;
3378 selection_anchor.pos = start_pos;
3380 if (((start == end) && (start_pos == end_pos)) || start == null || end == null) {
3381 SetSelectionVisible (false);
3383 SetSelectionVisible (true);
3384 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3388 internal void SetSelectionStart(Line start, int start_pos, bool invalidate) {
3389 // Invalidate from the previous to the new start pos
3391 Invalidate(selection_start.line, selection_start.pos, start, start_pos);
3393 selection_start.line = start;
3394 selection_start.pos = start_pos;
3395 selection_start.tag = LineTag.FindTag(start, start_pos);
3397 selection_anchor.line = start;
3398 selection_anchor.pos = start_pos;
3399 selection_anchor.tag = selection_start.tag;
3401 selection_end_anchor = false;
3404 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
3405 SetSelectionVisible (true);
3407 SetSelectionVisible (false);
3411 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3414 internal void SetSelectionStart(int character_index, bool invalidate) {
3419 if (character_index < 0) {
3423 CharIndexToLineTag(character_index, out line, out tag, out pos);
3424 SetSelectionStart(line, pos, invalidate);
3427 internal void SetSelectionEnd(Line end, int end_pos, bool invalidate) {
3429 if (end == selection_end.line && end_pos == selection_start.pos) {
3430 selection_anchor.line = selection_start.line;
3431 selection_anchor.tag = selection_start.tag;
3432 selection_anchor.pos = selection_start.pos;
3434 selection_end.line = selection_start.line;
3435 selection_end.tag = selection_start.tag;
3436 selection_end.pos = selection_start.pos;
3438 selection_end_anchor = false;
3439 } else if ((end.line_no < selection_anchor.line.line_no) || ((end == selection_anchor.line) && (end_pos <= selection_anchor.pos))) {
3440 selection_start.line = end;
3441 selection_start.tag = LineTag.FindTag(end, end_pos);
3442 selection_start.pos = end_pos;
3444 selection_end.line = selection_anchor.line;
3445 selection_end.tag = selection_anchor.tag;
3446 selection_end.pos = selection_anchor.pos;
3448 selection_end_anchor = true;
3450 selection_start.line = selection_anchor.line;
3451 selection_start.tag = selection_anchor.tag;
3452 selection_start.pos = selection_anchor.pos;
3454 selection_end.line = end;
3455 selection_end.tag = LineTag.FindTag(end, end_pos);
3456 selection_end.pos = end_pos;
3458 selection_end_anchor = false;
3461 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
3462 SetSelectionVisible (true);
3464 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3466 SetSelectionVisible (false);
3467 // ?? Do I need to invalidate here, tests seem to work without it, but I don't think they should :-s
3471 internal void SetSelectionEnd(int character_index, bool invalidate) {
3476 if (character_index < 0) {
3480 CharIndexToLineTag(character_index, out line, out tag, out pos);
3481 SetSelectionEnd(line, pos, invalidate);
3484 internal void SetSelection(Line start, int start_pos) {
3485 if (selection_visible) {
3486 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3489 selection_start.line = start;
3490 selection_start.pos = start_pos;
3491 selection_start.tag = LineTag.FindTag(start, start_pos);
3493 selection_end.line = start;
3494 selection_end.tag = selection_start.tag;
3495 selection_end.pos = start_pos;
3497 selection_anchor.line = start;
3498 selection_anchor.tag = selection_start.tag;
3499 selection_anchor.pos = start_pos;
3501 selection_end_anchor = false;
3502 SetSelectionVisible (false);
3505 internal void InvalidateSelectionArea() {
3506 Invalidate (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3509 // Return the current selection, as string
3510 internal string GetSelection() {
3511 // We return String.Empty if there is no selection
3512 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3513 return string.Empty;
3516 if (selection_start.line == selection_end.line) {
3517 return selection_start.line.text.ToString (selection_start.pos, selection_end.pos - selection_start.pos);
3524 sb = new StringBuilder();
3525 start = selection_start.line.line_no;
3526 end = selection_end.line.line_no;
3528 sb.Append(selection_start.line.text.ToString(selection_start.pos, selection_start.line.text.Length - selection_start.pos) + Environment.NewLine);
3530 if ((start + 1) < end) {
3531 for (i = start + 1; i < end; i++) {
3532 sb.Append(GetLine(i).text.ToString() + Environment.NewLine);
3536 sb.Append(selection_end.line.text.ToString(0, selection_end.pos));
3538 return sb.ToString();
3542 internal void ReplaceSelection(string s, bool select_new) {
3545 int selection_pos_on_line = selection_start.pos;
3546 int selection_start_pos = LineTagToCharIndex (selection_start.line, selection_start.pos);
3549 // First, delete any selected text
3550 if ((selection_start.pos != selection_end.pos) || (selection_start.line != selection_end.line)) {
3551 if (selection_start.line == selection_end.line) {
3552 undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3554 DeleteChars(selection_start.tag, selection_start.pos, selection_end.pos - selection_start.pos);
3556 // The tag might have been removed, we need to recalc it
3557 selection_start.tag = selection_start.line.FindTag(selection_start.pos);
3562 start = selection_start.line.line_no;
3563 end = selection_end.line.line_no;
3565 undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3567 InvalidateSelectionArea ();
3569 // Delete first line
3570 DeleteChars(selection_start.tag, selection_start.pos, selection_start.line.text.Length - selection_start.pos);
3571 selection_start.line.recalc = true;
3574 DeleteChars(selection_end.line.tags, 0, selection_end.pos);
3578 for (i = end - 1; i >= start; i--) {
3583 // BIG FAT WARNING - selection_end.line might be stale due
3584 // to the above Delete() call. DONT USE IT before hitting the end of this method!
3586 // Join start and end
3587 Combine(selection_start.line.line_no, start);
3592 Insert(selection_start.line, selection_start.pos, false, s);
3593 undo.RecordInsertString (selection_start.line, selection_start.pos, s);
3594 ResumeRecalc (false);
3597 CharIndexToLineTag(selection_start_pos + s.Length, out selection_start.line,
3598 out selection_start.tag, out selection_start.pos);
3600 selection_end.line = selection_start.line;
3601 selection_end.pos = selection_start.pos;
3602 selection_end.tag = selection_start.tag;
3603 selection_anchor.line = selection_start.line;
3604 selection_anchor.pos = selection_start.pos;
3605 selection_anchor.tag = selection_start.tag;
3607 SetSelectionVisible (false);
3609 CharIndexToLineTag(selection_start_pos, out selection_start.line,
3610 out selection_start.tag, out selection_start.pos);
3612 CharIndexToLineTag(selection_start_pos + s.Length, out selection_end.line,
3613 out selection_end.tag, out selection_end.pos);
3615 selection_anchor.line = selection_start.line;
3616 selection_anchor.pos = selection_start.pos;
3617 selection_anchor.tag = selection_start.tag;
3619 SetSelectionVisible (true);
3622 PositionCaret (selection_start.line, selection_start.pos);
3623 UpdateView (selection_start.line, selection_pos_on_line);
3626 internal void CharIndexToLineTag(int index, out Line line_out, out LineTag tag_out, out int pos) {
3635 for (i = 1; i <= lines; i++) {
3639 chars += line.text.Length;
3641 if (index <= chars) {
3642 // we found the line
3645 while (tag != null) {
3646 if (index < (start + tag.start + tag.length)) {
3648 tag_out = LineTag.GetFinalTag (tag);
3649 pos = index - start;
3652 if (tag.next == null) {
3655 next_line = GetLine(line.line_no + 1);
3657 if (next_line != null) {
3658 line_out = next_line;
3659 tag_out = LineTag.GetFinalTag (next_line.tags);
3664 tag_out = LineTag.GetFinalTag (tag);
3665 pos = line_out.text.Length;
3674 line_out = GetLine(lines);
3675 tag = line_out.tags;
3676 while (tag.next != null) {
3680 pos = line_out.text.Length;
3683 internal int LineTagToCharIndex(Line line, int pos) {
3687 // Count first and last line
3690 // Count the lines in the middle
3692 for (i = 1; i < line.line_no; i++) {
3693 length += GetLine(i).text.Length;
3701 internal int SelectionLength() {
3702 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3706 if (selection_start.line == selection_end.line) {
3707 return selection_end.pos - selection_start.pos;
3714 // Count first and last line
3715 length = selection_start.line.text.Length - selection_start.pos + selection_end.pos + crlf_size;
3717 // Count the lines in the middle
3718 start = selection_start.line.line_no + 1;
3719 end = selection_end.line.line_no;
3722 for (i = start; i < end; i++) {
3723 Line line = GetLine (i);
3724 length += line.text.Length + LineEndingLength (line.ending);
3735 /// <summary>Give it a Line number and it returns the Line object at with that line number</summary>
3736 internal Line GetLine(int LineNo) {
3737 Line line = document;
3739 while (line != sentinel) {
3740 if (LineNo == line.line_no) {
3742 } else if (LineNo < line.line_no) {
3752 /// <summary>Retrieve the previous tag; walks line boundaries</summary>
3753 internal LineTag PreviousTag(LineTag tag) {
3756 if (tag.previous != null) {
3757 return tag.previous;
3761 if (tag.line.line_no == 1) {
3765 l = GetLine(tag.line.line_no - 1);
3770 while (t.next != null) {
3779 /// <summary>Retrieve the next tag; walks line boundaries</summary>
3780 internal LineTag NextTag(LineTag tag) {
3783 if (tag.next != null) {
3788 l = GetLine(tag.line.line_no + 1);
3796 internal Line ParagraphStart(Line line) {
3797 while (line.ending == LineEnding.Wrap) {
3798 line = GetLine(line.line_no - 1);
3803 internal Line ParagraphEnd(Line line) {
3806 while (line.ending == LineEnding.Wrap) {
3807 l = GetLine(line.line_no + 1);
3808 if ((l == null) || (l.ending != LineEnding.Wrap)) {
3816 /// <summary>Give it a pixel offset coordinate and it returns the Line covering that are (offset
3817 /// is either X or Y depending on if we are multiline
3819 internal Line GetLineByPixel (int offset, bool exact)
3821 Line line = document;
3825 while (line != sentinel) {
3827 if ((offset >= line.Y) && (offset < (line.Y+line.height))) {
3829 } else if (offset < line.Y) {
3836 while (line != sentinel) {
3838 if ((offset >= line.X) && (offset < (line.X + line.Width)))
3840 else if (offset < line.X)
3853 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3854 internal LineTag FindTag(int x, int y, out int index, bool exact) {
3858 line = GetLineByPixel(y, exact);
3865 // Alignment adjustment
3869 if (x >= tag.X && x < (tag.X+tag.width)) {
3872 end = tag.start + tag.length - 1;
3874 for (int pos = tag.start; pos < end; pos++) {
3875 if (x < line.widths[pos]) {
3877 return LineTag.GetFinalTag (tag);
3881 return LineTag.GetFinalTag (tag);
3883 if (tag.next != null) {
3891 index = line.text.Length;
3892 return LineTag.GetFinalTag (tag);
3897 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3898 internal LineTag FindCursor(int x, int y, out int index) {
3902 line = GetLineByPixel(multiline ? y : x, false);
3905 /// Special case going leftwards of the first tag
3908 return LineTag.GetFinalTag (tag);
3912 if (x >= tag.X && x < (tag.X+tag.width)) {
3917 for (int pos = tag.start - 1; pos < end; pos++) {
3918 // When clicking on a character, we position the cursor to whatever edge
3919 // of the character the click was closer
3920 if (x < (line.X + line.widths[pos] + ((line.widths[pos+1]-line.widths[pos])/2))) {
3922 return LineTag.GetFinalTag (tag);
3926 return LineTag.GetFinalTag (tag);
3928 if (tag.next != null) {
3931 index = line.TextLengthWithoutEnding ();
3932 return LineTag.GetFinalTag (tag);
3937 /// <summary>Format area of document in specified font and color</summary>
3938 /// <param name="start_pos">1-based start position on start_line</param>
3939 /// <param name="end_pos">1-based end position on end_line </param>
3940 internal void FormatText (Line start_line, int start_pos, Line end_line, int end_pos, Font font,
3941 SolidBrush color, SolidBrush back_color, FormatSpecified specified)
3945 // First, format the first line
3946 if (start_line != end_line) {
3948 LineTag.FormatText(start_line, start_pos, start_line.text.Length - start_pos + 1, font, color, back_color, specified);
3951 LineTag.FormatText(end_line, 1, end_pos, font, color, back_color, specified);
3953 // Now all the lines inbetween
3954 for (int i = start_line.line_no + 1; i < end_line.line_no; i++) {
3956 LineTag.FormatText(l, 1, l.text.Length, font, color, back_color, specified);
3959 // Special case, single line
3960 LineTag.FormatText(start_line, start_pos, end_pos - start_pos, font, color, back_color, specified);
3964 /// <summary>Re-format areas of the document in specified font and color</summary>
3965 /// <param name="start_pos">1-based start position on start_line</param>
3966 /// <param name="end_pos">1-based end position on end_line </param>
3967 /// <param name="font">Font specifying attributes</param>
3968 /// <param name="color">Color (or NULL) to apply</param>
3969 /// <param name="apply">Attributes from font and color to apply</param>
3970 internal void FormatText(Line start_line, int start_pos, Line end_line, int end_pos, FontDefinition attributes) {
3973 // First, format the first line
3974 if (start_line != end_line) {
3976 LineTag.FormatText(start_line, start_pos, start_line.text.Length - start_pos + 1, attributes);
3979 LineTag.FormatText(end_line, 1, end_pos - 1, attributes);
3981 // Now all the lines inbetween
3982 for (int i = start_line.line_no + 1; i < end_line.line_no; i++) {
3984 LineTag.FormatText(l, 1, l.text.Length, attributes);
3987 // Special case, single line
3988 LineTag.FormatText(start_line, start_pos, end_pos - start_pos, attributes);
3992 internal void RecalculateAlignments ()
4001 while (line_no <= lines) {
4002 line = GetLine(line_no);
4005 switch (line.alignment) {
4006 case HorizontalAlignment.Left:
4007 line.align_shift = 0;
4009 case HorizontalAlignment.Center:
4010 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
4012 case HorizontalAlignment.Right:
4013 line.align_shift = viewport_width - (int)line.widths[line.text.Length] - right_margin;
4023 /// <summary>Calculate formatting for the whole document</summary>
4024 internal bool RecalculateDocument(Graphics g) {
4025 return RecalculateDocument(g, 1, this.lines, false);
4028 /// <summary>Calculate formatting starting at a certain line</summary>
4029 internal bool RecalculateDocument(Graphics g, int start) {
4030 return RecalculateDocument(g, start, this.lines, false);
4033 /// <summary>Calculate formatting within two given line numbers</summary>
4034 internal bool RecalculateDocument(Graphics g, int start, int end) {
4035 return RecalculateDocument(g, start, end, false);
4038 /// <summary>With optimize on, returns true if line heights changed</summary>
4039 internal bool RecalculateDocument(Graphics g, int start, int end, bool optimize) {
4047 if (recalc_suspended > 0) {
4048 recalc_pending = true;
4049 recalc_start = Math.Min (recalc_start, start);
4050 recalc_end = Math.Max (recalc_end, end);
4051 recalc_optimize = optimize;
4055 // Fixup the positions, they can go kinda nuts
4056 start = Math.Max (start, 1);
4057 end = Math.Min (end, lines);
4059 offset = GetLine(start).offset;
4064 changed = true; // We always return true if we run non-optimized
4069 while (line_no <= (end + this.lines - shift)) {
4070 line = GetLine(line_no++);
4071 line.offset = offset;
4075 line.RecalculateLine(g, this);
4077 if (line.recalc && line.RecalculateLine(g, this)) {
4079 // If the height changed, all subsequent lines change
4086 line.RecalculatePasswordLine(g, this);
4088 if (line.recalc && line.RecalculatePasswordLine(g, this)) {
4090 // If the height changed, all subsequent lines change
4097 if (line.widths[line.text.Length] > new_width) {
4098 new_width = (int)line.widths[line.text.Length];
4101 // Calculate alignment
4102 if (line.alignment != HorizontalAlignment.Left) {
4103 if (line.alignment == HorizontalAlignment.Center) {
4104 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
4106 line.align_shift = viewport_width - (int)line.widths[line.text.Length] - 1;
4111 offset += line.height;
4113 offset += (int) line.widths [line.text.Length];
4115 if (line_no > lines) {
4120 if (document_x != new_width) {
4121 document_x = new_width;
4122 if (WidthChanged != null) {
4123 WidthChanged(this, null);
4127 RecalculateAlignments();
4129 line = GetLine(lines);
4131 if (document_y != line.Y + line.height) {
4132 document_y = line.Y + line.height;
4133 if (HeightChanged != null) {
4134 HeightChanged(this, null);
4141 internal int Size() {
4145 private void owner_HandleCreated(object sender, EventArgs e) {
4146 RecalculateDocument(owner.CreateGraphicsInternal());
4150 private void owner_VisibleChanged(object sender, EventArgs e) {
4151 if (owner.Visible) {
4152 RecalculateDocument(owner.CreateGraphicsInternal());
4156 internal static bool IsWordSeparator (char ch)
4171 internal int FindWordSeparator(Line line, int pos, bool forward) {
4174 len = line.text.Length;
4177 for (int i = pos + 1; i < len; i++) {
4178 if (IsWordSeparator(line.Text[i])) {
4184 for (int i = pos - 1; i > 0; i--) {
4185 if (IsWordSeparator(line.Text[i - 1])) {
4193 /* Search document for text */
4194 internal bool FindChars(char[] chars, Marker start, Marker end, out Marker result) {
4200 // Search for occurence of any char in the chars array
4201 result = new Marker();
4204 line_no = start.line.line_no;
4206 while (line_no <= end.line.line_no) {
4207 line_len = line.text.Length;
4208 while (pos < line_len) {
4209 for (int i = 0; i < chars.Length; i++) {
4210 if (line.text[pos] == chars[i]) {
4212 if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
4226 line = GetLine(line_no);
4232 // This version does not build one big string for searching, instead it handles
4233 // line-boundaries, which is faster and less memory intensive
4234 // FIXME - Depending on culture stuff we might have to create a big string and use culturespecific
4235 // search stuff and change it to accept and return positions instead of Markers (which would match
4236 // RichTextBox behaviour better but would be inconsistent with the rest of TextControl)
4237 internal bool Find(string search, Marker start, Marker end, out Marker result, RichTextBoxFinds options) {
4239 string search_string;
4251 result = new Marker();
4252 word_option = ((options & RichTextBoxFinds.WholeWord) != 0);
4253 ignore_case = ((options & RichTextBoxFinds.MatchCase) == 0);
4254 reverse = ((options & RichTextBoxFinds.Reverse) != 0);
4257 line_no = start.line.line_no;
4261 // Prep our search string, lowercasing it if we do case-independent matching
4264 sb = new StringBuilder(search);
4265 for (int i = 0; i < sb.Length; i++) {
4266 sb[i] = Char.ToLower(sb[i]);
4268 search_string = sb.ToString();
4270 search_string = search;
4273 // We need to check if the character before our start position is a wordbreak
4276 if ((pos == 0) || (IsWordSeparator(line.text[pos - 1]))) {
4283 if (IsWordSeparator(line.text[pos - 1])) {
4289 // Need to check the end of the previous line
4292 prev_line = GetLine(line_no - 1);
4293 if (prev_line.ending == LineEnding.Wrap) {
4294 if (IsWordSeparator(prev_line.text[prev_line.text.Length - 1])) {
4308 // To avoid duplication of this loop with reverse logic, we search
4309 // through the document, remembering the last match and when returning
4310 // report that last remembered match
4312 last = new Marker();
4313 last.height = -1; // Abused - we use it to track change
4315 while (line_no <= end.line.line_no) {
4316 if (line_no != end.line.line_no) {
4317 line_len = line.text.Length;
4322 while (pos < line_len) {
4324 if (word_option && (current == search_string.Length)) {
4325 if (IsWordSeparator(line.text[pos])) {
4338 c = Char.ToLower(line.text[pos]);
4343 if (c == search_string[current]) {
4349 if (!word_option || (word_option && (word || (current > 0)))) {
4353 if (!word_option && (current == search_string.Length)) {
4370 if (IsWordSeparator(c)) {
4378 // Mark that we just saw a word boundary
4379 if (line.ending != LineEnding.Wrap || line.line_no == lines - 1) {
4383 if (current == search_string.Length) {
4399 line = GetLine(line_no);
4403 if (last.height != -1) {
4413 // if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
4425 internal void GetMarker(out Marker mark, bool start) {
4426 mark = new Marker();
4429 mark.line = GetLine(1);
4430 mark.tag = mark.line.tags;
4433 mark.line = GetLine(lines);
4434 mark.tag = mark.line.tags;
4435 while (mark.tag.next != null) {
4436 mark.tag = mark.tag.next;
4438 mark.pos = mark.line.text.Length;
4441 #endregion // Internal Methods
4444 internal event EventHandler CaretMoved;
4445 internal event EventHandler WidthChanged;
4446 internal event EventHandler HeightChanged;
4447 internal event EventHandler LengthChanged;
4448 #endregion // Events
4450 #region Administrative
4451 public IEnumerator GetEnumerator() {
4456 public override bool Equals(object obj) {
4461 if (!(obj is Document)) {
4469 if (ToString().Equals(((Document)obj).ToString())) {
4476 public override int GetHashCode() {
4480 public override string ToString() {
4481 return "document " + this.document_id;
4483 #endregion // Administrative
4486 internal class PictureTag : LineTag {
4488 internal RTF.Picture picture;
4490 internal PictureTag (Line line, int start, RTF.Picture picture) : base (line, start)
4492 this.picture = picture;
4495 public override bool IsTextTag {
4496 get { return false; }
4499 internal override SizeF SizeOfPosition (Graphics dc, int pos)
4501 return picture.Size;
4504 internal override int MaxHeight ()
4506 return (int) (picture.Height + 0.5F);
4509 internal override void Draw (Graphics dc, Brush brush, float x, float y, int start, int end)
4511 picture.DrawImage (dc, x, y, false);
4514 internal override void Draw (Graphics dc, Brush brush, float x, float y, int start, int end, string text)
4516 picture.DrawImage (dc, x, y, false);
4519 public override string Text ()
4525 internal class LineTag {
4526 #region Local Variables;
4527 // Payload; formatting
4528 internal Font font; // System.Drawing.Font object for this tag
4529 internal SolidBrush color; // The font color for this tag
4531 // In 2.0 tags can have background colours. I'm not going to #ifdef
4532 // at this level though since I want to reduce code paths
4533 internal SolidBrush back_color;
4536 internal int start; // start, in chars; index into Line.text
4537 internal bool r_to_l; // Which way is the font
4540 internal int height; // Height in pixels of the text this tag describes
4542 internal int ascent; // Ascent of the font for this tag
4543 internal int shift; // Shift down for this tag, to stay on baseline
4546 internal Line line; // The line we're on
4547 internal LineTag next; // Next tag on the same line
4548 internal LineTag previous; // Previous tag on the same line
4551 #region Constructors
4552 internal LineTag(Line line, int start) {
4556 #endregion // Constructors
4558 #region Internal Methods
4564 return line.X + line.widths [start - 1];
4569 get { return start + length; }
4572 public int TextEnd {
4573 get { return start + TextLength; }
4576 public float width {
4580 return line.widths [start + length - 1] - (start != 0 ? line.widths [start - 1] : 0);
4588 res = next.start - start;
4590 res = line.text.Length - (start - 1);
4592 return res > 0 ? res : 0;
4596 public int TextLength {
4600 res = next.start - start;
4602 res = line.TextLengthWithoutEnding () - (start - 1);
4604 return res > 0 ? res : 0;
4608 public virtual bool IsTextTag {
4609 get { return true; }
4612 internal virtual SizeF SizeOfPosition (Graphics dc, int pos)
4615 if (pos >= line.TextLengthWithoutEnding () && line.document.multiline)
4618 string text = line.text.ToString (pos, 1);
4619 switch ((int) text [0]) {
4622 return dc.MeasureString ("\u0013", font, 10000, Document.string_format);
4625 return dc.MeasureString (text, font, 10000, Document.string_format);
4628 internal virtual int MaxHeight ()
4633 internal virtual void Draw (Graphics dc, Brush brush, float x, float y, int start, int end)
4635 dc.DrawString (line.text.ToString (start, end), font, brush, x, y, StringFormat.GenericTypographic);
4638 internal virtual void Draw (Graphics dc, Brush brush, float x, float y, int start, int end, string text)
4640 dc.DrawString (text.Substring (start, end), font, brush, x, y, StringFormat.GenericTypographic);
4643 ///<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>
4644 internal LineTag Break(int pos) {
4649 if (pos == this.start) {
4651 } else if (pos >= (start + length)) {
4655 new_tag = new LineTag(line, pos);
4656 new_tag.CopyFormattingFrom (this);
4658 new_tag.next = this.next;
4659 this.next = new_tag;
4660 new_tag.previous = this;
4662 if (new_tag.next != null) {
4663 new_tag.next.previous = new_tag;
4669 public virtual string Text ()
4671 return line.text.ToString (start - 1, length);
4674 public void CopyFormattingFrom (LineTag other)
4676 height = other.height;
4678 color = other.color;
4679 back_color = other.back_color;
4682 ///<summary>Create new font and brush from existing font and given new attributes. Returns true if fontheight changes</summary>
4683 internal static bool GenerateTextFormat(Font font_from, SolidBrush color_from, FontDefinition attributes, out Font new_font, out SolidBrush new_color) {
4689 if (attributes.font_obj == null) {
4690 size = font_from.SizeInPoints;
4691 unit = font_from.Unit;
4692 face = font_from.Name;
4693 style = font_from.Style;
4695 if (attributes.face != null) {
4696 face = attributes.face;
4699 if (attributes.size != 0) {
4700 size = attributes.size;
4703 style |= attributes.add_style;
4704 style &= ~attributes.remove_style;
4707 new_font = new Font(face, size, style, unit);
4709 new_font = attributes.font_obj;
4712 // Create 'new' color brush
4713 if (attributes.color != Color.Empty) {
4714 new_color = new SolidBrush(attributes.color);
4716 new_color = color_from;
4719 if (new_font.Height == font_from.Height) {
4725 /// <summary>Applies 'font' and 'brush' to characters starting at 'start' for 'length' chars;
4726 /// Removes any previous tags overlapping the same area;
4727 /// returns true if lineheight has changed</summary>
4728 /// <param name="start">1-based character position on line</param>
4729 internal static bool FormatText(Line line, int start, int length, Font font, SolidBrush color, SolidBrush back_color, FormatSpecified specified)
4735 bool retval = false; // Assume line-height doesn't change
4738 if (((FormatSpecified.Font & specified) == FormatSpecified.Font) && font.Height != line.height) {
4741 line.recalc = true; // This forces recalculation of the line in RecalculateDocument
4743 // A little sanity, not sure if it's needed, might be able to remove for speed
4744 if (length > line.text.Length) {
4745 length = line.text.Length;
4749 end = start + length;
4751 // Common special case
4752 if ((start == 1) && (length == tag.length)) {
4754 SetFormat (tag, font, color, back_color, specified);
4758 start_tag = FindTag (line, start);
4759 tag = start_tag.Break (start);
4761 while (tag != null && tag.end <= end) {
4762 SetFormat (tag, font, color, back_color, specified);
4766 if (tag != null && tag.end == end)
4769 /// Now do the last tag
4770 end_tag = FindTag (line, end);
4772 if (end_tag != null) {
4773 end_tag.Break (end);
4774 SetFormat (end_tag, font, color, back_color, specified);
4780 private static void SetFormat (LineTag tag, Font font, SolidBrush color, SolidBrush back_color, FormatSpecified specified)
4782 if ((FormatSpecified.Font & specified) == FormatSpecified.Font)
4784 if ((FormatSpecified.Color & specified) == FormatSpecified.Color)
4786 if ((FormatSpecified.BackColor & specified) == FormatSpecified.BackColor) {
4787 tag.back_color = back_color;
4789 // Console.WriteLine ("setting format: {0} {1} new color {2}", color.Color, specified, tag.color.Color);
4792 /// <summary>Applies font attributes specified to characters starting at 'start' for 'length' chars;
4793 /// Breaks tags at start and end point, keeping middle tags with altered attributes.
4794 /// Returns true if lineheight has changed</summary>
4795 /// <param name="start">1-based character position on line</param>
4796 internal static bool FormatText(Line line, int start, int length, FontDefinition attributes) {
4800 bool retval = false; // Assume line-height doesn't change
4802 line.recalc = true; // This forces recalculation of the line in RecalculateDocument
4804 // A little sanity, not sure if it's needed, might be able to remove for speed
4805 if (length > line.text.Length) {
4806 length = line.text.Length;
4811 // Common special case
4812 if ((start == 1) && (length == tag.length)) {
4814 GenerateTextFormat(tag.font, tag.color, attributes, out tag.font, out tag.color);
4818 start_tag = FindTag(line, start);
4820 if (start_tag == null) {
4822 // We are 'starting' after all valid tags; create a new tag with the right attributes
4823 start_tag = FindTag(line, line.TextLengthWithoutEnding () - 1);
4824 start_tag.next = new LineTag(line, line.TextLengthWithoutEnding () + 1);
4825 start_tag.next.CopyFormattingFrom (start_tag);
4826 start_tag.next.previous = start_tag;
4827 start_tag = start_tag.next;
4829 throw new Exception(String.Format("Could not find start_tag in document at line {0} position {1}", line.line_no, start));
4832 start_tag = start_tag.Break(start);
4835 end_tag = FindTag(line, start + length);
4836 if (end_tag != null) {
4837 end_tag = end_tag.Break(start + length);
4840 // start_tag or end_tag might be null; we're cool with that
4841 // we now walk from start_tag to end_tag, applying new attributes
4843 while ((tag != null) && tag != end_tag) {
4844 if (LineTag.GenerateTextFormat(tag.font, tag.color, attributes, out tag.font, out tag.color)) {
4853 /// <summary>Finds the tag that describes the character at position 'pos' on 'line'</summary>
4854 internal static LineTag FindTag(Line line, int pos) {
4855 LineTag tag = line.tags;
4857 // Beginning of line is a bit special
4859 // Not sure if we should get the final tag here
4863 while (tag != null) {
4864 if ((tag.start <= pos) && (pos <= tag.end)) {
4865 return GetFinalTag (tag);
4874 // There can be multiple tags at the same position, we want to make
4875 // sure we are using the very last tag at the given position
4876 internal static LineTag GetFinalTag (LineTag tag)
4880 while (res.length == 0 && res.next != null && res.next.length == 0)
4886 /// <summary>Combines 'this' tag with 'other' tag</summary>
4887 internal bool Combine(LineTag other) {
4888 if (!this.Equals(other)) {
4892 this.next = other.next;
4893 if (this.next != null) {
4894 this.next.previous = this;
4901 /// <summary>Remove 'this' tag ; to be called when formatting is to be removed</summary>
4902 internal bool Remove() {
4903 if ((this.start == 1) && (this.next == null)) {
4904 // We cannot remove the only tag
4907 if (this.start != 1) {
4908 this.previous.next = this.next;
4909 this.next.previous = this.previous;
4911 this.next.start = 1;
4912 this.line.tags = this.next;
4913 this.next.previous = null;
4919 /// <summary>Checks if 'this' tag describes the same formatting options as 'obj'</summary>
4920 public override bool Equals(object obj) {
4927 if (!(obj is LineTag)) {
4935 other = (LineTag)obj;
4937 if (other.IsTextTag != IsTextTag)
4940 if (this.font.Equals(other.font) && this.color.Equals(other.color)) { // FIXME add checking for things like link or type later
4947 public override int GetHashCode() {
4948 return base.GetHashCode ();
4951 public override string ToString() {
4953 return GetType () + " Tag starts at index " + this.start + " length " + this.length + " text: " + Text () + "Font " + this.font.ToString();
4954 return "Zero Lengthed tag at index " + this.start;
4957 #endregion // Internal Methods
4960 internal class UndoManager {
4962 internal enum ActionType {
4966 // This is basically just cut & paste
4974 internal class Action {
4975 internal ActionType type;
4976 internal int line_no;
4978 internal object data;
4981 #region Local Variables
4982 private Document document;
4983 private Stack undo_actions;
4984 private Stack redo_actions;
4986 private int caret_line;
4987 private int caret_pos;
4989 // When performing an action, we lock the queue, so that the action can't be undone
4990 private bool locked;
4991 #endregion // Local Variables
4993 #region Constructors
4994 internal UndoManager (Document document)
4996 this.document = document;
4997 undo_actions = new Stack (50);
4998 redo_actions = new Stack (50);
5000 #endregion // Constructors
5003 internal bool CanUndo {
5004 get { return undo_actions.Count > 0; }
5007 internal bool CanRedo {
5008 get { return redo_actions.Count > 0; }
5011 internal string UndoActionName {
5013 foreach (Action action in undo_actions) {
5014 if (action.type == ActionType.UserActionBegin)
5015 return (string) action.data;
5016 if (action.type == ActionType.Typing)
5017 return Locale.GetText ("Typing");
5019 return String.Empty;
5023 internal string RedoActionName {
5025 foreach (Action action in redo_actions) {
5026 if (action.type == ActionType.UserActionBegin)
5027 return (string) action.data;
5028 if (action.type == ActionType.Typing)
5029 return Locale.GetText ("Typing");
5031 return String.Empty;
5034 #endregion // Properties
5036 #region Internal Methods
5037 internal void Clear ()
5039 undo_actions.Clear();
5040 redo_actions.Clear();
5043 internal void Undo ()
5046 bool user_action_finished = false;
5048 if (undo_actions.Count == 0)
5051 // Nuke the redo queue
5052 redo_actions.Clear ();
5057 action = (Action) undo_actions.Pop ();
5059 // Put onto redo stack
5060 redo_actions.Push(action);
5063 switch(action.type) {
5065 case ActionType.UserActionBegin:
5066 user_action_finished = true;
5069 case ActionType.UserActionEnd:
5073 case ActionType.InsertString:
5074 start = document.GetLine (action.line_no);
5075 document.SuspendUpdate ();
5076 document.DeleteMultiline (start, action.pos, ((string) action.data).Length + 1);
5077 document.PositionCaret (start, action.pos);
5078 document.SetSelectionToCaret (true);
5079 document.ResumeUpdate (true);
5082 case ActionType.Typing:
5083 start = document.GetLine (action.line_no);
5084 document.SuspendUpdate ();
5085 document.DeleteMultiline (start, action.pos, ((StringBuilder) action.data).Length);
5086 document.PositionCaret (start, action.pos);
5087 document.SetSelectionToCaret (true);
5088 document.ResumeUpdate (true);
5090 // This is an open ended operation, so only a single typing operation can be undone at once
5091 user_action_finished = true;
5094 case ActionType.DeleteString:
5095 start = document.GetLine (action.line_no);
5096 document.SuspendUpdate ();
5097 Insert (start, action.pos, (Line) action.data, true);
5098 document.ResumeUpdate (true);
5101 } while (!user_action_finished && undo_actions.Count > 0);
5106 internal void Redo ()
5109 bool user_action_finished = false;
5111 if (redo_actions.Count == 0)
5114 // You can't undo anything after redoing
5115 undo_actions.Clear ();
5122 action = (Action) redo_actions.Pop ();
5124 switch (action.type) {
5126 case ActionType.UserActionBegin:
5130 case ActionType.UserActionEnd:
5131 user_action_finished = true;
5134 case ActionType.InsertString:
5135 start = document.GetLine (action.line_no);
5136 document.SuspendUpdate ();
5137 start_index = document.LineTagToCharIndex (start, action.pos);
5138 document.InsertString (start, action.pos, (string) action.data);
5139 document.CharIndexToLineTag (start_index + ((string) action.data).Length,
5140 out document.caret.line, out document.caret.tag,
5141 out document.caret.pos);
5142 document.UpdateCaret ();
5143 document.SetSelectionToCaret (true);
5144 document.ResumeUpdate (true);
5147 case ActionType.Typing:
5148 start = document.GetLine (action.line_no);
5149 document.SuspendUpdate ();
5150 start_index = document.LineTagToCharIndex (start, action.pos);
5151 document.InsertString (start, action.pos, ((StringBuilder) action.data).ToString ());
5152 document.CharIndexToLineTag (start_index + ((StringBuilder) action.data).Length,
5153 out document.caret.line, out document.caret.tag,
5154 out document.caret.pos);
5155 document.UpdateCaret ();
5156 document.SetSelectionToCaret (true);
5157 document.ResumeUpdate (true);
5159 // This is an open ended operation, so only a single typing operation can be undone at once
5160 user_action_finished = true;
5163 case ActionType.DeleteString:
5164 start = document.GetLine (action.line_no);
5165 document.SuspendUpdate ();
5166 document.DeleteMultiline (start, action.pos, ((Line) action.data).text.Length);
5167 document.PositionCaret (start, action.pos);
5168 document.SetSelectionToCaret (true);
5169 document.ResumeUpdate (true);
5173 } while (!user_action_finished && redo_actions.Count > 0);
5177 #endregion // Internal Methods
5179 #region Private Methods
5181 public void BeginUserAction (string name)
5186 Action ua = new Action ();
5187 ua.type = ActionType.UserActionBegin;
5190 undo_actions.Push (ua);
5193 public void EndUserAction ()
5198 Action ua = new Action ();
5199 ua.type = ActionType.UserActionEnd;
5201 undo_actions.Push (ua);
5204 // start_pos, end_pos = 1 based
5205 public void RecordDeleteString (Line start_line, int start_pos, Line end_line, int end_pos)
5210 Action a = new Action ();
5212 // We cant simply store the string, because then formatting would be lost
5213 a.type = ActionType.DeleteString;
5214 a.line_no = start_line.line_no;
5216 a.data = Duplicate (start_line, start_pos, end_line, end_pos);
5218 undo_actions.Push(a);
5221 public void RecordInsertString (Line line, int pos, string str)
5223 if (locked || str.Length == 0)
5226 Action a = new Action ();
5228 a.type = ActionType.InsertString;
5230 a.line_no = line.line_no;
5233 undo_actions.Push (a);
5236 public void RecordTyping (Line line, int pos, char ch)
5243 if (undo_actions.Count > 0)
5244 a = (Action) undo_actions.Peek ();
5246 if (a == null || a.type != ActionType.Typing) {
5248 a.type = ActionType.Typing;
5249 a.data = new StringBuilder ();
5250 a.line_no = line.line_no;
5253 undo_actions.Push (a);
5256 StringBuilder data = (StringBuilder) a.data;
5260 // start_pos = 1-based
5261 // end_pos = 1-based
5262 public Line Duplicate(Line start_line, int start_pos, Line end_line, int end_pos)
5268 LineTag current_tag;
5273 line = new Line (start_line.document, start_line.ending);
5276 for (int i = start_line.line_no; i <= end_line.line_no; i++) {
5277 current = document.GetLine(i);
5279 if (start_line.line_no == i) {
5285 if (end_line.line_no == i) {
5288 end = current.text.Length;
5295 line.text = new StringBuilder (current.text.ToString (start, end - start));
5297 // Copy tags from start to start+length onto new line
5298 current_tag = current.FindTag (start);
5299 while ((current_tag != null) && (current_tag.start <= end)) {
5300 if ((current_tag.start <= start) && (start < (current_tag.start + current_tag.length))) {
5301 // start tag is within this tag
5304 tag_start = current_tag.start;
5307 tag = new LineTag(line, tag_start - start + 1);
5308 tag.CopyFormattingFrom (current_tag);
5310 current_tag = current_tag.next;
5312 // Add the new tag to the line
5313 if (line.tags == null) {
5319 while (tail.next != null) {
5323 tag.previous = tail;
5327 if ((i + 1) <= end_line.line_no) {
5328 line.ending = current.ending;
5330 // Chain them (we use right/left as next/previous)
5331 line.right = new Line (start_line.document, start_line.ending);
5332 line.right.left = line;
5340 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
5341 internal void Insert(Line line, int pos, Line insert, bool select)
5349 // Handle special case first
5350 if (insert.right == null) {
5352 // Single line insert
5353 document.Split(line, pos);
5355 if (insert.tags == null) {
5356 return; // Blank line
5359 //Insert our tags at the end
5362 while (tag.next != null) {
5366 offset = tag.start + tag.length - 1;
5368 tag.next = insert.tags;
5369 line.text.Insert(offset, insert.text.ToString());
5371 // Adjust start locations
5373 while (tag != null) {
5374 tag.start += offset;
5378 // Put it back together
5379 document.Combine(line.line_no, line.line_no + 1);
5382 document.SetSelectionStart (line, pos, false);
5383 document.SetSelectionEnd (line, pos + insert.text.Length, false);
5386 document.UpdateView(line, pos);
5394 while (current != null) {
5396 if (current == insert) {
5397 // Inserting the first line we split the line (and make space)
5398 document.Split(line.line_no, pos);
5399 //Insert our tags at the end of the line
5403 if (tag != null && tag.length != 0) {
5404 while (tag.next != null) {
5407 offset = tag.start + tag.length - 1;
5408 tag.next = current.tags;
5409 tag.next.previous = tag;
5415 line.tags = current.tags;
5416 line.tags.previous = null;
5420 line.ending = current.ending;
5422 document.Split(line.line_no, 0);
5424 line.tags = current.tags;
5425 line.tags.previous = null;
5426 line.ending = current.ending;
5430 // Adjust start locations and line pointers
5431 while (tag != null) {
5432 tag.start += offset - 1;
5437 line.text.Insert(offset, current.text.ToString());
5438 line.Grow(line.text.Length);
5441 line = document.GetLine(line.line_no + 1);
5443 // FIXME? Test undo of line-boundaries
5444 if ((current.right == null) && (current.tags.length != 0)) {
5445 document.Combine(line.line_no - 1, line.line_no);
5447 current = current.right;
5452 // Recalculate our document
5453 document.UpdateView(first, lines, pos);
5456 #endregion // Private Methods