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