1 ///////////////////////////////////////////////////////////////////////////// 
   2 // Name:        msw/button.cpp 
   4 // Author:      Julian Smart 
   8 // Copyright:   (c) Julian Smart 
   9 // Licence:     wxWindows licence 
  10 ///////////////////////////////////////////////////////////////////////////// 
  12 // ============================================================================ 
  14 // ============================================================================ 
  16 // ---------------------------------------------------------------------------- 
  18 // ---------------------------------------------------------------------------- 
  20 #if defined(__GNUG__) && !defined(NO_GCC_PRAGMA) 
  21     #pragma implementation "button.h" 
  24 // For compilers that support precompilation, includes "wx.h". 
  25 #include "wx/wxprec.h" 
  35     #include "wx/button.h" 
  38     #include "wx/bmpbuttn.h" 
  39     #include "wx/settings.h" 
  40     #include "wx/dcscreen.h" 
  43 #include "wx/msw/private.h" 
  45 // ---------------------------------------------------------------------------- 
  47 // ---------------------------------------------------------------------------- 
  49 #if wxUSE_EXTENDED_RTTI 
  51 WX_DEFINE_FLAGS( wxButtonStyle 
) 
  53 wxBEGIN_FLAGS( wxButtonStyle 
) 
  54     // new style border flags, we put them first to 
  55     // use them for streaming out 
  56     wxFLAGS_MEMBER(wxBORDER_SIMPLE
) 
  57     wxFLAGS_MEMBER(wxBORDER_SUNKEN
) 
  58     wxFLAGS_MEMBER(wxBORDER_DOUBLE
) 
  59     wxFLAGS_MEMBER(wxBORDER_RAISED
) 
  60     wxFLAGS_MEMBER(wxBORDER_STATIC
) 
  61     wxFLAGS_MEMBER(wxBORDER_NONE
) 
  63     // old style border flags 
  64     wxFLAGS_MEMBER(wxSIMPLE_BORDER
) 
  65     wxFLAGS_MEMBER(wxSUNKEN_BORDER
) 
  66     wxFLAGS_MEMBER(wxDOUBLE_BORDER
) 
  67     wxFLAGS_MEMBER(wxRAISED_BORDER
) 
  68     wxFLAGS_MEMBER(wxSTATIC_BORDER
) 
  69     wxFLAGS_MEMBER(wxBORDER
) 
  71     // standard window styles 
  72     wxFLAGS_MEMBER(wxTAB_TRAVERSAL
) 
  73     wxFLAGS_MEMBER(wxCLIP_CHILDREN
) 
  74     wxFLAGS_MEMBER(wxTRANSPARENT_WINDOW
) 
  75     wxFLAGS_MEMBER(wxWANTS_CHARS
) 
  76     wxFLAGS_MEMBER(wxFULL_REPAINT_ON_RESIZE
) 
  77     wxFLAGS_MEMBER(wxALWAYS_SHOW_SB 
) 
  78     wxFLAGS_MEMBER(wxVSCROLL
) 
  79     wxFLAGS_MEMBER(wxHSCROLL
) 
  81     wxFLAGS_MEMBER(wxBU_LEFT
) 
  82     wxFLAGS_MEMBER(wxBU_RIGHT
) 
  83     wxFLAGS_MEMBER(wxBU_TOP
) 
  84     wxFLAGS_MEMBER(wxBU_BOTTOM
) 
  85     wxFLAGS_MEMBER(wxBU_EXACTFIT
) 
  86 wxEND_FLAGS( wxButtonStyle 
) 
  88 IMPLEMENT_DYNAMIC_CLASS_XTI(wxButton
, wxControl
,"wx/button.h") 
  90 wxBEGIN_PROPERTIES_TABLE(wxButton
) 
  91         wxEVENT_PROPERTY( Click 
, wxEVT_COMMAND_BUTTON_CLICKED 
, wxCommandEvent
) 
  93         wxPROPERTY( Font 
, wxFont 
, SetFont 
, GetFont  
, , 0 /*flags*/ , wxT("Helpstring") , wxT("group")) 
  94         wxPROPERTY( Label
, wxString 
, SetLabel
, GetLabel
, wxString(), 0 /*flags*/ , wxT("Helpstring") , wxT("group") ) 
  96     wxPROPERTY_FLAGS( WindowStyle 
, wxButtonStyle 
, long , SetWindowStyleFlag 
, GetWindowStyleFlag 
, , 0 /*flags*/ , wxT("Helpstring") , wxT("group")) // style 
  98 wxEND_PROPERTIES_TABLE() 
 100 wxBEGIN_HANDLERS_TABLE(wxButton
) 
 101 wxEND_HANDLERS_TABLE() 
 103 wxCONSTRUCTOR_6( wxButton 
, wxWindow
* , Parent 
, wxWindowID 
, Id 
, wxString 
, Label 
, wxPoint 
, Position 
, wxSize 
, Size 
, long , WindowStyle  
) 
 107 IMPLEMENT_DYNAMIC_CLASS(wxButton
, wxControl
) 
 110 // this macro tries to adjust the default button height to a reasonable value 
 111 // using the char height as the base 
 112 #define BUTTON_HEIGHT_FROM_CHAR_HEIGHT(cy) (11*EDIT_HEIGHT_FROM_CHAR_HEIGHT(cy)/10) 
 114 // ============================================================================ 
 116 // ============================================================================ 
 118 // ---------------------------------------------------------------------------- 
 119 // creation/destruction 
 120 // ---------------------------------------------------------------------------- 
 122 bool wxButton::Create(wxWindow 
*parent
, 
 124                       const wxString
& label
, 
 128                       const wxValidator
& validator
, 
 129                       const wxString
& name
) 
 131     if ( !CreateControl(parent
, id
, pos
, size
, style
, validator
, name
) ) 
 135     WXDWORD msStyle 
= MSWGetStyle(style
, &exstyle
); 
 138     // if the label contains several lines we must explicitly tell the button 
 139     // about it or it wouldn't draw it correctly ("\n"s would just appear as 
 142     // NB: we do it here and not in MSWGetStyle() because we need the label 
 143     //     value and m_label is not set yet when MSWGetStyle() is called; 
 144     //     besides changing BS_MULTILINE during run-time is pointless anyhow 
 145     if ( label
.find(_T('\n')) != wxString::npos 
) 
 147         msStyle 
|= BS_MULTILINE
; 
 151     return MSWCreateControl(_T("BUTTON"), msStyle
, pos
, size
, label
, exstyle
); 
 154 wxButton::~wxButton() 
 158 // ---------------------------------------------------------------------------- 
 160 // ---------------------------------------------------------------------------- 
 162 WXDWORD 
wxButton::MSWGetStyle(long style
, WXDWORD 
*exstyle
) const 
 164     // buttons never have an external border, they draw their own one 
 165     WXDWORD msStyle 
= wxControl::MSWGetStyle
 
 167                         (style 
& ~wxBORDER_MASK
) | wxBORDER_NONE
, exstyle
 
 170     // we must use WS_CLIPSIBLINGS with the buttons or they would draw over 
 171     // each other in any resizeable dialog which has more than one button in 
 173     msStyle 
|= WS_CLIPSIBLINGS
; 
 176     // don't use "else if" here: weird as it is, but you may combine wxBU_LEFT 
 177     // and wxBU_RIGHT to get BS_CENTER! 
 178     if ( style 
& wxBU_LEFT 
) 
 180     if ( style 
& wxBU_RIGHT 
) 
 182     if ( style 
& wxBU_TOP 
) 
 184     if ( style 
& wxBU_BOTTOM 
) 
 185         msStyle 
|= BS_BOTTOM
; 
 188     if ( style 
& wxNO_BORDER 
) 
 190 #endif // __WXWINCE__ 
 196 // ---------------------------------------------------------------------------- 
 197 // size management including autosizing 
 198 // ---------------------------------------------------------------------------- 
 200 wxSize 
wxButton::DoGetBestSize() const 
 203     GetTextExtent(wxGetWindowText(GetHWND()), &wBtn
, NULL
); 
 206     wxGetCharSize(GetHWND(), &wChar
, &hChar
, &GetFont()); 
 208     // add a margin -- the button is wider than just its label 
 211     // the button height is proportional to the height of the font used 
 212     int hBtn 
= BUTTON_HEIGHT_FROM_CHAR_HEIGHT(hChar
); 
 214     // all buttons have at least the standard size unless the user explicitly 
 215     // wants them to be of smaller size and used wxBU_EXACTFIT style when 
 216     // creating the button 
 217     if ( !HasFlag(wxBU_EXACTFIT
) ) 
 219         wxSize sz 
= GetDefaultSize(); 
 228     return wxSize(wBtn
, hBtn
); 
 232 wxSize 
wxButtonBase::GetDefaultSize() 
 234     static wxSize s_sizeBtn
; 
 236     if ( s_sizeBtn
.x 
== 0 ) 
 239         dc
.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT
)); 
 241         // the size of a standard button in the dialog units is 50x14, 
 242         // translate this to pixels 
 243         // NB1: the multipliers come from the Windows convention 
 244         // NB2: the extra +1/+2 were needed to get the size be the same as the 
 245         //      size of the buttons in the standard dialog - I don't know how 
 246         //      this happens, but on my system this size is 75x23 in pixels and 
 247         //      23*8 isn't even divisible by 14... Would be nice to understand 
 248         //      why these constants are needed though! 
 249         s_sizeBtn
.x 
= (50 * (dc
.GetCharWidth() + 1))/4; 
 250         s_sizeBtn
.y 
= ((14 * dc
.GetCharHeight()) + 2)/8; 
 256 // ---------------------------------------------------------------------------- 
 257 // default button handling 
 258 // ---------------------------------------------------------------------------- 
 261    "Everything you ever wanted to know about the default buttons" or "Why do we 
 262    have to do all this?" 
 264    In MSW the default button should be activated when the user presses Enter 
 265    and the current control doesn't process Enter itself somehow. This is 
 266    handled by ::DefWindowProc() (or maybe ::DefDialogProc()) using DM_SETDEFID 
 267    Another aspect of "defaultness" is that the default button has different 
 268    appearance: this is due to BS_DEFPUSHBUTTON style which is completely 
 269    separate from DM_SETDEFID stuff (!). Also note that BS_DEFPUSHBUTTON should 
 270    be unset if our parent window is not active so it should be unset whenever 
 271    we lose activation and set back when we regain it. 
 273    Final complication is that when a button is active, it should be the default 
 274    one, i.e. pressing Enter on a button always activates it and not another 
 277    We handle this by maintaining a permanent and a temporary default items in 
 278    wxControlContainer (both may be NULL). When a button becomes the current 
 279    control (i.e. gets focus) it sets itself as the temporary default which 
 280    ensures that it has the right appearance and that Enter will be redirected 
 281    to it. When the button loses focus, it unsets the temporary default and so 
 282    the default item will be the permanent default -- that is the default button 
 283    if any had been set or none otherwise, which is just what we want. 
 285    NB: all this is quite complicated by now and the worst is that normally 
 286        it shouldn't be necessary at all as for the normal Windows programs 
 287        DefWindowProc() and IsDialogMessage() take care of all this 
 288        automatically -- however in wxWindows programs this doesn't work for 
 289        nested hierarchies (i.e. a notebook inside a notebook) for unknown 
 290        reason and so we have to reproduce all this code ourselves. It would be 
 291        very nice if we could avoid doing it. 
 294 // set this button as the (permanently) default one in its panel 
 295 void wxButton::SetDefault() 
 297     wxWindow 
*parent 
= GetParent(); 
 299     wxCHECK_RET( parent
, _T("button without parent?") ); 
 301     // set this one as the default button both for wxWindows ... 
 302     wxWindow 
*winOldDefault 
= parent
->SetDefaultItem(this); 
 305     SetDefaultStyle(wxDynamicCast(winOldDefault
, wxButton
), FALSE
); 
 306     SetDefaultStyle(this, TRUE
); 
 309 // set this button as being currently default 
 310 void wxButton::SetTmpDefault() 
 312     wxWindow 
*parent 
= GetParent(); 
 314     wxCHECK_RET( parent
, _T("button without parent?") ); 
 316     wxWindow 
*winOldDefault 
= parent
->GetDefaultItem(); 
 317     parent
->SetTmpDefaultItem(this); 
 319     SetDefaultStyle(wxDynamicCast(winOldDefault
, wxButton
), FALSE
); 
 320     SetDefaultStyle(this, TRUE
); 
 323 // unset this button as currently default, it may still stay permanent default 
 324 void wxButton::UnsetTmpDefault() 
 326     wxWindow 
*parent 
= GetParent(); 
 328     wxCHECK_RET( parent
, _T("button without parent?") ); 
 330     parent
->SetTmpDefaultItem(NULL
); 
 332     wxWindow 
*winOldDefault 
= parent
->GetDefaultItem(); 
 334     SetDefaultStyle(this, FALSE
); 
 335     SetDefaultStyle(wxDynamicCast(winOldDefault
, wxButton
), TRUE
); 
 340 wxButton::SetDefaultStyle(wxButton 
*btn
, bool on
) 
 342     // we may be called with NULL pointer -- simpler to do the check here than 
 343     // in the caller which does wxDynamicCast() 
 347     // first, let DefDlgProc() know about the new default button 
 350         // we shouldn't set BS_DEFPUSHBUTTON for any button if we don't have 
 351         // focus at all any more 
 352         if ( !wxTheApp
->IsActive() ) 
 355         // look for a panel-like window 
 356         wxWindow 
*win 
= btn
->GetParent(); 
 357         while ( win 
&& !win
->HasFlag(wxTAB_TRAVERSAL
) ) 
 358             win 
= win
->GetParent(); 
 362             ::SendMessage(GetHwndOf(win
), DM_SETDEFID
, btn
->GetId(), 0L); 
 364             // sending DM_SETDEFID also changes the button style to 
 365             // BS_DEFPUSHBUTTON so there is nothing more to do 
 369     // then also change the style as needed 
 370     long style 
= ::GetWindowLong(GetHwndOf(btn
), GWL_STYLE
); 
 371     if ( !(style 
& BS_DEFPUSHBUTTON
) == on 
) 
 373         // don't do it with the owner drawn buttons because it will 
 374         // reset BS_OWNERDRAW style bit too (as BS_OWNERDRAW & 
 375         // BS_DEFPUSHBUTTON != 0)! 
 376         if ( (style 
& BS_OWNERDRAW
) != BS_OWNERDRAW 
) 
 378             ::SendMessage(GetHwndOf(btn
), BM_SETSTYLE
, 
 379                           on 
? style 
| BS_DEFPUSHBUTTON
 
 380                              : style 
& ~BS_DEFPUSHBUTTON
, 
 385             // redraw the button - it will notice itself that it's 
 386             // [not] the default one [any longer] 
 390     //else: already has correct style 
 393 // ---------------------------------------------------------------------------- 
 395 // ---------------------------------------------------------------------------- 
 397 bool wxButton::SendClickEvent() 
 399     wxCommandEvent 
event(wxEVT_COMMAND_BUTTON_CLICKED
, GetId()); 
 400     event
.SetEventObject(this); 
 402     return ProcessCommand(event
); 
 405 void wxButton::Command(wxCommandEvent 
& event
) 
 407     ProcessCommand(event
); 
 410 // ---------------------------------------------------------------------------- 
 411 // event/message handlers 
 412 // ---------------------------------------------------------------------------- 
 414 bool wxButton::MSWCommand(WXUINT param
, WXWORD 
WXUNUSED(id
)) 
 416     bool processed 
= FALSE
; 
 419         // NOTE: Apparently older versions (NT 4?) of the common controls send 
 420         //       BN_DOUBLECLICKED but not a second BN_CLICKED for owner-drawn 
 421         //       buttons, so in order to send two EVET_BUTTON events we should 
 422         //       catch both types.  Currently (Feb 2003) up-to-date versions of 
 423         //       win98, win2k and winXP all send two BN_CLICKED messages for 
 424         //       all button types, so we don't catch BN_DOUBLECLICKED anymore 
 425         //       in order to not get 3 EVT_BUTTON events.  If this is a problem 
 426         //       then we need to figure out which version of the comctl32 changed 
 427         //       this behaviour and test for it. 
 429         case 1:                     // message came from an accelerator 
 430         case BN_CLICKED
:            // normal buttons send this 
 431             processed 
= SendClickEvent(); 
 438 WXLRESULT 
wxButton::MSWWindowProc(WXUINT nMsg
, WXWPARAM wParam
, WXLPARAM lParam
) 
 440     // when we receive focus, we want to temporary become the default button in 
 441     // our parent panel so that pressing "Enter" would activate us -- and when 
 442     // losing it we should restore the previous default button as well 
 443     if ( nMsg 
== WM_SETFOCUS 
) 
 447         // let the default processing take place too 
 449     else if ( nMsg 
== WM_KILLFOCUS 
) 
 453     else if ( nMsg 
== WM_LBUTTONDBLCLK 
) 
 455         // emulate a click event to force an owner-drawn button to change its 
 456         // appearance - without this, it won't do it 
 457         (void)wxControl::MSWWindowProc(WM_LBUTTONDOWN
, wParam
, lParam
); 
 459         // and continue with processing the message normally as well 
 462     // let the base class do all real processing 
 463     return wxControl::MSWWindowProc(nMsg
, wParam
, lParam
); 
 466 // ---------------------------------------------------------------------------- 
 467 // owner-drawn buttons support 
 468 // ---------------------------------------------------------------------------- 
 474 static void DrawButtonText(HDC hdc
, 
 476                            const wxString
& text
, 
 479     COLORREF colOld 
= SetTextColor(hdc
, col
); 
 480     int modeOld 
= SetBkMode(hdc
, TRANSPARENT
); 
 482     // Note: we must have DT_SINGLELINE for DT_VCENTER to work. 
 483     ::DrawText(hdc
, text
, text
.length(), pRect
, DT_SINGLELINE 
| DT_CENTER 
| DT_VCENTER
); 
 485     SetBkMode(hdc
, modeOld
); 
 486     SetTextColor(hdc
, colOld
); 
 489 static void DrawRect(HDC hdc
, const RECT
& r
) 
 491     wxDrawLine(hdc
, r
.left
, r
.top
, r
.right
, r
.top
); 
 492     wxDrawLine(hdc
, r
.right
, r
.top
, r
.right
, r
.bottom
); 
 493     wxDrawLine(hdc
, r
.right
, r
.bottom
, r
.left
, r
.bottom
); 
 494     wxDrawLine(hdc
, r
.left
, r
.bottom
, r
.left
, r
.top
); 
 497 void wxButton::MakeOwnerDrawn() 
 499     long style 
= GetWindowLong(GetHwnd(), GWL_STYLE
); 
 500     if ( (style 
& BS_OWNERDRAW
) != BS_OWNERDRAW 
) 
 503         style 
|= BS_OWNERDRAW
; 
 504         SetWindowLong(GetHwnd(), GWL_STYLE
, style
); 
 508 bool wxButton::SetBackgroundColour(const wxColour 
&colour
) 
 510     if ( !wxControl::SetBackgroundColour(colour
) ) 
 523 bool wxButton::SetForegroundColour(const wxColour 
&colour
) 
 525     if ( !wxControl::SetForegroundColour(colour
) ) 
 539    The button frame looks like this normally: 
 542    WHHHHHHHHHHHHHHHHGB  W = white       (HILIGHT) 
 543    WH               GB  H = light grey  (LIGHT) 
 544    WH               GB  G = dark grey   (SHADOW) 
 545    WH               GB  B = black       (DKSHADOW) 
 550    When the button is selected, the button becomes like this (the total button 
 551    size doesn't change): 
 562    When the button is pushed (while selected) it is like: 
 574 static void DrawButtonFrame(HDC hdc
, const RECT
& rectBtn
, 
 575                             bool selected
, bool pushed
) 
 578     CopyRect(&r
, &rectBtn
); 
 580     HPEN hpenBlack   
= CreatePen(PS_SOLID
, 1, GetSysColor(COLOR_3DDKSHADOW
)), 
 581          hpenGrey    
= CreatePen(PS_SOLID
, 1, GetSysColor(COLOR_3DSHADOW
)), 
 582          hpenLightGr 
= CreatePen(PS_SOLID
, 1, GetSysColor(COLOR_3DLIGHT
)), 
 583          hpenWhite   
= CreatePen(PS_SOLID
, 1, GetSysColor(COLOR_3DHILIGHT
)); 
 585     HPEN hpenOld 
= (HPEN
)SelectObject(hdc
, hpenBlack
); 
 594         (void)SelectObject(hdc
, hpenGrey
); 
 595         InflateRect(&r
, -1, -1); 
 605             InflateRect(&r
, -1, -1); 
 608         wxDrawLine(hdc
, r
.left
, r
.bottom
, r
.right
, r
.bottom
); 
 609         wxDrawLine(hdc
, r
.right
, r
.bottom
, r
.right
, r
.top 
- 1); 
 611         (void)SelectObject(hdc
, hpenWhite
); 
 612         wxDrawLine(hdc
, r
.left
, r
.bottom 
- 1, r
.left
, r
.top
); 
 613         wxDrawLine(hdc
, r
.left
, r
.top
, r
.right
, r
.top
); 
 615         (void)SelectObject(hdc
, hpenLightGr
); 
 616         wxDrawLine(hdc
, r
.left 
+ 1, r
.bottom 
- 2, r
.left 
+ 1, r
.top 
+ 1); 
 617         wxDrawLine(hdc
, r
.left 
+ 1, r
.top 
+ 1, r
.right 
- 1, r
.top 
+ 1); 
 619         (void)SelectObject(hdc
, hpenGrey
); 
 620         wxDrawLine(hdc
, r
.left 
+ 1, r
.bottom 
- 1, r
.right 
- 1, r
.bottom 
- 1); 
 621         wxDrawLine(hdc
, r
.right 
- 1, r
.bottom 
- 1, r
.right 
- 1, r
.top
); 
 624     (void)SelectObject(hdc
, hpenOld
); 
 625     DeleteObject(hpenWhite
); 
 626     DeleteObject(hpenLightGr
); 
 627     DeleteObject(hpenGrey
); 
 628     DeleteObject(hpenBlack
); 
 631 bool wxButton::MSWOnDraw(WXDRAWITEMSTRUCT 
*wxdis
) 
 633     LPDRAWITEMSTRUCT lpDIS 
= (LPDRAWITEMSTRUCT
)wxdis
; 
 636     CopyRect(&rectBtn
, &lpDIS
->rcItem
); 
 638     COLORREF colBg 
= wxColourToRGB(GetBackgroundColour()), 
 639              colFg 
= wxColourToRGB(GetForegroundColour()); 
 641     HDC hdc 
= lpDIS
->hDC
; 
 642     UINT state 
= lpDIS
->itemState
; 
 644     // first, draw the background 
 645     HBRUSH hbrushBackground 
= ::CreateSolidBrush(colBg
); 
 647     FillRect(hdc
, &rectBtn
, hbrushBackground
); 
 649     // draw the border for the current state 
 650     bool selected 
= (state 
& ODS_SELECTED
) != 0; 
 653         wxPanel 
*panel 
= wxDynamicCast(GetParent(), wxPanel
); 
 656             selected 
= panel
->GetDefaultItem() == this; 
 659     bool pushed 
= (SendMessage(GetHwnd(), BM_GETSTATE
, 0, 0) & BST_PUSHED
) != 0; 
 661     DrawButtonFrame(hdc
, rectBtn
, selected
, pushed
); 
 663     // draw the focus rect if needed 
 664     if ( state 
& ODS_FOCUS 
) 
 667         CopyRect(&rectFocus
, &rectBtn
); 
 669         // I don't know where does this constant come from, but this is how 
 670         // Windows draws them 
 671         InflateRect(&rectFocus
, -4, -4); 
 673         DrawFocusRect(hdc
, &rectFocus
); 
 678         // the label is shifted by 1 pixel to create "pushed" effect 
 679         OffsetRect(&rectBtn
, 1, 1); 
 682     DrawButtonText(hdc
, &rectBtn
, GetLabel(), 
 683                    state 
& ODS_DISABLED 
? GetSysColor(COLOR_GRAYTEXT
) 
 686     ::DeleteObject(hbrushBackground
); 
 693 #endif // wxUSE_BUTTON