1 // Written by Christopher E. Miller 2 // See the included license.txt for copyright and license details. 3 4 5 6 module dfl.richtextbox; 7 8 private import dfl.textbox, dfl.internal.winapi, dfl.event, dfl.application; 9 private import dfl.base, dfl.drawing, dfl.data; 10 private import dfl.control, dfl.internal.utf, dfl.internal.dlib; 11 12 version(DFL_NO_MENUS) { 13 } 14 else { 15 private import dfl.menu; 16 } 17 18 19 private extern(C) char* strcpy(char*, char*); 20 21 22 private extern(Windows) void _initRichtextbox(); 23 24 25 26 class LinkClickedEventArgs: EventArgs { 27 28 this(Dstring linkText) { 29 _linktxt = linkText; 30 } 31 32 33 34 final @property Dstring linkText() { // getter 35 return _linktxt; 36 } 37 38 39 private: 40 Dstring _linktxt; 41 } 42 43 44 45 enum RichTextBoxScrollBars: ubyte { 46 NONE, /// 47 HORIZONTAL, /// ditto 48 VERTICAL, /// ditto 49 BOTH, /// ditto 50 FORCED_HORIZONTAL, /// ditto 51 FORCED_VERTICAL, /// ditto 52 FORCED_BOTH, /// ditto 53 } 54 55 56 57 class RichTextBox: TextBoxBase { // docmain 58 this() { 59 super(); 60 61 _initRichtextbox(); 62 63 wstyle |= ES_MULTILINE | ES_WANTRETURN | ES_AUTOHSCROLL | ES_AUTOVSCROLL | WS_HSCROLL | WS_VSCROLL; 64 wcurs = null; // So that the control can change it accordingly. 65 wclassStyle = richtextboxClassStyle; 66 67 version(DFL_NO_MENUS) { 68 } 69 else { 70 with(miredo = new MenuItem) { 71 text = "&Redo"; 72 click ~= &menuRedo; 73 contextMenu.menuItems.insert(1, miredo); 74 } 75 76 contextMenu.popup ~= &menuPopup2; 77 } 78 } 79 80 81 private { 82 version(DFL_NO_MENUS) { 83 } 84 else 85 { 86 void menuRedo(Object sender, EventArgs ea) { 87 redo(); 88 } 89 90 91 void menuPopup2(Object sender, EventArgs ea) { 92 miredo.enabled = canRedo; 93 } 94 95 96 MenuItem miredo; 97 } 98 } 99 100 101 override @property Cursor cursor() { // getter 102 return wcurs; // Do return null and don't inherit. 103 } 104 105 alias TextBoxBase.cursor cursor; // Overload. 106 107 108 override @property Dstring selectedText() { // getter 109 if(created) { 110 /+ 111 uint len = selectionLength + 1; 112 Dstring result = new char[len]; 113 len = SendMessageA(handle, EM_GETSELTEXT, 0, cast(LPARAM)cast(char*)result); 114 assert(!result[len]); 115 return result[0 .. len]; 116 +/ 117 118 return dfl.internal.utf.emGetSelText(hwnd, selectionLength + 1); 119 } 120 return null; 121 } 122 123 alias TextBoxBase.selectedText selectedText; // Overload. 124 125 126 override @property void selectionLength(uint len) { // setter 127 if(created) { 128 CHARRANGE chrg; 129 SendMessageA(handle, EM_EXGETSEL, 0, cast(LPARAM)&chrg); 130 chrg.cpMax = chrg.cpMin + len; 131 SendMessageA(handle, EM_EXSETSEL, 0, cast(LPARAM)&chrg); 132 } 133 } 134 135 136 // Current selection length, in characters. 137 // This does not necessarily correspond to the length of chars; some characters use multiple chars. 138 // An end of line (\r\n) takes up 2 characters. 139 override @property uint selectionLength() { // getter 140 if(created) { 141 CHARRANGE chrg; 142 SendMessageA(handle, EM_EXGETSEL, 0, cast(LPARAM)&chrg); 143 assert(chrg.cpMax >= chrg.cpMin); 144 return chrg.cpMax - chrg.cpMin; 145 } 146 return 0; 147 } 148 149 150 override @property void selectionStart(uint pos) { // setter 151 if(created) { 152 CHARRANGE chrg; 153 SendMessageA(handle, EM_EXGETSEL, 0, cast(LPARAM)&chrg); 154 assert(chrg.cpMax >= chrg.cpMin); 155 chrg.cpMax = pos + (chrg.cpMax - chrg.cpMin); 156 chrg.cpMin = pos; 157 SendMessageA(handle, EM_EXSETSEL, 0, cast(LPARAM)&chrg); 158 } 159 } 160 161 162 // Current selection starting index, in characters. 163 // This does not necessarily correspond to the index of chars; some characters use multiple chars. 164 // An end of line (\r\n) takes up 2 characters. 165 override @property uint selectionStart() { // getter 166 if(created) { 167 CHARRANGE chrg; 168 SendMessageA(handle, EM_EXGETSEL, 0, cast(LPARAM)&chrg); 169 return chrg.cpMin; 170 } 171 return 0; 172 } 173 174 175 override @property void maxLength(uint len) { // setter 176 lim = len; 177 178 if(created) { 179 SendMessageA(handle, EM_EXLIMITTEXT, 0, cast(LPARAM)len); 180 } 181 } 182 183 alias TextBoxBase.maxLength maxLength; // Overload. 184 185 186 override @property Size defaultSize() { // getter 187 return Size(120, 120); // ? 188 } 189 190 191 private void _setbk(Color c) { 192 if(created) { 193 if(c._systemColorIndex == COLOR_WINDOW) { 194 SendMessageA(handle, EM_SETBKGNDCOLOR, 1, 0); 195 } else { 196 SendMessageA(handle, EM_SETBKGNDCOLOR, 0, cast(LPARAM)c.toRgb()); 197 } 198 } 199 } 200 201 202 override @property void backColor(Color c) { // setter 203 _setbk(c); 204 super.backColor(c); 205 } 206 207 alias TextBoxBase.backColor backColor; // Overload. 208 209 210 private void _setfc(Color c) { 211 if(created) { 212 CHARFORMAT2A cf; 213 214 cf.cbSize = cf.sizeof; 215 cf.dwMask = CFM_COLOR; 216 if(c._systemColorIndex == COLOR_WINDOWTEXT) { 217 cf.dwEffects = CFE_AUTOCOLOR; 218 } else { 219 cf.crTextColor = c.toRgb(); 220 } 221 222 _setFormat(&cf, SCF_ALL); 223 } 224 } 225 226 227 override @property void foreColor(Color c) { // setter 228 _setfc(c); 229 super.foreColor(c); 230 } 231 232 alias TextBoxBase.foreColor foreColor; // Overload. 233 234 235 236 final @property bool canRedo() { // getter 237 if(!created) { 238 return false; 239 } 240 return SendMessageA(handle, EM_CANREDO, 0, 0) != 0; 241 } 242 243 244 245 final bool canPaste(DataFormats.Format df) { 246 if(created) { 247 if(SendMessageA(handle, EM_CANPASTE, df.id, 0)) { 248 return true; 249 } 250 } 251 252 return false; 253 } 254 255 256 257 final void redo() { 258 if(created) { 259 SendMessageA(handle, EM_REDO, 0, 0); 260 } 261 } 262 263 264 265 // "Paste special." 266 final void paste(DataFormats.Format df) { 267 if(created) { 268 SendMessageA(handle, EM_PASTESPECIAL, df.id, cast(LPARAM)0); 269 } 270 } 271 272 alias TextBoxBase.paste paste; // Overload. 273 274 275 276 final @property void selectionCharOffset(int yoffset) { // setter 277 if(!created) { 278 return; 279 } 280 281 CHARFORMAT2A cf; 282 283 cf.cbSize = cf.sizeof; 284 cf.dwMask = CFM_OFFSET; 285 cf.yOffset = yoffset; 286 287 _setFormat(&cf); 288 } 289 290 /// ditto 291 final @property int selectionCharOffset() { // getter 292 if(created) { 293 CHARFORMAT2A cf; 294 cf.cbSize = cf.sizeof; 295 cf.dwMask = CFM_OFFSET; 296 _getFormat(&cf); 297 return cf.yOffset; 298 } 299 return 0; 300 } 301 302 303 304 final @property void selectionColor(Color c) { // setter 305 if(!created) { 306 return; 307 } 308 309 CHARFORMAT2A cf; 310 311 cf.cbSize = cf.sizeof; 312 cf.dwMask = CFM_COLOR; 313 if(c._systemColorIndex == COLOR_WINDOWTEXT) { 314 cf.dwEffects = CFE_AUTOCOLOR; 315 } else { 316 cf.crTextColor = c.toRgb(); 317 } 318 319 _setFormat(&cf); 320 } 321 322 /// ditto 323 final @property Color selectionColor() { // getter 324 if(created) { 325 CHARFORMAT2A cf; 326 327 cf.cbSize = cf.sizeof; 328 cf.dwMask = CFM_COLOR; 329 _getFormat(&cf); 330 331 if(cf.dwMask & CFM_COLOR) { 332 if(cf.dwEffects & CFE_AUTOCOLOR) { 333 return Color.systemColor(COLOR_WINDOWTEXT); 334 } 335 return Color.fromRgb(cf.crTextColor); 336 } 337 } 338 return Color.empty; 339 } 340 341 342 343 final @property void selectionBackColor(Color c) { // setter 344 if(!created) { 345 return; 346 } 347 348 CHARFORMAT2A cf; 349 350 cf.cbSize = cf.sizeof; 351 cf.dwMask = CFM_BACKCOLOR; 352 if(c._systemColorIndex == COLOR_WINDOW) { 353 cf.dwEffects = CFE_AUTOBACKCOLOR; 354 } else { 355 cf.crBackColor = c.toRgb(); 356 } 357 358 _setFormat(&cf); 359 } 360 361 /// ditto 362 final @property Color selectionBackColor() { // getter 363 if(created) { 364 CHARFORMAT2A cf; 365 366 cf.cbSize = cf.sizeof; 367 cf.dwMask = CFM_BACKCOLOR; 368 _getFormat(&cf); 369 370 if(cf.dwMask & CFM_BACKCOLOR) { 371 if(cf.dwEffects & CFE_AUTOBACKCOLOR) { 372 return Color.systemColor(COLOR_WINDOW); 373 } 374 return Color.fromRgb(cf.crBackColor); 375 } 376 } 377 return Color.empty; 378 } 379 380 381 382 final @property void selectionSubscript(bool byes) { // setter 383 if(!created) { 384 return; 385 } 386 387 CHARFORMAT2A cf; 388 389 cf.cbSize = cf.sizeof; 390 cf.dwMask = CFM_SUPERSCRIPT | CFM_SUBSCRIPT; 391 if(byes) { 392 cf.dwEffects = CFE_SUBSCRIPT; 393 } else { 394 // Make sure it doesn't accidentally unset superscript. 395 CHARFORMAT2A cf2get; 396 cf2get.cbSize = cf2get.sizeof; 397 cf2get.dwMask = CFM_SUPERSCRIPT | CFM_SUBSCRIPT; 398 _getFormat(&cf2get); 399 if(cf2get.dwEffects & CFE_SUPERSCRIPT) { 400 return; // Superscript is set, so don't bother. 401 } 402 if(!(cf2get.dwEffects & CFE_SUBSCRIPT)) { 403 return; // Don't need to unset twice. 404 } 405 } 406 407 _setFormat(&cf); 408 } 409 410 /// ditto 411 final @property bool selectionSubscript() { // getter 412 if(created) { 413 CHARFORMAT2A cf; 414 415 cf.cbSize = cf.sizeof; 416 cf.dwMask = CFM_SUPERSCRIPT | CFM_SUBSCRIPT; 417 _getFormat(&cf); 418 419 return (cf.dwEffects & CFE_SUBSCRIPT) == CFE_SUBSCRIPT; 420 } 421 return false; 422 } 423 424 425 426 final @property void selectionSuperscript(bool byes) { // setter 427 if(!created) { 428 return; 429 } 430 431 CHARFORMAT2A cf; 432 433 cf.cbSize = cf.sizeof; 434 cf.dwMask = CFM_SUPERSCRIPT | CFM_SUBSCRIPT; 435 if(byes) { 436 cf.dwEffects = CFE_SUPERSCRIPT; 437 } else { 438 // Make sure it doesn't accidentally unset subscript. 439 CHARFORMAT2A cf2get; 440 cf2get.cbSize = cf2get.sizeof; 441 cf2get.dwMask = CFM_SUPERSCRIPT | CFM_SUBSCRIPT; 442 _getFormat(&cf2get); 443 if(cf2get.dwEffects & CFE_SUBSCRIPT) { 444 return; // Subscript is set, so don't bother. 445 } 446 if(!(cf2get.dwEffects & CFE_SUPERSCRIPT)) { 447 return; // Don't need to unset twice. 448 } 449 } 450 451 _setFormat(&cf); 452 } 453 454 /// ditto 455 final @property bool selectionSuperscript() { // getter 456 if(created) { 457 CHARFORMAT2A cf; 458 459 cf.cbSize = cf.sizeof; 460 cf.dwMask = CFM_SUPERSCRIPT | CFM_SUBSCRIPT; 461 _getFormat(&cf); 462 463 return (cf.dwEffects & CFE_SUPERSCRIPT) == CFE_SUPERSCRIPT; 464 } 465 return false; 466 } 467 468 469 private enum DWORD FONT_MASK = CFM_BOLD | CFM_ITALIC | CFM_STRIKEOUT | 470 CFM_UNDERLINE | CFM_CHARSET | CFM_FACE | CFM_SIZE | CFM_UNDERLINETYPE | CFM_WEIGHT; 471 472 473 final @property void selectionFont(Font f) { // setter 474 if(created) { 475 // To-do: support Unicode font names. 476 477 CHARFORMAT2A cf; 478 LOGFONTA lf; 479 480 f._info(&lf); 481 482 cf.cbSize = cf.sizeof; 483 cf.dwMask = FONT_MASK; 484 485 //cf.dwEffects = 0; 486 if(lf.lfWeight >= FW_BOLD) { 487 cf.dwEffects |= CFE_BOLD; 488 } 489 if(lf.lfItalic) { 490 cf.dwEffects |= CFE_ITALIC; 491 } 492 if(lf.lfStrikeOut) { 493 cf.dwEffects |= CFE_STRIKEOUT; 494 } 495 if(lf.lfUnderline) { 496 cf.dwEffects |= CFE_UNDERLINE; 497 } 498 cf.yHeight = cast(typeof(cf.yHeight))Font.getEmSize(lf.lfHeight, GraphicsUnit.TWIP); 499 cf.bCharSet = lf.lfCharSet; 500 strcpy(cf.szFaceName.ptr, lf.lfFaceName.ptr); 501 cf.bUnderlineType = CFU_UNDERLINE; 502 cf.wWeight = cast(WORD)lf.lfWeight; 503 504 _setFormat(&cf); 505 } 506 } 507 508 /// ditto 509 // Returns null if the selection has different fonts. 510 final @property Font selectionFont() { // getter 511 if(created) { 512 CHARFORMAT2A cf; 513 514 cf.cbSize = cf.sizeof; 515 cf.dwMask = FONT_MASK; 516 _getFormat(&cf); 517 518 if((cf.dwMask & FONT_MASK) == FONT_MASK) { 519 LOGFONTA lf; 520 with(lf) { 521 lfHeight = -Font.getLfHeight(cast(float)cf.yHeight, GraphicsUnit.TWIP); 522 lfWidth = 0; // ? 523 lfEscapement = 0; // ? 524 lfOrientation = 0; // ? 525 lfWeight = cf.wWeight; 526 if(cf.dwEffects & CFE_BOLD) { 527 if(lfWeight < FW_BOLD) { 528 lfWeight = FW_BOLD; 529 } 530 } 531 lfItalic = (cf.dwEffects & CFE_ITALIC) != 0; 532 lfUnderline = (cf.dwEffects & CFE_UNDERLINE) != 0; 533 lfStrikeOut = (cf.dwEffects & CFE_STRIKEOUT) != 0; 534 lfCharSet = cf.bCharSet; 535 strcpy(lfFaceName.ptr, cf.szFaceName.ptr); 536 lfOutPrecision = OUT_DEFAULT_PRECIS; 537 lf.lfClipPrecision = CLIP_DEFAULT_PRECIS; 538 lf.lfQuality = DEFAULT_QUALITY; 539 lf.lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE; 540 } 541 //return new Font(Font._create(&lf)); 542 LogFont _lf; 543 Font.LOGFONTAtoLogFont(_lf, &lf); 544 return new Font(Font._create(_lf)); 545 } 546 } 547 548 return null; 549 } 550 551 552 553 final @property void selectionBold(bool byes) { // setter 554 if(!created) { 555 return; 556 } 557 558 CHARFORMAT2A cf; 559 560 cf.cbSize = cf.sizeof; 561 cf.dwMask = CFM_BOLD; 562 if(byes) { 563 cf.dwEffects |= CFE_BOLD; 564 } else { 565 cf.dwEffects &= ~CFE_BOLD; 566 } 567 _setFormat(&cf); 568 } 569 570 /// ditto 571 final @property bool selectionBold() { // getter 572 if(created) { 573 CHARFORMAT2A cf; 574 575 cf.cbSize = cf.sizeof; 576 cf.dwMask = CFM_BOLD; 577 _getFormat(&cf); 578 579 return (cf.dwEffects & CFE_BOLD) == CFE_BOLD; 580 } 581 return false; 582 } 583 584 585 586 final @property void selectionUnderline(bool byes) { // setter 587 if(!created) { 588 return; 589 } 590 591 CHARFORMAT2A cf; 592 593 cf.cbSize = cf.sizeof; 594 cf.dwMask = CFM_UNDERLINE; 595 if(byes) { 596 cf.dwEffects |= CFE_UNDERLINE; 597 } else { 598 cf.dwEffects &= ~CFE_UNDERLINE; 599 } 600 _setFormat(&cf); 601 } 602 603 /// ditto 604 final @property bool selectionUnderline() { // getter 605 if(created) { 606 CHARFORMAT2A cf; 607 608 cf.cbSize = cf.sizeof; 609 cf.dwMask = CFM_UNDERLINE; 610 _getFormat(&cf); 611 612 return (cf.dwEffects & CFE_UNDERLINE) == CFE_UNDERLINE; 613 } 614 return false; 615 } 616 617 618 619 final @property void scrollBars(RichTextBoxScrollBars sb) { // setter 620 LONG st; 621 st = _style() & ~(ES_DISABLENOSCROLL | WS_HSCROLL | WS_VSCROLL | 622 ES_AUTOHSCROLL | ES_AUTOVSCROLL); 623 624 final switch(sb) { 625 case RichTextBoxScrollBars.FORCED_BOTH: 626 st |= ES_DISABLENOSCROLL; 627 goto case RichTextBoxScrollBars.BOTH; 628 case RichTextBoxScrollBars.BOTH: 629 st |= WS_HSCROLL | WS_VSCROLL | ES_AUTOHSCROLL | ES_AUTOVSCROLL; 630 break; 631 632 case RichTextBoxScrollBars.FORCED_HORIZONTAL: 633 st |= ES_DISABLENOSCROLL; 634 goto case RichTextBoxScrollBars.HORIZONTAL; 635 case RichTextBoxScrollBars.HORIZONTAL: 636 st |= WS_HSCROLL | ES_AUTOHSCROLL; 637 break; 638 639 case RichTextBoxScrollBars.FORCED_VERTICAL: 640 st |= ES_DISABLENOSCROLL; 641 goto case RichTextBoxScrollBars.VERTICAL; 642 case RichTextBoxScrollBars.VERTICAL: 643 st |= WS_VSCROLL | ES_AUTOVSCROLL; 644 break; 645 646 case RichTextBoxScrollBars.NONE: 647 break; 648 } 649 650 _style(st); 651 652 _crecreate(); 653 } 654 655 /// ditto 656 final @property RichTextBoxScrollBars scrollBars() { // getter 657 LONG wl = _style(); 658 659 if(wl & WS_HSCROLL) { 660 if(wl & WS_VSCROLL) { 661 if(wl & ES_DISABLENOSCROLL) { 662 return RichTextBoxScrollBars.FORCED_BOTH; 663 } 664 return RichTextBoxScrollBars.BOTH; 665 } 666 667 if(wl & ES_DISABLENOSCROLL) { 668 return RichTextBoxScrollBars.FORCED_HORIZONTAL; 669 } 670 return RichTextBoxScrollBars.HORIZONTAL; 671 } 672 673 if(wl & WS_VSCROLL) { 674 if(wl & ES_DISABLENOSCROLL) { 675 return RichTextBoxScrollBars.FORCED_VERTICAL; 676 } 677 return RichTextBoxScrollBars.VERTICAL; 678 } 679 680 return RichTextBoxScrollBars.NONE; 681 } 682 683 684 685 override int getLineFromCharIndex(int charIndex) { 686 if(!isHandleCreated) { 687 return -1; // ... 688 } 689 if(charIndex < 0) { 690 return -1; 691 } 692 return SendMessageA(hwnd, EM_EXLINEFROMCHAR, 0, charIndex); 693 } 694 695 696 private void _getFormat(CHARFORMAT2A* cf, BOOL selection = TRUE) 697 in { 698 assert(created); 699 } 700 body { 701 //SendMessageA(handle, EM_GETCHARFORMAT, selection, cast(LPARAM)cf); 702 //CallWindowProcA(richtextboxPrevWndProc, hwnd, EM_GETCHARFORMAT, selection, cast(LPARAM)cf); 703 dfl.internal.utf.callWindowProc(richtextboxPrevWndProc, hwnd, EM_GETCHARFORMAT, selection, cast(LPARAM)cf); 704 } 705 706 707 private void _setFormat(CHARFORMAT2A* cf, WPARAM scf = SCF_SELECTION) 708 in { 709 assert(created); 710 } 711 body { 712 /+ 713 //if(!SendMessageA(handle, EM_SETCHARFORMAT, scf, cast(LPARAM)cf)) 714 //if(!CallWindowProcA(richtextboxPrevWndProc, hwnd, EM_SETCHARFORMAT, scf, cast(LPARAM)cf)) 715 if(!dfl.internal.utf.callWindowProc(richtextboxPrevWndProc, hwnd, EM_SETCHARFORMAT, scf, cast(LPARAM)cf)) { 716 throw new DflException("Unable to set text formatting"); 717 } 718 +/ 719 dfl.internal.utf.callWindowProc(richtextboxPrevWndProc, hwnd, EM_SETCHARFORMAT, scf, cast(LPARAM)cf); 720 } 721 722 723 private struct _StreamStr { 724 Dstring str; 725 } 726 727 728 // Note: RTF should only be ASCII so no conversions are necessary. 729 // TODO: verify this; I'm not certain. 730 731 private void _streamIn(UINT fmt, Dstring str) 732 in { 733 assert(created); 734 } 735 body { 736 _StreamStr si; 737 EDITSTREAM es; 738 739 si.str = str; 740 es.dwCookie = cast(DWORD)&si; 741 es.pfnCallback = &_streamingInStr; 742 743 //if(SendMessageA(handle, EM_STREAMIN, cast(WPARAM)fmt, cast(LPARAM)&es) != str.length) 744 // throw new DflException("Unable to set RTF"); 745 746 SendMessageA(handle, EM_STREAMIN, cast(WPARAM)fmt, cast(LPARAM)&es); 747 } 748 749 750 private Dstring _streamOut(UINT fmt) 751 in { 752 assert(created); 753 } 754 body { 755 _StreamStr so; 756 EDITSTREAM es; 757 758 so.str = null; 759 es.dwCookie = cast(DWORD)&so; 760 es.pfnCallback = &_streamingOutStr; 761 762 SendMessageA(handle, EM_STREAMOUT, cast(WPARAM)fmt, cast(LPARAM)&es); 763 return so.str; 764 } 765 766 767 768 final @property void selectedRtf(Dstring rtf) { // setter 769 _streamIn(SF_RTF | SFF_SELECTION, rtf); 770 } 771 772 /// ditto 773 final @property Dstring selectedRtf() { // getter 774 return _streamOut(SF_RTF | SFF_SELECTION); 775 } 776 777 778 779 final @property void rtf(Dstring newRtf) { // setter 780 _streamIn(SF_RTF, rtf); 781 } 782 783 /// ditto 784 final @property Dstring rtf() { // getter 785 return _streamOut(SF_RTF); 786 } 787 788 789 790 final @property void detectUrls(bool byes) { // setter 791 autoUrl = byes; 792 793 if(created) { 794 SendMessageA(handle, EM_AUTOURLDETECT, byes, 0); 795 } 796 } 797 798 /// ditto 799 final @property bool detectUrls() { // getter 800 return autoUrl; 801 } 802 803 804 /+ 805 override void createHandle() { 806 if(isHandleCreated) { 807 return; 808 } 809 810 createClassHandle(RICHTEXTBOX_CLASSNAME); 811 812 onHandleCreated(EventArgs.empty); 813 } 814 +/ 815 816 817 /+ 818 override void createHandle() { 819 /+ // TextBoxBase.createHandle() does this. 820 if(!isHandleCreated) { 821 Dstring txt; 822 txt = wtext; 823 824 super.createHandle(); 825 826 //dfl.internal.utf.setWindowText(hwnd, txt); 827 text = txt; // So that it can be overridden. 828 } 829 +/ 830 } 831 +/ 832 833 834 protected override void createParams(ref CreateParams cp) { 835 super.createParams(cp); 836 837 cp.className = RICHTEXTBOX_CLASSNAME; 838 //cp.caption = null; // Set in createHandle() to allow larger buffers. // TextBoxBase.createHandle() does this. 839 } 840 841 842 //LinkClickedEventHandler linkClicked; 843 Event!(RichTextBox, LinkClickedEventArgs) linkClicked; /// 844 845 846 protected: 847 848 849 void onLinkClicked(LinkClickedEventArgs ea) { 850 linkClicked(this, ea); 851 } 852 853 854 private Dstring _getRange(LONG min, LONG max) 855 in { 856 assert(created); 857 assert(max >= 0); 858 assert(max >= min); 859 } 860 body { 861 if(min == max) { 862 return null; 863 } 864 865 TEXTRANGEA tr; 866 char[] s; 867 868 tr.chrg.cpMin = min; 869 tr.chrg.cpMax = max; 870 max = max - min + 1; 871 if(dfl.internal.utf.useUnicode) { 872 max = cast(uint)max << 1; 873 } 874 s = new char[max]; 875 tr.lpstrText = s.ptr; 876 877 //max = SendMessageA(handle, EM_GETTEXTRANGE, 0, cast(LPARAM)&tr); 878 max = dfl.internal.utf.sendMessage(handle, EM_GETTEXTRANGE, 0, cast(LPARAM)&tr); 879 Dstring result; 880 if(dfl.internal.utf.useUnicode) { 881 result = fromUnicode(cast(wchar*)s.ptr, max); 882 } else 883 { result = fromAnsi(s.ptr, max); } 884 return result; 885 } 886 887 888 protected override void onReflectedMessage(ref Message m) { 889 super.onReflectedMessage(m); 890 891 switch(m.msg) { 892 case WM_NOTIFY: { 893 NMHDR* nmh; 894 nmh = cast(NMHDR*)m.lParam; 895 896 assert(nmh.hwndFrom == handle); 897 898 switch(nmh.code) { 899 case EN_LINK: { 900 ENLINK* enl; 901 enl = cast(ENLINK*)nmh; 902 903 if(enl.msg == WM_LBUTTONUP) { 904 if(!selectionLength) { 905 onLinkClicked(new LinkClickedEventArgs(_getRange(enl.chrg.cpMin, enl.chrg.cpMax))); 906 } 907 } 908 } 909 break; 910 911 default: 912 } 913 } 914 break; 915 916 default: 917 } 918 } 919 920 921 override void onHandleCreated(EventArgs ea) { 922 super.onHandleCreated(ea); 923 924 SendMessageA(handle, EM_AUTOURLDETECT, autoUrl, 0); 925 926 _setbk(this.backColor); 927 928 //Application.doEvents(); // foreColor won't work otherwise.. seems to work now 929 _setfc(this.foreColor); 930 931 SendMessageA(handle, EM_SETEVENTMASK, 0, ENM_CHANGE | ENM_CHANGE | ENM_LINK | ENM_PROTECTED); 932 } 933 934 935 override void prevWndProc(ref Message m) { 936 m.result = CallWindowProcA(richtextboxPrevWndProc, m.hWnd, m.msg, m.wParam, m.lParam); 937 //m.result = dfl.internal.utf.callWindowProc(richtextboxPrevWndProc, m.hWnd, m.msg, m.wParam, m.lParam); 938 } 939 940 941 private: 942 bool autoUrl = true; 943 } 944 945 946 private extern(Windows) DWORD _streamingInStr(DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG* pcb) nothrow { 947 RichTextBox._StreamStr* si; 948 si = cast(typeof(si))dwCookie; 949 950 if(!si.str.length) { 951 *pcb = 0; 952 return 1; // ? 953 } else if(cb >= si.str.length) { 954 pbBuff[0 .. si.str.length] = (cast(BYTE[])si.str)[]; 955 *pcb = si.str.length; 956 si.str = null; 957 } else 958 { 959 pbBuff[0 .. cb] = (cast(BYTE[])si.str)[0 .. cb]; 960 *pcb = cb; 961 si.str = si.str[cb .. si.str.length]; 962 } 963 964 return 0; 965 } 966 967 968 private extern(Windows) DWORD _streamingOutStr(DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG* pcb) nothrow { 969 RichTextBox._StreamStr* so; 970 so = cast(typeof(so))dwCookie; 971 972 so.str ~= cast(Dstring)pbBuff[0 .. cb]; 973 *pcb = cb; 974 975 return 0; 976 } 977