1 // Permission is hereby granted, free of charge, to any person obtaining
2 // a copy of this software and associated documentation files (the
3 // "Software"), to deal in the Software without restriction, including
4 // without limitation the rights to use, copy, modify, merge, publish,
5 // distribute, sublicense, and/or sell copies of the Software, and to
6 // permit persons to whom the Software is furnished to do so, subject to
7 // the following conditions:
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
12 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 // Copyright (c) 2004-2006 Novell, Inc. (http://www.novell.com)
23 // Peter Bartok pbartok@novell.com
29 // There's still plenty of things missing, I've got most of it planned, just hadn't had
30 // the time to write it all yet.
31 // Stuff missing (in no particular order):
32 // - Align text after RecalculateLine
33 // - Implement tag types for hotlinks, images, etc.
34 // - Implement CaretPgUp/PgDown
37 // selection_start.pos and selection_end.pos are 0-based
38 // selection_start.pos = first selected char
39 // selection_end.pos = first NOT-selected char
41 // FormatText methods are 1-based (as are all tags, LineTag.Start is 1 for
42 // the first character on a line; the reason is that 0 is the position
43 // *before* the first character on a line
49 using System
.Collections
;
51 using System
.Drawing
.Text
;
54 namespace System
.Windows
.Forms
{
55 internal enum LineColor
{
60 internal enum CaretSelection
{
61 Position
, // Selection=Caret
62 Word
, // Selection=Word under caret
63 Line
// Selection=Line under caret
66 internal class FontDefinition
{
69 internal FontStyle add_style
;
70 internal FontStyle remove_style
;
72 internal Font font_obj
;
74 internal FontDefinition() {
81 internal enum CaretDirection
{
82 CharForward
, // Move a char to the right
83 CharBack
, // Move a char to the left
84 LineUp
, // Move a line up
85 LineDown
, // Move a line down
86 Home
, // Move to the beginning of the line
87 End
, // Move to the end of the line
88 PgUp
, // Move one page up
89 PgDn
, // Move one page down
90 CtrlPgUp
, // Move caret to the first visible char in the viewport
91 CtrlPgDn
, // Move caret to the last visible char in the viewport
92 CtrlHome
, // Move to the beginning of the document
93 CtrlEnd
, // Move to the end of the document
94 WordBack
, // Move to the beginning of the previous word (or beginning of line)
95 WordForward
, // Move to the beginning of the next word (or end of line)
96 SelectionStart
, // Move to the beginning of the current selection
97 SelectionEnd
, // Move to the end of the current selection
98 CharForwardNoWrap
, // Move a char forward, but don't wrap onto the next line
99 CharBackNoWrap
// Move a char backward, but don't wrap onto the previous line
102 // Being cloneable should allow for nice line and document copies...
103 internal class Line
: ICloneable
, IComparable
{
104 #region Local Variables
105 // Stuff that matters for our line
106 internal StringBuilder text
; // Characters for the line
107 internal float[] widths
; // Width of each character; always one larger than text.Length
108 internal int space
; // Number of elements in text and widths
109 internal int line_no
; // Line number
110 internal LineTag tags
; // Tags describing the text
111 internal int Y
; // Baseline
112 internal int height
; // Height of the line (height of tallest tag)
113 internal int ascent
; // Ascent of the line (ascent of the tallest tag)
114 internal HorizontalAlignment alignment
; // Alignment of the line
115 internal int align_shift
; // Pixel shift caused by the alignment
116 internal bool soft_break
; // Tag is 'broken soft' and continuation from previous line
117 internal int indent
; // Left indent for the first line
118 internal int hanging_indent
; // Hanging indent (left indent for all but the first line)
119 internal int right_indent
; // Right indent for all lines
120 internal bool carriage_return
;
123 // Stuff that's important for the tree
124 internal Line parent
; // Our parent line
125 internal Line left
; // Line with smaller line number
126 internal Line right
; // Line with higher line number
127 internal LineColor color
; // We're doing a black/red tree. this is the node color
128 internal int DEFAULT_TEXT_LEN
; //
129 internal static StringFormat string_format
; // For calculating widths/heights
130 internal bool recalc
; // Line changed
131 #endregion // Local Variables
135 color
= LineColor
.Red
;
142 alignment
= HorizontalAlignment
.Left
;
144 if (string_format
== null) {
145 string_format
= new StringFormat(StringFormat
.GenericTypographic
);
146 string_format
.Trimming
= StringTrimming
.None
;
147 string_format
.FormatFlags
= StringFormatFlags
.MeasureTrailingSpaces
;
151 internal Line(int LineNo
, string Text
, Font font
, Brush color
) : this() {
152 space
= Text
.Length
> DEFAULT_TEXT_LEN
? Text
.Length
+1 : DEFAULT_TEXT_LEN
;
154 text
= new StringBuilder(Text
, space
);
157 widths
= new float[space
+ 1];
158 tags
= new LineTag(this, 1, text
.Length
);
163 internal Line(int LineNo
, string Text
, HorizontalAlignment align
, Font font
, Brush color
) : this() {
164 space
= Text
.Length
> DEFAULT_TEXT_LEN
? Text
.Length
+1 : DEFAULT_TEXT_LEN
;
166 text
= new StringBuilder(Text
, space
);
170 widths
= new float[space
+ 1];
171 tags
= new LineTag(this, 1, text
.Length
);
176 internal Line(int LineNo
, string Text
, LineTag tag
) : this() {
177 space
= Text
.Length
> DEFAULT_TEXT_LEN
? Text
.Length
+1 : DEFAULT_TEXT_LEN
;
179 text
= new StringBuilder(Text
, space
);
182 widths
= new float[space
+ 1];
186 #endregion // Constructors
188 #region Internal Properties
189 internal int Indent
{
200 internal int HangingIndent
{
202 return hanging_indent
;
206 hanging_indent
= value;
211 internal int RightIndent
{
217 right_indent
= value;
223 internal int Height
{
233 internal int LineNo
{
243 internal string Text
{
245 return text
.ToString();
249 text
= new StringBuilder(value, value.Length
> DEFAULT_TEXT_LEN
? value.Length
: DEFAULT_TEXT_LEN
);
253 internal HorizontalAlignment Alignment
{
259 if (alignment
!= value) {
266 internal StringBuilder Text
{
276 #endregion // Internal Properties
278 #region Internal Methods
279 // Make sure we always have enoughs space in text and widths
280 internal void Grow(int minimum
) {
284 length
= text
.Length
;
286 if ((length
+ minimum
) > space
) {
287 // We need to grow; double the size
289 if ((length
+ minimum
) > (space
* 2)) {
290 new_widths
= new float[length
+ minimum
* 2 + 1];
291 space
= length
+ minimum
* 2;
293 new_widths
= new float[space
* 2 + 1];
296 widths
.CopyTo(new_widths
, 0);
302 internal void Streamline(int lines
) {
309 // Catch what the loop below wont; eliminate 0 length
310 // tags, but only if there are other tags after us
311 while ((current
.length
== 0) && (next
!= null)) {
313 tags
.previous
= null;
322 while (next
!= null) {
323 // Take out 0 length tags unless it's the last tag in the document
324 if (next
.length
== 0) {
325 if ((next
.next
!= null) || (line_no
!= lines
)) {
326 current
.next
= next
.next
;
327 if (current
.next
!= null) {
328 current
.next
.previous
= current
;
334 if (current
.Combine(next
)) {
339 current
= current
.next
;
344 /// <summary> Find the tag on a line based on the character position, pos is 0-based</summary>
345 internal LineTag
FindTag(int pos
) {
354 if (pos
>= text
.Length
) {
355 pos
= text
.Length
- 1;
358 while (tag
!= null) {
359 if (((tag
.start
- 1) <= pos
) && (pos
< (tag
.start
+ tag
.length
- 1))) {
360 return LineTag
.GetFinalTag (tag
);
368 /// Recalculate a single line using the same char for every character in the line
371 internal bool RecalculatePasswordLine(Graphics g
, Document doc
) {
380 len
= this.text
.Length
;
390 w
= g
.MeasureString(doc
.password_char
, tags
.font
, 10000, string_format
).Width
;
392 if (this.height
!= (int)tag
.font
.Height
) {
398 this.height
= (int)tag
.font
.Height
;
399 tag
.height
= this.height
;
401 XplatUI
.GetFontMetrics(g
, tag
.font
, out tag
.ascent
, out descent
);
402 this.ascent
= tag
.ascent
;
407 widths
[pos
] = widths
[pos
-1] + w
;
414 /// Go through all tags on a line and recalculate all size-related values;
415 /// returns true if lineheight changed
417 internal bool RecalculateLine(Graphics g
, Document doc
) {
431 len
= this.text
.Length
;
433 prev_height
= this.height
; // For drawing optimization calculations
434 this.height
= 0; // Reset line height
435 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
458 if (tag
.previous
!= null) {
459 tag
.X
= tag
.previous
.X
;
461 tag
.X
= (int)widths
[pos
];
470 if (Char
.IsWhiteSpace(text
[pos
])) {
472 wrap_width
= tag
.width
+ w
;
476 if ((wrap_pos
> 0) && (wrap_pos
!= len
) && (widths
[pos
] + w
) + 5 > (doc
.viewport_width
- this.right_indent
)) {
478 tag
.width
= wrap_width
;
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
486 tag
.width
= tag
.width
+ w
;
487 doc
.Split(this, tag
, pos
, this.soft_break
);
488 this.soft_break
= true;
489 len
= this.text
.Length
;
495 // Contract all soft lines that follow back into our line
501 widths
[pos
] = widths
[pos
-1] + w
;
504 line
= doc
.GetLine(this.line_no
+ 1);
505 if ((line
!= null) && soft_break
) {
506 // Pull the two lines together
507 doc
.Combine(this.line_no
, this.line_no
+ 1);
508 len
= this.text
.Length
;
514 if (pos
== (tag
.start
-1 + tag
.length
)) {
515 // We just found the end of our current tag
516 tag
.height
= (int)tag
.font
.Height
;
518 // Check if we're the tallest on the line (so far)
519 if (tag
.height
> this.height
) {
520 this.height
= tag
.height
; // Yep; make sure the line knows
523 if (tag
.ascent
== 0) {
526 XplatUI
.GetFontMetrics(g
, tag
.font
, out tag
.ascent
, out descent
);
529 if (tag
.ascent
> this.ascent
) {
532 // We have a tag that has a taller ascent than the line;
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
;
558 wrap_width
= tag
.width
;
563 if (this.height
== 0) {
564 this.height
= tags
.font
.Height
;
565 tag
.height
= this.height
;
568 if (prev_height
!= this.height
) {
573 #endregion // Internal Methods
575 #region Administrative
576 public int CompareTo(object obj
) {
581 if (! (obj
is Line
)) {
582 throw new ArgumentException("Object is not of type Line", "obj");
585 if (line_no
< ((Line
)obj
).line_no
) {
587 } else if (line_no
> ((Line
)obj
).line_no
) {
594 public object Clone() {
602 clone
.left
= (Line
)left
.Clone();
606 clone
.left
= (Line
)left
.Clone();
612 internal object CloneLine() {
622 public override bool Equals(object obj
) {
627 if (!(obj
is Line
)) {
635 if (line_no
== ((Line
)obj
).line_no
) {
642 public override int GetHashCode() {
643 return base.GetHashCode ();
646 public override string ToString() {
647 return "Line " + line_no
;
650 #endregion // Administrative
653 internal class Document
: ICloneable
, IEnumerable
{
655 // FIXME - go through code and check for places where
656 // we do explicit comparisons instead of using the compare overloads
657 internal struct Marker
{
659 internal LineTag tag
;
663 public static bool operator<(Marker lhs
, Marker rhs
) {
664 if (lhs
.line
.line_no
< rhs
.line
.line_no
) {
668 if (lhs
.line
.line_no
== rhs
.line
.line_no
) {
669 if (lhs
.pos
< rhs
.pos
) {
676 public static bool operator>(Marker lhs
, Marker rhs
) {
677 if (lhs
.line
.line_no
> rhs
.line
.line_no
) {
681 if (lhs
.line
.line_no
== rhs
.line
.line_no
) {
682 if (lhs
.pos
> rhs
.pos
) {
689 public static bool operator==(Marker lhs
, Marker rhs
) {
690 if ((lhs
.line
.line_no
== rhs
.line
.line_no
) && (lhs
.pos
== rhs
.pos
)) {
696 public static bool operator!=(Marker lhs
, Marker rhs
) {
697 if ((lhs
.line
.line_no
!= rhs
.line
.line_no
) || (lhs
.pos
!= rhs
.pos
)) {
703 public void Combine(Line move_to_line
, int move_to_line_length
) {
705 pos
+= move_to_line_length
;
706 tag
= LineTag
.FindTag(line
, pos
);
709 // This is for future use, right now Document.Split does it by hand, with some added shortcut logic
710 public void Split(Line move_to_line
, int split_at
) {
713 tag
= LineTag
.FindTag(line
, pos
);
716 public override bool Equals(object obj
) {
717 return this==(Marker
)obj
;
720 public override int GetHashCode() {
721 return base.GetHashCode ();
724 public override string ToString() {
725 return "Marker Line " + line
+ ", Position " + pos
;
729 #endregion Structures
731 #region Local Variables
732 private Line document
;
734 private Line sentinel
;
735 private int document_id
;
736 private Random random
= new Random();
737 internal string password_char
;
738 private StringBuilder password_cache
;
739 private bool calc_pass
;
740 private int char_count
;
742 private bool no_recalc
;
743 private bool recalc_pending
;
744 private int recalc_start
;
745 private int recalc_end
;
746 private bool recalc_optimize
;
748 internal bool multiline
;
751 internal UndoClass undo
;
753 internal Marker caret
;
754 internal Marker selection_start
;
755 internal Marker selection_end
;
756 internal bool selection_visible
;
757 internal Marker selection_anchor
;
758 internal Marker selection_prev
;
759 internal bool selection_end_anchor
;
761 internal int viewport_x
;
762 internal int viewport_y
; // The visible area of the document
763 internal int viewport_width
;
764 internal int viewport_height
;
766 internal int document_x
; // Width of the document
767 internal int document_y
; // Height of the document
769 internal Rectangle invalid
;
771 internal int crlf_size
; // 1 or 2, depending on whether we use \r\n or just \n
773 internal TextBoxBase owner
; // Who's owning us?
774 static internal int caret_width
= 1;
775 static internal int caret_shift
= 1;
776 #endregion // Local Variables
779 internal Document(TextBoxBase owner
) {
788 recalc_pending
= false;
790 // Tree related stuff
791 sentinel
= new Line();
792 sentinel
.color
= LineColor
.Black
;
796 // We always have a blank line
797 owner
.HandleCreated
+= new EventHandler(owner_HandleCreated
);
798 owner
.VisibleChanged
+= new EventHandler(owner_VisibleChanged
);
800 Add(1, "", owner
.Font
, ThemeEngine
.Current
.ResPool
.GetSolidBrush(owner
.ForeColor
));
801 Line l
= GetLine (1);
804 undo
= new UndoClass(this);
806 selection_visible
= false;
807 selection_start
.line
= this.document
;
808 selection_start
.pos
= 0;
809 selection_start
.tag
= selection_start
.line
.tags
;
810 selection_end
.line
= this.document
;
811 selection_end
.pos
= 0;
812 selection_end
.tag
= selection_end
.line
.tags
;
813 selection_anchor
.line
= this.document
;
814 selection_anchor
.pos
= 0;
815 selection_anchor
.tag
= selection_anchor
.line
.tags
;
816 caret
.line
= this.document
;
818 caret
.tag
= caret
.line
.tags
;
825 // Default selection is empty
827 document_id
= random
.Next();
831 #region Internal Properties
848 internal Line CaretLine
{
854 internal int CaretPosition
{
860 internal Point Caret
{
862 return new Point((int)caret
.tag
.line
.widths
[caret
.pos
] + caret
.line
.align_shift
, caret
.line
.Y
);
866 internal LineTag CaretTag
{
876 internal int CRLFSize
{
886 internal string PasswordChar
{
888 return password_char
;
892 password_char
= value;
893 if ((password_char
.Length
!= 0) && (password_char
[0] != '\0')) {
898 password_cache
= new StringBuilder(1024);
899 for (int i
= 0; i
< 1024; i
++) {
900 password_cache
.Append(ch
);
904 password_cache
= null;
909 internal int ViewPortX
{
919 internal int Length
{
921 return char_count
+ lines
- 1; // Add \n for each line but the last
925 private int CharCount
{
933 if (LengthChanged
!= null) {
934 LengthChanged(this, EventArgs
.Empty
);
939 ///<summary>Setting NoRecalc to true will prevent the document from being recalculated.
940 ///This ensures that coordinates of added text are predictable after adding the text even with wrapped view</summary>
941 internal bool NoRecalc
{
948 if (!no_recalc
&& recalc_pending
) {
949 RecalculateDocument(owner
.CreateGraphicsInternal(), recalc_start
, recalc_end
, recalc_optimize
);
950 recalc_pending
= false;
955 internal int ViewPortY
{
965 internal int ViewPortWidth
{
967 return viewport_width
;
971 viewport_width
= value;
975 internal int ViewPortHeight
{
977 return viewport_height
;
981 viewport_height
= value;
988 return this.document_x
;
992 internal int Height
{
994 return this.document_y
;
998 internal bool SelectionVisible
{
1000 return selection_visible
;
1004 internal bool Wrap
{
1014 #endregion // Internal Properties
1016 #region Private Methods
1018 internal int DumpTree(Line line
, bool with_tags
) {
1023 Console
.Write("Line {0} [# {1}], Y: {2}, soft: {3}, Text {4}",
1024 line
.line_no
, line
.GetHashCode(), line
.Y
, line
.soft_break
,
1025 line
.text
!= null ? line
.text
.ToString() : "undefined");
1027 if (line
.left
== sentinel
) {
1028 Console
.Write(", left = sentinel");
1029 } else if (line
.left
== null) {
1030 Console
.Write(", left = NULL");
1033 if (line
.right
== sentinel
) {
1034 Console
.Write(", right = sentinel");
1035 } else if (line
.right
== null) {
1036 Console
.Write(", right = NULL");
1039 Console
.WriteLine("");
1049 Console
.Write(" Tags: ");
1050 while (tag
!= null) {
1051 Console
.Write("{0} <{1}>-<{2}> ", count
++, tag
.start
, 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
)
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
;
1415 internal void PositionCaret(Line line
, int pos
) {
1416 if (owner
.IsHandleCreated
) {
1417 undo
.RecordCursor();
1420 caret
.tag
= line
.FindTag(pos
);
1423 caret
.height
= caret
.tag
.height
;
1425 if (owner
.IsHandleCreated
) {
1426 if (owner
.Focused
) {
1427 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
);
1430 if (CaretMoved
!= null) CaretMoved(this, EventArgs
.Empty
);
1436 internal void PositionCaret(int x
, int y
) {
1437 if (!owner
.IsHandleCreated
) {
1441 undo
.RecordCursor();
1443 caret
.tag
= FindCursor(x
, y
, out caret
.pos
);
1444 caret
.line
= caret
.tag
.line
;
1445 caret
.height
= caret
.tag
.height
;
1447 if (owner
.Focused
) {
1448 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
);
1451 if (CaretMoved
!= null) CaretMoved(this, EventArgs
.Empty
);
1454 internal void CaretHasFocus() {
1455 if ((caret
.tag
!= null) && owner
.IsHandleCreated
) {
1456 XplatUI
.CreateCaret(owner
.Handle
, caret_width
, caret
.height
);
1457 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
);
1463 internal void CaretLostFocus() {
1464 if (!owner
.IsHandleCreated
) {
1467 XplatUI
.DestroyCaret(owner
.Handle
);
1470 internal void AlignCaret() {
1471 if (!owner
.IsHandleCreated
) {
1475 undo
.RecordCursor();
1477 caret
.tag
= LineTag
.FindTag(caret
.line
, caret
.pos
);
1478 caret
.height
= caret
.tag
.height
;
1480 if (owner
.Focused
) {
1481 XplatUI
.CreateCaret(owner
.Handle
, caret_width
, caret
.height
);
1482 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
);
1486 if (CaretMoved
!= null) CaretMoved(this, EventArgs
.Empty
);
1489 internal void UpdateCaret() {
1490 if (!owner
.IsHandleCreated
|| caret
.tag
== null) {
1494 undo
.RecordCursor();
1496 if (caret
.tag
.height
!= caret
.height
) {
1497 caret
.height
= caret
.tag
.height
;
1498 if (owner
.Focused
) {
1499 XplatUI
.CreateCaret(owner
.Handle
, caret_width
, caret
.height
);
1503 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
);
1507 if (CaretMoved
!= null) CaretMoved(this, EventArgs
.Empty
);
1510 internal void DisplayCaret() {
1511 if (!owner
.IsHandleCreated
) {
1515 if (owner
.Focused
&& !selection_visible
) {
1516 XplatUI
.CaretVisible(owner
.Handle
, true);
1520 internal void HideCaret() {
1521 if (!owner
.IsHandleCreated
) {
1525 if (owner
.Focused
) {
1526 XplatUI
.CaretVisible(owner
.Handle
, false);
1530 internal void MoveCaret(CaretDirection direction
) {
1531 // FIXME should we use IsWordSeparator to detect whitespace, instead
1532 // of looking for actual spaces in the Word move cases?
1534 bool nowrap
= false;
1536 case CaretDirection
.CharForwardNoWrap
:
1538 goto case CaretDirection
.CharForward
;
1539 case CaretDirection
.CharForward
: {
1541 if (caret
.pos
> caret
.line
.text
.Length
) {
1542 if (multiline
&& !nowrap
) {
1543 // Go into next line
1544 if (caret
.line
.line_no
< this.lines
) {
1545 caret
.line
= GetLine(caret
.line
.line_no
+1);
1547 caret
.tag
= caret
.line
.tags
;
1552 // Single line; we stay where we are
1556 if ((caret
.tag
.start
- 1 + caret
.tag
.length
) < caret
.pos
) {
1557 caret
.tag
= caret
.tag
.next
;
1564 case CaretDirection
.CharBackNoWrap
:
1566 goto case CaretDirection
.CharBack
;
1567 case CaretDirection
.CharBack
: {
1568 if (caret
.pos
> 0) {
1569 // caret.pos--; // folded into the if below
1570 if (--caret
.pos
> 0) {
1571 if (caret
.tag
.start
> caret
.pos
) {
1572 caret
.tag
= caret
.tag
.previous
;
1576 if (caret
.line
.line_no
> 1 && !nowrap
) {
1577 caret
.line
= GetLine(caret
.line
.line_no
- 1);
1578 caret
.pos
= caret
.line
.text
.Length
;
1579 caret
.tag
= LineTag
.FindTag(caret
.line
, caret
.pos
);
1586 case CaretDirection
.WordForward
: {
1589 len
= caret
.line
.text
.Length
;
1590 if (caret
.pos
< len
) {
1591 while ((caret
.pos
< len
) && (caret
.line
.text
[caret
.pos
] != ' ')) {
1594 if (caret
.pos
< len
) {
1595 // Skip any whitespace
1596 while ((caret
.pos
< len
) && (caret
.line
.text
[caret
.pos
] == ' ')) {
1600 caret
.tag
= LineTag
.FindTag(caret
.line
, caret
.pos
);
1602 if (caret
.line
.line_no
< this.lines
) {
1603 caret
.line
= GetLine(caret
.line
.line_no
+ 1);
1605 caret
.tag
= caret
.line
.tags
;
1612 case CaretDirection
.WordBack
: {
1613 if (caret
.pos
> 0) {
1616 while ((caret
.pos
> 0) && (caret
.line
.text
[caret
.pos
] == ' ')) {
1620 while ((caret
.pos
> 0) && (caret
.line
.text
[caret
.pos
] != ' ')) {
1624 if (caret
.line
.text
.ToString(caret
.pos
, 1) == " ") {
1625 if (caret
.pos
!= 0) {
1628 caret
.line
= GetLine(caret
.line
.line_no
- 1);
1629 caret
.pos
= caret
.line
.text
.Length
;
1632 caret
.tag
= LineTag
.FindTag(caret
.line
, caret
.pos
);
1634 if (caret
.line
.line_no
> 1) {
1635 caret
.line
= GetLine(caret
.line
.line_no
- 1);
1636 caret
.pos
= caret
.line
.text
.Length
;
1637 caret
.tag
= LineTag
.FindTag(caret
.line
, caret
.pos
);
1644 case CaretDirection
.LineUp
: {
1645 if (caret
.line
.line_no
> 1) {
1648 pixel
= (int)caret
.line
.widths
[caret
.pos
];
1649 PositionCaret(pixel
, GetLine(caret
.line
.line_no
- 1).Y
);
1656 case CaretDirection
.LineDown
: {
1657 if (caret
.line
.line_no
< lines
) {
1660 pixel
= (int)caret
.line
.widths
[caret
.pos
];
1661 PositionCaret(pixel
, GetLine(caret
.line
.line_no
+ 1).Y
);
1668 case CaretDirection
.Home
: {
1669 if (caret
.pos
> 0) {
1671 caret
.tag
= caret
.line
.tags
;
1677 case CaretDirection
.End
: {
1678 if (caret
.pos
< caret
.line
.text
.Length
) {
1679 caret
.pos
= caret
.line
.text
.Length
;
1680 caret
.tag
= LineTag
.FindTag(caret
.line
, caret
.pos
);
1686 case CaretDirection
.PgUp
: {
1688 int new_y
, y_offset
;
1690 if (viewport_y
== 0) {
1692 // This should probably be handled elsewhere
1693 if (!(owner
is RichTextBox
)) {
1694 // Page down doesn't do anything in a regular TextBox
1695 // if the bottom of the document
1696 // is already visible, the page and the caret stay still
1700 // We're just placing the caret at the end of the document, no scrolling needed
1701 owner
.vscroll
.Value
= 0;
1702 Line line
= GetLine (1);
1703 PositionCaret (line
, 0);
1706 y_offset
= caret
.line
.Y
- viewport_y
;
1707 new_y
= caret
.line
.Y
- viewport_height
;
1709 owner
.vscroll
.Value
= Math
.Max (new_y
, 0);
1710 PositionCaret ((int)caret
.line
.widths
[caret
.pos
], y_offset
+ viewport_y
);
1714 case CaretDirection
.PgDn
: {
1715 int new_y
, y_offset
;
1717 if ((viewport_y
+ viewport_height
) > document_y
) {
1719 // This should probably be handled elsewhere
1720 if (!(owner
is RichTextBox
)) {
1721 // Page up doesn't do anything in a regular TextBox
1722 // if the bottom of the document
1723 // is already visible, the page and the caret stay still
1727 // We're just placing the caret at the end of the document, no scrolling needed
1728 owner
.vscroll
.Value
= owner
.vscroll
.Maximum
- viewport_height
+ 1;
1729 Line line
= GetLine (lines
);
1730 PositionCaret (line
, line
.Text
.Length
);
1733 y_offset
= caret
.line
.Y
- viewport_y
;
1734 new_y
= caret
.line
.Y
+ viewport_height
;
1736 owner
.vscroll
.Value
= Math
.Min (new_y
, owner
.vscroll
.Maximum
- viewport_height
+ 1);
1737 PositionCaret ((int)caret
.line
.widths
[caret
.pos
], y_offset
+ viewport_y
);
1742 case CaretDirection
.CtrlPgUp
: {
1743 PositionCaret(0, viewport_y
);
1748 case CaretDirection
.CtrlPgDn
: {
1753 tag
= FindTag(0, viewport_y
+ viewport_height
, out index
, false);
1754 if (tag
.line
.line_no
> 1) {
1755 line
= GetLine(tag
.line
.line_no
- 1);
1759 PositionCaret(line
, line
.Text
.Length
);
1764 case CaretDirection
.CtrlHome
: {
1765 caret
.line
= GetLine(1);
1767 caret
.tag
= caret
.line
.tags
;
1773 case CaretDirection
.CtrlEnd
: {
1774 caret
.line
= GetLine(lines
);
1775 caret
.pos
= caret
.line
.text
.Length
;
1776 caret
.tag
= LineTag
.FindTag(caret
.line
, caret
.pos
);
1782 case CaretDirection
.SelectionStart
: {
1783 caret
.line
= selection_start
.line
;
1784 caret
.pos
= selection_start
.pos
;
1785 caret
.tag
= selection_start
.tag
;
1791 case CaretDirection
.SelectionEnd
: {
1792 caret
.line
= selection_end
.line
;
1793 caret
.pos
= selection_end
.pos
;
1794 caret
.tag
= selection_end
.tag
;
1802 // Draw the document
1803 internal void Draw(Graphics g
, Rectangle clip
) {
1804 Line line
; // Current line being drawn
1805 LineTag tag
; // Current tag being drawn
1806 int start
; // First line to draw
1807 int end
; // Last line to draw
1808 StringBuilder text
; // String representing the current line
1814 // First, figure out from what line to what line we need to draw
1815 start
= GetLineByPixel(clip
.Top
+ viewport_y
, false).line_no
;
1816 end
= GetLineByPixel(clip
.Bottom
+ viewport_y
, false).line_no
;
1817 //Console.WriteLine("Starting drawing at line {0}, ending at line {1} (clip-bottom:{2})", start, end, clip.Bottom);
1819 // Now draw our elements; try to only draw those that are visible
1823 DateTime n
= DateTime
.Now
;
1824 Console
.WriteLine("Started drawing: {0}s {1}ms", n
.Second
, n
.Millisecond
);
1827 disabled
= ThemeEngine
.Current
.ResPool
.GetSolidBrush(ThemeEngine
.Current
.ColorGrayText
);
1828 hilight
= ThemeEngine
.Current
.ResPool
.GetSolidBrush(ThemeEngine
.Current
.ColorHighlight
);
1829 hilight_text
= ThemeEngine
.Current
.ResPool
.GetSolidBrush(ThemeEngine
.Current
.ColorHighlightText
);
1831 while (line_no
<= end
) {
1832 line
= GetLine(line_no
);
1834 if (owner
.backcolor_set
|| (owner
.Enabled
&& !owner
.read_only
)) {
1835 g
.FillRectangle(ThemeEngine
.Current
.ResPool
.GetSolidBrush(owner
.BackColor
), new Rectangle(clip
.Left
, line
.Y
- viewport_y
, clip
.Width
, line
.Y
- viewport_y
+ line
.Height
));
1837 g
.FillRectangle(ThemeEngine
.Current
.ResPool
.GetSolidBrush(ThemeEngine
.Current
.ColorControl
), new Rectangle(clip
.Left
, line
.Y
- viewport_y
, clip
.Width
, line
.Y
- viewport_y
+ line
.Height
));
1846 // This fails if there's a password > 1024 chars...
1847 text
= this.password_cache
;
1849 while (tag
!= null) {
1850 if (tag
.length
== 0) {
1855 if (((tag
.X
+ tag
.width
) > (clip
.Left
- viewport_x
)) || (tag
.X
< (clip
.Right
- viewport_x
))) {
1856 // Check for selection
1857 if ((!selection_visible
) || (!owner
.ShowSelection
) || (line_no
< selection_start
.line
.line_no
) || (line_no
> selection_end
.line
.line_no
)) {
1858 // regular drawing, no selection to deal with
1859 //g.DrawString(s.Substring(tag.start-1, tag.length), tag.font, tag.color, tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
1860 if (owner
.is_enabled
) {
1861 g
.DrawString(text
.ToString(tag
.start
-1, tag
.length
), tag
.font
, tag
.color
, tag
.X
+ line
.align_shift
- viewport_x
, line
.Y
+ tag
.shift
- viewport_y
, StringFormat
.GenericTypographic
);
1866 a
= ((SolidBrush
)tag
.color
).Color
;
1867 b
= ThemeEngine
.Current
.ColorWindowText
;
1869 if ((a
.R
== b
.R
) && (a
.G
== b
.G
) && (a
.B
== b
.B
)) {
1870 g
.DrawString(text
.ToString(tag
.start
-1, tag
.length
), tag
.font
, disabled
, tag
.X
+ line
.align_shift
- viewport_x
, line
.Y
+ tag
.shift
- viewport_y
, StringFormat
.GenericTypographic
);
1872 g
.DrawString(text
.ToString(tag
.start
-1, tag
.length
), tag
.font
, tag
.color
, tag
.X
+ line
.align_shift
- viewport_x
, line
.Y
+ tag
.shift
- viewport_y
, StringFormat
.GenericTypographic
);
1876 // we might have to draw our selection
1877 if ((line_no
!= selection_start
.line
.line_no
) && (line_no
!= selection_end
.line
.line_no
)) {
1878 // Special case, whole line is selected, draw this tag selected
1881 tag
.X
+ line
.align_shift
- viewport_x
, // X
1882 line
.Y
+ tag
.shift
- viewport_y
, // Y
1883 line
.widths
[tag
.start
+ tag
.length
- 1], // width
1884 tag
.height
// Height
1888 //s.Substring(tag.start-1, tag.length), // String
1889 text
.ToString(tag
.start
-1, tag
.length
), // String
1891 hilight_text
, // Brush
1892 tag
.X
+ line
.align_shift
- viewport_x
, // X
1893 line
.Y
+ tag
.shift
- viewport_y
, // Y
1894 StringFormat
.GenericTypographic
);
1902 // One or more, but not all tags on the line are selected
1903 if ((selection_start
.tag
== tag
) && (selection_end
.tag
== tag
)) {
1904 // Single tag selected, draw "normalSELECTEDnormal"
1906 // First, the regular part
1908 //s.Substring(tag.start - 1, selection_start.pos - tag.start + 1), // String
1909 text
.ToString(tag
.start
- 1, selection_start
.pos
- tag
.start
+ 1), // String
1912 tag
.X
+ line
.align_shift
- viewport_x
, // X
1913 line
.Y
+ tag
.shift
- viewport_y
, // Y
1914 StringFormat
.GenericTypographic
);
1916 // Now the highlight
1919 line
.widths
[selection_start
.pos
] + line
.align_shift
- viewport_x
, // X
1920 line
.Y
+ tag
.shift
- viewport_y
, // Y
1921 line
.widths
[selection_end
.pos
] - line
.widths
[selection_start
.pos
], // Width
1922 tag
.height
); // Height
1925 //s.Substring(selection_start.pos, selection_end.pos - selection_start.pos), // String
1926 text
.ToString(selection_start
.pos
, selection_end
.pos
- selection_start
.pos
), // String
1928 hilight_text
, // Brush
1929 line
.widths
[selection_start
.pos
] + line
.align_shift
- viewport_x
, // X
1930 line
.Y
+ tag
.shift
- viewport_y
, // Y
1931 StringFormat
.GenericTypographic
);
1933 // And back to the regular
1935 //s.Substring(selection_end.pos, tag.start + tag.length - selection_end.pos - 1), // String
1936 text
.ToString(selection_end
.pos
, tag
.start
+ tag
.length
- selection_end
.pos
- 1), // String
1939 line
.widths
[selection_end
.pos
] + line
.align_shift
- viewport_x
, // X
1940 line
.Y
+ tag
.shift
- viewport_y
, // Y
1941 StringFormat
.GenericTypographic
);
1943 } else if (selection_start
.tag
== tag
) {
1946 // The highlighted part
1949 line
.widths
[selection_start
.pos
] + line
.align_shift
- viewport_x
,
1950 line
.Y
+ tag
.shift
- viewport_y
,
1951 line
.widths
[tag
.start
+ tag
.length
- 1] - line
.widths
[selection_start
.pos
],
1955 //s.Substring(selection_start.pos, tag.start + tag.length - selection_start.pos - 1), // String
1956 text
.ToString(selection_start
.pos
, tag
.start
+ tag
.length
- selection_start
.pos
- 1), // String
1958 hilight_text
, // Brush
1959 line
.widths
[selection_start
.pos
] + line
.align_shift
- viewport_x
, // X
1960 line
.Y
+ tag
.shift
- viewport_y
, // Y
1961 StringFormat
.GenericTypographic
);
1965 //s.Substring(tag.start - 1, selection_start.pos - tag.start + 1), // String
1966 text
.ToString(tag
.start
- 1, selection_start
.pos
- tag
.start
+ 1), // String
1969 tag
.X
+ line
.align_shift
- viewport_x
, // X
1970 line
.Y
+ tag
.shift
- viewport_y
, // Y
1971 StringFormat
.GenericTypographic
);
1972 } else if (selection_end
.tag
== tag
) {
1975 // The highlighted part
1978 tag
.X
+ line
.align_shift
- viewport_x
,
1979 line
.Y
+ tag
.shift
- viewport_y
,
1980 line
.widths
[selection_end
.pos
] - line
.widths
[tag
.start
- 1],
1984 //s.Substring(tag.start - 1, selection_end.pos - tag.start + 1), // String
1985 text
.ToString(tag
.start
- 1, selection_end
.pos
- tag
.start
+ 1), // String
1987 hilight_text
, // Brush
1988 tag
.X
+ line
.align_shift
- viewport_x
, // X
1989 line
.Y
+ tag
.shift
- viewport_y
, // Y
1990 StringFormat
.GenericTypographic
);
1994 //s.Substring(selection_end.pos, tag.start + tag.length - selection_end.pos - 1), // String
1995 text
.ToString(selection_end
.pos
, tag
.start
+ tag
.length
- selection_end
.pos
- 1), // String
1998 line
.widths
[selection_end
.pos
] + line
.align_shift
- viewport_x
, // X
1999 line
.Y
+ tag
.shift
- viewport_y
, // Y
2000 StringFormat
.GenericTypographic
);
2002 // no partially selected tags here, simple checks...
2003 if (selection_start
.line
== line
) {
2007 begin
= tag
.start
- 1;
2008 stop
= tag
.start
+ tag
.length
- 1;
2009 if (selection_end
.line
== line
) {
2010 if ((begin
>= selection_start
.pos
) && (stop
< selection_end
.pos
)) {
2014 if (stop
> selection_start
.pos
) {
2018 } else if (selection_end
.line
== line
) {
2019 if ((tag
.start
- 1) < selection_end
.pos
) {
2029 tag
.X
+ line
.align_shift
- viewport_x
,
2030 line
.Y
+ tag
.shift
- viewport_y
,
2031 line
.widths
[tag
.start
+ tag
.length
- 1] - line
.widths
[tag
.start
- 1],
2035 //s.Substring(tag.start-1, tag.length), // String
2036 text
.ToString(tag
.start
-1, tag
.length
), // String
2038 hilight_text
, // Brush
2039 tag
.X
+ line
.align_shift
- viewport_x
, // X
2040 line
.Y
+ tag
.shift
- viewport_y
, // Y
2041 StringFormat
.GenericTypographic
);
2044 //s.Substring(tag.start-1, tag.length), // String
2045 text
.ToString(tag
.start
-1, tag
.length
), // String
2048 tag
.X
+ line
.align_shift
- viewport_x
, // X
2049 line
.Y
+ tag
.shift
- viewport_y
, // Y
2050 StringFormat
.GenericTypographic
);
2065 Console
.WriteLine("Finished drawing: {0}s {1}ms", n
.Second
, n
.Millisecond
);
2070 internal void Insert(Line line
, int pos
, string s
) {
2071 Insert(line
, null, pos
, false, s
);
2074 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
2075 internal void Insert(Line line
, LineTag tag
, int pos
, bool update_caret
, string s
) {
2081 bool carriage_return
= false;
2085 // The formatting at the insertion point is used for the inserted text
2087 tag
= LineTag
.FindTag(line
, pos
);
2090 base_line
= line
.line_no
;
2092 ins
= s
.Split(new char[] {'\n'}
);
2094 insert_lines
= ins
.Length
;
2095 old_line_count
= lines
;
2097 // Bump the text at insertion point a line down if we're inserting more than one line
2098 if (insert_lines
> 1) {
2100 line
.soft_break
= false;
2101 // Remainder of start line is now in base_line + 1
2104 if (ins
[0].EndsWith ("\r")) {
2105 ins
[0] = ins
[0].Substring (0, ins
[0].Length
- 1);
2106 carriage_return
= true;
2109 // Insert the first line
2110 InsertString(tag
, pos
, ins
[0]);
2112 if (carriage_return
) {
2113 Line l
= GetLine (base_line
);
2114 l
.carriage_return
= true;
2117 if (insert_lines
> 1) {
2118 for (i
= 1; i
< insert_lines
; i
++) {
2119 carriage_return
= false;
2120 if (ins
[i
].EndsWith ("\r")) {
2121 ins
[i
] = ins
[i
].Substring (0, ins
[i
].Length
- 1);
2122 carriage_return
= true;
2124 Add(base_line
+ i
, ins
[i
], line
.alignment
, tag
.font
, tag
.color
);
2125 if (carriage_return
) {
2126 Line l
= GetLine (base_line
+ i
);
2127 l
.carriage_return
= true;
2130 if (!s
.EndsWith("\n\n")) {
2131 this.Combine(base_line
+ (lines
- old_line_count
) - 1, base_line
+ lines
- old_line_count
);
2136 UpdateView(line
, lines
- old_line_count
+ 1, pos
);
2139 // Move caret to the end of the inserted text
2140 if (insert_lines
> 1) {
2141 Line l
= GetLine (line
.line_no
+ lines
- old_line_count
);
2142 PositionCaret(l
, l
.text
.Length
);
2144 PositionCaret(line
, pos
+ ins
[0].Length
);
2150 // Inserts a character at the given position
2151 internal void InsertString(Line line
, int pos
, string s
) {
2152 InsertString(line
.FindTag(pos
), pos
, s
);
2155 // Inserts a string at the given position
2156 internal void InsertString(LineTag tag
, int pos
, string s
) {
2165 line
.text
.Insert(pos
, s
);
2168 // TODO: sometimes getting a null tag here when pasting ???
2170 while (tag
!= null) {
2177 UpdateView(line
, pos
);
2180 // Inserts a string at the caret position
2181 internal void InsertStringAtCaret(string s
, bool move_caret
) {
2189 caret
.line
.text
.Insert(caret
.pos
, s
);
2190 caret
.tag
.length
+= len
;
2192 if (caret
.tag
.next
!= null) {
2193 tag
= caret
.tag
.next
;
2194 while (tag
!= null) {
2199 caret
.line
.Grow(len
);
2200 caret
.line
.recalc
= true;
2202 UpdateView(caret
.line
, caret
.pos
);
2211 // Inserts a character at the given position
2212 internal void InsertChar(Line line
, int pos
, char ch
) {
2213 InsertChar(line
.FindTag(pos
), pos
, ch
);
2216 // Inserts a character at the given position
2217 internal void InsertChar(LineTag tag
, int pos
, char ch
) {
2223 line
.text
.Insert(pos
, ch
);
2227 while (tag
!= null) {
2234 UpdateView(line
, pos
);
2237 // Inserts a character at the current caret position
2238 internal void InsertCharAtCaret(char ch
, bool move_caret
) {
2243 caret
.line
.text
.Insert(caret
.pos
, ch
);
2246 if (caret
.tag
.next
!= null) {
2247 tag
= caret
.tag
.next
;
2248 while (tag
!= null) {
2254 caret
.line
.recalc
= true;
2256 UpdateView(caret
.line
, caret
.pos
);
2260 SetSelectionToCaret(true);
2264 internal void DeleteMultiline (Line start_line
, int pos
, int length
)
2266 Marker start
= new Marker ();
2267 Marker end
= new Marker ();
2268 int start_index
= LineTagToCharIndex (start_line
, pos
);
2270 start
.line
= start_line
;
2272 start
.tag
= LineTag
.FindTag (start_line
, pos
);
2274 CharIndexToLineTag (start_index
+ length
, out end
.line
,
2275 out end
.tag
, out end
.pos
);
2277 if (start
.line
== end
.line
) {
2278 DeleteChars (start
.tag
, pos
, end
.pos
- pos
);
2281 // Delete first and last lines
2282 DeleteChars (start
.tag
, start
.pos
, start
.line
.text
.Length
- start
.pos
);
2283 DeleteChars (end
.line
.tags
, 0, end
.pos
);
2285 int current
= start
.line
.line_no
+ 1;
2286 if (current
< end
.line
.line_no
) {
2287 for (int i
= end
.line
.line_no
- 1; i
>= current
; i
--) {
2292 // BIG FAT WARNING - selection_end.line might be stale due
2293 // to the above Delete() call. DONT USE IT before hitting the end of this method!
2295 // Join start and end
2296 Combine (start
.line
.line_no
, current
);
2301 // Deletes n characters at the given position; it will not delete past line limits
2303 internal void DeleteChars(LineTag tag
, int pos
, int count
) {
2312 if (pos
== line
.text
.Length
) {
2316 line
.text
.Remove(pos
, count
);
2318 // Make sure the tag points to the right spot
2319 while ((tag
!= null) && (tag
.start
+ tag
.length
- 1) <= pos
) {
2327 // Check if we're crossing tag boundaries
2328 if ((pos
+ count
) > (tag
.start
+ tag
.length
- 1)) {
2331 // We have to delete cross tag boundaries
2335 left
-= tag
.start
+ tag
.length
- pos
- 1;
2336 tag
.length
-= tag
.start
+ tag
.length
- pos
- 1;
2339 while ((tag
!= null) && (left
> 0)) {
2340 tag
.start
-= count
- left
;
2341 if (tag
.length
> left
) {
2352 // We got off easy, same tag
2354 tag
.length
-= count
;
2356 if (tag
.length
== 0) {
2361 // Delete empty orphaned tags at the end
2363 while (walk
!= null && walk
.next
!= null && walk
.next
.length
== 0) {
2365 walk
.next
= walk
.next
.next
;
2366 if (walk
.next
!= null)
2367 walk
.next
.previous
= t
;
2371 // Adjust the start point of any tags following
2374 while (tag
!= null) {
2382 line
.Streamline(lines
);
2385 UpdateView(line
, pos
);
2388 // Deletes a character at or after the given position (depending on forward); it will not delete past line limits
2389 internal void DeleteChar(LineTag tag
, int pos
, bool forward
) {
2398 if ((pos
== 0 && forward
== false) || (pos
== line
.text
.Length
&& forward
== true)) {
2404 line
.text
.Remove(pos
, 1);
2406 while ((tag
!= null) && (tag
.start
+ tag
.length
- 1) <= pos
) {
2416 if (tag
.length
== 0) {
2421 line
.text
.Remove(pos
, 1);
2422 if (pos
>= (tag
.start
- 1)) {
2424 if (tag
.length
== 0) {
2427 } else if (tag
.previous
!= null) {
2428 tag
.previous
.length
--;
2429 if (tag
.previous
.length
== 0) {
2435 // Delete empty orphaned tags at the end
2437 while (walk
!= null && walk
.next
!= null && walk
.next
.length
== 0) {
2439 walk
.next
= walk
.next
.next
;
2440 if (walk
.next
!= null)
2441 walk
.next
.previous
= t
;
2446 while (tag
!= null) {
2452 line
.Streamline(lines
);
2455 UpdateView(line
, pos
);
2458 // Combine two lines
2459 internal void Combine(int FirstLine
, int SecondLine
) {
2460 Combine(GetLine(FirstLine
), GetLine(SecondLine
));
2463 internal void Combine(Line first
, Line second
) {
2467 // Combine the two tag chains into one
2470 // Maintain the line ending style
2471 first
.soft_break
= second
.soft_break
;
2473 while (last
.next
!= null) {
2477 last
.next
= second
.tags
;
2478 last
.next
.previous
= last
;
2480 shift
= last
.start
+ last
.length
- 1;
2482 // Fix up references within the chain
2484 while (last
!= null) {
2486 last
.start
+= shift
;
2490 // Combine both lines' strings
2491 first
.text
.Insert(first
.text
.Length
, second
.text
.ToString());
2492 first
.Grow(first
.text
.Length
);
2494 // Remove the reference to our (now combined) tags from the doomed line
2498 DecrementLines(first
.line_no
+ 2); // first.line_no + 1 will be deleted, so we need to start renumbering one later
2501 first
.recalc
= true;
2502 first
.height
= 0; // This forces RecalcDocument/UpdateView to redraw from this line on
2503 first
.Streamline(lines
);
2505 // Update Caret, Selection, etc
2506 if (caret
.line
== second
) {
2507 caret
.Combine(first
, shift
);
2509 if (selection_anchor
.line
== second
) {
2510 selection_anchor
.Combine(first
, shift
);
2512 if (selection_start
.line
== second
) {
2513 selection_start
.Combine(first
, shift
);
2515 if (selection_end
.line
== second
) {
2516 selection_end
.Combine(first
, shift
);
2523 check_first
= GetLine(first
.line_no
);
2524 check_second
= GetLine(check_first
.line_no
+ 1);
2526 Console
.WriteLine("Pre-delete: Y of first line: {0}, second line: {1}", check_first
.Y
, check_second
.Y
);
2529 this.Delete(second
);
2532 check_first
= GetLine(first
.line_no
);
2533 check_second
= GetLine(check_first
.line_no
+ 1);
2535 Console
.WriteLine("Post-delete Y of first line: {0}, second line: {1}", check_first
.Y
, check_second
.Y
);
2539 // Split the line at the position into two
2540 internal void Split(int LineNo
, int pos
) {
2544 line
= GetLine(LineNo
);
2545 tag
= LineTag
.FindTag(line
, pos
);
2546 Split(line
, tag
, pos
, false);
2549 internal void Split(Line line
, int pos
) {
2552 tag
= LineTag
.FindTag(line
, pos
);
2553 Split(line
, tag
, pos
, false);
2556 ///<summary>Split line at given tag and position into two lines</summary>
2557 ///<param name="soft">True if the split should be marked as 'soft', indicating that it can be contracted
2558 ///if more space becomes available on previous line</param>
2559 internal void Split(Line line
, LineTag tag
, int pos
, bool soft
) {
2563 bool move_sel_start
;
2567 move_sel_start
= false;
2568 move_sel_end
= false;
2570 // Adjust selection and cursors
2571 if (soft
&& (caret
.line
== line
) && (caret
.pos
>= pos
)) {
2574 if (selection_start
.line
== line
&& selection_start
.pos
> pos
) {
2575 move_sel_start
= true;
2578 if (selection_end
.line
== line
&& selection_end
.pos
> pos
) {
2579 move_sel_end
= true;
2582 // cover the easy case first
2583 if (pos
== line
.text
.Length
) {
2584 Add(line
.line_no
+ 1, "", line
.alignment
, tag
.font
, tag
.color
);
2586 new_line
= GetLine(line
.line_no
+ 1);
2588 line
.carriage_return
= false;
2589 new_line
.carriage_return
= line
.carriage_return
;
2593 caret
.line
= new_line
;
2594 caret
.line
.soft_break
= true;
2595 caret
.tag
= new_line
.tags
;
2598 new_line
.soft_break
= true;
2602 if (move_sel_start
) {
2603 selection_start
.line
= new_line
;
2604 selection_start
.pos
= 0;
2605 selection_start
.tag
= new_line
.tags
;
2609 selection_end
.line
= new_line
;
2610 selection_end
.pos
= 0;
2611 selection_end
.tag
= new_line
.tags
;
2616 // We need to move the rest of the text into the new line
2617 Add(line
.line_no
+ 1, line
.text
.ToString(pos
, line
.text
.Length
- pos
), line
.alignment
, tag
.font
, tag
.color
);
2619 // Now transfer our tags from this line to the next
2620 new_line
= GetLine(line
.line_no
+ 1);
2622 line
.carriage_return
= false;
2623 new_line
.carriage_return
= line
.carriage_return
;
2626 new_line
.recalc
= true;
2628 if ((tag
.start
- 1) == pos
) {
2631 // We can simply break the chain and move the tag into the next line
2632 if (tag
== line
.tags
) {
2633 new_tag
= new LineTag(line
, 1, 0);
2634 new_tag
.font
= tag
.font
;
2635 new_tag
.color
= tag
.color
;
2636 line
.tags
= new_tag
;
2639 if (tag
.previous
!= null) {
2640 tag
.previous
.next
= null;
2642 new_line
.tags
= tag
;
2643 tag
.previous
= null;
2644 tag
.line
= new_line
;
2646 // Walk the list and correct the start location of the tags we just bumped into the next line
2647 shift
= tag
.start
- 1;
2650 while (new_tag
!= null) {
2651 new_tag
.start
-= shift
;
2652 new_tag
.line
= new_line
;
2653 new_tag
= new_tag
.next
;
2658 new_tag
= new LineTag(new_line
, 1, tag
.start
- 1 + tag
.length
- pos
);
2659 new_tag
.next
= tag
.next
;
2660 new_tag
.font
= tag
.font
;
2661 new_tag
.color
= tag
.color
;
2662 new_line
.tags
= new_tag
;
2663 if (new_tag
.next
!= null) {
2664 new_tag
.next
.previous
= new_tag
;
2667 tag
.length
= pos
- tag
.start
+ 1;
2670 new_tag
= new_tag
.next
;
2671 while (new_tag
!= null) {
2672 new_tag
.start
-= shift
;
2673 new_tag
.line
= new_line
;
2674 new_tag
= new_tag
.next
;
2681 caret
.line
= new_line
;
2682 caret
.pos
= caret
.pos
- pos
;
2683 caret
.tag
= caret
.line
.FindTag(caret
.pos
);
2685 new_line
.soft_break
= true;
2688 if (move_sel_start
) {
2689 selection_start
.line
= new_line
;
2690 selection_start
.pos
= selection_start
.pos
- pos
;
2691 selection_start
.tag
= new_line
.FindTag(selection_start
.pos
);
2695 selection_end
.line
= new_line
;
2696 selection_end
.pos
= selection_end
.pos
- pos
;
2697 selection_end
.tag
= new_line
.FindTag(selection_end
.pos
);
2700 CharCount
-= line
.text
.Length
- pos
;
2701 line
.text
.Remove(pos
, line
.text
.Length
- pos
);
2704 // Adds a line of text, with given font.
2705 // Bumps any line at that line number that already exists down
2706 internal void Add(int LineNo
, string Text
, Font font
, Brush color
) {
2707 Add(LineNo
, Text
, HorizontalAlignment
.Left
, font
, color
);
2710 internal void Add(int LineNo
, string Text
, HorizontalAlignment align
, Font font
, Brush color
) {
2715 CharCount
+= Text
.Length
;
2717 if (LineNo
<1 || Text
== null) {
2719 throw new ArgumentNullException("LineNo", "Line numbers must be positive");
2721 throw new ArgumentNullException("Text", "Cannot insert NULL line");
2725 add = new Line(LineNo
, Text
, align
, font
, color
);
2728 while (line
!= sentinel
) {
2730 line_no
= line
.line_no
;
2732 if (LineNo
> line_no
) {
2734 } else if (LineNo
< line_no
) {
2737 // Bump existing line numbers; walk all nodes to the right of this one and increment line_no
2738 IncrementLines(line
.line_no
);
2743 add.left
= sentinel
;
2744 add.right
= sentinel
;
2746 if (add.parent
!= null) {
2747 if (LineNo
> add.parent
.line_no
) {
2748 add.parent
.right
= add;
2750 add.parent
.left
= add;
2757 RebalanceAfterAdd(add);
2762 internal virtual void Clear() {
2765 document
= sentinel
;
2768 public virtual object Clone() {
2771 clone
= new Document(null);
2773 clone
.lines
= this.lines
;
2774 clone
.document
= (Line
)document
.Clone();
2779 internal void Delete(int LineNo
) {
2786 line
= GetLine(LineNo
);
2788 CharCount
-= line
.text
.Length
;
2790 DecrementLines(LineNo
+ 1);
2794 internal void Delete(Line line1
) {
2795 Line line2
;// = new Line();
2798 if ((line1
.left
== sentinel
) || (line1
.right
== sentinel
)) {
2801 line3
= line1
.right
;
2802 while (line3
.left
!= sentinel
) {
2807 if (line3
.left
!= sentinel
) {
2810 line2
= line3
.right
;
2813 line2
.parent
= line3
.parent
;
2814 if (line3
.parent
!= null) {
2815 if(line3
== line3
.parent
.left
) {
2816 line3
.parent
.left
= line2
;
2818 line3
.parent
.right
= line2
;
2824 if (line3
!= line1
) {
2827 if (selection_start
.line
== line3
) {
2828 selection_start
.line
= line1
;
2831 if (selection_end
.line
== line3
) {
2832 selection_end
.line
= line1
;
2835 if (selection_anchor
.line
== line3
) {
2836 selection_anchor
.line
= line1
;
2839 if (caret
.line
== line3
) {
2844 line1
.alignment
= line3
.alignment
;
2845 line1
.ascent
= line3
.ascent
;
2846 line1
.hanging_indent
= line3
.hanging_indent
;
2847 line1
.height
= line3
.height
;
2848 line1
.indent
= line3
.indent
;
2849 line1
.line_no
= line3
.line_no
;
2850 line1
.recalc
= line3
.recalc
;
2851 line1
.right_indent
= line3
.right_indent
;
2852 line1
.soft_break
= line3
.soft_break
;
2853 line1
.space
= line3
.space
;
2854 line1
.tags
= line3
.tags
;
2855 line1
.text
= line3
.text
;
2856 line1
.widths
= line3
.widths
;
2860 while (tag
!= null) {
2866 if (line3
.color
== LineColor
.Black
)
2867 RebalanceAfterDelete(line2
);
2872 // Invalidate a section of the document to trigger redraw
2873 internal void Invalidate(Line start
, int start_pos
, Line end
, int end_pos
) {
2879 if ((start
== end
) && (start_pos
== end_pos
)) {
2883 if (end_pos
== -1) {
2884 end_pos
= end
.text
.Length
;
2887 // figure out what's before what so the logic below is straightforward
2888 if (start
.line_no
< end
.line_no
) {
2894 } else if (start
.line_no
> end
.line_no
) {
2901 if (start_pos
< end_pos
) {
2916 Console
.WriteLine("Invaliding from {0}:{1} to {2}:{3}", l1
.line_no
, p1
, l2
.line_no
, p2
);
2921 (int)l1
.widths
[p1
] + l1
.align_shift
- viewport_x
,
2923 (int)l2
.widths
[p2
] - (int)l1
.widths
[p1
] + 1,
2931 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
);
2934 // Three invalidates:
2935 // First line from start
2936 owner
.Invalidate(new Rectangle((int)l1
.widths
[p1
] + l1
.align_shift
- viewport_x
, l1
.Y
- viewport_y
, viewport_width
, l1
.height
));
2939 if ((l1
.line_no
+ 1) < l2
.line_no
) {
2942 y
= GetLine(l1
.line_no
+ 1).Y
;
2943 owner
.Invalidate(new Rectangle(0, y
- viewport_y
, viewport_width
, GetLine(l2
.line_no
).Y
- y
- viewport_y
));
2946 Console
.WriteLine("Invaliding from {0}:{1} to {2}:{3} Middle => x={4}, y={5}, {6}x{7}", l1
.line_no
, p1
, l2
.line_no
, p2
, 0, y
- viewport_y
, viewport_width
, GetLine(l2
.line_no
).Y
- y
- viewport_y
);
2951 owner
.Invalidate(new Rectangle((int)l2
.widths
[0] + l2
.align_shift
- viewport_x
, l2
.Y
- viewport_y
, (int)l2
.widths
[p2
] + 1, l2
.height
));
2953 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
);
2957 /// <summary>Select text around caret</summary>
2958 internal void ExpandSelection(CaretSelection mode
, bool to_caret
) {
2960 // We're expanding the selection to the caret position
2962 case CaretSelection
.Line
: {
2963 // Invalidate the selection delta
2964 if (caret
> selection_prev
) {
2965 Invalidate(selection_prev
.line
, 0, caret
.line
, caret
.line
.text
.Length
);
2967 Invalidate(selection_prev
.line
, selection_prev
.line
.text
.Length
, caret
.line
, 0);
2970 if (caret
.line
.line_no
<= selection_anchor
.line
.line_no
) {
2971 selection_start
.line
= caret
.line
;
2972 selection_start
.tag
= caret
.line
.tags
;
2973 selection_start
.pos
= 0;
2975 selection_end
.line
= selection_anchor
.line
;
2976 selection_end
.tag
= selection_anchor
.tag
;
2977 selection_end
.pos
= selection_anchor
.pos
;
2979 selection_end_anchor
= true;
2981 selection_start
.line
= selection_anchor
.line
;
2982 selection_start
.pos
= selection_anchor
.height
;
2983 selection_start
.tag
= selection_anchor
.line
.FindTag(selection_anchor
.height
);
2985 selection_end
.line
= caret
.line
;
2986 selection_end
.tag
= caret
.line
.tags
;
2987 selection_end
.pos
= caret
.line
.text
.Length
;
2989 selection_end_anchor
= false;
2991 selection_prev
.line
= caret
.line
;
2992 selection_prev
.tag
= caret
.tag
;
2993 selection_prev
.pos
= caret
.pos
;
2998 case CaretSelection
.Word
: {
3002 start_pos
= FindWordSeparator(caret
.line
, caret
.pos
, false);
3003 end_pos
= FindWordSeparator(caret
.line
, caret
.pos
, true);
3006 // Invalidate the selection delta
3007 if (caret
> selection_prev
) {
3008 Invalidate(selection_prev
.line
, selection_prev
.pos
, caret
.line
, end_pos
);
3010 Invalidate(selection_prev
.line
, selection_prev
.pos
, caret
.line
, start_pos
);
3012 if (caret
< selection_anchor
) {
3013 selection_start
.line
= caret
.line
;
3014 selection_start
.tag
= caret
.line
.FindTag(start_pos
);
3015 selection_start
.pos
= start_pos
;
3017 selection_end
.line
= selection_anchor
.line
;
3018 selection_end
.tag
= selection_anchor
.tag
;
3019 selection_end
.pos
= selection_anchor
.pos
;
3021 selection_prev
.line
= caret
.line
;
3022 selection_prev
.tag
= caret
.tag
;
3023 selection_prev
.pos
= start_pos
;
3025 selection_end_anchor
= true;
3027 selection_start
.line
= selection_anchor
.line
;
3028 selection_start
.pos
= selection_anchor
.height
;
3029 selection_start
.tag
= selection_anchor
.line
.FindTag(selection_anchor
.height
);
3031 selection_end
.line
= caret
.line
;
3032 selection_end
.tag
= caret
.line
.FindTag(end_pos
);
3033 selection_end
.pos
= end_pos
;
3035 selection_prev
.line
= caret
.line
;
3036 selection_prev
.tag
= caret
.tag
;
3037 selection_prev
.pos
= end_pos
;
3039 selection_end_anchor
= false;
3044 case CaretSelection
.Position
: {
3045 SetSelectionToCaret(false);
3050 // We're setting the selection 'around' the caret position
3052 case CaretSelection
.Line
: {
3053 this.Invalidate(caret
.line
, 0, caret
.line
, caret
.line
.text
.Length
);
3055 selection_start
.line
= caret
.line
;
3056 selection_start
.tag
= caret
.line
.tags
;
3057 selection_start
.pos
= 0;
3059 selection_end
.line
= caret
.line
;
3060 selection_end
.pos
= caret
.line
.text
.Length
;
3061 selection_end
.tag
= caret
.line
.FindTag(selection_end
.pos
);
3063 selection_anchor
.line
= selection_end
.line
;
3064 selection_anchor
.tag
= selection_end
.tag
;
3065 selection_anchor
.pos
= selection_end
.pos
;
3066 selection_anchor
.height
= 0;
3068 selection_prev
.line
= caret
.line
;
3069 selection_prev
.tag
= caret
.tag
;
3070 selection_prev
.pos
= caret
.pos
;
3072 this.selection_end_anchor
= true;
3077 case CaretSelection
.Word
: {
3081 start_pos
= FindWordSeparator(caret
.line
, caret
.pos
, false);
3082 end_pos
= FindWordSeparator(caret
.line
, caret
.pos
, true);
3084 this.Invalidate(selection_start
.line
, start_pos
, caret
.line
, end_pos
);
3086 selection_start
.line
= caret
.line
;
3087 selection_start
.tag
= caret
.line
.FindTag(start_pos
);
3088 selection_start
.pos
= start_pos
;
3090 selection_end
.line
= caret
.line
;
3091 selection_end
.tag
= caret
.line
.FindTag(end_pos
);
3092 selection_end
.pos
= end_pos
;
3094 selection_anchor
.line
= selection_end
.line
;
3095 selection_anchor
.tag
= selection_end
.tag
;
3096 selection_anchor
.pos
= selection_end
.pos
;
3097 selection_anchor
.height
= start_pos
;
3099 selection_prev
.line
= caret
.line
;
3100 selection_prev
.tag
= caret
.tag
;
3101 selection_prev
.pos
= caret
.pos
;
3103 this.selection_end_anchor
= true;
3110 SetSelectionVisible (!(selection_start
== selection_end
));
3113 internal void SetSelectionToCaret(bool start
) {
3115 // Invalidate old selection; selection is being reset to empty
3116 this.Invalidate(selection_start
.line
, selection_start
.pos
, selection_end
.line
, selection_end
.pos
);
3118 selection_start
.line
= caret
.line
;
3119 selection_start
.tag
= caret
.tag
;
3120 selection_start
.pos
= caret
.pos
;
3122 // start always also selects end
3123 selection_end
.line
= caret
.line
;
3124 selection_end
.tag
= caret
.tag
;
3125 selection_end
.pos
= caret
.pos
;
3127 selection_anchor
.line
= caret
.line
;
3128 selection_anchor
.tag
= caret
.tag
;
3129 selection_anchor
.pos
= caret
.pos
;
3131 // Invalidate from previous end to caret (aka new end)
3132 if (selection_end_anchor
) {
3133 if (selection_start
!= caret
) {
3134 this.Invalidate(selection_start
.line
, selection_start
.pos
, caret
.line
, caret
.pos
);
3137 if (selection_end
!= caret
) {
3138 this.Invalidate(selection_end
.line
, selection_end
.pos
, caret
.line
, caret
.pos
);
3142 if (caret
< selection_anchor
) {
3143 selection_start
.line
= caret
.line
;
3144 selection_start
.tag
= caret
.tag
;
3145 selection_start
.pos
= caret
.pos
;
3147 selection_end
.line
= selection_anchor
.line
;
3148 selection_end
.tag
= selection_anchor
.tag
;
3149 selection_end
.pos
= selection_anchor
.pos
;
3151 selection_end_anchor
= true;
3153 selection_start
.line
= selection_anchor
.line
;
3154 selection_start
.tag
= selection_anchor
.tag
;
3155 selection_start
.pos
= selection_anchor
.pos
;
3157 selection_end
.line
= caret
.line
;
3158 selection_end
.tag
= caret
.tag
;
3159 selection_end
.pos
= caret
.pos
;
3161 selection_end_anchor
= false;
3165 SetSelectionVisible (!(selection_start
== selection_end
));
3168 internal void SetSelection(Line start
, int start_pos
, Line end
, int end_pos
) {
3169 if (selection_visible
) {
3170 Invalidate(selection_start
.line
, selection_start
.pos
, selection_end
.line
, selection_end
.pos
);
3173 if ((end
.line_no
< start
.line_no
) || ((end
== start
) && (end_pos
<= start_pos
))) {
3174 selection_start
.line
= end
;
3175 selection_start
.tag
= LineTag
.FindTag(end
, end_pos
);
3176 selection_start
.pos
= end_pos
;
3178 selection_end
.line
= start
;
3179 selection_end
.tag
= LineTag
.FindTag(start
, start_pos
);
3180 selection_end
.pos
= start_pos
;
3182 selection_end_anchor
= true;
3184 selection_start
.line
= start
;
3185 selection_start
.tag
= LineTag
.FindTag(start
, start_pos
);
3186 selection_start
.pos
= start_pos
;
3188 selection_end
.line
= end
;
3189 selection_end
.tag
= LineTag
.FindTag(end
, end_pos
);
3190 selection_end
.pos
= end_pos
;
3192 selection_end_anchor
= false;
3195 selection_anchor
.line
= start
;
3196 selection_anchor
.tag
= selection_start
.tag
;
3197 selection_anchor
.pos
= start_pos
;
3199 if (((start
== end
) && (start_pos
== end_pos
)) || start
== null || end
== null) {
3200 SetSelectionVisible (false);
3202 SetSelectionVisible (true);
3203 Invalidate(selection_start
.line
, selection_start
.pos
, selection_end
.line
, selection_end
.pos
);
3207 internal void SetSelectionStart(Line start
, int start_pos
) {
3208 // Invalidate from the previous to the new start pos
3209 Invalidate(selection_start
.line
, selection_start
.pos
, start
, start_pos
);
3211 selection_start
.line
= start
;
3212 selection_start
.pos
= start_pos
;
3213 selection_start
.tag
= LineTag
.FindTag(start
, start_pos
);
3215 selection_anchor
.line
= start
;
3216 selection_anchor
.pos
= start_pos
;
3217 selection_anchor
.tag
= selection_start
.tag
;
3219 selection_end_anchor
= false;
3222 if ((selection_end
.line
!= selection_start
.line
) || (selection_end
.pos
!= selection_start
.pos
)) {
3223 SetSelectionVisible (true);
3225 SetSelectionVisible (false);
3228 Invalidate(selection_start
.line
, selection_start
.pos
, selection_end
.line
, selection_end
.pos
);
3231 internal void SetSelectionStart(int character_index
) {
3236 if (character_index
< 0) {
3240 CharIndexToLineTag(character_index
, out line
, out tag
, out pos
);
3241 SetSelectionStart(line
, pos
);
3244 internal void SetSelectionEnd(Line end
, int end_pos
) {
3246 if (end
== selection_end
.line
&& end_pos
== selection_start
.pos
) {
3247 selection_anchor
.line
= selection_start
.line
;
3248 selection_anchor
.tag
= selection_start
.tag
;
3249 selection_anchor
.pos
= selection_start
.pos
;
3251 selection_end
.line
= selection_start
.line
;
3252 selection_end
.tag
= selection_start
.tag
;
3253 selection_end
.pos
= selection_start
.pos
;
3255 selection_end_anchor
= false;
3256 } else if ((end
.line_no
< selection_anchor
.line
.line_no
) || ((end
== selection_anchor
.line
) && (end_pos
<= selection_anchor
.pos
))) {
3257 selection_start
.line
= end
;
3258 selection_start
.tag
= LineTag
.FindTag(end
, end_pos
);
3259 selection_start
.pos
= end_pos
;
3261 selection_end
.line
= selection_anchor
.line
;
3262 selection_end
.tag
= selection_anchor
.tag
;
3263 selection_end
.pos
= selection_anchor
.pos
;
3265 selection_end_anchor
= true;
3267 selection_start
.line
= selection_anchor
.line
;
3268 selection_start
.tag
= selection_anchor
.tag
;
3269 selection_start
.pos
= selection_anchor
.pos
;
3271 selection_end
.line
= end
;
3272 selection_end
.tag
= LineTag
.FindTag(end
, end_pos
);
3273 selection_end
.pos
= end_pos
;
3275 selection_end_anchor
= false;
3278 if ((selection_end
.line
!= selection_start
.line
) || (selection_end
.pos
!= selection_start
.pos
)) {
3279 SetSelectionVisible (true);
3280 Invalidate(selection_start
.line
, selection_start
.pos
, selection_end
.line
, selection_end
.pos
);
3282 SetSelectionVisible (false);
3283 // ?? Do I need to invalidate here, tests seem to work without it, but I don't think they should :-s
3287 internal void SetSelectionEnd(int character_index
) {
3292 if (character_index
< 0) {
3296 CharIndexToLineTag(character_index
, out line
, out tag
, out pos
);
3297 SetSelectionEnd(line
, pos
);
3300 internal void SetSelection(Line start
, int start_pos
) {
3301 if (selection_visible
) {
3302 Invalidate(selection_start
.line
, selection_start
.pos
, selection_end
.line
, selection_end
.pos
);
3305 selection_start
.line
= start
;
3306 selection_start
.pos
= start_pos
;
3307 selection_start
.tag
= LineTag
.FindTag(start
, start_pos
);
3309 selection_end
.line
= start
;
3310 selection_end
.tag
= selection_start
.tag
;
3311 selection_end
.pos
= start_pos
;
3313 selection_anchor
.line
= start
;
3314 selection_anchor
.tag
= selection_start
.tag
;
3315 selection_anchor
.pos
= start_pos
;
3317 selection_end_anchor
= false;
3318 SetSelectionVisible (false);
3321 internal void InvalidateSelectionArea() {
3322 Invalidate (selection_start
.line
, selection_start
.pos
, selection_end
.line
, selection_end
.pos
);
3325 // Return the current selection, as string
3326 internal string GetSelection() {
3327 // We return String.Empty if there is no selection
3328 if ((selection_start
.pos
== selection_end
.pos
) && (selection_start
.line
== selection_end
.line
)) {
3329 return string.Empty
;
3332 if (!multiline
|| (selection_start
.line
== selection_end
.line
)) {
3333 return selection_start
.line
.text
.ToString(selection_start
.pos
, selection_end
.pos
- selection_start
.pos
);
3340 sb
= new StringBuilder();
3341 start
= selection_start
.line
.line_no
;
3342 end
= selection_end
.line
.line_no
;
3344 sb
.Append(selection_start
.line
.text
.ToString(selection_start
.pos
, selection_start
.line
.text
.Length
- selection_start
.pos
) + Environment
.NewLine
);
3346 if ((start
+ 1) < end
) {
3347 for (i
= start
+ 1; i
< end
; i
++) {
3348 sb
.Append(GetLine(i
).text
.ToString() + Environment
.NewLine
);
3352 sb
.Append(selection_end
.line
.text
.ToString(0, selection_end
.pos
));
3354 return sb
.ToString();
3358 internal void ReplaceSelection(string s
, bool select_new
) {
3361 undo
.BeginCompoundAction ();
3363 int selection_start_pos
= LineTagToCharIndex (selection_start
.line
, selection_start
.pos
);
3364 // First, delete any selected text
3365 if ((selection_start
.pos
!= selection_end
.pos
) || (selection_start
.line
!= selection_end
.line
)) {
3366 if (!multiline
|| (selection_start
.line
== selection_end
.line
)) {
3367 undo
.RecordDeleteChars(selection_start
.line
, selection_start
.pos
+ 1, selection_end
.pos
- selection_start
.pos
);
3369 DeleteChars(selection_start
.tag
, selection_start
.pos
, selection_end
.pos
- selection_start
.pos
);
3371 // The tag might have been removed, we need to recalc it
3372 selection_start
.tag
= selection_start
.line
.FindTag(selection_start
.pos
);
3377 start
= selection_start
.line
.line_no
;
3378 end
= selection_end
.line
.line_no
;
3380 undo
.RecordDelete(selection_start
.line
, selection_start
.pos
+ 1, selection_end
.line
, selection_end
.pos
);
3382 // Delete first line
3383 DeleteChars(selection_start
.tag
, selection_start
.pos
, selection_start
.line
.text
.Length
- selection_start
.pos
);
3386 DeleteChars(selection_end
.line
.tags
, 0, selection_end
.pos
);
3390 for (i
= end
- 1; i
>= start
; i
--) {
3395 // BIG FAT WARNING - selection_end.line might be stale due
3396 // to the above Delete() call. DONT USE IT before hitting the end of this method!
3398 // Join start and end
3399 Combine(selection_start
.line
.line_no
, start
);
3403 Insert(selection_start
.line
, null, selection_start
.pos
, true, s
);
3404 undo
.RecordInsertString (selection_start
.line
, selection_start
.pos
, s
);
3407 CharIndexToLineTag(selection_start_pos
+ s
.Length
, out selection_start
.line
,
3408 out selection_start
.tag
, out selection_start
.pos
);
3410 selection_end
.line
= selection_start
.line
;
3411 selection_end
.pos
= selection_start
.pos
;
3412 selection_end
.tag
= selection_start
.tag
;
3413 selection_anchor
.line
= selection_start
.line
;
3414 selection_anchor
.pos
= selection_start
.pos
;
3415 selection_anchor
.tag
= selection_start
.tag
;
3417 SetSelectionVisible (false);
3419 CharIndexToLineTag(selection_start_pos
, out selection_start
.line
,
3420 out selection_start
.tag
, out selection_start
.pos
);
3422 CharIndexToLineTag(selection_start_pos
+ s
.Length
, out selection_end
.line
,
3423 out selection_end
.tag
, out selection_end
.pos
);
3425 selection_anchor
.line
= selection_start
.line
;
3426 selection_anchor
.pos
= selection_start
.pos
;
3427 selection_anchor
.tag
= selection_start
.tag
;
3429 SetSelectionVisible (true);
3432 undo
.EndCompoundAction ();
3435 internal void CharIndexToLineTag(int index
, out Line line_out
, out LineTag tag_out
, out int pos
) {
3444 for (i
= 1; i
<= lines
; i
++) {
3448 chars
+= line
.text
.Length
+ crlf_size
;
3450 if (index
<= chars
) {
3451 // we found the line
3454 while (tag
!= null) {
3455 if (index
< (start
+ tag
.start
+ tag
.length
)) {
3457 tag_out
= LineTag
.GetFinalTag (tag
);
3458 pos
= index
- start
;
3461 if (tag
.next
== null) {
3464 next_line
= GetLine(line
.line_no
+ 1);
3466 if (next_line
!= null) {
3467 line_out
= next_line
;
3468 tag_out
= LineTag
.GetFinalTag (next_line
.tags
);
3473 tag_out
= LineTag
.GetFinalTag (tag
);
3474 pos
= line_out
.text
.Length
;
3483 line_out
= GetLine(lines
);
3484 tag
= line_out
.tags
;
3485 while (tag
.next
!= null) {
3489 pos
= line_out
.text
.Length
;
3492 internal int LineTagToCharIndex(Line line
, int pos
) {
3496 // Count first and last line
3499 // Count the lines in the middle
3501 for (i
= 1; i
< line
.line_no
; i
++) {
3502 length
+= GetLine(i
).text
.Length
+ crlf_size
;
3510 internal int SelectionLength() {
3511 if ((selection_start
.pos
== selection_end
.pos
) && (selection_start
.line
== selection_end
.line
)) {
3515 if (!multiline
|| (selection_start
.line
== selection_end
.line
)) {
3516 return selection_end
.pos
- selection_start
.pos
;
3523 // Count first and last line
3524 length
= selection_start
.line
.text
.Length
- selection_start
.pos
+ selection_end
.pos
+ crlf_size
;
3526 // Count the lines in the middle
3527 start
= selection_start
.line
.line_no
+ 1;
3528 end
= selection_end
.line
.line_no
;
3531 for (i
= start
; i
< end
; i
++) {
3532 length
+= GetLine(i
).text
.Length
+ crlf_size
;
3543 /// <summary>Give it a Line number and it returns the Line object at with that line number</summary>
3544 internal Line
GetLine(int LineNo
) {
3545 Line line
= document
;
3547 while (line
!= sentinel
) {
3548 if (LineNo
== line
.line_no
) {
3550 } else if (LineNo
< line
.line_no
) {
3560 /// <summary>Retrieve the previous tag; walks line boundaries</summary>
3561 internal LineTag
PreviousTag(LineTag tag
) {
3564 if (tag
.previous
!= null) {
3565 return tag
.previous
;
3569 if (tag
.line
.line_no
== 1) {
3573 l
= GetLine(tag
.line
.line_no
- 1);
3578 while (t
.next
!= null) {
3587 /// <summary>Retrieve the next tag; walks line boundaries</summary>
3588 internal LineTag
NextTag(LineTag tag
) {
3591 if (tag
.next
!= null) {
3596 l
= GetLine(tag
.line
.line_no
+ 1);
3604 internal Line
ParagraphStart(Line line
) {
3605 while (line
.soft_break
) {
3606 line
= GetLine(line
.line_no
- 1);
3611 internal Line
ParagraphEnd(Line line
) {
3614 while (line
.soft_break
) {
3615 l
= GetLine(line
.line_no
+ 1);
3616 if ((l
== null) || (!l
.soft_break
)) {
3624 /// <summary>Give it a Y pixel coordinate and it returns the Line covering that Y coordinate</summary>
3625 internal Line
GetLineByPixel(int y
, bool exact
) {
3626 Line line
= document
;
3629 while (line
!= sentinel
) {
3631 if ((y
>= line
.Y
) && (y
< (line
.Y
+line
.height
))) {
3633 } else if (y
< line
.Y
) {
3646 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3647 internal LineTag
FindTag(int x
, int y
, out int index
, bool exact
) {
3651 line
= GetLineByPixel(y
, exact
);
3658 // Alignment adjustment
3659 x
+= line
.align_shift
;
3662 if (x
>= tag
.X
&& x
< (tag
.X
+tag
.width
)) {
3665 end
= tag
.start
+ tag
.length
- 1;
3667 for (int pos
= tag
.start
; pos
< end
; pos
++) {
3668 if (x
< line
.widths
[pos
]) {
3670 return LineTag
.GetFinalTag (tag
);
3674 return LineTag
.GetFinalTag (tag
);
3676 if (tag
.next
!= null) {
3684 index
= line
.text
.Length
;
3685 return LineTag
.GetFinalTag (tag
);
3690 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3691 internal LineTag
FindCursor(int x
, int y
, out int index
) {
3695 line
= GetLineByPixel(y
, false);
3698 // Adjust for alignment
3699 x
-= line
.align_shift
;
3702 if (x
>= tag
.X
&& x
< (tag
.X
+tag
.width
)) {
3705 end
= tag
.start
+ tag
.length
- 1;
3707 for (int pos
= tag
.start
-1; pos
< end
; pos
++) {
3708 // When clicking on a character, we position the cursor to whatever edge
3709 // of the character the click was closer
3710 if (x
< (line
.widths
[pos
] + ((line
.widths
[pos
+1]-line
.widths
[pos
])/2))) {
3718 if (tag
.next
!= null) {
3721 index
= line
.text
.Length
;
3727 /// <summary>Format area of document in specified font and color</summary>
3728 /// <param name="start_pos">1-based start position on start_line</param>
3729 /// <param name="end_pos">1-based end position on end_line </param>
3730 internal void FormatText(Line start_line
, int start_pos
, Line end_line
, int end_pos
, Font font
, Brush color
) {
3733 // First, format the first line
3734 if (start_line
!= end_line
) {
3736 LineTag
.FormatText(start_line
, start_pos
, start_line
.text
.Length
- start_pos
+ 1, font
, color
);
3739 LineTag
.FormatText(end_line
, 1, end_pos
, font
, color
);
3741 // Now all the lines inbetween
3742 for (int i
= start_line
.line_no
+ 1; i
< end_line
.line_no
; i
++) {
3744 LineTag
.FormatText(l
, 1, l
.text
.Length
, font
, color
);
3747 // Special case, single line
3748 LineTag
.FormatText(start_line
, start_pos
, end_pos
- start_pos
, font
, color
);
3752 /// <summary>Re-format areas of the document in specified font and color</summary>
3753 /// <param name="start_pos">1-based start position on start_line</param>
3754 /// <param name="end_pos">1-based end position on end_line </param>
3755 /// <param name="font">Font specifying attributes</param>
3756 /// <param name="color">Color (or NULL) to apply</param>
3757 /// <param name="apply">Attributes from font and color to apply</param>
3758 internal void FormatText(Line start_line
, int start_pos
, Line end_line
, int end_pos
, FontDefinition attributes
) {
3761 // First, format the first line
3762 if (start_line
!= end_line
) {
3764 LineTag
.FormatText(start_line
, start_pos
, start_line
.text
.Length
- start_pos
+ 1, attributes
);
3767 LineTag
.FormatText(end_line
, 1, end_pos
- 1, attributes
);
3769 // Now all the lines inbetween
3770 for (int i
= start_line
.line_no
+ 1; i
< end_line
.line_no
; i
++) {
3772 LineTag
.FormatText(l
, 1, l
.text
.Length
, attributes
);
3775 // Special case, single line
3776 LineTag
.FormatText(start_line
, start_pos
, end_pos
- start_pos
, attributes
);
3780 internal void RecalculateAlignments() {
3786 while (line_no
<= lines
) {
3787 line
= GetLine(line_no
);
3790 switch (line
.alignment
) {
3791 case HorizontalAlignment
.Left
:
3792 line
.align_shift
= 0;
3794 case HorizontalAlignment
.Center
:
3795 line
.align_shift
= (viewport_width
- (int)line
.widths
[line
.text
.Length
]) / 2;
3797 case HorizontalAlignment
.Right
:
3798 line
.align_shift
= viewport_width
- (int)line
.widths
[line
.text
.Length
];
3808 /// <summary>Calculate formatting for the whole document</summary>
3809 internal bool RecalculateDocument(Graphics g
) {
3810 return RecalculateDocument(g
, 1, this.lines
, false);
3813 /// <summary>Calculate formatting starting at a certain line</summary>
3814 internal bool RecalculateDocument(Graphics g
, int start
) {
3815 return RecalculateDocument(g
, start
, this.lines
, false);
3818 /// <summary>Calculate formatting within two given line numbers</summary>
3819 internal bool RecalculateDocument(Graphics g
, int start
, int end
) {
3820 return RecalculateDocument(g
, start
, end
, false);
3823 /// <summary>With optimize on, returns true if line heights changed</summary>
3824 internal bool RecalculateDocument(Graphics g
, int start
, int end
, bool optimize
) {
3833 recalc_pending
= true;
3834 recalc_start
= start
;
3836 recalc_optimize
= optimize
;
3840 Y
= GetLine(start
).Y
;
3845 changed
= true; // We always return true if we run non-optimized
3850 while (line_no
<= (end
+ this.lines
- shift
)) {
3851 line
= GetLine(line_no
++);
3856 line
.RecalculateLine(g
, this);
3858 if (line
.recalc
&& line
.RecalculateLine(g
, this)) {
3860 // If the height changed, all subsequent lines change
3867 line
.RecalculatePasswordLine(g
, this);
3869 if (line
.recalc
&& line
.RecalculatePasswordLine(g
, this)) {
3871 // If the height changed, all subsequent lines change
3878 if (line
.widths
[line
.text
.Length
] > new_width
) {
3879 new_width
= (int)line
.widths
[line
.text
.Length
];
3882 // Calculate alignment
3883 if (line
.alignment
!= HorizontalAlignment
.Left
) {
3884 if (line
.alignment
== HorizontalAlignment
.Center
) {
3885 line
.align_shift
= (viewport_width
- (int)line
.widths
[line
.text
.Length
]) / 2;
3887 line
.align_shift
= viewport_width
- (int)line
.widths
[line
.text
.Length
] - 1;
3891 if (line_no
> lines
) {
3896 if (document_x
!= new_width
) {
3897 document_x
= new_width
;
3898 if (WidthChanged
!= null) {
3899 WidthChanged(this, null);
3903 RecalculateAlignments();
3905 line
= GetLine(lines
);
3907 if (document_y
!= line
.Y
+ line
.height
) {
3908 document_y
= line
.Y
+ line
.height
;
3909 if (HeightChanged
!= null) {
3910 HeightChanged(this, null);
3917 internal int Size() {
3921 private void owner_HandleCreated(object sender
, EventArgs e
) {
3922 RecalculateDocument(owner
.CreateGraphicsInternal());
3926 private void owner_VisibleChanged(object sender
, EventArgs e
) {
3927 if (owner
.Visible
) {
3928 RecalculateDocument(owner
.CreateGraphicsInternal());
3932 internal static bool IsWordSeparator(char ch
) {
3946 internal int FindWordSeparator(Line line
, int pos
, bool forward
) {
3949 len
= line
.text
.Length
;
3952 for (int i
= pos
+ 1; i
< len
; i
++) {
3953 if (IsWordSeparator(line
.Text
[i
])) {
3959 for (int i
= pos
- 1; i
> 0; i
--) {
3960 if (IsWordSeparator(line
.Text
[i
- 1])) {
3968 /* Search document for text */
3969 internal bool FindChars(char[] chars
, Marker start
, Marker end
, out Marker result
) {
3975 // Search for occurence of any char in the chars array
3976 result
= new Marker();
3979 line_no
= start
.line
.line_no
;
3981 while (line_no
<= end
.line
.line_no
) {
3982 line_len
= line
.text
.Length
;
3983 while (pos
< line_len
) {
3984 for (int i
= 0; i
< chars
.Length
; i
++) {
3985 if (line
.text
[pos
] == chars
[i
]) {
3987 if ((line
.line_no
== end
.line
.line_no
) && (pos
>= end
.pos
)) {
4001 line
= GetLine(line_no
);
4007 // This version does not build one big string for searching, instead it handles
4008 // line-boundaries, which is faster and less memory intensive
4009 // FIXME - Depending on culture stuff we might have to create a big string and use culturespecific
4010 // search stuff and change it to accept and return positions instead of Markers (which would match
4011 // RichTextBox behaviour better but would be inconsistent with the rest of TextControl)
4012 internal bool Find(string search
, Marker start
, Marker end
, out Marker result
, RichTextBoxFinds options
) {
4014 string search_string
;
4026 result
= new Marker();
4027 word_option
= ((options
& RichTextBoxFinds
.WholeWord
) != 0);
4028 ignore_case
= ((options
& RichTextBoxFinds
.MatchCase
) == 0);
4029 reverse
= ((options
& RichTextBoxFinds
.Reverse
) != 0);
4032 line_no
= start
.line
.line_no
;
4036 // Prep our search string, lowercasing it if we do case-independent matching
4039 sb
= new StringBuilder(search
);
4040 for (int i
= 0; i
< sb
.Length
; i
++) {
4041 sb
[i
] = Char
.ToLower(sb
[i
]);
4043 search_string
= sb
.ToString();
4045 search_string
= search
;
4048 // We need to check if the character before our start position is a wordbreak
4051 if ((pos
== 0) || (IsWordSeparator(line
.text
[pos
- 1]))) {
4058 if (IsWordSeparator(line
.text
[pos
- 1])) {
4064 // Need to check the end of the previous line
4067 prev_line
= GetLine(line_no
- 1);
4068 if (prev_line
.soft_break
) {
4069 if (IsWordSeparator(prev_line
.text
[prev_line
.text
.Length
- 1])) {
4083 // To avoid duplication of this loop with reverse logic, we search
4084 // through the document, remembering the last match and when returning
4085 // report that last remembered match
4087 last
= new Marker();
4088 last
.height
= -1; // Abused - we use it to track change
4090 while (line_no
<= end
.line
.line_no
) {
4091 if (line_no
!= end
.line
.line_no
) {
4092 line_len
= line
.text
.Length
;
4097 while (pos
< line_len
) {
4098 if (word_option
&& (current
== search_string
.Length
)) {
4099 if (IsWordSeparator(line
.text
[pos
])) {
4112 c
= Char
.ToLower(line
.text
[pos
]);
4117 if (c
== search_string
[current
]) {
4122 if (!word_option
|| (word_option
&& (word
|| (current
> 0)))) {
4126 if (!word_option
&& (current
== search_string
.Length
)) {
4143 if (IsWordSeparator(c
)) {
4151 // Mark that we just saw a word boundary
4152 if (!line
.soft_break
) {
4156 if (current
== search_string
.Length
) {
4172 line
= GetLine(line_no
);
4176 if (last
.height
!= -1) {
4186 // if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
4198 internal void GetMarker(out Marker mark
, bool start
) {
4199 mark
= new Marker();
4202 mark
.line
= GetLine(1);
4203 mark
.tag
= mark
.line
.tags
;
4206 mark
.line
= GetLine(lines
);
4207 mark
.tag
= mark
.line
.tags
;
4208 while (mark
.tag
.next
!= null) {
4209 mark
.tag
= mark
.tag
.next
;
4211 mark
.pos
= mark
.line
.text
.Length
;
4214 #endregion // Internal Methods
4217 internal event EventHandler CaretMoved
;
4218 internal event EventHandler WidthChanged
;
4219 internal event EventHandler HeightChanged
;
4220 internal event EventHandler LengthChanged
;
4221 #endregion // Events
4223 #region Administrative
4224 public IEnumerator
GetEnumerator() {
4229 public override bool Equals(object obj
) {
4234 if (!(obj
is Document
)) {
4242 if (ToString().Equals(((Document
)obj
).ToString())) {
4249 public override int GetHashCode() {
4253 public override string ToString() {
4254 return "document " + this.document_id
;
4256 #endregion // Administrative
4259 internal class LineTag
{
4260 #region Local Variables;
4261 // Payload; formatting
4262 internal Font font
; // System.Drawing.Font object for this tag
4263 internal Brush color
; // System.Drawing.Brush object
4266 internal int start
; // start, in chars; index into Line.text
4267 internal int length
; // length, in chars
4268 internal bool r_to_l
; // Which way is the font
4271 internal int height
; // Height in pixels of the text this tag describes
4272 internal int X
; // X location of the text this tag describes
4273 internal float width
; // Width in pixels of the text this tag describes
4274 internal int ascent
; // Ascent of the font for this tag
4275 internal int shift
; // Shift down for this tag, to stay on baseline
4278 internal Line line
; // The line we're on
4279 internal LineTag next
; // Next tag on the same line
4280 internal LineTag previous
; // Previous tag on the same line
4283 #region Constructors
4284 internal LineTag(Line line
, int start
, int length
) {
4287 this.length
= length
;
4291 #endregion // Constructors
4293 #region Internal Methods
4294 ///<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>
4295 internal LineTag
Break(int pos
) {
4299 if (pos
== this.start
) {
4301 } else if (pos
>= (start
+ length
)) {
4305 new_tag
= new LineTag(line
, pos
, start
+ length
- pos
);
4306 new_tag
.color
= color
;
4307 new_tag
.font
= font
;
4308 this.length
-= new_tag
.length
;
4309 new_tag
.next
= this.next
;
4310 this.next
= new_tag
;
4311 new_tag
.previous
= this;
4312 if (new_tag
.next
!= null) {
4313 new_tag
.next
.previous
= new_tag
;
4319 ///<summary>Create new font and brush from existing font and given new attributes. Returns true if fontheight changes</summary>
4320 internal static bool GenerateTextFormat(Font font_from
, Brush color_from
, FontDefinition attributes
, out Font new_font
, out Brush new_color
) {
4326 if (attributes
.font_obj
== null) {
4327 size
= font_from
.SizeInPoints
;
4328 unit
= font_from
.Unit
;
4329 face
= font_from
.Name
;
4330 style
= font_from
.Style
;
4332 if (attributes
.face
!= null) {
4333 face
= attributes
.face
;
4336 if (attributes
.size
!= 0) {
4337 size
= attributes
.size
;
4340 style
|= attributes
.add_style
;
4341 style
&= ~attributes
.remove_style
;
4344 new_font
= new Font(face
, size
, style
, unit
);
4346 new_font
= attributes
.font_obj
;
4349 // Create 'new' color brush
4350 if (attributes
.color
!= Color
.Empty
) {
4351 new_color
= new SolidBrush(attributes
.color
);
4353 new_color
= color_from
;
4356 if (new_font
.Height
== font_from
.Height
) {
4362 /// <summary>Applies 'font' and 'brush' to characters starting at 'start' for 'length' chars;
4363 /// Removes any previous tags overlapping the same area;
4364 /// returns true if lineheight has changed</summary>
4365 /// <param name="start">1-based character position on line</param>
4366 internal static bool FormatText(Line line
, int start
, int length
, Font font
, Brush color
) {
4371 bool retval
= false; // Assume line-height doesn't change
4374 if (font
.Height
!= line
.height
) {
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
;
4385 end
= start
+ length
;
4387 // Common special case
4388 if ((start
== 1) && (length
== tag
.length
)) {
4395 //Console.WriteLine("Finding tag for {0} {1}", line, start);
4396 start_tag
= FindTag(line
, start
);
4397 end_tag
= FindTag (line
, end
);
4399 if (start_tag
== null) { // FIXME - is there a better way to handle this, or do we even need it?
4400 throw new Exception(String
.Format("Could not find start_tag in document at line {0} position {1}", line
.line_no
, start
));
4403 tag
= new LineTag(line
, start
, length
);
4410 //Console.WriteLine("Start tag: '{0}'", start_tag!=null ? start_tag.ToString() : "NULL");
4411 if (start_tag
.start
== start
) {
4412 tag
.next
= start_tag
;
4413 tag
.previous
= start_tag
.previous
;
4414 if (start_tag
.previous
!= null) {
4415 start_tag
.previous
.next
= tag
;
4417 start_tag
.previous
= tag
;
4421 if (end_tag
!= null) {
4422 // Shorten up the end tag
4423 end_tag
.previous
= tag
;
4424 end_tag
.length
= end
- start_tag
.start
+ start_tag
.length
;
4425 end_tag
.start
= end
;
4431 while (tag
!= end_tag
) {
4432 if ((tag
.start
+ tag
.length
) <= end
) {
4434 tag
.previous
.next
= tag
.next
;
4435 if (tag
.next
!= null) {
4436 tag
.next
.previous
= tag
.previous
;
4446 /// <summary>Applies font attributes specified to characters starting at 'start' for 'length' chars;
4447 /// Breaks tags at start and end point, keeping middle tags with altered attributes.
4448 /// Returns true if lineheight has changed</summary>
4449 /// <param name="start">1-based character position on line</param>
4450 internal static bool FormatText(Line line
, int start
, int length
, FontDefinition attributes
) {
4454 bool retval
= false; // Assume line-height doesn't change
4456 line
.recalc
= true; // This forces recalculation of the line in RecalculateDocument
4458 // A little sanity, not sure if it's needed, might be able to remove for speed
4459 if (length
> line
.text
.Length
) {
4460 length
= line
.text
.Length
;
4465 // Common special case
4466 if ((start
== 1) && (length
== tag
.length
)) {
4468 GenerateTextFormat(tag
.font
, tag
.color
, attributes
, out tag
.font
, out tag
.color
);
4472 start_tag
= FindTag(line
, start
);
4474 if (start_tag
== null) {
4476 // We are 'starting' after all valid tags; create a new tag with the right attributes
4477 start_tag
= FindTag(line
, line
.text
.Length
- 1);
4478 start_tag
.next
= new LineTag(line
, line
.text
.Length
+ 1, 0);
4479 start_tag
.next
.font
= start_tag
.font
;
4480 start_tag
.next
.color
= start_tag
.color
;
4481 start_tag
.next
.previous
= start_tag
;
4482 start_tag
= start_tag
.next
;
4484 throw new Exception(String
.Format("Could not find start_tag in document at line {0} position {1}", line
.line_no
, start
));
4487 start_tag
= start_tag
.Break(start
);
4490 end_tag
= FindTag(line
, start
+ length
);
4491 if (end_tag
!= null) {
4492 end_tag
= end_tag
.Break(start
+ length
);
4495 // start_tag or end_tag might be null; we're cool with that
4496 // we now walk from start_tag to end_tag, applying new attributes
4498 while ((tag
!= null) && tag
!= end_tag
) {
4499 if (LineTag
.GenerateTextFormat(tag
.font
, tag
.color
, attributes
, out tag
.font
, out tag
.color
)) {
4508 /// <summary>Finds the tag that describes the character at position 'pos' on 'line'</summary>
4509 internal static LineTag
FindTag(Line line
, int pos
) {
4510 LineTag tag
= line
.tags
;
4512 // Beginning of line is a bit special
4514 // Not sure if we should get the final tag here
4518 while (tag
!= null) {
4519 if ((tag
.start
<= pos
) && (pos
< (tag
.start
+tag
.length
))) {
4520 return GetFinalTag (tag
);
4529 // There can be multiple tags at the same position, we want to make
4530 // sure we are using the very last tag at the given position
4531 internal static LineTag
GetFinalTag (LineTag tag
)
4535 while (res
.next
!= null && res
.next
.length
== 0)
4540 /// <summary>Combines 'this' tag with 'other' tag</summary>
4541 internal bool Combine(LineTag other
) {
4542 if (!this.Equals(other
)) {
4546 this.width
+= other
.width
;
4547 this.length
+= other
.length
;
4548 this.next
= other
.next
;
4549 if (this.next
!= null) {
4550 this.next
.previous
= this;
4557 /// <summary>Remove 'this' tag ; to be called when formatting is to be removed</summary>
4558 internal bool Remove() {
4559 if ((this.start
== 1) && (this.next
== null)) {
4560 // We cannot remove the only tag
4563 if (this.start
!= 1) {
4564 this.previous
.length
+= this.length
;
4565 this.previous
.width
= -1;
4566 this.previous
.next
= this.next
;
4567 this.next
.previous
= this.previous
;
4569 this.next
.start
= 1;
4570 this.next
.length
+= this.length
;
4571 this.next
.width
= -1;
4572 this.line
.tags
= this.next
;
4573 this.next
.previous
= null;
4579 /// <summary>Checks if 'this' tag describes the same formatting options as 'obj'</summary>
4580 public override bool Equals(object obj
) {
4587 if (!(obj
is LineTag
)) {
4595 other
= (LineTag
)obj
;
4597 if (this.font
.Equals(other
.font
) && this.color
.Equals(other
.color
)) { // FIXME add checking for things like link or type later
4604 public override int GetHashCode() {
4605 return base.GetHashCode ();
4608 public override string ToString() {
4610 return "Tag starts at index " + this.start
+ "length " + this.length
+ " text: " + this.line
.Text
.Substring(this.start
-1, this.length
) + "Font " + this.font
.ToString();
4611 return "Zero Lengthed tag at index " + this.start
;
4614 #endregion // Internal Methods
4617 internal class UndoClass
{
4618 internal enum ActionType
{
4629 internal class Action
{
4630 internal ActionType type
;
4631 internal int line_no
;
4633 internal object data
;
4636 #region Local Variables
4637 private Document document
;
4638 private Stack undo_actions
;
4639 private Stack redo_actions
;
4640 private int caret_line
;
4641 private int caret_pos
;
4642 #endregion // Local Variables
4644 #region Constructors
4645 internal UndoClass(Document doc
) {
4647 undo_actions
= new Stack(50);
4648 redo_actions
= new Stack(50);
4650 #endregion // Constructors
4653 [MonoTODO("Change this to be configurable")]
4654 internal int UndoLevels
{
4656 return undo_actions
.Count
;
4660 [MonoTODO("Change this to be configurable")]
4661 internal int RedoLevels
{
4663 return redo_actions
.Count
;
4667 [MonoTODO("Come up with good naming and localization")]
4668 internal string UndoName
{
4672 action
= (Action
)undo_actions
.Peek();
4673 switch(action
.type
) {
4674 case ActionType
.InsertChar
: {
4675 Locale
.GetText("Insert character");
4679 case ActionType
.DeleteChar
: {
4680 Locale
.GetText("Delete character");
4684 case ActionType
.InsertString
: {
4685 Locale
.GetText("Insert string");
4689 case ActionType
.DeleteChars
: {
4690 Locale
.GetText("Delete string");
4694 case ActionType
.CursorMove
: {
4695 Locale
.GetText("Cursor move");
4703 internal string RedoName() {
4706 #endregion // Properties
4708 #region Internal Methods
4709 internal void Clear() {
4710 undo_actions
.Clear();
4711 redo_actions
.Clear();
4714 internal void Undo() {
4716 int compound_stack
= 0;
4718 if (undo_actions
.Count
== 0) {
4725 action
= (Action
)undo_actions
.Pop();
4727 // Put onto redo stack
4728 redo_actions
.Push(action
);
4731 switch(action
.type
) {
4732 case ActionType
.CompoundEnd
:
4736 case ActionType
.CompoundBegin
:
4740 case ActionType
.InsertString
:
4741 document
.DeleteMultiline (document
.GetLine (action
.line_no
),
4742 action
.pos
, ((string) action
.data
).Length
+ 1);
4745 case ActionType
.InsertChar
: {
4746 // FIXME - implement me
4750 case ActionType
.DeleteChars
: {
4751 this.Insert(document
.GetLine(action
.line_no
), action
.pos
, (Line
)action
.data
);
4752 Undo(); // Grab the cursor location
4756 case ActionType
.CursorMove
: {
4757 document
.caret
.line
= document
.GetLine(action
.line_no
);
4758 if (document
.caret
.line
== null) {
4763 document
.caret
.tag
= document
.caret
.line
.FindTag(action
.pos
);
4764 document
.caret
.pos
= action
.pos
;
4765 document
.caret
.height
= document
.caret
.tag
.height
;
4767 if (document
.owner
.IsHandleCreated
) {
4768 XplatUI
.DestroyCaret(document
.owner
.Handle
);
4769 XplatUI
.CreateCaret(document
.owner
.Handle
, 2, document
.caret
.height
);
4770 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
);
4772 document
.DisplayCaret ();
4775 // FIXME - enable call
4776 //if (document.CaretMoved != null) document.CaretMoved(this, EventArgs.Empty);
4780 } while (compound_stack
> 0);
4783 internal void Redo() {
4784 if (redo_actions
.Count
== 0) {
4788 #endregion // Internal Methods
4790 #region Private Methods
4792 public void BeginCompoundAction ()
4794 Action cb
= new Action ();
4795 cb
.type
= ActionType
.CompoundBegin
;
4797 undo_actions
.Push (cb
);
4800 public void EndCompoundAction ()
4802 Action ce
= new Action ();
4803 ce
.type
= ActionType
.CompoundEnd
;
4805 undo_actions
.Push (ce
);
4809 public void RecordDeleteChars(Line line
, int pos
, int length
) {
4810 RecordDelete(line
, pos
, line
, pos
+ length
- 1);
4813 // start_pos, end_pos = 1 based
4814 public void RecordDelete(Line start_line
, int start_pos
, Line end_line
, int end_pos
) {
4818 l
= Duplicate(start_line
, start_pos
, end_line
, end_pos
);
4821 a
.type
= ActionType
.DeleteChars
;
4823 a
.line_no
= start_line
.line_no
;
4824 a
.pos
= start_pos
- 1;
4826 // Record the cursor position before, since the actions will occur in reverse order
4828 undo_actions
.Push(a
);
4831 public void RecordInsertString (Line line
, int pos
, string str
)
4833 Action a
= new Action ();
4835 a
.type
= ActionType
.InsertString
;
4837 a
.line_no
= line
.line_no
;
4840 undo_actions
.Push (a
);
4843 public void RecordCursor() {
4844 if (document
.caret
.line
== null) {
4848 RecordCursor(document
.caret
.line
, document
.caret
.pos
);
4851 public void RecordCursor(Line line
, int pos
) {
4854 if ((line
.line_no
== caret_line
) && (pos
== caret_pos
)) {
4858 caret_line
= line
.line_no
;
4862 a
.type
= ActionType
.CursorMove
;
4863 a
.line_no
= line
.line_no
;
4866 undo_actions
.Push(a
);
4869 // start_pos = 1-based
4870 // end_pos = 1-based
4871 public Line
Duplicate(Line start_line
, int start_pos
, Line end_line
, int end_pos
) {
4876 LineTag current_tag
;
4885 for (int i
= start_line
.line_no
; i
<= end_line
.line_no
; i
++) {
4886 current
= document
.GetLine(i
);
4888 if (start_line
.line_no
== i
) {
4894 if (end_line
.line_no
== i
) {
4897 end
= current
.text
.Length
;
4901 line
.text
= new StringBuilder(current
.text
.ToString(start
- 1, end
- start
+ 1));
4903 // Copy tags from start to start+length onto new line
4904 current_tag
= current
.FindTag(start
- 1);
4905 while ((current_tag
!= null) && (current_tag
.start
< end
)) {
4906 if ((current_tag
.start
<= start
) && (start
< (current_tag
.start
+ current_tag
.length
))) {
4907 // start tag is within this tag
4910 tag_start
= current_tag
.start
;
4913 if (end
< (current_tag
.start
+ current_tag
.length
)) {
4914 tag_length
= end
- tag_start
+ 1;
4916 tag_length
= current_tag
.start
+ current_tag
.length
- tag_start
;
4918 tag
= new LineTag(line
, tag_start
- start
+ 1, tag_length
);
4919 tag
.color
= current_tag
.color
;
4920 tag
.font
= current_tag
.font
;
4922 current_tag
= current_tag
.next
;
4924 // Add the new tag to the line
4925 if (line
.tags
== null) {
4931 while (tail
.next
!= null) {
4935 tag
.previous
= tail
;
4939 if ((i
+ 1) <= end_line
.line_no
) {
4940 line
.soft_break
= current
.soft_break
;
4942 // Chain them (we use right/left as next/previous)
4943 line
.right
= new Line();
4944 line
.right
.left
= line
;
4952 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
4953 internal void Insert(Line line
, int pos
, Line insert
) {
4960 // Handle special case first
4961 if (insert
.right
== null) {
4963 // Single line insert
4964 document
.Split(line
, pos
);
4966 if (insert
.tags
== null) {
4967 return; // Blank line
4970 //Insert our tags at the end
4973 while (tag
.next
!= null) {
4977 offset
= tag
.start
+ tag
.length
- 1;
4979 tag
.next
= insert
.tags
;
4980 line
.text
.Insert(offset
, insert
.text
.ToString());
4982 // Adjust start locations
4984 while (tag
!= null) {
4985 tag
.start
+= offset
;
4989 // Put it back together
4990 document
.Combine(line
.line_no
, line
.line_no
+ 1);
4991 document
.UpdateView(line
, pos
);
4998 while (current
!= null) {
4999 if (current
== insert
) {
5000 // Inserting the first line we split the line (and make space)
5001 document
.Split(line
, pos
);
5002 //Insert our tags at the end of the line
5006 while (tag
.next
!= null) {
5009 offset
= tag
.start
+ tag
.length
- 1;
5010 tag
.next
= current
.tags
;
5011 tag
.next
.previous
= tag
;
5017 line
.tags
= current
.tags
;
5018 line
.tags
.previous
= null;
5022 document
.Split(line
.line_no
, 0);
5024 line
.tags
= current
.tags
;
5025 line
.tags
.previous
= null;
5028 // Adjust start locations and line pointers
5029 while (tag
!= null) {
5030 tag
.start
+= offset
;
5035 line
.text
.Insert(offset
, current
.text
.ToString());
5036 line
.Grow(line
.text
.Length
);
5039 line
= document
.GetLine(line
.line_no
+ 1);
5041 // FIXME? Test undo of line-boundaries
5042 if ((current
.right
== null) && (current
.tags
.length
!= 0)) {
5043 document
.Combine(line
.line_no
- 1, line
.line_no
);
5045 current
= current
.right
;
5050 // Recalculate our document
5051 document
.UpdateView(first
, lines
, pos
);
5054 #endregion // Private Methods