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