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
30 using System.Collections;
32 using System.Drawing.Text;
35 namespace System.Windows.Forms {
36 public enum LineColor {
41 public enum CaretDirection {
42 CharForward, // Move a char to the right
43 CharBack, // Move a char to the left
44 LineUp, // Move a line up
45 LineDown, // Move a line down
46 Home, // Move to the beginning of the line
47 End, // Move to the end of the line
48 PgUp, // Move one page up
49 PgDn, // Move one page down
50 CtrlHome, // Move to the beginning of the document
51 CtrlEnd, // Move to the end of the document
52 WordBack, // Move to the beginning of the previous word (or beginning of line)
53 WordForward // Move to the beginning of the next word (or end of line)
56 // Being cloneable should allow for nice line and document copies...
57 public class Line : ICloneable, IComparable {
58 #region Local Variables
59 // Stuff that matters for our line
60 internal StringBuilder text; // Characters for the line
61 internal float[] widths; // Width of each character; always one larger than text.Length
62 internal int space; // Number of elements in text and widths
63 internal int line_no; // Line number
64 internal LineTag tags; // Tags describing the text
65 internal int Y; // Baseline
66 internal int height; // Height of the line (height of tallest tag)
67 internal int ascent; // Ascent of the line (ascent of the tallest tag)
69 // Stuff that's important for the tree
70 internal Line parent; // Our parent line
71 public Line left; // Line with smaller line number
72 public Line right; // Line with higher line number
73 internal LineColor color; // We're doing a black/red tree. this is the node color
74 internal int DEFAULT_TEXT_LEN; //
75 internal static StringFormat string_format; // For calculating widths/heights
76 internal bool recalc; // Line changed
77 #endregion // Local Variables
81 color = LineColor.Red;
88 if (string_format == null) {
89 string_format = new StringFormat(StringFormat.GenericTypographic);
90 string_format.Trimming = StringTrimming.None;
91 string_format.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;
95 public Line(int LineNo, string Text, Font font) : this() {
96 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
98 text = new StringBuilder(Text, space);
101 widths = new float[space + 1];
102 tags = new LineTag(this, 1, text.Length);
106 public Line(int LineNo, string Text, LineTag tag) : this() {
107 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
109 text = new StringBuilder(Text, space);
112 widths = new float[space + 1];
116 #endregion // Constructors
118 #region Public Properties
141 return text.ToString();
145 text = new StringBuilder(value, value.Length > DEFAULT_TEXT_LEN ? value.Length : DEFAULT_TEXT_LEN);
149 public StringBuilder Text {
159 #endregion // Public Properties
161 #region Public Methods
162 // Make sure we always have enoughs space in text and widths
163 public void Grow(int minimum) {
167 length = text.Length;
169 if ((length + minimum) > space) {
170 // We need to grow; double the size
172 if ((length + minimum) > (space * 2)) {
173 new_widths = new float[length + minimum * 2 + 1];
174 space = length + minimum * 2;
176 new_widths = new float[space * 2 + 1];
179 widths.CopyTo(new_widths, 0);
185 public void Streamline() {
196 while (next != null) {
197 // Take out 0 length tags
198 if (next.length == 0) {
199 current.next = next.next;
200 if (current.next != null) {
201 current.next.previous = current;
207 if (current.Combine(next)) {
212 current = current.next;
217 // Find the tag on a line based on the character position
218 public LineTag FindTag(int pos) {
227 if (pos > text.Length) {
231 while (tag != null) {
232 if ((tag.start <= pos) && (pos < (tag.start + tag.length))) {
242 // Go through all tags on a line and recalculate all size-related values
243 // returns true if lineheight changed
245 public bool RecalculateLine(Graphics g) {
254 len = this.text.Length;
256 prev_height = this.height; // For drawing optimization calculations
257 this.height = 0; // Reset line height
258 this.ascent = 0; // Reset the ascent for the line
264 size = g.MeasureString(this.text.ToString(pos, 1), tag.font, 10000, string_format);
272 widths[pos] = widths[pos-1] + w;
274 if (pos == (tag.start-1 + tag.length)) {
275 // We just found the end of our current tag
276 tag.height = (int)tag.font.Height;
277 // Check if we're the tallest on the line (so far)
278 if (tag.height > this.height) {
279 this.height = tag.height; // Yep; make sure the line knows
282 if (tag.ascent == 0) {
285 XplatUI.GetFontMetrics(g, tag.font, out tag.ascent, out descent);
288 if (tag.ascent > this.ascent) {
291 // We have a tag that has a taller ascent than the line;
294 t.shift = tag.ascent - this.ascent;
299 this.ascent = tag.ascent;
301 tag.shift = this.ascent - tag.ascent;
304 // Update our horizontal starting pixel position
305 if (tag.previous == null) {
308 tag.X = tag.previous.X + (int)tag.previous.width;
318 if (this.height == 0) {
319 this.height = tags.font.Height;
320 tag.height = this.height;
323 if (prev_height != this.height) {
328 #endregion // Public Methods
330 #region Administrative
331 public int CompareTo(object obj) {
336 if (! (obj is Line)) {
337 throw new ArgumentException("Object is not of type Line", "obj");
340 if (line_no < ((Line)obj).line_no) {
342 } else if (line_no > ((Line)obj).line_no) {
349 public object Clone() {
357 clone.left = (Line)left.Clone();
361 clone.left = (Line)left.Clone();
367 public object CloneLine() {
377 public override bool Equals(object obj) {
382 if (!(obj is Line)) {
390 if (line_no == ((Line)obj).line_no) {
398 public override string ToString() {
399 return "Line " + line_no;
402 #endregion // Administrative
405 public class Document : ICloneable, IEnumerable {
407 internal struct Marker {
409 internal LineTag tag;
413 #endregion Structures
415 #region Local Variables
416 private Line document;
418 private static Line sentinel;
419 private Line last_found;
420 private int document_id;
421 private Random random = new Random();
423 internal bool multiline;
425 private Line selection_start_line;
426 private int selection_start_pos;
427 private Line selection_end_line;
428 private int selection_end_pos;
430 internal Marker caret;
431 internal Marker selection_start;
432 internal Marker selection_end;
434 internal int viewport_x;
435 internal int viewport_y; // The visible area of the document
437 internal int document_x; // Width of the document
438 internal int document_y; // Height of the document
440 internal Control owner; // Who's owning us?
441 #endregion // Local Variables
444 public Document(Control owner) {
451 // Tree related stuff
452 sentinel = new Line();
453 sentinel.color = LineColor.Black;
456 last_found = sentinel;
458 // We always have a blank line
459 Add(1, "", owner.Font);
460 this.RecalculateDocument(owner.CreateGraphics());
464 // Default selection is empty
466 document_id = random.Next();
470 #region Public Properties
487 public Line CaretLine {
489 return caret.tag.line;
493 public int CaretPosition {
499 public LineTag CaretTag {
505 public int ViewPortX {
515 public int ViewPortY {
525 #endregion // Public Properties
527 #region Private Methods
529 internal void DumpTree(Line line, bool with_tags) {
530 Console.Write("Line {0}, Y: {1} Text {2}", line.line_no, line.Y, line.text.ToString());
532 if (line.left == sentinel) {
533 Console.Write(", left = sentinel");
534 } else if (line.left == null) {
535 Console.Write(", left = NULL");
538 if (line.right == sentinel) {
539 Console.Write(", right = sentinel");
540 } else if (line.right == null) {
541 Console.Write(", right = NULL");
544 Console.WriteLine("");
552 Console.Write(" Tags: ");
553 while (tag != null) {
554 Console.Write("{0} <{1}>-<{2}> ", count++, tag.start, tag.length);
555 if (tag.line != line) {
556 Console.Write("BAD line link");
557 throw new Exception("Bad line link in tree");
564 Console.WriteLine("");
566 if (line.left != null) {
567 if (line.left != sentinel) {
568 DumpTree(line.left, with_tags);
572 if (line.right != null) {
573 if (line.right != sentinel) {
574 DumpTree(line.right, with_tags);
579 private void DecrementLines(int line_no) {
583 while (current <= lines) {
584 GetLine(current).line_no--;
590 private void IncrementLines(int line_no) {
593 current = this.lines;
594 while (current >= line_no) {
595 GetLine(current).line_no++;
601 private void RebalanceAfterAdd(Line line1) {
604 while ((line1 != document) && (line1.parent.color == LineColor.Red)) {
605 if (line1.parent == line1.parent.parent.left) {
606 line2 = line1.parent.parent.right;
608 if ((line2 != null) && (line1.color == LineColor.Red)) {
609 line1.parent.color = LineColor.Black;
610 line2.color = LineColor.Black;
611 line1.parent.parent.color = LineColor.Red;
612 line1 = line1.parent.parent;
614 if (line1 == line1.parent.right) {
615 line1 = line1.parent;
619 line1.parent.color = LineColor.Black;
620 line1.parent.parent.color = LineColor.Red;
622 RotateRight(line1.parent.parent);
625 line2 = line1.parent.parent.left;
627 if ((line2 != null) && (line2.color == LineColor.Red)) {
628 line1.parent.color = LineColor.Black;
629 line2.color = LineColor.Black;
630 line1.parent.parent.color = LineColor.Red;
631 line1 = line1.parent.parent;
633 if (line1 == line1.parent.left) {
634 line1 = line1.parent;
638 line1.parent.color = LineColor.Black;
639 line1.parent.parent.color = LineColor.Red;
640 RotateLeft(line1.parent.parent);
644 document.color = LineColor.Black;
647 private void RebalanceAfterDelete(Line line1) {
650 while ((line1 != document) && (line1.color == LineColor.Black)) {
651 if (line1 == line1.parent.left) {
652 line2 = line1.parent.right;
653 if (line2.color == LineColor.Red) {
654 line2.color = LineColor.Black;
655 line1.parent.color = LineColor.Red;
656 RotateLeft(line1.parent);
657 line2 = line1.parent.right;
659 if ((line2.left.color == LineColor.Black) && (line2.right.color == LineColor.Black)) {
660 line2.color = LineColor.Red;
661 line1 = line1.parent;
663 if (line2.right.color == LineColor.Red) {
664 line2.left.color = LineColor.Black;
665 line2.color = LineColor.Red;
667 line2 = line1.parent.right;
669 line2.color = line1.parent.color;
670 line1.parent.color = LineColor.Black;
671 line2.right.color = LineColor.Black;
672 RotateLeft(line1.parent);
676 line2 = line1.parent.left;
677 if (line2.color == LineColor.Red) {
678 line2.color = LineColor.Black;
679 line1.parent.color = LineColor.Red;
680 RotateRight(line1.parent);
681 line2 = line1.parent.left;
683 if ((line2.right.color == LineColor.Black) && (line2.left.color == LineColor.Black)) {
684 line2.color = LineColor.Red;
685 line1 = line1.parent;
687 if (line2.left.color == LineColor.Black) {
688 line2.right.color = LineColor.Black;
689 line2.color = LineColor.Red;
691 line2 = line1.parent.left;
693 line2.color = line1.parent.color;
694 line1.parent.color = LineColor.Black;
695 line2.left.color = LineColor.Black;
696 RotateRight(line1.parent);
701 line1.color = LineColor.Black;
704 private void RotateLeft(Line line1) {
705 Line line2 = line1.right;
707 line1.right = line2.left;
709 if (line2.left != sentinel) {
710 line2.left.parent = line1;
713 if (line2 != sentinel) {
714 line2.parent = line1.parent;
717 if (line1.parent != null) {
718 if (line1 == line1.parent.left) {
719 line1.parent.left = line2;
721 line1.parent.right = line2;
728 if (line1 != sentinel) {
729 line1.parent = line2;
733 private void RotateRight(Line line1) {
734 Line line2 = line1.left;
736 line1.left = line2.right;
738 if (line2.right != sentinel) {
739 line2.right.parent = line1;
742 if (line2 != sentinel) {
743 line2.parent = line1.parent;
746 if (line1.parent != null) {
747 if (line1 == line1.parent.right) {
748 line1.parent.right = line2;
750 line1.parent.left = line2;
757 if (line1 != sentinel) {
758 line1.parent = line2;
763 public void UpdateView(Line line, int pos) {
766 // This is an optimization; we need to invalidate
767 prev_width = (int)line.widths[line.text.Length];
769 if (RecalculateDocument(owner.CreateGraphics(), line.line_no, line.line_no, true)) {
770 // Lineheight changed, invalidate the rest of the document
771 if ((line.Y - viewport_y) >=0 ) {
772 // We formatted something that's in view, only draw parts of the screen
773 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, owner.Width, owner.Height - line.Y - viewport_y));
775 // The tag was above the visible area, draw everything
779 owner.Invalidate(new Rectangle((int)line.widths[pos] - viewport_x, line.Y - viewport_y, (int)owner.Width, line.height));
784 // Update display from line, down line_count lines; pos is unused, but required for the signature
785 public void UpdateView(Line line, int line_count, int pos) {
788 // This is an optimization; we need to invalidate
789 prev_width = (int)line.widths[line.text.Length];
791 if (RecalculateDocument(owner.CreateGraphics(), line.line_no, line.line_no + line_count - 1, true)) {
792 // Lineheight changed, invalidate the rest of the document
793 if ((line.Y - viewport_y) >=0 ) {
794 // We formatted something that's in view, only draw parts of the screen
795 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, owner.Width, owner.Height - line.Y - viewport_y));
797 // The tag was above the visible area, draw everything
803 end_line = GetLine(line.line_no + line_count -1);
804 if (end_line == null) {
808 owner.Invalidate(new Rectangle(0 - viewport_x, line.Y - viewport_y, (int)line.widths[line.text.Length], end_line.Y + end_line.height));
811 #endregion // Private Methods
813 #region Public Methods
814 public void PositionCaret(Line line, int pos) {
815 caret.tag = line.FindTag(pos);
818 caret.height = caret.tag.height;
820 XplatUI.DestroyCaret(owner.Handle);
821 XplatUI.CreateCaret(owner.Handle, 2, caret.height);
822 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos], caret.tag.line.Y + caret.tag.shift);
825 public void PositionCaret(int x, int y) {
826 caret.tag = FindCursor(x, y, out caret.pos);
827 caret.line = caret.tag.line;
828 caret.height = caret.tag.height;
830 XplatUI.DestroyCaret(owner.Handle);
831 XplatUI.CreateCaret(owner.Handle, 2, caret.height);
832 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos], caret.tag.line.Y + caret.tag.shift);
835 public void CaretHasFocus() {
836 if (caret.tag != null) {
837 XplatUI.CreateCaret(owner.Handle, 2, caret.height);
838 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos], caret.tag.line.Y + caret.tag.shift);
839 XplatUI.CaretVisible(owner.Handle, true);
843 public void CaretLostFocus() {
844 XplatUI.DestroyCaret(owner.Handle);
847 public void AlignCaret() {
848 caret.tag = LineTag.FindTag(caret.line, caret.pos);
849 caret.height = caret.tag.height;
851 XplatUI.CreateCaret(owner.Handle, 2, caret.height);
852 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos], caret.tag.line.Y + caret.tag.shift);
853 XplatUI.CaretVisible(owner.Handle, true);
856 public void UpdateCaret() {
857 if (caret.tag.height != caret.height) {
858 caret.height = caret.tag.height;
859 XplatUI.CreateCaret(owner.Handle, 2, caret.height);
861 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos], caret.tag.line.Y + caret.tag.shift);
862 XplatUI.CaretVisible(owner.Handle, true);
865 public void DisplayCaret() {
866 XplatUI.CaretVisible(owner.Handle, true);
869 public void HideCaret() {
870 XplatUI.CaretVisible(owner.Handle, false);
873 public void MoveCaret(CaretDirection direction) {
875 case CaretDirection.CharForward: {
877 if (caret.pos > caret.line.text.Length) {
880 if (caret.line.line_no < this.lines) {
881 caret.line = GetLine(caret.line.line_no+1);
883 caret.tag = caret.line.tags;
888 // Single line; we stay where we are
892 if ((caret.tag.start - 1 + caret.tag.length) < caret.pos) {
893 caret.tag = caret.tag.next;
900 case CaretDirection.CharBack: {
902 // caret.pos--; // folded into the if below
903 if (--caret.pos > 0) {
904 if (caret.tag.start > caret.pos) {
905 caret.tag = caret.tag.previous;
909 if (caret.line.line_no > 1) {
910 caret.line = GetLine(caret.line.line_no - 1);
911 caret.pos = caret.line.text.Length;
912 caret.tag = LineTag.FindTag(caret.line, caret.pos);
919 case CaretDirection.WordForward: {
922 len = caret.line.text.Length;
923 if (caret.pos < len) {
924 while ((caret.pos < len) && (caret.line.text.ToString(caret.pos, 1) != " ")) {
927 if (caret.pos < len) {
928 // Skip any whitespace
929 while ((caret.pos < len) && (caret.line.text.ToString(caret.pos, 1) == " ")) {
934 if (caret.line.line_no < this.lines) {
935 caret.line = GetLine(caret.line.line_no+1);
937 caret.tag = caret.line.tags;
944 case CaretDirection.WordBack: {
948 len = caret.line.text.Length;
952 while ((caret.pos > 0) && (caret.line.text.ToString(caret.pos, 1) == " ")) {
956 while ((caret.pos > 0) && (caret.line.text.ToString(caret.pos, 1) != " ")) {
960 if (caret.line.text.ToString(caret.pos, 1) == " ") {
961 if (caret.pos != 0) {
964 caret.line = GetLine(caret.line.line_no - 1);
965 caret.pos = caret.line.text.Length;
966 caret.tag = LineTag.FindTag(caret.line, caret.pos);
970 if (caret.line.line_no > 1) {
971 caret.line = GetLine(caret.line.line_no - 1);
972 caret.pos = caret.line.text.Length;
973 caret.tag = LineTag.FindTag(caret.line, caret.pos);
980 case CaretDirection.LineUp: {
981 if (caret.line.line_no > 1) {
984 pixel = (int)caret.line.widths[caret.pos];
985 PositionCaret(pixel, GetLine(caret.line.line_no - 1).Y);
986 XplatUI.CaretVisible(owner.Handle, true);
991 case CaretDirection.LineDown: {
992 if (caret.line.line_no < lines) {
995 pixel = (int)caret.line.widths[caret.pos];
996 PositionCaret(pixel, GetLine(caret.line.line_no + 1).Y);
997 XplatUI.CaretVisible(owner.Handle, true);
1002 case CaretDirection.Home: {
1003 if (caret.pos > 0) {
1005 caret.tag = caret.line.tags;
1011 case CaretDirection.End: {
1012 if (caret.pos < caret.line.text.Length) {
1013 caret.pos = caret.line.text.Length;
1014 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1020 case CaretDirection.PgUp: {
1024 case CaretDirection.PgDn: {
1028 case CaretDirection.CtrlHome: {
1029 caret.line = GetLine(1);
1031 caret.tag = caret.line.tags;
1037 case CaretDirection.CtrlEnd: {
1038 caret.line = GetLine(lines);
1040 caret.tag = caret.line.tags;
1048 // Draw the document
1049 public void Draw(Graphics g, Rectangle clip, Brush brush) {
1050 Line line; // Current line being drawn
1051 LineTag tag; // Current tag being drawn
1052 int start; // First line to draw
1053 int end; // Last line to draw
1054 string s; // String representing the current line
1057 // First, figure out from what line to what line we need to draw
1058 start = GetLineByPixel(clip.Top - viewport_y, false).line_no;
1059 end = GetLineByPixel(clip.Bottom - viewport_y, false).line_no;
1061 // Now draw our elements; try to only draw those that are visible
1065 DateTime n = DateTime.Now;
1066 Console.WriteLine("Started drawing: {0}s {1}ms", n.Second, n.Millisecond);
1069 while (line_no <= end) {
1070 line = GetLine(line_no);
1072 s = line.text.ToString();
1073 while (tag != null) {
1074 if (((tag.X + tag.width) > (clip.Left - viewport_x)) || (tag.X < (clip.Right - viewport_x))) {
1075 g.DrawString(s.Substring(tag.start-1, tag.length), tag.font, brush, tag.X - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
1085 Console.WriteLine("Finished drawing: {0}s {1}ms", n.Second, n.Millisecond);
1091 // Inserts a character at the given position
1092 public void InsertChar(Line line, int pos, char ch) {
1093 InsertChar(line.FindTag(pos), pos, ch);
1096 // Inserts a character at the given position
1097 public void InsertChar(LineTag tag, int pos, char ch) {
1101 line.text.Insert(pos, ch);
1105 while (tag != null) {
1112 UpdateView(line, pos);
1115 // Inserts a character at the given position
1116 public void InsertCharAtCaret(char ch, bool move_caret) {
1117 caret.line.text.Insert(caret.pos, ch);
1119 if (caret.tag.next != null) {
1120 caret.tag.next.start++;
1123 caret.line.recalc = true;
1125 UpdateView(caret.line, caret.pos);
1133 // Inserts a character at the given position; it will not delete past line limits
1134 public void DeleteChar(LineTag tag, int pos, bool forward) {
1142 if ((pos == 0 && forward == false) || (pos == line.text.Length && forward == true)) {
1147 line.text.Remove(pos, 1);
1150 if (tag.length == 0) {
1155 line.text.Remove(pos, 1);
1156 if (pos >= (tag.start - 1)) {
1158 if (tag.length == 0) {
1161 } else if (tag.previous != null) {
1162 tag.previous.length--;
1163 if (tag.previous.length == 0) {
1170 while (tag != null) {
1177 UpdateView(line, pos);
1180 // Combine two lines
1181 public void Combine(int FirstLine, int SecondLine) {
1182 Combine(GetLine(FirstLine), GetLine(SecondLine));
1185 public void Combine(Line first, Line second) {
1189 // Combine the two tag chains into one
1192 while (last.next != null) {
1196 last.next = second.tags;
1197 last.next.previous = last;
1199 shift = last.start + last.length - 1;
1201 // Fix up references within the chain
1203 while (last != null) {
1205 last.start += shift;
1209 // Combine both lines' strings
1210 first.text.Insert(first.text.Length, second.text.ToString());
1211 first.Grow(first.text.Length);
1213 // Remove the reference to our (now combined) tags from the doomed line
1217 DecrementLines(first.line_no + 2); // first.line_no + 1 will be deleted, so we need to start renumbering one later
1220 first.recalc = true;
1221 first.height = 0; // This forces RecalcDocument/UpdateView to redraw from this line on
1224 this.Delete(second);
1228 // Split the line at the position into two
1229 public void Split(int LineNo, int pos) {
1233 line = GetLine(LineNo);
1234 tag = LineTag.FindTag(line, pos);
1235 Split(line, tag, pos);
1238 public void Split(Line line, int pos) {
1241 tag = LineTag.FindTag(line, pos);
1242 Split(line, tag, pos);
1245 public void Split(Line line, LineTag tag, int pos) {
1249 // cover the easy case first
1250 if (pos == line.text.Length) {
1251 Add(line.line_no + 1, "", tag.font);
1255 // We need to move the rest of the text into the new line
1256 Add(line.line_no + 1, line.text.ToString(pos, line.text.Length - pos), tag.font);
1258 // Now transfer our tags from this line to the next
1259 new_line = GetLine(line.line_no + 1);
1262 if ((tag.start - 1) == pos) {
1265 // We can simply break the chain and move the tag into the next line
1266 if (tag == line.tags) {
1267 new_tag = new LineTag(line, 1, 0);
1268 new_tag.font = tag.font;
1269 line.tags = new_tag;
1272 if (tag.previous != null) {
1273 tag.previous.next = null;
1275 new_line.tags = tag;
1276 tag.previous = null;
1277 tag.line = new_line;
1279 // Walk the list and correct the start location of the tags we just bumped into the next line
1280 shift = tag.start - 1;
1283 while (new_tag != null) {
1284 new_tag.start -= shift;
1285 new_tag.line = new_line;
1286 new_tag = new_tag.next;
1291 new_tag = new LineTag(new_line, 1, tag.start - 1 + tag.length - pos);
1292 new_tag.next = tag.next;
1293 new_tag.font = tag.font;
1294 new_line.tags = new_tag;
1295 if (new_tag.next != null) {
1296 new_tag.next.previous = new_tag;
1299 tag.length = pos - tag.start + 1;
1302 new_tag = new_tag.next;
1303 while (new_tag != null) {
1304 new_tag.start -= shift;
1305 new_tag.line = new_line;
1306 new_tag = new_tag.next;
1310 line.text.Remove(pos, line.text.Length - pos);
1313 // Adds a line of text, with given font.
1314 // Bumps any line at that line number that already exists down
1315 public void Add(int LineNo, string Text, Font font) {
1320 if (LineNo<1 || Text == null) {
1322 throw new ArgumentNullException("LineNo", "Line numbers must be positive");
1324 throw new ArgumentNullException("Text", "Cannot insert NULL line");
1328 add = new Line(LineNo, Text, font);
1331 while (line != sentinel) {
1333 line_no = line.line_no;
1335 if (LineNo > line_no) {
1337 } else if (LineNo < line_no) {
1340 // Bump existing line numbers; walk all nodes to the right of this one and increment line_no
1341 IncrementLines(line.line_no);
1346 add.left = sentinel;
1347 add.right = sentinel;
1349 if (add.parent != null) {
1350 if (LineNo > add.parent.line_no) {
1351 add.parent.right = add;
1353 add.parent.left = add;
1360 RebalanceAfterAdd(add);
1365 public virtual void Clear() {
1367 document = sentinel;
1370 public virtual object Clone() {
1373 clone = new Document(null);
1375 clone.lines = this.lines;
1376 clone.document = (Line)document.Clone();
1381 public void Delete(int LineNo) {
1386 Delete(GetLine(LineNo));
1389 public void Delete(Line line1) {
1390 Line line2;// = new Line();
1393 if ((line1.left == sentinel) || (line1.right == sentinel)) {
1396 line3 = line1.right;
1397 while (line3.left != sentinel) {
1402 if (line3.left != sentinel) {
1405 line2 = line3.right;
1408 line2.parent = line3.parent;
1409 if (line3.parent != null) {
1410 if(line3 == line3.parent.left) {
1411 line3.parent.left = line2;
1413 line3.parent.right = line2;
1419 if (line3 != line1) {
1422 line1.line_no = line3.line_no;
1423 line1.text = line3.text;
1424 line1.tags = line3.tags;
1425 line1.height = line3.height;
1426 line1.recalc = line3.recalc;
1427 line1.space = line3.space;
1428 line1.widths = line3.widths;
1432 while (tag != null) {
1438 if (line3.color == LineColor.Black)
1439 RebalanceAfterDelete(line2);
1443 last_found = sentinel;
1446 // Set our selection markers
1447 public void SetSelection(Line start, int start_pos, Line end, int end_pos) {
1448 selection_start_line = start;
1449 selection_start_pos = start_pos;
1450 selection_end_line = end;
1451 selection_end_pos = end_pos;
1454 public void SetSelection(Line start, int start_pos) {
1455 selection_start_line = start;
1456 selection_start_pos = start_pos;
1457 selection_end_line = start;
1458 selection_end_pos = start_pos;
1462 // Give it a Line number and it returns the Line object at with that line number
1463 public Line GetLine(int LineNo) {
1464 Line line = document;
1466 while (line != sentinel) {
1467 if (LineNo == line.line_no) {
1469 } else if (LineNo < line.line_no) {
1479 // Give it a Y pixel coordinate and it returns the Line covering that Y coordinate
1481 public Line GetLineByPixel(int y, bool exact) {
1482 Line line = document;
1485 while (line != sentinel) {
1487 if ((y >= line.Y) && (y < (line.Y+line.height))) {
1489 } else if (y < line.Y) {
1502 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
1503 public LineTag FindTag(int x, int y, out int index, bool exact) {
1507 line = GetLineByPixel(y, exact);
1515 if (x >= tag.X && x < (tag.X+tag.width)) {
1518 end = tag.start + tag.length - 1;
1520 for (int pos = tag.start; pos < end; pos++) {
1521 if (x < line.widths[pos]) {
1529 if (tag.next != null) {
1537 index = line.text.Length;
1543 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
1544 public LineTag FindCursor(int x, int y, out int index) {
1548 line = GetLineByPixel(y, false);
1552 if (x >= tag.X && x < (tag.X+tag.width)) {
1555 end = tag.start + tag.length - 1;
1557 for (int pos = tag.start-1; pos < end; pos++) {
1558 // When clicking on a character, we position the cursor to whatever edge
1559 // of the character the click was closer
1560 if (x < (line.widths[pos] + ((line.widths[pos+1]-line.widths[pos])/2))) {
1568 if (tag.next != null) {
1571 index = line.text.Length;
1577 // Calculate formatting for the whole document
1578 public bool RecalculateDocument(Graphics g) {
1579 return RecalculateDocument(g, 1, this.lines, false);
1582 // Calculate formatting starting at a certain line
1583 public bool RecalculateDocument(Graphics g, int start) {
1584 return RecalculateDocument(g, start, this.lines, false);
1587 // Calculate formatting within two given line numbers
1588 public bool RecalculateDocument(Graphics g, int start, int end) {
1589 return RecalculateDocument(g, start, end, false);
1592 // With optimize on, returns true if line heights changed
1593 public bool RecalculateDocument(Graphics g, int start, int end, bool optimize) {
1598 Y = GetLine(start).Y;
1605 while (line_no <= end) {
1606 line = GetLine(line_no++);
1609 if (line.RecalculateLine(g)) {
1611 // If the height changed, all subsequent lines change
1621 while (line_no <= end) {
1622 line = GetLine(line_no++);
1624 line.RecalculateLine(g);
1631 public bool SetCursor(int x, int y) {
1638 #endregion // Public Methods
1640 #region Administrative
1641 public IEnumerator GetEnumerator() {
1646 public override bool Equals(object obj) {
1651 if (!(obj is Document)) {
1659 if (ToString().Equals(((Document)obj).ToString())) {
1666 public override int GetHashCode() {
1670 public override string ToString() {
1671 return "document " + this.document_id;
1674 #endregion // Administrative
1677 public class LineTag {
1678 #region Local Variables;
1679 // Payload; formatting
1680 internal Font font; // System.Drawing.Font object for this tag
1683 internal int start; // start, in chars; index into Line.text
1684 internal int length; // length, in chars
1685 internal bool r_to_l; // Which way is the font
1688 internal int height; // Height in pixels of the text this tag describes
1689 internal int X; // X location of the text this tag describes
1690 internal float width; // Width in pixels of the text this tag describes
1691 internal int ascent; // Ascent of the font for this tag
1692 internal int shift; // Shift down for this tag, to stay on baseline
1695 internal Line line; // The line we're on
1696 internal LineTag next; // Next tag on the same line
1697 internal LineTag previous; // Previous tag on the same line
1700 #region Constructors
1701 public LineTag(Line line, int start, int length) {
1704 this.length = length;
1708 #endregion // Constructors
1710 #region Public Methods
1712 // Applies 'font' to characters starting at 'start' for 'length' chars
1713 // Removes any previous tags overlapping the same area
1714 // returns true if lineheight has changed
1716 public static bool FormatText(Line line, int start, int length, Font font) {
1723 bool retval = false; // Assume line-height doesn't change
1726 if (font.Height != line.height) {
1729 line.recalc = true; // This forces recalculation of the line in RecalculateDocument
1731 // A little sanity, not sure if it's needed, might be able to remove for speed
1732 if (length > line.text.Length) {
1733 length = line.text.Length;
1737 end = start + length;
1740 // Common special case
1741 if ((start == 1) && (length == tag.length)) {
1746 start_tag = FindTag(line, start);
1747 end_tag = FindTag(line, end);
1749 tag = new LineTag(line, start, length);
1756 if (start_tag.start == start) {
1757 tag.next = start_tag;
1758 tag.previous = start_tag.previous;
1759 if (start_tag.previous != null) {
1760 start_tag.previous.next = tag;
1762 start_tag.previous = tag;
1764 // Insert ourselves 'in the middle'
1765 if ((start_tag.next != null) && (start_tag.next.start < end)) {
1766 tag.next = start_tag.next;
1768 tag.next = new LineTag(line, start_tag.start, start_tag.length);
1769 tag.next.font = start_tag.font;
1771 if (start_tag.next != null) {
1772 tag.next.next = start_tag.next;
1773 tag.next.next.previous = tag.next;
1776 tag.next.previous = tag;
1778 start_tag.length = start - start_tag.start;
1780 tag.previous = start_tag;
1781 start_tag.next = tag;
1783 if (tag.next.start > (tag.start + tag.length)) {
1784 tag.next.length += tag.next.start - (tag.start + tag.length);
1785 tag.next.start = tag.start + tag.length;
1792 while ((tag != null) && (tag.start < end)) {
1793 if ((tag.start + tag.length) <= end) {
1795 tag.previous.next = tag.next;
1796 if (tag.next != null) {
1797 tag.next.previous = tag.previous;
1801 // Adjust the length of the tag
1802 tag.length = (tag.start + tag.length) - end;
1813 // Finds the tag that describes the character at position 'pos' on 'line'
1815 public static LineTag FindTag(Line line, int pos) {
1816 LineTag tag = line.tags;
1818 // Beginning of line is a bit special
1823 while (tag != null) {
1824 if ((tag.start <= pos) && (pos < (tag.start+tag.length))) {
1835 // Combines 'this' tag with 'other' tag.
1837 public bool Combine(LineTag other) {
1838 if (!this.Equals(other)) {
1842 this.width += other.width;
1843 this.length += other.length;
1844 this.next = other.next;
1845 if (this.next != null) {
1846 this.next.previous = this;
1854 // Remove 'this' tag ; to be called when formatting is to be removed
1856 public bool Remove() {
1857 if ((this.start == 1) && (this.next == null)) {
1858 // We cannot remove the only tag
1861 if (this.start != 1) {
1862 this.previous.length += this.length;
1863 this.previous.width = -1;
1864 this.previous.next = this.next;
1865 this.next.previous = this.previous;
1867 this.next.start = 1;
1868 this.next.length += this.length;
1869 this.next.width = -1;
1870 this.line.tags = this.next;
1871 this.next.previous = null;
1878 // Checks if 'this' tag describes the same formatting options as 'obj'
1880 public override bool Equals(object obj) {
1887 if (!(obj is LineTag)) {
1895 other = (LineTag)obj;
1897 if (this.font.Equals(other.font)) { // FIXME add checking for things like link or type later
1904 public override string ToString() {
1905 return "Tag starts at index " + this.start + "length " + this.length + " text: " + this.line.Text.Substring(this.start-1, this.length) + "Font " + this.font.ToString();
1908 #endregion // Public Methods