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