1 // Permission is hereby granted, free of charge, to any person obtaining
2 // a copy of this software and associated documentation files (the
3 // "Software"), to deal in the Software without restriction, including
4 // without limitation the rights to use, copy, modify, merge, publish,
5 // distribute, sublicense, and/or sell copies of the Software, and to
6 // permit persons to whom the Software is furnished to do so, subject to
7 // the following conditions:
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
12 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 // Copyright (c) 2004-2006 Novell, Inc. (http://www.novell.com)
23 // Peter Bartok pbartok@novell.com
29 // There's still plenty of things missing, I've got most of it planned, just hadn't had
30 // the time to write it all yet.
31 // Stuff missing (in no particular order):
32 // - Align text after RecalculateLine
33 // - Implement tag types for hotlinks, images, etc.
34 // - Implement CaretPgUp/PgDown
37 // selection_start.pos and selection_end.pos are 0-based
38 // selection_start.pos = first selected char
39 // selection_end.pos = first NOT-selected char
41 // FormatText methods are 1-based (as are all tags, LineTag.Start is 1 for
42 // the first character on a line; the reason is that 0 is the position
43 // *before* the first character on a line
49 using System
.Collections
;
51 using System
.Drawing
.Text
;
54 namespace System
.Windows
.Forms
{
55 internal enum LineColor
{
60 internal enum CaretSelection
{
61 Position
, // Selection=Caret
62 Word
, // Selection=Word under caret
63 Line
// Selection=Line under caret
66 internal class FontDefinition
{
69 internal FontStyle add_style
;
70 internal FontStyle remove_style
;
72 internal Font font_obj
;
74 internal FontDefinition() {
81 internal enum CaretDirection
{
82 CharForward
, // Move a char to the right
83 CharBack
, // Move a char to the left
84 LineUp
, // Move a line up
85 LineDown
, // Move a line down
86 Home
, // Move to the beginning of the line
87 End
, // Move to the end of the line
88 PgUp
, // Move one page up
89 PgDn
, // Move one page down
90 CtrlPgUp
, // Move caret to the first visible char in the viewport
91 CtrlPgDn
, // Move caret to the last visible char in the viewport
92 CtrlHome
, // Move to the beginning of the document
93 CtrlEnd
, // Move to the end of the document
94 WordBack
, // Move to the beginning of the previous word (or beginning of line)
95 WordForward
, // Move to the beginning of the next word (or end of line)
96 SelectionStart
, // Move to the beginning of the current selection
97 SelectionEnd
// Move to the end of the current selection
100 // Being cloneable should allow for nice line and document copies...
101 internal class Line
: ICloneable
, IComparable
{
102 #region Local Variables
103 // Stuff that matters for our line
104 internal StringBuilder text
; // Characters for the line
105 internal float[] widths
; // Width of each character; always one larger than text.Length
106 internal int space
; // Number of elements in text and widths
107 internal int line_no
; // Line number
108 internal LineTag tags
; // Tags describing the text
109 internal int Y
; // Baseline
110 internal int height
; // Height of the line (height of tallest tag)
111 internal int ascent
; // Ascent of the line (ascent of the tallest tag)
112 internal HorizontalAlignment alignment
; // Alignment of the line
113 internal int align_shift
; // Pixel shift caused by the alignment
114 internal bool soft_break
; // Tag is 'broken soft' and continuation from previous line
115 internal int indent
; // Left indent for the first line
116 internal int hanging_indent
; // Hanging indent (left indent for all but the first line)
117 internal int right_indent
; // Right indent for all lines
120 // Stuff that's important for the tree
121 internal Line parent
; // Our parent line
122 internal Line left
; // Line with smaller line number
123 internal Line right
; // Line with higher line number
124 internal LineColor color
; // We're doing a black/red tree. this is the node color
125 internal int DEFAULT_TEXT_LEN
; //
126 internal static StringFormat string_format
; // For calculating widths/heights
127 internal bool recalc
; // Line changed
128 #endregion // Local Variables
132 color
= LineColor
.Red
;
139 alignment
= HorizontalAlignment
.Left
;
141 if (string_format
== null) {
142 string_format
= new StringFormat(StringFormat
.GenericTypographic
);
143 string_format
.Trimming
= StringTrimming
.None
;
144 string_format
.FormatFlags
= StringFormatFlags
.MeasureTrailingSpaces
;
148 internal Line(int LineNo
, string Text
, Font font
, Brush color
) : this() {
149 space
= Text
.Length
> DEFAULT_TEXT_LEN
? Text
.Length
+1 : DEFAULT_TEXT_LEN
;
151 text
= new StringBuilder(Text
, space
);
154 widths
= new float[space
+ 1];
155 tags
= new LineTag(this, 1, text
.Length
);
160 internal Line(int LineNo
, string Text
, HorizontalAlignment align
, Font font
, Brush color
) : this() {
161 space
= Text
.Length
> DEFAULT_TEXT_LEN
? Text
.Length
+1 : DEFAULT_TEXT_LEN
;
163 text
= new StringBuilder(Text
, space
);
167 widths
= new float[space
+ 1];
168 tags
= new LineTag(this, 1, text
.Length
);
173 internal Line(int LineNo
, string Text
, LineTag tag
) : this() {
174 space
= Text
.Length
> DEFAULT_TEXT_LEN
? Text
.Length
+1 : DEFAULT_TEXT_LEN
;
176 text
= new StringBuilder(Text
, space
);
179 widths
= new float[space
+ 1];
183 #endregion // Constructors
185 #region Internal Properties
186 internal int Indent
{
197 internal int HangingIndent
{
199 return hanging_indent
;
203 hanging_indent
= value;
208 internal int RightIndent
{
214 right_indent
= value;
220 internal int Height
{
230 internal int LineNo
{
240 internal string Text
{
242 return text
.ToString();
246 text
= new StringBuilder(value, value.Length
> DEFAULT_TEXT_LEN
? value.Length
: DEFAULT_TEXT_LEN
);
250 internal HorizontalAlignment Alignment
{
256 if (alignment
!= value) {
263 internal StringBuilder Text
{
273 #endregion // Internal Properties
275 #region Internal Methods
276 // Make sure we always have enoughs space in text and widths
277 internal void Grow(int minimum
) {
281 length
= text
.Length
;
283 if ((length
+ minimum
) > space
) {
284 // We need to grow; double the size
286 if ((length
+ minimum
) > (space
* 2)) {
287 new_widths
= new float[length
+ minimum
* 2 + 1];
288 space
= length
+ minimum
* 2;
290 new_widths
= new float[space
* 2 + 1];
293 widths
.CopyTo(new_widths
, 0);
299 internal void Streamline(int lines
) {
306 // Catch what the loop below wont; eliminate 0 length
307 // tags, but only if there are other tags after us
308 while ((current
.length
== 0) && (next
!= null)) {
310 tags
.previous
= null;
319 while (next
!= null) {
320 // Take out 0 length tags unless it's the last tag in the document
321 if (next
.length
== 0) {
322 if ((next
.next
!= null) || (line_no
!= lines
)) {
323 current
.next
= next
.next
;
324 if (current
.next
!= null) {
325 current
.next
.previous
= current
;
331 if (current
.Combine(next
)) {
336 current
= current
.next
;
341 /// <summary> Find the tag on a line based on the character position, pos is 0-based</summary>
342 internal LineTag
FindTag(int pos
) {
351 if (pos
>= text
.Length
) {
352 pos
= text
.Length
- 1;
355 while (tag
!= null) {
356 if (((tag
.start
- 1) <= pos
) && (pos
< (tag
.start
+ tag
.length
- 1))) {
365 /// Recalculate a single line using the same char for every character in the line
368 internal bool RecalculatePasswordLine(Graphics g
, Document doc
) {
377 len
= this.text
.Length
;
387 w
= g
.MeasureString(doc
.password_char
, tags
.font
, 10000, string_format
).Width
;
389 if (this.height
!= (int)tag
.font
.Height
) {
395 this.height
= (int)tag
.font
.Height
;
396 tag
.height
= this.height
;
398 XplatUI
.GetFontMetrics(g
, tag
.font
, out tag
.ascent
, out descent
);
399 this.ascent
= tag
.ascent
;
404 widths
[pos
] = widths
[pos
-1] + w
;
411 /// Go through all tags on a line and recalculate all size-related values;
412 /// returns true if lineheight changed
414 internal bool RecalculateLine(Graphics g
, Document doc
) {
428 len
= this.text
.Length
;
430 prev_height
= this.height
; // For drawing optimization calculations
431 this.height
= 0; // Reset line height
432 this.ascent
= 0; // Reset the ascent for the line
436 if (this.soft_break
) {
437 widths
[0] = hanging_indent
;
450 size
= g
.MeasureString(this.text
.ToString(pos
, 1), tag
.font
, 10000, string_format
);
452 while (tag
.length
== 0) { // We should always have tags after a tag.length==0 unless len==0
455 if (tag
.previous
!= null) {
456 tag
.X
= tag
.previous
.X
;
458 tag
.X
= (int)widths
[pos
];
467 if (Char
.IsWhiteSpace(text
[pos
])) {
469 wrap_width
= tag
.width
+ w
;
473 if ((wrap_pos
> 0) && (wrap_pos
!= len
) && (widths
[pos
] + w
) + 27 > (doc
.viewport_width
- this.right_indent
)) {
475 tag
.width
= wrap_width
;
476 doc
.Split(this, tag
, pos
, true);
477 len
= this.text
.Length
;
483 // Contract all soft lines that follow back into our line
489 widths
[pos
] = widths
[pos
-1] + w
;
492 line
= doc
.GetLine(this.line_no
+ 1);
493 if ((line
!= null) && (line
.soft_break
)) {
494 // Pull the previous line back into this one
495 doc
.Combine(this.line_no
, this.line_no
+ 1);
496 len
= this.text
.Length
;
502 if (pos
== (tag
.start
-1 + tag
.length
)) {
503 // We just found the end of our current tag
504 tag
.height
= (int)tag
.font
.Height
;
506 // Check if we're the tallest on the line (so far)
507 if (tag
.height
> this.height
) {
508 this.height
= tag
.height
; // Yep; make sure the line knows
511 if (tag
.ascent
== 0) {
514 XplatUI
.GetFontMetrics(g
, tag
.font
, out tag
.ascent
, out descent
);
517 if (tag
.ascent
> this.ascent
) {
520 // We have a tag that has a taller ascent than the line;
524 t
.shift
= tag
.ascent
- t
.ascent
;
529 this.ascent
= tag
.ascent
;
531 tag
.shift
= this.ascent
- tag
.ascent
;
534 // Update our horizontal starting pixel position
535 if (tag
.previous
== null) {
536 tag
.X
= (int)widths
[0];
538 tag
.X
= tag
.previous
.X
+ (int)tag
.previous
.width
;
546 wrap_width
= tag
.width
;
551 if (this.height
== 0) {
552 this.height
= tags
.font
.Height
;
553 tag
.height
= this.height
;
556 if (prev_height
!= this.height
) {
561 #endregion // Internal Methods
563 #region Administrative
564 public int CompareTo(object obj
) {
569 if (! (obj
is Line
)) {
570 throw new ArgumentException("Object is not of type Line", "obj");
573 if (line_no
< ((Line
)obj
).line_no
) {
575 } else if (line_no
> ((Line
)obj
).line_no
) {
582 public object Clone() {
590 clone
.left
= (Line
)left
.Clone();
594 clone
.left
= (Line
)left
.Clone();
600 internal object CloneLine() {
610 public override bool Equals(object obj
) {
615 if (!(obj
is Line
)) {
623 if (line_no
== ((Line
)obj
).line_no
) {
630 public override int GetHashCode() {
631 return base.GetHashCode ();
634 public override string ToString() {
635 return "Line " + line_no
;
638 #endregion // Administrative
641 internal class Document
: ICloneable
, IEnumerable
{
643 // FIXME - go through code and check for places where
644 // we do explicit comparisons instead of using the compare overloads
645 internal struct Marker
{
647 internal LineTag tag
;
651 public static bool operator<(Marker lhs
, Marker rhs
) {
652 if (lhs
.line
.line_no
< rhs
.line
.line_no
) {
656 if (lhs
.line
.line_no
== rhs
.line
.line_no
) {
657 if (lhs
.pos
< rhs
.pos
) {
664 public static bool operator>(Marker lhs
, Marker rhs
) {
665 if (lhs
.line
.line_no
> rhs
.line
.line_no
) {
669 if (lhs
.line
.line_no
== rhs
.line
.line_no
) {
670 if (lhs
.pos
> rhs
.pos
) {
677 public static bool operator==(Marker lhs
, Marker rhs
) {
678 if ((lhs
.line
.line_no
== rhs
.line
.line_no
) && (lhs
.pos
== rhs
.pos
)) {
684 public static bool operator!=(Marker lhs
, Marker rhs
) {
685 if ((lhs
.line
.line_no
!= rhs
.line
.line_no
) || (lhs
.pos
!= rhs
.pos
)) {
691 public void Combine(Line move_to_line
, int move_to_line_length
) {
693 pos
+= move_to_line_length
;
694 tag
= LineTag
.FindTag(line
, pos
);
697 // This is for future use, right now Document.Split does it by hand, with some added shortcut logic
698 public void Split(Line move_to_line
, int split_at
) {
701 tag
= LineTag
.FindTag(line
, pos
);
704 public override bool Equals(object obj
) {
705 return this==(Marker
)obj
;
708 public override int GetHashCode() {
709 return base.GetHashCode ();
712 public override string ToString() {
713 return "Marker Line " + line
+ ", Position " + pos
;
717 #endregion Structures
719 #region Local Variables
720 private Line document
;
722 private Line sentinel
;
723 private Line last_found
;
724 private int document_id
;
725 private Random random
= new Random();
726 internal string password_char
;
727 private StringBuilder password_cache
;
728 private bool calc_pass
;
729 private int char_count
;
731 private bool no_recalc
;
732 private bool recalc_pending
;
733 private int recalc_start
;
734 private int recalc_end
;
735 private bool recalc_optimize
;
737 internal bool multiline
;
740 internal UndoClass undo
;
742 internal Marker caret
;
743 internal Marker selection_start
;
744 internal Marker selection_end
;
745 internal bool selection_visible
;
746 internal Marker selection_anchor
;
747 internal Marker selection_prev
;
748 internal bool selection_end_anchor
;
750 internal int viewport_x
;
751 internal int viewport_y
; // The visible area of the document
752 internal int viewport_width
;
753 internal int viewport_height
;
755 internal int document_x
; // Width of the document
756 internal int document_y
; // Height of the document
758 internal Rectangle invalid
;
760 internal int crlf_size
; // 1 or 2, depending on whether we use \r\n or just \n
762 internal TextBoxBase owner
; // Who's owning us?
763 static internal int caret_width
= 1;
764 static internal int caret_shift
= 1;
765 #endregion // Local Variables
768 internal Document(TextBoxBase owner
) {
777 recalc_pending
= false;
779 // Tree related stuff
780 sentinel
= new Line();
781 sentinel
.color
= LineColor
.Black
;
784 last_found
= sentinel
;
786 // We always have a blank line
787 owner
.HandleCreated
+= new EventHandler(owner_HandleCreated
);
788 owner
.VisibleChanged
+= new EventHandler(owner_VisibleChanged
);
789 Add(1, "", owner
.Font
, ThemeEngine
.Current
.ResPool
.GetSolidBrush(owner
.ForeColor
));
792 undo
= new UndoClass(this);
794 selection_visible
= false;
795 selection_start
.line
= this.document
;
796 selection_start
.pos
= 0;
797 selection_start
.tag
= selection_start
.line
.tags
;
798 selection_end
.line
= this.document
;
799 selection_end
.pos
= 0;
800 selection_end
.tag
= selection_end
.line
.tags
;
801 selection_anchor
.line
= this.document
;
802 selection_anchor
.pos
= 0;
803 selection_anchor
.tag
= selection_anchor
.line
.tags
;
804 caret
.line
= this.document
;
806 caret
.tag
= caret
.line
.tags
;
813 // Default selection is empty
815 document_id
= random
.Next();
819 #region Internal Properties
836 internal Line CaretLine
{
842 internal int CaretPosition
{
848 internal Point Caret
{
850 return new Point((int)caret
.tag
.line
.widths
[caret
.pos
] + caret
.line
.align_shift
, caret
.line
.Y
);
854 internal LineTag CaretTag
{
864 internal int CRLFSize
{
874 internal string PasswordChar
{
876 return password_char
;
880 password_char
= value;
881 if ((password_char
.Length
!= 0) && (password_char
[0] != '\0')) {
886 password_cache
= new StringBuilder(1024);
887 for (int i
= 0; i
< 1024; i
++) {
888 password_cache
.Append(ch
);
892 password_cache
= null;
897 internal int ViewPortX
{
907 internal int Length
{
909 return char_count
+ lines
- 1; // Add \n for each line but the last
913 private int CharCount
{
921 if (LengthChanged
!= null) {
922 LengthChanged(this, EventArgs
.Empty
);
927 ///<summary>Setting NoRecalc to true will prevent the document from being recalculated.
928 ///This ensures that coordinates of added text are predictable after adding the text even with wrapped view</summary>
929 internal bool NoRecalc
{
936 if (!no_recalc
&& recalc_pending
) {
937 RecalculateDocument(owner
.CreateGraphicsInternal(), recalc_start
, recalc_end
, recalc_optimize
);
938 recalc_pending
= false;
943 internal int ViewPortY
{
953 internal int ViewPortWidth
{
955 return viewport_width
;
959 viewport_width
= value;
963 internal int ViewPortHeight
{
965 return viewport_height
;
969 viewport_height
= value;
976 return this.document_x
;
980 internal int Height
{
982 return this.document_y
;
986 internal bool SelectionVisible
{
988 return selection_visible
;
1002 #endregion // Internal Properties
1004 #region Private Methods
1006 internal int DumpTree(Line line
, bool with_tags
) {
1011 Console
.Write("Line {0} [# {1}], Y: {1} Text {2}", line
.line_no
, line
.GetHashCode(), line
.Y
, line
.text
!= null ? line
.text
.ToString() : "undefined");
1013 if (line
.left
== sentinel
) {
1014 Console
.Write(", left = sentinel");
1015 } else if (line
.left
== null) {
1016 Console
.Write(", left = NULL");
1019 if (line
.right
== sentinel
) {
1020 Console
.Write(", right = sentinel");
1021 } else if (line
.right
== null) {
1022 Console
.Write(", right = NULL");
1025 Console
.WriteLine("");
1035 Console
.Write(" Tags: ");
1036 while (tag
!= null) {
1037 Console
.Write("{0} <{1}>-<{2}> ", count
++, tag
.start
, tag
.length
);
1038 length
+= tag
.length
;
1040 if (tag
.line
!= line
) {
1041 Console
.Write("BAD line link");
1042 throw new Exception("Bad line link in tree");
1046 Console
.Write(", ");
1049 if (length
> line
.text
.Length
) {
1050 throw new Exception(String
.Format("Length of tags more than length of text on line (expected {0} calculated {1})", line
.text
.Length
, length
));
1051 } else if (length
< line
.text
.Length
) {
1052 throw new Exception(String
.Format("Length of tags less than length of text on line (expected {0} calculated {1})", line
.text
.Length
, length
));
1054 Console
.WriteLine("");
1056 if (line
.left
!= null) {
1057 if (line
.left
!= sentinel
) {
1058 total
+= DumpTree(line
.left
, with_tags
);
1061 if (line
!= sentinel
) {
1062 throw new Exception("Left should not be NULL");
1066 if (line
.right
!= null) {
1067 if (line
.right
!= sentinel
) {
1068 total
+= DumpTree(line
.right
, with_tags
);
1071 if (line
!= sentinel
) {
1072 throw new Exception("Right should not be NULL");
1076 for (int i
= 1; i
<= this.lines
; i
++) {
1077 if (GetLine(i
) == null) {
1078 throw new Exception(String
.Format("Hole in line order, missing {0}", i
));
1082 if (line
== this.Root
) {
1083 if (total
< this.lines
) {
1084 throw new Exception(String
.Format("Not enough nodes in tree, found {0}, expected {1}", total
, this.lines
));
1085 } else if (total
> this.lines
) {
1086 throw new Exception(String
.Format("Too many nodes in tree, found {0}, expected {1}", total
, this.lines
));
1093 private void DecrementLines(int line_no
) {
1097 while (current
<= lines
) {
1098 GetLine(current
).line_no
--;
1104 private void IncrementLines(int line_no
) {
1107 current
= this.lines
;
1108 while (current
>= line_no
) {
1109 GetLine(current
).line_no
++;
1115 private void RebalanceAfterAdd(Line line1
) {
1118 while ((line1
!= document
) && (line1
.parent
.color
== LineColor
.Red
)) {
1119 if (line1
.parent
== line1
.parent
.parent
.left
) {
1120 line2
= line1
.parent
.parent
.right
;
1122 if ((line2
!= null) && (line2
.color
== LineColor
.Red
)) {
1123 line1
.parent
.color
= LineColor
.Black
;
1124 line2
.color
= LineColor
.Black
;
1125 line1
.parent
.parent
.color
= LineColor
.Red
;
1126 line1
= line1
.parent
.parent
;
1128 if (line1
== line1
.parent
.right
) {
1129 line1
= line1
.parent
;
1133 line1
.parent
.color
= LineColor
.Black
;
1134 line1
.parent
.parent
.color
= LineColor
.Red
;
1136 RotateRight(line1
.parent
.parent
);
1139 line2
= line1
.parent
.parent
.left
;
1141 if ((line2
!= null) && (line2
.color
== LineColor
.Red
)) {
1142 line1
.parent
.color
= LineColor
.Black
;
1143 line2
.color
= LineColor
.Black
;
1144 line1
.parent
.parent
.color
= LineColor
.Red
;
1145 line1
= line1
.parent
.parent
;
1147 if (line1
== line1
.parent
.left
) {
1148 line1
= line1
.parent
;
1152 line1
.parent
.color
= LineColor
.Black
;
1153 line1
.parent
.parent
.color
= LineColor
.Red
;
1154 RotateLeft(line1
.parent
.parent
);
1158 document
.color
= LineColor
.Black
;
1161 private void RebalanceAfterDelete(Line line1
) {
1164 while ((line1
!= document
) && (line1
.color
== LineColor
.Black
)) {
1165 if (line1
== line1
.parent
.left
) {
1166 line2
= line1
.parent
.right
;
1167 if (line2
.color
== LineColor
.Red
) {
1168 line2
.color
= LineColor
.Black
;
1169 line1
.parent
.color
= LineColor
.Red
;
1170 RotateLeft(line1
.parent
);
1171 line2
= line1
.parent
.right
;
1173 if ((line2
.left
.color
== LineColor
.Black
) && (line2
.right
.color
== LineColor
.Black
)) {
1174 line2
.color
= LineColor
.Red
;
1175 line1
= line1
.parent
;
1177 if (line2
.right
.color
== LineColor
.Black
) {
1178 line2
.left
.color
= LineColor
.Black
;
1179 line2
.color
= LineColor
.Red
;
1181 line2
= line1
.parent
.right
;
1183 line2
.color
= line1
.parent
.color
;
1184 line1
.parent
.color
= LineColor
.Black
;
1185 line2
.right
.color
= LineColor
.Black
;
1186 RotateLeft(line1
.parent
);
1190 line2
= line1
.parent
.left
;
1191 if (line2
.color
== LineColor
.Red
) {
1192 line2
.color
= LineColor
.Black
;
1193 line1
.parent
.color
= LineColor
.Red
;
1194 RotateRight(line1
.parent
);
1195 line2
= line1
.parent
.left
;
1197 if ((line2
.right
.color
== LineColor
.Black
) && (line2
.left
.color
== LineColor
.Black
)) {
1198 line2
.color
= LineColor
.Red
;
1199 line1
= line1
.parent
;
1201 if (line2
.left
.color
== LineColor
.Black
) {
1202 line2
.right
.color
= LineColor
.Black
;
1203 line2
.color
= LineColor
.Red
;
1205 line2
= line1
.parent
.left
;
1207 line2
.color
= line1
.parent
.color
;
1208 line1
.parent
.color
= LineColor
.Black
;
1209 line2
.left
.color
= LineColor
.Black
;
1210 RotateRight(line1
.parent
);
1215 line1
.color
= LineColor
.Black
;
1218 private void RotateLeft(Line line1
) {
1219 Line line2
= line1
.right
;
1221 line1
.right
= line2
.left
;
1223 if (line2
.left
!= sentinel
) {
1224 line2
.left
.parent
= line1
;
1227 if (line2
!= sentinel
) {
1228 line2
.parent
= line1
.parent
;
1231 if (line1
.parent
!= null) {
1232 if (line1
== line1
.parent
.left
) {
1233 line1
.parent
.left
= line2
;
1235 line1
.parent
.right
= line2
;
1242 if (line1
!= sentinel
) {
1243 line1
.parent
= line2
;
1247 private void RotateRight(Line line1
) {
1248 Line line2
= line1
.left
;
1250 line1
.left
= line2
.right
;
1252 if (line2
.right
!= sentinel
) {
1253 line2
.right
.parent
= line1
;
1256 if (line2
!= sentinel
) {
1257 line2
.parent
= line1
.parent
;
1260 if (line1
.parent
!= null) {
1261 if (line1
== line1
.parent
.right
) {
1262 line1
.parent
.right
= line2
;
1264 line1
.parent
.left
= line2
;
1270 line2
.right
= line1
;
1271 if (line1
!= sentinel
) {
1272 line1
.parent
= line2
;
1277 internal void UpdateView(Line line
, int pos
) {
1278 if (!owner
.IsHandleCreated
) {
1283 recalc_start
= line
.line_no
;
1284 recalc_end
= line
.line_no
;
1285 recalc_optimize
= true;
1286 recalc_pending
= true;
1290 // Optimize invalidation based on Line alignment
1291 if (RecalculateDocument(owner
.CreateGraphicsInternal(), line
.line_no
, line
.line_no
, true)) {
1292 // Lineheight changed, invalidate the rest of the document
1293 if ((line
.Y
- viewport_y
) >=0 ) {
1294 // We formatted something that's in view, only draw parts of the screen
1295 owner
.Invalidate(new Rectangle(0, line
.Y
- viewport_y
, viewport_width
, owner
.Height
- line
.Y
- viewport_y
));
1297 // The tag was above the visible area, draw everything
1301 switch(line
.alignment
) {
1302 case HorizontalAlignment
.Left
: {
1303 owner
.Invalidate(new Rectangle((int)line
.widths
[pos
] - viewport_x
- 1, line
.Y
- viewport_y
, viewport_width
, line
.height
+ 1));
1307 case HorizontalAlignment
.Center
: {
1308 owner
.Invalidate(new Rectangle(0, line
.Y
- viewport_y
, viewport_width
, line
.height
+ 1));
1312 case HorizontalAlignment
.Right
: {
1313 owner
.Invalidate(new Rectangle(0, line
.Y
- viewport_y
, (int)line
.widths
[pos
+ 1] - viewport_x
+ line
.align_shift
, line
.height
+ 1));
1321 // Update display from line, down line_count lines; pos is unused, but required for the signature
1322 internal void UpdateView(Line line
, int line_count
, int pos
) {
1323 if (!owner
.IsHandleCreated
) {
1328 recalc_start
= line
.line_no
;
1329 recalc_end
= line
.line_no
+ line_count
- 1;
1330 recalc_optimize
= true;
1331 recalc_pending
= true;
1335 if (RecalculateDocument(owner
.CreateGraphicsInternal(), line
.line_no
, line
.line_no
+ line_count
- 1, true)) {
1336 // Lineheight changed, invalidate the rest of the document
1337 if ((line
.Y
- viewport_y
) >=0 ) {
1338 // We formatted something that's in view, only draw parts of the screen
1339 //blah Console.WriteLine("TextControl.cs(981) Invalidate called in UpdateView(line, line_count, pos)");
1340 owner
.Invalidate(new Rectangle(0, line
.Y
- viewport_y
, viewport_width
, owner
.Height
- line
.Y
- viewport_y
));
1342 // The tag was above the visible area, draw everything
1343 //blah Console.WriteLine("TextControl.cs(985) Invalidate called in UpdateView(line, line_count, pos)");
1349 end_line
= GetLine(line
.line_no
+ line_count
-1);
1350 if (end_line
== null) {
1354 //blah Console.WriteLine("TextControl.cs(996) Invalidate called in UpdateView(line, line_count, pos)");
1355 owner
.Invalidate(new Rectangle(0 - viewport_x
, line
.Y
- viewport_y
, (int)line
.widths
[line
.text
.Length
], end_line
.Y
+ end_line
.height
));
1358 #endregion // Private Methods
1360 #region Internal Methods
1361 // Clear the document and reset state
1362 internal void Empty() {
1364 document
= sentinel
;
1365 last_found
= sentinel
;
1368 // We always have a blank line
1369 Add(1, "", owner
.Font
, ThemeEngine
.Current
.ResPool
.GetSolidBrush(owner
.ForeColor
));
1370 this.RecalculateDocument(owner
.CreateGraphicsInternal());
1371 PositionCaret(0, 0);
1373 selection_visible
= false;
1374 selection_start
.line
= this.document
;
1375 selection_start
.pos
= 0;
1376 selection_start
.tag
= selection_start
.line
.tags
;
1377 selection_end
.line
= this.document
;
1378 selection_end
.pos
= 0;
1379 selection_end
.tag
= selection_end
.line
.tags
;
1389 internal void PositionCaret(Line line
, int pos
) {
1390 if (owner
.IsHandleCreated
) {
1391 undo
.RecordCursor();
1394 caret
.tag
= line
.FindTag(pos
);
1397 caret
.height
= caret
.tag
.height
;
1399 if (owner
.IsHandleCreated
) {
1400 if (owner
.Focused
) {
1401 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
+ caret_shift
);
1404 if (CaretMoved
!= null) CaretMoved(this, EventArgs
.Empty
);
1410 internal void PositionCaret(int x
, int y
) {
1411 if (!owner
.IsHandleCreated
) {
1415 undo
.RecordCursor();
1417 caret
.tag
= FindCursor(x
, y
, out caret
.pos
);
1418 caret
.line
= caret
.tag
.line
;
1419 caret
.height
= caret
.tag
.height
;
1421 if (owner
.Focused
) {
1422 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
+ caret_shift
);
1425 if (CaretMoved
!= null) CaretMoved(this, EventArgs
.Empty
);
1428 internal void CaretHasFocus() {
1429 if ((caret
.tag
!= null) && owner
.IsHandleCreated
) {
1430 XplatUI
.CreateCaret(owner
.Handle
, caret_width
, caret
.height
);
1431 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
+ caret_shift
);
1433 if (!selection_visible
) {
1434 XplatUI
.CaretVisible(owner
.Handle
, true);
1436 XplatUI
.CaretVisible(owner
.Handle
, false);
1441 internal void CaretLostFocus() {
1442 if (!owner
.IsHandleCreated
) {
1445 XplatUI
.DestroyCaret(owner
.Handle
);
1448 internal void AlignCaret() {
1449 if (!owner
.IsHandleCreated
) {
1453 undo
.RecordCursor();
1455 caret
.tag
= LineTag
.FindTag(caret
.line
, caret
.pos
);
1456 caret
.height
= caret
.tag
.height
;
1458 if (owner
.Focused
) {
1459 XplatUI
.CreateCaret(owner
.Handle
, caret_width
, caret
.height
);
1460 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
+ caret_shift
);
1461 XplatUI
.CaretVisible(owner
.Handle
, true);
1464 if (CaretMoved
!= null) CaretMoved(this, EventArgs
.Empty
);
1467 internal void UpdateCaret() {
1468 if (!owner
.IsHandleCreated
|| caret
.tag
== null) {
1472 undo
.RecordCursor();
1474 if (caret
.tag
.height
!= caret
.height
) {
1475 caret
.height
= caret
.tag
.height
;
1476 if (owner
.Focused
) {
1477 XplatUI
.CreateCaret(owner
.Handle
, caret_width
, caret
.height
);
1481 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
+ caret_shift
);
1482 XplatUI
.CaretVisible(owner
.Handle
, true);
1484 if (CaretMoved
!= null) CaretMoved(this, EventArgs
.Empty
);
1487 internal void DisplayCaret() {
1488 if (!owner
.IsHandleCreated
) {
1492 if (owner
.Focused
) {
1493 XplatUI
.CaretVisible(owner
.Handle
, true);
1497 internal void HideCaret() {
1498 if (!owner
.IsHandleCreated
) {
1502 if (owner
.Focused
) {
1503 XplatUI
.CaretVisible(owner
.Handle
, false);
1507 internal void MoveCaret(CaretDirection direction
) {
1508 // FIXME should we use IsWordSeparator to detect whitespace, instead
1509 // of looking for actual spaces in the Word move cases?
1511 case CaretDirection
.CharForward
: {
1513 if (caret
.pos
> caret
.line
.text
.Length
) {
1515 // Go into next line
1516 if (caret
.line
.line_no
< this.lines
) {
1517 caret
.line
= GetLine(caret
.line
.line_no
+1);
1519 caret
.tag
= caret
.line
.tags
;
1524 // Single line; we stay where we are
1528 if ((caret
.tag
.start
- 1 + caret
.tag
.length
) < caret
.pos
) {
1529 caret
.tag
= caret
.tag
.next
;
1536 case CaretDirection
.CharBack
: {
1537 if (caret
.pos
> 0) {
1538 // caret.pos--; // folded into the if below
1539 if (--caret
.pos
> 0) {
1540 if (caret
.tag
.start
> caret
.pos
) {
1541 caret
.tag
= caret
.tag
.previous
;
1545 if (caret
.line
.line_no
> 1) {
1546 caret
.line
= GetLine(caret
.line
.line_no
- 1);
1547 caret
.pos
= caret
.line
.text
.Length
;
1548 caret
.tag
= LineTag
.FindTag(caret
.line
, caret
.pos
);
1555 case CaretDirection
.WordForward
: {
1558 len
= caret
.line
.text
.Length
;
1559 if (caret
.pos
< len
) {
1560 while ((caret
.pos
< len
) && (caret
.line
.text
[caret
.pos
] != ' ')) {
1563 if (caret
.pos
< len
) {
1564 // Skip any whitespace
1565 while ((caret
.pos
< len
) && (caret
.line
.text
[caret
.pos
] == ' ')) {
1569 caret
.tag
= LineTag
.FindTag(caret
.line
, caret
.pos
);
1571 if (caret
.line
.line_no
< this.lines
) {
1572 caret
.line
= GetLine(caret
.line
.line_no
+ 1);
1574 caret
.tag
= caret
.line
.tags
;
1581 case CaretDirection
.WordBack
: {
1582 if (caret
.pos
> 0) {
1585 while ((caret
.pos
> 0) && (caret
.line
.text
[caret
.pos
] == ' ')) {
1589 while ((caret
.pos
> 0) && (caret
.line
.text
[caret
.pos
] != ' ')) {
1593 if (caret
.line
.text
.ToString(caret
.pos
, 1) == " ") {
1594 if (caret
.pos
!= 0) {
1597 caret
.line
= GetLine(caret
.line
.line_no
- 1);
1598 caret
.pos
= caret
.line
.text
.Length
;
1601 caret
.tag
= LineTag
.FindTag(caret
.line
, caret
.pos
);
1603 if (caret
.line
.line_no
> 1) {
1604 caret
.line
= GetLine(caret
.line
.line_no
- 1);
1605 caret
.pos
= caret
.line
.text
.Length
;
1606 caret
.tag
= LineTag
.FindTag(caret
.line
, caret
.pos
);
1613 case CaretDirection
.LineUp
: {
1614 if (caret
.line
.line_no
> 1) {
1617 pixel
= (int)caret
.line
.widths
[caret
.pos
];
1618 PositionCaret(pixel
, GetLine(caret
.line
.line_no
- 1).Y
);
1619 if (!owner
.IsHandleCreated
) {
1622 XplatUI
.CaretVisible(owner
.Handle
, true);
1627 case CaretDirection
.LineDown
: {
1628 if (caret
.line
.line_no
< lines
) {
1631 pixel
= (int)caret
.line
.widths
[caret
.pos
];
1632 PositionCaret(pixel
, GetLine(caret
.line
.line_no
+ 1).Y
);
1633 if (!owner
.IsHandleCreated
) {
1636 XplatUI
.CaretVisible(owner
.Handle
, true);
1641 case CaretDirection
.Home
: {
1642 if (caret
.pos
> 0) {
1644 caret
.tag
= caret
.line
.tags
;
1650 case CaretDirection
.End
: {
1651 if (caret
.pos
< caret
.line
.text
.Length
) {
1652 caret
.pos
= caret
.line
.text
.Length
;
1653 caret
.tag
= LineTag
.FindTag(caret
.line
, caret
.pos
);
1659 case CaretDirection
.PgUp
: {
1664 if ((viewport_y
- viewport_height
) < 0) {
1665 // We're just placing the caret at the end of the document, no scrolling needed
1666 owner
.vscroll
.Value
= 0;
1668 PositionCaret(line
, 0);
1669 XplatUI
.CaretVisible(owner
.Handle
, true);
1674 new_y
= caret
.line
.Y
- viewport_height
;
1677 PositionCaret(line
, 0);
1679 line
= FindTag((int)caret
.line
.widths
[caret
.pos
], caret
.line
.Y
- viewport_height
, out index
, false).line
;
1680 if (caret
.pos
> 0) {
1681 PositionCaret(line
, index
);
1683 PositionCaret(line
, 0);
1687 // Line up to fill line starts
1688 new_y
= viewport_y
- viewport_height
;
1689 line
= FindTag(0, new_y
, out index
, false).line
;
1691 owner
.vscroll
.Value
= line
.Y
;
1693 owner
.vscroll
.Value
= new_y
;
1695 XplatUI
.CaretVisible(owner
.Handle
, true);
1700 case CaretDirection
.PgDn
: {
1705 if ((viewport_y
+ viewport_height
) > document_y
) {
1706 // We're just placing the caret at the end of the document, no scrolling needed
1707 owner
.vscroll
.Value
= owner
.vscroll
.Maximum
;
1708 line
= GetLine(lines
);
1709 PositionCaret(line
, line
.Text
.Length
);
1710 XplatUI
.CaretVisible(owner
.Handle
, true);
1715 new_y
= caret
.line
.Y
+ viewport_height
;
1716 if (new_y
> document_y
) {
1717 line
= GetLine(lines
);
1718 PositionCaret(line
, line
.text
.Length
);
1720 line
= FindTag((int)caret
.line
.widths
[caret
.pos
], caret
.line
.Y
+ viewport_height
, out index
, false).line
;
1721 if (caret
.pos
> 0) {
1722 PositionCaret(line
, index
);
1724 PositionCaret(line
, 0);
1728 // Line up to fill line starts
1729 new_y
= viewport_y
+ viewport_height
;
1730 line
= FindTag(0, new_y
, out index
, false).line
;
1732 if (line
.Y
> owner
.vscroll
.Maximum
) {
1733 owner
.vscroll
.Value
= owner
.vscroll
.Maximum
;
1735 owner
.vscroll
.Value
= line
.Y
;
1738 owner
.vscroll
.Value
= new_y
;
1740 XplatUI
.CaretVisible(owner
.Handle
, true);
1745 case CaretDirection
.CtrlPgUp
: {
1746 PositionCaret(0, viewport_y
);
1747 if (!owner
.IsHandleCreated
) {
1750 XplatUI
.CaretVisible(owner
.Handle
, true);
1754 case CaretDirection
.CtrlPgDn
: {
1759 tag
= FindTag(0, viewport_y
+ viewport_height
, out index
, false);
1760 if (tag
.line
.line_no
> 1) {
1761 line
= GetLine(tag
.line
.line_no
- 1);
1765 PositionCaret(line
, line
.Text
.Length
);
1766 if (!owner
.IsHandleCreated
) {
1769 XplatUI
.CaretVisible(owner
.Handle
, true);
1773 case CaretDirection
.CtrlHome
: {
1774 caret
.line
= GetLine(1);
1776 caret
.tag
= caret
.line
.tags
;
1782 case CaretDirection
.CtrlEnd
: {
1783 caret
.line
= GetLine(lines
);
1784 caret
.pos
= caret
.line
.text
.Length
;
1785 caret
.tag
= LineTag
.FindTag(caret
.line
, caret
.pos
);
1791 case CaretDirection
.SelectionStart
: {
1792 caret
.line
= selection_start
.line
;
1793 caret
.pos
= selection_start
.pos
;
1794 caret
.tag
= selection_start
.tag
;
1800 case CaretDirection
.SelectionEnd
: {
1801 caret
.line
= selection_end
.line
;
1802 caret
.pos
= selection_end
.pos
;
1803 caret
.tag
= selection_end
.tag
;
1811 // Draw the document
1812 internal void Draw(Graphics g
, Rectangle clip
) {
1813 Line line
; // Current line being drawn
1814 LineTag tag
; // Current tag being drawn
1815 int start
; // First line to draw
1816 int end
; // Last line to draw
1817 StringBuilder text
; // String representing the current line
1823 // First, figure out from what line to what line we need to draw
1824 start
= GetLineByPixel(clip
.Top
+ viewport_y
, false).line_no
;
1825 end
= GetLineByPixel(clip
.Bottom
+ viewport_y
, false).line_no
;
1826 //Console.WriteLine("Starting drawing at line {0}, ending at line {1} (clip-bottom:{2})", start, end, clip.Bottom);
1828 // Now draw our elements; try to only draw those that are visible
1832 DateTime n
= DateTime
.Now
;
1833 Console
.WriteLine("Started drawing: {0}s {1}ms", n
.Second
, n
.Millisecond
);
1836 disabled
= ThemeEngine
.Current
.ResPool
.GetSolidBrush(ThemeEngine
.Current
.ColorGrayText
);
1837 hilight
= ThemeEngine
.Current
.ResPool
.GetSolidBrush(ThemeEngine
.Current
.ColorHighlight
);
1838 hilight_text
= ThemeEngine
.Current
.ResPool
.GetSolidBrush(ThemeEngine
.Current
.ColorHighlightText
);
1840 while (line_no
<= end
) {
1841 line
= GetLine(line_no
);
1843 if (owner
.backcolor_set
|| (owner
.Enabled
&& !owner
.read_only
)) {
1844 g
.FillRectangle(ThemeEngine
.Current
.ResPool
.GetSolidBrush(owner
.BackColor
), new Rectangle(clip
.Left
, line
.Y
- viewport_y
, clip
.Width
, line
.Y
- viewport_y
+ line
.Height
));
1846 g
.FillRectangle(ThemeEngine
.Current
.ResPool
.GetSolidBrush(ThemeEngine
.Current
.ColorControl
), new Rectangle(clip
.Left
, line
.Y
- viewport_y
, clip
.Width
, line
.Y
- viewport_y
+ line
.Height
));
1855 // This fails if there's a password > 1024 chars...
1856 text
= this.password_cache
;
1858 while (tag
!= null) {
1859 if (tag
.length
== 0) {
1864 if (((tag
.X
+ tag
.width
) > (clip
.Left
- viewport_x
)) || (tag
.X
< (clip
.Right
- viewport_x
))) {
1865 // Check for selection
1866 if ((!selection_visible
) || (!owner
.ShowSelection
) || (line_no
< selection_start
.line
.line_no
) || (line_no
> selection_end
.line
.line_no
)) {
1867 // regular drawing, no selection to deal with
1868 //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);
1869 if (owner
.is_enabled
) {
1870 g
.DrawString(text
.ToString(tag
.start
-1, tag
.length
), tag
.font
, tag
.color
, tag
.X
+ line
.align_shift
- viewport_x
, line
.Y
+ tag
.shift
- viewport_y
, StringFormat
.GenericTypographic
);
1875 a
= ((SolidBrush
)tag
.color
).Color
;
1876 b
= ThemeEngine
.Current
.ColorWindowText
;
1878 if ((a
.R
== b
.R
) && (a
.G
== b
.G
) && (a
.B
== b
.B
)) {
1879 g
.DrawString(text
.ToString(tag
.start
-1, tag
.length
), tag
.font
, disabled
, tag
.X
+ line
.align_shift
- viewport_x
, line
.Y
+ tag
.shift
- viewport_y
, StringFormat
.GenericTypographic
);
1881 g
.DrawString(text
.ToString(tag
.start
-1, tag
.length
), tag
.font
, tag
.color
, tag
.X
+ line
.align_shift
- viewport_x
, line
.Y
+ tag
.shift
- viewport_y
, StringFormat
.GenericTypographic
);
1885 // we might have to draw our selection
1886 if ((line_no
!= selection_start
.line
.line_no
) && (line_no
!= selection_end
.line
.line_no
)) {
1887 // Special case, whole line is selected, draw this tag selected
1890 tag
.X
+ line
.align_shift
- viewport_x
, // X
1891 line
.Y
+ tag
.shift
- viewport_y
, // Y
1892 line
.widths
[tag
.start
+ tag
.length
- 1], // width
1893 tag
.height
// Height
1897 //s.Substring(tag.start-1, tag.length), // String
1898 text
.ToString(tag
.start
-1, tag
.length
), // String
1900 hilight_text
, // Brush
1901 tag
.X
+ line
.align_shift
- viewport_x
, // X
1902 line
.Y
+ tag
.shift
- viewport_y
, // Y
1903 StringFormat
.GenericTypographic
);
1911 // One or more, but not all tags on the line are selected
1912 if ((selection_start
.tag
== tag
) && (selection_end
.tag
== tag
)) {
1913 // Single tag selected, draw "normalSELECTEDnormal"
1915 // First, the regular part
1917 //s.Substring(tag.start - 1, selection_start.pos - tag.start + 1), // String
1918 text
.ToString(tag
.start
- 1, selection_start
.pos
- tag
.start
+ 1), // String
1921 tag
.X
+ line
.align_shift
- viewport_x
, // X
1922 line
.Y
+ tag
.shift
- viewport_y
, // Y
1923 StringFormat
.GenericTypographic
);
1925 // Now the highlight
1928 line
.widths
[selection_start
.pos
] + line
.align_shift
, // X
1929 line
.Y
+ tag
.shift
- viewport_y
, // Y
1930 line
.widths
[selection_end
.pos
] - line
.widths
[selection_start
.pos
], // Width
1931 tag
.height
); // Height
1934 //s.Substring(selection_start.pos, selection_end.pos - selection_start.pos), // String
1935 text
.ToString(selection_start
.pos
, selection_end
.pos
- selection_start
.pos
), // String
1937 hilight_text
, // Brush
1938 line
.widths
[selection_start
.pos
] + line
.align_shift
- viewport_x
, // X
1939 line
.Y
+ tag
.shift
- viewport_y
, // Y
1940 StringFormat
.GenericTypographic
);
1942 // And back to the regular
1944 //s.Substring(selection_end.pos, tag.start + tag.length - selection_end.pos - 1), // String
1945 text
.ToString(selection_end
.pos
, tag
.start
+ tag
.length
- selection_end
.pos
- 1), // String
1948 line
.widths
[selection_end
.pos
] + line
.align_shift
- viewport_x
, // X
1949 line
.Y
+ tag
.shift
- viewport_y
, // Y
1950 StringFormat
.GenericTypographic
);
1952 } else if (selection_start
.tag
== tag
) {
1955 // The highlighted part
1958 line
.widths
[selection_start
.pos
] + line
.align_shift
,
1959 line
.Y
+ tag
.shift
- viewport_y
,
1960 line
.widths
[tag
.start
+ tag
.length
- 1] - line
.widths
[selection_start
.pos
],
1964 //s.Substring(selection_start.pos, tag.start + tag.length - selection_start.pos - 1), // String
1965 text
.ToString(selection_start
.pos
, tag
.start
+ tag
.length
- selection_start
.pos
- 1), // String
1967 hilight_text
, // Brush
1968 line
.widths
[selection_start
.pos
] + line
.align_shift
- viewport_x
, // X
1969 line
.Y
+ tag
.shift
- viewport_y
, // Y
1970 StringFormat
.GenericTypographic
);
1974 //s.Substring(tag.start - 1, selection_start.pos - tag.start + 1), // String
1975 text
.ToString(tag
.start
- 1, selection_start
.pos
- tag
.start
+ 1), // String
1978 tag
.X
+ line
.align_shift
- viewport_x
, // X
1979 line
.Y
+ tag
.shift
- viewport_y
, // Y
1980 StringFormat
.GenericTypographic
);
1981 } else if (selection_end
.tag
== tag
) {
1984 // The highlighted part
1987 tag
.X
+ line
.align_shift
- viewport_x
,
1988 line
.Y
+ tag
.shift
- viewport_y
,
1989 line
.widths
[selection_end
.pos
] - line
.widths
[tag
.start
- 1],
1993 //s.Substring(tag.start - 1, selection_end.pos - tag.start + 1), // String
1994 text
.ToString(tag
.start
- 1, selection_end
.pos
- tag
.start
+ 1), // String
1996 hilight_text
, // Brush
1997 tag
.X
+ line
.align_shift
- viewport_x
, // X
1998 line
.Y
+ tag
.shift
- viewport_y
, // Y
1999 StringFormat
.GenericTypographic
);
2003 //s.Substring(selection_end.pos, tag.start + tag.length - selection_end.pos - 1), // String
2004 text
.ToString(selection_end
.pos
, tag
.start
+ tag
.length
- selection_end
.pos
- 1), // String
2007 line
.widths
[selection_end
.pos
] + line
.align_shift
- viewport_x
, // X
2008 line
.Y
+ tag
.shift
- viewport_y
, // Y
2009 StringFormat
.GenericTypographic
);
2011 // no partially selected tags here, simple checks...
2012 if (selection_start
.line
== line
) {
2016 begin
= tag
.start
- 1;
2017 stop
= tag
.start
+ tag
.length
- 1;
2018 if (selection_end
.line
== line
) {
2019 if ((begin
>= selection_start
.pos
) && (stop
< selection_end
.pos
)) {
2023 if (stop
> selection_start
.pos
) {
2027 } else if (selection_end
.line
== line
) {
2028 if ((tag
.start
- 1) < selection_end
.pos
) {
2038 tag
.X
+ line
.align_shift
- viewport_x
,
2039 line
.Y
+ tag
.shift
- viewport_y
,
2040 line
.widths
[tag
.start
+ tag
.length
- 1] - line
.widths
[tag
.start
- 1],
2044 //s.Substring(tag.start-1, tag.length), // String
2045 text
.ToString(tag
.start
-1, tag
.length
), // String
2047 hilight_text
, // Brush
2048 tag
.X
+ line
.align_shift
- viewport_x
, // X
2049 line
.Y
+ tag
.shift
- viewport_y
, // Y
2050 StringFormat
.GenericTypographic
);
2053 //s.Substring(tag.start-1, tag.length), // String
2054 text
.ToString(tag
.start
-1, tag
.length
), // String
2057 tag
.X
+ line
.align_shift
- viewport_x
, // X
2058 line
.Y
+ tag
.shift
- viewport_y
, // Y
2059 StringFormat
.GenericTypographic
);
2074 Console
.WriteLine("Finished drawing: {0}s {1}ms", n
.Second
, n
.Millisecond
);
2079 internal void Insert(Line line
, int pos
, string s
) {
2080 Insert(line
, null, pos
, false, s
);
2083 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
2084 internal void Insert(Line line
, LineTag tag
, int pos
, bool update_caret
, string s
) {
2091 // The formatting at the insertion point is used for the inserted text
2093 tag
= LineTag
.FindTag(line
, pos
);
2096 base_line
= line
.line_no
;
2098 ins
= s
.Split(new char[] {'\n'}
);
2100 for (int j
= 0; j
< ins
.Length
; j
++) {
2101 if (ins
[j
].EndsWith("\r")) {
2102 ins
[j
] = ins
[j
].Substring(0, ins
[j
].Length
- 1);
2106 insert_lines
= ins
.Length
;
2108 // Bump the text at insertion point a line down if we're inserting more than one line
2109 if (insert_lines
> 1) {
2111 // Remainder of start line is now in base_line + 1
2114 // Insert the first line
2115 InsertString(tag
, pos
, ins
[0]);
2117 if (insert_lines
> 1) {
2118 for (i
= 1; i
< insert_lines
; i
++) {
2119 Add(base_line
+ i
, ins
[i
], line
.alignment
, tag
.font
, tag
.color
);
2121 if (!s
.EndsWith("\n\n")) {
2122 this.Combine(base_line
+ insert_lines
- 1, base_line
+ insert_lines
);
2126 UpdateView(line
, insert_lines
+ 1, pos
);
2129 // Move caret to the end of the inserted text
2130 if (insert_lines
> 1) {
2131 PositionCaret(GetLine(line
.line_no
+ insert_lines
- 1), ins
[ins
.Length
- 1].Length
);
2133 PositionCaret(line
, pos
+ ins
[0].Length
);
2135 if (owner
.IsHandleCreated
) {
2136 XplatUI
.CaretVisible(owner
.Handle
, true);
2141 // Inserts a character at the given position
2142 internal void InsertString(Line line
, int pos
, string s
) {
2143 InsertString(line
.FindTag(pos
), pos
, s
);
2146 // Inserts a string at the given position
2147 internal void InsertString(LineTag tag
, int pos
, string s
) {
2156 line
.text
.Insert(pos
, s
);
2160 while (tag
!= null) {
2167 UpdateView(line
, pos
);
2170 // Inserts a string at the caret position
2171 internal void InsertStringAtCaret(string s
, bool move_caret
) {
2179 caret
.line
.text
.Insert(caret
.pos
, s
);
2180 caret
.tag
.length
+= len
;
2182 if (caret
.tag
.next
!= null) {
2183 tag
= caret
.tag
.next
;
2184 while (tag
!= null) {
2189 caret
.line
.Grow(len
);
2190 caret
.line
.recalc
= true;
2192 UpdateView(caret
.line
, caret
.pos
);
2201 // Inserts a character at the given position
2202 internal void InsertChar(Line line
, int pos
, char ch
) {
2203 InsertChar(line
.FindTag(pos
), pos
, ch
);
2206 // Inserts a character at the given position
2207 internal void InsertChar(LineTag tag
, int pos
, char ch
) {
2213 line
.text
.Insert(pos
, ch
);
2217 while (tag
!= null) {
2224 UpdateView(line
, pos
);
2227 // Inserts a character at the current caret position
2228 internal void InsertCharAtCaret(char ch
, bool move_caret
) {
2233 caret
.line
.text
.Insert(caret
.pos
, ch
);
2236 if (caret
.tag
.next
!= null) {
2237 tag
= caret
.tag
.next
;
2238 while (tag
!= null) {
2244 caret
.line
.recalc
= true;
2246 UpdateView(caret
.line
, caret
.pos
);
2250 SetSelectionToCaret(true);
2254 // Deletes n characters at the given position; it will not delete past line limits
2256 internal void DeleteChars(LineTag tag
, int pos
, int count
) {
2265 if (pos
== line
.text
.Length
) {
2269 line
.text
.Remove(pos
, count
);
2271 // Make sure the tag points to the right spot
2272 while ((tag
!= null) && (tag
.start
+ tag
.length
- 1) <= pos
) {
2280 // Check if we're crossing tag boundaries
2281 if ((pos
+ count
) > (tag
.start
+ tag
.length
- 1)) {
2284 // We have to delete cross tag boundaries
2288 left
-= tag
.start
+ tag
.length
- pos
- 1;
2289 tag
.length
-= tag
.start
+ tag
.length
- pos
- 1;
2292 while ((tag
!= null) && (left
> 0)) {
2293 tag
.start
-= count
- left
;
2294 if (tag
.length
> left
) {
2305 // We got off easy, same tag
2307 tag
.length
-= count
;
2309 if (tag
.length
== 0) {
2314 // Adjust the start point of any tags following
2317 while (tag
!= null) {
2325 line
.Streamline(lines
);
2328 UpdateView(line
, pos
);
2331 // Deletes a character at or after the given position (depending on forward); it will not delete past line limits
2332 internal void DeleteChar(LineTag tag
, int pos
, bool forward
) {
2341 if ((pos
== 0 && forward
== false) || (pos
== line
.text
.Length
&& forward
== true)) {
2347 line
.text
.Remove(pos
, 1);
2349 while ((tag
!= null) && (tag
.start
+ tag
.length
- 1) <= pos
) {
2359 if (tag
.length
== 0) {
2364 line
.text
.Remove(pos
, 1);
2365 if (pos
>= (tag
.start
- 1)) {
2367 if (tag
.length
== 0) {
2370 } else if (tag
.previous
!= null) {
2371 tag
.previous
.length
--;
2372 if (tag
.previous
.length
== 0) {
2379 while (tag
!= null) {
2385 line
.Streamline(lines
);
2388 UpdateView(line
, pos
);
2391 // Combine two lines
2392 internal void Combine(int FirstLine
, int SecondLine
) {
2393 Combine(GetLine(FirstLine
), GetLine(SecondLine
));
2396 internal void Combine(Line first
, Line second
) {
2400 // Combine the two tag chains into one
2403 while (last
.next
!= null) {
2407 last
.next
= second
.tags
;
2408 last
.next
.previous
= last
;
2410 shift
= last
.start
+ last
.length
- 1;
2412 // Fix up references within the chain
2414 while (last
!= null) {
2416 last
.start
+= shift
;
2420 // Combine both lines' strings
2421 first
.text
.Insert(first
.text
.Length
, second
.text
.ToString());
2422 first
.Grow(first
.text
.Length
);
2424 // Remove the reference to our (now combined) tags from the doomed line
2428 DecrementLines(first
.line_no
+ 2); // first.line_no + 1 will be deleted, so we need to start renumbering one later
2431 first
.recalc
= true;
2432 first
.height
= 0; // This forces RecalcDocument/UpdateView to redraw from this line on
2433 first
.Streamline(lines
);
2435 // Update Caret, Selection, etc
2436 if (caret
.line
== second
) {
2437 caret
.Combine(first
, shift
);
2439 if (selection_anchor
.line
== second
) {
2440 selection_anchor
.Combine(first
, shift
);
2442 if (selection_start
.line
== second
) {
2443 selection_start
.Combine(first
, shift
);
2445 if (selection_end
.line
== second
) {
2446 selection_end
.Combine(first
, shift
);
2453 check_first
= GetLine(first
.line_no
);
2454 check_second
= GetLine(check_first
.line_no
+ 1);
2456 Console
.WriteLine("Pre-delete: Y of first line: {0}, second line: {1}", check_first
.Y
, check_second
.Y
);
2459 this.Delete(second
);
2462 check_first
= GetLine(first
.line_no
);
2463 check_second
= GetLine(check_first
.line_no
+ 1);
2465 Console
.WriteLine("Post-delete Y of first line: {0}, second line: {1}", check_first
.Y
, check_second
.Y
);
2469 // Split the line at the position into two
2470 internal void Split(int LineNo
, int pos
) {
2474 line
= GetLine(LineNo
);
2475 tag
= LineTag
.FindTag(line
, pos
);
2476 Split(line
, tag
, pos
, false);
2479 internal void Split(Line line
, int pos
) {
2482 tag
= LineTag
.FindTag(line
, pos
);
2483 Split(line
, tag
, pos
, false);
2486 ///<summary>Split line at given tag and position into two lines</summary>
2487 ///<param name="soft">True if the split should be marked as 'soft', indicating that it can be contracted
2488 ///if more space becomes available on previous line</param>
2489 internal void Split(Line line
, LineTag tag
, int pos
, bool soft
) {
2493 bool move_sel_start
;
2497 move_sel_start
= false;
2498 move_sel_end
= false;
2500 // Adjust selection and cursors
2501 if (soft
&& (caret
.line
== line
) && (caret
.pos
>= pos
)) {
2504 if (selection_start
.line
== line
&& selection_start
.pos
> pos
) {
2505 move_sel_start
= true;
2508 if (selection_end
.line
== line
&& selection_end
.pos
> pos
) {
2509 move_sel_end
= true;
2512 // cover the easy case first
2513 if (pos
== line
.text
.Length
) {
2514 Add(line
.line_no
+ 1, "", line
.alignment
, tag
.font
, tag
.color
);
2516 new_line
= GetLine(line
.line_no
+ 1);
2520 caret
.line
= new_line
;
2521 caret
.line
.soft_break
= true;
2522 caret
.tag
= new_line
.tags
;
2525 new_line
.soft_break
= true;
2529 if (move_sel_start
) {
2530 selection_start
.line
= new_line
;
2531 selection_start
.pos
= 0;
2532 selection_start
.tag
= new_line
.tags
;
2536 selection_end
.line
= new_line
;
2537 selection_end
.pos
= 0;
2538 selection_end
.tag
= new_line
.tags
;
2543 // We need to move the rest of the text into the new line
2544 Add(line
.line_no
+ 1, line
.text
.ToString(pos
, line
.text
.Length
- pos
), line
.alignment
, tag
.font
, tag
.color
);
2546 // Now transfer our tags from this line to the next
2547 new_line
= GetLine(line
.line_no
+ 1);
2549 new_line
.recalc
= true;
2551 if ((tag
.start
- 1) == pos
) {
2554 // We can simply break the chain and move the tag into the next line
2555 if (tag
== line
.tags
) {
2556 new_tag
= new LineTag(line
, 1, 0);
2557 new_tag
.font
= tag
.font
;
2558 new_tag
.color
= tag
.color
;
2559 line
.tags
= new_tag
;
2562 if (tag
.previous
!= null) {
2563 tag
.previous
.next
= null;
2565 new_line
.tags
= tag
;
2566 tag
.previous
= null;
2567 tag
.line
= new_line
;
2569 // Walk the list and correct the start location of the tags we just bumped into the next line
2570 shift
= tag
.start
- 1;
2573 while (new_tag
!= null) {
2574 new_tag
.start
-= shift
;
2575 new_tag
.line
= new_line
;
2576 new_tag
= new_tag
.next
;
2581 new_tag
= new LineTag(new_line
, 1, tag
.start
- 1 + tag
.length
- pos
);
2582 new_tag
.next
= tag
.next
;
2583 new_tag
.font
= tag
.font
;
2584 new_tag
.color
= tag
.color
;
2585 new_line
.tags
= new_tag
;
2586 if (new_tag
.next
!= null) {
2587 new_tag
.next
.previous
= new_tag
;
2590 tag
.length
= pos
- tag
.start
+ 1;
2593 new_tag
= new_tag
.next
;
2594 while (new_tag
!= null) {
2595 new_tag
.start
-= shift
;
2596 new_tag
.line
= new_line
;
2597 new_tag
= new_tag
.next
;
2604 caret
.line
= new_line
;
2605 caret
.pos
= caret
.pos
- pos
;
2606 caret
.tag
= caret
.line
.FindTag(caret
.pos
);
2608 new_line
.soft_break
= true;
2611 if (move_sel_start
) {
2612 selection_start
.line
= new_line
;
2613 selection_start
.pos
= selection_start
.pos
- pos
;
2614 selection_start
.tag
= new_line
.FindTag(selection_start
.pos
);
2618 selection_end
.line
= new_line
;
2619 selection_end
.pos
= selection_end
.pos
- pos
;
2620 selection_end
.tag
= new_line
.FindTag(selection_end
.pos
);
2623 CharCount
-= line
.text
.Length
- pos
;
2624 line
.text
.Remove(pos
, line
.text
.Length
- pos
);
2627 // Adds a line of text, with given font.
2628 // Bumps any line at that line number that already exists down
2629 internal void Add(int LineNo
, string Text
, Font font
, Brush color
) {
2630 Add(LineNo
, Text
, HorizontalAlignment
.Left
, font
, color
);
2633 internal void Add(int LineNo
, string Text
, HorizontalAlignment align
, Font font
, Brush color
) {
2638 CharCount
+= Text
.Length
;
2640 if (LineNo
<1 || Text
== null) {
2642 throw new ArgumentNullException("LineNo", "Line numbers must be positive");
2644 throw new ArgumentNullException("Text", "Cannot insert NULL line");
2648 add = new Line(LineNo
, Text
, align
, font
, color
);
2651 while (line
!= sentinel
) {
2653 line_no
= line
.line_no
;
2655 if (LineNo
> line_no
) {
2657 } else if (LineNo
< line_no
) {
2660 // Bump existing line numbers; walk all nodes to the right of this one and increment line_no
2661 IncrementLines(line
.line_no
);
2666 add.left
= sentinel
;
2667 add.right
= sentinel
;
2669 if (add.parent
!= null) {
2670 if (LineNo
> add.parent
.line_no
) {
2671 add.parent
.right
= add;
2673 add.parent
.left
= add;
2680 RebalanceAfterAdd(add);
2685 internal virtual void Clear() {
2688 document
= sentinel
;
2691 public virtual object Clone() {
2694 clone
= new Document(null);
2696 clone
.lines
= this.lines
;
2697 clone
.document
= (Line
)document
.Clone();
2702 internal void Delete(int LineNo
) {
2709 line
= GetLine(LineNo
);
2711 CharCount
-= line
.text
.Length
;
2713 DecrementLines(LineNo
+ 1);
2717 internal void Delete(Line line1
) {
2718 Line line2
;// = new Line();
2721 if ((line1
.left
== sentinel
) || (line1
.right
== sentinel
)) {
2724 line3
= line1
.right
;
2725 while (line3
.left
!= sentinel
) {
2730 if (line3
.left
!= sentinel
) {
2733 line2
= line3
.right
;
2736 line2
.parent
= line3
.parent
;
2737 if (line3
.parent
!= null) {
2738 if(line3
== line3
.parent
.left
) {
2739 line3
.parent
.left
= line2
;
2741 line3
.parent
.right
= line2
;
2747 if (line3
!= line1
) {
2750 if (selection_start
.line
== line3
) {
2751 selection_start
.line
= line1
;
2754 if (selection_end
.line
== line3
) {
2755 selection_end
.line
= line1
;
2758 if (selection_anchor
.line
== line3
) {
2759 selection_anchor
.line
= line1
;
2762 if (caret
.line
== line3
) {
2767 line1
.alignment
= line3
.alignment
;
2768 line1
.ascent
= line3
.ascent
;
2769 line1
.hanging_indent
= line3
.hanging_indent
;
2770 line1
.height
= line3
.height
;
2771 line1
.indent
= line3
.indent
;
2772 line1
.line_no
= line3
.line_no
;
2773 line1
.recalc
= line3
.recalc
;
2774 line1
.right_indent
= line3
.right_indent
;
2775 line1
.soft_break
= line3
.soft_break
;
2776 line1
.space
= line3
.space
;
2777 line1
.tags
= line3
.tags
;
2778 line1
.text
= line3
.text
;
2779 line1
.widths
= line3
.widths
;
2783 while (tag
!= null) {
2789 if (line3
.color
== LineColor
.Black
)
2790 RebalanceAfterDelete(line2
);
2794 last_found
= sentinel
;
2797 // Invalidate a section of the document to trigger redraw
2798 internal void Invalidate(Line start
, int start_pos
, Line end
, int end_pos
) {
2804 if ((start
== end
) && (start_pos
== end_pos
)) {
2808 if (end_pos
== -1) {
2809 end_pos
= end
.text
.Length
;
2812 // figure out what's before what so the logic below is straightforward
2813 if (start
.line_no
< end
.line_no
) {
2819 } else if (start
.line_no
> end
.line_no
) {
2826 if (start_pos
< end_pos
) {
2841 Console
.WriteLine("Invaliding from {0}:{1} to {2}:{3}", l1
.line_no
, p1
, l2
.line_no
, p2
);
2846 (int)l1
.widths
[p1
] + l1
.align_shift
- viewport_x
,
2848 (int)l2
.widths
[p2
] - (int)l1
.widths
[p1
] + 1,
2856 Console
.WriteLine("Invaliding from {0}:{1} to {2}:{3} Start => x={4}, y={5}, {6}x{7}", l1
.line_no
, p1
, l2
.line_no
, p2
, (int)l1
.widths
[p1
] + l1
.align_shift
- viewport_x
, l1
.Y
- viewport_y
, viewport_width
, l1
.height
);
2859 // Three invalidates:
2860 // First line from start
2861 owner
.Invalidate(new Rectangle((int)l1
.widths
[p1
] + l1
.align_shift
- viewport_x
, l1
.Y
- viewport_y
, viewport_width
, l1
.height
));
2864 if ((l1
.line_no
+ 1) < l2
.line_no
) {
2867 y
= GetLine(l1
.line_no
+ 1).Y
;
2868 owner
.Invalidate(new Rectangle(0, y
- viewport_y
, viewport_width
, GetLine(l2
.line_no
).Y
- y
- viewport_y
));
2871 Console
.WriteLine("Invaliding from {0}:{1} to {2}:{3} Middle => x={4}, y={5}, {6}x{7}", l1
.line_no
, p1
, l2
.line_no
, p2
, 0, y
- viewport_y
, viewport_width
, GetLine(l2
.line_no
).Y
- y
- viewport_y
);
2876 owner
.Invalidate(new Rectangle((int)l2
.widths
[0] + l2
.align_shift
- viewport_x
, l2
.Y
- viewport_y
, (int)l2
.widths
[p2
] + 1, l2
.height
));
2878 Console
.WriteLine("Invaliding from {0}:{1} to {2}:{3} End => x={4}, y={5}, {6}x{7}", l1
.line_no
, p1
, l2
.line_no
, p2
, (int)l2
.widths
[0] + l2
.align_shift
- viewport_x
, l2
.Y
- viewport_y
, (int)l2
.widths
[p2
] + 1, l2
.height
);
2882 /// <summary>Select text around caret</summary>
2883 internal void ExpandSelection(CaretSelection mode
, bool to_caret
) {
2885 // We're expanding the selection to the caret position
2887 case CaretSelection
.Line
: {
2888 // Invalidate the selection delta
2889 if (caret
> selection_prev
) {
2890 Invalidate(selection_prev
.line
, 0, caret
.line
, caret
.line
.text
.Length
);
2892 Invalidate(selection_prev
.line
, selection_prev
.line
.text
.Length
, caret
.line
, 0);
2895 if (caret
.line
.line_no
<= selection_anchor
.line
.line_no
) {
2896 selection_start
.line
= caret
.line
;
2897 selection_start
.tag
= caret
.line
.tags
;
2898 selection_start
.pos
= 0;
2900 selection_end
.line
= selection_anchor
.line
;
2901 selection_end
.tag
= selection_anchor
.tag
;
2902 selection_end
.pos
= selection_anchor
.pos
;
2904 selection_end_anchor
= true;
2906 selection_start
.line
= selection_anchor
.line
;
2907 selection_start
.pos
= selection_anchor
.height
;
2908 selection_start
.tag
= selection_anchor
.line
.FindTag(selection_anchor
.height
);
2910 selection_end
.line
= caret
.line
;
2911 selection_end
.tag
= caret
.line
.tags
;
2912 selection_end
.pos
= caret
.line
.text
.Length
;
2914 selection_end_anchor
= false;
2916 selection_prev
.line
= caret
.line
;
2917 selection_prev
.tag
= caret
.tag
;
2918 selection_prev
.pos
= caret
.pos
;
2923 case CaretSelection
.Word
: {
2927 start_pos
= FindWordSeparator(caret
.line
, caret
.pos
, false);
2928 end_pos
= FindWordSeparator(caret
.line
, caret
.pos
, true);
2931 // Invalidate the selection delta
2932 if (caret
> selection_prev
) {
2933 Invalidate(selection_prev
.line
, selection_prev
.pos
, caret
.line
, end_pos
);
2935 Invalidate(selection_prev
.line
, selection_prev
.pos
, caret
.line
, start_pos
);
2937 if (caret
< selection_anchor
) {
2938 selection_start
.line
= caret
.line
;
2939 selection_start
.tag
= caret
.line
.FindTag(start_pos
);
2940 selection_start
.pos
= start_pos
;
2942 selection_end
.line
= selection_anchor
.line
;
2943 selection_end
.tag
= selection_anchor
.tag
;
2944 selection_end
.pos
= selection_anchor
.pos
;
2946 selection_prev
.line
= caret
.line
;
2947 selection_prev
.tag
= caret
.tag
;
2948 selection_prev
.pos
= start_pos
;
2950 selection_end_anchor
= true;
2952 selection_start
.line
= selection_anchor
.line
;
2953 selection_start
.pos
= selection_anchor
.height
;
2954 selection_start
.tag
= selection_anchor
.line
.FindTag(selection_anchor
.height
);
2956 selection_end
.line
= caret
.line
;
2957 selection_end
.tag
= caret
.line
.FindTag(end_pos
);
2958 selection_end
.pos
= end_pos
;
2960 selection_prev
.line
= caret
.line
;
2961 selection_prev
.tag
= caret
.tag
;
2962 selection_prev
.pos
= end_pos
;
2964 selection_end_anchor
= false;
2969 case CaretSelection
.Position
: {
2970 SetSelectionToCaret(false);
2975 // We're setting the selection 'around' the caret position
2977 case CaretSelection
.Line
: {
2978 this.Invalidate(caret
.line
, 0, caret
.line
, caret
.line
.text
.Length
);
2980 selection_start
.line
= caret
.line
;
2981 selection_start
.tag
= caret
.line
.tags
;
2982 selection_start
.pos
= 0;
2984 selection_end
.line
= caret
.line
;
2985 selection_end
.pos
= caret
.line
.text
.Length
;
2986 selection_end
.tag
= caret
.line
.FindTag(selection_end
.pos
);
2988 selection_anchor
.line
= selection_end
.line
;
2989 selection_anchor
.tag
= selection_end
.tag
;
2990 selection_anchor
.pos
= selection_end
.pos
;
2991 selection_anchor
.height
= 0;
2993 selection_prev
.line
= caret
.line
;
2994 selection_prev
.tag
= caret
.tag
;
2995 selection_prev
.pos
= caret
.pos
;
2997 this.selection_end_anchor
= true;
3002 case CaretSelection
.Word
: {
3006 start_pos
= FindWordSeparator(caret
.line
, caret
.pos
, false);
3007 end_pos
= FindWordSeparator(caret
.line
, caret
.pos
, true);
3009 this.Invalidate(selection_start
.line
, start_pos
, caret
.line
, end_pos
);
3011 selection_start
.line
= caret
.line
;
3012 selection_start
.tag
= caret
.line
.FindTag(start_pos
);
3013 selection_start
.pos
= start_pos
;
3015 selection_end
.line
= caret
.line
;
3016 selection_end
.tag
= caret
.line
.FindTag(end_pos
);
3017 selection_end
.pos
= end_pos
;
3019 selection_anchor
.line
= selection_end
.line
;
3020 selection_anchor
.tag
= selection_end
.tag
;
3021 selection_anchor
.pos
= selection_end
.pos
;
3022 selection_anchor
.height
= start_pos
;
3024 selection_prev
.line
= caret
.line
;
3025 selection_prev
.tag
= caret
.tag
;
3026 selection_prev
.pos
= caret
.pos
;
3028 this.selection_end_anchor
= true;
3035 if (selection_start
== selection_end
) {
3036 selection_visible
= false;
3038 selection_visible
= true;
3042 internal void SetSelectionToCaret(bool start
) {
3044 // Invalidate old selection; selection is being reset to empty
3045 this.Invalidate(selection_start
.line
, selection_start
.pos
, selection_end
.line
, selection_end
.pos
);
3047 selection_start
.line
= caret
.line
;
3048 selection_start
.tag
= caret
.tag
;
3049 selection_start
.pos
= caret
.pos
;
3051 // start always also selects end
3052 selection_end
.line
= caret
.line
;
3053 selection_end
.tag
= caret
.tag
;
3054 selection_end
.pos
= caret
.pos
;
3056 selection_anchor
.line
= caret
.line
;
3057 selection_anchor
.tag
= caret
.tag
;
3058 selection_anchor
.pos
= caret
.pos
;
3060 // Invalidate from previous end to caret (aka new end)
3061 if (selection_end_anchor
) {
3062 if (selection_start
!= caret
) {
3063 this.Invalidate(selection_start
.line
, selection_start
.pos
, caret
.line
, caret
.pos
);
3066 if (selection_end
!= caret
) {
3067 this.Invalidate(selection_end
.line
, selection_end
.pos
, caret
.line
, caret
.pos
);
3071 if (caret
< selection_anchor
) {
3072 selection_start
.line
= caret
.line
;
3073 selection_start
.tag
= caret
.tag
;
3074 selection_start
.pos
= caret
.pos
;
3076 selection_end
.line
= selection_anchor
.line
;
3077 selection_end
.tag
= selection_anchor
.tag
;
3078 selection_end
.pos
= selection_anchor
.pos
;
3080 selection_end_anchor
= true;
3082 selection_start
.line
= selection_anchor
.line
;
3083 selection_start
.tag
= selection_anchor
.tag
;
3084 selection_start
.pos
= selection_anchor
.pos
;
3086 selection_end
.line
= caret
.line
;
3087 selection_end
.tag
= caret
.tag
;
3088 selection_end
.pos
= caret
.pos
;
3090 selection_end_anchor
= false;
3094 if (selection_start
== selection_end
) {
3095 selection_visible
= false;
3097 selection_visible
= true;
3101 internal void SetSelection(Line start
, int start_pos
, Line end
, int end_pos
) {
3102 if (selection_visible
) {
3103 Invalidate(selection_start
.line
, selection_start
.pos
, selection_end
.line
, selection_end
.pos
);
3106 if ((end
.line_no
< start
.line_no
) || ((end
== start
) && (end_pos
<= start_pos
))) {
3107 selection_start
.line
= end
;
3108 selection_start
.tag
= LineTag
.FindTag(end
, end_pos
);
3109 selection_start
.pos
= end_pos
;
3111 selection_end
.line
= start
;
3112 selection_end
.tag
= LineTag
.FindTag(start
, start_pos
);
3113 selection_end
.pos
= start_pos
;
3115 selection_end_anchor
= true;
3117 selection_start
.line
= start
;
3118 selection_start
.tag
= LineTag
.FindTag(start
, start_pos
);
3119 selection_start
.pos
= start_pos
;
3121 selection_end
.line
= end
;
3122 selection_end
.tag
= LineTag
.FindTag(end
, end_pos
);
3123 selection_end
.pos
= end_pos
;
3125 selection_end_anchor
= false;
3128 selection_anchor
.line
= start
;
3129 selection_anchor
.tag
= selection_start
.tag
;
3130 selection_anchor
.pos
= start_pos
;
3132 if (((start
== end
) && (start_pos
== end_pos
)) || start
== null || end
== null) {
3133 selection_visible
= false;
3135 selection_visible
= true;
3140 internal void SetSelectionStart(Line start
, int start_pos
) {
3141 // Invalidate from the previous to the new start pos
3142 Invalidate(selection_start
.line
, selection_start
.pos
, start
, start_pos
);
3144 selection_start
.line
= start
;
3145 selection_start
.pos
= start_pos
;
3146 selection_start
.tag
= LineTag
.FindTag(start
, start_pos
);
3148 selection_anchor
.line
= start
;
3149 selection_anchor
.pos
= start_pos
;
3150 selection_anchor
.tag
= selection_start
.tag
;
3152 selection_end_anchor
= false;
3154 if ((selection_end
.line
!= selection_start
.line
) || (selection_end
.pos
!= selection_start
.pos
)) {
3155 selection_visible
= true;
3157 // This could be calculated better
3158 Invalidate(selection_start
.line
, selection_start
.pos
, selection_end
.line
, selection_end
.pos
);
3163 internal void SetSelectionStart(int character_index
) {
3168 if (character_index
< 0) {
3172 CharIndexToLineTag(character_index
, out line
, out tag
, out pos
);
3173 SetSelectionStart(line
, pos
);
3176 internal void SetSelectionEnd(Line end
, int end_pos
) {
3177 if ((end
.line_no
< selection_anchor
.line
.line_no
) || ((end
== selection_anchor
.line
) && (end_pos
<= selection_anchor
.pos
))) {
3178 selection_start
.line
= end
;
3179 selection_start
.tag
= LineTag
.FindTag(end
, end_pos
);
3180 selection_start
.pos
= end_pos
;
3182 selection_end
.line
= selection_anchor
.line
;
3183 selection_end
.tag
= selection_anchor
.tag
;
3184 selection_end
.pos
= selection_anchor
.pos
;
3186 selection_end_anchor
= true;
3188 selection_start
.line
= selection_anchor
.line
;
3189 selection_start
.tag
= selection_anchor
.tag
;
3190 selection_start
.pos
= selection_anchor
.pos
;
3192 selection_end
.line
= end
;
3193 selection_end
.tag
= LineTag
.FindTag(end
, end_pos
);
3194 selection_end
.pos
= end_pos
;
3196 selection_end_anchor
= false;
3199 if ((selection_end
.line
!= selection_start
.line
) || (selection_end
.pos
!= selection_start
.pos
)) {
3200 selection_visible
= true;
3201 Invalidate(selection_start
.line
, selection_start
.pos
, selection_end
.line
, selection_end
.pos
);
3205 internal void SetSelectionEnd(int character_index
) {
3210 if (character_index
< 0) {
3214 CharIndexToLineTag(character_index
, out line
, out tag
, out pos
);
3215 SetSelectionEnd(line
, pos
);
3218 internal void SetSelection(Line start
, int start_pos
) {
3219 if (selection_visible
) {
3220 Invalidate(selection_start
.line
, selection_start
.pos
, selection_end
.line
, selection_end
.pos
);
3223 selection_start
.line
= start
;
3224 selection_start
.pos
= start_pos
;
3225 selection_start
.tag
= LineTag
.FindTag(start
, start_pos
);
3227 selection_end
.line
= start
;
3228 selection_end
.tag
= selection_start
.tag
;
3229 selection_end
.pos
= start_pos
;
3231 selection_anchor
.line
= start
;
3232 selection_anchor
.tag
= selection_start
.tag
;
3233 selection_anchor
.pos
= start_pos
;
3235 selection_end_anchor
= false;
3236 selection_visible
= false;
3239 internal void InvalidateSelectionArea() {
3240 Invalidate (selection_start
.line
, selection_start
.pos
, selection_end
.line
, selection_end
.pos
);
3243 // Return the current selection, as string
3244 internal string GetSelection() {
3245 // We return String.Empty if there is no selection
3246 if ((selection_start
.pos
== selection_end
.pos
) && (selection_start
.line
== selection_end
.line
)) {
3247 return string.Empty
;
3250 if (!multiline
|| (selection_start
.line
== selection_end
.line
)) {
3251 return selection_start
.line
.text
.ToString(selection_start
.pos
, selection_end
.pos
- selection_start
.pos
);
3258 sb
= new StringBuilder();
3259 start
= selection_start
.line
.line_no
;
3260 end
= selection_end
.line
.line_no
;
3262 sb
.Append(selection_start
.line
.text
.ToString(selection_start
.pos
, selection_start
.line
.text
.Length
- selection_start
.pos
) + Environment
.NewLine
);
3264 if ((start
+ 1) < end
) {
3265 for (i
= start
+ 1; i
< end
; i
++) {
3266 sb
.Append(GetLine(i
).text
.ToString() + Environment
.NewLine
);
3270 sb
.Append(selection_end
.line
.text
.ToString(0, selection_end
.pos
));
3272 return sb
.ToString();
3276 internal void ReplaceSelection(string s
) {
3279 // First, delete any selected text
3280 if ((selection_start
.pos
!= selection_end
.pos
) || (selection_start
.line
!= selection_end
.line
)) {
3281 if (!multiline
|| (selection_start
.line
== selection_end
.line
)) {
3282 undo
.RecordDeleteChars(selection_start
.line
, selection_start
.pos
+ 1, selection_end
.pos
- selection_start
.pos
);
3284 DeleteChars(selection_start
.tag
, selection_start
.pos
, selection_end
.pos
- selection_start
.pos
);
3286 // The tag might have been removed, we need to recalc it
3287 selection_start
.tag
= selection_start
.line
.FindTag(selection_start
.pos
);
3292 start
= selection_start
.line
.line_no
;
3293 end
= selection_end
.line
.line_no
;
3295 undo
.RecordDelete(selection_start
.line
, selection_start
.pos
+ 1, selection_end
.line
, selection_end
.pos
);
3297 // Delete first line
3298 DeleteChars(selection_start
.tag
, selection_start
.pos
, selection_start
.line
.text
.Length
- selection_start
.pos
);
3301 DeleteChars(selection_end
.line
.tags
, 0, selection_end
.pos
);
3305 for (i
= end
- 1; i
>= start
; i
--) {
3310 // BIG FAT WARNING - selection_end.line might be stale due
3311 // to the above Delete() call. DONT USE IT before hitting the end of this method!
3313 // Join start and end
3314 Combine(selection_start
.line
.line_no
, start
);
3318 Insert(selection_start
.line
, null, selection_start
.pos
, true, s
);
3320 selection_end
.line
= selection_start
.line
;
3321 selection_end
.pos
= selection_start
.pos
;
3322 selection_end
.tag
= selection_start
.tag
;
3324 selection_visible
= false;
3327 internal void CharIndexToLineTag(int index
, out Line line_out
, out LineTag tag_out
, out int pos
) {
3336 for (i
= 1; i
<= lines
; i
++) {
3340 chars
+= line
.text
.Length
+ crlf_size
;
3342 if (index
<= chars
) {
3343 // we found the line
3346 while (tag
!= null) {
3347 if (index
< (start
+ tag
.start
+ tag
.length
)) {
3350 pos
= index
- start
;
3353 if (tag
.next
== null) {
3356 next_line
= GetLine(line
.line_no
+ 1);
3358 if (next_line
!= null) {
3359 line_out
= next_line
;
3360 tag_out
= next_line
.tags
;
3366 pos
= line_out
.text
.Length
;
3375 line_out
= GetLine(lines
);
3376 tag
= line_out
.tags
;
3377 while (tag
.next
!= null) {
3381 pos
= line_out
.text
.Length
;
3384 internal int LineTagToCharIndex(Line line
, int pos
) {
3388 // Count first and last line
3391 // Count the lines in the middle
3393 for (i
= 1; i
< line
.line_no
; i
++) {
3394 length
+= GetLine(i
).text
.Length
+ crlf_size
;
3402 internal int SelectionLength() {
3403 if ((selection_start
.pos
== selection_end
.pos
) && (selection_start
.line
== selection_end
.line
)) {
3407 if (!multiline
|| (selection_start
.line
== selection_end
.line
)) {
3408 return selection_end
.pos
- selection_start
.pos
;
3415 // Count first and last line
3416 length
= selection_start
.line
.text
.Length
- selection_start
.pos
+ selection_end
.pos
+ crlf_size
;
3418 // Count the lines in the middle
3419 start
= selection_start
.line
.line_no
+ 1;
3420 end
= selection_end
.line
.line_no
;
3423 for (i
= start
; i
< end
; i
++) {
3424 length
+= GetLine(i
).text
.Length
+ crlf_size
;
3435 /// <summary>Give it a Line number and it returns the Line object at with that line number</summary>
3436 internal Line
GetLine(int LineNo
) {
3437 Line line
= document
;
3439 while (line
!= sentinel
) {
3440 if (LineNo
== line
.line_no
) {
3442 } else if (LineNo
< line
.line_no
) {
3452 /// <summary>Retrieve the previous tag; walks line boundaries</summary>
3453 internal LineTag
PreviousTag(LineTag tag
) {
3456 if (tag
.previous
!= null) {
3457 return tag
.previous
;
3461 if (tag
.line
.line_no
== 1) {
3465 l
= GetLine(tag
.line
.line_no
- 1);
3470 while (t
.next
!= null) {
3479 /// <summary>Retrieve the next tag; walks line boundaries</summary>
3480 internal LineTag
NextTag(LineTag tag
) {
3483 if (tag
.next
!= null) {
3488 l
= GetLine(tag
.line
.line_no
+ 1);
3496 internal Line
ParagraphStart(Line line
) {
3497 while (line
.soft_break
) {
3498 line
= GetLine(line
.line_no
- 1);
3503 internal Line
ParagraphEnd(Line line
) {
3506 while (line
.soft_break
) {
3507 l
= GetLine(line
.line_no
+ 1);
3508 if ((l
== null) || (!l
.soft_break
)) {
3516 /// <summary>Give it a Y pixel coordinate and it returns the Line covering that Y coordinate</summary>
3517 internal Line
GetLineByPixel(int y
, bool exact
) {
3518 Line line
= document
;
3521 while (line
!= sentinel
) {
3523 if ((y
>= line
.Y
) && (y
< (line
.Y
+line
.height
))) {
3525 } else if (y
< line
.Y
) {
3538 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3539 internal LineTag
FindTag(int x
, int y
, out int index
, bool exact
) {
3543 line
= GetLineByPixel(y
, exact
);
3550 // Alignment adjustment
3551 x
+= line
.align_shift
;
3554 if (x
>= tag
.X
&& x
< (tag
.X
+tag
.width
)) {
3557 end
= tag
.start
+ tag
.length
- 1;
3559 for (int pos
= tag
.start
; pos
< end
; pos
++) {
3560 if (x
< line
.widths
[pos
]) {
3568 if (tag
.next
!= null) {
3576 index
= line
.text
.Length
;
3582 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3583 internal LineTag
FindCursor(int x
, int y
, out int index
) {
3587 line
= GetLineByPixel(y
, false);
3590 // Adjust for alignment
3591 x
-= line
.align_shift
;
3594 if (x
>= tag
.X
&& x
< (tag
.X
+tag
.width
)) {
3597 end
= tag
.start
+ tag
.length
- 1;
3599 for (int pos
= tag
.start
-1; pos
< end
; pos
++) {
3600 // When clicking on a character, we position the cursor to whatever edge
3601 // of the character the click was closer
3602 if (x
< (line
.widths
[pos
] + ((line
.widths
[pos
+1]-line
.widths
[pos
])/2))) {
3610 if (tag
.next
!= null) {
3613 index
= line
.text
.Length
;
3619 /// <summary>Format area of document in specified font and color</summary>
3620 /// <param name="start_pos">1-based start position on start_line</param>
3621 /// <param name="end_pos">1-based end position on end_line </param>
3622 internal void FormatText(Line start_line
, int start_pos
, Line end_line
, int end_pos
, Font font
, Brush color
) {
3625 // First, format the first line
3626 if (start_line
!= end_line
) {
3628 LineTag
.FormatText(start_line
, start_pos
, start_line
.text
.Length
- start_pos
+ 1, font
, color
);
3631 LineTag
.FormatText(end_line
, 1, end_pos
, font
, color
);
3633 // Now all the lines inbetween
3634 for (int i
= start_line
.line_no
+ 1; i
< end_line
.line_no
; i
++) {
3636 LineTag
.FormatText(l
, 1, l
.text
.Length
, font
, color
);
3639 // Special case, single line
3640 LineTag
.FormatText(start_line
, start_pos
, end_pos
- start_pos
, font
, color
);
3644 /// <summary>Re-format areas of the document in specified font and color</summary>
3645 /// <param name="start_pos">1-based start position on start_line</param>
3646 /// <param name="end_pos">1-based end position on end_line </param>
3647 /// <param name="font">Font specifying attributes</param>
3648 /// <param name="color">Color (or NULL) to apply</param>
3649 /// <param name="apply">Attributes from font and color to apply</param>
3650 internal void FormatText(Line start_line
, int start_pos
, Line end_line
, int end_pos
, FontDefinition attributes
) {
3653 // First, format the first line
3654 if (start_line
!= end_line
) {
3656 LineTag
.FormatText(start_line
, start_pos
, start_line
.text
.Length
- start_pos
+ 1, attributes
);
3659 LineTag
.FormatText(end_line
, 1, end_pos
- 1, attributes
);
3661 // Now all the lines inbetween
3662 for (int i
= start_line
.line_no
+ 1; i
< end_line
.line_no
; i
++) {
3664 LineTag
.FormatText(l
, 1, l
.text
.Length
, attributes
);
3667 // Special case, single line
3668 LineTag
.FormatText(start_line
, start_pos
, end_pos
- start_pos
, attributes
);
3672 internal void RecalculateAlignments() {
3678 while (line_no
<= lines
) {
3679 line
= GetLine(line_no
);
3681 if (line
!= null && line
.alignment
!= HorizontalAlignment
.Left
) {
3682 if (line
.alignment
== HorizontalAlignment
.Center
) {
3683 line
.align_shift
= (viewport_width
- (int)line
.widths
[line
.text
.Length
]) / 2;
3685 line
.align_shift
= viewport_width
- (int)line
.widths
[line
.text
.Length
] - 1;
3694 /// <summary>Calculate formatting for the whole document</summary>
3695 internal bool RecalculateDocument(Graphics g
) {
3696 return RecalculateDocument(g
, 1, this.lines
, false);
3699 /// <summary>Calculate formatting starting at a certain line</summary>
3700 internal bool RecalculateDocument(Graphics g
, int start
) {
3701 return RecalculateDocument(g
, start
, this.lines
, false);
3704 /// <summary>Calculate formatting within two given line numbers</summary>
3705 internal bool RecalculateDocument(Graphics g
, int start
, int end
) {
3706 return RecalculateDocument(g
, start
, end
, false);
3709 /// <summary>With optimize on, returns true if line heights changed</summary>
3710 internal bool RecalculateDocument(Graphics g
, int start
, int end
, bool optimize
) {
3719 recalc_pending
= true;
3720 recalc_start
= start
;
3722 recalc_optimize
= optimize
;
3726 Y
= GetLine(start
).Y
;
3731 changed
= true; // We always return true if we run non-optimized
3736 while (line_no
<= (end
+ this.lines
- shift
)) {
3737 line
= GetLine(line_no
++);
3742 line
.RecalculateLine(g
, this);
3744 if (line
.recalc
&& line
.RecalculateLine(g
, this)) {
3746 // If the height changed, all subsequent lines change
3753 line
.RecalculatePasswordLine(g
, this);
3755 if (line
.recalc
&& line
.RecalculatePasswordLine(g
, this)) {
3757 // If the height changed, all subsequent lines change
3764 if (line
.widths
[line
.text
.Length
] > new_width
) {
3765 new_width
= (int)line
.widths
[line
.text
.Length
];
3768 // Calculate alignment
3769 if (line
.alignment
!= HorizontalAlignment
.Left
) {
3770 if (line
.alignment
== HorizontalAlignment
.Center
) {
3771 line
.align_shift
= (viewport_width
- (int)line
.widths
[line
.text
.Length
]) / 2;
3773 line
.align_shift
= viewport_width
- (int)line
.widths
[line
.text
.Length
] - 1;
3779 if (line_no
> lines
) {
3784 if (document_x
!= new_width
) {
3785 document_x
= new_width
;
3786 if (WidthChanged
!= null) {
3787 WidthChanged(this, null);
3791 RecalculateAlignments();
3793 line
= GetLine(lines
);
3795 if (document_y
!= line
.Y
+ line
.height
) {
3796 document_y
= line
.Y
+ line
.height
;
3797 if (HeightChanged
!= null) {
3798 HeightChanged(this, null);
3805 internal int Size() {
3809 private void owner_HandleCreated(object sender
, EventArgs e
) {
3810 RecalculateDocument(owner
.CreateGraphicsInternal());
3814 private void owner_VisibleChanged(object sender
, EventArgs e
) {
3815 if (owner
.Visible
) {
3816 RecalculateDocument(owner
.CreateGraphicsInternal());
3820 internal static bool IsWordSeparator(char ch
) {
3834 internal int FindWordSeparator(Line line
, int pos
, bool forward
) {
3837 len
= line
.text
.Length
;
3840 for (int i
= pos
+ 1; i
< len
; i
++) {
3841 if (IsWordSeparator(line
.Text
[i
])) {
3847 for (int i
= pos
- 1; i
> 0; i
--) {
3848 if (IsWordSeparator(line
.Text
[i
- 1])) {
3856 /* Search document for text */
3857 internal bool FindChars(char[] chars
, Marker start
, Marker end
, out Marker result
) {
3863 // Search for occurence of any char in the chars array
3864 result
= new Marker();
3867 line_no
= start
.line
.line_no
;
3869 while (line_no
<= end
.line
.line_no
) {
3870 line_len
= line
.text
.Length
;
3871 while (pos
< line_len
) {
3872 for (int i
= 0; i
< chars
.Length
; i
++) {
3873 if (line
.text
[pos
] == chars
[i
]) {
3875 if ((line
.line_no
== end
.line
.line_no
) && (pos
>= end
.pos
)) {
3889 line
= GetLine(line_no
);
3895 // This version does not build one big string for searching, instead it handles
3896 // line-boundaries, which is faster and less memory intensive
3897 // FIXME - Depending on culture stuff we might have to create a big string and use culturespecific
3898 // search stuff and change it to accept and return positions instead of Markers (which would match
3899 // RichTextBox behaviour better but would be inconsistent with the rest of TextControl)
3900 internal bool Find(string search
, Marker start
, Marker end
, out Marker result
, RichTextBoxFinds options
) {
3902 string search_string
;
3914 result
= new Marker();
3915 word_option
= ((options
& RichTextBoxFinds
.WholeWord
) != 0);
3916 ignore_case
= ((options
& RichTextBoxFinds
.MatchCase
) == 0);
3917 reverse
= ((options
& RichTextBoxFinds
.Reverse
) != 0);
3920 line_no
= start
.line
.line_no
;
3924 // Prep our search string, lowercasing it if we do case-independent matching
3927 sb
= new StringBuilder(search
);
3928 for (int i
= 0; i
< sb
.Length
; i
++) {
3929 sb
[i
] = Char
.ToLower(sb
[i
]);
3931 search_string
= sb
.ToString();
3933 search_string
= search
;
3936 // We need to check if the character before our start position is a wordbreak
3939 if ((pos
== 0) || (IsWordSeparator(line
.text
[pos
- 1]))) {
3946 if (IsWordSeparator(line
.text
[pos
- 1])) {
3952 // Need to check the end of the previous line
3955 prev_line
= GetLine(line_no
- 1);
3956 if (prev_line
.soft_break
) {
3957 if (IsWordSeparator(prev_line
.text
[prev_line
.text
.Length
- 1])) {
3971 // To avoid duplication of this loop with reverse logic, we search
3972 // through the document, remembering the last match and when returning
3973 // report that last remembered match
3975 last
= new Marker();
3976 last
.height
= -1; // Abused - we use it to track change
3978 while (line_no
<= end
.line
.line_no
) {
3979 if (line_no
!= end
.line
.line_no
) {
3980 line_len
= line
.text
.Length
;
3985 while (pos
< line_len
) {
3986 if (word_option
&& (current
== search_string
.Length
)) {
3987 if (IsWordSeparator(line
.text
[pos
])) {
4000 c
= Char
.ToLower(line
.text
[pos
]);
4005 if (c
== search_string
[current
]) {
4010 if (!word_option
|| (word_option
&& (word
|| (current
> 0)))) {
4014 if (!word_option
&& (current
== search_string
.Length
)) {
4031 if (IsWordSeparator(c
)) {
4039 // Mark that we just saw a word boundary
4040 if (!line
.soft_break
) {
4044 if (current
== search_string
.Length
) {
4060 line
= GetLine(line_no
);
4064 if (last
.height
!= -1) {
4074 // if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
4086 internal void GetMarker(out Marker mark
, bool start
) {
4087 mark
= new Marker();
4090 mark
.line
= GetLine(1);
4091 mark
.tag
= mark
.line
.tags
;
4094 mark
.line
= GetLine(lines
);
4095 mark
.tag
= mark
.line
.tags
;
4096 while (mark
.tag
.next
!= null) {
4097 mark
.tag
= mark
.tag
.next
;
4099 mark
.pos
= mark
.line
.text
.Length
;
4102 #endregion // Internal Methods
4105 internal event EventHandler CaretMoved
;
4106 internal event EventHandler WidthChanged
;
4107 internal event EventHandler HeightChanged
;
4108 internal event EventHandler LengthChanged
;
4109 #endregion // Events
4111 #region Administrative
4112 public IEnumerator
GetEnumerator() {
4117 public override bool Equals(object obj
) {
4122 if (!(obj
is Document
)) {
4130 if (ToString().Equals(((Document
)obj
).ToString())) {
4137 public override int GetHashCode() {
4141 public override string ToString() {
4142 return "document " + this.document_id
;
4144 #endregion // Administrative
4147 internal class LineTag
{
4148 #region Local Variables;
4149 // Payload; formatting
4150 internal Font font
; // System.Drawing.Font object for this tag
4151 internal Brush color
; // System.Drawing.Brush object
4154 internal int start
; // start, in chars; index into Line.text
4155 internal int length
; // length, in chars
4156 internal bool r_to_l
; // Which way is the font
4159 internal int height
; // Height in pixels of the text this tag describes
4160 internal int X
; // X location of the text this tag describes
4161 internal float width
; // Width in pixels of the text this tag describes
4162 internal int ascent
; // Ascent of the font for this tag
4163 internal int shift
; // Shift down for this tag, to stay on baseline
4166 internal Line line
; // The line we're on
4167 internal LineTag next
; // Next tag on the same line
4168 internal LineTag previous
; // Previous tag on the same line
4171 #region Constructors
4172 internal LineTag(Line line
, int start
, int length
) {
4175 this.length
= length
;
4179 #endregion // Constructors
4181 #region Internal Methods
4182 ///<summary>Break a tag into two with identical attributes; pos is 1-based; returns tag starting at >pos< or null if end-of-line</summary>
4183 internal LineTag
Break(int pos
) {
4187 if (pos
== this.start
) {
4189 } else if (pos
>= (start
+ length
)) {
4193 new_tag
= new LineTag(line
, pos
, start
+ length
- pos
);
4194 new_tag
.color
= color
;
4195 new_tag
.font
= font
;
4196 this.length
-= new_tag
.length
;
4197 new_tag
.next
= this.next
;
4198 this.next
= new_tag
;
4199 new_tag
.previous
= this;
4200 if (new_tag
.next
!= null) {
4201 new_tag
.next
.previous
= new_tag
;
4207 ///<summary>Create new font and brush from existing font and given new attributes. Returns true if fontheight changes</summary>
4208 internal static bool GenerateTextFormat(Font font_from
, Brush color_from
, FontDefinition attributes
, out Font new_font
, out Brush new_color
) {
4214 if (attributes
.font_obj
== null) {
4215 size
= font_from
.SizeInPoints
;
4216 unit
= font_from
.Unit
;
4217 face
= font_from
.Name
;
4218 style
= font_from
.Style
;
4220 if (attributes
.face
!= null) {
4221 face
= attributes
.face
;
4224 if (attributes
.size
!= 0) {
4225 size
= attributes
.size
;
4228 style
|= attributes
.add_style
;
4229 style
&= ~attributes
.remove_style
;
4232 new_font
= new Font(face
, size
, style
, unit
);
4234 new_font
= attributes
.font_obj
;
4237 // Create 'new' color brush
4238 if (attributes
.color
!= Color
.Empty
) {
4239 new_color
= new SolidBrush(attributes
.color
);
4241 new_color
= color_from
;
4244 if (new_font
.Height
== font_from
.Height
) {
4250 /// <summary>Applies 'font' and 'brush' to characters starting at 'start' for 'length' chars;
4251 /// Removes any previous tags overlapping the same area;
4252 /// returns true if lineheight has changed</summary>
4253 /// <param name="start">1-based character position on line</param>
4254 internal static bool FormatText(Line line
, int start
, int length
, Font font
, Brush color
) {
4258 bool retval
= false; // Assume line-height doesn't change
4261 if (font
.Height
!= line
.height
) {
4264 line
.recalc
= true; // This forces recalculation of the line in RecalculateDocument
4266 // A little sanity, not sure if it's needed, might be able to remove for speed
4267 if (length
> line
.text
.Length
) {
4268 length
= line
.text
.Length
;
4272 end
= start
+ length
;
4274 // Common special case
4275 if ((start
== 1) && (length
== tag
.length
)) {
4282 //Console.WriteLine("Finding tag for {0} {1}", line, start);
4283 start_tag
= FindTag(line
, start
);
4284 if (start_tag
== null) { // FIXME - is there a better way to handle this, or do we even need it?
4285 throw new Exception(String
.Format("Could not find start_tag in document at line {0} position {1}", line
.line_no
, start
));
4288 tag
= new LineTag(line
, start
, length
);
4295 //Console.WriteLine("Start tag: '{0}'", start_tag!=null ? start_tag.ToString() : "NULL");
4296 if (start_tag
.start
== start
) {
4297 tag
.next
= start_tag
;
4298 tag
.previous
= start_tag
.previous
;
4299 if (start_tag
.previous
!= null) {
4300 start_tag
.previous
.next
= tag
;
4302 start_tag
.previous
= tag
;
4304 // Insert ourselves 'in the middle'
4305 if ((start_tag
.next
!= null) && (start_tag
.next
.start
< end
)) {
4306 tag
.next
= start_tag
.next
;
4308 tag
.next
= new LineTag(line
, start_tag
.start
, start_tag
.length
);
4309 tag
.next
.font
= start_tag
.font
;
4310 tag
.next
.color
= start_tag
.color
;
4312 if (start_tag
.next
!= null) {
4313 tag
.next
.next
= start_tag
.next
;
4314 tag
.next
.next
.previous
= tag
.next
;
4317 tag
.next
.previous
= tag
;
4319 start_tag
.length
= start
- start_tag
.start
;
4321 tag
.previous
= start_tag
;
4322 start_tag
.next
= tag
;
4324 if (tag
.next
.start
> (tag
.start
+ tag
.length
)) {
4325 tag
.next
.length
+= tag
.next
.start
- (tag
.start
+ tag
.length
);
4326 tag
.next
.start
= tag
.start
+ tag
.length
;
4333 while ((tag
!= null) && (tag
.start
< end
)) {
4334 if ((tag
.start
+ tag
.length
) <= end
) {
4336 tag
.previous
.next
= tag
.next
;
4337 if (tag
.next
!= null) {
4338 tag
.next
.previous
= tag
.previous
;
4342 // Adjust the length of the tag
4343 tag
.length
= (tag
.start
+ tag
.length
) - end
;
4352 /// <summary>Applies font attributes specified to characters starting at 'start' for 'length' chars;
4353 /// Breaks tags at start and end point, keeping middle tags with altered attributes.
4354 /// Returns true if lineheight has changed</summary>
4355 /// <param name="start">1-based character position on line</param>
4356 internal static bool FormatText(Line line
, int start
, int length
, FontDefinition attributes
) {
4360 bool retval
= false; // Assume line-height doesn't change
4362 line
.recalc
= true; // This forces recalculation of the line in RecalculateDocument
4364 // A little sanity, not sure if it's needed, might be able to remove for speed
4365 if (length
> line
.text
.Length
) {
4366 length
= line
.text
.Length
;
4371 // Common special case
4372 if ((start
== 1) && (length
== tag
.length
)) {
4374 GenerateTextFormat(tag
.font
, tag
.color
, attributes
, out tag
.font
, out tag
.color
);
4378 start_tag
= FindTag(line
, start
);
4380 if (start_tag
== null) {
4382 // We are 'starting' after all valid tags; create a new tag with the right attributes
4383 start_tag
= FindTag(line
, line
.text
.Length
- 1);
4384 start_tag
.next
= new LineTag(line
, line
.text
.Length
+ 1, 0);
4385 start_tag
.next
.font
= start_tag
.font
;
4386 start_tag
.next
.color
= start_tag
.color
;
4387 start_tag
.next
.previous
= start_tag
;
4388 start_tag
= start_tag
.next
;
4390 throw new Exception(String
.Format("Could not find start_tag in document at line {0} position {1}", line
.line_no
, start
));
4393 start_tag
= start_tag
.Break(start
);
4396 end_tag
= FindTag(line
, start
+ length
);
4397 if (end_tag
!= null) {
4398 end_tag
= end_tag
.Break(start
+ length
);
4401 // start_tag or end_tag might be null; we're cool with that
4402 // we now walk from start_tag to end_tag, applying new attributes
4404 while ((tag
!= null) && tag
!= end_tag
) {
4405 if (LineTag
.GenerateTextFormat(tag
.font
, tag
.color
, attributes
, out tag
.font
, out tag
.color
)) {
4414 /// <summary>Finds the tag that describes the character at position 'pos' on 'line'</summary>
4415 internal static LineTag
FindTag(Line line
, int pos
) {
4416 LineTag tag
= line
.tags
;
4418 // Beginning of line is a bit special
4423 while (tag
!= null) {
4424 if ((tag
.start
<= pos
) && (pos
< (tag
.start
+tag
.length
))) {
4434 /// <summary>Combines 'this' tag with 'other' tag</summary>
4435 internal bool Combine(LineTag other
) {
4436 if (!this.Equals(other
)) {
4440 this.width
+= other
.width
;
4441 this.length
+= other
.length
;
4442 this.next
= other
.next
;
4443 if (this.next
!= null) {
4444 this.next
.previous
= this;
4451 /// <summary>Remove 'this' tag ; to be called when formatting is to be removed</summary>
4452 internal bool Remove() {
4453 if ((this.start
== 1) && (this.next
== null)) {
4454 // We cannot remove the only tag
4457 if (this.start
!= 1) {
4458 this.previous
.length
+= this.length
;
4459 this.previous
.width
= -1;
4460 this.previous
.next
= this.next
;
4461 this.next
.previous
= this.previous
;
4463 this.next
.start
= 1;
4464 this.next
.length
+= this.length
;
4465 this.next
.width
= -1;
4466 this.line
.tags
= this.next
;
4467 this.next
.previous
= null;
4473 /// <summary>Checks if 'this' tag describes the same formatting options as 'obj'</summary>
4474 public override bool Equals(object obj
) {
4481 if (!(obj
is LineTag
)) {
4489 other
= (LineTag
)obj
;
4491 if (this.font
.Equals(other
.font
) && this.color
.Equals(other
.color
)) { // FIXME add checking for things like link or type later
4498 public override int GetHashCode() {
4499 return base.GetHashCode ();
4502 public override string ToString() {
4503 return "Tag starts at index " + this.start
+ "length " + this.length
+ " text: " + this.line
.Text
.Substring(this.start
-1, this.length
) + "Font " + this.font
.ToString();
4506 #endregion // Internal Methods
4509 internal class UndoClass
{
4510 internal enum ActionType
{
4519 internal class Action
{
4520 internal ActionType type
;
4521 internal int line_no
;
4523 internal object data
;
4526 #region Local Variables
4527 private Document document
;
4528 private Stack undo_actions
;
4529 private Stack redo_actions
;
4530 private int caret_line
;
4531 private int caret_pos
;
4532 #endregion // Local Variables
4534 #region Constructors
4535 internal UndoClass(Document doc
) {
4537 undo_actions
= new Stack(50);
4538 redo_actions
= new Stack(50);
4540 #endregion // Constructors
4543 [MonoTODO("Change this to be configurable")]
4544 internal int UndoLevels
{
4546 return undo_actions
.Count
;
4550 [MonoTODO("Change this to be configurable")]
4551 internal int RedoLevels
{
4553 return redo_actions
.Count
;
4557 [MonoTODO("Come up with good naming and localization")]
4558 internal string UndoName
{
4562 action
= (Action
)undo_actions
.Peek();
4563 switch(action
.type
) {
4564 case ActionType
.InsertChar
: {
4565 Locale
.GetText("Insert character");
4569 case ActionType
.DeleteChar
: {
4570 Locale
.GetText("Delete character");
4574 case ActionType
.InsertString
: {
4575 Locale
.GetText("Insert string");
4579 case ActionType
.DeleteChars
: {
4580 Locale
.GetText("Delete string");
4584 case ActionType
.CursorMove
: {
4585 Locale
.GetText("Cursor move");
4593 internal string RedoName() {
4596 #endregion // Properties
4598 #region Internal Methods
4599 internal void Clear() {
4600 undo_actions
.Clear();
4601 redo_actions
.Clear();
4604 internal void Undo() {
4607 if (undo_actions
.Count
== 0) {
4611 action
= (Action
)undo_actions
.Pop();
4613 // Put onto redo stack
4614 redo_actions
.Push(action
);
4617 switch(action
.type
) {
4618 case ActionType
.InsertChar
: {
4619 // FIXME - implement me
4623 case ActionType
.DeleteChars
: {
4624 this.Insert(document
.GetLine(action
.line_no
), action
.pos
, (Line
)action
.data
);
4625 Undo(); // Grab the cursor location
4629 case ActionType
.CursorMove
: {
4630 document
.caret
.line
= document
.GetLine(action
.line_no
);
4631 if (document
.caret
.line
== null) {
4636 document
.caret
.tag
= document
.caret
.line
.FindTag(action
.pos
);
4637 document
.caret
.pos
= action
.pos
;
4638 document
.caret
.height
= document
.caret
.tag
.height
;
4640 if (document
.owner
.IsHandleCreated
) {
4641 XplatUI
.DestroyCaret(document
.owner
.Handle
);
4642 XplatUI
.CreateCaret(document
.owner
.Handle
, 2, document
.caret
.height
);
4643 XplatUI
.SetCaretPos(document
.owner
.Handle
, (int)document
.caret
.tag
.line
.widths
[document
.caret
.pos
] + document
.caret
.line
.align_shift
- document
.viewport_x
, document
.caret
.line
.Y
+ document
.caret
.tag
.shift
- document
.viewport_y
+ Document
.caret_shift
);
4644 XplatUI
.CaretVisible(document
.owner
.Handle
, true);
4647 // FIXME - enable call
4648 //if (document.CaretMoved != null) document.CaretMoved(this, EventArgs.Empty);
4654 internal void Redo() {
4655 if (redo_actions
.Count
== 0) {
4659 #endregion // Internal Methods
4661 #region Private Methods
4663 public void RecordDeleteChars(Line line
, int pos
, int length
) {
4664 RecordDelete(line
, pos
, line
, pos
+ length
- 1);
4667 // start_pos, end_pos = 1 based
4668 public void RecordDelete(Line start_line
, int start_pos
, Line end_line
, int end_pos
) {
4672 l
= Duplicate(start_line
, start_pos
, end_line
, end_pos
);
4675 a
.type
= ActionType
.DeleteChars
;
4677 a
.line_no
= start_line
.line_no
;
4678 a
.pos
= start_pos
- 1;
4680 // Record the cursor position before, since the actions will occur in reverse order
4682 undo_actions
.Push(a
);
4685 public void RecordCursor() {
4686 if (document
.caret
.line
== null) {
4690 RecordCursor(document
.caret
.line
, document
.caret
.pos
);
4693 public void RecordCursor(Line line
, int pos
) {
4696 if ((line
.line_no
== caret_line
) && (pos
== caret_pos
)) {
4700 caret_line
= line
.line_no
;
4704 a
.type
= ActionType
.CursorMove
;
4705 a
.line_no
= line
.line_no
;
4708 undo_actions
.Push(a
);
4711 // start_pos = 1-based
4712 // end_pos = 1-based
4713 public Line
Duplicate(Line start_line
, int start_pos
, Line end_line
, int end_pos
) {
4718 LineTag current_tag
;
4727 for (int i
= start_line
.line_no
; i
<= end_line
.line_no
; i
++) {
4728 current
= document
.GetLine(i
);
4730 if (start_line
.line_no
== i
) {
4736 if (end_line
.line_no
== i
) {
4739 end
= current
.text
.Length
;
4743 line
.text
= new StringBuilder(current
.text
.ToString(start
- 1, end
- start
+ 1));
4745 // Copy tags from start to start+length onto new line
4746 current_tag
= current
.FindTag(start
- 1);
4747 while ((current_tag
!= null) && (current_tag
.start
< end
)) {
4748 if ((current_tag
.start
<= start
) && (start
< (current_tag
.start
+ current_tag
.length
))) {
4749 // start tag is within this tag
4752 tag_start
= current_tag
.start
;
4755 if (end
< (current_tag
.start
+ current_tag
.length
)) {
4756 tag_length
= end
- tag_start
+ 1;
4758 tag_length
= current_tag
.start
+ current_tag
.length
- tag_start
;
4760 tag
= new LineTag(line
, tag_start
- start
+ 1, tag_length
);
4761 tag
.color
= current_tag
.color
;
4762 tag
.font
= current_tag
.font
;
4764 current_tag
= current_tag
.next
;
4766 // Add the new tag to the line
4767 if (line
.tags
== null) {
4773 while (tail
.next
!= null) {
4777 tag
.previous
= tail
;
4781 if ((i
+ 1) <= end_line
.line_no
) {
4782 line
.soft_break
= current
.soft_break
;
4784 // Chain them (we use right/left as next/previous)
4785 line
.right
= new Line();
4786 line
.right
.left
= line
;
4794 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
4795 internal void Insert(Line line
, int pos
, Line insert
) {
4802 // Handle special case first
4803 if (insert
.right
== null) {
4805 // Single line insert
4806 document
.Split(line
, pos
);
4808 if (insert
.tags
== null) {
4809 return; // Blank line
4812 //Insert our tags at the end
4815 while (tag
.next
!= null) {
4819 offset
= tag
.start
+ tag
.length
- 1;
4821 tag
.next
= insert
.tags
;
4822 line
.text
.Insert(offset
, insert
.text
.ToString());
4824 // Adjust start locations
4826 while (tag
!= null) {
4827 tag
.start
+= offset
;
4831 // Put it back together
4832 document
.Combine(line
.line_no
, line
.line_no
+ 1);
4833 document
.UpdateView(line
, pos
);
4840 while (current
!= null) {
4841 if (current
== insert
) {
4842 // Inserting the first line we split the line (and make space)
4843 document
.Split(line
, pos
);
4844 //Insert our tags at the end of the line
4848 while (tag
.next
!= null) {
4851 offset
= tag
.start
+ tag
.length
- 1;
4852 tag
.next
= current
.tags
;
4853 tag
.next
.previous
= tag
;
4859 line
.tags
= current
.tags
;
4860 line
.tags
.previous
= null;
4864 document
.Split(line
.line_no
, 0);
4866 line
.tags
= current
.tags
;
4867 line
.tags
.previous
= null;
4870 // Adjust start locations and line pointers
4871 while (tag
!= null) {
4872 tag
.start
+= offset
;
4877 line
.text
.Insert(offset
, current
.text
.ToString());
4878 line
.Grow(line
.text
.Length
);
4881 line
= document
.GetLine(line
.line_no
+ 1);
4883 // FIXME? Test undo of line-boundaries
4884 if ((current
.right
== null) && (current
.tags
.length
!= 0)) {
4885 document
.Combine(line
.line_no
- 1, line
.line_no
);
4887 current
= current
.right
;
4892 // Recalculate our document
4893 document
.UpdateView(first
, lines
, pos
);
4896 #endregion // Private Methods