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 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 to support wrapping (ie have a 'newline' tag), for images, etc.
34 // - Wrap and recalculate lines
35 // - Implement CaretPgUp/PgDown
36 // - Finish selection calculations (invalidate only changed, more ways to select)
43 using System.Collections;
45 using System.Drawing.Text;
48 namespace System.Windows.Forms {
49 internal enum LineColor {
54 internal enum CaretDirection {
55 CharForward, // Move a char to the right
56 CharBack, // Move a char to the left
57 LineUp, // Move a line up
58 LineDown, // Move a line down
59 Home, // Move to the beginning of the line
60 End, // Move to the end of the line
61 PgUp, // Move one page up
62 PgDn, // Move one page down
63 CtrlHome, // Move to the beginning of the document
64 CtrlEnd, // Move to the end of the document
65 WordBack, // Move to the beginning of the previous word (or beginning of line)
66 WordForward // Move to the beginning of the next word (or end of line)
69 // Being cloneable should allow for nice line and document copies...
70 internal class Line : ICloneable, IComparable {
71 #region Local Variables
72 // Stuff that matters for our line
73 internal StringBuilder text; // Characters for the line
74 internal float[] widths; // Width of each character; always one larger than text.Length
75 internal int space; // Number of elements in text and widths
76 internal int line_no; // Line number
77 internal LineTag tags; // Tags describing the text
78 internal int Y; // Baseline
79 internal int height; // Height of the line (height of tallest tag)
80 internal int ascent; // Ascent of the line (ascent of the tallest tag)
81 internal HorizontalAlignment alignment; // Alignment of the line
82 internal int align_shift; // Pixel shift caused by the alignment
84 // Stuff that's important for the tree
85 internal Line parent; // Our parent line
86 internal Line left; // Line with smaller line number
87 internal Line right; // Line with higher line number
88 internal LineColor color; // We're doing a black/red tree. this is the node color
89 internal int DEFAULT_TEXT_LEN; //
90 internal static StringFormat string_format; // For calculating widths/heights
91 internal bool recalc; // Line changed
92 #endregion // Local Variables
96 color = LineColor.Red;
102 alignment = HorizontalAlignment.Left;
104 if (string_format == null) {
105 string_format = new StringFormat(StringFormat.GenericTypographic);
106 string_format.Trimming = StringTrimming.None;
107 string_format.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;
111 internal Line(int LineNo, string Text, Font font, Brush color) : this() {
112 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
114 text = new StringBuilder(Text, space);
117 widths = new float[space + 1];
118 tags = new LineTag(this, 1, text.Length);
123 internal Line(int LineNo, string Text, HorizontalAlignment align, Font font, Brush color) : this() {
124 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
126 text = new StringBuilder(Text, space);
130 widths = new float[space + 1];
131 tags = new LineTag(this, 1, text.Length);
136 internal Line(int LineNo, string Text, LineTag tag) : this() {
137 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
139 text = new StringBuilder(Text, space);
142 widths = new float[space + 1];
146 #endregion // Constructors
148 #region Internal Properties
149 internal int Height {
159 internal int LineNo {
169 internal string Text {
171 return text.ToString();
175 text = new StringBuilder(value, value.Length > DEFAULT_TEXT_LEN ? value.Length : DEFAULT_TEXT_LEN);
179 internal HorizontalAlignment Alignment {
185 if (alignment != value) {
192 internal StringBuilder Text {
202 #endregion // Internal Properties
204 #region Internal Methods
205 // Make sure we always have enoughs space in text and widths
206 internal void Grow(int minimum) {
210 length = text.Length;
212 if ((length + minimum) > space) {
213 // We need to grow; double the size
215 if ((length + minimum) > (space * 2)) {
216 new_widths = new float[length + minimum * 2 + 1];
217 space = length + minimum * 2;
219 new_widths = new float[space * 2 + 1];
222 widths.CopyTo(new_widths, 0);
228 internal void Streamline() {
235 // Catch what the loop below wont; eliminate 0 length
236 // tags, but only if there are other tags after us
237 while ((current.length == 0) && (next != null)) {
247 while (next != null) {
248 // Take out 0 length tags
249 if (next.length == 0) {
250 current.next = next.next;
251 if (current.next != null) {
252 current.next.previous = current;
258 if (current.Combine(next)) {
263 current = current.next;
268 // Find the tag on a line based on the character position
269 internal LineTag FindTag(int pos) {
278 if (pos > text.Length) {
282 while (tag != null) {
283 if ((tag.start <= pos) && (pos < (tag.start + tag.length))) {
293 // Go through all tags on a line and recalculate all size-related values
294 // returns true if lineheight changed
296 internal bool RecalculateLine(Graphics g) {
305 len = this.text.Length;
307 prev_height = this.height; // For drawing optimization calculations
308 this.height = 0; // Reset line height
309 this.ascent = 0; // Reset the ascent for the line
316 size = g.MeasureString(this.text.ToString(pos, 1), tag.font, 10000, string_format);
324 widths[pos] = widths[pos-1] + w;
326 if (pos == (tag.start-1 + tag.length)) {
327 // We just found the end of our current tag
328 tag.height = (int)tag.font.Height;
330 // Check if we're the tallest on the line (so far)
331 if (tag.height > this.height) {
332 this.height = tag.height; // Yep; make sure the line knows
335 if (tag.ascent == 0) {
338 XplatUI.GetFontMetrics(g, tag.font, out tag.ascent, out descent);
341 if (tag.ascent > this.ascent) {
344 // We have a tag that has a taller ascent than the line;
348 t.shift = tag.ascent - t.ascent;
353 this.ascent = tag.ascent;
355 tag.shift = this.ascent - tag.ascent;
358 // Update our horizontal starting pixel position
359 if (tag.previous == null) {
362 tag.X = tag.previous.X + (int)tag.previous.width;
373 if (this.height == 0) {
374 this.height = tags.font.Height;
375 tag.height = this.height;
378 if (prev_height != this.height) {
383 #endregion // Internal Methods
385 #region Administrative
386 public int CompareTo(object obj) {
391 if (! (obj is Line)) {
392 throw new ArgumentException("Object is not of type Line", "obj");
395 if (line_no < ((Line)obj).line_no) {
397 } else if (line_no > ((Line)obj).line_no) {
404 public object Clone() {
412 clone.left = (Line)left.Clone();
416 clone.left = (Line)left.Clone();
422 internal object CloneLine() {
432 public override bool Equals(object obj) {
437 if (!(obj is Line)) {
445 if (line_no == ((Line)obj).line_no) {
452 public override int GetHashCode() {
\r
453 return base.GetHashCode ();
\r
456 public override string ToString() {
457 return "Line " + line_no;
460 #endregion // Administrative
463 internal class Document : ICloneable, IEnumerable {
465 internal struct Marker {
467 internal LineTag tag;
471 #endregion Structures
473 #region Local Variables
474 private Line document;
476 private static Line sentinel;
477 private Line last_found;
478 private int document_id;
479 private Random random = new Random();
481 internal bool multiline;
484 internal Marker caret;
485 internal Marker selection_start;
486 internal Marker selection_end;
487 internal bool selection_visible;
489 internal int viewport_x;
490 internal int viewport_y; // The visible area of the document
492 internal int document_x; // Width of the document
493 internal int document_y; // Height of the document
495 internal Control owner; // Who's owning us?
496 #endregion // Local Variables
499 internal Document(Control owner) {
506 // Tree related stuff
507 sentinel = new Line();
508 sentinel.color = LineColor.Black;
511 last_found = sentinel;
513 // We always have a blank line
514 Add(1, "", owner.Font, new SolidBrush(owner.ForeColor));
515 this.RecalculateDocument(owner.CreateGraphics());
519 selection_visible = false;
520 selection_start.line = this.document;
521 selection_start.pos = 0;
522 selection_end.line = this.document;
523 selection_end.pos = 0;
527 // Default selection is empty
529 document_id = random.Next();
533 #region Internal Properties
550 internal Line CaretLine {
556 internal int CaretPosition {
562 internal LineTag CaretTag {
568 internal int ViewPortX {
578 internal int ViewPortY {
590 return this.document_x;
594 internal int Height {
596 return this.document_y;
600 #endregion // Internal Properties
602 #region Private Methods
604 internal void DumpTree(Line line, bool with_tags) {
605 Console.Write("Line {0}, Y: {1} Text {2}", line.line_no, line.Y, line.text != null ? line.text.ToString() : "undefined");
607 if (line.left == sentinel) {
608 Console.Write(", left = sentinel");
609 } else if (line.left == null) {
610 Console.Write(", left = NULL");
613 if (line.right == sentinel) {
614 Console.Write(", right = sentinel");
615 } else if (line.right == null) {
616 Console.Write(", right = NULL");
619 Console.WriteLine("");
627 Console.Write(" Tags: ");
628 while (tag != null) {
629 Console.Write("{0} <{1}>-<{2}> ", count++, tag.start, tag.length);
630 if (tag.line != line) {
631 Console.Write("BAD line link");
632 throw new Exception("Bad line link in tree");
639 Console.WriteLine("");
641 if (line.left != null) {
642 if (line.left != sentinel) {
643 DumpTree(line.left, with_tags);
646 if (line != sentinel) {
647 throw new Exception("Left should not be NULL");
651 if (line.right != null) {
652 if (line.right != sentinel) {
653 DumpTree(line.right, with_tags);
656 if (line != sentinel) {
657 throw new Exception("Right should not be NULL");
662 private void DecrementLines(int line_no) {
666 while (current <= lines) {
667 GetLine(current).line_no--;
673 private void IncrementLines(int line_no) {
676 current = this.lines;
677 while (current >= line_no) {
678 GetLine(current).line_no++;
684 private void RebalanceAfterAdd(Line line1) {
687 while ((line1 != document) && (line1.parent.color == LineColor.Red)) {
688 if (line1.parent == line1.parent.parent.left) {
689 line2 = line1.parent.parent.right;
691 if ((line2 != null) && (line2.color == LineColor.Red)) {
692 line1.parent.color = LineColor.Black;
693 line2.color = LineColor.Black;
694 line1.parent.parent.color = LineColor.Red;
695 line1 = line1.parent.parent;
697 if (line1 == line1.parent.right) {
698 line1 = line1.parent;
702 line1.parent.color = LineColor.Black;
703 line1.parent.parent.color = LineColor.Red;
705 RotateRight(line1.parent.parent);
708 line2 = line1.parent.parent.left;
710 if ((line2 != null) && (line2.color == LineColor.Red)) {
711 line1.parent.color = LineColor.Black;
712 line2.color = LineColor.Black;
713 line1.parent.parent.color = LineColor.Red;
714 line1 = line1.parent.parent;
716 if (line1 == line1.parent.left) {
717 line1 = line1.parent;
721 line1.parent.color = LineColor.Black;
722 line1.parent.parent.color = LineColor.Red;
723 RotateLeft(line1.parent.parent);
727 document.color = LineColor.Black;
730 private void RebalanceAfterDelete(Line line1) {
733 while ((line1 != document) && (line1.color == LineColor.Black)) {
734 if (line1 == line1.parent.left) {
735 line2 = line1.parent.right;
736 if (line2.color == LineColor.Red) {
737 line2.color = LineColor.Black;
738 line1.parent.color = LineColor.Red;
739 RotateLeft(line1.parent);
740 line2 = line1.parent.right;
742 if ((line2.left.color == LineColor.Black) && (line2.right.color == LineColor.Black)) {
743 line2.color = LineColor.Red;
744 line1 = line1.parent;
746 if (line2.right.color == LineColor.Black) {
747 line2.left.color = LineColor.Black;
748 line2.color = LineColor.Red;
750 line2 = line1.parent.right;
752 line2.color = line1.parent.color;
753 line1.parent.color = LineColor.Black;
754 line2.right.color = LineColor.Black;
755 RotateLeft(line1.parent);
759 line2 = line1.parent.left;
760 if (line2.color == LineColor.Red) {
761 line2.color = LineColor.Black;
762 line1.parent.color = LineColor.Red;
763 RotateRight(line1.parent);
764 line2 = line1.parent.left;
766 if ((line2.right.color == LineColor.Black) && (line2.left.color == LineColor.Black)) {
767 line2.color = LineColor.Red;
768 line1 = line1.parent;
770 if (line2.left.color == LineColor.Black) {
771 line2.right.color = LineColor.Black;
772 line2.color = LineColor.Red;
774 line2 = line1.parent.left;
776 line2.color = line1.parent.color;
777 line1.parent.color = LineColor.Black;
778 line2.left.color = LineColor.Black;
779 RotateRight(line1.parent);
784 line1.color = LineColor.Black;
787 private void RotateLeft(Line line1) {
788 Line line2 = line1.right;
790 line1.right = line2.left;
792 if (line2.left != sentinel) {
793 line2.left.parent = line1;
796 if (line2 != sentinel) {
797 line2.parent = line1.parent;
800 if (line1.parent != null) {
801 if (line1 == line1.parent.left) {
802 line1.parent.left = line2;
804 line1.parent.right = line2;
811 if (line1 != sentinel) {
812 line1.parent = line2;
816 private void RotateRight(Line line1) {
817 Line line2 = line1.left;
819 line1.left = line2.right;
821 if (line2.right != sentinel) {
822 line2.right.parent = line1;
825 if (line2 != sentinel) {
826 line2.parent = line1.parent;
829 if (line1.parent != null) {
830 if (line1 == line1.parent.right) {
831 line1.parent.right = line2;
833 line1.parent.left = line2;
840 if (line1 != sentinel) {
841 line1.parent = line2;
846 internal void UpdateView(Line line, int pos) {
847 if (RecalculateDocument(owner.CreateGraphics(), line.line_no, line.line_no, true)) {
848 // Lineheight changed, invalidate the rest of the document
849 if ((line.Y - viewport_y) >=0 ) {
850 // We formatted something that's in view, only draw parts of the screen
851 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, owner.Width, owner.Height - line.Y - viewport_y));
853 // The tag was above the visible area, draw everything
857 owner.Invalidate(new Rectangle((int)line.widths[pos] - viewport_x - 1, line.Y - viewport_y, (int)owner.Width, line.height));
862 // Update display from line, down line_count lines; pos is unused, but required for the signature
863 internal void UpdateView(Line line, int line_count, int pos) {
864 if (RecalculateDocument(owner.CreateGraphics(), line.line_no, line.line_no + line_count - 1, true)) {
865 // Lineheight changed, invalidate the rest of the document
866 if ((line.Y - viewport_y) >=0 ) {
867 // We formatted something that's in view, only draw parts of the screen
868 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, owner.Width, owner.Height - line.Y - viewport_y));
870 // The tag was above the visible area, draw everything
876 end_line = GetLine(line.line_no + line_count -1);
877 if (end_line == null) {
881 owner.Invalidate(new Rectangle(0 - viewport_x, line.Y - viewport_y, (int)line.widths[line.text.Length], end_line.Y + end_line.height));
884 #endregion // Private Methods
886 #region Internal Methods
887 // Clear the document and reset state
888 internal void Empty() {
891 last_found = sentinel;
894 // We always have a blank line
895 Add(1, "", owner.Font, new SolidBrush(owner.ForeColor));
896 this.RecalculateDocument(owner.CreateGraphics());
899 selection_visible = false;
900 selection_start.line = this.document;
901 selection_start.pos = 0;
902 selection_end.line = this.document;
903 selection_end.pos = 0;
912 internal void PositionCaret(Line line, int pos) {
913 caret.tag = line.FindTag(pos);
916 caret.height = caret.tag.height;
918 XplatUI.DestroyCaret(owner.Handle);
919 XplatUI.CreateCaret(owner.Handle, 2, caret.height);
920 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.align_shift - viewport_x, caret.line.Y + caret.tag.shift - viewport_y);
923 internal void PositionCaret(int x, int y) {
924 caret.tag = FindCursor(x + viewport_x, y + viewport_y, out caret.pos);
925 caret.line = caret.tag.line;
926 caret.height = caret.tag.height;
928 XplatUI.DestroyCaret(owner.Handle);
929 XplatUI.CreateCaret(owner.Handle, 2, caret.height);
930 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.align_shift - viewport_x, caret.line.Y + caret.tag.shift - viewport_y);
933 internal void CaretHasFocus() {
934 if (caret.tag != null) {
935 XplatUI.CreateCaret(owner.Handle, 2, caret.height);
936 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.align_shift - viewport_x, caret.line.Y + caret.tag.shift - viewport_y);
937 XplatUI.CaretVisible(owner.Handle, true);
941 internal void CaretLostFocus() {
942 XplatUI.DestroyCaret(owner.Handle);
945 internal void AlignCaret() {
946 caret.tag = LineTag.FindTag(caret.line, caret.pos);
947 caret.height = caret.tag.height;
949 XplatUI.CreateCaret(owner.Handle, 2, caret.height);
950 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.align_shift - viewport_x, caret.line.Y + caret.tag.shift - viewport_y);
951 XplatUI.CaretVisible(owner.Handle, true);
954 internal void UpdateCaret() {
955 if (caret.tag.height != caret.height) {
956 caret.height = caret.tag.height;
957 XplatUI.CreateCaret(owner.Handle, 2, caret.height);
959 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.align_shift - viewport_x, caret.line.Y + caret.tag.shift - viewport_y);
960 XplatUI.CaretVisible(owner.Handle, true);
963 internal void DisplayCaret() {
964 XplatUI.CaretVisible(owner.Handle, true);
967 internal void HideCaret() {
968 XplatUI.CaretVisible(owner.Handle, false);
971 internal void MoveCaret(CaretDirection direction) {
973 case CaretDirection.CharForward: {
975 if (caret.pos > caret.line.text.Length) {
978 if (caret.line.line_no < this.lines) {
979 caret.line = GetLine(caret.line.line_no+1);
981 caret.tag = caret.line.tags;
986 // Single line; we stay where we are
990 if ((caret.tag.start - 1 + caret.tag.length) < caret.pos) {
991 caret.tag = caret.tag.next;
998 case CaretDirection.CharBack: {
1000 // caret.pos--; // folded into the if below
1001 if (--caret.pos > 0) {
1002 if (caret.tag.start > caret.pos) {
1003 caret.tag = caret.tag.previous;
1007 if (caret.line.line_no > 1) {
1008 caret.line = GetLine(caret.line.line_no - 1);
1009 caret.pos = caret.line.text.Length;
1010 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1017 case CaretDirection.WordForward: {
1020 len = caret.line.text.Length;
1021 if (caret.pos < len) {
1022 while ((caret.pos < len) && (caret.line.text.ToString(caret.pos, 1) != " ")) {
1025 if (caret.pos < len) {
1026 // Skip any whitespace
1027 while ((caret.pos < len) && (caret.line.text.ToString(caret.pos, 1) == " ")) {
1032 if (caret.line.line_no < this.lines) {
1033 caret.line = GetLine(caret.line.line_no+1);
1035 caret.tag = caret.line.tags;
1042 case CaretDirection.WordBack: {
1043 if (caret.pos > 0) {
1046 while ((caret.pos > 0) && (caret.line.text.ToString(caret.pos, 1) == " ")) {
1050 while ((caret.pos > 0) && (caret.line.text.ToString(caret.pos, 1) != " ")) {
1054 if (caret.line.text.ToString(caret.pos, 1) == " ") {
1055 if (caret.pos != 0) {
1058 caret.line = GetLine(caret.line.line_no - 1);
1059 caret.pos = caret.line.text.Length;
1060 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1064 if (caret.line.line_no > 1) {
1065 caret.line = GetLine(caret.line.line_no - 1);
1066 caret.pos = caret.line.text.Length;
1067 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1074 case CaretDirection.LineUp: {
1075 if (caret.line.line_no > 1) {
1078 pixel = (int)caret.line.widths[caret.pos];
1079 PositionCaret(pixel, GetLine(caret.line.line_no - 1).Y);
1080 XplatUI.CaretVisible(owner.Handle, true);
1085 case CaretDirection.LineDown: {
1086 if (caret.line.line_no < lines) {
1089 pixel = (int)caret.line.widths[caret.pos];
1090 PositionCaret(pixel, GetLine(caret.line.line_no + 1).Y);
1091 XplatUI.CaretVisible(owner.Handle, true);
1096 case CaretDirection.Home: {
1097 if (caret.pos > 0) {
1099 caret.tag = caret.line.tags;
1105 case CaretDirection.End: {
1106 if (caret.pos < caret.line.text.Length) {
1107 caret.pos = caret.line.text.Length;
1108 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1114 case CaretDirection.PgUp: {
1118 case CaretDirection.PgDn: {
1122 case CaretDirection.CtrlHome: {
1123 caret.line = GetLine(1);
1125 caret.tag = caret.line.tags;
1131 case CaretDirection.CtrlEnd: {
1132 caret.line = GetLine(lines);
1134 caret.tag = caret.line.tags;
1142 // Draw the document
1143 internal void Draw(Graphics g, Rectangle clip) {
1144 Line line; // Current line being drawn
1145 LineTag tag; // Current tag being drawn
1146 int start; // First line to draw
1147 int end; // Last line to draw
1148 string s; // String representing the current line
1151 // First, figure out from what line to what line we need to draw
1152 start = GetLineByPixel(clip.Top - viewport_y, false).line_no;
1153 end = GetLineByPixel(clip.Bottom - viewport_y, false).line_no;
1155 // Now draw our elements; try to only draw those that are visible
1159 DateTime n = DateTime.Now;
1160 Console.WriteLine("Started drawing: {0}s {1}ms", n.Second, n.Millisecond);
1163 while (line_no <= end) {
1164 line = GetLine(line_no);
1166 s = line.text.ToString();
1167 while (tag != null) {
1168 if (((tag.X + tag.width) > (clip.Left - viewport_x)) || (tag.X < (clip.Right - viewport_x))) {
1169 // Check for selection
1170 if ((!selection_visible) || (line_no < selection_start.line.line_no) || (line_no > selection_end.line.line_no)) {
1172 g.DrawString(s.Substring(tag.start-1, tag.length), tag.font, tag.color, tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
1174 // we might have to draw our selection
1175 if ((line_no != selection_start.line.line_no) && (line_no != selection_end.line.line_no)) {
1176 g.FillRectangle(tag.color, tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, line.widths[tag.start + tag.length - 1], tag.height);
1177 g.DrawString(s.Substring(tag.start-1, tag.length), tag.font, ThemeEngine.Current.ResPool.GetSolidBrush(owner.BackColor), tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
1185 // Check the partial drawings first
1186 if ((selection_start.tag == tag) && (selection_end.tag == tag)) {
1189 // First, the regular part
1190 g.DrawString(s.Substring(tag.start - 1, selection_start.pos - tag.start + 1), tag.font, tag.color, tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
1192 // Now the highlight
1193 g.FillRectangle(tag.color, line.widths[selection_start.pos] + line.align_shift, line.Y + tag.shift - viewport_y, line.widths[selection_end.pos] - line.widths[selection_start.pos], tag.height);
1194 g.DrawString(s.Substring(selection_start.pos, selection_end.pos - selection_start.pos), tag.font, ThemeEngine.Current.ResPool.GetSolidBrush(owner.BackColor), line.widths[selection_start.pos] + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
1196 // And back to the regular
1197 g.DrawString(s.Substring(selection_end.pos, tag.length - selection_end.pos), tag.font, tag.color, line.widths[selection_end.pos] + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
1198 } else if (selection_start.tag == tag) {
1201 // The highlighted part
1202 g.FillRectangle(tag.color, line.widths[selection_start.pos] + line.align_shift, line.Y + tag.shift - viewport_y, line.widths[tag.start + tag.length - 1] - line.widths[selection_start.pos], tag.height);
1203 g.DrawString(s.Substring(selection_start.pos, tag.length - selection_start.pos), tag.font, ThemeEngine.Current.ResPool.GetSolidBrush(owner.BackColor), line.widths[selection_start.pos] + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
1206 g.DrawString(s.Substring(tag.start - 1, selection_start.pos - tag.start + 1), tag.font, tag.color, tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
1207 } else if (selection_end.tag == tag) {
1210 // The highlighted part
1211 g.FillRectangle(tag.color, tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, line.widths[selection_end.pos], tag.height);
1212 g.DrawString(s.Substring(tag.start - 1, selection_end.pos - tag.start + 1), tag.font, ThemeEngine.Current.ResPool.GetSolidBrush(owner.BackColor), tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
1215 g.DrawString(s.Substring(selection_end.pos, tag.length - selection_end.pos), tag.font, tag.color, line.widths[selection_end.pos] + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
1217 // no partially selected tags here, simple checks...
1218 if (selection_start.line == line) {
1219 if ((tag.start + tag.length - 1) > selection_start.pos) {
1223 if (selection_end.line == line) {
1224 if ((tag.start + tag.length - 1) < selection_start.pos) {
1232 g.FillRectangle(tag.color, tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, line.widths[tag.start + tag.length - 1], tag.height);
1233 g.DrawString(s.Substring(tag.start-1, tag.length), tag.font, ThemeEngine.Current.ResPool.GetSolidBrush(owner.BackColor), tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
1235 g.DrawString(s.Substring(tag.start-1, tag.length), tag.font, tag.color, tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
1250 Console.WriteLine("Finished drawing: {0}s {1}ms", n.Second, n.Millisecond);
1256 // Inserts a character at the given position
1257 internal void InsertString(Line line, int pos, string s) {
1258 InsertString(line.FindTag(pos), pos, s);
1261 // Inserts a string at the given position
1262 internal void InsertString(LineTag tag, int pos, string s) {
1269 line.text.Insert(pos, s);
1273 while (tag != null) {
1280 UpdateView(line, pos);
1283 // Inserts a string at the caret position
1284 internal void InsertStringAtCaret(string s, bool move_caret) {
1290 caret.line.text.Insert(caret.pos, s);
1291 caret.tag.length += len;
1293 if (caret.tag.next != null) {
1294 tag = caret.tag.next;
1295 while (tag != null) {
1300 caret.line.Grow(len);
1301 caret.line.recalc = true;
1303 UpdateView(caret.line, caret.pos);
1312 // Inserts a character at the given position
1313 internal void InsertChar(Line line, int pos, char ch) {
1314 InsertChar(line.FindTag(pos), pos, ch);
1317 // Inserts a character at the given position
1318 internal void InsertChar(LineTag tag, int pos, char ch) {
1322 line.text.Insert(pos, ch);
1326 while (tag != null) {
1333 UpdateView(line, pos);
1336 // Inserts a character at the current caret position
1337 internal void InsertCharAtCaret(char ch, bool move_caret) {
1340 caret.line.text.Insert(caret.pos, ch);
1343 if (caret.tag.next != null) {
1344 tag = caret.tag.next;
1345 while (tag != null) {
1351 caret.line.recalc = true;
1353 UpdateView(caret.line, caret.pos);
1360 // Inserts n characters at the given position; it will not delete past line limits
1361 internal void DeleteChars(LineTag tag, int pos, int count) {
1369 if (pos == line.text.Length) {
1373 line.text.Remove(pos, count);
1375 // Check if we're crossing tag boundaries
1376 if ((pos + count) > (tag.start + tag.length)) {
1379 // We have to delete cross tag boundaries
1383 left -= pos - tag.start;
1384 tag.length -= pos - tag.start;
1387 while ((tag != null) && (left > 0)) {
1388 if (tag.length > left) {
1399 // We got off easy, same tag
1401 tag.length -= count;
1405 while (tag != null) {
1415 UpdateView(line, pos);
1419 // Deletes a character at or after the given position (depending on forward); it will not delete past line limits
1420 internal void DeleteChar(LineTag tag, int pos, bool forward) {
1427 if ((pos == 0 && forward == false) || (pos == line.text.Length && forward == true)) {
1432 line.text.Remove(pos, 1);
1435 if (tag.length == 0) {
1440 line.text.Remove(pos, 1);
1441 if (pos >= (tag.start - 1)) {
1443 if (tag.length == 0) {
1446 } else if (tag.previous != null) {
1447 tag.previous.length--;
1448 if (tag.previous.length == 0) {
1455 while (tag != null) {
1464 UpdateView(line, pos);
1467 // Combine two lines
1468 internal void Combine(int FirstLine, int SecondLine) {
1469 Combine(GetLine(FirstLine), GetLine(SecondLine));
1472 internal void Combine(Line first, Line second) {
1476 // Combine the two tag chains into one
1479 while (last.next != null) {
1483 last.next = second.tags;
1484 last.next.previous = last;
1486 shift = last.start + last.length - 1;
1488 // Fix up references within the chain
1490 while (last != null) {
1492 last.start += shift;
1496 // Combine both lines' strings
1497 first.text.Insert(first.text.Length, second.text.ToString());
1498 first.Grow(first.text.Length);
1500 // Remove the reference to our (now combined) tags from the doomed line
1504 DecrementLines(first.line_no + 2); // first.line_no + 1 will be deleted, so we need to start renumbering one later
1507 first.recalc = true;
1508 first.height = 0; // This forces RecalcDocument/UpdateView to redraw from this line on
1515 check_first = GetLine(first.line_no);
1516 check_second = GetLine(check_first.line_no + 1);
1518 Console.WriteLine("Pre-delete: Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
1521 this.Delete(second);
1524 check_first = GetLine(first.line_no);
1525 check_second = GetLine(check_first.line_no + 1);
1527 Console.WriteLine("Post-delete Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
1532 // Split the line at the position into two
1533 internal void Split(int LineNo, int pos) {
1537 line = GetLine(LineNo);
1538 tag = LineTag.FindTag(line, pos);
1539 Split(line, tag, pos);
1542 internal void Split(Line line, int pos) {
1545 tag = LineTag.FindTag(line, pos);
1546 Split(line, tag, pos);
1549 internal void Split(Line line, LineTag tag, int pos) {
1553 // cover the easy case first
1554 if (pos == line.text.Length) {
1555 Add(line.line_no + 1, "", line.alignment, tag.font, tag.color);
1559 // We need to move the rest of the text into the new line
1560 Add(line.line_no + 1, line.text.ToString(pos, line.text.Length - pos), line.alignment, tag.font, tag.color);
1562 // Now transfer our tags from this line to the next
1563 new_line = GetLine(line.line_no + 1);
1566 if ((tag.start - 1) == pos) {
1569 // We can simply break the chain and move the tag into the next line
1570 if (tag == line.tags) {
1571 new_tag = new LineTag(line, 1, 0);
1572 new_tag.font = tag.font;
1573 new_tag.color = tag.color;
1574 line.tags = new_tag;
1577 if (tag.previous != null) {
1578 tag.previous.next = null;
1580 new_line.tags = tag;
1581 tag.previous = null;
1582 tag.line = new_line;
1584 // Walk the list and correct the start location of the tags we just bumped into the next line
1585 shift = tag.start - 1;
1588 while (new_tag != null) {
1589 new_tag.start -= shift;
1590 new_tag.line = new_line;
1591 new_tag = new_tag.next;
1596 new_tag = new LineTag(new_line, 1, tag.start - 1 + tag.length - pos);
1597 new_tag.next = tag.next;
1598 new_tag.font = tag.font;
1599 new_tag.color = tag.color;
1600 new_line.tags = new_tag;
1601 if (new_tag.next != null) {
1602 new_tag.next.previous = new_tag;
1605 tag.length = pos - tag.start + 1;
1608 new_tag = new_tag.next;
1609 while (new_tag != null) {
1610 new_tag.start -= shift;
1611 new_tag.line = new_line;
1612 new_tag = new_tag.next;
1616 line.text.Remove(pos, line.text.Length - pos);
1619 // Adds a line of text, with given font.
1620 // Bumps any line at that line number that already exists down
1621 internal void Add(int LineNo, string Text, Font font, Brush color) {
1622 Add(LineNo, Text, HorizontalAlignment.Left, font, color);
1625 internal void Add(int LineNo, string Text, HorizontalAlignment align, Font font, Brush color) {
1630 if (LineNo<1 || Text == null) {
1632 throw new ArgumentNullException("LineNo", "Line numbers must be positive");
1634 throw new ArgumentNullException("Text", "Cannot insert NULL line");
1638 add = new Line(LineNo, Text, align, font, color);
1641 while (line != sentinel) {
1643 line_no = line.line_no;
1645 if (LineNo > line_no) {
1647 } else if (LineNo < line_no) {
1650 // Bump existing line numbers; walk all nodes to the right of this one and increment line_no
1651 IncrementLines(line.line_no);
1656 add.left = sentinel;
1657 add.right = sentinel;
1659 if (add.parent != null) {
1660 if (LineNo > add.parent.line_no) {
1661 add.parent.right = add;
1663 add.parent.left = add;
1670 RebalanceAfterAdd(add);
1675 internal virtual void Clear() {
1677 document = sentinel;
1680 public virtual object Clone() {
1683 clone = new Document(null);
1685 clone.lines = this.lines;
1686 clone.document = (Line)document.Clone();
1691 internal void Delete(int LineNo) {
1696 Delete(GetLine(LineNo));
1699 internal void Delete(Line line1) {
1700 Line line2;// = new Line();
1703 if ((line1.left == sentinel) || (line1.right == sentinel)) {
1706 line3 = line1.right;
1707 while (line3.left != sentinel) {
1712 if (line3.left != sentinel) {
1715 line2 = line3.right;
1718 line2.parent = line3.parent;
1719 if (line3.parent != null) {
1720 if(line3 == line3.parent.left) {
1721 line3.parent.left = line2;
1723 line3.parent.right = line2;
1729 if (line3 != line1) {
1732 line1.ascent = line3.ascent;
1733 line1.height = line3.height;
1734 line1.line_no = line3.line_no;
1735 line1.recalc = line3.recalc;
1736 line1.space = line3.space;
1737 line1.tags = line3.tags;
1738 line1.text = line3.text;
1739 line1.widths = line3.widths;
1743 while (tag != null) {
1749 if (line3.color == LineColor.Black)
1750 RebalanceAfterDelete(line2);
1754 last_found = sentinel;
1757 // Set our selection markers
1758 internal void Invalidate(Line start, int start_pos, Line end, int end_pos) {
1764 // figure out what's before what so the logic below is straightforward
1765 if (start.line_no < end.line_no) {
1771 } else if (start.line_no > end.line_no) {
1778 if (start_pos < end_pos) {
1792 owner.Invalidate(new Rectangle((int)l1.widths[p1] - viewport_x, l1.Y - viewport_y, (int)l2.widths[p2] - (int)l1.widths[p1], l1.height));
1796 // Three invalidates:
1797 // First line from start
1798 owner.Invalidate(new Rectangle((int)l1.widths[p1] - viewport_x, l1.Y - viewport_y, owner.Width, l1.height));
1801 if ((l1.line_no + 1) < l2.line_no) {
1804 y = GetLine(l1.line_no + 1).Y;
1805 owner.Invalidate(new Rectangle(0 - viewport_x, y - viewport_y, owner.Width, GetLine(l2.line_no - 1).Y - y - viewport_y));
1809 owner.Invalidate(new Rectangle((int)l1.widths[p1] - viewport_x, l1.Y - viewport_y, owner.Width, l1.height));
1814 // It's nothing short of pathetic to always invalidate the whole control
1815 // I will find time to finish the optimization and make it invalidate deltas only
1816 internal void SetSelectionToCaret(bool start) {
1818 selection_start.line = caret.line;
1819 selection_start.tag = caret.tag;
1820 selection_start.pos = caret.pos;
1822 // start always also selects end
1823 selection_end.line = caret.line;
1824 selection_end.tag = caret.tag;
1825 selection_end.pos = caret.pos;
1827 if (caret.line.line_no <= selection_end.line.line_no) {
1828 if ((caret.line != selection_end.line) || (caret.pos < selection_end.pos)) {
1829 selection_start.line = caret.line;
1830 selection_start.tag = caret.tag;
1831 selection_start.pos = caret.pos;
1833 selection_end.line = caret.line;
1834 selection_end.tag = caret.tag;
1835 selection_end.pos = caret.pos;
1838 selection_end.line = caret.line;
1839 selection_end.tag = caret.tag;
1840 selection_end.pos = caret.pos;
1845 if ((selection_start.line == selection_end.line) && (selection_start.pos == selection_end.pos)) {
1846 selection_visible = false;
1848 selection_visible = true;
1854 internal void SetSelection(Line start, int start_pos, Line end, int end_pos) {
1855 // Ok, this is ugly, bad and slow, but I don't have time right now to optimize it.
1856 if (selection_visible) {
1857 // Try to only invalidate what's changed so we don't redraw the whole thing
1858 if ((start != selection_start.line) || (start_pos != selection_start.pos) && ((end == selection_end.line) && (end_pos == selection_end.pos))) {
1859 Invalidate(start, start_pos, selection_start.line, selection_start.pos);
1860 } else if ((end != selection_end.line) || (end_pos != selection_end.pos) && ((start == selection_start.line) && (start_pos == selection_start.pos))) {
1861 Invalidate(end, end_pos, selection_end.line, selection_end.pos);
1863 // both start and end changed
1864 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
1867 selection_start.line = start;
1868 selection_start.pos = start_pos;
1869 if (start != null) {
1870 selection_start.tag = LineTag.FindTag(start, start_pos);
1873 selection_end.line = end;
1874 selection_end.pos = end_pos;
1876 selection_end.tag = LineTag.FindTag(end, end_pos);
1879 if (((start == end) && (start_pos == end_pos)) || start == null || end == null) {
1880 selection_visible = false;
1882 selection_visible = true;
1887 // Make sure that start is always before end
1888 private void FixupSelection() {
1889 if (selection_start.line.line_no > selection_end.line.line_no) {
1894 line = selection_start.line;
1895 tag = selection_start.tag;
1896 pos = selection_start.pos;
1898 selection_start.line = selection_end.line;
1899 selection_start.tag = selection_end.tag;
1900 selection_start.pos = selection_end.pos;
1902 selection_end.line = line;
1903 selection_end.tag = tag;
1904 selection_end.pos = pos;
1909 if ((selection_start.line == selection_end.line) && (selection_start.pos > selection_end.pos)) {
1912 pos = selection_start.pos;
1913 selection_start.pos = selection_end.pos;
1914 selection_end.pos = pos;
1915 Console.WriteLine("flipped: sel start: {0} end: {1}", selection_start.pos, selection_end.pos);
1922 internal void SetSelectionStart(Line start, int start_pos) {
1923 selection_start.line = start;
1924 selection_start.pos = start_pos;
1925 selection_start.tag = LineTag.FindTag(start, start_pos);
1929 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
1930 selection_visible = true;
1933 if (selection_visible) {
1934 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
1939 internal void SetSelectionEnd(Line end, int end_pos) {
1940 selection_end.line = end;
1941 selection_end.pos = end_pos;
1942 selection_end.tag = LineTag.FindTag(end, end_pos);
1946 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
1947 selection_visible = true;
1950 if (selection_visible) {
1951 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
1955 internal void SetSelection(Line start, int start_pos) {
1956 if (selection_visible) {
1957 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
1961 selection_start.line = start;
1962 selection_start.pos = start_pos;
1963 selection_start.tag = LineTag.FindTag(start, start_pos);
1965 selection_end.line = start;
1966 selection_end.tag = selection_start.tag;
1967 selection_end.pos = start_pos;
1969 selection_visible = false;
1972 internal void InvalidateSelectionArea() {
1976 // Return the current selection, as string
1977 internal string GetSelection() {
1978 // We return String.Empty if there is no selection
1979 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
1980 return string.Empty;
1983 if (!multiline || (selection_start.line == selection_end.line)) {
1984 return selection_start.line.text.ToString(selection_start.pos, selection_end.pos - selection_start.pos);
1991 sb = new StringBuilder();
1992 start = selection_start.line.line_no;
1993 end = selection_end.line.line_no;
1995 sb.Append(selection_start.line.text.ToString(selection_start.pos, selection_start.line.text.Length - selection_start.pos) + Environment.NewLine);
1997 if ((start + 1) < end) {
1998 for (i = start + 1; i < end; i++) {
1999 sb.Append(GetLine(i).text.ToString() + Environment.NewLine);
2003 sb.Append(selection_end.line.text.ToString(0, selection_end.pos));
2005 return sb.ToString();
2009 internal void ReplaceSelection(string s) {
2010 // The easiest is to break the lines where the selection tags are and delete those lines
2011 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
2012 // Nothing to delete, simply insert
2013 InsertString(selection_start.tag, selection_start.pos, s);
2016 if (!multiline || (selection_start.line == selection_end.line)) {
2017 DeleteChars(selection_start.tag, selection_start.pos, selection_end.pos - selection_start.pos);
2019 // The tag might have been removed, we need to recalc it
2020 selection_start.tag = selection_start.line.FindTag(selection_start.pos);
2022 InsertString(selection_start.tag, selection_start.pos, s);
2031 start = selection_start.line.line_no;
2032 end = selection_end.line.line_no;
2034 // Delete first line
2035 DeleteChars(selection_start.tag, selection_start.pos, selection_end.pos - selection_start.pos);
2039 for (i = end - 1; i >= start; i++) {
2045 DeleteChars(selection_end.line.tags, 0, selection_end.pos);
2047 ins = s.Split(new char[] {'\n'});
2049 for (int j = 0; j < ins.Length; j++) {
2050 if (ins[j].EndsWith("\r")) {
2051 ins[j] = ins[j].Substring(0, ins[j].Length - 1);
2055 insert_lines = ins.Length;
2057 // Bump the text at insertion point a line down if we're inserting more than one line
2058 if (insert_lines > 1) {
2059 Split(selection_start.line, selection_start.pos);
2061 // Reminder of start line is now in startline+1
2063 // if the last line does not end with a \n we will insert the last line in front of the just moved text
2064 if (s.EndsWith("\n")) {
2065 insert_lines--; // We don't want to insert the last line as part of the loop anymore
2067 InsertString(GetLine(selection_start.line.line_no + 1), 0, ins[insert_lines - 1]);
2071 // Insert the first line
2072 InsertString(selection_start.line, selection_start.pos, ins[0]);
2074 if (insert_lines > 1) {
2075 base_line = selection_start.line.line_no + 1;
2077 for (i = 1; i < insert_lines; i++) {
2078 Add(base_line + i, ins[i], selection_start.line.alignment, selection_start.tag.font, selection_start.tag.color);
2082 selection_end.line = selection_start.line;
2083 selection_end.pos = selection_start.pos;
2084 selection_end.tag = selection_start.tag;
2085 selection_visible = false;
2086 InvalidateSelectionArea();
2089 internal void CharIndexToLineTag(int index, out Line line_out, out LineTag tag_out, out int pos) {
2098 for (i = 1; i < lines; i++) {
2102 chars += line.text.Length + 2; // We count the trailing \n as a char
2104 if (index <= chars) {
2105 // we found the line
2108 while (tag != null) {
2109 if (index < (start + tag.start + tag.length)) {
2112 pos = index - start;
2115 if (tag.next == null) {
2118 next_line = GetLine(line.line_no + 1);
2120 if (next_line != null) {
2121 line_out = next_line;
2122 tag_out = next_line.tags;
2128 pos = line_out.text.Length;
2137 line_out = GetLine(lines);
2138 tag = line_out.tags;
2139 while (tag.next != null) {
2143 pos = line_out.text.Length;
2146 internal int LineTagToCharIndex(Line line, int pos) {
2150 // Count first and last line
2153 // Count the lines in the middle
2155 for (i = 1; i < line.line_no; i++) {
2156 length += GetLine(i).text.Length + 2; // Add one for the \n at the end of the line
2164 internal int SelectionLength() {
2165 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
2169 if (!multiline || (selection_start.line == selection_end.line)) {
2170 return selection_end.pos - selection_start.pos;
2177 // Count first and last line
2178 length = selection_start.line.text.Length - selection_start.pos + selection_end.pos;
2180 // Count the lines in the middle
2181 start = selection_start.line.line_no + 1;
2182 end = selection_end.line.line_no;
2185 for (i = start; i < end; i++) {
2186 length += GetLine(i).text.Length;
2197 // Give it a Line number and it returns the Line object at with that line number
2198 internal Line GetLine(int LineNo) {
2199 Line line = document;
2201 while (line != sentinel) {
2202 if (LineNo == line.line_no) {
2204 } else if (LineNo < line.line_no) {
2214 // Give it a Y pixel coordinate and it returns the Line covering that Y coordinate
2216 internal Line GetLineByPixel(int y, bool exact) {
2217 Line line = document;
2220 while (line != sentinel) {
2222 if ((y >= line.Y) && (y < (line.Y+line.height))) {
2224 } else if (y < line.Y) {
2237 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
2238 internal LineTag FindTag(int x, int y, out int index, bool exact) {
2242 line = GetLineByPixel(y, exact);
2249 // Alignment adjustment
2250 x += line.align_shift;
2253 if (x >= tag.X && x < (tag.X+tag.width)) {
2256 end = tag.start + tag.length - 1;
2258 for (int pos = tag.start; pos < end; pos++) {
2259 if (x < line.widths[pos]) {
2267 if (tag.next != null) {
2275 index = line.text.Length;
2281 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
2282 internal LineTag FindCursor(int x, int y, out int index) {
2286 line = GetLineByPixel(y, false);
2289 // Adjust for alignment
2290 x += line.align_shift;
2293 if (x >= tag.X && x < (tag.X+tag.width)) {
2296 end = tag.start + tag.length - 1;
2298 for (int pos = tag.start-1; pos < end; pos++) {
2299 // When clicking on a character, we position the cursor to whatever edge
2300 // of the character the click was closer
2301 if (x < (line.widths[pos] + ((line.widths[pos+1]-line.widths[pos])/2))) {
2309 if (tag.next != null) {
2312 index = line.text.Length;
2318 internal void RecalculateAlignments() {
2324 while (line_no <= lines) {
2325 line = GetLine(line_no);
2327 if (line.alignment != HorizontalAlignment.Left) {
2328 if (line.alignment == HorizontalAlignment.Center) {
2329 line.align_shift = (document_x - (int)line.widths[line.text.Length]) / 2;
2331 line.align_shift = document_x - (int)line.widths[line.text.Length];
2340 // Calculate formatting for the whole document
2341 internal bool RecalculateDocument(Graphics g) {
2342 return RecalculateDocument(g, 1, this.lines, false);
2345 // Calculate formatting starting at a certain line
2346 internal bool RecalculateDocument(Graphics g, int start) {
2347 return RecalculateDocument(g, start, this.lines, false);
2350 // Calculate formatting within two given line numbers
2351 internal bool RecalculateDocument(Graphics g, int start, int end) {
2352 return RecalculateDocument(g, start, end, false);
2355 // With optimize on, returns true if line heights changed
2356 internal bool RecalculateDocument(Graphics g, int start, int end, bool optimize) {
2361 Y = GetLine(start).Y;
2365 bool alignment_recalc;
2368 alignment_recalc = false;
2370 while (line_no <= end) {
2371 line = GetLine(line_no++);
2374 if (line.RecalculateLine(g)) {
2376 // If the height changed, all subsequent lines change
2380 if (line.widths[line.text.Length] > this.document_x) {
2381 this.document_x = (int)line.widths[line.text.Length];
2382 alignment_recalc = true;
2385 // Calculate alignment
2386 if (line.alignment != HorizontalAlignment.Left) {
2387 if (line.alignment == HorizontalAlignment.Center) {
2388 line.align_shift = (document_x - (int)line.widths[line.text.Length]) / 2;
2390 line.align_shift = document_x - (int)line.widths[line.text.Length];
2398 if (alignment_recalc) {
2399 RecalculateAlignments();
2404 while (line_no <= end) {
2405 line = GetLine(line_no++);
2407 line.RecalculateLine(g);
2408 if (line.widths[line.text.Length] > this.document_x) {
2409 this.document_x = (int)line.widths[line.text.Length];
2412 // Calculate alignment
2413 if (line.alignment != HorizontalAlignment.Left) {
2414 if (line.alignment == HorizontalAlignment.Center) {
2415 line.align_shift = (document_x - (int)line.widths[line.text.Length]) / 2;
2417 line.align_shift = document_x - (int)line.widths[line.text.Length];
2423 RecalculateAlignments();
2428 internal bool SetCursor(int x, int y) {
2432 internal int Size() {
2435 #endregion // Internal Methods
2437 #region Administrative
2438 public IEnumerator GetEnumerator() {
2443 public override bool Equals(object obj) {
2448 if (!(obj is Document)) {
2456 if (ToString().Equals(((Document)obj).ToString())) {
2463 public override int GetHashCode() {
2467 public override string ToString() {
2468 return "document " + this.document_id;
2471 #endregion // Administrative
2474 internal class LineTag {
2475 #region Local Variables;
2476 // Payload; formatting
2477 internal Font font; // System.Drawing.Font object for this tag
2478 internal Brush color; // System.Drawing.Brush object
2481 internal int start; // start, in chars; index into Line.text
2482 internal int length; // length, in chars
2483 internal bool r_to_l; // Which way is the font
2486 internal int height; // Height in pixels of the text this tag describes
2487 internal int X; // X location of the text this tag describes
2488 internal float width; // Width in pixels of the text this tag describes
2489 internal int ascent; // Ascent of the font for this tag
2490 internal int shift; // Shift down for this tag, to stay on baseline
2492 internal int soft_break; // Tag is 'broken soft' and continues in the next line
2495 internal Line line; // The line we're on
2496 internal LineTag next; // Next tag on the same line
2497 internal LineTag previous; // Previous tag on the same line
2500 #region Constructors
2501 internal LineTag(Line line, int start, int length) {
2504 this.length = length;
2508 #endregion // Constructors
2510 #region Internal Methods
2512 // Applies 'font' to characters starting at 'start' for 'length' chars
2513 // Removes any previous tags overlapping the same area
2514 // returns true if lineheight has changed
2516 internal static bool FormatText(Line line, int start, int length, Font font, Brush color) {
2520 bool retval = false; // Assume line-height doesn't change
2523 if (font.Height != line.height) {
2526 line.recalc = true; // This forces recalculation of the line in RecalculateDocument
2528 // A little sanity, not sure if it's needed, might be able to remove for speed
2529 if (length > line.text.Length) {
2530 length = line.text.Length;
2534 end = start + length;
2536 // Common special case
2537 if ((start == 1) && (length == tag.length)) {
2544 start_tag = FindTag(line, start);
2546 tag = new LineTag(line, start, length);
2554 if (start_tag.start == start) {
2555 tag.next = start_tag;
2556 tag.previous = start_tag.previous;
2557 if (start_tag.previous != null) {
2558 start_tag.previous.next = tag;
2560 start_tag.previous = tag;
2562 // Insert ourselves 'in the middle'
2563 if ((start_tag.next != null) && (start_tag.next.start < end)) {
2564 tag.next = start_tag.next;
2566 tag.next = new LineTag(line, start_tag.start, start_tag.length);
2567 tag.next.font = start_tag.font;
2568 tag.next.color = start_tag.color;
2570 if (start_tag.next != null) {
2571 tag.next.next = start_tag.next;
2572 tag.next.next.previous = tag.next;
2575 tag.next.previous = tag;
2577 start_tag.length = start - start_tag.start;
2579 tag.previous = start_tag;
2580 start_tag.next = tag;
2582 if (tag.next.start > (tag.start + tag.length)) {
2583 tag.next.length += tag.next.start - (tag.start + tag.length);
2584 tag.next.start = tag.start + tag.length;
2591 while ((tag != null) && (tag.start < end)) {
2592 if ((tag.start + tag.length) <= end) {
2594 tag.previous.next = tag.next;
2595 if (tag.next != null) {
2596 tag.next.previous = tag.previous;
2600 // Adjust the length of the tag
2601 tag.length = (tag.start + tag.length) - end;
2612 // Finds the tag that describes the character at position 'pos' on 'line'
2614 internal static LineTag FindTag(Line line, int pos) {
2615 LineTag tag = line.tags;
2617 // Beginning of line is a bit special
2622 while (tag != null) {
2623 if ((tag.start <= pos) && (pos < (tag.start+tag.length))) {
2634 // Combines 'this' tag with 'other' tag.
2636 internal bool Combine(LineTag other) {
2637 if (!this.Equals(other)) {
2641 this.width += other.width;
2642 this.length += other.length;
2643 this.next = other.next;
2644 if (this.next != null) {
2645 this.next.previous = this;
2653 // Remove 'this' tag ; to be called when formatting is to be removed
2655 internal bool Remove() {
2656 if ((this.start == 1) && (this.next == null)) {
2657 // We cannot remove the only tag
2660 if (this.start != 1) {
2661 this.previous.length += this.length;
2662 this.previous.width = -1;
2663 this.previous.next = this.next;
2664 this.next.previous = this.previous;
2666 this.next.start = 1;
2667 this.next.length += this.length;
2668 this.next.width = -1;
2669 this.line.tags = this.next;
2670 this.next.previous = null;
2677 // Checks if 'this' tag describes the same formatting options as 'obj'
2679 public override bool Equals(object obj) {
2686 if (!(obj is LineTag)) {
2694 other = (LineTag)obj;
2696 if (this.font.Equals(other.font) && this.color.Equals(other.color)) { // FIXME add checking for things like link or type later
2703 public override int GetHashCode() {
\r
2704 return base.GetHashCode ();
\r
2707 public override string ToString() {
2708 return "Tag starts at index " + this.start + "length " + this.length + " text: " + this.line.Text.Substring(this.start-1, this.length) + "Font " + this.font.ToString();
2711 #endregion // Internal Methods