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
98 CharForwardNoWrap
, // Move a char forward, but don't wrap onto the next line
99 CharBackNoWrap
// Move a char backward, but don't wrap onto the previous line
102 // Being cloneable should allow for nice line and document copies...
103 internal class Line
: ICloneable
, IComparable
{
104 #region Local Variables
105 // Stuff that matters for our line
106 internal StringBuilder text
; // Characters for the line
107 internal float[] widths
; // Width of each character; always one larger than text.Length
108 internal int space
; // Number of elements in text and widths
109 internal int line_no
; // Line number
110 internal LineTag tags
; // Tags describing the text
111 internal int Y
; // Baseline
112 internal int height
; // Height of the line (height of tallest tag)
113 internal int ascent
; // Ascent of the line (ascent of the tallest tag)
114 internal HorizontalAlignment alignment
; // Alignment of the line
115 internal int align_shift
; // Pixel shift caused by the alignment
116 internal bool soft_break
; // Tag is 'broken soft' and continuation from previous line
117 internal int indent
; // Left indent for the first line
118 internal int hanging_indent
; // Hanging indent (left indent for all but the first line)
119 internal int right_indent
; // Right indent for all lines
122 // Stuff that's important for the tree
123 internal Line parent
; // Our parent line
124 internal Line left
; // Line with smaller line number
125 internal Line right
; // Line with higher line number
126 internal LineColor color
; // We're doing a black/red tree. this is the node color
127 internal int DEFAULT_TEXT_LEN
; //
128 internal static StringFormat string_format
; // For calculating widths/heights
129 internal bool recalc
; // Line changed
130 #endregion // Local Variables
134 color
= LineColor
.Red
;
141 alignment
= HorizontalAlignment
.Left
;
143 if (string_format
== null) {
144 string_format
= new StringFormat(StringFormat
.GenericTypographic
);
145 string_format
.Trimming
= StringTrimming
.None
;
146 string_format
.FormatFlags
= StringFormatFlags
.MeasureTrailingSpaces
;
150 internal Line(int LineNo
, string Text
, Font font
, Brush color
) : this() {
151 space
= Text
.Length
> DEFAULT_TEXT_LEN
? Text
.Length
+1 : DEFAULT_TEXT_LEN
;
153 text
= new StringBuilder(Text
, space
);
156 widths
= new float[space
+ 1];
157 tags
= new LineTag(this, 1, text
.Length
);
162 internal Line(int LineNo
, string Text
, HorizontalAlignment align
, Font font
, Brush color
) : this() {
163 space
= Text
.Length
> DEFAULT_TEXT_LEN
? Text
.Length
+1 : DEFAULT_TEXT_LEN
;
165 text
= new StringBuilder(Text
, space
);
169 widths
= new float[space
+ 1];
170 tags
= new LineTag(this, 1, text
.Length
);
175 internal Line(int LineNo
, string Text
, LineTag tag
) : this() {
176 space
= Text
.Length
> DEFAULT_TEXT_LEN
? Text
.Length
+1 : DEFAULT_TEXT_LEN
;
178 text
= new StringBuilder(Text
, space
);
181 widths
= new float[space
+ 1];
185 #endregion // Constructors
187 #region Internal Properties
188 internal int Indent
{
199 internal int HangingIndent
{
201 return hanging_indent
;
205 hanging_indent
= value;
210 internal int RightIndent
{
216 right_indent
= value;
222 internal int Height
{
232 internal int LineNo
{
242 internal string Text
{
244 return text
.ToString();
248 text
= new StringBuilder(value, value.Length
> DEFAULT_TEXT_LEN
? value.Length
: DEFAULT_TEXT_LEN
);
252 internal HorizontalAlignment Alignment
{
258 if (alignment
!= value) {
265 internal StringBuilder Text
{
275 #endregion // Internal Properties
277 #region Internal Methods
278 // Make sure we always have enoughs space in text and widths
279 internal void Grow(int minimum
) {
283 length
= text
.Length
;
285 if ((length
+ minimum
) > space
) {
286 // We need to grow; double the size
288 if ((length
+ minimum
) > (space
* 2)) {
289 new_widths
= new float[length
+ minimum
* 2 + 1];
290 space
= length
+ minimum
* 2;
292 new_widths
= new float[space
* 2 + 1];
295 widths
.CopyTo(new_widths
, 0);
301 internal void Streamline(int lines
) {
308 // Catch what the loop below wont; eliminate 0 length
309 // tags, but only if there are other tags after us
310 while ((current
.length
== 0) && (next
!= null)) {
312 tags
.previous
= null;
321 while (next
!= null) {
322 // Take out 0 length tags unless it's the last tag in the document
323 if (next
.length
== 0) {
324 if ((next
.next
!= null) || (line_no
!= lines
)) {
325 current
.next
= next
.next
;
326 if (current
.next
!= null) {
327 current
.next
.previous
= current
;
333 if (current
.Combine(next
)) {
338 current
= current
.next
;
343 /// <summary> Find the tag on a line based on the character position, pos is 0-based</summary>
344 internal LineTag
FindTag(int pos
) {
353 if (pos
>= text
.Length
) {
354 pos
= text
.Length
- 1;
357 while (tag
!= null) {
358 if (((tag
.start
- 1) <= pos
) && (pos
< (tag
.start
+ tag
.length
- 1))) {
359 return LineTag
.GetFinalTag (tag
);
367 /// Recalculate a single line using the same char for every character in the line
370 internal bool RecalculatePasswordLine(Graphics g
, Document doc
) {
379 len
= this.text
.Length
;
389 w
= g
.MeasureString(doc
.password_char
, tags
.font
, 10000, string_format
).Width
;
391 if (this.height
!= (int)tag
.font
.Height
) {
397 this.height
= (int)tag
.font
.Height
;
398 tag
.height
= this.height
;
400 XplatUI
.GetFontMetrics(g
, tag
.font
, out tag
.ascent
, out descent
);
401 this.ascent
= tag
.ascent
;
406 widths
[pos
] = widths
[pos
-1] + w
;
413 /// Go through all tags on a line and recalculate all size-related values;
414 /// returns true if lineheight changed
416 internal bool RecalculateLine(Graphics g
, Document doc
) {
430 len
= this.text
.Length
;
432 prev_height
= this.height
; // For drawing optimization calculations
433 this.height
= 0; // Reset line height
434 this.ascent
= 0; // Reset the ascent for the line
438 if (this.soft_break
) {
439 widths
[0] = hanging_indent
;
452 size
= g
.MeasureString(this.text
.ToString(pos
, 1), tag
.font
, 10000, string_format
);
454 while (tag
.length
== 0) { // We should always have tags after a tag.length==0 unless len==0
457 if (tag
.previous
!= null) {
458 tag
.X
= tag
.previous
.X
;
460 tag
.X
= (int)widths
[pos
];
469 if (Char
.IsWhiteSpace(text
[pos
])) {
471 wrap_width
= tag
.width
+ w
;
475 if ((wrap_pos
> 0) && (wrap_pos
!= len
) && (widths
[pos
] + w
) + 27 > (doc
.viewport_width
- this.right_indent
)) {
477 tag
.width
= wrap_width
;
478 doc
.Split(this, tag
, pos
, true);
479 len
= this.text
.Length
;
482 } else if ((widths
[pos
] + w
) > (doc
.viewport_width
- this.right_indent
)) {
483 // No suitable wrap position was found so break right in the middle of a word
484 tag
.width
= tag
.width
+ w
;
485 doc
.Split(this, tag
, pos
, true);
486 len
= this.text
.Length
;
492 // Contract all soft lines that follow back into our line
498 widths
[pos
] = widths
[pos
-1] + w
;
501 line
= doc
.GetLine(this.line_no
+ 1);
502 if ((line
!= null) && (line
.soft_break
)) {
503 // Pull the previous line back into this one
504 doc
.Combine(this.line_no
, this.line_no
+ 1);
505 len
= this.text
.Length
;
511 if (pos
== (tag
.start
-1 + tag
.length
)) {
512 // We just found the end of our current tag
513 tag
.height
= (int)tag
.font
.Height
;
515 // Check if we're the tallest on the line (so far)
516 if (tag
.height
> this.height
) {
517 this.height
= tag
.height
; // Yep; make sure the line knows
520 if (tag
.ascent
== 0) {
523 XplatUI
.GetFontMetrics(g
, tag
.font
, out tag
.ascent
, out descent
);
526 if (tag
.ascent
> this.ascent
) {
529 // We have a tag that has a taller ascent than the line;
533 t
.shift
= tag
.ascent
- t
.ascent
;
538 this.ascent
= tag
.ascent
;
540 tag
.shift
= this.ascent
- tag
.ascent
;
543 // Update our horizontal starting pixel position
544 if (tag
.previous
== null) {
545 tag
.X
= (int)widths
[0];
547 tag
.X
= tag
.previous
.X
+ (int)tag
.previous
.width
;
555 wrap_width
= tag
.width
;
560 if (this.height
== 0) {
561 this.height
= tags
.font
.Height
;
562 tag
.height
= this.height
;
565 if (prev_height
!= this.height
) {
570 #endregion // Internal Methods
572 #region Administrative
573 public int CompareTo(object obj
) {
578 if (! (obj
is Line
)) {
579 throw new ArgumentException("Object is not of type Line", "obj");
582 if (line_no
< ((Line
)obj
).line_no
) {
584 } else if (line_no
> ((Line
)obj
).line_no
) {
591 public object Clone() {
599 clone
.left
= (Line
)left
.Clone();
603 clone
.left
= (Line
)left
.Clone();
609 internal object CloneLine() {
619 public override bool Equals(object obj
) {
624 if (!(obj
is Line
)) {
632 if (line_no
== ((Line
)obj
).line_no
) {
639 public override int GetHashCode() {
640 return base.GetHashCode ();
643 public override string ToString() {
644 return "Line " + line_no
;
647 #endregion // Administrative
650 internal class Document
: ICloneable
, IEnumerable
{
652 // FIXME - go through code and check for places where
653 // we do explicit comparisons instead of using the compare overloads
654 internal struct Marker
{
656 internal LineTag tag
;
660 public static bool operator<(Marker lhs
, Marker rhs
) {
661 if (lhs
.line
.line_no
< rhs
.line
.line_no
) {
665 if (lhs
.line
.line_no
== rhs
.line
.line_no
) {
666 if (lhs
.pos
< rhs
.pos
) {
673 public static bool operator>(Marker lhs
, Marker rhs
) {
674 if (lhs
.line
.line_no
> rhs
.line
.line_no
) {
678 if (lhs
.line
.line_no
== rhs
.line
.line_no
) {
679 if (lhs
.pos
> rhs
.pos
) {
686 public static bool operator==(Marker lhs
, Marker rhs
) {
687 if ((lhs
.line
.line_no
== rhs
.line
.line_no
) && (lhs
.pos
== rhs
.pos
)) {
693 public static bool operator!=(Marker lhs
, Marker rhs
) {
694 if ((lhs
.line
.line_no
!= rhs
.line
.line_no
) || (lhs
.pos
!= rhs
.pos
)) {
700 public void Combine(Line move_to_line
, int move_to_line_length
) {
702 pos
+= move_to_line_length
;
703 tag
= LineTag
.FindTag(line
, pos
);
706 // This is for future use, right now Document.Split does it by hand, with some added shortcut logic
707 public void Split(Line move_to_line
, int split_at
) {
710 tag
= LineTag
.FindTag(line
, pos
);
713 public override bool Equals(object obj
) {
714 return this==(Marker
)obj
;
717 public override int GetHashCode() {
718 return base.GetHashCode ();
721 public override string ToString() {
722 return "Marker Line " + line
+ ", Position " + pos
;
726 #endregion Structures
728 #region Local Variables
729 private Line document
;
731 private Line sentinel
;
732 private Line last_found
;
733 private int document_id
;
734 private Random random
= new Random();
735 internal string password_char
;
736 private StringBuilder password_cache
;
737 private bool calc_pass
;
738 private int char_count
;
740 private bool no_recalc
;
741 private bool recalc_pending
;
742 private int recalc_start
;
743 private int recalc_end
;
744 private bool recalc_optimize
;
746 internal bool multiline
;
749 internal UndoClass undo
;
751 internal Marker caret
;
752 internal Marker selection_start
;
753 internal Marker selection_end
;
754 internal bool selection_visible
;
755 internal Marker selection_anchor
;
756 internal Marker selection_prev
;
757 internal bool selection_end_anchor
;
759 internal int viewport_x
;
760 internal int viewport_y
; // The visible area of the document
761 internal int viewport_width
;
762 internal int viewport_height
;
764 internal int document_x
; // Width of the document
765 internal int document_y
; // Height of the document
767 internal Rectangle invalid
;
769 internal int crlf_size
; // 1 or 2, depending on whether we use \r\n or just \n
771 internal TextBoxBase owner
; // Who's owning us?
772 static internal int caret_width
= 1;
773 static internal int caret_shift
= 1;
774 #endregion // Local Variables
777 internal Document(TextBoxBase owner
) {
786 recalc_pending
= false;
788 // Tree related stuff
789 sentinel
= new Line();
790 sentinel
.color
= LineColor
.Black
;
793 last_found
= sentinel
;
795 // We always have a blank line
796 owner
.HandleCreated
+= new EventHandler(owner_HandleCreated
);
797 owner
.VisibleChanged
+= new EventHandler(owner_VisibleChanged
);
799 Add(1, "", owner
.Font
, ThemeEngine
.Current
.ResPool
.GetSolidBrush(owner
.ForeColor
));
800 Line l
= GetLine (1);
803 undo
= new UndoClass(this);
805 selection_visible
= false;
806 selection_start
.line
= this.document
;
807 selection_start
.pos
= 0;
808 selection_start
.tag
= selection_start
.line
.tags
;
809 selection_end
.line
= this.document
;
810 selection_end
.pos
= 0;
811 selection_end
.tag
= selection_end
.line
.tags
;
812 selection_anchor
.line
= this.document
;
813 selection_anchor
.pos
= 0;
814 selection_anchor
.tag
= selection_anchor
.line
.tags
;
815 caret
.line
= this.document
;
817 caret
.tag
= caret
.line
.tags
;
824 // Default selection is empty
826 document_id
= random
.Next();
830 #region Internal Properties
847 internal Line CaretLine
{
853 internal int CaretPosition
{
859 internal Point Caret
{
861 return new Point((int)caret
.tag
.line
.widths
[caret
.pos
] + caret
.line
.align_shift
, caret
.line
.Y
);
865 internal LineTag CaretTag
{
875 internal int CRLFSize
{
885 internal string PasswordChar
{
887 return password_char
;
891 password_char
= value;
892 if ((password_char
.Length
!= 0) && (password_char
[0] != '\0')) {
897 password_cache
= new StringBuilder(1024);
898 for (int i
= 0; i
< 1024; i
++) {
899 password_cache
.Append(ch
);
903 password_cache
= null;
908 internal int ViewPortX
{
918 internal int Length
{
920 return char_count
+ lines
- 1; // Add \n for each line but the last
924 private int CharCount
{
932 if (LengthChanged
!= null) {
933 LengthChanged(this, EventArgs
.Empty
);
938 ///<summary>Setting NoRecalc to true will prevent the document from being recalculated.
939 ///This ensures that coordinates of added text are predictable after adding the text even with wrapped view</summary>
940 internal bool NoRecalc
{
947 if (!no_recalc
&& recalc_pending
) {
948 RecalculateDocument(owner
.CreateGraphicsInternal(), recalc_start
, recalc_end
, recalc_optimize
);
949 recalc_pending
= false;
954 internal int ViewPortY
{
964 internal int ViewPortWidth
{
966 return viewport_width
;
970 viewport_width
= value;
974 internal int ViewPortHeight
{
976 return viewport_height
;
980 viewport_height
= value;
987 return this.document_x
;
991 internal int Height
{
993 return this.document_y
;
997 internal bool SelectionVisible
{
999 return selection_visible
;
1003 internal bool Wrap
{
1013 #endregion // Internal Properties
1015 #region Private Methods
1017 internal int DumpTree(Line line
, bool with_tags
) {
1022 Console
.Write("Line {0} [# {1}], Y: {2} Text {3}", line
.line_no
, line
.GetHashCode(), line
.Y
, line
.text
!= null ? line
.text
.ToString() : "undefined");
1024 if (line
.left
== sentinel
) {
1025 Console
.Write(", left = sentinel");
1026 } else if (line
.left
== null) {
1027 Console
.Write(", left = NULL");
1030 if (line
.right
== sentinel
) {
1031 Console
.Write(", right = sentinel");
1032 } else if (line
.right
== null) {
1033 Console
.Write(", right = NULL");
1036 Console
.WriteLine("");
1046 Console
.Write(" Tags: ");
1047 while (tag
!= null) {
1048 Console
.Write("{0} <{1}>-<{2}> ", count
++, tag
.start
, tag
.length
);
1049 length
+= tag
.length
;
1051 if (tag
.line
!= line
) {
1052 Console
.Write("BAD line link");
1053 throw new Exception("Bad line link in tree");
1057 Console
.Write(", ");
1060 if (length
> line
.text
.Length
) {
1061 throw new Exception(String
.Format("Length of tags more than length of text on line (expected {0} calculated {1})", line
.text
.Length
, length
));
1062 } else if (length
< line
.text
.Length
) {
1063 throw new Exception(String
.Format("Length of tags less than length of text on line (expected {0} calculated {1})", line
.text
.Length
, length
));
1065 Console
.WriteLine("");
1067 if (line
.left
!= null) {
1068 if (line
.left
!= sentinel
) {
1069 total
+= DumpTree(line
.left
, with_tags
);
1072 if (line
!= sentinel
) {
1073 throw new Exception("Left should not be NULL");
1077 if (line
.right
!= null) {
1078 if (line
.right
!= sentinel
) {
1079 total
+= DumpTree(line
.right
, with_tags
);
1082 if (line
!= sentinel
) {
1083 throw new Exception("Right should not be NULL");
1087 for (int i
= 1; i
<= this.lines
; i
++) {
1088 if (GetLine(i
) == null) {
1089 throw new Exception(String
.Format("Hole in line order, missing {0}", i
));
1093 if (line
== this.Root
) {
1094 if (total
< this.lines
) {
1095 throw new Exception(String
.Format("Not enough nodes in tree, found {0}, expected {1}", total
, this.lines
));
1096 } else if (total
> this.lines
) {
1097 throw new Exception(String
.Format("Too many nodes in tree, found {0}, expected {1}", total
, this.lines
));
1104 private void SetSelectionVisible (bool value)
1106 selection_visible
= value;
1108 // cursor and selection are enemies, we can't have both in the same room at the same time
1109 if (owner
.IsHandleCreated
)
1110 XplatUI
.CaretVisible (owner
.Handle
, !selection_visible
);
1113 private void DecrementLines(int line_no
) {
1117 while (current
<= lines
) {
1118 GetLine(current
).line_no
--;
1124 private void IncrementLines(int line_no
) {
1127 current
= this.lines
;
1128 while (current
>= line_no
) {
1129 GetLine(current
).line_no
++;
1135 private void RebalanceAfterAdd(Line line1
) {
1138 while ((line1
!= document
) && (line1
.parent
.color
== LineColor
.Red
)) {
1139 if (line1
.parent
== line1
.parent
.parent
.left
) {
1140 line2
= line1
.parent
.parent
.right
;
1142 if ((line2
!= null) && (line2
.color
== LineColor
.Red
)) {
1143 line1
.parent
.color
= LineColor
.Black
;
1144 line2
.color
= LineColor
.Black
;
1145 line1
.parent
.parent
.color
= LineColor
.Red
;
1146 line1
= line1
.parent
.parent
;
1148 if (line1
== line1
.parent
.right
) {
1149 line1
= line1
.parent
;
1153 line1
.parent
.color
= LineColor
.Black
;
1154 line1
.parent
.parent
.color
= LineColor
.Red
;
1156 RotateRight(line1
.parent
.parent
);
1159 line2
= line1
.parent
.parent
.left
;
1161 if ((line2
!= null) && (line2
.color
== LineColor
.Red
)) {
1162 line1
.parent
.color
= LineColor
.Black
;
1163 line2
.color
= LineColor
.Black
;
1164 line1
.parent
.parent
.color
= LineColor
.Red
;
1165 line1
= line1
.parent
.parent
;
1167 if (line1
== line1
.parent
.left
) {
1168 line1
= line1
.parent
;
1172 line1
.parent
.color
= LineColor
.Black
;
1173 line1
.parent
.parent
.color
= LineColor
.Red
;
1174 RotateLeft(line1
.parent
.parent
);
1178 document
.color
= LineColor
.Black
;
1181 private void RebalanceAfterDelete(Line line1
) {
1184 while ((line1
!= document
) && (line1
.color
== LineColor
.Black
)) {
1185 if (line1
== line1
.parent
.left
) {
1186 line2
= line1
.parent
.right
;
1187 if (line2
.color
== LineColor
.Red
) {
1188 line2
.color
= LineColor
.Black
;
1189 line1
.parent
.color
= LineColor
.Red
;
1190 RotateLeft(line1
.parent
);
1191 line2
= line1
.parent
.right
;
1193 if ((line2
.left
.color
== LineColor
.Black
) && (line2
.right
.color
== LineColor
.Black
)) {
1194 line2
.color
= LineColor
.Red
;
1195 line1
= line1
.parent
;
1197 if (line2
.right
.color
== LineColor
.Black
) {
1198 line2
.left
.color
= LineColor
.Black
;
1199 line2
.color
= LineColor
.Red
;
1201 line2
= line1
.parent
.right
;
1203 line2
.color
= line1
.parent
.color
;
1204 line1
.parent
.color
= LineColor
.Black
;
1205 line2
.right
.color
= LineColor
.Black
;
1206 RotateLeft(line1
.parent
);
1210 line2
= line1
.parent
.left
;
1211 if (line2
.color
== LineColor
.Red
) {
1212 line2
.color
= LineColor
.Black
;
1213 line1
.parent
.color
= LineColor
.Red
;
1214 RotateRight(line1
.parent
);
1215 line2
= line1
.parent
.left
;
1217 if ((line2
.right
.color
== LineColor
.Black
) && (line2
.left
.color
== LineColor
.Black
)) {
1218 line2
.color
= LineColor
.Red
;
1219 line1
= line1
.parent
;
1221 if (line2
.left
.color
== LineColor
.Black
) {
1222 line2
.right
.color
= LineColor
.Black
;
1223 line2
.color
= LineColor
.Red
;
1225 line2
= line1
.parent
.left
;
1227 line2
.color
= line1
.parent
.color
;
1228 line1
.parent
.color
= LineColor
.Black
;
1229 line2
.left
.color
= LineColor
.Black
;
1230 RotateRight(line1
.parent
);
1235 line1
.color
= LineColor
.Black
;
1238 private void RotateLeft(Line line1
) {
1239 Line line2
= line1
.right
;
1241 line1
.right
= line2
.left
;
1243 if (line2
.left
!= sentinel
) {
1244 line2
.left
.parent
= line1
;
1247 if (line2
!= sentinel
) {
1248 line2
.parent
= line1
.parent
;
1251 if (line1
.parent
!= null) {
1252 if (line1
== line1
.parent
.left
) {
1253 line1
.parent
.left
= line2
;
1255 line1
.parent
.right
= line2
;
1262 if (line1
!= sentinel
) {
1263 line1
.parent
= line2
;
1267 private void RotateRight(Line line1
) {
1268 Line line2
= line1
.left
;
1270 line1
.left
= line2
.right
;
1272 if (line2
.right
!= sentinel
) {
1273 line2
.right
.parent
= line1
;
1276 if (line2
!= sentinel
) {
1277 line2
.parent
= line1
.parent
;
1280 if (line1
.parent
!= null) {
1281 if (line1
== line1
.parent
.right
) {
1282 line1
.parent
.right
= line2
;
1284 line1
.parent
.left
= line2
;
1290 line2
.right
= line1
;
1291 if (line1
!= sentinel
) {
1292 line1
.parent
= line2
;
1297 internal void UpdateView(Line line
, int pos
) {
1298 if (!owner
.IsHandleCreated
) {
1303 recalc_start
= line
.line_no
;
1304 recalc_end
= line
.line_no
;
1305 recalc_optimize
= true;
1306 recalc_pending
= true;
1310 // Optimize invalidation based on Line alignment
1311 if (RecalculateDocument(owner
.CreateGraphicsInternal(), line
.line_no
, line
.line_no
, true)) {
1312 // Lineheight changed, invalidate the rest of the document
1313 if ((line
.Y
- viewport_y
) >=0 ) {
1314 // We formatted something that's in view, only draw parts of the screen
1315 owner
.Invalidate(new Rectangle(0, line
.Y
- viewport_y
, viewport_width
, owner
.Height
- line
.Y
- viewport_y
));
1317 // The tag was above the visible area, draw everything
1321 switch(line
.alignment
) {
1322 case HorizontalAlignment
.Left
: {
1323 owner
.Invalidate(new Rectangle((int)line
.widths
[pos
] - viewport_x
- 1, line
.Y
- viewport_y
, viewport_width
, line
.height
+ 1));
1327 case HorizontalAlignment
.Center
: {
1328 owner
.Invalidate(new Rectangle(0, line
.Y
- viewport_y
, viewport_width
, line
.height
+ 1));
1332 case HorizontalAlignment
.Right
: {
1333 owner
.Invalidate(new Rectangle(0, line
.Y
- viewport_y
, (int)line
.widths
[pos
+ 1] - viewport_x
+ line
.align_shift
, line
.height
+ 1));
1341 // Update display from line, down line_count lines; pos is unused, but required for the signature
1342 internal void UpdateView(Line line
, int line_count
, int pos
) {
1343 if (!owner
.IsHandleCreated
) {
1348 recalc_start
= line
.line_no
;
1349 recalc_end
= line
.line_no
+ line_count
- 1;
1350 recalc_optimize
= true;
1351 recalc_pending
= true;
1355 if (RecalculateDocument(owner
.CreateGraphicsInternal(), line
.line_no
, line
.line_no
+ line_count
- 1, true)) {
1356 // Lineheight changed, invalidate the rest of the document
1357 if ((line
.Y
- viewport_y
) >=0 ) {
1358 // We formatted something that's in view, only draw parts of the screen
1359 //blah Console.WriteLine("TextControl.cs(981) Invalidate called in UpdateView(line, line_count, pos)");
1360 owner
.Invalidate(new Rectangle(0, line
.Y
- viewport_y
, viewport_width
, owner
.Height
- line
.Y
- viewport_y
));
1362 // The tag was above the visible area, draw everything
1363 //blah Console.WriteLine("TextControl.cs(985) Invalidate called in UpdateView(line, line_count, pos)");
1369 end_line
= GetLine(line
.line_no
+ line_count
-1);
1370 if (end_line
== null) {
1374 //blah Console.WriteLine("TextControl.cs(996) Invalidate called in UpdateView(line, line_count, pos)");
1375 owner
.Invalidate(new Rectangle(0 - viewport_x
, line
.Y
- viewport_y
, (int)line
.widths
[line
.text
.Length
], end_line
.Y
+ end_line
.height
));
1378 #endregion // Private Methods
1380 #region Internal Methods
1381 // Clear the document and reset state
1382 internal void Empty() {
1384 document
= sentinel
;
1385 last_found
= sentinel
;
1388 // We always have a blank line
1389 Add(1, "", owner
.Font
, ThemeEngine
.Current
.ResPool
.GetSolidBrush(owner
.ForeColor
));
1390 Line l
= GetLine (1);
1391 l
.soft_break
= true;
1393 this.RecalculateDocument(owner
.CreateGraphicsInternal());
1394 PositionCaret(0, 0);
1396 SetSelectionVisible (false);
1398 selection_start
.line
= this.document
;
1399 selection_start
.pos
= 0;
1400 selection_start
.tag
= selection_start
.line
.tags
;
1401 selection_end
.line
= this.document
;
1402 selection_end
.pos
= 0;
1403 selection_end
.tag
= selection_end
.line
.tags
;
1413 internal void PositionCaret(Line line
, int pos
) {
1414 if (owner
.IsHandleCreated
) {
1415 undo
.RecordCursor();
1418 caret
.tag
= line
.FindTag(pos
);
1421 caret
.height
= caret
.tag
.height
;
1423 if (owner
.IsHandleCreated
) {
1424 if (owner
.Focused
) {
1425 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
);
1428 if (CaretMoved
!= null) CaretMoved(this, EventArgs
.Empty
);
1434 internal void PositionCaret(int x
, int y
) {
1435 if (!owner
.IsHandleCreated
) {
1439 undo
.RecordCursor();
1441 caret
.tag
= FindCursor(x
, y
, out caret
.pos
);
1442 caret
.line
= caret
.tag
.line
;
1443 caret
.height
= caret
.tag
.height
;
1445 if (owner
.Focused
) {
1446 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
);
1449 if (CaretMoved
!= null) CaretMoved(this, EventArgs
.Empty
);
1452 internal void CaretHasFocus() {
1453 if ((caret
.tag
!= null) && owner
.IsHandleCreated
) {
1454 XplatUI
.CreateCaret(owner
.Handle
, caret_width
, caret
.height
);
1455 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 internal void CaretLostFocus() {
1462 if (!owner
.IsHandleCreated
) {
1465 XplatUI
.DestroyCaret(owner
.Handle
);
1468 internal void AlignCaret() {
1469 if (!owner
.IsHandleCreated
) {
1473 undo
.RecordCursor();
1475 caret
.tag
= LineTag
.FindTag(caret
.line
, caret
.pos
);
1476 caret
.height
= caret
.tag
.height
;
1478 if (owner
.Focused
) {
1479 XplatUI
.CreateCaret(owner
.Handle
, caret_width
, caret
.height
);
1480 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
);
1484 if (CaretMoved
!= null) CaretMoved(this, EventArgs
.Empty
);
1487 internal void UpdateCaret() {
1488 if (!owner
.IsHandleCreated
|| caret
.tag
== null) {
1492 undo
.RecordCursor();
1494 if (caret
.tag
.height
!= caret
.height
) {
1495 caret
.height
= caret
.tag
.height
;
1496 if (owner
.Focused
) {
1497 XplatUI
.CreateCaret(owner
.Handle
, caret_width
, caret
.height
);
1501 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
);
1505 if (CaretMoved
!= null) CaretMoved(this, EventArgs
.Empty
);
1508 internal void DisplayCaret() {
1509 if (!owner
.IsHandleCreated
) {
1513 if (owner
.Focused
&& !selection_visible
) {
1514 XplatUI
.CaretVisible(owner
.Handle
, true);
1518 internal void HideCaret() {
1519 if (!owner
.IsHandleCreated
) {
1523 if (owner
.Focused
) {
1524 XplatUI
.CaretVisible(owner
.Handle
, false);
1528 internal void MoveCaret(CaretDirection direction
) {
1529 // FIXME should we use IsWordSeparator to detect whitespace, instead
1530 // of looking for actual spaces in the Word move cases?
1532 bool nowrap
= false;
1534 case CaretDirection
.CharForwardNoWrap
:
1536 goto case CaretDirection
.CharForward
;
1537 case CaretDirection
.CharForward
: {
1539 if (caret
.pos
> caret
.line
.text
.Length
) {
1540 if (multiline
&& !nowrap
) {
1541 // Go into next line
1542 if (caret
.line
.line_no
< this.lines
) {
1543 caret
.line
= GetLine(caret
.line
.line_no
+1);
1545 caret
.tag
= caret
.line
.tags
;
1550 // Single line; we stay where we are
1554 if ((caret
.tag
.start
- 1 + caret
.tag
.length
) < caret
.pos
) {
1555 caret
.tag
= caret
.tag
.next
;
1562 case CaretDirection
.CharBackNoWrap
:
1564 goto case CaretDirection
.CharBack
;
1565 case CaretDirection
.CharBack
: {
1566 if (caret
.pos
> 0) {
1567 // caret.pos--; // folded into the if below
1568 if (--caret
.pos
> 0) {
1569 if (caret
.tag
.start
> caret
.pos
) {
1570 caret
.tag
= caret
.tag
.previous
;
1574 if (caret
.line
.line_no
> 1 && !nowrap
) {
1575 caret
.line
= GetLine(caret
.line
.line_no
- 1);
1576 caret
.pos
= caret
.line
.text
.Length
;
1577 caret
.tag
= LineTag
.FindTag(caret
.line
, caret
.pos
);
1584 case CaretDirection
.WordForward
: {
1587 len
= caret
.line
.text
.Length
;
1588 if (caret
.pos
< len
) {
1589 while ((caret
.pos
< len
) && (caret
.line
.text
[caret
.pos
] != ' ')) {
1592 if (caret
.pos
< len
) {
1593 // Skip any whitespace
1594 while ((caret
.pos
< len
) && (caret
.line
.text
[caret
.pos
] == ' ')) {
1598 caret
.tag
= LineTag
.FindTag(caret
.line
, caret
.pos
);
1600 if (caret
.line
.line_no
< this.lines
) {
1601 caret
.line
= GetLine(caret
.line
.line_no
+ 1);
1603 caret
.tag
= caret
.line
.tags
;
1610 case CaretDirection
.WordBack
: {
1611 if (caret
.pos
> 0) {
1614 while ((caret
.pos
> 0) && (caret
.line
.text
[caret
.pos
] == ' ')) {
1618 while ((caret
.pos
> 0) && (caret
.line
.text
[caret
.pos
] != ' ')) {
1622 if (caret
.line
.text
.ToString(caret
.pos
, 1) == " ") {
1623 if (caret
.pos
!= 0) {
1626 caret
.line
= GetLine(caret
.line
.line_no
- 1);
1627 caret
.pos
= caret
.line
.text
.Length
;
1630 caret
.tag
= LineTag
.FindTag(caret
.line
, caret
.pos
);
1632 if (caret
.line
.line_no
> 1) {
1633 caret
.line
= GetLine(caret
.line
.line_no
- 1);
1634 caret
.pos
= caret
.line
.text
.Length
;
1635 caret
.tag
= LineTag
.FindTag(caret
.line
, caret
.pos
);
1642 case CaretDirection
.LineUp
: {
1643 if (caret
.line
.line_no
> 1) {
1646 pixel
= (int)caret
.line
.widths
[caret
.pos
];
1647 PositionCaret(pixel
, GetLine(caret
.line
.line_no
- 1).Y
);
1654 case CaretDirection
.LineDown
: {
1655 if (caret
.line
.line_no
< lines
) {
1658 pixel
= (int)caret
.line
.widths
[caret
.pos
];
1659 PositionCaret(pixel
, GetLine(caret
.line
.line_no
+ 1).Y
);
1666 case CaretDirection
.Home
: {
1667 if (caret
.pos
> 0) {
1669 caret
.tag
= caret
.line
.tags
;
1675 case CaretDirection
.End
: {
1676 if (caret
.pos
< caret
.line
.text
.Length
) {
1677 caret
.pos
= caret
.line
.text
.Length
;
1678 caret
.tag
= LineTag
.FindTag(caret
.line
, caret
.pos
);
1684 case CaretDirection
.PgUp
: {
1686 int new_y
, y_offset
;
1688 if (viewport_y
== 0) {
1690 // This should probably be handled elsewhere
1691 if (!(owner
is RichTextBox
)) {
1692 // Page down doesn't do anything in a regular TextBox
1693 // if the bottom of the document
1694 // is already visible, the page and the caret stay still
1698 // We're just placing the caret at the end of the document, no scrolling needed
1699 owner
.vscroll
.Value
= 0;
1700 Line line
= GetLine (1);
1701 PositionCaret (line
, 0);
1704 y_offset
= caret
.line
.Y
- viewport_y
;
1705 new_y
= caret
.line
.Y
- viewport_height
;
1707 owner
.vscroll
.Value
= Math
.Max (new_y
, 0);
1708 PositionCaret ((int)caret
.line
.widths
[caret
.pos
], y_offset
+ viewport_y
);
1712 case CaretDirection
.PgDn
: {
1713 int new_y
, y_offset
;
1715 if ((viewport_y
+ viewport_height
) > document_y
) {
1717 // This should probably be handled elsewhere
1718 if (!(owner
is RichTextBox
)) {
1719 // Page up doesn't do anything in a regular TextBox
1720 // if the bottom of the document
1721 // is already visible, the page and the caret stay still
1725 // We're just placing the caret at the end of the document, no scrolling needed
1726 owner
.vscroll
.Value
= owner
.vscroll
.Maximum
- viewport_height
+ 1;
1727 Line line
= GetLine (lines
);
1728 PositionCaret (line
, line
.Text
.Length
);
1731 y_offset
= caret
.line
.Y
- viewport_y
;
1732 new_y
= caret
.line
.Y
+ viewport_height
;
1734 owner
.vscroll
.Value
= Math
.Min (new_y
, owner
.vscroll
.Maximum
- viewport_height
+ 1);
1735 PositionCaret ((int)caret
.line
.widths
[caret
.pos
], y_offset
+ viewport_y
);
1740 case CaretDirection
.CtrlPgUp
: {
1741 PositionCaret(0, viewport_y
);
1746 case CaretDirection
.CtrlPgDn
: {
1751 tag
= FindTag(0, viewport_y
+ viewport_height
, out index
, false);
1752 if (tag
.line
.line_no
> 1) {
1753 line
= GetLine(tag
.line
.line_no
- 1);
1757 PositionCaret(line
, line
.Text
.Length
);
1762 case CaretDirection
.CtrlHome
: {
1763 caret
.line
= GetLine(1);
1765 caret
.tag
= caret
.line
.tags
;
1771 case CaretDirection
.CtrlEnd
: {
1772 caret
.line
= GetLine(lines
);
1773 caret
.pos
= caret
.line
.text
.Length
;
1774 caret
.tag
= LineTag
.FindTag(caret
.line
, caret
.pos
);
1780 case CaretDirection
.SelectionStart
: {
1781 caret
.line
= selection_start
.line
;
1782 caret
.pos
= selection_start
.pos
;
1783 caret
.tag
= selection_start
.tag
;
1789 case CaretDirection
.SelectionEnd
: {
1790 caret
.line
= selection_end
.line
;
1791 caret
.pos
= selection_end
.pos
;
1792 caret
.tag
= selection_end
.tag
;
1800 // Draw the document
1801 internal void Draw(Graphics g
, Rectangle clip
) {
1802 Line line
; // Current line being drawn
1803 LineTag tag
; // Current tag being drawn
1804 int start
; // First line to draw
1805 int end
; // Last line to draw
1806 StringBuilder text
; // String representing the current line
1812 // First, figure out from what line to what line we need to draw
1813 start
= GetLineByPixel(clip
.Top
+ viewport_y
, false).line_no
;
1814 end
= GetLineByPixel(clip
.Bottom
+ viewport_y
, false).line_no
;
1815 //Console.WriteLine("Starting drawing at line {0}, ending at line {1} (clip-bottom:{2})", start, end, clip.Bottom);
1817 // Now draw our elements; try to only draw those that are visible
1821 DateTime n
= DateTime
.Now
;
1822 Console
.WriteLine("Started drawing: {0}s {1}ms", n
.Second
, n
.Millisecond
);
1825 disabled
= ThemeEngine
.Current
.ResPool
.GetSolidBrush(ThemeEngine
.Current
.ColorGrayText
);
1826 hilight
= ThemeEngine
.Current
.ResPool
.GetSolidBrush(ThemeEngine
.Current
.ColorHighlight
);
1827 hilight_text
= ThemeEngine
.Current
.ResPool
.GetSolidBrush(ThemeEngine
.Current
.ColorHighlightText
);
1829 while (line_no
<= end
) {
1830 line
= GetLine(line_no
);
1832 if (owner
.backcolor_set
|| (owner
.Enabled
&& !owner
.read_only
)) {
1833 g
.FillRectangle(ThemeEngine
.Current
.ResPool
.GetSolidBrush(owner
.BackColor
), new Rectangle(clip
.Left
, line
.Y
- viewport_y
, clip
.Width
, line
.Y
- viewport_y
+ line
.Height
));
1835 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
));
1844 // This fails if there's a password > 1024 chars...
1845 text
= this.password_cache
;
1847 while (tag
!= null) {
1848 if (tag
.length
== 0) {
1853 if (((tag
.X
+ tag
.width
) > (clip
.Left
- viewport_x
)) || (tag
.X
< (clip
.Right
- viewport_x
))) {
1854 // Check for selection
1855 if ((!selection_visible
) || (!owner
.ShowSelection
) || (line_no
< selection_start
.line
.line_no
) || (line_no
> selection_end
.line
.line_no
)) {
1856 // regular drawing, no selection to deal with
1857 //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);
1858 if (owner
.is_enabled
) {
1859 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
);
1864 a
= ((SolidBrush
)tag
.color
).Color
;
1865 b
= ThemeEngine
.Current
.ColorWindowText
;
1867 if ((a
.R
== b
.R
) && (a
.G
== b
.G
) && (a
.B
== b
.B
)) {
1868 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
);
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
);
1874 // we might have to draw our selection
1875 if ((line_no
!= selection_start
.line
.line_no
) && (line_no
!= selection_end
.line
.line_no
)) {
1876 // Special case, whole line is selected, draw this tag selected
1879 tag
.X
+ line
.align_shift
- viewport_x
, // X
1880 line
.Y
+ tag
.shift
- viewport_y
, // Y
1881 line
.widths
[tag
.start
+ tag
.length
- 1], // width
1882 tag
.height
// Height
1886 //s.Substring(tag.start-1, tag.length), // String
1887 text
.ToString(tag
.start
-1, tag
.length
), // String
1889 hilight_text
, // Brush
1890 tag
.X
+ line
.align_shift
- viewport_x
, // X
1891 line
.Y
+ tag
.shift
- viewport_y
, // Y
1892 StringFormat
.GenericTypographic
);
1900 // One or more, but not all tags on the line are selected
1901 if ((selection_start
.tag
== tag
) && (selection_end
.tag
== tag
)) {
1902 // Single tag selected, draw "normalSELECTEDnormal"
1904 // First, the regular part
1906 //s.Substring(tag.start - 1, selection_start.pos - tag.start + 1), // String
1907 text
.ToString(tag
.start
- 1, selection_start
.pos
- tag
.start
+ 1), // String
1910 tag
.X
+ line
.align_shift
- viewport_x
, // X
1911 line
.Y
+ tag
.shift
- viewport_y
, // Y
1912 StringFormat
.GenericTypographic
);
1914 // Now the highlight
1917 line
.widths
[selection_start
.pos
] + line
.align_shift
- viewport_x
, // X
1918 line
.Y
+ tag
.shift
- viewport_y
, // Y
1919 line
.widths
[selection_end
.pos
] - line
.widths
[selection_start
.pos
], // Width
1920 tag
.height
); // Height
1923 //s.Substring(selection_start.pos, selection_end.pos - selection_start.pos), // String
1924 text
.ToString(selection_start
.pos
, selection_end
.pos
- selection_start
.pos
), // String
1926 hilight_text
, // Brush
1927 line
.widths
[selection_start
.pos
] + line
.align_shift
- viewport_x
, // X
1928 line
.Y
+ tag
.shift
- viewport_y
, // Y
1929 StringFormat
.GenericTypographic
);
1931 // And back to the regular
1933 //s.Substring(selection_end.pos, tag.start + tag.length - selection_end.pos - 1), // String
1934 text
.ToString(selection_end
.pos
, tag
.start
+ tag
.length
- selection_end
.pos
- 1), // String
1937 line
.widths
[selection_end
.pos
] + line
.align_shift
- viewport_x
, // X
1938 line
.Y
+ tag
.shift
- viewport_y
, // Y
1939 StringFormat
.GenericTypographic
);
1941 } else if (selection_start
.tag
== tag
) {
1944 // The highlighted part
1947 line
.widths
[selection_start
.pos
] + line
.align_shift
- viewport_x
,
1948 line
.Y
+ tag
.shift
- viewport_y
,
1949 line
.widths
[tag
.start
+ tag
.length
- 1] - line
.widths
[selection_start
.pos
],
1953 //s.Substring(selection_start.pos, tag.start + tag.length - selection_start.pos - 1), // String
1954 text
.ToString(selection_start
.pos
, tag
.start
+ tag
.length
- selection_start
.pos
- 1), // String
1956 hilight_text
, // Brush
1957 line
.widths
[selection_start
.pos
] + line
.align_shift
- viewport_x
, // X
1958 line
.Y
+ tag
.shift
- viewport_y
, // Y
1959 StringFormat
.GenericTypographic
);
1963 //s.Substring(tag.start - 1, selection_start.pos - tag.start + 1), // String
1964 text
.ToString(tag
.start
- 1, selection_start
.pos
- tag
.start
+ 1), // String
1967 tag
.X
+ line
.align_shift
- viewport_x
, // X
1968 line
.Y
+ tag
.shift
- viewport_y
, // Y
1969 StringFormat
.GenericTypographic
);
1970 } else if (selection_end
.tag
== tag
) {
1973 // The highlighted part
1976 tag
.X
+ line
.align_shift
- viewport_x
,
1977 line
.Y
+ tag
.shift
- viewport_y
,
1978 line
.widths
[selection_end
.pos
] - line
.widths
[tag
.start
- 1],
1982 //s.Substring(tag.start - 1, selection_end.pos - tag.start + 1), // String
1983 text
.ToString(tag
.start
- 1, selection_end
.pos
- tag
.start
+ 1), // String
1985 hilight_text
, // Brush
1986 tag
.X
+ line
.align_shift
- viewport_x
, // X
1987 line
.Y
+ tag
.shift
- viewport_y
, // Y
1988 StringFormat
.GenericTypographic
);
1992 //s.Substring(selection_end.pos, tag.start + tag.length - selection_end.pos - 1), // String
1993 text
.ToString(selection_end
.pos
, tag
.start
+ tag
.length
- selection_end
.pos
- 1), // String
1996 line
.widths
[selection_end
.pos
] + line
.align_shift
- viewport_x
, // X
1997 line
.Y
+ tag
.shift
- viewport_y
, // Y
1998 StringFormat
.GenericTypographic
);
2000 // no partially selected tags here, simple checks...
2001 if (selection_start
.line
== line
) {
2005 begin
= tag
.start
- 1;
2006 stop
= tag
.start
+ tag
.length
- 1;
2007 if (selection_end
.line
== line
) {
2008 if ((begin
>= selection_start
.pos
) && (stop
< selection_end
.pos
)) {
2012 if (stop
> selection_start
.pos
) {
2016 } else if (selection_end
.line
== line
) {
2017 if ((tag
.start
- 1) < selection_end
.pos
) {
2027 tag
.X
+ line
.align_shift
- viewport_x
,
2028 line
.Y
+ tag
.shift
- viewport_y
,
2029 line
.widths
[tag
.start
+ tag
.length
- 1] - line
.widths
[tag
.start
- 1],
2033 //s.Substring(tag.start-1, tag.length), // String
2034 text
.ToString(tag
.start
-1, tag
.length
), // String
2036 hilight_text
, // Brush
2037 tag
.X
+ line
.align_shift
- viewport_x
, // X
2038 line
.Y
+ tag
.shift
- viewport_y
, // Y
2039 StringFormat
.GenericTypographic
);
2042 //s.Substring(tag.start-1, tag.length), // String
2043 text
.ToString(tag
.start
-1, tag
.length
), // String
2046 tag
.X
+ line
.align_shift
- viewport_x
, // X
2047 line
.Y
+ tag
.shift
- viewport_y
, // Y
2048 StringFormat
.GenericTypographic
);
2063 Console
.WriteLine("Finished drawing: {0}s {1}ms", n
.Second
, n
.Millisecond
);
2068 internal void Insert(Line line
, int pos
, string s
) {
2069 Insert(line
, null, pos
, false, s
);
2072 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
2073 internal void Insert(Line line
, LineTag tag
, int pos
, bool update_caret
, string s
) {
2080 // The formatting at the insertion point is used for the inserted text
2082 tag
= LineTag
.FindTag(line
, pos
);
2085 base_line
= line
.line_no
;
2087 ins
= s
.Split(new char[] {'\n'}
);
2089 for (int j
= 0; j
< ins
.Length
; j
++) {
2090 if (ins
[j
].EndsWith("\r")) {
2091 ins
[j
] = ins
[j
].Substring(0, ins
[j
].Length
- 1);
2095 insert_lines
= ins
.Length
;
2096 old_line_count
= lines
;
2098 // Bump the text at insertion point a line down if we're inserting more than one line
2099 if (insert_lines
> 1) {
2101 // Remainder of start line is now in base_line + 1
2104 // Insert the first line
2105 InsertString(tag
, pos
, ins
[0]);
2107 if (insert_lines
> 1) {
2108 for (i
= 1; i
< insert_lines
; i
++) {
2109 Add(base_line
+ i
, ins
[i
], line
.alignment
, tag
.font
, tag
.color
);
2111 if (!s
.EndsWith("\n\n")) {
2112 this.Combine(base_line
+ (lines
- old_line_count
) - 1, base_line
+ lines
- old_line_count
);
2116 UpdateView(line
, lines
- old_line_count
+ 1, pos
);
2119 // Move caret to the end of the inserted text
2120 if (insert_lines
> 1) {
2121 Line l
= GetLine (line
.line_no
+ lines
- old_line_count
);
2122 PositionCaret(l
, l
.text
.Length
);
2124 PositionCaret(line
, pos
+ ins
[0].Length
);
2130 // Inserts a character at the given position
2131 internal void InsertString(Line line
, int pos
, string s
) {
2132 InsertString(line
.FindTag(pos
), pos
, s
);
2135 // Inserts a string at the given position
2136 internal void InsertString(LineTag tag
, int pos
, string s
) {
2145 line
.text
.Insert(pos
, s
);
2148 // TODO: sometimes getting a null tag here when pasting ???
2150 while (tag
!= null) {
2157 UpdateView(line
, pos
);
2160 // Inserts a string at the caret position
2161 internal void InsertStringAtCaret(string s
, bool move_caret
) {
2169 caret
.line
.text
.Insert(caret
.pos
, s
);
2170 caret
.tag
.length
+= len
;
2172 if (caret
.tag
.next
!= null) {
2173 tag
= caret
.tag
.next
;
2174 while (tag
!= null) {
2179 caret
.line
.Grow(len
);
2180 caret
.line
.recalc
= true;
2182 UpdateView(caret
.line
, caret
.pos
);
2191 // Inserts a character at the given position
2192 internal void InsertChar(Line line
, int pos
, char ch
) {
2193 InsertChar(line
.FindTag(pos
), pos
, ch
);
2196 // Inserts a character at the given position
2197 internal void InsertChar(LineTag tag
, int pos
, char ch
) {
2203 line
.text
.Insert(pos
, ch
);
2207 while (tag
!= null) {
2214 UpdateView(line
, pos
);
2217 // Inserts a character at the current caret position
2218 internal void InsertCharAtCaret(char ch
, bool move_caret
) {
2223 caret
.line
.text
.Insert(caret
.pos
, ch
);
2226 if (caret
.tag
.next
!= null) {
2227 tag
= caret
.tag
.next
;
2228 while (tag
!= null) {
2234 caret
.line
.recalc
= true;
2236 UpdateView(caret
.line
, caret
.pos
);
2240 SetSelectionToCaret(true);
2244 // Deletes n characters at the given position; it will not delete past line limits
2246 internal void DeleteChars(LineTag tag
, int pos
, int count
) {
2255 if (pos
== line
.text
.Length
) {
2259 line
.text
.Remove(pos
, count
);
2261 // Make sure the tag points to the right spot
2262 while ((tag
!= null) && (tag
.start
+ tag
.length
- 1) <= pos
) {
2270 // Check if we're crossing tag boundaries
2271 if ((pos
+ count
) > (tag
.start
+ tag
.length
- 1)) {
2274 // We have to delete cross tag boundaries
2278 left
-= tag
.start
+ tag
.length
- pos
- 1;
2279 tag
.length
-= tag
.start
+ tag
.length
- pos
- 1;
2282 while ((tag
!= null) && (left
> 0)) {
2283 tag
.start
-= count
- left
;
2284 if (tag
.length
> left
) {
2295 // We got off easy, same tag
2297 tag
.length
-= count
;
2299 if (tag
.length
== 0) {
2304 // Delete empty orphaned tags at the end
2306 while (walk
!= null && walk
.next
!= null && walk
.next
.length
== 0) {
2308 walk
.next
= walk
.next
.next
;
2309 if (walk
.next
!= null)
2310 walk
.next
.previous
= t
;
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) {
2378 // Delete empty orphaned tags at the end
2380 while (walk
!= null && walk
.next
!= null && walk
.next
.length
== 0) {
2382 walk
.next
= walk
.next
.next
;
2383 if (walk
.next
!= null)
2384 walk
.next
.previous
= t
;
2389 while (tag
!= null) {
2395 line
.Streamline(lines
);
2398 UpdateView(line
, pos
);
2401 // Combine two lines
2402 internal void Combine(int FirstLine
, int SecondLine
) {
2403 Combine(GetLine(FirstLine
), GetLine(SecondLine
));
2406 internal void Combine(Line first
, Line second
) {
2410 // Combine the two tag chains into one
2413 // Maintain the line ending style
2414 first
.soft_break
= second
.soft_break
;
2416 while (last
.next
!= null) {
2420 last
.next
= second
.tags
;
2421 last
.next
.previous
= last
;
2423 shift
= last
.start
+ last
.length
- 1;
2425 // Fix up references within the chain
2427 while (last
!= null) {
2429 last
.start
+= shift
;
2433 // Combine both lines' strings
2434 first
.text
.Insert(first
.text
.Length
, second
.text
.ToString());
2435 first
.Grow(first
.text
.Length
);
2437 // Remove the reference to our (now combined) tags from the doomed line
2441 DecrementLines(first
.line_no
+ 2); // first.line_no + 1 will be deleted, so we need to start renumbering one later
2444 first
.recalc
= true;
2445 first
.height
= 0; // This forces RecalcDocument/UpdateView to redraw from this line on
2446 first
.Streamline(lines
);
2448 // Update Caret, Selection, etc
2449 if (caret
.line
== second
) {
2450 caret
.Combine(first
, shift
);
2452 if (selection_anchor
.line
== second
) {
2453 selection_anchor
.Combine(first
, shift
);
2455 if (selection_start
.line
== second
) {
2456 selection_start
.Combine(first
, shift
);
2458 if (selection_end
.line
== second
) {
2459 selection_end
.Combine(first
, shift
);
2466 check_first
= GetLine(first
.line_no
);
2467 check_second
= GetLine(check_first
.line_no
+ 1);
2469 Console
.WriteLine("Pre-delete: Y of first line: {0}, second line: {1}", check_first
.Y
, check_second
.Y
);
2472 this.Delete(second
);
2475 check_first
= GetLine(first
.line_no
);
2476 check_second
= GetLine(check_first
.line_no
+ 1);
2478 Console
.WriteLine("Post-delete Y of first line: {0}, second line: {1}", check_first
.Y
, check_second
.Y
);
2482 // Split the line at the position into two
2483 internal void Split(int LineNo
, int pos
) {
2487 line
= GetLine(LineNo
);
2488 tag
= LineTag
.FindTag(line
, pos
);
2489 Split(line
, tag
, pos
, false);
2492 internal void Split(Line line
, int pos
) {
2495 tag
= LineTag
.FindTag(line
, pos
);
2496 Split(line
, tag
, pos
, false);
2499 ///<summary>Split line at given tag and position into two lines</summary>
2500 ///<param name="soft">True if the split should be marked as 'soft', indicating that it can be contracted
2501 ///if more space becomes available on previous line</param>
2502 internal void Split(Line line
, LineTag tag
, int pos
, bool soft
) {
2506 bool move_sel_start
;
2510 move_sel_start
= false;
2511 move_sel_end
= false;
2513 // Adjust selection and cursors
2514 if (soft
&& (caret
.line
== line
) && (caret
.pos
>= pos
)) {
2517 if (selection_start
.line
== line
&& selection_start
.pos
> pos
) {
2518 move_sel_start
= true;
2521 if (selection_end
.line
== line
&& selection_end
.pos
> pos
) {
2522 move_sel_end
= true;
2525 // cover the easy case first
2526 if (pos
== line
.text
.Length
) {
2527 Add(line
.line_no
+ 1, "", line
.alignment
, tag
.font
, tag
.color
);
2529 new_line
= GetLine(line
.line_no
+ 1);
2533 caret
.line
= new_line
;
2534 caret
.line
.soft_break
= true;
2535 caret
.tag
= new_line
.tags
;
2538 new_line
.soft_break
= true;
2542 if (move_sel_start
) {
2543 selection_start
.line
= new_line
;
2544 selection_start
.pos
= 0;
2545 selection_start
.tag
= new_line
.tags
;
2549 selection_end
.line
= new_line
;
2550 selection_end
.pos
= 0;
2551 selection_end
.tag
= new_line
.tags
;
2556 // We need to move the rest of the text into the new line
2557 Add(line
.line_no
+ 1, line
.text
.ToString(pos
, line
.text
.Length
- pos
), line
.alignment
, tag
.font
, tag
.color
);
2559 // Now transfer our tags from this line to the next
2560 new_line
= GetLine(line
.line_no
+ 1);
2562 new_line
.recalc
= true;
2564 if ((tag
.start
- 1) == pos
) {
2567 // We can simply break the chain and move the tag into the next line
2568 if (tag
== line
.tags
) {
2569 new_tag
= new LineTag(line
, 1, 0);
2570 new_tag
.font
= tag
.font
;
2571 new_tag
.color
= tag
.color
;
2572 line
.tags
= new_tag
;
2575 if (tag
.previous
!= null) {
2576 tag
.previous
.next
= null;
2578 new_line
.tags
= tag
;
2579 tag
.previous
= null;
2580 tag
.line
= new_line
;
2582 // Walk the list and correct the start location of the tags we just bumped into the next line
2583 shift
= tag
.start
- 1;
2586 while (new_tag
!= null) {
2587 new_tag
.start
-= shift
;
2588 new_tag
.line
= new_line
;
2589 new_tag
= new_tag
.next
;
2594 new_tag
= new LineTag(new_line
, 1, tag
.start
- 1 + tag
.length
- pos
);
2595 new_tag
.next
= tag
.next
;
2596 new_tag
.font
= tag
.font
;
2597 new_tag
.color
= tag
.color
;
2598 new_line
.tags
= new_tag
;
2599 if (new_tag
.next
!= null) {
2600 new_tag
.next
.previous
= new_tag
;
2603 tag
.length
= pos
- tag
.start
+ 1;
2606 new_tag
= new_tag
.next
;
2607 while (new_tag
!= null) {
2608 new_tag
.start
-= shift
;
2609 new_tag
.line
= new_line
;
2610 new_tag
= new_tag
.next
;
2617 caret
.line
= new_line
;
2618 caret
.pos
= caret
.pos
- pos
;
2619 caret
.tag
= caret
.line
.FindTag(caret
.pos
);
2621 new_line
.soft_break
= true;
2624 if (move_sel_start
) {
2625 selection_start
.line
= new_line
;
2626 selection_start
.pos
= selection_start
.pos
- pos
;
2627 selection_start
.tag
= new_line
.FindTag(selection_start
.pos
);
2631 selection_end
.line
= new_line
;
2632 selection_end
.pos
= selection_end
.pos
- pos
;
2633 selection_end
.tag
= new_line
.FindTag(selection_end
.pos
);
2636 CharCount
-= line
.text
.Length
- pos
;
2637 line
.text
.Remove(pos
, line
.text
.Length
- pos
);
2640 // Adds a line of text, with given font.
2641 // Bumps any line at that line number that already exists down
2642 internal void Add(int LineNo
, string Text
, Font font
, Brush color
) {
2643 Add(LineNo
, Text
, HorizontalAlignment
.Left
, font
, color
);
2646 internal void Add(int LineNo
, string Text
, HorizontalAlignment align
, Font font
, Brush color
) {
2651 CharCount
+= Text
.Length
;
2653 if (LineNo
<1 || Text
== null) {
2655 throw new ArgumentNullException("LineNo", "Line numbers must be positive");
2657 throw new ArgumentNullException("Text", "Cannot insert NULL line");
2661 add = new Line(LineNo
, Text
, align
, font
, color
);
2664 while (line
!= sentinel
) {
2666 line_no
= line
.line_no
;
2668 if (LineNo
> line_no
) {
2670 } else if (LineNo
< line_no
) {
2673 // Bump existing line numbers; walk all nodes to the right of this one and increment line_no
2674 IncrementLines(line
.line_no
);
2679 add.left
= sentinel
;
2680 add.right
= sentinel
;
2682 if (add.parent
!= null) {
2683 if (LineNo
> add.parent
.line_no
) {
2684 add.parent
.right
= add;
2686 add.parent
.left
= add;
2693 RebalanceAfterAdd(add);
2698 internal virtual void Clear() {
2701 document
= sentinel
;
2704 public virtual object Clone() {
2707 clone
= new Document(null);
2709 clone
.lines
= this.lines
;
2710 clone
.document
= (Line
)document
.Clone();
2715 internal void Delete(int LineNo
) {
2722 line
= GetLine(LineNo
);
2724 CharCount
-= line
.text
.Length
;
2726 DecrementLines(LineNo
+ 1);
2730 internal void Delete(Line line1
) {
2731 Line line2
;// = new Line();
2734 if ((line1
.left
== sentinel
) || (line1
.right
== sentinel
)) {
2737 line3
= line1
.right
;
2738 while (line3
.left
!= sentinel
) {
2743 if (line3
.left
!= sentinel
) {
2746 line2
= line3
.right
;
2749 line2
.parent
= line3
.parent
;
2750 if (line3
.parent
!= null) {
2751 if(line3
== line3
.parent
.left
) {
2752 line3
.parent
.left
= line2
;
2754 line3
.parent
.right
= line2
;
2760 if (line3
!= line1
) {
2763 if (selection_start
.line
== line3
) {
2764 selection_start
.line
= line1
;
2767 if (selection_end
.line
== line3
) {
2768 selection_end
.line
= line1
;
2771 if (selection_anchor
.line
== line3
) {
2772 selection_anchor
.line
= line1
;
2775 if (caret
.line
== line3
) {
2780 line1
.alignment
= line3
.alignment
;
2781 line1
.ascent
= line3
.ascent
;
2782 line1
.hanging_indent
= line3
.hanging_indent
;
2783 line1
.height
= line3
.height
;
2784 line1
.indent
= line3
.indent
;
2785 line1
.line_no
= line3
.line_no
;
2786 line1
.recalc
= line3
.recalc
;
2787 line1
.right_indent
= line3
.right_indent
;
2788 line1
.soft_break
= line3
.soft_break
;
2789 line1
.space
= line3
.space
;
2790 line1
.tags
= line3
.tags
;
2791 line1
.text
= line3
.text
;
2792 line1
.widths
= line3
.widths
;
2796 while (tag
!= null) {
2802 if (line3
.color
== LineColor
.Black
)
2803 RebalanceAfterDelete(line2
);
2807 last_found
= sentinel
;
2810 // Invalidate a section of the document to trigger redraw
2811 internal void Invalidate(Line start
, int start_pos
, Line end
, int end_pos
) {
2817 if ((start
== end
) && (start_pos
== end_pos
)) {
2821 if (end_pos
== -1) {
2822 end_pos
= end
.text
.Length
;
2825 // figure out what's before what so the logic below is straightforward
2826 if (start
.line_no
< end
.line_no
) {
2832 } else if (start
.line_no
> end
.line_no
) {
2839 if (start_pos
< end_pos
) {
2854 Console
.WriteLine("Invaliding from {0}:{1} to {2}:{3}", l1
.line_no
, p1
, l2
.line_no
, p2
);
2859 (int)l1
.widths
[p1
] + l1
.align_shift
- viewport_x
,
2861 (int)l2
.widths
[p2
] - (int)l1
.widths
[p1
] + 1,
2869 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
);
2872 // Three invalidates:
2873 // First line from start
2874 owner
.Invalidate(new Rectangle((int)l1
.widths
[p1
] + l1
.align_shift
- viewport_x
, l1
.Y
- viewport_y
, viewport_width
, l1
.height
));
2877 if ((l1
.line_no
+ 1) < l2
.line_no
) {
2880 y
= GetLine(l1
.line_no
+ 1).Y
;
2881 owner
.Invalidate(new Rectangle(0, y
- viewport_y
, viewport_width
, GetLine(l2
.line_no
).Y
- y
- viewport_y
));
2884 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
);
2889 owner
.Invalidate(new Rectangle((int)l2
.widths
[0] + l2
.align_shift
- viewport_x
, l2
.Y
- viewport_y
, (int)l2
.widths
[p2
] + 1, l2
.height
));
2891 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
);
2895 /// <summary>Select text around caret</summary>
2896 internal void ExpandSelection(CaretSelection mode
, bool to_caret
) {
2898 // We're expanding the selection to the caret position
2900 case CaretSelection
.Line
: {
2901 // Invalidate the selection delta
2902 if (caret
> selection_prev
) {
2903 Invalidate(selection_prev
.line
, 0, caret
.line
, caret
.line
.text
.Length
);
2905 Invalidate(selection_prev
.line
, selection_prev
.line
.text
.Length
, caret
.line
, 0);
2908 if (caret
.line
.line_no
<= selection_anchor
.line
.line_no
) {
2909 selection_start
.line
= caret
.line
;
2910 selection_start
.tag
= caret
.line
.tags
;
2911 selection_start
.pos
= 0;
2913 selection_end
.line
= selection_anchor
.line
;
2914 selection_end
.tag
= selection_anchor
.tag
;
2915 selection_end
.pos
= selection_anchor
.pos
;
2917 selection_end_anchor
= true;
2919 selection_start
.line
= selection_anchor
.line
;
2920 selection_start
.pos
= selection_anchor
.height
;
2921 selection_start
.tag
= selection_anchor
.line
.FindTag(selection_anchor
.height
);
2923 selection_end
.line
= caret
.line
;
2924 selection_end
.tag
= caret
.line
.tags
;
2925 selection_end
.pos
= caret
.line
.text
.Length
;
2927 selection_end_anchor
= false;
2929 selection_prev
.line
= caret
.line
;
2930 selection_prev
.tag
= caret
.tag
;
2931 selection_prev
.pos
= caret
.pos
;
2936 case CaretSelection
.Word
: {
2940 start_pos
= FindWordSeparator(caret
.line
, caret
.pos
, false);
2941 end_pos
= FindWordSeparator(caret
.line
, caret
.pos
, true);
2944 // Invalidate the selection delta
2945 if (caret
> selection_prev
) {
2946 Invalidate(selection_prev
.line
, selection_prev
.pos
, caret
.line
, end_pos
);
2948 Invalidate(selection_prev
.line
, selection_prev
.pos
, caret
.line
, start_pos
);
2950 if (caret
< selection_anchor
) {
2951 selection_start
.line
= caret
.line
;
2952 selection_start
.tag
= caret
.line
.FindTag(start_pos
);
2953 selection_start
.pos
= start_pos
;
2955 selection_end
.line
= selection_anchor
.line
;
2956 selection_end
.tag
= selection_anchor
.tag
;
2957 selection_end
.pos
= selection_anchor
.pos
;
2959 selection_prev
.line
= caret
.line
;
2960 selection_prev
.tag
= caret
.tag
;
2961 selection_prev
.pos
= start_pos
;
2963 selection_end_anchor
= true;
2965 selection_start
.line
= selection_anchor
.line
;
2966 selection_start
.pos
= selection_anchor
.height
;
2967 selection_start
.tag
= selection_anchor
.line
.FindTag(selection_anchor
.height
);
2969 selection_end
.line
= caret
.line
;
2970 selection_end
.tag
= caret
.line
.FindTag(end_pos
);
2971 selection_end
.pos
= end_pos
;
2973 selection_prev
.line
= caret
.line
;
2974 selection_prev
.tag
= caret
.tag
;
2975 selection_prev
.pos
= end_pos
;
2977 selection_end_anchor
= false;
2982 case CaretSelection
.Position
: {
2983 SetSelectionToCaret(false);
2988 // We're setting the selection 'around' the caret position
2990 case CaretSelection
.Line
: {
2991 this.Invalidate(caret
.line
, 0, caret
.line
, caret
.line
.text
.Length
);
2993 selection_start
.line
= caret
.line
;
2994 selection_start
.tag
= caret
.line
.tags
;
2995 selection_start
.pos
= 0;
2997 selection_end
.line
= caret
.line
;
2998 selection_end
.pos
= caret
.line
.text
.Length
;
2999 selection_end
.tag
= caret
.line
.FindTag(selection_end
.pos
);
3001 selection_anchor
.line
= selection_end
.line
;
3002 selection_anchor
.tag
= selection_end
.tag
;
3003 selection_anchor
.pos
= selection_end
.pos
;
3004 selection_anchor
.height
= 0;
3006 selection_prev
.line
= caret
.line
;
3007 selection_prev
.tag
= caret
.tag
;
3008 selection_prev
.pos
= caret
.pos
;
3010 this.selection_end_anchor
= true;
3015 case CaretSelection
.Word
: {
3019 start_pos
= FindWordSeparator(caret
.line
, caret
.pos
, false);
3020 end_pos
= FindWordSeparator(caret
.line
, caret
.pos
, true);
3022 this.Invalidate(selection_start
.line
, start_pos
, caret
.line
, end_pos
);
3024 selection_start
.line
= caret
.line
;
3025 selection_start
.tag
= caret
.line
.FindTag(start_pos
);
3026 selection_start
.pos
= start_pos
;
3028 selection_end
.line
= caret
.line
;
3029 selection_end
.tag
= caret
.line
.FindTag(end_pos
);
3030 selection_end
.pos
= end_pos
;
3032 selection_anchor
.line
= selection_end
.line
;
3033 selection_anchor
.tag
= selection_end
.tag
;
3034 selection_anchor
.pos
= selection_end
.pos
;
3035 selection_anchor
.height
= start_pos
;
3037 selection_prev
.line
= caret
.line
;
3038 selection_prev
.tag
= caret
.tag
;
3039 selection_prev
.pos
= caret
.pos
;
3041 this.selection_end_anchor
= true;
3048 SetSelectionVisible (!(selection_start
== selection_end
));
3051 internal void SetSelectionToCaret(bool start
) {
3053 // Invalidate old selection; selection is being reset to empty
3054 this.Invalidate(selection_start
.line
, selection_start
.pos
, selection_end
.line
, selection_end
.pos
);
3056 selection_start
.line
= caret
.line
;
3057 selection_start
.tag
= caret
.tag
;
3058 selection_start
.pos
= caret
.pos
;
3060 // start always also selects end
3061 selection_end
.line
= caret
.line
;
3062 selection_end
.tag
= caret
.tag
;
3063 selection_end
.pos
= caret
.pos
;
3065 selection_anchor
.line
= caret
.line
;
3066 selection_anchor
.tag
= caret
.tag
;
3067 selection_anchor
.pos
= caret
.pos
;
3069 // Invalidate from previous end to caret (aka new end)
3070 if (selection_end_anchor
) {
3071 if (selection_start
!= caret
) {
3072 this.Invalidate(selection_start
.line
, selection_start
.pos
, caret
.line
, caret
.pos
);
3075 if (selection_end
!= caret
) {
3076 this.Invalidate(selection_end
.line
, selection_end
.pos
, caret
.line
, caret
.pos
);
3080 if (caret
< selection_anchor
) {
3081 selection_start
.line
= caret
.line
;
3082 selection_start
.tag
= caret
.tag
;
3083 selection_start
.pos
= caret
.pos
;
3085 selection_end
.line
= selection_anchor
.line
;
3086 selection_end
.tag
= selection_anchor
.tag
;
3087 selection_end
.pos
= selection_anchor
.pos
;
3089 selection_end_anchor
= true;
3091 selection_start
.line
= selection_anchor
.line
;
3092 selection_start
.tag
= selection_anchor
.tag
;
3093 selection_start
.pos
= selection_anchor
.pos
;
3095 selection_end
.line
= caret
.line
;
3096 selection_end
.tag
= caret
.tag
;
3097 selection_end
.pos
= caret
.pos
;
3099 selection_end_anchor
= false;
3103 SetSelectionVisible (!(selection_start
== selection_end
));
3106 internal void SetSelection(Line start
, int start_pos
, Line end
, int end_pos
) {
3107 if (selection_visible
) {
3108 Invalidate(selection_start
.line
, selection_start
.pos
, selection_end
.line
, selection_end
.pos
);
3111 if ((end
.line_no
< start
.line_no
) || ((end
== start
) && (end_pos
<= start_pos
))) {
3112 selection_start
.line
= end
;
3113 selection_start
.tag
= LineTag
.FindTag(end
, end_pos
);
3114 selection_start
.pos
= end_pos
;
3116 selection_end
.line
= start
;
3117 selection_end
.tag
= LineTag
.FindTag(start
, start_pos
);
3118 selection_end
.pos
= start_pos
;
3120 selection_end_anchor
= true;
3122 selection_start
.line
= start
;
3123 selection_start
.tag
= LineTag
.FindTag(start
, start_pos
);
3124 selection_start
.pos
= start_pos
;
3126 selection_end
.line
= end
;
3127 selection_end
.tag
= LineTag
.FindTag(end
, end_pos
);
3128 selection_end
.pos
= end_pos
;
3130 selection_end_anchor
= false;
3133 selection_anchor
.line
= start
;
3134 selection_anchor
.tag
= selection_start
.tag
;
3135 selection_anchor
.pos
= start_pos
;
3137 if (((start
== end
) && (start_pos
== end_pos
)) || start
== null || end
== null) {
3138 SetSelectionVisible (false);
3140 SetSelectionVisible (true);
3141 Invalidate(selection_start
.line
, selection_start
.pos
, selection_end
.line
, selection_end
.pos
);
3145 internal void SetSelectionStart(Line start
, int start_pos
) {
3146 // Invalidate from the previous to the new start pos
3147 Invalidate(selection_start
.line
, selection_start
.pos
, start
, start_pos
);
3149 selection_start
.line
= start
;
3150 selection_start
.pos
= start_pos
;
3151 selection_start
.tag
= LineTag
.FindTag(start
, start_pos
);
3153 selection_anchor
.line
= start
;
3154 selection_anchor
.pos
= start_pos
;
3155 selection_anchor
.tag
= selection_start
.tag
;
3157 selection_end_anchor
= false;
3160 if ((selection_end
.line
!= selection_start
.line
) || (selection_end
.pos
!= selection_start
.pos
)) {
3161 SetSelectionVisible (true);
3163 SetSelectionVisible (false);
3166 Invalidate(selection_start
.line
, selection_start
.pos
, selection_end
.line
, selection_end
.pos
);
3169 internal void SetSelectionStart(int character_index
) {
3174 if (character_index
< 0) {
3178 CharIndexToLineTag(character_index
, out line
, out tag
, out pos
);
3179 SetSelectionStart(line
, pos
);
3182 internal void SetSelectionEnd(Line end
, int end_pos
) {
3184 if (end
== selection_end
.line
&& end_pos
== selection_start
.pos
) {
3185 selection_anchor
.line
= selection_start
.line
;
3186 selection_anchor
.tag
= selection_start
.tag
;
3187 selection_anchor
.pos
= selection_start
.pos
;
3189 selection_end
.line
= selection_start
.line
;
3190 selection_end
.tag
= selection_start
.tag
;
3191 selection_end
.pos
= selection_start
.pos
;
3193 selection_end_anchor
= false;
3194 } else if ((end
.line_no
< selection_anchor
.line
.line_no
) || ((end
== selection_anchor
.line
) && (end_pos
<= selection_anchor
.pos
))) {
3195 selection_start
.line
= end
;
3196 selection_start
.tag
= LineTag
.FindTag(end
, end_pos
);
3197 selection_start
.pos
= end_pos
;
3199 selection_end
.line
= selection_anchor
.line
;
3200 selection_end
.tag
= selection_anchor
.tag
;
3201 selection_end
.pos
= selection_anchor
.pos
;
3203 selection_end_anchor
= true;
3205 selection_start
.line
= selection_anchor
.line
;
3206 selection_start
.tag
= selection_anchor
.tag
;
3207 selection_start
.pos
= selection_anchor
.pos
;
3209 selection_end
.line
= end
;
3210 selection_end
.tag
= LineTag
.FindTag(end
, end_pos
);
3211 selection_end
.pos
= end_pos
;
3213 selection_end_anchor
= false;
3216 if ((selection_end
.line
!= selection_start
.line
) || (selection_end
.pos
!= selection_start
.pos
)) {
3217 SetSelectionVisible (true);
3218 Invalidate(selection_start
.line
, selection_start
.pos
, selection_end
.line
, selection_end
.pos
);
3220 SetSelectionVisible (false);
3221 // ?? Do I need to invalidate here, tests seem to work without it, but I don't think they should :-s
3225 internal void SetSelectionEnd(int character_index
) {
3230 if (character_index
< 0) {
3234 CharIndexToLineTag(character_index
, out line
, out tag
, out pos
);
3235 SetSelectionEnd(line
, pos
);
3238 internal void SetSelection(Line start
, int start_pos
) {
3239 if (selection_visible
) {
3240 Invalidate(selection_start
.line
, selection_start
.pos
, selection_end
.line
, selection_end
.pos
);
3243 selection_start
.line
= start
;
3244 selection_start
.pos
= start_pos
;
3245 selection_start
.tag
= LineTag
.FindTag(start
, start_pos
);
3247 selection_end
.line
= start
;
3248 selection_end
.tag
= selection_start
.tag
;
3249 selection_end
.pos
= start_pos
;
3251 selection_anchor
.line
= start
;
3252 selection_anchor
.tag
= selection_start
.tag
;
3253 selection_anchor
.pos
= start_pos
;
3255 selection_end_anchor
= false;
3256 SetSelectionVisible (false);
3259 internal void InvalidateSelectionArea() {
3260 Invalidate (selection_start
.line
, selection_start
.pos
, selection_end
.line
, selection_end
.pos
);
3263 // Return the current selection, as string
3264 internal string GetSelection() {
3265 // We return String.Empty if there is no selection
3266 if ((selection_start
.pos
== selection_end
.pos
) && (selection_start
.line
== selection_end
.line
)) {
3267 return string.Empty
;
3270 if (!multiline
|| (selection_start
.line
== selection_end
.line
)) {
3271 return selection_start
.line
.text
.ToString(selection_start
.pos
, selection_end
.pos
- selection_start
.pos
);
3278 sb
= new StringBuilder();
3279 start
= selection_start
.line
.line_no
;
3280 end
= selection_end
.line
.line_no
;
3282 sb
.Append(selection_start
.line
.text
.ToString(selection_start
.pos
, selection_start
.line
.text
.Length
- selection_start
.pos
) + Environment
.NewLine
);
3284 if ((start
+ 1) < end
) {
3285 for (i
= start
+ 1; i
< end
; i
++) {
3286 sb
.Append(GetLine(i
).text
.ToString() + Environment
.NewLine
);
3290 sb
.Append(selection_end
.line
.text
.ToString(0, selection_end
.pos
));
3292 return sb
.ToString();
3296 internal void ReplaceSelection(string s
) {
3299 int selection_start_pos
= LineTagToCharIndex (selection_start
.line
, selection_start
.pos
);
3300 // First, delete any selected text
3301 if ((selection_start
.pos
!= selection_end
.pos
) || (selection_start
.line
!= selection_end
.line
)) {
3302 if (!multiline
|| (selection_start
.line
== selection_end
.line
)) {
3303 undo
.RecordDeleteChars(selection_start
.line
, selection_start
.pos
+ 1, selection_end
.pos
- selection_start
.pos
);
3305 DeleteChars(selection_start
.tag
, selection_start
.pos
, selection_end
.pos
- selection_start
.pos
);
3307 // The tag might have been removed, we need to recalc it
3308 selection_start
.tag
= selection_start
.line
.FindTag(selection_start
.pos
);
3313 start
= selection_start
.line
.line_no
;
3314 end
= selection_end
.line
.line_no
;
3316 undo
.RecordDelete(selection_start
.line
, selection_start
.pos
+ 1, selection_end
.line
, selection_end
.pos
);
3318 // Delete first line
3319 DeleteChars(selection_start
.tag
, selection_start
.pos
, selection_start
.line
.text
.Length
- selection_start
.pos
);
3322 DeleteChars(selection_end
.line
.tags
, 0, selection_end
.pos
);
3326 for (i
= end
- 1; i
>= start
; i
--) {
3331 // BIG FAT WARNING - selection_end.line might be stale due
3332 // to the above Delete() call. DONT USE IT before hitting the end of this method!
3334 // Join start and end
3335 Combine(selection_start
.line
.line_no
, start
);
3340 Insert(selection_start
.line
, null, selection_start
.pos
, true, s
);
3342 CharIndexToLineTag(selection_start_pos
+ s
.Length
+ 1, out selection_start
.line
,
3343 out selection_start
.tag
, out selection_start
.pos
);
3345 selection_end
.line
= selection_start
.line
;
3346 selection_end
.pos
= selection_start
.pos
;
3347 selection_end
.tag
= selection_start
.tag
;
3348 selection_anchor
.line
= selection_start
.line
;
3349 selection_anchor
.pos
= selection_start
.pos
;
3350 selection_anchor
.tag
= selection_start
.tag
;
3352 SetSelectionVisible (false);
3355 internal void CharIndexToLineTag(int index
, out Line line_out
, out LineTag tag_out
, out int pos
) {
3364 for (i
= 1; i
<= lines
; i
++) {
3368 chars
+= line
.text
.Length
+ crlf_size
;
3370 if (index
<= chars
) {
3371 // we found the line
3374 while (tag
!= null) {
3375 if (index
< (start
+ tag
.start
+ tag
.length
)) {
3377 tag_out
= LineTag
.GetFinalTag (tag
);
3378 pos
= index
- start
;
3381 if (tag
.next
== null) {
3384 next_line
= GetLine(line
.line_no
+ 1);
3386 if (next_line
!= null) {
3387 line_out
= next_line
;
3388 tag_out
= LineTag
.GetFinalTag (next_line
.tags
);
3393 tag_out
= LineTag
.GetFinalTag (tag
);
3394 pos
= line_out
.text
.Length
;
3403 line_out
= GetLine(lines
);
3404 tag
= line_out
.tags
;
3405 while (tag
.next
!= null) {
3409 pos
= line_out
.text
.Length
;
3412 internal int LineTagToCharIndex(Line line
, int pos
) {
3416 // Count first and last line
3419 // Count the lines in the middle
3421 for (i
= 1; i
< line
.line_no
; i
++) {
3422 length
+= GetLine(i
).text
.Length
+ crlf_size
;
3430 internal int SelectionLength() {
3431 if ((selection_start
.pos
== selection_end
.pos
) && (selection_start
.line
== selection_end
.line
)) {
3435 if (!multiline
|| (selection_start
.line
== selection_end
.line
)) {
3436 return selection_end
.pos
- selection_start
.pos
;
3443 // Count first and last line
3444 length
= selection_start
.line
.text
.Length
- selection_start
.pos
+ selection_end
.pos
+ crlf_size
;
3446 // Count the lines in the middle
3447 start
= selection_start
.line
.line_no
+ 1;
3448 end
= selection_end
.line
.line_no
;
3451 for (i
= start
; i
< end
; i
++) {
3452 length
+= GetLine(i
).text
.Length
+ crlf_size
;
3463 /// <summary>Give it a Line number and it returns the Line object at with that line number</summary>
3464 internal Line
GetLine(int LineNo
) {
3465 Line line
= document
;
3467 while (line
!= sentinel
) {
3468 if (LineNo
== line
.line_no
) {
3470 } else if (LineNo
< line
.line_no
) {
3480 /// <summary>Retrieve the previous tag; walks line boundaries</summary>
3481 internal LineTag
PreviousTag(LineTag tag
) {
3484 if (tag
.previous
!= null) {
3485 return tag
.previous
;
3489 if (tag
.line
.line_no
== 1) {
3493 l
= GetLine(tag
.line
.line_no
- 1);
3498 while (t
.next
!= null) {
3507 /// <summary>Retrieve the next tag; walks line boundaries</summary>
3508 internal LineTag
NextTag(LineTag tag
) {
3511 if (tag
.next
!= null) {
3516 l
= GetLine(tag
.line
.line_no
+ 1);
3524 internal Line
ParagraphStart(Line line
) {
3525 while (line
.soft_break
) {
3526 line
= GetLine(line
.line_no
- 1);
3531 internal Line
ParagraphEnd(Line line
) {
3534 while (line
.soft_break
) {
3535 l
= GetLine(line
.line_no
+ 1);
3536 if ((l
== null) || (!l
.soft_break
)) {
3544 /// <summary>Give it a Y pixel coordinate and it returns the Line covering that Y coordinate</summary>
3545 internal Line
GetLineByPixel(int y
, bool exact
) {
3546 Line line
= document
;
3549 while (line
!= sentinel
) {
3551 if ((y
>= line
.Y
) && (y
< (line
.Y
+line
.height
))) {
3553 } else if (y
< line
.Y
) {
3566 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3567 internal LineTag
FindTag(int x
, int y
, out int index
, bool exact
) {
3571 line
= GetLineByPixel(y
, exact
);
3578 // Alignment adjustment
3579 x
+= line
.align_shift
;
3582 if (x
>= tag
.X
&& x
< (tag
.X
+tag
.width
)) {
3585 end
= tag
.start
+ tag
.length
- 1;
3587 for (int pos
= tag
.start
; pos
< end
; pos
++) {
3588 if (x
< line
.widths
[pos
]) {
3590 return LineTag
.GetFinalTag (tag
);
3594 return LineTag
.GetFinalTag (tag
);
3596 if (tag
.next
!= null) {
3604 index
= line
.text
.Length
;
3605 return LineTag
.GetFinalTag (tag
);
3610 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3611 internal LineTag
FindCursor(int x
, int y
, out int index
) {
3615 line
= GetLineByPixel(y
, false);
3618 // Adjust for alignment
3619 x
-= line
.align_shift
;
3622 if (x
>= tag
.X
&& x
< (tag
.X
+tag
.width
)) {
3625 end
= tag
.start
+ tag
.length
- 1;
3627 for (int pos
= tag
.start
-1; pos
< end
; pos
++) {
3628 // When clicking on a character, we position the cursor to whatever edge
3629 // of the character the click was closer
3630 if (x
< (line
.widths
[pos
] + ((line
.widths
[pos
+1]-line
.widths
[pos
])/2))) {
3638 if (tag
.next
!= null) {
3641 index
= line
.text
.Length
;
3647 /// <summary>Format area of document in specified font and color</summary>
3648 /// <param name="start_pos">1-based start position on start_line</param>
3649 /// <param name="end_pos">1-based end position on end_line </param>
3650 internal void FormatText(Line start_line
, int start_pos
, Line end_line
, int end_pos
, Font font
, Brush color
) {
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, font
, color
);
3659 LineTag
.FormatText(end_line
, 1, end_pos
, font
, color
);
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
, font
, color
);
3667 // Special case, single line
3668 LineTag
.FormatText(start_line
, start_pos
, end_pos
- start_pos
, font
, color
);
3672 /// <summary>Re-format areas of the document in specified font and color</summary>
3673 /// <param name="start_pos">1-based start position on start_line</param>
3674 /// <param name="end_pos">1-based end position on end_line </param>
3675 /// <param name="font">Font specifying attributes</param>
3676 /// <param name="color">Color (or NULL) to apply</param>
3677 /// <param name="apply">Attributes from font and color to apply</param>
3678 internal void FormatText(Line start_line
, int start_pos
, Line end_line
, int end_pos
, FontDefinition attributes
) {
3681 // First, format the first line
3682 if (start_line
!= end_line
) {
3684 LineTag
.FormatText(start_line
, start_pos
, start_line
.text
.Length
- start_pos
+ 1, attributes
);
3687 LineTag
.FormatText(end_line
, 1, end_pos
- 1, attributes
);
3689 // Now all the lines inbetween
3690 for (int i
= start_line
.line_no
+ 1; i
< end_line
.line_no
; i
++) {
3692 LineTag
.FormatText(l
, 1, l
.text
.Length
, attributes
);
3695 // Special case, single line
3696 LineTag
.FormatText(start_line
, start_pos
, end_pos
- start_pos
, attributes
);
3700 internal void RecalculateAlignments() {
3706 while (line_no
<= lines
) {
3707 line
= GetLine(line_no
);
3710 switch (line
.alignment
) {
3711 case HorizontalAlignment
.Left
:
3712 line
.align_shift
= 0;
3714 case HorizontalAlignment
.Center
:
3715 line
.align_shift
= (viewport_width
- (int)line
.widths
[line
.text
.Length
]) / 2;
3717 case HorizontalAlignment
.Right
:
3718 line
.align_shift
= viewport_width
- (int)line
.widths
[line
.text
.Length
];
3728 /// <summary>Calculate formatting for the whole document</summary>
3729 internal bool RecalculateDocument(Graphics g
) {
3730 return RecalculateDocument(g
, 1, this.lines
, false);
3733 /// <summary>Calculate formatting starting at a certain line</summary>
3734 internal bool RecalculateDocument(Graphics g
, int start
) {
3735 return RecalculateDocument(g
, start
, this.lines
, false);
3738 /// <summary>Calculate formatting within two given line numbers</summary>
3739 internal bool RecalculateDocument(Graphics g
, int start
, int end
) {
3740 return RecalculateDocument(g
, start
, end
, false);
3743 /// <summary>With optimize on, returns true if line heights changed</summary>
3744 internal bool RecalculateDocument(Graphics g
, int start
, int end
, bool optimize
) {
3753 recalc_pending
= true;
3754 recalc_start
= start
;
3756 recalc_optimize
= optimize
;
3760 Y
= GetLine(start
).Y
;
3765 changed
= true; // We always return true if we run non-optimized
3770 while (line_no
<= (end
+ this.lines
- shift
)) {
3771 line
= GetLine(line_no
++);
3776 line
.RecalculateLine(g
, this);
3778 if (line
.recalc
&& line
.RecalculateLine(g
, this)) {
3780 // If the height changed, all subsequent lines change
3787 line
.RecalculatePasswordLine(g
, this);
3789 if (line
.recalc
&& line
.RecalculatePasswordLine(g
, this)) {
3791 // If the height changed, all subsequent lines change
3798 if (line
.widths
[line
.text
.Length
] > new_width
) {
3799 new_width
= (int)line
.widths
[line
.text
.Length
];
3802 // Calculate alignment
3803 if (line
.alignment
!= HorizontalAlignment
.Left
) {
3804 if (line
.alignment
== HorizontalAlignment
.Center
) {
3805 line
.align_shift
= (viewport_width
- (int)line
.widths
[line
.text
.Length
]) / 2;
3807 line
.align_shift
= viewport_width
- (int)line
.widths
[line
.text
.Length
] - 1;
3813 if (line_no
> lines
) {
3818 if (document_x
!= new_width
) {
3819 document_x
= new_width
;
3820 if (WidthChanged
!= null) {
3821 WidthChanged(this, null);
3825 RecalculateAlignments();
3827 line
= GetLine(lines
);
3829 if (document_y
!= line
.Y
+ line
.height
) {
3830 document_y
= line
.Y
+ line
.height
;
3831 if (HeightChanged
!= null) {
3832 HeightChanged(this, null);
3839 internal int Size() {
3843 private void owner_HandleCreated(object sender
, EventArgs e
) {
3844 RecalculateDocument(owner
.CreateGraphicsInternal());
3848 private void owner_VisibleChanged(object sender
, EventArgs e
) {
3849 if (owner
.Visible
) {
3850 RecalculateDocument(owner
.CreateGraphicsInternal());
3854 internal static bool IsWordSeparator(char ch
) {
3868 internal int FindWordSeparator(Line line
, int pos
, bool forward
) {
3871 len
= line
.text
.Length
;
3874 for (int i
= pos
+ 1; i
< len
; i
++) {
3875 if (IsWordSeparator(line
.Text
[i
])) {
3881 for (int i
= pos
- 1; i
> 0; i
--) {
3882 if (IsWordSeparator(line
.Text
[i
- 1])) {
3890 /* Search document for text */
3891 internal bool FindChars(char[] chars
, Marker start
, Marker end
, out Marker result
) {
3897 // Search for occurence of any char in the chars array
3898 result
= new Marker();
3901 line_no
= start
.line
.line_no
;
3903 while (line_no
<= end
.line
.line_no
) {
3904 line_len
= line
.text
.Length
;
3905 while (pos
< line_len
) {
3906 for (int i
= 0; i
< chars
.Length
; i
++) {
3907 if (line
.text
[pos
] == chars
[i
]) {
3909 if ((line
.line_no
== end
.line
.line_no
) && (pos
>= end
.pos
)) {
3923 line
= GetLine(line_no
);
3929 // This version does not build one big string for searching, instead it handles
3930 // line-boundaries, which is faster and less memory intensive
3931 // FIXME - Depending on culture stuff we might have to create a big string and use culturespecific
3932 // search stuff and change it to accept and return positions instead of Markers (which would match
3933 // RichTextBox behaviour better but would be inconsistent with the rest of TextControl)
3934 internal bool Find(string search
, Marker start
, Marker end
, out Marker result
, RichTextBoxFinds options
) {
3936 string search_string
;
3948 result
= new Marker();
3949 word_option
= ((options
& RichTextBoxFinds
.WholeWord
) != 0);
3950 ignore_case
= ((options
& RichTextBoxFinds
.MatchCase
) == 0);
3951 reverse
= ((options
& RichTextBoxFinds
.Reverse
) != 0);
3954 line_no
= start
.line
.line_no
;
3958 // Prep our search string, lowercasing it if we do case-independent matching
3961 sb
= new StringBuilder(search
);
3962 for (int i
= 0; i
< sb
.Length
; i
++) {
3963 sb
[i
] = Char
.ToLower(sb
[i
]);
3965 search_string
= sb
.ToString();
3967 search_string
= search
;
3970 // We need to check if the character before our start position is a wordbreak
3973 if ((pos
== 0) || (IsWordSeparator(line
.text
[pos
- 1]))) {
3980 if (IsWordSeparator(line
.text
[pos
- 1])) {
3986 // Need to check the end of the previous line
3989 prev_line
= GetLine(line_no
- 1);
3990 if (prev_line
.soft_break
) {
3991 if (IsWordSeparator(prev_line
.text
[prev_line
.text
.Length
- 1])) {
4005 // To avoid duplication of this loop with reverse logic, we search
4006 // through the document, remembering the last match and when returning
4007 // report that last remembered match
4009 last
= new Marker();
4010 last
.height
= -1; // Abused - we use it to track change
4012 while (line_no
<= end
.line
.line_no
) {
4013 if (line_no
!= end
.line
.line_no
) {
4014 line_len
= line
.text
.Length
;
4019 while (pos
< line_len
) {
4020 if (word_option
&& (current
== search_string
.Length
)) {
4021 if (IsWordSeparator(line
.text
[pos
])) {
4034 c
= Char
.ToLower(line
.text
[pos
]);
4039 if (c
== search_string
[current
]) {
4044 if (!word_option
|| (word_option
&& (word
|| (current
> 0)))) {
4048 if (!word_option
&& (current
== search_string
.Length
)) {
4065 if (IsWordSeparator(c
)) {
4073 // Mark that we just saw a word boundary
4074 if (!line
.soft_break
) {
4078 if (current
== search_string
.Length
) {
4094 line
= GetLine(line_no
);
4098 if (last
.height
!= -1) {
4108 // if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
4120 internal void GetMarker(out Marker mark
, bool start
) {
4121 mark
= new Marker();
4124 mark
.line
= GetLine(1);
4125 mark
.tag
= mark
.line
.tags
;
4128 mark
.line
= GetLine(lines
);
4129 mark
.tag
= mark
.line
.tags
;
4130 while (mark
.tag
.next
!= null) {
4131 mark
.tag
= mark
.tag
.next
;
4133 mark
.pos
= mark
.line
.text
.Length
;
4136 #endregion // Internal Methods
4139 internal event EventHandler CaretMoved
;
4140 internal event EventHandler WidthChanged
;
4141 internal event EventHandler HeightChanged
;
4142 internal event EventHandler LengthChanged
;
4143 #endregion // Events
4145 #region Administrative
4146 public IEnumerator
GetEnumerator() {
4151 public override bool Equals(object obj
) {
4156 if (!(obj
is Document
)) {
4164 if (ToString().Equals(((Document
)obj
).ToString())) {
4171 public override int GetHashCode() {
4175 public override string ToString() {
4176 return "document " + this.document_id
;
4178 #endregion // Administrative
4181 internal class LineTag
{
4182 #region Local Variables;
4183 // Payload; formatting
4184 internal Font font
; // System.Drawing.Font object for this tag
4185 internal Brush color
; // System.Drawing.Brush object
4188 internal int start
; // start, in chars; index into Line.text
4189 internal int length
; // length, in chars
4190 internal bool r_to_l
; // Which way is the font
4193 internal int height
; // Height in pixels of the text this tag describes
4194 internal int X
; // X location of the text this tag describes
4195 internal float width
; // Width in pixels of the text this tag describes
4196 internal int ascent
; // Ascent of the font for this tag
4197 internal int shift
; // Shift down for this tag, to stay on baseline
4200 internal Line line
; // The line we're on
4201 internal LineTag next
; // Next tag on the same line
4202 internal LineTag previous
; // Previous tag on the same line
4205 #region Constructors
4206 internal LineTag(Line line
, int start
, int length
) {
4209 this.length
= length
;
4213 #endregion // Constructors
4215 #region Internal Methods
4216 ///<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>
4217 internal LineTag
Break(int pos
) {
4221 if (pos
== this.start
) {
4223 } else if (pos
>= (start
+ length
)) {
4227 new_tag
= new LineTag(line
, pos
, start
+ length
- pos
);
4228 new_tag
.color
= color
;
4229 new_tag
.font
= font
;
4230 this.length
-= new_tag
.length
;
4231 new_tag
.next
= this.next
;
4232 this.next
= new_tag
;
4233 new_tag
.previous
= this;
4234 if (new_tag
.next
!= null) {
4235 new_tag
.next
.previous
= new_tag
;
4241 ///<summary>Create new font and brush from existing font and given new attributes. Returns true if fontheight changes</summary>
4242 internal static bool GenerateTextFormat(Font font_from
, Brush color_from
, FontDefinition attributes
, out Font new_font
, out Brush new_color
) {
4248 if (attributes
.font_obj
== null) {
4249 size
= font_from
.SizeInPoints
;
4250 unit
= font_from
.Unit
;
4251 face
= font_from
.Name
;
4252 style
= font_from
.Style
;
4254 if (attributes
.face
!= null) {
4255 face
= attributes
.face
;
4258 if (attributes
.size
!= 0) {
4259 size
= attributes
.size
;
4262 style
|= attributes
.add_style
;
4263 style
&= ~attributes
.remove_style
;
4266 new_font
= new Font(face
, size
, style
, unit
);
4268 new_font
= attributes
.font_obj
;
4271 // Create 'new' color brush
4272 if (attributes
.color
!= Color
.Empty
) {
4273 new_color
= new SolidBrush(attributes
.color
);
4275 new_color
= color_from
;
4278 if (new_font
.Height
== font_from
.Height
) {
4284 /// <summary>Applies 'font' and 'brush' to characters starting at 'start' for 'length' chars;
4285 /// Removes any previous tags overlapping the same area;
4286 /// returns true if lineheight has changed</summary>
4287 /// <param name="start">1-based character position on line</param>
4288 internal static bool FormatText(Line line
, int start
, int length
, Font font
, Brush color
) {
4293 bool retval
= false; // Assume line-height doesn't change
4296 if (font
.Height
!= line
.height
) {
4299 line
.recalc
= true; // This forces recalculation of the line in RecalculateDocument
4301 // A little sanity, not sure if it's needed, might be able to remove for speed
4302 if (length
> line
.text
.Length
) {
4303 length
= line
.text
.Length
;
4307 end
= start
+ length
;
4309 // Common special case
4310 if ((start
== 1) && (length
== tag
.length
)) {
4317 //Console.WriteLine("Finding tag for {0} {1}", line, start);
4318 start_tag
= FindTag(line
, start
);
4319 end_tag
= FindTag (line
, end
);
4321 if (start_tag
== null) { // FIXME - is there a better way to handle this, or do we even need it?
4322 throw new Exception(String
.Format("Could not find start_tag in document at line {0} position {1}", line
.line_no
, start
));
4325 tag
= new LineTag(line
, start
, length
);
4332 //Console.WriteLine("Start tag: '{0}'", start_tag!=null ? start_tag.ToString() : "NULL");
4333 if (start_tag
.start
== start
) {
4334 tag
.next
= start_tag
;
4335 tag
.previous
= start_tag
.previous
;
4336 if (start_tag
.previous
!= null) {
4337 start_tag
.previous
.next
= tag
;
4339 start_tag
.previous
= tag
;
4343 if (end_tag
!= null) {
4344 // Shorten up the end tag
4345 end_tag
.previous
= tag
;
4346 end_tag
.length
= end
- start_tag
.start
+ start_tag
.length
;
4347 end_tag
.start
= end
;
4353 while (tag
!= end_tag
) {
4354 if ((tag
.start
+ tag
.length
) <= end
) {
4356 tag
.previous
.next
= tag
.next
;
4357 if (tag
.next
!= null) {
4358 tag
.next
.previous
= tag
.previous
;
4368 /// <summary>Applies font attributes specified to characters starting at 'start' for 'length' chars;
4369 /// Breaks tags at start and end point, keeping middle tags with altered attributes.
4370 /// Returns true if lineheight has changed</summary>
4371 /// <param name="start">1-based character position on line</param>
4372 internal static bool FormatText(Line line
, int start
, int length
, FontDefinition attributes
) {
4376 bool retval
= false; // Assume line-height doesn't change
4378 line
.recalc
= true; // This forces recalculation of the line in RecalculateDocument
4380 // A little sanity, not sure if it's needed, might be able to remove for speed
4381 if (length
> line
.text
.Length
) {
4382 length
= line
.text
.Length
;
4387 // Common special case
4388 if ((start
== 1) && (length
== tag
.length
)) {
4390 GenerateTextFormat(tag
.font
, tag
.color
, attributes
, out tag
.font
, out tag
.color
);
4394 start_tag
= FindTag(line
, start
);
4396 if (start_tag
== null) {
4398 // We are 'starting' after all valid tags; create a new tag with the right attributes
4399 start_tag
= FindTag(line
, line
.text
.Length
- 1);
4400 start_tag
.next
= new LineTag(line
, line
.text
.Length
+ 1, 0);
4401 start_tag
.next
.font
= start_tag
.font
;
4402 start_tag
.next
.color
= start_tag
.color
;
4403 start_tag
.next
.previous
= start_tag
;
4404 start_tag
= start_tag
.next
;
4406 throw new Exception(String
.Format("Could not find start_tag in document at line {0} position {1}", line
.line_no
, start
));
4409 start_tag
= start_tag
.Break(start
);
4412 end_tag
= FindTag(line
, start
+ length
);
4413 if (end_tag
!= null) {
4414 end_tag
= end_tag
.Break(start
+ length
);
4417 // start_tag or end_tag might be null; we're cool with that
4418 // we now walk from start_tag to end_tag, applying new attributes
4420 while ((tag
!= null) && tag
!= end_tag
) {
4421 if (LineTag
.GenerateTextFormat(tag
.font
, tag
.color
, attributes
, out tag
.font
, out tag
.color
)) {
4430 /// <summary>Finds the tag that describes the character at position 'pos' on 'line'</summary>
4431 internal static LineTag
FindTag(Line line
, int pos
) {
4432 LineTag tag
= line
.tags
;
4434 // Beginning of line is a bit special
4436 // Not sure if we should get the final tag here
4440 while (tag
!= null) {
4441 if ((tag
.start
<= pos
) && (pos
< (tag
.start
+tag
.length
))) {
4442 return GetFinalTag (tag
);
4451 // There can be multiple tags at the same position, we want to make
4452 // sure we are using the very last tag at the given position
4453 internal static LineTag
GetFinalTag (LineTag tag
)
4457 while (res
.next
!= null && res
.next
.length
== 0)
4462 /// <summary>Combines 'this' tag with 'other' tag</summary>
4463 internal bool Combine(LineTag other
) {
4464 if (!this.Equals(other
)) {
4468 this.width
+= other
.width
;
4469 this.length
+= other
.length
;
4470 this.next
= other
.next
;
4471 if (this.next
!= null) {
4472 this.next
.previous
= this;
4479 /// <summary>Remove 'this' tag ; to be called when formatting is to be removed</summary>
4480 internal bool Remove() {
4481 if ((this.start
== 1) && (this.next
== null)) {
4482 // We cannot remove the only tag
4485 if (this.start
!= 1) {
4486 this.previous
.length
+= this.length
;
4487 this.previous
.width
= -1;
4488 this.previous
.next
= this.next
;
4489 this.next
.previous
= this.previous
;
4491 this.next
.start
= 1;
4492 this.next
.length
+= this.length
;
4493 this.next
.width
= -1;
4494 this.line
.tags
= this.next
;
4495 this.next
.previous
= null;
4501 /// <summary>Checks if 'this' tag describes the same formatting options as 'obj'</summary>
4502 public override bool Equals(object obj
) {
4509 if (!(obj
is LineTag
)) {
4517 other
= (LineTag
)obj
;
4519 if (this.font
.Equals(other
.font
) && this.color
.Equals(other
.color
)) { // FIXME add checking for things like link or type later
4526 public override int GetHashCode() {
4527 return base.GetHashCode ();
4530 public override string ToString() {
4532 return "Tag starts at index " + this.start
+ "length " + this.length
+ " text: " + this.line
.Text
.Substring(this.start
-1, this.length
) + "Font " + this.font
.ToString();
4533 return "Zero Lengthed tag at index " + this.start
;
4536 #endregion // Internal Methods
4539 internal class UndoClass
{
4540 internal enum ActionType
{
4549 internal class Action
{
4550 internal ActionType type
;
4551 internal int line_no
;
4553 internal object data
;
4556 #region Local Variables
4557 private Document document
;
4558 private Stack undo_actions
;
4559 private Stack redo_actions
;
4560 private int caret_line
;
4561 private int caret_pos
;
4562 #endregion // Local Variables
4564 #region Constructors
4565 internal UndoClass(Document doc
) {
4567 undo_actions
= new Stack(50);
4568 redo_actions
= new Stack(50);
4570 #endregion // Constructors
4573 [MonoTODO("Change this to be configurable")]
4574 internal int UndoLevels
{
4576 return undo_actions
.Count
;
4580 [MonoTODO("Change this to be configurable")]
4581 internal int RedoLevels
{
4583 return redo_actions
.Count
;
4587 [MonoTODO("Come up with good naming and localization")]
4588 internal string UndoName
{
4592 action
= (Action
)undo_actions
.Peek();
4593 switch(action
.type
) {
4594 case ActionType
.InsertChar
: {
4595 Locale
.GetText("Insert character");
4599 case ActionType
.DeleteChar
: {
4600 Locale
.GetText("Delete character");
4604 case ActionType
.InsertString
: {
4605 Locale
.GetText("Insert string");
4609 case ActionType
.DeleteChars
: {
4610 Locale
.GetText("Delete string");
4614 case ActionType
.CursorMove
: {
4615 Locale
.GetText("Cursor move");
4623 internal string RedoName() {
4626 #endregion // Properties
4628 #region Internal Methods
4629 internal void Clear() {
4630 undo_actions
.Clear();
4631 redo_actions
.Clear();
4634 internal void Undo() {
4637 if (undo_actions
.Count
== 0) {
4641 action
= (Action
)undo_actions
.Pop();
4643 // Put onto redo stack
4644 redo_actions
.Push(action
);
4647 switch(action
.type
) {
4648 case ActionType
.InsertChar
: {
4649 // FIXME - implement me
4653 case ActionType
.DeleteChars
: {
4654 this.Insert(document
.GetLine(action
.line_no
), action
.pos
, (Line
)action
.data
);
4655 Undo(); // Grab the cursor location
4659 case ActionType
.CursorMove
: {
4660 document
.caret
.line
= document
.GetLine(action
.line_no
);
4661 if (document
.caret
.line
== null) {
4666 document
.caret
.tag
= document
.caret
.line
.FindTag(action
.pos
);
4667 document
.caret
.pos
= action
.pos
;
4668 document
.caret
.height
= document
.caret
.tag
.height
;
4670 if (document
.owner
.IsHandleCreated
) {
4671 XplatUI
.DestroyCaret(document
.owner
.Handle
);
4672 XplatUI
.CreateCaret(document
.owner
.Handle
, 2, document
.caret
.height
);
4673 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
);
4675 document
.DisplayCaret ();
4678 // FIXME - enable call
4679 //if (document.CaretMoved != null) document.CaretMoved(this, EventArgs.Empty);
4685 internal void Redo() {
4686 if (redo_actions
.Count
== 0) {
4690 #endregion // Internal Methods
4692 #region Private Methods
4694 public void RecordDeleteChars(Line line
, int pos
, int length
) {
4695 RecordDelete(line
, pos
, line
, pos
+ length
- 1);
4698 // start_pos, end_pos = 1 based
4699 public void RecordDelete(Line start_line
, int start_pos
, Line end_line
, int end_pos
) {
4703 l
= Duplicate(start_line
, start_pos
, end_line
, end_pos
);
4706 a
.type
= ActionType
.DeleteChars
;
4708 a
.line_no
= start_line
.line_no
;
4709 a
.pos
= start_pos
- 1;
4711 // Record the cursor position before, since the actions will occur in reverse order
4713 undo_actions
.Push(a
);
4716 public void RecordCursor() {
4717 if (document
.caret
.line
== null) {
4721 RecordCursor(document
.caret
.line
, document
.caret
.pos
);
4724 public void RecordCursor(Line line
, int pos
) {
4727 if ((line
.line_no
== caret_line
) && (pos
== caret_pos
)) {
4731 caret_line
= line
.line_no
;
4735 a
.type
= ActionType
.CursorMove
;
4736 a
.line_no
= line
.line_no
;
4739 undo_actions
.Push(a
);
4742 // start_pos = 1-based
4743 // end_pos = 1-based
4744 public Line
Duplicate(Line start_line
, int start_pos
, Line end_line
, int end_pos
) {
4749 LineTag current_tag
;
4758 for (int i
= start_line
.line_no
; i
<= end_line
.line_no
; i
++) {
4759 current
= document
.GetLine(i
);
4761 if (start_line
.line_no
== i
) {
4767 if (end_line
.line_no
== i
) {
4770 end
= current
.text
.Length
;
4774 line
.text
= new StringBuilder(current
.text
.ToString(start
- 1, end
- start
+ 1));
4776 // Copy tags from start to start+length onto new line
4777 current_tag
= current
.FindTag(start
- 1);
4778 while ((current_tag
!= null) && (current_tag
.start
< end
)) {
4779 if ((current_tag
.start
<= start
) && (start
< (current_tag
.start
+ current_tag
.length
))) {
4780 // start tag is within this tag
4783 tag_start
= current_tag
.start
;
4786 if (end
< (current_tag
.start
+ current_tag
.length
)) {
4787 tag_length
= end
- tag_start
+ 1;
4789 tag_length
= current_tag
.start
+ current_tag
.length
- tag_start
;
4791 tag
= new LineTag(line
, tag_start
- start
+ 1, tag_length
);
4792 tag
.color
= current_tag
.color
;
4793 tag
.font
= current_tag
.font
;
4795 current_tag
= current_tag
.next
;
4797 // Add the new tag to the line
4798 if (line
.tags
== null) {
4804 while (tail
.next
!= null) {
4808 tag
.previous
= tail
;
4812 if ((i
+ 1) <= end_line
.line_no
) {
4813 line
.soft_break
= current
.soft_break
;
4815 // Chain them (we use right/left as next/previous)
4816 line
.right
= new Line();
4817 line
.right
.left
= line
;
4825 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
4826 internal void Insert(Line line
, int pos
, Line insert
) {
4833 // Handle special case first
4834 if (insert
.right
== null) {
4836 // Single line insert
4837 document
.Split(line
, pos
);
4839 if (insert
.tags
== null) {
4840 return; // Blank line
4843 //Insert our tags at the end
4846 while (tag
.next
!= null) {
4850 offset
= tag
.start
+ tag
.length
- 1;
4852 tag
.next
= insert
.tags
;
4853 line
.text
.Insert(offset
, insert
.text
.ToString());
4855 // Adjust start locations
4857 while (tag
!= null) {
4858 tag
.start
+= offset
;
4862 // Put it back together
4863 document
.Combine(line
.line_no
, line
.line_no
+ 1);
4864 document
.UpdateView(line
, pos
);
4871 while (current
!= null) {
4872 if (current
== insert
) {
4873 // Inserting the first line we split the line (and make space)
4874 document
.Split(line
, pos
);
4875 //Insert our tags at the end of the line
4879 while (tag
.next
!= null) {
4882 offset
= tag
.start
+ tag
.length
- 1;
4883 tag
.next
= current
.tags
;
4884 tag
.next
.previous
= tag
;
4890 line
.tags
= current
.tags
;
4891 line
.tags
.previous
= null;
4895 document
.Split(line
.line_no
, 0);
4897 line
.tags
= current
.tags
;
4898 line
.tags
.previous
= null;
4901 // Adjust start locations and line pointers
4902 while (tag
!= null) {
4903 tag
.start
+= offset
;
4908 line
.text
.Insert(offset
, current
.text
.ToString());
4909 line
.Grow(line
.text
.Length
);
4912 line
= document
.GetLine(line
.line_no
+ 1);
4914 // FIXME? Test undo of line-boundaries
4915 if ((current
.right
== null) && (current
.tags
.length
!= 0)) {
4916 document
.Combine(line
.line_no
- 1, line
.line_no
);
4918 current
= current
.right
;
4923 // Recalculate our document
4924 document
.UpdateView(first
, lines
, pos
);
4927 #endregion // Private Methods