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 if (viewport_y == 0 && owner.richtext) {
1844 owner.vscroll.Value = 0;
1845 Line line = GetLine (1);
1846 PositionCaret (line, 0);
1849 int y_offset = caret.line.Y + caret.line.height - 1 - viewport_y;
1851 LineTag top = FindCursor ((int) caret.line.widths [caret.pos],
1852 viewport_y - viewport_height, out index);
1854 owner.vscroll.Value = Math.Min (top.line.Y, owner.vscroll.Maximum - viewport_height);
1855 PositionCaret ((int) caret.line.widths [caret.pos], y_offset + viewport_y);
1860 case CaretDirection.PgDn: {
1862 if (viewport_y + viewport_height >= document_y && owner.richtext) {
1863 owner.vscroll.Value = owner.vscroll.Maximum - viewport_height + 1;
1864 Line line = GetLine (lines);
1865 PositionCaret (line, line.Text.Length);
1868 int y_offset = caret.line.Y - viewport_y;
1870 LineTag top = FindCursor ((int) caret.line.widths [caret.pos],
1871 viewport_y + viewport_height, out index);
1873 owner.vscroll.Value = Math.Min (top.line.Y, owner.vscroll.Maximum - viewport_height);
1874 PositionCaret ((int) caret.line.widths [caret.pos], y_offset + viewport_y);
1879 case CaretDirection.CtrlPgUp: {
1880 PositionCaret(0, viewport_y);
1885 case CaretDirection.CtrlPgDn: {
1890 tag = FindTag(0, viewport_y + viewport_height, out index, false);
1891 if (tag.line.line_no > 1) {
1892 line = GetLine(tag.line.line_no - 1);
1896 PositionCaret(line, line.Text.Length);
1901 case CaretDirection.CtrlHome: {
1902 caret.line = GetLine(1);
1904 caret.tag = caret.line.tags;
1910 case CaretDirection.CtrlEnd: {
1911 caret.line = GetLine(lines);
1912 caret.pos = caret.line.TextLengthWithoutEnding ();
1913 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1919 case CaretDirection.SelectionStart: {
1920 caret.line = selection_start.line;
1921 caret.pos = selection_start.pos;
1922 caret.tag = selection_start.tag;
1928 case CaretDirection.SelectionEnd: {
1929 caret.line = selection_end.line;
1930 caret.pos = selection_end.pos;
1931 caret.tag = selection_end.tag;
1939 internal void DumpDoc ()
1941 Console.WriteLine ("<doc lines='{0}'>", lines);
1942 for (int i = 1; i <= lines ; i++) {
1943 Line line = GetLine (i);
1944 Console.WriteLine ("<line no='{0}' ending='{1}'>", line.line_no, line.ending);
1946 LineTag tag = line.tags;
1947 while (tag != null) {
1948 Console.Write ("\t<tag type='{0}' span='{1}->{2}' font='{3}' color='{4}'>",
1949 tag.GetType (), tag.start, tag.length, tag.font, tag.color.Color);
1950 Console.Write (tag.Text ());
1951 Console.WriteLine ("</tag>");
1954 Console.WriteLine ("</line>");
1956 Console.WriteLine ("</doc>");
1959 internal void Draw (Graphics g, Rectangle clip)
1961 Line line; // Current line being drawn
1962 LineTag tag; // Current tag being drawn
1963 int start; // First line to draw
1964 int end; // Last line to draw
1965 StringBuilder text; // String representing the current line
1968 Brush current_brush;
1969 Brush disabled_brush;
1970 Brush readonly_brush;
1974 // First, figure out from what line to what line we need to draw
1977 start = GetLineByPixel(clip.Top + viewport_y, false).line_no;
1978 end = GetLineByPixel(clip.Bottom + viewport_y, false).line_no;
1980 start = GetLineByPixel(clip.Left + viewport_x, false).line_no;
1981 end = GetLineByPixel(clip.Right + viewport_x, false).line_no;
1985 /// We draw the single border ourself
1987 if (owner.actual_border_style == BorderStyle.FixedSingle) {
1988 ControlPaint.DrawBorder (g, owner.ClientRectangle, Color.Black, ButtonBorderStyle.Solid);
1991 /// Make sure that we aren't drawing one more line then we need to
1992 line = GetLine (end - 1);
1993 if (line != null && clip.Bottom == line.Y + line.height + viewport_y)
1999 DateTime n = DateTime.Now;
2000 Console.WriteLine ("Started drawing: {0}s {1}ms", n.Second, n.Millisecond);
2001 Console.WriteLine ("CLIP: {0}", clip);
2002 Console.WriteLine ("S: {0}", GetLine (start).text);
2003 Console.WriteLine ("E: {0}", GetLine (end).text);
2006 disabled_brush = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorGrayText);
2007 readonly_brush = ThemeEngine.Current.ResPool.GetSolidBrush (ThemeEngine.Current.ColorControlText);
2008 hilight = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorHighlight);
2009 hilight_text = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorHighlightText);
2011 // Non multiline selection can be handled outside of the loop
2012 if (!multiline && selection_visible && owner.ShowSelection) {
2013 g.FillRectangle (hilight,
2014 selection_start.line.widths [selection_start.pos] +
2015 selection_start.line.X - viewport_x,
2016 selection_start.line.Y,
2017 (selection_end.line.X + selection_end.line.widths [selection_end.pos]) -
2018 (selection_start.line.X + selection_start.line.widths [selection_start.pos]),
2019 selection_start.line.height);
2022 while (line_no <= end) {
2023 line = GetLine (line_no);
2024 float line_y = line.Y - viewport_y;
2030 if (PasswordCache.Length < line.text.Length)
2031 PasswordCache.Append(Char.Parse(password_char), line.text.Length - PasswordCache.Length);
2032 else if (PasswordCache.Length > line.text.Length)
2033 PasswordCache.Remove(line.text.Length, PasswordCache.Length - line.text.Length);
2034 text = PasswordCache;
2037 int line_selection_start = text.Length + 1;
2038 int line_selection_end = text.Length + 1;
2039 if (selection_visible && owner.ShowSelection &&
2040 (line_no >= selection_start.line.line_no) &&
2041 (line_no <= selection_end.line.line_no)) {
2043 if (line_no == selection_start.line.line_no)
2044 line_selection_start = selection_start.pos + 1;
2046 line_selection_start = 1;
2048 if (line_no == selection_end.line.line_no)
2049 line_selection_end = selection_end.pos + 1;
2051 line_selection_end = text.Length + 1;
2053 if (line_selection_end == line_selection_start) {
2054 // There isn't really selection
2055 line_selection_start = text.Length + 1;
2056 line_selection_end = line_selection_start;
2057 } else if (multiline) {
2058 // lets draw some selection baby!! (non multiline selection is drawn outside the loop)
2059 g.FillRectangle (hilight,
2060 line.widths [line_selection_start - 1] + line.X - viewport_x,
2061 line_y, line.widths [line_selection_end - 1] - line.widths [line_selection_start - 1],
2066 current_brush = line.tags.color;
2067 while (tag != null) {
2070 if (tag.length == 0) {
2075 if (((tag.X + tag.width) < (clip.Left - viewport_x)) && (tag.X > (clip.Right - viewport_x))) {
2080 if (tag.back_color != null) {
2081 g.FillRectangle (tag.back_color, tag.X + line.X - viewport_x,
2082 line_y + tag.shift, tag.width, line.height);
2085 tag_brush = tag.color;
2086 current_brush = tag_brush;
2088 if (!owner.is_enabled) {
2089 Color a = ((SolidBrush) tag.color).Color;
2090 Color b = ThemeEngine.Current.ColorWindowText;
2092 if ((a.R == b.R) && (a.G == b.G) && (a.B == b.B)) {
2093 tag_brush = disabled_brush;
2095 } else if (owner.read_only && !owner.backcolor_set) {
2096 tag_brush = readonly_brush;
2099 int tag_pos = tag.start;
2100 current_brush = tag_brush;
2101 while (tag_pos < tag.start + tag.length) {
2102 int old_tag_pos = tag_pos;
2104 if (tag_pos >= line_selection_start && tag_pos < line_selection_end) {
2105 current_brush = hilight_text;
2106 tag_pos = Math.Min (tag.end, line_selection_end);
2107 } else if (tag_pos < line_selection_start) {
2108 current_brush = tag_brush;
2109 tag_pos = Math.Min (tag.end, line_selection_start);
2111 current_brush = tag_brush;
2115 tag.Draw (g, current_brush,
2116 line.X - viewport_x,
2118 old_tag_pos - 1, Math.Min (tag.length, tag_pos - old_tag_pos),
2124 line.DrawEnding (g, line_y);
2129 internal int GetLineEnding (string line, int start, out LineEnding ending)
2133 res = line.IndexOf ('\r', start);
2135 if (res + 2 < line.Length && line [res + 1] == '\r' && line [res + 2] == '\n') {
2136 ending = LineEnding.Soft;
2139 if (res + 1 < line.Length && line [res + 1] == '\n') {
2140 ending = LineEnding.Hard;
2143 ending = LineEnding.Limp;
2147 res = line.IndexOf ('\n', start);
2149 ending = LineEnding.Rich;
2153 ending = LineEnding.Wrap;
2157 internal int LineEndingLength (LineEnding ending)
2162 case LineEnding.Limp:
2163 case LineEnding.Rich:
2166 case LineEnding.Hard:
2169 case LineEnding.Soft:
2177 internal string LineEndingToString (LineEnding ending)
2179 string res = String.Empty;
2181 case LineEnding.Limp:
2184 case LineEnding.Hard:
2187 case LineEnding.Soft:
2190 case LineEnding.Rich:
2198 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
2199 internal void Insert(Line line, int pos, bool update_caret, string s) {
2205 LineTag tag = LineTag.FindTag (line, pos);
2209 base_line = line.line_no;
2210 old_line_count = lines;
2212 break_index = GetLineEnding (s, 0, out ending);
2214 // Bump the text at insertion point a line down if we're inserting more than one line
2215 if (break_index != s.Length) {
2217 line.ending = ending;
2218 // Remainder of start line is now in base_line + 1
2221 InsertString (line, pos, s.Substring (0, break_index + LineEndingLength (ending)));
2223 break_index += LineEndingLength (ending);
2224 while (break_index < s.Length) {
2225 int next_break = GetLineEnding (s, break_index, out ending);
2226 string line_text = s.Substring (break_index, next_break - break_index +
2227 LineEndingLength (ending));
2229 Add (base_line + count, line_text, line.alignment, tag.font, tag.color, ending);
2231 Line last = GetLine (base_line + count);
2232 last.ending = ending;
2235 break_index = next_break + LineEndingLength (ending);
2238 ResumeRecalc (true);
2240 UpdateView(line, lines - old_line_count + 1, pos);
2243 // Move caret to the end of the inserted text
2244 Line l = GetLine (line.line_no + lines - old_line_count);
2245 PositionCaret(l, l.text.Length);
2250 // Inserts a character at the given position
2251 internal void InsertString(Line line, int pos, string s) {
2252 InsertString(line.FindTag(pos), pos, s);
2255 // Inserts a string at the given position
2256 internal void InsertString(LineTag tag, int pos, string s) {
2265 line.text.Insert(pos, s);
2268 while (tag != null) {
2275 UpdateView(line, pos);
2278 // Inserts a string at the caret position
2279 internal void InsertStringAtCaret(string s, bool move_caret) {
2281 InsertString (caret.tag, caret.pos, s);
2283 UpdateView(caret.line, caret.pos);
2285 caret.pos += s.Length;
2292 // Inserts a character at the given position
2293 internal void InsertChar(Line line, int pos, char ch) {
2294 InsertChar(line.FindTag(pos), pos, ch);
2297 // Inserts a character at the given position
2298 internal void InsertChar(LineTag tag, int pos, char ch) {
2304 line.text.Insert(pos, ch);
2307 while (tag != null) {
2314 undo.RecordTyping (line, pos, ch);
2315 UpdateView(line, pos);
2318 // Inserts a character at the current caret position
2319 internal void InsertCharAtCaret(char ch, bool move_caret) {
2325 caret.line.text.Insert(caret.pos, ch);
2328 if (caret.tag.next != null) {
2329 tag = caret.tag.next;
2330 while (tag != null) {
2336 caret.line.recalc = true;
2338 InsertChar (caret.tag, caret.pos, ch);
2340 UpdateView(caret.line, caret.pos);
2344 SetSelectionToCaret(true);
2349 internal void InsertPicture (Line line, int pos, RTF.Picture picture)
2357 // Just a place holder basically
2358 line.text.Insert (pos, "I");
2360 PictureTag picture_tag = new PictureTag (line, pos + 1, picture);
2362 tag = LineTag.FindTag (line, pos);
2363 picture_tag.CopyFormattingFrom (tag);
2364 next_tag = tag.Break (pos + 1);
2365 picture_tag.previous = tag;
2366 picture_tag.next = tag.next;
2367 tag.next = picture_tag;
2370 // Picture tags need to be surrounded by text tags
2372 if (picture_tag.next == null) {
2373 picture_tag.next = new LineTag (line, pos + 1);
2374 picture_tag.next.CopyFormattingFrom (tag);
2375 picture_tag.next.previous = picture_tag;
2378 tag = picture_tag.next;
2379 while (tag != null) {
2387 UpdateView (line, pos);
2390 internal void DeleteMultiline (Line start_line, int pos, int length)
2392 Marker start = new Marker ();
2393 Marker end = new Marker ();
2394 int start_index = LineTagToCharIndex (start_line, pos);
2396 start.line = start_line;
2398 start.tag = LineTag.FindTag (start_line, pos);
2400 CharIndexToLineTag (start_index + length, out end.line,
2401 out end.tag, out end.pos);
2405 if (start.line == end.line) {
2406 DeleteChars (start.tag, pos, end.pos - pos);
2409 // Delete first and last lines
2410 DeleteChars (start.tag, start.pos, start.line.text.Length - start.pos);
2411 DeleteChars (end.line.tags, 0, end.pos);
2413 int current = start.line.line_no + 1;
2414 if (current < end.line.line_no) {
2415 for (int i = end.line.line_no - 1; i >= current; i--) {
2420 // BIG FAT WARNING - selection_end.line might be stale due
2421 // to the above Delete() call. DONT USE IT before hitting the end of this method!
2423 // Join start and end
2424 Combine (start.line.line_no, current);
2427 ResumeUpdate (true);
2431 // Deletes n characters at the given position; it will not delete past line limits
2433 internal void DeleteChars(LineTag tag, int pos, int count) {
2442 if (pos == line.text.Length) {
2446 line.text.Remove(pos, count);
2448 // Make sure the tag points to the right spot
2449 while ((tag != null) && (tag.end) < pos) {
2457 // Check if we're crossing tag boundaries
2458 if ((pos + count) > (tag.start + tag.length - 1)) {
2461 // We have to delete cross tag boundaries
2465 left -= tag.start + tag.length - pos - 1;
2468 while ((tag != null) && (left > 0)) {
2469 tag.start -= count - left;
2471 if (tag.length > left) {
2480 // We got off easy, same tag
2482 if (tag.length == 0) {
2487 // Delete empty orphaned tags at the end
2489 while (walk != null && walk.next != null && walk.next.length == 0) {
2491 walk.next = walk.next.next;
2492 if (walk.next != null)
2493 walk.next.previous = t;
2497 // Adjust the start point of any tags following
2500 while (tag != null) {
2508 line.Streamline(lines);
2512 if (pos >= line.TextLengthWithoutEnding ()) {
2513 LineEnding ending = line.ending;
2514 GetLineEnding (line.text.ToString (), 0, out ending);
2515 if (ending != line.ending) {
2516 line.ending = ending;
2519 UpdateView (line, lines, pos);
2520 owner.Invalidate ();
2526 UpdateView (line, lines, pos);
2527 owner.Invalidate ();
2529 UpdateView(line, pos);
2532 // Deletes a character at or after the given position (depending on forward); it will not delete past line limits
2533 internal void DeleteChar(LineTag tag, int pos, bool forward) {
2542 if ((pos == 0 && forward == false) || (pos == line.text.Length && forward == true)) {
2548 line.text.Remove(pos, 1);
2550 while ((tag != null) && (tag.start + tag.length - 1) <= pos) {
2560 if (tag.length == 0) {
2565 line.text.Remove(pos, 1);
2566 if (pos >= (tag.start - 1)) {
2568 if (tag.length == 0) {
2571 } else if (tag.previous != null) {
2572 // tag.previous.length--;
2573 if (tag.previous.length == 0) {
2579 // Delete empty orphaned tags at the end
2581 while (walk != null && walk.next != null && walk.next.length == 0) {
2583 walk.next = walk.next.next;
2584 if (walk.next != null)
2585 walk.next.previous = t;
2590 while (tag != null) {
2596 line.Streamline(lines);
2600 if (pos >= line.TextLengthWithoutEnding ()) {
2601 LineEnding ending = line.ending;
2602 GetLineEnding (line.text.ToString (), 0, out ending);
2603 if (ending != line.ending) {
2604 line.ending = ending;
2607 UpdateView (line, lines, pos);
2608 owner.Invalidate ();
2614 UpdateView (line, lines, pos);
2615 owner.Invalidate ();
2617 UpdateView(line, pos);
2620 // Combine two lines
2621 internal void Combine(int FirstLine, int SecondLine) {
2622 Combine(GetLine(FirstLine), GetLine(SecondLine));
2625 internal void Combine(Line first, Line second) {
2629 // strip the ending off of the first lines text
2630 first.text.Length = first.text.Length - LineEndingLength (first.ending);
2632 // Combine the two tag chains into one
2635 // Maintain the line ending style
2636 first.ending = second.ending;
2638 while (last.next != null) {
2642 // need to get the shift before setting the next tag since that effects length
2643 shift = last.start + last.length - 1;
2644 last.next = second.tags;
2645 last.next.previous = last;
2647 // Fix up references within the chain
2649 while (last != null) {
2651 last.start += shift;
2655 // Combine both lines' strings
2656 first.text.Insert(first.text.Length, second.text.ToString());
2657 first.Grow(first.text.Length);
2659 // Remove the reference to our (now combined) tags from the doomed line
2663 DecrementLines(first.line_no + 2); // first.line_no + 1 will be deleted, so we need to start renumbering one later
2666 first.recalc = true;
2667 first.height = 0; // This forces RecalcDocument/UpdateView to redraw from this line on
2668 first.Streamline(lines);
2670 // Update Caret, Selection, etc
2671 if (caret.line == second) {
2672 caret.Combine(first, shift);
2674 if (selection_anchor.line == second) {
2675 selection_anchor.Combine(first, shift);
2677 if (selection_start.line == second) {
2678 selection_start.Combine(first, shift);
2680 if (selection_end.line == second) {
2681 selection_end.Combine(first, shift);
2688 check_first = GetLine(first.line_no);
2689 check_second = GetLine(check_first.line_no + 1);
2691 Console.WriteLine("Pre-delete: Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2694 this.Delete(second);
2697 check_first = GetLine(first.line_no);
2698 check_second = GetLine(check_first.line_no + 1);
2700 Console.WriteLine("Post-delete Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2704 // Split the line at the position into two
2705 internal void Split(int LineNo, int pos) {
2709 line = GetLine(LineNo);
2710 tag = LineTag.FindTag(line, pos);
2711 Split(line, tag, pos);
2714 internal void Split(Line line, int pos) {
2717 tag = LineTag.FindTag(line, pos);
2718 Split(line, tag, pos);
2721 ///<summary>Split line at given tag and position into two lines</summary>
2722 ///if more space becomes available on previous line</param>
2723 internal void Split(Line line, LineTag tag, int pos) {
2727 bool move_sel_start;
2731 move_sel_start = false;
2732 move_sel_end = false;
2734 // Adjust selection and cursors
2735 if (caret.line == line && caret.pos >= pos) {
2738 if (selection_start.line == line && selection_start.pos > pos) {
2739 move_sel_start = true;
2742 if (selection_end.line == line && selection_end.pos > pos) {
2743 move_sel_end = true;
2746 // cover the easy case first
2747 if (pos == line.text.Length) {
2748 Add (line.line_no + 1, String.Empty, line.alignment, tag.font, tag.color, line.ending);
2750 new_line = GetLine (line.line_no + 1);
2753 caret.line = new_line;
2754 caret.tag = new_line.tags;
2758 if (move_sel_start) {
2759 selection_start.line = new_line;
2760 selection_start.pos = 0;
2761 selection_start.tag = new_line.tags;
2765 selection_end.line = new_line;
2766 selection_end.pos = 0;
2767 selection_end.tag = new_line.tags;
2772 // We need to move the rest of the text into the new line
2773 Add (line.line_no + 1, line.text.ToString (pos, line.text.Length - pos), line.alignment, tag.font, tag.color, line.ending);
2775 // Now transfer our tags from this line to the next
2776 new_line = GetLine(line.line_no + 1);
2779 new_line.recalc = true;
2781 if ((tag.start - 1) == pos) {
2784 // We can simply break the chain and move the tag into the next line
2785 if (tag == line.tags) {
2786 new_tag = new LineTag(line, 1);
2787 new_tag.CopyFormattingFrom (tag);
2788 line.tags = new_tag;
2791 if (tag.previous != null) {
2792 tag.previous.next = null;
2794 new_line.tags = tag;
2795 tag.previous = null;
2796 tag.line = new_line;
2798 // Walk the list and correct the start location of the tags we just bumped into the next line
2799 shift = tag.start - 1;
2802 while (new_tag != null) {
2803 new_tag.start -= shift;
2804 new_tag.line = new_line;
2805 new_tag = new_tag.next;
2810 new_tag = new LineTag (new_line, 1);
2811 new_tag.next = tag.next;
2812 new_tag.CopyFormattingFrom (tag);
2813 new_line.tags = new_tag;
2814 if (new_tag.next != null) {
2815 new_tag.next.previous = new_tag;
2820 new_tag = new_tag.next;
2821 while (new_tag != null) {
2822 new_tag.start -= shift;
2823 new_tag.line = new_line;
2824 new_tag = new_tag.next;
2830 caret.line = new_line;
2831 caret.pos = caret.pos - pos;
2832 caret.tag = caret.line.FindTag(caret.pos);
2835 if (move_sel_start) {
2836 selection_start.line = new_line;
2837 selection_start.pos = selection_start.pos - pos;
2838 selection_start.tag = new_line.FindTag(selection_start.pos);
2842 selection_end.line = new_line;
2843 selection_end.pos = selection_end.pos - pos;
2844 selection_end.tag = new_line.FindTag(selection_end.pos);
2847 CharCount -= line.text.Length - pos;
2848 line.text.Remove(pos, line.text.Length - pos);
2851 // Adds a line of text, with given font.
2852 // Bumps any line at that line number that already exists down
2853 internal void Add (int LineNo, string Text, Font font, SolidBrush color, LineEnding ending)
2855 Add (LineNo, Text, alignment, font, color, ending);
2858 internal void Add (int LineNo, string Text, HorizontalAlignment align, Font font, SolidBrush color, LineEnding ending)
2864 CharCount += Text.Length;
2866 if (LineNo<1 || Text == null) {
2868 throw new ArgumentNullException("LineNo", "Line numbers must be positive");
2870 throw new ArgumentNullException("Text", "Cannot insert NULL line");
2874 add = new Line (this, LineNo, Text, align, font, color, ending);
2877 while (line != sentinel) {
2879 line_no = line.line_no;
2881 if (LineNo > line_no) {
2883 } else if (LineNo < line_no) {
2886 // Bump existing line numbers; walk all nodes to the right of this one and increment line_no
2887 IncrementLines(line.line_no);
2892 add.left = sentinel;
2893 add.right = sentinel;
2895 if (add.parent != null) {
2896 if (LineNo > add.parent.line_no) {
2897 add.parent.right = add;
2899 add.parent.left = add;
2906 RebalanceAfterAdd(add);
2911 internal virtual void Clear() {
2914 document = sentinel;
2917 public virtual object Clone() {
2920 clone = new Document(null);
2922 clone.lines = this.lines;
2923 clone.document = (Line)document.Clone();
2928 internal void Delete(int LineNo) {
2935 line = GetLine(LineNo);
2937 CharCount -= line.text.Length;
2939 DecrementLines(LineNo + 1);
2943 internal void Delete(Line line1) {
2944 Line line2;// = new Line();
2947 if ((line1.left == sentinel) || (line1.right == sentinel)) {
2950 line3 = line1.right;
2951 while (line3.left != sentinel) {
2956 if (line3.left != sentinel) {
2959 line2 = line3.right;
2962 line2.parent = line3.parent;
2963 if (line3.parent != null) {
2964 if(line3 == line3.parent.left) {
2965 line3.parent.left = line2;
2967 line3.parent.right = line2;
2973 if (line3 != line1) {
2976 if (selection_start.line == line3) {
2977 selection_start.line = line1;
2980 if (selection_end.line == line3) {
2981 selection_end.line = line1;
2984 if (selection_anchor.line == line3) {
2985 selection_anchor.line = line1;
2988 if (caret.line == line3) {
2993 line1.alignment = line3.alignment;
2994 line1.ascent = line3.ascent;
2995 line1.hanging_indent = line3.hanging_indent;
2996 line1.height = line3.height;
2997 line1.indent = line3.indent;
2998 line1.line_no = line3.line_no;
2999 line1.recalc = line3.recalc;
3000 line1.right_indent = line3.right_indent;
3001 line1.ending = line3.ending;
3002 line1.space = line3.space;
3003 line1.tags = line3.tags;
3004 line1.text = line3.text;
3005 line1.widths = line3.widths;
3006 line1.offset = line3.offset;
3009 while (tag != null) {
3015 if (line3.color == LineColor.Black)
3016 RebalanceAfterDelete(line2);
3021 // Invalidate a section of the document to trigger redraw
3022 internal void Invalidate(Line start, int start_pos, Line end, int end_pos) {
3028 if ((start == end) && (start_pos == end_pos)) {
3032 if (end_pos == -1) {
3033 end_pos = end.text.Length;
3036 // figure out what's before what so the logic below is straightforward
3037 if (start.line_no < end.line_no) {
3043 } else if (start.line_no > end.line_no) {
3050 if (start_pos < end_pos) {
3064 int endpoint = (int) l1.widths [p2];
3065 if (p2 == l1.text.Length + 1) {
3066 endpoint = (int) viewport_width;
3070 Console.WriteLine("Invaliding backwards from {0}:{1} to {2}:{3} {4}",
3071 l1.line_no, p1, l2.line_no, p2,
3073 (int)l1.widths[p1] + l1.X - viewport_x,
3081 owner.Invalidate(new Rectangle (
3082 (int)l1.widths[p1] + l1.X - viewport_x,
3084 endpoint - (int)l1.widths[p1] + 1,
3090 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);
3091 Console.WriteLine ("invalidate start line: {0} position: {1}", l1.text, p1);
3094 // Three invalidates:
3095 // First line from start
3096 owner.Invalidate(new Rectangle((int)l1.widths[p1] + l1.X - viewport_x, l1.Y - viewport_y, viewport_width, l1.height));
3100 if ((l1.line_no + 1) < l2.line_no) {
3103 y = GetLine(l1.line_no + 1).Y;
3104 owner.Invalidate(new Rectangle(0, y - viewport_y, viewport_width, l2.Y - y));
3107 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);
3113 owner.Invalidate(new Rectangle((int)l2.widths[0] + l2.X - viewport_x, l2.Y - viewport_y, (int)l2.widths[p2] + 1, l2.height));
3115 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);
3120 /// <summary>Select text around caret</summary>
3121 internal void ExpandSelection(CaretSelection mode, bool to_caret) {
3123 // We're expanding the selection to the caret position
3125 case CaretSelection.Line: {
3126 // Invalidate the selection delta
3127 if (caret > selection_prev) {
3128 Invalidate(selection_prev.line, 0, caret.line, caret.line.text.Length);
3130 Invalidate(selection_prev.line, selection_prev.line.text.Length, caret.line, 0);
3133 if (caret.line.line_no <= selection_anchor.line.line_no) {
3134 selection_start.line = caret.line;
3135 selection_start.tag = caret.line.tags;
3136 selection_start.pos = 0;
3138 selection_end.line = selection_anchor.line;
3139 selection_end.tag = selection_anchor.tag;
3140 selection_end.pos = selection_anchor.pos;
3142 selection_end_anchor = true;
3144 selection_start.line = selection_anchor.line;
3145 selection_start.pos = selection_anchor.height;
3146 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height);
3148 selection_end.line = caret.line;
3149 selection_end.tag = caret.line.tags;
3150 selection_end.pos = caret.line.text.Length;
3152 selection_end_anchor = false;
3154 selection_prev.line = caret.line;
3155 selection_prev.tag = caret.tag;
3156 selection_prev.pos = caret.pos;
3161 case CaretSelection.Word: {
3165 start_pos = FindWordSeparator(caret.line, caret.pos, false);
3166 end_pos = FindWordSeparator(caret.line, caret.pos, true);
3169 // Invalidate the selection delta
3170 if (caret > selection_prev) {
3171 Invalidate(selection_prev.line, selection_prev.pos, caret.line, end_pos);
3173 Invalidate(selection_prev.line, selection_prev.pos, caret.line, start_pos);
3175 if (caret < selection_anchor) {
3176 selection_start.line = caret.line;
3177 selection_start.tag = caret.line.FindTag(start_pos);
3178 selection_start.pos = start_pos;
3180 selection_end.line = selection_anchor.line;
3181 selection_end.tag = selection_anchor.tag;
3182 selection_end.pos = selection_anchor.pos;
3184 selection_prev.line = caret.line;
3185 selection_prev.tag = caret.tag;
3186 selection_prev.pos = start_pos;
3188 selection_end_anchor = true;
3190 selection_start.line = selection_anchor.line;
3191 selection_start.pos = selection_anchor.height;
3192 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height);
3194 selection_end.line = caret.line;
3195 selection_end.tag = caret.line.FindTag(end_pos);
3196 selection_end.pos = end_pos;
3198 selection_prev.line = caret.line;
3199 selection_prev.tag = caret.tag;
3200 selection_prev.pos = end_pos;
3202 selection_end_anchor = false;
3207 case CaretSelection.Position: {
3208 SetSelectionToCaret(false);
3213 // We're setting the selection 'around' the caret position
3215 case CaretSelection.Line: {
3216 this.Invalidate(caret.line, 0, caret.line, caret.line.text.Length);
3218 selection_start.line = caret.line;
3219 selection_start.tag = caret.line.tags;
3220 selection_start.pos = 0;
3222 selection_end.line = caret.line;
3223 selection_end.pos = caret.line.text.Length;
3224 selection_end.tag = caret.line.FindTag(selection_end.pos);
3226 selection_anchor.line = selection_end.line;
3227 selection_anchor.tag = selection_end.tag;
3228 selection_anchor.pos = selection_end.pos;
3229 selection_anchor.height = 0;
3231 selection_prev.line = caret.line;
3232 selection_prev.tag = caret.tag;
3233 selection_prev.pos = caret.pos;
3235 this.selection_end_anchor = true;
3240 case CaretSelection.Word: {
3244 start_pos = FindWordSeparator(caret.line, caret.pos, false);
3245 end_pos = FindWordSeparator(caret.line, caret.pos, true);
3247 this.Invalidate(selection_start.line, start_pos, caret.line, end_pos);
3249 selection_start.line = caret.line;
3250 selection_start.tag = caret.line.FindTag(start_pos);
3251 selection_start.pos = start_pos;
3253 selection_end.line = caret.line;
3254 selection_end.tag = caret.line.FindTag(end_pos);
3255 selection_end.pos = end_pos;
3257 selection_anchor.line = selection_end.line;
3258 selection_anchor.tag = selection_end.tag;
3259 selection_anchor.pos = selection_end.pos;
3260 selection_anchor.height = start_pos;
3262 selection_prev.line = caret.line;
3263 selection_prev.tag = caret.tag;
3264 selection_prev.pos = caret.pos;
3266 this.selection_end_anchor = true;
3273 SetSelectionVisible (!(selection_start == selection_end));
3276 internal void SetSelectionToCaret(bool start) {
3278 // Invalidate old selection; selection is being reset to empty
3279 this.Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3281 selection_start.line = caret.line;
3282 selection_start.tag = caret.tag;
3283 selection_start.pos = caret.pos;
3285 // start always also selects end
3286 selection_end.line = caret.line;
3287 selection_end.tag = caret.tag;
3288 selection_end.pos = caret.pos;
3290 selection_anchor.line = caret.line;
3291 selection_anchor.tag = caret.tag;
3292 selection_anchor.pos = caret.pos;
3294 // Invalidate from previous end to caret (aka new end)
3295 if (selection_end_anchor) {
3296 if (selection_start != caret) {
3297 this.Invalidate(selection_start.line, selection_start.pos, caret.line, caret.pos);
3300 if (selection_end != caret) {
3301 this.Invalidate(selection_end.line, selection_end.pos, caret.line, caret.pos);
3305 if (caret < selection_anchor) {
3306 selection_start.line = caret.line;
3307 selection_start.tag = caret.tag;
3308 selection_start.pos = caret.pos;
3310 selection_end.line = selection_anchor.line;
3311 selection_end.tag = selection_anchor.tag;
3312 selection_end.pos = selection_anchor.pos;
3314 selection_end_anchor = true;
3316 selection_start.line = selection_anchor.line;
3317 selection_start.tag = selection_anchor.tag;
3318 selection_start.pos = selection_anchor.pos;
3320 selection_end.line = caret.line;
3321 selection_end.tag = caret.tag;
3322 selection_end.pos = caret.pos;
3324 selection_end_anchor = false;
3328 SetSelectionVisible (!(selection_start == selection_end));
3331 internal void SetSelection(Line start, int start_pos, Line end, int end_pos) {
3332 if (selection_visible) {
3333 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3336 if ((end.line_no < start.line_no) || ((end == start) && (end_pos <= start_pos))) {
3337 selection_start.line = end;
3338 selection_start.tag = LineTag.FindTag(end, end_pos);
3339 selection_start.pos = end_pos;
3341 selection_end.line = start;
3342 selection_end.tag = LineTag.FindTag(start, start_pos);
3343 selection_end.pos = start_pos;
3345 selection_end_anchor = true;
3347 selection_start.line = start;
3348 selection_start.tag = LineTag.FindTag(start, start_pos);
3349 selection_start.pos = start_pos;
3351 selection_end.line = end;
3352 selection_end.tag = LineTag.FindTag(end, end_pos);
3353 selection_end.pos = end_pos;
3355 selection_end_anchor = false;
3358 selection_anchor.line = start;
3359 selection_anchor.tag = selection_start.tag;
3360 selection_anchor.pos = start_pos;
3362 if (((start == end) && (start_pos == end_pos)) || start == null || end == null) {
3363 SetSelectionVisible (false);
3365 SetSelectionVisible (true);
3366 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3370 internal void SetSelectionStart(Line start, int start_pos, bool invalidate) {
3371 // Invalidate from the previous to the new start pos
3373 Invalidate(selection_start.line, selection_start.pos, start, start_pos);
3375 selection_start.line = start;
3376 selection_start.pos = start_pos;
3377 selection_start.tag = LineTag.FindTag(start, start_pos);
3379 selection_anchor.line = start;
3380 selection_anchor.pos = start_pos;
3381 selection_anchor.tag = selection_start.tag;
3383 selection_end_anchor = false;
3386 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
3387 SetSelectionVisible (true);
3389 SetSelectionVisible (false);
3393 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3396 internal void SetSelectionStart(int character_index, bool invalidate) {
3401 if (character_index < 0) {
3405 CharIndexToLineTag(character_index, out line, out tag, out pos);
3406 SetSelectionStart(line, pos, invalidate);
3409 internal void SetSelectionEnd(Line end, int end_pos, bool invalidate) {
3411 if (end == selection_end.line && end_pos == selection_start.pos) {
3412 selection_anchor.line = selection_start.line;
3413 selection_anchor.tag = selection_start.tag;
3414 selection_anchor.pos = selection_start.pos;
3416 selection_end.line = selection_start.line;
3417 selection_end.tag = selection_start.tag;
3418 selection_end.pos = selection_start.pos;
3420 selection_end_anchor = false;
3421 } else if ((end.line_no < selection_anchor.line.line_no) || ((end == selection_anchor.line) && (end_pos <= selection_anchor.pos))) {
3422 selection_start.line = end;
3423 selection_start.tag = LineTag.FindTag(end, end_pos);
3424 selection_start.pos = end_pos;
3426 selection_end.line = selection_anchor.line;
3427 selection_end.tag = selection_anchor.tag;
3428 selection_end.pos = selection_anchor.pos;
3430 selection_end_anchor = true;
3432 selection_start.line = selection_anchor.line;
3433 selection_start.tag = selection_anchor.tag;
3434 selection_start.pos = selection_anchor.pos;
3436 selection_end.line = end;
3437 selection_end.tag = LineTag.FindTag(end, end_pos);
3438 selection_end.pos = end_pos;
3440 selection_end_anchor = false;
3443 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
3444 SetSelectionVisible (true);
3446 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3448 SetSelectionVisible (false);
3449 // ?? Do I need to invalidate here, tests seem to work without it, but I don't think they should :-s
3453 internal void SetSelectionEnd(int character_index, bool invalidate) {
3458 if (character_index < 0) {
3462 CharIndexToLineTag(character_index, out line, out tag, out pos);
3463 SetSelectionEnd(line, pos, invalidate);
3466 internal void SetSelection(Line start, int start_pos) {
3467 if (selection_visible) {
3468 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3471 selection_start.line = start;
3472 selection_start.pos = start_pos;
3473 selection_start.tag = LineTag.FindTag(start, start_pos);
3475 selection_end.line = start;
3476 selection_end.tag = selection_start.tag;
3477 selection_end.pos = start_pos;
3479 selection_anchor.line = start;
3480 selection_anchor.tag = selection_start.tag;
3481 selection_anchor.pos = start_pos;
3483 selection_end_anchor = false;
3484 SetSelectionVisible (false);
3487 internal void InvalidateSelectionArea() {
3488 Invalidate (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3491 // Return the current selection, as string
3492 internal string GetSelection() {
3493 // We return String.Empty if there is no selection
3494 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3495 return string.Empty;
3498 if (selection_start.line == selection_end.line) {
3499 return selection_start.line.text.ToString (selection_start.pos, selection_end.pos - selection_start.pos);
3506 sb = new StringBuilder();
3507 start = selection_start.line.line_no;
3508 end = selection_end.line.line_no;
3510 sb.Append(selection_start.line.text.ToString(selection_start.pos, selection_start.line.text.Length - selection_start.pos) + Environment.NewLine);
3512 if ((start + 1) < end) {
3513 for (i = start + 1; i < end; i++) {
3514 sb.Append(GetLine(i).text.ToString() + Environment.NewLine);
3518 sb.Append(selection_end.line.text.ToString(0, selection_end.pos));
3520 return sb.ToString();
3524 internal void ReplaceSelection(string s, bool select_new) {
3527 int selection_pos_on_line = selection_start.pos;
3528 int selection_start_pos = LineTagToCharIndex (selection_start.line, selection_start.pos);
3531 // First, delete any selected text
3532 if ((selection_start.pos != selection_end.pos) || (selection_start.line != selection_end.line)) {
3533 if (selection_start.line == selection_end.line) {
3534 undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3536 DeleteChars(selection_start.tag, selection_start.pos, selection_end.pos - selection_start.pos);
3538 // The tag might have been removed, we need to recalc it
3539 selection_start.tag = selection_start.line.FindTag(selection_start.pos);
3544 start = selection_start.line.line_no;
3545 end = selection_end.line.line_no;
3547 undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3549 InvalidateSelectionArea ();
3551 // Delete first line
3552 DeleteChars(selection_start.tag, selection_start.pos, selection_start.line.text.Length - selection_start.pos);
3553 selection_start.line.recalc = true;
3556 DeleteChars(selection_end.line.tags, 0, selection_end.pos);
3560 for (i = end - 1; i >= start; i--) {
3565 // BIG FAT WARNING - selection_end.line might be stale due
3566 // to the above Delete() call. DONT USE IT before hitting the end of this method!
3568 // Join start and end
3569 Combine(selection_start.line.line_no, start);
3574 Insert(selection_start.line, selection_start.pos, false, s);
3575 undo.RecordInsertString (selection_start.line, selection_start.pos, s);
3576 ResumeRecalc (false);
3579 CharIndexToLineTag(selection_start_pos + s.Length, out selection_start.line,
3580 out selection_start.tag, out selection_start.pos);
3582 selection_end.line = selection_start.line;
3583 selection_end.pos = selection_start.pos;
3584 selection_end.tag = selection_start.tag;
3585 selection_anchor.line = selection_start.line;
3586 selection_anchor.pos = selection_start.pos;
3587 selection_anchor.tag = selection_start.tag;
3589 SetSelectionVisible (false);
3591 CharIndexToLineTag(selection_start_pos, out selection_start.line,
3592 out selection_start.tag, out selection_start.pos);
3594 CharIndexToLineTag(selection_start_pos + s.Length, out selection_end.line,
3595 out selection_end.tag, out selection_end.pos);
3597 selection_anchor.line = selection_start.line;
3598 selection_anchor.pos = selection_start.pos;
3599 selection_anchor.tag = selection_start.tag;
3601 SetSelectionVisible (true);
3604 PositionCaret (selection_start.line, selection_start.pos);
3605 UpdateView (selection_start.line, selection_pos_on_line);
3608 internal void CharIndexToLineTag(int index, out Line line_out, out LineTag tag_out, out int pos) {
3617 for (i = 1; i <= lines; i++) {
3621 chars += line.text.Length;
3623 if (index <= chars) {
3624 // we found the line
3627 while (tag != null) {
3628 if (index < (start + tag.start + tag.length - 1)) {
3630 tag_out = LineTag.GetFinalTag (tag);
3631 pos = index - start;
3634 if (tag.next == null) {
3637 next_line = GetLine(line.line_no + 1);
3639 if (next_line != null) {
3640 line_out = next_line;
3641 tag_out = LineTag.GetFinalTag (next_line.tags);
3646 tag_out = LineTag.GetFinalTag (tag);
3647 pos = line_out.text.Length;
3656 line_out = GetLine(lines);
3657 tag = line_out.tags;
3658 while (tag.next != null) {
3662 pos = line_out.text.Length;
3665 internal int LineTagToCharIndex(Line line, int pos) {
3669 // Count first and last line
3672 // Count the lines in the middle
3674 for (i = 1; i < line.line_no; i++) {
3675 length += GetLine(i).text.Length;
3683 internal int SelectionLength() {
3684 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3688 if (selection_start.line == selection_end.line) {
3689 return selection_end.pos - selection_start.pos;
3696 // Count first and last line
3697 length = selection_start.line.text.Length - selection_start.pos + selection_end.pos + crlf_size;
3699 // Count the lines in the middle
3700 start = selection_start.line.line_no + 1;
3701 end = selection_end.line.line_no;
3704 for (i = start; i < end; i++) {
3705 Line line = GetLine (i);
3706 length += line.text.Length + LineEndingLength (line.ending);
3717 /// <summary>Give it a Line number and it returns the Line object at with that line number</summary>
3718 internal Line GetLine(int LineNo) {
3719 Line line = document;
3721 while (line != sentinel) {
3722 if (LineNo == line.line_no) {
3724 } else if (LineNo < line.line_no) {
3734 /// <summary>Retrieve the previous tag; walks line boundaries</summary>
3735 internal LineTag PreviousTag(LineTag tag) {
3738 if (tag.previous != null) {
3739 return tag.previous;
3743 if (tag.line.line_no == 1) {
3747 l = GetLine(tag.line.line_no - 1);
3752 while (t.next != null) {
3761 /// <summary>Retrieve the next tag; walks line boundaries</summary>
3762 internal LineTag NextTag(LineTag tag) {
3765 if (tag.next != null) {
3770 l = GetLine(tag.line.line_no + 1);
3778 internal Line ParagraphStart(Line line) {
3779 while (line.ending == LineEnding.Wrap) {
3780 line = GetLine(line.line_no - 1);
3785 internal Line ParagraphEnd(Line line) {
3788 while (line.ending == LineEnding.Wrap) {
3789 l = GetLine(line.line_no + 1);
3790 if ((l == null) || (l.ending != LineEnding.Wrap)) {
3798 /// <summary>Give it a pixel offset coordinate and it returns the Line covering that are (offset
3799 /// is either X or Y depending on if we are multiline
3801 internal Line GetLineByPixel (int offset, bool exact)
3803 Line line = document;
3807 while (line != sentinel) {
3809 if ((offset >= line.Y) && (offset < (line.Y+line.height))) {
3811 } else if (offset < line.Y) {
3818 while (line != sentinel) {
3820 if ((offset >= line.X) && (offset < (line.X + line.Width)))
3822 else if (offset < line.X)
3835 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3836 internal LineTag FindTag(int x, int y, out int index, bool exact) {
3840 line = GetLineByPixel(y, exact);
3847 // Alignment adjustment
3851 if (x >= tag.X && x < (tag.X+tag.width)) {
3854 end = tag.start + tag.length - 1;
3856 for (int pos = tag.start; pos < end; pos++) {
3857 if (x < line.widths[pos]) {
3859 return LineTag.GetFinalTag (tag);
3863 return LineTag.GetFinalTag (tag);
3865 if (tag.next != null) {
3873 index = line.text.Length;
3874 return LineTag.GetFinalTag (tag);
3879 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3880 internal LineTag FindCursor(int x, int y, out int index) {
3884 line = GetLineByPixel(multiline ? y : x, false);
3887 /// Special case going leftwards of the first tag
3890 return LineTag.GetFinalTag (tag);
3894 if (x >= tag.X && x < (tag.X+tag.width)) {
3899 for (int pos = tag.start - 1; pos < end; pos++) {
3900 // When clicking on a character, we position the cursor to whatever edge
3901 // of the character the click was closer
3902 if (x < (line.X + line.widths[pos] + ((line.widths[pos+1]-line.widths[pos])/2))) {
3904 return LineTag.GetFinalTag (tag);
3908 return LineTag.GetFinalTag (tag);
3910 if (tag.next != null) {
3913 index = line.TextLengthWithoutEnding ();
3914 return LineTag.GetFinalTag (tag);
3919 /// <summary>Format area of document in specified font and color</summary>
3920 /// <param name="start_pos">1-based start position on start_line</param>
3921 /// <param name="end_pos">1-based end position on end_line </param>
3922 internal void FormatText (Line start_line, int start_pos, Line end_line, int end_pos, Font font,
3923 SolidBrush color, SolidBrush back_color, FormatSpecified specified)
3927 // First, format the first line
3928 if (start_line != end_line) {
3930 LineTag.FormatText(start_line, start_pos, start_line.text.Length - start_pos + 1, font, color, back_color, specified);
3933 LineTag.FormatText(end_line, 1, end_pos, font, color, back_color, specified);
3935 // Now all the lines inbetween
3936 for (int i = start_line.line_no + 1; i < end_line.line_no; i++) {
3938 LineTag.FormatText(l, 1, l.text.Length, font, color, back_color, specified);
3941 // Special case, single line
3942 LineTag.FormatText(start_line, start_pos, end_pos - start_pos, font, color, back_color, specified);
3946 /// <summary>Re-format areas of the document in specified font and color</summary>
3947 /// <param name="start_pos">1-based start position on start_line</param>
3948 /// <param name="end_pos">1-based end position on end_line </param>
3949 /// <param name="font">Font specifying attributes</param>
3950 /// <param name="color">Color (or NULL) to apply</param>
3951 /// <param name="apply">Attributes from font and color to apply</param>
3952 internal void FormatText(Line start_line, int start_pos, Line end_line, int end_pos, FontDefinition attributes) {
3955 // First, format the first line
3956 if (start_line != end_line) {
3958 LineTag.FormatText(start_line, start_pos, start_line.text.Length - start_pos + 1, attributes);
3961 LineTag.FormatText(end_line, 1, end_pos - 1, attributes);
3963 // Now all the lines inbetween
3964 for (int i = start_line.line_no + 1; i < end_line.line_no; i++) {
3966 LineTag.FormatText(l, 1, l.text.Length, attributes);
3969 // Special case, single line
3970 LineTag.FormatText(start_line, start_pos, end_pos - start_pos, attributes);
3974 internal void RecalculateAlignments ()
3983 while (line_no <= lines) {
3984 line = GetLine(line_no);
3987 switch (line.alignment) {
3988 case HorizontalAlignment.Left:
3989 line.align_shift = 0;
3991 case HorizontalAlignment.Center:
3992 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3994 case HorizontalAlignment.Right:
3995 line.align_shift = viewport_width - (int)line.widths[line.text.Length] - right_margin;
4005 /// <summary>Calculate formatting for the whole document</summary>
4006 internal bool RecalculateDocument(Graphics g) {
4007 return RecalculateDocument(g, 1, this.lines, false);
4010 /// <summary>Calculate formatting starting at a certain line</summary>
4011 internal bool RecalculateDocument(Graphics g, int start) {
4012 return RecalculateDocument(g, start, this.lines, false);
4015 /// <summary>Calculate formatting within two given line numbers</summary>
4016 internal bool RecalculateDocument(Graphics g, int start, int end) {
4017 return RecalculateDocument(g, start, end, false);
4020 /// <summary>With optimize on, returns true if line heights changed</summary>
4021 internal bool RecalculateDocument(Graphics g, int start, int end, bool optimize) {
4029 if (recalc_suspended > 0) {
4030 recalc_pending = true;
4031 recalc_start = Math.Min (recalc_start, start);
4032 recalc_end = Math.Max (recalc_end, end);
4033 recalc_optimize = optimize;
4037 // Fixup the positions, they can go kinda nuts
4038 start = Math.Max (start, 1);
4039 end = Math.Min (end, lines);
4041 offset = GetLine(start).offset;
4046 changed = true; // We always return true if we run non-optimized
4051 while (line_no <= (end + this.lines - shift)) {
4052 line = GetLine(line_no++);
4053 line.offset = offset;
4057 line.RecalculateLine(g, this);
4059 if (line.recalc && line.RecalculateLine(g, this)) {
4061 // If the height changed, all subsequent lines change
4068 line.RecalculatePasswordLine(g, this);
4070 if (line.recalc && line.RecalculatePasswordLine(g, this)) {
4072 // If the height changed, all subsequent lines change
4079 if (line.widths[line.text.Length] > new_width) {
4080 new_width = (int)line.widths[line.text.Length];
4083 // Calculate alignment
4084 if (line.alignment != HorizontalAlignment.Left) {
4085 if (line.alignment == HorizontalAlignment.Center) {
4086 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
4088 line.align_shift = viewport_width - (int)line.widths[line.text.Length] - 1;
4093 offset += line.height;
4095 offset += (int) line.widths [line.text.Length];
4097 if (line_no > lines) {
4102 if (document_x != new_width) {
4103 document_x = new_width;
4104 if (WidthChanged != null) {
4105 WidthChanged(this, null);
4109 RecalculateAlignments();
4111 line = GetLine(lines);
4113 if (document_y != line.Y + line.height) {
4114 document_y = line.Y + line.height;
4115 if (HeightChanged != null) {
4116 HeightChanged(this, null);
4123 internal int Size() {
4127 private void owner_HandleCreated(object sender, EventArgs e) {
4128 RecalculateDocument(owner.CreateGraphicsInternal());
4132 private void owner_VisibleChanged(object sender, EventArgs e) {
4133 if (owner.Visible) {
4134 RecalculateDocument(owner.CreateGraphicsInternal());
4138 internal static bool IsWordSeparator (char ch)
4153 internal int FindWordSeparator(Line line, int pos, bool forward) {
4156 len = line.text.Length;
4159 for (int i = pos + 1; i < len; i++) {
4160 if (IsWordSeparator(line.Text[i])) {
4166 for (int i = pos - 1; i > 0; i--) {
4167 if (IsWordSeparator(line.Text[i - 1])) {
4175 /* Search document for text */
4176 internal bool FindChars(char[] chars, Marker start, Marker end, out Marker result) {
4182 // Search for occurence of any char in the chars array
4183 result = new Marker();
4186 line_no = start.line.line_no;
4188 while (line_no <= end.line.line_no) {
4189 line_len = line.text.Length;
4190 while (pos < line_len) {
4191 for (int i = 0; i < chars.Length; i++) {
4192 if (line.text[pos] == chars[i]) {
4194 if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
4208 line = GetLine(line_no);
4214 // This version does not build one big string for searching, instead it handles
4215 // line-boundaries, which is faster and less memory intensive
4216 // FIXME - Depending on culture stuff we might have to create a big string and use culturespecific
4217 // search stuff and change it to accept and return positions instead of Markers (which would match
4218 // RichTextBox behaviour better but would be inconsistent with the rest of TextControl)
4219 internal bool Find(string search, Marker start, Marker end, out Marker result, RichTextBoxFinds options) {
4221 string search_string;
4233 result = new Marker();
4234 word_option = ((options & RichTextBoxFinds.WholeWord) != 0);
4235 ignore_case = ((options & RichTextBoxFinds.MatchCase) == 0);
4236 reverse = ((options & RichTextBoxFinds.Reverse) != 0);
4239 line_no = start.line.line_no;
4243 // Prep our search string, lowercasing it if we do case-independent matching
4246 sb = new StringBuilder(search);
4247 for (int i = 0; i < sb.Length; i++) {
4248 sb[i] = Char.ToLower(sb[i]);
4250 search_string = sb.ToString();
4252 search_string = search;
4255 // We need to check if the character before our start position is a wordbreak
4258 if ((pos == 0) || (IsWordSeparator(line.text[pos - 1]))) {
4265 if (IsWordSeparator(line.text[pos - 1])) {
4271 // Need to check the end of the previous line
4274 prev_line = GetLine(line_no - 1);
4275 if (prev_line.ending == LineEnding.Wrap) {
4276 if (IsWordSeparator(prev_line.text[prev_line.text.Length - 1])) {
4290 // To avoid duplication of this loop with reverse logic, we search
4291 // through the document, remembering the last match and when returning
4292 // report that last remembered match
4294 last = new Marker();
4295 last.height = -1; // Abused - we use it to track change
4297 while (line_no <= end.line.line_no) {
4298 if (line_no != end.line.line_no) {
4299 line_len = line.text.Length;
4304 while (pos < line_len) {
4306 if (word_option && (current == search_string.Length)) {
4307 if (IsWordSeparator(line.text[pos])) {
4320 c = Char.ToLower(line.text[pos]);
4325 if (c == search_string[current]) {
4331 if (!word_option || (word_option && (word || (current > 0)))) {
4335 if (!word_option && (current == search_string.Length)) {
4352 if (IsWordSeparator(c)) {
4360 // Mark that we just saw a word boundary
4361 if (line.ending != LineEnding.Wrap || line.line_no == lines - 1) {
4365 if (current == search_string.Length) {
4381 line = GetLine(line_no);
4385 if (last.height != -1) {
4395 // if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
4407 internal void GetMarker(out Marker mark, bool start) {
4408 mark = new Marker();
4411 mark.line = GetLine(1);
4412 mark.tag = mark.line.tags;
4415 mark.line = GetLine(lines);
4416 mark.tag = mark.line.tags;
4417 while (mark.tag.next != null) {
4418 mark.tag = mark.tag.next;
4420 mark.pos = mark.line.text.Length;
4423 #endregion // Internal Methods
4426 internal event EventHandler CaretMoved;
4427 internal event EventHandler WidthChanged;
4428 internal event EventHandler HeightChanged;
4429 internal event EventHandler LengthChanged;
4430 #endregion // Events
4432 #region Administrative
4433 public IEnumerator GetEnumerator() {
4438 public override bool Equals(object obj) {
4443 if (!(obj is Document)) {
4451 if (ToString().Equals(((Document)obj).ToString())) {
4458 public override int GetHashCode() {
4462 public override string ToString() {
4463 return "document " + this.document_id;
4465 #endregion // Administrative
4468 internal class PictureTag : LineTag {
4470 internal RTF.Picture picture;
4472 internal PictureTag (Line line, int start, RTF.Picture picture) : base (line, start)
4474 this.picture = picture;
4477 public override bool IsTextTag {
4478 get { return false; }
4481 internal override SizeF SizeOfPosition (Graphics dc, int pos)
4483 return picture.Size;
4486 internal override int MaxHeight ()
4488 return (int) (picture.Height + 0.5F);
4491 internal override void Draw (Graphics dc, Brush brush, float xoff, float y, int start, int end)
4493 picture.DrawImage (dc, xoff + line.widths [start], y, false);
4496 internal override void Draw (Graphics dc, Brush brush, float xoff, float y, int start, int end, string text)
4498 picture.DrawImage (dc, xoff + + line.widths [start], y, false);
4501 public override string Text ()
4507 internal class LineTag {
4508 #region Local Variables;
4509 // Payload; formatting
4510 internal Font font; // System.Drawing.Font object for this tag
4511 internal SolidBrush color; // The font color for this tag
4513 // In 2.0 tags can have background colours. I'm not going to #ifdef
4514 // at this level though since I want to reduce code paths
4515 internal SolidBrush back_color;
4518 internal int start; // start, in chars; index into Line.text
4519 internal bool r_to_l; // Which way is the font
4522 internal int height; // Height in pixels of the text this tag describes
4524 internal int ascent; // Ascent of the font for this tag
4525 internal int shift; // Shift down for this tag, to stay on baseline
4528 internal Line line; // The line we're on
4529 internal LineTag next; // Next tag on the same line
4530 internal LineTag previous; // Previous tag on the same line
4533 #region Constructors
4534 internal LineTag(Line line, int start) {
4538 #endregion // Constructors
4540 #region Internal Methods
4546 return line.X + line.widths [start - 1];
4551 get { return start + length; }
4554 public int TextEnd {
4555 get { return start + TextLength; }
4558 public float width {
4562 return line.widths [start + length - 1] - (start != 0 ? line.widths [start - 1] : 0);
4570 res = next.start - start;
4572 res = line.text.Length - (start - 1);
4574 return res > 0 ? res : 0;
4578 public int TextLength {
4582 res = next.start - start;
4584 res = line.TextLengthWithoutEnding () - (start - 1);
4586 return res > 0 ? res : 0;
4590 public virtual bool IsTextTag {
4591 get { return true; }
4594 internal virtual SizeF SizeOfPosition (Graphics dc, int pos)
4596 if (pos >= line.TextLengthWithoutEnding () && line.document.multiline)
4599 string text = line.text.ToString (pos, 1);
4600 switch ((int) text [0]) {
4602 if (!line.document.multiline)
4604 SizeF res = dc.MeasureString (" ", font, 10000, Document.string_format);
4609 return dc.MeasureString ("\u0013", font, 10000, Document.string_format);
4612 return dc.MeasureString (text, font, 10000, Document.string_format);
4615 internal virtual int MaxHeight ()
4620 internal virtual void Draw (Graphics dc, Brush brush, float x, float y, int start, int end)
4622 dc.DrawString (line.text.ToString (start, end), font, brush, x, y, StringFormat.GenericTypographic);
4625 internal virtual void Draw (Graphics dc, Brush brush, float xoff, float y, int start, int end, string text)
4627 while (start < end) {
4628 int tab_index = text.IndexOf ("\t", start);
4629 if (tab_index == -1)
4631 dc.DrawString (text.Substring (start, tab_index - start), font, brush, xoff + line.widths [start],
4632 y, StringFormat.GenericTypographic);
4634 // non multilines get the unknown char
4635 if (!line.document.multiline && tab_index != end)
4636 dc.DrawString ("\u0013", font, brush, xoff + line.widths [tab_index], y, Document.string_format);
4638 start = tab_index + 1;
4642 ///<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>
4643 internal LineTag Break(int pos) {
4648 if (pos == this.start) {
4650 } else if (pos >= (start + length)) {
4654 new_tag = new LineTag(line, pos);
4655 new_tag.CopyFormattingFrom (this);
4657 new_tag.next = this.next;
4658 this.next = new_tag;
4659 new_tag.previous = this;
4661 if (new_tag.next != null) {
4662 new_tag.next.previous = new_tag;
4668 public virtual string Text ()
4670 return line.text.ToString (start - 1, length);
4673 public void CopyFormattingFrom (LineTag other)
4675 height = other.height;
4677 color = other.color;
4678 back_color = other.back_color;
4681 ///<summary>Create new font and brush from existing font and given new attributes. Returns true if fontheight changes</summary>
4682 internal static bool GenerateTextFormat(Font font_from, SolidBrush color_from, FontDefinition attributes, out Font new_font, out SolidBrush new_color) {
4688 if (attributes.font_obj == null) {
4689 size = font_from.SizeInPoints;
4690 unit = font_from.Unit;
4691 face = font_from.Name;
4692 style = font_from.Style;
4694 if (attributes.face != null) {
4695 face = attributes.face;
4698 if (attributes.size != 0) {
4699 size = attributes.size;
4702 style |= attributes.add_style;
4703 style &= ~attributes.remove_style;
4706 new_font = new Font(face, size, style, unit);
4708 new_font = attributes.font_obj;
4711 // Create 'new' color brush
4712 if (attributes.color != Color.Empty) {
4713 new_color = new SolidBrush(attributes.color);
4715 new_color = color_from;
4718 if (new_font.Height == font_from.Height) {
4724 /// <summary>Applies 'font' and 'brush' to characters starting at 'start' for 'length' chars;
4725 /// Removes any previous tags overlapping the same area;
4726 /// returns true if lineheight has changed</summary>
4727 /// <param name="start">1-based character position on line</param>
4728 internal static bool FormatText(Line line, int start, int length, Font font, SolidBrush color, SolidBrush back_color, FormatSpecified specified)
4734 bool retval = false; // Assume line-height doesn't change
4737 if (((FormatSpecified.Font & specified) == FormatSpecified.Font) && font.Height != line.height) {
4740 line.recalc = true; // This forces recalculation of the line in RecalculateDocument
4742 // A little sanity, not sure if it's needed, might be able to remove for speed
4743 if (length > line.text.Length) {
4744 length = line.text.Length;
4748 end = start + length;
4750 // Common special case
4751 if ((start == 1) && (length == tag.length)) {
4753 SetFormat (tag, font, color, back_color, specified);
4757 start_tag = FindTag (line, start);
4758 tag = start_tag.Break (start);
4760 while (tag != null && tag.end <= end) {
4761 SetFormat (tag, font, color, back_color, specified);
4765 if (tag != null && tag.end == end)
4768 /// Now do the last tag
4769 end_tag = FindTag (line, end);
4771 if (end_tag != null) {
4772 end_tag.Break (end);
4773 SetFormat (end_tag, font, color, back_color, specified);
4779 private static void SetFormat (LineTag tag, Font font, SolidBrush color, SolidBrush back_color, FormatSpecified specified)
4781 if ((FormatSpecified.Font & specified) == FormatSpecified.Font)
4783 if ((FormatSpecified.Color & specified) == FormatSpecified.Color)
4785 if ((FormatSpecified.BackColor & specified) == FormatSpecified.BackColor) {
4786 tag.back_color = back_color;
4788 // Console.WriteLine ("setting format: {0} {1} new color {2}", color.Color, specified, tag.color.Color);
4791 /// <summary>Applies font attributes specified to characters starting at 'start' for 'length' chars;
4792 /// Breaks tags at start and end point, keeping middle tags with altered attributes.
4793 /// Returns true if lineheight has changed</summary>
4794 /// <param name="start">1-based character position on line</param>
4795 internal static bool FormatText(Line line, int start, int length, FontDefinition attributes) {
4799 bool retval = false; // Assume line-height doesn't change
4801 line.recalc = true; // This forces recalculation of the line in RecalculateDocument
4803 // A little sanity, not sure if it's needed, might be able to remove for speed
4804 if (length > line.text.Length) {
4805 length = line.text.Length;
4810 // Common special case
4811 if ((start == 1) && (length == tag.length)) {
4813 GenerateTextFormat(tag.font, tag.color, attributes, out tag.font, out tag.color);
4817 start_tag = FindTag(line, start);
4819 if (start_tag == null) {
4821 // We are 'starting' after all valid tags; create a new tag with the right attributes
4822 start_tag = FindTag(line, line.TextLengthWithoutEnding () - 1);
4823 start_tag.next = new LineTag(line, line.TextLengthWithoutEnding () + 1);
4824 start_tag.next.CopyFormattingFrom (start_tag);
4825 start_tag.next.previous = start_tag;
4826 start_tag = start_tag.next;
4828 throw new Exception(String.Format("Could not find start_tag in document at line {0} position {1}", line.line_no, start));
4831 start_tag = start_tag.Break(start);
4834 end_tag = FindTag(line, start + length);
4835 if (end_tag != null) {
4836 end_tag = end_tag.Break(start + length);
4839 // start_tag or end_tag might be null; we're cool with that
4840 // we now walk from start_tag to end_tag, applying new attributes
4842 while ((tag != null) && tag != end_tag) {
4843 if (LineTag.GenerateTextFormat(tag.font, tag.color, attributes, out tag.font, out tag.color)) {
4852 /// <summary>Finds the tag that describes the character at position 'pos' on 'line'</summary>
4853 internal static LineTag FindTag(Line line, int pos) {
4854 LineTag tag = line.tags;
4856 // Beginning of line is a bit special
4858 // Not sure if we should get the final tag here
4862 while (tag != null) {
4863 if ((tag.start <= pos) && (pos <= tag.end)) {
4864 return GetFinalTag (tag);
4873 // There can be multiple tags at the same position, we want to make
4874 // sure we are using the very last tag at the given position
4875 internal static LineTag GetFinalTag (LineTag tag)
4879 while (res.length == 0 && res.next != null && res.next.length == 0)
4885 /// <summary>Combines 'this' tag with 'other' tag</summary>
4886 internal bool Combine(LineTag other) {
4887 if (!this.Equals(other)) {
4891 this.next = other.next;
4892 if (this.next != null) {
4893 this.next.previous = this;
4900 /// <summary>Remove 'this' tag ; to be called when formatting is to be removed</summary>
4901 internal bool Remove() {
4902 if ((this.start == 1) && (this.next == null)) {
4903 // We cannot remove the only tag
4906 if (this.start != 1) {
4907 this.previous.next = this.next;
4908 this.next.previous = this.previous;
4910 this.next.start = 1;
4911 this.line.tags = this.next;
4912 this.next.previous = null;
4918 /// <summary>Checks if 'this' tag describes the same formatting options as 'obj'</summary>
4919 public override bool Equals(object obj) {
4926 if (!(obj is LineTag)) {
4934 other = (LineTag)obj;
4936 if (other.IsTextTag != IsTextTag)
4939 if (this.font.Equals(other.font) && this.color.Equals(other.color)) { // FIXME add checking for things like link or type later
4946 public override int GetHashCode() {
4947 return base.GetHashCode ();
4950 public override string ToString() {
4952 return GetType () + " Tag starts at index " + this.start + " length " + this.length + " text: " + Text () + "Font " + this.font.ToString();
4953 return "Zero Lengthed tag at index " + this.start;
4956 #endregion // Internal Methods
4959 internal class UndoManager {
4961 internal enum ActionType {
4965 // This is basically just cut & paste
4973 internal class Action {
4974 internal ActionType type;
4975 internal int line_no;
4977 internal object data;
4980 #region Local Variables
4981 private Document document;
4982 private Stack undo_actions;
4983 private Stack redo_actions;
4985 private int caret_line;
4986 private int caret_pos;
4988 // When performing an action, we lock the queue, so that the action can't be undone
4989 private bool locked;
4990 #endregion // Local Variables
4992 #region Constructors
4993 internal UndoManager (Document document)
4995 this.document = document;
4996 undo_actions = new Stack (50);
4997 redo_actions = new Stack (50);
4999 #endregion // Constructors
5002 internal bool CanUndo {
5003 get { return undo_actions.Count > 0; }
5006 internal bool CanRedo {
5007 get { return redo_actions.Count > 0; }
5010 internal string UndoActionName {
5012 foreach (Action action in undo_actions) {
5013 if (action.type == ActionType.UserActionBegin)
5014 return (string) action.data;
5015 if (action.type == ActionType.Typing)
5016 return Locale.GetText ("Typing");
5018 return String.Empty;
5022 internal string RedoActionName {
5024 foreach (Action action in redo_actions) {
5025 if (action.type == ActionType.UserActionBegin)
5026 return (string) action.data;
5027 if (action.type == ActionType.Typing)
5028 return Locale.GetText ("Typing");
5030 return String.Empty;
5033 #endregion // Properties
5035 #region Internal Methods
5036 internal void Clear ()
5038 undo_actions.Clear();
5039 redo_actions.Clear();
5042 internal void Undo ()
5045 bool user_action_finished = false;
5047 if (undo_actions.Count == 0)
5050 // Nuke the redo queue
5051 redo_actions.Clear ();
5056 action = (Action) undo_actions.Pop ();
5058 // Put onto redo stack
5059 redo_actions.Push(action);
5062 switch(action.type) {
5064 case ActionType.UserActionBegin:
5065 user_action_finished = true;
5068 case ActionType.UserActionEnd:
5072 case ActionType.InsertString:
5073 start = document.GetLine (action.line_no);
5074 document.SuspendUpdate ();
5075 document.DeleteMultiline (start, action.pos, ((string) action.data).Length + 1);
5076 document.PositionCaret (start, action.pos);
5077 document.SetSelectionToCaret (true);
5078 document.ResumeUpdate (true);
5081 case ActionType.Typing:
5082 start = document.GetLine (action.line_no);
5083 document.SuspendUpdate ();
5084 document.DeleteMultiline (start, action.pos, ((StringBuilder) action.data).Length);
5085 document.PositionCaret (start, action.pos);
5086 document.SetSelectionToCaret (true);
5087 document.ResumeUpdate (true);
5089 // This is an open ended operation, so only a single typing operation can be undone at once
5090 user_action_finished = true;
5093 case ActionType.DeleteString:
5094 start = document.GetLine (action.line_no);
5095 document.SuspendUpdate ();
5096 Insert (start, action.pos, (Line) action.data, true);
5097 document.ResumeUpdate (true);
5100 } while (!user_action_finished && undo_actions.Count > 0);
5105 internal void Redo ()
5108 bool user_action_finished = false;
5110 if (redo_actions.Count == 0)
5113 // You can't undo anything after redoing
5114 undo_actions.Clear ();
5121 action = (Action) redo_actions.Pop ();
5123 switch (action.type) {
5125 case ActionType.UserActionBegin:
5129 case ActionType.UserActionEnd:
5130 user_action_finished = true;
5133 case ActionType.InsertString:
5134 start = document.GetLine (action.line_no);
5135 document.SuspendUpdate ();
5136 start_index = document.LineTagToCharIndex (start, action.pos);
5137 document.InsertString (start, action.pos, (string) action.data);
5138 document.CharIndexToLineTag (start_index + ((string) action.data).Length,
5139 out document.caret.line, out document.caret.tag,
5140 out document.caret.pos);
5141 document.UpdateCaret ();
5142 document.SetSelectionToCaret (true);
5143 document.ResumeUpdate (true);
5146 case ActionType.Typing:
5147 start = document.GetLine (action.line_no);
5148 document.SuspendUpdate ();
5149 start_index = document.LineTagToCharIndex (start, action.pos);
5150 document.InsertString (start, action.pos, ((StringBuilder) action.data).ToString ());
5151 document.CharIndexToLineTag (start_index + ((StringBuilder) action.data).Length,
5152 out document.caret.line, out document.caret.tag,
5153 out document.caret.pos);
5154 document.UpdateCaret ();
5155 document.SetSelectionToCaret (true);
5156 document.ResumeUpdate (true);
5158 // This is an open ended operation, so only a single typing operation can be undone at once
5159 user_action_finished = true;
5162 case ActionType.DeleteString:
5163 start = document.GetLine (action.line_no);
5164 document.SuspendUpdate ();
5165 document.DeleteMultiline (start, action.pos, ((Line) action.data).text.Length);
5166 document.PositionCaret (start, action.pos);
5167 document.SetSelectionToCaret (true);
5168 document.ResumeUpdate (true);
5172 } while (!user_action_finished && redo_actions.Count > 0);
5176 #endregion // Internal Methods
5178 #region Private Methods
5180 public void BeginUserAction (string name)
5185 Action ua = new Action ();
5186 ua.type = ActionType.UserActionBegin;
5189 undo_actions.Push (ua);
5192 public void EndUserAction ()
5197 Action ua = new Action ();
5198 ua.type = ActionType.UserActionEnd;
5200 undo_actions.Push (ua);
5203 // start_pos, end_pos = 1 based
5204 public void RecordDeleteString (Line start_line, int start_pos, Line end_line, int end_pos)
5209 Action a = new Action ();
5211 // We cant simply store the string, because then formatting would be lost
5212 a.type = ActionType.DeleteString;
5213 a.line_no = start_line.line_no;
5215 a.data = Duplicate (start_line, start_pos, end_line, end_pos);
5217 undo_actions.Push(a);
5220 public void RecordInsertString (Line line, int pos, string str)
5222 if (locked || str.Length == 0)
5225 Action a = new Action ();
5227 a.type = ActionType.InsertString;
5229 a.line_no = line.line_no;
5232 undo_actions.Push (a);
5235 public void RecordTyping (Line line, int pos, char ch)
5242 if (undo_actions.Count > 0)
5243 a = (Action) undo_actions.Peek ();
5245 if (a == null || a.type != ActionType.Typing) {
5247 a.type = ActionType.Typing;
5248 a.data = new StringBuilder ();
5249 a.line_no = line.line_no;
5252 undo_actions.Push (a);
5255 StringBuilder data = (StringBuilder) a.data;
5259 // start_pos = 1-based
5260 // end_pos = 1-based
5261 public Line Duplicate(Line start_line, int start_pos, Line end_line, int end_pos)
5267 LineTag current_tag;
5272 line = new Line (start_line.document, start_line.ending);
5275 for (int i = start_line.line_no; i <= end_line.line_no; i++) {
5276 current = document.GetLine(i);
5278 if (start_line.line_no == i) {
5284 if (end_line.line_no == i) {
5287 end = current.text.Length;
5294 line.text = new StringBuilder (current.text.ToString (start, end - start));
5296 // Copy tags from start to start+length onto new line
5297 current_tag = current.FindTag (start);
5298 while ((current_tag != null) && (current_tag.start <= end)) {
5299 if ((current_tag.start <= start) && (start < (current_tag.start + current_tag.length))) {
5300 // start tag is within this tag
5303 tag_start = current_tag.start;
5306 tag = new LineTag(line, tag_start - start + 1);
5307 tag.CopyFormattingFrom (current_tag);
5309 current_tag = current_tag.next;
5311 // Add the new tag to the line
5312 if (line.tags == null) {
5318 while (tail.next != null) {
5322 tag.previous = tail;
5326 if ((i + 1) <= end_line.line_no) {
5327 line.ending = current.ending;
5329 // Chain them (we use right/left as next/previous)
5330 line.right = new Line (start_line.document, start_line.ending);
5331 line.right.left = line;
5339 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
5340 internal void Insert(Line line, int pos, Line insert, bool select)
5348 // Handle special case first
5349 if (insert.right == null) {
5351 // Single line insert
5352 document.Split(line, pos);
5354 if (insert.tags == null) {
5355 return; // Blank line
5358 //Insert our tags at the end
5361 while (tag.next != null) {
5365 offset = tag.start + tag.length - 1;
5367 tag.next = insert.tags;
5368 line.text.Insert(offset, insert.text.ToString());
5370 // Adjust start locations
5372 while (tag != null) {
5373 tag.start += offset;
5377 // Put it back together
5378 document.Combine(line.line_no, line.line_no + 1);
5381 document.SetSelectionStart (line, pos, false);
5382 document.SetSelectionEnd (line, pos + insert.text.Length, false);
5385 document.UpdateView(line, pos);
5393 while (current != null) {
5395 if (current == insert) {
5396 // Inserting the first line we split the line (and make space)
5397 document.Split(line.line_no, pos);
5398 //Insert our tags at the end of the line
5402 if (tag != null && tag.length != 0) {
5403 while (tag.next != null) {
5406 offset = tag.start + tag.length - 1;
5407 tag.next = current.tags;
5408 tag.next.previous = tag;
5414 line.tags = current.tags;
5415 line.tags.previous = null;
5419 line.ending = current.ending;
5421 document.Split(line.line_no, 0);
5423 line.tags = current.tags;
5424 line.tags.previous = null;
5425 line.ending = current.ending;
5429 // Adjust start locations and line pointers
5430 while (tag != null) {
5431 tag.start += offset - 1;
5436 line.text.Insert(offset, current.text.ToString());
5437 line.Grow(line.text.Length);
5440 line = document.GetLine(line.line_no + 1);
5442 // FIXME? Test undo of line-boundaries
5443 if ((current.right == null) && (current.tags.length != 0)) {
5444 document.Combine(line.line_no - 1, line.line_no);
5446 current = current.right;
5451 // Recalculate our document
5452 document.UpdateView(first, lines, pos);
5455 #endregion // Private Methods