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