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: 8 // 9 // The above copyright notice and this permission notice shall be 10 // included in all copies or substantial portions of the Software. 11 // 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. 19 // 20 // Copyright (c) 2004-2006 Novell, Inc. (http://www.novell.com) 21 // 22 // Authors: 23 // Peter Bartok pbartok@novell.com 24 // 25 // 26 27 // NOT COMPLETE 28 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, etc. 34 // - Implement CaretPgUp/PgDown 35 36 // NOTE: 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 40 // 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 44 45 46 #undef Debug 47 48 using System; 49 using System.Collections; 50 using System.Drawing; 51 using System.Drawing.Text; 52 using System.Text; 53 using RTF=System.Windows.Forms.RTF; 54 55 namespace System.Windows.Forms { 56 internal enum LineColor { 57 Red = 0, 58 Black = 1 59 } 60 61 internal enum CaretSelection { 62 Position, // Selection=Caret 63 Word, // Selection=Word under caret 64 Line // Selection=Line under caret 65 } 66 67 [Flags] 68 internal enum FormatSpecified { 69 None, 70 71 BackColor = 2, 72 Font = 4, 73 Color = 8, 74 } 75 76 internal enum CaretDirection { 77 CharForward, // Move a char to the right 78 CharBack, // Move a char to the left 79 LineUp, // Move a line up 80 LineDown, // Move a line down 81 Home, // Move to the beginning of the line 82 End, // Move to the end of the line 83 PgUp, // Move one page up 84 PgDn, // Move one page down 85 CtrlPgUp, // Move caret to the first visible char in the viewport 86 CtrlPgDn, // Move caret to the last visible char in the viewport 87 CtrlHome, // Move to the beginning of the document 88 CtrlEnd, // Move to the end of the document 89 WordBack, // Move to the beginning of the previous word (or beginning of line) 90 WordForward, // Move to the beginning of the next word (or end of line) 91 SelectionStart, // Move to the beginning of the current selection 92 SelectionEnd, // Move to the end of the current selection 93 CharForwardNoWrap, // Move a char forward, but don't wrap onto the next line 94 CharBackNoWrap // Move a char backward, but don't wrap onto the previous line 95 } 96 97 internal enum LineEnding { 98 Wrap = 1, // line wraps to the next line 99 Limp = 2, // \r 100 Hard = 4, // \r\n 101 Soft = 8, // \r\r\n 102 Rich = 16, // \n 103 104 None = 0 105 } 106 107 internal class Document : ICloneable, IEnumerable { 108 #region Structures 109 // FIXME - go through code and check for places where 110 // we do explicit comparisons instead of using the compare overloads 111 internal struct Marker { 112 internal Line line; 113 internal LineTag tag; 114 internal int pos; 115 internal int height; 116 operator <System.Windows.Forms.Document.Marker117 public static bool operator<(Marker lhs, Marker rhs) { 118 if (lhs.line.line_no < rhs.line.line_no) { 119 return true; 120 } 121 122 if (lhs.line.line_no == rhs.line.line_no) { 123 if (lhs.pos < rhs.pos) { 124 return true; 125 } 126 } 127 return false; 128 } 129 operator >System.Windows.Forms.Document.Marker130 public static bool operator>(Marker lhs, Marker rhs) { 131 if (lhs.line.line_no > rhs.line.line_no) { 132 return true; 133 } 134 135 if (lhs.line.line_no == rhs.line.line_no) { 136 if (lhs.pos > rhs.pos) { 137 return true; 138 } 139 } 140 return false; 141 } 142 operator ==System.Windows.Forms.Document.Marker143 public static bool operator==(Marker lhs, Marker rhs) { 144 if ((lhs.line.line_no == rhs.line.line_no) && (lhs.pos == rhs.pos)) { 145 return true; 146 } 147 return false; 148 } 149 operator !=System.Windows.Forms.Document.Marker150 public static bool operator!=(Marker lhs, Marker rhs) { 151 if ((lhs.line.line_no != rhs.line.line_no) || (lhs.pos != rhs.pos)) { 152 return true; 153 } 154 return false; 155 } 156 CombineSystem.Windows.Forms.Document.Marker157 public void Combine(Line move_to_line, int move_to_line_length) { 158 line = move_to_line; 159 pos += move_to_line_length; 160 tag = LineTag.FindTag(line, pos); 161 } 162 163 // This is for future use, right now Document.Split does it by hand, with some added shortcut logic SplitSystem.Windows.Forms.Document.Marker164 public void Split(Line move_to_line, int split_at) { 165 line = move_to_line; 166 pos -= split_at; 167 tag = LineTag.FindTag(line, pos); 168 } 169 EqualsSystem.Windows.Forms.Document.Marker170 public override bool Equals(object obj) { 171 return this==(Marker)obj; 172 } 173 GetHashCodeSystem.Windows.Forms.Document.Marker174 public override int GetHashCode() { 175 return base.GetHashCode (); 176 } 177 ToStringSystem.Windows.Forms.Document.Marker178 public override string ToString() { 179 return "Marker Line " + line + ", Position " + pos; 180 } 181 182 } 183 #endregion Structures 184 185 #region Local Variables 186 private Line document; 187 private int lines; 188 private Line sentinel; 189 private int document_id; 190 private Random random = new Random(); 191 internal string password_char; 192 private StringBuilder password_cache; 193 private bool calc_pass; 194 private int char_count; 195 private bool enable_links; 196 197 // For calculating widths/heights 198 public static readonly StringFormat string_format = new StringFormat (StringFormat.GenericTypographic); 199 200 private int recalc_suspended; 201 private bool recalc_pending; 202 private int recalc_start = 1; // This starts at one, since lines are 1 based 203 private int recalc_end; 204 private bool recalc_optimize; 205 206 private int update_suspended; 207 private bool update_pending; 208 private int update_start = 1; 209 210 internal bool multiline; 211 internal HorizontalAlignment alignment; 212 internal bool wrap; 213 214 internal UndoManager undo; 215 216 internal Marker caret; 217 internal Marker selection_start; 218 internal Marker selection_end; 219 internal bool selection_visible; 220 internal Marker selection_anchor; 221 internal Marker selection_prev; 222 internal bool selection_end_anchor; 223 224 internal int viewport_x; 225 internal int viewport_y; // The visible area of the document 226 internal int offset_x; 227 internal int offset_y; 228 internal int viewport_width; 229 internal int viewport_height; 230 231 internal int document_x; // Width of the document 232 internal int document_y; // Height of the document 233 234 internal int crlf_size; // 1 or 2, depending on whether we use \r\n or just \n 235 236 internal TextBoxBase owner; // Who's owning us? 237 static internal int caret_width = 1; 238 static internal int caret_shift = 1; 239 240 internal int left_margin = 2; // A left margin for all lines 241 internal int top_margin = 2; 242 internal int right_margin = 2; 243 #endregion // Local Variables 244 245 #region Constructors Document(TextBoxBase owner)246 internal Document (TextBoxBase owner) 247 { 248 lines = 0; 249 250 this.owner = owner; 251 252 multiline = true; 253 password_char = ""; 254 calc_pass = false; 255 recalc_pending = false; 256 257 // Tree related stuff 258 sentinel = new Line (this, LineEnding.None); 259 sentinel.color = LineColor.Black; 260 261 document = sentinel; 262 263 // We always have a blank line 264 owner.HandleCreated += new EventHandler(owner_HandleCreated); 265 owner.VisibleChanged += new EventHandler(owner_VisibleChanged); 266 267 Add (1, String.Empty, owner.Font, owner.ForeColor, LineEnding.None); 268 269 undo = new UndoManager (this); 270 271 selection_visible = false; 272 selection_start.line = this.document; 273 selection_start.pos = 0; 274 selection_start.tag = selection_start.line.tags; 275 selection_end.line = this.document; 276 selection_end.pos = 0; 277 selection_end.tag = selection_end.line.tags; 278 selection_anchor.line = this.document; 279 selection_anchor.pos = 0; 280 selection_anchor.tag = selection_anchor.line.tags; 281 caret.line = this.document; 282 caret.pos = 0; 283 caret.tag = caret.line.tags; 284 285 viewport_x = 0; 286 viewport_y = 0; 287 288 offset_x = 0; 289 offset_y = 0; 290 291 crlf_size = 2; 292 293 // Default selection is empty 294 295 document_id = random.Next(); 296 297 string_format.Trimming = StringTrimming.None; 298 string_format.FormatFlags = StringFormatFlags.DisplayFormatControl; 299 300 UpdateMargins (); 301 } 302 #endregion 303 304 #region Internal Properties 305 internal Line Root { 306 get { 307 return document; 308 } 309 310 set { 311 document = value; 312 } 313 } 314 315 // UIA: Method used via reflection in TextRangeProvider 316 internal int Lines { 317 get { 318 return lines; 319 } 320 } 321 322 internal Line CaretLine { 323 get { 324 return caret.line; 325 } 326 } 327 328 internal int CaretPosition { 329 get { 330 return caret.pos; 331 } 332 } 333 334 internal Point Caret { 335 get { 336 return new Point((int)caret.tag.Line.widths[caret.pos] + caret.line.X, caret.line.Y); 337 } 338 } 339 340 internal LineTag CaretTag { 341 get { 342 return caret.tag; 343 } 344 345 set { 346 caret.tag = value; 347 } 348 } 349 350 internal int CRLFSize { 351 get { 352 return crlf_size; 353 } 354 355 set { 356 crlf_size = value; 357 } 358 } 359 360 /// <summary> 361 /// Whether text is scanned for links 362 /// </summary> 363 internal bool EnableLinks { 364 get { return enable_links; } 365 set { enable_links = value; } 366 } 367 368 internal string PasswordChar { 369 get { 370 return password_char; 371 } 372 373 set { 374 password_char = value; 375 PasswordCache.Length = 0; 376 if ((password_char.Length != 0) && (password_char[0] != '\0')) { 377 calc_pass = true; 378 } else { 379 calc_pass = false; 380 } 381 } 382 } 383 384 private StringBuilder PasswordCache { 385 get { 386 if (password_cache == null) 387 password_cache = new StringBuilder(); 388 return password_cache; 389 } 390 } 391 392 internal int ViewPortX { 393 get { 394 return viewport_x; 395 } 396 397 set { 398 viewport_x = value; 399 } 400 } 401 402 internal int Length { 403 get { 404 return char_count + lines - 1; // Add \n for each line but the last 405 } 406 } 407 408 private int CharCount { 409 get { 410 return char_count; 411 } 412 413 set { 414 char_count = value; 415 416 if (LengthChanged != null) { 417 LengthChanged(this, EventArgs.Empty); 418 } 419 } 420 } 421 422 internal int ViewPortY { 423 get { 424 return viewport_y; 425 } 426 427 set { 428 viewport_y = value; 429 } 430 } 431 432 internal int OffsetX 433 { 434 get 435 { 436 return offset_x; 437 } 438 439 set 440 { 441 offset_x = value; 442 } 443 } 444 445 internal int OffsetY 446 { 447 get 448 { 449 return offset_y; 450 } 451 452 set 453 { 454 offset_y = value; 455 } 456 } 457 458 internal int ViewPortWidth { 459 get { 460 return viewport_width; 461 } 462 463 set { 464 viewport_width = value; 465 } 466 } 467 468 internal int ViewPortHeight { 469 get { 470 return viewport_height; 471 } 472 473 set { 474 viewport_height = value; 475 } 476 } 477 478 479 internal int Width { 480 get { 481 return this.document_x; 482 } 483 } 484 485 internal int Height { 486 get { 487 return this.document_y; 488 } 489 } 490 491 internal bool SelectionVisible { 492 get { 493 return selection_visible; 494 } 495 } 496 497 internal bool Wrap { 498 get { 499 return wrap; 500 } 501 502 set { 503 wrap = value; 504 } 505 } 506 507 #endregion // Internal Properties 508 509 #region Private Methods 510 UpdateMargins()511 internal void UpdateMargins () 512 { 513 switch (owner.actual_border_style) { 514 case BorderStyle.None: 515 left_margin = 0; 516 top_margin = 0; 517 right_margin = 1; 518 break; 519 case BorderStyle.FixedSingle: 520 left_margin = 2; 521 top_margin = 2; 522 right_margin = 3; 523 break; 524 case BorderStyle.Fixed3D: 525 left_margin = 1; 526 top_margin = 1; 527 right_margin = 2; 528 break; 529 } 530 } 531 SuspendRecalc()532 internal void SuspendRecalc () 533 { 534 if (recalc_suspended == 0) { 535 recalc_start = int.MaxValue; 536 recalc_end = int.MinValue; 537 } 538 539 recalc_suspended++; 540 } 541 ResumeRecalc(bool immediate_update)542 internal void ResumeRecalc (bool immediate_update) 543 { 544 if (recalc_suspended > 0) 545 recalc_suspended--; 546 547 if (recalc_suspended == 0 && (immediate_update || recalc_pending) && !(recalc_start == int.MaxValue && recalc_end == int.MinValue)) { 548 RecalculateDocument (owner.CreateGraphicsInternal (), recalc_start, recalc_end, recalc_optimize); 549 recalc_pending = false; 550 } 551 } 552 SuspendUpdate()553 internal void SuspendUpdate () 554 { 555 update_suspended++; 556 } 557 ResumeUpdate(bool immediate_update)558 internal void ResumeUpdate (bool immediate_update) 559 { 560 if (update_suspended > 0) 561 update_suspended--; 562 563 if (immediate_update && update_suspended == 0 && update_pending) { 564 UpdateView (GetLine (update_start), 0); 565 update_pending = false; 566 } 567 } 568 569 // For debugging DumpTree(Line line, bool with_tags)570 internal int DumpTree(Line line, bool with_tags) { 571 int total; 572 573 total = 1; 574 575 Console.Write("Line {0} [# {1}], Y: {2}, ending style: {3}, Text: '{4}'", 576 line.line_no, line.GetHashCode(), line.Y, line.ending, 577 line.text != null ? line.text.ToString() : "undefined"); 578 579 if (line.left == sentinel) { 580 Console.Write(", left = sentinel"); 581 } else if (line.left == null) { 582 Console.Write(", left = NULL"); 583 } 584 585 if (line.right == sentinel) { 586 Console.Write(", right = sentinel"); 587 } else if (line.right == null) { 588 Console.Write(", right = NULL"); 589 } 590 591 Console.WriteLine(""); 592 593 if (with_tags) { 594 LineTag tag; 595 int count; 596 int length; 597 598 tag = line.tags; 599 count = 1; 600 length = 0; 601 Console.Write(" Tags: "); 602 while (tag != null) { 603 Console.Write("{0} <{1}>-<{2}>", count++, tag.Start, tag.End 604 /*line.text.ToString (tag.start - 1, tag.length)*/); 605 length += tag.Length; 606 607 if (tag.Line != line) { 608 Console.Write("BAD line link"); 609 throw new Exception("Bad line link in tree"); 610 } 611 tag = tag.Next; 612 if (tag != null) { 613 Console.Write(", "); 614 } 615 } 616 if (length > line.text.Length) { 617 throw new Exception(String.Format("Length of tags more than length of text on line (expected {0} calculated {1})", line.text.Length, length)); 618 } else if (length < line.text.Length) { 619 throw new Exception(String.Format("Length of tags less than length of text on line (expected {0} calculated {1})", line.text.Length, length)); 620 } 621 Console.WriteLine(""); 622 } 623 if (line.left != null) { 624 if (line.left != sentinel) { 625 total += DumpTree(line.left, with_tags); 626 } 627 } else { 628 if (line != sentinel) { 629 throw new Exception("Left should not be NULL"); 630 } 631 } 632 633 if (line.right != null) { 634 if (line.right != sentinel) { 635 total += DumpTree(line.right, with_tags); 636 } 637 } else { 638 if (line != sentinel) { 639 throw new Exception("Right should not be NULL"); 640 } 641 } 642 643 for (int i = 1; i <= this.lines; i++) { 644 if (GetLine(i) == null) { 645 throw new Exception(String.Format("Hole in line order, missing {0}", i)); 646 } 647 } 648 649 if (line == this.Root) { 650 if (total < this.lines) { 651 throw new Exception(String.Format("Not enough nodes in tree, found {0}, expected {1}", total, this.lines)); 652 } else if (total > this.lines) { 653 throw new Exception(String.Format("Too many nodes in tree, found {0}, expected {1}", total, this.lines)); 654 } 655 } 656 657 return total; 658 } 659 SetSelectionVisible(bool value)660 private void SetSelectionVisible (bool value) 661 { 662 bool old_selection_visible = selection_visible; 663 selection_visible = value; 664 665 // cursor and selection are enemies, we can't have both in the same room at the same time 666 if (owner.IsHandleCreated && !owner.show_caret_w_selection) 667 XplatUI.CaretVisible (owner.Handle, !selection_visible); 668 if (UIASelectionChanged != null && (selection_visible || old_selection_visible)) 669 UIASelectionChanged (this, EventArgs.Empty); 670 } 671 DecrementLines(int line_no)672 private void DecrementLines(int line_no) { 673 int current; 674 675 current = line_no; 676 while (current <= lines) { 677 GetLine(current).line_no--; 678 current++; 679 } 680 return; 681 } 682 IncrementLines(int line_no)683 private void IncrementLines(int line_no) { 684 int current; 685 686 current = this.lines; 687 while (current >= line_no) { 688 GetLine(current).line_no++; 689 current--; 690 } 691 return; 692 } 693 RebalanceAfterAdd(Line line1)694 private void RebalanceAfterAdd(Line line1) { 695 Line line2; 696 697 while ((line1 != document) && (line1.parent.color == LineColor.Red)) { 698 if (line1.parent == line1.parent.parent.left) { 699 line2 = line1.parent.parent.right; 700 701 if ((line2 != null) && (line2.color == LineColor.Red)) { 702 line1.parent.color = LineColor.Black; 703 line2.color = LineColor.Black; 704 line1.parent.parent.color = LineColor.Red; 705 line1 = line1.parent.parent; 706 } else { 707 if (line1 == line1.parent.right) { 708 line1 = line1.parent; 709 RotateLeft(line1); 710 } 711 712 line1.parent.color = LineColor.Black; 713 line1.parent.parent.color = LineColor.Red; 714 715 RotateRight(line1.parent.parent); 716 } 717 } else { 718 line2 = line1.parent.parent.left; 719 720 if ((line2 != null) && (line2.color == LineColor.Red)) { 721 line1.parent.color = LineColor.Black; 722 line2.color = LineColor.Black; 723 line1.parent.parent.color = LineColor.Red; 724 line1 = line1.parent.parent; 725 } else { 726 if (line1 == line1.parent.left) { 727 line1 = line1.parent; 728 RotateRight(line1); 729 } 730 731 line1.parent.color = LineColor.Black; 732 line1.parent.parent.color = LineColor.Red; 733 RotateLeft(line1.parent.parent); 734 } 735 } 736 } 737 document.color = LineColor.Black; 738 } 739 RebalanceAfterDelete(Line line1)740 private void RebalanceAfterDelete(Line line1) { 741 Line line2; 742 743 while ((line1 != document) && (line1.color == LineColor.Black)) { 744 if (line1 == line1.parent.left) { 745 line2 = line1.parent.right; 746 if (line2.color == LineColor.Red) { 747 line2.color = LineColor.Black; 748 line1.parent.color = LineColor.Red; 749 RotateLeft(line1.parent); 750 line2 = line1.parent.right; 751 } 752 if ((line2.left.color == LineColor.Black) && (line2.right.color == LineColor.Black)) { 753 line2.color = LineColor.Red; 754 line1 = line1.parent; 755 } else { 756 if (line2.right.color == LineColor.Black) { 757 line2.left.color = LineColor.Black; 758 line2.color = LineColor.Red; 759 RotateRight(line2); 760 line2 = line1.parent.right; 761 } 762 line2.color = line1.parent.color; 763 line1.parent.color = LineColor.Black; 764 line2.right.color = LineColor.Black; 765 RotateLeft(line1.parent); 766 line1 = document; 767 } 768 } else { 769 line2 = line1.parent.left; 770 if (line2.color == LineColor.Red) { 771 line2.color = LineColor.Black; 772 line1.parent.color = LineColor.Red; 773 RotateRight(line1.parent); 774 line2 = line1.parent.left; 775 } 776 if ((line2.right.color == LineColor.Black) && (line2.left.color == LineColor.Black)) { 777 line2.color = LineColor.Red; 778 line1 = line1.parent; 779 } else { 780 if (line2.left.color == LineColor.Black) { 781 line2.right.color = LineColor.Black; 782 line2.color = LineColor.Red; 783 RotateLeft(line2); 784 line2 = line1.parent.left; 785 } 786 line2.color = line1.parent.color; 787 line1.parent.color = LineColor.Black; 788 line2.left.color = LineColor.Black; 789 RotateRight(line1.parent); 790 line1 = document; 791 } 792 } 793 } 794 line1.color = LineColor.Black; 795 } 796 RotateLeft(Line line1)797 private void RotateLeft(Line line1) { 798 Line line2 = line1.right; 799 800 line1.right = line2.left; 801 802 if (line2.left != sentinel) { 803 line2.left.parent = line1; 804 } 805 806 if (line2 != sentinel) { 807 line2.parent = line1.parent; 808 } 809 810 if (line1.parent != null) { 811 if (line1 == line1.parent.left) { 812 line1.parent.left = line2; 813 } else { 814 line1.parent.right = line2; 815 } 816 } else { 817 document = line2; 818 } 819 820 line2.left = line1; 821 if (line1 != sentinel) { 822 line1.parent = line2; 823 } 824 } 825 RotateRight(Line line1)826 private void RotateRight(Line line1) { 827 Line line2 = line1.left; 828 829 line1.left = line2.right; 830 831 if (line2.right != sentinel) { 832 line2.right.parent = line1; 833 } 834 835 if (line2 != sentinel) { 836 line2.parent = line1.parent; 837 } 838 839 if (line1.parent != null) { 840 if (line1 == line1.parent.right) { 841 line1.parent.right = line2; 842 } else { 843 line1.parent.left = line2; 844 } 845 } else { 846 document = line2; 847 } 848 849 line2.right = line1; 850 if (line1 != sentinel) { 851 line1.parent = line2; 852 } 853 } 854 855 UpdateView(Line line, int pos)856 internal void UpdateView(Line line, int pos) { 857 if (!owner.IsHandleCreated) { 858 return; 859 } 860 861 if (update_suspended > 0) { 862 update_start = Math.Min (update_start, line.line_no); 863 // update_end = Math.Max (update_end, line.line_no); 864 // recalc_optimize = true; 865 update_pending = true; 866 return; 867 } 868 869 // Optimize invalidation based on Line alignment 870 if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no, true)) { 871 // Lineheight changed, invalidate the rest of the document 872 if ((line.Y - viewport_y) >=0 ) { 873 // We formatted something that's in view, only draw parts of the screen 874 owner.Invalidate(new Rectangle( 875 offset_x, 876 line.Y - viewport_y + offset_y, 877 viewport_width, 878 owner.Height - (line.Y - viewport_y))); 879 } else { 880 // The tag was above the visible area, draw everything 881 owner.Invalidate(); 882 } 883 } else { 884 switch(line.alignment) { 885 case HorizontalAlignment.Left: { 886 owner.Invalidate(new Rectangle( 887 line.X + ((int)line.widths[pos] - viewport_x - 1) + offset_x, 888 line.Y - viewport_y + offset_y, 889 viewport_width, 890 line.height + 1)); 891 break; 892 } 893 894 case HorizontalAlignment.Center: { 895 owner.Invalidate(new Rectangle( 896 line.X + offset_x, 897 line.Y - viewport_y + offset_y, 898 viewport_width, 899 line.height + 1)); 900 break; 901 } 902 903 case HorizontalAlignment.Right: { 904 owner.Invalidate(new Rectangle( 905 line.X + offset_x, 906 line.Y - viewport_y + offset_y, 907 (int)line.widths[pos + 1] - viewport_x + line.X, 908 line.height + 1)); 909 break; 910 } 911 } 912 } 913 } 914 915 916 // Update display from line, down line_count lines; pos is unused, but required for the signature UpdateView(Line line, int line_count, int pos)917 internal void UpdateView(Line line, int line_count, int pos) { 918 if (!owner.IsHandleCreated) { 919 return; 920 } 921 922 if (recalc_suspended > 0) { 923 recalc_start = Math.Min (recalc_start, line.line_no); 924 recalc_end = Math.Max (recalc_end, line.line_no + line_count); 925 recalc_optimize = true; 926 recalc_pending = true; 927 return; 928 } 929 930 int start_line_top = line.Y; 931 932 Line end_line = GetLine (line.line_no + line_count); 933 if (end_line == null) 934 end_line = GetLine (lines); 935 936 if (end_line == null) 937 return; 938 939 int end_line_bottom = end_line.Y + end_line.height; 940 941 if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no + line_count, true)) { 942 // Lineheight changed, invalidate the rest of the document 943 if ((line.Y - viewport_y) >=0 ) { 944 // We formatted something that's in view, only draw parts of the screen 945 owner.Invalidate(new Rectangle( 946 offset_x, 947 line.Y - viewport_y + offset_y, 948 viewport_width, 949 owner.Height - (line.Y - viewport_y))); 950 } else { 951 // The tag was above the visible area, draw everything 952 owner.Invalidate(); 953 } 954 } else { 955 int x = 0 - viewport_x + offset_x; 956 int w = viewport_width; 957 int y = Math.Min (start_line_top - viewport_y, line.Y - viewport_y) + offset_y; 958 int h = Math.Max (end_line_bottom - y, end_line.Y + end_line.height - y); 959 960 owner.Invalidate (new Rectangle (x, y, w, h)); 961 } 962 } 963 964 /// <summary> 965 /// Scans the next paragraph for http:/ ftp:/ www. https:/ etc and marks the tags 966 /// as links. 967 /// </summary> 968 /// <param name="start_line">The line to start on</param> 969 /// <param name="link_changed">marks as true if something is changed</param> ScanForLinks(Line start_line, ref bool link_changed)970 private void ScanForLinks (Line start_line, ref bool link_changed) 971 { 972 Line current_line = start_line; 973 StringBuilder line_no_breaks = new StringBuilder (); 974 StringBuilder line_link_record = new StringBuilder (); 975 ArrayList cumulative_length_list = new ArrayList (); 976 bool update_caret_tag = false; 977 978 cumulative_length_list.Add (0); 979 980 while (current_line != null) { 981 line_no_breaks.Append (current_line.text); 982 983 if (link_changed == false) 984 current_line.LinkRecord (line_link_record); 985 986 current_line.ClearLinks (); 987 988 cumulative_length_list.Add (line_no_breaks.Length); 989 990 if (current_line.ending == LineEnding.Wrap) 991 current_line = GetLine (current_line.LineNo + 1); 992 else 993 break; 994 } 995 996 // search for protocols.. make sure www. is first! 997 string [] search_terms = new string [] { "www.", "http:/", "ftp:/", "https:/" }; 998 int search_found = 0; 999 int index_found = 0; 1000 string line_no_breaks_string = line_no_breaks.ToString (); 1001 int line_no_breaks_index = 0; 1002 int link_end = 0; 1003 1004 while (true) { 1005 if (line_no_breaks_index >= line_no_breaks_string.Length) 1006 break; 1007 1008 index_found = FirstIndexOfAny (line_no_breaks_string, search_terms, line_no_breaks_index, out search_found); 1009 1010 //no links found on this line 1011 if (index_found == -1) 1012 break; 1013 1014 if (search_found == 0) { 1015 // if we are at the end of the line to analyse and the end of the line 1016 // is "www." then there are no links here 1017 if (line_no_breaks_string.Length == index_found + search_terms [0].Length) 1018 break; 1019 1020 // if after www. we don't have a letter a digit or a @ or - or / 1021 // then it is not a web address, we should continue searching 1022 if (char.IsLetterOrDigit (line_no_breaks_string [index_found + search_terms [0].Length]) == false && 1023 "@/~".IndexOf (line_no_breaks_string [index_found + search_terms [0].Length].ToString ()) == -1) { 1024 line_no_breaks_index = index_found + search_terms [0].Length; 1025 continue; 1026 } 1027 } 1028 1029 link_end = line_no_breaks_string.Length - 1; 1030 line_no_breaks_index = line_no_breaks_string.Length; 1031 1032 // we've found a link, we just need to find where it ends now 1033 for (int i = index_found + search_terms [search_found].Length; i < line_no_breaks_string.Length; i++) { 1034 if (line_no_breaks_string [i - 1] == '.') { 1035 if (char.IsLetterOrDigit (line_no_breaks_string [i]) == false && 1036 "@/~".IndexOf (line_no_breaks_string [i].ToString ()) == -1) { 1037 link_end = i - 1; 1038 line_no_breaks_index = i; 1039 break; 1040 } 1041 } else { 1042 if (char.IsLetterOrDigit (line_no_breaks_string [i]) == false && 1043 "@-/:~.?=_&".IndexOf (line_no_breaks_string [i].ToString ()) == -1) { 1044 link_end = i - 1; 1045 line_no_breaks_index = i; 1046 break; 1047 } 1048 } 1049 } 1050 1051 string link_text = line_no_breaks_string.Substring (index_found, link_end - index_found + 1); 1052 int current_cumulative = 0; 1053 1054 // we've found a link - index_found -> link_end 1055 // now we just make all the tags as containing link and 1056 // point them to the text for the whole link 1057 1058 current_line = start_line; 1059 1060 //find the line we start on 1061 for (current_cumulative = 1; current_cumulative < cumulative_length_list.Count; current_cumulative++) 1062 if ((int)cumulative_length_list [current_cumulative] > index_found) 1063 break; 1064 1065 current_line = GetLine (start_line.LineNo + current_cumulative - 1); 1066 1067 // find the tag we start on 1068 LineTag current_tag = current_line.FindTag (index_found - (int)cumulative_length_list [current_cumulative - 1] + 1); 1069 1070 if (current_tag.Start != (index_found - (int)cumulative_length_list [current_cumulative - 1]) + 1) { 1071 if (current_tag == CaretTag) 1072 update_caret_tag = true; 1073 1074 current_tag = current_tag.Break ((index_found - (int)cumulative_length_list [current_cumulative - 1]) + 1); 1075 } 1076 1077 // set the tag 1078 current_tag.IsLink = true; 1079 current_tag.LinkText = link_text; 1080 1081 //go through each character 1082 // find the tag we are in 1083 // skip the number of characters in the tag 1084 for (int i = 1; i < link_text.Length; i++) { 1085 // on to a new word-wrapped line 1086 if ((int)cumulative_length_list [current_cumulative] <= index_found + i) { 1087 1088 current_line = GetLine (start_line.LineNo + current_cumulative++); 1089 current_tag = current_line.FindTag (index_found + i - (int)cumulative_length_list [current_cumulative - 1] + 1); 1090 1091 current_tag.IsLink = true; 1092 current_tag.LinkText = link_text; 1093 1094 continue; 1095 } 1096 1097 if (current_tag.End < index_found + 1 + i - (int)cumulative_length_list [current_cumulative - 1]) { 1098 // skip empty tags in the middle of the URL 1099 do { 1100 current_tag = current_tag.Next; 1101 } while (current_tag.Length == 0); 1102 1103 current_tag.IsLink = true; 1104 current_tag.LinkText = link_text; 1105 } 1106 } 1107 1108 //if there are characters left in the tag after the link 1109 // split the tag 1110 // make the second part a non link 1111 if (current_tag.End > (index_found + link_text.Length + 1) - (int)cumulative_length_list [current_cumulative - 1]) { 1112 if (current_tag == CaretTag) 1113 update_caret_tag = true; 1114 1115 current_tag.Break ((index_found + link_text.Length + 1) - (int)cumulative_length_list [current_cumulative - 1]); 1116 } 1117 } 1118 1119 if (update_caret_tag) { 1120 CaretTag = LineTag.FindTag (CaretLine, CaretPosition); 1121 link_changed = true; 1122 } else { 1123 if (link_changed == false) { 1124 current_line = start_line; 1125 StringBuilder new_link_record = new StringBuilder (); 1126 1127 while (current_line != null) { 1128 current_line.LinkRecord (new_link_record); 1129 1130 if (current_line.ending == LineEnding.Wrap) 1131 current_line = GetLine (current_line.LineNo + 1); 1132 else 1133 break; 1134 } 1135 1136 if (new_link_record.Equals (line_link_record) == false) 1137 link_changed = true; 1138 } 1139 } 1140 } 1141 FirstIndexOfAny(string haystack, string [] needles, int start_index, out int term_found)1142 private int FirstIndexOfAny (string haystack, string [] needles, int start_index, out int term_found) 1143 { 1144 term_found = -1; 1145 int best_index = -1; 1146 1147 for (int i = 0; i < needles.Length; i++) { 1148 int index = haystack.IndexOf (needles [i], start_index, StringComparison.InvariantCultureIgnoreCase); 1149 1150 if (index > -1) { 1151 if (term_found > -1) { 1152 if (index < best_index) { 1153 best_index = index; 1154 term_found = i; 1155 } 1156 } else { 1157 best_index = index; 1158 term_found = i; 1159 } 1160 } 1161 } 1162 1163 return best_index; 1164 } 1165 1166 1167 InvalidateLinks(Rectangle clip)1168 private void InvalidateLinks (Rectangle clip) 1169 { 1170 for (int i = (owner.list_links.Count - 1); i >= 0; i--) { 1171 TextBoxBase.LinkRectangle link = (TextBoxBase.LinkRectangle) owner.list_links [i]; 1172 1173 if (clip.IntersectsWith (link.LinkAreaRectangle)) 1174 owner.list_links.RemoveAt (i); 1175 } 1176 } 1177 #endregion // Private Methods 1178 1179 #region Internal Methods 1180 ScanForLinks(int start, int end, ref bool link_changed)1181 internal void ScanForLinks (int start, int end, ref bool link_changed) 1182 { 1183 Line line = null; 1184 LineEnding lastending = LineEnding.Rich; 1185 1186 // make sure we start scanning at the real begining of the line 1187 while (true) { 1188 if (start != 1 && GetLine (start - 1).ending == LineEnding.Wrap) 1189 start--; 1190 else 1191 break; 1192 } 1193 1194 for (int i = start; i <= end && i <= lines; i++) { 1195 line = GetLine (i); 1196 1197 if (lastending != LineEnding.Wrap) 1198 ScanForLinks (line, ref link_changed); 1199 1200 lastending = line.ending; 1201 1202 if (lastending == LineEnding.Wrap && (i + 1) <= end) 1203 end++; 1204 } 1205 } 1206 1207 // Clear the document and reset state Empty()1208 internal void Empty() { 1209 1210 document = sentinel; 1211 lines = 0; 1212 1213 // We always have a blank line 1214 Add (1, String.Empty, owner.Font, owner.ForeColor, LineEnding.None); 1215 1216 this.RecalculateDocument(owner.CreateGraphicsInternal()); 1217 PositionCaret(0, 0); 1218 1219 SetSelectionVisible (false); 1220 1221 selection_start.line = this.document; 1222 selection_start.pos = 0; 1223 selection_start.tag = selection_start.line.tags; 1224 selection_end.line = this.document; 1225 selection_end.pos = 0; 1226 selection_end.tag = selection_end.line.tags; 1227 char_count = 0; 1228 1229 viewport_x = 0; 1230 viewport_y = 0; 1231 1232 document_x = 0; 1233 document_y = 0; 1234 1235 if (owner.IsHandleCreated) 1236 owner.Invalidate (); 1237 } 1238 PositionCaret(Line line, int pos)1239 internal void PositionCaret(Line line, int pos) { 1240 caret.tag = line.FindTag (pos); 1241 1242 MoveCaretToTextTag (); 1243 1244 caret.line = line; 1245 caret.pos = pos; 1246 1247 if (owner.IsHandleCreated) { 1248 if (owner.Focused) { 1249 if (caret.height != caret.tag.Height) 1250 XplatUI.CreateCaret (owner.Handle, caret_width, caret.height); 1251 XplatUI.SetCaretPos(owner.Handle, 1252 offset_x + (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x, 1253 offset_y + caret.line.Y + caret.tag.Shift - viewport_y + caret_shift); 1254 } 1255 1256 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty); 1257 } 1258 1259 // We set this at the end because we use the heights to determine whether or 1260 // not we need to recreate the caret 1261 caret.height = caret.tag.Height; 1262 1263 } 1264 PositionCaret(int x, int y)1265 internal void PositionCaret(int x, int y) { 1266 if (!owner.IsHandleCreated) { 1267 return; 1268 } 1269 1270 caret.tag = FindCursor(x, y, out caret.pos); 1271 1272 MoveCaretToTextTag (); 1273 1274 caret.line = caret.tag.Line; 1275 caret.height = caret.tag.Height; 1276 1277 if (owner.ShowSelection && (!selection_visible || owner.show_caret_w_selection)) { 1278 XplatUI.CreateCaret (owner.Handle, caret_width, caret.height); 1279 XplatUI.SetCaretPos(owner.Handle, 1280 (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x + offset_x, 1281 offset_y + caret.line.Y + caret.tag.Shift - viewport_y + caret_shift); 1282 } 1283 1284 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty); 1285 } 1286 CaretHasFocus()1287 internal void CaretHasFocus() { 1288 if ((caret.tag != null) && owner.IsHandleCreated) { 1289 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height); 1290 XplatUI.SetCaretPos(owner.Handle, 1291 offset_x + (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x, 1292 offset_y + caret.line.Y + caret.tag.Shift - viewport_y + caret_shift); 1293 1294 DisplayCaret (); 1295 } 1296 1297 if (owner.IsHandleCreated && SelectionLength () > 0) { 1298 InvalidateSelectionArea (); 1299 } 1300 } 1301 CaretLostFocus()1302 internal void CaretLostFocus() { 1303 if (!owner.IsHandleCreated) { 1304 return; 1305 } 1306 XplatUI.DestroyCaret(owner.Handle); 1307 } 1308 AlignCaret()1309 internal void AlignCaret () 1310 { 1311 AlignCaret (true); 1312 } 1313 AlignCaret(bool changeCaretTag)1314 internal void AlignCaret(bool changeCaretTag) { 1315 if (!owner.IsHandleCreated) { 1316 return; 1317 } 1318 1319 if (changeCaretTag) { 1320 caret.tag = LineTag.FindTag (caret.line, caret.pos); 1321 1322 MoveCaretToTextTag (); 1323 } 1324 1325 // if the caret has had SelectionFont changed to a 1326 // different height, we reflect changes unless the new 1327 // font is larger than the line (line recalculations 1328 // ignore empty tags) in which case we make it equal 1329 // the line height and then when text is entered 1330 if (caret.tag.Height > caret.tag.Line.Height) { 1331 caret.height = caret.line.height; 1332 } else { 1333 caret.height = caret.tag.Height; 1334 } 1335 1336 if (owner.Focused) { 1337 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height); 1338 XplatUI.SetCaretPos (owner.Handle, 1339 offset_x + (int) caret.tag.Line.widths [caret.pos] + caret.line.X - viewport_x, 1340 offset_y + caret.line.Y + viewport_y + caret_shift); 1341 DisplayCaret (); 1342 } 1343 1344 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty); 1345 } 1346 UpdateCaret()1347 internal void UpdateCaret() { 1348 if (!owner.IsHandleCreated || caret.tag == null) { 1349 return; 1350 } 1351 1352 MoveCaretToTextTag (); 1353 1354 if (caret.tag.Height != caret.height) { 1355 caret.height = caret.tag.Height; 1356 if (owner.Focused) { 1357 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height); 1358 } 1359 } 1360 1361 if (owner.Focused) { 1362 XplatUI.SetCaretPos(owner.Handle, 1363 offset_x + (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x, 1364 offset_y + caret.line.Y + caret.tag.Shift - viewport_y + caret_shift); 1365 DisplayCaret (); 1366 } 1367 1368 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty); 1369 } 1370 DisplayCaret()1371 internal void DisplayCaret() { 1372 if (!owner.IsHandleCreated) { 1373 return; 1374 } 1375 1376 if (owner.ShowSelection && (!selection_visible || owner.show_caret_w_selection)) { 1377 XplatUI.CaretVisible(owner.Handle, true); 1378 } 1379 } 1380 HideCaret()1381 internal void HideCaret() { 1382 if (!owner.IsHandleCreated) { 1383 return; 1384 } 1385 1386 if (owner.Focused) { 1387 XplatUI.CaretVisible(owner.Handle, false); 1388 } 1389 } 1390 1391 MoveCaretToTextTag()1392 internal void MoveCaretToTextTag () 1393 { 1394 if (caret.tag == null || caret.tag.IsTextTag) 1395 return; 1396 1397 1398 1399 if (caret.pos < caret.tag.Start) { 1400 caret.tag = caret.tag.Previous; 1401 } else { 1402 caret.tag = caret.tag.Next; 1403 } 1404 } 1405 MoveCaret(CaretDirection direction)1406 internal void MoveCaret(CaretDirection direction) { 1407 // FIXME should we use IsWordSeparator to detect whitespace, instead 1408 // of looking for actual spaces in the Word move cases? 1409 1410 bool nowrap = false; 1411 switch(direction) { 1412 case CaretDirection.CharForwardNoWrap: 1413 nowrap = true; 1414 goto case CaretDirection.CharForward; 1415 case CaretDirection.CharForward: { 1416 caret.pos++; 1417 if (caret.pos > caret.line.TextLengthWithoutEnding ()) { 1418 if (!nowrap) { 1419 // Go into next line 1420 if (caret.line.line_no < this.lines) { 1421 caret.line = GetLine(caret.line.line_no+1); 1422 caret.pos = 0; 1423 caret.tag = caret.line.tags; 1424 } else { 1425 caret.pos--; 1426 } 1427 } else { 1428 // Single line; we stay where we are 1429 caret.pos--; 1430 } 1431 } else { 1432 if ((caret.tag.Start - 1 + caret.tag.Length) < caret.pos) { 1433 caret.tag = caret.tag.Next; 1434 } 1435 } 1436 UpdateCaret(); 1437 return; 1438 } 1439 1440 case CaretDirection.CharBackNoWrap: 1441 nowrap = true; 1442 goto case CaretDirection.CharBack; 1443 case CaretDirection.CharBack: { 1444 if (caret.pos > 0) { 1445 // caret.pos--; // folded into the if below 1446 1447 if (--caret.pos > 0) { 1448 if (caret.tag.Start > caret.pos) { 1449 caret.tag = caret.tag.Previous; 1450 } 1451 } 1452 } else { 1453 if (caret.line.line_no > 1 && !nowrap) { 1454 caret.line = GetLine(caret.line.line_no - 1); 1455 caret.pos = caret.line.TextLengthWithoutEnding (); 1456 caret.tag = LineTag.FindTag(caret.line, caret.pos); 1457 } 1458 } 1459 UpdateCaret(); 1460 return; 1461 } 1462 1463 case CaretDirection.WordForward: { 1464 int len; 1465 1466 len = caret.line.text.Length; 1467 if (caret.pos < len) { 1468 while ((caret.pos < len) && (caret.line.text[caret.pos] != ' ')) { 1469 caret.pos++; 1470 } 1471 if (caret.pos < len) { 1472 // Skip any whitespace 1473 while ((caret.pos < len) && (caret.line.text[caret.pos] == ' ')) { 1474 caret.pos++; 1475 } 1476 } 1477 caret.tag = LineTag.FindTag(caret.line, caret.pos); 1478 } else { 1479 if (caret.line.line_no < this.lines) { 1480 caret.line = GetLine(caret.line.line_no + 1); 1481 caret.pos = 0; 1482 caret.tag = caret.line.tags; 1483 } 1484 } 1485 UpdateCaret(); 1486 return; 1487 } 1488 1489 case CaretDirection.WordBack: { 1490 if (caret.pos > 0) { 1491 caret.pos--; 1492 1493 while ((caret.pos > 0) && (caret.line.text[caret.pos] == ' ')) { 1494 caret.pos--; 1495 } 1496 1497 while ((caret.pos > 0) && (caret.line.text[caret.pos] != ' ')) { 1498 caret.pos--; 1499 } 1500 1501 if (caret.line.text.ToString(caret.pos, 1) == " ") { 1502 if (caret.pos != 0) { 1503 caret.pos++; 1504 } else { 1505 caret.line = GetLine(caret.line.line_no - 1); 1506 caret.pos = caret.line.text.Length; 1507 } 1508 } 1509 caret.tag = LineTag.FindTag(caret.line, caret.pos); 1510 } else { 1511 if (caret.line.line_no > 1) { 1512 caret.line = GetLine(caret.line.line_no - 1); 1513 caret.pos = caret.line.text.Length; 1514 caret.tag = LineTag.FindTag(caret.line, caret.pos); 1515 } 1516 } 1517 UpdateCaret(); 1518 return; 1519 } 1520 1521 case CaretDirection.LineUp: { 1522 if (caret.line.line_no > 1) { 1523 int pixel; 1524 1525 pixel = (int)caret.line.widths[caret.pos]; 1526 PositionCaret(pixel, GetLine(caret.line.line_no - 1).Y); 1527 1528 DisplayCaret (); 1529 } 1530 return; 1531 } 1532 1533 case CaretDirection.LineDown: { 1534 if (caret.line.line_no < lines) { 1535 int pixel; 1536 1537 pixel = (int)caret.line.widths[caret.pos]; 1538 PositionCaret(pixel, GetLine(caret.line.line_no + 1).Y); 1539 1540 DisplayCaret (); 1541 } 1542 return; 1543 } 1544 1545 case CaretDirection.Home: { 1546 if (caret.pos > 0) { 1547 caret.pos = 0; 1548 caret.tag = caret.line.tags; 1549 UpdateCaret(); 1550 } 1551 return; 1552 } 1553 1554 case CaretDirection.End: { 1555 if (caret.pos < caret.line.TextLengthWithoutEnding ()) { 1556 caret.pos = caret.line.TextLengthWithoutEnding (); 1557 caret.tag = LineTag.FindTag(caret.line, caret.pos); 1558 UpdateCaret(); 1559 } 1560 return; 1561 } 1562 1563 case CaretDirection.PgUp: { 1564 1565 if (caret.line.line_no == 1 && owner.richtext) { 1566 owner.vscroll.Value = 0; 1567 Line line = GetLine (1); 1568 PositionCaret (line, 0); 1569 } 1570 1571 int y_offset = caret.line.Y + caret.line.height - 1 - viewport_y; 1572 int index; 1573 LineTag top = FindCursor ((int) caret.line.widths [caret.pos], 1574 viewport_y - viewport_height, out index); 1575 1576 owner.vscroll.Value = Math.Min (top.Line.Y, owner.vscroll.Maximum - viewport_height); 1577 PositionCaret ((int) caret.line.widths [caret.pos], y_offset + viewport_y); 1578 1579 return; 1580 } 1581 1582 case CaretDirection.PgDn: { 1583 1584 if (caret.line.line_no == lines && owner.richtext) { 1585 owner.vscroll.Value = owner.vscroll.Maximum - viewport_height + 1; 1586 Line line = GetLine (lines); 1587 PositionCaret (line, line.TextLengthWithoutEnding()); 1588 } 1589 1590 int y_offset = caret.line.Y - viewport_y; 1591 int index; 1592 LineTag top = FindCursor ((int) caret.line.widths [caret.pos], 1593 viewport_y + viewport_height, out index); 1594 1595 owner.vscroll.Value = Math.Min (top.Line.Y, owner.vscroll.Maximum - viewport_height); 1596 PositionCaret ((int) caret.line.widths [caret.pos], y_offset + viewport_y); 1597 1598 return; 1599 } 1600 1601 case CaretDirection.CtrlPgUp: { 1602 PositionCaret(0, viewport_y); 1603 DisplayCaret (); 1604 return; 1605 } 1606 1607 case CaretDirection.CtrlPgDn: { 1608 Line line; 1609 LineTag tag; 1610 int index; 1611 1612 tag = FindCursor (0, viewport_y + viewport_height, out index); 1613 if (tag.Line.line_no > 1) { 1614 line = GetLine(tag.Line.line_no - 1); 1615 } else { 1616 line = tag.Line; 1617 } 1618 PositionCaret(line, line.Text.Length); 1619 DisplayCaret (); 1620 return; 1621 } 1622 1623 case CaretDirection.CtrlHome: { 1624 caret.line = GetLine(1); 1625 caret.pos = 0; 1626 caret.tag = caret.line.tags; 1627 1628 UpdateCaret(); 1629 return; 1630 } 1631 1632 case CaretDirection.CtrlEnd: { 1633 caret.line = GetLine(lines); 1634 caret.pos = caret.line.TextLengthWithoutEnding (); 1635 caret.tag = LineTag.FindTag(caret.line, caret.pos); 1636 1637 UpdateCaret(); 1638 return; 1639 } 1640 1641 case CaretDirection.SelectionStart: { 1642 caret.line = selection_start.line; 1643 caret.pos = selection_start.pos; 1644 caret.tag = selection_start.tag; 1645 1646 UpdateCaret(); 1647 return; 1648 } 1649 1650 case CaretDirection.SelectionEnd: { 1651 caret.line = selection_end.line; 1652 caret.pos = selection_end.pos; 1653 caret.tag = selection_end.tag; 1654 1655 UpdateCaret(); 1656 return; 1657 } 1658 } 1659 } 1660 DumpDoc()1661 internal void DumpDoc () 1662 { 1663 Console.WriteLine ("<doc lines='{0}'>", lines); 1664 for (int i = 1; i <= lines ; i++) { 1665 Line line = GetLine (i); 1666 Console.WriteLine ("<line no='{0}' ending='{1}'>", line.line_no, line.ending); 1667 1668 LineTag tag = line.tags; 1669 while (tag != null) { 1670 Console.Write ("\t<tag type='{0}' span='{1}->{2}' font='{3}' color='{4}'>", 1671 tag.GetType (), tag.Start, tag.Length, tag.Font, tag.Color); 1672 Console.Write (tag.Text ()); 1673 Console.WriteLine ("</tag>"); 1674 tag = tag.Next; 1675 } 1676 Console.WriteLine ("</line>"); 1677 } 1678 Console.WriteLine ("</doc>"); 1679 } 1680 1681 // UIA: Used via reflection by TextProviderBehavior GetVisibleLineIndexes(Rectangle clip, out int start, out int end)1682 internal void GetVisibleLineIndexes (Rectangle clip, out int start, out int end) 1683 { 1684 if (multiline) { 1685 /* Expand the region slightly to be sure to 1686 * paint the full extent of the line of text. 1687 * See bug 464464. 1688 */ 1689 start = GetLineByPixel(clip.Top + viewport_y - offset_y - 1, false).line_no; 1690 end = GetLineByPixel(clip.Bottom + viewport_y - offset_y + 1, false).line_no; 1691 } else { 1692 start = GetLineByPixel(clip.Left + viewport_x - offset_x, false).line_no; 1693 end = GetLineByPixel(clip.Right + viewport_x - offset_x, false).line_no; 1694 } 1695 } 1696 Draw(Graphics g, Rectangle clip)1697 internal void Draw (Graphics g, Rectangle clip) 1698 { 1699 Line line; // Current line being drawn 1700 LineTag tag; // Current tag being drawn 1701 int start; // First line to draw 1702 int end; // Last line to draw 1703 StringBuilder text; // String representing the current line 1704 int line_no; 1705 Color tag_color; 1706 Color current_color; 1707 1708 // First, figure out from what line to what line we need to draw 1709 GetVisibleLineIndexes (clip, out start, out end); 1710 1711 // remove links in the list (used for mouse down events) that are within the clip area. 1712 InvalidateLinks (clip); 1713 1714 /// 1715 /// We draw the single border ourself 1716 /// 1717 if (owner.actual_border_style == BorderStyle.FixedSingle) { 1718 ControlPaint.DrawBorder (g, owner.ClientRectangle, Color.Black, ButtonBorderStyle.Solid); 1719 } 1720 1721 /// Make sure that we aren't drawing one more line then we need to 1722 line = GetLine (end - 1); 1723 if (line != null && clip.Bottom == offset_y + line.Y + line.height - viewport_y) 1724 end--; 1725 1726 line_no = start; 1727 1728 #if Debug 1729 DateTime n = DateTime.Now; 1730 Console.WriteLine ("Started drawing: {0}s {1}ms", n.Second, n.Millisecond); 1731 Console.WriteLine ("CLIP: {0}", clip); 1732 Console.WriteLine ("S: {0}", GetLine (start).text); 1733 Console.WriteLine ("E: {0}", GetLine (end).text); 1734 #endif 1735 1736 // Non multiline selection can be handled outside of the loop 1737 if (!multiline && selection_visible && owner.ShowSelection) { 1738 g.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (ThemeEngine.Current.ColorHighlight), 1739 offset_x + selection_start.line.widths [selection_start.pos] + 1740 selection_start.line.X - viewport_x, 1741 offset_y + selection_start.line.Y, 1742 (selection_end.line.X + selection_end.line.widths [selection_end.pos]) - 1743 (selection_start.line.X + selection_start.line.widths [selection_start.pos]), 1744 selection_start.line.height); 1745 } 1746 1747 while (line_no <= end) { 1748 line = GetLine (line_no); 1749 float line_y = line.Y - viewport_y + offset_y; 1750 1751 tag = line.tags; 1752 if (!calc_pass) { 1753 text = line.text; 1754 } else { 1755 if (PasswordCache.Length < line.text.Length) 1756 PasswordCache.Append(Char.Parse(password_char), line.text.Length - PasswordCache.Length); 1757 else if (PasswordCache.Length > line.text.Length) 1758 PasswordCache.Remove(line.text.Length, PasswordCache.Length - line.text.Length); 1759 text = PasswordCache; 1760 } 1761 1762 int line_selection_start = text.Length + 1; 1763 int line_selection_end = text.Length + 1; 1764 if (selection_visible && owner.ShowSelection && 1765 (line_no >= selection_start.line.line_no) && 1766 (line_no <= selection_end.line.line_no)) { 1767 1768 if (line_no == selection_start.line.line_no) 1769 line_selection_start = selection_start.pos + 1; 1770 else 1771 line_selection_start = 1; 1772 1773 if (line_no == selection_end.line.line_no) 1774 line_selection_end = selection_end.pos + 1; 1775 else 1776 line_selection_end = text.Length + 1; 1777 1778 if (line_selection_end == line_selection_start) { 1779 // There isn't really selection 1780 line_selection_start = text.Length + 1; 1781 line_selection_end = line_selection_start; 1782 } else if (multiline) { 1783 // lets draw some selection baby!! (non multiline selection is drawn outside the loop) 1784 g.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (ThemeEngine.Current.ColorHighlight), 1785 offset_x + line.widths [line_selection_start - 1] + line.X - viewport_x, 1786 line_y, line.widths [line_selection_end - 1] - line.widths [line_selection_start - 1], 1787 line.height); 1788 } 1789 } 1790 1791 current_color = line.tags.ColorToDisplay; 1792 while (tag != null) { 1793 1794 // Skip empty tags 1795 if (tag.Length == 0) { 1796 tag = tag.Next; 1797 continue; 1798 } 1799 1800 if (((tag.X + tag.Width) < (clip.Left - viewport_x - offset_x)) && 1801 (tag.X > (clip.Right - viewport_x - offset_x))) { 1802 tag = tag.Next; 1803 continue; 1804 } 1805 1806 if (tag.BackColor != Color.Empty) { 1807 g.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (tag.BackColor), 1808 offset_x + tag.X + line.X - viewport_x, 1809 line_y + tag.Shift, tag.Width, line.height); 1810 } 1811 1812 tag_color = tag.ColorToDisplay; 1813 current_color = tag_color; 1814 1815 if (!owner.Enabled) { 1816 Color a = tag.Color; 1817 Color b = ThemeEngine.Current.ColorWindowText; 1818 1819 if ((a.R == b.R) && (a.G == b.G) && (a.B == b.B)) 1820 tag_color = ThemeEngine.Current.ColorGrayText; 1821 1822 } 1823 1824 int tag_pos = tag.Start; 1825 current_color = tag_color; 1826 while (tag_pos < tag.Start + tag.Length) { 1827 int old_tag_pos = tag_pos; 1828 1829 if (tag_pos >= line_selection_start && tag_pos < line_selection_end) { 1830 current_color = ThemeEngine.Current.ColorHighlightText; 1831 tag_pos = Math.Min (tag.End, line_selection_end); 1832 } else if (tag_pos < line_selection_start) { 1833 current_color = tag_color; 1834 tag_pos = Math.Min (tag.End, line_selection_start); 1835 } else { 1836 current_color = tag_color; 1837 tag_pos = tag.End; 1838 } 1839 1840 Rectangle text_size; 1841 1842 tag.Draw (g, current_color, 1843 offset_x + line.X - viewport_x, 1844 line_y + tag.Shift, 1845 old_tag_pos - 1, Math.Min (tag.Start + tag.Length, tag_pos) - 1, 1846 text.ToString (), out text_size, tag.IsLink); 1847 1848 if (tag.IsLink) { 1849 TextBoxBase.LinkRectangle link = new TextBoxBase.LinkRectangle (text_size); 1850 link.LinkTag = tag; 1851 owner.list_links.Add (link); 1852 } 1853 } 1854 tag = tag.Next; 1855 } 1856 1857 line.DrawEnding (g, line_y); 1858 line_no++; 1859 } 1860 } 1861 GetLineEnding(string line, int start, out LineEnding ending)1862 private int GetLineEnding (string line, int start, out LineEnding ending) 1863 { 1864 int res; 1865 int rich_index; 1866 1867 if (start >= line.Length) { 1868 ending = LineEnding.Wrap; 1869 return -1; 1870 } 1871 1872 res = line.IndexOf ('\r', start); 1873 rich_index = line.IndexOf ('\n', start); 1874 1875 // Handle the case where we find both of them, and the \n is before the \r 1876 if (res != -1 && rich_index != -1) 1877 if (rich_index < res) { 1878 ending = LineEnding.Rich; 1879 return rich_index; 1880 } 1881 1882 if (res != -1) { 1883 if (res + 2 < line.Length && line [res + 1] == '\r' && line [res + 2] == '\n') { 1884 ending = LineEnding.Soft; 1885 return res; 1886 } 1887 if (res + 1 < line.Length && line [res + 1] == '\n') { 1888 ending = LineEnding.Hard; 1889 return res; 1890 } 1891 ending = LineEnding.Limp; 1892 return res; 1893 } 1894 1895 if (rich_index != -1) { 1896 ending = LineEnding.Rich; 1897 return rich_index; 1898 } 1899 1900 ending = LineEnding.Wrap; 1901 return line.Length; 1902 } 1903 1904 // Get the line ending, but only of the types specified GetLineEnding(string line, int start, out LineEnding ending, LineEnding type)1905 private int GetLineEnding (string line, int start, out LineEnding ending, LineEnding type) 1906 { 1907 int index = start; 1908 int last_length = 0; 1909 1910 do { 1911 index = GetLineEnding (line, index + last_length, out ending); 1912 last_length = LineEndingLength (ending); 1913 } while 1914 ((ending & type) != ending && index != -1); 1915 1916 return index == -1 ? line.Length : index; 1917 } 1918 LineEndingLength(LineEnding ending)1919 internal int LineEndingLength (LineEnding ending) 1920 { 1921 switch (ending) { 1922 case LineEnding.Limp: 1923 case LineEnding.Rich: 1924 return 1; 1925 case LineEnding.Hard: 1926 return 2; 1927 case LineEnding.Soft: 1928 return 3; 1929 } 1930 1931 return 0; 1932 } 1933 LineEndingToString(LineEnding ending)1934 internal string LineEndingToString (LineEnding ending) 1935 { 1936 switch (ending) { 1937 case LineEnding.Limp: 1938 return "\r"; 1939 case LineEnding.Hard: 1940 return "\r\n"; 1941 case LineEnding.Soft: 1942 return "\r\r\n"; 1943 case LineEnding.Rich: 1944 return "\n"; 1945 } 1946 1947 return string.Empty; 1948 } 1949 StringToLineEnding(string ending)1950 internal LineEnding StringToLineEnding (string ending) 1951 { 1952 switch (ending) { 1953 case "\r": 1954 return LineEnding.Limp; 1955 case "\r\n": 1956 return LineEnding.Hard; 1957 case "\r\r\n": 1958 return LineEnding.Soft; 1959 case "\n": 1960 return LineEnding.Rich; 1961 default: 1962 return LineEnding.None; 1963 } 1964 } 1965 Insert(Line line, int pos, bool update_caret, string s)1966 internal void Insert (Line line, int pos, bool update_caret, string s) 1967 { 1968 Insert (line, pos, update_caret, s, line.FindTag (pos)); 1969 } 1970 1971 // Insert text at the given position; use formatting at insertion point for inserted text Insert(Line line, int pos, bool update_caret, string s, LineTag tag)1972 internal void Insert (Line line, int pos, bool update_caret, string s, LineTag tag) 1973 { 1974 int break_index; 1975 int base_line; 1976 int old_line_count; 1977 int count = 1; 1978 LineEnding ending; 1979 Line split_line; 1980 1981 // Don't recalculate while we mess around 1982 SuspendRecalc (); 1983 1984 base_line = line.line_no; 1985 old_line_count = lines; 1986 1987 // Discard chars after any possible -unlikely- end of file 1988 int eof_index = s.IndexOf ('\0'); 1989 if (eof_index != -1) 1990 s = s.Substring (0, eof_index); 1991 1992 break_index = GetLineEnding (s, 0, out ending, LineEnding.Hard | LineEnding.Rich); 1993 1994 // There are no line feeds in our text to be pasted 1995 if (break_index == s.Length) { 1996 line.InsertString (pos, s, tag); 1997 } else { 1998 // Add up to the first line feed to our current position 1999 line.InsertString (pos, s.Substring (0, break_index + LineEndingLength (ending)), tag); 2000 2001 // Split the rest of the original line to a new line 2002 Split (line, pos + (break_index + LineEndingLength (ending))); 2003 line.ending = ending; 2004 break_index += LineEndingLength (ending); 2005 split_line = GetLine (line.line_no + 1); 2006 2007 // Insert brand new lines for any more line feeds in the inserted string 2008 while (true) { 2009 int next_break = GetLineEnding (s, break_index, out ending, LineEnding.Hard | LineEnding.Rich); 2010 2011 if (next_break == s.Length) 2012 break; 2013 2014 string line_text = s.Substring (break_index, next_break - break_index + 2015 LineEndingLength (ending)); 2016 2017 Add (base_line + count, line_text, line.alignment, tag.Font, tag.Color, ending); 2018 2019 Line last = GetLine (base_line + count); 2020 last.ending = ending; 2021 2022 count++; 2023 break_index = next_break + LineEndingLength (ending); 2024 } 2025 2026 // Add the remainder of the insert text to the split 2027 // part of the original line 2028 split_line.InsertString (0, s.Substring (break_index)); 2029 } 2030 2031 // Allow the document to recalculate things 2032 ResumeRecalc (false); 2033 2034 // Update our character count 2035 CharCount += s.Length; 2036 2037 UpdateView (line, lines - old_line_count + 1, pos); 2038 2039 // Move the caret to the end of the inserted text if requested 2040 if (update_caret) { 2041 Line l = GetLine (line.line_no + lines - old_line_count); 2042 PositionCaret (l, l.text.Length); 2043 DisplayCaret (); 2044 } 2045 } 2046 2047 // Inserts a string at the given position InsertString(Line line, int pos, string s)2048 internal void InsertString (Line line, int pos, string s) 2049 { 2050 // Update our character count 2051 CharCount += s.Length; 2052 2053 // Insert the text into the Line 2054 line.InsertString (pos, s); 2055 } 2056 2057 // Inserts a character at the current caret position InsertCharAtCaret(char ch, bool move_caret)2058 internal void InsertCharAtCaret (char ch, bool move_caret) 2059 { 2060 caret.line.InsertString (caret.pos, ch.ToString(), caret.tag); 2061 2062 // Update our character count 2063 CharCount++; 2064 2065 undo.RecordTyping (caret.line, caret.pos, ch); 2066 2067 UpdateView (caret.line, caret.pos); 2068 2069 if (move_caret) { 2070 caret.pos++; 2071 UpdateCaret (); 2072 SetSelectionToCaret (true); 2073 } 2074 } 2075 InsertPicture(Line line, int pos, RTF.Picture picture)2076 internal void InsertPicture (Line line, int pos, RTF.Picture picture) 2077 { 2078 //LineTag next_tag; 2079 LineTag tag; 2080 int len; 2081 2082 len = 1; 2083 2084 // Just a place holder basically 2085 line.text.Insert (pos, "I"); 2086 2087 PictureTag picture_tag = new PictureTag (line, pos + 1, picture); 2088 2089 tag = LineTag.FindTag (line, pos); 2090 picture_tag.CopyFormattingFrom (tag); 2091 /*next_tag = */tag.Break (pos + 1); 2092 picture_tag.Previous = tag; 2093 picture_tag.Next = tag.Next; 2094 tag.Next = picture_tag; 2095 2096 // 2097 // Picture tags need to be surrounded by text tags 2098 // 2099 if (picture_tag.Next == null) { 2100 picture_tag.Next = new LineTag (line, pos + 1); 2101 picture_tag.Next.CopyFormattingFrom (tag); 2102 picture_tag.Next.Previous = picture_tag; 2103 } 2104 2105 tag = picture_tag.Next; 2106 while (tag != null) { 2107 tag.Start += len; 2108 tag = tag.Next; 2109 } 2110 2111 line.Grow (len); 2112 line.recalc = true; 2113 2114 UpdateView (line, pos); 2115 } 2116 DeleteMultiline(Line start_line, int pos, int length)2117 internal void DeleteMultiline (Line start_line, int pos, int length) 2118 { 2119 Marker start = new Marker (); 2120 Marker end = new Marker (); 2121 int start_index = LineTagToCharIndex (start_line, pos); 2122 2123 start.line = start_line; 2124 start.pos = pos; 2125 start.tag = LineTag.FindTag (start_line, pos); 2126 2127 CharIndexToLineTag (start_index + length, out end.line, 2128 out end.tag, out end.pos); 2129 2130 SuspendUpdate (); 2131 2132 if (start.line == end.line) { 2133 DeleteChars (start.line, pos, end.pos - pos); 2134 } else { 2135 2136 // Delete first and last lines 2137 DeleteChars (start.line, start.pos, start.line.text.Length - start.pos); 2138 DeleteChars (end.line, 0, end.pos); 2139 2140 int current = start.line.line_no + 1; 2141 if (current < end.line.line_no) { 2142 for (int i = end.line.line_no - 1; i >= current; i--) { 2143 Delete (i); 2144 } 2145 } 2146 2147 // BIG FAT WARNING - selection_end.line might be stale due 2148 // to the above Delete() call. DONT USE IT before hitting the end of this method! 2149 2150 // Join start and end 2151 Combine (start.line.line_no, current); 2152 } 2153 2154 ResumeUpdate (true); 2155 } 2156 2157 2158 // Deletes n characters at the given position; it will not delete past line limits 2159 // pos is 0-based DeleteChars(Line line, int pos, int count)2160 public void DeleteChars (Line line, int pos, int count) 2161 { 2162 // Reduce our character count 2163 CharCount -= count; 2164 2165 line.DeleteCharacters (pos, count); 2166 2167 if (pos >= line.TextLengthWithoutEnding ()) { 2168 LineEnding ending = line.ending; 2169 GetLineEnding (line.text.ToString (), 0, out ending); 2170 2171 if (ending != line.ending) { 2172 line.ending = ending; 2173 2174 if (!multiline) { 2175 UpdateView (line, lines, pos); 2176 owner.Invalidate (); 2177 return; 2178 } 2179 } 2180 } 2181 if (!multiline) { 2182 UpdateView (line, lines, pos); 2183 owner.Invalidate (); 2184 } else 2185 UpdateView (line, pos); 2186 } 2187 2188 // Deletes a character at or after the given position (depending on forward); it will not delete past line limits DeleteChar(Line line, int pos, bool forward)2189 public void DeleteChar (Line line, int pos, bool forward) 2190 { 2191 if ((pos == 0 && forward == false) || (pos == line.text.Length && forward == true)) 2192 return; 2193 2194 undo.BeginUserAction ("Delete"); 2195 2196 if (forward) { 2197 undo.RecordDeleteString (line, pos, line, pos + 1); 2198 DeleteChars (line, pos, 1); 2199 } else { 2200 undo.RecordDeleteString (line, pos - 1, line, pos); 2201 DeleteChars (line, pos - 1, 1); 2202 } 2203 2204 undo.EndUserAction (); 2205 } 2206 2207 // Combine two lines Combine(int FirstLine, int SecondLine)2208 internal void Combine(int FirstLine, int SecondLine) { 2209 Combine(GetLine(FirstLine), GetLine(SecondLine)); 2210 } 2211 Combine(Line first, Line second)2212 internal void Combine(Line first, Line second) { 2213 LineTag last; 2214 int shift; 2215 2216 // strip the ending off of the first lines text 2217 first.text.Length = first.text.Length - LineEndingLength (first.ending); 2218 2219 // Combine the two tag chains into one 2220 last = first.tags; 2221 2222 // Maintain the line ending style 2223 first.ending = second.ending; 2224 2225 while (last.Next != null) { 2226 last = last.Next; 2227 } 2228 2229 // need to get the shift before setting the next tag since that effects length 2230 shift = last.Start + last.Length - 1; 2231 last.Next = second.tags; 2232 last.Next.Previous = last; 2233 2234 // Fix up references within the chain 2235 last = last.Next; 2236 while (last != null) { 2237 last.Line = first; 2238 last.Start += shift; 2239 last = last.Next; 2240 } 2241 2242 // Combine both lines' strings 2243 first.text.Insert(first.text.Length, second.text.ToString()); 2244 first.Grow(first.text.Length); 2245 2246 // Remove the reference to our (now combined) tags from the doomed line 2247 second.tags = null; 2248 2249 // Renumber lines 2250 DecrementLines(first.line_no + 2); // first.line_no + 1 will be deleted, so we need to start renumbering one later 2251 2252 // Mop up 2253 first.recalc = true; 2254 first.height = 0; // This forces RecalcDocument/UpdateView to redraw from this line on 2255 first.Streamline(lines); 2256 2257 // Update Caret, Selection, etc 2258 if (caret.line == second) { 2259 caret.Combine(first, shift); 2260 } 2261 if (selection_anchor.line == second) { 2262 selection_anchor.Combine(first, shift); 2263 } 2264 if (selection_start.line == second) { 2265 selection_start.Combine(first, shift); 2266 } 2267 if (selection_end.line == second) { 2268 selection_end.Combine(first, shift); 2269 } 2270 2271 #if Debug 2272 Line check_first; 2273 Line check_second; 2274 2275 check_first = GetLine(first.line_no); 2276 check_second = GetLine(check_first.line_no + 1); 2277 2278 Console.WriteLine("Pre-delete: Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y); 2279 #endif 2280 2281 this.Delete(second); 2282 2283 #if Debug 2284 check_first = GetLine(first.line_no); 2285 check_second = GetLine(check_first.line_no + 1); 2286 2287 Console.WriteLine("Post-delete Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y); 2288 #endif 2289 } 2290 2291 // Split the line at the position into two Split(int LineNo, int pos)2292 internal void Split(int LineNo, int pos) { 2293 Line line; 2294 LineTag tag; 2295 2296 line = GetLine(LineNo); 2297 tag = LineTag.FindTag(line, pos); 2298 Split(line, tag, pos); 2299 } 2300 Split(Line line, int pos)2301 internal void Split(Line line, int pos) { 2302 LineTag tag; 2303 2304 tag = LineTag.FindTag(line, pos); 2305 Split(line, tag, pos); 2306 } 2307 2308 ///<summary>Split line at given tag and position into two lines</summary> 2309 ///if more space becomes available on previous line Split(Line line, LineTag tag, int pos)2310 internal void Split(Line line, LineTag tag, int pos) { 2311 LineTag new_tag; 2312 Line new_line; 2313 bool move_caret; 2314 bool move_sel_start; 2315 bool move_sel_end; 2316 2317 move_caret = false; 2318 move_sel_start = false; 2319 move_sel_end = false; 2320 2321 #if DEBUG 2322 SanityCheck(); 2323 2324 if (tag.End < pos) 2325 throw new Exception ("Split called with the wrong tag"); 2326 #endif 2327 2328 // Adjust selection and cursors 2329 if (caret.line == line && caret.pos >= pos) { 2330 move_caret = true; 2331 } 2332 if (selection_start.line == line && selection_start.pos > pos) { 2333 move_sel_start = true; 2334 } 2335 2336 if (selection_end.line == line && selection_end.pos > pos) { 2337 move_sel_end = true; 2338 } 2339 2340 // cover the easy case first 2341 if (pos == line.text.Length) { 2342 Add (line.line_no + 1, String.Empty, line.alignment, tag.Font, tag.Color, line.ending); 2343 2344 new_line = GetLine (line.line_no + 1); 2345 2346 if (move_caret) { 2347 caret.line = new_line; 2348 caret.tag = new_line.tags; 2349 caret.pos = 0; 2350 2351 if (selection_visible == false) { 2352 SetSelectionToCaret (true); 2353 } 2354 } 2355 2356 if (move_sel_start) { 2357 selection_start.line = new_line; 2358 selection_start.pos = 0; 2359 selection_start.tag = new_line.tags; 2360 } 2361 2362 if (move_sel_end) { 2363 selection_end.line = new_line; 2364 selection_end.pos = 0; 2365 selection_end.tag = new_line.tags; 2366 } 2367 2368 #if DEBUG 2369 SanityCheck (); 2370 #endif 2371 return; 2372 } 2373 2374 // We need to move the rest of the text into the new line 2375 Add (line.line_no + 1, line.text.ToString (pos, line.text.Length - pos), line.alignment, tag.Font, tag.Color, line.ending); 2376 2377 // Now transfer our tags from this line to the next 2378 new_line = GetLine(line.line_no + 1); 2379 2380 line.recalc = true; 2381 new_line.recalc = true; 2382 2383 //make sure that if we are at the end of a tag, we start on the begining 2384 //of a new one, if one exists... Stops us creating an empty tag and 2385 //make the operation easier. 2386 if (tag.Next != null && (tag.Next.Start - 1) == pos) 2387 tag = tag.Next; 2388 2389 if ((tag.Start - 1) == pos) { 2390 int shift; 2391 2392 // We can simply break the chain and move the tag into the next line 2393 2394 // if the tag we are moving is the first, create an empty tag 2395 // for the line we are leaving behind 2396 if (tag == line.tags) { 2397 new_tag = new LineTag(line, 1); 2398 new_tag.CopyFormattingFrom (tag); 2399 line.tags = new_tag; 2400 } 2401 2402 if (tag.Previous != null) { 2403 tag.Previous.Next = null; 2404 } 2405 new_line.tags = tag; 2406 tag.Previous = null; 2407 tag.Line = new_line; 2408 2409 // Walk the list and correct the start location of the tags we just bumped into the next line 2410 shift = tag.Start - 1; 2411 2412 new_tag = tag; 2413 while (new_tag != null) { 2414 new_tag.Start -= shift; 2415 new_tag.Line = new_line; 2416 new_tag = new_tag.Next; 2417 } 2418 } else { 2419 int shift; 2420 2421 new_tag = new LineTag (new_line, 1); 2422 new_tag.Next = tag.Next; 2423 new_tag.CopyFormattingFrom (tag); 2424 new_line.tags = new_tag; 2425 if (new_tag.Next != null) { 2426 new_tag.Next.Previous = new_tag; 2427 } 2428 tag.Next = null; 2429 2430 shift = pos; 2431 new_tag = new_tag.Next; 2432 while (new_tag != null) { 2433 new_tag.Start -= shift; 2434 new_tag.Line = new_line; 2435 new_tag = new_tag.Next; 2436 2437 } 2438 } 2439 2440 if (move_caret) { 2441 caret.line = new_line; 2442 caret.pos = caret.pos - pos; 2443 caret.tag = caret.line.FindTag(caret.pos); 2444 2445 if (selection_visible == false) { 2446 SetSelectionToCaret (true); 2447 move_sel_start = false; 2448 move_sel_end = false; 2449 } 2450 } 2451 2452 if (move_sel_start) { 2453 selection_start.line = new_line; 2454 selection_start.pos = selection_start.pos - pos; 2455 if (selection_start.Equals(selection_end)) 2456 selection_start.tag = new_line.FindTag(selection_start.pos); 2457 else 2458 selection_start.tag = new_line.FindTag (selection_start.pos + 1); 2459 } 2460 2461 if (move_sel_end) { 2462 selection_end.line = new_line; 2463 selection_end.pos = selection_end.pos - pos; 2464 selection_end.tag = new_line.FindTag(selection_end.pos); 2465 } 2466 2467 CharCount -= line.text.Length - pos; 2468 line.text.Remove(pos, line.text.Length - pos); 2469 #if DEBUG 2470 SanityCheck (); 2471 #endif 2472 } 2473 2474 #if DEBUG SanityCheck()2475 private void SanityCheck () { 2476 for (int i = 1; i < lines; i++) { 2477 LineTag tag = GetLine (i).tags; 2478 2479 if (tag.Start != 1) 2480 throw new Exception ("Line doesn't start at the begining"); 2481 2482 int start = 1; 2483 tag = tag.Next; 2484 2485 while (tag != null) { 2486 if (tag.Start == start) 2487 throw new Exception ("Empty tag!"); 2488 2489 if (tag.Start < start) 2490 throw new Exception ("Insane!!"); 2491 2492 start = tag.Start; 2493 tag = tag.Next; 2494 } 2495 } 2496 } 2497 #endif 2498 2499 // Adds a line of text, with given font. 2500 // Bumps any line at that line number that already exists down Add(int LineNo, string Text, Font font, Color color, LineEnding ending)2501 internal void Add (int LineNo, string Text, Font font, Color color, LineEnding ending) 2502 { 2503 Add (LineNo, Text, alignment, font, color, ending); 2504 } 2505 Add(int LineNo, string Text, HorizontalAlignment align, Font font, Color color, LineEnding ending)2506 internal void Add (int LineNo, string Text, HorizontalAlignment align, Font font, Color color, LineEnding ending) 2507 { 2508 Line add; 2509 Line line; 2510 int line_no; 2511 2512 CharCount += Text.Length; 2513 2514 if (LineNo<1 || Text == null) { 2515 if (LineNo<1) { 2516 throw new ArgumentNullException("LineNo", "Line numbers must be positive"); 2517 } else { 2518 throw new ArgumentNullException("Text", "Cannot insert NULL line"); 2519 } 2520 } 2521 2522 add = new Line (this, LineNo, Text, align, font, color, ending); 2523 2524 line = document; 2525 while (line != sentinel) { 2526 add.parent = line; 2527 line_no = line.line_no; 2528 2529 if (LineNo > line_no) { 2530 line = line.right; 2531 } else if (LineNo < line_no) { 2532 line = line.left; 2533 } else { 2534 // Bump existing line numbers; walk all nodes to the right of this one and increment line_no 2535 IncrementLines(line.line_no); 2536 line = line.left; 2537 } 2538 } 2539 2540 add.left = sentinel; 2541 add.right = sentinel; 2542 2543 if (add.parent != null) { 2544 if (LineNo > add.parent.line_no) { 2545 add.parent.right = add; 2546 } else { 2547 add.parent.left = add; 2548 } 2549 } else { 2550 // Root node 2551 document = add; 2552 } 2553 2554 RebalanceAfterAdd(add); 2555 2556 lines++; 2557 } 2558 Clear()2559 internal virtual void Clear() { 2560 lines = 0; 2561 CharCount = 0; 2562 document = sentinel; 2563 } 2564 Clone()2565 public virtual object Clone() { 2566 Document clone; 2567 2568 clone = new Document(null); 2569 2570 clone.lines = this.lines; 2571 clone.document = (Line)document.Clone(); 2572 2573 return clone; 2574 } 2575 Delete(int LineNo)2576 private void Delete (int LineNo) 2577 { 2578 Line line; 2579 2580 if (LineNo > lines) 2581 return; 2582 2583 line = GetLine (LineNo); 2584 2585 CharCount -= line.text.Length; 2586 2587 DecrementLines (LineNo + 1); 2588 Delete (line); 2589 } 2590 Delete(Line line1)2591 private void Delete(Line line1) { 2592 Line line2;// = new Line(); 2593 Line line3; 2594 2595 if ((line1.left == sentinel) || (line1.right == sentinel)) { 2596 line3 = line1; 2597 } else { 2598 line3 = line1.right; 2599 while (line3.left != sentinel) { 2600 line3 = line3.left; 2601 } 2602 } 2603 2604 if (line3.left != sentinel) { 2605 line2 = line3.left; 2606 } else { 2607 line2 = line3.right; 2608 } 2609 2610 line2.parent = line3.parent; 2611 if (line3.parent != null) { 2612 if(line3 == line3.parent.left) { 2613 line3.parent.left = line2; 2614 } else { 2615 line3.parent.right = line2; 2616 } 2617 } else { 2618 document = line2; 2619 } 2620 2621 if (line3 != line1) { 2622 LineTag tag; 2623 2624 if (selection_start.line == line3) { 2625 selection_start.line = line1; 2626 } 2627 2628 if (selection_end.line == line3) { 2629 selection_end.line = line1; 2630 } 2631 2632 if (selection_anchor.line == line3) { 2633 selection_anchor.line = line1; 2634 } 2635 2636 if (caret.line == line3) { 2637 caret.line = line1; 2638 } 2639 2640 2641 line1.alignment = line3.alignment; 2642 line1.ascent = line3.ascent; 2643 line1.hanging_indent = line3.hanging_indent; 2644 line1.height = line3.height; 2645 line1.indent = line3.indent; 2646 line1.line_no = line3.line_no; 2647 line1.recalc = line3.recalc; 2648 line1.right_indent = line3.right_indent; 2649 line1.ending = line3.ending; 2650 line1.space = line3.space; 2651 line1.tags = line3.tags; 2652 line1.text = line3.text; 2653 line1.widths = line3.widths; 2654 line1.offset = line3.offset; 2655 2656 tag = line1.tags; 2657 while (tag != null) { 2658 tag.Line = line1; 2659 tag = tag.Next; 2660 } 2661 } 2662 2663 if (line3.color == LineColor.Black) 2664 RebalanceAfterDelete(line2); 2665 2666 this.lines--; 2667 } 2668 2669 // Invalidates the start line until the end of the viewstate InvalidateLinesAfter(Line start)2670 internal void InvalidateLinesAfter (Line start) { 2671 owner.Invalidate (new Rectangle (0, start.Y - viewport_y, viewport_width, viewport_height - start.Y)); 2672 } 2673 2674 // Invalidate a section of the document to trigger redraw Invalidate(Line start, int start_pos, Line end, int end_pos)2675 internal void Invalidate(Line start, int start_pos, Line end, int end_pos) { 2676 Line l1; 2677 Line l2; 2678 int p1; 2679 int p2; 2680 2681 if ((start == end) && (start_pos == end_pos)) { 2682 return; 2683 } 2684 2685 if (end_pos == -1) { 2686 end_pos = end.text.Length; 2687 } 2688 2689 // figure out what's before what so the logic below is straightforward 2690 if (start.line_no < end.line_no) { 2691 l1 = start; 2692 p1 = start_pos; 2693 2694 l2 = end; 2695 p2 = end_pos; 2696 } else if (start.line_no > end.line_no) { 2697 l1 = end; 2698 p1 = end_pos; 2699 2700 l2 = start; 2701 p2 = start_pos; 2702 } else { 2703 if (start_pos < end_pos) { 2704 l1 = start; 2705 p1 = start_pos; 2706 2707 l2 = end; 2708 p2 = end_pos; 2709 } else { 2710 l1 = end; 2711 p1 = end_pos; 2712 2713 l2 = start; 2714 p2 = start_pos; 2715 } 2716 2717 int endpoint = (int) l1.widths [p2]; 2718 if (p2 == l1.text.Length + 1) { 2719 endpoint = (int) viewport_width; 2720 } 2721 2722 #if Debug 2723 Console.WriteLine("Invaliding backwards from {0}:{1} to {2}:{3} {4}", 2724 l1.line_no, p1, l2.line_no, p2, 2725 new Rectangle( 2726 (int)l1.widths[p1] + l1.X - viewport_x, 2727 l1.Y - viewport_y, 2728 (int)l1.widths[p2], 2729 l1.height 2730 ) 2731 ); 2732 #endif 2733 2734 owner.Invalidate(new Rectangle ( 2735 offset_x + (int)l1.widths[p1] + l1.X - viewport_x, 2736 offset_y + l1.Y - viewport_y, 2737 endpoint - (int) l1.widths [p1] + 1, 2738 l1.height)); 2739 return; 2740 } 2741 2742 #if Debug 2743 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.X - viewport_x, l1.Y - viewport_y, viewport_width, l1.height); 2744 Console.WriteLine ("invalidate start line: {0} position: {1}", l1.text, p1); 2745 #endif 2746 2747 // Three invalidates: 2748 // First line from start 2749 owner.Invalidate(new Rectangle( 2750 offset_x + (int)l1.widths[p1] + l1.X - viewport_x, 2751 offset_y + l1.Y - viewport_y, 2752 viewport_width, 2753 l1.height)); 2754 2755 2756 // lines inbetween 2757 if ((l1.line_no + 1) < l2.line_no) { 2758 int y; 2759 2760 y = GetLine(l1.line_no + 1).Y; 2761 owner.Invalidate(new Rectangle( 2762 offset_x, 2763 offset_y + y - viewport_y, 2764 viewport_width, 2765 l2.Y - y)); 2766 2767 #if Debug 2768 Console.WriteLine("Invaliding from {0}:{1} to {2}:{3} Middle => x={4}, y={5}, {6}x{7}", l1.line_no, p1, l2.line_no, p2, 0, y - viewport_y, viewport_width, l2.Y - y); 2769 #endif 2770 } 2771 2772 2773 // Last line to end 2774 owner.Invalidate(new Rectangle( 2775 offset_x + (int)l2.widths[0] + l2.X - viewport_x, 2776 offset_y + l2.Y - viewport_y, 2777 (int)l2.widths[p2] + 1, 2778 l2.height)); 2779 2780 #if Debug 2781 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.X - viewport_x, l2.Y - viewport_y, (int)l2.widths[p2] + 1, l2.height); 2782 #endif 2783 } 2784 2785 /// <summary>Select text around caret</summary> ExpandSelection(CaretSelection mode, bool to_caret)2786 internal void ExpandSelection(CaretSelection mode, bool to_caret) { 2787 if (to_caret) { 2788 // We're expanding the selection to the caret position 2789 switch(mode) { 2790 case CaretSelection.Line: { 2791 // Invalidate the selection delta 2792 if (caret > selection_prev) { 2793 Invalidate(selection_prev.line, 0, caret.line, caret.line.text.Length); 2794 } else { 2795 Invalidate(selection_prev.line, selection_prev.line.text.Length, caret.line, 0); 2796 } 2797 2798 if (caret.line.line_no <= selection_anchor.line.line_no) { 2799 selection_start.line = caret.line; 2800 selection_start.tag = caret.line.tags; 2801 selection_start.pos = 0; 2802 2803 selection_end.line = selection_anchor.line; 2804 selection_end.tag = selection_anchor.tag; 2805 selection_end.pos = selection_anchor.pos; 2806 2807 selection_end_anchor = true; 2808 } else { 2809 selection_start.line = selection_anchor.line; 2810 selection_start.pos = selection_anchor.height; 2811 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height + 1); 2812 2813 selection_end.line = caret.line; 2814 selection_end.tag = caret.line.tags; 2815 selection_end.pos = caret.line.text.Length; 2816 2817 selection_end_anchor = false; 2818 } 2819 selection_prev.line = caret.line; 2820 selection_prev.tag = caret.tag; 2821 selection_prev.pos = caret.pos; 2822 2823 break; 2824 } 2825 2826 case CaretSelection.Word: { 2827 int start_pos; 2828 int end_pos; 2829 2830 start_pos = FindWordSeparator(caret.line, caret.pos, false); 2831 end_pos = FindWordSeparator(caret.line, caret.pos, true); 2832 2833 2834 // Invalidate the selection delta 2835 if (caret > selection_prev) { 2836 Invalidate(selection_prev.line, selection_prev.pos, caret.line, end_pos); 2837 } else { 2838 Invalidate(selection_prev.line, selection_prev.pos, caret.line, start_pos); 2839 } 2840 if (caret < selection_anchor) { 2841 selection_start.line = caret.line; 2842 selection_start.tag = caret.line.FindTag(start_pos + 1); 2843 selection_start.pos = start_pos; 2844 2845 selection_end.line = selection_anchor.line; 2846 selection_end.tag = selection_anchor.tag; 2847 selection_end.pos = selection_anchor.pos; 2848 2849 selection_prev.line = caret.line; 2850 selection_prev.tag = caret.tag; 2851 selection_prev.pos = start_pos; 2852 2853 selection_end_anchor = true; 2854 } else { 2855 selection_start.line = selection_anchor.line; 2856 selection_start.pos = selection_anchor.height; 2857 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height + 1); 2858 2859 selection_end.line = caret.line; 2860 selection_end.tag = caret.line.FindTag(end_pos); 2861 selection_end.pos = end_pos; 2862 2863 selection_prev.line = caret.line; 2864 selection_prev.tag = caret.tag; 2865 selection_prev.pos = end_pos; 2866 2867 selection_end_anchor = false; 2868 } 2869 break; 2870 } 2871 2872 case CaretSelection.Position: { 2873 SetSelectionToCaret(false); 2874 return; 2875 } 2876 } 2877 } else { 2878 // We're setting the selection 'around' the caret position 2879 switch(mode) { 2880 case CaretSelection.Line: { 2881 this.Invalidate(caret.line, 0, caret.line, caret.line.text.Length); 2882 2883 selection_start.line = caret.line; 2884 selection_start.tag = caret.line.tags; 2885 selection_start.pos = 0; 2886 2887 selection_end.line = caret.line; 2888 selection_end.pos = caret.line.text.Length; 2889 selection_end.tag = caret.line.FindTag(selection_end.pos); 2890 2891 selection_anchor.line = selection_end.line; 2892 selection_anchor.tag = selection_end.tag; 2893 selection_anchor.pos = selection_end.pos; 2894 selection_anchor.height = 0; 2895 2896 selection_prev.line = caret.line; 2897 selection_prev.tag = caret.tag; 2898 selection_prev.pos = caret.pos; 2899 2900 this.selection_end_anchor = true; 2901 2902 break; 2903 } 2904 2905 case CaretSelection.Word: { 2906 int start_pos; 2907 int end_pos; 2908 2909 start_pos = FindWordSeparator(caret.line, caret.pos, false); 2910 end_pos = FindWordSeparator(caret.line, caret.pos, true); 2911 2912 this.Invalidate(selection_start.line, start_pos, caret.line, end_pos); 2913 2914 selection_start.line = caret.line; 2915 selection_start.tag = caret.line.FindTag(start_pos + 1); 2916 selection_start.pos = start_pos; 2917 2918 selection_end.line = caret.line; 2919 selection_end.tag = caret.line.FindTag(end_pos); 2920 selection_end.pos = end_pos; 2921 2922 selection_anchor.line = selection_end.line; 2923 selection_anchor.tag = selection_end.tag; 2924 selection_anchor.pos = selection_end.pos; 2925 selection_anchor.height = start_pos; 2926 2927 selection_prev.line = caret.line; 2928 selection_prev.tag = caret.tag; 2929 selection_prev.pos = caret.pos; 2930 2931 this.selection_end_anchor = true; 2932 2933 break; 2934 } 2935 } 2936 } 2937 2938 SetSelectionVisible (!(selection_start == selection_end)); 2939 } 2940 SetSelectionToCaret(bool start)2941 internal void SetSelectionToCaret(bool start) { 2942 if (start) { 2943 // Invalidate old selection; selection is being reset to empty 2944 this.Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos); 2945 2946 selection_start.line = caret.line; 2947 selection_start.tag = caret.tag; 2948 selection_start.pos = caret.pos; 2949 2950 // start always also selects end 2951 selection_end.line = caret.line; 2952 selection_end.tag = caret.tag; 2953 selection_end.pos = caret.pos; 2954 2955 selection_anchor.line = caret.line; 2956 selection_anchor.tag = caret.tag; 2957 selection_anchor.pos = caret.pos; 2958 } else { 2959 // Invalidate from previous end to caret (aka new end) 2960 if (selection_end_anchor) { 2961 if (selection_start != caret) { 2962 this.Invalidate(selection_start.line, selection_start.pos, caret.line, caret.pos); 2963 } 2964 } else { 2965 if (selection_end != caret) { 2966 this.Invalidate(selection_end.line, selection_end.pos, caret.line, caret.pos); 2967 } 2968 } 2969 2970 if (caret < selection_anchor) { 2971 selection_start.line = caret.line; 2972 selection_start.tag = caret.tag; 2973 selection_start.pos = caret.pos; 2974 2975 selection_end.line = selection_anchor.line; 2976 selection_end.tag = selection_anchor.tag; 2977 selection_end.pos = selection_anchor.pos; 2978 2979 selection_end_anchor = true; 2980 } else { 2981 selection_start.line = selection_anchor.line; 2982 selection_start.tag = selection_anchor.tag; 2983 selection_start.pos = selection_anchor.pos; 2984 2985 selection_end.line = caret.line; 2986 selection_end.tag = caret.tag; 2987 selection_end.pos = caret.pos; 2988 2989 selection_end_anchor = false; 2990 } 2991 } 2992 2993 SetSelectionVisible (!(selection_start == selection_end)); 2994 } 2995 SetSelection(Line start, int start_pos, Line end, int end_pos)2996 internal void SetSelection(Line start, int start_pos, Line end, int end_pos) { 2997 if (selection_visible) { 2998 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos); 2999 } 3000 3001 if ((end.line_no < start.line_no) || ((end == start) && (end_pos <= start_pos))) { 3002 selection_start.line = end; 3003 selection_start.tag = LineTag.FindTag(end, end_pos); 3004 selection_start.pos = end_pos; 3005 3006 selection_end.line = start; 3007 selection_end.tag = LineTag.FindTag(start, start_pos); 3008 selection_end.pos = start_pos; 3009 3010 selection_end_anchor = true; 3011 } else { 3012 selection_start.line = start; 3013 selection_start.tag = LineTag.FindTag(start, start_pos); 3014 selection_start.pos = start_pos; 3015 3016 selection_end.line = end; 3017 selection_end.tag = LineTag.FindTag(end, end_pos); 3018 selection_end.pos = end_pos; 3019 3020 selection_end_anchor = false; 3021 } 3022 3023 selection_anchor.line = start; 3024 selection_anchor.tag = selection_start.tag; 3025 selection_anchor.pos = start_pos; 3026 3027 if (((start == end) && (start_pos == end_pos)) || start == null || end == null) { 3028 SetSelectionVisible (false); 3029 } else { 3030 SetSelectionVisible (true); 3031 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos); 3032 } 3033 } 3034 SetSelectionStart(Line start, int start_pos, bool invalidate)3035 internal void SetSelectionStart(Line start, int start_pos, bool invalidate) { 3036 // Invalidate from the previous to the new start pos 3037 if (invalidate) 3038 Invalidate(selection_start.line, selection_start.pos, start, start_pos); 3039 3040 selection_start.line = start; 3041 selection_start.pos = start_pos; 3042 selection_start.tag = LineTag.FindTag(start, start_pos); 3043 3044 selection_anchor.line = start; 3045 selection_anchor.pos = start_pos; 3046 selection_anchor.tag = selection_start.tag; 3047 3048 selection_end_anchor = false; 3049 3050 3051 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) { 3052 SetSelectionVisible (true); 3053 } else { 3054 SetSelectionVisible (false); 3055 } 3056 3057 if (invalidate) 3058 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos); 3059 } 3060 SetSelectionStart(int character_index, bool invalidate)3061 internal void SetSelectionStart(int character_index, bool invalidate) { 3062 Line line; 3063 LineTag tag; 3064 int pos; 3065 3066 if (character_index < 0) { 3067 return; 3068 } 3069 3070 CharIndexToLineTag(character_index, out line, out tag, out pos); 3071 SetSelectionStart(line, pos, invalidate); 3072 } 3073 SetSelectionEnd(Line end, int end_pos, bool invalidate)3074 internal void SetSelectionEnd(Line end, int end_pos, bool invalidate) { 3075 3076 if (end == selection_end.line && end_pos == selection_start.pos) { 3077 selection_anchor.line = selection_start.line; 3078 selection_anchor.tag = selection_start.tag; 3079 selection_anchor.pos = selection_start.pos; 3080 3081 selection_end.line = selection_start.line; 3082 selection_end.tag = selection_start.tag; 3083 selection_end.pos = selection_start.pos; 3084 3085 selection_end_anchor = false; 3086 } else if ((end.line_no < selection_anchor.line.line_no) || ((end == selection_anchor.line) && (end_pos <= selection_anchor.pos))) { 3087 selection_start.line = end; 3088 selection_start.tag = LineTag.FindTag(end, end_pos); 3089 selection_start.pos = end_pos; 3090 3091 selection_end.line = selection_anchor.line; 3092 selection_end.tag = selection_anchor.tag; 3093 selection_end.pos = selection_anchor.pos; 3094 3095 selection_end_anchor = true; 3096 } else { 3097 selection_start.line = selection_anchor.line; 3098 selection_start.tag = selection_anchor.tag; 3099 selection_start.pos = selection_anchor.pos; 3100 3101 selection_end.line = end; 3102 selection_end.tag = LineTag.FindTag(end, end_pos); 3103 selection_end.pos = end_pos; 3104 3105 selection_end_anchor = false; 3106 } 3107 3108 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) { 3109 SetSelectionVisible (true); 3110 if (invalidate) 3111 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos); 3112 } else { 3113 SetSelectionVisible (false); 3114 // ?? Do I need to invalidate here, tests seem to work without it, but I don't think they should :-s 3115 } 3116 } 3117 SetSelectionEnd(int character_index, bool invalidate)3118 internal void SetSelectionEnd(int character_index, bool invalidate) { 3119 Line line; 3120 LineTag tag; 3121 int pos; 3122 3123 if (character_index < 0) { 3124 return; 3125 } 3126 3127 CharIndexToLineTag(character_index, out line, out tag, out pos); 3128 SetSelectionEnd(line, pos, invalidate); 3129 } 3130 SetSelection(Line start, int start_pos)3131 internal void SetSelection(Line start, int start_pos) { 3132 if (selection_visible) { 3133 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos); 3134 } 3135 3136 selection_start.line = start; 3137 selection_start.pos = start_pos; 3138 selection_start.tag = LineTag.FindTag(start, start_pos); 3139 3140 selection_end.line = start; 3141 selection_end.tag = selection_start.tag; 3142 selection_end.pos = start_pos; 3143 3144 selection_anchor.line = start; 3145 selection_anchor.tag = selection_start.tag; 3146 selection_anchor.pos = start_pos; 3147 3148 selection_end_anchor = false; 3149 SetSelectionVisible (false); 3150 } 3151 InvalidateSelectionArea()3152 internal void InvalidateSelectionArea() { 3153 Invalidate (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos); 3154 } 3155 3156 // Return the current selection, as string GetSelection()3157 internal string GetSelection() { 3158 // We return String.Empty if there is no selection 3159 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) { 3160 return string.Empty; 3161 } 3162 3163 if (selection_start.line == selection_end.line) { 3164 return selection_start.line.text.ToString (selection_start.pos, selection_end.pos - selection_start.pos); 3165 } else { 3166 StringBuilder sb; 3167 int i; 3168 int start; 3169 int end; 3170 3171 sb = new StringBuilder(); 3172 start = selection_start.line.line_no; 3173 end = selection_end.line.line_no; 3174 3175 sb.Append(selection_start.line.text.ToString(selection_start.pos, selection_start.line.text.Length - selection_start.pos)); 3176 3177 if ((start + 1) < end) { 3178 for (i = start + 1; i < end; i++) { 3179 sb.Append(GetLine(i).text.ToString()); 3180 } 3181 } 3182 3183 sb.Append(selection_end.line.text.ToString(0, selection_end.pos)); 3184 3185 return sb.ToString(); 3186 } 3187 } 3188 ReplaceSelection(string s, bool select_new)3189 internal void ReplaceSelection(string s, bool select_new) { 3190 int i; 3191 3192 int selection_start_pos = LineTagToCharIndex (selection_start.line, selection_start.pos); 3193 SuspendRecalc (); 3194 3195 // First, delete any selected text 3196 if ((selection_start.pos != selection_end.pos) || (selection_start.line != selection_end.line)) { 3197 if (selection_start.line == selection_end.line) { 3198 undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos); 3199 3200 DeleteChars (selection_start.line, selection_start.pos, selection_end.pos - selection_start.pos); 3201 3202 // The tag might have been removed, we need to recalc it 3203 selection_start.tag = selection_start.line.FindTag(selection_start.pos + 1); 3204 } else { 3205 int start; 3206 int end; 3207 3208 start = selection_start.line.line_no; 3209 end = selection_end.line.line_no; 3210 3211 undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos); 3212 3213 InvalidateLinesAfter(selection_start.line); 3214 3215 // Delete first line 3216 DeleteChars (selection_start.line, selection_start.pos, selection_start.line.text.Length - selection_start.pos); 3217 selection_start.line.recalc = true; 3218 3219 // Delete last line 3220 DeleteChars(selection_end.line, 0, selection_end.pos); 3221 3222 start++; 3223 if (start < end) { 3224 for (i = end - 1; i >= start; i--) { 3225 Delete(i); 3226 } 3227 } 3228 3229 // BIG FAT WARNING - selection_end.line might be stale due 3230 // to the above Delete() call. DONT USE IT before hitting the end of this method! 3231 3232 // Join start and end 3233 Combine(selection_start.line.line_no, start); 3234 } 3235 } 3236 3237 3238 Insert(selection_start.line, selection_start.pos, false, s); 3239 undo.RecordInsertString (selection_start.line, selection_start.pos, s); 3240 ResumeRecalc (false); 3241 3242 Line begin_update_line = selection_start.line; 3243 int begin_update_pos = selection_start.pos; 3244 3245 if (!select_new) { 3246 CharIndexToLineTag(selection_start_pos + s.Length, out selection_start.line, 3247 out selection_start.tag, out selection_start.pos); 3248 3249 selection_end.line = selection_start.line; 3250 selection_end.pos = selection_start.pos; 3251 selection_end.tag = selection_start.tag; 3252 selection_anchor.line = selection_start.line; 3253 selection_anchor.pos = selection_start.pos; 3254 selection_anchor.tag = selection_start.tag; 3255 3256 SetSelectionVisible (false); 3257 } else { 3258 CharIndexToLineTag(selection_start_pos, out selection_start.line, 3259 out selection_start.tag, out selection_start.pos); 3260 3261 CharIndexToLineTag(selection_start_pos + s.Length, out selection_end.line, 3262 out selection_end.tag, out selection_end.pos); 3263 3264 selection_anchor.line = selection_start.line; 3265 selection_anchor.pos = selection_start.pos; 3266 selection_anchor.tag = selection_start.tag; 3267 3268 SetSelectionVisible (true); 3269 } 3270 3271 PositionCaret (selection_start.line, selection_start.pos); 3272 UpdateView (begin_update_line, selection_end.line.line_no - begin_update_line.line_no, begin_update_pos); 3273 } 3274 CharIndexToLineTag(int index, out Line line_out, out LineTag tag_out, out int pos)3275 internal void CharIndexToLineTag(int index, out Line line_out, out LineTag tag_out, out int pos) { 3276 Line line; 3277 LineTag tag; 3278 int i; 3279 int chars; 3280 int start; 3281 3282 chars = 0; 3283 3284 for (i = 1; i <= lines; i++) { 3285 line = GetLine(i); 3286 3287 start = chars; 3288 chars += line.text.Length; 3289 3290 if (index <= chars) { 3291 // we found the line 3292 tag = line.tags; 3293 3294 while (tag != null) { 3295 if (index < (start + tag.Start + tag.Length - 1)) { 3296 line_out = line; 3297 tag_out = LineTag.GetFinalTag (tag); 3298 pos = index - start; 3299 return; 3300 } 3301 if (tag.Next == null) { 3302 Line next_line; 3303 3304 next_line = GetLine(line.line_no + 1); 3305 3306 if (next_line != null) { 3307 line_out = next_line; 3308 tag_out = LineTag.GetFinalTag (next_line.tags); 3309 pos = 0; 3310 return; 3311 } else { 3312 line_out = line; 3313 tag_out = LineTag.GetFinalTag (tag); 3314 pos = line_out.text.Length; 3315 return; 3316 } 3317 } 3318 tag = tag.Next; 3319 } 3320 } 3321 } 3322 3323 line_out = GetLine(lines); 3324 tag = line_out.tags; 3325 while (tag.Next != null) { 3326 tag = tag.Next; 3327 } 3328 tag_out = tag; 3329 pos = line_out.text.Length; 3330 } 3331 LineTagToCharIndex(Line line, int pos)3332 internal int LineTagToCharIndex(Line line, int pos) { 3333 int i; 3334 int length; 3335 3336 // Count first and last line 3337 length = 0; 3338 3339 // Count the lines in the middle 3340 3341 for (i = 1; i < line.line_no; i++) { 3342 length += GetLine(i).text.Length; 3343 } 3344 3345 length += pos; 3346 3347 return length; 3348 } 3349 SelectionLength()3350 internal int SelectionLength() { 3351 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) { 3352 return 0; 3353 } 3354 3355 if (selection_start.line == selection_end.line) { 3356 return selection_end.pos - selection_start.pos; 3357 } else { 3358 int i; 3359 int start; 3360 int end; 3361 int length; 3362 3363 // Count first and last line 3364 length = selection_start.line.text.Length - selection_start.pos + selection_end.pos + crlf_size; 3365 3366 // Count the lines in the middle 3367 start = selection_start.line.line_no + 1; 3368 end = selection_end.line.line_no; 3369 3370 if (start < end) { 3371 for (i = start; i < end; i++) { 3372 Line line = GetLine (i); 3373 length += line.text.Length + LineEndingLength (line.ending); 3374 } 3375 } 3376 3377 return length; 3378 } 3379 3380 3381 } 3382 3383 3384 // UIA: Method used via reflection in TextRangeProvider 3385 3386 /// <summary>Give it a Line number and it returns the Line object at with that line number</summary> GetLine(int LineNo)3387 internal Line GetLine(int LineNo) { 3388 Line line = document; 3389 3390 while (line != sentinel) { 3391 if (LineNo == line.line_no) { 3392 return line; 3393 } else if (LineNo < line.line_no) { 3394 line = line.left; 3395 } else { 3396 line = line.right; 3397 } 3398 } 3399 3400 return null; 3401 } 3402 3403 /// <summary>Retrieve the previous tag; walks line boundaries</summary> PreviousTag(LineTag tag)3404 internal LineTag PreviousTag(LineTag tag) { 3405 Line l; 3406 3407 if (tag.Previous != null) { 3408 return tag.Previous; 3409 } 3410 3411 // Next line 3412 if (tag.Line.line_no == 1) { 3413 return null; 3414 } 3415 3416 l = GetLine(tag.Line.line_no - 1); 3417 if (l != null) { 3418 LineTag t; 3419 3420 t = l.tags; 3421 while (t.Next != null) { 3422 t = t.Next; 3423 } 3424 return t; 3425 } 3426 3427 return null; 3428 } 3429 3430 /// <summary>Retrieve the next tag; walks line boundaries</summary> NextTag(LineTag tag)3431 internal LineTag NextTag(LineTag tag) { 3432 Line l; 3433 3434 if (tag.Next != null) { 3435 return tag.Next; 3436 } 3437 3438 // Next line 3439 l = GetLine(tag.Line.line_no + 1); 3440 if (l != null) { 3441 return l.tags; 3442 } 3443 3444 return null; 3445 } 3446 ParagraphStart(Line line)3447 internal Line ParagraphStart(Line line) { 3448 Line lastline = line; 3449 do { 3450 if (line.line_no <= 1) 3451 break; 3452 3453 line = lastline; 3454 lastline = GetLine (line.line_no - 1); 3455 } while (lastline.ending == LineEnding.Wrap); 3456 3457 return line; 3458 } 3459 ParagraphEnd(Line line)3460 internal Line ParagraphEnd(Line line) { 3461 Line l; 3462 3463 while (line.ending == LineEnding.Wrap) { 3464 l = GetLine(line.line_no + 1); 3465 if ((l == null) || (l.ending != LineEnding.Wrap)) { 3466 break; 3467 } 3468 line = l; 3469 } 3470 return line; 3471 } 3472 3473 /// <summary>Give it a pixel offset coordinate and it returns the Line covering that are (offset 3474 /// is either X or Y depending on if we are multiline 3475 /// </summary> GetLineByPixel(int offset, bool exact)3476 internal Line GetLineByPixel (int offset, bool exact) 3477 { 3478 Line line = document; 3479 Line last = null; 3480 3481 if (multiline) { 3482 while (line != sentinel) { 3483 last = line; 3484 if ((offset >= line.Y) && (offset < (line.Y+line.height))) { 3485 return line; 3486 } else if (offset < line.Y) { 3487 line = line.left; 3488 } else { 3489 line = line.right; 3490 } 3491 } 3492 } else { 3493 while (line != sentinel) { 3494 last = line; 3495 if ((offset >= line.X) && (offset < (line.X + line.Width))) 3496 return line; 3497 else if (offset < line.X) 3498 line = line.left; 3499 else 3500 line = line.right; 3501 } 3502 } 3503 3504 if (exact) { 3505 return null; 3506 } 3507 return last; 3508 } 3509 3510 // UIA: Method used via reflection in TextProviderBehavior 3511 3512 // Give it x/y pixel coordinates and it returns the Tag at that position FindCursor(int x, int y, out int index)3513 internal LineTag FindCursor (int x, int y, out int index) 3514 { 3515 Line line; 3516 3517 x -= offset_x; 3518 y -= offset_y; 3519 3520 line = GetLineByPixel (multiline ? y : x, false); 3521 3522 LineTag tag = line.GetTag (x); 3523 3524 if (tag.Length == 0 && tag.Start == 1) 3525 index = 0; 3526 else 3527 index = tag.GetCharIndex (x - line.align_shift); 3528 3529 return tag; 3530 } 3531 3532 /// <summary>Format area of document in specified font and color</summary> 3533 /// <param name="start_pos">1-based start position on start_line</param> 3534 /// <param name="end_pos">1-based end position on end_line </param> FormatText(Line start_line, int start_pos, Line end_line, int end_pos, Font font, Color color, Color back_color, FormatSpecified specified)3535 internal void FormatText (Line start_line, int start_pos, Line end_line, int end_pos, Font font, 3536 Color color, Color back_color, FormatSpecified specified) 3537 { 3538 Line l; 3539 3540 // First, format the first line 3541 if (start_line != end_line) { 3542 // First line 3543 LineTag.FormatText(start_line, start_pos, start_line.text.Length - start_pos + 1, font, color, back_color, specified); 3544 3545 // Format last line 3546 LineTag.FormatText(end_line, 1, end_pos, font, color, back_color, specified); 3547 3548 // Now all the lines inbetween 3549 for (int i = start_line.line_no + 1; i < end_line.line_no; i++) { 3550 l = GetLine(i); 3551 LineTag.FormatText(l, 1, l.text.Length, font, color, back_color, specified); 3552 } 3553 } else { 3554 // Special case, single line 3555 LineTag.FormatText(start_line, start_pos, end_pos - start_pos, font, color, back_color, specified); 3556 3557 if ((end_pos - start_pos) == 0 && CaretTag.Length != 0) 3558 CaretTag = CaretTag.Next; 3559 } 3560 } 3561 RecalculateAlignments()3562 internal void RecalculateAlignments () 3563 { 3564 Line line; 3565 int line_no; 3566 3567 line_no = 1; 3568 3569 3570 3571 while (line_no <= lines) { 3572 line = GetLine(line_no); 3573 3574 if (line != null) { 3575 switch (line.alignment) { 3576 case HorizontalAlignment.Left: 3577 line.align_shift = 0; 3578 break; 3579 case HorizontalAlignment.Center: 3580 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2; 3581 break; 3582 case HorizontalAlignment.Right: 3583 line.align_shift = viewport_width - (int)line.widths[line.text.Length] - right_margin; 3584 break; 3585 } 3586 } 3587 3588 line_no++; 3589 } 3590 return; 3591 } 3592 3593 /// <summary>Calculate formatting for the whole document</summary> RecalculateDocument(Graphics g)3594 internal bool RecalculateDocument(Graphics g) { 3595 return RecalculateDocument(g, 1, this.lines, false); 3596 } 3597 3598 /// <summary>Calculate formatting starting at a certain line</summary> RecalculateDocument(Graphics g, int start)3599 internal bool RecalculateDocument(Graphics g, int start) { 3600 return RecalculateDocument(g, start, this.lines, false); 3601 } 3602 3603 /// <summary>Calculate formatting within two given line numbers</summary> RecalculateDocument(Graphics g, int start, int end)3604 internal bool RecalculateDocument(Graphics g, int start, int end) { 3605 return RecalculateDocument(g, start, end, false); 3606 } 3607 3608 /// <summary>With optimize on, returns true if line heights changed</summary> RecalculateDocument(Graphics g, int start, int end, bool optimize)3609 internal bool RecalculateDocument(Graphics g, int start, int end, bool optimize) { 3610 Line line; 3611 int line_no; 3612 int offset; 3613 int new_width; 3614 bool changed; 3615 int shift; 3616 3617 if (recalc_suspended > 0) { 3618 recalc_pending = true; 3619 recalc_start = Math.Min (recalc_start, start); 3620 recalc_end = Math.Max (recalc_end, end); 3621 recalc_optimize = optimize; 3622 return false; 3623 } 3624 3625 // Fixup the positions, they can go kinda nuts 3626 // (this is suspend and resume recalc - they set them to 1 and max) 3627 start = Math.Max (start, 1); 3628 end = Math.Min (end, lines); 3629 3630 offset = GetLine(start).offset; 3631 line_no = start; 3632 new_width = 0; 3633 shift = this.lines; 3634 if (!optimize) { 3635 changed = true; // We always return true if we run non-optimized 3636 } else { 3637 changed = false; 3638 } 3639 3640 while (line_no <= (end + this.lines - shift)) { 3641 line = GetLine(line_no++); 3642 line.offset = offset; 3643 3644 // if we are not calculating a password 3645 if (!calc_pass) { 3646 if (!optimize) { 3647 line.RecalculateLine(g, this); 3648 } else { 3649 if (line.recalc && line.RecalculateLine(g, this)) { 3650 changed = true; 3651 // If the height changed, all subsequent lines change 3652 end = this.lines; 3653 shift = this.lines; 3654 } 3655 } 3656 } else { 3657 if (!optimize) { 3658 line.RecalculatePasswordLine(g, this); 3659 } else { 3660 if (line.recalc && line.RecalculatePasswordLine(g, this)) { 3661 changed = true; 3662 // If the height changed, all subsequent lines change 3663 end = this.lines; 3664 shift = this.lines; 3665 } 3666 } 3667 } 3668 3669 if (line.widths[line.text.Length] > new_width) { 3670 new_width = (int)line.widths[line.text.Length]; 3671 } 3672 3673 // Calculate alignment 3674 if (line.alignment != HorizontalAlignment.Left) { 3675 if (line.alignment == HorizontalAlignment.Center) { 3676 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2; 3677 } else { 3678 line.align_shift = viewport_width - (int)line.widths[line.text.Length] - 1; 3679 } 3680 } 3681 3682 if (multiline) 3683 offset += line.height; 3684 else 3685 offset += (int) line.widths [line.text.Length]; 3686 3687 if (line_no > lines) { 3688 break; 3689 } 3690 } 3691 3692 if (document_x != new_width) { 3693 document_x = new_width; 3694 if (WidthChanged != null) { 3695 WidthChanged(this, null); 3696 } 3697 } 3698 3699 RecalculateAlignments(); 3700 3701 line = GetLine(lines); 3702 3703 if (document_y != line.Y + line.height) { 3704 document_y = line.Y + line.height; 3705 if (HeightChanged != null) { 3706 HeightChanged(this, null); 3707 } 3708 } 3709 3710 // scan for links and tell us if its all 3711 // changed, so we can update everything 3712 if (EnableLinks) 3713 ScanForLinks (start, end, ref changed); 3714 3715 UpdateCaret(); 3716 return changed; 3717 } 3718 Size()3719 internal int Size() { 3720 return lines; 3721 } 3722 owner_HandleCreated(object sender, EventArgs e)3723 private void owner_HandleCreated(object sender, EventArgs e) { 3724 RecalculateDocument(owner.CreateGraphicsInternal()); 3725 AlignCaret(); 3726 } 3727 owner_VisibleChanged(object sender, EventArgs e)3728 private void owner_VisibleChanged(object sender, EventArgs e) { 3729 if (owner.Visible) { 3730 RecalculateDocument(owner.CreateGraphicsInternal()); 3731 } 3732 } 3733 IsWordSeparator(char ch)3734 internal static bool IsWordSeparator (char ch) 3735 { 3736 switch (ch) { 3737 case ' ': 3738 case '\t': 3739 case '(': 3740 case ')': 3741 case '\r': 3742 case '\n': 3743 return true; 3744 default: 3745 return false; 3746 } 3747 } 3748 FindWordSeparator(Line line, int pos, bool forward)3749 internal int FindWordSeparator(Line line, int pos, bool forward) { 3750 int len; 3751 3752 len = line.text.Length; 3753 3754 if (forward) { 3755 for (int i = pos + 1; i < len; i++) { 3756 if (IsWordSeparator(line.Text[i])) { 3757 return i + 1; 3758 } 3759 } 3760 return len; 3761 } else { 3762 for (int i = pos - 1; i > 0; i--) { 3763 if (IsWordSeparator(line.Text[i - 1])) { 3764 return i; 3765 } 3766 } 3767 return 0; 3768 } 3769 } 3770 3771 /* Search document for text */ FindChars(char[] chars, Marker start, Marker end, out Marker result)3772 internal bool FindChars(char[] chars, Marker start, Marker end, out Marker result) { 3773 Line line; 3774 int line_no; 3775 int pos; 3776 int line_len; 3777 3778 // Search for occurence of any char in the chars array 3779 result = new Marker(); 3780 3781 line = start.line; 3782 line_no = start.line.line_no; 3783 pos = start.pos; 3784 while (line_no <= end.line.line_no) { 3785 line_len = line.text.Length; 3786 while (pos < line_len) { 3787 for (int i = 0; i < chars.Length; i++) { 3788 if (line.text[pos] == chars[i]) { 3789 // Special case 3790 if ((line.line_no == end.line.line_no) && (pos >= end.pos)) { 3791 return false; 3792 } 3793 3794 result.line = line; 3795 result.pos = pos; 3796 return true; 3797 } 3798 } 3799 pos++; 3800 } 3801 3802 pos = 0; 3803 line_no++; 3804 line = GetLine(line_no); 3805 } 3806 3807 return false; 3808 } 3809 3810 // This version does not build one big string for searching, instead it handles 3811 // line-boundaries, which is faster and less memory intensive 3812 // FIXME - Depending on culture stuff we might have to create a big string and use culturespecific 3813 // search stuff and change it to accept and return positions instead of Markers (which would match 3814 // RichTextBox behaviour better but would be inconsistent with the rest of TextControl) Find(string search, Marker start, Marker end, out Marker result, RichTextBoxFinds options)3815 internal bool Find(string search, Marker start, Marker end, out Marker result, RichTextBoxFinds options) { 3816 Marker last; 3817 string search_string; 3818 Line line; 3819 int line_no; 3820 int pos; 3821 int line_len; 3822 int current; 3823 bool word; 3824 bool word_option; 3825 bool ignore_case; 3826 bool reverse; 3827 char c; 3828 3829 result = new Marker(); 3830 word_option = ((options & RichTextBoxFinds.WholeWord) != 0); 3831 ignore_case = ((options & RichTextBoxFinds.MatchCase) == 0); 3832 reverse = ((options & RichTextBoxFinds.Reverse) != 0); 3833 3834 line = start.line; 3835 line_no = start.line.line_no; 3836 pos = start.pos; 3837 current = 0; 3838 3839 // Prep our search string, lowercasing it if we do case-independent matching 3840 if (ignore_case) { 3841 StringBuilder sb; 3842 sb = new StringBuilder(search); 3843 for (int i = 0; i < sb.Length; i++) { 3844 sb[i] = Char.ToLower(sb[i]); 3845 } 3846 search_string = sb.ToString(); 3847 } else { 3848 search_string = search; 3849 } 3850 3851 // We need to check if the character before our start position is a wordbreak 3852 if (word_option) { 3853 if (line_no == 1) { 3854 if ((pos == 0) || (IsWordSeparator(line.text[pos - 1]))) { 3855 word = true; 3856 } else { 3857 word = false; 3858 } 3859 } else { 3860 if (pos > 0) { 3861 if (IsWordSeparator(line.text[pos - 1])) { 3862 word = true; 3863 } else { 3864 word = false; 3865 } 3866 } else { 3867 // Need to check the end of the previous line 3868 Line prev_line; 3869 3870 prev_line = GetLine(line_no - 1); 3871 if (prev_line.ending == LineEnding.Wrap) { 3872 if (IsWordSeparator(prev_line.text[prev_line.text.Length - 1])) { 3873 word = true; 3874 } else { 3875 word = false; 3876 } 3877 } else { 3878 word = true; 3879 } 3880 } 3881 } 3882 } else { 3883 word = false; 3884 } 3885 3886 // To avoid duplication of this loop with reverse logic, we search 3887 // through the document, remembering the last match and when returning 3888 // report that last remembered match 3889 3890 last = new Marker(); 3891 last.height = -1; // Abused - we use it to track change 3892 3893 while (line_no <= end.line.line_no) { 3894 if (line_no != end.line.line_no) { 3895 line_len = line.text.Length; 3896 } else { 3897 line_len = end.pos; 3898 } 3899 3900 while (pos < line_len) { 3901 3902 if (word_option && (current == search_string.Length)) { 3903 if (IsWordSeparator(line.text[pos])) { 3904 if (!reverse) { 3905 goto FindFound; 3906 } else { 3907 last = result; 3908 current = 0; 3909 } 3910 } else { 3911 current = 0; 3912 } 3913 } 3914 3915 if (ignore_case) { 3916 c = Char.ToLower(line.text[pos]); 3917 } else { 3918 c = line.text[pos]; 3919 } 3920 3921 if (c == search_string[current]) { 3922 3923 if (current == 0) { 3924 result.line = line; 3925 result.pos = pos; 3926 } 3927 if (!word_option || (word_option && (word || (current > 0)))) { 3928 current++; 3929 } 3930 3931 if (!word_option && (current == search_string.Length)) { 3932 if (!reverse) { 3933 goto FindFound; 3934 } else { 3935 last = result; 3936 current = 0; 3937 } 3938 } 3939 } else { 3940 current = 0; 3941 } 3942 pos++; 3943 3944 if (!word_option) { 3945 continue; 3946 } 3947 3948 if (IsWordSeparator(c)) { 3949 word = true; 3950 } else { 3951 word = false; 3952 } 3953 } 3954 3955 if (word_option) { 3956 // Mark that we just saw a word boundary 3957 if (line.ending != LineEnding.Wrap || line.line_no == lines - 1) { 3958 word = true; 3959 } 3960 3961 if (current == search_string.Length) { 3962 if (word) { 3963 if (!reverse) { 3964 goto FindFound; 3965 } else { 3966 last = result; 3967 current = 0; 3968 } 3969 } else { 3970 current = 0; 3971 } 3972 } 3973 } 3974 3975 pos = 0; 3976 line_no++; 3977 line = GetLine(line_no); 3978 } 3979 3980 if (reverse) { 3981 if (last.height != -1) { 3982 result = last; 3983 return true; 3984 } 3985 } 3986 3987 return false; 3988 3989 FindFound: 3990 if (!reverse) { 3991 // if ((line.line_no == end.line.line_no) && (pos >= end.pos)) { 3992 // return false; 3993 // } 3994 return true; 3995 } 3996 3997 result = last; 3998 return true; 3999 4000 } 4001 4002 /* Marker stuff */ GetMarker(out Marker mark, bool start)4003 internal void GetMarker(out Marker mark, bool start) { 4004 mark = new Marker(); 4005 4006 if (start) { 4007 mark.line = GetLine(1); 4008 mark.tag = mark.line.tags; 4009 mark.pos = 0; 4010 } else { 4011 mark.line = GetLine(lines); 4012 mark.tag = mark.line.tags; 4013 while (mark.tag.Next != null) { 4014 mark.tag = mark.tag.Next; 4015 } 4016 mark.pos = mark.line.text.Length; 4017 } 4018 } 4019 #endregion // Internal Methods 4020 4021 #region Events 4022 internal event EventHandler CaretMoved; 4023 internal event EventHandler WidthChanged; 4024 internal event EventHandler HeightChanged; 4025 internal event EventHandler LengthChanged; 4026 internal event EventHandler UIASelectionChanged; 4027 #endregion // Events 4028 4029 #region Administrative GetEnumerator()4030 public IEnumerator GetEnumerator() { 4031 // FIXME 4032 return null; 4033 } 4034 Equals(object obj)4035 public override bool Equals(object obj) { 4036 if (obj == null) { 4037 return false; 4038 } 4039 4040 if (!(obj is Document)) { 4041 return false; 4042 } 4043 4044 if (obj == this) { 4045 return true; 4046 } 4047 4048 if (ToString().Equals(((Document)obj).ToString())) { 4049 return true; 4050 } 4051 4052 return false; 4053 } 4054 GetHashCode()4055 public override int GetHashCode() { 4056 return document_id; 4057 } 4058 ToString()4059 public override string ToString() { 4060 return "document " + this.document_id; 4061 } 4062 #endregion // Administrative 4063 } 4064 4065 internal class PictureTag : LineTag { 4066 4067 internal RTF.Picture picture; 4068 PictureTag(Line line, int start, RTF.Picture picture)4069 internal PictureTag (Line line, int start, RTF.Picture picture) : base (line, start) 4070 { 4071 this.picture = picture; 4072 } 4073 4074 public override bool IsTextTag { 4075 get { return false; } 4076 } 4077 SizeOfPosition(Graphics dc, int pos)4078 public override SizeF SizeOfPosition (Graphics dc, int pos) 4079 { 4080 return picture.Size; 4081 } 4082 MaxHeight()4083 internal override int MaxHeight () 4084 { 4085 return (int) (picture.Height + 0.5F); 4086 } 4087 Draw(Graphics dc, Color color, float xoff, float y, int start, int end)4088 public override void Draw (Graphics dc, Color color, float xoff, float y, int start, int end) 4089 { 4090 picture.DrawImage (dc, xoff + Line.widths [start], y, false); 4091 } 4092 Draw(Graphics dc, Color color, float xoff, float y, int start, int end, string text)4093 public override void Draw (Graphics dc, Color color, float xoff, float y, int start, int end, string text) 4094 { 4095 picture.DrawImage (dc, xoff + + Line.widths [start], y, false); 4096 } 4097 Text()4098 public override string Text () 4099 { 4100 return "I"; 4101 } 4102 } 4103 4104 internal class UndoManager { 4105 4106 internal enum ActionType { 4107 4108 Typing, 4109 4110 // This is basically just cut & paste 4111 InsertString, 4112 DeleteString, 4113 4114 UserActionBegin, 4115 UserActionEnd 4116 } 4117 4118 internal class Action { 4119 internal ActionType type; 4120 internal int line_no; 4121 internal int pos; 4122 internal object data; 4123 } 4124 4125 #region Local Variables 4126 private Document document; 4127 private Stack undo_actions; 4128 private Stack redo_actions; 4129 4130 //private int caret_line; 4131 //private int caret_pos; 4132 4133 // When performing an action, we lock the queue, so that the action can't be undone 4134 private bool locked; 4135 #endregion // Local Variables 4136 4137 #region Constructors UndoManager(Document document)4138 internal UndoManager (Document document) 4139 { 4140 this.document = document; 4141 undo_actions = new Stack (50); 4142 redo_actions = new Stack (50); 4143 } 4144 #endregion // Constructors 4145 4146 #region Properties 4147 internal bool CanUndo { 4148 get { return undo_actions.Count > 0; } 4149 } 4150 4151 internal bool CanRedo { 4152 get { return redo_actions.Count > 0; } 4153 } 4154 4155 internal string UndoActionName { 4156 get { 4157 foreach (Action action in undo_actions) { 4158 if (action.type == ActionType.UserActionBegin) 4159 return (string) action.data; 4160 if (action.type == ActionType.Typing) 4161 return Locale.GetText ("Typing"); 4162 } 4163 return String.Empty; 4164 } 4165 } 4166 4167 internal string RedoActionName { 4168 get { 4169 foreach (Action action in redo_actions) { 4170 if (action.type == ActionType.UserActionBegin) 4171 return (string) action.data; 4172 if (action.type == ActionType.Typing) 4173 return Locale.GetText ("Typing"); 4174 } 4175 return String.Empty; 4176 } 4177 } 4178 #endregion // Properties 4179 4180 #region Internal Methods Clear()4181 internal void Clear () 4182 { 4183 undo_actions.Clear(); 4184 redo_actions.Clear(); 4185 } 4186 Undo()4187 internal bool Undo () 4188 { 4189 Action action; 4190 bool user_action_finished = false; 4191 4192 if (undo_actions.Count == 0) 4193 return false; 4194 4195 locked = true; 4196 do { 4197 Line start; 4198 action = (Action) undo_actions.Pop (); 4199 4200 // Put onto redo stack 4201 redo_actions.Push(action); 4202 4203 // Do the thing 4204 switch(action.type) { 4205 4206 case ActionType.UserActionBegin: 4207 user_action_finished = true; 4208 break; 4209 4210 case ActionType.UserActionEnd: 4211 // noop 4212 break; 4213 4214 case ActionType.InsertString: 4215 start = document.GetLine (action.line_no); 4216 document.SuspendUpdate (); 4217 document.DeleteMultiline (start, action.pos, ((string) action.data).Length + 1); 4218 document.PositionCaret (start, action.pos); 4219 document.SetSelectionToCaret (true); 4220 document.ResumeUpdate (true); 4221 break; 4222 4223 case ActionType.Typing: 4224 start = document.GetLine (action.line_no); 4225 document.SuspendUpdate (); 4226 document.DeleteMultiline (start, action.pos, ((StringBuilder) action.data).Length); 4227 document.PositionCaret (start, action.pos); 4228 document.SetSelectionToCaret (true); 4229 document.ResumeUpdate (true); 4230 4231 // This is an open ended operation, so only a single typing operation can be undone at once 4232 user_action_finished = true; 4233 break; 4234 4235 case ActionType.DeleteString: 4236 start = document.GetLine (action.line_no); 4237 document.SuspendUpdate (); 4238 Insert (start, action.pos, (Line) action.data, true); 4239 document.ResumeUpdate (true); 4240 break; 4241 } 4242 } while (!user_action_finished && undo_actions.Count > 0); 4243 4244 locked = false; 4245 4246 return true; 4247 } 4248 Redo()4249 internal bool Redo () 4250 { 4251 Action action; 4252 bool user_action_finished = false; 4253 4254 if (redo_actions.Count == 0) 4255 return false; 4256 4257 locked = true; 4258 do { 4259 Line start; 4260 int start_index; 4261 4262 action = (Action) redo_actions.Pop (); 4263 undo_actions.Push (action); 4264 4265 switch (action.type) { 4266 4267 case ActionType.UserActionBegin: 4268 // Noop 4269 break; 4270 4271 case ActionType.UserActionEnd: 4272 user_action_finished = true; 4273 break; 4274 4275 case ActionType.InsertString: 4276 start = document.GetLine (action.line_no); 4277 document.SuspendUpdate (); 4278 start_index = document.LineTagToCharIndex (start, action.pos); 4279 document.InsertString (start, action.pos, (string) action.data); 4280 document.CharIndexToLineTag (start_index + ((string) action.data).Length, 4281 out document.caret.line, out document.caret.tag, 4282 out document.caret.pos); 4283 document.UpdateCaret (); 4284 document.SetSelectionToCaret (true); 4285 document.ResumeUpdate (true); 4286 break; 4287 4288 case ActionType.Typing: 4289 start = document.GetLine (action.line_no); 4290 document.SuspendUpdate (); 4291 start_index = document.LineTagToCharIndex (start, action.pos); 4292 document.InsertString (start, action.pos, ((StringBuilder) action.data).ToString ()); 4293 document.CharIndexToLineTag (start_index + ((StringBuilder) action.data).Length, 4294 out document.caret.line, out document.caret.tag, 4295 out document.caret.pos); 4296 document.UpdateCaret (); 4297 document.SetSelectionToCaret (true); 4298 document.ResumeUpdate (true); 4299 4300 // This is an open ended operation, so only a single typing operation can be undone at once 4301 user_action_finished = true; 4302 break; 4303 4304 case ActionType.DeleteString: 4305 start = document.GetLine (action.line_no); 4306 document.SuspendUpdate (); 4307 document.DeleteMultiline (start, action.pos, ((Line) action.data).text.Length); 4308 document.PositionCaret (start, action.pos); 4309 document.SetSelectionToCaret (true); 4310 document.ResumeUpdate (true); 4311 4312 break; 4313 } 4314 } while (!user_action_finished && redo_actions.Count > 0); 4315 4316 locked = false; 4317 4318 return true; 4319 } 4320 #endregion // Internal Methods 4321 4322 #region Private Methods 4323 BeginUserAction(string name)4324 public void BeginUserAction (string name) 4325 { 4326 if (locked) 4327 return; 4328 4329 // Nuke the redo queue 4330 redo_actions.Clear (); 4331 4332 Action ua = new Action (); 4333 ua.type = ActionType.UserActionBegin; 4334 ua.data = name; 4335 4336 undo_actions.Push (ua); 4337 } 4338 EndUserAction()4339 public void EndUserAction () 4340 { 4341 if (locked) 4342 return; 4343 4344 Action ua = new Action (); 4345 ua.type = ActionType.UserActionEnd; 4346 4347 undo_actions.Push (ua); 4348 } 4349 4350 // start_pos, end_pos = 1 based RecordDeleteString(Line start_line, int start_pos, Line end_line, int end_pos)4351 public void RecordDeleteString (Line start_line, int start_pos, Line end_line, int end_pos) 4352 { 4353 if (locked) 4354 return; 4355 4356 // Nuke the redo queue 4357 redo_actions.Clear (); 4358 4359 Action a = new Action (); 4360 4361 // We cant simply store the string, because then formatting would be lost 4362 a.type = ActionType.DeleteString; 4363 a.line_no = start_line.line_no; 4364 a.pos = start_pos; 4365 a.data = Duplicate (start_line, start_pos, end_line, end_pos); 4366 4367 undo_actions.Push(a); 4368 } 4369 RecordInsertString(Line line, int pos, string str)4370 public void RecordInsertString (Line line, int pos, string str) 4371 { 4372 if (locked || str.Length == 0) 4373 return; 4374 4375 // Nuke the redo queue 4376 redo_actions.Clear (); 4377 4378 Action a = new Action (); 4379 4380 a.type = ActionType.InsertString; 4381 a.data = str; 4382 a.line_no = line.line_no; 4383 a.pos = pos; 4384 4385 undo_actions.Push (a); 4386 } 4387 RecordTyping(Line line, int pos, char ch)4388 public void RecordTyping (Line line, int pos, char ch) 4389 { 4390 if (locked) 4391 return; 4392 4393 // Nuke the redo queue 4394 redo_actions.Clear (); 4395 4396 Action a = null; 4397 4398 if (undo_actions.Count > 0) 4399 a = (Action) undo_actions.Peek (); 4400 4401 if (a == null || a.type != ActionType.Typing) { 4402 a = new Action (); 4403 a.type = ActionType.Typing; 4404 a.data = new StringBuilder (); 4405 a.line_no = line.line_no; 4406 a.pos = pos; 4407 4408 undo_actions.Push (a); 4409 } 4410 4411 StringBuilder data = (StringBuilder) a.data; 4412 data.Append (ch); 4413 } 4414 4415 // start_pos = 1-based 4416 // end_pos = 1-based Duplicate(Line start_line, int start_pos, Line end_line, int end_pos)4417 public Line Duplicate(Line start_line, int start_pos, Line end_line, int end_pos) 4418 { 4419 Line ret; 4420 Line line; 4421 Line current; 4422 LineTag tag; 4423 LineTag current_tag; 4424 int start; 4425 int end; 4426 int tag_start; 4427 4428 line = new Line (start_line.document, start_line.ending); 4429 ret = line; 4430 4431 for (int i = start_line.line_no; i <= end_line.line_no; i++) { 4432 current = document.GetLine(i); 4433 4434 if (start_line.line_no == i) { 4435 start = start_pos; 4436 } else { 4437 start = 0; 4438 } 4439 4440 if (end_line.line_no == i) { 4441 end = end_pos; 4442 } else { 4443 end = current.text.Length; 4444 } 4445 4446 if (end_pos == 0) 4447 continue; 4448 4449 // Text for the tag 4450 line.text = new StringBuilder (current.text.ToString (start, end - start)); 4451 4452 // Copy tags from start to start+length onto new line 4453 current_tag = current.FindTag (start + 1); 4454 while ((current_tag != null) && (current_tag.Start <= end)) { 4455 if ((current_tag.Start <= start) && (start < (current_tag.Start + current_tag.Length))) { 4456 // start tag is within this tag 4457 tag_start = start; 4458 } else { 4459 tag_start = current_tag.Start; 4460 } 4461 4462 tag = new LineTag(line, tag_start - start + 1); 4463 tag.CopyFormattingFrom (current_tag); 4464 4465 current_tag = current_tag.Next; 4466 4467 // Add the new tag to the line 4468 if (line.tags == null) { 4469 line.tags = tag; 4470 } else { 4471 LineTag tail; 4472 tail = line.tags; 4473 4474 while (tail.Next != null) { 4475 tail = tail.Next; 4476 } 4477 tail.Next = tag; 4478 tag.Previous = tail; 4479 } 4480 } 4481 4482 if ((i + 1) <= end_line.line_no) { 4483 line.ending = current.ending; 4484 4485 // Chain them (we use right/left as next/previous) 4486 line.right = new Line (start_line.document, start_line.ending); 4487 line.right.left = line; 4488 line = line.right; 4489 } 4490 } 4491 4492 return ret; 4493 } 4494 4495 // Insert multi-line text at the given position; use formatting at insertion point for inserted text Insert(Line line, int pos, Line insert, bool select)4496 internal void Insert(Line line, int pos, Line insert, bool select) 4497 { 4498 Line current; 4499 LineTag tag; 4500 int offset; 4501 int lines; 4502 Line first; 4503 4504 // Handle special case first 4505 if (insert.right == null) { 4506 4507 // Single line insert 4508 document.Split(line, pos); 4509 4510 if (insert.tags == null) { 4511 return; // Blank line 4512 } 4513 4514 //Insert our tags at the end 4515 tag = line.tags; 4516 4517 while (tag.Next != null) { 4518 tag = tag.Next; 4519 } 4520 4521 offset = tag.Start + tag.Length - 1; 4522 4523 tag.Next = insert.tags; 4524 line.text.Insert(offset, insert.text.ToString()); 4525 4526 // Adjust start locations 4527 tag = tag.Next; 4528 while (tag != null) { 4529 tag.Start += offset; 4530 tag.Line = line; 4531 tag = tag.Next; 4532 } 4533 // Put it back together 4534 document.Combine(line.line_no, line.line_no + 1); 4535 4536 if (select) { 4537 document.SetSelectionStart (line, pos, false); 4538 document.SetSelectionEnd (line, pos + insert.text.Length, false); 4539 } 4540 4541 document.UpdateView(line, pos); 4542 return; 4543 } 4544 4545 first = line; 4546 lines = 1; 4547 current = insert; 4548 4549 while (current != null) { 4550 4551 if (current == insert) { 4552 // Inserting the first line we split the line (and make space) 4553 document.Split(line.line_no, pos); 4554 //Insert our tags at the end of the line 4555 tag = line.tags; 4556 4557 4558 if (tag != null && tag.Length != 0) { 4559 while (tag.Next != null) { 4560 tag = tag.Next; 4561 } 4562 offset = tag.Start + tag.Length - 1; 4563 tag.Next = current.tags; 4564 tag.Next.Previous = tag; 4565 4566 tag = tag.Next; 4567 4568 } else { 4569 offset = 0; 4570 line.tags = current.tags; 4571 line.tags.Previous = null; 4572 tag = line.tags; 4573 } 4574 4575 line.ending = current.ending; 4576 } else { 4577 document.Split(line.line_no, 0); 4578 offset = 0; 4579 line.tags = current.tags; 4580 line.tags.Previous = null; 4581 line.ending = current.ending; 4582 tag = line.tags; 4583 } 4584 4585 // Adjust start locations and line pointers 4586 while (tag != null) { 4587 tag.Start += offset - 1; 4588 tag.Line = line; 4589 tag = tag.Next; 4590 } 4591 4592 line.text.Insert(offset, current.text.ToString()); 4593 line.Grow(line.text.Length); 4594 4595 line.recalc = true; 4596 line = document.GetLine(line.line_no + 1); 4597 4598 // FIXME? Test undo of line-boundaries 4599 if ((current.right == null) && (current.tags.Length != 0)) { 4600 document.Combine(line.line_no - 1, line.line_no); 4601 } 4602 current = current.right; 4603 lines++; 4604 4605 } 4606 4607 // Recalculate our document 4608 document.UpdateView(first, lines, pos); 4609 return; 4610 } 4611 #endregion // Private Methods 4612 } 4613 } 4614