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 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
1153 // First, figure out from what line to what line we need to draw
1154 start = GetLineByPixel(clip.Top - viewport_y, false).line_no;
1155 end = GetLineByPixel(clip.Bottom - viewport_y, false).line_no;
1157 // Now draw our elements; try to only draw those that are visible
1161 DateTime n = DateTime.Now;
1162 Console.WriteLine("Started drawing: {0}s {1}ms", n.Second, n.Millisecond);
1165 hilight = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorHilight);
1166 hilight_text = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorHilightText);
1168 while (line_no <= end) {
1169 line = GetLine(line_no);
1171 s = line.text.ToString();
1172 while (tag != null) {
1173 if (((tag.X + tag.width) > (clip.Left - viewport_x)) || (tag.X < (clip.Right - viewport_x))) {
1174 // Check for selection
1175 if ((!selection_visible) || (line_no < selection_start.line.line_no) || (line_no > selection_end.line.line_no)) {
1177 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);
1179 // we might have to draw our selection
1180 if ((line_no != selection_start.line.line_no) && (line_no != selection_end.line.line_no)) {
1181 g.FillRectangle(hilight, tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, line.widths[tag.start + tag.length - 1], tag.height);
1182 g.DrawString(s.Substring(tag.start-1, tag.length), tag.font, hilight_text, tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
1190 // Check the partial drawings first
1191 if ((selection_start.tag == tag) && (selection_end.tag == tag)) {
1194 // First, the regular part
1195 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);
1197 // Now the highlight
1198 g.FillRectangle(hilight, 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);
1199 g.DrawString(s.Substring(selection_start.pos, selection_end.pos - selection_start.pos), tag.font, hilight_text, line.widths[selection_start.pos] + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
1201 // And back to the regular
1202 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);
1203 } else if (selection_start.tag == tag) {
1206 // The highlighted part
1207 g.FillRectangle(hilight, 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);
1208 g.DrawString(s.Substring(selection_start.pos, tag.length - selection_start.pos), tag.font, hilight_text, line.widths[selection_start.pos] + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
1211 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);
1212 } else if (selection_end.tag == tag) {
1215 // The highlighted part
1216 g.FillRectangle(hilight, tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, line.widths[selection_end.pos], tag.height);
1217 g.DrawString(s.Substring(tag.start - 1, selection_end.pos - tag.start + 1), tag.font, hilight_text, tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
1220 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);
1222 // no partially selected tags here, simple checks...
1223 if (selection_start.line == line) {
1224 if ((tag.start + tag.length - 1) > selection_start.pos) {
1228 if (selection_end.line == line) {
1229 if ((tag.start + tag.length - 1) < selection_start.pos) {
1237 g.FillRectangle(hilight, tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, line.widths[tag.start + tag.length - 1], tag.height);
1238 g.DrawString(s.Substring(tag.start-1, tag.length), tag.font, hilight_text, tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
1240 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);
1255 Console.WriteLine("Finished drawing: {0}s {1}ms", n.Second, n.Millisecond);
1261 // Inserts a character at the given position
1262 internal void InsertString(Line line, int pos, string s) {
1263 InsertString(line.FindTag(pos), pos, s);
1266 // Inserts a string at the given position
1267 internal void InsertString(LineTag tag, int pos, string s) {
1274 line.text.Insert(pos, s);
1278 while (tag != null) {
1285 UpdateView(line, pos);
1288 // Inserts a string at the caret position
1289 internal void InsertStringAtCaret(string s, bool move_caret) {
1295 caret.line.text.Insert(caret.pos, s);
1296 caret.tag.length += len;
1298 if (caret.tag.next != null) {
1299 tag = caret.tag.next;
1300 while (tag != null) {
1305 caret.line.Grow(len);
1306 caret.line.recalc = true;
1308 UpdateView(caret.line, caret.pos);
1317 // Inserts a character at the given position
1318 internal void InsertChar(Line line, int pos, char ch) {
1319 InsertChar(line.FindTag(pos), pos, ch);
1322 // Inserts a character at the given position
1323 internal void InsertChar(LineTag tag, int pos, char ch) {
1327 line.text.Insert(pos, ch);
1331 while (tag != null) {
1338 UpdateView(line, pos);
1341 // Inserts a character at the current caret position
1342 internal void InsertCharAtCaret(char ch, bool move_caret) {
1345 caret.line.text.Insert(caret.pos, ch);
1348 if (caret.tag.next != null) {
1349 tag = caret.tag.next;
1350 while (tag != null) {
1356 caret.line.recalc = true;
1358 UpdateView(caret.line, caret.pos);
1365 // Inserts n characters at the given position; it will not delete past line limits
1366 internal void DeleteChars(LineTag tag, int pos, int count) {
1374 if (pos == line.text.Length) {
1378 line.text.Remove(pos, count);
1380 // Check if we're crossing tag boundaries
1381 if ((pos + count) > (tag.start + tag.length)) {
1384 // We have to delete cross tag boundaries
1388 left -= pos - tag.start;
1389 tag.length -= pos - tag.start;
1392 while ((tag != null) && (left > 0)) {
1393 if (tag.length > left) {
1404 // We got off easy, same tag
1406 tag.length -= count;
1410 while (tag != null) {
1420 UpdateView(line, pos);
1424 // Deletes a character at or after the given position (depending on forward); it will not delete past line limits
1425 internal void DeleteChar(LineTag tag, int pos, bool forward) {
1432 if ((pos == 0 && forward == false) || (pos == line.text.Length && forward == true)) {
1437 line.text.Remove(pos, 1);
1440 if (tag.length == 0) {
1445 line.text.Remove(pos, 1);
1446 if (pos >= (tag.start - 1)) {
1448 if (tag.length == 0) {
1451 } else if (tag.previous != null) {
1452 tag.previous.length--;
1453 if (tag.previous.length == 0) {
1460 while (tag != null) {
1469 UpdateView(line, pos);
1472 // Combine two lines
1473 internal void Combine(int FirstLine, int SecondLine) {
1474 Combine(GetLine(FirstLine), GetLine(SecondLine));
1477 internal void Combine(Line first, Line second) {
1481 // Combine the two tag chains into one
1484 while (last.next != null) {
1488 last.next = second.tags;
1489 last.next.previous = last;
1491 shift = last.start + last.length - 1;
1493 // Fix up references within the chain
1495 while (last != null) {
1497 last.start += shift;
1501 // Combine both lines' strings
1502 first.text.Insert(first.text.Length, second.text.ToString());
1503 first.Grow(first.text.Length);
1505 // Remove the reference to our (now combined) tags from the doomed line
1509 DecrementLines(first.line_no + 2); // first.line_no + 1 will be deleted, so we need to start renumbering one later
1512 first.recalc = true;
1513 first.height = 0; // This forces RecalcDocument/UpdateView to redraw from this line on
1520 check_first = GetLine(first.line_no);
1521 check_second = GetLine(check_first.line_no + 1);
1523 Console.WriteLine("Pre-delete: Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
1526 this.Delete(second);
1529 check_first = GetLine(first.line_no);
1530 check_second = GetLine(check_first.line_no + 1);
1532 Console.WriteLine("Post-delete Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
1537 // Split the line at the position into two
1538 internal void Split(int LineNo, int pos) {
1542 line = GetLine(LineNo);
1543 tag = LineTag.FindTag(line, pos);
1544 Split(line, tag, pos);
1547 internal void Split(Line line, int pos) {
1550 tag = LineTag.FindTag(line, pos);
1551 Split(line, tag, pos);
1554 internal void Split(Line line, LineTag tag, int pos) {
1558 // cover the easy case first
1559 if (pos == line.text.Length) {
1560 Add(line.line_no + 1, "", line.alignment, tag.font, tag.color);
1564 // We need to move the rest of the text into the new line
1565 Add(line.line_no + 1, line.text.ToString(pos, line.text.Length - pos), line.alignment, tag.font, tag.color);
1567 // Now transfer our tags from this line to the next
1568 new_line = GetLine(line.line_no + 1);
1571 if ((tag.start - 1) == pos) {
1574 // We can simply break the chain and move the tag into the next line
1575 if (tag == line.tags) {
1576 new_tag = new LineTag(line, 1, 0);
1577 new_tag.font = tag.font;
1578 new_tag.color = tag.color;
1579 line.tags = new_tag;
1582 if (tag.previous != null) {
1583 tag.previous.next = null;
1585 new_line.tags = tag;
1586 tag.previous = null;
1587 tag.line = new_line;
1589 // Walk the list and correct the start location of the tags we just bumped into the next line
1590 shift = tag.start - 1;
1593 while (new_tag != null) {
1594 new_tag.start -= shift;
1595 new_tag.line = new_line;
1596 new_tag = new_tag.next;
1601 new_tag = new LineTag(new_line, 1, tag.start - 1 + tag.length - pos);
1602 new_tag.next = tag.next;
1603 new_tag.font = tag.font;
1604 new_tag.color = tag.color;
1605 new_line.tags = new_tag;
1606 if (new_tag.next != null) {
1607 new_tag.next.previous = new_tag;
1610 tag.length = pos - tag.start + 1;
1613 new_tag = new_tag.next;
1614 while (new_tag != null) {
1615 new_tag.start -= shift;
1616 new_tag.line = new_line;
1617 new_tag = new_tag.next;
1621 line.text.Remove(pos, line.text.Length - pos);
1624 // Adds a line of text, with given font.
1625 // Bumps any line at that line number that already exists down
1626 internal void Add(int LineNo, string Text, Font font, Brush color) {
1627 Add(LineNo, Text, HorizontalAlignment.Left, font, color);
1630 internal void Add(int LineNo, string Text, HorizontalAlignment align, Font font, Brush color) {
1635 if (LineNo<1 || Text == null) {
1637 throw new ArgumentNullException("LineNo", "Line numbers must be positive");
1639 throw new ArgumentNullException("Text", "Cannot insert NULL line");
1643 add = new Line(LineNo, Text, align, font, color);
1646 while (line != sentinel) {
1648 line_no = line.line_no;
1650 if (LineNo > line_no) {
1652 } else if (LineNo < line_no) {
1655 // Bump existing line numbers; walk all nodes to the right of this one and increment line_no
1656 IncrementLines(line.line_no);
1661 add.left = sentinel;
1662 add.right = sentinel;
1664 if (add.parent != null) {
1665 if (LineNo > add.parent.line_no) {
1666 add.parent.right = add;
1668 add.parent.left = add;
1675 RebalanceAfterAdd(add);
1680 internal virtual void Clear() {
1682 document = sentinel;
1685 public virtual object Clone() {
1688 clone = new Document(null);
1690 clone.lines = this.lines;
1691 clone.document = (Line)document.Clone();
1696 internal void Delete(int LineNo) {
1701 Delete(GetLine(LineNo));
1704 internal void Delete(Line line1) {
1705 Line line2;// = new Line();
1708 if ((line1.left == sentinel) || (line1.right == sentinel)) {
1711 line3 = line1.right;
1712 while (line3.left != sentinel) {
1717 if (line3.left != sentinel) {
1720 line2 = line3.right;
1723 line2.parent = line3.parent;
1724 if (line3.parent != null) {
1725 if(line3 == line3.parent.left) {
1726 line3.parent.left = line2;
1728 line3.parent.right = line2;
1734 if (line3 != line1) {
1737 line1.ascent = line3.ascent;
1738 line1.height = line3.height;
1739 line1.line_no = line3.line_no;
1740 line1.recalc = line3.recalc;
1741 line1.space = line3.space;
1742 line1.tags = line3.tags;
1743 line1.text = line3.text;
1744 line1.widths = line3.widths;
1748 while (tag != null) {
1754 if (line3.color == LineColor.Black)
1755 RebalanceAfterDelete(line2);
1759 last_found = sentinel;
1762 // Set our selection markers
1763 internal void Invalidate(Line start, int start_pos, Line end, int end_pos) {
1769 // figure out what's before what so the logic below is straightforward
1770 if (start.line_no < end.line_no) {
1776 } else if (start.line_no > end.line_no) {
1783 if (start_pos < end_pos) {
1797 owner.Invalidate(new Rectangle((int)l1.widths[p1] - viewport_x, l1.Y - viewport_y, (int)l2.widths[p2] - (int)l1.widths[p1], l1.height));
1801 // Three invalidates:
1802 // First line from start
1803 owner.Invalidate(new Rectangle((int)l1.widths[p1] - viewport_x, l1.Y - viewport_y, owner.Width, l1.height));
1806 if ((l1.line_no + 1) < l2.line_no) {
1809 y = GetLine(l1.line_no + 1).Y;
1810 owner.Invalidate(new Rectangle(0 - viewport_x, y - viewport_y, owner.Width, GetLine(l2.line_no - 1).Y - y - viewport_y));
1814 owner.Invalidate(new Rectangle((int)l1.widths[p1] - viewport_x, l1.Y - viewport_y, owner.Width, l1.height));
1819 // It's nothing short of pathetic to always invalidate the whole control
1820 // I will find time to finish the optimization and make it invalidate deltas only
1821 internal void SetSelectionToCaret(bool start) {
1823 selection_start.line = caret.line;
1824 selection_start.tag = caret.tag;
1825 selection_start.pos = caret.pos;
1827 // start always also selects end
1828 selection_end.line = caret.line;
1829 selection_end.tag = caret.tag;
1830 selection_end.pos = caret.pos;
1832 if (caret.line.line_no <= selection_end.line.line_no) {
1833 if ((caret.line != selection_end.line) || (caret.pos < selection_end.pos)) {
1834 selection_start.line = caret.line;
1835 selection_start.tag = caret.tag;
1836 selection_start.pos = caret.pos;
1838 selection_end.line = caret.line;
1839 selection_end.tag = caret.tag;
1840 selection_end.pos = caret.pos;
1843 selection_end.line = caret.line;
1844 selection_end.tag = caret.tag;
1845 selection_end.pos = caret.pos;
1850 if ((selection_start.line == selection_end.line) && (selection_start.pos == selection_end.pos)) {
1851 selection_visible = false;
1853 selection_visible = true;
1859 internal void SetSelection(Line start, int start_pos, Line end, int end_pos) {
1860 // Ok, this is ugly, bad and slow, but I don't have time right now to optimize it.
1861 if (selection_visible) {
1862 // Try to only invalidate what's changed so we don't redraw the whole thing
1863 if ((start != selection_start.line) || (start_pos != selection_start.pos) && ((end == selection_end.line) && (end_pos == selection_end.pos))) {
1864 Invalidate(start, start_pos, selection_start.line, selection_start.pos);
1865 } else if ((end != selection_end.line) || (end_pos != selection_end.pos) && ((start == selection_start.line) && (start_pos == selection_start.pos))) {
1866 Invalidate(end, end_pos, selection_end.line, selection_end.pos);
1868 // both start and end changed
1869 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
1872 selection_start.line = start;
1873 selection_start.pos = start_pos;
1874 if (start != null) {
1875 selection_start.tag = LineTag.FindTag(start, start_pos);
1878 selection_end.line = end;
1879 selection_end.pos = end_pos;
1881 selection_end.tag = LineTag.FindTag(end, end_pos);
1884 if (((start == end) && (start_pos == end_pos)) || start == null || end == null) {
1885 selection_visible = false;
1887 selection_visible = true;
1892 // Make sure that start is always before end
1893 private void FixupSelection() {
1894 if (selection_start.line.line_no > selection_end.line.line_no) {
1899 line = selection_start.line;
1900 tag = selection_start.tag;
1901 pos = selection_start.pos;
1903 selection_start.line = selection_end.line;
1904 selection_start.tag = selection_end.tag;
1905 selection_start.pos = selection_end.pos;
1907 selection_end.line = line;
1908 selection_end.tag = tag;
1909 selection_end.pos = pos;
1914 if ((selection_start.line == selection_end.line) && (selection_start.pos > selection_end.pos)) {
1917 pos = selection_start.pos;
1918 selection_start.pos = selection_end.pos;
1919 selection_end.pos = pos;
1920 Console.WriteLine("flipped: sel start: {0} end: {1}", selection_start.pos, selection_end.pos);
1927 internal void SetSelectionStart(Line start, int start_pos) {
1928 selection_start.line = start;
1929 selection_start.pos = start_pos;
1930 selection_start.tag = LineTag.FindTag(start, start_pos);
1934 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
1935 selection_visible = true;
1938 if (selection_visible) {
1939 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
1944 internal void SetSelectionEnd(Line end, int end_pos) {
1945 selection_end.line = end;
1946 selection_end.pos = end_pos;
1947 selection_end.tag = LineTag.FindTag(end, end_pos);
1951 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
1952 selection_visible = true;
1955 if (selection_visible) {
1956 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
1960 internal void SetSelection(Line start, int start_pos) {
1961 if (selection_visible) {
1962 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
1966 selection_start.line = start;
1967 selection_start.pos = start_pos;
1968 selection_start.tag = LineTag.FindTag(start, start_pos);
1970 selection_end.line = start;
1971 selection_end.tag = selection_start.tag;
1972 selection_end.pos = start_pos;
1974 selection_visible = false;
1977 internal void InvalidateSelectionArea() {
1981 // Return the current selection, as string
1982 internal string GetSelection() {
1983 // We return String.Empty if there is no selection
1984 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
1985 return string.Empty;
1988 if (!multiline || (selection_start.line == selection_end.line)) {
1989 return selection_start.line.text.ToString(selection_start.pos, selection_end.pos - selection_start.pos);
1996 sb = new StringBuilder();
1997 start = selection_start.line.line_no;
1998 end = selection_end.line.line_no;
2000 sb.Append(selection_start.line.text.ToString(selection_start.pos, selection_start.line.text.Length - selection_start.pos) + Environment.NewLine);
2002 if ((start + 1) < end) {
2003 for (i = start + 1; i < end; i++) {
2004 sb.Append(GetLine(i).text.ToString() + Environment.NewLine);
2008 sb.Append(selection_end.line.text.ToString(0, selection_end.pos));
2010 return sb.ToString();
2014 internal void ReplaceSelection(string s) {
2015 // The easiest is to break the lines where the selection tags are and delete those lines
2016 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
2017 // Nothing to delete, simply insert
2018 InsertString(selection_start.tag, selection_start.pos, s);
2021 if (!multiline || (selection_start.line == selection_end.line)) {
2022 DeleteChars(selection_start.tag, selection_start.pos, selection_end.pos - selection_start.pos);
2024 // The tag might have been removed, we need to recalc it
2025 selection_start.tag = selection_start.line.FindTag(selection_start.pos);
2027 InsertString(selection_start.tag, selection_start.pos, s);
2036 start = selection_start.line.line_no;
2037 end = selection_end.line.line_no;
2039 // Delete first line
2040 DeleteChars(selection_start.tag, selection_start.pos, selection_end.pos - selection_start.pos);
2044 for (i = end - 1; i >= start; i++) {
2050 DeleteChars(selection_end.line.tags, 0, selection_end.pos);
2052 ins = s.Split(new char[] {'\n'});
2054 for (int j = 0; j < ins.Length; j++) {
2055 if (ins[j].EndsWith("\r")) {
2056 ins[j] = ins[j].Substring(0, ins[j].Length - 1);
2060 insert_lines = ins.Length;
2062 // Bump the text at insertion point a line down if we're inserting more than one line
2063 if (insert_lines > 1) {
2064 Split(selection_start.line, selection_start.pos);
2066 // Reminder of start line is now in startline+1
2068 // if the last line does not end with a \n we will insert the last line in front of the just moved text
2069 if (s.EndsWith("\n")) {
2070 insert_lines--; // We don't want to insert the last line as part of the loop anymore
2072 InsertString(GetLine(selection_start.line.line_no + 1), 0, ins[insert_lines - 1]);
2076 // Insert the first line
2077 InsertString(selection_start.line, selection_start.pos, ins[0]);
2079 if (insert_lines > 1) {
2080 base_line = selection_start.line.line_no + 1;
2082 for (i = 1; i < insert_lines; i++) {
2083 Add(base_line + i, ins[i], selection_start.line.alignment, selection_start.tag.font, selection_start.tag.color);
2087 selection_end.line = selection_start.line;
2088 selection_end.pos = selection_start.pos;
2089 selection_end.tag = selection_start.tag;
2090 selection_visible = false;
2091 InvalidateSelectionArea();
2094 internal void CharIndexToLineTag(int index, out Line line_out, out LineTag tag_out, out int pos) {
2103 for (i = 1; i < lines; i++) {
2107 chars += line.text.Length + 2; // We count the trailing \n as a char
2109 if (index <= chars) {
2110 // we found the line
2113 while (tag != null) {
2114 if (index < (start + tag.start + tag.length)) {
2117 pos = index - start;
2120 if (tag.next == null) {
2123 next_line = GetLine(line.line_no + 1);
2125 if (next_line != null) {
2126 line_out = next_line;
2127 tag_out = next_line.tags;
2133 pos = line_out.text.Length;
2142 line_out = GetLine(lines);
2143 tag = line_out.tags;
2144 while (tag.next != null) {
2148 pos = line_out.text.Length;
2151 internal int LineTagToCharIndex(Line line, int pos) {
2155 // Count first and last line
2158 // Count the lines in the middle
2160 for (i = 1; i < line.line_no; i++) {
2161 length += GetLine(i).text.Length + 2; // Add one for the \n at the end of the line
2169 internal int SelectionLength() {
2170 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
2174 if (!multiline || (selection_start.line == selection_end.line)) {
2175 return selection_end.pos - selection_start.pos;
2182 // Count first and last line
2183 length = selection_start.line.text.Length - selection_start.pos + selection_end.pos;
2185 // Count the lines in the middle
2186 start = selection_start.line.line_no + 1;
2187 end = selection_end.line.line_no;
2190 for (i = start; i < end; i++) {
2191 length += GetLine(i).text.Length;
2202 // Give it a Line number and it returns the Line object at with that line number
2203 internal Line GetLine(int LineNo) {
2204 Line line = document;
2206 while (line != sentinel) {
2207 if (LineNo == line.line_no) {
2209 } else if (LineNo < line.line_no) {
2219 // Give it a Y pixel coordinate and it returns the Line covering that Y coordinate
2221 internal Line GetLineByPixel(int y, bool exact) {
2222 Line line = document;
2225 while (line != sentinel) {
2227 if ((y >= line.Y) && (y < (line.Y+line.height))) {
2229 } else if (y < line.Y) {
2242 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
2243 internal LineTag FindTag(int x, int y, out int index, bool exact) {
2247 line = GetLineByPixel(y, exact);
2254 // Alignment adjustment
2255 x += line.align_shift;
2258 if (x >= tag.X && x < (tag.X+tag.width)) {
2261 end = tag.start + tag.length - 1;
2263 for (int pos = tag.start; pos < end; pos++) {
2264 if (x < line.widths[pos]) {
2272 if (tag.next != null) {
2280 index = line.text.Length;
2286 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
2287 internal LineTag FindCursor(int x, int y, out int index) {
2291 line = GetLineByPixel(y, false);
2294 // Adjust for alignment
2295 x += line.align_shift;
2298 if (x >= tag.X && x < (tag.X+tag.width)) {
2301 end = tag.start + tag.length - 1;
2303 for (int pos = tag.start-1; pos < end; pos++) {
2304 // When clicking on a character, we position the cursor to whatever edge
2305 // of the character the click was closer
2306 if (x < (line.widths[pos] + ((line.widths[pos+1]-line.widths[pos])/2))) {
2314 if (tag.next != null) {
2317 index = line.text.Length;
2323 internal void RecalculateAlignments() {
2329 while (line_no <= lines) {
2330 line = GetLine(line_no);
2332 if (line.alignment != HorizontalAlignment.Left) {
2333 if (line.alignment == HorizontalAlignment.Center) {
2334 line.align_shift = (document_x - (int)line.widths[line.text.Length]) / 2;
2336 line.align_shift = document_x - (int)line.widths[line.text.Length];
2345 // Calculate formatting for the whole document
2346 internal bool RecalculateDocument(Graphics g) {
2347 return RecalculateDocument(g, 1, this.lines, false);
2350 // Calculate formatting starting at a certain line
2351 internal bool RecalculateDocument(Graphics g, int start) {
2352 return RecalculateDocument(g, start, this.lines, false);
2355 // Calculate formatting within two given line numbers
2356 internal bool RecalculateDocument(Graphics g, int start, int end) {
2357 return RecalculateDocument(g, start, end, false);
2360 // With optimize on, returns true if line heights changed
2361 internal bool RecalculateDocument(Graphics g, int start, int end, bool optimize) {
2366 Y = GetLine(start).Y;
2370 bool alignment_recalc;
2373 alignment_recalc = false;
2375 while (line_no <= end) {
2376 line = GetLine(line_no++);
2379 if (line.RecalculateLine(g)) {
2381 // If the height changed, all subsequent lines change
2385 if (line.widths[line.text.Length] > this.document_x) {
2386 this.document_x = (int)line.widths[line.text.Length];
2387 alignment_recalc = true;
2390 // Calculate alignment
2391 if (line.alignment != HorizontalAlignment.Left) {
2392 if (line.alignment == HorizontalAlignment.Center) {
2393 line.align_shift = (document_x - (int)line.widths[line.text.Length]) / 2;
2395 line.align_shift = document_x - (int)line.widths[line.text.Length];
2403 if (alignment_recalc) {
2404 RecalculateAlignments();
2409 while (line_no <= end) {
2410 line = GetLine(line_no++);
2412 line.RecalculateLine(g);
2413 if (line.widths[line.text.Length] > this.document_x) {
2414 this.document_x = (int)line.widths[line.text.Length];
2417 // Calculate alignment
2418 if (line.alignment != HorizontalAlignment.Left) {
2419 if (line.alignment == HorizontalAlignment.Center) {
2420 line.align_shift = (document_x - (int)line.widths[line.text.Length]) / 2;
2422 line.align_shift = document_x - (int)line.widths[line.text.Length];
2428 RecalculateAlignments();
2433 internal bool SetCursor(int x, int y) {
2437 internal int Size() {
2440 #endregion // Internal Methods
2442 #region Administrative
2443 public IEnumerator GetEnumerator() {
2448 public override bool Equals(object obj) {
2453 if (!(obj is Document)) {
2461 if (ToString().Equals(((Document)obj).ToString())) {
2468 public override int GetHashCode() {
2472 public override string ToString() {
2473 return "document " + this.document_id;
2476 #endregion // Administrative
2479 internal class LineTag {
2480 #region Local Variables;
2481 // Payload; formatting
2482 internal Font font; // System.Drawing.Font object for this tag
2483 internal Brush color; // System.Drawing.Brush object
2486 internal int start; // start, in chars; index into Line.text
2487 internal int length; // length, in chars
2488 internal bool r_to_l; // Which way is the font
2491 internal int height; // Height in pixels of the text this tag describes
2492 internal int X; // X location of the text this tag describes
2493 internal float width; // Width in pixels of the text this tag describes
2494 internal int ascent; // Ascent of the font for this tag
2495 internal int shift; // Shift down for this tag, to stay on baseline
2497 internal int soft_break; // Tag is 'broken soft' and continues in the next line
2500 internal Line line; // The line we're on
2501 internal LineTag next; // Next tag on the same line
2502 internal LineTag previous; // Previous tag on the same line
2505 #region Constructors
2506 internal LineTag(Line line, int start, int length) {
2509 this.length = length;
2513 #endregion // Constructors
2515 #region Internal Methods
2517 // Applies 'font' to characters starting at 'start' for 'length' chars
2518 // Removes any previous tags overlapping the same area
2519 // returns true if lineheight has changed
2521 internal static bool FormatText(Line line, int start, int length, Font font, Brush color) {
2525 bool retval = false; // Assume line-height doesn't change
2528 if (font.Height != line.height) {
2531 line.recalc = true; // This forces recalculation of the line in RecalculateDocument
2533 // A little sanity, not sure if it's needed, might be able to remove for speed
2534 if (length > line.text.Length) {
2535 length = line.text.Length;
2539 end = start + length;
2541 // Common special case
2542 if ((start == 1) && (length == tag.length)) {
2549 start_tag = FindTag(line, start);
2551 tag = new LineTag(line, start, length);
2559 if (start_tag.start == start) {
2560 tag.next = start_tag;
2561 tag.previous = start_tag.previous;
2562 if (start_tag.previous != null) {
2563 start_tag.previous.next = tag;
2565 start_tag.previous = tag;
2567 // Insert ourselves 'in the middle'
2568 if ((start_tag.next != null) && (start_tag.next.start < end)) {
2569 tag.next = start_tag.next;
2571 tag.next = new LineTag(line, start_tag.start, start_tag.length);
2572 tag.next.font = start_tag.font;
2573 tag.next.color = start_tag.color;
2575 if (start_tag.next != null) {
2576 tag.next.next = start_tag.next;
2577 tag.next.next.previous = tag.next;
2580 tag.next.previous = tag;
2582 start_tag.length = start - start_tag.start;
2584 tag.previous = start_tag;
2585 start_tag.next = tag;
2587 if (tag.next.start > (tag.start + tag.length)) {
2588 tag.next.length += tag.next.start - (tag.start + tag.length);
2589 tag.next.start = tag.start + tag.length;
2596 while ((tag != null) && (tag.start < end)) {
2597 if ((tag.start + tag.length) <= end) {
2599 tag.previous.next = tag.next;
2600 if (tag.next != null) {
2601 tag.next.previous = tag.previous;
2605 // Adjust the length of the tag
2606 tag.length = (tag.start + tag.length) - end;
2617 // Finds the tag that describes the character at position 'pos' on 'line'
2619 internal static LineTag FindTag(Line line, int pos) {
2620 LineTag tag = line.tags;
2622 // Beginning of line is a bit special
2627 while (tag != null) {
2628 if ((tag.start <= pos) && (pos < (tag.start+tag.length))) {
2639 // Combines 'this' tag with 'other' tag.
2641 internal bool Combine(LineTag other) {
2642 if (!this.Equals(other)) {
2646 this.width += other.width;
2647 this.length += other.length;
2648 this.next = other.next;
2649 if (this.next != null) {
2650 this.next.previous = this;
2658 // Remove 'this' tag ; to be called when formatting is to be removed
2660 internal bool Remove() {
2661 if ((this.start == 1) && (this.next == null)) {
2662 // We cannot remove the only tag
2665 if (this.start != 1) {
2666 this.previous.length += this.length;
2667 this.previous.width = -1;
2668 this.previous.next = this.next;
2669 this.next.previous = this.previous;
2671 this.next.start = 1;
2672 this.next.length += this.length;
2673 this.next.width = -1;
2674 this.line.tags = this.next;
2675 this.next.previous = null;
2682 // Checks if 'this' tag describes the same formatting options as 'obj'
2684 public override bool Equals(object obj) {
2691 if (!(obj is LineTag)) {
2699 other = (LineTag)obj;
2701 if (this.font.Equals(other.font) && this.color.Equals(other.color)) { // FIXME add checking for things like link or type later
2708 public override int GetHashCode() {
\r
2709 return base.GetHashCode ();
\r
2712 public override string ToString() {
2713 return "Tag starts at index " + this.start + "length " + this.length + " text: " + this.line.Text.Substring(this.start-1, this.length) + "Font " + this.font.ToString();
2716 #endregion // Internal Methods