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 // For compilers that support precompilation, includes "wx.h".
21 #include "wx/wxprec.h"
31 #include "wx/button.h"
34 #include "wx/bmpbuttn.h"
35 #include "wx/settings.h"
36 #include "wx/dcscreen.h"
39 #include "wx/stockitem.h"
40 #include "wx/tokenzr.h"
41 #include "wx/msw/private.h"
43 // ----------------------------------------------------------------------------
45 // ----------------------------------------------------------------------------
47 #if wxUSE_EXTENDED_RTTI
49 WX_DEFINE_FLAGS( wxButtonStyle
)
51 wxBEGIN_FLAGS( wxButtonStyle
)
52 // new style border flags, we put them first to
53 // use them for streaming out
54 wxFLAGS_MEMBER(wxBORDER_SIMPLE
)
55 wxFLAGS_MEMBER(wxBORDER_SUNKEN
)
56 wxFLAGS_MEMBER(wxBORDER_DOUBLE
)
57 wxFLAGS_MEMBER(wxBORDER_RAISED
)
58 wxFLAGS_MEMBER(wxBORDER_STATIC
)
59 wxFLAGS_MEMBER(wxBORDER_NONE
)
61 // old style border flags
62 wxFLAGS_MEMBER(wxSIMPLE_BORDER
)
63 wxFLAGS_MEMBER(wxSUNKEN_BORDER
)
64 wxFLAGS_MEMBER(wxDOUBLE_BORDER
)
65 wxFLAGS_MEMBER(wxRAISED_BORDER
)
66 wxFLAGS_MEMBER(wxSTATIC_BORDER
)
67 wxFLAGS_MEMBER(wxBORDER
)
69 // standard window styles
70 wxFLAGS_MEMBER(wxTAB_TRAVERSAL
)
71 wxFLAGS_MEMBER(wxCLIP_CHILDREN
)
72 wxFLAGS_MEMBER(wxTRANSPARENT_WINDOW
)
73 wxFLAGS_MEMBER(wxWANTS_CHARS
)
74 wxFLAGS_MEMBER(wxFULL_REPAINT_ON_RESIZE
)
75 wxFLAGS_MEMBER(wxALWAYS_SHOW_SB
)
76 wxFLAGS_MEMBER(wxVSCROLL
)
77 wxFLAGS_MEMBER(wxHSCROLL
)
79 wxFLAGS_MEMBER(wxBU_LEFT
)
80 wxFLAGS_MEMBER(wxBU_RIGHT
)
81 wxFLAGS_MEMBER(wxBU_TOP
)
82 wxFLAGS_MEMBER(wxBU_BOTTOM
)
83 wxFLAGS_MEMBER(wxBU_EXACTFIT
)
84 wxEND_FLAGS( wxButtonStyle
)
86 IMPLEMENT_DYNAMIC_CLASS_XTI(wxButton
, wxControl
,"wx/button.h")
88 wxBEGIN_PROPERTIES_TABLE(wxButton
)
89 wxEVENT_PROPERTY( Click
, wxEVT_COMMAND_BUTTON_CLICKED
, wxCommandEvent
)
91 wxPROPERTY( Font
, wxFont
, SetFont
, GetFont
, EMPTY_MACROVALUE
, 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
92 wxPROPERTY( Label
, wxString
, SetLabel
, GetLabel
, wxString(), 0 /*flags*/ , wxT("Helpstring") , wxT("group") )
94 wxPROPERTY_FLAGS( WindowStyle
, wxButtonStyle
, long , SetWindowStyleFlag
, GetWindowStyleFlag
, EMPTY_MACROVALUE
, 0 /*flags*/ , wxT("Helpstring") , wxT("group")) // style
96 wxEND_PROPERTIES_TABLE()
98 wxBEGIN_HANDLERS_TABLE(wxButton
)
99 wxEND_HANDLERS_TABLE()
101 wxCONSTRUCTOR_6( wxButton
, wxWindow
* , Parent
, wxWindowID
, Id
, wxString
, Label
, wxPoint
, Position
, wxSize
, Size
, long , WindowStyle
)
105 IMPLEMENT_DYNAMIC_CLASS(wxButton
, wxControl
)
108 // this macro tries to adjust the default button height to a reasonable value
109 // using the char height as the base
110 #define BUTTON_HEIGHT_FROM_CHAR_HEIGHT(cy) (11*EDIT_HEIGHT_FROM_CHAR_HEIGHT(cy)/10)
112 // ============================================================================
114 // ============================================================================
116 // ----------------------------------------------------------------------------
117 // creation/destruction
118 // ----------------------------------------------------------------------------
120 bool wxButton::Create(wxWindow
*parent
,
126 const wxValidator
& validator
,
127 const wxString
& name
)
130 if (label
.empty() && wxIsStockID(id
))
132 // On Windows, some buttons aren't supposed to have
133 // mnemonics, so strip them out.
135 label
= wxGetStockLabel(id
136 #if defined(__WXMSW__) || defined(__WXWINCE__)
144 if ( !CreateControl(parent
, id
, pos
, size
, style
, validator
, name
) )
148 WXDWORD msStyle
= MSWGetStyle(style
, &exstyle
);
151 // if the label contains several lines we must explicitly tell the button
152 // about it or it wouldn't draw it correctly ("\n"s would just appear as
155 // NB: we do it here and not in MSWGetStyle() because we need the label
156 // value and m_label is not set yet when MSWGetStyle() is called;
157 // besides changing BS_MULTILINE during run-time is pointless anyhow
158 if ( label
.find(_T('\n')) != wxString::npos
)
160 msStyle
|= BS_MULTILINE
;
164 return MSWCreateControl(_T("BUTTON"), msStyle
, pos
, size
, label
, exstyle
);
167 wxButton::~wxButton()
171 // ----------------------------------------------------------------------------
173 // ----------------------------------------------------------------------------
175 WXDWORD
wxButton::MSWGetStyle(long style
, WXDWORD
*exstyle
) const
177 // buttons never have an external border, they draw their own one
178 WXDWORD msStyle
= wxControl::MSWGetStyle
180 (style
& ~wxBORDER_MASK
) | wxBORDER_NONE
, exstyle
183 // we must use WS_CLIPSIBLINGS with the buttons or they would draw over
184 // each other in any resizeable dialog which has more than one button in
186 msStyle
|= WS_CLIPSIBLINGS
;
189 // don't use "else if" here: weird as it is, but you may combine wxBU_LEFT
190 // and wxBU_RIGHT to get BS_CENTER!
191 if ( style
& wxBU_LEFT
)
193 if ( style
& wxBU_RIGHT
)
195 if ( style
& wxBU_TOP
)
197 if ( style
& wxBU_BOTTOM
)
198 msStyle
|= BS_BOTTOM
;
201 if ( style
& wxNO_BORDER
)
203 #endif // __WXWINCE__
209 // ----------------------------------------------------------------------------
210 // size management including autosizing
211 // ----------------------------------------------------------------------------
213 wxSize
wxButton::DoGetBestSize() const
215 wxClientDC
dc(wx_const_cast(wxButton
*, this));
216 dc
.SetFont(GetFont());
220 dc
.GetMultiLineTextExtent(GetLabel(), &wBtn
, &hBtn
);
222 // add a margin -- the button is wider than just its label
223 wBtn
+= 3*GetCharWidth();
224 hBtn
= BUTTON_HEIGHT_FROM_CHAR_HEIGHT(hBtn
);
226 // all buttons have at least the standard size unless the user explicitly
227 // wants them to be of smaller size and used wxBU_EXACTFIT style when
228 // creating the button
229 if ( !HasFlag(wxBU_EXACTFIT
) )
231 wxSize sz
= GetDefaultSize();
240 wxSize
best(wBtn
, hBtn
);
246 wxSize
wxButtonBase::GetDefaultSize()
248 static wxSize s_sizeBtn
;
250 if ( s_sizeBtn
.x
== 0 )
253 dc
.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT
));
255 // the size of a standard button in the dialog units is 50x14,
256 // translate this to pixels
257 // NB1: the multipliers come from the Windows convention
258 // NB2: the extra +1/+2 were needed to get the size be the same as the
259 // size of the buttons in the standard dialog - I don't know how
260 // this happens, but on my system this size is 75x23 in pixels and
261 // 23*8 isn't even divisible by 14... Would be nice to understand
262 // why these constants are needed though!
263 s_sizeBtn
.x
= (50 * (dc
.GetCharWidth() + 1))/4;
264 s_sizeBtn
.y
= ((14 * dc
.GetCharHeight()) + 2)/8;
270 // ----------------------------------------------------------------------------
271 // default button handling
272 // ----------------------------------------------------------------------------
275 "Everything you ever wanted to know about the default buttons" or "Why do we
276 have to do all this?"
278 In MSW the default button should be activated when the user presses Enter
279 and the current control doesn't process Enter itself somehow. This is
280 handled by ::DefWindowProc() (or maybe ::DefDialogProc()) using DM_SETDEFID
281 Another aspect of "defaultness" is that the default button has different
282 appearance: this is due to BS_DEFPUSHBUTTON style which is completely
283 separate from DM_SETDEFID stuff (!). Also note that BS_DEFPUSHBUTTON should
284 be unset if our parent window is not active so it should be unset whenever
285 we lose activation and set back when we regain it.
287 Final complication is that when a button is active, it should be the default
288 one, i.e. pressing Enter on a button always activates it and not another
291 We handle this by maintaining a permanent and a temporary default items in
292 wxControlContainer (both may be NULL). When a button becomes the current
293 control (i.e. gets focus) it sets itself as the temporary default which
294 ensures that it has the right appearance and that Enter will be redirected
295 to it. When the button loses focus, it unsets the temporary default and so
296 the default item will be the permanent default -- that is the default button
297 if any had been set or none otherwise, which is just what we want.
299 NB: all this is quite complicated by now and the worst is that normally
300 it shouldn't be necessary at all as for the normal Windows programs
301 DefWindowProc() and IsDialogMessage() take care of all this
302 automatically -- however in wxWidgets programs this doesn't work for
303 nested hierarchies (i.e. a notebook inside a notebook) for unknown
304 reason and so we have to reproduce all this code ourselves. It would be
305 very nice if we could avoid doing it.
308 // set this button as the (permanently) default one in its panel
309 void wxButton::SetDefault()
311 wxWindow
*parent
= GetParent();
313 wxCHECK_RET( parent
, _T("button without parent?") );
315 // set this one as the default button both for wxWidgets ...
316 wxWindow
*winOldDefault
= parent
->SetDefaultItem(this);
319 SetDefaultStyle(wxDynamicCast(winOldDefault
, wxButton
), false);
320 SetDefaultStyle(this, true);
323 // set this button as being currently default
324 void wxButton::SetTmpDefault()
326 wxWindow
*parent
= GetParent();
328 wxCHECK_RET( parent
, _T("button without parent?") );
330 wxWindow
*winOldDefault
= parent
->GetDefaultItem();
331 parent
->SetTmpDefaultItem(this);
333 SetDefaultStyle(wxDynamicCast(winOldDefault
, wxButton
), false);
334 SetDefaultStyle(this, true);
337 // unset this button as currently default, it may still stay permanent default
338 void wxButton::UnsetTmpDefault()
340 wxWindow
*parent
= GetParent();
342 wxCHECK_RET( parent
, _T("button without parent?") );
344 parent
->SetTmpDefaultItem(NULL
);
346 wxWindow
*winOldDefault
= parent
->GetDefaultItem();
348 SetDefaultStyle(this, false);
349 SetDefaultStyle(wxDynamicCast(winOldDefault
, wxButton
), true);
354 wxButton::SetDefaultStyle(wxButton
*btn
, bool on
)
356 // we may be called with NULL pointer -- simpler to do the check here than
357 // in the caller which does wxDynamicCast()
361 // first, let DefDlgProc() know about the new default button
364 // we shouldn't set BS_DEFPUSHBUTTON for any button if we don't have
365 // focus at all any more
366 if ( !wxTheApp
->IsActive() )
369 // look for a panel-like window
370 wxWindow
*win
= btn
->GetParent();
371 while ( win
&& !win
->HasFlag(wxTAB_TRAVERSAL
) )
372 win
= win
->GetParent();
376 ::SendMessage(GetHwndOf(win
), DM_SETDEFID
, btn
->GetId(), 0L);
378 // sending DM_SETDEFID also changes the button style to
379 // BS_DEFPUSHBUTTON so there is nothing more to do
383 // then also change the style as needed
384 long style
= ::GetWindowLong(GetHwndOf(btn
), GWL_STYLE
);
385 if ( !(style
& BS_DEFPUSHBUTTON
) == on
)
387 // don't do it with the owner drawn buttons because it will
388 // reset BS_OWNERDRAW style bit too (as BS_OWNERDRAW &
389 // BS_DEFPUSHBUTTON != 0)!
390 if ( (style
& BS_OWNERDRAW
) != BS_OWNERDRAW
)
392 ::SendMessage(GetHwndOf(btn
), BM_SETSTYLE
,
393 on
? style
| BS_DEFPUSHBUTTON
394 : style
& ~BS_DEFPUSHBUTTON
,
399 // redraw the button - it will notice itself that it's
400 // [not] the default one [any longer]
404 //else: already has correct style
407 // ----------------------------------------------------------------------------
409 // ----------------------------------------------------------------------------
411 bool wxButton::SendClickEvent()
413 wxCommandEvent
event(wxEVT_COMMAND_BUTTON_CLICKED
, GetId());
414 event
.SetEventObject(this);
416 return ProcessCommand(event
);
419 void wxButton::Command(wxCommandEvent
& event
)
421 ProcessCommand(event
);
424 // ----------------------------------------------------------------------------
425 // event/message handlers
426 // ----------------------------------------------------------------------------
428 bool wxButton::MSWCommand(WXUINT param
, WXWORD
WXUNUSED(id
))
430 bool processed
= false;
433 // NOTE: Apparently older versions (NT 4?) of the common controls send
434 // BN_DOUBLECLICKED but not a second BN_CLICKED for owner-drawn
435 // buttons, so in order to send two EVT_BUTTON events we should
436 // catch both types. Currently (Feb 2003) up-to-date versions of
437 // win98, win2k and winXP all send two BN_CLICKED messages for
438 // all button types, so we don't catch BN_DOUBLECLICKED anymore
439 // in order to not get 3 EVT_BUTTON events. If this is a problem
440 // then we need to figure out which version of the comctl32 changed
441 // this behaviour and test for it.
443 case 1: // message came from an accelerator
444 case BN_CLICKED
: // normal buttons send this
445 processed
= SendClickEvent();
452 WXLRESULT
wxButton::MSWWindowProc(WXUINT nMsg
, WXWPARAM wParam
, WXLPARAM lParam
)
454 // when we receive focus, we want to temporarily become the default button in
455 // our parent panel so that pressing "Enter" would activate us -- and when
456 // losing it we should restore the previous default button as well
457 if ( nMsg
== WM_SETFOCUS
)
461 // let the default processing take place too
463 else if ( nMsg
== WM_KILLFOCUS
)
467 else if ( nMsg
== WM_LBUTTONDBLCLK
)
469 // emulate a click event to force an owner-drawn button to change its
470 // appearance - without this, it won't do it
471 (void)wxControl::MSWWindowProc(WM_LBUTTONDOWN
, wParam
, lParam
);
473 // and continue with processing the message normally as well
476 // let the base class do all real processing
477 return wxControl::MSWWindowProc(nMsg
, wParam
, lParam
);
480 // ----------------------------------------------------------------------------
481 // owner-drawn buttons support
482 // ----------------------------------------------------------------------------
488 static void DrawButtonText(HDC hdc
,
490 const wxString
& text
,
493 COLORREF colOld
= SetTextColor(hdc
, col
);
494 int modeOld
= SetBkMode(hdc
, TRANSPARENT
);
496 if ( text
.find(_T('\n')) != wxString::npos
)
498 // draw multiline label
500 // first we need to compute its bounding rect
502 ::CopyRect(&rc
, pRect
);
503 ::DrawText(hdc
, text
, text
.length(), &rc
, DT_CENTER
| DT_CALCRECT
);
505 // now center this rect inside the entire button area
506 const LONG w
= rc
.right
- rc
.left
;
507 const LONG h
= rc
.bottom
- rc
.top
;
508 rc
.left
= (pRect
->right
- pRect
->left
)/2 - w
/2;
509 rc
.right
= rc
.left
+w
;
510 rc
.top
= (pRect
->bottom
- pRect
->top
)/2 - h
/2;
511 rc
.bottom
= rc
.top
+h
;
513 ::DrawText(hdc
, text
, text
.length(), &rc
, DT_CENTER
);
515 else // single line label
517 // Note: we must have DT_SINGLELINE for DT_VCENTER to work.
518 ::DrawText(hdc
, text
, text
.length(), pRect
,
519 DT_SINGLELINE
| DT_CENTER
| DT_VCENTER
);
522 SetBkMode(hdc
, modeOld
);
523 SetTextColor(hdc
, colOld
);
526 static void DrawRect(HDC hdc
, const RECT
& r
)
528 wxDrawLine(hdc
, r
.left
, r
.top
, r
.right
, r
.top
);
529 wxDrawLine(hdc
, r
.right
, r
.top
, r
.right
, r
.bottom
);
530 wxDrawLine(hdc
, r
.right
, r
.bottom
, r
.left
, r
.bottom
);
531 wxDrawLine(hdc
, r
.left
, r
.bottom
, r
.left
, r
.top
);
534 void wxButton::MakeOwnerDrawn()
536 long style
= GetWindowLong(GetHwnd(), GWL_STYLE
);
537 if ( (style
& BS_OWNERDRAW
) != BS_OWNERDRAW
)
540 style
|= BS_OWNERDRAW
;
541 SetWindowLong(GetHwnd(), GWL_STYLE
, style
);
545 bool wxButton::SetBackgroundColour(const wxColour
&colour
)
547 if ( !wxControl::SetBackgroundColour(colour
) )
560 bool wxButton::SetForegroundColour(const wxColour
&colour
)
562 if ( !wxControl::SetForegroundColour(colour
) )
576 The button frame looks like this normally:
579 WHHHHHHHHHHHHHHHHGB W = white (HILIGHT)
580 WH GB H = light grey (LIGHT)
581 WH GB G = dark grey (SHADOW)
582 WH GB B = black (DKSHADOW)
587 When the button is selected, the button becomes like this (the total button
588 size doesn't change):
599 When the button is pushed (while selected) it is like:
611 static void DrawButtonFrame(HDC hdc
, const RECT
& rectBtn
,
612 bool selected
, bool pushed
)
615 CopyRect(&r
, &rectBtn
);
617 HPEN hpenBlack
= CreatePen(PS_SOLID
, 1, GetSysColor(COLOR_3DDKSHADOW
)),
618 hpenGrey
= CreatePen(PS_SOLID
, 1, GetSysColor(COLOR_3DSHADOW
)),
619 hpenLightGr
= CreatePen(PS_SOLID
, 1, GetSysColor(COLOR_3DLIGHT
)),
620 hpenWhite
= CreatePen(PS_SOLID
, 1, GetSysColor(COLOR_3DHILIGHT
));
622 HPEN hpenOld
= (HPEN
)SelectObject(hdc
, hpenBlack
);
631 (void)SelectObject(hdc
, hpenGrey
);
632 ::InflateRect(&r
, -1, -1);
642 ::InflateRect(&r
, -1, -1);
645 wxDrawLine(hdc
, r
.left
, r
.bottom
, r
.right
, r
.bottom
);
646 wxDrawLine(hdc
, r
.right
, r
.bottom
, r
.right
, r
.top
- 1);
648 (void)SelectObject(hdc
, hpenWhite
);
649 wxDrawLine(hdc
, r
.left
, r
.bottom
- 1, r
.left
, r
.top
);
650 wxDrawLine(hdc
, r
.left
, r
.top
, r
.right
, r
.top
);
652 (void)SelectObject(hdc
, hpenLightGr
);
653 wxDrawLine(hdc
, r
.left
+ 1, r
.bottom
- 2, r
.left
+ 1, r
.top
+ 1);
654 wxDrawLine(hdc
, r
.left
+ 1, r
.top
+ 1, r
.right
- 1, r
.top
+ 1);
656 (void)SelectObject(hdc
, hpenGrey
);
657 wxDrawLine(hdc
, r
.left
+ 1, r
.bottom
- 1, r
.right
- 1, r
.bottom
- 1);
658 wxDrawLine(hdc
, r
.right
- 1, r
.bottom
- 1, r
.right
- 1, r
.top
);
661 (void)SelectObject(hdc
, hpenOld
);
662 DeleteObject(hpenWhite
);
663 DeleteObject(hpenLightGr
);
664 DeleteObject(hpenGrey
);
665 DeleteObject(hpenBlack
);
668 bool wxButton::MSWOnDraw(WXDRAWITEMSTRUCT
*wxdis
)
670 LPDRAWITEMSTRUCT lpDIS
= (LPDRAWITEMSTRUCT
)wxdis
;
673 CopyRect(&rectBtn
, &lpDIS
->rcItem
);
675 COLORREF colBg
= wxColourToRGB(GetBackgroundColour()),
676 colFg
= wxColourToRGB(GetForegroundColour());
678 HDC hdc
= lpDIS
->hDC
;
679 UINT state
= lpDIS
->itemState
;
681 // first, draw the background
682 HBRUSH hbrushBackground
= ::CreateSolidBrush(colBg
);
684 FillRect(hdc
, &rectBtn
, hbrushBackground
);
686 // draw the border for the current state
687 bool selected
= (state
& ODS_SELECTED
) != 0;
690 wxPanel
*panel
= wxDynamicCast(GetParent(), wxPanel
);
693 selected
= panel
->GetDefaultItem() == this;
696 bool pushed
= (SendMessage(GetHwnd(), BM_GETSTATE
, 0, 0) & BST_PUSHED
) != 0;
698 DrawButtonFrame(hdc
, rectBtn
, selected
, pushed
);
700 // draw the focus rect if needed
701 if ( state
& ODS_FOCUS
)
704 CopyRect(&rectFocus
, &rectBtn
);
706 // I don't know where does this constant come from, but this is how
707 // Windows draws them
708 InflateRect(&rectFocus
, -4, -4);
710 DrawFocusRect(hdc
, &rectFocus
);
715 // the label is shifted by 1 pixel to create "pushed" effect
716 OffsetRect(&rectBtn
, 1, 1);
719 DrawButtonText(hdc
, &rectBtn
, GetLabel(),
720 state
& ODS_DISABLED
? GetSysColor(COLOR_GRAYTEXT
)
723 ::DeleteObject(hbrushBackground
);
730 #endif // wxUSE_BUTTON