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