1 // Written by Christopher E. Miller
2 // See the included license.txt for copyright and license details.
3 
4 
5 
6 module dfl.button;
7 
8 private import dfl.base, dfl.control, dfl.application, dfl.internal.winapi;
9 private import dfl.event, dfl.drawing, dfl.internal.dlib;
10 
11 
12 private extern(Windows) void _initButton();
13 
14 
15 
16 abstract class ButtonBase: ControlSuperClass { // docmain
17 
18    @property void textAlign(ContentAlignment calign) { // setter
19       LONG wl = _bstyle() & ~(BS_BOTTOM | BS_CENTER | BS_TOP | BS_RIGHT | BS_LEFT | BS_VCENTER);
20 
21       final switch(calign) {
22          case ContentAlignment.TOP_LEFT:
23             wl |= BS_TOP | BS_LEFT;
24             break;
25 
26          case ContentAlignment.BOTTOM_CENTER:
27             wl |= BS_BOTTOM | BS_CENTER;
28             break;
29 
30          case ContentAlignment.BOTTOM_LEFT:
31             wl |= BS_BOTTOM | BS_LEFT;
32             break;
33 
34          case ContentAlignment.BOTTOM_RIGHT:
35             wl |= BS_BOTTOM | BS_RIGHT;
36             break;
37 
38          case ContentAlignment.MIDDLE_CENTER:
39             wl |= BS_CENTER | BS_VCENTER;
40             break;
41 
42          case ContentAlignment.MIDDLE_LEFT:
43             wl |= BS_VCENTER | BS_LEFT;
44             break;
45 
46          case ContentAlignment.MIDDLE_RIGHT:
47             wl |= BS_VCENTER | BS_RIGHT;
48             break;
49 
50          case ContentAlignment.TOP_CENTER:
51             wl |= BS_TOP | BS_CENTER;
52             break;
53 
54          case ContentAlignment.TOP_RIGHT:
55             wl |= BS_TOP | BS_RIGHT;
56             break;
57       }
58 
59       _bstyle(wl);
60 
61       _crecreate();
62    }
63 
64    /// ditto
65    @property ContentAlignment textAlign() { // getter
66       LONG wl = _bstyle();
67 
68       if(wl & BS_VCENTER) { // Middle.
69          if(wl & BS_CENTER) {
70             return ContentAlignment.MIDDLE_CENTER;
71          }
72          if(wl & BS_RIGHT) {
73             return ContentAlignment.MIDDLE_RIGHT;
74          }
75          return ContentAlignment.MIDDLE_LEFT;
76       } else if(wl & BS_BOTTOM) { // Bottom.
77          if(wl & BS_CENTER) {
78             return ContentAlignment.BOTTOM_CENTER;
79          }
80          if(wl & BS_RIGHT) {
81             return ContentAlignment.BOTTOM_RIGHT;
82          }
83          return ContentAlignment.BOTTOM_LEFT;
84       } else { // Top.
85          if(wl & BS_CENTER) {
86             return ContentAlignment.TOP_CENTER;
87          }
88          if(wl & BS_RIGHT) {
89             return ContentAlignment.TOP_RIGHT;
90          }
91          return ContentAlignment.TOP_LEFT;
92       }
93    }
94 
95 
96    // Border stuff...
97 
98 
99    /+
100    override void createHandle() {
101       if(isHandleCreated) {
102          return;
103       }
104 
105       createClassHandle(BUTTON_CLASSNAME);
106 
107       onHandleCreated(EventArgs.empty);
108    }
109    +/
110 
111 
112    protected override void createParams(ref CreateParams cp) {
113       super.createParams(cp);
114 
115       cp.className = BUTTON_CLASSNAME;
116       if(isdef) {
117          cp.menu = cast(HMENU)IDOK;
118          if(!(cp.style & WS_DISABLED)) {
119             cp.style |= BS_DEFPUSHBUTTON;
120          }
121       } else if(cp.style & WS_DISABLED) {
122          cp.style &= ~BS_DEFPUSHBUTTON;
123       }
124    }
125 
126 
127    protected override void prevWndProc(ref Message msg) {
128       //msg.result = CallWindowProcA(buttonPrevWndProc, msg.hWnd, msg.msg, msg.wParam, msg.lParam);
129       msg.result = dfl.internal.utf.callWindowProc(buttonPrevWndProc, msg.hWnd, msg.msg, msg.wParam, msg.lParam);
130    }
131 
132 
133    protected override void onReflectedMessage(ref Message m) {
134       super.onReflectedMessage(m);
135 
136       switch(m.msg) {
137          case WM_COMMAND:
138             assert(cast(HWND)m.lParam == handle);
139 
140             switch(HIWORD(m.wParam)) {
141                case BN_CLICKED:
142                   onClick(EventArgs.empty);
143                   break;
144 
145                default:
146             }
147             break;
148 
149          default:
150       }
151    }
152 
153 
154    protected override void wndProc(ref Message msg) {
155       switch(msg.msg) {
156          case WM_LBUTTONDOWN:
157             onMouseDown(new MouseEventArgs(MouseButtons.LEFT, 0, cast(short)LOWORD(msg.lParam), cast(short)HIWORD(msg.lParam), 0));
158             break;
159 
160          case WM_LBUTTONUP:
161             onMouseUp(new MouseEventArgs(MouseButtons.LEFT, 1, cast(short)LOWORD(msg.lParam), cast(short)HIWORD(msg.lParam), 0));
162             break;
163 
164          default:
165             super.wndProc(msg);
166             return;
167       }
168       prevWndProc(msg);
169    }
170 
171 
172    /+
173    protected override void onHandleCreated(EventArgs ea) {
174       super.onHandleCreated(ea);
175 
176       /+
177       // Done in createParams() now.
178       if(isdef) {
179          SetWindowLongA(handle, GWL_ID, IDOK);
180       }
181       +/
182    }
183    +/
184 
185 
186    this() {
187       _initButton();
188 
189       wstyle |= WS_TABSTOP /+ | BS_NOTIFY +/;
190       ctrlStyle |= ControlStyles.SELECTABLE;
191       wclassStyle = buttonClassStyle;
192    }
193 
194 
195  protected:
196 
197 
198    final @property void isDefault(bool byes) { // setter
199       isdef = byes;
200    }
201 
202    /// ditto
203    final @property bool isDefault() { // getter
204       //return (_bstyle() & BS_DEFPUSHBUTTON) == BS_DEFPUSHBUTTON;
205       //return GetDlgCtrlID(m.hWnd) == IDOK;
206       return isdef;
207    }
208 
209 
210    protected override bool processMnemonic(dchar charCode) {
211       if(canSelect) {
212          if(isMnemonic(charCode, text)) {
213             select();
214             //Application.doEvents(); // ?
215             //performClick();
216             onClick(EventArgs.empty);
217             return true;
218          }
219       }
220       return false;
221    }
222 
223 
224 
225    override @property Size defaultSize() { // getter
226       return Size(75, 23);
227    }
228 
229 
230  private:
231    bool isdef = false;
232 
233 
234  package:
235  final:
236    // Automatically redraws button styles, unlike _style().
237    // Don't use with regular window styles ?
238    void _bstyle(LONG newStyle) {
239       if(isHandleCreated)
240          //SendMessageA(handle, BM_SETSTYLE, LOWORD(newStyle), MAKELPARAM(TRUE, 0));
241       {
242          SendMessageA(handle, BM_SETSTYLE, newStyle, MAKELPARAM(TRUE, 0));
243       }
244 
245       wstyle = newStyle;
246       //_style(newStyle);
247    }
248 
249 
250    LONG _bstyle() {
251       return _style();
252    }
253 }
254 
255 
256 
257 class Button: ButtonBase, IButtonControl { // docmain
258    this() {
259    }
260 
261 
262 
263    @property DialogResult dialogResult() { // getter
264       return dresult;
265    }
266 
267    /// ditto
268    @property void dialogResult(DialogResult dr) { // setter
269       dresult = dr;
270    }
271 
272 
273 
274    // True if default button.
275    void notifyDefault(bool byes) {
276       isDefault = byes;
277 
278       if(byes) {
279          if(enabled) { // Only show thick border if enabled.
280             _bstyle(_bstyle() | BS_DEFPUSHBUTTON);
281          }
282       } else {
283          _bstyle(_bstyle() & ~BS_DEFPUSHBUTTON);
284       }
285    }
286 
287 
288 
289    void performClick() {
290       if(!enabled || !visible || !isHandleCreated) { // ?
291          return;   // ?
292       }
293 
294       // This is actually not so good because it sets focus to the control.
295       //SendMessageA(handle, BM_CLICK, 0, 0); // So that wndProc() gets it.
296 
297       onClick(EventArgs.empty);
298    }
299 
300 
301    protected override void onClick(EventArgs ea) {
302       super.onClick(ea);
303 
304       if(!(Application._compat & DflCompat.FORM_DIALOGRESULT_096)) {
305          if(DialogResult.NONE != this.dialogResult) {
306             auto xx = cast(IDialogResult)topLevelControl;
307             if(xx) {
308                xx.dialogResult = this.dialogResult;
309             }
310          }
311       }
312    }
313 
314 
315    protected override void wndProc(ref Message m) {
316       switch(m.msg) {
317          case WM_ENABLE: {
318                // Fixing the thick border of a default button when enabling and disabling it.
319 
320                // To-do: check if correct implementation.
321 
322                DWORD bst;
323                bst = _bstyle();
324                if(bst & BS_DEFPUSHBUTTON) {
325                   //_bstyle(bst); // Force the border to be updated. Only works when enabling.
326                   if(!m.wParam) {
327                      _bstyle(bst & ~BS_DEFPUSHBUTTON);
328                   }
329                } else if(m.wParam) {
330                   //if(GetDlgCtrlID(m.hWnd) == IDOK)
331                   if(isdef) {
332                      _bstyle(bst | BS_DEFPUSHBUTTON);
333                   }
334                }
335             }
336             break;
337 
338          default:
339       }
340 
341       super.wndProc(m);
342    }
343 
344 
345    override @property void text(Dstring txt) { // setter
346       if(txt.length) {
347          assert(!this.image, "Button image with text not supported");
348       }
349 
350       super.text = txt;
351    }
352 
353    alias Control.text text; // Overload.
354 
355 
356 
357    final @property Image image() { // getter
358       return _img;
359    }
360 
361    /// ditto
362    final @property void image(Image img) // setter
363    in {
364       if(img) {
365          assert(!this.text.length, "Button image with text not supported");
366       }
367    }
368    body {
369       /+
370       if(_picbm) {
371          _picbm.dispose();
372          _picbm = null;
373       }
374       +/
375 
376       _img = null; // In case of exception.
377       LONG imgst = 0;
378       if(img) {
379          /+
380          if(cast(Bitmap)img) {
381             imgst = BS_BITMAP;
382          } else if(cast(Icon)img) {
383             imgst = BS_ICON;
384          } else {
385             if(cast(Picture)img) {
386                _picbm = (cast(Picture)img).toBitmap();
387                imgst = BS_BITMAP;
388                goto not_unsupported;
389             }
390 
391             throw new DflException("Unsupported image format");
392          not_unsupported: ;
393          }
394          +/
395          switch(img._imgtype(null)) {
396             case 1:
397                imgst = BS_BITMAP;
398                break;
399 
400             case 2:
401                imgst = BS_ICON;
402                break;
403 
404             default:
405                throw new DflException("Unsupported image format");
406             not_unsupported: ;
407          }
408       }
409 
410       _img = img;
411       _style((_style() & ~(BS_BITMAP | BS_ICON)) | imgst); // Redrawn manually in setImg().
412       if(img) {
413          if(isHandleCreated) {
414             setImg(imgst);
415          }
416       }
417       //_bstyle((_bstyle() & ~(BS_BITMAP | BS_ICON)) | imgst);
418    }
419 
420 
421    private void setImg(LONG bsImageStyle)
422    in {
423       assert(isHandleCreated);
424    }
425    body {
426       WPARAM wparam = 0;
427       LPARAM lparam = 0;
428 
429       /+
430       if(bsImageStyle & BS_BITMAP) {
431          wparam = IMAGE_BITMAP;
432          lparam = cast(LPARAM)(_picbm ? _picbm.handle : (cast(Bitmap)_img).handle);
433       } else if(bsImageStyle & BS_ICON) {
434          wparam = IMAGE_ICON;
435          lparam = cast(LPARAM)((cast(Icon)(_img)).handle);
436       } else
437       {
438          return;
439       }
440       +/
441       if(!_img) {
442          return;
443       }
444       HGDIOBJ hgo;
445       switch(_img._imgtype(&hgo)) {
446          case 1:
447             wparam = IMAGE_BITMAP;
448             break;
449 
450          case 2:
451             wparam = IMAGE_ICON;
452             break;
453 
454          default:
455             return;
456       }
457       lparam = cast(LPARAM)hgo;
458 
459       //assert(lparam);
460       SendMessageA(handle, BM_SETIMAGE, wparam, lparam);
461       invalidate();
462    }
463 
464 
465    protected override void onHandleCreated(EventArgs ea) {
466       super.onHandleCreated(ea);
467 
468       setImg(_bstyle());
469    }
470 
471 
472    protected override void onHandleDestroyed(EventArgs ea) {
473       super.onHandleDestroyed(ea);
474 
475       /+
476       if(_picbm) {
477          _picbm.dispose();
478          _picbm = null;
479       }
480       +/
481    }
482 
483 
484  private:
485    DialogResult dresult = DialogResult.NONE;
486    Image _img = null;
487    //Bitmap _picbm = null; // If -_img- is a Picture, need to keep a separate Bitmap.
488 }
489 
490 
491 
492 class CheckBox: ButtonBase { // docmain
493 
494    final @property void appearance(Appearance ap) { // setter
495       final switch(ap) {
496          case Appearance.NORMAL:
497             _bstyle(_bstyle() & ~BS_PUSHLIKE);
498             break;
499 
500          case Appearance.BUTTON:
501             _bstyle(_bstyle() | BS_PUSHLIKE);
502             break;
503       }
504 
505       _crecreate();
506    }
507 
508    /// ditto
509    final @property Appearance appearance() { // getter
510       if(_bstyle() & BS_PUSHLIKE) {
511          return Appearance.BUTTON;
512       }
513       return Appearance.NORMAL;
514    }
515 
516 
517 
518    final @property void autoCheck(bool byes) { // setter
519       if(byes) {
520          _bstyle((_bstyle() & ~BS_CHECKBOX) | BS_AUTOCHECKBOX);
521       } else {
522          _bstyle((_bstyle() & ~BS_AUTOCHECKBOX) | BS_CHECKBOX);
523       }
524       // Enabling/disabling the window before creation messes
525       // up the autocheck style flag, so handle it manually.
526       _autocheck = byes;
527    }
528 
529    /// ditto
530    final @property bool autoCheck() { // getter
531       /+
532       return (_bstyle() & BS_AUTOCHECKBOX) == BS_AUTOCHECKBOX;
533       +/
534       return _autocheck;
535    }
536 
537 
538    this() {
539       wstyle |= BS_AUTOCHECKBOX | BS_LEFT | BS_VCENTER; // Auto check and MIDDLE_LEFT by default.
540    }
541 
542 
543    /+
544    protected override void onClick(EventArgs ea) {
545       _updateState();
546 
547       super.onClick(ea);
548    }
549    +/
550 
551 
552 
553    final @property void checked(bool byes) { // setter
554       if(byes) {
555          _check = CheckState.CHECKED;
556       } else {
557          _check = CheckState.UNCHECKED;
558       }
559 
560       if(isHandleCreated) {
561          SendMessageA(handle, BM_SETCHECK, cast(WPARAM)_check, 0);
562       }
563    }
564 
565    /// ditto
566    // Returns true for indeterminate too.
567    final @property bool checked() { // getter
568       if(isHandleCreated) {
569          _updateState();
570       }
571       return _check != CheckState.UNCHECKED;
572    }
573 
574 
575 
576    final @property void checkState(CheckState st) { // setter
577       _check = st;
578 
579       if(isHandleCreated) {
580          SendMessageA(handle, BM_SETCHECK, cast(WPARAM)st, 0);
581       }
582    }
583 
584    /// ditto
585    final @property CheckState checkState() { // getter
586       if(isHandleCreated) {
587          _updateState();
588       }
589       return _check;
590    }
591 
592 
593    protected override void onHandleCreated(EventArgs ea) {
594       super.onHandleCreated(ea);
595 
596       if(_autocheck) {
597          _bstyle((_bstyle() & ~BS_CHECKBOX) | BS_AUTOCHECKBOX);
598       } else {
599          _bstyle((_bstyle() & ~BS_AUTOCHECKBOX) | BS_CHECKBOX);
600       }
601 
602       SendMessageA(handle, BM_SETCHECK, cast(WPARAM)_check, 0);
603    }
604 
605 
606  private:
607    CheckState _check = CheckState.UNCHECKED; // Not always accurate.
608    bool _autocheck = true;
609 
610 
611    void _updateState() {
612       _check = cast(CheckState)SendMessageA(handle, BM_GETCHECK, 0, 0);
613    }
614 }
615 
616 
617 
618 class RadioButton: ButtonBase { // docmain
619 
620    final @property void appearance(Appearance ap) { // setter
621       final switch(ap) {
622          case Appearance.NORMAL:
623             _bstyle(_bstyle() & ~BS_PUSHLIKE);
624             break;
625 
626          case Appearance.BUTTON:
627             _bstyle(_bstyle() | BS_PUSHLIKE);
628             break;
629       }
630 
631       _crecreate();
632    }
633 
634    /// ditto
635    final @property Appearance appearance() { // getter
636       if(_bstyle() & BS_PUSHLIKE) {
637          return Appearance.BUTTON;
638       }
639       return Appearance.NORMAL;
640    }
641 
642 
643 
644    final @property void autoCheck(bool byes) { // setter
645       /+
646       if(byes) {
647          _bstyle((_bstyle() & ~BS_RADIOBUTTON) | BS_AUTORADIOBUTTON);
648       } else {
649          _bstyle((_bstyle() & ~BS_AUTORADIOBUTTON) | BS_RADIOBUTTON);
650       }
651       // Enabling/disabling the window before creation messes
652       // up the autocheck style flag, so handle it manually.
653       +/
654       _autocheck = byes;
655    }
656 
657 
658    /// ditto
659    final @property bool autoCheck() { // getter
660       /+ // Also commented out when using BS_AUTORADIOBUTTON.
661       return (_bstyle() & BS_AUTOCHECKBOX) == BS_AUTOCHECKBOX;
662       +/
663       return _autocheck;
664    }
665 
666 
667    this() {
668       wstyle &= ~WS_TABSTOP;
669       //wstyle |= BS_AUTORADIOBUTTON | BS_LEFT | BS_VCENTER; // MIDDLE_LEFT by default.
670       wstyle |= BS_RADIOBUTTON | BS_LEFT | BS_VCENTER; // MIDDLE_LEFT by default.
671    }
672 
673 
674    protected override void onClick(EventArgs ea) {
675       if(autoCheck) {
676          if(parent) { // Sanity.
677             foreach(Control ctrl; parent.controls) {
678                if(ctrl is this) {
679                   continue;
680                }
681                if((ctrl._rtype() & (1 | 8)) == (1 | 8)) { // Radio button + auto check.
682                   (cast(RadioButton)ctrl).checked = false;
683                }
684             }
685          }
686          checked = true;
687       }
688 
689       super.onClick(ea);
690    }
691 
692 
693    /+
694    protected override void onClick(EventArgs ea) {
695       _updateState();
696 
697       super.onClick(ea);
698    }
699    +/
700 
701 
702 
703    final @property void checked(bool byes) { // setter
704       if(byes) {
705          _check = CheckState.CHECKED;
706       } else {
707          _check = CheckState.UNCHECKED;
708       }
709 
710       if(isHandleCreated) {
711          SendMessageA(handle, BM_SETCHECK, cast(WPARAM)_check, 0);
712       }
713    }
714 
715    /// ditto
716    // Returns true for indeterminate too.
717    final @property bool checked() { // getter
718       if(isHandleCreated) {
719          _updateState();
720       }
721       return _check != CheckState.UNCHECKED;
722    }
723 
724 
725 
726    final @property void checkState(CheckState st) { // setter
727       _check = st;
728 
729       if(isHandleCreated) {
730          SendMessageA(handle, BM_SETCHECK, cast(WPARAM)st, 0);
731       }
732    }
733 
734    /// ditto
735    final @property CheckState checkState() { // getter
736       if(isHandleCreated) {
737          _updateState();
738       }
739       return _check;
740    }
741 
742 
743 
744    void performClick() {
745       //onClick(EventArgs.empty);
746       SendMessageA(handle, BM_CLICK, 0, 0); // So that wndProc() gets it.
747    }
748 
749 
750    protected override void onHandleCreated(EventArgs ea) {
751       super.onHandleCreated(ea);
752 
753       /+
754       if(_autocheck) {
755          _bstyle((_bstyle() & ~BS_RADIOBUTTON) | BS_AUTORADIOBUTTON);
756       } else {
757          _bstyle((_bstyle() & ~BS_AUTORADIOBUTTON) | BS_RADIOBUTTON);
758       }
759       +/
760 
761       SendMessageA(handle, BM_SETCHECK, cast(WPARAM)_check, 0);
762    }
763 
764 
765    /+
766    protected override void onReflectedMessage(ref Message m) {
767       super.onReflectedMessage(m);
768 
769       switch(m.msg) {
770             /+
771          // Without this, with XP styles, the background just ends up transparent; not the requested color.
772          // This erases the text when XP styles aren't enabled.
773          case WM_CTLCOLORSTATIC:
774          case WM_CTLCOLORBTN: {
775                //if(hasVisualStyle)
776                {
777                   RECT rect;
778                   rect.right = width;
779                   rect.bottom = height;
780                   FillRect(cast(HDC)m.wParam, &rect, hbrBg);
781                }
782             }
783             break;
784             +/
785 
786          default:
787       }
788    }
789    +/
790 
791 
792    /+ package +/ /+ protected +/ override int _rtype() { // package
793       if(autoCheck) {
794          return 1 | 8;   // Radio button + auto check.
795       }
796       return 1; // Radio button.
797    }
798 
799 
800  private:
801    CheckState _check = CheckState.UNCHECKED; // Not always accurate.
802    bool _autocheck = true;
803 
804 
805    void _updateState() {
806       _check = cast(CheckState)SendMessageA(handle, BM_GETCHECK, 0, 0);
807    }
808 }
809