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 }