1 // Written by Christopher E. Miller
2 // See the included license.txt for copyright and license details.
3 
4 
5 ///
6 module dfl.textbox;
7 
8 private import dfl.internal.dlib;
9 
10 private import dfl.control, dfl.base, dfl.internal.winapi, dfl.application;
11 private import dfl.drawing, dfl.event, dfl.internal.utf;
12 
13 version(DFL_NO_MENUS)
14 {
15 }
16 else
17 {
18 	private import dfl.menu;
19 }
20 
21 
22 private extern(Windows) void _initTextBox();
23 
24 
25 // Note: ControlStyles.CACHE_TEXT might not work correctly with a text box.
26 // It's not actually a bug, but a limitation of this control.
27 
28 ///
29 abstract class TextBoxBase: ControlSuperClass // docmain
30 {
31 	///
32 	final @property void acceptsTab(bool byes) // setter
33 	{
34 		atab = byes;
35 		setStyle(ControlStyles.WANT_TAB_KEY, atab);
36 	}
37 	
38 	/// ditto
39 	final @property bool acceptsTab() // getter
40 	{
41 		return atab;
42 	}
43 	
44 	
45 	///
46 	@property void borderStyle(BorderStyle bs) // setter
47 	{
48 		final switch(bs)
49 		{
50 			case BorderStyle.FIXED_3D:
51 				_style(_style() & ~WS_BORDER);
52 				_exStyle(_exStyle() | WS_EX_CLIENTEDGE);
53 				break;
54 				
55 			case BorderStyle.FIXED_SINGLE:
56 				_exStyle(_exStyle() & ~WS_EX_CLIENTEDGE);
57 				_style(_style() | WS_BORDER);
58 				break;
59 				
60 			case BorderStyle.NONE:
61 				_style(_style() & ~WS_BORDER);
62 				_exStyle(_exStyle() & ~WS_EX_CLIENTEDGE);
63 				break;
64 		}
65 		
66 		if(created)
67 		{
68 			redrawEntire();
69 		}
70 	}
71 	
72 	/// ditto
73 	@property BorderStyle borderStyle() // getter
74 	{
75 		if(_exStyle() & WS_EX_CLIENTEDGE)
76 			return BorderStyle.FIXED_3D;
77 		else if(_style() & WS_BORDER)
78 			return BorderStyle.FIXED_SINGLE;
79 		return BorderStyle.NONE;
80 	}
81 	
82 	
83 	///
84 	final @property bool canUndo() // getter
85 	{
86 		if(!created)
87 			return false;
88 		return SendMessageA(handle, EM_CANUNDO, 0, 0) != 0;
89 	}
90 	
91 	
92 	///
93 	final @property void hideSelection(bool byes) // setter
94 	{
95 		if(byes)
96 			_style(_style() & ~ES_NOHIDESEL);
97 		else
98 			_style(_style() | ES_NOHIDESEL);
99 	}
100 	
101 	/// ditto
102 	final @property bool hideSelection() // getter
103 	{
104 		return (_style() & ES_NOHIDESEL) == 0;
105 	}
106 	
107 	
108 	///
109 	final @property void lines(Dstring[] lns) // setter
110 	{
111 		Dstring result;
112 		foreach(Dstring s; lns)
113 		{
114 			result ~= s ~ "\r\n";
115 		}
116 		if(result.length) // Remove last \r\n.
117 			result = result[0 .. result.length - 2];
118 		text = result;
119 	}
120 	
121 	/// ditto
122 	final @property Dstring[] lines() // getter
123 	{
124 		return stringSplitLines(text);
125 	}
126 	
127 	
128 	///
129 	@property void maxLength(uint len) // setter
130 	{
131 		if(!len)
132 		{
133 			if(multiline)
134 				lim = 0xFFFFFFFF;
135 			else
136 				lim = 0x7FFFFFFE;
137 		}
138 		else
139 		{
140 			lim = len;
141 		}
142 		
143 		if(created)
144 		{
145 			Message m;
146 			m = Message(handle, EM_SETLIMITTEXT, cast(WPARAM)lim, 0);
147 			prevWndProc(m);
148 		}
149 	}
150 	
151 	/// ditto
152 	@property uint maxLength() // getter
153 	{
154 		if(created)
155 			lim = cast(uint)SendMessageA(handle, EM_GETLIMITTEXT, 0, 0);
156 		return lim;
157 	}
158 	
159 	
160 	///
161 	final uint getLineCount()
162 	{
163 		if(!multiline)
164 			return 1;
165 		
166 		if(created)
167 		{
168 			return cast(uint)SendMessageA(handle, EM_GETLINECOUNT, 0, 0);
169 		}
170 		
171 		Dstring s;
172 		size_t iw = 0;
173 		uint count = 1;
174 		s = text;
175 		for(; iw != s.length; iw++)
176 		{
177 			if('\r' == s[iw])
178 			{
179 				if(iw + 1 == s.length)
180 					break;
181 				if('\n' == s[iw + 1])
182 				{
183 					iw++;
184 					count++;
185 				}
186 			}
187 		}
188 		return count;
189 	}
190 	
191 	
192 	///
193 	final @property void modified(bool byes) // setter
194 	{
195 		if(created)
196 			SendMessageA(handle, EM_SETMODIFY, byes, 0);
197 	}
198 	
199 	/// ditto
200 	final @property bool modified() // getter
201 	{
202 		if(!created)
203 			return false;
204 		return SendMessageA(handle, EM_GETMODIFY, 0, 0) != 0;
205 	}
206 	
207 	
208 	///
209 	@property void multiline(bool byes) // setter
210 	{
211 		/+
212 		if(byes)
213 			_style(_style() & ~ES_AUTOHSCROLL | ES_MULTILINE);
214 		else
215 			_style(_style() & ~ES_MULTILINE | ES_AUTOHSCROLL);
216 		+/
217 		
218 		// TODO: check if correct implementation.
219 		
220 		LONG st;
221 		
222 		if(byes)
223 		{
224 			st = _style() | ES_MULTILINE | ES_AUTOVSCROLL;
225 			
226 			if(_wrap)
227 				st &= ~ES_AUTOHSCROLL;
228 			else
229 				st |= ES_AUTOHSCROLL;
230 		}
231 		else
232 		{
233 			st = _style() & ~(ES_MULTILINE | ES_AUTOVSCROLL);
234 			
235 			// Always H-scroll when single line.
236 			st |= ES_AUTOHSCROLL;
237 		}
238 		
239 		_style(st);
240 		
241 		_crecreate();
242 	}
243 	
244 	/// ditto
245 	@property bool multiline() // getter
246 	{
247 		return (_style() & ES_MULTILINE) != 0;
248 	}
249 	
250 	
251 	///
252 	final @property void readOnly(bool byes) // setter
253 	{
254 		if(created)
255 		{
256 			SendMessageA(handle, EM_SETREADONLY, byes, 0); // Should trigger WM_STYLECHANGED.
257 			invalidate(); // ?
258 		}
259 		else
260 		{
261 			if(byes)
262 				_style(_style() | ES_READONLY);
263 			else
264 				_style(_style() & ~ES_READONLY);
265 		}
266 	}
267 	
268 	/// ditto
269 	final @property bool readOnly() // getter
270 	{
271 		return (_style() & ES_READONLY) != 0;
272 	}
273 	
274 	
275 	///
276 	@property void selectedText(Dstring sel) // setter
277 	{
278 		/+
279 		if(created)
280 			SendMessageA(handle, EM_REPLACESEL, FALSE, cast(LPARAM)unsafeStringz(sel));
281 		+/
282 		
283 		if(created)
284 		{
285 			//dfl.internal.utf.sendMessage(handle, EM_REPLACESEL, FALSE, sel);
286 			dfl.internal.utf.sendMessageUnsafe(handle, EM_REPLACESEL, FALSE, sel);
287 		}
288 	}
289 	
290 	/// ditto
291 	@property Dstring selectedText() // getter
292 	{
293 		/+
294 		if(created)
295 		{
296 			uint v1, v2;
297 			SendMessageA(handle, EM_GETSEL, cast(WPARAM)&v1, cast(LPARAM)&v2);
298 			if(v1 == v2)
299 				return null;
300 			assert(v2 > v1);
301 			Dstring result = new char[v2 - v1 + 1];
302 			result[result.length - 1] = 0;
303 			result = result[0 .. result.length - 1];
304 			result[] = text[v1 .. v2];
305 			return result;
306 		}
307 		return null;
308 		+/
309 		
310 		if(created)
311 			return dfl.internal.utf.getSelectedText(handle);
312 		return null;
313 	}
314 	
315 	
316 	///
317 	@property void selectionLength(uint len) // setter
318 	{
319 		if(created)
320 		{
321 			uint v1, v2;
322 			SendMessageA(handle, EM_GETSEL, cast(WPARAM)&v1, cast(LPARAM)&v2);
323 			v2 = v1 + len;
324 			SendMessageA(handle, EM_SETSEL, v1, v2);
325 		}
326 	}
327 	
328 	/// ditto
329 	// Current selection length, in characters.
330 	// This does not necessarily correspond to the length of chars; some characters use multiple chars.
331 	// An end of line (\r\n) takes up 2 characters.
332 	@property uint selectionLength() // getter
333 	{
334 		if(created)
335 		{
336 			uint v1, v2;
337 			SendMessageA(handle, EM_GETSEL, cast(WPARAM)&v1, cast(LPARAM)&v2);
338 			assert(v2 >= v1);
339 			return v2 - v1;
340 		}
341 		return 0;
342 	}
343 	
344 	
345 	///
346 	@property void selectionStart(uint pos) // setter
347 	{
348 		if(created)
349 		{
350 			uint v1, v2;
351 			SendMessageA(handle, EM_GETSEL, cast(WPARAM)&v1, cast(LPARAM)&v2);
352 			assert(v2 >= v1);
353 			v2 = pos + (v2 - v1);
354 			SendMessageA(handle, EM_SETSEL, pos, v2);
355 		}
356 	}
357 	
358 	/// ditto
359 	// Current selection starting index, in characters.
360 	// This does not necessarily correspond to the index of chars; some characters use multiple chars.
361 	// An end of line (\r\n) takes up 2 characters.
362 	@property uint selectionStart() // getter
363 	{
364 		if(created)
365 		{
366 			uint v1, v2;
367 			SendMessageA(handle, EM_GETSEL, cast(WPARAM)&v1, cast(LPARAM)&v2);
368 			return v1;
369 		}
370 		return 0;
371 	}
372 	
373 	
374 	///
375 	// Number of characters in the textbox.
376 	// This does not necessarily correspond to the number of chars; some characters use multiple chars.
377 	// An end of line (\r\n) takes up 2 characters.
378 	// Return may be larger than the amount of characters.
379 	// This is a lot faster than retrieving the text, but retrieving the text is completely accurate.
380 	@property uint textLength() // getter
381 	{
382 		if(!(ctrlStyle & ControlStyles.CACHE_TEXT) && created())
383 			//return cast(uint)SendMessageA(handle, WM_GETTEXTLENGTH, 0, 0);
384 			return cast(uint)dfl.internal.utf.sendMessage(handle, WM_GETTEXTLENGTH, 0, 0);
385 		return wtext.length;
386 	}
387 	
388 	
389 	///
390 	@property final void wordWrap(bool byes) // setter
391 	{
392 		/+
393 		if(byes)
394 			_style(_style() | ES_AUTOVSCROLL);
395 		else
396 			_style(_style() & ~ES_AUTOVSCROLL);
397 		+/
398 		
399 		// TODO: check if correct implementation.
400 		
401 		if(_wrap == byes)
402 			return;
403 		
404 		_wrap = byes;
405 		
406 		// Always H-scroll when single line.
407 		if(multiline)
408 		{
409 			if(byes)
410 			{
411 				_style(_style() & ~(ES_AUTOHSCROLL | WS_HSCROLL));
412 			}
413 			else
414 			{
415 				LONG st;
416 				st = _style();
417 				
418 				st |=  ES_AUTOHSCROLL;
419 				
420 				if(_hscroll)
421 					st |= WS_HSCROLL;
422 				
423 				_style(st);
424 			}
425 		}
426 		
427 		_crecreate();
428 	}
429 	
430 	/// ditto
431 	final @property bool wordWrap() // getter
432 	{
433 		//return (_style() & ES_AUTOVSCROLL) != 0;
434 		
435 		return _wrap;
436 	}
437 	
438 	
439 	///
440 	final void appendText(Dstring txt)
441 	{
442 		if(created)
443 		{
444 			selectionStart = textLength;
445 			selectedText = txt;
446 		}
447 		else
448 		{
449 			text = text ~ txt;
450 		}
451 	}
452 	
453 	
454 	///
455 	final void clear()
456 	{
457 		/+
458 		// WM_CLEAR only clears the selection ?
459 		if(created)
460 			SendMessageA(handle, WM_CLEAR, 0, 0);
461 		else
462 			wtext = null;
463 		+/
464 		
465 		text = null;
466 	}
467 	
468 	
469 	///
470 	final void clearUndo()
471 	{
472 		if(created)
473 			SendMessageA(handle, EM_EMPTYUNDOBUFFER, 0, 0);
474 	}
475 	
476 	
477 	///
478 	final void copy()
479 	{
480 		if(created)
481 		{
482 			SendMessageA(handle, WM_COPY, 0, 0);
483 		}
484 		else
485 		{
486 			// There's never a selection if the window isn't created; so just empty the clipboard.
487 			
488 			if(!OpenClipboard(null))
489 			{
490 				debug(APP_PRINT)
491 					cprintf("Unable to OpenClipboard().\n");
492 				//throw new DflException("Unable to set clipboard data.");
493 				return;
494 			}
495 			EmptyClipboard();
496 			CloseClipboard();
497 		}
498 	}
499 	
500 	
501 	///
502 	final void cut()
503 	{
504 		if(created)
505 		{
506 			SendMessageA(handle, WM_CUT, 0, 0);
507 		}
508 		else
509 		{
510 			// There's never a selection if the window isn't created; so just empty the clipboard.
511 			
512 			if(!OpenClipboard(null))
513 			{
514 				debug(APP_PRINT)
515 					cprintf("Unable to OpenClipboard().\n");
516 				//throw new DflException("Unable to set clipboard data.");
517 				return;
518 			}
519 			EmptyClipboard();
520 			CloseClipboard();
521 		}
522 	}
523 	
524 	
525 	///
526 	final void paste()
527 	{
528 		if(created)
529 		{
530 			SendMessageA(handle, WM_PASTE, 0, 0);
531 		}
532 		else
533 		{
534 			// Can't do anything because there's no selection ?
535 		}
536 	}
537 	
538 	
539 	///
540 	final void scrollToCaret()
541 	{
542 		if(created)
543 			SendMessageA(handle, EM_SCROLLCARET, 0, 0);
544 	}
545 	
546 	
547 	///
548 	final void select(uint start, uint length)
549 	{
550 		if(created)
551 			SendMessageA(handle, EM_SETSEL, start, start + length);
552 	}
553 	
554 	alias Control.select select; // Overload.
555 	
556 	
557 	///
558 	final void selectAll()
559 	{
560 		if(created)
561 			SendMessageA(handle, EM_SETSEL, 0, -1);
562 	}
563 	
564 	
565 	override Dstring toString()
566 	{
567 		return text; // ?
568 	}
569 	
570 	
571 	///
572 	final void undo()
573 	{
574 		if(created)
575 			SendMessageA(handle, EM_UNDO, 0, 0);
576 	}
577 	
578 	
579 	/+
580 	override void createHandle()
581 	{
582 		if(isHandleCreated)
583 			return;
584 		
585 		createClassHandle(TEXTBOX_CLASSNAME);
586 		
587 		onHandleCreated(EventArgs.empty);
588 	}
589 	+/
590 	
591 	
592 	override void createHandle()
593 	{
594 		if(!isHandleCreated)
595 		{
596 			Dstring txt;
597 			txt = wtext;
598 			
599 			super.createHandle();
600 			
601 			//dfl.internal.utf.setWindowText(hwnd, txt);
602 			text = txt; // So that it can be overridden.
603 		}
604 	}
605 	
606 	
607 	protected override void createParams(ref CreateParams cp)
608 	{
609 		super.createParams(cp);
610 		
611 		cp.className = TEXTBOX_CLASSNAME;
612 		cp.caption = null; // Set in createHandle() to allow larger buffers.
613 	}
614 	
615 	
616 	protected override void onHandleCreated(EventArgs ea)
617 	{
618 		super.onHandleCreated(ea);
619 		
620 		//SendMessageA(hwnd, EM_SETLIMITTEXT, cast(WPARAM)lim, 0);
621 		maxLength = lim; // Call virtual function.
622 	}
623 	
624 	
625 	private
626 	{
627 		version(DFL_NO_MENUS)
628 		{
629 		}
630 		else
631 		{
632 			void menuUndo(Object sender, EventArgs ea)
633 			{
634 				undo();
635 			}
636 			
637 			
638 			void menuCut(Object sender, EventArgs ea)
639 			{
640 				cut();
641 			}
642 			
643 			
644 			void menuCopy(Object sender, EventArgs ea)
645 			{
646 				copy();
647 			}
648 			
649 			
650 			void menuPaste(Object sender, EventArgs ea)
651 			{
652 				paste();
653 			}
654 			
655 			
656 			void menuDelete(Object sender, EventArgs ea)
657 			{
658 				// Only clear selection.
659 				SendMessageA(handle, WM_CLEAR, 0, 0);
660 			}
661 			
662 			
663 			void menuSelectAll(Object sender, EventArgs ea)
664 			{
665 				selectAll();
666 			}
667 			
668 			
669 			bool isClipboardText()
670 			{
671 				if(!OpenClipboard(handle))
672 					return false;
673 				
674 				bool result;
675 				result = GetClipboardData(CF_TEXT) != null;
676 				
677 				CloseClipboard();
678 				
679 				return result;
680 			}
681 			
682 			
683 			void menuPopup(Object sender, EventArgs ea)
684 			{
685 				int slen, tlen;
686 				bool issel;
687 				
688 				slen = selectionLength;
689 				tlen = textLength;
690 				issel = slen != 0;
691 				
692 				miundo.enabled = canUndo;
693 				micut.enabled = !readOnly() && issel;
694 				micopy.enabled = issel;
695 				mipaste.enabled = !readOnly() && isClipboardText();
696 				midel.enabled = !readOnly() && issel;
697 				misel.enabled = tlen != 0 && tlen != slen;
698 			}
699 			
700 			
701 			MenuItem miundo, micut, micopy, mipaste, midel, misel;
702 		}
703 	}
704 	
705 	
706 	this()
707 	{
708 		_initTextBox();
709 		
710 		wstyle |= WS_TABSTOP | ES_AUTOHSCROLL;
711 		wexstyle |= WS_EX_CLIENTEDGE;
712 		ctrlStyle |= ControlStyles.SELECTABLE;
713 		wclassStyle = textBoxClassStyle;
714 		
715 		version(DFL_NO_MENUS)
716 		{
717 		}
718 		else
719 		{
720 			MenuItem mi;
721 			
722 			cmenu = new ContextMenu;
723 			cmenu.popup ~= &menuPopup;
724 			
725 			miundo = new MenuItem;
726 			miundo.text = "&Undo";
727 			miundo.click ~= &menuUndo;
728 			miundo.index = 0;
729 			cmenu.menuItems.add(miundo);
730 			
731 			mi = new MenuItem;
732 			mi.text = "-";
733 			mi.index = 1;
734 			cmenu.menuItems.add(mi);
735 			
736 			micut = new MenuItem;
737 			micut.text = "Cu&t";
738 			micut.click ~= &menuCut;
739 			micut.index = 2;
740 			cmenu.menuItems.add(micut);
741 			
742 			micopy = new MenuItem;
743 			micopy.text = "&Copy";
744 			micopy.click ~= &menuCopy;
745 			micopy.index = 3;
746 			cmenu.menuItems.add(micopy);
747 			
748 			mipaste = new MenuItem;
749 			mipaste.text = "&Paste";
750 			mipaste.click ~= &menuPaste;
751 			mipaste.index = 4;
752 			cmenu.menuItems.add(mipaste);
753 			
754 			midel = new MenuItem;
755 			midel.text = "&Delete";
756 			midel.click ~= &menuDelete;
757 			midel.index = 5;
758 			cmenu.menuItems.add(midel);
759 			
760 			mi = new MenuItem;
761 			mi.text = "-";
762 			mi.index = 6;
763 			cmenu.menuItems.add(mi);
764 			
765 			misel = new MenuItem;
766 			misel.text = "Select &All";
767 			misel.click ~= &menuSelectAll;
768 			misel.index = 7;
769 			cmenu.menuItems.add(misel);
770 		}
771 	}
772 	
773 	
774 	override @property Color backColor() // getter
775 	{
776 		if(Color.empty == backc)
777 			return defaultBackColor;
778 		return backc;
779 	}
780 	
781 	alias Control.backColor backColor; // Overload.
782 	
783 	
784 	static @property Color defaultBackColor() // getter
785 	{
786 		return Color.systemColor(COLOR_WINDOW);
787 	}
788 	
789 	
790 	override @property Color foreColor() // getter
791 	{
792 		if(Color.empty == forec)
793 			return defaultForeColor;
794 		return forec;
795 	}
796 	
797 	alias Control.foreColor foreColor; // Overload.
798 	
799 	
800 	static @property Color defaultForeColor() //getter
801 	{
802 		return Color.systemColor(COLOR_WINDOWTEXT);
803 	}
804 	
805 	
806 	override @property Cursor cursor() // getter
807 	{
808 		if(!wcurs)
809 			return _defaultCursor;
810 		return wcurs;
811 	}
812 	
813 	alias Control.cursor cursor; // Overload.
814 	
815 	
816 	///
817 	int getFirstCharIndexFromLine(int line)
818 	{
819 		if(!isHandleCreated)
820 			return -1; // ...
821 		if(line < 0)
822 			return -1;
823 		return SendMessageA(hwnd, EM_LINEINDEX, line, 0);
824 	}
825 	
826 	/// ditto
827 	int getFirstCharIndexOfCurrentLine()
828 	{
829 		if(!isHandleCreated)
830 			return -1; // ...
831 		return SendMessageA(hwnd, EM_LINEINDEX, -1, 0);
832 	}
833 	
834 	
835 	///
836 	int getLineFromCharIndex(int charIndex)
837 	{
838 		if(!isHandleCreated)
839 			return -1; // ...
840 		if(charIndex < 0)
841 			return -1;
842 		return SendMessageA(hwnd, EM_LINEFROMCHAR, charIndex, 0);
843 	}
844 	
845 	
846 	///
847 	Point getPositionFromCharIndex(int charIndex)
848 	{
849 		if(!isHandleCreated)
850 			return Point(0, 0); // ...
851 		if(charIndex < 0)
852 			return Point(0, 0);
853 		POINT point;
854 		SendMessageA(hwnd, EM_POSFROMCHAR, cast(WPARAM)&point, charIndex);
855 		return Point(point.x, point.y);
856 	}
857 	
858 	/// ditto
859 	int getCharIndexFromPosition(Point pt)
860 	{
861 		if(!isHandleCreated)
862 			return -1; // ...
863 		if(!multiline)
864 			return 0;
865 		auto lresult = SendMessageA(hwnd, EM_CHARFROMPOS, 0, MAKELPARAM(pt.x, pt.y));
866 		if(-1 == lresult)
867 			return -1;
868 		return cast(int)cast(short)(lresult & 0xFFFF);
869 	}
870 	
871 	
872 	package static @property Cursor _defaultCursor() // getter
873 	{
874 		static Cursor def = null;
875 		
876 		if(!def)
877 		{
878 			synchronized
879 			{
880 				if(!def)
881 					def = new SafeCursor(LoadCursorA(null, IDC_IBEAM));
882 			}
883 		}
884 		
885 		return def;
886 	}
887 	
888 	
889 	protected:
890 	protected override void onReflectedMessage(ref Message m)
891 	{
892 		super.onReflectedMessage(m);
893 		
894 		switch(m.msg)
895 		{
896 			case WM_COMMAND:
897 				switch(HIWORD(m.wParam))
898 				{
899 					case EN_CHANGE:
900 						onTextChanged(EventArgs.empty);
901 						break;
902 					
903 					default:
904 				}
905 				break;
906 			
907 			/+
908 			case WM_CTLCOLORSTATIC:
909 			case WM_CTLCOLOREDIT:
910 				/+
911 				//SetBkColor(cast(HDC)m.wParam, backColor.toRgb()); // ?
912 				SetBkMode(cast(HDC)m.wParam, OPAQUE); // ?
913 				+/
914 				break;
915 			+/
916 			
917 			default:
918 		}
919 	}
920 	
921 	
922 	override void prevWndProc(ref Message msg)
923 	{
924 		version(DFL_NO_MENUS)
925 		{
926 			// Don't prevent WM_CONTEXTMENU so at least it'll have a default menu.
927 		}
928 		else
929 		{
930 			if(msg.msg == WM_CONTEXTMENU) // Ignore the default context menu.
931 				return;
932 		}
933 		
934 		//msg.result = CallWindowProcA(textBoxPrevWndProc, msg.hWnd, msg.msg, msg.wParam, msg.lParam);
935 		msg.result = dfl.internal.utf.callWindowProc(textBoxPrevWndProc, msg.hWnd, msg.msg, msg.wParam, msg.lParam);
936 	}
937 	
938 	
939 	protected override bool processKeyEventArgs(ref Message msg) // package
940 	{
941 		switch(msg.msg)
942 		{
943 			case WM_KEYDOWN:
944 			case WM_KEYUP:
945 			case WM_CHAR:
946 				if('\t' == msg.wParam)
947 				{
948 					// TODO: fix this. This case shouldn't be needed.
949 					if(atab)
950 					{
951 						if(super.processKeyEventArgs(msg))
952 							return true; // Handled.
953 						if(WM_KEYDOWN == msg.msg)
954 						{
955 							if(multiline) // Only multiline textboxes can have real tabs..
956 							{
957 								//selectedText = "\t";
958 								//SendMessageA(handle, EM_REPLACESEL, TRUE, cast(LPARAM)"\t".ptr); // Allow undo. // Crashes DMD 0.161.
959 								auto str = "\t".ptr;
960 								SendMessageA(handle, EM_REPLACESEL, TRUE, cast(LPARAM)str); // Allow undo.
961 							}
962 						}
963 						return true; // Handled.
964 					}
965 				}
966 				break;
967 			
968 			default:
969 		}
970 		return super.processKeyEventArgs(msg);
971 	}
972 	
973 	
974 	override void wndProc(ref Message msg)
975 	{
976 		switch(msg.msg)
977 		{
978 			case WM_GETDLGCODE:
979 				super.wndProc(msg);
980 				if(atab)
981 				{
982 					//if(GetKeyState(Keys.TAB) & 0x8000)
983 					{
984 						//msg.result |= DLGC_WANTALLKEYS;
985 						msg.result |= DLGC_WANTTAB;
986 					}
987 				}
988 				else
989 				{
990 					msg.result &= ~DLGC_WANTTAB;
991 				}
992 				return;
993 			
994 			default:
995 				super.wndProc(msg);
996 		}
997 	}
998 	
999 	
1000 	override @property Size defaultSize() // getter
1001 	{
1002 		return Size(120, 23); // ?
1003 	}
1004 	
1005 	
1006 	private:
1007 	package uint lim = 30_000; // Documented as default.
1008 	bool _wrap = true;
1009 	bool _hscroll;
1010 	
1011 	bool atab = false;
1012 	
1013 	/+
1014 	@property bool atab() // getter
1015 	{
1016 		if(_style() & X)
1017 			return true;
1018 		return false;
1019 	}
1020 	
1021 	@property void atab(bool byes) // setter
1022 	{
1023 		if(byes)
1024 			_style(_style() | X);
1025 		else
1026 			_style(_style() & ~X);
1027 	}
1028 	+/
1029 	
1030 	
1031 	@property void hscroll(bool byes) // setter
1032 	{
1033 		_hscroll = byes;
1034 		
1035 		if(byes && (!_wrap || !multiline))
1036 			_style(_style() | WS_HSCROLL | ES_AUTOHSCROLL);
1037 	}
1038 	
1039 	
1040 	@property bool hscroll() // getter
1041 	{
1042 		return _hscroll;
1043 	}
1044 }
1045 
1046 
1047 ///
1048 class TextBox: TextBoxBase // docmain
1049 {
1050 	///
1051 	final @property void acceptsReturn(bool byes) // setter
1052 	{
1053 		if(byes)
1054 			_style(_style() | ES_WANTRETURN);
1055 		else
1056 			_style(_style() & ~ES_WANTRETURN);
1057 	}
1058 	
1059 	/// ditto
1060 	final @property bool acceptsReturn() // getter
1061 	{
1062 		return (_style() & ES_WANTRETURN) != 0;
1063 	}
1064 	
1065 	
1066 	///
1067 	final @property void characterCasing(CharacterCasing cc) // setter
1068 	{
1069 		LONG wl = _style() & ~(ES_UPPERCASE | ES_LOWERCASE);
1070 		
1071 		final switch(cc)
1072 		{
1073 			case CharacterCasing.UPPER:
1074 				wl |= ES_UPPERCASE;
1075 				break;
1076 			
1077 			case CharacterCasing.LOWER:
1078 				wl |= ES_LOWERCASE;
1079 				break;
1080 			
1081 			case CharacterCasing.NORMAL:
1082 				break;
1083 		}
1084 		
1085 		_style(wl);
1086 	}
1087 	
1088 	/// ditto
1089 	final @property CharacterCasing characterCasing() // getter
1090 	{
1091 		LONG wl = _style();
1092 		if(wl & ES_UPPERCASE)
1093 			return CharacterCasing.UPPER;
1094 		else if(wl & ES_LOWERCASE)
1095 			return CharacterCasing.LOWER;
1096 		return CharacterCasing.NORMAL;
1097 	}
1098 	
1099 	
1100 	///
1101 	// Set to 0 (NUL) to remove.
1102 	final @property void passwordChar(dchar pwc) // setter
1103 	{
1104 		if(pwc)
1105 		{
1106 			// When the EM_SETPASSWORDCHAR message is received by an edit control,
1107 			// the edit control redraws all visible characters by using the
1108 			// character specified by the ch parameter.
1109 			
1110 			if(created)
1111 				//SendMessageA(handle, EM_SETPASSWORDCHAR, pwc, 0);
1112 				dfl.internal.utf.emSetPasswordChar(handle, pwc);
1113 			else
1114 				_style(_style() | ES_PASSWORD);
1115 		}
1116 		else
1117 		{
1118 			// The style ES_PASSWORD is removed if an EM_SETPASSWORDCHAR message
1119 			// is sent with the ch parameter set to zero.
1120 			
1121 			if(created)
1122 				//SendMessageA(handle, EM_SETPASSWORDCHAR, 0, 0);
1123 				dfl.internal.utf.emSetPasswordChar(handle, 0);
1124 			else
1125 				_style(_style() & ~ES_PASSWORD);
1126 		}
1127 		
1128 		passchar = pwc;
1129 	}
1130 	
1131 	/// ditto
1132 	final @property dchar passwordChar() // getter
1133 	{
1134 		if(created)
1135 			//passchar = cast(dchar)SendMessageA(handle, EM_GETPASSWORDCHAR, 0, 0);
1136 			passchar = dfl.internal.utf.emGetPasswordChar(handle);
1137 		return passchar;
1138 	}
1139 	
1140 	
1141 	///
1142 	final @property void scrollBars(ScrollBars sb) // setter
1143 	{
1144 		/+
1145 		switch(sb)
1146 		{
1147 			case ScrollBars.BOTH:
1148 				_style(_style() | WS_HSCROLL | WS_VSCROLL);
1149 				break;
1150 			
1151 			case ScrollBars.HORIZONTAL:
1152 				_style(_style() & ~WS_VSCROLL | WS_HSCROLL);
1153 				break;
1154 			
1155 			case ScrollBars.VERTICAL:
1156 				_style(_style() & ~WS_HSCROLL | WS_VSCROLL);
1157 				break;
1158 			
1159 			case ScrollBars.NONE:
1160 				_style(_style() & ~(WS_HSCROLL | WS_VSCROLL));
1161 				break;
1162 		}
1163 		+/
1164 		final switch(sb)
1165 		{
1166 			case ScrollBars.BOTH:
1167 				_style(_style() | WS_VSCROLL);
1168 				hscroll = true;
1169 				break;
1170 			
1171 			case ScrollBars.HORIZONTAL:
1172 				_style(_style() & ~WS_VSCROLL);
1173 				hscroll = true;
1174 				break;
1175 			
1176 			case ScrollBars.VERTICAL:
1177 				_style(_style() | WS_VSCROLL);
1178 				hscroll = false;
1179 				break;
1180 			
1181 			case ScrollBars.NONE:
1182 				_style(_style() & ~WS_VSCROLL);
1183 				hscroll = false;
1184 				break;
1185 		}
1186 		
1187 		if(created)
1188 			redrawEntire();
1189 	}
1190 	
1191 	/// ditto
1192 	final @property ScrollBars scrollBars() // getter
1193 	{
1194 		LONG wl = _style();
1195 		
1196 		//if(wl & WS_HSCROLL)
1197 		if(hscroll)
1198 		{
1199 			if(wl & WS_VSCROLL)
1200 				return ScrollBars.BOTH;
1201 			return ScrollBars.HORIZONTAL;
1202 		}
1203 		if(wl & WS_VSCROLL)
1204 			return ScrollBars.VERTICAL;
1205 		return ScrollBars.NONE;
1206 	}
1207 	
1208 	
1209 	///
1210 	final @property void textAlign(HorizontalAlignment ha) // setter
1211 	{
1212 		LONG wl = _style() & ~(ES_RIGHT | ES_CENTER | ES_LEFT);
1213 		
1214 		final switch(ha)
1215 		{
1216 			case HorizontalAlignment.RIGHT:
1217 				wl |= ES_RIGHT;
1218 				break;
1219 			
1220 			case HorizontalAlignment.CENTER:
1221 				wl |= ES_CENTER;
1222 				break;
1223 			
1224 			case HorizontalAlignment.LEFT:
1225 				wl |= ES_LEFT;
1226 				break;
1227 		}
1228 		
1229 		_style(wl);
1230 		
1231 		_crecreate();
1232 	}
1233 	
1234 	/// ditto
1235 	final @property HorizontalAlignment textAlign() // getter
1236 	{
1237 		LONG wl = _style();
1238 		
1239 		if(wl & ES_RIGHT)
1240 			return HorizontalAlignment.RIGHT;
1241 		if(wl & ES_CENTER)
1242 			return HorizontalAlignment.CENTER;
1243 		return HorizontalAlignment.LEFT;
1244 	}
1245 	
1246 	
1247 	this()
1248 	{
1249 		wstyle |= ES_LEFT;
1250 	}
1251 	
1252 	
1253 	protected override @property void onHandleCreated(EventArgs ea)
1254 	{
1255 		super.onHandleCreated(ea);
1256 		
1257 		if(passchar)
1258 		{
1259 			SendMessageA(hwnd, EM_SETPASSWORDCHAR, passchar, 0);
1260 		}
1261 	}
1262 	
1263 	
1264 	/+
1265 	override @property void wndProc(ref Message msg)
1266 	{
1267 		switch(msg.msg)
1268 		{
1269 			/+
1270 			case WM_GETDLGCODE:
1271 				if(!acceptsReturn && (GetKeyState(Keys.RETURN) & 0x8000))
1272 				{
1273 					// Hack.
1274 					msg.result = DLGC_HASSETSEL | DLGC_WANTCHARS | DLGC_WANTARROWS;
1275 					return;
1276 				}
1277 				break;
1278 			+/
1279 			
1280 			default:
1281 		}
1282 		
1283 		super.wndProc(msg);
1284 	}
1285 	+/
1286 	
1287 	
1288 	private:
1289 	dchar passchar = 0;
1290 }
1291