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)
68 // Stuff that's important for the tree
69 internal Line parent; // Our parent line
70 public Line left; // Line with smaller line number
71 public Line right; // Line with higher line number
72 internal LineColor color; // We're doing a black/red tree. this is the node color
73 internal int DEFAULT_TEXT_LEN; //
74 internal static StringFormat string_format; // For calculating widths/heights
75 internal bool recalc; // Line changed
76 #endregion // Local Variables
80 color = LineColor.Red;
87 if (string_format == null) {
88 string_format = new StringFormat(StringFormat.GenericTypographic);
89 string_format.Trimming = StringTrimming.None;
90 string_format.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;
94 public Line(int LineNo, string Text, Font font) : this() {
95 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
97 text = new StringBuilder(Text, space);
100 widths = new float[space + 1];
101 tags = new LineTag(this, 1, text.Length);
105 public Line(int LineNo, string Text, LineTag tag) : this() {
106 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
108 text = new StringBuilder(Text, space);
111 widths = new float[space + 1];
115 #endregion // Constructors
117 #region Public Properties
140 return text.ToString();
144 text = new StringBuilder(value, value.Length > DEFAULT_TEXT_LEN ? value.Length : DEFAULT_TEXT_LEN);
148 public StringBuilder Text {
158 #endregion // Public Properties
160 #region Public Methods
161 // Make sure we always have enoughs space in text and widths
162 public void Grow(int minimum) {
166 length = text.Length;
168 if ((length + minimum) > space) {
169 // We need to grow; double the size
171 if ((length + minimum) > (space * 2)) {
172 new_widths = new float[length + minimum * 2 + 1];
173 space = length + minimum * 2;
175 new_widths = new float[space * 2 + 1];
178 widths.CopyTo(new_widths, 0);
184 public void Streamline() {
195 while (next != null) {
196 current.Combine(next);
197 current = current.next;
203 // Go through all tags on a line and recalculate all size-related values
204 // returns true if lineheight changed
206 public bool RecalculateLine(Graphics g) {
215 len = this.text.Length;
217 prev_height = this.height; // For drawing optimization calculations
218 this.height = 0; // Reset line height
224 size = g.MeasureString(this.text.ToString(pos, 1), tag.font, 10000, string_format);
232 widths[pos] = widths[pos-1] + w;
234 if (pos == (tag.start-1 + tag.length)) {
235 // We just found the end of our current tag
237 tag.height = (int)size.Height;
238 // Check if we're the tallest on the line (so far)
239 if (tag.height > this.height) {
240 this.height = tag.height; // Yep; make sure the line knows
243 // Update our horizontal starting pixel position
244 if (tag.previous == null) {
247 tag.X = tag.previous.X + (int)tag.previous.width;
257 if (this.height == 0) {
258 this.height = tags.font.Height;
259 tag.height = this.height;
262 if (prev_height != this.height) {
267 #endregion // Public Methods
269 #region Administrative
270 public int CompareTo(object obj) {
275 if (! (obj is Line)) {
276 throw new ArgumentException("Object is not of type Line", "obj");
279 if (line_no < ((Line)obj).line_no) {
281 } else if (line_no > ((Line)obj).line_no) {
288 public object Clone() {
296 clone.left = (Line)left.Clone();
300 clone.left = (Line)left.Clone();
306 public object CloneLine() {
316 public override bool Equals(object obj) {
321 if (!(obj is Line)) {
329 if (line_no == ((Line)obj).line_no) {
337 public override string ToString() {
338 return "Line " + line_no;
341 #endregion // Administrative
344 public class Document : ICloneable, IEnumerable {
346 internal struct Marker {
348 internal LineTag tag;
352 #endregion Structures
354 #region Local Variables
355 private Line document;
357 private static Line sentinel;
358 private Line last_found;
359 private int document_id;
360 private Random random = new Random();
362 internal bool multiline;
364 private Line selection_start_line;
365 private int selection_start_pos;
366 private Line selection_end_line;
367 private int selection_end_pos;
369 internal Marker caret;
370 internal Marker selection_start;
371 internal Marker selection_end;
373 internal int viewport_x;
374 internal int viewport_y; // The visible area of the document
376 internal int document_x; // Width of the document
377 internal int document_y; // Height of the document
379 internal Control owner; // Who's owning us?
380 #endregion // Local Variables
383 public Document(Control owner) {
390 // Tree related stuff
391 sentinel = new Line();
392 sentinel.color = LineColor.Black;
395 last_found = sentinel;
397 // We always have a blank line
398 Add(1, "", owner.Font);
399 this.RecalculateDocument(owner.CreateGraphics());
403 // Default selection is empty
405 document_id = random.Next();
409 #region Public Properties
426 public Line CaretLine {
428 return caret.tag.line;
432 public int CaretPosition {
438 public LineTag CaretTag {
444 public int ViewPortX {
454 public int ViewPortY {
464 #endregion // Public Properties
466 #region Private Methods
467 private void IncrementLines(int line_no) {
470 current = this.lines;
471 while (current >= line_no) {
472 GetLine(current).line_no++;
478 private void RebalanceAfterAdd(Line line1) {
481 while ((line1 != document) && (line1.parent.color == LineColor.Red)) {
482 if (line1.parent == line1.parent.parent.left) {
483 line2 = line1.parent.parent.right;
485 if ((line2 != null) && (line1.color == LineColor.Red)) {
486 line1.parent.color = LineColor.Black;
487 line2.color = LineColor.Black;
488 line1.parent.parent.color = LineColor.Red;
489 line1 = line1.parent.parent;
491 if (line1 == line1.parent.right) {
492 line1 = line1.parent;
496 line1.parent.color = LineColor.Black;
497 line1.parent.parent.color = LineColor.Red;
499 RotateRight(line1.parent.parent);
502 line2 = line1.parent.parent.left;
504 if ((line2 != null) && (line2.color == LineColor.Red)) {
505 line1.parent.color = LineColor.Black;
506 line2.color = LineColor.Black;
507 line1.parent.parent.color = LineColor.Red;
508 line1 = line1.parent.parent;
510 if (line1 == line1.parent.left) {
511 line1 = line1.parent;
515 line1.parent.color = LineColor.Black;
516 line1.parent.parent.color = LineColor.Red;
517 RotateLeft(line1.parent.parent);
521 document.color = LineColor.Black;
524 private void RebalanceAfterDelete(Line line1) {
527 while ((line1 != document) && (line1.color == LineColor.Black)) {
528 if (line1 == line1.parent.left) {
529 line2 = line1.parent.right;
530 if (line2.color == LineColor.Red) {
531 line2.color = LineColor.Black;
532 line1.parent.color = LineColor.Red;
533 RotateLeft(line1.parent);
534 line2 = line1.parent.right;
536 if ((line2.left.color == LineColor.Black) && (line2.right.color == LineColor.Black)) {
537 line2.color = LineColor.Red;
538 line1 = line1.parent;
540 if (line2.right.color == LineColor.Red) {
541 line2.left.color = LineColor.Black;
542 line2.color = LineColor.Red;
544 line2 = line1.parent.right;
546 line2.color = line1.parent.color;
547 line1.parent.color = LineColor.Black;
548 line2.right.color = LineColor.Black;
549 RotateLeft(line1.parent);
553 line2 = line1.parent.left;
554 if (line2.color == LineColor.Red) {
555 line2.color = LineColor.Black;
556 line1.parent.color = LineColor.Red;
557 RotateRight(line1.parent);
558 line2 = line1.parent.left;
560 if ((line2.right.color == LineColor.Black) && (line2.left.color == LineColor.Black)) {
561 line2.color = LineColor.Red;
562 line1 = line1.parent;
564 if (line2.left.color == LineColor.Black) {
565 line2.right.color = LineColor.Black;
566 line2.color = LineColor.Red;
568 line2 = line1.parent.left;
570 line2.color = line1.parent.color;
571 line1.parent.color = LineColor.Black;
572 line2.left.color = LineColor.Black;
573 RotateRight(line1.parent);
578 line1.color = LineColor.Black;
581 private void RotateLeft(Line line1) {
582 Line line2 = line1.right;
584 line1.right = line2.left;
586 if (line2.left != sentinel) {
587 line2.left.parent = line1;
590 if (line2 != sentinel) {
591 line2.parent = line1.parent;
594 if (line1.parent != null) {
595 if (line1 == line1.parent.left) {
596 line1.parent.left = line2;
598 line1.parent.right = line2;
605 if (line1 != sentinel) {
606 line1.parent = line2;
610 private void RotateRight(Line line1) {
611 Line line2 = line1.left;
613 line1.left = line2.right;
615 if (line2.right != sentinel) {
616 line2.right.parent = line1;
619 if (line2 != sentinel) {
620 line2.parent = line1.parent;
623 if (line1.parent != null) {
624 if (line1 == line1.parent.right) {
625 line1.parent.right = line2;
627 line1.parent.left = line2;
634 if (line1 != sentinel) {
635 line1.parent = line2;
640 public void UpdateView(Line line, int pos) {
643 // This is an optimization; we need to invalidate
644 prev_width = (int)line.widths[line.text.Length];
646 if (RecalculateDocument(owner.CreateGraphics(), line.line_no, line.line_no, true)) {
647 // Lineheight changed, invalidate the rest of the document
648 if ((line.Y - viewport_y) >=0 ) {
649 // We formatted something that's in view, only draw parts of the screen
650 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, owner.Width, owner.Height - line.Y - viewport_y));
652 // The tag was above the visible area, draw everything
656 owner.Invalidate(new Rectangle((int)line.widths[pos] - viewport_x, line.Y - viewport_y, (int)line.widths[line.text.Length], line.height));
661 // Update display from line, down line_count lines; pos is unused, but required for the signature
662 public void UpdateView(Line line, int line_count, int pos) {
665 // This is an optimization; we need to invalidate
666 prev_width = (int)line.widths[line.text.Length];
668 if (RecalculateDocument(owner.CreateGraphics(), line.line_no, line.line_no + line_count - 1, true)) {
669 // Lineheight changed, invalidate the rest of the document
670 if ((line.Y - viewport_y) >=0 ) {
671 // We formatted something that's in view, only draw parts of the screen
672 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, owner.Width, owner.Height - line.Y - viewport_y));
674 // The tag was above the visible area, draw everything
680 end_line = GetLine(line.line_no + line_count -1);
681 if (end_line == null) {
685 owner.Invalidate(new Rectangle(0 - viewport_x, line.Y - viewport_y, (int)line.widths[line.text.Length], end_line.Y + end_line.height));
688 #endregion // Private Methods
690 #region Public Methods
691 public void PositionCaret(int x, int y) {
692 caret.tag = FindCursor(x, y, out caret.pos);
693 caret.line = caret.tag.line;
694 caret.height = caret.tag.height;
696 XplatUI.DestroyCaret(owner.Handle);
697 XplatUI.CreateCaret(owner.Handle, 2, caret.height);
698 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos], caret.tag.line.Y + caret.tag.line.height - caret.height);
701 public void CaretHasFocus() {
702 if (caret.tag != null) {
703 XplatUI.CreateCaret(owner.Handle, 2, caret.height);
704 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos], caret.tag.line.Y + caret.tag.line.height - caret.height);
705 XplatUI.CaretVisible(owner.Handle, true);
709 public void CaretLostFocus() {
710 XplatUI.DestroyCaret(owner.Handle);
713 public void AlignCaret() {
714 caret.tag = LineTag.FindTag(caret.line, caret.pos);
715 caret.height = caret.tag.height;
717 XplatUI.CreateCaret(owner.Handle, 2, caret.height);
718 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos], caret.tag.line.Y + caret.tag.line.height - caret.height);
719 XplatUI.CaretVisible(owner.Handle, true);
722 public void UpdateCaret() {
723 if (caret.tag.height != caret.height) {
724 caret.height = caret.tag.height;
725 XplatUI.CreateCaret(owner.Handle, 2, caret.height);
727 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos], caret.tag.line.Y + caret.tag.line.height - caret.height);
728 XplatUI.CaretVisible(owner.Handle, true);
731 public void DisplayCaret() {
732 XplatUI.CaretVisible(owner.Handle, true);
735 public void HideCaret() {
736 XplatUI.CaretVisible(owner.Handle, false);
739 public void MoveCaret(CaretDirection direction) {
741 case CaretDirection.CharForward: {
743 if (caret.pos > caret.line.text.Length) {
746 if (caret.line.line_no < this.lines) {
747 caret.line = GetLine(caret.line.line_no+1);
749 caret.tag = caret.line.tags;
754 // Single line; we stay where we are
758 if ((caret.tag.start - 1 + caret.tag.length) < caret.pos) {
759 caret.tag = caret.tag.next;
766 case CaretDirection.CharBack: {
768 // caret.pos--; // folded into the if below
769 if (--caret.pos > 0) {
770 if (caret.tag.start > caret.pos) {
771 caret.tag = caret.tag.previous;
775 if (caret.line.line_no > 1) {
776 caret.line = GetLine(caret.line.line_no - 1);
777 caret.pos = caret.line.text.Length;
778 caret.tag = LineTag.FindTag(caret.line, caret.pos);
785 case CaretDirection.WordForward: {
788 len = caret.line.text.Length;
789 if (caret.pos < len) {
790 while ((caret.pos < len) && (caret.line.text.ToString(caret.pos, 1) != " ")) {
793 if (caret.pos < len) {
794 // Skip any whitespace
795 while ((caret.pos < len) && (caret.line.text.ToString(caret.pos, 1) == " ")) {
800 if (caret.line.line_no < this.lines) {
801 caret.line = GetLine(caret.line.line_no+1);
803 caret.tag = caret.line.tags;
810 case CaretDirection.WordBack: {
814 len = caret.line.text.Length;
818 while ((caret.pos > 0) && (caret.line.text.ToString(caret.pos, 1) == " ")) {
822 while ((caret.pos > 0) && (caret.line.text.ToString(caret.pos, 1) != " ")) {
826 if (caret.line.text.ToString(caret.pos, 1) == " ") {
827 if (caret.pos != 0) {
830 caret.line = GetLine(caret.line.line_no - 1);
831 caret.pos = caret.line.text.Length;
832 caret.tag = LineTag.FindTag(caret.line, caret.pos);
836 if (caret.line.line_no > 1) {
837 caret.line = GetLine(caret.line.line_no - 1);
838 caret.pos = caret.line.text.Length;
839 caret.tag = LineTag.FindTag(caret.line, caret.pos);
846 case CaretDirection.LineUp: {
847 if (caret.line.line_no > 1) {
850 pixel = (int)caret.line.widths[caret.pos];
851 PositionCaret(pixel, GetLine(caret.line.line_no - 1).Y);
852 XplatUI.CaretVisible(owner.Handle, true);
857 case CaretDirection.LineDown: {
858 if ((caret.line.line_no + 1) < lines) {
861 pixel = (int)caret.line.widths[caret.pos];
862 PositionCaret(pixel, GetLine(caret.line.line_no + 1).Y);
863 XplatUI.CaretVisible(owner.Handle, true);
868 case CaretDirection.Home: {
871 caret.tag = caret.line.tags;
877 case CaretDirection.End: {
878 if (caret.pos < caret.line.text.Length) {
879 caret.pos = caret.line.text.Length;
880 caret.tag = LineTag.FindTag(caret.line, caret.pos);
886 case CaretDirection.PgUp: {
890 case CaretDirection.PgDn: {
894 case CaretDirection.CtrlHome: {
895 caret.line = GetLine(1);
897 caret.tag = caret.line.tags;
903 case CaretDirection.CtrlEnd: {
904 caret.line = GetLine(lines);
906 caret.tag = caret.line.tags;
915 public void Draw(Graphics g, Rectangle clip, Brush brush) {
916 Line line; // Current line being drawn
917 LineTag tag; // Current tag being drawn
918 int start; // First line to draw
919 int end; // Last line to draw
920 string s; // String representing the current line
923 // First, figure out from what line to what line we need to draw
924 start = GetLineByPixel(clip.Top - viewport_y, false).line_no;
925 end = GetLineByPixel(clip.Bottom - viewport_y, false).line_no;
927 // Now draw our elements; try to only draw those that are visible
929 while (line_no <= end) {
930 line = GetLine(line_no);
932 s = line.text.ToString();
933 while (tag != null) {
934 if (((tag.X + tag.width) > (clip.Left - viewport_x)) || (tag.X < (clip.Right - viewport_x))) {
935 g.DrawString(s.Substring(tag.start-1, tag.length), tag.font, brush, tag.X - viewport_x, line.Y+line.height-tag.height - viewport_y, StringFormat.GenericTypographic);
945 // Inserts a character at the given position
946 public void InsertChar(LineTag tag, int pos, char ch) {
950 line.text.Insert(pos, ch);
952 if (tag.next != null) {
958 UpdateView(line, pos);
961 // Inserts a character at the given position
962 public void InsertCharAtCaret(char ch, bool move_caret) {
963 caret.line.text.Insert(caret.pos, ch);
965 if (caret.tag.next != null) {
966 caret.tag.next.start++;
969 caret.line.recalc = true;
971 UpdateView(caret.line, caret.pos);
978 // Split the line at the position into two
979 public void Split(int LineNo, int pos) {
983 line = GetLine(LineNo);
984 tag = LineTag.FindTag(line, pos);
985 Split(line, tag, pos);
988 public void Split(Line line, int pos) {
991 tag = LineTag.FindTag(line, pos);
992 Split(line, tag, pos);
995 public void Split(Line line, LineTag tag, int pos) {
999 // cover the easy case first
1000 if (pos == line.text.Length) {
1001 Add(line.line_no + 1, "", tag.font);
1005 // We need to move the rest of the text into the new line
1006 Add(line.line_no + 1, line.text.ToString(pos, line.text.Length - pos), tag.font);
1008 // Now transfer our tags from this line to the next
1009 new_line = GetLine(line.line_no + 1);
1012 if ((tag.start - 1) == pos) {
1015 // We can simply break the chain and move the tag into the next line
1016 if (tag == line.tags) {
1017 new_tag = new LineTag(line, 1, 0);
1018 new_tag.font = tag.font;
1019 line.tags = new_tag;
1022 if (tag.previous != null) {
1023 tag.previous.next = null;
1025 new_line.tags = tag;
1026 tag.previous = null;
1027 tag.line = new_line;
1029 // Walk the list and correct the start location of the tags we just bumped into the next line
1030 shift = tag.start - 1;
1033 while (new_tag != null) {
1034 new_tag.start -= shift;
1035 new_tag.line = new_line;
1036 new_tag = new_tag.next;
1041 new_tag = new LineTag(new_line, 1, tag.start - 1 + tag.length - pos);
1042 new_tag.next = tag.next;
1043 new_tag.font = tag.font;
1044 new_line.tags = new_tag;
1045 if (new_tag.next != null) {
1046 new_tag.next.previous = new_tag;
1049 tag.length = pos - tag.start + 1;
1052 new_tag = new_tag.next;
1053 while (new_tag != null) {
1054 new_tag.start -= shift;
1055 new_tag.line = new_line;
1056 new_tag = new_tag.next;
1060 line.text.Remove(pos, line.text.Length - pos);
1063 // Adds a line of text, with given font.
1064 // Bumps any line at that line number that already exists down
1065 public void Add(int LineNo, string Text, Font font) {
1070 if (LineNo<1 || Text == null) {
1072 throw new ArgumentNullException("LineNo", "Line numbers must be positive");
1074 throw new ArgumentNullException("Text", "Cannot insert NULL line");
1078 add = new Line(LineNo, Text, font);
1081 while (line != sentinel) {
1083 line_no = line.line_no;
1085 if (LineNo > line_no) {
1087 } else if (LineNo < line_no) {
1090 // Bump existing line numbers; walk all nodes to the right of this one and increment line_no
1091 IncrementLines(line.line_no);
1096 add.left = sentinel;
1097 add.right = sentinel;
1099 if (add.parent != null) {
1100 if (LineNo > add.parent.line_no) {
1101 add.parent.right = add;
1103 add.parent.left = add;
1110 RebalanceAfterAdd(add);
1115 public virtual void Clear() {
1117 document = sentinel;
1120 public virtual object Clone() {
1123 clone = new Document(null);
1125 clone.lines = this.lines;
1126 clone.document = (Line)document.Clone();
1131 public void Delete(int LineNo) {
1136 Delete(GetLine(LineNo));
1139 public void Delete(Line line1) {
1140 Line line2 = new Line();
1143 if ((line1.left == sentinel) || (line1.right == sentinel)) {
1146 line3 = line1.right;
1147 while (line3.left != sentinel) {
1152 if (line3.left != sentinel) {
1155 line2 = line3.right;
1158 line2.parent = line3.parent;
1159 if (line3.parent != null) {
1160 if(line3 == line3.parent.left) {
1161 line3.parent.left = line2;
1163 line3.parent.right = line2;
1169 if (line3 != line1) {
1170 line1.line_no = line3.line_no;
1171 line1.text = line3.text;
1174 if (line3.color == LineColor.Black)
1175 RebalanceAfterDelete(line2);
1177 last_found = sentinel;
1180 // Set our selection markers
1181 public void SetSelection(Line start, int start_pos, Line end, int end_pos) {
1182 selection_start_line = start;
1183 selection_start_pos = start_pos;
1184 selection_end_line = end;
1185 selection_end_pos = end_pos;
1188 public void SetSelection(Line start, int start_pos) {
1189 selection_start_line = start;
1190 selection_start_pos = start_pos;
1191 selection_end_line = start;
1192 selection_end_pos = start_pos;
1196 // Give it a Line number and it returns the Line object at with that line number
1197 public Line GetLine(int LineNo) {
1198 Line line = document;
1200 while (line != sentinel) {
1201 if (LineNo == line.line_no) {
1203 } else if (LineNo < line.line_no) {
1213 // Give it a Y pixel coordinate and it returns the Line covering that Y coordinate
1215 public Line GetLineByPixel(int y, bool exact) {
1216 Line line = document;
1219 while (line != sentinel) {
1221 if ((y >= line.Y) && (y < (line.Y+line.height))) {
1223 } else if (y < line.Y) {
1236 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
1237 public LineTag FindTag(int x, int y, out int index, bool exact) {
1241 line = GetLineByPixel(y, exact);
1249 if (x >= tag.X && x < (tag.X+tag.width)) {
1252 end = tag.start + tag.length - 1;
1254 for (int pos = tag.start; pos < end; pos++) {
1255 if (x < line.widths[pos]) {
1263 if (tag.next != null) {
1271 index = line.text.Length;
1277 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
1278 public LineTag FindCursor(int x, int y, out int index) {
1282 line = GetLineByPixel(y, false);
1286 if (x >= tag.X && x < (tag.X+tag.width)) {
1289 end = tag.start + tag.length - 1;
1291 for (int pos = tag.start-1; pos < end; pos++) {
1292 // When clicking on a character, we position the cursor to whatever edge
1293 // of the character the click was closer
1294 if (x < (line.widths[pos] + ((line.widths[pos+1]-line.widths[pos])/2))) {
1302 if (tag.next != null) {
1305 index = line.text.Length;
1311 // Calculate formatting for the whole document
1312 public bool RecalculateDocument(Graphics g) {
1313 return RecalculateDocument(g, 1, this.lines, false);
1316 // Calculate formatting starting at a certain line
1317 public bool RecalculateDocument(Graphics g, int start) {
1318 return RecalculateDocument(g, start, this.lines, false);
1321 // Calculate formatting within two given line numbers
1322 public bool RecalculateDocument(Graphics g, int start, int end) {
1323 return RecalculateDocument(g, start, end, false);
1326 // With optimize on, returns true if line heights changed
1327 public bool RecalculateDocument(Graphics g, int start, int end, bool optimize) {
1332 Y = GetLine(start).Y;
1339 while (line_no <= end) {
1340 line = GetLine(line_no++);
1343 if (line.RecalculateLine(g)) {
1345 // If the height changed, all subsequent lines change
1355 while (line_no <= end) {
1356 line = GetLine(line_no++);
1358 line.RecalculateLine(g);
1365 public bool SetCursor(int x, int y) {
1372 #endregion // Public Methods
1374 #region Administrative
1375 public IEnumerator GetEnumerator() {
1380 public override bool Equals(object obj) {
1385 if (!(obj is Document)) {
1393 if (ToString().Equals(((Document)obj).ToString())) {
1400 public override int GetHashCode() {
1404 public override string ToString() {
1405 return "document " + this.document_id;
1408 #endregion // Administrative
1411 public class LineTag {
1412 #region Local Variables;
1413 // Payload; formatting
1414 internal Font font; // System.Drawing.Font object for this tag
1417 internal int start; // start, in chars; index into Line.text
1418 internal int length; // length, in chars
1419 internal bool r_to_l; // Which way is the font
1422 internal int height; // Height in pixels of the text this tag describes
1423 internal int X; // X location of the text this tag describes
1424 internal float width; // Width in pixels of the text this tag describes
1427 internal Line line; // The line we're on
1428 internal LineTag next; // Next tag on the same line
1429 internal LineTag previous; // Previous tag on the same line
1432 #region Constructors
1433 public LineTag(Line line, int start, int length) {
1436 this.length = length;
1440 #endregion // Constructors
1442 #region Public Methods
1444 // Applies 'font' to characters starting at 'start' for 'length' chars
1445 // Removes any previous tags overlapping the same area
1446 // returns true if lineheight has changed
1448 public static bool FormatText(Line line, int start, int length, Font font) {
1455 bool retval = false; // Assume line-height doesn't change
1458 if (font.Height != line.height) {
1461 line.recalc = true; // This forces recalculation of the line in RecalculateDocument
1463 // A little sanity, not sure if it's needed, might be able to remove for speed
1464 if (length > line.text.Length) {
1465 length = line.text.Length;
1469 end = start + length;
1472 // Common special case
1473 if ((start == 1) && (length == tag.length)) {
1478 start_tag = FindTag(line, start);
1479 end_tag = FindTag(line, end);
1481 tag = new LineTag(line, start, length);
1488 if (start_tag.start == start) {
1489 tag.next = start_tag;
1490 tag.previous = start_tag.previous;
1491 if (start_tag.previous != null) {
1492 start_tag.previous.next = tag;
1494 start_tag.previous = tag;
1496 // Insert ourselves 'in the middle'
1497 if ((start_tag.next != null) && (start_tag.next.start < end)) {
1498 tag.next = start_tag.next;
1500 tag.next = new LineTag(line, start_tag.start, start_tag.length);
1501 tag.next.font = start_tag.font;
1503 if (start_tag.next != null) {
1504 tag.next.next = start_tag.next;
1505 tag.next.next.previous = tag.next;
1508 tag.next.previous = tag;
1510 start_tag.length = start - start_tag.start;
1512 tag.previous = start_tag;
1513 start_tag.next = tag;
1515 if (tag.next.start > (tag.start + tag.length)) {
1516 tag.next.length += tag.next.start - (tag.start + tag.length);
1517 tag.next.start = tag.start + tag.length;
1524 while ((tag != null) && (tag.start < end)) {
1525 if ((tag.start + tag.length) <= end) {
1527 tag.previous.next = tag.next;
1528 if (tag.next != null) {
1529 tag.next.previous = tag.previous;
1533 // Adjust the length of the tag
1534 tag.length = (tag.start + tag.length) - end;
1545 // Finds the tag that describes the character at position 'pos' on 'line'
1547 public static LineTag FindTag(Line line, int pos) {
1548 LineTag tag = line.tags;
1550 // Beginning of line is a bit special
1555 while (tag != null) {
1556 if ((tag.start <= pos) && (pos < (tag.start+tag.length))) {
1567 // Combines 'this' tag with 'other' tag.
1569 public void Combine(LineTag other) {
1570 if (!this.Equals(other)) {
1574 this.width += other.width;
1575 this.length += other.length;
1576 this.next = other.next;
1577 if (this.next != null) {
1578 this.next.previous = this;
1584 // Remove 'this' tag ; to be called when formatting is to be removed
1586 public bool Remove() {
1587 if ((this.start == 1) && (this.next == null)) {
1588 // We cannot remove the only tag
1591 if (this.start != 1) {
1592 this.previous.length += this.length;
1593 this.previous.width = -1;
1594 this.previous.next = this.next;
1595 this.next.previous = this.previous;
1597 this.next.start = 1;
1598 this.next.length += this.length;
1599 this.next.width = -1;
1600 this.line.tags = this.next;
1601 this.next.previous = null;
1608 // Checks if 'this' tag describes the same formatting options as 'obj'
1610 public override bool Equals(object obj) {
1617 if (!(obj is LineTag)) {
1625 other = (LineTag)obj;
1627 if (this.font.Equals(other.font)) { // FIXME add checking for things like link or type later
1634 public override string ToString() {
1635 return "Tag starts at index " + this.start + "length " + this.length + " text: " + this.line.Text.Substring(this.start-1, this.length) + "Font " + this.font.ToString();
1638 #endregion // Public Methods