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