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
;
76 internal enum FormatSpecified
{
84 internal enum CaretDirection
{
85 CharForward
, // Move a char to the right
86 CharBack
, // Move a char to the left
87 LineUp
, // Move a line up
88 LineDown
, // Move a line down
89 Home
, // Move to the beginning of the line
90 End
, // Move to the end of the line
91 PgUp
, // Move one page up
92 PgDn
, // Move one page down
93 CtrlPgUp
, // Move caret to the first visible char in the viewport
94 CtrlPgDn
, // Move caret to the last visible char in the viewport
95 CtrlHome
, // Move to the beginning of the document
96 CtrlEnd
, // Move to the end of the document
97 WordBack
, // Move to the beginning of the previous word (or beginning of line)
98 WordForward
, // Move to the beginning of the next word (or end of line)
99 SelectionStart
, // Move to the beginning of the current selection
100 SelectionEnd
, // Move to the end of the current selection
101 CharForwardNoWrap
, // Move a char forward, but don't wrap onto the next line
102 CharBackNoWrap
// Move a char backward, but don't wrap onto the previous line
105 // Being cloneable should allow for nice line and document copies...
106 internal class Line
: ICloneable
, IComparable
{
107 #region Local Variables
108 // Stuff that matters for our line
109 internal StringBuilder text
; // Characters for the line
110 internal float[] widths
; // Width of each character; always one larger than text.Length
111 internal int space
; // Number of elements in text and widths
112 internal int line_no
; // Line number
113 internal LineTag tags
; // Tags describing the text
114 internal int Y
; // Baseline
115 internal int height
; // Height of the line (height of tallest tag)
116 internal int ascent
; // Ascent of the line (ascent of the tallest tag)
117 internal HorizontalAlignment alignment
; // Alignment of the line
118 internal int align_shift
; // Pixel shift caused by the alignment
119 internal bool soft_break
; // Tag is 'broken soft' and continuation from previous line
120 internal int indent
; // Left indent for the first line
121 internal int hanging_indent
; // Hanging indent (left indent for all but the first line)
122 internal int right_indent
; // Right indent for all lines
123 internal bool carriage_return
;
126 // Stuff that's important for the tree
127 internal Line parent
; // Our parent line
128 internal Line left
; // Line with smaller line number
129 internal Line right
; // Line with higher line number
130 internal LineColor color
; // We're doing a black/red tree. this is the node color
131 internal int DEFAULT_TEXT_LEN
; //
132 internal static StringFormat string_format
; // For calculating widths/heights
133 internal bool recalc
; // Line changed
134 #endregion // Local Variables
138 color
= LineColor
.Red
;
145 alignment
= HorizontalAlignment
.Left
;
147 if (string_format
== null) {
148 string_format
= new StringFormat(StringFormat
.GenericTypographic
);
149 string_format
.Trimming
= StringTrimming
.None
;
150 string_format
.FormatFlags
= StringFormatFlags
.MeasureTrailingSpaces
;
154 internal Line(int LineNo
, string Text
, Font font
, SolidBrush color
) : this() {
155 space
= Text
.Length
> DEFAULT_TEXT_LEN
? Text
.Length
+1 : DEFAULT_TEXT_LEN
;
157 text
= new StringBuilder(Text
, space
);
160 widths
= new float[space
+ 1];
161 tags
= new LineTag(this, 1, text
.Length
);
166 internal Line(int LineNo
, string Text
, HorizontalAlignment align
, Font font
, SolidBrush color
) : this() {
167 space
= Text
.Length
> DEFAULT_TEXT_LEN
? Text
.Length
+1 : DEFAULT_TEXT_LEN
;
169 text
= new StringBuilder(Text
, space
);
173 widths
= new float[space
+ 1];
174 tags
= new LineTag(this, 1, text
.Length
);
179 internal Line(int LineNo
, string Text
, LineTag tag
) : this() {
180 space
= Text
.Length
> DEFAULT_TEXT_LEN
? Text
.Length
+1 : DEFAULT_TEXT_LEN
;
182 text
= new StringBuilder(Text
, space
);
185 widths
= new float[space
+ 1];
189 #endregion // Constructors
191 #region Internal Properties
192 internal int Indent
{
203 internal int HangingIndent
{
205 return hanging_indent
;
209 hanging_indent
= value;
214 internal int RightIndent
{
220 right_indent
= value;
226 internal int Height
{
236 internal int LineNo
{
246 internal string Text
{
248 return text
.ToString();
252 text
= new StringBuilder(value, value.Length
> DEFAULT_TEXT_LEN
? value.Length
: DEFAULT_TEXT_LEN
);
256 internal HorizontalAlignment Alignment
{
262 if (alignment
!= value) {
269 internal StringBuilder Text
{
279 #endregion // Internal Properties
281 #region Internal Methods
282 // Make sure we always have enoughs space in text and widths
283 internal void Grow(int minimum
) {
287 length
= text
.Length
;
289 if ((length
+ minimum
) > space
) {
290 // We need to grow; double the size
292 if ((length
+ minimum
) > (space
* 2)) {
293 new_widths
= new float[length
+ minimum
* 2 + 1];
294 space
= length
+ minimum
* 2;
296 new_widths
= new float[space
* 2 + 1];
299 widths
.CopyTo(new_widths
, 0);
305 internal void Streamline(int lines
) {
312 // Catch what the loop below wont; eliminate 0 length
313 // tags, but only if there are other tags after us
314 while ((current
.length
== 0) && (next
!= null)) {
316 tags
.previous
= null;
325 while (next
!= null) {
326 // Take out 0 length tags unless it's the last tag in the document
327 if (next
.length
== 0) {
328 if ((next
.next
!= null) || (line_no
!= lines
)) {
329 current
.next
= next
.next
;
330 if (current
.next
!= null) {
331 current
.next
.previous
= current
;
337 if (current
.Combine(next
)) {
342 current
= current
.next
;
347 /// <summary> Find the tag on a line based on the character position, pos is 0-based</summary>
348 internal LineTag
FindTag(int pos
) {
357 if (pos
>= text
.Length
) {
358 pos
= text
.Length
- 1;
361 while (tag
!= null) {
362 if (((tag
.start
- 1) <= pos
) && (pos
< (tag
.start
+ tag
.length
- 1))) {
363 return LineTag
.GetFinalTag (tag
);
371 /// Recalculate a single line using the same char for every character in the line
374 internal bool RecalculatePasswordLine(Graphics g
, Document doc
) {
383 len
= this.text
.Length
;
392 w
= g
.MeasureString(doc
.password_char
, tags
.font
, 10000, string_format
).Width
;
394 if (this.height
!= (int)tag
.font
.Height
) {
400 this.height
= (int)tag
.font
.Height
;
401 tag
.height
= this.height
;
403 XplatUI
.GetFontMetrics(g
, tag
.font
, out tag
.ascent
, out descent
);
404 this.ascent
= tag
.ascent
;
408 widths
[pos
] = widths
[pos
-1] + w
;
415 /// Go through all tags on a line and recalculate all size-related values;
416 /// returns true if lineheight changed
418 internal bool RecalculateLine(Graphics g
, Document doc
) {
432 len
= this.text
.Length
;
434 prev_height
= this.height
; // For drawing optimization calculations
435 this.height
= 0; // Reset line height
436 this.ascent
= 0; // Reset the ascent for the line
439 if (this.soft_break
) {
440 widths
[0] = hanging_indent
;
453 size
= g
.MeasureString(this.text
.ToString(pos
, 1), tag
.font
, 10000, string_format
);
455 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
];
468 if (Char
.IsWhiteSpace(text
[pos
])) {
470 wrap_width
= tag
.width
+ w
;
474 if ((wrap_pos
> 0) && (wrap_pos
!= len
) && (widths
[pos
] + w
) + 5 > (doc
.viewport_width
- this.right_indent
)) {
475 // Make sure to set the last width of the line before wrapping
476 widths
[pos
+ 1] = widths
[pos
] + w
;
479 doc
.Split(this, tag
, pos
, this.soft_break
);
480 this.soft_break
= true;
481 len
= this.text
.Length
;
484 } else if (pos
> 1 && (widths
[pos
] + w
) > (doc
.viewport_width
- this.right_indent
)) {
485 // No suitable wrap position was found so break right in the middle of a word
487 // Make sure to set the last width of the line before wrapping
488 widths
[pos
+ 1] = widths
[pos
] + w
;
490 doc
.Split(this, tag
, pos
, this.soft_break
);
491 this.soft_break
= true;
492 len
= this.text
.Length
;
498 // Contract all soft lines that follow back into our line
502 widths
[pos
] = widths
[pos
-1] + w
;
505 line
= doc
.GetLine(this.line_no
+ 1);
506 if ((line
!= null) && soft_break
) {
507 // Pull the two lines together
508 doc
.Combine(this.line_no
, this.line_no
+ 1);
509 len
= this.text
.Length
;
515 if (pos
== (tag
.start
-1 + tag
.length
)) {
516 // We just found the end of our current tag
517 tag
.height
= (int)tag
.font
.Height
;
519 // Check if we're the tallest on the line (so far)
520 if (tag
.height
> this.height
) {
521 this.height
= tag
.height
; // Yep; make sure the line knows
524 if (tag
.ascent
== 0) {
527 XplatUI
.GetFontMetrics(g
, tag
.font
, out tag
.ascent
, out descent
);
530 if (tag
.ascent
> this.ascent
) {
533 // We have a tag that has a taller ascent than the line;
535 while (t
!= null && t
!= tag
) {
536 t
.shift
= tag
.ascent
- t
.ascent
;
541 this.ascent
= tag
.ascent
;
543 tag
.shift
= this.ascent
- tag
.ascent
;
546 // Update our horizontal starting pixel position
547 if (tag
.previous
== null) {
548 tag
.X
= (int)widths
[0];
550 tag
.X
= tag
.previous
.X
+ (int)tag
.previous
.width
;
557 wrap_width
= tag
.width
;
562 if (this.height
== 0) {
563 this.height
= tags
.font
.Height
;
564 tag
.height
= this.height
;
567 if (prev_height
!= this.height
) {
572 #endregion // Internal Methods
574 #region Administrative
575 public int CompareTo(object obj
) {
580 if (! (obj
is Line
)) {
581 throw new ArgumentException("Object is not of type Line", "obj");
584 if (line_no
< ((Line
)obj
).line_no
) {
586 } else if (line_no
> ((Line
)obj
).line_no
) {
593 public object Clone() {
601 clone
.left
= (Line
)left
.Clone();
605 clone
.left
= (Line
)left
.Clone();
611 internal object CloneLine() {
621 public override bool Equals(object obj
) {
626 if (!(obj
is Line
)) {
634 if (line_no
== ((Line
)obj
).line_no
) {
641 public override int GetHashCode() {
642 return base.GetHashCode ();
645 public override string ToString() {
646 return "Line " + line_no
;
649 #endregion // Administrative
652 internal class Document
: ICloneable
, IEnumerable
{
654 // FIXME - go through code and check for places where
655 // we do explicit comparisons instead of using the compare overloads
656 internal struct Marker
{
658 internal LineTag tag
;
662 public static bool operator<(Marker lhs
, Marker rhs
) {
663 if (lhs
.line
.line_no
< rhs
.line
.line_no
) {
667 if (lhs
.line
.line_no
== rhs
.line
.line_no
) {
668 if (lhs
.pos
< rhs
.pos
) {
675 public static bool operator>(Marker lhs
, Marker rhs
) {
676 if (lhs
.line
.line_no
> rhs
.line
.line_no
) {
680 if (lhs
.line
.line_no
== rhs
.line
.line_no
) {
681 if (lhs
.pos
> rhs
.pos
) {
688 public static bool operator==(Marker lhs
, Marker rhs
) {
689 if ((lhs
.line
.line_no
== rhs
.line
.line_no
) && (lhs
.pos
== rhs
.pos
)) {
695 public static bool operator!=(Marker lhs
, Marker rhs
) {
696 if ((lhs
.line
.line_no
!= rhs
.line
.line_no
) || (lhs
.pos
!= rhs
.pos
)) {
702 public void Combine(Line move_to_line
, int move_to_line_length
) {
704 pos
+= move_to_line_length
;
705 tag
= LineTag
.FindTag(line
, pos
);
708 // This is for future use, right now Document.Split does it by hand, with some added shortcut logic
709 public void Split(Line move_to_line
, int split_at
) {
712 tag
= LineTag
.FindTag(line
, pos
);
715 public override bool Equals(object obj
) {
716 return this==(Marker
)obj
;
719 public override int GetHashCode() {
720 return base.GetHashCode ();
723 public override string ToString() {
724 return "Marker Line " + line
+ ", Position " + pos
;
728 #endregion Structures
730 #region Local Variables
731 private Line document
;
733 private Line sentinel
;
734 private int document_id
;
735 private Random random
= new Random();
736 internal string password_char
;
737 private StringBuilder password_cache
;
738 private bool calc_pass
;
739 private int char_count
;
741 private bool no_recalc
;
742 private bool recalc_pending
;
743 private int recalc_start
;
744 private int recalc_end
;
745 private bool recalc_optimize
;
747 internal bool multiline
;
750 internal UndoClass undo
;
752 internal Marker caret
;
753 internal Marker selection_start
;
754 internal Marker selection_end
;
755 internal bool selection_visible
;
756 internal Marker selection_anchor
;
757 internal Marker selection_prev
;
758 internal bool selection_end_anchor
;
760 internal int viewport_x
;
761 internal int viewport_y
; // The visible area of the document
762 internal int viewport_width
;
763 internal int viewport_height
;
765 internal int document_x
; // Width of the document
766 internal int document_y
; // Height of the document
768 internal Rectangle invalid
;
770 internal int crlf_size
; // 1 or 2, depending on whether we use \r\n or just \n
772 internal TextBoxBase owner
; // Who's owning us?
773 static internal int caret_width
= 1;
774 static internal int caret_shift
= 1;
775 #endregion // Local Variables
778 internal Document(TextBoxBase owner
) {
787 recalc_pending
= false;
789 // Tree related stuff
790 sentinel
= new Line();
791 sentinel
.color
= LineColor
.Black
;
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}, soft: {3}, Text: '{4}'",
1023 line
.line_no
, line
.GetHashCode(), line
.Y
, line
.soft_break
,
1024 line
.text
!= null ? line
.text
.ToString() : "undefined");
1026 if (line
.left
== sentinel
) {
1027 Console
.Write(", left = sentinel");
1028 } else if (line
.left
== null) {
1029 Console
.Write(", left = NULL");
1032 if (line
.right
== sentinel
) {
1033 Console
.Write(", right = sentinel");
1034 } else if (line
.right
== null) {
1035 Console
.Write(", right = NULL");
1038 Console
.WriteLine("");
1048 Console
.Write(" Tags: ");
1049 while (tag
!= null) {
1050 Console
.Write("{0} <{1}>-<{2}>", count
++, tag
.start
, tag
.start
+ tag
.length
1051 /*line.text.ToString (tag.start - 1, tag.length)*/);
1052 length
+= tag
.length
;
1054 if (tag
.line
!= line
) {
1055 Console
.Write("BAD line link");
1056 throw new Exception("Bad line link in tree");
1060 Console
.Write(", ");
1063 if (length
> line
.text
.Length
) {
1064 throw new Exception(String
.Format("Length of tags more than length of text on line (expected {0} calculated {1})", line
.text
.Length
, length
));
1065 } else if (length
< line
.text
.Length
) {
1066 throw new Exception(String
.Format("Length of tags less than length of text on line (expected {0} calculated {1})", line
.text
.Length
, length
));
1068 Console
.WriteLine("");
1070 if (line
.left
!= null) {
1071 if (line
.left
!= sentinel
) {
1072 total
+= DumpTree(line
.left
, with_tags
);
1075 if (line
!= sentinel
) {
1076 throw new Exception("Left should not be NULL");
1080 if (line
.right
!= null) {
1081 if (line
.right
!= sentinel
) {
1082 total
+= DumpTree(line
.right
, with_tags
);
1085 if (line
!= sentinel
) {
1086 throw new Exception("Right should not be NULL");
1090 for (int i
= 1; i
<= this.lines
; i
++) {
1091 if (GetLine(i
) == null) {
1092 throw new Exception(String
.Format("Hole in line order, missing {0}", i
));
1096 if (line
== this.Root
) {
1097 if (total
< this.lines
) {
1098 throw new Exception(String
.Format("Not enough nodes in tree, found {0}, expected {1}", total
, this.lines
));
1099 } else if (total
> this.lines
) {
1100 throw new Exception(String
.Format("Too many nodes in tree, found {0}, expected {1}", total
, this.lines
));
1107 private void SetSelectionVisible (bool value)
1109 selection_visible
= value;
1111 // cursor and selection are enemies, we can't have both in the same room at the same time
1112 if (owner
.IsHandleCreated
&& !owner
.show_caret_w_selection
)
1113 XplatUI
.CaretVisible (owner
.Handle
, !selection_visible
);
1116 private void DecrementLines(int line_no
) {
1120 while (current
<= lines
) {
1121 GetLine(current
).line_no
--;
1127 private void IncrementLines(int line_no
) {
1130 current
= this.lines
;
1131 while (current
>= line_no
) {
1132 GetLine(current
).line_no
++;
1138 private void RebalanceAfterAdd(Line line1
) {
1141 while ((line1
!= document
) && (line1
.parent
.color
== LineColor
.Red
)) {
1142 if (line1
.parent
== line1
.parent
.parent
.left
) {
1143 line2
= line1
.parent
.parent
.right
;
1145 if ((line2
!= null) && (line2
.color
== LineColor
.Red
)) {
1146 line1
.parent
.color
= LineColor
.Black
;
1147 line2
.color
= LineColor
.Black
;
1148 line1
.parent
.parent
.color
= LineColor
.Red
;
1149 line1
= line1
.parent
.parent
;
1151 if (line1
== line1
.parent
.right
) {
1152 line1
= line1
.parent
;
1156 line1
.parent
.color
= LineColor
.Black
;
1157 line1
.parent
.parent
.color
= LineColor
.Red
;
1159 RotateRight(line1
.parent
.parent
);
1162 line2
= line1
.parent
.parent
.left
;
1164 if ((line2
!= null) && (line2
.color
== LineColor
.Red
)) {
1165 line1
.parent
.color
= LineColor
.Black
;
1166 line2
.color
= LineColor
.Black
;
1167 line1
.parent
.parent
.color
= LineColor
.Red
;
1168 line1
= line1
.parent
.parent
;
1170 if (line1
== line1
.parent
.left
) {
1171 line1
= line1
.parent
;
1175 line1
.parent
.color
= LineColor
.Black
;
1176 line1
.parent
.parent
.color
= LineColor
.Red
;
1177 RotateLeft(line1
.parent
.parent
);
1181 document
.color
= LineColor
.Black
;
1184 private void RebalanceAfterDelete(Line line1
) {
1187 while ((line1
!= document
) && (line1
.color
== LineColor
.Black
)) {
1188 if (line1
== line1
.parent
.left
) {
1189 line2
= line1
.parent
.right
;
1190 if (line2
.color
== LineColor
.Red
) {
1191 line2
.color
= LineColor
.Black
;
1192 line1
.parent
.color
= LineColor
.Red
;
1193 RotateLeft(line1
.parent
);
1194 line2
= line1
.parent
.right
;
1196 if ((line2
.left
.color
== LineColor
.Black
) && (line2
.right
.color
== LineColor
.Black
)) {
1197 line2
.color
= LineColor
.Red
;
1198 line1
= line1
.parent
;
1200 if (line2
.right
.color
== LineColor
.Black
) {
1201 line2
.left
.color
= LineColor
.Black
;
1202 line2
.color
= LineColor
.Red
;
1204 line2
= line1
.parent
.right
;
1206 line2
.color
= line1
.parent
.color
;
1207 line1
.parent
.color
= LineColor
.Black
;
1208 line2
.right
.color
= LineColor
.Black
;
1209 RotateLeft(line1
.parent
);
1213 line2
= line1
.parent
.left
;
1214 if (line2
.color
== LineColor
.Red
) {
1215 line2
.color
= LineColor
.Black
;
1216 line1
.parent
.color
= LineColor
.Red
;
1217 RotateRight(line1
.parent
);
1218 line2
= line1
.parent
.left
;
1220 if ((line2
.right
.color
== LineColor
.Black
) && (line2
.left
.color
== LineColor
.Black
)) {
1221 line2
.color
= LineColor
.Red
;
1222 line1
= line1
.parent
;
1224 if (line2
.left
.color
== LineColor
.Black
) {
1225 line2
.right
.color
= LineColor
.Black
;
1226 line2
.color
= LineColor
.Red
;
1228 line2
= line1
.parent
.left
;
1230 line2
.color
= line1
.parent
.color
;
1231 line1
.parent
.color
= LineColor
.Black
;
1232 line2
.left
.color
= LineColor
.Black
;
1233 RotateRight(line1
.parent
);
1238 line1
.color
= LineColor
.Black
;
1241 private void RotateLeft(Line line1
) {
1242 Line line2
= line1
.right
;
1244 line1
.right
= line2
.left
;
1246 if (line2
.left
!= sentinel
) {
1247 line2
.left
.parent
= line1
;
1250 if (line2
!= sentinel
) {
1251 line2
.parent
= line1
.parent
;
1254 if (line1
.parent
!= null) {
1255 if (line1
== line1
.parent
.left
) {
1256 line1
.parent
.left
= line2
;
1258 line1
.parent
.right
= line2
;
1265 if (line1
!= sentinel
) {
1266 line1
.parent
= line2
;
1270 private void RotateRight(Line line1
) {
1271 Line line2
= line1
.left
;
1273 line1
.left
= line2
.right
;
1275 if (line2
.right
!= sentinel
) {
1276 line2
.right
.parent
= line1
;
1279 if (line2
!= sentinel
) {
1280 line2
.parent
= line1
.parent
;
1283 if (line1
.parent
!= null) {
1284 if (line1
== line1
.parent
.right
) {
1285 line1
.parent
.right
= line2
;
1287 line1
.parent
.left
= line2
;
1293 line2
.right
= line1
;
1294 if (line1
!= sentinel
) {
1295 line1
.parent
= line2
;
1300 internal void UpdateView(Line line
, int pos
) {
1301 if (!owner
.IsHandleCreated
) {
1306 recalc_start
= line
.line_no
;
1307 recalc_end
= line
.line_no
;
1308 recalc_optimize
= true;
1309 recalc_pending
= true;
1313 // Optimize invalidation based on Line alignment
1314 if (RecalculateDocument(owner
.CreateGraphicsInternal(), line
.line_no
, line
.line_no
, true)) {
1315 // Lineheight changed, invalidate the rest of the document
1316 if ((line
.Y
- viewport_y
) >=0 ) {
1317 // We formatted something that's in view, only draw parts of the screen
1318 owner
.Invalidate(new Rectangle(0, line
.Y
- viewport_y
, viewport_width
, owner
.Height
- line
.Y
- viewport_y
));
1320 // The tag was above the visible area, draw everything
1324 switch(line
.alignment
) {
1325 case HorizontalAlignment
.Left
: {
1326 owner
.Invalidate(new Rectangle((int)line
.widths
[pos
] - viewport_x
- 1, line
.Y
- viewport_y
, viewport_width
, line
.height
+ 1));
1330 case HorizontalAlignment
.Center
: {
1331 owner
.Invalidate(new Rectangle(0, line
.Y
- viewport_y
, viewport_width
, line
.height
+ 1));
1335 case HorizontalAlignment
.Right
: {
1336 owner
.Invalidate(new Rectangle(0, line
.Y
- viewport_y
, (int)line
.widths
[pos
+ 1] - viewport_x
+ line
.align_shift
, line
.height
+ 1));
1344 // Update display from line, down line_count lines; pos is unused, but required for the signature
1345 internal void UpdateView(Line line
, int line_count
, int pos
) {
1346 if (!owner
.IsHandleCreated
) {
1351 recalc_start
= line
.line_no
;
1352 recalc_end
= line
.line_no
+ line_count
- 1;
1353 recalc_optimize
= true;
1354 recalc_pending
= true;
1358 if (RecalculateDocument(owner
.CreateGraphicsInternal(), line
.line_no
, line
.line_no
+ line_count
- 1, true)) {
1359 // Lineheight changed, invalidate the rest of the document
1360 if ((line
.Y
- viewport_y
) >=0 ) {
1361 // We formatted something that's in view, only draw parts of the screen
1362 //blah Console.WriteLine("TextControl.cs(981) Invalidate called in UpdateView(line, line_count, pos)");
1363 owner
.Invalidate(new Rectangle(0, line
.Y
- viewport_y
, viewport_width
, owner
.Height
- line
.Y
- viewport_y
));
1365 // The tag was above the visible area, draw everything
1366 //blah Console.WriteLine("TextControl.cs(985) Invalidate called in UpdateView(line, line_count, pos)");
1372 end_line
= GetLine(line
.line_no
+ line_count
-1);
1373 if (end_line
== null) {
1377 //blah Console.WriteLine("TextControl.cs(996) Invalidate called in UpdateView(line, line_count, pos)");
1378 owner
.Invalidate(new Rectangle(0 - viewport_x
, line
.Y
- viewport_y
, (int)line
.widths
[line
.text
.Length
], end_line
.Y
+ end_line
.height
));
1381 #endregion // Private Methods
1383 #region Internal Methods
1384 // Clear the document and reset state
1385 internal void Empty() {
1387 document
= sentinel
;
1390 // We always have a blank line
1391 Add(1, "", owner
.Font
, ThemeEngine
.Current
.ResPool
.GetSolidBrush(owner
.ForeColor
));
1392 Line l
= GetLine (1);
1393 l
.soft_break
= true;
1395 this.RecalculateDocument(owner
.CreateGraphicsInternal());
1396 PositionCaret(0, 0);
1398 SetSelectionVisible (false);
1400 selection_start
.line
= this.document
;
1401 selection_start
.pos
= 0;
1402 selection_start
.tag
= selection_start
.line
.tags
;
1403 selection_end
.line
= this.document
;
1404 selection_end
.pos
= 0;
1405 selection_end
.tag
= selection_end
.line
.tags
;
1414 if (owner
.IsHandleCreated
)
1415 owner
.Invalidate ();
1418 internal void PositionCaret(Line line
, int pos
) {
1419 if (owner
.IsHandleCreated
) {
1420 undo
.RecordCursor();
1423 caret
.tag
= line
.FindTag(pos
);
1427 if (owner
.IsHandleCreated
) {
1428 if (owner
.Focused
) {
1429 if (caret
.height
!= caret
.tag
.height
)
1430 XplatUI
.CreateCaret (owner
.Handle
, caret_width
, caret
.height
);
1431 XplatUI
.SetCaretPos(owner
.Handle
, (int)caret
.tag
.line
.widths
[caret
.pos
] + caret
.line
.align_shift
- viewport_x
, caret
.line
.Y
+ caret
.tag
.shift
- viewport_y
+ caret_shift
);
1434 if (CaretMoved
!= null) CaretMoved(this, EventArgs
.Empty
);
1437 // We set this at the end because we use the heights to determine whether or
1438 // not we need to recreate the caret
1439 caret
.height
= caret
.tag
.height
;
1443 internal void PositionCaret(int x
, int y
) {
1444 if (!owner
.IsHandleCreated
) {
1448 undo
.RecordCursor();
1450 caret
.tag
= FindCursor(x
, y
, out caret
.pos
);
1451 caret
.line
= caret
.tag
.line
;
1452 caret
.height
= caret
.tag
.height
;
1454 if (owner
.Focused
) {
1455 XplatUI
.CreateCaret (owner
.Handle
, caret_width
, caret
.height
);
1456 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
);
1459 if (CaretMoved
!= null) CaretMoved(this, EventArgs
.Empty
);
1462 internal void CaretHasFocus() {
1463 if ((caret
.tag
!= null) && owner
.IsHandleCreated
) {
1464 XplatUI
.CreateCaret(owner
.Handle
, caret_width
, caret
.height
);
1465 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
);
1470 if (owner
.IsHandleCreated
&& selection_visible
) {
1471 InvalidateSelectionArea ();
1475 internal void CaretLostFocus() {
1476 if (!owner
.IsHandleCreated
) {
1479 XplatUI
.DestroyCaret(owner
.Handle
);
1482 internal void AlignCaret() {
1483 if (!owner
.IsHandleCreated
) {
1487 undo
.RecordCursor();
1489 caret
.tag
= LineTag
.FindTag(caret
.line
, caret
.pos
);
1490 caret
.height
= caret
.tag
.height
;
1492 if (owner
.Focused
) {
1493 XplatUI
.CreateCaret(owner
.Handle
, caret_width
, caret
.height
);
1494 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
);
1498 if (CaretMoved
!= null) CaretMoved(this, EventArgs
.Empty
);
1501 internal void UpdateCaret() {
1502 if (!owner
.IsHandleCreated
|| caret
.tag
== null) {
1506 undo
.RecordCursor();
1508 if (caret
.tag
.height
!= caret
.height
) {
1509 caret
.height
= caret
.tag
.height
;
1510 if (owner
.Focused
) {
1511 XplatUI
.CreateCaret(owner
.Handle
, caret_width
, caret
.height
);
1515 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
);
1519 if (CaretMoved
!= null) CaretMoved(this, EventArgs
.Empty
);
1522 internal void DisplayCaret() {
1523 if (!owner
.IsHandleCreated
) {
1527 if (owner
.Focused
&& (!selection_visible
|| owner
.show_caret_w_selection
)) {
1528 XplatUI
.CaretVisible(owner
.Handle
, true);
1532 internal void HideCaret() {
1533 if (!owner
.IsHandleCreated
) {
1537 if (owner
.Focused
) {
1538 XplatUI
.CaretVisible(owner
.Handle
, false);
1542 internal void MoveCaret(CaretDirection direction
) {
1543 // FIXME should we use IsWordSeparator to detect whitespace, instead
1544 // of looking for actual spaces in the Word move cases?
1546 bool nowrap
= false;
1548 case CaretDirection
.CharForwardNoWrap
:
1550 goto case CaretDirection
.CharForward
;
1551 case CaretDirection
.CharForward
: {
1553 if (caret
.pos
> caret
.line
.text
.Length
) {
1554 if (multiline
&& !nowrap
) {
1555 // Go into next line
1556 if (caret
.line
.line_no
< this.lines
) {
1557 caret
.line
= GetLine(caret
.line
.line_no
+1);
1559 caret
.tag
= caret
.line
.tags
;
1564 // Single line; we stay where we are
1568 if ((caret
.tag
.start
- 1 + caret
.tag
.length
) < caret
.pos
) {
1569 caret
.tag
= caret
.tag
.next
;
1576 case CaretDirection
.CharBackNoWrap
:
1578 goto case CaretDirection
.CharBack
;
1579 case CaretDirection
.CharBack
: {
1580 if (caret
.pos
> 0) {
1581 // caret.pos--; // folded into the if below
1582 if (--caret
.pos
> 0) {
1583 if (caret
.tag
.start
> caret
.pos
) {
1584 caret
.tag
= caret
.tag
.previous
;
1588 if (caret
.line
.line_no
> 1 && !nowrap
) {
1589 caret
.line
= GetLine(caret
.line
.line_no
- 1);
1590 caret
.pos
= caret
.line
.text
.Length
;
1591 caret
.tag
= LineTag
.FindTag(caret
.line
, caret
.pos
);
1598 case CaretDirection
.WordForward
: {
1601 len
= caret
.line
.text
.Length
;
1602 if (caret
.pos
< len
) {
1603 while ((caret
.pos
< len
) && (caret
.line
.text
[caret
.pos
] != ' ')) {
1606 if (caret
.pos
< len
) {
1607 // Skip any whitespace
1608 while ((caret
.pos
< len
) && (caret
.line
.text
[caret
.pos
] == ' ')) {
1612 caret
.tag
= LineTag
.FindTag(caret
.line
, caret
.pos
);
1614 if (caret
.line
.line_no
< this.lines
) {
1615 caret
.line
= GetLine(caret
.line
.line_no
+ 1);
1617 caret
.tag
= caret
.line
.tags
;
1624 case CaretDirection
.WordBack
: {
1625 if (caret
.pos
> 0) {
1628 while ((caret
.pos
> 0) && (caret
.line
.text
[caret
.pos
] == ' ')) {
1632 while ((caret
.pos
> 0) && (caret
.line
.text
[caret
.pos
] != ' ')) {
1636 if (caret
.line
.text
.ToString(caret
.pos
, 1) == " ") {
1637 if (caret
.pos
!= 0) {
1640 caret
.line
= GetLine(caret
.line
.line_no
- 1);
1641 caret
.pos
= caret
.line
.text
.Length
;
1644 caret
.tag
= LineTag
.FindTag(caret
.line
, caret
.pos
);
1646 if (caret
.line
.line_no
> 1) {
1647 caret
.line
= GetLine(caret
.line
.line_no
- 1);
1648 caret
.pos
= caret
.line
.text
.Length
;
1649 caret
.tag
= LineTag
.FindTag(caret
.line
, caret
.pos
);
1656 case CaretDirection
.LineUp
: {
1657 if (caret
.line
.line_no
> 1) {
1660 pixel
= (int)caret
.line
.widths
[caret
.pos
];
1661 PositionCaret(pixel
, GetLine(caret
.line
.line_no
- 1).Y
);
1668 case CaretDirection
.LineDown
: {
1669 if (caret
.line
.line_no
< lines
) {
1672 pixel
= (int)caret
.line
.widths
[caret
.pos
];
1673 PositionCaret(pixel
, GetLine(caret
.line
.line_no
+ 1).Y
);
1680 case CaretDirection
.Home
: {
1681 if (caret
.pos
> 0) {
1683 caret
.tag
= caret
.line
.tags
;
1689 case CaretDirection
.End
: {
1690 if (caret
.pos
< caret
.line
.text
.Length
) {
1691 caret
.pos
= caret
.line
.text
.Length
;
1692 caret
.tag
= LineTag
.FindTag(caret
.line
, caret
.pos
);
1698 case CaretDirection
.PgUp
: {
1700 int new_y
, y_offset
;
1702 if (viewport_y
== 0) {
1704 // This should probably be handled elsewhere
1705 if (!(owner
is RichTextBox
)) {
1706 // Page down doesn't do anything in a regular TextBox
1707 // if the bottom of the document
1708 // is already visible, the page and the caret stay still
1712 // We're just placing the caret at the end of the document, no scrolling needed
1713 owner
.vscroll
.Value
= 0;
1714 Line line
= GetLine (1);
1715 PositionCaret (line
, 0);
1718 y_offset
= caret
.line
.Y
- viewport_y
;
1719 new_y
= caret
.line
.Y
- viewport_height
;
1721 owner
.vscroll
.Value
= Math
.Max (new_y
, 0);
1722 PositionCaret ((int)caret
.line
.widths
[caret
.pos
], y_offset
+ viewport_y
);
1726 case CaretDirection
.PgDn
: {
1727 int new_y
, y_offset
;
1729 if ((viewport_y
+ viewport_height
) > document_y
) {
1731 // This should probably be handled elsewhere
1732 if (!(owner
is RichTextBox
)) {
1733 // Page up doesn't do anything in a regular TextBox
1734 // if the bottom of the document
1735 // is already visible, the page and the caret stay still
1739 // We're just placing the caret at the end of the document, no scrolling needed
1740 owner
.vscroll
.Value
= owner
.vscroll
.Maximum
- viewport_height
+ 1;
1741 Line line
= GetLine (lines
);
1742 PositionCaret (line
, line
.Text
.Length
);
1745 y_offset
= caret
.line
.Y
- viewport_y
;
1746 new_y
= caret
.line
.Y
+ viewport_height
;
1748 owner
.vscroll
.Value
= Math
.Min (new_y
, owner
.vscroll
.Maximum
- viewport_height
+ 1);
1749 PositionCaret ((int)caret
.line
.widths
[caret
.pos
], y_offset
+ viewport_y
);
1754 case CaretDirection
.CtrlPgUp
: {
1755 PositionCaret(0, viewport_y
);
1760 case CaretDirection
.CtrlPgDn
: {
1765 tag
= FindTag(0, viewport_y
+ viewport_height
, out index
, false);
1766 if (tag
.line
.line_no
> 1) {
1767 line
= GetLine(tag
.line
.line_no
- 1);
1771 PositionCaret(line
, line
.Text
.Length
);
1776 case CaretDirection
.CtrlHome
: {
1777 caret
.line
= GetLine(1);
1779 caret
.tag
= caret
.line
.tags
;
1785 case CaretDirection
.CtrlEnd
: {
1786 caret
.line
= GetLine(lines
);
1787 caret
.pos
= caret
.line
.text
.Length
;
1788 caret
.tag
= LineTag
.FindTag(caret
.line
, caret
.pos
);
1794 case CaretDirection
.SelectionStart
: {
1795 caret
.line
= selection_start
.line
;
1796 caret
.pos
= selection_start
.pos
;
1797 caret
.tag
= selection_start
.tag
;
1803 case CaretDirection
.SelectionEnd
: {
1804 caret
.line
= selection_end
.line
;
1805 caret
.pos
= selection_end
.pos
;
1806 caret
.tag
= selection_end
.tag
;
1814 internal void Draw (Graphics g
, Rectangle clip
)
1816 Line line
; // Current line being drawn
1817 LineTag tag
; // Current tag being drawn
1818 int start
; // First line to draw
1819 int end
; // Last line to draw
1820 StringBuilder text
; // String representing the current line
1823 Brush current_brush
;
1824 Brush disabled_brush
;
1828 // First, figure out from what line to what line we need to draw
1829 start
= GetLineByPixel(clip
.Top
+ viewport_y
, false).line_no
;
1830 end
= GetLineByPixel(clip
.Bottom
+ viewport_y
, false).line_no
;
1832 /// Make sure that we aren't drawing one more line then we need to
1833 line
= GetLine (end
- 1);
1834 if (line
!= null && clip
.Bottom
== line
.Y
+ line
.height
+ viewport_y
)
1840 DateTime n
= DateTime
.Now
;
1841 Console
.WriteLine ("Started drawing: {0}s {1}ms", n
.Second
, n
.Millisecond
);
1842 Console
.WriteLine ("CLIP: {0}", clip
);
1843 Console
.WriteLine ("S: {0}", GetLine (start
).text
);
1844 Console
.WriteLine ("E: {0}", GetLine (end
).text
);
1847 disabled_brush
= ThemeEngine
.Current
.ResPool
.GetSolidBrush(ThemeEngine
.Current
.ColorGrayText
);
1848 hilight
= ThemeEngine
.Current
.ResPool
.GetSolidBrush(ThemeEngine
.Current
.ColorHighlight
);
1849 hilight_text
= ThemeEngine
.Current
.ResPool
.GetSolidBrush(ThemeEngine
.Current
.ColorHighlightText
);
1851 while (line_no
<= end
) {
1852 line
= GetLine (line_no
);
1858 // This fails if there's a password > 1024 chars...
1859 text
= this.password_cache
;
1862 int line_selection_start
= text
.Length
+ 1;
1863 int line_selection_end
= text
.Length
+ 1;
1864 if (selection_visible
&& owner
.ShowSelection
&&
1865 (line_no
>= selection_start
.line
.line_no
) &&
1866 (line_no
<= selection_end
.line
.line_no
)) {
1868 if (line_no
== selection_start
.line
.line_no
)
1869 line_selection_start
= selection_start
.pos
+ 1;
1871 line_selection_start
= 1;
1873 if (line_no
== selection_end
.line
.line_no
)
1874 line_selection_end
= selection_end
.pos
+ 1;
1876 line_selection_end
= text
.Length
+ 1;
1878 if (line_selection_end
== line_selection_start
) {
1879 // There isn't really selection
1880 line_selection_start
= text
.Length
+ 1;
1881 line_selection_end
= line_selection_start
;
1883 // lets draw some selection baby!!
1885 g
.FillRectangle (hilight
,
1886 line
.widths
[line_selection_start
- 1] + line
.align_shift
- viewport_x
,
1887 line
.Y
- viewport_y
,
1888 line
.widths
[line_selection_end
- 1] - line
.widths
[line_selection_start
- 1],
1893 current_brush
= line
.tags
.color
;
1894 while (tag
!= null) {
1898 if (tag
.length
== 0) {
1903 if (((tag
.X
+ tag
.width
) < (clip
.Left
- viewport_x
)) || (tag
.X
> (clip
.Right
- viewport_x
))) {
1908 if (tag
.back_color
!= null) {
1909 g
.FillRectangle (tag
.back_color
, tag
.X
+ line
.align_shift
- viewport_x
,
1910 line
.Y
+ tag
.shift
- viewport_y
, tag
.width
, line
.height
);
1913 tag_brush
= tag
.color
;
1914 current_brush
= tag_brush
;
1916 if (!owner
.is_enabled
) {
1917 Color a
= ((SolidBrush
) tag
.color
).Color
;
1918 Color b
= ThemeEngine
.Current
.ColorWindowText
;
1920 if ((a
.R
== b
.R
) && (a
.G
== b
.G
) && (a
.B
== b
.B
)) {
1921 tag_brush
= disabled_brush
;
1925 int tag_pos
= tag
.start
;
1926 current_brush
= tag_brush
;
1927 while (tag_pos
< tag
.start
+ tag
.length
) {
1928 int old_tag_pos
= tag_pos
;
1930 if (tag_pos
>= line_selection_start
&& tag_pos
< line_selection_end
) {
1931 current_brush
= hilight_text
;
1932 tag_pos
= Math
.Min (tag
.start
+ tag
.length
, line_selection_end
);
1933 } else if (tag_pos
< line_selection_start
) {
1934 current_brush
= tag
.color
;
1935 tag_pos
= Math
.Min (tag
.start
+ tag
.length
, line_selection_start
);
1937 current_brush
= tag
.color
;
1938 tag_pos
= tag
.start
+ tag
.length
;
1942 g
.DrawString (text
.ToString (old_tag_pos
- 1,
1943 Math
.Min (tag
.length
, tag_pos
- old_tag_pos
)),
1944 tag
.font
, current_brush
,
1945 line
.widths
[old_tag_pos
- 1] + line
.align_shift
- viewport_x
,
1946 line
.Y
+ tag
.shift
- viewport_y
,
1947 StringFormat
.GenericTypographic
);
1955 private void InsertLineString (Line line
, int pos
, string s
)
1957 bool carriage_return
= false;
1959 if (s
.EndsWith ("\r")) {
1960 s
= s
.Substring (0, s
.Length
- 1);
1961 carriage_return
= true;
1964 InsertString (line
, pos
, s
);
1966 if (carriage_return
) {
1967 Line l
= GetLine (line
.line_no
);
1968 l
.carriage_return
= true;
1972 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
1973 internal void Insert(Line line
, int pos
, bool update_caret
, string s
) {
1978 LineTag tag
= LineTag
.FindTag (line
, pos
);
1981 undo
.BeginCompoundAction ();
1983 base_line
= line
.line_no
;
1984 old_line_count
= lines
;
1986 break_index
= s
.IndexOf ('\n');
1988 // Bump the text at insertion point a line down if we're inserting more than one line
1989 if (break_index
> -1) {
1991 line
.soft_break
= false;
1992 // Remainder of start line is now in base_line + 1
1995 if (break_index
== -1)
1996 break_index
= s
.Length
;
1998 InsertLineString (line
, pos
, s
.Substring (0, break_index
));
2001 while (break_index
< s
.Length
) {
2003 int next_break
= s
.IndexOf ('\n', break_index
);
2004 int adjusted_next_break
;
2005 bool carriage_return
= false;
2007 if (next_break
== -1) {
2008 next_break
= s
.Length
;
2012 adjusted_next_break
= next_break
;
2013 if (s
[next_break
- 1] == '\r') {
2014 adjusted_next_break
--;
2015 carriage_return
= true;
2018 string line_text
= s
.Substring (break_index
, adjusted_next_break
- break_index
);
2019 Add (base_line
+ count
, line_text
, line
.alignment
, tag
.font
, tag
.color
);
2021 if (carriage_return
) {
2022 Line last
= GetLine (base_line
+ count
);
2023 last
.carriage_return
= true;
2026 last
.soft_break
= true;
2028 Line last
= GetLine (base_line
+ count
);
2029 last
.soft_break
= true;
2033 break_index
= next_break
+ 1;
2038 UpdateView(line
, lines
- old_line_count
+ 1, pos
);
2041 // Move caret to the end of the inserted text
2042 Line l
= GetLine (line
.line_no
+ lines
- old_line_count
);
2043 PositionCaret(l
, l
.text
.Length
);
2047 undo
.EndCompoundAction ();
2050 // Inserts a character at the given position
2051 internal void InsertString(Line line
, int pos
, string s
) {
2052 InsertString(line
.FindTag(pos
), pos
, s
);
2055 // Inserts a string at the given position
2056 internal void InsertString(LineTag tag
, int pos
, string s
) {
2065 line
.text
.Insert(pos
, s
);
2068 // TODO: sometimes getting a null tag here when pasting ???
2070 while (tag
!= null) {
2077 UpdateView(line
, pos
);
2080 // Inserts a string at the caret position
2081 internal void InsertStringAtCaret(string s
, bool move_caret
) {
2089 caret
.line
.text
.Insert(caret
.pos
, s
);
2090 caret
.tag
.length
+= len
;
2092 if (caret
.tag
.next
!= null) {
2093 tag
= caret
.tag
.next
;
2094 while (tag
!= null) {
2099 caret
.line
.Grow(len
);
2100 caret
.line
.recalc
= true;
2102 UpdateView(caret
.line
, caret
.pos
);
2111 // Inserts a character at the given position
2112 internal void InsertChar(Line line
, int pos
, char ch
) {
2113 InsertChar(line
.FindTag(pos
), pos
, ch
);
2116 // Inserts a character at the given position
2117 internal void InsertChar(LineTag tag
, int pos
, char ch
) {
2123 line
.text
.Insert(pos
, ch
);
2127 while (tag
!= null) {
2134 UpdateView(line
, pos
);
2137 // Inserts a character at the current caret position
2138 internal void InsertCharAtCaret(char ch
, bool move_caret
) {
2143 caret
.line
.text
.Insert(caret
.pos
, ch
);
2146 if (caret
.tag
.next
!= null) {
2147 tag
= caret
.tag
.next
;
2148 while (tag
!= null) {
2154 caret
.line
.recalc
= true;
2156 UpdateView(caret
.line
, caret
.pos
);
2160 SetSelectionToCaret(true);
2164 internal void DeleteMultiline (Line start_line
, int pos
, int length
)
2166 Marker start
= new Marker ();
2167 Marker end
= new Marker ();
2168 int start_index
= LineTagToCharIndex (start_line
, pos
);
2170 start
.line
= start_line
;
2172 start
.tag
= LineTag
.FindTag (start_line
, pos
);
2174 CharIndexToLineTag (start_index
+ length
, out end
.line
,
2175 out end
.tag
, out end
.pos
);
2177 if (start
.line
== end
.line
) {
2178 DeleteChars (start
.tag
, pos
, end
.pos
- pos
);
2181 // Delete first and last lines
2182 DeleteChars (start
.tag
, start
.pos
, start
.line
.text
.Length
- start
.pos
);
2183 DeleteChars (end
.line
.tags
, 0, end
.pos
);
2185 int current
= start
.line
.line_no
+ 1;
2186 if (current
< end
.line
.line_no
) {
2187 for (int i
= end
.line
.line_no
- 1; i
>= current
; i
--) {
2192 // BIG FAT WARNING - selection_end.line might be stale due
2193 // to the above Delete() call. DONT USE IT before hitting the end of this method!
2195 // Join start and end
2196 Combine (start
.line
.line_no
, current
);
2201 // Deletes n characters at the given position; it will not delete past line limits
2203 internal void DeleteChars(LineTag tag
, int pos
, int count
) {
2212 if (pos
== line
.text
.Length
) {
2216 line
.text
.Remove(pos
, count
);
2218 // Make sure the tag points to the right spot
2219 while ((tag
!= null) && (tag
.start
+ tag
.length
- 1) <= pos
) {
2227 // Check if we're crossing tag boundaries
2228 if ((pos
+ count
) > (tag
.start
+ tag
.length
- 1)) {
2231 // We have to delete cross tag boundaries
2235 left
-= tag
.start
+ tag
.length
- pos
- 1;
2236 tag
.length
-= tag
.start
+ tag
.length
- pos
- 1;
2239 while ((tag
!= null) && (left
> 0)) {
2240 tag
.start
-= count
- left
;
2241 if (tag
.length
> left
) {
2252 // We got off easy, same tag
2254 tag
.length
-= count
;
2256 if (tag
.length
== 0) {
2261 // Delete empty orphaned tags at the end
2263 while (walk
!= null && walk
.next
!= null && walk
.next
.length
== 0) {
2265 walk
.next
= walk
.next
.next
;
2266 if (walk
.next
!= null)
2267 walk
.next
.previous
= t
;
2271 // Adjust the start point of any tags following
2274 while (tag
!= null) {
2282 line
.Streamline(lines
);
2285 UpdateView(line
, pos
);
2288 // Deletes a character at or after the given position (depending on forward); it will not delete past line limits
2289 internal void DeleteChar(LineTag tag
, int pos
, bool forward
) {
2298 if ((pos
== 0 && forward
== false) || (pos
== line
.text
.Length
&& forward
== true)) {
2304 line
.text
.Remove(pos
, 1);
2306 while ((tag
!= null) && (tag
.start
+ tag
.length
- 1) <= pos
) {
2316 if (tag
.length
== 0) {
2321 line
.text
.Remove(pos
, 1);
2322 if (pos
>= (tag
.start
- 1)) {
2324 if (tag
.length
== 0) {
2327 } else if (tag
.previous
!= null) {
2328 tag
.previous
.length
--;
2329 if (tag
.previous
.length
== 0) {
2335 // Delete empty orphaned tags at the end
2337 while (walk
!= null && walk
.next
!= null && walk
.next
.length
== 0) {
2339 walk
.next
= walk
.next
.next
;
2340 if (walk
.next
!= null)
2341 walk
.next
.previous
= t
;
2346 while (tag
!= null) {
2352 line
.Streamline(lines
);
2355 UpdateView(line
, pos
);
2358 // Combine two lines
2359 internal void Combine(int FirstLine
, int SecondLine
) {
2360 Combine(GetLine(FirstLine
), GetLine(SecondLine
));
2363 internal void Combine(Line first
, Line second
) {
2367 // Combine the two tag chains into one
2370 // Maintain the line ending style
2371 first
.soft_break
= second
.soft_break
;
2373 while (last
.next
!= null) {
2377 last
.next
= second
.tags
;
2378 last
.next
.previous
= last
;
2380 shift
= last
.start
+ last
.length
- 1;
2382 // Fix up references within the chain
2384 while (last
!= null) {
2386 last
.start
+= shift
;
2390 // Combine both lines' strings
2391 first
.text
.Insert(first
.text
.Length
, second
.text
.ToString());
2392 first
.Grow(first
.text
.Length
);
2394 // Remove the reference to our (now combined) tags from the doomed line
2398 DecrementLines(first
.line_no
+ 2); // first.line_no + 1 will be deleted, so we need to start renumbering one later
2401 first
.recalc
= true;
2402 first
.height
= 0; // This forces RecalcDocument/UpdateView to redraw from this line on
2403 first
.Streamline(lines
);
2405 // Update Caret, Selection, etc
2406 if (caret
.line
== second
) {
2407 caret
.Combine(first
, shift
);
2409 if (selection_anchor
.line
== second
) {
2410 selection_anchor
.Combine(first
, shift
);
2412 if (selection_start
.line
== second
) {
2413 selection_start
.Combine(first
, shift
);
2415 if (selection_end
.line
== second
) {
2416 selection_end
.Combine(first
, shift
);
2423 check_first
= GetLine(first
.line_no
);
2424 check_second
= GetLine(check_first
.line_no
+ 1);
2426 Console
.WriteLine("Pre-delete: Y of first line: {0}, second line: {1}", check_first
.Y
, check_second
.Y
);
2429 this.Delete(second
);
2432 check_first
= GetLine(first
.line_no
);
2433 check_second
= GetLine(check_first
.line_no
+ 1);
2435 Console
.WriteLine("Post-delete Y of first line: {0}, second line: {1}", check_first
.Y
, check_second
.Y
);
2439 // Split the line at the position into two
2440 internal void Split(int LineNo
, int pos
) {
2444 line
= GetLine(LineNo
);
2445 tag
= LineTag
.FindTag(line
, pos
);
2446 Split(line
, tag
, pos
, false);
2449 internal void Split(Line line
, int pos
) {
2452 tag
= LineTag
.FindTag(line
, pos
);
2453 Split(line
, tag
, pos
, false);
2456 ///<summary>Split line at given tag and position into two lines</summary>
2457 ///<param name="soft">True if the split should be marked as 'soft', indicating that it can be contracted
2458 ///if more space becomes available on previous line</param>
2459 internal void Split(Line line
, LineTag tag
, int pos
, bool soft
) {
2463 bool move_sel_start
;
2467 move_sel_start
= false;
2468 move_sel_end
= false;
2470 // Adjust selection and cursors
2471 if (caret
.line
== line
&& caret
.pos
>= pos
) {
2474 if (selection_start
.line
== line
&& selection_start
.pos
> pos
) {
2475 move_sel_start
= true;
2478 if (selection_end
.line
== line
&& selection_end
.pos
> pos
) {
2479 move_sel_end
= true;
2482 // cover the easy case first
2483 if (pos
== line
.text
.Length
) {
2484 Add(line
.line_no
+ 1, "", line
.alignment
, tag
.font
, tag
.color
);
2486 new_line
= GetLine(line
.line_no
+ 1);
2488 line
.carriage_return
= false;
2489 new_line
.carriage_return
= line
.carriage_return
;
2490 new_line
.soft_break
= soft
;
2493 caret
.line
= new_line
;
2494 caret
.tag
= new_line
.tags
;
2498 if (move_sel_start
) {
2499 selection_start
.line
= new_line
;
2500 selection_start
.pos
= 0;
2501 selection_start
.tag
= new_line
.tags
;
2505 selection_end
.line
= new_line
;
2506 selection_end
.pos
= 0;
2507 selection_end
.tag
= new_line
.tags
;
2512 // We need to move the rest of the text into the new line
2513 Add(line
.line_no
+ 1, line
.text
.ToString(pos
, line
.text
.Length
- pos
), line
.alignment
, tag
.font
, tag
.color
);
2515 // Now transfer our tags from this line to the next
2516 new_line
= GetLine(line
.line_no
+ 1);
2518 line
.carriage_return
= false;
2519 new_line
.carriage_return
= line
.carriage_return
;
2520 new_line
.soft_break
= soft
;
2523 new_line
.recalc
= true;
2525 if ((tag
.start
- 1) == pos
) {
2528 // We can simply break the chain and move the tag into the next line
2529 if (tag
== line
.tags
) {
2530 new_tag
= new LineTag(line
, 1, 0);
2531 new_tag
.CopyFormattingFrom (tag
);
2532 line
.tags
= new_tag
;
2535 if (tag
.previous
!= null) {
2536 tag
.previous
.next
= null;
2538 new_line
.tags
= tag
;
2539 tag
.previous
= null;
2540 tag
.line
= new_line
;
2542 // Walk the list and correct the start location of the tags we just bumped into the next line
2543 shift
= tag
.start
- 1;
2546 while (new_tag
!= null) {
2547 new_tag
.start
-= shift
;
2548 new_tag
.line
= new_line
;
2549 new_tag
= new_tag
.next
;
2554 new_tag
= new LineTag(new_line
, 1, tag
.start
- 1 + tag
.length
- pos
);
2555 new_tag
.next
= tag
.next
;
2556 new_tag
.CopyFormattingFrom (tag
);
2557 new_line
.tags
= new_tag
;
2558 if (new_tag
.next
!= null) {
2559 new_tag
.next
.previous
= new_tag
;
2562 tag
.length
= pos
- tag
.start
+ 1;
2565 new_tag
= new_tag
.next
;
2566 while (new_tag
!= null) {
2567 new_tag
.start
-= shift
;
2568 new_tag
.line
= new_line
;
2569 new_tag
= new_tag
.next
;
2575 caret
.line
= new_line
;
2576 caret
.pos
= caret
.pos
- pos
;
2577 caret
.tag
= caret
.line
.FindTag(caret
.pos
);
2580 if (move_sel_start
) {
2581 selection_start
.line
= new_line
;
2582 selection_start
.pos
= selection_start
.pos
- pos
;
2583 selection_start
.tag
= new_line
.FindTag(selection_start
.pos
);
2587 selection_end
.line
= new_line
;
2588 selection_end
.pos
= selection_end
.pos
- pos
;
2589 selection_end
.tag
= new_line
.FindTag(selection_end
.pos
);
2592 CharCount
-= line
.text
.Length
- pos
;
2593 line
.text
.Remove(pos
, line
.text
.Length
- pos
);
2596 // Adds a line of text, with given font.
2597 // Bumps any line at that line number that already exists down
2598 internal void Add(int LineNo
, string Text
, Font font
, SolidBrush color
) {
2599 Add(LineNo
, Text
, HorizontalAlignment
.Left
, font
, color
);
2602 internal void Add(int LineNo
, string Text
, HorizontalAlignment align
, Font font
, SolidBrush color
) {
2607 CharCount
+= Text
.Length
;
2609 if (LineNo
<1 || Text
== null) {
2611 throw new ArgumentNullException("LineNo", "Line numbers must be positive");
2613 throw new ArgumentNullException("Text", "Cannot insert NULL line");
2617 add = new Line(LineNo
, Text
, align
, font
, color
);
2620 while (line
!= sentinel
) {
2622 line_no
= line
.line_no
;
2624 if (LineNo
> line_no
) {
2626 } else if (LineNo
< line_no
) {
2629 // Bump existing line numbers; walk all nodes to the right of this one and increment line_no
2630 IncrementLines(line
.line_no
);
2635 add.left
= sentinel
;
2636 add.right
= sentinel
;
2638 if (add.parent
!= null) {
2639 if (LineNo
> add.parent
.line_no
) {
2640 add.parent
.right
= add;
2642 add.parent
.left
= add;
2649 RebalanceAfterAdd(add);
2654 internal virtual void Clear() {
2657 document
= sentinel
;
2660 public virtual object Clone() {
2663 clone
= new Document(null);
2665 clone
.lines
= this.lines
;
2666 clone
.document
= (Line
)document
.Clone();
2671 internal void Delete(int LineNo
) {
2678 line
= GetLine(LineNo
);
2680 CharCount
-= line
.text
.Length
;
2682 DecrementLines(LineNo
+ 1);
2686 internal void Delete(Line line1
) {
2687 Line line2
;// = new Line();
2690 if ((line1
.left
== sentinel
) || (line1
.right
== sentinel
)) {
2693 line3
= line1
.right
;
2694 while (line3
.left
!= sentinel
) {
2699 if (line3
.left
!= sentinel
) {
2702 line2
= line3
.right
;
2705 line2
.parent
= line3
.parent
;
2706 if (line3
.parent
!= null) {
2707 if(line3
== line3
.parent
.left
) {
2708 line3
.parent
.left
= line2
;
2710 line3
.parent
.right
= line2
;
2716 if (line3
!= line1
) {
2719 if (selection_start
.line
== line3
) {
2720 selection_start
.line
= line1
;
2723 if (selection_end
.line
== line3
) {
2724 selection_end
.line
= line1
;
2727 if (selection_anchor
.line
== line3
) {
2728 selection_anchor
.line
= line1
;
2731 if (caret
.line
== line3
) {
2736 line1
.alignment
= line3
.alignment
;
2737 line1
.ascent
= line3
.ascent
;
2738 line1
.hanging_indent
= line3
.hanging_indent
;
2739 line1
.height
= line3
.height
;
2740 line1
.indent
= line3
.indent
;
2741 line1
.line_no
= line3
.line_no
;
2742 line1
.recalc
= line3
.recalc
;
2743 line1
.right_indent
= line3
.right_indent
;
2744 line1
.soft_break
= line3
.soft_break
;
2745 line1
.space
= line3
.space
;
2746 line1
.tags
= line3
.tags
;
2747 line1
.text
= line3
.text
;
2748 line1
.widths
= line3
.widths
;
2752 while (tag
!= null) {
2758 if (line3
.color
== LineColor
.Black
)
2759 RebalanceAfterDelete(line2
);
2764 // Invalidate a section of the document to trigger redraw
2765 internal void Invalidate(Line start
, int start_pos
, Line end
, int end_pos
) {
2771 if ((start
== end
) && (start_pos
== end_pos
)) {
2775 if (end_pos
== -1) {
2776 end_pos
= end
.text
.Length
;
2779 // figure out what's before what so the logic below is straightforward
2780 if (start
.line_no
< end
.line_no
) {
2786 } else if (start
.line_no
> end
.line_no
) {
2793 if (start_pos
< end_pos
) {
2808 Console
.WriteLine("Invaliding from {0}:{1} to {2}:{3} {4}",
2809 l1
.line_no
, p1
, l2
.line_no
, p2
,
2811 (int)l1
.widths
[p1
] + l1
.align_shift
- viewport_x
,
2813 (int)l2
.widths
[p2
] - (int)l1
.widths
[p1
] + 1,
2821 (int)l1
.widths
[p1
] + l1
.align_shift
- viewport_x
,
2823 (int)l2
.widths
[p2
] - (int)l1
.widths
[p1
] + 1,
2831 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
);
2832 Console
.WriteLine ("invalidate start line: {0} position: {1}", l1
.text
, p1
);
2835 // Three invalidates:
2836 // First line from start
2837 owner
.Invalidate(new Rectangle((int)l1
.widths
[p1
] + l1
.align_shift
- viewport_x
, l1
.Y
- viewport_y
, viewport_width
, l1
.height
));
2841 if ((l1
.line_no
+ 1) < l2
.line_no
) {
2844 y
= GetLine(l1
.line_no
+ 1).Y
;
2845 owner
.Invalidate(new Rectangle(0, y
- viewport_y
, viewport_width
, l2
.Y
- y
));
2848 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
, l2
.Y
- y
);
2854 owner
.Invalidate(new Rectangle((int)l2
.widths
[0] + l2
.align_shift
- viewport_x
, l2
.Y
- viewport_y
, (int)l2
.widths
[p2
] + 1, l2
.height
));
2856 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
);
2860 /// <summary>Select text around caret</summary>
2861 internal void ExpandSelection(CaretSelection mode
, bool to_caret
) {
2863 // We're expanding the selection to the caret position
2865 case CaretSelection
.Line
: {
2866 // Invalidate the selection delta
2867 if (caret
> selection_prev
) {
2868 Invalidate(selection_prev
.line
, 0, caret
.line
, caret
.line
.text
.Length
);
2870 Invalidate(selection_prev
.line
, selection_prev
.line
.text
.Length
, caret
.line
, 0);
2873 if (caret
.line
.line_no
<= selection_anchor
.line
.line_no
) {
2874 selection_start
.line
= caret
.line
;
2875 selection_start
.tag
= caret
.line
.tags
;
2876 selection_start
.pos
= 0;
2878 selection_end
.line
= selection_anchor
.line
;
2879 selection_end
.tag
= selection_anchor
.tag
;
2880 selection_end
.pos
= selection_anchor
.pos
;
2882 selection_end_anchor
= true;
2884 selection_start
.line
= selection_anchor
.line
;
2885 selection_start
.pos
= selection_anchor
.height
;
2886 selection_start
.tag
= selection_anchor
.line
.FindTag(selection_anchor
.height
);
2888 selection_end
.line
= caret
.line
;
2889 selection_end
.tag
= caret
.line
.tags
;
2890 selection_end
.pos
= caret
.line
.text
.Length
;
2892 selection_end_anchor
= false;
2894 selection_prev
.line
= caret
.line
;
2895 selection_prev
.tag
= caret
.tag
;
2896 selection_prev
.pos
= caret
.pos
;
2901 case CaretSelection
.Word
: {
2905 start_pos
= FindWordSeparator(caret
.line
, caret
.pos
, false);
2906 end_pos
= FindWordSeparator(caret
.line
, caret
.pos
, true);
2909 // Invalidate the selection delta
2910 if (caret
> selection_prev
) {
2911 Invalidate(selection_prev
.line
, selection_prev
.pos
, caret
.line
, end_pos
);
2913 Invalidate(selection_prev
.line
, selection_prev
.pos
, caret
.line
, start_pos
);
2915 if (caret
< selection_anchor
) {
2916 selection_start
.line
= caret
.line
;
2917 selection_start
.tag
= caret
.line
.FindTag(start_pos
);
2918 selection_start
.pos
= start_pos
;
2920 selection_end
.line
= selection_anchor
.line
;
2921 selection_end
.tag
= selection_anchor
.tag
;
2922 selection_end
.pos
= selection_anchor
.pos
;
2924 selection_prev
.line
= caret
.line
;
2925 selection_prev
.tag
= caret
.tag
;
2926 selection_prev
.pos
= start_pos
;
2928 selection_end_anchor
= true;
2930 selection_start
.line
= selection_anchor
.line
;
2931 selection_start
.pos
= selection_anchor
.height
;
2932 selection_start
.tag
= selection_anchor
.line
.FindTag(selection_anchor
.height
);
2934 selection_end
.line
= caret
.line
;
2935 selection_end
.tag
= caret
.line
.FindTag(end_pos
);
2936 selection_end
.pos
= end_pos
;
2938 selection_prev
.line
= caret
.line
;
2939 selection_prev
.tag
= caret
.tag
;
2940 selection_prev
.pos
= end_pos
;
2942 selection_end_anchor
= false;
2947 case CaretSelection
.Position
: {
2948 SetSelectionToCaret(false);
2953 // We're setting the selection 'around' the caret position
2955 case CaretSelection
.Line
: {
2956 this.Invalidate(caret
.line
, 0, caret
.line
, caret
.line
.text
.Length
);
2958 selection_start
.line
= caret
.line
;
2959 selection_start
.tag
= caret
.line
.tags
;
2960 selection_start
.pos
= 0;
2962 selection_end
.line
= caret
.line
;
2963 selection_end
.pos
= caret
.line
.text
.Length
;
2964 selection_end
.tag
= caret
.line
.FindTag(selection_end
.pos
);
2966 selection_anchor
.line
= selection_end
.line
;
2967 selection_anchor
.tag
= selection_end
.tag
;
2968 selection_anchor
.pos
= selection_end
.pos
;
2969 selection_anchor
.height
= 0;
2971 selection_prev
.line
= caret
.line
;
2972 selection_prev
.tag
= caret
.tag
;
2973 selection_prev
.pos
= caret
.pos
;
2975 this.selection_end_anchor
= true;
2980 case CaretSelection
.Word
: {
2984 start_pos
= FindWordSeparator(caret
.line
, caret
.pos
, false);
2985 end_pos
= FindWordSeparator(caret
.line
, caret
.pos
, true);
2987 this.Invalidate(selection_start
.line
, start_pos
, caret
.line
, end_pos
);
2989 selection_start
.line
= caret
.line
;
2990 selection_start
.tag
= caret
.line
.FindTag(start_pos
);
2991 selection_start
.pos
= start_pos
;
2993 selection_end
.line
= caret
.line
;
2994 selection_end
.tag
= caret
.line
.FindTag(end_pos
);
2995 selection_end
.pos
= end_pos
;
2997 selection_anchor
.line
= selection_end
.line
;
2998 selection_anchor
.tag
= selection_end
.tag
;
2999 selection_anchor
.pos
= selection_end
.pos
;
3000 selection_anchor
.height
= start_pos
;
3002 selection_prev
.line
= caret
.line
;
3003 selection_prev
.tag
= caret
.tag
;
3004 selection_prev
.pos
= caret
.pos
;
3006 this.selection_end_anchor
= true;
3013 SetSelectionVisible (!(selection_start
== selection_end
));
3016 internal void SetSelectionToCaret(bool start
) {
3018 // Invalidate old selection; selection is being reset to empty
3019 this.Invalidate(selection_start
.line
, selection_start
.pos
, selection_end
.line
, selection_end
.pos
);
3021 selection_start
.line
= caret
.line
;
3022 selection_start
.tag
= caret
.tag
;
3023 selection_start
.pos
= caret
.pos
;
3025 // start always also selects end
3026 selection_end
.line
= caret
.line
;
3027 selection_end
.tag
= caret
.tag
;
3028 selection_end
.pos
= caret
.pos
;
3030 selection_anchor
.line
= caret
.line
;
3031 selection_anchor
.tag
= caret
.tag
;
3032 selection_anchor
.pos
= caret
.pos
;
3034 // Invalidate from previous end to caret (aka new end)
3035 if (selection_end_anchor
) {
3036 if (selection_start
!= caret
) {
3037 this.Invalidate(selection_start
.line
, selection_start
.pos
, caret
.line
, caret
.pos
);
3040 if (selection_end
!= caret
) {
3041 this.Invalidate(selection_end
.line
, selection_end
.pos
, caret
.line
, caret
.pos
);
3045 if (caret
< selection_anchor
) {
3046 selection_start
.line
= caret
.line
;
3047 selection_start
.tag
= caret
.tag
;
3048 selection_start
.pos
= caret
.pos
;
3050 selection_end
.line
= selection_anchor
.line
;
3051 selection_end
.tag
= selection_anchor
.tag
;
3052 selection_end
.pos
= selection_anchor
.pos
;
3054 selection_end_anchor
= true;
3056 selection_start
.line
= selection_anchor
.line
;
3057 selection_start
.tag
= selection_anchor
.tag
;
3058 selection_start
.pos
= selection_anchor
.pos
;
3060 selection_end
.line
= caret
.line
;
3061 selection_end
.tag
= caret
.tag
;
3062 selection_end
.pos
= caret
.pos
;
3064 selection_end_anchor
= false;
3068 SetSelectionVisible (!(selection_start
== selection_end
));
3071 internal void SetSelection(Line start
, int start_pos
, Line end
, int end_pos
) {
3072 if (selection_visible
) {
3073 Invalidate(selection_start
.line
, selection_start
.pos
, selection_end
.line
, selection_end
.pos
);
3076 if ((end
.line_no
< start
.line_no
) || ((end
== start
) && (end_pos
<= start_pos
))) {
3077 selection_start
.line
= end
;
3078 selection_start
.tag
= LineTag
.FindTag(end
, end_pos
);
3079 selection_start
.pos
= end_pos
;
3081 selection_end
.line
= start
;
3082 selection_end
.tag
= LineTag
.FindTag(start
, start_pos
);
3083 selection_end
.pos
= start_pos
;
3085 selection_end_anchor
= true;
3087 selection_start
.line
= start
;
3088 selection_start
.tag
= LineTag
.FindTag(start
, start_pos
);
3089 selection_start
.pos
= start_pos
;
3091 selection_end
.line
= end
;
3092 selection_end
.tag
= LineTag
.FindTag(end
, end_pos
);
3093 selection_end
.pos
= end_pos
;
3095 selection_end_anchor
= false;
3098 selection_anchor
.line
= start
;
3099 selection_anchor
.tag
= selection_start
.tag
;
3100 selection_anchor
.pos
= start_pos
;
3102 if (((start
== end
) && (start_pos
== end_pos
)) || start
== null || end
== null) {
3103 SetSelectionVisible (false);
3105 SetSelectionVisible (true);
3106 Invalidate(selection_start
.line
, selection_start
.pos
, selection_end
.line
, selection_end
.pos
);
3110 internal void SetSelectionStart(Line start
, int start_pos
) {
3111 // Invalidate from the previous to the new start pos
3112 Invalidate(selection_start
.line
, selection_start
.pos
, start
, start_pos
);
3114 selection_start
.line
= start
;
3115 selection_start
.pos
= start_pos
;
3116 selection_start
.tag
= LineTag
.FindTag(start
, start_pos
);
3118 selection_anchor
.line
= start
;
3119 selection_anchor
.pos
= start_pos
;
3120 selection_anchor
.tag
= selection_start
.tag
;
3122 selection_end_anchor
= false;
3125 if ((selection_end
.line
!= selection_start
.line
) || (selection_end
.pos
!= selection_start
.pos
)) {
3126 SetSelectionVisible (true);
3128 SetSelectionVisible (false);
3131 Invalidate(selection_start
.line
, selection_start
.pos
, selection_end
.line
, selection_end
.pos
);
3134 internal void SetSelectionStart(int character_index
) {
3139 if (character_index
< 0) {
3143 CharIndexToLineTag(character_index
, out line
, out tag
, out pos
);
3144 SetSelectionStart(line
, pos
);
3147 internal void SetSelectionEnd(Line end
, int end_pos
) {
3149 if (end
== selection_end
.line
&& end_pos
== selection_start
.pos
) {
3150 selection_anchor
.line
= selection_start
.line
;
3151 selection_anchor
.tag
= selection_start
.tag
;
3152 selection_anchor
.pos
= selection_start
.pos
;
3154 selection_end
.line
= selection_start
.line
;
3155 selection_end
.tag
= selection_start
.tag
;
3156 selection_end
.pos
= selection_start
.pos
;
3158 selection_end_anchor
= false;
3159 } else if ((end
.line_no
< selection_anchor
.line
.line_no
) || ((end
== selection_anchor
.line
) && (end_pos
<= selection_anchor
.pos
))) {
3160 selection_start
.line
= end
;
3161 selection_start
.tag
= LineTag
.FindTag(end
, end_pos
);
3162 selection_start
.pos
= end_pos
;
3164 selection_end
.line
= selection_anchor
.line
;
3165 selection_end
.tag
= selection_anchor
.tag
;
3166 selection_end
.pos
= selection_anchor
.pos
;
3168 selection_end_anchor
= true;
3170 selection_start
.line
= selection_anchor
.line
;
3171 selection_start
.tag
= selection_anchor
.tag
;
3172 selection_start
.pos
= selection_anchor
.pos
;
3174 selection_end
.line
= end
;
3175 selection_end
.tag
= LineTag
.FindTag(end
, end_pos
);
3176 selection_end
.pos
= end_pos
;
3178 selection_end_anchor
= false;
3181 if ((selection_end
.line
!= selection_start
.line
) || (selection_end
.pos
!= selection_start
.pos
)) {
3182 SetSelectionVisible (true);
3183 Invalidate(selection_start
.line
, selection_start
.pos
, selection_end
.line
, selection_end
.pos
);
3185 SetSelectionVisible (false);
3186 // ?? Do I need to invalidate here, tests seem to work without it, but I don't think they should :-s
3190 internal void SetSelectionEnd(int character_index
) {
3195 if (character_index
< 0) {
3199 CharIndexToLineTag(character_index
, out line
, out tag
, out pos
);
3200 SetSelectionEnd(line
, pos
);
3203 internal void SetSelection(Line start
, int start_pos
) {
3204 if (selection_visible
) {
3205 Invalidate(selection_start
.line
, selection_start
.pos
, selection_end
.line
, selection_end
.pos
);
3208 selection_start
.line
= start
;
3209 selection_start
.pos
= start_pos
;
3210 selection_start
.tag
= LineTag
.FindTag(start
, start_pos
);
3212 selection_end
.line
= start
;
3213 selection_end
.tag
= selection_start
.tag
;
3214 selection_end
.pos
= start_pos
;
3216 selection_anchor
.line
= start
;
3217 selection_anchor
.tag
= selection_start
.tag
;
3218 selection_anchor
.pos
= start_pos
;
3220 selection_end_anchor
= false;
3221 SetSelectionVisible (false);
3224 internal void InvalidateSelectionArea() {
3225 Invalidate (selection_start
.line
, selection_start
.pos
, selection_end
.line
, selection_end
.pos
);
3228 // Return the current selection, as string
3229 internal string GetSelection() {
3230 // We return String.Empty if there is no selection
3231 if ((selection_start
.pos
== selection_end
.pos
) && (selection_start
.line
== selection_end
.line
)) {
3232 return string.Empty
;
3235 if (!multiline
|| (selection_start
.line
== selection_end
.line
)) {
3236 return selection_start
.line
.text
.ToString(selection_start
.pos
, selection_end
.pos
- selection_start
.pos
);
3243 sb
= new StringBuilder();
3244 start
= selection_start
.line
.line_no
;
3245 end
= selection_end
.line
.line_no
;
3247 sb
.Append(selection_start
.line
.text
.ToString(selection_start
.pos
, selection_start
.line
.text
.Length
- selection_start
.pos
) + Environment
.NewLine
);
3249 if ((start
+ 1) < end
) {
3250 for (i
= start
+ 1; i
< end
; i
++) {
3251 sb
.Append(GetLine(i
).text
.ToString() + Environment
.NewLine
);
3255 sb
.Append(selection_end
.line
.text
.ToString(0, selection_end
.pos
));
3257 return sb
.ToString();
3261 internal void ReplaceSelection(string s
, bool select_new
) {
3264 undo
.BeginCompoundAction ();
3266 InvalidateSelectionArea ();
3268 int selection_start_pos
= LineTagToCharIndex (selection_start
.line
, selection_start
.pos
);
3271 // First, delete any selected text
3272 if ((selection_start
.pos
!= selection_end
.pos
) || (selection_start
.line
!= selection_end
.line
)) {
3273 if (!multiline
|| (selection_start
.line
== selection_end
.line
)) {
3274 undo
.RecordDeleteChars(selection_start
.line
, selection_start
.pos
+ 1, selection_end
.pos
- selection_start
.pos
);
3276 DeleteChars(selection_start
.tag
, selection_start
.pos
, selection_end
.pos
- selection_start
.pos
);
3278 // The tag might have been removed, we need to recalc it
3279 selection_start
.tag
= selection_start
.line
.FindTag(selection_start
.pos
);
3284 start
= selection_start
.line
.line_no
;
3285 end
= selection_end
.line
.line_no
;
3287 undo
.RecordDelete(selection_start
.line
, selection_start
.pos
+ 1, selection_end
.line
, selection_end
.pos
);
3289 // Delete first line
3290 DeleteChars(selection_start
.tag
, selection_start
.pos
, selection_start
.line
.text
.Length
- selection_start
.pos
);
3293 DeleteChars(selection_end
.line
.tags
, 0, selection_end
.pos
);
3297 for (i
= end
- 1; i
>= start
; i
--) {
3302 // BIG FAT WARNING - selection_end.line might be stale due
3303 // to the above Delete() call. DONT USE IT before hitting the end of this method!
3305 // Join start and end
3306 Combine(selection_start
.line
.line_no
, start
);
3311 Insert(selection_start
.line
, selection_start
.pos
, false, s
);
3314 undo
.RecordInsertString (selection_start
.line
, selection_start
.pos
, s
);
3317 CharIndexToLineTag(selection_start_pos
+ s
.Length
, out selection_start
.line
,
3318 out selection_start
.tag
, out selection_start
.pos
);
3320 selection_end
.line
= selection_start
.line
;
3321 selection_end
.pos
= selection_start
.pos
;
3322 selection_end
.tag
= selection_start
.tag
;
3323 selection_anchor
.line
= selection_start
.line
;
3324 selection_anchor
.pos
= selection_start
.pos
;
3325 selection_anchor
.tag
= selection_start
.tag
;
3327 SetSelectionVisible (false);
3329 CharIndexToLineTag(selection_start_pos
, out selection_start
.line
,
3330 out selection_start
.tag
, out selection_start
.pos
);
3332 CharIndexToLineTag(selection_start_pos
+ s
.Length
, out selection_end
.line
,
3333 out selection_end
.tag
, out selection_end
.pos
);
3335 selection_anchor
.line
= selection_start
.line
;
3336 selection_anchor
.pos
= selection_start
.pos
;
3337 selection_anchor
.tag
= selection_start
.tag
;
3339 SetSelectionVisible (true);
3342 PositionCaret (selection_start
.line
, selection_start
.pos
);
3344 undo
.EndCompoundAction ();
3347 internal void CharIndexToLineTag(int index
, out Line line_out
, out LineTag tag_out
, out int pos
) {
3356 for (i
= 1; i
<= lines
; i
++) {
3360 chars
+= line
.text
.Length
+ (line
.soft_break
? 0 : crlf_size
);
3362 if (index
<= chars
) {
3363 // we found the line
3366 while (tag
!= null) {
3367 if (index
< (start
+ tag
.start
+ tag
.length
)) {
3369 tag_out
= LineTag
.GetFinalTag (tag
);
3370 pos
= index
- start
;
3373 if (tag
.next
== null) {
3376 next_line
= GetLine(line
.line_no
+ 1);
3378 if (next_line
!= null) {
3379 line_out
= next_line
;
3380 tag_out
= LineTag
.GetFinalTag (next_line
.tags
);
3385 tag_out
= LineTag
.GetFinalTag (tag
);
3386 pos
= line_out
.text
.Length
;
3395 line_out
= GetLine(lines
);
3396 tag
= line_out
.tags
;
3397 while (tag
.next
!= null) {
3401 pos
= line_out
.text
.Length
;
3404 internal int LineTagToCharIndex(Line line
, int pos
) {
3408 // Count first and last line
3411 // Count the lines in the middle
3413 for (i
= 1; i
< line
.line_no
; i
++) {
3414 length
+= GetLine(i
).text
.Length
+ crlf_size
;
3422 internal int SelectionLength() {
3423 if ((selection_start
.pos
== selection_end
.pos
) && (selection_start
.line
== selection_end
.line
)) {
3427 if (!multiline
|| (selection_start
.line
== selection_end
.line
)) {
3428 return selection_end
.pos
- selection_start
.pos
;
3435 // Count first and last line
3436 length
= selection_start
.line
.text
.Length
- selection_start
.pos
+ selection_end
.pos
+ crlf_size
;
3438 // Count the lines in the middle
3439 start
= selection_start
.line
.line_no
+ 1;
3440 end
= selection_end
.line
.line_no
;
3443 for (i
= start
; i
< end
; i
++) {
3444 length
+= GetLine(i
).text
.Length
+ crlf_size
;
3455 /// <summary>Give it a Line number and it returns the Line object at with that line number</summary>
3456 internal Line
GetLine(int LineNo
) {
3457 Line line
= document
;
3459 while (line
!= sentinel
) {
3460 if (LineNo
== line
.line_no
) {
3462 } else if (LineNo
< line
.line_no
) {
3472 /// <summary>Retrieve the previous tag; walks line boundaries</summary>
3473 internal LineTag
PreviousTag(LineTag tag
) {
3476 if (tag
.previous
!= null) {
3477 return tag
.previous
;
3481 if (tag
.line
.line_no
== 1) {
3485 l
= GetLine(tag
.line
.line_no
- 1);
3490 while (t
.next
!= null) {
3499 /// <summary>Retrieve the next tag; walks line boundaries</summary>
3500 internal LineTag
NextTag(LineTag tag
) {
3503 if (tag
.next
!= null) {
3508 l
= GetLine(tag
.line
.line_no
+ 1);
3516 internal Line
ParagraphStart(Line line
) {
3517 while (line
.soft_break
) {
3518 line
= GetLine(line
.line_no
- 1);
3523 internal Line
ParagraphEnd(Line line
) {
3526 while (line
.soft_break
) {
3527 l
= GetLine(line
.line_no
+ 1);
3528 if ((l
== null) || (!l
.soft_break
)) {
3536 /// <summary>Give it a Y pixel coordinate and it returns the Line covering that Y coordinate</summary>
3537 internal Line
GetLineByPixel(int y
, bool exact
) {
3538 Line line
= document
;
3541 while (line
!= sentinel
) {
3543 if ((y
>= line
.Y
) && (y
< (line
.Y
+line
.height
))) {
3545 } else if (y
< line
.Y
) {
3558 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3559 internal LineTag
FindTag(int x
, int y
, out int index
, bool exact
) {
3563 line
= GetLineByPixel(y
, exact
);
3570 // Alignment adjustment
3571 x
+= line
.align_shift
;
3574 if (x
>= tag
.X
&& x
< (tag
.X
+tag
.width
)) {
3577 end
= tag
.start
+ tag
.length
- 1;
3579 for (int pos
= tag
.start
; pos
< end
; pos
++) {
3580 if (x
< line
.widths
[pos
]) {
3582 return LineTag
.GetFinalTag (tag
);
3586 return LineTag
.GetFinalTag (tag
);
3588 if (tag
.next
!= null) {
3596 index
= line
.text
.Length
;
3597 return LineTag
.GetFinalTag (tag
);
3602 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3603 internal LineTag
FindCursor(int x
, int y
, out int index
) {
3607 line
= GetLineByPixel(y
, false);
3610 // Adjust for alignment
3611 x
-= line
.align_shift
;
3614 if (x
>= tag
.X
&& x
< (tag
.X
+tag
.width
)) {
3617 end
= tag
.start
+ tag
.length
- 1;
3619 for (int pos
= tag
.start
-1; pos
< end
; pos
++) {
3620 // When clicking on a character, we position the cursor to whatever edge
3621 // of the character the click was closer
3622 if (x
< (line
.widths
[pos
] + ((line
.widths
[pos
+1]-line
.widths
[pos
])/2))) {
3630 if (tag
.next
!= null) {
3633 index
= line
.text
.Length
;
3639 /// <summary>Format area of document in specified font and color</summary>
3640 /// <param name="start_pos">1-based start position on start_line</param>
3641 /// <param name="end_pos">1-based end position on end_line </param>
3642 internal void FormatText (Line start_line
, int start_pos
, Line end_line
, int end_pos
, Font font
,
3643 SolidBrush color
, SolidBrush back_color
, FormatSpecified specified
)
3647 // First, format the first line
3648 if (start_line
!= end_line
) {
3650 LineTag
.FormatText(start_line
, start_pos
, start_line
.text
.Length
- start_pos
+ 1, font
, color
, back_color
, specified
);
3653 LineTag
.FormatText(end_line
, 1, end_pos
, font
, color
, back_color
, specified
);
3655 // Now all the lines inbetween
3656 for (int i
= start_line
.line_no
+ 1; i
< end_line
.line_no
; i
++) {
3658 LineTag
.FormatText(l
, 1, l
.text
.Length
, font
, color
, back_color
, specified
);
3661 // Special case, single line
3662 LineTag
.FormatText(start_line
, start_pos
, end_pos
- start_pos
, font
, color
, back_color
, specified
);
3666 /// <summary>Re-format areas of the document in specified font and color</summary>
3667 /// <param name="start_pos">1-based start position on start_line</param>
3668 /// <param name="end_pos">1-based end position on end_line </param>
3669 /// <param name="font">Font specifying attributes</param>
3670 /// <param name="color">Color (or NULL) to apply</param>
3671 /// <param name="apply">Attributes from font and color to apply</param>
3672 internal void FormatText(Line start_line
, int start_pos
, Line end_line
, int end_pos
, FontDefinition attributes
) {
3675 // First, format the first line
3676 if (start_line
!= end_line
) {
3678 LineTag
.FormatText(start_line
, start_pos
, start_line
.text
.Length
- start_pos
+ 1, attributes
);
3681 LineTag
.FormatText(end_line
, 1, end_pos
- 1, attributes
);
3683 // Now all the lines inbetween
3684 for (int i
= start_line
.line_no
+ 1; i
< end_line
.line_no
; i
++) {
3686 LineTag
.FormatText(l
, 1, l
.text
.Length
, attributes
);
3689 // Special case, single line
3690 LineTag
.FormatText(start_line
, start_pos
, end_pos
- start_pos
, attributes
);
3694 internal void RecalculateAlignments() {
3700 while (line_no
<= lines
) {
3701 line
= GetLine(line_no
);
3704 switch (line
.alignment
) {
3705 case HorizontalAlignment
.Left
:
3706 line
.align_shift
= 0;
3708 case HorizontalAlignment
.Center
:
3709 line
.align_shift
= (viewport_width
- (int)line
.widths
[line
.text
.Length
]) / 2;
3711 case HorizontalAlignment
.Right
:
3712 line
.align_shift
= viewport_width
- (int)line
.widths
[line
.text
.Length
];
3722 /// <summary>Calculate formatting for the whole document</summary>
3723 internal bool RecalculateDocument(Graphics g
) {
3724 return RecalculateDocument(g
, 1, this.lines
, false);
3727 /// <summary>Calculate formatting starting at a certain line</summary>
3728 internal bool RecalculateDocument(Graphics g
, int start
) {
3729 return RecalculateDocument(g
, start
, this.lines
, false);
3732 /// <summary>Calculate formatting within two given line numbers</summary>
3733 internal bool RecalculateDocument(Graphics g
, int start
, int end
) {
3734 return RecalculateDocument(g
, start
, end
, false);
3737 /// <summary>With optimize on, returns true if line heights changed</summary>
3738 internal bool RecalculateDocument(Graphics g
, int start
, int end
, bool optimize
) {
3747 recalc_pending
= true;
3748 recalc_start
= start
;
3750 recalc_optimize
= optimize
;
3754 Y
= GetLine(start
).Y
;
3759 changed
= true; // We always return true if we run non-optimized
3764 while (line_no
<= (end
+ this.lines
- shift
)) {
3765 line
= GetLine(line_no
++);
3770 line
.RecalculateLine(g
, this);
3772 if (line
.recalc
&& line
.RecalculateLine(g
, this)) {
3774 // If the height changed, all subsequent lines change
3781 line
.RecalculatePasswordLine(g
, this);
3783 if (line
.recalc
&& line
.RecalculatePasswordLine(g
, this)) {
3785 // If the height changed, all subsequent lines change
3792 if (line
.widths
[line
.text
.Length
] > new_width
) {
3793 new_width
= (int)line
.widths
[line
.text
.Length
];
3796 // Calculate alignment
3797 if (line
.alignment
!= HorizontalAlignment
.Left
) {
3798 if (line
.alignment
== HorizontalAlignment
.Center
) {
3799 line
.align_shift
= (viewport_width
- (int)line
.widths
[line
.text
.Length
]) / 2;
3801 line
.align_shift
= viewport_width
- (int)line
.widths
[line
.text
.Length
] - 1;
3807 if (line_no
> lines
) {
3812 if (document_x
!= new_width
) {
3813 document_x
= new_width
;
3814 if (WidthChanged
!= null) {
3815 WidthChanged(this, null);
3819 RecalculateAlignments();
3821 line
= GetLine(lines
);
3823 if (document_y
!= line
.Y
+ line
.height
) {
3824 document_y
= line
.Y
+ line
.height
;
3825 if (HeightChanged
!= null) {
3826 HeightChanged(this, null);
3833 internal int Size() {
3837 private void owner_HandleCreated(object sender
, EventArgs e
) {
3838 RecalculateDocument(owner
.CreateGraphicsInternal());
3842 private void owner_VisibleChanged(object sender
, EventArgs e
) {
3843 if (owner
.Visible
) {
3844 RecalculateDocument(owner
.CreateGraphicsInternal());
3848 internal static bool IsWordSeparator(char ch
) {
3862 internal int FindWordSeparator(Line line
, int pos
, bool forward
) {
3865 len
= line
.text
.Length
;
3868 for (int i
= pos
+ 1; i
< len
; i
++) {
3869 if (IsWordSeparator(line
.Text
[i
])) {
3875 for (int i
= pos
- 1; i
> 0; i
--) {
3876 if (IsWordSeparator(line
.Text
[i
- 1])) {
3884 /* Search document for text */
3885 internal bool FindChars(char[] chars
, Marker start
, Marker end
, out Marker result
) {
3891 // Search for occurence of any char in the chars array
3892 result
= new Marker();
3895 line_no
= start
.line
.line_no
;
3897 while (line_no
<= end
.line
.line_no
) {
3898 line_len
= line
.text
.Length
;
3899 while (pos
< line_len
) {
3900 for (int i
= 0; i
< chars
.Length
; i
++) {
3901 if (line
.text
[pos
] == chars
[i
]) {
3903 if ((line
.line_no
== end
.line
.line_no
) && (pos
>= end
.pos
)) {
3917 line
= GetLine(line_no
);
3923 // This version does not build one big string for searching, instead it handles
3924 // line-boundaries, which is faster and less memory intensive
3925 // FIXME - Depending on culture stuff we might have to create a big string and use culturespecific
3926 // search stuff and change it to accept and return positions instead of Markers (which would match
3927 // RichTextBox behaviour better but would be inconsistent with the rest of TextControl)
3928 internal bool Find(string search
, Marker start
, Marker end
, out Marker result
, RichTextBoxFinds options
) {
3930 string search_string
;
3942 result
= new Marker();
3943 word_option
= ((options
& RichTextBoxFinds
.WholeWord
) != 0);
3944 ignore_case
= ((options
& RichTextBoxFinds
.MatchCase
) == 0);
3945 reverse
= ((options
& RichTextBoxFinds
.Reverse
) != 0);
3948 line_no
= start
.line
.line_no
;
3952 // Prep our search string, lowercasing it if we do case-independent matching
3955 sb
= new StringBuilder(search
);
3956 for (int i
= 0; i
< sb
.Length
; i
++) {
3957 sb
[i
] = Char
.ToLower(sb
[i
]);
3959 search_string
= sb
.ToString();
3961 search_string
= search
;
3964 // We need to check if the character before our start position is a wordbreak
3967 if ((pos
== 0) || (IsWordSeparator(line
.text
[pos
- 1]))) {
3974 if (IsWordSeparator(line
.text
[pos
- 1])) {
3980 // Need to check the end of the previous line
3983 prev_line
= GetLine(line_no
- 1);
3984 if (prev_line
.soft_break
) {
3985 if (IsWordSeparator(prev_line
.text
[prev_line
.text
.Length
- 1])) {
3999 // To avoid duplication of this loop with reverse logic, we search
4000 // through the document, remembering the last match and when returning
4001 // report that last remembered match
4003 last
= new Marker();
4004 last
.height
= -1; // Abused - we use it to track change
4006 while (line_no
<= end
.line
.line_no
) {
4007 if (line_no
!= end
.line
.line_no
) {
4008 line_len
= line
.text
.Length
;
4013 while (pos
< line_len
) {
4014 if (word_option
&& (current
== search_string
.Length
)) {
4015 if (IsWordSeparator(line
.text
[pos
])) {
4028 c
= Char
.ToLower(line
.text
[pos
]);
4033 if (c
== search_string
[current
]) {
4038 if (!word_option
|| (word_option
&& (word
|| (current
> 0)))) {
4042 if (!word_option
&& (current
== search_string
.Length
)) {
4059 if (IsWordSeparator(c
)) {
4067 // Mark that we just saw a word boundary
4068 if (!line
.soft_break
) {
4072 if (current
== search_string
.Length
) {
4088 line
= GetLine(line_no
);
4092 if (last
.height
!= -1) {
4102 // if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
4114 internal void GetMarker(out Marker mark
, bool start
) {
4115 mark
= new Marker();
4118 mark
.line
= GetLine(1);
4119 mark
.tag
= mark
.line
.tags
;
4122 mark
.line
= GetLine(lines
);
4123 mark
.tag
= mark
.line
.tags
;
4124 while (mark
.tag
.next
!= null) {
4125 mark
.tag
= mark
.tag
.next
;
4127 mark
.pos
= mark
.line
.text
.Length
;
4130 #endregion // Internal Methods
4133 internal event EventHandler CaretMoved
;
4134 internal event EventHandler WidthChanged
;
4135 internal event EventHandler HeightChanged
;
4136 internal event EventHandler LengthChanged
;
4137 #endregion // Events
4139 #region Administrative
4140 public IEnumerator
GetEnumerator() {
4145 public override bool Equals(object obj
) {
4150 if (!(obj
is Document
)) {
4158 if (ToString().Equals(((Document
)obj
).ToString())) {
4165 public override int GetHashCode() {
4169 public override string ToString() {
4170 return "document " + this.document_id
;
4172 #endregion // Administrative
4175 internal class LineTag
{
4176 #region Local Variables;
4177 // Payload; formatting
4178 internal Font font
; // System.Drawing.Font object for this tag
4179 internal SolidBrush color
; // The font color for this tag
4181 // In 2.0 tags can have background colours. I'm not going to #ifdef
4182 // at this level though since I want to reduce code paths
4183 internal SolidBrush back_color
;
4186 internal int start
; // start, in chars; index into Line.text
4187 internal int length
; // length, in chars
4188 internal bool r_to_l
; // Which way is the font
4191 internal int height
; // Height in pixels of the text this tag describes
4192 internal int X
; // X location of the text this tag describes
4193 // internal float width; // Width in pixels of the text this tag describes
4194 internal int ascent
; // Ascent of the font for this tag
4195 internal int shift
; // Shift down for this tag, to stay on baseline
4198 internal Line line
; // The line we're on
4199 internal LineTag next
; // Next tag on the same line
4200 internal LineTag previous
; // Previous tag on the same line
4203 #region Constructors
4204 internal LineTag(Line line
, int start
, int length
) {
4207 this.length
= length
;
4210 #endregion // Constructors
4212 #region Internal Methods
4215 get { return start + length; }
4218 public float width
{
4222 return line
.widths
[start
+ length
- 1];
4225 ///<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>
4226 internal LineTag
Break(int pos
) {
4231 if (pos
== this.start
) {
4233 } else if (pos
>= (start
+ length
)) {
4237 new_tag
= new LineTag(line
, pos
, start
+ length
- pos
);
4238 new_tag
.CopyFormattingFrom (this);
4239 this.length
-= new_tag
.length
;
4240 new_tag
.next
= this.next
;
4241 this.next
= new_tag
;
4242 new_tag
.previous
= this;
4243 if (new_tag
.next
!= null) {
4244 new_tag
.next
.previous
= new_tag
;
4250 public void CopyFormattingFrom (LineTag other
)
4252 height
= other
.height
;
4254 color
= other
.color
;
4255 back_color
= other
.back_color
;
4258 ///<summary>Create new font and brush from existing font and given new attributes. Returns true if fontheight changes</summary>
4259 internal static bool GenerateTextFormat(Font font_from
, SolidBrush color_from
, FontDefinition attributes
, out Font new_font
, out SolidBrush new_color
) {
4265 if (attributes
.font_obj
== null) {
4266 size
= font_from
.SizeInPoints
;
4267 unit
= font_from
.Unit
;
4268 face
= font_from
.Name
;
4269 style
= font_from
.Style
;
4271 if (attributes
.face
!= null) {
4272 face
= attributes
.face
;
4275 if (attributes
.size
!= 0) {
4276 size
= attributes
.size
;
4279 style
|= attributes
.add_style
;
4280 style
&= ~attributes
.remove_style
;
4283 new_font
= new Font(face
, size
, style
, unit
);
4285 new_font
= attributes
.font_obj
;
4288 // Create 'new' color brush
4289 if (attributes
.color
!= Color
.Empty
) {
4290 new_color
= new SolidBrush(attributes
.color
);
4292 new_color
= color_from
;
4295 if (new_font
.Height
== font_from
.Height
) {
4301 /// <summary>Applies 'font' and 'brush' to characters starting at 'start' for 'length' chars;
4302 /// Removes any previous tags overlapping the same area;
4303 /// returns true if lineheight has changed</summary>
4304 /// <param name="start">1-based character position on line</param>
4305 internal static bool FormatText(Line line
, int start
, int length
, Font font
, SolidBrush color
, SolidBrush back_color
, FormatSpecified specified
)
4311 bool retval
= false; // Assume line-height doesn't change
4314 if (((FormatSpecified
.Font
& specified
) == FormatSpecified
.Font
) && font
.Height
!= line
.height
) {
4317 line
.recalc
= true; // This forces recalculation of the line in RecalculateDocument
4319 // A little sanity, not sure if it's needed, might be able to remove for speed
4320 if (length
> line
.text
.Length
) {
4321 length
= line
.text
.Length
;
4325 end
= start
+ length
;
4327 // Common special case
4328 if ((start
== 1) && (length
== tag
.length
)) {
4330 SetFormat (tag
, font
, color
, back_color
, specified
);
4334 start_tag
= FindTag (line
, start
);
4336 tag
= start_tag
.Break (start
);
4338 while (tag
!= null && tag
.End
< end
) {
4339 SetFormat (tag
, font
, color
, back_color
, specified
);
4343 if (end
!= line
.text
.Length
) {
4344 /// Now do the last tag
4345 end_tag
= FindTag (line
, end
);
4347 if (end_tag
!= null) {
4348 end_tag
.Break (end
);
4349 SetFormat (end_tag
, font
, color
, back_color
, specified
);
4356 private static void SetFormat (LineTag tag
, Font font
, SolidBrush color
, SolidBrush back_color
, FormatSpecified specified
)
4358 if ((FormatSpecified
.Font
& specified
) == FormatSpecified
.Font
)
4360 if ((FormatSpecified
.Color
& specified
) == FormatSpecified
.Color
)
4362 if ((FormatSpecified
.BackColor
& specified
) == FormatSpecified
.BackColor
) {
4363 tag
.back_color
= back_color
;
4367 /// <summary>Applies font attributes specified to characters starting at 'start' for 'length' chars;
4368 /// Breaks tags at start and end point, keeping middle tags with altered attributes.
4369 /// Returns true if lineheight has changed</summary>
4370 /// <param name="start">1-based character position on line</param>
4371 internal static bool FormatText(Line line
, int start
, int length
, FontDefinition attributes
) {
4375 bool retval
= false; // Assume line-height doesn't change
4377 line
.recalc
= true; // This forces recalculation of the line in RecalculateDocument
4379 // A little sanity, not sure if it's needed, might be able to remove for speed
4380 if (length
> line
.text
.Length
) {
4381 length
= line
.text
.Length
;
4386 // Common special case
4387 if ((start
== 1) && (length
== tag
.length
)) {
4389 GenerateTextFormat(tag
.font
, tag
.color
, attributes
, out tag
.font
, out tag
.color
);
4393 start_tag
= FindTag(line
, start
);
4395 if (start_tag
== null) {
4397 // We are 'starting' after all valid tags; create a new tag with the right attributes
4398 start_tag
= FindTag(line
, line
.text
.Length
- 1);
4399 start_tag
.next
= new LineTag(line
, line
.text
.Length
+ 1, 0);
4400 start_tag
.next
.CopyFormattingFrom (start_tag
);
4401 start_tag
.next
.previous
= start_tag
;
4402 start_tag
= start_tag
.next
;
4404 throw new Exception(String
.Format("Could not find start_tag in document at line {0} position {1}", line
.line_no
, start
));
4407 start_tag
= start_tag
.Break(start
);
4410 end_tag
= FindTag(line
, start
+ length
);
4411 if (end_tag
!= null) {
4412 end_tag
= end_tag
.Break(start
+ length
);
4415 // start_tag or end_tag might be null; we're cool with that
4416 // we now walk from start_tag to end_tag, applying new attributes
4418 while ((tag
!= null) && tag
!= end_tag
) {
4419 if (LineTag
.GenerateTextFormat(tag
.font
, tag
.color
, attributes
, out tag
.font
, out tag
.color
)) {
4428 /// <summary>Finds the tag that describes the character at position 'pos' on 'line'</summary>
4429 internal static LineTag
FindTag(Line line
, int pos
) {
4430 LineTag tag
= line
.tags
;
4432 // Beginning of line is a bit special
4434 // Not sure if we should get the final tag here
4438 while (tag
!= null) {
4439 if ((tag
.start
<= pos
) && (pos
< (tag
.start
+tag
.length
))) {
4440 return GetFinalTag (tag
);
4449 // There can be multiple tags at the same position, we want to make
4450 // sure we are using the very last tag at the given position
4451 internal static LineTag
GetFinalTag (LineTag tag
)
4455 while (res
.next
!= null && res
.next
.length
== 0)
4460 /// <summary>Combines 'this' tag with 'other' tag</summary>
4461 internal bool Combine(LineTag other
) {
4462 if (!this.Equals(other
)) {
4466 this.length
+= other
.length
;
4467 this.next
= other
.next
;
4468 if (this.next
!= null) {
4469 this.next
.previous
= this;
4476 /// <summary>Remove 'this' tag ; to be called when formatting is to be removed</summary>
4477 internal bool Remove() {
4478 if ((this.start
== 1) && (this.next
== null)) {
4479 // We cannot remove the only tag
4482 if (this.start
!= 1) {
4483 this.previous
.length
+= this.length
;
4484 this.previous
.next
= this.next
;
4485 this.next
.previous
= this.previous
;
4487 this.next
.start
= 1;
4488 this.next
.length
+= this.length
;
4489 this.line
.tags
= this.next
;
4490 this.next
.previous
= null;
4496 /// <summary>Checks if 'this' tag describes the same formatting options as 'obj'</summary>
4497 public override bool Equals(object obj
) {
4504 if (!(obj
is LineTag
)) {
4512 other
= (LineTag
)obj
;
4514 if (this.font
.Equals(other
.font
) && this.color
.Equals(other
.color
)) { // FIXME add checking for things like link or type later
4521 public override int GetHashCode() {
4522 return base.GetHashCode ();
4525 public override string ToString() {
4527 return "Tag starts at index " + this.start
+ " length " + this.length
+ " text: " + this.line
.Text
.Substring(this.start
-1, this.length
) + "Font " + this.font
.ToString();
4528 return "Zero Lengthed tag at index " + this.start
;
4531 #endregion // Internal Methods
4534 internal class UndoClass
{
4535 internal enum ActionType
{
4546 internal class Action
{
4547 internal ActionType type
;
4548 internal int line_no
;
4550 internal object data
;
4553 #region Local Variables
4554 private Document document
;
4555 private Stack undo_actions
;
4556 private Stack redo_actions
;
4558 private int undo_levels
;
4559 private int redo_levels
;
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 internal int UndoLevels
{
4579 internal int RedoLevels
{
4585 internal string UndoName
{
4589 action
= (Action
)undo_actions
.Peek();
4591 if (action
.type
== ActionType
.CompoundEnd
)
4592 return (string) action
.data
;
4594 switch(action
.type
) {
4595 case ActionType
.InsertChar
: {
4596 Locale
.GetText("Insert character");
4600 case ActionType
.DeleteChar
: {
4601 Locale
.GetText("Delete character");
4605 case ActionType
.InsertString
: {
4606 Locale
.GetText("Insert string");
4610 case ActionType
.DeleteChars
: {
4611 Locale
.GetText("Delete string");
4615 case ActionType
.CursorMove
: {
4616 Locale
.GetText("Cursor move");
4624 internal string RedoName() {
4627 #endregion // Properties
4629 #region Internal Methods
4630 internal void Clear() {
4631 undo_actions
.Clear();
4632 redo_actions
.Clear();
4637 internal void Undo() {
4639 int compound_stack
= 0;
4641 if (undo_actions
.Count
== 0) {
4648 action
= (Action
)undo_actions
.Pop();
4650 // Put onto redo stack
4651 redo_actions
.Push(action
);
4654 switch(action
.type
) {
4655 case ActionType
.CompoundEnd
:
4659 case ActionType
.CompoundBegin
:
4665 case ActionType
.InsertString
:
4666 document
.DeleteMultiline (document
.GetLine (action
.line_no
),
4667 action
.pos
, ((string) action
.data
).Length
+ 1);
4670 case ActionType
.InsertChar
: {
4671 // FIXME - implement me
4675 case ActionType
.DeleteChars
: {
4676 this.Insert(document
.GetLine(action
.line_no
), action
.pos
, (Line
)action
.data
);
4677 Undo(); // Grab the cursor location
4681 case ActionType
.CursorMove
: {
4682 document
.caret
.line
= document
.GetLine(action
.line_no
);
4683 if (document
.caret
.line
== null) {
4688 document
.caret
.tag
= document
.caret
.line
.FindTag(action
.pos
);
4689 document
.caret
.pos
= action
.pos
;
4690 document
.caret
.height
= document
.caret
.tag
.height
;
4692 if (document
.owner
.IsHandleCreated
) {
4693 XplatUI
.DestroyCaret(document
.owner
.Handle
);
4694 XplatUI
.CreateCaret(document
.owner
.Handle
, 2, document
.caret
.height
);
4695 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
);
4697 document
.DisplayCaret ();
4700 // FIXME - enable call
4701 //if (document.CaretMoved != null) document.CaretMoved(this, EventArgs.Empty);
4705 } while (compound_stack
> 0);
4708 internal void Redo() {
4709 if (redo_actions
.Count
== 0) {
4713 #endregion // Internal Methods
4715 #region Private Methods
4717 public void BeginCompoundAction ()
4719 Action cb
= new Action ();
4720 cb
.type
= ActionType
.CompoundBegin
;
4722 undo_actions
.Push (cb
);
4725 public void EndCompoundAction ()
4727 Action ce
= new Action ();
4728 ce
.type
= ActionType
.CompoundEnd
;
4730 undo_actions
.Push (ce
);
4735 public void RecordDeleteChars(Line line
, int pos
, int length
) {
4736 RecordDelete(line
, pos
, line
, pos
+ length
- 1);
4739 // start_pos, end_pos = 1 based
4740 public void RecordDelete(Line start_line
, int start_pos
, Line end_line
, int end_pos
) {
4744 l
= Duplicate(start_line
, start_pos
, end_line
, end_pos
);
4747 a
.type
= ActionType
.DeleteChars
;
4749 a
.line_no
= start_line
.line_no
;
4750 a
.pos
= start_pos
- 1;
4752 // Record the cursor position before, since the actions will occur in reverse order
4754 undo_actions
.Push(a
);
4757 public void RecordInsertString (Line line
, int pos
, string str
)
4759 Action a
= new Action ();
4761 a
.type
= ActionType
.InsertString
;
4763 a
.line_no
= line
.line_no
;
4766 undo_actions
.Push (a
);
4769 public void RecordCursor() {
4770 if (document
.caret
.line
== null) {
4774 RecordCursor(document
.caret
.line
, document
.caret
.pos
);
4777 public void RecordCursor(Line line
, int pos
) {
4780 if ((line
.line_no
== caret_line
) && (pos
== caret_pos
)) {
4784 caret_line
= line
.line_no
;
4788 a
.type
= ActionType
.CursorMove
;
4789 a
.line_no
= line
.line_no
;
4792 undo_actions
.Push(a
);
4795 // start_pos = 1-based
4796 // end_pos = 1-based
4797 public Line
Duplicate(Line start_line
, int start_pos
, Line end_line
, int end_pos
) {
4802 LineTag current_tag
;
4811 for (int i
= start_line
.line_no
; i
<= end_line
.line_no
; i
++) {
4812 current
= document
.GetLine(i
);
4814 if (start_line
.line_no
== i
) {
4820 if (end_line
.line_no
== i
) {
4823 end
= current
.text
.Length
;
4827 line
.text
= new StringBuilder(current
.text
.ToString(start
- 1, end
- start
+ 1));
4829 // Copy tags from start to start+length onto new line
4830 current_tag
= current
.FindTag(start
- 1);
4831 while ((current_tag
!= null) && (current_tag
.start
< end
)) {
4832 if ((current_tag
.start
<= start
) && (start
< (current_tag
.start
+ current_tag
.length
))) {
4833 // start tag is within this tag
4836 tag_start
= current_tag
.start
;
4839 if (end
< (current_tag
.start
+ current_tag
.length
)) {
4840 tag_length
= end
- tag_start
+ 1;
4842 tag_length
= current_tag
.start
+ current_tag
.length
- tag_start
;
4844 tag
= new LineTag(line
, tag_start
- start
+ 1, tag_length
);
4845 tag
.CopyFormattingFrom (current_tag
);
4847 current_tag
= current_tag
.next
;
4849 // Add the new tag to the line
4850 if (line
.tags
== null) {
4856 while (tail
.next
!= null) {
4860 tag
.previous
= tail
;
4864 if ((i
+ 1) <= end_line
.line_no
) {
4865 line
.soft_break
= current
.soft_break
;
4867 // Chain them (we use right/left as next/previous)
4868 line
.right
= new Line();
4869 line
.right
.left
= line
;
4877 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
4878 internal void Insert(Line line
, int pos
, Line insert
) {
4885 // Handle special case first
4886 if (insert
.right
== null) {
4888 // Single line insert
4889 document
.Split(line
, pos
);
4891 if (insert
.tags
== null) {
4892 return; // Blank line
4895 //Insert our tags at the end
4898 while (tag
.next
!= null) {
4902 offset
= tag
.start
+ tag
.length
- 1;
4904 tag
.next
= insert
.tags
;
4905 line
.text
.Insert(offset
, insert
.text
.ToString());
4907 // Adjust start locations
4909 while (tag
!= null) {
4910 tag
.start
+= offset
;
4914 // Put it back together
4915 document
.Combine(line
.line_no
, line
.line_no
+ 1);
4916 document
.UpdateView(line
, pos
);
4923 while (current
!= null) {
4924 if (current
== insert
) {
4925 // Inserting the first line we split the line (and make space)
4926 document
.Split(line
, pos
);
4927 //Insert our tags at the end of the line
4931 while (tag
.next
!= null) {
4934 offset
= tag
.start
+ tag
.length
- 1;
4935 tag
.next
= current
.tags
;
4936 tag
.next
.previous
= tag
;
4942 line
.tags
= current
.tags
;
4943 line
.tags
.previous
= null;
4947 document
.Split(line
.line_no
, 0);
4949 line
.tags
= current
.tags
;
4950 line
.tags
.previous
= null;
4953 // Adjust start locations and line pointers
4954 while (tag
!= null) {
4955 tag
.start
+= offset
;
4960 line
.text
.Insert(offset
, current
.text
.ToString());
4961 line
.Grow(line
.text
.Length
);
4964 line
= document
.GetLine(line
.line_no
+ 1);
4966 // FIXME? Test undo of line-boundaries
4967 if ((current
.right
== null) && (current
.tags
.length
!= 0)) {
4968 document
.Combine(line
.line_no
- 1, line
.line_no
);
4970 current
= current
.right
;
4975 // Recalculate our document
4976 document
.UpdateView(first
, lines
, pos
);
4979 #endregion // Private Methods