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