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
68 internal enum FormatSpecified {
76 internal enum CaretDirection {
77 CharForward, // Move a char to the right
78 CharBack, // Move a char to the left
79 LineUp, // Move a line up
80 LineDown, // Move a line down
81 Home, // Move to the beginning of the line
82 End, // Move to the end of the line
83 PgUp, // Move one page up
84 PgDn, // Move one page down
85 CtrlPgUp, // Move caret to the first visible char in the viewport
86 CtrlPgDn, // Move caret to the last visible char in the viewport
87 CtrlHome, // Move to the beginning of the document
88 CtrlEnd, // Move to the end of the document
89 WordBack, // Move to the beginning of the previous word (or beginning of line)
90 WordForward, // Move to the beginning of the next word (or end of line)
91 SelectionStart, // Move to the beginning of the current selection
92 SelectionEnd, // Move to the end of the current selection
93 CharForwardNoWrap, // Move a char forward, but don't wrap onto the next line
94 CharBackNoWrap // Move a char backward, but don't wrap onto the previous line
97 internal enum LineEnding {
98 Wrap, // line wraps to the next line
107 // Being cloneable should allow for nice line and document copies...
108 internal class Line : ICloneable, IComparable {
109 #region Local Variables
111 internal Document document;
113 // Stuff that matters for our line
114 internal StringBuilder text; // Characters for the line
115 internal float[] widths; // Width of each character; always one larger than text.Length
116 internal int space; // Number of elements in text and widths
117 internal int line_no; // Line number
118 internal LineTag tags; // Tags describing the text
119 internal int offset; // Baseline can be on the X or Y axis depending if we are in multiline mode or not
120 internal int height; // Height of the line (height of tallest tag)
121 internal int ascent; // Ascent of the line (ascent of the tallest tag)
122 internal HorizontalAlignment alignment; // Alignment of the line
123 internal int align_shift; // Pixel shift caused by the alignment
124 internal int indent; // Left indent for the first line
125 internal int hanging_indent; // Hanging indent (left indent for all but the first line)
126 internal int right_indent; // Right indent for all lines
127 internal LineEnding ending;
130 // Stuff that's important for the tree
131 internal Line parent; // Our parent line
132 internal Line left; // Line with smaller line number
133 internal Line right; // Line with higher line number
134 internal LineColor color; // We're doing a black/red tree. this is the node color
135 internal int DEFAULT_TEXT_LEN; //
136 internal bool recalc; // Line changed
137 #endregion // Local Variables
140 internal Line (Document document, LineEnding ending)
142 this.document = document;
143 color = LineColor.Red;
149 alignment = document.alignment;
151 this.ending = ending;
154 internal Line(Document document, int LineNo, string Text, Font font, SolidBrush color, LineEnding ending) : this (document, ending)
156 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
158 text = new StringBuilder(Text, space);
160 this.ending = ending;
162 widths = new float[space + 1];
165 tags = new LineTag(this, 1);
170 internal Line(Document document, int LineNo, string Text, HorizontalAlignment align, Font font, SolidBrush color, LineEnding ending) : this(document, ending)
172 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
174 text = new StringBuilder(Text, space);
176 this.ending = ending;
179 widths = new float[space + 1];
182 tags = new LineTag(this, 1);
187 internal Line(Document document, int LineNo, string Text, LineTag tag, LineEnding ending) : this(document, ending)
189 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
191 text = new StringBuilder(Text, space);
192 this.ending = ending;
195 widths = new float[space + 1];
199 #endregion // Constructors
201 #region Internal Properties
205 if (!document.multiline)
206 return document.top_margin;
207 return document.top_margin + offset;
213 if (document.multiline)
215 return offset + align_shift;
221 int res = (int) widths [text.Length];
222 if (!document.multiline) {
229 internal int Indent {
240 internal int HangingIndent {
242 return hanging_indent;
246 hanging_indent = value;
251 internal int RightIndent {
257 right_indent = value;
263 internal int Height {
273 internal int LineNo {
283 internal string Text {
285 return text.ToString();
289 text = new StringBuilder(value, value.Length > DEFAULT_TEXT_LEN ? value.Length : DEFAULT_TEXT_LEN);
293 internal HorizontalAlignment Alignment {
299 if (alignment != value) {
306 internal StringBuilder Text {
316 #endregion // Internal Properties
318 #region Internal Methods
320 // This doesn't do exactly what you would think, it just pulls of the \n part of the ending
321 internal string TextWithoutEnding ()
323 return text.ToString (0, text.Length - document.LineEndingLength (ending));
326 internal int TextLengthWithoutEnding ()
328 return text.Length - document.LineEndingLength (ending);
331 internal void DrawEnding (Graphics dc, float y)
333 if (document.multiline)
336 while (last.next != null)
339 string end_str = null;
340 switch (document.LineEndingLength (ending)) {
347 end_str = "\u0013\u0013";
350 end_str = "\u0013\u0013\u0013";
353 dc.DrawString (end_str, last.font, last.color, X + widths [TextLengthWithoutEnding ()] - document.viewport_x,
354 y, Document.string_format);
358 // Make sure we always have enoughs space in text and widths
359 internal void Grow(int minimum) {
363 length = text.Length;
365 if ((length + minimum) > space) {
366 // We need to grow; double the size
368 if ((length + minimum) > (space * 2)) {
369 new_widths = new float[length + minimum * 2 + 1];
370 space = length + minimum * 2;
372 new_widths = new float[space * 2 + 1];
375 widths.CopyTo(new_widths, 0);
381 internal void Streamline(int lines) {
389 // Catch what the loop below wont; eliminate 0 length
390 // tags, but only if there are other tags after us
391 // We only eliminate text tags if there is another text tag
392 // after it. Otherwise we wind up trying to type on picture tags
394 while ((current.length == 0) && (next != null) && (next.IsTextTag)) {
396 tags.previous = null;
406 while (next != null) {
407 // Take out 0 length tags unless it's the last tag in the document
408 if (current.IsTextTag && next.length == 0 && next.IsTextTag) {
409 if ((next.next != null) || (line_no != lines)) {
410 current.next = next.next;
411 if (current.next != null) {
412 current.next.previous = current;
418 if (current.Combine(next)) {
423 current = current.next;
428 /// <summary> Find the tag on a line based on the character position, pos is 0-based</summary>
429 internal LineTag FindTag(int pos) {
438 if (pos >= text.Length) {
439 pos = text.Length - 1;
442 while (tag != null) {
443 if (((tag.start - 1) <= pos) && (pos < (tag.start + tag.length - 1))) {
444 return LineTag.GetFinalTag (tag);
452 /// Recalculate a single line using the same char for every character in the line
455 internal bool RecalculatePasswordLine(Graphics g, Document doc) {
464 len = this.text.Length;
470 widths[0] = document.left_margin + indent;
472 w = g.MeasureString(doc.password_char, tags.font, 10000, Document.string_format).Width;
474 if (this.height != (int)tag.font.Height) {
480 this.height = (int)tag.font.Height;
481 tag.height = this.height;
483 XplatUI.GetFontMetrics(g, tag.font, out tag.ascent, out descent);
484 this.ascent = tag.ascent;
488 widths[pos] = widths[pos-1] + w;
495 /// Go through all tags on a line and recalculate all size-related values;
496 /// returns true if lineheight changed
498 internal bool RecalculateLine(Graphics g, Document doc) {
511 len = this.text.Length;
513 prev_offset = this.offset; // For drawing optimization calculations
514 this.height = 0; // Reset line height
515 this.ascent = 0; // Reset the ascent for the line
518 if (ending == LineEnding.Wrap) {
519 widths[0] = document.left_margin + hanging_indent;
521 widths[0] = document.left_margin + indent;
532 while (tag.length == 0) { // We should always have tags after a tag.length==0 unless len==0
538 size = tag.SizeOfPosition (g, pos);
541 if (Char.IsWhiteSpace(text[pos])) {
546 if ((wrap_pos > 0) && (wrap_pos != len) && (widths[pos] + w) + 5 > (doc.viewport_width - this.right_indent)) {
547 // Make sure to set the last width of the line before wrapping
548 widths [pos + 1] = widths [pos] + w;
552 doc.Split(this, tag, pos);
553 ending = LineEnding.Wrap;
554 len = this.text.Length;
558 } else if (pos > 1 && (widths[pos] + w) > (doc.viewport_width - this.right_indent)) {
559 // No suitable wrap position was found so break right in the middle of a word
561 // Make sure to set the last width of the line before wrapping
562 widths [pos + 1] = widths [pos] + w;
564 doc.Split(this, tag, pos);
565 ending = LineEnding.Wrap;
566 len = this.text.Length;
572 // Contract all wrapped lines that follow back into our line
576 widths[pos] = widths[pos-1] + w;
579 line = doc.GetLine(this.line_no + 1);
580 if ((line != null) && (ending == LineEnding.Wrap || ending == LineEnding.None)) {
581 // Pull the two lines together
582 doc.Combine(this.line_no, this.line_no + 1);
583 len = this.text.Length;
589 if (pos == (tag.start-1 + tag.length)) {
590 // We just found the end of our current tag
591 tag.height = tag.MaxHeight ();
593 // Check if we're the tallest on the line (so far)
594 if (tag.height > this.height) {
595 this.height = tag.height; // Yep; make sure the line knows
598 if (tag.ascent == 0) {
601 XplatUI.GetFontMetrics(g, tag.font, out tag.ascent, out descent);
604 if (tag.ascent > this.ascent) {
607 // We have a tag that has a taller ascent than the line;
609 while (t != null && t != tag) {
610 t.shift = tag.ascent - t.ascent;
615 this.ascent = tag.ascent;
617 tag.shift = this.ascent - tag.ascent;
628 if (this.height == 0) {
629 this.height = tags.font.Height;
630 tag.height = this.height;
633 if (prev_offset != offset) {
638 #endregion // Internal Methods
640 #region Administrative
641 public int CompareTo(object obj) {
646 if (! (obj is Line)) {
647 throw new ArgumentException("Object is not of type Line", "obj");
650 if (line_no < ((Line)obj).line_no) {
652 } else if (line_no > ((Line)obj).line_no) {
659 public object Clone() {
662 clone = new Line (document, ending);
667 clone.left = (Line)left.Clone();
671 clone.left = (Line)left.Clone();
677 internal object CloneLine() {
680 clone = new Line (document, ending);
687 public override bool Equals(object obj) {
692 if (!(obj is Line)) {
700 if (line_no == ((Line)obj).line_no) {
707 public override int GetHashCode() {
708 return base.GetHashCode ();
711 public override string ToString() {
712 return "Line " + line_no;
715 #endregion // Administrative
718 internal class Document : ICloneable, IEnumerable {
720 // FIXME - go through code and check for places where
721 // we do explicit comparisons instead of using the compare overloads
722 internal struct Marker {
724 internal LineTag tag;
728 public static bool operator<(Marker lhs, Marker rhs) {
729 if (lhs.line.line_no < rhs.line.line_no) {
733 if (lhs.line.line_no == rhs.line.line_no) {
734 if (lhs.pos < rhs.pos) {
741 public static bool operator>(Marker lhs, Marker rhs) {
742 if (lhs.line.line_no > rhs.line.line_no) {
746 if (lhs.line.line_no == rhs.line.line_no) {
747 if (lhs.pos > rhs.pos) {
754 public static bool operator==(Marker lhs, Marker rhs) {
755 if ((lhs.line.line_no == rhs.line.line_no) && (lhs.pos == rhs.pos)) {
761 public static bool operator!=(Marker lhs, Marker rhs) {
762 if ((lhs.line.line_no != rhs.line.line_no) || (lhs.pos != rhs.pos)) {
768 public void Combine(Line move_to_line, int move_to_line_length) {
770 pos += move_to_line_length;
771 tag = LineTag.FindTag(line, pos);
774 // This is for future use, right now Document.Split does it by hand, with some added shortcut logic
775 public void Split(Line move_to_line, int split_at) {
778 tag = LineTag.FindTag(line, pos);
781 public override bool Equals(object obj) {
782 return this==(Marker)obj;
785 public override int GetHashCode() {
786 return base.GetHashCode ();
789 public override string ToString() {
790 return "Marker Line " + line + ", Position " + pos;
794 #endregion Structures
796 #region Local Variables
797 private Line document;
799 private Line sentinel;
800 private int document_id;
801 private Random random = new Random();
802 internal string password_char;
803 private StringBuilder password_cache;
804 private bool calc_pass;
805 private int char_count;
807 // For calculating widths/heights
808 public static readonly StringFormat string_format = new StringFormat (StringFormat.GenericTypographic);
810 private int recalc_suspended;
811 private bool recalc_pending;
812 private int recalc_start = 1; // This starts at one, since lines are 1 based
813 private int recalc_end;
814 private bool recalc_optimize;
816 private int update_suspended;
817 private bool update_pending;
818 private int update_start = 1;
820 internal bool multiline;
821 internal HorizontalAlignment alignment;
824 internal UndoManager undo;
826 internal Marker caret;
827 internal Marker selection_start;
828 internal Marker selection_end;
829 internal bool selection_visible;
830 internal Marker selection_anchor;
831 internal Marker selection_prev;
832 internal bool selection_end_anchor;
834 internal int viewport_x;
835 internal int viewport_y; // The visible area of the document
836 internal int viewport_width;
837 internal int viewport_height;
839 internal int document_x; // Width of the document
840 internal int document_y; // Height of the document
842 internal Rectangle invalid;
844 internal int crlf_size; // 1 or 2, depending on whether we use \r\n or just \n
846 internal TextBoxBase owner; // Who's owning us?
847 static internal int caret_width = 1;
848 static internal int caret_shift = 1;
850 internal int left_margin = 2; // A left margin for all lines
851 internal int top_margin = 2;
852 internal int right_margin = 2;
853 #endregion // Local Variables
856 internal Document (TextBoxBase owner)
865 recalc_pending = false;
867 // Tree related stuff
868 sentinel = new Line (this, LineEnding.None);
869 sentinel.color = LineColor.Black;
873 // We always have a blank line
874 owner.HandleCreated += new EventHandler(owner_HandleCreated);
875 owner.VisibleChanged += new EventHandler(owner_VisibleChanged);
877 Add (1, String.Empty, owner.Font, ThemeEngine.Current.ResPool.GetSolidBrush (owner.ForeColor), LineEnding.None);
879 undo = new UndoManager (this);
881 selection_visible = false;
882 selection_start.line = this.document;
883 selection_start.pos = 0;
884 selection_start.tag = selection_start.line.tags;
885 selection_end.line = this.document;
886 selection_end.pos = 0;
887 selection_end.tag = selection_end.line.tags;
888 selection_anchor.line = this.document;
889 selection_anchor.pos = 0;
890 selection_anchor.tag = selection_anchor.line.tags;
891 caret.line = this.document;
893 caret.tag = caret.line.tags;
900 // Default selection is empty
902 document_id = random.Next();
904 string_format.Trimming = StringTrimming.None;
905 string_format.FormatFlags = StringFormatFlags.DisplayFormatControl;
911 #region Internal Properties
928 internal Line CaretLine {
934 internal int CaretPosition {
940 internal Point Caret {
942 return new Point((int)caret.tag.line.widths[caret.pos] + caret.line.X, caret.line.Y);
946 internal LineTag CaretTag {
956 internal int CRLFSize {
966 internal string PasswordChar {
968 return password_char;
972 password_char = value;
973 PasswordCache.Length = 0;
974 if ((password_char.Length != 0) && (password_char[0] != '\0')) {
982 private StringBuilder PasswordCache {
984 if (password_cache == null)
985 password_cache = new StringBuilder();
986 return password_cache;
990 internal int ViewPortX {
1000 internal int Length {
1002 return char_count + lines - 1; // Add \n for each line but the last
1006 private int CharCount {
1014 if (LengthChanged != null) {
1015 LengthChanged(this, EventArgs.Empty);
1020 internal int ViewPortY {
1030 internal int ViewPortWidth {
1032 return viewport_width;
1036 viewport_width = value;
1040 internal int ViewPortHeight {
1042 return viewport_height;
1046 viewport_height = value;
1051 internal int Width {
1053 return this.document_x;
1057 internal int Height {
1059 return this.document_y;
1063 internal bool SelectionVisible {
1065 return selection_visible;
1069 internal bool Wrap {
1079 #endregion // Internal Properties
1081 #region Private Methods
1083 internal void UpdateMargins ()
1085 switch (owner.actual_border_style) {
1086 case BorderStyle.None:
1091 case BorderStyle.FixedSingle:
1096 case BorderStyle.Fixed3D:
1104 internal void SuspendRecalc ()
1109 internal void ResumeRecalc (bool immediate_update)
1111 if (recalc_suspended > 0)
1114 if (immediate_update && recalc_suspended == 0 && recalc_pending) {
1115 RecalculateDocument (owner.CreateGraphicsInternal(), recalc_start, recalc_end, recalc_optimize);
1116 recalc_pending = false;
1120 internal void SuspendUpdate ()
1125 internal void ResumeUpdate (bool immediate_update)
1127 if (update_suspended > 0)
1130 if (immediate_update && update_suspended == 0 && update_pending) {
1131 UpdateView (GetLine (update_start), 0);
1132 update_pending = false;
1137 internal int DumpTree(Line line, bool with_tags) {
1142 Console.Write("Line {0} [# {1}], Y: {2}, ending style: {3}, Text: '{4}'",
1143 line.line_no, line.GetHashCode(), line.Y, line.ending,
1144 line.text != null ? line.text.ToString() : "undefined");
1146 if (line.left == sentinel) {
1147 Console.Write(", left = sentinel");
1148 } else if (line.left == null) {
1149 Console.Write(", left = NULL");
1152 if (line.right == sentinel) {
1153 Console.Write(", right = sentinel");
1154 } else if (line.right == null) {
1155 Console.Write(", right = NULL");
1158 Console.WriteLine("");
1168 Console.Write(" Tags: ");
1169 while (tag != null) {
1170 Console.Write("{0} <{1}>-<{2}>", count++, tag.start, tag.end
1171 /*line.text.ToString (tag.start - 1, tag.length)*/);
1172 length += tag.length;
1174 if (tag.line != line) {
1175 Console.Write("BAD line link");
1176 throw new Exception("Bad line link in tree");
1180 Console.Write(", ");
1183 if (length > line.text.Length) {
1184 throw new Exception(String.Format("Length of tags more than length of text on line (expected {0} calculated {1})", line.text.Length, length));
1185 } else if (length < line.text.Length) {
1186 throw new Exception(String.Format("Length of tags less than length of text on line (expected {0} calculated {1})", line.text.Length, length));
1188 Console.WriteLine("");
1190 if (line.left != null) {
1191 if (line.left != sentinel) {
1192 total += DumpTree(line.left, with_tags);
1195 if (line != sentinel) {
1196 throw new Exception("Left should not be NULL");
1200 if (line.right != null) {
1201 if (line.right != sentinel) {
1202 total += DumpTree(line.right, with_tags);
1205 if (line != sentinel) {
1206 throw new Exception("Right should not be NULL");
1210 for (int i = 1; i <= this.lines; i++) {
1211 if (GetLine(i) == null) {
1212 throw new Exception(String.Format("Hole in line order, missing {0}", i));
1216 if (line == this.Root) {
1217 if (total < this.lines) {
1218 throw new Exception(String.Format("Not enough nodes in tree, found {0}, expected {1}", total, this.lines));
1219 } else if (total > this.lines) {
1220 throw new Exception(String.Format("Too many nodes in tree, found {0}, expected {1}", total, this.lines));
1227 private void SetSelectionVisible (bool value)
1229 selection_visible = value;
1231 // cursor and selection are enemies, we can't have both in the same room at the same time
1232 if (owner.IsHandleCreated && !owner.show_caret_w_selection)
1233 XplatUI.CaretVisible (owner.Handle, !selection_visible);
1236 private void DecrementLines(int line_no) {
1240 while (current <= lines) {
1241 GetLine(current).line_no--;
1247 private void IncrementLines(int line_no) {
1250 current = this.lines;
1251 while (current >= line_no) {
1252 GetLine(current).line_no++;
1258 private void RebalanceAfterAdd(Line line1) {
1261 while ((line1 != document) && (line1.parent.color == LineColor.Red)) {
1262 if (line1.parent == line1.parent.parent.left) {
1263 line2 = line1.parent.parent.right;
1265 if ((line2 != null) && (line2.color == LineColor.Red)) {
1266 line1.parent.color = LineColor.Black;
1267 line2.color = LineColor.Black;
1268 line1.parent.parent.color = LineColor.Red;
1269 line1 = line1.parent.parent;
1271 if (line1 == line1.parent.right) {
1272 line1 = line1.parent;
1276 line1.parent.color = LineColor.Black;
1277 line1.parent.parent.color = LineColor.Red;
1279 RotateRight(line1.parent.parent);
1282 line2 = line1.parent.parent.left;
1284 if ((line2 != null) && (line2.color == LineColor.Red)) {
1285 line1.parent.color = LineColor.Black;
1286 line2.color = LineColor.Black;
1287 line1.parent.parent.color = LineColor.Red;
1288 line1 = line1.parent.parent;
1290 if (line1 == line1.parent.left) {
1291 line1 = line1.parent;
1295 line1.parent.color = LineColor.Black;
1296 line1.parent.parent.color = LineColor.Red;
1297 RotateLeft(line1.parent.parent);
1301 document.color = LineColor.Black;
1304 private void RebalanceAfterDelete(Line line1) {
1307 while ((line1 != document) && (line1.color == LineColor.Black)) {
1308 if (line1 == line1.parent.left) {
1309 line2 = line1.parent.right;
1310 if (line2.color == LineColor.Red) {
1311 line2.color = LineColor.Black;
1312 line1.parent.color = LineColor.Red;
1313 RotateLeft(line1.parent);
1314 line2 = line1.parent.right;
1316 if ((line2.left.color == LineColor.Black) && (line2.right.color == LineColor.Black)) {
1317 line2.color = LineColor.Red;
1318 line1 = line1.parent;
1320 if (line2.right.color == LineColor.Black) {
1321 line2.left.color = LineColor.Black;
1322 line2.color = LineColor.Red;
1324 line2 = line1.parent.right;
1326 line2.color = line1.parent.color;
1327 line1.parent.color = LineColor.Black;
1328 line2.right.color = LineColor.Black;
1329 RotateLeft(line1.parent);
1333 line2 = line1.parent.left;
1334 if (line2.color == LineColor.Red) {
1335 line2.color = LineColor.Black;
1336 line1.parent.color = LineColor.Red;
1337 RotateRight(line1.parent);
1338 line2 = line1.parent.left;
1340 if ((line2.right.color == LineColor.Black) && (line2.left.color == LineColor.Black)) {
1341 line2.color = LineColor.Red;
1342 line1 = line1.parent;
1344 if (line2.left.color == LineColor.Black) {
1345 line2.right.color = LineColor.Black;
1346 line2.color = LineColor.Red;
1348 line2 = line1.parent.left;
1350 line2.color = line1.parent.color;
1351 line1.parent.color = LineColor.Black;
1352 line2.left.color = LineColor.Black;
1353 RotateRight(line1.parent);
1358 line1.color = LineColor.Black;
1361 private void RotateLeft(Line line1) {
1362 Line line2 = line1.right;
1364 line1.right = line2.left;
1366 if (line2.left != sentinel) {
1367 line2.left.parent = line1;
1370 if (line2 != sentinel) {
1371 line2.parent = line1.parent;
1374 if (line1.parent != null) {
1375 if (line1 == line1.parent.left) {
1376 line1.parent.left = line2;
1378 line1.parent.right = line2;
1385 if (line1 != sentinel) {
1386 line1.parent = line2;
1390 private void RotateRight(Line line1) {
1391 Line line2 = line1.left;
1393 line1.left = line2.right;
1395 if (line2.right != sentinel) {
1396 line2.right.parent = line1;
1399 if (line2 != sentinel) {
1400 line2.parent = line1.parent;
1403 if (line1.parent != null) {
1404 if (line1 == line1.parent.right) {
1405 line1.parent.right = line2;
1407 line1.parent.left = line2;
1413 line2.right = line1;
1414 if (line1 != sentinel) {
1415 line1.parent = line2;
1420 internal void UpdateView(Line line, int pos) {
1421 if (!owner.IsHandleCreated) {
1425 if (update_suspended > 0) {
1426 update_start = Math.Min (update_start, line.line_no);
1427 // update_end = Math.Max (update_end, line.line_no);
1428 // recalc_optimize = true;
1429 update_pending = true;
1433 // Optimize invalidation based on Line alignment
1434 if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no, true)) {
1435 // Lineheight changed, invalidate the rest of the document
1436 if ((line.Y - viewport_y) >=0 ) {
1437 // We formatted something that's in view, only draw parts of the screen
1438 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, owner.Height - line.Y - viewport_y));
1440 // The tag was above the visible area, draw everything
1444 switch(line.alignment) {
1445 case HorizontalAlignment.Left: {
1446 owner.Invalidate(new Rectangle(line.X + (int)line.widths[pos] - viewport_x - 1, line.Y - viewport_y, viewport_width, line.height + 1));
1450 case HorizontalAlignment.Center: {
1451 owner.Invalidate(new Rectangle(line.X, line.Y - viewport_y, viewport_width, line.height + 1));
1455 case HorizontalAlignment.Right: {
1456 owner.Invalidate(new Rectangle(line.X, line.Y - viewport_y, (int)line.widths[pos + 1] - viewport_x + line.X, line.height + 1));
1464 // Update display from line, down line_count lines; pos is unused, but required for the signature
1465 internal void UpdateView(Line line, int line_count, int pos) {
1466 if (!owner.IsHandleCreated) {
1470 if (recalc_suspended > 0) {
1471 recalc_start = Math.Min (recalc_start, line.line_no);
1472 recalc_end = Math.Max (recalc_end, line.line_no + line_count);
1473 recalc_optimize = true;
1474 recalc_pending = true;
1478 int start_line_top = line.Y;
1480 int end_line_bottom;
1483 end_line = GetLine (line.line_no + line_count);
1484 if (end_line == null)
1485 end_line = GetLine (lines);
1488 end_line_bottom = end_line.Y + end_line.height;
1490 if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no + line_count, true)) {
1491 // Lineheight changed, invalidate the rest of the document
1492 if ((line.Y - viewport_y) >=0 ) {
1493 // We formatted something that's in view, only draw parts of the screen
1494 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, owner.Height - line.Y - viewport_y));
1496 // The tag was above the visible area, draw everything
1500 int x = 0 - viewport_x;
1501 int w = viewport_width;
1502 int y = Math.Min (start_line_top - viewport_y, line.Y - viewport_y);
1503 int h = Math.Max (end_line_bottom - y, end_line.Y + end_line.height - y);
1505 owner.Invalidate (new Rectangle (x, y, w, h));
1508 #endregion // Private Methods
1510 #region Internal Methods
1511 // Clear the document and reset state
1512 internal void Empty() {
1514 document = sentinel;
1517 // We always have a blank line
1518 Add (1, String.Empty, owner.Font, ThemeEngine.Current.ResPool.GetSolidBrush (owner.ForeColor), LineEnding.None);
1520 this.RecalculateDocument(owner.CreateGraphicsInternal());
1521 PositionCaret(0, 0);
1523 SetSelectionVisible (false);
1525 selection_start.line = this.document;
1526 selection_start.pos = 0;
1527 selection_start.tag = selection_start.line.tags;
1528 selection_end.line = this.document;
1529 selection_end.pos = 0;
1530 selection_end.tag = selection_end.line.tags;
1539 if (owner.IsHandleCreated)
1540 owner.Invalidate ();
1543 internal void PositionCaret(Line line, int pos) {
1544 caret.tag = line.FindTag (pos);
1546 MoveCaretToTextTag ();
1551 if (owner.IsHandleCreated) {
1552 if (owner.Focused) {
1553 if (caret.height != caret.tag.height)
1554 XplatUI.CreateCaret (owner.Handle, caret_width, caret.height);
1555 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);
1558 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1561 // We set this at the end because we use the heights to determine whether or
1562 // not we need to recreate the caret
1563 caret.height = caret.tag.height;
1567 internal void PositionCaret(int x, int y) {
1568 if (!owner.IsHandleCreated) {
1572 caret.tag = FindCursor(x, y, out caret.pos);
1574 MoveCaretToTextTag ();
1576 caret.line = caret.tag.line;
1577 caret.height = caret.tag.height;
1579 if (owner.ShowSelection && (!selection_visible || owner.show_caret_w_selection)) {
1580 XplatUI.CreateCaret (owner.Handle, caret_width, caret.height);
1581 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);
1584 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1587 internal void CaretHasFocus() {
1588 if ((caret.tag != null) && owner.IsHandleCreated) {
1589 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1590 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);
1595 if (owner.IsHandleCreated && SelectionLength () > 0) {
1596 InvalidateSelectionArea ();
1600 internal void CaretLostFocus() {
1601 if (!owner.IsHandleCreated) {
1604 XplatUI.DestroyCaret(owner.Handle);
1607 internal void AlignCaret() {
1608 if (!owner.IsHandleCreated) {
1612 caret.tag = LineTag.FindTag (caret.line, caret.pos);
1614 MoveCaretToTextTag ();
1616 caret.height = caret.tag.height;
1618 if (owner.Focused) {
1619 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1620 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);
1624 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1627 internal void UpdateCaret() {
1628 if (!owner.IsHandleCreated || caret.tag == null) {
1632 MoveCaretToTextTag ();
1634 if (caret.tag.height != caret.height) {
1635 caret.height = caret.tag.height;
1636 if (owner.Focused) {
1637 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1641 if (owner.Focused) {
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.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.Max (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 internal void RecalculateAlignments ()
3955 while (line_no <= lines) {
3956 line = GetLine(line_no);
3959 switch (line.alignment) {
3960 case HorizontalAlignment.Left:
3961 line.align_shift = 0;
3963 case HorizontalAlignment.Center:
3964 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3966 case HorizontalAlignment.Right:
3967 line.align_shift = viewport_width - (int)line.widths[line.text.Length] - right_margin;
3977 /// <summary>Calculate formatting for the whole document</summary>
3978 internal bool RecalculateDocument(Graphics g) {
3979 return RecalculateDocument(g, 1, this.lines, false);
3982 /// <summary>Calculate formatting starting at a certain line</summary>
3983 internal bool RecalculateDocument(Graphics g, int start) {
3984 return RecalculateDocument(g, start, this.lines, false);
3987 /// <summary>Calculate formatting within two given line numbers</summary>
3988 internal bool RecalculateDocument(Graphics g, int start, int end) {
3989 return RecalculateDocument(g, start, end, false);
3992 /// <summary>With optimize on, returns true if line heights changed</summary>
3993 internal bool RecalculateDocument(Graphics g, int start, int end, bool optimize) {
4001 if (recalc_suspended > 0) {
4002 recalc_pending = true;
4003 recalc_start = Math.Min (recalc_start, start);
4004 recalc_end = Math.Max (recalc_end, end);
4005 recalc_optimize = optimize;
4009 // Fixup the positions, they can go kinda nuts
4010 start = Math.Max (start, 1);
4011 end = Math.Min (end, lines);
4013 offset = GetLine(start).offset;
4018 changed = true; // We always return true if we run non-optimized
4023 while (line_no <= (end + this.lines - shift)) {
4024 line = GetLine(line_no++);
4025 line.offset = offset;
4029 line.RecalculateLine(g, this);
4031 if (line.recalc && line.RecalculateLine(g, this)) {
4033 // If the height changed, all subsequent lines change
4040 line.RecalculatePasswordLine(g, this);
4042 if (line.recalc && line.RecalculatePasswordLine(g, this)) {
4044 // If the height changed, all subsequent lines change
4051 if (line.widths[line.text.Length] > new_width) {
4052 new_width = (int)line.widths[line.text.Length];
4055 // Calculate alignment
4056 if (line.alignment != HorizontalAlignment.Left) {
4057 if (line.alignment == HorizontalAlignment.Center) {
4058 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
4060 line.align_shift = viewport_width - (int)line.widths[line.text.Length] - 1;
4065 offset += line.height;
4067 offset += (int) line.widths [line.text.Length];
4069 if (line_no > lines) {
4074 if (document_x != new_width) {
4075 document_x = new_width;
4076 if (WidthChanged != null) {
4077 WidthChanged(this, null);
4081 RecalculateAlignments();
4083 line = GetLine(lines);
4085 if (document_y != line.Y + line.height) {
4086 document_y = line.Y + line.height;
4087 if (HeightChanged != null) {
4088 HeightChanged(this, null);
4095 internal int Size() {
4099 private void owner_HandleCreated(object sender, EventArgs e) {
4100 RecalculateDocument(owner.CreateGraphicsInternal());
4104 private void owner_VisibleChanged(object sender, EventArgs e) {
4105 if (owner.Visible) {
4106 RecalculateDocument(owner.CreateGraphicsInternal());
4110 internal static bool IsWordSeparator (char ch)
4125 internal int FindWordSeparator(Line line, int pos, bool forward) {
4128 len = line.text.Length;
4131 for (int i = pos + 1; i < len; i++) {
4132 if (IsWordSeparator(line.Text[i])) {
4138 for (int i = pos - 1; i > 0; i--) {
4139 if (IsWordSeparator(line.Text[i - 1])) {
4147 /* Search document for text */
4148 internal bool FindChars(char[] chars, Marker start, Marker end, out Marker result) {
4154 // Search for occurence of any char in the chars array
4155 result = new Marker();
4158 line_no = start.line.line_no;
4160 while (line_no <= end.line.line_no) {
4161 line_len = line.text.Length;
4162 while (pos < line_len) {
4163 for (int i = 0; i < chars.Length; i++) {
4164 if (line.text[pos] == chars[i]) {
4166 if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
4180 line = GetLine(line_no);
4186 // This version does not build one big string for searching, instead it handles
4187 // line-boundaries, which is faster and less memory intensive
4188 // FIXME - Depending on culture stuff we might have to create a big string and use culturespecific
4189 // search stuff and change it to accept and return positions instead of Markers (which would match
4190 // RichTextBox behaviour better but would be inconsistent with the rest of TextControl)
4191 internal bool Find(string search, Marker start, Marker end, out Marker result, RichTextBoxFinds options) {
4193 string search_string;
4205 result = new Marker();
4206 word_option = ((options & RichTextBoxFinds.WholeWord) != 0);
4207 ignore_case = ((options & RichTextBoxFinds.MatchCase) == 0);
4208 reverse = ((options & RichTextBoxFinds.Reverse) != 0);
4211 line_no = start.line.line_no;
4215 // Prep our search string, lowercasing it if we do case-independent matching
4218 sb = new StringBuilder(search);
4219 for (int i = 0; i < sb.Length; i++) {
4220 sb[i] = Char.ToLower(sb[i]);
4222 search_string = sb.ToString();
4224 search_string = search;
4227 // We need to check if the character before our start position is a wordbreak
4230 if ((pos == 0) || (IsWordSeparator(line.text[pos - 1]))) {
4237 if (IsWordSeparator(line.text[pos - 1])) {
4243 // Need to check the end of the previous line
4246 prev_line = GetLine(line_no - 1);
4247 if (prev_line.ending == LineEnding.Wrap) {
4248 if (IsWordSeparator(prev_line.text[prev_line.text.Length - 1])) {
4262 // To avoid duplication of this loop with reverse logic, we search
4263 // through the document, remembering the last match and when returning
4264 // report that last remembered match
4266 last = new Marker();
4267 last.height = -1; // Abused - we use it to track change
4269 while (line_no <= end.line.line_no) {
4270 if (line_no != end.line.line_no) {
4271 line_len = line.text.Length;
4276 while (pos < line_len) {
4278 if (word_option && (current == search_string.Length)) {
4279 if (IsWordSeparator(line.text[pos])) {
4292 c = Char.ToLower(line.text[pos]);
4297 if (c == search_string[current]) {
4303 if (!word_option || (word_option && (word || (current > 0)))) {
4307 if (!word_option && (current == search_string.Length)) {
4324 if (IsWordSeparator(c)) {
4332 // Mark that we just saw a word boundary
4333 if (line.ending != LineEnding.Wrap || line.line_no == lines - 1) {
4337 if (current == search_string.Length) {
4353 line = GetLine(line_no);
4357 if (last.height != -1) {
4367 // if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
4379 internal void GetMarker(out Marker mark, bool start) {
4380 mark = new Marker();
4383 mark.line = GetLine(1);
4384 mark.tag = mark.line.tags;
4387 mark.line = GetLine(lines);
4388 mark.tag = mark.line.tags;
4389 while (mark.tag.next != null) {
4390 mark.tag = mark.tag.next;
4392 mark.pos = mark.line.text.Length;
4395 #endregion // Internal Methods
4398 internal event EventHandler CaretMoved;
4399 internal event EventHandler WidthChanged;
4400 internal event EventHandler HeightChanged;
4401 internal event EventHandler LengthChanged;
4402 #endregion // Events
4404 #region Administrative
4405 public IEnumerator GetEnumerator() {
4410 public override bool Equals(object obj) {
4415 if (!(obj is Document)) {
4423 if (ToString().Equals(((Document)obj).ToString())) {
4430 public override int GetHashCode() {
4434 public override string ToString() {
4435 return "document " + this.document_id;
4437 #endregion // Administrative
4440 internal class PictureTag : LineTag {
4442 internal RTF.Picture picture;
4444 internal PictureTag (Line line, int start, RTF.Picture picture) : base (line, start)
4446 this.picture = picture;
4449 public override bool IsTextTag {
4450 get { return false; }
4453 internal override SizeF SizeOfPosition (Graphics dc, int pos)
4455 return picture.Size;
4458 internal override int MaxHeight ()
4460 return (int) (picture.Height + 0.5F);
4463 internal override void Draw (Graphics dc, Brush brush, float xoff, float y, int start, int end)
4465 picture.DrawImage (dc, xoff + line.widths [start], y, false);
4468 internal override void Draw (Graphics dc, Brush brush, float xoff, float y, int start, int end, string text)
4470 picture.DrawImage (dc, xoff + + line.widths [start], y, false);
4473 public override string Text ()
4479 internal class LineTag {
4480 #region Local Variables;
4481 // Payload; formatting
4482 internal Font font; // System.Drawing.Font object for this tag
4483 internal SolidBrush color; // The font color for this tag
4485 // In 2.0 tags can have background colours. I'm not going to #ifdef
4486 // at this level though since I want to reduce code paths
4487 internal SolidBrush back_color;
4490 internal int start; // start, in chars; index into Line.text
4491 internal bool r_to_l; // Which way is the font
4494 internal int height; // Height in pixels of the text this tag describes
4496 internal int ascent; // Ascent of the font for this tag
4497 internal int shift; // Shift down for this tag, to stay on baseline
4500 internal Line line; // The line we're on
4501 internal LineTag next; // Next tag on the same line
4502 internal LineTag previous; // Previous tag on the same line
4505 #region Constructors
4506 internal LineTag(Line line, int start) {
4510 #endregion // Constructors
4512 #region Internal Methods
4518 return line.X + line.widths [start - 1];
4523 get { return start + length; }
4526 public int TextEnd {
4527 get { return start + TextLength; }
4530 public float width {
4534 return line.widths [start + length - 1] - (start != 0 ? line.widths [start - 1] : 0);
4542 res = next.start - start;
4544 res = line.text.Length - (start - 1);
4546 return res > 0 ? res : 0;
4550 public int TextLength {
4554 res = next.start - start;
4556 res = line.TextLengthWithoutEnding () - (start - 1);
4558 return res > 0 ? res : 0;
4562 public virtual bool IsTextTag {
4563 get { return true; }
4566 internal virtual SizeF SizeOfPosition (Graphics dc, int pos)
4568 if (pos >= line.TextLengthWithoutEnding () && line.document.multiline)
4571 string text = line.text.ToString (pos, 1);
4572 switch ((int) text [0]) {
4574 if (!line.document.multiline)
4576 SizeF res = dc.MeasureString (" ", font, 10000, Document.string_format);
4581 return dc.MeasureString ("\u0013", font, 10000, Document.string_format);
4584 return dc.MeasureString (text, font, 10000, Document.string_format);
4587 internal virtual int MaxHeight ()
4592 internal virtual void Draw (Graphics dc, Brush brush, float x, float y, int start, int end)
4594 dc.DrawString (line.text.ToString (start, end), font, brush, x, y, StringFormat.GenericTypographic);
4597 internal virtual void Draw (Graphics dc, Brush brush, float xoff, float y, int start, int end, string text)
4599 while (start < end) {
4600 int tab_index = text.IndexOf ("\t", start);
4601 if (tab_index == -1)
4603 dc.DrawString (text.Substring (start, tab_index - start), font, brush, xoff + line.widths [start],
4604 y, StringFormat.GenericTypographic);
4606 // non multilines get the unknown char
4607 if (!line.document.multiline && tab_index != end)
4608 dc.DrawString ("\u0013", font, brush, xoff + line.widths [tab_index], y, Document.string_format);
4610 start = tab_index + 1;
4614 ///<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>
4615 internal LineTag Break(int pos) {
4620 if (pos == this.start) {
4622 } else if (pos >= (start + length)) {
4626 new_tag = new LineTag(line, pos);
4627 new_tag.CopyFormattingFrom (this);
4629 new_tag.next = this.next;
4630 this.next = new_tag;
4631 new_tag.previous = this;
4633 if (new_tag.next != null) {
4634 new_tag.next.previous = new_tag;
4640 public virtual string Text ()
4642 return line.text.ToString (start - 1, length);
4645 public void CopyFormattingFrom (LineTag other)
4647 height = other.height;
4649 color = other.color;
4650 back_color = other.back_color;
4653 /// <summary>Applies 'font' and 'brush' to characters starting at 'start' for 'length' chars;
4654 /// Removes any previous tags overlapping the same area;
4655 /// returns true if lineheight has changed</summary>
4656 /// <param name="start">1-based character position on line</param>
4657 internal static bool FormatText(Line line, int start, int length, Font font, SolidBrush color, SolidBrush back_color, FormatSpecified specified)
4663 bool retval = false; // Assume line-height doesn't change
4666 if (((FormatSpecified.Font & specified) == FormatSpecified.Font) && font.Height != line.height) {
4669 line.recalc = true; // This forces recalculation of the line in RecalculateDocument
4671 // A little sanity, not sure if it's needed, might be able to remove for speed
4672 if (length > line.text.Length) {
4673 length = line.text.Length;
4677 end = start + length;
4679 // Common special case
4680 if ((start == 1) && (length == tag.length)) {
4682 SetFormat (tag, font, color, back_color, specified);
4686 start_tag = FindTag (line, start);
4687 tag = start_tag.Break (start);
4689 while (tag != null && tag.end <= end) {
4690 SetFormat (tag, font, color, back_color, specified);
4694 if (tag != null && tag.end == end)
4697 /// Now do the last tag
4698 end_tag = FindTag (line, end);
4700 if (end_tag != null) {
4701 end_tag.Break (end);
4702 SetFormat (end_tag, font, color, back_color, specified);
4708 private static void SetFormat (LineTag tag, Font font, SolidBrush color, SolidBrush back_color, FormatSpecified specified)
4710 if ((FormatSpecified.Font & specified) == FormatSpecified.Font)
4712 if ((FormatSpecified.Color & specified) == FormatSpecified.Color)
4714 if ((FormatSpecified.BackColor & specified) == FormatSpecified.BackColor) {
4715 tag.back_color = back_color;
4717 // Console.WriteLine ("setting format: {0} {1} new color {2}", color.Color, specified, tag.color.Color);
4720 /// <summary>Finds the tag that describes the character at position 'pos' on 'line'</summary>
4721 internal static LineTag FindTag(Line line, int pos) {
4722 LineTag tag = line.tags;
4724 // Beginning of line is a bit special
4726 // Not sure if we should get the final tag here
4730 while (tag != null) {
4731 if ((tag.start <= pos) && (pos <= tag.end)) {
4732 return GetFinalTag (tag);
4741 // There can be multiple tags at the same position, we want to make
4742 // sure we are using the very last tag at the given position
4743 internal static LineTag GetFinalTag (LineTag tag)
4747 while (res.length == 0 && res.next != null && res.next.length == 0)
4753 /// <summary>Combines 'this' tag with 'other' tag</summary>
4754 internal bool Combine(LineTag other) {
4755 if (!this.Equals(other)) {
4759 this.next = other.next;
4760 if (this.next != null) {
4761 this.next.previous = this;
4768 /// <summary>Remove 'this' tag ; to be called when formatting is to be removed</summary>
4769 internal bool Remove() {
4770 if ((this.start == 1) && (this.next == null)) {
4771 // We cannot remove the only tag
4774 if (this.start != 1) {
4775 this.previous.next = this.next;
4776 this.next.previous = this.previous;
4778 this.next.start = 1;
4779 this.line.tags = this.next;
4780 this.next.previous = null;
4786 /// <summary>Checks if 'this' tag describes the same formatting options as 'obj'</summary>
4787 public override bool Equals(object obj) {
4794 if (!(obj is LineTag)) {
4802 other = (LineTag)obj;
4804 if (other.IsTextTag != IsTextTag)
4807 if (this.font.Equals(other.font) && this.color.Equals(other.color)) { // FIXME add checking for things like link or type later
4814 public override int GetHashCode() {
4815 return base.GetHashCode ();
4818 public override string ToString() {
4820 return GetType () + " Tag starts at index " + this.start + " length " + this.length + " text: " + Text () + "Font " + this.font.ToString();
4821 return "Zero Lengthed tag at index " + this.start;
4824 #endregion // Internal Methods
4827 internal class UndoManager {
4829 internal enum ActionType {
4833 // This is basically just cut & paste
4841 internal class Action {
4842 internal ActionType type;
4843 internal int line_no;
4845 internal object data;
4848 #region Local Variables
4849 private Document document;
4850 private Stack undo_actions;
4851 private Stack redo_actions;
4853 //private int caret_line;
4854 //private int caret_pos;
4856 // When performing an action, we lock the queue, so that the action can't be undone
4857 private bool locked;
4858 #endregion // Local Variables
4860 #region Constructors
4861 internal UndoManager (Document document)
4863 this.document = document;
4864 undo_actions = new Stack (50);
4865 redo_actions = new Stack (50);
4867 #endregion // Constructors
4870 internal bool CanUndo {
4871 get { return undo_actions.Count > 0; }
4874 internal bool CanRedo {
4875 get { return redo_actions.Count > 0; }
4878 internal string UndoActionName {
4880 foreach (Action action in undo_actions) {
4881 if (action.type == ActionType.UserActionBegin)
4882 return (string) action.data;
4883 if (action.type == ActionType.Typing)
4884 return Locale.GetText ("Typing");
4886 return String.Empty;
4890 internal string RedoActionName {
4892 foreach (Action action in redo_actions) {
4893 if (action.type == ActionType.UserActionBegin)
4894 return (string) action.data;
4895 if (action.type == ActionType.Typing)
4896 return Locale.GetText ("Typing");
4898 return String.Empty;
4901 #endregion // Properties
4903 #region Internal Methods
4904 internal void Clear ()
4906 undo_actions.Clear();
4907 redo_actions.Clear();
4910 internal void Undo ()
4913 bool user_action_finished = false;
4915 if (undo_actions.Count == 0)
4918 // Nuke the redo queue
4919 redo_actions.Clear ();
4924 action = (Action) undo_actions.Pop ();
4926 // Put onto redo stack
4927 redo_actions.Push(action);
4930 switch(action.type) {
4932 case ActionType.UserActionBegin:
4933 user_action_finished = true;
4936 case ActionType.UserActionEnd:
4940 case ActionType.InsertString:
4941 start = document.GetLine (action.line_no);
4942 document.SuspendUpdate ();
4943 document.DeleteMultiline (start, action.pos, ((string) action.data).Length + 1);
4944 document.PositionCaret (start, action.pos);
4945 document.SetSelectionToCaret (true);
4946 document.ResumeUpdate (true);
4949 case ActionType.Typing:
4950 start = document.GetLine (action.line_no);
4951 document.SuspendUpdate ();
4952 document.DeleteMultiline (start, action.pos, ((StringBuilder) action.data).Length);
4953 document.PositionCaret (start, action.pos);
4954 document.SetSelectionToCaret (true);
4955 document.ResumeUpdate (true);
4957 // This is an open ended operation, so only a single typing operation can be undone at once
4958 user_action_finished = true;
4961 case ActionType.DeleteString:
4962 start = document.GetLine (action.line_no);
4963 document.SuspendUpdate ();
4964 Insert (start, action.pos, (Line) action.data, true);
4965 document.ResumeUpdate (true);
4968 } while (!user_action_finished && undo_actions.Count > 0);
4973 internal void Redo ()
4976 bool user_action_finished = false;
4978 if (redo_actions.Count == 0)
4981 // You can't undo anything after redoing
4982 undo_actions.Clear ();
4989 action = (Action) redo_actions.Pop ();
4991 switch (action.type) {
4993 case ActionType.UserActionBegin:
4997 case ActionType.UserActionEnd:
4998 user_action_finished = true;
5001 case ActionType.InsertString:
5002 start = document.GetLine (action.line_no);
5003 document.SuspendUpdate ();
5004 start_index = document.LineTagToCharIndex (start, action.pos);
5005 document.InsertString (start, action.pos, (string) action.data);
5006 document.CharIndexToLineTag (start_index + ((string) action.data).Length,
5007 out document.caret.line, out document.caret.tag,
5008 out document.caret.pos);
5009 document.UpdateCaret ();
5010 document.SetSelectionToCaret (true);
5011 document.ResumeUpdate (true);
5014 case ActionType.Typing:
5015 start = document.GetLine (action.line_no);
5016 document.SuspendUpdate ();
5017 start_index = document.LineTagToCharIndex (start, action.pos);
5018 document.InsertString (start, action.pos, ((StringBuilder) action.data).ToString ());
5019 document.CharIndexToLineTag (start_index + ((StringBuilder) action.data).Length,
5020 out document.caret.line, out document.caret.tag,
5021 out document.caret.pos);
5022 document.UpdateCaret ();
5023 document.SetSelectionToCaret (true);
5024 document.ResumeUpdate (true);
5026 // This is an open ended operation, so only a single typing operation can be undone at once
5027 user_action_finished = true;
5030 case ActionType.DeleteString:
5031 start = document.GetLine (action.line_no);
5032 document.SuspendUpdate ();
5033 document.DeleteMultiline (start, action.pos, ((Line) action.data).text.Length);
5034 document.PositionCaret (start, action.pos);
5035 document.SetSelectionToCaret (true);
5036 document.ResumeUpdate (true);
5040 } while (!user_action_finished && redo_actions.Count > 0);
5044 #endregion // Internal Methods
5046 #region Private Methods
5048 public void BeginUserAction (string name)
5053 Action ua = new Action ();
5054 ua.type = ActionType.UserActionBegin;
5057 undo_actions.Push (ua);
5060 public void EndUserAction ()
5065 Action ua = new Action ();
5066 ua.type = ActionType.UserActionEnd;
5068 undo_actions.Push (ua);
5071 // start_pos, end_pos = 1 based
5072 public void RecordDeleteString (Line start_line, int start_pos, Line end_line, int end_pos)
5077 Action a = new Action ();
5079 // We cant simply store the string, because then formatting would be lost
5080 a.type = ActionType.DeleteString;
5081 a.line_no = start_line.line_no;
5083 a.data = Duplicate (start_line, start_pos, end_line, end_pos);
5085 undo_actions.Push(a);
5088 public void RecordInsertString (Line line, int pos, string str)
5090 if (locked || str.Length == 0)
5093 Action a = new Action ();
5095 a.type = ActionType.InsertString;
5097 a.line_no = line.line_no;
5100 undo_actions.Push (a);
5103 public void RecordTyping (Line line, int pos, char ch)
5110 if (undo_actions.Count > 0)
5111 a = (Action) undo_actions.Peek ();
5113 if (a == null || a.type != ActionType.Typing) {
5115 a.type = ActionType.Typing;
5116 a.data = new StringBuilder ();
5117 a.line_no = line.line_no;
5120 undo_actions.Push (a);
5123 StringBuilder data = (StringBuilder) a.data;
5127 // start_pos = 1-based
5128 // end_pos = 1-based
5129 public Line Duplicate(Line start_line, int start_pos, Line end_line, int end_pos)
5135 LineTag current_tag;
5140 line = new Line (start_line.document, start_line.ending);
5143 for (int i = start_line.line_no; i <= end_line.line_no; i++) {
5144 current = document.GetLine(i);
5146 if (start_line.line_no == i) {
5152 if (end_line.line_no == i) {
5155 end = current.text.Length;
5162 line.text = new StringBuilder (current.text.ToString (start, end - start));
5164 // Copy tags from start to start+length onto new line
5165 current_tag = current.FindTag (start);
5166 while ((current_tag != null) && (current_tag.start <= end)) {
5167 if ((current_tag.start <= start) && (start < (current_tag.start + current_tag.length))) {
5168 // start tag is within this tag
5171 tag_start = current_tag.start;
5174 tag = new LineTag(line, tag_start - start + 1);
5175 tag.CopyFormattingFrom (current_tag);
5177 current_tag = current_tag.next;
5179 // Add the new tag to the line
5180 if (line.tags == null) {
5186 while (tail.next != null) {
5190 tag.previous = tail;
5194 if ((i + 1) <= end_line.line_no) {
5195 line.ending = current.ending;
5197 // Chain them (we use right/left as next/previous)
5198 line.right = new Line (start_line.document, start_line.ending);
5199 line.right.left = line;
5207 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
5208 internal void Insert(Line line, int pos, Line insert, bool select)
5216 // Handle special case first
5217 if (insert.right == null) {
5219 // Single line insert
5220 document.Split(line, pos);
5222 if (insert.tags == null) {
5223 return; // Blank line
5226 //Insert our tags at the end
5229 while (tag.next != null) {
5233 offset = tag.start + tag.length - 1;
5235 tag.next = insert.tags;
5236 line.text.Insert(offset, insert.text.ToString());
5238 // Adjust start locations
5240 while (tag != null) {
5241 tag.start += offset;
5245 // Put it back together
5246 document.Combine(line.line_no, line.line_no + 1);
5249 document.SetSelectionStart (line, pos, false);
5250 document.SetSelectionEnd (line, pos + insert.text.Length, false);
5253 document.UpdateView(line, pos);
5261 while (current != null) {
5263 if (current == insert) {
5264 // Inserting the first line we split the line (and make space)
5265 document.Split(line.line_no, pos);
5266 //Insert our tags at the end of the line
5270 if (tag != null && tag.length != 0) {
5271 while (tag.next != null) {
5274 offset = tag.start + tag.length - 1;
5275 tag.next = current.tags;
5276 tag.next.previous = tag;
5282 line.tags = current.tags;
5283 line.tags.previous = null;
5287 line.ending = current.ending;
5289 document.Split(line.line_no, 0);
5291 line.tags = current.tags;
5292 line.tags.previous = null;
5293 line.ending = current.ending;
5297 // Adjust start locations and line pointers
5298 while (tag != null) {
5299 tag.start += offset - 1;
5304 line.text.Insert(offset, current.text.ToString());
5305 line.Grow(line.text.Length);
5308 line = document.GetLine(line.line_no + 1);
5310 // FIXME? Test undo of line-boundaries
5311 if ((current.right == null) && (current.tags.length != 0)) {
5312 document.Combine(line.line_no - 1, line.line_no);
5314 current = current.right;
5319 // Recalculate our document
5320 document.UpdateView(first, lines, pos);
5323 #endregion // Private Methods